authorgravatar for alex@alexrp.comAlex Rønne Petersen <alex@alexrp.com> 2026-04-29 22:10:21+02:00
committergravatar for alex@alexrp.comAlex Rønne Petersen <alex@alexrp.com> 2026-04-29 22:10:21+02:00
log2407ee954b780e5a6a73f88b0865feff365c5ce5
tree1bd5428a2514140a27bf109d9f4f64b60afee512
parentce198b7c28e89c0bb502d215f9f9de99abb25f08
parent0227253677e6579deb1849554638fd9d8f20d9e3

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>

10 files changed, 71 insertions(+), 45 deletions(-)

src/Compilation.zig+6-4
......@@ -7426,7 +7426,7 @@ fn buildOutputFromZig(
74267426 assert(out.* == null);
74277427 out.* = crt_file;
74287428
7429 try comp.queuePrelinkTaskMode(crt_file.full_object_path, &config);
7429 try comp.queuePrelinkTaskMode(crt_file.full_object_path, false, &config);
74307430}
74317431
74327432pub const CrtFileOptions = struct {
......@@ -7560,7 +7560,7 @@ pub fn build_crt_file(
75607560 try comp.updateSubCompilation(sub_compilation, misc_task_tag, prog_node);
75617561
75627562 const crt_file = try sub_compilation.toCrtFile();
7563 try comp.queuePrelinkTaskMode(crt_file.full_object_path, &config);
7563 try comp.queuePrelinkTaskMode(crt_file.full_object_path, false, &config);
75647564
75657565 {
75667566 comp.mutex.lockUncancelable(io);
......@@ -7570,12 +7570,14 @@ pub fn build_crt_file(
75707570 }
75717571}
75727572
7573pub fn queuePrelinkTaskMode(comp: *Compilation, path: Cache.Path, config: *const Compilation.Config) Io.Cancelable!void {
7573/// If `must_link` is set, then static library inputs will have all member objects linked into the
7574/// output, instead of only those required to resolve symbol references.
7575pub fn queuePrelinkTaskMode(comp: *Compilation, path: Cache.Path, must_link: bool, config: *const Compilation.Config) Io.Cancelable!void {
75747576 try comp.queuePrelinkTasks(switch (config.output_mode) {
75757577 .Exe => unreachable,
75767578 .Obj => &.{.{ .load_object = path }},
75777579 .Lib => &.{switch (config.link_mode) {
7578 .static => .{ .load_archive = path },
7580 .static => .{ .load_archive = .{ .path = path, .must_link = must_link } },
75797581 .dynamic => .{ .load_dso = path },
75807582 }},
75817583 });
src/libs/libcxx.zig+2-2
......@@ -295,7 +295,7 @@ pub fn buildLibCxx(comp: *Compilation, prog_node: std.Progress.Node) BuildError!
295295 assert(comp.libcxx_static_lib == null);
296296 const crt_file = try sub_compilation.toCrtFile();
297297 comp.libcxx_static_lib = crt_file;
298 try comp.queuePrelinkTaskMode(crt_file.full_object_path, &config);
298 try comp.queuePrelinkTaskMode(crt_file.full_object_path, false, &config);
299299}
300300
301301pub 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
492492 assert(comp.libcxxabi_static_lib == null);
493493 const crt_file = try sub_compilation.toCrtFile();
494494 comp.libcxxabi_static_lib = crt_file;
495 try comp.queuePrelinkTaskMode(crt_file.full_object_path, &config);
495 try comp.queuePrelinkTaskMode(crt_file.full_object_path, false, &config);
496496}
497497
498498pub fn addCxxArgs(
src/libs/libtsan.zig+2-1
......@@ -320,8 +320,9 @@ pub fn buildTsan(comp: *Compilation, prog_node: std.Progress.Node) BuildError!vo
320320 },
321321 };
322322
323 // libtsan contains `.preinit_array` entries that must run for correctness, hence `must_link = true`.
323324 const crt_file = try sub_compilation.toCrtFile();
324 try comp.queuePrelinkTaskMode(crt_file.full_object_path, &config);
325 try comp.queuePrelinkTaskMode(crt_file.full_object_path, true, &config);
325326 assert(comp.tsan_lib == null);
326327 comp.tsan_lib = crt_file;
327328}
src/libs/libunwind.zig+1-1
......@@ -179,7 +179,7 @@ pub fn buildStaticLib(comp: *Compilation, prog_node: std.Progress.Node) BuildErr
179179 };
180180
181181 const crt_file = try sub_compilation.toCrtFile();
182 try comp.queuePrelinkTaskMode(crt_file.full_object_path, &config);
182 try comp.queuePrelinkTaskMode(crt_file.full_object_path, false, &config);
183183 assert(comp.libunwind_static_lib == null);
184184 comp.libunwind_static_lib = crt_file;
185185}
src/libs/musl.zig+1-1
......@@ -279,7 +279,7 @@ pub fn buildCrtFile(comp: *Compilation, in_crt_file: CrtFile, prog_node: std.Pro
279279 errdefer comp.gpa.free(basename);
280280
281281 const crt_file = try sub_compilation.toCrtFile();
282 try comp.queuePrelinkTaskMode(crt_file.full_object_path, &config);
282 try comp.queuePrelinkTaskMode(crt_file.full_object_path, false, &config);
283283 {
284284 comp.mutex.lockUncancelable(io);
285285 defer comp.mutex.unlock(io);
src/link.zig+31-26
......@@ -1105,27 +1105,29 @@ pub const File = struct {
11051105 }
11061106
11071107 /// Opens a path as a static library and parses it into the linker.
1108 /// If `query` is non-null, allows GNU ld scripts.
1109 fn openLoadArchive(base: *File, path: Path, opt_query: ?UnresolvedInput.Query) anyerror!void {
1108 fn openLoadArchive(base: *File, path: Path, must_link: bool) anyerror!void {
11101109 if (base.tag == .lld) return;
11111110 const io = base.comp.io;
1112 if (opt_query) |query| {
1113 const archive = try openObject(io, path, query.must_link, query.hidden);
1114 errdefer archive.file.close(io);
1115 loadInput(base, .{ .archive = archive }) catch |err| switch (err) {
1116 error.BadMagic, error.UnexpectedEndOfFile => {
1117 if (base.tag != .elf and base.tag != .elf2) return err;
1118 try loadGnuLdScript(base, path, query, archive.file);
1119 archive.file.close(io);
1120 return;
1121 },
1122 else => return err,
1123 };
1124 } else {
1125 const archive = try openObject(io, path, false, false);
1126 errdefer archive.file.close(io);
1127 try loadInput(base, .{ .archive = archive });
1128 }
1111 const archive = try openObject(io, path, must_link, false);
1112 errdefer archive.file.close(io);
1113 try loadInput(base, .{ .archive = archive });
1114 }
1115
1116 /// Opens a path as a static library and parses it into the linker. Allows GNU ld scripts.
1117 fn openLoadArchiveQuery(base: *File, path: Path, query: UnresolvedInput.Query) anyerror!void {
1118 if (base.tag == .lld) return;
1119 const io = base.comp.io;
1120 const archive = try openObject(io, path, query.must_link, query.hidden);
1121 errdefer archive.file.close(io);
1122 loadInput(base, .{ .archive = archive }) catch |err| switch (err) {
1123 error.BadMagic, error.UnexpectedEndOfFile => {
1124 if (base.tag != .elf and base.tag != .elf2) return err;
1125 try loadGnuLdScript(base, path, query, archive.file);
1126 archive.file.close(io);
1127 return;
1128 },
1129 else => return err,
1130 };
11291131 }
11301132
11311133 /// Opens a path as a shared library and parses it into the linker.
......@@ -1180,7 +1182,7 @@ pub const File = struct {
11801182 switch (Compilation.classifyFileExt(arg.path)) {
11811183 .shared_library => try openLoadDso(base, new_path, query),
11821184 .object => try openLoadObject(base, new_path),
1183 .static_library => try openLoadArchive(base, new_path, query),
1185 .static_library => try openLoadArchiveQuery(base, new_path, query),
11841186 else => diags.addParseError(path, "GNU ld script references file with unrecognized extension: {s}", .{arg.path}),
11851187 }
11861188 } else {
......@@ -1380,7 +1382,10 @@ pub const PrelinkTask = union(enum) {
13801382 /// Tells the linker to load an object file by path.
13811383 load_object: Path,
13821384 /// Tells the linker to load a static library by path.
1383 load_archive: Path,
1385 load_archive: struct {
1386 path: Path,
1387 must_link: bool,
1388 },
13841389 /// Tells the linker to load a shared library, possibly one that is a
13851390 /// GNU ld script.
13861391 load_dso: Path,
......@@ -1462,7 +1467,7 @@ pub fn doPrelinkTask(comp: *Compilation, task: PrelinkTask) void {
14621467 crt_dir, target.libPrefix(), lib_name, target.staticLibSuffix(),
14631468 }) catch return diags.setAllocFailure(),
14641469 );
1465 base.openLoadArchive(archive_path, .{
1470 base.openLoadArchiveQuery(archive_path, .{
14661471 .preferred_mode = .dynamic,
14671472 .search_strategy = .paths_first,
14681473 }) catch |archive_err| switch (archive_err) {
......@@ -1481,7 +1486,7 @@ pub fn doPrelinkTask(comp: *Compilation, task: PrelinkTask) void {
14811486 }) catch return diags.setAllocFailure(),
14821487 );
14831488 // glibc sometimes makes even archive files GNU ld scripts.
1484 base.openLoadArchive(path, .{
1489 base.openLoadArchiveQuery(path, .{
14851490 .preferred_mode = .static,
14861491 .search_strategy = .no_fallback,
14871492 }) catch |err| switch (err) {
......@@ -1500,12 +1505,12 @@ pub fn doPrelinkTask(comp: *Compilation, task: PrelinkTask) void {
15001505 else => |e| diags.addParseError(path, "failed to parse object: {s}", .{@errorName(e)}),
15011506 };
15021507 },
1503 .load_archive => |path| {
1508 .load_archive => |load_archive| {
15041509 const prog_node = comp.link_prog_node.start("Parse Archive", 0);
15051510 defer prog_node.end();
1506 base.openLoadArchive(path, null) catch |err| switch (err) {
1511 base.openLoadArchive(load_archive.path, load_archive.must_link) catch |err| switch (err) {
15071512 error.LinkFailure => return, // error reported via link_diags
1508 else => |e| diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(e)}),
1513 else => |e| diags.addParseError(load_archive.path, "failed to parse archive: {s}", .{@errorName(e)}),
15091514 };
15101515 },
15111516 .load_dso => |path| {
src/link/Elf.zig+5-3
......@@ -1392,9 +1392,9 @@ pub fn initOutputSection(self: *Elf, args: struct {
13921392 if (self.base.isRelocatable()) break :blk args.name;
13931393 if (args.flags & elf.SHF_MERGE != 0) break :blk args.name;
13941394 const name_prefixes: []const [:0]const u8 = &.{
1395 ".text", ".data.rel.ro", ".data", ".rodata", ".bss.rel.ro", ".bss",
1396 ".init_array", ".fini_array", ".tbss", ".tdata", ".gcc_except_table", ".ctors",
1397 ".dtors", ".gnu.warning",
1395 ".text", ".data.rel.ro", ".data", ".rodata", ".bss.rel.ro", ".bss",
1396 ".preinit_array", ".init_array", ".fini_array", ".tbss", ".tdata", ".gcc_except_table",
1397 ".ctors", ".dtors", ".gnu.warning",
13981398 };
13991399 inline for (name_prefixes) |prefix| {
14001400 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 {
14091409 switch (args.type) {
14101410 elf.SHT_NULL => unreachable,
14111411 elf.SHT_PROGBITS => {
1412 if (mem.eql(u8, args.name, ".preinit_array") or mem.startsWith(u8, args.name, ".preinit_array."))
1413 break :tt elf.SHT_PREINIT_ARRAY;
14121414 if (mem.eql(u8, args.name, ".init_array") or mem.startsWith(u8, args.name, ".init_array."))
14131415 break :tt elf.SHT_INIT_ARRAY;
14141416 if (mem.eql(u8, args.name, ".fini_array") or mem.startsWith(u8, args.name, ".fini_array."))
src/link/Elf/ZigObject.zig+4
......@@ -1237,6 +1237,10 @@ fn getNavShdrIndex(
12371237 self.debug_rnglists_index = section_index;
12381238 } else if (std.mem.startsWith(u8, section_name, ".debug")) {
12391239 elf_file.sections.items(.shdr)[osec].sh_flags = 0;
1240 } else if (std.mem.eql(u8, section_name, ".preinit_array") or std.mem.startsWith(u8, section_name, ".preinit_array.")) {
1241 const shdr = &elf_file.sections.items(.shdr)[osec];
1242 shdr.sh_type = elf.SHT_PREINIT_ARRAY;
1243 shdr.sh_flags = elf.SHF_ALLOC | elf.SHF_WRITE;
12401244 } else if (std.mem.eql(u8, section_name, ".init_array") or std.mem.startsWith(u8, section_name, ".init_array.")) {
12411245 const shdr = &elf_file.sections.items(.shdr)[osec];
12421246 shdr.sh_type = elf.SHT_INIT_ARRAY;
src/link/Elf/synthetic_sections.zig+8
......@@ -73,6 +73,7 @@ pub const DynamicSection = struct {
7373 if (dt.rpath > 0) nentries += 1; // RUNPATH
7474 if (elf_file.sectionByName(".init") != null) nentries += 1; // INIT
7575 if (elf_file.sectionByName(".fini") != null) nentries += 1; // FINI
76 if (elf_file.sectionByName(".preinit_array") != null) nentries += 2; // PREINIT_ARRAY
7677 if (elf_file.sectionByName(".init_array") != null) nentries += 2; // INIT_ARRAY
7778 if (elf_file.sectionByName(".fini_array") != null) nentries += 2; // FINI_ARRAY
7879 if (elf_file.section_indexes.rela_dyn != null) nentries += 3; // RELA
......@@ -124,6 +125,13 @@ pub const DynamicSection = struct {
124125 try writer.writeStruct(@as(elf.Elf64_Dyn, .{ .d_tag = elf.DT_FINI, .d_val = addr }), .little);
125126 }
126127
128 // PREINIT_ARRAY
129 if (elf_file.sectionByName(".preinit_array")) |shndx| {
130 const shdr = shdrs[shndx];
131 try writer.writeStruct(@as(elf.Elf64_Dyn, .{ .d_tag = elf.DT_PREINIT_ARRAY, .d_val = shdr.sh_addr }), .little);
132 try writer.writeStruct(@as(elf.Elf64_Dyn, .{ .d_tag = elf.DT_PREINIT_ARRAYSZ, .d_val = shdr.sh_size }), .little);
133 }
134
127135 // INIT_ARRAY
128136 if (elf_file.sectionByName(".init_array")) |shndx| {
129137 const shdr = shdrs[shndx];
src/link/Lld.zig+11-7
......@@ -1086,10 +1086,10 @@ fn elfLink(lld: *Lld, arena: Allocator) !void {
10861086 .dso => continue,
10871087 .object, .archive => |obj| {
10881088 if (obj.must_link and !whole_archive) {
1089 try argv.append("-whole-archive");
1089 try argv.append("--whole-archive");
10901090 whole_archive = true;
10911091 } else if (!obj.must_link and whole_archive) {
1092 try argv.append("-no-whole-archive");
1092 try argv.append("--no-whole-archive");
10931093 whole_archive = false;
10941094 }
10951095 try argv.append(try obj.path.toString(arena));
......@@ -1101,7 +1101,7 @@ fn elfLink(lld: *Lld, arena: Allocator) !void {
11011101 };
11021102
11031103 if (whole_archive) {
1104 try argv.append("-no-whole-archive");
1104 try argv.append("--no-whole-archive");
11051105 whole_archive = false;
11061106 }
11071107
......@@ -1115,7 +1115,11 @@ fn elfLink(lld: *Lld, arena: Allocator) !void {
11151115
11161116 if (comp.tsan_lib) |lib| {
11171117 assert(comp.config.any_sanitize_thread);
1118 try argv.append(try lib.full_object_path.toString(arena));
1118 try argv.appendSlice(&.{
1119 "--whole-archive",
1120 try lib.full_object_path.toString(arena),
1121 "--no-whole-archive",
1122 });
11191123 }
11201124
11211125 if (comp.fuzzer_lib) |lib| {
......@@ -1549,10 +1553,10 @@ fn wasmLink(lld: *Lld, arena: Allocator) !void {
15491553 for (comp.link_inputs) |link_input| switch (link_input) {
15501554 .object, .archive => |obj| {
15511555 if (obj.must_link and !whole_archive) {
1552 try argv.append("-whole-archive");
1556 try argv.append("--whole-archive");
15531557 whole_archive = true;
15541558 } else if (!obj.must_link and whole_archive) {
1555 try argv.append("-no-whole-archive");
1559 try argv.append("--no-whole-archive");
15561560 whole_archive = false;
15571561 }
15581562 try argv.append(try obj.path.toString(arena));
......@@ -1564,7 +1568,7 @@ fn wasmLink(lld: *Lld, arena: Allocator) !void {
15641568 .res => unreachable,
15651569 };
15661570 if (whole_archive) {
1567 try argv.append("-no-whole-archive");
1571 try argv.append("--no-whole-archive");
15681572 whole_archive = false;
15691573 }
15701574