zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

commit 2407ee954b780e5a6a73f88b0865feff365c5ce5 (tree)
parent ce198b7c28e89c0bb502d215f9f9de99abb25f08
Author: Alex Rønne Petersen <alex@alexrp.com>
Date:   Wed, 29 Apr 2026 22:10:21 +0200

Merge pull request 'implement `.preinit_array` support in `link.Elf`; link libtsan with `-whole-archive`' (#32107) from alexrp/zig:preinit-array into master

Reviewed-on: https://codeberg.org/ziglang/zig/pulls/32107
Reviewed-by: mlugg <mlugg@noreply.codeberg.org>

Diffstat:
Msrc/Compilation.zig | 10++++++----
Msrc/libs/libcxx.zig | 4++--
Msrc/libs/libtsan.zig | 3++-
Msrc/libs/libunwind.zig | 2+-
Msrc/libs/musl.zig | 2+-
Msrc/link.zig | 57+++++++++++++++++++++++++++++++--------------------------
Msrc/link/Elf.zig | 8+++++---
Msrc/link/Elf/ZigObject.zig | 4++++
Msrc/link/Elf/synthetic_sections.zig | 8++++++++
Msrc/link/Lld.zig | 18+++++++++++-------
10 files changed, 71 insertions(+), 45 deletions(-)

diff --git a/src/Compilation.zig b/src/Compilation.zig @@ -7426,7 +7426,7 @@ fn buildOutputFromZig( assert(out.* == null); out.* = crt_file; - try comp.queuePrelinkTaskMode(crt_file.full_object_path, &config); + try comp.queuePrelinkTaskMode(crt_file.full_object_path, false, &config); } pub const CrtFileOptions = struct { @@ -7560,7 +7560,7 @@ pub fn build_crt_file( try comp.updateSubCompilation(sub_compilation, misc_task_tag, prog_node); const crt_file = try sub_compilation.toCrtFile(); - try comp.queuePrelinkTaskMode(crt_file.full_object_path, &config); + try comp.queuePrelinkTaskMode(crt_file.full_object_path, false, &config); { comp.mutex.lockUncancelable(io); @@ -7570,12 +7570,14 @@ pub fn build_crt_file( } } -pub fn queuePrelinkTaskMode(comp: *Compilation, path: Cache.Path, config: *const Compilation.Config) Io.Cancelable!void { +/// If `must_link` is set, then static library inputs will have all member objects linked into the +/// output, instead of only those required to resolve symbol references. +pub fn queuePrelinkTaskMode(comp: *Compilation, path: Cache.Path, must_link: bool, config: *const Compilation.Config) Io.Cancelable!void { try comp.queuePrelinkTasks(switch (config.output_mode) { .Exe => unreachable, .Obj => &.{.{ .load_object = path }}, .Lib => &.{switch (config.link_mode) { - .static => .{ .load_archive = path }, + .static => .{ .load_archive = .{ .path = path, .must_link = must_link } }, .dynamic => .{ .load_dso = path }, }}, }); diff --git a/src/libs/libcxx.zig b/src/libs/libcxx.zig @@ -295,7 +295,7 @@ pub fn buildLibCxx(comp: *Compilation, prog_node: std.Progress.Node) BuildError! assert(comp.libcxx_static_lib == null); const crt_file = try sub_compilation.toCrtFile(); comp.libcxx_static_lib = crt_file; - try comp.queuePrelinkTaskMode(crt_file.full_object_path, &config); + try comp.queuePrelinkTaskMode(crt_file.full_object_path, false, &config); } pub fn buildLibCxxAbi(comp: *Compilation, prog_node: std.Progress.Node) BuildError!void { @@ -492,7 +492,7 @@ pub fn buildLibCxxAbi(comp: *Compilation, prog_node: std.Progress.Node) BuildErr assert(comp.libcxxabi_static_lib == null); const crt_file = try sub_compilation.toCrtFile(); comp.libcxxabi_static_lib = crt_file; - try comp.queuePrelinkTaskMode(crt_file.full_object_path, &config); + try comp.queuePrelinkTaskMode(crt_file.full_object_path, false, &config); } pub fn addCxxArgs( diff --git a/src/libs/libtsan.zig b/src/libs/libtsan.zig @@ -320,8 +320,9 @@ pub fn buildTsan(comp: *Compilation, prog_node: std.Progress.Node) BuildError!vo }, }; + // libtsan contains `.preinit_array` entries that must run for correctness, hence `must_link = true`. const crt_file = try sub_compilation.toCrtFile(); - try comp.queuePrelinkTaskMode(crt_file.full_object_path, &config); + try comp.queuePrelinkTaskMode(crt_file.full_object_path, true, &config); assert(comp.tsan_lib == null); comp.tsan_lib = crt_file; } diff --git a/src/libs/libunwind.zig b/src/libs/libunwind.zig @@ -179,7 +179,7 @@ pub fn buildStaticLib(comp: *Compilation, prog_node: std.Progress.Node) BuildErr }; const crt_file = try sub_compilation.toCrtFile(); - try comp.queuePrelinkTaskMode(crt_file.full_object_path, &config); + try comp.queuePrelinkTaskMode(crt_file.full_object_path, false, &config); assert(comp.libunwind_static_lib == null); comp.libunwind_static_lib = crt_file; } diff --git a/src/libs/musl.zig b/src/libs/musl.zig @@ -279,7 +279,7 @@ pub fn buildCrtFile(comp: *Compilation, in_crt_file: CrtFile, prog_node: std.Pro errdefer comp.gpa.free(basename); const crt_file = try sub_compilation.toCrtFile(); - try comp.queuePrelinkTaskMode(crt_file.full_object_path, &config); + try comp.queuePrelinkTaskMode(crt_file.full_object_path, false, &config); { comp.mutex.lockUncancelable(io); defer comp.mutex.unlock(io); diff --git a/src/link.zig b/src/link.zig @@ -1105,27 +1105,29 @@ pub const File = struct { } /// Opens a path as a static library and parses it into the linker. - /// If `query` is non-null, allows GNU ld scripts. - fn openLoadArchive(base: *File, path: Path, opt_query: ?UnresolvedInput.Query) anyerror!void { + fn openLoadArchive(base: *File, path: Path, must_link: bool) anyerror!void { if (base.tag == .lld) return; const io = base.comp.io; - if (opt_query) |query| { - const archive = try openObject(io, path, query.must_link, query.hidden); - errdefer archive.file.close(io); - loadInput(base, .{ .archive = archive }) catch |err| switch (err) { - error.BadMagic, error.UnexpectedEndOfFile => { - if (base.tag != .elf and base.tag != .elf2) return err; - try loadGnuLdScript(base, path, query, archive.file); - archive.file.close(io); - return; - }, - else => return err, - }; - } else { - const archive = try openObject(io, path, false, false); - errdefer archive.file.close(io); - try loadInput(base, .{ .archive = archive }); - } + const archive = try openObject(io, path, must_link, false); + errdefer archive.file.close(io); + try loadInput(base, .{ .archive = archive }); + } + + /// Opens a path as a static library and parses it into the linker. Allows GNU ld scripts. + fn openLoadArchiveQuery(base: *File, path: Path, query: UnresolvedInput.Query) anyerror!void { + if (base.tag == .lld) return; + const io = base.comp.io; + const archive = try openObject(io, path, query.must_link, query.hidden); + errdefer archive.file.close(io); + loadInput(base, .{ .archive = archive }) catch |err| switch (err) { + error.BadMagic, error.UnexpectedEndOfFile => { + if (base.tag != .elf and base.tag != .elf2) return err; + try loadGnuLdScript(base, path, query, archive.file); + archive.file.close(io); + return; + }, + else => return err, + }; } /// Opens a path as a shared library and parses it into the linker. @@ -1180,7 +1182,7 @@ pub const File = struct { switch (Compilation.classifyFileExt(arg.path)) { .shared_library => try openLoadDso(base, new_path, query), .object => try openLoadObject(base, new_path), - .static_library => try openLoadArchive(base, new_path, query), + .static_library => try openLoadArchiveQuery(base, new_path, query), else => diags.addParseError(path, "GNU ld script references file with unrecognized extension: {s}", .{arg.path}), } } else { @@ -1380,7 +1382,10 @@ pub const PrelinkTask = union(enum) { /// Tells the linker to load an object file by path. load_object: Path, /// Tells the linker to load a static library by path. - load_archive: Path, + load_archive: struct { + path: Path, + must_link: bool, + }, /// Tells the linker to load a shared library, possibly one that is a /// GNU ld script. load_dso: Path, @@ -1462,7 +1467,7 @@ pub fn doPrelinkTask(comp: *Compilation, task: PrelinkTask) void { crt_dir, target.libPrefix(), lib_name, target.staticLibSuffix(), }) catch return diags.setAllocFailure(), ); - base.openLoadArchive(archive_path, .{ + base.openLoadArchiveQuery(archive_path, .{ .preferred_mode = .dynamic, .search_strategy = .paths_first, }) catch |archive_err| switch (archive_err) { @@ -1481,7 +1486,7 @@ pub fn doPrelinkTask(comp: *Compilation, task: PrelinkTask) void { }) catch return diags.setAllocFailure(), ); // glibc sometimes makes even archive files GNU ld scripts. - base.openLoadArchive(path, .{ + base.openLoadArchiveQuery(path, .{ .preferred_mode = .static, .search_strategy = .no_fallback, }) catch |err| switch (err) { @@ -1500,12 +1505,12 @@ pub fn doPrelinkTask(comp: *Compilation, task: PrelinkTask) void { else => |e| diags.addParseError(path, "failed to parse object: {s}", .{@errorName(e)}), }; }, - .load_archive => |path| { + .load_archive => |load_archive| { const prog_node = comp.link_prog_node.start("Parse Archive", 0); defer prog_node.end(); - base.openLoadArchive(path, null) catch |err| switch (err) { + base.openLoadArchive(load_archive.path, load_archive.must_link) catch |err| switch (err) { error.LinkFailure => return, // error reported via link_diags - else => |e| diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(e)}), + else => |e| diags.addParseError(load_archive.path, "failed to parse archive: {s}", .{@errorName(e)}), }; }, .load_dso => |path| { diff --git a/src/link/Elf.zig b/src/link/Elf.zig @@ -1392,9 +1392,9 @@ pub fn initOutputSection(self: *Elf, args: struct { if (self.base.isRelocatable()) break :blk args.name; if (args.flags & elf.SHF_MERGE != 0) break :blk args.name; const 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", + ".text", ".data.rel.ro", ".data", ".rodata", ".bss.rel.ro", ".bss", + ".preinit_array", ".init_array", ".fini_array", ".tbss", ".tdata", ".gcc_except_table", + ".ctors", ".dtors", ".gnu.warning", }; inline for (name_prefixes) |prefix| { if (mem.eql(u8, args.name, prefix) or mem.startsWith(u8, args.name, prefix ++ ".")) { @@ -1409,6 +1409,8 @@ pub fn initOutputSection(self: *Elf, args: struct { switch (args.type) { elf.SHT_NULL => unreachable, elf.SHT_PROGBITS => { + if (mem.eql(u8, args.name, ".preinit_array") or mem.startsWith(u8, args.name, ".preinit_array.")) + break :tt elf.SHT_PREINIT_ARRAY; if (mem.eql(u8, args.name, ".init_array") or mem.startsWith(u8, args.name, ".init_array.")) break :tt elf.SHT_INIT_ARRAY; if (mem.eql(u8, args.name, ".fini_array") or mem.startsWith(u8, args.name, ".fini_array.")) diff --git a/src/link/Elf/ZigObject.zig b/src/link/Elf/ZigObject.zig @@ -1237,6 +1237,10 @@ fn getNavShdrIndex( self.debug_rnglists_index = section_index; } else if (std.mem.startsWith(u8, section_name, ".debug")) { elf_file.sections.items(.shdr)[osec].sh_flags = 0; + } else if (std.mem.eql(u8, section_name, ".preinit_array") or std.mem.startsWith(u8, section_name, ".preinit_array.")) { + const shdr = &elf_file.sections.items(.shdr)[osec]; + shdr.sh_type = elf.SHT_PREINIT_ARRAY; + shdr.sh_flags = elf.SHF_ALLOC | elf.SHF_WRITE; } else if (std.mem.eql(u8, section_name, ".init_array") or std.mem.startsWith(u8, section_name, ".init_array.")) { const shdr = &elf_file.sections.items(.shdr)[osec]; shdr.sh_type = elf.SHT_INIT_ARRAY; diff --git a/src/link/Elf/synthetic_sections.zig b/src/link/Elf/synthetic_sections.zig @@ -73,6 +73,7 @@ pub const DynamicSection = struct { if (dt.rpath > 0) nentries += 1; // RUNPATH if (elf_file.sectionByName(".init") != null) nentries += 1; // INIT if (elf_file.sectionByName(".fini") != null) nentries += 1; // FINI + if (elf_file.sectionByName(".preinit_array") != null) nentries += 2; // PREINIT_ARRAY if (elf_file.sectionByName(".init_array") != null) nentries += 2; // INIT_ARRAY if (elf_file.sectionByName(".fini_array") != null) nentries += 2; // FINI_ARRAY if (elf_file.section_indexes.rela_dyn != null) nentries += 3; // RELA @@ -124,6 +125,13 @@ pub const DynamicSection = struct { try writer.writeStruct(@as(elf.Elf64_Dyn, .{ .d_tag = elf.DT_FINI, .d_val = addr }), .little); } + // PREINIT_ARRAY + if (elf_file.sectionByName(".preinit_array")) |shndx| { + const shdr = shdrs[shndx]; + try writer.writeStruct(@as(elf.Elf64_Dyn, .{ .d_tag = elf.DT_PREINIT_ARRAY, .d_val = shdr.sh_addr }), .little); + try writer.writeStruct(@as(elf.Elf64_Dyn, .{ .d_tag = elf.DT_PREINIT_ARRAYSZ, .d_val = shdr.sh_size }), .little); + } + // INIT_ARRAY if (elf_file.sectionByName(".init_array")) |shndx| { const shdr = shdrs[shndx]; diff --git a/src/link/Lld.zig b/src/link/Lld.zig @@ -1086,10 +1086,10 @@ fn elfLink(lld: *Lld, arena: Allocator) !void { .dso => continue, .object, .archive => |obj| { if (obj.must_link and !whole_archive) { - try argv.append("-whole-archive"); + try argv.append("--whole-archive"); whole_archive = true; } else if (!obj.must_link and whole_archive) { - try argv.append("-no-whole-archive"); + try argv.append("--no-whole-archive"); whole_archive = false; } try argv.append(try obj.path.toString(arena)); @@ -1101,7 +1101,7 @@ fn elfLink(lld: *Lld, arena: Allocator) !void { }; if (whole_archive) { - try argv.append("-no-whole-archive"); + try argv.append("--no-whole-archive"); whole_archive = false; } @@ -1115,7 +1115,11 @@ fn elfLink(lld: *Lld, arena: Allocator) !void { if (comp.tsan_lib) |lib| { assert(comp.config.any_sanitize_thread); - try argv.append(try lib.full_object_path.toString(arena)); + try argv.appendSlice(&.{ + "--whole-archive", + try lib.full_object_path.toString(arena), + "--no-whole-archive", + }); } if (comp.fuzzer_lib) |lib| { @@ -1549,10 +1553,10 @@ fn wasmLink(lld: *Lld, arena: Allocator) !void { for (comp.link_inputs) |link_input| switch (link_input) { .object, .archive => |obj| { if (obj.must_link and !whole_archive) { - try argv.append("-whole-archive"); + try argv.append("--whole-archive"); whole_archive = true; } else if (!obj.must_link and whole_archive) { - try argv.append("-no-whole-archive"); + try argv.append("--no-whole-archive"); whole_archive = false; } try argv.append(try obj.path.toString(arena)); @@ -1564,7 +1568,7 @@ fn wasmLink(lld: *Lld, arena: Allocator) !void { .res => unreachable, }; if (whole_archive) { - try argv.append("-no-whole-archive"); + try argv.append("--no-whole-archive"); whole_archive = false; }