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:
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;
}