From 292edd2440baa02864f1d2fe2dc08140d0c3c7a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Tue, 18 Aug 2026 18:00:36 +0200 Subject: [PATCH] move std.os.linux.elf_aux_maybe to compiler_rt as __zig_elf_auxv This gets rid of the super confusing and (as far as I can tell) incorrect getauxval logic in std.os.linux, and clarifies exactly who owns this state. The ABI that std now expects is: extern var __zig_elf_auxv: [*]std.elf.Auxv; Users can technically override this weak symbol with a strong symbol if they wish, although I see no particularly useful reason to do so. It is now a compile error to use std.os.linux.getauxval if libc is linked, whereas previously it would just give useless results in some cases. I consider this to be strictly an improvement. Just use std.c.getauxval when linking libc, which std already does correctly where it needs getauxval. This code is expected to be further simplified once our baseline Linux version allows implementing https://codeberg.org/ziglang/zig/issues/31290. closes https://codeberg.org/ziglang/zig/issues/36203 --- lib/compiler_rt.zig | 2 ++ lib/compiler_rt/elf.zig | 3 +++ lib/compiler_rt/elf/auxv.zig | 21 ++++++++++++++++++ lib/std/os/linux.zig | 43 +++++++++--------------------------- lib/std/os/linux/vdso.zig | 4 +++- lib/std/start.zig | 5 ++++- 6 files changed, 44 insertions(+), 34 deletions(-) create mode 100644 lib/compiler_rt/elf.zig create mode 100644 lib/compiler_rt/elf/auxv.zig diff --git a/lib/compiler_rt.zig b/lib/compiler_rt.zig index 5660847ce84fbbedc64d4e980202e3865f8df592..e4d4824a38654c26da6a763f75e8df8cfd408681 100644 --- a/lib/compiler_rt.zig +++ b/lib/compiler_rt.zig @@ -164,6 +164,8 @@ comptime { symbol(&strlen, "strlen"); } + _ = @import("compiler_rt/elf.zig"); + // Temporarily used for uefi until https://github.com/ziglang/zig/issues/21630 is addressed. if (!builtin.link_libc and (builtin.os.tag == .windows or builtin.os.tag == .uefi) and (builtin.abi == .none or builtin.abi == .msvc)) { symbol(&_fltused, "_fltused"); diff --git a/lib/compiler_rt/elf.zig b/lib/compiler_rt/elf.zig new file mode 100644 index 0000000000000000000000000000000000000000..e68d5ad7c459cccfc3fefe34739507349a5ed350 --- /dev/null +++ b/lib/compiler_rt/elf.zig @@ -0,0 +1,3 @@ +comptime { + _ = @import("elf/auxv.zig"); +} diff --git a/lib/compiler_rt/elf/auxv.zig b/lib/compiler_rt/elf/auxv.zig new file mode 100644 index 0000000000000000000000000000000000000000..de5a45e501b5850f8f25aa18ffec75ccf23927ac --- /dev/null +++ b/lib/compiler_rt/elf/auxv.zig @@ -0,0 +1,21 @@ +const builtin = @import("builtin"); +const std = @import("std"); + +const compiler_rt = @import("../../compiler_rt.zig"); +const symbol = compiler_rt.symbol; + +comptime { + // FreeBSD, NetBSD, and OpenBSD are expected to also use this in the future. + if (!builtin.link_libc and builtin.target.os.tag == .linux) { + symbol(@ptrCast(&__zig_elf_auxv), "__zig_elf_auxv"); + } +} + +/// Populated by `std.start` when libc is not linked on targets that provide an ELF auxiliary vector +/// to the process on startup. Provided by compiler-rt so that there is no confusion about where +/// this state lives regardless of how the final artifact (executable or shared library) is +/// compiled. +/// +/// It is expected that this will gradually be replaced with better mechanisms as operating systems +/// improve; see https://codeberg.org/ziglang/zig/issues/31290. +var __zig_elf_auxv: [*]std.elf.Auxv = undefined; diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index 38e4abd197ba6c17245e7fe7038d7db0528ca395..e75a0d822467481b245997d4818989a9c7654b50 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -960,43 +960,22 @@ pub const RENAME = packed struct(u32) { _: u29 = 0, }; -/// Set by startup code, used by `getauxval`. -pub var elf_aux_maybe: ?[*]std.elf.Auxv = null; +/// Available only when libc is *not* linked; use `std.c.getauxval` if libc is linked. +pub const getauxval = if (builtin.link_libc) {} else struct { + /// Provided by compiler-rt and set by `std.start`. + extern var __zig_elf_auxv: [*]std.elf.Auxv; -/// Whether an external or internal getauxval implementation is used. -const extern_getauxval = switch (builtin.zig_backend) { - // Calling extern functions is not yet supported with these backends - .stage2_arm, - .stage2_loongarch, - .stage2_powerpc, - .stage2_riscv64, - .stage2_sparc64, - => false, - else => !builtin.link_libc, -}; + pub fn getauxval(index: usize) usize { + @disableInstrumentation(); -pub const getauxval = if (extern_getauxval) struct { - comptime { - const root = @import("root"); - // Export this only when building an executable, otherwise it is overriding - // the libc implementation - if (builtin.output_mode == .Exe or @hasDecl(root, "main")) { - @export(&getauxvalImpl, .{ .name = "getauxval", .linkage = .weak }); + var i: usize = 0; + while (__zig_elf_auxv[i].a_type != std.elf.AT.NULL) : (i += 1) { + if (__zig_elf_auxv[i].a_type == index) return __zig_elf_auxv[i].a_un.a_val; } - } - extern fn getauxval(index: usize) usize; -}.getauxval else getauxvalImpl; -fn getauxvalImpl(index: usize) callconv(.c) usize { - @disableInstrumentation(); - const auxv = elf_aux_maybe orelse return 0; - var i: usize = 0; - while (auxv[i].a_type != std.elf.AT.NULL) : (i += 1) { - if (auxv[i].a_type == index) - return auxv[i].a_un.a_val; + return 0; } - return 0; -} +}.getauxval; // Some architectures (and some syscalls) require 64bit parameters to be passed // in a even-aligned register pair. diff --git a/lib/std/os/linux/vdso.zig b/lib/std/os/linux/vdso.zig index bdf46337b59ecc863a543d3722ddf47bd967374a..5add23f5c42bd6f93cfdc39825b42cc969d8e321 100644 --- a/lib/std/os/linux/vdso.zig +++ b/lib/std/os/linux/vdso.zig @@ -1,3 +1,4 @@ +const builtin = @import("builtin"); const std = @import("../../std.zig"); const elf = std.elf; const linux = std.os.linux; @@ -5,7 +6,8 @@ const mem = std.mem; const maxInt = std.math.maxInt; pub fn lookup(vername: []const u8, name: []const u8) usize { - const vdso_addr = linux.getauxval(std.elf.AT.SYSINFO_EHDR); + const getauxval = if (builtin.link_libc) std.c.getauxval else linux.getauxval; + const vdso_addr = getauxval(std.elf.AT.SYSINFO_EHDR); if (vdso_addr == 0) return 0; const eh = @as(*elf.Ehdr, @ptrFromInt(vdso_addr)); diff --git a/lib/std/start.zig b/lib/std/start.zig index 96190d835614718793d94832f9a0b72e415d964d..f8c85adeded05726074c5ed0516b336892d34154 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -596,6 +596,9 @@ fn wWinMain(hInstance: *anyopaque, hPrevInstance: ?*anyopaque, pCmdLine: [*:0]u1 return root.wWinMain(@ptrCast(hInstance), @ptrCast(hPrevInstance), pCmdLine, @intCast(nCmdShow)); } +/// Provided by compiler-rt. +extern var __zig_elf_auxv: [*]std.elf.Auxv; + fn posixCallMainAndExit(argc_argv_ptr: [*]usize) callconv(.c) noreturn { // We're not ready to panic until thread local storage is initialized. @setRuntimeSafety(false); @@ -638,7 +641,7 @@ fn posixCallMainAndExit(argc_argv_ptr: [*]usize) callconv(.c) noreturn { if (native_os == .linux) { // This must be done after PIE relocations have been applied or we may crash // while trying to access the global variable (happens on MIPS at least). - std.os.linux.elf_aux_maybe = auxv; + __zig_elf_auxv = auxv; if (!builtin.single_threaded) { // ARMv6 targets (and earlier) have no support for TLS in hardware. -- 2.54.0