authorgravatar for alex@alexrp.comAlex Rønne Petersen <alex@alexrp.com> 2026-08-18 18:00:36+02:00
committergravatar for alex@alexrp.comAlex Rønne Petersen <alex@alexrp.com> 2026-10-05 03:26:41+02:00
log292edd2440baa02864f1d2fe2dc08140d0c3c7a6
treea814259b8231019ef01c9dd1cf7757d07755a873
parent65b3cb2db988f7cf67fe5c2f39c96824e9497e3e
signature Signed by SSH key SHA256:7B/LJ7bpR1eX8aCXSr4mtd5M45VMPKcx9zY8e95b5QM

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

6 files changed, 47 insertions(+), 37 deletions(-)

lib/compiler_rt.zig+2
......@@ -164,6 +164,8 @@ comptime {
164164 symbol(&strlen, "strlen");
165165 }
166166
167 _ = @import("compiler_rt/elf.zig");
168
167169 // Temporarily used for uefi until https://github.com/ziglang/zig/issues/21630 is addressed.
168170 if (!builtin.link_libc and (builtin.os.tag == .windows or builtin.os.tag == .uefi) and (builtin.abi == .none or builtin.abi == .msvc)) {
169171 symbol(&_fltused, "_fltused");
lib/compiler_rt/elf.zig created+3
......@@ -0,0 +1,3 @@
1comptime {
2 _ = @import("elf/auxv.zig");
3}
lib/compiler_rt/elf/auxv.zig created+21
......@@ -0,0 +1,21 @@
1const builtin = @import("builtin");
2const std = @import("std");
3
4const compiler_rt = @import("../../compiler_rt.zig");
5const symbol = compiler_rt.symbol;
6
7comptime {
8 // FreeBSD, NetBSD, and OpenBSD are expected to also use this in the future.
9 if (!builtin.link_libc and builtin.target.os.tag == .linux) {
10 symbol(@ptrCast(&__zig_elf_auxv), "__zig_elf_auxv");
11 }
12}
13
14/// Populated by `std.start` when libc is not linked on targets that provide an ELF auxiliary vector
15/// to the process on startup. Provided by compiler-rt so that there is no confusion about where
16/// this state lives regardless of how the final artifact (executable or shared library) is
17/// compiled.
18///
19/// It is expected that this will gradually be replaced with better mechanisms as operating systems
20/// improve; see https://codeberg.org/ziglang/zig/issues/31290.
21var __zig_elf_auxv: [*]std.elf.Auxv = undefined;
lib/std/os/linux.zig+14-35
......@@ -960,43 +960,22 @@ pub const RENAME = packed struct(u32) {
960960 _: u29 = 0,
961961};
962962
963/// Set by startup code, used by `getauxval`.
964pub var elf_aux_maybe: ?[*]std.elf.Auxv = null;
965
966/// Whether an external or internal getauxval implementation is used.
967const extern_getauxval = switch (builtin.zig_backend) {
968 // Calling extern functions is not yet supported with these backends
969 .stage2_arm,
970 .stage2_loongarch,
971 .stage2_powerpc,
972 .stage2_riscv64,
973 .stage2_sparc64,
974 => false,
975 else => !builtin.link_libc,
976};
977
978pub const getauxval = if (extern_getauxval) struct {
979 comptime {
980 const root = @import("root");
981 // Export this only when building an executable, otherwise it is overriding
982 // the libc implementation
983 if (builtin.output_mode == .Exe or @hasDecl(root, "main")) {
984 @export(&getauxvalImpl, .{ .name = "getauxval", .linkage = .weak });
963/// Available only when libc is *not* linked; use `std.c.getauxval` if libc is linked.
964pub const getauxval = if (builtin.link_libc) {} else struct {
965 /// Provided by compiler-rt and set by `std.start`.
966 extern var __zig_elf_auxv: [*]std.elf.Auxv;
967
968 pub fn getauxval(index: usize) usize {
969 @disableInstrumentation();
970
971 var i: usize = 0;
972 while (__zig_elf_auxv[i].a_type != std.elf.AT.NULL) : (i += 1) {
973 if (__zig_elf_auxv[i].a_type == index) return __zig_elf_auxv[i].a_un.a_val;
985974 }
975
976 return 0;
986977 }
987 extern fn getauxval(index: usize) usize;
988}.getauxval else getauxvalImpl;
989
990fn getauxvalImpl(index: usize) callconv(.c) usize {
991 @disableInstrumentation();
992 const auxv = elf_aux_maybe orelse return 0;
993 var i: usize = 0;
994 while (auxv[i].a_type != std.elf.AT.NULL) : (i += 1) {
995 if (auxv[i].a_type == index)
996 return auxv[i].a_un.a_val;
997 }
998 return 0;
999}
978}.getauxval;
1000979
1001980// Some architectures (and some syscalls) require 64bit parameters to be passed
1002981// in a even-aligned register pair.
lib/std/os/linux/vdso.zig+3-1
......@@ -1,3 +1,4 @@
1const builtin = @import("builtin");
12const std = @import("../../std.zig");
23const elf = std.elf;
34const linux = std.os.linux;
......@@ -5,7 +6,8 @@ const mem = std.mem;
56const maxInt = std.math.maxInt;
67
78pub fn lookup(vername: []const u8, name: []const u8) usize {
8 const vdso_addr = linux.getauxval(std.elf.AT.SYSINFO_EHDR);
9 const getauxval = if (builtin.link_libc) std.c.getauxval else linux.getauxval;
10 const vdso_addr = getauxval(std.elf.AT.SYSINFO_EHDR);
911 if (vdso_addr == 0) return 0;
1012
1113 const eh = @as(*elf.Ehdr, @ptrFromInt(vdso_addr));
lib/std/start.zig+4-1
......@@ -596,6 +596,9 @@ fn wWinMain(hInstance: *anyopaque, hPrevInstance: ?*anyopaque, pCmdLine: [*:0]u1
596596 return root.wWinMain(@ptrCast(hInstance), @ptrCast(hPrevInstance), pCmdLine, @intCast(nCmdShow));
597597}
598598
599/// Provided by compiler-rt.
600extern var __zig_elf_auxv: [*]std.elf.Auxv;
601
599602fn posixCallMainAndExit(argc_argv_ptr: [*]usize) callconv(.c) noreturn {
600603 // We're not ready to panic until thread local storage is initialized.
601604 @setRuntimeSafety(false);
......@@ -638,7 +641,7 @@ fn posixCallMainAndExit(argc_argv_ptr: [*]usize) callconv(.c) noreturn {
638641 if (native_os == .linux) {
639642 // This must be done after PIE relocations have been applied or we may crash
640643 // while trying to access the global variable (happens on MIPS at least).
641 std.os.linux.elf_aux_maybe = auxv;
644 __zig_elf_auxv = auxv;
642645
643646 if (!builtin.single_threaded) {
644647 // ARMv6 targets (and earlier) have no support for TLS in hardware.