From aac04b4a5a70fc45f88d7b005e20c2cde271ff89 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 25 Sep 2023 14:19:20 +0200 Subject: [PATCH 1/8] elf: port some of zld's test harness --- test/link.zig | 6 ++++ test/link/elf.zig | 89 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 test/link/elf.zig diff --git a/test/link.zig b/test/link.zig index df3862f428..ea519c3b7a 100644 --- a/test/link.zig +++ b/test/link.zig @@ -29,6 +29,12 @@ pub const cases = [_]Case{ .import = @import("link/glibc_compat/build.zig"), }, + // Elf Cases + .{ + .build_root = "test/link", + .import = @import("link/elf.zig"), + }, + // WASM Cases // https://github.com/ziglang/zig/issues/16938 //.{ diff --git a/test/link/elf.zig b/test/link/elf.zig new file mode 100644 index 0000000000..6f0fa962b6 --- /dev/null +++ b/test/link/elf.zig @@ -0,0 +1,89 @@ +//! Here we test our ELF linker for correctness and functionality. +//! Currently, we support linking x86_64 Linux, but in the future we +//! will progressively relax those to exercise more combinations. + +pub fn build(b: *Build) void { + const elf_step = b.step("test-elf", "Run ELF tests"); + b.default_step = elf_step; + + const target: CrossTarget = .{ + .cpu_arch = .x86_64, // TODO relax this once ELF linker is able to handle other archs + .os_tag = .linux, + }; + + elf_step.dependOn(testHelloStatic(b, .{ .target = target, .use_llvm = true })); + elf_step.dependOn(testHelloStatic(b, .{ .target = target, .use_llvm = false })); +} + +fn testHelloStatic(b: *Build, opts: Options) *Step { + const test_step = addTestStep(b, "hello-static", opts); + + const exe = addExecutable(b, opts); + addZigSourceBytes(exe, + \\pub fn main() void { + \\ @import("std").debug.print("Hello World!\n", .{}); + \\} + ); + exe.linkage = .static; + + const run = b.addRunArtifact(exe); + run.expectStdErrEqual("Hello World!\n"); + test_step.dependOn(&run.step); + + const check = exe.checkObject(); + check.checkStart(); + check.checkExact("header"); + check.checkExact("type EXEC"); + check.checkStart(); + check.checkExact("section headers"); + check.checkNotPresent("name .dynamic"); + test_step.dependOn(&check.step); + + return test_step; +} + +const Options = struct { + target: CrossTarget = .{ .os_tag = .linux }, + optimize: std.builtin.OptimizeMode = .Debug, + use_llvm: bool = true, +}; + +fn addTestStep(b: *Build, comptime prefix: []const u8, opts: Options) *Step { + const target = opts.target.zigTriple(b.allocator) catch @panic("OOM"); + const optimize = @tagName(opts.optimize); + const use_llvm = if (opts.use_llvm) "llvm" else "no-llvm"; + const name = std.fmt.allocPrint(b.allocator, "test-elf-" ++ prefix ++ "-{s}-{s}-{s}", .{ + target, + optimize, + use_llvm, + }) catch @panic("OOM"); + return b.step(name, ""); +} + +fn addExecutable(b: *Build, opts: Options) *Compile { + return b.addExecutable(.{ + .name = "test", + .target = opts.target, + .optimize = opts.optimize, + .single_threaded = true, // TODO temp until we teach linker how to handle TLS + .use_llvm = opts.use_llvm, + .use_lld = false, + }); +} + +fn addZigSourceBytes(comp: *Compile, bytes: []const u8) void { + const b = comp.step.owner; + const file = WriteFile.create(b).add("a.zig", bytes); + file.addStepDependencies(&comp.step); + comp.root_src = file; +} + +const std = @import("std"); + +const Build = std.Build; +const Compile = Step.Compile; +const CrossTarget = std.zig.CrossTarget; +const LazyPath = Build.LazyPath; +const Run = Step.Run; +const Step = Build.Step; +const WriteFile = Step.WriteFile; From 8abfb3559ab6e1be7852b61455fe1197bf1cbd69 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 25 Sep 2023 17:05:16 +0200 Subject: [PATCH 2/8] elf: test statically linking libc --- test/link/elf.zig | 46 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 42 insertions(+), 4 deletions(-) diff --git a/test/link/elf.zig b/test/link/elf.zig index 6f0fa962b6..e29a5af158 100644 --- a/test/link/elf.zig +++ b/test/link/elf.zig @@ -11,12 +11,14 @@ pub fn build(b: *Build) void { .os_tag = .linux, }; - elf_step.dependOn(testHelloStatic(b, .{ .target = target, .use_llvm = true })); - elf_step.dependOn(testHelloStatic(b, .{ .target = target, .use_llvm = false })); + elf_step.dependOn(testLinkingZigStatic(b, .{ .target = target, .use_llvm = true })); + elf_step.dependOn(testLinkingZigStatic(b, .{ .target = target, .use_llvm = false })); + elf_step.dependOn(testLinkingCStatic(b, .{ .target = target, .use_llvm = true })); + // elf_step.dependOn(testLinkingCStatic(b, .{ .target = target, .use_llvm = false })); // TODO arocc } -fn testHelloStatic(b: *Build, opts: Options) *Step { - const test_step = addTestStep(b, "hello-static", opts); +fn testLinkingZigStatic(b: *Build, opts: Options) *Step { + const test_step = addTestStep(b, "linking-zig-static", opts); const exe = addExecutable(b, opts); addZigSourceBytes(exe, @@ -42,6 +44,36 @@ fn testHelloStatic(b: *Build, opts: Options) *Step { return test_step; } +fn testLinkingCStatic(b: *Build, opts: Options) *Step { + const test_step = addTestStep(b, "linking-c-static", opts); + + const exe = addExecutable(b, opts); + addCSourceBytes(exe, + \\#include + \\int main() { + \\ printf("Hello World!\n"); + \\ return 0; + \\} + ); + exe.is_linking_libc = true; + exe.linkage = .static; + + const run = b.addRunArtifact(exe); + run.expectStdOutEqual("Hello World!\n"); + test_step.dependOn(&run.step); + + const check = exe.checkObject(); + check.checkStart(); + check.checkExact("header"); + check.checkExact("type EXEC"); + check.checkStart(); + check.checkExact("section headers"); + check.checkNotPresent("name .dynamic"); + test_step.dependOn(&check.step); + + return test_step; +} + const Options = struct { target: CrossTarget = .{ .os_tag = .linux }, optimize: std.builtin.OptimizeMode = .Debug, @@ -78,6 +110,12 @@ fn addZigSourceBytes(comp: *Compile, bytes: []const u8) void { comp.root_src = file; } +fn addCSourceBytes(comp: *Compile, bytes: []const u8) void { + const b = comp.step.owner; + const file = WriteFile.create(b).add("a.c", bytes); + comp.addCSourceFile(.{ .file = file, .flags = &.{} }); +} + const std = @import("std"); const Build = std.Build; From e7c6dfde3d2e52ac8180fc3c147064528124301d Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 25 Sep 2023 17:05:39 +0200 Subject: [PATCH 3/8] elf: do not try to create LlvmObject if module is null --- src/link/Elf.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/link/Elf.zig b/src/link/Elf.zig index bf7bfaeb43..c42ee20b49 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -254,7 +254,7 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*Elf { .default_sym_version = default_sym_version, }; const use_llvm = options.use_llvm; - if (use_llvm) { + if (use_llvm and options.module != null) { self.llvm_object = try LlvmObject.create(gpa, options); } From 5e617e4b0c35c75e8079f3c0918392327a55d413 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 25 Sep 2023 17:35:48 +0200 Subject: [PATCH 4/8] elf: put libc on the linker line if requested --- src/link/Elf.zig | 75 ++++++++++++++++++++++++++++++++++++----------- test/link/elf.zig | 19 ++++++------ 2 files changed, 67 insertions(+), 27 deletions(-) diff --git a/src/link/Elf.zig b/src/link/Elf.zig index c42ee20b49..68101ae964 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -965,6 +965,7 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); + const target = self.base.options.target; const directory = self.base.options.emit.?.directory; // Just an alias to make it shorter to type. const full_out_path = try directory.join(arena, &[_][]const u8{self.base.options.emit.?.sub_path}); @@ -993,22 +994,63 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node try positionals.append(.{ .path = key.status.success.object_path }); } + for (positionals.items) |obj| { + const in_file = try std.fs.cwd().openFile(obj.path, .{}); + defer in_file.close(); + var parse_ctx: ParseErrorCtx = .{ .detected_cpu_arch = undefined }; + self.parsePositional(in_file, obj.path, obj.must_link, &parse_ctx) catch |err| + try self.handleAndReportParseError(obj.path, err, &parse_ctx); + } + + var system_libs = std.ArrayList(SystemLib).init(arena); + + // libc dep + self.error_flags.missing_libc = false; + if (self.base.options.link_libc) { + if (self.base.options.libc_installation != null) { + @panic("TODO explicit libc_installation"); + } else if (target.isGnuLibC()) { + try system_libs.ensureUnusedCapacity(glibc.libs.len + 1); + for (glibc.libs) |lib| { + const lib_path = try std.fmt.allocPrint(arena, "{s}{c}lib{s}.so.{d}", .{ + comp.glibc_so_files.?.dir_path, fs.path.sep, lib.name, lib.sover, + }); + system_libs.appendAssumeCapacity(.{ .path = lib_path }); + } + system_libs.appendAssumeCapacity(.{ + .path = try comp.get_libc_crt_file(arena, "libc_nonshared.a"), + }); + } else if (target.isMusl()) { + const path = try comp.get_libc_crt_file(arena, switch (self.base.options.link_mode) { + .Static => "libc.a", + .Dynamic => "libc.so", + }); + try system_libs.append(.{ .path = path }); + } else { + self.error_flags.missing_libc = true; + } + } + + for (system_libs.items) |lib| { + const in_file = try std.fs.cwd().openFile(lib.path, .{}); + defer in_file.close(); + var parse_ctx: ParseErrorCtx = .{ .detected_cpu_arch = undefined }; + self.parseLibrary(in_file, lib, false, &parse_ctx) catch |err| + try self.handleAndReportParseError(lib.path, err, &parse_ctx); + } + + // Finally, as the last input object add compiler_rt if any. const compiler_rt_path: ?[]const u8 = blk: { if (comp.compiler_rt_lib) |x| break :blk x.full_object_path; if (comp.compiler_rt_obj) |x| break :blk x.full_object_path; break :blk null; }; if (compiler_rt_path) |path| { - try positionals.append(.{ .path = path }); - } - - for (positionals.items) |obj| { - const in_file = try std.fs.cwd().openFile(obj.path, .{}); + const in_file = try std.fs.cwd().openFile(path, .{}); defer in_file.close(); - var parse_ctx: ParseErrorCtx = .{ .detected_cpu_arch = undefined }; - self.parsePositional(in_file, obj.path, obj.must_link, &parse_ctx) catch |err| - try self.handleAndReportParseError(obj.path, err, &parse_ctx); + self.parsePositional(in_file, path, false, &parse_ctx) catch |err| + try self.handleAndReportParseError(path, err, &parse_ctx); } // Handle any lazy symbols that were emitted by incremental compilation. @@ -1347,28 +1389,22 @@ fn parsePositional( if (Object.isObject(in_file)) { try self.parseObject(in_file, path, ctx); } else { - try self.parseLibrary(in_file, path, .{ - .path = null, - .needed = false, - .weak = false, - }, must_link, ctx); + try self.parseLibrary(in_file, .{ .path = path }, must_link, ctx); } } fn parseLibrary( self: *Elf, in_file: std.fs.File, - path: []const u8, - lib: link.SystemLib, + lib: SystemLib, must_link: bool, ctx: *ParseErrorCtx, ) ParseError!void { const tracy = trace(@src()); defer tracy.end(); - _ = lib; if (Archive.isArchive(in_file)) { - try self.parseArchive(in_file, path, must_link, ctx); + try self.parseArchive(in_file, lib.path, must_link, ctx); } else return error.UnknownFileType; } @@ -4149,6 +4185,11 @@ pub const null_sym = elf.Elf64_Sym{ .st_size = 0, }; +const SystemLib = struct { + needed: bool = false, + path: []const u8, +}; + const std = @import("std"); const build_options = @import("build_options"); const builtin = @import("builtin"); diff --git a/test/link/elf.zig b/test/link/elf.zig index e29a5af158..b20498e7df 100644 --- a/test/link/elf.zig +++ b/test/link/elf.zig @@ -6,18 +6,19 @@ pub fn build(b: *Build) void { const elf_step = b.step("test-elf", "Run ELF tests"); b.default_step = elf_step; - const target: CrossTarget = .{ + const musl_target = CrossTarget{ .cpu_arch = .x86_64, // TODO relax this once ELF linker is able to handle other archs .os_tag = .linux, + .abi = .musl, }; - elf_step.dependOn(testLinkingZigStatic(b, .{ .target = target, .use_llvm = true })); - elf_step.dependOn(testLinkingZigStatic(b, .{ .target = target, .use_llvm = false })); - elf_step.dependOn(testLinkingCStatic(b, .{ .target = target, .use_llvm = true })); - // elf_step.dependOn(testLinkingCStatic(b, .{ .target = target, .use_llvm = false })); // TODO arocc + elf_step.dependOn(testLinkingZig(b, .{ .use_llvm = true })); + elf_step.dependOn(testLinkingZig(b, .{ .use_llvm = false })); + elf_step.dependOn(testLinkingC(b, .{ .target = musl_target, .use_llvm = true })); + // elf_step.dependOn(testLinkingC(b, .{ .target = musl_target, .use_llvm = false })); // TODO arocc } -fn testLinkingZigStatic(b: *Build, opts: Options) *Step { +fn testLinkingZig(b: *Build, opts: Options) *Step { const test_step = addTestStep(b, "linking-zig-static", opts); const exe = addExecutable(b, opts); @@ -26,7 +27,6 @@ fn testLinkingZigStatic(b: *Build, opts: Options) *Step { \\ @import("std").debug.print("Hello World!\n", .{}); \\} ); - exe.linkage = .static; const run = b.addRunArtifact(exe); run.expectStdErrEqual("Hello World!\n"); @@ -44,7 +44,7 @@ fn testLinkingZigStatic(b: *Build, opts: Options) *Step { return test_step; } -fn testLinkingCStatic(b: *Build, opts: Options) *Step { +fn testLinkingC(b: *Build, opts: Options) *Step { const test_step = addTestStep(b, "linking-c-static", opts); const exe = addExecutable(b, opts); @@ -56,7 +56,6 @@ fn testLinkingCStatic(b: *Build, opts: Options) *Step { \\} ); exe.is_linking_libc = true; - exe.linkage = .static; const run = b.addRunArtifact(exe); run.expectStdOutEqual("Hello World!\n"); @@ -75,7 +74,7 @@ fn testLinkingCStatic(b: *Build, opts: Options) *Step { } const Options = struct { - target: CrossTarget = .{ .os_tag = .linux }, + target: CrossTarget = .{ .cpu_arch = .x86_64, .os_tag = .linux }, optimize: std.builtin.OptimizeMode = .Debug, use_llvm: bool = true, }; From eb497c50e331550a2d28d45a77a7558a49a83e7c Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 25 Sep 2023 19:40:11 +0200 Subject: [PATCH 5/8] elf: dynamically allocate remaining alloc sections (and segments) --- src/link/Elf.zig | 54 ++++++++++++++++++++++++++++++----------- src/link/Elf/Object.zig | 44 +++++++++++++++++++++++++++------ 2 files changed, 76 insertions(+), 22 deletions(-) diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 68101ae964..15aacffc82 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -409,16 +409,24 @@ fn findFreeSpace(self: *Elf, object_size: u64, min_alignment: u64) u64 { } const AllocateSegmentOpts = struct { - addr: u64, // TODO find free VM space size: u64, alignment: u64, + addr: ?u64 = null, // TODO find free VM space flags: u32 = elf.PF_R, }; -fn allocateSegment(self: *Elf, opts: AllocateSegmentOpts) error{OutOfMemory}!u16 { +pub fn allocateSegment(self: *Elf, opts: AllocateSegmentOpts) error{OutOfMemory}!u16 { const index = @as(u16, @intCast(self.phdrs.items.len)); try self.phdrs.ensureUnusedCapacity(self.base.allocator, 1); const off = self.findFreeSpace(opts.size, opts.alignment); + // Memory is always allocated in sequence. + // TODO is this correct? Or should we implement something similar to `findFreeSpace`? + // How would that impact HCS? + const addr = opts.addr orelse blk: { + assert(self.phdr_table_load_index != null); + const phdr = &self.phdrs.items[index - 1]; + break :blk mem.alignForward(u64, phdr.p_vaddr + phdr.p_memsz, opts.alignment); + }; log.debug("allocating phdr({d})({c}{c}{c}) from 0x{x} to 0x{x} (0x{x} - 0x{x})", .{ index, if (opts.flags & elf.PF_R != 0) @as(u8, 'R') else '_', @@ -426,15 +434,15 @@ fn allocateSegment(self: *Elf, opts: AllocateSegmentOpts) error{OutOfMemory}!u16 if (opts.flags & elf.PF_X != 0) @as(u8, 'X') else '_', off, off + opts.size, - opts.addr, - opts.addr + opts.size, + addr, + addr + opts.size, }); self.phdrs.appendAssumeCapacity(.{ .p_type = elf.PT_LOAD, .p_offset = off, .p_filesz = opts.size, - .p_vaddr = opts.addr, - .p_paddr = opts.addr, + .p_vaddr = addr, + .p_paddr = addr, .p_memsz = opts.size, .p_align = opts.alignment, .p_flags = opts.flags, @@ -446,12 +454,12 @@ fn allocateSegment(self: *Elf, opts: AllocateSegmentOpts) error{OutOfMemory}!u16 const AllocateAllocSectionOpts = struct { name: [:0]const u8, phdr_index: u16, - alignment: u16 = 1, - flags: u16 = elf.SHF_ALLOC, + alignment: u64 = 1, + flags: u64 = elf.SHF_ALLOC, type: u32 = elf.SHT_PROGBITS, }; -fn allocateAllocSection(self: *Elf, opts: AllocateAllocSectionOpts) error{OutOfMemory}!u16 { +pub fn allocateAllocSection(self: *Elf, opts: AllocateAllocSectionOpts) error{OutOfMemory}!u16 { const gpa = self.base.allocator; const phdr = &self.phdrs.items[opts.phdr_index]; const index = @as(u16, @intCast(self.shdrs.items.len)); @@ -622,6 +630,7 @@ pub fn populateMissingMetadata(self: *Elf) !void { }); const phdr = &self.phdrs.items[self.phdr_load_zerofill_index.?]; phdr.p_offset = self.phdrs.items[self.phdr_load_rw_index.?].p_offset; // .bss overlaps .data + phdr.p_memsz = 1024; } if (self.shstrtab_section_index == null) { @@ -994,6 +1003,12 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node try positionals.append(.{ .path = key.status.success.object_path }); } + // csu prelude + var csu = try CsuObjects.init(arena, self.base.options, comp); + if (csu.crt0) |v| try positionals.append(.{ .path = v }); + if (csu.crti) |v| try positionals.append(.{ .path = v }); + if (csu.crtbegin) |v| try positionals.append(.{ .path = v }); + for (positionals.items) |obj| { const in_file = try std.fs.cwd().openFile(obj.path, .{}); defer in_file.close(); @@ -1039,18 +1054,29 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node try self.handleAndReportParseError(lib.path, err, &parse_ctx); } - // Finally, as the last input object add compiler_rt if any. + // Finally, as the last input objects we add compiler_rt and CSU postlude (if any). + positionals.clearRetainingCapacity(); + + // compiler-rt. Since compiler_rt exports symbols like `memset`, it needs + // to be after the shared libraries, so they are picked up from the shared + // libraries, not libcompiler_rt. const compiler_rt_path: ?[]const u8 = blk: { if (comp.compiler_rt_lib) |x| break :blk x.full_object_path; if (comp.compiler_rt_obj) |x| break :blk x.full_object_path; break :blk null; }; - if (compiler_rt_path) |path| { - const in_file = try std.fs.cwd().openFile(path, .{}); + if (compiler_rt_path) |path| try positionals.append(.{ .path = path }); + + // csu postlude + if (csu.crtend) |v| try positionals.append(.{ .path = v }); + if (csu.crtn) |v| try positionals.append(.{ .path = v }); + + for (positionals.items) |obj| { + const in_file = try std.fs.cwd().openFile(obj.path, .{}); defer in_file.close(); var parse_ctx: ParseErrorCtx = .{ .detected_cpu_arch = undefined }; - self.parsePositional(in_file, path, false, &parse_ctx) catch |err| - try self.handleAndReportParseError(path, err, &parse_ctx); + self.parsePositional(in_file, obj.path, obj.must_link, &parse_ctx) catch |err| + try self.handleAndReportParseError(obj.path, err, &parse_ctx); } // Handle any lazy symbols that were emitted by incremental compilation. diff --git a/src/link/Elf/Object.zig b/src/link/Elf/Object.zig index 7402bb4f59..8ee37b7676 100644 --- a/src/link/Elf/Object.zig +++ b/src/link/Elf/Object.zig @@ -136,7 +136,7 @@ fn initAtoms(self: *Object, elf_file: *Elf) !void { try self.comdat_groups.append(elf_file.base.allocator, comdat_group_index); }, - elf.SHT_SYMTAB_SHNDX => @panic("TODO"), + elf.SHT_SYMTAB_SHNDX => @panic("TODO SHT_SYMTAB_SHNDX"), elf.SHT_NULL, elf.SHT_REL, @@ -166,14 +166,20 @@ fn initAtoms(self: *Object, elf_file: *Elf) !void { }; } -fn addAtom(self: *Object, shdr: elf.Elf64_Shdr, shndx: u16, name: [:0]const u8, elf_file: *Elf) !void { +fn addAtom( + self: *Object, + shdr: elf.Elf64_Shdr, + shndx: u16, + name: [:0]const u8, + elf_file: *Elf, +) error{ OutOfMemory, Overflow }!void { const atom_index = try elf_file.addAtom(); const atom = elf_file.atom(atom_index).?; atom.atom_index = atom_index; atom.name_offset = try elf_file.strtab.insert(elf_file.base.allocator, name); atom.file_index = self.index; atom.input_section_index = shndx; - atom.output_section_index = self.getOutputSectionIndex(elf_file, shdr); + atom.output_section_index = try self.getOutputSectionIndex(elf_file, shdr); atom.alive = true; self.atoms.items[shndx] = atom_index; @@ -188,7 +194,7 @@ fn addAtom(self: *Object, shdr: elf.Elf64_Shdr, shndx: u16, name: [:0]const u8, } } -fn getOutputSectionIndex(self: *Object, elf_file: *Elf, shdr: elf.Elf64_Shdr) u16 { +fn getOutputSectionIndex(self: *Object, elf_file: *Elf, shdr: elf.Elf64_Shdr) error{OutOfMemory}!u16 { const name = blk: { const name = self.strings.getAssumeExists(shdr.sh_name); // if (shdr.sh_flags & elf.SHF_MERGE != 0) break :blk name; @@ -223,10 +229,32 @@ fn getOutputSectionIndex(self: *Object, elf_file: *Elf, shdr: elf.Elf64_Shdr) u1 else => flags, }; }; - _ = flags; - const out_shndx = elf_file.sectionByName(name) orelse { - log.err("{}: output section {s} not found", .{ self.fmtPath(), name }); - @panic("TODO: missing output section!"); + const out_shndx = elf_file.sectionByName(name) orelse blk: { + const is_alloc = flags & elf.SHF_ALLOC != 0; + const is_write = flags & elf.SHF_WRITE != 0; + const is_exec = flags & elf.SHF_EXECINSTR != 0; + const is_tls = flags & elf.SHF_TLS != 0; + if (!is_alloc or is_tls) { + log.err("{}: output section {s} not found", .{ self.fmtPath(), name }); + @panic("TODO: missing output section!"); + } + var phdr_flags: u32 = elf.PF_R; + if (is_write) phdr_flags |= elf.PF_W; + if (is_exec) phdr_flags |= elf.PF_X; + const phdr_index = try elf_file.allocateSegment(.{ + .size = Elf.padToIdeal(shdr.sh_size), + .alignment = if (is_tls) shdr.sh_addralign else elf_file.page_size, + .flags = phdr_flags, + }); + const shndx = try elf_file.allocateAllocSection(.{ + .name = name, + .phdr_index = phdr_index, + .alignment = shdr.sh_addralign, + .flags = flags, + .type = @"type", + }); + try elf_file.last_atom_and_free_list_table.putNoClobber(elf_file.base.allocator, shndx, .{}); + break :blk shndx; }; return out_shndx; } From b01b972999f024a694734fa9861827e5dc15a88b Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 25 Sep 2023 19:50:12 +0200 Subject: [PATCH 6/8] elf: test linking against empty C object --- test/link/elf.zig | 58 +++++++++++++++++++++++++++++++---------------- 1 file changed, 38 insertions(+), 20 deletions(-) diff --git a/test/link/elf.zig b/test/link/elf.zig index b20498e7df..5c910df1ab 100644 --- a/test/link/elf.zig +++ b/test/link/elf.zig @@ -12,35 +12,27 @@ pub fn build(b: *Build) void { .abi = .musl, }; - elf_step.dependOn(testLinkingZig(b, .{ .use_llvm = true })); + // Exercise linker with self-hosted backend (no LLVM) elf_step.dependOn(testLinkingZig(b, .{ .use_llvm = false })); - elf_step.dependOn(testLinkingC(b, .{ .target = musl_target, .use_llvm = true })); - // elf_step.dependOn(testLinkingC(b, .{ .target = musl_target, .use_llvm = false })); // TODO arocc + + // Exercise linker with LLVM backend + elf_step.dependOn(testEmptyObject(b, .{ .target = musl_target })); + elf_step.dependOn(testLinkingC(b, .{ .target = musl_target })); + elf_step.dependOn(testLinkingZig(b, .{})); } -fn testLinkingZig(b: *Build, opts: Options) *Step { - const test_step = addTestStep(b, "linking-zig-static", opts); +fn testEmptyObject(b: *Build, opts: Options) *Step { + const test_step = addTestStep(b, "empty-object", opts); const exe = addExecutable(b, opts); - addZigSourceBytes(exe, - \\pub fn main() void { - \\ @import("std").debug.print("Hello World!\n", .{}); - \\} - ); + addCSourceBytes(exe, "int main() { return 0; }"); + addCSourceBytes(exe, ""); + exe.is_linking_libc = true; const run = b.addRunArtifact(exe); - run.expectStdErrEqual("Hello World!\n"); + run.expectExitCode(0); test_step.dependOn(&run.step); - const check = exe.checkObject(); - check.checkStart(); - check.checkExact("header"); - check.checkExact("type EXEC"); - check.checkStart(); - check.checkExact("section headers"); - check.checkNotPresent("name .dynamic"); - test_step.dependOn(&check.step); - return test_step; } @@ -73,6 +65,32 @@ fn testLinkingC(b: *Build, opts: Options) *Step { return test_step; } +fn testLinkingZig(b: *Build, opts: Options) *Step { + const test_step = addTestStep(b, "linking-zig-static", opts); + + const exe = addExecutable(b, opts); + addZigSourceBytes(exe, + \\pub fn main() void { + \\ @import("std").debug.print("Hello World!\n", .{}); + \\} + ); + + const run = b.addRunArtifact(exe); + run.expectStdErrEqual("Hello World!\n"); + test_step.dependOn(&run.step); + + const check = exe.checkObject(); + check.checkStart(); + check.checkExact("header"); + check.checkExact("type EXEC"); + check.checkStart(); + check.checkExact("section headers"); + check.checkNotPresent("name .dynamic"); + test_step.dependOn(&check.step); + + return test_step; +} + const Options = struct { target: CrossTarget = .{ .cpu_arch = .x86_64, .os_tag = .linux }, optimize: std.builtin.OptimizeMode = .Debug, From 7617486f1d3d79f08d9d7a8641674c2525e83235 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 25 Sep 2023 21:28:06 +0200 Subject: [PATCH 7/8] elf: skip running exe on foreign hosts --- test/link/elf.zig | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/test/link/elf.zig b/test/link/elf.zig index 5c910df1ab..a5b57e7b93 100644 --- a/test/link/elf.zig +++ b/test/link/elf.zig @@ -29,7 +29,7 @@ fn testEmptyObject(b: *Build, opts: Options) *Step { addCSourceBytes(exe, ""); exe.is_linking_libc = true; - const run = b.addRunArtifact(exe); + const run = addRunArtifact(exe); run.expectExitCode(0); test_step.dependOn(&run.step); @@ -49,7 +49,7 @@ fn testLinkingC(b: *Build, opts: Options) *Step { ); exe.is_linking_libc = true; - const run = b.addRunArtifact(exe); + const run = addRunArtifact(exe); run.expectStdOutEqual("Hello World!\n"); test_step.dependOn(&run.step); @@ -75,7 +75,7 @@ fn testLinkingZig(b: *Build, opts: Options) *Step { \\} ); - const run = b.addRunArtifact(exe); + const run = addRunArtifact(exe); run.expectStdErrEqual("Hello World!\n"); test_step.dependOn(&run.step); @@ -120,6 +120,13 @@ fn addExecutable(b: *Build, opts: Options) *Compile { }); } +fn addRunArtifact(comp: *Compile) *Run { + const b = comp.step.owner; + const run = b.addRunArtifact(comp); + run.skip_foreign_checks = true; + return run; +} + fn addZigSourceBytes(comp: *Compile, bytes: []const u8) void { const b = comp.step.owner; const file = WriteFile.create(b).add("a.zig", bytes); From e30f396b732f7fcb68a701f1b385bb47eba48b89 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 26 Sep 2023 20:57:22 +0200 Subject: [PATCH 8/8] elf: properly close the output file when linking --- src/link.zig | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/link.zig b/src/link.zig index fb71262f66..214d538df9 100644 --- a/src/link.zig +++ b/src/link.zig @@ -456,9 +456,10 @@ pub const File = struct { .Exe => {}, } switch (base.tag) { - .coff, .elf, .macho, .plan9, .wasm => if (base.file) |f| { + .elf => if (base.file) |f| { if (build_options.only_c) unreachable; - if (base.intermediary_basename != null) { + const use_lld = build_options.have_llvm and base.options.use_lld; + if (base.intermediary_basename != null and use_lld) { // The file we have open is not the final file that we want to // make executable, so we don't have to close it. return; @@ -471,6 +472,22 @@ pub const File = struct { .linux => std.os.ptrace(std.os.linux.PTRACE.DETACH, pid, 0, 0) catch |err| { log.warn("ptrace failure: {s}", .{@errorName(err)}); }, + else => return error.HotSwapUnavailableOnHostOperatingSystem, + } + } + }, + .coff, .macho, .plan9, .wasm => if (base.file) |f| { + if (build_options.only_c) unreachable; + if (base.intermediary_basename != null) { + // The file we have open is not the final file that we want to + // make executable, so we don't have to close it. + return; + } + f.close(); + base.file = null; + + if (base.child_pid) |pid| { + switch (builtin.os.tag) { .macos => base.cast(MachO).?.ptraceDetach(pid) catch |err| { log.warn("detaching failed with error: {s}", .{@errorName(err)}); },