diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2ad7253f..83a5a0f9 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -24,5 +24,5 @@ jobs: version: master - run: zig version - run: zig fmt --check src - - run: zig build test --summary all + - run: zig build test --summary all -Dhas_static diff --git a/build.zig b/build.zig index a2297c1f..5f7a1caa 100644 --- a/build.zig +++ b/build.zig @@ -68,8 +68,12 @@ pub fn build(b: *std.Build.Builder) void { }); symlinks.step.dependOn(&install.step); + const has_static = b.option(bool, "has_static", "Whether the system compiler supports '-static' flag") orelse false; + const test_step = b.step("test", "Run tests"); - test_step.dependOn(@import("test/test.zig").addTests(b, exe)); + test_step.dependOn(@import("test/test.zig").addTests(b, exe, .{ + .has_static = has_static, + })); } fn addSymlinks( diff --git a/src/Elf.zig b/src/Elf.zig index 10f70a95..79c24ce8 100644 --- a/src/Elf.zig +++ b/src/Elf.zig @@ -36,14 +36,21 @@ verneed_sect_index: ?u16 = null, internal_object_index: ?u32 = null, dynamic_index: ?u32 = null, +ehdr_start_index: ?u32 = null, init_array_start_index: ?u32 = null, init_array_end_index: ?u32 = null, fini_array_start_index: ?u32 = null, fini_array_end_index: ?u32 = null, +preinit_array_start_index: ?u32 = null, +preinit_array_end_index: ?u32 = null, got_index: ?u32 = null, plt_index: ?u32 = null, dso_handle_index: ?u32 = null, gnu_eh_frame_hdr_index: ?u32 = null, +rela_iplt_start_index: ?u32 = null, +rela_iplt_end_index: ?u32 = null, +end_index: ?u32 = null, +start_stop_indexes: std.ArrayListUnmanaged(u32) = .{}, entry_index: ?u32 = null, @@ -129,6 +136,7 @@ pub fn deinit(self: *Elf) void { self.symbols.deinit(gpa); self.symbols_extra.deinit(gpa); self.globals.deinit(gpa); + self.start_stop_indexes.deinit(gpa); self.got.deinit(gpa); self.plt.deinit(gpa); self.plt_got.deinit(gpa); @@ -1254,7 +1262,7 @@ fn sortSections(self: *Elf) !void { if (self.rela_dyn_sect_index) |index| { const shdr = &self.sections.items(.shdr)[index]; - shdr.sh_link = self.dynsymtab_sect_index.?; + shdr.sh_link = self.dynsymtab_sect_index orelse 0; } if (self.rela_plt_sect_index) |index| { @@ -1310,6 +1318,13 @@ fn allocateSyntheticSymbols(self: *Elf) void { symbol.shndx = shndx; } + // __ehdr_start + { + const symbol = self.getSymbol(self.ehdr_start_index.?); + symbol.value = self.options.image_base; + symbol.shndx = 1; + } + // __init_array_start, __init_array_end if (self.getSectionByName(".init_array")) |shndx| { const start_sym = self.getSymbol(self.init_array_start_index.?); @@ -1332,6 +1347,17 @@ fn allocateSyntheticSymbols(self: *Elf) void { end_sym.value = shdr.sh_addr + shdr.sh_size; } + // __preinit_array_start, __preinit_array_end + if (self.getSectionByName(".preinit_array")) |shndx| { + const start_sym = self.getSymbol(self.preinit_array_start_index.?); + const end_sym = self.getSymbol(self.preinit_array_end_index.?); + const shdr = self.sections.items(.shdr)[shndx]; + start_sym.shndx = shndx; + start_sym.value = shdr.sh_addr; + end_sym.shndx = shndx; + end_sym.value = shdr.sh_addr + shdr.sh_size; + } + // _GLOBAL_OFFSET_TABLE_ { const shndx = self.got_plt_sect_index orelse self.got_sect_index.?; @@ -1364,6 +1390,47 @@ fn allocateSyntheticSymbols(self: *Elf) void { symbol.value = shdr.sh_addr; symbol.shndx = shndx; } + + // __rela_iplt_start, __rela_iplt_end + if (self.rela_dyn_sect_index != null and self.options.static and !self.options.pie) { + const shndx = self.rela_dyn_sect_index.?; + const shdr = self.sections.items(.shdr)[shndx]; + const end_addr = shdr.sh_addr + shdr.sh_size; + const start_addr = end_addr - self.getNumIRelativeRelocs() * @sizeOf(elf.Elf64_Rela); + const start_sym = self.getSymbol(self.rela_iplt_start_index.?); + const end_sym = self.getSymbol(self.rela_iplt_end_index.?); + start_sym.value = start_addr; + start_sym.shndx = shndx; + end_sym.value = end_addr; + end_sym.shndx = shndx; + } + + // _end + { + const end_symbol = self.getSymbol(self.end_index.?); + for (self.sections.items(.shdr), 0..) |shdr, shndx| { + if (shdr.sh_flags & elf.SHF_ALLOC != 0) { + end_symbol.value = shdr.sh_addr + shdr.sh_size; + end_symbol.shndx = @intCast(shndx); + } + } + } + + // __start_*, __stop_* + { + var index: usize = 0; + while (index < self.start_stop_indexes.items.len) : (index += 2) { + const start = self.getSymbol(self.start_stop_indexes.items[index]); + const name = start.getName(self); + const stop = self.getSymbol(self.start_stop_indexes.items[index + 1]); + const shndx = self.getSectionByName(name["__start_".len..]).?; + const shdr = self.sections.items(.shdr)[shndx]; + start.value = shdr.sh_addr; + start.shndx = shndx; + stop.value = shdr.sh_addr + shdr.sh_size; + stop.shndx = shndx; + } + } } fn unpackPositionals(self: *Elf, positionals: *std.ArrayList(LinkObject)) !void { @@ -1695,12 +1762,16 @@ fn resolveSyntheticSymbols(self: *Elf) !void { const internal_index = self.internal_object_index orelse return; const internal = self.getFile(internal_index).?.internal; self.dynamic_index = try internal.addSyntheticGlobal("_DYNAMIC", self); + self.ehdr_start_index = try internal.addSyntheticGlobal("__ehdr_start", self); self.init_array_start_index = try internal.addSyntheticGlobal("__init_array_start", self); self.init_array_end_index = try internal.addSyntheticGlobal("__init_array_end", self); self.fini_array_start_index = try internal.addSyntheticGlobal("__fini_array_start", self); self.fini_array_end_index = try internal.addSyntheticGlobal("__fini_array_end", self); + self.preinit_array_start_index = try internal.addSyntheticGlobal("__preinit_array_start", self); + self.preinit_array_end_index = try internal.addSyntheticGlobal("__preinit_array_end", self); self.got_index = try internal.addSyntheticGlobal("_GLOBAL_OFFSET_TABLE_", self); self.plt_index = try internal.addSyntheticGlobal("_PROCEDURE_LINKAGE_TABLE_", self); + self.end_index = try internal.addSyntheticGlobal("_end", self); if (self.options.eh_frame_hdr) { self.gnu_eh_frame_hdr_index = try internal.addSyntheticGlobal("__GNU_EH_FRAME_HDR", self); @@ -1711,6 +1782,27 @@ fn resolveSyntheticSymbols(self: *Elf) !void { self.dso_handle_index = try internal.addSyntheticGlobal("__dso_handle", self); } + self.rela_iplt_start_index = try internal.addSyntheticGlobal("__rela_iplt_start", self); + self.rela_iplt_end_index = try internal.addSyntheticGlobal("__rela_iplt_end", self); + + for (self.objects.items) |index| { + const object = self.getFile(index).?.object; + for (object.atoms.items) |atom_index| { + if (self.getStartStopBasename(atom_index)) |name| { + const gpa = self.base.allocator; + try self.start_stop_indexes.ensureUnusedCapacity(gpa, 2); + + const start = try std.fmt.allocPrintZ(gpa, "__start_{s}", .{name}); + defer gpa.free(start); + const stop = try std.fmt.allocPrintZ(gpa, "__stop_{s}", .{name}); + defer gpa.free(stop); + + self.start_stop_indexes.appendAssumeCapacity(try internal.addSyntheticGlobal(start, self)); + self.start_stop_indexes.appendAssumeCapacity(try internal.addSyntheticGlobal(stop, self)); + } + } + } + internal.resolveSymbols(self); } @@ -1806,7 +1898,7 @@ fn scanRelocs(self: *Elf) !void { if (symbol.flags.got) { log.debug("'{s}' needs GOT", .{symbol.getName(self)}); try self.got.addSymbol(index, self); - if (symbol.flags.import) self.got.needs_rela = true; + if (symbol.flags.import or symbol.isIFunc(self)) self.got.needs_rela = true; } if (symbol.flags.plt) { if (symbol.flags.is_canonical) { @@ -2349,6 +2441,36 @@ fn sortRelaDyn(self: *Elf) void { mem.sort(elf.Elf64_Rela, self.rela_dyn.items, {}, Sort.lessThan); } +fn getNumIRelativeRelocs(self: *Elf) usize { + var count: usize = 0; + + for (self.got.symbols.items) |sym_index| { + const sym = self.getSymbol(sym_index); + if (sym.isIFunc(self)) count += 1; + } + + return count; +} + +pub fn isCIdentifier(name: []const u8) bool { + if (name.len == 0) return false; + const first_c = name[0]; + if (!std.ascii.isAlphabetic(first_c) and first_c != '_') return false; + for (name[1..]) |c| { + if (!std.ascii.isAlphanumeric(c) and c != '_') return false; + } + return true; +} + +fn getStartStopBasename(self: *Elf, atom_index: Atom.Index) ?[]const u8 { + const atom = self.getAtom(atom_index) orelse return null; + const name = atom.getName(self); + if (atom.getInputShdr(self).sh_flags & elf.SHF_ALLOC != 0 and name.len > 0) { + if (isCIdentifier(name)) return name; + } + return null; +} + pub inline fn getSectionAddress(self: *Elf, shndx: u16) u64 { return self.sections.items(.shdr)[shndx].sh_addr; } diff --git a/src/Elf/Atom.zig b/src/Elf/Atom.zig index 817442af..f143ce9f 100644 --- a/src/Elf/Atom.zig +++ b/src/Elf/Atom.zig @@ -107,88 +107,46 @@ pub fn markFdesDead(self: Atom, elf_file: *Elf) void { pub fn initOutputSection(self: *Atom, elf_file: *Elf) !void { const shdr = self.getInputShdr(elf_file); - const name = self.getName(elf_file); - const flags = shdr.sh_flags & ~@as(u64, elf.SHF_COMPRESSED | elf.SHF_GROUP | elf.SHF_GNU_RETAIN); - const @"type" = shdr.sh_type; - const is_tls = flags & elf.SHF_TLS != 0; - 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 opts: Elf.AddSectionOpts = switch (@"type") { + const name = blk: { + const name = self.getName(elf_file); + if (shdr.sh_flags & elf.SHF_MERGE != 0) break :blk name; + const sh_name_prefixes: []const [:0]const u8 = &.{ + ".text", ".data.rel.ro", ".data", ".rodata", ".bss.rel.ro", ".bss", + ".init_array", ".fini_array", ".tbss", ".tdata", ".gcc_except_table", ".ctors", + ".dtors", ".gnu.warning", + }; + inline for (sh_name_prefixes) |prefix| { + if (std.mem.eql(u8, name, prefix) or std.mem.startsWith(u8, name, prefix ++ ".")) { + break :blk prefix; + } + } + break :blk name; + }; + const @"type" = switch (shdr.sh_type) { elf.SHT_NULL => unreachable, - elf.SHT_NOBITS => blk: { - var out_flags: u32 = elf.SHF_ALLOC | elf.SHF_WRITE; - if (is_tls) out_flags |= elf.SHF_TLS; - break :blk .{ - .flags = out_flags, - .name = if (is_tls) ".tbss" else ".bss", - .type = @"type", - }; - }, elf.SHT_PROGBITS => blk: { - if (!is_alloc) break :blk .{ - .name = name, - .type = @"type", - .flags = flags, - }; - - if (is_exec) { - const out_name = if (mem.eql(u8, name, ".init")) - ".init" - else if (mem.eql(u8, name, ".fini")) ".fini" else ".text"; - var out_flags: u32 = elf.SHF_ALLOC | elf.SHF_EXECINSTR; - if (is_write) out_flags |= elf.SHF_WRITE; - break :blk .{ - .flags = out_flags, - .name = out_name, - .type = @"type", - }; - } - - if (is_write) { - const out_name = if (mem.startsWith(u8, name, ".data.rel.ro")) - ".data.rel.ro" - else if (is_tls) - ".tdata" - else - ".data"; - var out_flags: u32 = elf.SHF_ALLOC | elf.SHF_WRITE; - if (is_tls) out_flags |= elf.SHF_TLS; - break :blk .{ - .flags = out_flags, - .name = out_name, - .type = @"type", - }; - } - - const out_name = if (mem.startsWith(u8, name, ".gcc_except_table")) - ".gcc_except_table" - else - ".rodata"; - - break :blk .{ - .flags = elf.SHF_ALLOC, - .name = out_name, - .type = @"type", - }; - }, - elf.SHT_INIT_ARRAY, elf.SHT_FINI_ARRAY => .{ - .flags = elf.SHF_ALLOC | elf.SHF_WRITE, - .name = if (shdr.sh_type == elf.SHT_INIT_ARRAY) ".init_array" else ".fini_array", - .type = @"type", - .entsize = shdr.sh_entsize, - }, - // TODO handle more section types - else => .{ - .name = name, - .type = @"type", - .flags = flags, - .info = shdr.sh_info, - .entsize = shdr.sh_entsize, + if (std.mem.eql(u8, name, ".init_array") or std.mem.startsWith(u8, name, ".init_array.")) + break :blk elf.SHT_INIT_ARRAY; + if (std.mem.eql(u8, name, ".fini_array") or std.mem.startsWith(u8, name, ".fini_array.")) + break :blk elf.SHT_FINI_ARRAY; + break :blk shdr.sh_type; }, + elf.SHT_X86_64_UNWIND => elf.SHT_PROGBITS, + else => shdr.sh_type, }; - const out_shndx = elf_file.getSectionByName(opts.name) orelse try elf_file.addSection(opts); - if (mem.eql(u8, ".text", opts.name)) { + const flags = blk: { + const flags = shdr.sh_flags & ~@as(u64, elf.SHF_COMPRESSED | elf.SHF_GROUP | elf.SHF_GNU_RETAIN); + break :blk switch (@"type") { + elf.SHT_INIT_ARRAY, elf.SHT_FINI_ARRAY => flags | elf.SHF_WRITE, + else => flags, + }; + }; + const out_shndx = elf_file.getSectionByName(name) orelse try elf_file.addSection(.{ + .type = @"type", + .flags = flags, + .name = name, + }); + if (mem.eql(u8, ".text", name)) { elf_file.text_sect_index = out_shndx; } self.out_shndx = out_shndx; @@ -309,10 +267,12 @@ fn scanReloc(self: Atom, symbol: *Symbol, rel: elf.Elf64_Rela, action: RelocActi switch (action) { .none => {}, + .@"error" => if (symbol.isAbs(elf_file)) self.noPicError(symbol, rel, elf_file) else self.picError(symbol, rel, elf_file), + .copyrel => { if (elf_file.options.z_nocopyreloc) { if (symbol.isAbs(elf_file)) @@ -322,6 +282,7 @@ fn scanReloc(self: Atom, symbol: *Symbol, rel: elf.Elf64_Rela, action: RelocActi } symbol.flags.copy_rel = true; }, + .dyn_copyrel => { if (is_writeable or elf_file.options.z_nocopyreloc) { self.checkTextReloc(symbol, elf_file); @@ -330,13 +291,16 @@ fn scanReloc(self: Atom, symbol: *Symbol, rel: elf.Elf64_Rela, action: RelocActi symbol.flags.copy_rel = true; } }, + .plt => { symbol.flags.plt = true; }, + .cplt => { symbol.flags.plt = true; symbol.flags.is_canonical = true; }, + .dyn_cplt => { if (is_writeable) { object.num_dynrelocs += 1; @@ -345,14 +309,17 @@ fn scanReloc(self: Atom, symbol: *Symbol, rel: elf.Elf64_Rela, action: RelocActi symbol.flags.is_canonical = true; } }, + .dynrel => { self.checkTextReloc(symbol, elf_file); object.num_dynrelocs += 1; }, + .baserel => { self.checkTextReloc(symbol, elf_file); object.num_dynrelocs += 1; }, + .ifunc => self.unhandledRelocError(symbol, rel, action, elf_file), } } diff --git a/src/Elf/gc.zig b/src/Elf/gc.zig index 7e20c4db..f7c8bfb0 100644 --- a/src/Elf/gc.zig +++ b/src/Elf/gc.zig @@ -53,6 +53,7 @@ fn collectRoots(roots: *std.ArrayList(*Atom), elf_file: *Elf) !void { if (mem.startsWith(u8, name, ".dtors")) break :blk true; if (mem.startsWith(u8, name, ".init")) break :blk true; if (mem.startsWith(u8, name, ".fini")) break :blk true; + if (Elf.isCIdentifier(name)) break :blk true; break :blk false; }; if (is_gc_root and markAtom(atom)) try roots.append(atom); diff --git a/test/elf.zig b/test/elf.zig index f76910ea..0e47516e 100644 --- a/test/elf.zig +++ b/test/elf.zig @@ -9,6 +9,8 @@ pub fn addElfTests(b: *Build, opts: Options) *Step { elf_step.dependOn(testDsoPlt(b, opts)); elf_step.dependOn(testIfuncDynamic(b, opts)); elf_step.dependOn(testHelloDynamic(b, opts)); + elf_step.dependOn(testHelloStatic(b, opts)); + elf_step.dependOn(testTlsStatic(b, opts)); } return elf_step; @@ -230,6 +232,25 @@ fn testIfuncDynamic(b: *Build, opts: Options) *Step { return test_step; } +fn testHelloStatic(b: *Build, opts: Options) *Step { + const test_step = b.step("test-elf-hello-static", ""); + + if (!opts.has_static) { + skipTestStep(test_step); + return test_step; + } + + const exe = cc(b, null, opts); + exe.addHelloWorldMain(); + exe.addArg("-static"); + + const run = exe.run(); + run.expectHelloWorld(); + test_step.dependOn(run.step()); + + return test_step; +} + fn testHelloDynamic(b: *Build, opts: Options) *Step { const test_step = b.step("test-elf-hello-dynamic", ""); @@ -244,6 +265,42 @@ fn testHelloDynamic(b: *Build, opts: Options) *Step { return test_step; } +fn testTlsStatic(b: *Build, opts: Options) *Step { + const test_step = b.step("test-elf-tls-static", ""); + + if (!opts.has_static) { + skipTestStep(test_step); + return test_step; + } + + const exe = cc(b, null, opts); + exe.addSourceBytes( + \\#include + \\_Thread_local int a = 10; + \\_Thread_local int b; + \\_Thread_local char c = 'a'; + \\int main(int argc, char* argv[]) { + \\ printf("%d %d %c\n", a, b, c); + \\ a += 1; + \\ b += 1; + \\ c += 1; + \\ printf("%d %d %c\n", a, b, c); + \\ return 0; + \\} + , "main.c"); + exe.addArg("-static"); + + const run = exe.run(); + run.expectStdOutEqual( + \\10 0 a + \\11 1 b + \\ + ); + test_step.dependOn(run.step()); + + return test_step; +} + fn cc(b: *Build, name: ?[]const u8, opts: Options) SysCmd { const cmd = Run.create(b, "cc"); cmd.addArgs(&.{ "cc", "-fno-lto" }); @@ -271,6 +328,7 @@ fn ld(b: *Build, name: ?[]const u8, opts: Options) SysCmd { const std = @import("std"); const builtin = @import("builtin"); const common = @import("test.zig"); +const skipTestStep = common.skipTestStep; const Build = std.Build; const Compile = Step.Compile; diff --git a/test/test.zig b/test/test.zig index 33e77102..2d614e9f 100644 --- a/test/test.zig +++ b/test/test.zig @@ -1,4 +1,6 @@ -pub fn addTests(b: *Build, comp: *Compile) *Step { +pub fn addTests(b: *Build, comp: *Compile, build_opts: struct { + has_static: bool, +}) *Step { const test_step = b.step("test-system-tools", "Run all system tools tests"); test_step.dependOn(&comp.step); @@ -7,9 +9,11 @@ pub fn addTests(b: *Build, comp: *Compile) *Step { std.zig.system.darwin.getDarwinSDK(b.allocator, builtin.target) else null; + const opts: Options = .{ .zld = zld, .sdk_path = sdk_path, + .has_static = build_opts.has_static, }; test_step.dependOn(macho.addMachOTests(b, opts)); @@ -21,6 +25,7 @@ pub fn addTests(b: *Build, comp: *Compile) *Step { pub const Options = struct { zld: FileSourceWithDir, sdk_path: ?std.zig.system.darwin.DarwinSDK = null, + has_static: bool = false, }; /// A system command that tracks the command itself via `cmd` Step.Run and output file @@ -145,6 +150,38 @@ pub const FileSourceWithDir = struct { } }; +pub const SkipTestStep = struct { + pub const base_id = .custom; + + step: Step, + builder: *Build, + + pub fn create(builder: *Build) *SkipTestStep { + const self = builder.allocator.create(SkipTestStep) catch unreachable; + self.* = SkipTestStep{ + .builder = builder, + .step = Step.init(.{ + .id = .custom, + .name = "test skipped", + .owner = builder, + .makeFn = make, + }), + }; + return self; + } + + fn make(step: *Step, prog_node: *std.Progress.Node) anyerror!void { + _ = step; + _ = prog_node; + return error.MakeSkipped; + } +}; + +pub fn skipTestStep(test_step: *Step) void { + const skip = SkipTestStep.create(test_step.owner); + test_step.dependOn(&skip.step); +} + const std = @import("std"); const builtin = @import("builtin"); const elf = @import("elf.zig");