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 {...@@ -164,6 +164,8 @@ comptime {
164 symbol(&strlen, "strlen");164 symbol(&strlen, "strlen");
165 }165 }
166166
167 _ = @import("compiler_rt/elf.zig");
168
167 // Temporarily used for uefi until https://github.com/ziglang/zig/issues/21630 is addressed.169 // Temporarily used for uefi until https://github.com/ziglang/zig/issues/21630 is addressed.
168 if (!builtin.link_libc and (builtin.os.tag == .windows or builtin.os.tag == .uefi) and (builtin.abi == .none or builtin.abi == .msvc)) {170 if (!builtin.link_libc and (builtin.os.tag == .windows or builtin.os.tag == .uefi) and (builtin.abi == .none or builtin.abi == .msvc)) {
169 symbol(&_fltused, "_fltused");171 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) {...@@ -960,43 +960,22 @@ pub const RENAME = packed struct(u32) {
960 _: u29 = 0,960 _: u29 = 0,
961};961};
962962
963/// Set by startup code, used by `getauxval`.963/// Available only when libc is *not* linked; use `std.c.getauxval` if libc is linked.
964pub var elf_aux_maybe: ?[*]std.elf.Auxv = null;964pub const getauxval = if (builtin.link_libc) {} else struct {
965965 /// Provided by compiler-rt and set by `std.start`.
966/// Whether an external or internal getauxval implementation is used.966 extern var __zig_elf_auxv: [*]std.elf.Auxv;
967const extern_getauxval = switch (builtin.zig_backend) {967
968 // Calling extern functions is not yet supported with these backends968 pub fn getauxval(index: usize) usize {
969 .stage2_arm,969 @disableInstrumentation();
970 .stage2_loongarch,970
971 .stage2_powerpc,971 var i: usize = 0;
972 .stage2_riscv64,972 while (__zig_elf_auxv[i].a_type != std.elf.AT.NULL) : (i += 1) {
973 .stage2_sparc64,973 if (__zig_elf_auxv[i].a_type == index) return __zig_elf_auxv[i].a_un.a_val;
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 });
985 }974 }
975
976 return 0;
986 }977 }
987 extern fn getauxval(index: usize) usize;978}.getauxval;
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}
1000979
1001// Some architectures (and some syscalls) require 64bit parameters to be passed980// Some architectures (and some syscalls) require 64bit parameters to be passed
1002// in a even-aligned register pair.981// in a even-aligned register pair.
lib/std/os/linux/vdso.zig+3-1
...@@ -1,3 +1,4 @@...@@ -1,3 +1,4 @@
1const builtin = @import("builtin");
1const std = @import("../../std.zig");2const std = @import("../../std.zig");
2const elf = std.elf;3const elf = std.elf;
3const linux = std.os.linux;4const linux = std.os.linux;
...@@ -5,7 +6,8 @@ const mem = std.mem;...@@ -5,7 +6,8 @@ const mem = std.mem;
5const maxInt = std.math.maxInt;6const maxInt = std.math.maxInt;
67
7pub fn lookup(vername: []const u8, name: []const u8) usize {8pub 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);
9 if (vdso_addr == 0) return 0;11 if (vdso_addr == 0) return 0;
1012
11 const eh = @as(*elf.Ehdr, @ptrFromInt(vdso_addr));13 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...@@ -596,6 +596,9 @@ fn wWinMain(hInstance: *anyopaque, hPrevInstance: ?*anyopaque, pCmdLine: [*:0]u1
596 return root.wWinMain(@ptrCast(hInstance), @ptrCast(hPrevInstance), pCmdLine, @intCast(nCmdShow));596 return root.wWinMain(@ptrCast(hInstance), @ptrCast(hPrevInstance), pCmdLine, @intCast(nCmdShow));
597}597}
598598
599/// Provided by compiler-rt.
600extern var __zig_elf_auxv: [*]std.elf.Auxv;
601
599fn posixCallMainAndExit(argc_argv_ptr: [*]usize) callconv(.c) noreturn {602fn posixCallMainAndExit(argc_argv_ptr: [*]usize) callconv(.c) noreturn {
600 // We're not ready to panic until thread local storage is initialized.603 // We're not ready to panic until thread local storage is initialized.
601 @setRuntimeSafety(false);604 @setRuntimeSafety(false);
...@@ -638,7 +641,7 @@ fn posixCallMainAndExit(argc_argv_ptr: [*]usize) callconv(.c) noreturn {...@@ -638,7 +641,7 @@ fn posixCallMainAndExit(argc_argv_ptr: [*]usize) callconv(.c) noreturn {
638 if (native_os == .linux) {641 if (native_os == .linux) {
639 // This must be done after PIE relocations have been applied or we may crash642 // This must be done after PIE relocations have been applied or we may crash
640 // while trying to access the global variable (happens on MIPS at least).643 // 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
643 if (!builtin.single_threaded) {646 if (!builtin.single_threaded) {
644 // ARMv6 targets (and earlier) have no support for TLS in hardware.647 // ARMv6 targets (and earlier) have no support for TLS in hardware.