From 3a22ad70354903ccde2e7274c97e5393ef32d274 Mon Sep 17 00:00:00 2001 From: Aikawa Yataro Date: Sat, 6 Jul 2024 02:05:55 +0000 Subject: [PATCH 001/266] zig ld: handle -v linker arg The "-v" argument is the same as "--version", but the linker should not exit after the version is printed. --- src/main.zig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main.zig b/src/main.zig index 3ba1276abf..b3241f8107 100644 --- a/src/main.zig +++ b/src/main.zig @@ -2475,6 +2475,8 @@ fn buildOutputType( fatal("unable to parse /version '{s}': {s}", .{ arg, @errorName(err) }); }; have_version = true; + } else if (mem.eql(u8, arg, "-v")) { + try std.io.getStdOut().writeAll("zig ld " ++ build_options.version ++ "\n"); } else if (mem.eql(u8, arg, "--version")) { try std.io.getStdOut().writeAll("zig ld " ++ build_options.version ++ "\n"); process.exit(0); From 73021c1e0c72534b28c35714e9b1ff32824f5cb8 Mon Sep 17 00:00:00 2001 From: Aikawa Yataro Date: Tue, 23 Jul 2024 16:00:18 +0000 Subject: [PATCH 002/266] zig ld: handle -V linker arg The "-V' argument lists the supported emulations. While linker emulation is not implemented, it's beneficial to warn user regarding this. --- src/main.zig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main.zig b/src/main.zig index b3241f8107..b3fc0ad6de 100644 --- a/src/main.zig +++ b/src/main.zig @@ -2475,6 +2475,8 @@ fn buildOutputType( fatal("unable to parse /version '{s}': {s}", .{ arg, @errorName(err) }); }; have_version = true; + } else if (mem.eql(u8, arg, "-V")) { + warn("ignoring request for supported emulations: unimplemented", .{}); } else if (mem.eql(u8, arg, "-v")) { try std.io.getStdOut().writeAll("zig ld " ++ build_options.version ++ "\n"); } else if (mem.eql(u8, arg, "--version")) { From 7c98a65ae4fcebf9ebc918fe3e7af9d062805d36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Wed, 24 Jul 2024 22:18:29 +0200 Subject: [PATCH 003/266] start: Add POSIX hexagon support. --- lib/std/start.zig | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/std/start.zig b/lib/std/start.zig index 326857d9c0..d97381ef0d 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -307,6 +307,15 @@ fn _start() callconv(.Naked) noreturn { \\ and sp, #-16 \\ b %[posixCallMainAndExit] , + .hexagon => + // r29 = SP, r30 = FP + \\ r30 = #0 + \\ r0 = r29 + \\ r29 = and(r29, #-16) + \\ memw(r29 + #-8) = r29 + \\ r29 = add(r29, #-8) + \\ call %[posixCallMainAndExit] + , .loongarch32, .loongarch64 => \\ move $fp, $zero \\ move $a0, $sp From 389ce984b5fc601b1b4e28e4ba47d051750afd83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Wed, 24 Jul 2024 22:37:26 +0200 Subject: [PATCH 004/266] start: Add POSIX arc support. --- lib/std/start.zig | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/std/start.zig b/lib/std/start.zig index d97381ef0d..74e177ce0b 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -300,6 +300,15 @@ fn _start() callconv(.Naked) noreturn { \\ and sp, x0, #-16 \\ b %[posixCallMainAndExit] , + .arc => + // The `arc` tag currently means ARCv2, which has an unusually low stack alignment + // requirement. ARCv3 increases it from 4 to 16, but we don't support ARCv3 yet. + \\ mov fp, 0 + \\ mov blink, 0 + \\ mov r0, sp + \\ and sp, sp, -4 + \\ b %[posixCallMainAndExit] + , .arm, .armeb, .thumb, .thumbeb => \\ mov fp, #0 \\ mov lr, #0 From 728024f9f3fc4f454c91cf29c169acb660a13c21 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 28 Jul 2024 23:53:06 -0700 Subject: [PATCH 005/266] start code: implement __init_array_start, __init_array_end --- lib/std/start.zig | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/lib/std/start.zig b/lib/std/start.zig index aeefbaffc0..7449d2d5bc 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -435,6 +435,22 @@ fn posixCallMainAndExit(argc_argv_ptr: [*]usize) callconv(.C) noreturn { // Here we look for the stack size in our program headers and use setrlimit // to ask for more stack space. expandStackSize(phdrs); + + { + const opt_init_array_start = @extern([*]*const fn () callconv(.C) void, .{ + .name = "__init_array_start", + .linkage = .weak, + }); + const opt_init_array_end = @extern([*]*const fn () callconv(.C) void, .{ + .name = "__init_array_end", + .linkage = .weak, + }); + if (opt_init_array_start) |init_array_start| { + const init_array_end = opt_init_array_end.?; + const slice = init_array_start[0 .. init_array_end - init_array_start]; + for (slice) |func| func(); + } + } } std.posix.exit(callMainWithArgs(argc, argv, envp)); From 3f2d1b17fc304c8a71e6fcce4a17500b19c3fbac Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 29 Jul 2024 11:38:11 -0700 Subject: [PATCH 006/266] disable the new code for self-hosted riscv backend --- lib/std/start.zig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/std/start.zig b/lib/std/start.zig index 7449d2d5bc..ba02652db7 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -436,7 +436,8 @@ fn posixCallMainAndExit(argc_argv_ptr: [*]usize) callconv(.C) noreturn { // to ask for more stack space. expandStackSize(phdrs); - { + // Disabled with the riscv backend because it cannot handle this code yet. + if (builtin.zig_backend != .stage2_riscv64) { const opt_init_array_start = @extern([*]*const fn () callconv(.C) void, .{ .name = "__init_array_start", .linkage = .weak, From adfbd8a98b2525dbd5fa9891968794f7ace31cd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Thu, 25 Jul 2024 01:04:20 +0200 Subject: [PATCH 007/266] std.os.linux.start_pie: Add mips and mips64 support. --- lib/std/os/linux/start_pie.zig | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/lib/std/os/linux/start_pie.zig b/lib/std/os/linux/start_pie.zig index 5c35262bc8..4f0d88196e 100644 --- a/lib/std/os/linux/start_pie.zig +++ b/lib/std/os/linux/start_pie.zig @@ -12,6 +12,7 @@ const R_CSKY_RELATIVE = 9; const R_HEXAGON_RELATIVE = 35; const R_LARCH_RELATIVE = 3; const R_68K_RELATIVE = 22; +const R_MIPS_RELATIVE = 128; const R_RISCV_RELATIVE = 3; const R_390_RELATIVE = 12; const R_SPARC_RELATIVE = 22; @@ -26,6 +27,7 @@ const R_RELATIVE = switch (builtin.cpu.arch) { .hexagon => R_HEXAGON_RELATIVE, .loongarch32, .loongarch64 => R_LARCH_RELATIVE, .m68k => R_68K_RELATIVE, + .mips, .mipsel, .mips64, .mips64el => R_MIPS_RELATIVE, .riscv32, .riscv64 => R_RISCV_RELATIVE, .s390x => R_390_RELATIVE, else => @compileError("Missing R_RELATIVE definition for this target"), @@ -111,6 +113,31 @@ fn getDynamicSymbol() [*]elf.Dyn { \\ lea (%[ret], %%pc), %[ret] : [ret] "=r" (-> [*]elf.Dyn), ), + .mips, .mipsel => asm volatile ( + \\ .weak _DYNAMIC + \\ .hidden _DYNAMIC + \\ bal 1f + \\ .gpword _DYNAMIC + \\ 1: + \\ lw %[ret], 0($ra) + \\ addu %[ret], %[ret], $gp + : [ret] "=r" (-> [*]elf.Dyn), + : + : "lr" + ), + .mips64, .mips64el => asm volatile ( + \\ .weak _DYNAMIC + \\ .hidden _DYNAMIC + \\ .balign 8 + \\ bal 1f + \\ .gpdword _DYNAMIC + \\ 1: + \\ ld %[ret], 0($ra) + \\ daddu %[ret], %[ret], $gp + : [ret] "=r" (-> [*]elf.Dyn), + : + : "lr" + ), .riscv32, .riscv64 => asm volatile ( \\ .weak _DYNAMIC \\ .hidden _DYNAMIC From 5633767b20b1e2c9a650bdf6045c260ec4ce42ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Thu, 25 Jul 2024 01:31:42 +0200 Subject: [PATCH 008/266] std.os.linux.start_pie: Add powerpc and powerpc64 support. Closes #20305. --- lib/std/os/linux/start_pie.zig | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/lib/std/os/linux/start_pie.zig b/lib/std/os/linux/start_pie.zig index 4f0d88196e..174d5640ad 100644 --- a/lib/std/os/linux/start_pie.zig +++ b/lib/std/os/linux/start_pie.zig @@ -13,6 +13,7 @@ const R_HEXAGON_RELATIVE = 35; const R_LARCH_RELATIVE = 3; const R_68K_RELATIVE = 22; const R_MIPS_RELATIVE = 128; +const R_PPC_RELATIVE = 22; const R_RISCV_RELATIVE = 3; const R_390_RELATIVE = 12; const R_SPARC_RELATIVE = 22; @@ -28,6 +29,7 @@ const R_RELATIVE = switch (builtin.cpu.arch) { .loongarch32, .loongarch64 => R_LARCH_RELATIVE, .m68k => R_68K_RELATIVE, .mips, .mipsel, .mips64, .mips64el => R_MIPS_RELATIVE, + .powerpc, .powerpcle, .powerpc64, .powerpc64le => R_PPC_RELATIVE, .riscv32, .riscv64 => R_RISCV_RELATIVE, .s390x => R_390_RELATIVE, else => @compileError("Missing R_RELATIVE definition for this target"), @@ -138,6 +140,32 @@ fn getDynamicSymbol() [*]elf.Dyn { : : "lr" ), + .powerpc, .powerpcle => asm volatile ( + \\ .weak _DYNAMIC + \\ .hidden _DYNAMIC + \\ bl 1f + \\ .long _DYNAMIC - . + \\ 1: + \\ mflr %[ret] + \\ lwz 4, 0(%[ret]) + \\ add %[ret], 4, %[ret] + : [ret] "=r" (-> [*]elf.Dyn), + : + : "lr", "r4" + ), + .powerpc64, .powerpc64le => asm volatile ( + \\ .weak _DYNAMIC + \\ .hidden _DYNAMIC + \\ bl 1f + \\ .quad _DYNAMIC - . + \\ 1: + \\ mflr %[ret] + \\ ld 4, 0(%[ret]) + \\ add %[ret], 4, %[ret] + : [ret] "=r" (-> [*]elf.Dyn), + : + : "lr", "r4" + ), .riscv32, .riscv64 => asm volatile ( \\ .weak _DYNAMIC \\ .hidden _DYNAMIC From 68cebde186eb8507509c9418c1ab3b843c6f24ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Thu, 25 Jul 2024 19:02:26 +0200 Subject: [PATCH 009/266] std.os.linux.start_pie: Inline the getDynamicSymbol() function. On MIPS, this call will require a relocation, which we can't do until after PIE relocations have been applied. --- lib/std/os/linux/start_pie.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/os/linux/start_pie.zig b/lib/std/os/linux/start_pie.zig index 174d5640ad..ab44abd898 100644 --- a/lib/std/os/linux/start_pie.zig +++ b/lib/std/os/linux/start_pie.zig @@ -38,7 +38,7 @@ const R_RELATIVE = switch (builtin.cpu.arch) { // Obtain a pointer to the _DYNAMIC array. // We have to compute its address as a PC-relative quantity not to require a // relocation that, at this point, is not yet applied. -fn getDynamicSymbol() [*]elf.Dyn { +inline fn getDynamicSymbol() [*]elf.Dyn { return switch (builtin.cpu.arch) { .x86 => asm volatile ( \\ .weak _DYNAMIC From 2386bfe854826e0726b7002c04cd9fc4c08d68f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Thu, 25 Jul 2024 23:55:37 +0200 Subject: [PATCH 010/266] std.os.linux.start_pie: Rewrite relocate() to avoid jump tables and libcalls. The code would cause LLVM to emit a jump table for the switch in the loop over the dynamic tags. That jump table was far enough away that the compiler decided to go through the GOT, which would of course break at this early stage as we haven't applied MIPS's local GOT relocations yet, nor can we until we've walked through the _DYNAMIC array. The first attempt at rewriting this used code like this: var sorted_dynv = [_]elf.Addr{0} ** elf.DT_NUM; But this is also problematic as it results in a memcpy() call. Instead, we explicitly initialize it to undefined and use a loop of volatile stores to clear it. --- lib/std/os/linux/start_pie.zig | 48 +++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/lib/std/os/linux/start_pie.zig b/lib/std/os/linux/start_pie.zig index ab44abd898..b5cc06f429 100644 --- a/lib/std/os/linux/start_pie.zig +++ b/lib/std/os/linux/start_pie.zig @@ -193,6 +193,7 @@ pub fn relocate(phdrs: []elf.Phdr) void { @disableInstrumentation(); const dynv = getDynamicSymbol(); + // Recover the delta applied by the loader by comparing the effective and // the theoretical load addresses for the `_DYNAMIC` symbol. const base_addr = base: { @@ -204,34 +205,45 @@ pub fn relocate(phdrs: []elf.Phdr) void { @trap(); }; - var rel_addr: usize = 0; - var rela_addr: usize = 0; - var rel_size: usize = 0; - var rela_size: usize = 0; + var sorted_dynv: [elf.DT_NUM]elf.Addr = undefined; + + // Zero-initialized this way to prevent the compiler from turning this into + // `memcpy` or `memset` calls (which can require relocations). + for (&sorted_dynv) |*dyn| { + const pdyn: *volatile elf.Addr = @ptrCast(dyn); + pdyn.* = 0; + } + { + // `dynv` has no defined order. Fix that. var i: usize = 0; while (dynv[i].d_tag != elf.DT_NULL) : (i += 1) { - switch (dynv[i].d_tag) { - elf.DT_REL => rel_addr = base_addr + dynv[i].d_val, - elf.DT_RELA => rela_addr = base_addr + dynv[i].d_val, - elf.DT_RELSZ => rel_size = dynv[i].d_val, - elf.DT_RELASZ => rela_size = dynv[i].d_val, - else => {}, - } + if (dynv[i].d_tag < elf.DT_NUM) sorted_dynv[@bitCast(dynv[i].d_tag)] = dynv[i].d_val; } } - // Apply the relocations. - if (rel_addr != 0) { - const rel = std.mem.bytesAsSlice(elf.Rel, @as([*]u8, @ptrFromInt(rel_addr))[0..rel_size]); - for (rel) |r| { + + // Apply normal relocations. + + const rel = sorted_dynv[elf.DT_REL]; + if (rel != 0) { + const rels = @call(.always_inline, std.mem.bytesAsSlice, .{ + elf.Rel, + @as([*]u8, @ptrFromInt(base_addr + rel))[0..sorted_dynv[elf.DT_RELSZ]], + }); + for (rels) |r| { if (r.r_type() != R_RELATIVE) continue; @as(*usize, @ptrFromInt(base_addr + r.r_offset)).* += base_addr; } } - if (rela_addr != 0) { - const rela = std.mem.bytesAsSlice(elf.Rela, @as([*]u8, @ptrFromInt(rela_addr))[0..rela_size]); - for (rela) |r| { + + const rela = sorted_dynv[elf.DT_RELA]; + if (rela != 0) { + const relas = @call(.always_inline, std.mem.bytesAsSlice, .{ + elf.Rela, + @as([*]u8, @ptrFromInt(base_addr + rela))[0..sorted_dynv[elf.DT_RELASZ]], + }); + for (relas) |r| { if (r.r_type() != R_RELATIVE) continue; @as(*usize, @ptrFromInt(base_addr + r.r_offset)).* = base_addr + @as(usize, @bitCast(r.r_addend)); } From b2d568e813d61ac05ea99020296d24fd8ddd5824 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Wed, 24 Jul 2024 08:21:51 +0200 Subject: [PATCH 011/266] std.elf.EM: Add missing MICROBLAZE value. --- lib/std/elf.zig | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/std/elf.zig b/lib/std/elf.zig index b53ad91382..af3f1813e2 100644 --- a/lib/std/elf.zig +++ b/lib/std/elf.zig @@ -1531,6 +1531,9 @@ pub const EM = enum(u16) { /// Tilera TILEPro multicore architecture family TILEPRO = 188, + /// Xilinx MicroBlaze + MICROBLAZE = 189, + /// NVIDIA CUDA architecture CUDA = 190, From ce5063e188845abff15f6a2f665fd716fbfa25d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Wed, 24 Jul 2024 06:36:45 +0200 Subject: [PATCH 012/266] std.os.linux.AUDIT: Rewrite ARCH in terms of std.elf.EM. Closes #20743. --- lib/std/os/linux.zig | 133 +++++++++++++++++++++++++++---------------- 1 file changed, 84 insertions(+), 49 deletions(-) diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index 4f3db110b2..5f27fb8e21 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -13,6 +13,7 @@ const elf = std.elf; const vdso = @import("linux/vdso.zig"); const dl = @import("../dynamic_library.zig"); const native_arch = builtin.cpu.arch; +const native_abi = builtin.abi; const native_endian = native_arch.endian(); const is_mips = native_arch.isMIPS(); const is_ppc = native_arch.isPPC(); @@ -7353,67 +7354,101 @@ pub const PERF = struct { // TODO: Add the rest of the AUDIT defines? pub const AUDIT = struct { pub const ARCH = enum(u32) { + const CONVENTION_MIPS64_N32 = 0x20000000; const @"64BIT" = 0x80000000; const LE = 0x40000000; + AARCH64 = toAudit(.AARCH64, @"64BIT" | LE), + ALPHA = toAudit(.ALPHA, @"64BIT" | LE), + ARCOMPACT = toAudit(.ARC_COMPACT, LE), + ARCOMPACTBE = toAudit(.ARC_COMPACT, 0), + ARCV2 = toAudit(.ARC_COMPACT2, LE), + ARCV2BE = toAudit(.ARC_COMPACT2, 0), + ARM = toAudit(.ARM, LE), + ARMEB = toAudit(.ARM, 0), + C6X = toAudit(.TI_C6000, LE), + C6XBE = toAudit(.TI_C6000, 0), + CRIS = toAudit(.CRIS, LE), + CSKY = toAudit(.CSKY, LE), + FRV = toAudit(.FRV, 0), + H8300 = toAudit(.H8_300, 0), + HEXAGON = toAudit(.HEXAGON, 0), + I386 = toAudit(.@"386", LE), + IA64 = toAudit(.IA_64, @"64BIT" | LE), + M32R = toAudit(.M32R, 0), + M68K = toAudit(.@"68K", 0), + MICROBLAZE = toAudit(.MICROBLAZE, 0), + MIPS = toAudit(.MIPS, 0), + MIPSEL = toAudit(.MIPS, LE), + MIPS64 = toAudit(.MIPS, @"64BIT"), + MIPS64N32 = toAudit(.MIPS, @"64BIT" | CONVENTION_MIPS64_N32), + MIPSEL64 = toAudit(.MIPS, @"64BIT" | LE), + MIPSEL64N32 = toAudit(.MIPS, @"64BIT" | LE | CONVENTION_MIPS64_N32), + NDS32 = toAudit(.NDS32, LE), + NDS32BE = toAudit(.NDS32, 0), + NIOS2 = toAudit(.ALTERA_NIOS2, LE), + OPENRISC = toAudit(.OPENRISC, 0), + PARISC = toAudit(.PARISC, 0), + PARISC64 = toAudit(.PARISC, @"64BIT"), + PPC = toAudit(.PPC, 0), + PPC64 = toAudit(.PPC64, @"64BIT"), + PPC64LE = toAudit(.PPC64, @"64BIT" | LE), + RISCV32 = toAudit(.RISCV, LE), + RISCV64 = toAudit(.RISCV, @"64BIT" | LE), + S390 = toAudit(.S390, 0), + S390X = toAudit(.S390, @"64BIT"), + SH = toAudit(.SH, 0), + SHEL = toAudit(.SH, LE), + SH64 = toAudit(.SH, @"64BIT"), + SHEL64 = toAudit(.SH, @"64BIT" | LE), + SPARC = toAudit(.SPARC, 0), + SPARC64 = toAudit(.SPARCV9, @"64BIT"), + TILEGX = toAudit(.TILEGX, @"64BIT" | LE), + TILEGX32 = toAudit(.TILEGX, LE), + TILEPRO = toAudit(.TILEPRO, LE), + UNICORE = toAudit(.UNICORE, LE), + X86_64 = toAudit(.X86_64, @"64BIT" | LE), + XTENSA = toAudit(.XTENSA, 0), + LOONGARCH32 = toAudit(.LOONGARCH, LE), + LOONGARCH64 = toAudit(.LOONGARCH, @"64BIT" | LE), + + fn toAudit(em: elf.EM, flags: u32) u32 { + return @intFromEnum(em) | flags; + } + pub const current: AUDIT.ARCH = switch (native_arch) { - .x86 => .X86, - .x86_64 => .X86_64, - .aarch64 => .AARCH64, .arm, .thumb => .ARM, - .riscv32 => .RISCV32, - .riscv64 => .RISCV64, - .sparc64 => .SPARC64, + .armeb, .thumbeb => .ARMEB, + .aarch64 => .AARCH64, + .arc => .ARCV2, + .csky => .CSKY, + .hexagon => .HEXAGON, + .loongarch32 => .LOONGARCH32, + .loongarch64 => .LOONGARCH64, + .m68k => .M68K, .mips => .MIPS, .mipsel => .MIPSEL, + .mips64 => switch (native_abi) { + .gnuabin32 => .MIPS64N32, + else => .MIPS64, + }, + .mips64el => switch (native_abi) { + .gnuabin32 => .MIPSEL64N32, + else => .MIPSEL64, + }, .powerpc => .PPC, .powerpc64 => .PPC64, .powerpc64le => .PPC64LE, + .riscv32 => .RISCV32, + .riscv64 => .RISCV64, + .sparc => .SPARC, + .sparc64 => .SPARC64, + .s390x => .S390X, + .x86 => .I386, + .x86_64 => .X86_64, + .xtensa => .XTENSA, else => @compileError("unsupported architecture"), }; - - AARCH64 = toAudit(.aarch64), - ARM = toAudit(.arm), - ARMEB = toAudit(.armeb), - CSKY = toAudit(.csky), - HEXAGON = @intFromEnum(std.elf.EM.HEXAGON), - LOONGARCH64 = toAudit(.loongarch64), - M68K = toAudit(.m68k), - MIPS = toAudit(.mips), - MIPSEL = toAudit(.mips) | LE, - MIPS64 = toAudit(.mips64), - MIPSEL64 = toAudit(.mips64) | LE, - PPC = toAudit(.powerpc), - PPC64 = toAudit(.powerpc64), - PPC64LE = toAudit(.powerpc64le), - RISCV32 = toAudit(.riscv32), - RISCV64 = toAudit(.riscv64), - S390X = toAudit(.s390x), - SPARC = toAudit(.sparc), - SPARC64 = toAudit(.sparc64), - X86 = toAudit(.x86), - X86_64 = toAudit(.x86_64), - XTENSA = toAudit(.xtensa), - - fn toAudit(arch: std.Target.Cpu.Arch) u32 { - var res: u32 = @intFromEnum(arch.toElfMachine()); - if (arch.endian() == .little) res |= LE; - switch (arch) { - .aarch64, - .loongarch64, - .mips64, - .mips64el, - .powerpc64, - .powerpc64le, - .riscv64, - .s390x, - .sparc64, - .x86_64, - => res |= @"64BIT", - else => {}, - } - return res; - } }; }; From c9664cb657e8bd30fefdd2eb4249e89d75edc91a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Fri, 26 Jul 2024 02:51:03 +0200 Subject: [PATCH 013/266] generate_linux_syscalls: Bring loongarch64 generation code in line with other newer ports. --- tools/generate_linux_syscalls.zig | 37 ++++++++++++------------------- 1 file changed, 14 insertions(+), 23 deletions(-) diff --git a/tools/generate_linux_syscalls.zig b/tools/generate_linux_syscalls.zig index 9da0737c1e..3860935017 100644 --- a/tools/generate_linux_syscalls.zig +++ b/tools/generate_linux_syscalls.zig @@ -427,16 +427,11 @@ pub fn main() !void { try writer.writeAll("};\n\n"); } { - try writer.writeAll( - \\ - \\pub const LoongArch64 = enum(usize) { - \\ - ); + try writer.writeAll("pub const LoongArch64 = enum(usize) {\n"); const child_args = [_][]const u8{ zig_exe, "cc", - "-march=loongarch64", "-target", "loongarch64-linux-gnu", "-E", @@ -445,6 +440,8 @@ pub fn main() !void { "-nostdinc", "-Iinclude", "-Iinclude/uapi", + "-Iarch/loongarch/include/uapi", + "-D __SYSCALL(nr, nm)=zigsyscall nm nr", "arch/loongarch/include/uapi/asm/unistd.h", }; @@ -468,27 +465,21 @@ pub fn main() !void { }; var lines = mem.tokenizeScalar(u8, defines, '\n'); - loop: while (lines.next()) |line| { - var fields = mem.tokenizeAny(u8, line, " \t"); - const cmd = fields.next() orelse return error.Incomplete; - if (!mem.eql(u8, cmd, "#define")) continue; - const define = fields.next() orelse return error.Incomplete; - const number = fields.next() orelse continue; + while (lines.next()) |line| { + var fields = mem.tokenizeAny(u8, line, " "); + const prefix = fields.next() orelse return error.Incomplete; - if (!std.ascii.isDigit(number[0])) continue; - if (!mem.startsWith(u8, define, "__NR")) continue; - const name = mem.trimLeft(u8, mem.trimLeft(u8, define, "__NR3264_"), "__NR_"); - if (mem.eql(u8, name, "arch_specific_syscall")) continue; - if (mem.eql(u8, name, "syscalls")) break :loop; + if (!mem.eql(u8, prefix, "zigsyscall")) continue; - const fixed_name = if (stdlib_renames.get(name)) |fixed| fixed else name; - try writer.print(" {p} = {s},\n", .{ zig.fmtId(fixed_name), number }); + const sys_name = fields.next() orelse return error.Incomplete; + const value = fields.rest(); + const name = (getOverridenNameNew(value) orelse sys_name)["sys_".len..]; + const fixed_name = if (stdlib_renames_new.get(name)) |f| f else if (stdlib_renames.get(name)) |f| f else name; + + try writer.print(" {p} = {s},\n", .{ zig.fmtId(fixed_name), value }); } - try writer.writeAll( - \\}; - \\ - ); + try writer.writeAll("};\n\n"); } try buf_out.flush(); From 2747fca2267d909dad582bc129e4a5b6f686639b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Fri, 26 Jul 2024 04:50:20 +0200 Subject: [PATCH 014/266] generate_linux_syscalls: Simplify include paths. Using the tooling includes means we won't run into the asm/bitsperlong.h issue. --- tools/generate_linux_syscalls.zig | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/tools/generate_linux_syscalls.zig b/tools/generate_linux_syscalls.zig index 3860935017..29ae775a8e 100644 --- a/tools/generate_linux_syscalls.zig +++ b/tools/generate_linux_syscalls.zig @@ -273,8 +273,8 @@ pub fn main() !void { "-P", "-nostdinc", // Using -I=[dir] includes the zig linux headers, which we don't want. - "-Iinclude", - "-Iinclude/uapi", + "-Itools/include", + "-Itools/include/uapi", // Output the syscall in a format we can easily recognize. "-D __SYSCALL(nr, nm)=zigsyscall nm nr", "arch/arm64/include/uapi/asm/unistd.h", @@ -328,9 +328,8 @@ pub fn main() !void { "-dD", "-P", "-nostdinc", - "-Iinclude", - "-Iinclude/uapi", - "-Iarch/riscv/include/uapi", + "-Itools/include", + "-Itools/include/uapi", "-D __SYSCALL(nr, nm)=zigsyscall nm nr", "arch/riscv/include/uapi/asm/unistd.h", }; @@ -383,9 +382,8 @@ pub fn main() !void { "-dD", "-P", "-nostdinc", - "-Iinclude", - "-Iinclude/uapi", - "-Iarch/riscv/include/uapi", + "-Itools/include", + "-Itools/include/uapi", "-D __SYSCALL(nr, nm)=zigsyscall nm nr", "arch/riscv/include/uapi/asm/unistd.h", }; @@ -438,9 +436,8 @@ pub fn main() !void { "-dD", "-P", "-nostdinc", - "-Iinclude", - "-Iinclude/uapi", - "-Iarch/loongarch/include/uapi", + "-Itools/include", + "-Itools/include/uapi", "-D __SYSCALL(nr, nm)=zigsyscall nm nr", "arch/loongarch/include/uapi/asm/unistd.h", }; From 1f9dcff747d652dae545941524d82bb9313aa38f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Fri, 26 Jul 2024 05:32:58 +0200 Subject: [PATCH 015/266] generate_linux_syscalls: Don't expose the helper constants on mips/mips64. --- tools/generate_linux_syscalls.zig | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/generate_linux_syscalls.zig b/tools/generate_linux_syscalls.zig index 29ae775a8e..309f5ce9b0 100644 --- a/tools/generate_linux_syscalls.zig +++ b/tools/generate_linux_syscalls.zig @@ -170,7 +170,7 @@ pub fn main() !void { { try writer.writeAll( \\pub const Mips = enum(usize) { - \\ pub const Linux = 4000; + \\ const linux_base = 4000; \\ \\ ); @@ -188,7 +188,7 @@ pub fn main() !void { if (mem.startsWith(u8, name, "unused")) continue; const fixed_name = if (stdlib_renames.get(name)) |fixed| fixed else name; - try writer.print(" {p} = Linux + {s},\n", .{ zig.fmtId(fixed_name), number }); + try writer.print(" {p} = linux_base + {s},\n", .{ zig.fmtId(fixed_name), number }); } try writer.writeAll("};\n\n"); @@ -196,7 +196,7 @@ pub fn main() !void { { try writer.writeAll( \\pub const Mips64 = enum(usize) { - \\ pub const Linux = 5000; + \\ const linux_base = 5000; \\ \\ ); @@ -213,7 +213,7 @@ pub fn main() !void { const name = fields.next() orelse return error.Incomplete; const fixed_name = if (stdlib_renames.get(name)) |fixed| fixed else name; - try writer.print(" {p} = Linux + {s},\n", .{ zig.fmtId(fixed_name), number }); + try writer.print(" {p} = linux_base + {s},\n", .{ zig.fmtId(fixed_name), number }); } try writer.writeAll("};\n\n"); From 94a1fd6e8ec37a0a24c1cd720017c6ba8b809012 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Fri, 26 Jul 2024 05:36:32 +0200 Subject: [PATCH 016/266] generate_linux_syscalls: Name mips types according to ABI. --- tools/generate_linux_syscalls.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/generate_linux_syscalls.zig b/tools/generate_linux_syscalls.zig index 309f5ce9b0..7367c4f26e 100644 --- a/tools/generate_linux_syscalls.zig +++ b/tools/generate_linux_syscalls.zig @@ -169,7 +169,7 @@ pub fn main() !void { } { try writer.writeAll( - \\pub const Mips = enum(usize) { + \\pub const MipsO32 = enum(usize) { \\ const linux_base = 4000; \\ \\ @@ -195,7 +195,7 @@ pub fn main() !void { } { try writer.writeAll( - \\pub const Mips64 = enum(usize) { + \\pub const MipsN64 = enum(usize) { \\ const linux_base = 5000; \\ \\ From 2e910f23f9143610f49a70c1dad3b8d565119fd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Fri, 26 Jul 2024 04:53:04 +0200 Subject: [PATCH 017/266] generate_linux_syscalls: Add generation code for arc. --- tools/generate_linux_syscalls.zig | 54 +++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/tools/generate_linux_syscalls.zig b/tools/generate_linux_syscalls.zig index 7367c4f26e..3676232041 100644 --- a/tools/generate_linux_syscalls.zig +++ b/tools/generate_linux_syscalls.zig @@ -478,6 +478,60 @@ pub fn main() !void { try writer.writeAll("};\n\n"); } + { + try writer.writeAll("pub const Arc = enum(usize) {\n"); + + const child_args = [_][]const u8{ + zig_exe, + "cc", + "-target", + "arc-freestanding-none", + "-E", + "-dD", + "-P", + "-nostdinc", + "-Itools/include", + "-Itools/include/uapi", + "-D __SYSCALL(nr, nm)=zigsyscall nm nr", + "arch/arc/include/uapi/asm/unistd.h", + }; + + const child_result = try std.process.Child.run(.{ + .allocator = allocator, + .argv = &child_args, + .cwd = linux_path, + .cwd_dir = linux_dir, + }); + if (child_result.stderr.len > 0) std.debug.print("{s}\n", .{child_result.stderr}); + + const defines = switch (child_result.term) { + .Exited => |code| if (code == 0) child_result.stdout else { + std.debug.print("zig cc exited with code {d}\n", .{code}); + std.process.exit(1); + }, + else => { + std.debug.print("zig cc crashed\n", .{}); + std.process.exit(1); + }, + }; + + var lines = mem.tokenizeScalar(u8, defines, '\n'); + while (lines.next()) |line| { + var fields = mem.tokenizeAny(u8, line, " "); + const prefix = fields.next() orelse return error.Incomplete; + + if (!mem.eql(u8, prefix, "zigsyscall")) continue; + + const sys_name = fields.next() orelse return error.Incomplete; + const value = fields.rest(); + const name = (getOverridenNameNew(value) orelse sys_name)["sys_".len..]; + const fixed_name = if (stdlib_renames_new.get(name)) |f| f else if (stdlib_renames.get(name)) |f| f else name; + + try writer.print(" {p} = {s},\n", .{ zig.fmtId(fixed_name), value }); + } + + try writer.writeAll("};\n\n"); + } try buf_out.flush(); } From dd78ee43e4aa377650d15be84821f72914fad571 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Fri, 26 Jul 2024 04:53:59 +0200 Subject: [PATCH 018/266] generate_linux_syscalls: Add generation code for csky. --- tools/generate_linux_syscalls.zig | 54 +++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/tools/generate_linux_syscalls.zig b/tools/generate_linux_syscalls.zig index 3676232041..301c62d37f 100644 --- a/tools/generate_linux_syscalls.zig +++ b/tools/generate_linux_syscalls.zig @@ -532,6 +532,60 @@ pub fn main() !void { try writer.writeAll("};\n\n"); } + { + try writer.writeAll("pub const CSky = enum(usize) {\n"); + + const child_args = [_][]const u8{ + zig_exe, + "cc", + "-target", + "csky-freestanding-none", + "-E", + "-dD", + "-P", + "-nostdinc", + "-Itools/include", + "-Itools/include/uapi", + "-D __SYSCALL(nr, nm)=zigsyscall nm nr", + "arch/csky/include/uapi/asm/unistd.h", + }; + + const child_result = try std.process.Child.run(.{ + .allocator = allocator, + .argv = &child_args, + .cwd = linux_path, + .cwd_dir = linux_dir, + }); + if (child_result.stderr.len > 0) std.debug.print("{s}\n", .{child_result.stderr}); + + const defines = switch (child_result.term) { + .Exited => |code| if (code == 0) child_result.stdout else { + std.debug.print("zig cc exited with code {d}\n", .{code}); + std.process.exit(1); + }, + else => { + std.debug.print("zig cc crashed\n", .{}); + std.process.exit(1); + }, + }; + + var lines = mem.tokenizeScalar(u8, defines, '\n'); + while (lines.next()) |line| { + var fields = mem.tokenizeAny(u8, line, " "); + const prefix = fields.next() orelse return error.Incomplete; + + if (!mem.eql(u8, prefix, "zigsyscall")) continue; + + const sys_name = fields.next() orelse return error.Incomplete; + const value = fields.rest(); + const name = (getOverridenNameNew(value) orelse sys_name)["sys_".len..]; + const fixed_name = if (stdlib_renames_new.get(name)) |f| f else if (stdlib_renames.get(name)) |f| f else name; + + try writer.print(" {p} = {s},\n", .{ zig.fmtId(fixed_name), value }); + } + + try writer.writeAll("};\n\n"); + } try buf_out.flush(); } From fb249cf3e130c292c639f2357ba3b7f66b2aa1b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Fri, 26 Jul 2024 05:22:44 +0200 Subject: [PATCH 019/266] generate_linux_syscalls: Add generation code for hexagon. --- tools/generate_linux_syscalls.zig | 54 +++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/tools/generate_linux_syscalls.zig b/tools/generate_linux_syscalls.zig index 301c62d37f..c34fede294 100644 --- a/tools/generate_linux_syscalls.zig +++ b/tools/generate_linux_syscalls.zig @@ -586,6 +586,60 @@ pub fn main() !void { try writer.writeAll("};\n\n"); } + { + try writer.writeAll("pub const Hexagon = enum(usize) {\n"); + + const child_args = [_][]const u8{ + zig_exe, + "cc", + "-target", + "hexagon-freestanding-none", + "-E", + "-dD", + "-P", + "-nostdinc", + "-Itools/include", + "-Itools/include/uapi", + "-D __SYSCALL(nr, nm)=zigsyscall nm nr", + "arch/hexagon/include/uapi/asm/unistd.h", + }; + + const child_result = try std.process.Child.run(.{ + .allocator = allocator, + .argv = &child_args, + .cwd = linux_path, + .cwd_dir = linux_dir, + }); + if (child_result.stderr.len > 0) std.debug.print("{s}\n", .{child_result.stderr}); + + const defines = switch (child_result.term) { + .Exited => |code| if (code == 0) child_result.stdout else { + std.debug.print("zig cc exited with code {d}\n", .{code}); + std.process.exit(1); + }, + else => { + std.debug.print("zig cc crashed\n", .{}); + std.process.exit(1); + }, + }; + + var lines = mem.tokenizeScalar(u8, defines, '\n'); + while (lines.next()) |line| { + var fields = mem.tokenizeAny(u8, line, " "); + const prefix = fields.next() orelse return error.Incomplete; + + if (!mem.eql(u8, prefix, "zigsyscall")) continue; + + const sys_name = fields.next() orelse return error.Incomplete; + const value = fields.rest(); + const name = (getOverridenNameNew(value) orelse sys_name)["sys_".len..]; + const fixed_name = if (stdlib_renames_new.get(name)) |f| f else if (stdlib_renames.get(name)) |f| f else name; + + try writer.print(" {p} = {s},\n", .{ zig.fmtId(fixed_name), value }); + } + + try writer.writeAll("};\n\n"); + } try buf_out.flush(); } From 2598aa574bf8a4c033a29b441b2faa791849e1a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Fri, 26 Jul 2024 05:29:15 +0200 Subject: [PATCH 020/266] generate_linux_syscalls: Add generation code for sparc32. --- tools/generate_linux_syscalls.zig | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tools/generate_linux_syscalls.zig b/tools/generate_linux_syscalls.zig index c34fede294..6aa61bfeb7 100644 --- a/tools/generate_linux_syscalls.zig +++ b/tools/generate_linux_syscalls.zig @@ -148,6 +148,25 @@ pub fn main() !void { \\ ); } + { + try writer.writeAll("pub const Sparc = enum(usize) {\n"); + const table = try linux_dir.readFile("arch/sparc/kernel/syscalls/syscall.tbl", buf); + var lines = mem.tokenizeScalar(u8, table, '\n'); + while (lines.next()) |line| { + if (line[0] == '#') continue; + + var fields = mem.tokenizeAny(u8, line, " \t"); + const number = fields.next() orelse return error.Incomplete; + const abi = fields.next() orelse return error.Incomplete; + if (mem.eql(u8, abi, "64")) continue; + const name = fields.next() orelse return error.Incomplete; + const fixed_name = if (stdlib_renames.get(name)) |fixed| fixed else name; + + try writer.print(" {p} = {s},\n", .{ zig.fmtId(fixed_name), number }); + } + + try writer.writeAll("};\n\n"); + } { try writer.writeAll("pub const Sparc64 = enum(usize) {\n"); const table = try linux_dir.readFile("arch/sparc/kernel/syscalls/syscall.tbl", buf); From 4028762a9a7fed702e87622ed89800d87bfe7077 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Fri, 26 Jul 2024 05:37:43 +0200 Subject: [PATCH 021/266] generate_linux_syscalls: Add generation code for mips n32. --- tools/generate_linux_syscalls.zig | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tools/generate_linux_syscalls.zig b/tools/generate_linux_syscalls.zig index 6aa61bfeb7..6bcb494e4c 100644 --- a/tools/generate_linux_syscalls.zig +++ b/tools/generate_linux_syscalls.zig @@ -237,6 +237,31 @@ pub fn main() !void { try writer.writeAll("};\n\n"); } + { + try writer.writeAll( + \\pub const MipsN32 = enum(usize) { + \\ const linux_base = 6000; + \\ + \\ + ); + + const table = try linux_dir.readFile("arch/mips/kernel/syscalls/syscall_n32.tbl", buf); + var lines = mem.tokenizeScalar(u8, table, '\n'); + while (lines.next()) |line| { + if (line[0] == '#') continue; + + var fields = mem.tokenizeAny(u8, line, " \t"); + const number = fields.next() orelse return error.Incomplete; + // abi is always n32 + _ = fields.next() orelse return error.Incomplete; + const name = fields.next() orelse return error.Incomplete; + const fixed_name = if (stdlib_renames.get(name)) |fixed| fixed else name; + + try writer.print(" {p} = linux_base + {s},\n", .{ zig.fmtId(fixed_name), number }); + } + + try writer.writeAll("};\n\n"); + } { try writer.writeAll("pub const PowerPC = enum(usize) {\n"); From 6e7d619dc33c3b9968d6fccdd51661e22f9db5e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Fri, 26 Jul 2024 05:43:52 +0200 Subject: [PATCH 022/266] generate_linux_syscalls: Add generation code for s390x. --- tools/generate_linux_syscalls.zig | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tools/generate_linux_syscalls.zig b/tools/generate_linux_syscalls.zig index 6bcb494e4c..fb0e0fb93d 100644 --- a/tools/generate_linux_syscalls.zig +++ b/tools/generate_linux_syscalls.zig @@ -298,6 +298,26 @@ pub fn main() !void { try writer.writeAll(list_64.items); try writer.writeAll("};\n\n"); } + { + try writer.writeAll("pub const S390x = enum(usize) {\n"); + + const table = try linux_dir.readFile("arch/s390/kernel/syscalls/syscall.tbl", buf); + var lines = mem.tokenizeScalar(u8, table, '\n'); + while (lines.next()) |line| { + if (line[0] == '#') continue; + + var fields = mem.tokenizeAny(u8, line, " \t"); + const number = fields.next() orelse return error.Incomplete; + const abi = fields.next() orelse return error.Incomplete; + if (mem.eql(u8, abi, "32")) continue; // 32-bit s390 support in linux is deprecated + const name = fields.next() orelse return error.Incomplete; + const fixed_name = if (stdlib_renames.get(name)) |fixed| fixed else name; + + try writer.print(" {p} = {s},\n", .{ zig.fmtId(fixed_name), number }); + } + + try writer.writeAll("};\n\n"); + } // Newer architectures (starting with aarch64 c. 2012) now use the same C // header file for their syscall numbers. Arch-specific headers are used to From 09914868ea552167130e06977aee689b8be989f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Fri, 26 Jul 2024 05:47:13 +0200 Subject: [PATCH 023/266] generate_linux_syscalls: Add generation code for m68k. --- tools/generate_linux_syscalls.zig | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tools/generate_linux_syscalls.zig b/tools/generate_linux_syscalls.zig index fb0e0fb93d..e730c47642 100644 --- a/tools/generate_linux_syscalls.zig +++ b/tools/generate_linux_syscalls.zig @@ -186,6 +186,26 @@ pub fn main() !void { try writer.writeAll("};\n\n"); } + { + try writer.writeAll("pub const M68k = enum(usize) {\n"); + + const table = try linux_dir.readFile("arch/m68k/kernel/syscalls/syscall.tbl", buf); + var lines = mem.tokenizeScalar(u8, table, '\n'); + while (lines.next()) |line| { + if (line[0] == '#') continue; + + var fields = mem.tokenizeAny(u8, line, " \t"); + const number = fields.next() orelse return error.Incomplete; + // abi is always common + _ = fields.next() orelse return error.Incomplete; + const name = fields.next() orelse return error.Incomplete; + const fixed_name = if (stdlib_renames.get(name)) |fixed| fixed else name; + + try writer.print(" {p} = {s},\n", .{ zig.fmtId(fixed_name), number }); + } + + try writer.writeAll("};\n\n"); + } { try writer.writeAll( \\pub const MipsO32 = enum(usize) { From 264b83096411f0fb948cd399ef99c4f4883a838d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Fri, 26 Jul 2024 05:49:43 +0200 Subject: [PATCH 024/266] generate_linux_syscalls: Add generation code for xtensa. --- tools/generate_linux_syscalls.zig | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tools/generate_linux_syscalls.zig b/tools/generate_linux_syscalls.zig index e730c47642..3a296f85bf 100644 --- a/tools/generate_linux_syscalls.zig +++ b/tools/generate_linux_syscalls.zig @@ -338,6 +338,26 @@ pub fn main() !void { try writer.writeAll("};\n\n"); } + { + try writer.writeAll("pub const Xtensa = enum(usize) {\n"); + + const table = try linux_dir.readFile("arch/xtensa/kernel/syscalls/syscall.tbl", buf); + var lines = mem.tokenizeScalar(u8, table, '\n'); + while (lines.next()) |line| { + if (line[0] == '#') continue; + + var fields = mem.tokenizeAny(u8, line, " \t"); + const number = fields.next() orelse return error.Incomplete; + // abi is always common + _ = fields.next() orelse return error.Incomplete; + const name = fields.next() orelse return error.Incomplete; + const fixed_name = if (stdlib_renames.get(name)) |fixed| fixed else name; + + try writer.print(" {p} = {s},\n", .{ zig.fmtId(fixed_name), number }); + } + + try writer.writeAll("};\n\n"); + } // Newer architectures (starting with aarch64 c. 2012) now use the same C // header file for their syscall numbers. Arch-specific headers are used to From f29967f46c2508668c568fdbb834a3c396485ab8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Fri, 26 Jul 2024 05:58:40 +0200 Subject: [PATCH 025/266] generate_linux_syscalls: Skip some reserved syscalls on mips and xtensa. --- tools/generate_linux_syscalls.zig | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tools/generate_linux_syscalls.zig b/tools/generate_linux_syscalls.zig index 3a296f85bf..b95fe9c524 100644 --- a/tools/generate_linux_syscalls.zig +++ b/tools/generate_linux_syscalls.zig @@ -42,6 +42,12 @@ fn getOverridenNameNew(value: []const u8) ?[]const u8 { } } +fn isReservedNameOld(name: []const u8) bool { + return std.mem.startsWith(u8, name, "available") or + std.mem.startsWith(u8, name, "reserved") or + std.mem.startsWith(u8, name, "unused"); +} + pub fn main() !void { var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer arena.deinit(); @@ -224,7 +230,7 @@ pub fn main() !void { // abi is always o32 _ = fields.next() orelse return error.Incomplete; const name = fields.next() orelse return error.Incomplete; - if (mem.startsWith(u8, name, "unused")) continue; + if (isReservedNameOld(name)) continue; const fixed_name = if (stdlib_renames.get(name)) |fixed| fixed else name; try writer.print(" {p} = linux_base + {s},\n", .{ zig.fmtId(fixed_name), number }); @@ -250,6 +256,7 @@ pub fn main() !void { // abi is always n64 _ = fields.next() orelse return error.Incomplete; const name = fields.next() orelse return error.Incomplete; + if (isReservedNameOld(name)) continue; const fixed_name = if (stdlib_renames.get(name)) |fixed| fixed else name; try writer.print(" {p} = linux_base + {s},\n", .{ zig.fmtId(fixed_name), number }); @@ -275,6 +282,7 @@ pub fn main() !void { // abi is always n32 _ = fields.next() orelse return error.Incomplete; const name = fields.next() orelse return error.Incomplete; + if (isReservedNameOld(name)) continue; const fixed_name = if (stdlib_renames.get(name)) |fixed| fixed else name; try writer.print(" {p} = linux_base + {s},\n", .{ zig.fmtId(fixed_name), number }); @@ -351,6 +359,7 @@ pub fn main() !void { // abi is always common _ = fields.next() orelse return error.Incomplete; const name = fields.next() orelse return error.Incomplete; + if (isReservedNameOld(name)) continue; const fixed_name = if (stdlib_renames.get(name)) |fixed| fixed else name; try writer.print(" {p} = {s},\n", .{ zig.fmtId(fixed_name), number }); From e77b3ff74f11a697ce24f4538fda6f9a227f8697 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Fri, 26 Jul 2024 06:00:22 +0200 Subject: [PATCH 026/266] std.os.linux.syscalls: Regenerate based on Linux v6.7. --- lib/std/os/linux/syscalls.zig | 4591 +++++++++++++++++++++++++++------ 1 file changed, 3800 insertions(+), 791 deletions(-) diff --git a/lib/std/os/linux/syscalls.zig b/lib/std/os/linux/syscalls.zig index 1ede5ac620..2388b7fdf7 100644 --- a/lib/std/os/linux/syscalls.zig +++ b/lib/std/os/linux/syscalls.zig @@ -1242,6 +1242,434 @@ pub const Arm = enum(usize) { get_tls = arm_base + 6, }; +pub const Sparc = enum(usize) { + restart_syscall = 0, + exit = 1, + fork = 2, + read = 3, + write = 4, + open = 5, + close = 6, + wait4 = 7, + creat = 8, + link = 9, + unlink = 10, + execv = 11, + chdir = 12, + chown = 13, + mknod = 14, + chmod = 15, + lchown = 16, + brk = 17, + perfctr = 18, + lseek = 19, + getpid = 20, + capget = 21, + capset = 22, + setuid = 23, + getuid = 24, + vmsplice = 25, + ptrace = 26, + alarm = 27, + sigaltstack = 28, + pause = 29, + utime = 30, + lchown32 = 31, + fchown32 = 32, + access = 33, + nice = 34, + chown32 = 35, + sync = 36, + kill = 37, + stat = 38, + sendfile = 39, + lstat = 40, + dup = 41, + pipe = 42, + times = 43, + getuid32 = 44, + umount2 = 45, + setgid = 46, + getgid = 47, + signal = 48, + geteuid = 49, + getegid = 50, + acct = 51, + getgid32 = 53, + ioctl = 54, + reboot = 55, + mmap2 = 56, + symlink = 57, + readlink = 58, + execve = 59, + umask = 60, + chroot = 61, + fstat = 62, + fstat64 = 63, + getpagesize = 64, + msync = 65, + vfork = 66, + pread64 = 67, + pwrite64 = 68, + geteuid32 = 69, + getegid32 = 70, + mmap = 71, + setreuid32 = 72, + munmap = 73, + mprotect = 74, + madvise = 75, + vhangup = 76, + truncate64 = 77, + mincore = 78, + getgroups = 79, + setgroups = 80, + getpgrp = 81, + setgroups32 = 82, + setitimer = 83, + ftruncate64 = 84, + swapon = 85, + getitimer = 86, + setuid32 = 87, + sethostname = 88, + setgid32 = 89, + dup2 = 90, + setfsuid32 = 91, + fcntl = 92, + select = 93, + setfsgid32 = 94, + fsync = 95, + setpriority = 96, + socket = 97, + connect = 98, + accept = 99, + getpriority = 100, + rt_sigreturn = 101, + rt_sigaction = 102, + rt_sigprocmask = 103, + rt_sigpending = 104, + rt_sigtimedwait = 105, + rt_sigqueueinfo = 106, + rt_sigsuspend = 107, + setresuid32 = 108, + getresuid32 = 109, + setresgid32 = 110, + getresgid32 = 111, + setregid32 = 112, + recvmsg = 113, + sendmsg = 114, + getgroups32 = 115, + gettimeofday = 116, + getrusage = 117, + getsockopt = 118, + getcwd = 119, + readv = 120, + writev = 121, + settimeofday = 122, + fchown = 123, + fchmod = 124, + recvfrom = 125, + setreuid = 126, + setregid = 127, + rename = 128, + truncate = 129, + ftruncate = 130, + flock = 131, + lstat64 = 132, + sendto = 133, + shutdown = 134, + socketpair = 135, + mkdir = 136, + rmdir = 137, + utimes = 138, + stat64 = 139, + sendfile64 = 140, + getpeername = 141, + futex = 142, + gettid = 143, + getrlimit = 144, + setrlimit = 145, + pivot_root = 146, + prctl = 147, + pciconfig_read = 148, + pciconfig_write = 149, + getsockname = 150, + inotify_init = 151, + inotify_add_watch = 152, + poll = 153, + getdents64 = 154, + fcntl64 = 155, + inotify_rm_watch = 156, + statfs = 157, + fstatfs = 158, + umount = 159, + sched_set_affinity = 160, + sched_get_affinity = 161, + getdomainname = 162, + setdomainname = 163, + quotactl = 165, + set_tid_address = 166, + mount = 167, + ustat = 168, + setxattr = 169, + lsetxattr = 170, + fsetxattr = 171, + getxattr = 172, + lgetxattr = 173, + getdents = 174, + setsid = 175, + fchdir = 176, + fgetxattr = 177, + listxattr = 178, + llistxattr = 179, + flistxattr = 180, + removexattr = 181, + lremovexattr = 182, + sigpending = 183, + query_module = 184, + setpgid = 185, + fremovexattr = 186, + tkill = 187, + exit_group = 188, + uname = 189, + init_module = 190, + personality = 191, + remap_file_pages = 192, + epoll_create = 193, + epoll_ctl = 194, + epoll_wait = 195, + ioprio_set = 196, + getppid = 197, + sigaction = 198, + sgetmask = 199, + ssetmask = 200, + sigsuspend = 201, + oldlstat = 202, + uselib = 203, + readdir = 204, + readahead = 205, + socketcall = 206, + syslog = 207, + lookup_dcookie = 208, + fadvise64 = 209, + fadvise64_64 = 210, + tgkill = 211, + waitpid = 212, + swapoff = 213, + sysinfo = 214, + ipc = 215, + sigreturn = 216, + clone = 217, + ioprio_get = 218, + adjtimex = 219, + sigprocmask = 220, + create_module = 221, + delete_module = 222, + get_kernel_syms = 223, + getpgid = 224, + bdflush = 225, + sysfs = 226, + afs_syscall = 227, + setfsuid = 228, + setfsgid = 229, + newselect = 230, + time = 231, + splice = 232, + stime = 233, + statfs64 = 234, + fstatfs64 = 235, + llseek = 236, + mlock = 237, + munlock = 238, + mlockall = 239, + munlockall = 240, + sched_setparam = 241, + sched_getparam = 242, + sched_setscheduler = 243, + sched_getscheduler = 244, + sched_yield = 245, + sched_get_priority_max = 246, + sched_get_priority_min = 247, + sched_rr_get_interval = 248, + nanosleep = 249, + mremap = 250, + sysctl = 251, + getsid = 252, + fdatasync = 253, + nfsservctl = 254, + sync_file_range = 255, + clock_settime = 256, + clock_gettime = 257, + clock_getres = 258, + clock_nanosleep = 259, + sched_getaffinity = 260, + sched_setaffinity = 261, + timer_settime = 262, + timer_gettime = 263, + timer_getoverrun = 264, + timer_delete = 265, + timer_create = 266, + vserver = 267, + io_setup = 268, + io_destroy = 269, + io_submit = 270, + io_cancel = 271, + io_getevents = 272, + mq_open = 273, + mq_unlink = 274, + mq_timedsend = 275, + mq_timedreceive = 276, + mq_notify = 277, + mq_getsetattr = 278, + waitid = 279, + tee = 280, + add_key = 281, + request_key = 282, + keyctl = 283, + openat = 284, + mkdirat = 285, + mknodat = 286, + fchownat = 287, + futimesat = 288, + fstatat64 = 289, + unlinkat = 290, + renameat = 291, + linkat = 292, + symlinkat = 293, + readlinkat = 294, + fchmodat = 295, + faccessat = 296, + pselect6 = 297, + ppoll = 298, + unshare = 299, + set_robust_list = 300, + get_robust_list = 301, + migrate_pages = 302, + mbind = 303, + get_mempolicy = 304, + set_mempolicy = 305, + kexec_load = 306, + move_pages = 307, + getcpu = 308, + epoll_pwait = 309, + utimensat = 310, + signalfd = 311, + timerfd_create = 312, + eventfd = 313, + fallocate = 314, + timerfd_settime = 315, + timerfd_gettime = 316, + signalfd4 = 317, + eventfd2 = 318, + epoll_create1 = 319, + dup3 = 320, + pipe2 = 321, + inotify_init1 = 322, + accept4 = 323, + preadv = 324, + pwritev = 325, + rt_tgsigqueueinfo = 326, + perf_event_open = 327, + recvmmsg = 328, + fanotify_init = 329, + fanotify_mark = 330, + prlimit64 = 331, + name_to_handle_at = 332, + open_by_handle_at = 333, + clock_adjtime = 334, + syncfs = 335, + sendmmsg = 336, + setns = 337, + process_vm_readv = 338, + process_vm_writev = 339, + kern_features = 340, + kcmp = 341, + finit_module = 342, + sched_setattr = 343, + sched_getattr = 344, + renameat2 = 345, + seccomp = 346, + getrandom = 347, + memfd_create = 348, + bpf = 349, + execveat = 350, + membarrier = 351, + userfaultfd = 352, + bind = 353, + listen = 354, + setsockopt = 355, + mlock2 = 356, + copy_file_range = 357, + preadv2 = 358, + pwritev2 = 359, + statx = 360, + io_pgetevents = 361, + pkey_mprotect = 362, + pkey_alloc = 363, + pkey_free = 364, + rseq = 365, + semget = 393, + semctl = 394, + shmget = 395, + shmctl = 396, + shmat = 397, + shmdt = 398, + msgget = 399, + msgsnd = 400, + msgrcv = 401, + msgctl = 402, + clock_gettime64 = 403, + clock_settime64 = 404, + clock_adjtime64 = 405, + clock_getres_time64 = 406, + clock_nanosleep_time64 = 407, + timer_gettime64 = 408, + timer_settime64 = 409, + timerfd_gettime64 = 410, + timerfd_settime64 = 411, + utimensat_time64 = 412, + pselect6_time64 = 413, + ppoll_time64 = 414, + io_pgetevents_time64 = 416, + recvmmsg_time64 = 417, + mq_timedsend_time64 = 418, + mq_timedreceive_time64 = 419, + semtimedop_time64 = 420, + rt_sigtimedwait_time64 = 421, + futex_time64 = 422, + sched_rr_get_interval_time64 = 423, + pidfd_send_signal = 424, + io_uring_setup = 425, + io_uring_enter = 426, + io_uring_register = 427, + open_tree = 428, + move_mount = 429, + fsopen = 430, + fsconfig = 431, + fsmount = 432, + fspick = 433, + pidfd_open = 434, + close_range = 436, + openat2 = 437, + pidfd_getfd = 438, + faccessat2 = 439, + process_madvise = 440, + epoll_pwait2 = 441, + mount_setattr = 442, + quotactl_fd = 443, + landlock_create_ruleset = 444, + landlock_add_rule = 445, + landlock_restrict_self = 446, + process_mrelease = 448, + futex_waitv = 449, + set_mempolicy_home_node = 450, + cachestat = 451, + fchmodat2 = 452, + map_shadow_stack = 453, + futex_wake = 454, + futex_wait = 455, + futex_requeue = 456, +}; + pub const Sparc64 = enum(usize) { restart_syscall = 0, exit = 1, @@ -1633,798 +2061,1612 @@ pub const Sparc64 = enum(usize) { futex_requeue = 456, }; -pub const Mips = enum(usize) { - pub const Linux = 4000; - - syscall = Linux + 0, - exit = Linux + 1, - fork = Linux + 2, - read = Linux + 3, - write = Linux + 4, - open = Linux + 5, - close = Linux + 6, - waitpid = Linux + 7, - creat = Linux + 8, - link = Linux + 9, - unlink = Linux + 10, - execve = Linux + 11, - chdir = Linux + 12, - time = Linux + 13, - mknod = Linux + 14, - chmod = Linux + 15, - lchown = Linux + 16, - @"break" = Linux + 17, - lseek = Linux + 19, - getpid = Linux + 20, - mount = Linux + 21, - umount = Linux + 22, - setuid = Linux + 23, - getuid = Linux + 24, - stime = Linux + 25, - ptrace = Linux + 26, - alarm = Linux + 27, - pause = Linux + 29, - utime = Linux + 30, - stty = Linux + 31, - gtty = Linux + 32, - access = Linux + 33, - nice = Linux + 34, - ftime = Linux + 35, - sync = Linux + 36, - kill = Linux + 37, - rename = Linux + 38, - mkdir = Linux + 39, - rmdir = Linux + 40, - dup = Linux + 41, - pipe = Linux + 42, - times = Linux + 43, - prof = Linux + 44, - brk = Linux + 45, - setgid = Linux + 46, - getgid = Linux + 47, - signal = Linux + 48, - geteuid = Linux + 49, - getegid = Linux + 50, - acct = Linux + 51, - umount2 = Linux + 52, - lock = Linux + 53, - ioctl = Linux + 54, - fcntl = Linux + 55, - mpx = Linux + 56, - setpgid = Linux + 57, - ulimit = Linux + 58, - umask = Linux + 60, - chroot = Linux + 61, - ustat = Linux + 62, - dup2 = Linux + 63, - getppid = Linux + 64, - getpgrp = Linux + 65, - setsid = Linux + 66, - sigaction = Linux + 67, - sgetmask = Linux + 68, - ssetmask = Linux + 69, - setreuid = Linux + 70, - setregid = Linux + 71, - sigsuspend = Linux + 72, - sigpending = Linux + 73, - sethostname = Linux + 74, - setrlimit = Linux + 75, - getrlimit = Linux + 76, - getrusage = Linux + 77, - gettimeofday = Linux + 78, - settimeofday = Linux + 79, - getgroups = Linux + 80, - setgroups = Linux + 81, - reserved82 = Linux + 82, - symlink = Linux + 83, - readlink = Linux + 85, - uselib = Linux + 86, - swapon = Linux + 87, - reboot = Linux + 88, - readdir = Linux + 89, - mmap = Linux + 90, - munmap = Linux + 91, - truncate = Linux + 92, - ftruncate = Linux + 93, - fchmod = Linux + 94, - fchown = Linux + 95, - getpriority = Linux + 96, - setpriority = Linux + 97, - profil = Linux + 98, - statfs = Linux + 99, - fstatfs = Linux + 100, - ioperm = Linux + 101, - socketcall = Linux + 102, - syslog = Linux + 103, - setitimer = Linux + 104, - getitimer = Linux + 105, - stat = Linux + 106, - lstat = Linux + 107, - fstat = Linux + 108, - iopl = Linux + 110, - vhangup = Linux + 111, - idle = Linux + 112, - vm86 = Linux + 113, - wait4 = Linux + 114, - swapoff = Linux + 115, - sysinfo = Linux + 116, - ipc = Linux + 117, - fsync = Linux + 118, - sigreturn = Linux + 119, - clone = Linux + 120, - setdomainname = Linux + 121, - uname = Linux + 122, - modify_ldt = Linux + 123, - adjtimex = Linux + 124, - mprotect = Linux + 125, - sigprocmask = Linux + 126, - create_module = Linux + 127, - init_module = Linux + 128, - delete_module = Linux + 129, - get_kernel_syms = Linux + 130, - quotactl = Linux + 131, - getpgid = Linux + 132, - fchdir = Linux + 133, - bdflush = Linux + 134, - sysfs = Linux + 135, - personality = Linux + 136, - afs_syscall = Linux + 137, - setfsuid = Linux + 138, - setfsgid = Linux + 139, - llseek = Linux + 140, - getdents = Linux + 141, - newselect = Linux + 142, - flock = Linux + 143, - msync = Linux + 144, - readv = Linux + 145, - writev = Linux + 146, - cacheflush = Linux + 147, - cachectl = Linux + 148, - sysmips = Linux + 149, - getsid = Linux + 151, - fdatasync = Linux + 152, - sysctl = Linux + 153, - mlock = Linux + 154, - munlock = Linux + 155, - mlockall = Linux + 156, - munlockall = Linux + 157, - sched_setparam = Linux + 158, - sched_getparam = Linux + 159, - sched_setscheduler = Linux + 160, - sched_getscheduler = Linux + 161, - sched_yield = Linux + 162, - sched_get_priority_max = Linux + 163, - sched_get_priority_min = Linux + 164, - sched_rr_get_interval = Linux + 165, - nanosleep = Linux + 166, - mremap = Linux + 167, - accept = Linux + 168, - bind = Linux + 169, - connect = Linux + 170, - getpeername = Linux + 171, - getsockname = Linux + 172, - getsockopt = Linux + 173, - listen = Linux + 174, - recv = Linux + 175, - recvfrom = Linux + 176, - recvmsg = Linux + 177, - send = Linux + 178, - sendmsg = Linux + 179, - sendto = Linux + 180, - setsockopt = Linux + 181, - shutdown = Linux + 182, - socket = Linux + 183, - socketpair = Linux + 184, - setresuid = Linux + 185, - getresuid = Linux + 186, - query_module = Linux + 187, - poll = Linux + 188, - nfsservctl = Linux + 189, - setresgid = Linux + 190, - getresgid = Linux + 191, - prctl = Linux + 192, - rt_sigreturn = Linux + 193, - rt_sigaction = Linux + 194, - rt_sigprocmask = Linux + 195, - rt_sigpending = Linux + 196, - rt_sigtimedwait = Linux + 197, - rt_sigqueueinfo = Linux + 198, - rt_sigsuspend = Linux + 199, - pread64 = Linux + 200, - pwrite64 = Linux + 201, - chown = Linux + 202, - getcwd = Linux + 203, - capget = Linux + 204, - capset = Linux + 205, - sigaltstack = Linux + 206, - sendfile = Linux + 207, - getpmsg = Linux + 208, - putpmsg = Linux + 209, - mmap2 = Linux + 210, - truncate64 = Linux + 211, - ftruncate64 = Linux + 212, - stat64 = Linux + 213, - lstat64 = Linux + 214, - fstat64 = Linux + 215, - pivot_root = Linux + 216, - mincore = Linux + 217, - madvise = Linux + 218, - getdents64 = Linux + 219, - fcntl64 = Linux + 220, - reserved221 = Linux + 221, - gettid = Linux + 222, - readahead = Linux + 223, - setxattr = Linux + 224, - lsetxattr = Linux + 225, - fsetxattr = Linux + 226, - getxattr = Linux + 227, - lgetxattr = Linux + 228, - fgetxattr = Linux + 229, - listxattr = Linux + 230, - llistxattr = Linux + 231, - flistxattr = Linux + 232, - removexattr = Linux + 233, - lremovexattr = Linux + 234, - fremovexattr = Linux + 235, - tkill = Linux + 236, - sendfile64 = Linux + 237, - futex = Linux + 238, - sched_setaffinity = Linux + 239, - sched_getaffinity = Linux + 240, - io_setup = Linux + 241, - io_destroy = Linux + 242, - io_getevents = Linux + 243, - io_submit = Linux + 244, - io_cancel = Linux + 245, - exit_group = Linux + 246, - lookup_dcookie = Linux + 247, - epoll_create = Linux + 248, - epoll_ctl = Linux + 249, - epoll_wait = Linux + 250, - remap_file_pages = Linux + 251, - set_tid_address = Linux + 252, - restart_syscall = Linux + 253, - fadvise64 = Linux + 254, - statfs64 = Linux + 255, - fstatfs64 = Linux + 256, - timer_create = Linux + 257, - timer_settime = Linux + 258, - timer_gettime = Linux + 259, - timer_getoverrun = Linux + 260, - timer_delete = Linux + 261, - clock_settime = Linux + 262, - clock_gettime = Linux + 263, - clock_getres = Linux + 264, - clock_nanosleep = Linux + 265, - tgkill = Linux + 266, - utimes = Linux + 267, - mbind = Linux + 268, - get_mempolicy = Linux + 269, - set_mempolicy = Linux + 270, - mq_open = Linux + 271, - mq_unlink = Linux + 272, - mq_timedsend = Linux + 273, - mq_timedreceive = Linux + 274, - mq_notify = Linux + 275, - mq_getsetattr = Linux + 276, - vserver = Linux + 277, - waitid = Linux + 278, - add_key = Linux + 280, - request_key = Linux + 281, - keyctl = Linux + 282, - set_thread_area = Linux + 283, - inotify_init = Linux + 284, - inotify_add_watch = Linux + 285, - inotify_rm_watch = Linux + 286, - migrate_pages = Linux + 287, - openat = Linux + 288, - mkdirat = Linux + 289, - mknodat = Linux + 290, - fchownat = Linux + 291, - futimesat = Linux + 292, - fstatat64 = Linux + 293, - unlinkat = Linux + 294, - renameat = Linux + 295, - linkat = Linux + 296, - symlinkat = Linux + 297, - readlinkat = Linux + 298, - fchmodat = Linux + 299, - faccessat = Linux + 300, - pselect6 = Linux + 301, - ppoll = Linux + 302, - unshare = Linux + 303, - splice = Linux + 304, - sync_file_range = Linux + 305, - tee = Linux + 306, - vmsplice = Linux + 307, - move_pages = Linux + 308, - set_robust_list = Linux + 309, - get_robust_list = Linux + 310, - kexec_load = Linux + 311, - getcpu = Linux + 312, - epoll_pwait = Linux + 313, - ioprio_set = Linux + 314, - ioprio_get = Linux + 315, - utimensat = Linux + 316, - signalfd = Linux + 317, - timerfd = Linux + 318, - eventfd = Linux + 319, - fallocate = Linux + 320, - timerfd_create = Linux + 321, - timerfd_gettime = Linux + 322, - timerfd_settime = Linux + 323, - signalfd4 = Linux + 324, - eventfd2 = Linux + 325, - epoll_create1 = Linux + 326, - dup3 = Linux + 327, - pipe2 = Linux + 328, - inotify_init1 = Linux + 329, - preadv = Linux + 330, - pwritev = Linux + 331, - rt_tgsigqueueinfo = Linux + 332, - perf_event_open = Linux + 333, - accept4 = Linux + 334, - recvmmsg = Linux + 335, - fanotify_init = Linux + 336, - fanotify_mark = Linux + 337, - prlimit64 = Linux + 338, - name_to_handle_at = Linux + 339, - open_by_handle_at = Linux + 340, - clock_adjtime = Linux + 341, - syncfs = Linux + 342, - sendmmsg = Linux + 343, - setns = Linux + 344, - process_vm_readv = Linux + 345, - process_vm_writev = Linux + 346, - kcmp = Linux + 347, - finit_module = Linux + 348, - sched_setattr = Linux + 349, - sched_getattr = Linux + 350, - renameat2 = Linux + 351, - seccomp = Linux + 352, - getrandom = Linux + 353, - memfd_create = Linux + 354, - bpf = Linux + 355, - execveat = Linux + 356, - userfaultfd = Linux + 357, - membarrier = Linux + 358, - mlock2 = Linux + 359, - copy_file_range = Linux + 360, - preadv2 = Linux + 361, - pwritev2 = Linux + 362, - pkey_mprotect = Linux + 363, - pkey_alloc = Linux + 364, - pkey_free = Linux + 365, - statx = Linux + 366, - rseq = Linux + 367, - io_pgetevents = Linux + 368, - semget = Linux + 393, - semctl = Linux + 394, - shmget = Linux + 395, - shmctl = Linux + 396, - shmat = Linux + 397, - shmdt = Linux + 398, - msgget = Linux + 399, - msgsnd = Linux + 400, - msgrcv = Linux + 401, - msgctl = Linux + 402, - clock_gettime64 = Linux + 403, - clock_settime64 = Linux + 404, - clock_adjtime64 = Linux + 405, - clock_getres_time64 = Linux + 406, - clock_nanosleep_time64 = Linux + 407, - timer_gettime64 = Linux + 408, - timer_settime64 = Linux + 409, - timerfd_gettime64 = Linux + 410, - timerfd_settime64 = Linux + 411, - utimensat_time64 = Linux + 412, - pselect6_time64 = Linux + 413, - ppoll_time64 = Linux + 414, - io_pgetevents_time64 = Linux + 416, - recvmmsg_time64 = Linux + 417, - mq_timedsend_time64 = Linux + 418, - mq_timedreceive_time64 = Linux + 419, - semtimedop_time64 = Linux + 420, - rt_sigtimedwait_time64 = Linux + 421, - futex_time64 = Linux + 422, - sched_rr_get_interval_time64 = Linux + 423, - pidfd_send_signal = Linux + 424, - io_uring_setup = Linux + 425, - io_uring_enter = Linux + 426, - io_uring_register = Linux + 427, - open_tree = Linux + 428, - move_mount = Linux + 429, - fsopen = Linux + 430, - fsconfig = Linux + 431, - fsmount = Linux + 432, - fspick = Linux + 433, - pidfd_open = Linux + 434, - clone3 = Linux + 435, - close_range = Linux + 436, - openat2 = Linux + 437, - pidfd_getfd = Linux + 438, - faccessat2 = Linux + 439, - process_madvise = Linux + 440, - epoll_pwait2 = Linux + 441, - mount_setattr = Linux + 442, - quotactl_fd = Linux + 443, - landlock_create_ruleset = Linux + 444, - landlock_add_rule = Linux + 445, - landlock_restrict_self = Linux + 446, - process_mrelease = Linux + 448, - futex_waitv = Linux + 449, - set_mempolicy_home_node = Linux + 450, - cachestat = Linux + 451, - fchmodat2 = Linux + 452, - map_shadow_stack = Linux + 453, - futex_wake = Linux + 454, - futex_wait = Linux + 455, - futex_requeue = Linux + 456, +pub const M68k = enum(usize) { + restart_syscall = 0, + exit = 1, + fork = 2, + read = 3, + write = 4, + open = 5, + close = 6, + waitpid = 7, + creat = 8, + link = 9, + unlink = 10, + execve = 11, + chdir = 12, + time = 13, + mknod = 14, + chmod = 15, + chown = 16, + oldstat = 18, + lseek = 19, + getpid = 20, + mount = 21, + umount = 22, + setuid = 23, + getuid = 24, + stime = 25, + ptrace = 26, + alarm = 27, + oldfstat = 28, + pause = 29, + utime = 30, + access = 33, + nice = 34, + sync = 36, + kill = 37, + rename = 38, + mkdir = 39, + rmdir = 40, + dup = 41, + pipe = 42, + times = 43, + brk = 45, + setgid = 46, + getgid = 47, + signal = 48, + geteuid = 49, + getegid = 50, + acct = 51, + umount2 = 52, + ioctl = 54, + fcntl = 55, + setpgid = 57, + umask = 60, + chroot = 61, + ustat = 62, + dup2 = 63, + getppid = 64, + getpgrp = 65, + setsid = 66, + sigaction = 67, + sgetmask = 68, + ssetmask = 69, + setreuid = 70, + setregid = 71, + sigsuspend = 72, + sigpending = 73, + sethostname = 74, + setrlimit = 75, + getrlimit = 76, + getrusage = 77, + gettimeofday = 78, + settimeofday = 79, + getgroups = 80, + setgroups = 81, + select = 82, + symlink = 83, + oldlstat = 84, + readlink = 85, + uselib = 86, + swapon = 87, + reboot = 88, + readdir = 89, + mmap = 90, + munmap = 91, + truncate = 92, + ftruncate = 93, + fchmod = 94, + fchown = 95, + getpriority = 96, + setpriority = 97, + statfs = 99, + fstatfs = 100, + socketcall = 102, + syslog = 103, + setitimer = 104, + getitimer = 105, + stat = 106, + lstat = 107, + fstat = 108, + vhangup = 111, + wait4 = 114, + swapoff = 115, + sysinfo = 116, + ipc = 117, + fsync = 118, + sigreturn = 119, + clone = 120, + setdomainname = 121, + uname = 122, + cacheflush = 123, + adjtimex = 124, + mprotect = 125, + sigprocmask = 126, + create_module = 127, + init_module = 128, + delete_module = 129, + get_kernel_syms = 130, + quotactl = 131, + getpgid = 132, + fchdir = 133, + bdflush = 134, + sysfs = 135, + personality = 136, + setfsuid = 138, + setfsgid = 139, + llseek = 140, + getdents = 141, + newselect = 142, + flock = 143, + msync = 144, + readv = 145, + writev = 146, + getsid = 147, + fdatasync = 148, + sysctl = 149, + mlock = 150, + munlock = 151, + mlockall = 152, + munlockall = 153, + sched_setparam = 154, + sched_getparam = 155, + sched_setscheduler = 156, + sched_getscheduler = 157, + sched_yield = 158, + sched_get_priority_max = 159, + sched_get_priority_min = 160, + sched_rr_get_interval = 161, + nanosleep = 162, + mremap = 163, + setresuid = 164, + getresuid = 165, + getpagesize = 166, + query_module = 167, + poll = 168, + nfsservctl = 169, + setresgid = 170, + getresgid = 171, + prctl = 172, + rt_sigreturn = 173, + rt_sigaction = 174, + rt_sigprocmask = 175, + rt_sigpending = 176, + rt_sigtimedwait = 177, + rt_sigqueueinfo = 178, + rt_sigsuspend = 179, + pread64 = 180, + pwrite64 = 181, + lchown = 182, + getcwd = 183, + capget = 184, + capset = 185, + sigaltstack = 186, + sendfile = 187, + getpmsg = 188, + putpmsg = 189, + vfork = 190, + ugetrlimit = 191, + mmap2 = 192, + truncate64 = 193, + ftruncate64 = 194, + stat64 = 195, + lstat64 = 196, + fstat64 = 197, + chown32 = 198, + getuid32 = 199, + getgid32 = 200, + geteuid32 = 201, + getegid32 = 202, + setreuid32 = 203, + setregid32 = 204, + getgroups32 = 205, + setgroups32 = 206, + fchown32 = 207, + setresuid32 = 208, + getresuid32 = 209, + setresgid32 = 210, + getresgid32 = 211, + lchown32 = 212, + setuid32 = 213, + setgid32 = 214, + setfsuid32 = 215, + setfsgid32 = 216, + pivot_root = 217, + getdents64 = 220, + gettid = 221, + tkill = 222, + setxattr = 223, + lsetxattr = 224, + fsetxattr = 225, + getxattr = 226, + lgetxattr = 227, + fgetxattr = 228, + listxattr = 229, + llistxattr = 230, + flistxattr = 231, + removexattr = 232, + lremovexattr = 233, + fremovexattr = 234, + futex = 235, + sendfile64 = 236, + mincore = 237, + madvise = 238, + fcntl64 = 239, + readahead = 240, + io_setup = 241, + io_destroy = 242, + io_getevents = 243, + io_submit = 244, + io_cancel = 245, + fadvise64 = 246, + exit_group = 247, + lookup_dcookie = 248, + epoll_create = 249, + epoll_ctl = 250, + epoll_wait = 251, + remap_file_pages = 252, + set_tid_address = 253, + timer_create = 254, + timer_settime = 255, + timer_gettime = 256, + timer_getoverrun = 257, + timer_delete = 258, + clock_settime = 259, + clock_gettime = 260, + clock_getres = 261, + clock_nanosleep = 262, + statfs64 = 263, + fstatfs64 = 264, + tgkill = 265, + utimes = 266, + fadvise64_64 = 267, + mbind = 268, + get_mempolicy = 269, + set_mempolicy = 270, + mq_open = 271, + mq_unlink = 272, + mq_timedsend = 273, + mq_timedreceive = 274, + mq_notify = 275, + mq_getsetattr = 276, + waitid = 277, + add_key = 279, + request_key = 280, + keyctl = 281, + ioprio_set = 282, + ioprio_get = 283, + inotify_init = 284, + inotify_add_watch = 285, + inotify_rm_watch = 286, + migrate_pages = 287, + openat = 288, + mkdirat = 289, + mknodat = 290, + fchownat = 291, + futimesat = 292, + fstatat64 = 293, + unlinkat = 294, + renameat = 295, + linkat = 296, + symlinkat = 297, + readlinkat = 298, + fchmodat = 299, + faccessat = 300, + pselect6 = 301, + ppoll = 302, + unshare = 303, + set_robust_list = 304, + get_robust_list = 305, + splice = 306, + sync_file_range = 307, + tee = 308, + vmsplice = 309, + move_pages = 310, + sched_setaffinity = 311, + sched_getaffinity = 312, + kexec_load = 313, + getcpu = 314, + epoll_pwait = 315, + utimensat = 316, + signalfd = 317, + timerfd_create = 318, + eventfd = 319, + fallocate = 320, + timerfd_settime = 321, + timerfd_gettime = 322, + signalfd4 = 323, + eventfd2 = 324, + epoll_create1 = 325, + dup3 = 326, + pipe2 = 327, + inotify_init1 = 328, + preadv = 329, + pwritev = 330, + rt_tgsigqueueinfo = 331, + perf_event_open = 332, + get_thread_area = 333, + set_thread_area = 334, + atomic_cmpxchg_32 = 335, + atomic_barrier = 336, + fanotify_init = 337, + fanotify_mark = 338, + prlimit64 = 339, + name_to_handle_at = 340, + open_by_handle_at = 341, + clock_adjtime = 342, + syncfs = 343, + setns = 344, + process_vm_readv = 345, + process_vm_writev = 346, + kcmp = 347, + finit_module = 348, + sched_setattr = 349, + sched_getattr = 350, + renameat2 = 351, + getrandom = 352, + memfd_create = 353, + bpf = 354, + execveat = 355, + socket = 356, + socketpair = 357, + bind = 358, + connect = 359, + listen = 360, + accept4 = 361, + getsockopt = 362, + setsockopt = 363, + getsockname = 364, + getpeername = 365, + sendto = 366, + sendmsg = 367, + recvfrom = 368, + recvmsg = 369, + shutdown = 370, + recvmmsg = 371, + sendmmsg = 372, + userfaultfd = 373, + membarrier = 374, + mlock2 = 375, + copy_file_range = 376, + preadv2 = 377, + pwritev2 = 378, + statx = 379, + seccomp = 380, + pkey_mprotect = 381, + pkey_alloc = 382, + pkey_free = 383, + rseq = 384, + semget = 393, + semctl = 394, + shmget = 395, + shmctl = 396, + shmat = 397, + shmdt = 398, + msgget = 399, + msgsnd = 400, + msgrcv = 401, + msgctl = 402, + clock_gettime64 = 403, + clock_settime64 = 404, + clock_adjtime64 = 405, + clock_getres_time64 = 406, + clock_nanosleep_time64 = 407, + timer_gettime64 = 408, + timer_settime64 = 409, + timerfd_gettime64 = 410, + timerfd_settime64 = 411, + utimensat_time64 = 412, + pselect6_time64 = 413, + ppoll_time64 = 414, + io_pgetevents_time64 = 416, + recvmmsg_time64 = 417, + mq_timedsend_time64 = 418, + mq_timedreceive_time64 = 419, + semtimedop_time64 = 420, + rt_sigtimedwait_time64 = 421, + futex_time64 = 422, + sched_rr_get_interval_time64 = 423, + pidfd_send_signal = 424, + io_uring_setup = 425, + io_uring_enter = 426, + io_uring_register = 427, + open_tree = 428, + move_mount = 429, + fsopen = 430, + fsconfig = 431, + fsmount = 432, + fspick = 433, + pidfd_open = 434, + clone3 = 435, + close_range = 436, + openat2 = 437, + pidfd_getfd = 438, + faccessat2 = 439, + process_madvise = 440, + epoll_pwait2 = 441, + mount_setattr = 442, + quotactl_fd = 443, + landlock_create_ruleset = 444, + landlock_add_rule = 445, + landlock_restrict_self = 446, + process_mrelease = 448, + futex_waitv = 449, + set_mempolicy_home_node = 450, + cachestat = 451, + fchmodat2 = 452, + map_shadow_stack = 453, + futex_wake = 454, + futex_wait = 455, + futex_requeue = 456, }; -pub const Mips64 = enum(usize) { - pub const Linux = 5000; +pub const MipsO32 = enum(usize) { + const linux_base = 4000; - read = Linux + 0, - write = Linux + 1, - open = Linux + 2, - close = Linux + 3, - stat = Linux + 4, - fstat = Linux + 5, - lstat = Linux + 6, - poll = Linux + 7, - lseek = Linux + 8, - mmap = Linux + 9, - mprotect = Linux + 10, - munmap = Linux + 11, - brk = Linux + 12, - rt_sigaction = Linux + 13, - rt_sigprocmask = Linux + 14, - ioctl = Linux + 15, - pread64 = Linux + 16, - pwrite64 = Linux + 17, - readv = Linux + 18, - writev = Linux + 19, - access = Linux + 20, - pipe = Linux + 21, - newselect = Linux + 22, - sched_yield = Linux + 23, - mremap = Linux + 24, - msync = Linux + 25, - mincore = Linux + 26, - madvise = Linux + 27, - shmget = Linux + 28, - shmat = Linux + 29, - shmctl = Linux + 30, - dup = Linux + 31, - dup2 = Linux + 32, - pause = Linux + 33, - nanosleep = Linux + 34, - getitimer = Linux + 35, - setitimer = Linux + 36, - alarm = Linux + 37, - getpid = Linux + 38, - sendfile = Linux + 39, - socket = Linux + 40, - connect = Linux + 41, - accept = Linux + 42, - sendto = Linux + 43, - recvfrom = Linux + 44, - sendmsg = Linux + 45, - recvmsg = Linux + 46, - shutdown = Linux + 47, - bind = Linux + 48, - listen = Linux + 49, - getsockname = Linux + 50, - getpeername = Linux + 51, - socketpair = Linux + 52, - setsockopt = Linux + 53, - getsockopt = Linux + 54, - clone = Linux + 55, - fork = Linux + 56, - execve = Linux + 57, - exit = Linux + 58, - wait4 = Linux + 59, - kill = Linux + 60, - uname = Linux + 61, - semget = Linux + 62, - semop = Linux + 63, - semctl = Linux + 64, - shmdt = Linux + 65, - msgget = Linux + 66, - msgsnd = Linux + 67, - msgrcv = Linux + 68, - msgctl = Linux + 69, - fcntl = Linux + 70, - flock = Linux + 71, - fsync = Linux + 72, - fdatasync = Linux + 73, - truncate = Linux + 74, - ftruncate = Linux + 75, - getdents = Linux + 76, - getcwd = Linux + 77, - chdir = Linux + 78, - fchdir = Linux + 79, - rename = Linux + 80, - mkdir = Linux + 81, - rmdir = Linux + 82, - creat = Linux + 83, - link = Linux + 84, - unlink = Linux + 85, - symlink = Linux + 86, - readlink = Linux + 87, - chmod = Linux + 88, - fchmod = Linux + 89, - chown = Linux + 90, - fchown = Linux + 91, - lchown = Linux + 92, - umask = Linux + 93, - gettimeofday = Linux + 94, - getrlimit = Linux + 95, - getrusage = Linux + 96, - sysinfo = Linux + 97, - times = Linux + 98, - ptrace = Linux + 99, - getuid = Linux + 100, - syslog = Linux + 101, - getgid = Linux + 102, - setuid = Linux + 103, - setgid = Linux + 104, - geteuid = Linux + 105, - getegid = Linux + 106, - setpgid = Linux + 107, - getppid = Linux + 108, - getpgrp = Linux + 109, - setsid = Linux + 110, - setreuid = Linux + 111, - setregid = Linux + 112, - getgroups = Linux + 113, - setgroups = Linux + 114, - setresuid = Linux + 115, - getresuid = Linux + 116, - setresgid = Linux + 117, - getresgid = Linux + 118, - getpgid = Linux + 119, - setfsuid = Linux + 120, - setfsgid = Linux + 121, - getsid = Linux + 122, - capget = Linux + 123, - capset = Linux + 124, - rt_sigpending = Linux + 125, - rt_sigtimedwait = Linux + 126, - rt_sigqueueinfo = Linux + 127, - rt_sigsuspend = Linux + 128, - sigaltstack = Linux + 129, - utime = Linux + 130, - mknod = Linux + 131, - personality = Linux + 132, - ustat = Linux + 133, - statfs = Linux + 134, - fstatfs = Linux + 135, - sysfs = Linux + 136, - getpriority = Linux + 137, - setpriority = Linux + 138, - sched_setparam = Linux + 139, - sched_getparam = Linux + 140, - sched_setscheduler = Linux + 141, - sched_getscheduler = Linux + 142, - sched_get_priority_max = Linux + 143, - sched_get_priority_min = Linux + 144, - sched_rr_get_interval = Linux + 145, - mlock = Linux + 146, - munlock = Linux + 147, - mlockall = Linux + 148, - munlockall = Linux + 149, - vhangup = Linux + 150, - pivot_root = Linux + 151, - sysctl = Linux + 152, - prctl = Linux + 153, - adjtimex = Linux + 154, - setrlimit = Linux + 155, - chroot = Linux + 156, - sync = Linux + 157, - acct = Linux + 158, - settimeofday = Linux + 159, - mount = Linux + 160, - umount2 = Linux + 161, - swapon = Linux + 162, - swapoff = Linux + 163, - reboot = Linux + 164, - sethostname = Linux + 165, - setdomainname = Linux + 166, - create_module = Linux + 167, - init_module = Linux + 168, - delete_module = Linux + 169, - get_kernel_syms = Linux + 170, - query_module = Linux + 171, - quotactl = Linux + 172, - nfsservctl = Linux + 173, - getpmsg = Linux + 174, - putpmsg = Linux + 175, - afs_syscall = Linux + 176, - reserved177 = Linux + 177, - gettid = Linux + 178, - readahead = Linux + 179, - setxattr = Linux + 180, - lsetxattr = Linux + 181, - fsetxattr = Linux + 182, - getxattr = Linux + 183, - lgetxattr = Linux + 184, - fgetxattr = Linux + 185, - listxattr = Linux + 186, - llistxattr = Linux + 187, - flistxattr = Linux + 188, - removexattr = Linux + 189, - lremovexattr = Linux + 190, - fremovexattr = Linux + 191, - tkill = Linux + 192, - reserved193 = Linux + 193, - futex = Linux + 194, - sched_setaffinity = Linux + 195, - sched_getaffinity = Linux + 196, - cacheflush = Linux + 197, - cachectl = Linux + 198, - sysmips = Linux + 199, - io_setup = Linux + 200, - io_destroy = Linux + 201, - io_getevents = Linux + 202, - io_submit = Linux + 203, - io_cancel = Linux + 204, - exit_group = Linux + 205, - lookup_dcookie = Linux + 206, - epoll_create = Linux + 207, - epoll_ctl = Linux + 208, - epoll_wait = Linux + 209, - remap_file_pages = Linux + 210, - rt_sigreturn = Linux + 211, - set_tid_address = Linux + 212, - restart_syscall = Linux + 213, - semtimedop = Linux + 214, - fadvise64 = Linux + 215, - timer_create = Linux + 216, - timer_settime = Linux + 217, - timer_gettime = Linux + 218, - timer_getoverrun = Linux + 219, - timer_delete = Linux + 220, - clock_settime = Linux + 221, - clock_gettime = Linux + 222, - clock_getres = Linux + 223, - clock_nanosleep = Linux + 224, - tgkill = Linux + 225, - utimes = Linux + 226, - mbind = Linux + 227, - get_mempolicy = Linux + 228, - set_mempolicy = Linux + 229, - mq_open = Linux + 230, - mq_unlink = Linux + 231, - mq_timedsend = Linux + 232, - mq_timedreceive = Linux + 233, - mq_notify = Linux + 234, - mq_getsetattr = Linux + 235, - vserver = Linux + 236, - waitid = Linux + 237, - add_key = Linux + 239, - request_key = Linux + 240, - keyctl = Linux + 241, - set_thread_area = Linux + 242, - inotify_init = Linux + 243, - inotify_add_watch = Linux + 244, - inotify_rm_watch = Linux + 245, - migrate_pages = Linux + 246, - openat = Linux + 247, - mkdirat = Linux + 248, - mknodat = Linux + 249, - fchownat = Linux + 250, - futimesat = Linux + 251, - fstatat64 = Linux + 252, - unlinkat = Linux + 253, - renameat = Linux + 254, - linkat = Linux + 255, - symlinkat = Linux + 256, - readlinkat = Linux + 257, - fchmodat = Linux + 258, - faccessat = Linux + 259, - pselect6 = Linux + 260, - ppoll = Linux + 261, - unshare = Linux + 262, - splice = Linux + 263, - sync_file_range = Linux + 264, - tee = Linux + 265, - vmsplice = Linux + 266, - move_pages = Linux + 267, - set_robust_list = Linux + 268, - get_robust_list = Linux + 269, - kexec_load = Linux + 270, - getcpu = Linux + 271, - epoll_pwait = Linux + 272, - ioprio_set = Linux + 273, - ioprio_get = Linux + 274, - utimensat = Linux + 275, - signalfd = Linux + 276, - timerfd = Linux + 277, - eventfd = Linux + 278, - fallocate = Linux + 279, - timerfd_create = Linux + 280, - timerfd_gettime = Linux + 281, - timerfd_settime = Linux + 282, - signalfd4 = Linux + 283, - eventfd2 = Linux + 284, - epoll_create1 = Linux + 285, - dup3 = Linux + 286, - pipe2 = Linux + 287, - inotify_init1 = Linux + 288, - preadv = Linux + 289, - pwritev = Linux + 290, - rt_tgsigqueueinfo = Linux + 291, - perf_event_open = Linux + 292, - accept4 = Linux + 293, - recvmmsg = Linux + 294, - fanotify_init = Linux + 295, - fanotify_mark = Linux + 296, - prlimit64 = Linux + 297, - name_to_handle_at = Linux + 298, - open_by_handle_at = Linux + 299, - clock_adjtime = Linux + 300, - syncfs = Linux + 301, - sendmmsg = Linux + 302, - setns = Linux + 303, - process_vm_readv = Linux + 304, - process_vm_writev = Linux + 305, - kcmp = Linux + 306, - finit_module = Linux + 307, - getdents64 = Linux + 308, - sched_setattr = Linux + 309, - sched_getattr = Linux + 310, - renameat2 = Linux + 311, - seccomp = Linux + 312, - getrandom = Linux + 313, - memfd_create = Linux + 314, - bpf = Linux + 315, - execveat = Linux + 316, - userfaultfd = Linux + 317, - membarrier = Linux + 318, - mlock2 = Linux + 319, - copy_file_range = Linux + 320, - preadv2 = Linux + 321, - pwritev2 = Linux + 322, - pkey_mprotect = Linux + 323, - pkey_alloc = Linux + 324, - pkey_free = Linux + 325, - statx = Linux + 326, - rseq = Linux + 327, - io_pgetevents = Linux + 328, - pidfd_send_signal = Linux + 424, - io_uring_setup = Linux + 425, - io_uring_enter = Linux + 426, - io_uring_register = Linux + 427, - open_tree = Linux + 428, - move_mount = Linux + 429, - fsopen = Linux + 430, - fsconfig = Linux + 431, - fsmount = Linux + 432, - fspick = Linux + 433, - pidfd_open = Linux + 434, - clone3 = Linux + 435, - close_range = Linux + 436, - openat2 = Linux + 437, - pidfd_getfd = Linux + 438, - faccessat2 = Linux + 439, - process_madvise = Linux + 440, - epoll_pwait2 = Linux + 441, - mount_setattr = Linux + 442, - quotactl_fd = Linux + 443, - landlock_create_ruleset = Linux + 444, - landlock_add_rule = Linux + 445, - landlock_restrict_self = Linux + 446, - process_mrelease = Linux + 448, - futex_waitv = Linux + 449, - set_mempolicy_home_node = Linux + 450, - cachestat = Linux + 451, - fchmodat2 = Linux + 452, - map_shadow_stack = Linux + 453, - futex_wake = Linux + 454, - futex_wait = Linux + 455, - futex_requeue = Linux + 456, + syscall = linux_base + 0, + exit = linux_base + 1, + fork = linux_base + 2, + read = linux_base + 3, + write = linux_base + 4, + open = linux_base + 5, + close = linux_base + 6, + waitpid = linux_base + 7, + creat = linux_base + 8, + link = linux_base + 9, + unlink = linux_base + 10, + execve = linux_base + 11, + chdir = linux_base + 12, + time = linux_base + 13, + mknod = linux_base + 14, + chmod = linux_base + 15, + lchown = linux_base + 16, + @"break" = linux_base + 17, + lseek = linux_base + 19, + getpid = linux_base + 20, + mount = linux_base + 21, + umount = linux_base + 22, + setuid = linux_base + 23, + getuid = linux_base + 24, + stime = linux_base + 25, + ptrace = linux_base + 26, + alarm = linux_base + 27, + pause = linux_base + 29, + utime = linux_base + 30, + stty = linux_base + 31, + gtty = linux_base + 32, + access = linux_base + 33, + nice = linux_base + 34, + ftime = linux_base + 35, + sync = linux_base + 36, + kill = linux_base + 37, + rename = linux_base + 38, + mkdir = linux_base + 39, + rmdir = linux_base + 40, + dup = linux_base + 41, + pipe = linux_base + 42, + times = linux_base + 43, + prof = linux_base + 44, + brk = linux_base + 45, + setgid = linux_base + 46, + getgid = linux_base + 47, + signal = linux_base + 48, + geteuid = linux_base + 49, + getegid = linux_base + 50, + acct = linux_base + 51, + umount2 = linux_base + 52, + lock = linux_base + 53, + ioctl = linux_base + 54, + fcntl = linux_base + 55, + mpx = linux_base + 56, + setpgid = linux_base + 57, + ulimit = linux_base + 58, + umask = linux_base + 60, + chroot = linux_base + 61, + ustat = linux_base + 62, + dup2 = linux_base + 63, + getppid = linux_base + 64, + getpgrp = linux_base + 65, + setsid = linux_base + 66, + sigaction = linux_base + 67, + sgetmask = linux_base + 68, + ssetmask = linux_base + 69, + setreuid = linux_base + 70, + setregid = linux_base + 71, + sigsuspend = linux_base + 72, + sigpending = linux_base + 73, + sethostname = linux_base + 74, + setrlimit = linux_base + 75, + getrlimit = linux_base + 76, + getrusage = linux_base + 77, + gettimeofday = linux_base + 78, + settimeofday = linux_base + 79, + getgroups = linux_base + 80, + setgroups = linux_base + 81, + symlink = linux_base + 83, + readlink = linux_base + 85, + uselib = linux_base + 86, + swapon = linux_base + 87, + reboot = linux_base + 88, + readdir = linux_base + 89, + mmap = linux_base + 90, + munmap = linux_base + 91, + truncate = linux_base + 92, + ftruncate = linux_base + 93, + fchmod = linux_base + 94, + fchown = linux_base + 95, + getpriority = linux_base + 96, + setpriority = linux_base + 97, + profil = linux_base + 98, + statfs = linux_base + 99, + fstatfs = linux_base + 100, + ioperm = linux_base + 101, + socketcall = linux_base + 102, + syslog = linux_base + 103, + setitimer = linux_base + 104, + getitimer = linux_base + 105, + stat = linux_base + 106, + lstat = linux_base + 107, + fstat = linux_base + 108, + iopl = linux_base + 110, + vhangup = linux_base + 111, + idle = linux_base + 112, + vm86 = linux_base + 113, + wait4 = linux_base + 114, + swapoff = linux_base + 115, + sysinfo = linux_base + 116, + ipc = linux_base + 117, + fsync = linux_base + 118, + sigreturn = linux_base + 119, + clone = linux_base + 120, + setdomainname = linux_base + 121, + uname = linux_base + 122, + modify_ldt = linux_base + 123, + adjtimex = linux_base + 124, + mprotect = linux_base + 125, + sigprocmask = linux_base + 126, + create_module = linux_base + 127, + init_module = linux_base + 128, + delete_module = linux_base + 129, + get_kernel_syms = linux_base + 130, + quotactl = linux_base + 131, + getpgid = linux_base + 132, + fchdir = linux_base + 133, + bdflush = linux_base + 134, + sysfs = linux_base + 135, + personality = linux_base + 136, + afs_syscall = linux_base + 137, + setfsuid = linux_base + 138, + setfsgid = linux_base + 139, + llseek = linux_base + 140, + getdents = linux_base + 141, + newselect = linux_base + 142, + flock = linux_base + 143, + msync = linux_base + 144, + readv = linux_base + 145, + writev = linux_base + 146, + cacheflush = linux_base + 147, + cachectl = linux_base + 148, + sysmips = linux_base + 149, + getsid = linux_base + 151, + fdatasync = linux_base + 152, + sysctl = linux_base + 153, + mlock = linux_base + 154, + munlock = linux_base + 155, + mlockall = linux_base + 156, + munlockall = linux_base + 157, + sched_setparam = linux_base + 158, + sched_getparam = linux_base + 159, + sched_setscheduler = linux_base + 160, + sched_getscheduler = linux_base + 161, + sched_yield = linux_base + 162, + sched_get_priority_max = linux_base + 163, + sched_get_priority_min = linux_base + 164, + sched_rr_get_interval = linux_base + 165, + nanosleep = linux_base + 166, + mremap = linux_base + 167, + accept = linux_base + 168, + bind = linux_base + 169, + connect = linux_base + 170, + getpeername = linux_base + 171, + getsockname = linux_base + 172, + getsockopt = linux_base + 173, + listen = linux_base + 174, + recv = linux_base + 175, + recvfrom = linux_base + 176, + recvmsg = linux_base + 177, + send = linux_base + 178, + sendmsg = linux_base + 179, + sendto = linux_base + 180, + setsockopt = linux_base + 181, + shutdown = linux_base + 182, + socket = linux_base + 183, + socketpair = linux_base + 184, + setresuid = linux_base + 185, + getresuid = linux_base + 186, + query_module = linux_base + 187, + poll = linux_base + 188, + nfsservctl = linux_base + 189, + setresgid = linux_base + 190, + getresgid = linux_base + 191, + prctl = linux_base + 192, + rt_sigreturn = linux_base + 193, + rt_sigaction = linux_base + 194, + rt_sigprocmask = linux_base + 195, + rt_sigpending = linux_base + 196, + rt_sigtimedwait = linux_base + 197, + rt_sigqueueinfo = linux_base + 198, + rt_sigsuspend = linux_base + 199, + pread64 = linux_base + 200, + pwrite64 = linux_base + 201, + chown = linux_base + 202, + getcwd = linux_base + 203, + capget = linux_base + 204, + capset = linux_base + 205, + sigaltstack = linux_base + 206, + sendfile = linux_base + 207, + getpmsg = linux_base + 208, + putpmsg = linux_base + 209, + mmap2 = linux_base + 210, + truncate64 = linux_base + 211, + ftruncate64 = linux_base + 212, + stat64 = linux_base + 213, + lstat64 = linux_base + 214, + fstat64 = linux_base + 215, + pivot_root = linux_base + 216, + mincore = linux_base + 217, + madvise = linux_base + 218, + getdents64 = linux_base + 219, + fcntl64 = linux_base + 220, + gettid = linux_base + 222, + readahead = linux_base + 223, + setxattr = linux_base + 224, + lsetxattr = linux_base + 225, + fsetxattr = linux_base + 226, + getxattr = linux_base + 227, + lgetxattr = linux_base + 228, + fgetxattr = linux_base + 229, + listxattr = linux_base + 230, + llistxattr = linux_base + 231, + flistxattr = linux_base + 232, + removexattr = linux_base + 233, + lremovexattr = linux_base + 234, + fremovexattr = linux_base + 235, + tkill = linux_base + 236, + sendfile64 = linux_base + 237, + futex = linux_base + 238, + sched_setaffinity = linux_base + 239, + sched_getaffinity = linux_base + 240, + io_setup = linux_base + 241, + io_destroy = linux_base + 242, + io_getevents = linux_base + 243, + io_submit = linux_base + 244, + io_cancel = linux_base + 245, + exit_group = linux_base + 246, + lookup_dcookie = linux_base + 247, + epoll_create = linux_base + 248, + epoll_ctl = linux_base + 249, + epoll_wait = linux_base + 250, + remap_file_pages = linux_base + 251, + set_tid_address = linux_base + 252, + restart_syscall = linux_base + 253, + fadvise64 = linux_base + 254, + statfs64 = linux_base + 255, + fstatfs64 = linux_base + 256, + timer_create = linux_base + 257, + timer_settime = linux_base + 258, + timer_gettime = linux_base + 259, + timer_getoverrun = linux_base + 260, + timer_delete = linux_base + 261, + clock_settime = linux_base + 262, + clock_gettime = linux_base + 263, + clock_getres = linux_base + 264, + clock_nanosleep = linux_base + 265, + tgkill = linux_base + 266, + utimes = linux_base + 267, + mbind = linux_base + 268, + get_mempolicy = linux_base + 269, + set_mempolicy = linux_base + 270, + mq_open = linux_base + 271, + mq_unlink = linux_base + 272, + mq_timedsend = linux_base + 273, + mq_timedreceive = linux_base + 274, + mq_notify = linux_base + 275, + mq_getsetattr = linux_base + 276, + vserver = linux_base + 277, + waitid = linux_base + 278, + add_key = linux_base + 280, + request_key = linux_base + 281, + keyctl = linux_base + 282, + set_thread_area = linux_base + 283, + inotify_init = linux_base + 284, + inotify_add_watch = linux_base + 285, + inotify_rm_watch = linux_base + 286, + migrate_pages = linux_base + 287, + openat = linux_base + 288, + mkdirat = linux_base + 289, + mknodat = linux_base + 290, + fchownat = linux_base + 291, + futimesat = linux_base + 292, + fstatat64 = linux_base + 293, + unlinkat = linux_base + 294, + renameat = linux_base + 295, + linkat = linux_base + 296, + symlinkat = linux_base + 297, + readlinkat = linux_base + 298, + fchmodat = linux_base + 299, + faccessat = linux_base + 300, + pselect6 = linux_base + 301, + ppoll = linux_base + 302, + unshare = linux_base + 303, + splice = linux_base + 304, + sync_file_range = linux_base + 305, + tee = linux_base + 306, + vmsplice = linux_base + 307, + move_pages = linux_base + 308, + set_robust_list = linux_base + 309, + get_robust_list = linux_base + 310, + kexec_load = linux_base + 311, + getcpu = linux_base + 312, + epoll_pwait = linux_base + 313, + ioprio_set = linux_base + 314, + ioprio_get = linux_base + 315, + utimensat = linux_base + 316, + signalfd = linux_base + 317, + timerfd = linux_base + 318, + eventfd = linux_base + 319, + fallocate = linux_base + 320, + timerfd_create = linux_base + 321, + timerfd_gettime = linux_base + 322, + timerfd_settime = linux_base + 323, + signalfd4 = linux_base + 324, + eventfd2 = linux_base + 325, + epoll_create1 = linux_base + 326, + dup3 = linux_base + 327, + pipe2 = linux_base + 328, + inotify_init1 = linux_base + 329, + preadv = linux_base + 330, + pwritev = linux_base + 331, + rt_tgsigqueueinfo = linux_base + 332, + perf_event_open = linux_base + 333, + accept4 = linux_base + 334, + recvmmsg = linux_base + 335, + fanotify_init = linux_base + 336, + fanotify_mark = linux_base + 337, + prlimit64 = linux_base + 338, + name_to_handle_at = linux_base + 339, + open_by_handle_at = linux_base + 340, + clock_adjtime = linux_base + 341, + syncfs = linux_base + 342, + sendmmsg = linux_base + 343, + setns = linux_base + 344, + process_vm_readv = linux_base + 345, + process_vm_writev = linux_base + 346, + kcmp = linux_base + 347, + finit_module = linux_base + 348, + sched_setattr = linux_base + 349, + sched_getattr = linux_base + 350, + renameat2 = linux_base + 351, + seccomp = linux_base + 352, + getrandom = linux_base + 353, + memfd_create = linux_base + 354, + bpf = linux_base + 355, + execveat = linux_base + 356, + userfaultfd = linux_base + 357, + membarrier = linux_base + 358, + mlock2 = linux_base + 359, + copy_file_range = linux_base + 360, + preadv2 = linux_base + 361, + pwritev2 = linux_base + 362, + pkey_mprotect = linux_base + 363, + pkey_alloc = linux_base + 364, + pkey_free = linux_base + 365, + statx = linux_base + 366, + rseq = linux_base + 367, + io_pgetevents = linux_base + 368, + semget = linux_base + 393, + semctl = linux_base + 394, + shmget = linux_base + 395, + shmctl = linux_base + 396, + shmat = linux_base + 397, + shmdt = linux_base + 398, + msgget = linux_base + 399, + msgsnd = linux_base + 400, + msgrcv = linux_base + 401, + msgctl = linux_base + 402, + clock_gettime64 = linux_base + 403, + clock_settime64 = linux_base + 404, + clock_adjtime64 = linux_base + 405, + clock_getres_time64 = linux_base + 406, + clock_nanosleep_time64 = linux_base + 407, + timer_gettime64 = linux_base + 408, + timer_settime64 = linux_base + 409, + timerfd_gettime64 = linux_base + 410, + timerfd_settime64 = linux_base + 411, + utimensat_time64 = linux_base + 412, + pselect6_time64 = linux_base + 413, + ppoll_time64 = linux_base + 414, + io_pgetevents_time64 = linux_base + 416, + recvmmsg_time64 = linux_base + 417, + mq_timedsend_time64 = linux_base + 418, + mq_timedreceive_time64 = linux_base + 419, + semtimedop_time64 = linux_base + 420, + rt_sigtimedwait_time64 = linux_base + 421, + futex_time64 = linux_base + 422, + sched_rr_get_interval_time64 = linux_base + 423, + pidfd_send_signal = linux_base + 424, + io_uring_setup = linux_base + 425, + io_uring_enter = linux_base + 426, + io_uring_register = linux_base + 427, + open_tree = linux_base + 428, + move_mount = linux_base + 429, + fsopen = linux_base + 430, + fsconfig = linux_base + 431, + fsmount = linux_base + 432, + fspick = linux_base + 433, + pidfd_open = linux_base + 434, + clone3 = linux_base + 435, + close_range = linux_base + 436, + openat2 = linux_base + 437, + pidfd_getfd = linux_base + 438, + faccessat2 = linux_base + 439, + process_madvise = linux_base + 440, + epoll_pwait2 = linux_base + 441, + mount_setattr = linux_base + 442, + quotactl_fd = linux_base + 443, + landlock_create_ruleset = linux_base + 444, + landlock_add_rule = linux_base + 445, + landlock_restrict_self = linux_base + 446, + process_mrelease = linux_base + 448, + futex_waitv = linux_base + 449, + set_mempolicy_home_node = linux_base + 450, + cachestat = linux_base + 451, + fchmodat2 = linux_base + 452, + map_shadow_stack = linux_base + 453, + futex_wake = linux_base + 454, + futex_wait = linux_base + 455, + futex_requeue = linux_base + 456, +}; + +pub const MipsN64 = enum(usize) { + const linux_base = 5000; + + read = linux_base + 0, + write = linux_base + 1, + open = linux_base + 2, + close = linux_base + 3, + stat = linux_base + 4, + fstat = linux_base + 5, + lstat = linux_base + 6, + poll = linux_base + 7, + lseek = linux_base + 8, + mmap = linux_base + 9, + mprotect = linux_base + 10, + munmap = linux_base + 11, + brk = linux_base + 12, + rt_sigaction = linux_base + 13, + rt_sigprocmask = linux_base + 14, + ioctl = linux_base + 15, + pread64 = linux_base + 16, + pwrite64 = linux_base + 17, + readv = linux_base + 18, + writev = linux_base + 19, + access = linux_base + 20, + pipe = linux_base + 21, + newselect = linux_base + 22, + sched_yield = linux_base + 23, + mremap = linux_base + 24, + msync = linux_base + 25, + mincore = linux_base + 26, + madvise = linux_base + 27, + shmget = linux_base + 28, + shmat = linux_base + 29, + shmctl = linux_base + 30, + dup = linux_base + 31, + dup2 = linux_base + 32, + pause = linux_base + 33, + nanosleep = linux_base + 34, + getitimer = linux_base + 35, + setitimer = linux_base + 36, + alarm = linux_base + 37, + getpid = linux_base + 38, + sendfile = linux_base + 39, + socket = linux_base + 40, + connect = linux_base + 41, + accept = linux_base + 42, + sendto = linux_base + 43, + recvfrom = linux_base + 44, + sendmsg = linux_base + 45, + recvmsg = linux_base + 46, + shutdown = linux_base + 47, + bind = linux_base + 48, + listen = linux_base + 49, + getsockname = linux_base + 50, + getpeername = linux_base + 51, + socketpair = linux_base + 52, + setsockopt = linux_base + 53, + getsockopt = linux_base + 54, + clone = linux_base + 55, + fork = linux_base + 56, + execve = linux_base + 57, + exit = linux_base + 58, + wait4 = linux_base + 59, + kill = linux_base + 60, + uname = linux_base + 61, + semget = linux_base + 62, + semop = linux_base + 63, + semctl = linux_base + 64, + shmdt = linux_base + 65, + msgget = linux_base + 66, + msgsnd = linux_base + 67, + msgrcv = linux_base + 68, + msgctl = linux_base + 69, + fcntl = linux_base + 70, + flock = linux_base + 71, + fsync = linux_base + 72, + fdatasync = linux_base + 73, + truncate = linux_base + 74, + ftruncate = linux_base + 75, + getdents = linux_base + 76, + getcwd = linux_base + 77, + chdir = linux_base + 78, + fchdir = linux_base + 79, + rename = linux_base + 80, + mkdir = linux_base + 81, + rmdir = linux_base + 82, + creat = linux_base + 83, + link = linux_base + 84, + unlink = linux_base + 85, + symlink = linux_base + 86, + readlink = linux_base + 87, + chmod = linux_base + 88, + fchmod = linux_base + 89, + chown = linux_base + 90, + fchown = linux_base + 91, + lchown = linux_base + 92, + umask = linux_base + 93, + gettimeofday = linux_base + 94, + getrlimit = linux_base + 95, + getrusage = linux_base + 96, + sysinfo = linux_base + 97, + times = linux_base + 98, + ptrace = linux_base + 99, + getuid = linux_base + 100, + syslog = linux_base + 101, + getgid = linux_base + 102, + setuid = linux_base + 103, + setgid = linux_base + 104, + geteuid = linux_base + 105, + getegid = linux_base + 106, + setpgid = linux_base + 107, + getppid = linux_base + 108, + getpgrp = linux_base + 109, + setsid = linux_base + 110, + setreuid = linux_base + 111, + setregid = linux_base + 112, + getgroups = linux_base + 113, + setgroups = linux_base + 114, + setresuid = linux_base + 115, + getresuid = linux_base + 116, + setresgid = linux_base + 117, + getresgid = linux_base + 118, + getpgid = linux_base + 119, + setfsuid = linux_base + 120, + setfsgid = linux_base + 121, + getsid = linux_base + 122, + capget = linux_base + 123, + capset = linux_base + 124, + rt_sigpending = linux_base + 125, + rt_sigtimedwait = linux_base + 126, + rt_sigqueueinfo = linux_base + 127, + rt_sigsuspend = linux_base + 128, + sigaltstack = linux_base + 129, + utime = linux_base + 130, + mknod = linux_base + 131, + personality = linux_base + 132, + ustat = linux_base + 133, + statfs = linux_base + 134, + fstatfs = linux_base + 135, + sysfs = linux_base + 136, + getpriority = linux_base + 137, + setpriority = linux_base + 138, + sched_setparam = linux_base + 139, + sched_getparam = linux_base + 140, + sched_setscheduler = linux_base + 141, + sched_getscheduler = linux_base + 142, + sched_get_priority_max = linux_base + 143, + sched_get_priority_min = linux_base + 144, + sched_rr_get_interval = linux_base + 145, + mlock = linux_base + 146, + munlock = linux_base + 147, + mlockall = linux_base + 148, + munlockall = linux_base + 149, + vhangup = linux_base + 150, + pivot_root = linux_base + 151, + sysctl = linux_base + 152, + prctl = linux_base + 153, + adjtimex = linux_base + 154, + setrlimit = linux_base + 155, + chroot = linux_base + 156, + sync = linux_base + 157, + acct = linux_base + 158, + settimeofday = linux_base + 159, + mount = linux_base + 160, + umount2 = linux_base + 161, + swapon = linux_base + 162, + swapoff = linux_base + 163, + reboot = linux_base + 164, + sethostname = linux_base + 165, + setdomainname = linux_base + 166, + create_module = linux_base + 167, + init_module = linux_base + 168, + delete_module = linux_base + 169, + get_kernel_syms = linux_base + 170, + query_module = linux_base + 171, + quotactl = linux_base + 172, + nfsservctl = linux_base + 173, + getpmsg = linux_base + 174, + putpmsg = linux_base + 175, + afs_syscall = linux_base + 176, + gettid = linux_base + 178, + readahead = linux_base + 179, + setxattr = linux_base + 180, + lsetxattr = linux_base + 181, + fsetxattr = linux_base + 182, + getxattr = linux_base + 183, + lgetxattr = linux_base + 184, + fgetxattr = linux_base + 185, + listxattr = linux_base + 186, + llistxattr = linux_base + 187, + flistxattr = linux_base + 188, + removexattr = linux_base + 189, + lremovexattr = linux_base + 190, + fremovexattr = linux_base + 191, + tkill = linux_base + 192, + futex = linux_base + 194, + sched_setaffinity = linux_base + 195, + sched_getaffinity = linux_base + 196, + cacheflush = linux_base + 197, + cachectl = linux_base + 198, + sysmips = linux_base + 199, + io_setup = linux_base + 200, + io_destroy = linux_base + 201, + io_getevents = linux_base + 202, + io_submit = linux_base + 203, + io_cancel = linux_base + 204, + exit_group = linux_base + 205, + lookup_dcookie = linux_base + 206, + epoll_create = linux_base + 207, + epoll_ctl = linux_base + 208, + epoll_wait = linux_base + 209, + remap_file_pages = linux_base + 210, + rt_sigreturn = linux_base + 211, + set_tid_address = linux_base + 212, + restart_syscall = linux_base + 213, + semtimedop = linux_base + 214, + fadvise64 = linux_base + 215, + timer_create = linux_base + 216, + timer_settime = linux_base + 217, + timer_gettime = linux_base + 218, + timer_getoverrun = linux_base + 219, + timer_delete = linux_base + 220, + clock_settime = linux_base + 221, + clock_gettime = linux_base + 222, + clock_getres = linux_base + 223, + clock_nanosleep = linux_base + 224, + tgkill = linux_base + 225, + utimes = linux_base + 226, + mbind = linux_base + 227, + get_mempolicy = linux_base + 228, + set_mempolicy = linux_base + 229, + mq_open = linux_base + 230, + mq_unlink = linux_base + 231, + mq_timedsend = linux_base + 232, + mq_timedreceive = linux_base + 233, + mq_notify = linux_base + 234, + mq_getsetattr = linux_base + 235, + vserver = linux_base + 236, + waitid = linux_base + 237, + add_key = linux_base + 239, + request_key = linux_base + 240, + keyctl = linux_base + 241, + set_thread_area = linux_base + 242, + inotify_init = linux_base + 243, + inotify_add_watch = linux_base + 244, + inotify_rm_watch = linux_base + 245, + migrate_pages = linux_base + 246, + openat = linux_base + 247, + mkdirat = linux_base + 248, + mknodat = linux_base + 249, + fchownat = linux_base + 250, + futimesat = linux_base + 251, + fstatat64 = linux_base + 252, + unlinkat = linux_base + 253, + renameat = linux_base + 254, + linkat = linux_base + 255, + symlinkat = linux_base + 256, + readlinkat = linux_base + 257, + fchmodat = linux_base + 258, + faccessat = linux_base + 259, + pselect6 = linux_base + 260, + ppoll = linux_base + 261, + unshare = linux_base + 262, + splice = linux_base + 263, + sync_file_range = linux_base + 264, + tee = linux_base + 265, + vmsplice = linux_base + 266, + move_pages = linux_base + 267, + set_robust_list = linux_base + 268, + get_robust_list = linux_base + 269, + kexec_load = linux_base + 270, + getcpu = linux_base + 271, + epoll_pwait = linux_base + 272, + ioprio_set = linux_base + 273, + ioprio_get = linux_base + 274, + utimensat = linux_base + 275, + signalfd = linux_base + 276, + timerfd = linux_base + 277, + eventfd = linux_base + 278, + fallocate = linux_base + 279, + timerfd_create = linux_base + 280, + timerfd_gettime = linux_base + 281, + timerfd_settime = linux_base + 282, + signalfd4 = linux_base + 283, + eventfd2 = linux_base + 284, + epoll_create1 = linux_base + 285, + dup3 = linux_base + 286, + pipe2 = linux_base + 287, + inotify_init1 = linux_base + 288, + preadv = linux_base + 289, + pwritev = linux_base + 290, + rt_tgsigqueueinfo = linux_base + 291, + perf_event_open = linux_base + 292, + accept4 = linux_base + 293, + recvmmsg = linux_base + 294, + fanotify_init = linux_base + 295, + fanotify_mark = linux_base + 296, + prlimit64 = linux_base + 297, + name_to_handle_at = linux_base + 298, + open_by_handle_at = linux_base + 299, + clock_adjtime = linux_base + 300, + syncfs = linux_base + 301, + sendmmsg = linux_base + 302, + setns = linux_base + 303, + process_vm_readv = linux_base + 304, + process_vm_writev = linux_base + 305, + kcmp = linux_base + 306, + finit_module = linux_base + 307, + getdents64 = linux_base + 308, + sched_setattr = linux_base + 309, + sched_getattr = linux_base + 310, + renameat2 = linux_base + 311, + seccomp = linux_base + 312, + getrandom = linux_base + 313, + memfd_create = linux_base + 314, + bpf = linux_base + 315, + execveat = linux_base + 316, + userfaultfd = linux_base + 317, + membarrier = linux_base + 318, + mlock2 = linux_base + 319, + copy_file_range = linux_base + 320, + preadv2 = linux_base + 321, + pwritev2 = linux_base + 322, + pkey_mprotect = linux_base + 323, + pkey_alloc = linux_base + 324, + pkey_free = linux_base + 325, + statx = linux_base + 326, + rseq = linux_base + 327, + io_pgetevents = linux_base + 328, + pidfd_send_signal = linux_base + 424, + io_uring_setup = linux_base + 425, + io_uring_enter = linux_base + 426, + io_uring_register = linux_base + 427, + open_tree = linux_base + 428, + move_mount = linux_base + 429, + fsopen = linux_base + 430, + fsconfig = linux_base + 431, + fsmount = linux_base + 432, + fspick = linux_base + 433, + pidfd_open = linux_base + 434, + clone3 = linux_base + 435, + close_range = linux_base + 436, + openat2 = linux_base + 437, + pidfd_getfd = linux_base + 438, + faccessat2 = linux_base + 439, + process_madvise = linux_base + 440, + epoll_pwait2 = linux_base + 441, + mount_setattr = linux_base + 442, + quotactl_fd = linux_base + 443, + landlock_create_ruleset = linux_base + 444, + landlock_add_rule = linux_base + 445, + landlock_restrict_self = linux_base + 446, + process_mrelease = linux_base + 448, + futex_waitv = linux_base + 449, + set_mempolicy_home_node = linux_base + 450, + cachestat = linux_base + 451, + fchmodat2 = linux_base + 452, + map_shadow_stack = linux_base + 453, + futex_wake = linux_base + 454, + futex_wait = linux_base + 455, + futex_requeue = linux_base + 456, +}; + +pub const MipsN32 = enum(usize) { + const linux_base = 6000; + + read = linux_base + 0, + write = linux_base + 1, + open = linux_base + 2, + close = linux_base + 3, + stat = linux_base + 4, + fstat = linux_base + 5, + lstat = linux_base + 6, + poll = linux_base + 7, + lseek = linux_base + 8, + mmap = linux_base + 9, + mprotect = linux_base + 10, + munmap = linux_base + 11, + brk = linux_base + 12, + rt_sigaction = linux_base + 13, + rt_sigprocmask = linux_base + 14, + ioctl = linux_base + 15, + pread64 = linux_base + 16, + pwrite64 = linux_base + 17, + readv = linux_base + 18, + writev = linux_base + 19, + access = linux_base + 20, + pipe = linux_base + 21, + newselect = linux_base + 22, + sched_yield = linux_base + 23, + mremap = linux_base + 24, + msync = linux_base + 25, + mincore = linux_base + 26, + madvise = linux_base + 27, + shmget = linux_base + 28, + shmat = linux_base + 29, + shmctl = linux_base + 30, + dup = linux_base + 31, + dup2 = linux_base + 32, + pause = linux_base + 33, + nanosleep = linux_base + 34, + getitimer = linux_base + 35, + setitimer = linux_base + 36, + alarm = linux_base + 37, + getpid = linux_base + 38, + sendfile = linux_base + 39, + socket = linux_base + 40, + connect = linux_base + 41, + accept = linux_base + 42, + sendto = linux_base + 43, + recvfrom = linux_base + 44, + sendmsg = linux_base + 45, + recvmsg = linux_base + 46, + shutdown = linux_base + 47, + bind = linux_base + 48, + listen = linux_base + 49, + getsockname = linux_base + 50, + getpeername = linux_base + 51, + socketpair = linux_base + 52, + setsockopt = linux_base + 53, + getsockopt = linux_base + 54, + clone = linux_base + 55, + fork = linux_base + 56, + execve = linux_base + 57, + exit = linux_base + 58, + wait4 = linux_base + 59, + kill = linux_base + 60, + uname = linux_base + 61, + semget = linux_base + 62, + semop = linux_base + 63, + semctl = linux_base + 64, + shmdt = linux_base + 65, + msgget = linux_base + 66, + msgsnd = linux_base + 67, + msgrcv = linux_base + 68, + msgctl = linux_base + 69, + fcntl = linux_base + 70, + flock = linux_base + 71, + fsync = linux_base + 72, + fdatasync = linux_base + 73, + truncate = linux_base + 74, + ftruncate = linux_base + 75, + getdents = linux_base + 76, + getcwd = linux_base + 77, + chdir = linux_base + 78, + fchdir = linux_base + 79, + rename = linux_base + 80, + mkdir = linux_base + 81, + rmdir = linux_base + 82, + creat = linux_base + 83, + link = linux_base + 84, + unlink = linux_base + 85, + symlink = linux_base + 86, + readlink = linux_base + 87, + chmod = linux_base + 88, + fchmod = linux_base + 89, + chown = linux_base + 90, + fchown = linux_base + 91, + lchown = linux_base + 92, + umask = linux_base + 93, + gettimeofday = linux_base + 94, + getrlimit = linux_base + 95, + getrusage = linux_base + 96, + sysinfo = linux_base + 97, + times = linux_base + 98, + ptrace = linux_base + 99, + getuid = linux_base + 100, + syslog = linux_base + 101, + getgid = linux_base + 102, + setuid = linux_base + 103, + setgid = linux_base + 104, + geteuid = linux_base + 105, + getegid = linux_base + 106, + setpgid = linux_base + 107, + getppid = linux_base + 108, + getpgrp = linux_base + 109, + setsid = linux_base + 110, + setreuid = linux_base + 111, + setregid = linux_base + 112, + getgroups = linux_base + 113, + setgroups = linux_base + 114, + setresuid = linux_base + 115, + getresuid = linux_base + 116, + setresgid = linux_base + 117, + getresgid = linux_base + 118, + getpgid = linux_base + 119, + setfsuid = linux_base + 120, + setfsgid = linux_base + 121, + getsid = linux_base + 122, + capget = linux_base + 123, + capset = linux_base + 124, + rt_sigpending = linux_base + 125, + rt_sigtimedwait = linux_base + 126, + rt_sigqueueinfo = linux_base + 127, + rt_sigsuspend = linux_base + 128, + sigaltstack = linux_base + 129, + utime = linux_base + 130, + mknod = linux_base + 131, + personality = linux_base + 132, + ustat = linux_base + 133, + statfs = linux_base + 134, + fstatfs = linux_base + 135, + sysfs = linux_base + 136, + getpriority = linux_base + 137, + setpriority = linux_base + 138, + sched_setparam = linux_base + 139, + sched_getparam = linux_base + 140, + sched_setscheduler = linux_base + 141, + sched_getscheduler = linux_base + 142, + sched_get_priority_max = linux_base + 143, + sched_get_priority_min = linux_base + 144, + sched_rr_get_interval = linux_base + 145, + mlock = linux_base + 146, + munlock = linux_base + 147, + mlockall = linux_base + 148, + munlockall = linux_base + 149, + vhangup = linux_base + 150, + pivot_root = linux_base + 151, + sysctl = linux_base + 152, + prctl = linux_base + 153, + adjtimex = linux_base + 154, + setrlimit = linux_base + 155, + chroot = linux_base + 156, + sync = linux_base + 157, + acct = linux_base + 158, + settimeofday = linux_base + 159, + mount = linux_base + 160, + umount2 = linux_base + 161, + swapon = linux_base + 162, + swapoff = linux_base + 163, + reboot = linux_base + 164, + sethostname = linux_base + 165, + setdomainname = linux_base + 166, + create_module = linux_base + 167, + init_module = linux_base + 168, + delete_module = linux_base + 169, + get_kernel_syms = linux_base + 170, + query_module = linux_base + 171, + quotactl = linux_base + 172, + nfsservctl = linux_base + 173, + getpmsg = linux_base + 174, + putpmsg = linux_base + 175, + afs_syscall = linux_base + 176, + gettid = linux_base + 178, + readahead = linux_base + 179, + setxattr = linux_base + 180, + lsetxattr = linux_base + 181, + fsetxattr = linux_base + 182, + getxattr = linux_base + 183, + lgetxattr = linux_base + 184, + fgetxattr = linux_base + 185, + listxattr = linux_base + 186, + llistxattr = linux_base + 187, + flistxattr = linux_base + 188, + removexattr = linux_base + 189, + lremovexattr = linux_base + 190, + fremovexattr = linux_base + 191, + tkill = linux_base + 192, + futex = linux_base + 194, + sched_setaffinity = linux_base + 195, + sched_getaffinity = linux_base + 196, + cacheflush = linux_base + 197, + cachectl = linux_base + 198, + sysmips = linux_base + 199, + io_setup = linux_base + 200, + io_destroy = linux_base + 201, + io_getevents = linux_base + 202, + io_submit = linux_base + 203, + io_cancel = linux_base + 204, + exit_group = linux_base + 205, + lookup_dcookie = linux_base + 206, + epoll_create = linux_base + 207, + epoll_ctl = linux_base + 208, + epoll_wait = linux_base + 209, + remap_file_pages = linux_base + 210, + rt_sigreturn = linux_base + 211, + fcntl64 = linux_base + 212, + set_tid_address = linux_base + 213, + restart_syscall = linux_base + 214, + semtimedop = linux_base + 215, + fadvise64 = linux_base + 216, + statfs64 = linux_base + 217, + fstatfs64 = linux_base + 218, + sendfile64 = linux_base + 219, + timer_create = linux_base + 220, + timer_settime = linux_base + 221, + timer_gettime = linux_base + 222, + timer_getoverrun = linux_base + 223, + timer_delete = linux_base + 224, + clock_settime = linux_base + 225, + clock_gettime = linux_base + 226, + clock_getres = linux_base + 227, + clock_nanosleep = linux_base + 228, + tgkill = linux_base + 229, + utimes = linux_base + 230, + mbind = linux_base + 231, + get_mempolicy = linux_base + 232, + set_mempolicy = linux_base + 233, + mq_open = linux_base + 234, + mq_unlink = linux_base + 235, + mq_timedsend = linux_base + 236, + mq_timedreceive = linux_base + 237, + mq_notify = linux_base + 238, + mq_getsetattr = linux_base + 239, + vserver = linux_base + 240, + waitid = linux_base + 241, + add_key = linux_base + 243, + request_key = linux_base + 244, + keyctl = linux_base + 245, + set_thread_area = linux_base + 246, + inotify_init = linux_base + 247, + inotify_add_watch = linux_base + 248, + inotify_rm_watch = linux_base + 249, + migrate_pages = linux_base + 250, + openat = linux_base + 251, + mkdirat = linux_base + 252, + mknodat = linux_base + 253, + fchownat = linux_base + 254, + futimesat = linux_base + 255, + fstatat64 = linux_base + 256, + unlinkat = linux_base + 257, + renameat = linux_base + 258, + linkat = linux_base + 259, + symlinkat = linux_base + 260, + readlinkat = linux_base + 261, + fchmodat = linux_base + 262, + faccessat = linux_base + 263, + pselect6 = linux_base + 264, + ppoll = linux_base + 265, + unshare = linux_base + 266, + splice = linux_base + 267, + sync_file_range = linux_base + 268, + tee = linux_base + 269, + vmsplice = linux_base + 270, + move_pages = linux_base + 271, + set_robust_list = linux_base + 272, + get_robust_list = linux_base + 273, + kexec_load = linux_base + 274, + getcpu = linux_base + 275, + epoll_pwait = linux_base + 276, + ioprio_set = linux_base + 277, + ioprio_get = linux_base + 278, + utimensat = linux_base + 279, + signalfd = linux_base + 280, + timerfd = linux_base + 281, + eventfd = linux_base + 282, + fallocate = linux_base + 283, + timerfd_create = linux_base + 284, + timerfd_gettime = linux_base + 285, + timerfd_settime = linux_base + 286, + signalfd4 = linux_base + 287, + eventfd2 = linux_base + 288, + epoll_create1 = linux_base + 289, + dup3 = linux_base + 290, + pipe2 = linux_base + 291, + inotify_init1 = linux_base + 292, + preadv = linux_base + 293, + pwritev = linux_base + 294, + rt_tgsigqueueinfo = linux_base + 295, + perf_event_open = linux_base + 296, + accept4 = linux_base + 297, + recvmmsg = linux_base + 298, + getdents64 = linux_base + 299, + fanotify_init = linux_base + 300, + fanotify_mark = linux_base + 301, + prlimit64 = linux_base + 302, + name_to_handle_at = linux_base + 303, + open_by_handle_at = linux_base + 304, + clock_adjtime = linux_base + 305, + syncfs = linux_base + 306, + sendmmsg = linux_base + 307, + setns = linux_base + 308, + process_vm_readv = linux_base + 309, + process_vm_writev = linux_base + 310, + kcmp = linux_base + 311, + finit_module = linux_base + 312, + sched_setattr = linux_base + 313, + sched_getattr = linux_base + 314, + renameat2 = linux_base + 315, + seccomp = linux_base + 316, + getrandom = linux_base + 317, + memfd_create = linux_base + 318, + bpf = linux_base + 319, + execveat = linux_base + 320, + userfaultfd = linux_base + 321, + membarrier = linux_base + 322, + mlock2 = linux_base + 323, + copy_file_range = linux_base + 324, + preadv2 = linux_base + 325, + pwritev2 = linux_base + 326, + pkey_mprotect = linux_base + 327, + pkey_alloc = linux_base + 328, + pkey_free = linux_base + 329, + statx = linux_base + 330, + rseq = linux_base + 331, + io_pgetevents = linux_base + 332, + clock_gettime64 = linux_base + 403, + clock_settime64 = linux_base + 404, + clock_adjtime64 = linux_base + 405, + clock_getres_time64 = linux_base + 406, + clock_nanosleep_time64 = linux_base + 407, + timer_gettime64 = linux_base + 408, + timer_settime64 = linux_base + 409, + timerfd_gettime64 = linux_base + 410, + timerfd_settime64 = linux_base + 411, + utimensat_time64 = linux_base + 412, + pselect6_time64 = linux_base + 413, + ppoll_time64 = linux_base + 414, + io_pgetevents_time64 = linux_base + 416, + recvmmsg_time64 = linux_base + 417, + mq_timedsend_time64 = linux_base + 418, + mq_timedreceive_time64 = linux_base + 419, + semtimedop_time64 = linux_base + 420, + rt_sigtimedwait_time64 = linux_base + 421, + futex_time64 = linux_base + 422, + sched_rr_get_interval_time64 = linux_base + 423, + pidfd_send_signal = linux_base + 424, + io_uring_setup = linux_base + 425, + io_uring_enter = linux_base + 426, + io_uring_register = linux_base + 427, + open_tree = linux_base + 428, + move_mount = linux_base + 429, + fsopen = linux_base + 430, + fsconfig = linux_base + 431, + fsmount = linux_base + 432, + fspick = linux_base + 433, + pidfd_open = linux_base + 434, + clone3 = linux_base + 435, + close_range = linux_base + 436, + openat2 = linux_base + 437, + pidfd_getfd = linux_base + 438, + faccessat2 = linux_base + 439, + process_madvise = linux_base + 440, + epoll_pwait2 = linux_base + 441, + mount_setattr = linux_base + 442, + quotactl_fd = linux_base + 443, + landlock_create_ruleset = linux_base + 444, + landlock_add_rule = linux_base + 445, + landlock_restrict_self = linux_base + 446, + process_mrelease = linux_base + 448, + futex_waitv = linux_base + 449, + set_mempolicy_home_node = linux_base + 450, + cachestat = linux_base + 451, + fchmodat2 = linux_base + 452, + map_shadow_stack = linux_base + 453, + futex_wake = linux_base + 454, + futex_wait = linux_base + 455, + futex_requeue = linux_base + 456, }; pub const PowerPC = enum(usize) { @@ -3279,6 +4521,766 @@ pub const PowerPC64 = enum(usize) { futex_requeue = 456, }; +pub const S390x = enum(usize) { + exit = 1, + fork = 2, + read = 3, + write = 4, + open = 5, + close = 6, + restart_syscall = 7, + creat = 8, + link = 9, + unlink = 10, + execve = 11, + chdir = 12, + mknod = 14, + chmod = 15, + lseek = 19, + getpid = 20, + mount = 21, + umount = 22, + ptrace = 26, + alarm = 27, + pause = 29, + utime = 30, + access = 33, + nice = 34, + sync = 36, + kill = 37, + rename = 38, + mkdir = 39, + rmdir = 40, + dup = 41, + pipe = 42, + times = 43, + brk = 45, + signal = 48, + acct = 51, + umount2 = 52, + ioctl = 54, + fcntl = 55, + setpgid = 57, + umask = 60, + chroot = 61, + ustat = 62, + dup2 = 63, + getppid = 64, + getpgrp = 65, + setsid = 66, + sigaction = 67, + sigsuspend = 72, + sigpending = 73, + sethostname = 74, + setrlimit = 75, + getrusage = 77, + gettimeofday = 78, + settimeofday = 79, + symlink = 83, + readlink = 85, + uselib = 86, + swapon = 87, + reboot = 88, + readdir = 89, + mmap = 90, + munmap = 91, + truncate = 92, + ftruncate = 93, + fchmod = 94, + getpriority = 96, + setpriority = 97, + statfs = 99, + fstatfs = 100, + socketcall = 102, + syslog = 103, + setitimer = 104, + getitimer = 105, + stat = 106, + lstat = 107, + fstat = 108, + lookup_dcookie = 110, + vhangup = 111, + idle = 112, + wait4 = 114, + swapoff = 115, + sysinfo = 116, + ipc = 117, + fsync = 118, + sigreturn = 119, + clone = 120, + setdomainname = 121, + uname = 122, + adjtimex = 124, + mprotect = 125, + sigprocmask = 126, + create_module = 127, + init_module = 128, + delete_module = 129, + get_kernel_syms = 130, + quotactl = 131, + getpgid = 132, + fchdir = 133, + bdflush = 134, + sysfs = 135, + personality = 136, + afs_syscall = 137, + getdents = 141, + select = 142, + flock = 143, + msync = 144, + readv = 145, + writev = 146, + getsid = 147, + fdatasync = 148, + sysctl = 149, + mlock = 150, + munlock = 151, + mlockall = 152, + munlockall = 153, + sched_setparam = 154, + sched_getparam = 155, + sched_setscheduler = 156, + sched_getscheduler = 157, + sched_yield = 158, + sched_get_priority_max = 159, + sched_get_priority_min = 160, + sched_rr_get_interval = 161, + nanosleep = 162, + mremap = 163, + query_module = 167, + poll = 168, + nfsservctl = 169, + prctl = 172, + rt_sigreturn = 173, + rt_sigaction = 174, + rt_sigprocmask = 175, + rt_sigpending = 176, + rt_sigtimedwait = 177, + rt_sigqueueinfo = 178, + rt_sigsuspend = 179, + pread64 = 180, + pwrite64 = 181, + getcwd = 183, + capget = 184, + capset = 185, + sigaltstack = 186, + sendfile = 187, + getpmsg = 188, + putpmsg = 189, + vfork = 190, + getrlimit = 191, + lchown = 198, + getuid = 199, + getgid = 200, + geteuid = 201, + getegid = 202, + setreuid = 203, + setregid = 204, + getgroups = 205, + setgroups = 206, + fchown = 207, + setresuid = 208, + getresuid = 209, + setresgid = 210, + getresgid = 211, + chown = 212, + setuid = 213, + setgid = 214, + setfsuid = 215, + setfsgid = 216, + pivot_root = 217, + mincore = 218, + madvise = 219, + getdents64 = 220, + readahead = 222, + setxattr = 224, + lsetxattr = 225, + fsetxattr = 226, + getxattr = 227, + lgetxattr = 228, + fgetxattr = 229, + listxattr = 230, + llistxattr = 231, + flistxattr = 232, + removexattr = 233, + lremovexattr = 234, + fremovexattr = 235, + gettid = 236, + tkill = 237, + futex = 238, + sched_setaffinity = 239, + sched_getaffinity = 240, + tgkill = 241, + io_setup = 243, + io_destroy = 244, + io_getevents = 245, + io_submit = 246, + io_cancel = 247, + exit_group = 248, + epoll_create = 249, + epoll_ctl = 250, + epoll_wait = 251, + set_tid_address = 252, + fadvise64 = 253, + timer_create = 254, + timer_settime = 255, + timer_gettime = 256, + timer_getoverrun = 257, + timer_delete = 258, + clock_settime = 259, + clock_gettime = 260, + clock_getres = 261, + clock_nanosleep = 262, + statfs64 = 265, + fstatfs64 = 266, + remap_file_pages = 267, + mbind = 268, + get_mempolicy = 269, + set_mempolicy = 270, + mq_open = 271, + mq_unlink = 272, + mq_timedsend = 273, + mq_timedreceive = 274, + mq_notify = 275, + mq_getsetattr = 276, + kexec_load = 277, + add_key = 278, + request_key = 279, + keyctl = 280, + waitid = 281, + ioprio_set = 282, + ioprio_get = 283, + inotify_init = 284, + inotify_add_watch = 285, + inotify_rm_watch = 286, + migrate_pages = 287, + openat = 288, + mkdirat = 289, + mknodat = 290, + fchownat = 291, + futimesat = 292, + fstatat64 = 293, + unlinkat = 294, + renameat = 295, + linkat = 296, + symlinkat = 297, + readlinkat = 298, + fchmodat = 299, + faccessat = 300, + pselect6 = 301, + ppoll = 302, + unshare = 303, + set_robust_list = 304, + get_robust_list = 305, + splice = 306, + sync_file_range = 307, + tee = 308, + vmsplice = 309, + move_pages = 310, + getcpu = 311, + epoll_pwait = 312, + utimes = 313, + fallocate = 314, + utimensat = 315, + signalfd = 316, + timerfd = 317, + eventfd = 318, + timerfd_create = 319, + timerfd_settime = 320, + timerfd_gettime = 321, + signalfd4 = 322, + eventfd2 = 323, + inotify_init1 = 324, + pipe2 = 325, + dup3 = 326, + epoll_create1 = 327, + preadv = 328, + pwritev = 329, + rt_tgsigqueueinfo = 330, + perf_event_open = 331, + fanotify_init = 332, + fanotify_mark = 333, + prlimit64 = 334, + name_to_handle_at = 335, + open_by_handle_at = 336, + clock_adjtime = 337, + syncfs = 338, + setns = 339, + process_vm_readv = 340, + process_vm_writev = 341, + s390_runtime_instr = 342, + kcmp = 343, + finit_module = 344, + sched_setattr = 345, + sched_getattr = 346, + renameat2 = 347, + seccomp = 348, + getrandom = 349, + memfd_create = 350, + bpf = 351, + s390_pci_mmio_write = 352, + s390_pci_mmio_read = 353, + execveat = 354, + userfaultfd = 355, + membarrier = 356, + recvmmsg = 357, + sendmmsg = 358, + socket = 359, + socketpair = 360, + bind = 361, + connect = 362, + listen = 363, + accept4 = 364, + getsockopt = 365, + setsockopt = 366, + getsockname = 367, + getpeername = 368, + sendto = 369, + sendmsg = 370, + recvfrom = 371, + recvmsg = 372, + shutdown = 373, + mlock2 = 374, + copy_file_range = 375, + preadv2 = 376, + pwritev2 = 377, + s390_guarded_storage = 378, + statx = 379, + s390_sthyi = 380, + kexec_file_load = 381, + io_pgetevents = 382, + rseq = 383, + pkey_mprotect = 384, + pkey_alloc = 385, + pkey_free = 386, + semtimedop = 392, + semget = 393, + semctl = 394, + shmget = 395, + shmctl = 396, + shmat = 397, + shmdt = 398, + msgget = 399, + msgsnd = 400, + msgrcv = 401, + msgctl = 402, + pidfd_send_signal = 424, + io_uring_setup = 425, + io_uring_enter = 426, + io_uring_register = 427, + open_tree = 428, + move_mount = 429, + fsopen = 430, + fsconfig = 431, + fsmount = 432, + fspick = 433, + pidfd_open = 434, + clone3 = 435, + close_range = 436, + openat2 = 437, + pidfd_getfd = 438, + faccessat2 = 439, + process_madvise = 440, + epoll_pwait2 = 441, + mount_setattr = 442, + quotactl_fd = 443, + landlock_create_ruleset = 444, + landlock_add_rule = 445, + landlock_restrict_self = 446, + memfd_secret = 447, + process_mrelease = 448, + futex_waitv = 449, + set_mempolicy_home_node = 450, + cachestat = 451, + fchmodat2 = 452, + map_shadow_stack = 453, + futex_wake = 454, + futex_wait = 455, + futex_requeue = 456, +}; + +pub const Xtensa = enum(usize) { + spill = 0, + xtensa = 1, + open = 8, + close = 9, + dup = 10, + dup2 = 11, + read = 12, + write = 13, + select = 14, + lseek = 15, + poll = 16, + llseek = 17, + epoll_wait = 18, + epoll_ctl = 19, + epoll_create = 20, + creat = 21, + truncate = 22, + ftruncate = 23, + readv = 24, + writev = 25, + fsync = 26, + fdatasync = 27, + truncate64 = 28, + ftruncate64 = 29, + pread64 = 30, + pwrite64 = 31, + link = 32, + rename = 33, + symlink = 34, + readlink = 35, + mknod = 36, + pipe = 37, + unlink = 38, + rmdir = 39, + mkdir = 40, + chdir = 41, + fchdir = 42, + getcwd = 43, + chmod = 44, + chown = 45, + stat = 46, + stat64 = 47, + lchown = 48, + lstat = 49, + lstat64 = 50, + fchmod = 52, + fchown = 53, + fstat = 54, + fstat64 = 55, + flock = 56, + access = 57, + umask = 58, + getdents = 59, + getdents64 = 60, + fcntl64 = 61, + fallocate = 62, + fadvise64_64 = 63, + utime = 64, + utimes = 65, + ioctl = 66, + fcntl = 67, + setxattr = 68, + getxattr = 69, + listxattr = 70, + removexattr = 71, + lsetxattr = 72, + lgetxattr = 73, + llistxattr = 74, + lremovexattr = 75, + fsetxattr = 76, + fgetxattr = 77, + flistxattr = 78, + fremovexattr = 79, + mmap2 = 80, + munmap = 81, + mprotect = 82, + brk = 83, + mlock = 84, + munlock = 85, + mlockall = 86, + munlockall = 87, + mremap = 88, + msync = 89, + mincore = 90, + madvise = 91, + shmget = 92, + shmat = 93, + shmctl = 94, + shmdt = 95, + socket = 96, + setsockopt = 97, + getsockopt = 98, + shutdown = 99, + bind = 100, + connect = 101, + listen = 102, + accept = 103, + getsockname = 104, + getpeername = 105, + sendmsg = 106, + recvmsg = 107, + send = 108, + recv = 109, + sendto = 110, + recvfrom = 111, + socketpair = 112, + sendfile = 113, + sendfile64 = 114, + sendmmsg = 115, + clone = 116, + execve = 117, + exit = 118, + exit_group = 119, + getpid = 120, + wait4 = 121, + waitid = 122, + kill = 123, + tkill = 124, + tgkill = 125, + set_tid_address = 126, + gettid = 127, + setsid = 128, + getsid = 129, + prctl = 130, + personality = 131, + getpriority = 132, + setpriority = 133, + setitimer = 134, + getitimer = 135, + setuid = 136, + getuid = 137, + setgid = 138, + getgid = 139, + geteuid = 140, + getegid = 141, + setreuid = 142, + setregid = 143, + setresuid = 144, + getresuid = 145, + setresgid = 146, + getresgid = 147, + setpgid = 148, + getpgid = 149, + getppid = 150, + getpgrp = 151, + times = 154, + acct = 155, + sched_setaffinity = 156, + sched_getaffinity = 157, + capget = 158, + capset = 159, + ptrace = 160, + semtimedop = 161, + semget = 162, + semop = 163, + semctl = 164, + msgget = 166, + msgsnd = 167, + msgrcv = 168, + msgctl = 169, + umount2 = 171, + mount = 172, + swapon = 173, + chroot = 174, + pivot_root = 175, + umount = 176, + swapoff = 177, + sync = 178, + syncfs = 179, + setfsuid = 180, + setfsgid = 181, + sysfs = 182, + ustat = 183, + statfs = 184, + fstatfs = 185, + statfs64 = 186, + fstatfs64 = 187, + setrlimit = 188, + getrlimit = 189, + getrusage = 190, + futex = 191, + gettimeofday = 192, + settimeofday = 193, + adjtimex = 194, + nanosleep = 195, + getgroups = 196, + setgroups = 197, + sethostname = 198, + setdomainname = 199, + syslog = 200, + vhangup = 201, + uselib = 202, + reboot = 203, + quotactl = 204, + nfsservctl = 205, + sysctl = 206, + bdflush = 207, + uname = 208, + sysinfo = 209, + init_module = 210, + delete_module = 211, + sched_setparam = 212, + sched_getparam = 213, + sched_setscheduler = 214, + sched_getscheduler = 215, + sched_get_priority_max = 216, + sched_get_priority_min = 217, + sched_rr_get_interval = 218, + sched_yield = 219, + restart_syscall = 223, + sigaltstack = 224, + rt_sigreturn = 225, + rt_sigaction = 226, + rt_sigprocmask = 227, + rt_sigpending = 228, + rt_sigtimedwait = 229, + rt_sigqueueinfo = 230, + rt_sigsuspend = 231, + mq_open = 232, + mq_unlink = 233, + mq_timedsend = 234, + mq_timedreceive = 235, + mq_notify = 236, + mq_getsetattr = 237, + io_setup = 239, + io_destroy = 240, + io_submit = 241, + io_getevents = 242, + io_cancel = 243, + clock_settime = 244, + clock_gettime = 245, + clock_getres = 246, + clock_nanosleep = 247, + timer_create = 248, + timer_delete = 249, + timer_settime = 250, + timer_gettime = 251, + timer_getoverrun = 252, + lookup_dcookie = 254, + add_key = 256, + request_key = 257, + keyctl = 258, + readahead = 260, + remap_file_pages = 261, + migrate_pages = 262, + mbind = 263, + get_mempolicy = 264, + set_mempolicy = 265, + unshare = 266, + move_pages = 267, + splice = 268, + tee = 269, + vmsplice = 270, + pselect6 = 272, + ppoll = 273, + epoll_pwait = 274, + epoll_create1 = 275, + inotify_init = 276, + inotify_add_watch = 277, + inotify_rm_watch = 278, + inotify_init1 = 279, + getcpu = 280, + kexec_load = 281, + ioprio_set = 282, + ioprio_get = 283, + set_robust_list = 284, + get_robust_list = 285, + openat = 288, + mkdirat = 289, + mknodat = 290, + unlinkat = 291, + renameat = 292, + linkat = 293, + symlinkat = 294, + readlinkat = 295, + utimensat = 296, + fchownat = 297, + futimesat = 298, + fstatat64 = 299, + fchmodat = 300, + faccessat = 301, + signalfd = 304, + eventfd = 306, + recvmmsg = 307, + setns = 308, + signalfd4 = 309, + dup3 = 310, + pipe2 = 311, + timerfd_create = 312, + timerfd_settime = 313, + timerfd_gettime = 314, + eventfd2 = 316, + preadv = 317, + pwritev = 318, + fanotify_init = 320, + fanotify_mark = 321, + process_vm_readv = 322, + process_vm_writev = 323, + name_to_handle_at = 324, + open_by_handle_at = 325, + sync_file_range = 326, + perf_event_open = 327, + rt_tgsigqueueinfo = 328, + clock_adjtime = 329, + prlimit64 = 330, + kcmp = 331, + finit_module = 332, + accept4 = 333, + sched_setattr = 334, + sched_getattr = 335, + renameat2 = 336, + seccomp = 337, + getrandom = 338, + memfd_create = 339, + bpf = 340, + execveat = 341, + userfaultfd = 342, + membarrier = 343, + mlock2 = 344, + copy_file_range = 345, + preadv2 = 346, + pwritev2 = 347, + pkey_mprotect = 348, + pkey_alloc = 349, + pkey_free = 350, + statx = 351, + rseq = 352, + clock_gettime64 = 403, + clock_settime64 = 404, + clock_adjtime64 = 405, + clock_getres_time64 = 406, + clock_nanosleep_time64 = 407, + timer_gettime64 = 408, + timer_settime64 = 409, + timerfd_gettime64 = 410, + timerfd_settime64 = 411, + utimensat_time64 = 412, + pselect6_time64 = 413, + ppoll_time64 = 414, + io_pgetevents_time64 = 416, + recvmmsg_time64 = 417, + mq_timedsend_time64 = 418, + mq_timedreceive_time64 = 419, + semtimedop_time64 = 420, + rt_sigtimedwait_time64 = 421, + futex_time64 = 422, + sched_rr_get_interval_time64 = 423, + pidfd_send_signal = 424, + io_uring_setup = 425, + io_uring_enter = 426, + io_uring_register = 427, + open_tree = 428, + move_mount = 429, + fsopen = 430, + fsconfig = 431, + fsmount = 432, + fspick = 433, + pidfd_open = 434, + clone3 = 435, + close_range = 436, + openat2 = 437, + pidfd_getfd = 438, + faccessat2 = 439, + process_madvise = 440, + epoll_pwait2 = 441, + mount_setattr = 442, + quotactl_fd = 443, + landlock_create_ruleset = 444, + landlock_add_rule = 445, + landlock_restrict_self = 446, + process_mrelease = 448, + futex_waitv = 449, + set_mempolicy_home_node = 450, + cachestat = 451, + fchmodat2 = 452, + map_shadow_stack = 453, + futex_wake = 454, + futex_wait = 455, + futex_requeue = 456, +}; + pub const Arm64 = enum(usize) { io_setup = 0, io_destroy = 1, @@ -4287,7 +6289,7 @@ pub const LoongArch64 = enum(usize) { pwrite64 = 68, preadv = 69, pwritev = 70, - sendfile = 71, + sendfile64 = 71, pselect6 = 72, ppoll = 73, signalfd4 = 74, @@ -4435,7 +6437,7 @@ pub const LoongArch64 = enum(usize) { clone = 220, execve = 221, mmap = 222, - fadvise64 = 223, + fadvise64_64 = 223, swapon = 224, swapoff = 225, mprotect = 226, @@ -4524,3 +6526,1010 @@ pub const LoongArch64 = enum(usize) { futex_wait = 455, futex_requeue = 456, }; + +pub const Arc = enum(usize) { + io_setup = 0, + io_destroy = 1, + io_submit = 2, + io_cancel = 3, + io_getevents_time32 = 4, + setxattr = 5, + lsetxattr = 6, + fsetxattr = 7, + getxattr = 8, + lgetxattr = 9, + fgetxattr = 10, + listxattr = 11, + llistxattr = 12, + flistxattr = 13, + removexattr = 14, + lremovexattr = 15, + fremovexattr = 16, + getcwd = 17, + lookup_dcookie = 18, + eventfd2 = 19, + epoll_create1 = 20, + epoll_ctl = 21, + epoll_pwait = 22, + dup = 23, + dup3 = 24, + fcntl64 = 25, + inotify_init1 = 26, + inotify_add_watch = 27, + inotify_rm_watch = 28, + ioctl = 29, + ioprio_set = 30, + ioprio_get = 31, + flock = 32, + mknodat = 33, + mkdirat = 34, + unlinkat = 35, + symlinkat = 36, + linkat = 37, + renameat = 38, + umount2 = 39, + mount = 40, + pivot_root = 41, + nfsservctl = 42, + statfs64 = 43, + fstatfs64 = 44, + truncate64 = 45, + ftruncate64 = 46, + fallocate = 47, + faccessat = 48, + chdir = 49, + fchdir = 50, + chroot = 51, + fchmod = 52, + fchmodat = 53, + fchownat = 54, + fchown = 55, + openat = 56, + close = 57, + vhangup = 58, + pipe2 = 59, + quotactl = 60, + getdents64 = 61, + llseek = 62, + read = 63, + write = 64, + readv = 65, + writev = 66, + pread64 = 67, + pwrite64 = 68, + preadv = 69, + pwritev = 70, + sendfile64 = 71, + pselect6_time32 = 72, + ppoll_time32 = 73, + signalfd4 = 74, + vmsplice = 75, + splice = 76, + tee = 77, + readlinkat = 78, + fstatat64 = 79, + fstat64 = 80, + sync = 81, + fsync = 82, + fdatasync = 83, + sync_file_range = 84, + timerfd_create = 85, + timerfd_settime32 = 86, + timerfd_gettime32 = 87, + utimensat_time32 = 88, + acct = 89, + capget = 90, + capset = 91, + personality = 92, + exit = 93, + exit_group = 94, + waitid = 95, + set_tid_address = 96, + unshare = 97, + futex_time32 = 98, + set_robust_list = 99, + get_robust_list = 100, + nanosleep_time32 = 101, + getitimer = 102, + setitimer = 103, + kexec_load = 104, + init_module = 105, + delete_module = 106, + timer_create = 107, + timer_gettime32 = 108, + timer_getoverrun = 109, + timer_settime32 = 110, + timer_delete = 111, + clock_settime32 = 112, + clock_gettime32 = 113, + clock_getres_time32 = 114, + clock_nanosleep_time32 = 115, + syslog = 116, + ptrace = 117, + sched_setparam = 118, + sched_setscheduler = 119, + sched_getscheduler = 120, + sched_getparam = 121, + sched_setaffinity = 122, + sched_getaffinity = 123, + sched_yield = 124, + sched_get_priority_max = 125, + sched_get_priority_min = 126, + sched_rr_get_interval_time32 = 127, + restart_syscall = 128, + kill = 129, + tkill = 130, + tgkill = 131, + sigaltstack = 132, + rt_sigsuspend = 133, + rt_sigaction = 134, + rt_sigprocmask = 135, + rt_sigpending = 136, + rt_sigtimedwait_time32 = 137, + rt_sigqueueinfo = 138, + rt_sigreturn = 139, + setpriority = 140, + getpriority = 141, + reboot = 142, + setregid = 143, + setgid = 144, + setreuid = 145, + setuid = 146, + setresuid = 147, + getresuid = 148, + setresgid = 149, + getresgid = 150, + setfsuid = 151, + setfsgid = 152, + times = 153, + setpgid = 154, + getpgid = 155, + getsid = 156, + setsid = 157, + getgroups = 158, + setgroups = 159, + uname = 160, + sethostname = 161, + setdomainname = 162, + getrlimit = 163, + setrlimit = 164, + getrusage = 165, + umask = 166, + prctl = 167, + getcpu = 168, + gettimeofday = 169, + settimeofday = 170, + adjtimex_time32 = 171, + getpid = 172, + getppid = 173, + getuid = 174, + geteuid = 175, + getgid = 176, + getegid = 177, + gettid = 178, + sysinfo = 179, + mq_open = 180, + mq_unlink = 181, + mq_timedsend_time32 = 182, + mq_timedreceive_time32 = 183, + mq_notify = 184, + mq_getsetattr = 185, + msgget = 186, + msgctl = 187, + msgrcv = 188, + msgsnd = 189, + semget = 190, + semctl = 191, + semtimedop_time32 = 192, + semop = 193, + shmget = 194, + shmctl = 195, + shmat = 196, + shmdt = 197, + socket = 198, + socketpair = 199, + bind = 200, + listen = 201, + accept = 202, + connect = 203, + getsockname = 204, + getpeername = 205, + sendto = 206, + recvfrom = 207, + setsockopt = 208, + getsockopt = 209, + shutdown = 210, + sendmsg = 211, + recvmsg = 212, + readahead = 213, + brk = 214, + munmap = 215, + mremap = 216, + add_key = 217, + request_key = 218, + keyctl = 219, + clone = 220, + execve = 221, + mmap_pgoff = 222, + fadvise64_64 = 223, + swapon = 224, + swapoff = 225, + mprotect = 226, + msync = 227, + mlock = 228, + munlock = 229, + mlockall = 230, + munlockall = 231, + mincore = 232, + madvise = 233, + remap_file_pages = 234, + mbind = 235, + get_mempolicy = 236, + set_mempolicy = 237, + migrate_pages = 238, + move_pages = 239, + rt_tgsigqueueinfo = 240, + perf_event_open = 241, + accept4 = 242, + recvmmsg_time32 = 243, + wait4 = 260, + prlimit64 = 261, + fanotify_init = 262, + fanotify_mark = 263, + name_to_handle_at = 264, + open_by_handle_at = 265, + clock_adjtime32 = 266, + syncfs = 267, + setns = 268, + sendmmsg = 269, + process_vm_readv = 270, + process_vm_writev = 271, + kcmp = 272, + finit_module = 273, + sched_setattr = 274, + sched_getattr = 275, + renameat2 = 276, + seccomp = 277, + getrandom = 278, + memfd_create = 279, + bpf = 280, + execveat = 281, + userfaultfd = 282, + membarrier = 283, + mlock2 = 284, + copy_file_range = 285, + preadv2 = 286, + pwritev2 = 287, + pkey_mprotect = 288, + pkey_alloc = 289, + pkey_free = 290, + statx = 291, + io_pgetevents_time32 = 292, + rseq = 293, + kexec_file_load = 294, + clock_gettime = 403, + clock_settime = 404, + clock_adjtime = 405, + clock_getres = 406, + clock_nanosleep = 407, + timer_gettime = 408, + timer_settime = 409, + timerfd_gettime = 410, + timerfd_settime = 411, + utimensat = 412, + pselect6 = 413, + ppoll = 414, + io_pgetevents = 416, + recvmmsg = 417, + mq_timedsend = 418, + mq_timedreceive = 419, + semtimedop = 420, + rt_sigtimedwait = 421, + futex = 422, + sched_rr_get_interval = 423, + pidfd_send_signal = 424, + io_uring_setup = 425, + io_uring_enter = 426, + io_uring_register = 427, + open_tree = 428, + move_mount = 429, + fsopen = 430, + fsconfig = 431, + fsmount = 432, + fspick = 433, + pidfd_open = 434, + clone3 = 435, + close_range = 436, + openat2 = 437, + pidfd_getfd = 438, + faccessat2 = 439, + process_madvise = 440, + epoll_pwait2 = 441, + mount_setattr = 442, + quotactl_fd = 443, + landlock_create_ruleset = 444, + landlock_add_rule = 445, + landlock_restrict_self = 446, + process_mrelease = 448, + futex_waitv = 449, + set_mempolicy_home_node = 450, + cachestat = 451, + fchmodat2 = 452, + map_shadow_stack = 453, + futex_wake = 454, + futex_wait = 455, + futex_requeue = 456, + cacheflush = (244 + 0), + arc_settls = (244 + 1), + arc_gettls = (244 + 2), + arc_usr_cmpxchg = (244 + 4), + sysfs = (244 + 3), +}; + +pub const CSky = enum(usize) { + io_setup = 0, + io_destroy = 1, + io_submit = 2, + io_cancel = 3, + io_getevents_time32 = 4, + setxattr = 5, + lsetxattr = 6, + fsetxattr = 7, + getxattr = 8, + lgetxattr = 9, + fgetxattr = 10, + listxattr = 11, + llistxattr = 12, + flistxattr = 13, + removexattr = 14, + lremovexattr = 15, + fremovexattr = 16, + getcwd = 17, + lookup_dcookie = 18, + eventfd2 = 19, + epoll_create1 = 20, + epoll_ctl = 21, + epoll_pwait = 22, + dup = 23, + dup3 = 24, + fcntl64 = 25, + inotify_init1 = 26, + inotify_add_watch = 27, + inotify_rm_watch = 28, + ioctl = 29, + ioprio_set = 30, + ioprio_get = 31, + flock = 32, + mknodat = 33, + mkdirat = 34, + unlinkat = 35, + symlinkat = 36, + linkat = 37, + umount2 = 39, + mount = 40, + pivot_root = 41, + nfsservctl = 42, + statfs64 = 43, + fstatfs64 = 44, + truncate64 = 45, + ftruncate64 = 46, + fallocate = 47, + faccessat = 48, + chdir = 49, + fchdir = 50, + chroot = 51, + fchmod = 52, + fchmodat = 53, + fchownat = 54, + fchown = 55, + openat = 56, + close = 57, + vhangup = 58, + pipe2 = 59, + quotactl = 60, + getdents64 = 61, + llseek = 62, + read = 63, + write = 64, + readv = 65, + writev = 66, + pread64 = 67, + pwrite64 = 68, + preadv = 69, + pwritev = 70, + sendfile64 = 71, + pselect6_time32 = 72, + ppoll_time32 = 73, + signalfd4 = 74, + vmsplice = 75, + splice = 76, + tee = 77, + readlinkat = 78, + fstatat64 = 79, + fstat64 = 80, + sync = 81, + fsync = 82, + fdatasync = 83, + sync_file_range = 84, + timerfd_create = 85, + timerfd_settime32 = 86, + timerfd_gettime32 = 87, + utimensat_time32 = 88, + acct = 89, + capget = 90, + capset = 91, + personality = 92, + exit = 93, + exit_group = 94, + waitid = 95, + set_tid_address = 96, + unshare = 97, + futex_time32 = 98, + set_robust_list = 99, + get_robust_list = 100, + nanosleep_time32 = 101, + getitimer = 102, + setitimer = 103, + kexec_load = 104, + init_module = 105, + delete_module = 106, + timer_create = 107, + timer_gettime32 = 108, + timer_getoverrun = 109, + timer_settime32 = 110, + timer_delete = 111, + clock_settime32 = 112, + clock_gettime32 = 113, + clock_getres_time32 = 114, + clock_nanosleep_time32 = 115, + syslog = 116, + ptrace = 117, + sched_setparam = 118, + sched_setscheduler = 119, + sched_getscheduler = 120, + sched_getparam = 121, + sched_setaffinity = 122, + sched_getaffinity = 123, + sched_yield = 124, + sched_get_priority_max = 125, + sched_get_priority_min = 126, + sched_rr_get_interval_time32 = 127, + restart_syscall = 128, + kill = 129, + tkill = 130, + tgkill = 131, + sigaltstack = 132, + rt_sigsuspend = 133, + rt_sigaction = 134, + rt_sigprocmask = 135, + rt_sigpending = 136, + rt_sigtimedwait_time32 = 137, + rt_sigqueueinfo = 138, + rt_sigreturn = 139, + setpriority = 140, + getpriority = 141, + reboot = 142, + setregid = 143, + setgid = 144, + setreuid = 145, + setuid = 146, + setresuid = 147, + getresuid = 148, + setresgid = 149, + getresgid = 150, + setfsuid = 151, + setfsgid = 152, + times = 153, + setpgid = 154, + getpgid = 155, + getsid = 156, + setsid = 157, + getgroups = 158, + setgroups = 159, + uname = 160, + sethostname = 161, + setdomainname = 162, + getrlimit = 163, + setrlimit = 164, + getrusage = 165, + umask = 166, + prctl = 167, + getcpu = 168, + gettimeofday = 169, + settimeofday = 170, + adjtimex_time32 = 171, + getpid = 172, + getppid = 173, + getuid = 174, + geteuid = 175, + getgid = 176, + getegid = 177, + gettid = 178, + sysinfo = 179, + mq_open = 180, + mq_unlink = 181, + mq_timedsend_time32 = 182, + mq_timedreceive_time32 = 183, + mq_notify = 184, + mq_getsetattr = 185, + msgget = 186, + msgctl = 187, + msgrcv = 188, + msgsnd = 189, + semget = 190, + semctl = 191, + semtimedop_time32 = 192, + semop = 193, + shmget = 194, + shmctl = 195, + shmat = 196, + shmdt = 197, + socket = 198, + socketpair = 199, + bind = 200, + listen = 201, + accept = 202, + connect = 203, + getsockname = 204, + getpeername = 205, + sendto = 206, + recvfrom = 207, + setsockopt = 208, + getsockopt = 209, + shutdown = 210, + sendmsg = 211, + recvmsg = 212, + readahead = 213, + brk = 214, + munmap = 215, + mremap = 216, + add_key = 217, + request_key = 218, + keyctl = 219, + clone = 220, + execve = 221, + mmap2 = 222, + fadvise64_64 = 223, + swapon = 224, + swapoff = 225, + mprotect = 226, + msync = 227, + mlock = 228, + munlock = 229, + mlockall = 230, + munlockall = 231, + mincore = 232, + madvise = 233, + remap_file_pages = 234, + mbind = 235, + get_mempolicy = 236, + set_mempolicy = 237, + migrate_pages = 238, + move_pages = 239, + rt_tgsigqueueinfo = 240, + perf_event_open = 241, + accept4 = 242, + recvmmsg_time32 = 243, + wait4 = 260, + prlimit64 = 261, + fanotify_init = 262, + fanotify_mark = 263, + name_to_handle_at = 264, + open_by_handle_at = 265, + clock_adjtime32 = 266, + syncfs = 267, + setns = 268, + sendmmsg = 269, + process_vm_readv = 270, + process_vm_writev = 271, + kcmp = 272, + finit_module = 273, + sched_setattr = 274, + sched_getattr = 275, + renameat2 = 276, + seccomp = 277, + getrandom = 278, + memfd_create = 279, + bpf = 280, + execveat = 281, + userfaultfd = 282, + membarrier = 283, + mlock2 = 284, + copy_file_range = 285, + preadv2 = 286, + pwritev2 = 287, + pkey_mprotect = 288, + pkey_alloc = 289, + pkey_free = 290, + statx = 291, + io_pgetevents_time32 = 292, + rseq = 293, + kexec_file_load = 294, + clock_gettime = 403, + clock_settime = 404, + clock_adjtime = 405, + clock_getres = 406, + clock_nanosleep = 407, + timer_gettime = 408, + timer_settime = 409, + timerfd_gettime = 410, + timerfd_settime = 411, + utimensat = 412, + pselect6 = 413, + ppoll = 414, + io_pgetevents = 416, + recvmmsg = 417, + mq_timedsend = 418, + mq_timedreceive = 419, + semtimedop = 420, + rt_sigtimedwait = 421, + futex = 422, + sched_rr_get_interval = 423, + pidfd_send_signal = 424, + io_uring_setup = 425, + io_uring_enter = 426, + io_uring_register = 427, + open_tree = 428, + move_mount = 429, + fsopen = 430, + fsconfig = 431, + fsmount = 432, + fspick = 433, + pidfd_open = 434, + clone3 = 435, + close_range = 436, + openat2 = 437, + pidfd_getfd = 438, + faccessat2 = 439, + process_madvise = 440, + epoll_pwait2 = 441, + mount_setattr = 442, + quotactl_fd = 443, + landlock_create_ruleset = 444, + landlock_add_rule = 445, + landlock_restrict_self = 446, + process_mrelease = 448, + futex_waitv = 449, + set_mempolicy_home_node = 450, + cachestat = 451, + fchmodat2 = 452, + map_shadow_stack = 453, + futex_wake = 454, + futex_wait = 455, + futex_requeue = 456, + set_thread_area = (244 + 0), + cacheflush = (244 + 1), +}; + +pub const Hexagon = enum(usize) { + io_setup = 0, + io_destroy = 1, + io_submit = 2, + io_cancel = 3, + io_getevents_time32 = 4, + setxattr = 5, + lsetxattr = 6, + fsetxattr = 7, + getxattr = 8, + lgetxattr = 9, + fgetxattr = 10, + listxattr = 11, + llistxattr = 12, + flistxattr = 13, + removexattr = 14, + lremovexattr = 15, + fremovexattr = 16, + getcwd = 17, + lookup_dcookie = 18, + eventfd2 = 19, + epoll_create1 = 20, + epoll_ctl = 21, + epoll_pwait = 22, + dup = 23, + dup3 = 24, + fcntl64 = 25, + inotify_init1 = 26, + inotify_add_watch = 27, + inotify_rm_watch = 28, + ioctl = 29, + ioprio_set = 30, + ioprio_get = 31, + flock = 32, + mknodat = 33, + mkdirat = 34, + unlinkat = 35, + symlinkat = 36, + linkat = 37, + renameat = 38, + umount2 = 39, + mount = 40, + pivot_root = 41, + nfsservctl = 42, + statfs64 = 43, + fstatfs64 = 44, + truncate64 = 45, + ftruncate64 = 46, + fallocate = 47, + faccessat = 48, + chdir = 49, + fchdir = 50, + chroot = 51, + fchmod = 52, + fchmodat = 53, + fchownat = 54, + fchown = 55, + openat = 56, + close = 57, + vhangup = 58, + pipe2 = 59, + quotactl = 60, + getdents64 = 61, + llseek = 62, + read = 63, + write = 64, + readv = 65, + writev = 66, + pread64 = 67, + pwrite64 = 68, + preadv = 69, + pwritev = 70, + sendfile64 = 71, + pselect6_time32 = 72, + ppoll_time32 = 73, + signalfd4 = 74, + vmsplice = 75, + splice = 76, + tee = 77, + readlinkat = 78, + fstatat64 = 79, + fstat64 = 80, + sync = 81, + fsync = 82, + fdatasync = 83, + sync_file_range = 84, + timerfd_create = 85, + timerfd_settime32 = 86, + timerfd_gettime32 = 87, + utimensat_time32 = 88, + acct = 89, + capget = 90, + capset = 91, + personality = 92, + exit = 93, + exit_group = 94, + waitid = 95, + set_tid_address = 96, + unshare = 97, + futex_time32 = 98, + set_robust_list = 99, + get_robust_list = 100, + nanosleep_time32 = 101, + getitimer = 102, + setitimer = 103, + kexec_load = 104, + init_module = 105, + delete_module = 106, + timer_create = 107, + timer_gettime32 = 108, + timer_getoverrun = 109, + timer_settime32 = 110, + timer_delete = 111, + clock_settime32 = 112, + clock_gettime32 = 113, + clock_getres_time32 = 114, + clock_nanosleep_time32 = 115, + syslog = 116, + ptrace = 117, + sched_setparam = 118, + sched_setscheduler = 119, + sched_getscheduler = 120, + sched_getparam = 121, + sched_setaffinity = 122, + sched_getaffinity = 123, + sched_yield = 124, + sched_get_priority_max = 125, + sched_get_priority_min = 126, + sched_rr_get_interval_time32 = 127, + restart_syscall = 128, + kill = 129, + tkill = 130, + tgkill = 131, + sigaltstack = 132, + rt_sigsuspend = 133, + rt_sigaction = 134, + rt_sigprocmask = 135, + rt_sigpending = 136, + rt_sigtimedwait_time32 = 137, + rt_sigqueueinfo = 138, + rt_sigreturn = 139, + setpriority = 140, + getpriority = 141, + reboot = 142, + setregid = 143, + setgid = 144, + setreuid = 145, + setuid = 146, + setresuid = 147, + getresuid = 148, + setresgid = 149, + getresgid = 150, + setfsuid = 151, + setfsgid = 152, + times = 153, + setpgid = 154, + getpgid = 155, + getsid = 156, + setsid = 157, + getgroups = 158, + setgroups = 159, + uname = 160, + sethostname = 161, + setdomainname = 162, + getrlimit = 163, + setrlimit = 164, + getrusage = 165, + umask = 166, + prctl = 167, + getcpu = 168, + gettimeofday = 169, + settimeofday = 170, + adjtimex_time32 = 171, + getpid = 172, + getppid = 173, + getuid = 174, + geteuid = 175, + getgid = 176, + getegid = 177, + gettid = 178, + sysinfo = 179, + mq_open = 180, + mq_unlink = 181, + mq_timedsend_time32 = 182, + mq_timedreceive_time32 = 183, + mq_notify = 184, + mq_getsetattr = 185, + msgget = 186, + msgctl = 187, + msgrcv = 188, + msgsnd = 189, + semget = 190, + semctl = 191, + semtimedop_time32 = 192, + semop = 193, + shmget = 194, + shmctl = 195, + shmat = 196, + shmdt = 197, + socket = 198, + socketpair = 199, + bind = 200, + listen = 201, + accept = 202, + connect = 203, + getsockname = 204, + getpeername = 205, + sendto = 206, + recvfrom = 207, + setsockopt = 208, + getsockopt = 209, + shutdown = 210, + sendmsg = 211, + recvmsg = 212, + readahead = 213, + brk = 214, + munmap = 215, + mremap = 216, + add_key = 217, + request_key = 218, + keyctl = 219, + clone = 220, + execve = 221, + mmap_pgoff = 222, + fadvise64_64 = 223, + swapon = 224, + swapoff = 225, + mprotect = 226, + msync = 227, + mlock = 228, + munlock = 229, + mlockall = 230, + munlockall = 231, + mincore = 232, + madvise = 233, + remap_file_pages = 234, + mbind = 235, + get_mempolicy = 236, + set_mempolicy = 237, + migrate_pages = 238, + move_pages = 239, + rt_tgsigqueueinfo = 240, + perf_event_open = 241, + accept4 = 242, + recvmmsg_time32 = 243, + wait4 = 260, + prlimit64 = 261, + fanotify_init = 262, + fanotify_mark = 263, + name_to_handle_at = 264, + open_by_handle_at = 265, + clock_adjtime32 = 266, + syncfs = 267, + setns = 268, + sendmmsg = 269, + process_vm_readv = 270, + process_vm_writev = 271, + kcmp = 272, + finit_module = 273, + sched_setattr = 274, + sched_getattr = 275, + renameat2 = 276, + seccomp = 277, + getrandom = 278, + memfd_create = 279, + bpf = 280, + execveat = 281, + userfaultfd = 282, + membarrier = 283, + mlock2 = 284, + copy_file_range = 285, + preadv2 = 286, + pwritev2 = 287, + pkey_mprotect = 288, + pkey_alloc = 289, + pkey_free = 290, + statx = 291, + io_pgetevents_time32 = 292, + rseq = 293, + kexec_file_load = 294, + clock_gettime = 403, + clock_settime = 404, + clock_adjtime = 405, + clock_getres = 406, + clock_nanosleep = 407, + timer_gettime = 408, + timer_settime = 409, + timerfd_gettime = 410, + timerfd_settime = 411, + utimensat = 412, + pselect6 = 413, + ppoll = 414, + io_pgetevents = 416, + recvmmsg = 417, + mq_timedsend = 418, + mq_timedreceive = 419, + semtimedop = 420, + rt_sigtimedwait = 421, + futex = 422, + sched_rr_get_interval = 423, + pidfd_send_signal = 424, + io_uring_setup = 425, + io_uring_enter = 426, + io_uring_register = 427, + open_tree = 428, + move_mount = 429, + fsopen = 430, + fsconfig = 431, + fsmount = 432, + fspick = 433, + pidfd_open = 434, + close_range = 436, + openat2 = 437, + pidfd_getfd = 438, + faccessat2 = 439, + process_madvise = 440, + epoll_pwait2 = 441, + mount_setattr = 442, + quotactl_fd = 443, + landlock_create_ruleset = 444, + landlock_add_rule = 445, + landlock_restrict_self = 446, + process_mrelease = 448, + futex_waitv = 449, + set_mempolicy_home_node = 450, + cachestat = 451, + fchmodat2 = 452, + map_shadow_stack = 453, + futex_wake = 454, + futex_wait = 455, + futex_requeue = 456, +}; From 7e0f9c45f2ee6980cf6582c8f7ef09f3e10f200e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Fri, 26 Jul 2024 06:01:06 +0200 Subject: [PATCH 027/266] std.os.linux: Adjust for rename of mips syscall enums. --- lib/std/os/linux.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index 4f3db110b2..2e77d342ae 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -108,8 +108,8 @@ pub const SYS = switch (@import("builtin").cpu.arch) { .riscv32 => syscalls.RiscV32, .riscv64 => syscalls.RiscV64, .sparc64 => syscalls.Sparc64, - .mips, .mipsel => syscalls.Mips, - .mips64, .mips64el => syscalls.Mips64, + .mips, .mipsel => syscalls.MipsO32, + .mips64, .mips64el => syscalls.MipsN64, .powerpc, .powerpcle => syscalls.PowerPC, .powerpc64, .powerpc64le => syscalls.PowerPC64, else => @compileError("The Zig Standard Library is missing syscall definitions for the target CPU architecture"), From 876383cb2a003484a81c47e60b5034b342fa9b14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Fri, 26 Jul 2024 06:14:04 +0200 Subject: [PATCH 028/266] std.os.linux: Hook up newly added syscall enums. --- lib/std/os/linux.zig | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index 2e77d342ae..c7732128b2 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -104,14 +104,24 @@ pub const SYS = switch (@import("builtin").cpu.arch) { .x86 => syscalls.X86, .x86_64 => syscalls.X64, .aarch64, .aarch64_be => syscalls.Arm64, + .arc => syscalls.Arc, .arm, .armeb, .thumb, .thumbeb => syscalls.Arm, + .csky => syscalls.CSky, + .hexagon => syscalls.Hexagon, .riscv32 => syscalls.RiscV32, .riscv64 => syscalls.RiscV64, + .sparc, .sparcel => syscalls.Sparc, .sparc64 => syscalls.Sparc64, + .m68k => syscalls.M68k, .mips, .mipsel => syscalls.MipsO32, - .mips64, .mips64el => syscalls.MipsN64, + .mips64, .mips64el => if (builtin.abi == .gnuabin32) + syscalls.MipsN32 + else + syscalls.MipsN64, .powerpc, .powerpcle => syscalls.PowerPC, .powerpc64, .powerpc64le => syscalls.PowerPC64, + .s390x => syscalls.S390x, + .xtensa => syscalls.Xtensa, else => @compileError("The Zig Standard Library is missing syscall definitions for the target CPU architecture"), }; From b52e05426160c193c29e01072736a319e5fb6f24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Tue, 23 Jul 2024 00:57:25 +0200 Subject: [PATCH 029/266] std.os.linux.tls: Refactor and improve documentation. * Elaborate on the sub-variants of Variant I. * Clarify the use of the TCB term. * Rename a bunch of stuff to be more accurate/descriptive. * Follow Zig's style around namespacing more. * Use a structure for the ABI TCB. No functional change intended. --- lib/std/Thread.zig | 8 +- lib/std/os/linux/tls.zig | 455 +++++++++++++++++++++++---------------- lib/std/start.zig | 2 +- 3 files changed, 273 insertions(+), 192 deletions(-) diff --git a/lib/std/Thread.zig b/lib/std/Thread.zig index c9c867090d..224a309730 100644 --- a/lib/std/Thread.zig +++ b/lib/std/Thread.zig @@ -1261,9 +1261,9 @@ const LinuxThreadImpl = struct { bytes = std.mem.alignForward(usize, bytes, page_size); stack_offset = bytes; - bytes = std.mem.alignForward(usize, bytes, linux.tls.tls_image.alloc_align); + bytes = std.mem.alignForward(usize, bytes, linux.tls.area_desc.alignment); tls_offset = bytes; - bytes += linux.tls.tls_image.alloc_size; + bytes += linux.tls.area_desc.size; bytes = std.mem.alignForward(usize, bytes, @alignOf(Instance)); instance_offset = bytes; @@ -1304,12 +1304,12 @@ const LinuxThreadImpl = struct { }; // Prepare the TLS segment and prepare a user_desc struct when needed on x86 - var tls_ptr = linux.tls.prepareTLS(mapped[tls_offset..]); + var tls_ptr = linux.tls.prepareArea(mapped[tls_offset..]); var user_desc: if (target.cpu.arch == .x86) linux.user_desc else void = undefined; if (target.cpu.arch == .x86) { defer tls_ptr = @intFromPtr(&user_desc); user_desc = .{ - .entry_number = linux.tls.tls_image.gdt_entry_number, + .entry_number = linux.tls.area_desc.gdt_entry_number, .base_addr = tls_ptr, .limit = 0xfffff, .flags = .{ diff --git a/lib/std/os/linux/tls.zig b/lib/std/os/linux/tls.zig index f6aef8aa91..feaeb8699a 100644 --- a/lib/std/os/linux/tls.zig +++ b/lib/std/os/linux/tls.zig @@ -1,3 +1,14 @@ +//! This file implements the two TLS variants [1] used by ELF-based systems. Note that, in reality, +//! Variant I has two sub-variants. +//! +//! It is important to understand that the term TCB (Thread Control Block) is overloaded here. +//! Official ABI documentation uses it simply to mean the ABI TCB, i.e. a small area of ABI-defined +//! data, usually one or two words (see the `AbiTcb` type below). People will also often use TCB to +//! refer to the libc TCB, which can be any size and contain anything. (One could even omit it!) We +//! refer to the latter as the Zig TCB; see the `ZigTcb` type below. +//! +//! [1] https://www.akkadia.org/drepper/tls.pdf + const std = @import("std"); const mem = std.mem; const elf = std.elf; @@ -7,56 +18,58 @@ const native_arch = @import("builtin").cpu.arch; const linux = std.os.linux; const posix = std.posix; -// This file implements the two TLS variants [1] used by ELF-based systems. -// -// The variant I has the following layout in memory: -// ------------------------------------------------------- -// | DTV | Zig | DTV | Alignment | TLS | -// | storage | thread data | pointer | | block | -// ------------------------^------------------------------ -// `-- The thread pointer register points here -// -// In this case we allocate additional space for our control structure that's -// placed _before_ the DTV pointer together with the DTV. -// -// NOTE: Some systems such as power64 or mips use this variant with a twist: the -// alignment is not present and the tp and DTV addresses are offset by a -// constant. -// -// On the other hand the variant II has the following layout in memory: -// --------------------------------------- -// | TLS | TCB | Zig | DTV | -// | block | | thread data | storage | -// --------^------------------------------ -// `-- The thread pointer register points here -// -// The structure of the TCB is not defined by the ABI so we reserve enough space -// for a single pointer as some architectures such as x86 and x86_64 need a -// pointer to the TCB block itself at the address pointed by the tp. -// -// In this case the control structure and DTV are placed one after another right -// after the TLS block data. -// -// At the moment the DTV is very simple since we only support static TLS, all we -// need is a two word vector to hold the number of entries (1) and the address -// of the first TLS block. -// -// [1] https://www.akkadia.org/drepper/tls.pdf - -const TLSVariant = enum { - VariantI, - VariantII, +/// Represents an ELF TLS variant. +/// +/// In all variants, the TP and the TLS blocks must be aligned to the `p_align` value in the +/// `PT_TLS` ELF program header. Everything else has natural alignment. +/// +/// The location of the DTV does not actually matter. For simplicity, we put it in the TLS area, but +/// there is no actual ABI requirement that it reside there. +const Variant = enum { + /// The original Variant I: + /// + /// ---------------------------------------- + /// | DTV | Zig TCB | ABI TCB | TLS Blocks | + /// ----------------^----------------------- + /// `-- The TP register points here. + /// + /// The layout in this variant necessitates separate alignment of both the TP and the TLS + /// blocks. + /// + /// The first word in the ABI TCB points to the DTV. For some architectures, there may be a + /// second word with an unspecified meaning. + I_original, + /// The modified Variant I: + /// + /// --------------------------------------------------- + /// | DTV | Zig TCB | ABI TCB | [Offset] | TLS Blocks | + /// -------------------------------------^------------- + /// `-- The TP register points here. + /// + /// The offset (which can be zero) is applied to the TP only; there is never physical gap + /// between the ABI TCB and the TLS blocks. This implies that we only need to align the TP. + /// + /// The first (and only) word in the ABI TCB points to the DTV. + I_modified, + /// Variant II: + /// + /// ---------------------------------------- + /// | TLS Blocks | ABI TCB | Zig TCB | DTV | + /// -------------^-------------------------- + /// `-- The TP register points here. + /// + /// The first (and only) word in the ABI TCB points to the ABI TCB itself. + II, }; -const tls_variant = switch (native_arch) { +const current_variant: Variant = switch (native_arch) { .arm, .armeb, - .thumb, - .thumbeb, .aarch64, .aarch64_be, - .riscv32, - .riscv64, + .thumb, + .thumbeb, + => .I_original, .mips, .mipsel, .mips64, @@ -65,73 +78,126 @@ const tls_variant = switch (native_arch) { .powerpcle, .powerpc64, .powerpc64le, - => TLSVariant.VariantI, - .x86_64, .x86, .sparc64 => TLSVariant.VariantII, - else => @compileError("undefined tls_variant for this architecture"), + .riscv32, + .riscv64, + => .I_modified, + .sparc64, + .x86, + .x86_64, + => .II, + else => @compileError("undefined TLS variant for this architecture"), }; -// Controls how many bytes are reserved for the Thread Control Block -const tls_tcb_size = switch (native_arch) { - // ARM EABI mandates enough space for two pointers: the first one points to - // the DTV while the second one is unspecified but reserved - .arm, .armeb, .thumb, .thumbeb, .aarch64, .aarch64_be => 2 * @sizeOf(usize), - // One pointer-sized word that points either to the DTV or the TCB itself - else => @sizeOf(usize), -}; - -// Controls if the TP points to the end of the TCB instead of its beginning -const tls_tp_points_past_tcb = switch (native_arch) { - .riscv32, .riscv64, .mips, .mipsel, .mips64, .mips64el, .powerpc, .powerpcle, .powerpc64, .powerpc64le => true, - else => false, -}; - -// Some architectures add some offset to the tp and dtv addresses in order to -// make the generated code more efficient - -const tls_tp_offset = switch (native_arch) { - .mips, .mipsel, .mips64, .mips64el, .powerpc, .powerpcle, .powerpc64, .powerpc64le => 0x7000, +/// The Offset value for the modified Variant I. +const current_tp_offset = switch (native_arch) { + .mips, + .mipsel, + .mips64, + .mips64el, + .powerpc, + .powerpcle, + .powerpc64, + .powerpc64le, + => 0x7000, else => 0, }; -const tls_dtv_offset = switch (native_arch) { - .mips, .mipsel, .mips64, .mips64el, .powerpc, .powerpcle, .powerpc64, .powerpc64le => 0x8000, - .riscv32, .riscv64 => 0x800, +/// Usually only used by the modified Variant I. +const current_dtv_offset = switch (native_arch) { + .mips, + .mipsel, + .mips64, + .mips64el, + .powerpc, + .powerpcle, + .powerpc64, + .powerpc64le, + => 0x8000, + .riscv32, + .riscv64, + => 0x800, else => 0, }; -// Per-thread storage for Zig's use -const CustomData = struct { +/// Per-thread storage for the ELF TLS ABI. +const AbiTcb = switch (current_variant) { + .I_original, .I_modified => switch (native_arch) { + // ARM EABI mandates enough space for two pointers: the first one points to the DTV as + // usual, while the second one is unspecified. + .aarch64, + .aarch64_be, + .arm, + .armeb, + .thumb, + .thumbeb, + => extern struct { + /// This is offset by `current_dtv_offset`. + dtv: usize, + reserved: ?*anyopaque, + }, + else => extern struct { + /// This is offset by `current_dtv_offset`. + dtv: usize, + }, + }, + .II => extern struct { + /// This is self-referential. + self: *AbiTcb, + }, +}; + +/// Per-thread storage for Zig's use. Currently unused. +const ZigTcb = struct { dummy: usize, }; -// Dynamic Thread Vector -const DTV = extern struct { - entries: usize, - tls_block: [1][*]u8, +/// Dynamic Thread Vector as specified in the ELF TLS ABI. Ordinarily, there is a block pointer per +/// dynamically-loaded module, but since we only support static TLS, we only need one block pointer. +const Dtv = extern struct { + len: usize = 1, + tls_block: [*]u8, }; -// Holds all the information about the process TLS image -const TLSImage = struct { - init_data: []const u8, - alloc_size: usize, - alloc_align: usize, - tcb_offset: usize, - dtv_offset: usize, - data_offset: usize, - data_size: usize, - // Only used on the x86 architecture +/// Describes a process's TLS area. The area encompasses the DTV, both TCBs, and the TLS block, with +/// the exact layout of these being dependent primarily on `current_variant`. +const AreaDesc = struct { + size: usize, + alignment: usize, + + dtv: struct { + /// Offset into the TLS area. + offset: usize, + }, + + abi_tcb: struct { + /// Offset into the TLS area. + offset: usize, + }, + + block: struct { + /// The initial data to be copied into the TLS block. Note that this may be smaller than + /// `size`, in which case any remaining data in the TLS block is simply left uninitialized. + init: []const u8, + /// Offset into the TLS area. + offset: usize, + /// This is the effective size of the TLS block, which may be greater than `init.len`. + size: usize, + }, + + /// Only used on the 32-bit x86 architecture (not x86_64, nor x32). gdt_entry_number: usize, }; -pub var tls_image: TLSImage = undefined; +pub var area_desc: AreaDesc = undefined; pub fn setThreadPointer(addr: usize) void { @setRuntimeSafety(false); @disableInstrumentation(); + switch (native_arch) { .x86 => { var user_desc: linux.user_desc = .{ - .entry_number = tls_image.gdt_entry_number, + .entry_number = area_desc.gdt_entry_number, .base_addr = addr, .limit = 0xfffff, .flags = .{ @@ -148,7 +214,7 @@ pub fn setThreadPointer(addr: usize) void { const gdt_entry_number = user_desc.entry_number; // We have to keep track of our slot as it's also needed for clone() - tls_image.gdt_entry_number = gdt_entry_number; + area_desc.gdt_entry_number = gdt_entry_number; // Update the %gs selector asm volatile ("movl %[gs_val], %%gs" : @@ -206,7 +272,7 @@ pub fn setThreadPointer(addr: usize) void { } } -fn initTLS(phdrs: []elf.Phdr) void { +fn computeAreaDesc(phdrs: []elf.Phdr) void { @setRuntimeSafety(false); @disableInstrumentation(); @@ -221,72 +287,85 @@ fn initTLS(phdrs: []elf.Phdr) void { } } - var tls_align_factor: usize = undefined; - var tls_data: []const u8 = undefined; - var tls_data_alloc_size: usize = undefined; + var align_factor: usize = undefined; + var block_init: []const u8 = undefined; + var block_size: usize = undefined; + if (tls_phdr) |phdr| { - // The effective size in memory is represented by p_memsz, the length of - // the data stored in the PT_TLS segment is p_filesz and may be less - // than the former - tls_align_factor = phdr.p_align; - tls_data = @as([*]u8, @ptrFromInt(img_base + phdr.p_vaddr))[0..phdr.p_filesz]; - tls_data_alloc_size = phdr.p_memsz; + align_factor = phdr.p_align; + + // The effective size in memory is represented by `p_memsz`; the length of the data stored + // in the `PT_TLS` segment is `p_filesz` and may be less than the former. + block_init = @as([*]u8, @ptrFromInt(img_base + phdr.p_vaddr))[0..phdr.p_filesz]; + block_size = phdr.p_memsz; } else { - tls_align_factor = @alignOf(usize); - tls_data = &[_]u8{}; - tls_data_alloc_size = 0; + align_factor = @alignOf(usize); + + block_init = &[_]u8{}; + block_size = 0; } - // Offsets into the allocated TLS area - var tcb_offset: usize = undefined; + // Offsets into the allocated TLS area. var dtv_offset: usize = undefined; - var data_offset: usize = undefined; - // Compute the total size of the ABI-specific data plus our own control - // structures. All the offset calculated here assume a well-aligned base - // address. - const alloc_size = switch (tls_variant) { - .VariantI => blk: { + var abi_tcb_offset: usize = undefined; + var block_offset: usize = undefined; + + // Compute the total size of the ABI-specific data plus our own `ZigTcb` structure. All the + // offsets calculated here assume a well-aligned base address. + const area_size = switch (current_variant) { + .I_original, .I_modified => blk: { var l: usize = 0; dtv_offset = l; - l += @sizeOf(DTV); - // Add some padding here so that the thread pointer (tcb_offset) is - // aligned to p_align and the CustomData structure can be found by - // simply subtracting its @sizeOf from the tp value - const delta = (l + @sizeOf(CustomData)) & (tls_align_factor - 1); + l += @sizeOf(Dtv); + // Add some padding here so that the TP (`abi_tcb_offset`) is aligned to `align_factor` + // and the `ZigTcb` structure can be found by simply subtracting `@sizeOf(ZigTcb)` from + // the TP. + const delta = (l + @sizeOf(ZigTcb)) & (align_factor - 1); if (delta > 0) - l += tls_align_factor - delta; - l += @sizeOf(CustomData); - tcb_offset = l; - l += alignForward(tls_tcb_size, tls_align_factor); - data_offset = l; - l += tls_data_alloc_size; + l += align_factor - delta; + l += @sizeOf(ZigTcb); + abi_tcb_offset = l; + l += alignForward(@sizeOf(AbiTcb), align_factor); + block_offset = l; + l += block_size; break :blk l; }, - .VariantII => blk: { + .II => blk: { var l: usize = 0; - data_offset = l; - l += alignForward(tls_data_alloc_size, tls_align_factor); - // The thread pointer is aligned to p_align - tcb_offset = l; - l += tls_tcb_size; - // The CustomData structure is right after the TCB with no padding - // in between so it can be easily found - l += @sizeOf(CustomData); - l = alignForward(l, @alignOf(DTV)); + block_offset = l; + l += alignForward(block_size, align_factor); + // The TP is aligned to `align_factor`. + abi_tcb_offset = l; + l += @sizeOf(AbiTcb); + // The `ZigTcb` structure is right after the `AbiTcb` with no padding in between so it + // can be easily found. + l += @sizeOf(ZigTcb); + // It doesn't really matter where we put the DTV, so give it natural alignment. + l = alignForward(l, @alignOf(Dtv)); dtv_offset = l; - l += @sizeOf(DTV); + l += @sizeOf(Dtv); break :blk l; }, }; - tls_image = TLSImage{ - .init_data = tls_data, - .alloc_size = alloc_size, - .alloc_align = tls_align_factor, - .tcb_offset = tcb_offset, - .dtv_offset = dtv_offset, - .data_offset = data_offset, - .data_size = tls_data_alloc_size, + area_desc = .{ + .size = area_size, + .alignment = align_factor, + + .dtv = .{ + .offset = dtv_offset, + }, + + .abi_tcb = .{ + .offset = abi_tcb_offset, + }, + + .block = .{ + .init = block_init, + .offset = block_offset, + .size = block_size, + }, + .gdt_entry_number = @as(usize, @bitCast(@as(isize, -1))), }; } @@ -306,78 +385,80 @@ inline fn alignPtrCast(comptime T: type, ptr: [*]u8) *T { return @ptrCast(@alignCast(ptr)); } -/// Initializes all the fields of the static TLS area and returns the computed -/// architecture-specific value of the thread-pointer register -/// -/// This function is inline because thread local storage is not set up yet. -pub fn prepareTLS(area: []u8) usize { +/// Initializes all the fields of the static TLS area and returns the computed architecture-specific +/// value of the TP register. +pub fn prepareArea(area: []u8) usize { @setRuntimeSafety(false); @disableInstrumentation(); - // Clear the area we're going to use, just to be safe - @memset(area, 0); - // Prepare the DTV - const dtv = alignPtrCast(DTV, area.ptr + tls_image.dtv_offset); - dtv.entries = 1; - dtv.tls_block[0] = area.ptr + tls_dtv_offset + tls_image.data_offset; - // Prepare the TCB - const tcb_ptr = alignPtrCast([*]u8, area.ptr + tls_image.tcb_offset); - tcb_ptr.* = switch (tls_variant) { - .VariantI => area.ptr + tls_image.dtv_offset, - .VariantII => area.ptr + tls_image.tcb_offset, - }; - // Copy the data - @memcpy(area[tls_image.data_offset..][0..tls_image.init_data.len], tls_image.init_data); - // Return the corrected value (if needed) for the tp register. - // Overflow here is not a problem, the pointer arithmetic involving the tp - // is done with wrapping semantics. - return @intFromPtr(area.ptr) +% tls_tp_offset +% - if (tls_tp_points_past_tcb) tls_image.data_offset else tls_image.tcb_offset; + // Clear the area we're going to use, just to be safe. + @memset(area, 0); + + // Prepare the ABI TCB. + const abi_tcb = alignPtrCast(AbiTcb, area.ptr + area_desc.abi_tcb.offset); + switch (current_variant) { + .I_original, .I_modified => abi_tcb.dtv = @intFromPtr(area.ptr + area_desc.dtv.offset), + .II => abi_tcb.self = abi_tcb, + } + + // Prepare the DTV. + const dtv = alignPtrCast(Dtv, area.ptr + area_desc.dtv.offset); + dtv.len = 1; + dtv.tls_block = area.ptr + current_dtv_offset + area_desc.block.offset; + + // Copy the initial data. + @memcpy(area[area_desc.block.offset..][0..area_desc.block.init.len], area_desc.block.init); + + // Return the corrected value (if needed) for the TP register. Overflow here is not a problem; + // the pointer arithmetic involving the TP is done with wrapping semantics. + return @intFromPtr(area.ptr) +% switch (current_variant) { + .I_original, .II => area_desc.abi_tcb.offset, + .I_modified => area_desc.block.offset +% current_tp_offset, + }; } -// The main motivation for the size chosen here is this is how much ends up being -// requested for the thread local variables of the std.crypto.random implementation. -// I'm not sure why it ends up being so much; the struct itself is only 64 bytes. -// I think it has to do with being page aligned and LLVM or LLD is not smart enough -// to lay out the TLS data in a space conserving way. Anyway I think it's fine -// because it's less than 3 pages of memory, and putting it in the ELF like this -// is equivalent to moving the mmap call below into the kernel, avoiding syscall -// overhead. -var main_thread_tls_buffer: [0x2100]u8 align(mem.page_size) = undefined; +// The main motivation for the size chosen here is that this is how much ends up being requested for +// the thread-local variables of the `std.crypto.random` implementation. I'm not sure why it ends up +// being so much; the struct itself is only 64 bytes. I think it has to do with being page-aligned +// and LLVM or LLD is not smart enough to lay out the TLS data in a space-conserving way. Anyway, I +// think it's fine because it's less than 3 pages of memory, and putting it in the ELF like this is +// equivalent to moving the `mmap` call below into the kernel, avoiding syscall overhead. +var main_thread_area_buffer: [0x2100]u8 align(mem.page_size) = undefined; -pub fn initStaticTLS(phdrs: []elf.Phdr) void { +/// Computes the layout of the static TLS area, allocates the area, initializes all of its fields, +/// and assigns the architecture-specific value to the TP register. +pub fn initStatic(phdrs: []elf.Phdr) void { @setRuntimeSafety(false); @disableInstrumentation(); - initTLS(phdrs); + computeAreaDesc(phdrs); - const tls_area = blk: { - // Fast path for the common case where the TLS data is really small, - // avoid an allocation and use our local buffer. - if (tls_image.alloc_align <= mem.page_size and - tls_image.alloc_size <= main_thread_tls_buffer.len) - { - break :blk main_thread_tls_buffer[0..tls_image.alloc_size]; + const area = blk: { + // Fast path for the common case where the TLS data is really small, avoid an allocation and + // use our local buffer. + if (area_desc.alignment <= mem.page_size and area_desc.size <= main_thread_area_buffer.len) { + break :blk main_thread_area_buffer[0..area_desc.size]; } const begin_addr = mmap( null, - tls_image.alloc_size + tls_image.alloc_align - 1, + area_desc.size + area_desc.alignment - 1, posix.PROT.READ | posix.PROT.WRITE, .{ .TYPE = .PRIVATE, .ANONYMOUS = true }, -1, 0, ); if (@as(isize, @bitCast(begin_addr)) < 0) @trap(); - const alloc_tls_area: [*]align(mem.page_size) u8 = @ptrFromInt(begin_addr); + + const area_ptr: [*]align(mem.page_size) u8 = @ptrFromInt(begin_addr); // Make sure the slice is correctly aligned. - const begin_aligned_addr = alignForward(begin_addr, tls_image.alloc_align); + const begin_aligned_addr = alignForward(begin_addr, area_desc.alignment); const start = begin_aligned_addr - begin_addr; - break :blk alloc_tls_area[start..][0..tls_image.alloc_size]; + break :blk area_ptr[start..][0..area_desc.size]; }; - const tp_value = prepareTLS(tls_area); + const tp_value = prepareArea(area); setThreadPointer(tp_value); } diff --git a/lib/std/start.zig b/lib/std/start.zig index 2a61be46e4..b5172d4f28 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -456,7 +456,7 @@ fn posixCallMainAndExit(argc_argv_ptr: [*]usize) callconv(.C) noreturn { } // Initialize the TLS area. - std.os.linux.tls.initStaticTLS(phdrs); + std.os.linux.tls.initStatic(phdrs); } // The way Linux executables represent stack size is via the PT_GNU_STACK From 37275c0f69769774ba4f1fc577b04aa36e24ac6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Mon, 29 Jul 2024 12:19:15 +0200 Subject: [PATCH 030/266] std.os.linux.tls: Fix layout computation for the modified Variant I. --- lib/std/os/linux/tls.zig | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/lib/std/os/linux/tls.zig b/lib/std/os/linux/tls.zig index feaeb8699a..844585ef62 100644 --- a/lib/std/os/linux/tls.zig +++ b/lib/std/os/linux/tls.zig @@ -313,7 +313,7 @@ fn computeAreaDesc(phdrs: []elf.Phdr) void { // Compute the total size of the ABI-specific data plus our own `ZigTcb` structure. All the // offsets calculated here assume a well-aligned base address. const area_size = switch (current_variant) { - .I_original, .I_modified => blk: { + .I_original => blk: { var l: usize = 0; dtv_offset = l; l += @sizeOf(Dtv); @@ -330,6 +330,24 @@ fn computeAreaDesc(phdrs: []elf.Phdr) void { l += block_size; break :blk l; }, + .I_modified => blk: { + var l: usize = 0; + dtv_offset = l; + l += @sizeOf(Dtv); + // In this variant, the TLS blocks must begin immediately after the end of the ABI TCB, + // with the TP pointing to the beginning of the TLS blocks. Add padding so that the TP + // (`abi_tcb_offset`) is aligned to `align_factor` and the `ZigTcb` structure can be + // found by subtracting `@sizeOf(AbiTcb) + @sizeOf(ZigTcb)` from the TP. + const delta = (l + @sizeOf(ZigTcb) + @sizeOf(AbiTcb)) & (align_factor - 1); + if (delta > 0) + l += align_factor - delta; + l += @sizeOf(ZigTcb); + abi_tcb_offset = l; + l += @sizeOf(AbiTcb); + block_offset = l; + l += block_size; + break :blk l; + }, .II => blk: { var l: usize = 0; block_offset = l; From 44d4656dfc8ecf10a4dea0462767ca4e6513a818 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Tue, 23 Jul 2024 07:27:07 +0200 Subject: [PATCH 031/266] std.os.linux.tls: Add m68k support. --- lib/std/os/linux/tls.zig | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/std/os/linux/tls.zig b/lib/std/os/linux/tls.zig index 844585ef62..1ca38c2ebd 100644 --- a/lib/std/os/linux/tls.zig +++ b/lib/std/os/linux/tls.zig @@ -70,6 +70,7 @@ const current_variant: Variant = switch (native_arch) { .thumb, .thumbeb, => .I_original, + .m68k, .mips, .mipsel, .mips64, @@ -90,6 +91,7 @@ const current_variant: Variant = switch (native_arch) { /// The Offset value for the modified Variant I. const current_tp_offset = switch (native_arch) { + .m68k, .mips, .mipsel, .mips64, @@ -104,6 +106,7 @@ const current_tp_offset = switch (native_arch) { /// Usually only used by the modified Variant I. const current_dtv_offset = switch (native_arch) { + .m68k, .mips, .mipsel, .mips64, @@ -236,6 +239,10 @@ pub fn setThreadPointer(addr: usize) void { const rc = @call(.always_inline, linux.syscall1, .{ .set_tls, addr }); assert(rc == 0); }, + .m68k => { + const rc = linux.syscall1(.set_thread_area, addr); + assert(rc == 0); + }, .riscv32, .riscv64 => { asm volatile ( \\ mv tp, %[addr] From 9db992bd9bf67efef3a81c76fc3896ac1c94d7e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Tue, 23 Jul 2024 07:27:17 +0200 Subject: [PATCH 032/266] std.os.linux.tls: Add loongarch support. --- lib/std/os/linux/tls.zig | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/std/os/linux/tls.zig b/lib/std/os/linux/tls.zig index 1ca38c2ebd..212de9ccb2 100644 --- a/lib/std/os/linux/tls.zig +++ b/lib/std/os/linux/tls.zig @@ -70,6 +70,8 @@ const current_variant: Variant = switch (native_arch) { .thumb, .thumbeb, => .I_original, + .loongarch32, + .loongarch64, .m68k, .mips, .mipsel, @@ -243,6 +245,13 @@ pub fn setThreadPointer(addr: usize) void { const rc = linux.syscall1(.set_thread_area, addr); assert(rc == 0); }, + .loongarch32, .loongarch64 => { + asm volatile ( + \\ mv tp, %[addr] + : + : [addr] "r" (addr), + ); + }, .riscv32, .riscv64 => { asm volatile ( \\ mv tp, %[addr] From d2b21d582341a2958cf41dde046466a1607926fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Sun, 28 Jul 2024 18:46:25 +0200 Subject: [PATCH 033/266] std.os.linux.tls: Add s390x support. --- lib/std/os/linux/tls.zig | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/std/os/linux/tls.zig b/lib/std/os/linux/tls.zig index 212de9ccb2..7319f4863e 100644 --- a/lib/std/os/linux/tls.zig +++ b/lib/std/os/linux/tls.zig @@ -84,6 +84,7 @@ const current_variant: Variant = switch (native_arch) { .riscv32, .riscv64, => .I_modified, + .s390x, .sparc64, .x86, .x86_64, @@ -277,6 +278,17 @@ pub fn setThreadPointer(addr: usize) void { : [addr] "r" (addr), ); }, + .s390x => { + asm volatile ( + \\ lgr %%r0, %[addr] + \\ sar %%a1, %%r0 + \\ srlg %%r0, %%r0, 32 + \\ sar %%a0, %%r0 + : + : [addr] "r" (addr), + : "r0" + ); + }, .sparc64 => { asm volatile ( \\ mov %[addr], %%g7 From e6fe7a329ab42d259437b83a4294009feae7bfcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Sun, 28 Jul 2024 19:05:58 +0200 Subject: [PATCH 034/266] std.os.linux.tls: Add hexagon support. --- lib/std/os/linux/tls.zig | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/std/os/linux/tls.zig b/lib/std/os/linux/tls.zig index 7319f4863e..e39a3a0308 100644 --- a/lib/std/os/linux/tls.zig +++ b/lib/std/os/linux/tls.zig @@ -84,6 +84,7 @@ const current_variant: Variant = switch (native_arch) { .riscv32, .riscv64, => .I_modified, + .hexagon, .s390x, .sparc64, .x86, @@ -246,6 +247,13 @@ pub fn setThreadPointer(addr: usize) void { const rc = linux.syscall1(.set_thread_area, addr); assert(rc == 0); }, + .hexagon => { + asm volatile ( + \\ ugp = %[addr] + : + : [addr] "r" (addr), + ); + }, .loongarch32, .loongarch64 => { asm volatile ( \\ mv tp, %[addr] From 9abe3aeeea2f9b1d301a11046d43be279bf24fd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Sun, 28 Jul 2024 21:10:16 +0200 Subject: [PATCH 035/266] std.os.linux.tls: Add arc support. --- lib/std/os/linux/tls.zig | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/std/os/linux/tls.zig b/lib/std/os/linux/tls.zig index e39a3a0308..f47ccea353 100644 --- a/lib/std/os/linux/tls.zig +++ b/lib/std/os/linux/tls.zig @@ -63,6 +63,7 @@ const Variant = enum { }; const current_variant: Variant = switch (native_arch) { + .arc, .arm, .armeb, .aarch64, @@ -239,6 +240,16 @@ pub fn setThreadPointer(addr: usize) void { : [addr] "r" (addr), ); }, + .arc => { + // We apparently need to both set r25 (TP) *and* inform the kernel... + asm volatile ( + \\ mov r25, %[addr] + : + : [addr] "r" (addr), + ); + const rc = @call(.always_inline, linux.syscall1, .{ .arc_settls, addr }); + assert(rc == 0); + }, .arm, .armeb, .thumb, .thumbeb => { const rc = @call(.always_inline, linux.syscall1, .{ .set_tls, addr }); assert(rc == 0); From 36332a4fdc8b5bfdaa3a2b6b76772312be6f6a79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Sun, 28 Jul 2024 21:24:31 +0200 Subject: [PATCH 036/266] std.os.linux.tls: Add csky support. --- lib/std/os/linux/tls.zig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/std/os/linux/tls.zig b/lib/std/os/linux/tls.zig index f47ccea353..251d4084aa 100644 --- a/lib/std/os/linux/tls.zig +++ b/lib/std/os/linux/tls.zig @@ -68,6 +68,7 @@ const current_variant: Variant = switch (native_arch) { .armeb, .aarch64, .aarch64_be, + .csky, .thumb, .thumbeb, => .I_original, @@ -279,7 +280,7 @@ pub fn setThreadPointer(addr: usize) void { : [addr] "r" (addr), ); }, - .mips, .mipsel, .mips64, .mips64el => { + .csky, .mips, .mipsel, .mips64, .mips64el => { const rc = @call(.always_inline, linux.syscall1, .{ .set_thread_area, addr }); assert(rc == 0); }, From 2bd7af63d76d1c61bfd77a5402f94d105c375a1d Mon Sep 17 00:00:00 2001 From: Marc Tiehuis Date: Mon, 29 Jul 2024 16:26:09 +1200 Subject: [PATCH 037/266] std.math.complex: fix acosh/atan/cosh/sqrt Some of these are upstream changes since the original port, others are translation errors. --- lib/std/math/complex/acosh.zig | 6 +++++- lib/std/math/complex/atan.zig | 38 ++++------------------------------ lib/std/math/complex/cosh.zig | 6 +++--- lib/std/math/complex/sqrt.zig | 4 ++-- 4 files changed, 14 insertions(+), 40 deletions(-) diff --git a/lib/std/math/complex/acosh.zig b/lib/std/math/complex/acosh.zig index dba8b03794..24224fd93b 100644 --- a/lib/std/math/complex/acosh.zig +++ b/lib/std/math/complex/acosh.zig @@ -8,7 +8,11 @@ const Complex = cmath.Complex; pub fn acosh(z: anytype) Complex(@TypeOf(z.re, z.im)) { const T = @TypeOf(z.re, z.im); const q = cmath.acos(z); - return Complex(T).init(-q.im, q.re); + + return if (math.signbit(z.im)) + Complex(T).init(q.im, -q.re) + else + Complex(T).init(-q.im, q.re); } const epsilon = 0.0001; diff --git a/lib/std/math/complex/atan.zig b/lib/std/math/complex/atan.zig index 33109178f8..7269ea9ead 100644 --- a/lib/std/math/complex/atan.zig +++ b/lib/std/math/complex/atan.zig @@ -32,37 +32,22 @@ fn redupif32(x: f32) f32 { t -= 0.5; } - const u = @as(f32, @floatFromInt(@as(i32, @intFromFloat(t)))); - return ((x - u * DP1) - u * DP2) - t * DP3; + const u: f32 = @trunc(t); + return ((x - u * DP1) - u * DP2) - u * DP3; } fn atan32(z: Complex(f32)) Complex(f32) { - const maxnum = 1.0e38; - const x = z.re; const y = z.im; - if ((x == 0.0) and (y > 1.0)) { - // overflow - return Complex(f32).init(maxnum, maxnum); - } - const x2 = x * x; var a = 1.0 - x2 - (y * y); - if (a == 0.0) { - // overflow - return Complex(f32).init(maxnum, maxnum); - } var t = 0.5 * math.atan2(2.0 * x, a); const w = redupif32(t); t = y - 1.0; a = x2 + t * t; - if (a == 0.0) { - // overflow - return Complex(f32).init(maxnum, maxnum); - } t = y + 1.0; a = (x2 + (t * t)) / a; @@ -81,37 +66,22 @@ fn redupif64(x: f64) f64 { t -= 0.5; } - const u = @as(f64, @floatFromInt(@as(i64, @intFromFloat(t)))); - return ((x - u * DP1) - u * DP2) - t * DP3; + const u: f64 = @trunc(t); + return ((x - u * DP1) - u * DP2) - u * DP3; } fn atan64(z: Complex(f64)) Complex(f64) { - const maxnum = 1.0e308; - const x = z.re; const y = z.im; - if ((x == 0.0) and (y > 1.0)) { - // overflow - return Complex(f64).init(maxnum, maxnum); - } - const x2 = x * x; var a = 1.0 - x2 - (y * y); - if (a == 0.0) { - // overflow - return Complex(f64).init(maxnum, maxnum); - } var t = 0.5 * math.atan2(2.0 * x, a); const w = redupif64(t); t = y - 1.0; a = x2 + t * t; - if (a == 0.0) { - // overflow - return Complex(f64).init(maxnum, maxnum); - } t = y + 1.0; a = (x2 + (t * t)) / a; diff --git a/lib/std/math/complex/cosh.zig b/lib/std/math/complex/cosh.zig index 42f5b432ec..18443da171 100644 --- a/lib/std/math/complex/cosh.zig +++ b/lib/std/math/complex/cosh.zig @@ -34,7 +34,7 @@ fn cosh32(z: Complex(f32)) Complex(f32) { if (ix < 0x7f800000 and iy < 0x7f800000) { if (iy == 0) { - return Complex(f32).init(math.cosh(x), y); + return Complex(f32).init(math.cosh(x), x * y); } // small x: normal case if (ix < 0x41100000) { @@ -45,7 +45,7 @@ fn cosh32(z: Complex(f32)) Complex(f32) { if (ix < 0x42b17218) { // x < 88.7: exp(|x|) won't overflow const h = @exp(@abs(x)) * 0.5; - return Complex(f32).init(math.copysign(h, x) * @cos(y), h * @sin(y)); + return Complex(f32).init(h * @cos(y), math.copysign(h, x) * @sin(y)); } // x < 192.7: scale to avoid overflow else if (ix < 0x4340b1e7) { @@ -68,7 +68,7 @@ fn cosh32(z: Complex(f32)) Complex(f32) { if (hx & 0x7fffff == 0) { return Complex(f32).init(x * x, math.copysign(@as(f32, 0.0), x) * y); } - return Complex(f32).init(x, math.copysign(@as(f32, 0.0), (x + x) * y)); + return Complex(f32).init(x * x, math.copysign(@as(f32, 0.0), (x + x) * y)); } if (ix < 0x7f800000 and iy >= 0x7f800000) { diff --git a/lib/std/math/complex/sqrt.zig b/lib/std/math/complex/sqrt.zig index 0edf18fb23..ef24cc7726 100644 --- a/lib/std/math/complex/sqrt.zig +++ b/lib/std/math/complex/sqrt.zig @@ -43,7 +43,7 @@ fn sqrt32(z: Complex(f32)) Complex(f32) { // sqrt(-inf + i nan) = nan +- inf i // sqrt(-inf + iy) = 0 + inf i if (math.signbit(x)) { - return Complex(f32).init(@abs(x - y), math.copysign(x, y)); + return Complex(f32).init(@abs(y - y), math.copysign(x, y)); } else { return Complex(f32).init(x, math.copysign(y - y, y)); } @@ -94,7 +94,7 @@ fn sqrt64(z: Complex(f64)) Complex(f64) { // sqrt(-inf + i nan) = nan +- inf i // sqrt(-inf + iy) = 0 + inf i if (math.signbit(x)) { - return Complex(f64).init(@abs(x - y), math.copysign(x, y)); + return Complex(f64).init(@abs(y - y), math.copysign(x, y)); } else { return Complex(f64).init(x, math.copysign(y - y, y)); } From 0fda2f31aae2109886c74b352ea4dee23ce6eb74 Mon Sep 17 00:00:00 2001 From: Marc Tiehuis Date: Tue, 30 Jul 2024 16:29:51 +1200 Subject: [PATCH 038/266] std.math.complex: tighten existing test bounds --- lib/std/math/complex/abs.zig | 5 ++--- lib/std/math/complex/acos.zig | 7 +++---- lib/std/math/complex/acosh.zig | 7 +++---- lib/std/math/complex/arg.zig | 5 ++--- lib/std/math/complex/asin.zig | 7 +++---- lib/std/math/complex/asinh.zig | 7 +++---- lib/std/math/complex/atan.zig | 12 ++++++------ lib/std/math/complex/atanh.zig | 7 +++---- lib/std/math/complex/conj.zig | 3 ++- lib/std/math/complex/cos.zig | 7 +++---- lib/std/math/complex/cosh.zig | 12 ++++++------ lib/std/math/complex/log.zig | 7 +++---- lib/std/math/complex/pow.zig | 7 +++---- lib/std/math/complex/proj.zig | 3 ++- lib/std/math/complex/sin.zig | 7 +++---- lib/std/math/complex/sinh.zig | 12 ++++++------ lib/std/math/complex/sqrt.zig | 12 ++++++------ lib/std/math/complex/tan.zig | 7 +++---- lib/std/math/complex/tanh.zig | 12 ++++++------ 19 files changed, 68 insertions(+), 78 deletions(-) diff --git a/lib/std/math/complex/abs.zig b/lib/std/math/complex/abs.zig index ab85f2c36c..8b75db4a61 100644 --- a/lib/std/math/complex/abs.zig +++ b/lib/std/math/complex/abs.zig @@ -9,10 +9,9 @@ pub fn abs(z: anytype) @TypeOf(z.re, z.im) { return math.hypot(z.re, z.im); } -const epsilon = 0.0001; - test abs { + const epsilon = math.floatEps(f32); const a = Complex(f32).init(5, 3); const c = abs(a); - try testing.expect(math.approxEqAbs(f32, c, 5.83095, epsilon)); + try testing.expectApproxEqAbs(5.8309517, c, epsilon); } diff --git a/lib/std/math/complex/acos.zig b/lib/std/math/complex/acos.zig index 9cbfaffe4b..afaae740d9 100644 --- a/lib/std/math/complex/acos.zig +++ b/lib/std/math/complex/acos.zig @@ -11,12 +11,11 @@ pub fn acos(z: anytype) Complex(@TypeOf(z.re, z.im)) { return Complex(T).init(@as(T, math.pi) / 2 - q.re, -q.im); } -const epsilon = 0.0001; - test acos { + const epsilon = math.floatEps(f32); const a = Complex(f32).init(5, 3); const c = acos(a); - try testing.expect(math.approxEqAbs(f32, c.re, 0.546975, epsilon)); - try testing.expect(math.approxEqAbs(f32, c.im, -2.452914, epsilon)); + try testing.expectApproxEqAbs(0.5469737, c.re, epsilon); + try testing.expectApproxEqAbs(-2.4529128, c.im, epsilon); } diff --git a/lib/std/math/complex/acosh.zig b/lib/std/math/complex/acosh.zig index 24224fd93b..dbb7104343 100644 --- a/lib/std/math/complex/acosh.zig +++ b/lib/std/math/complex/acosh.zig @@ -15,12 +15,11 @@ pub fn acosh(z: anytype) Complex(@TypeOf(z.re, z.im)) { Complex(T).init(-q.im, q.re); } -const epsilon = 0.0001; - test acosh { + const epsilon = math.floatEps(f32); const a = Complex(f32).init(5, 3); const c = acosh(a); - try testing.expect(math.approxEqAbs(f32, c.re, 2.452914, epsilon)); - try testing.expect(math.approxEqAbs(f32, c.im, 0.546975, epsilon)); + try testing.expectApproxEqAbs(2.4529128, c.re, epsilon); + try testing.expectApproxEqAbs(0.5469737, c.im, epsilon); } diff --git a/lib/std/math/complex/arg.zig b/lib/std/math/complex/arg.zig index ac69276d96..12986c7c8d 100644 --- a/lib/std/math/complex/arg.zig +++ b/lib/std/math/complex/arg.zig @@ -9,10 +9,9 @@ pub fn arg(z: anytype) @TypeOf(z.re, z.im) { return math.atan2(z.im, z.re); } -const epsilon = 0.0001; - test arg { + const epsilon = math.floatEps(f32); const a = Complex(f32).init(5, 3); const c = arg(a); - try testing.expect(math.approxEqAbs(f32, c, 0.540420, epsilon)); + try testing.expectApproxEqAbs(0.5404195, c, epsilon); } diff --git a/lib/std/math/complex/asin.zig b/lib/std/math/complex/asin.zig index deacfa26ea..bf3e9a00da 100644 --- a/lib/std/math/complex/asin.zig +++ b/lib/std/math/complex/asin.zig @@ -17,12 +17,11 @@ pub fn asin(z: anytype) Complex(@TypeOf(z.re, z.im)) { return Complex(T).init(r.im, -r.re); } -const epsilon = 0.0001; - test asin { + const epsilon = math.floatEps(f32); const a = Complex(f32).init(5, 3); const c = asin(a); - try testing.expect(math.approxEqAbs(f32, c.re, 1.023822, epsilon)); - try testing.expect(math.approxEqAbs(f32, c.im, 2.452914, epsilon)); + try testing.expectApproxEqAbs(1.0238227, c.re, epsilon); + try testing.expectApproxEqAbs(2.4529128, c.im, epsilon); } diff --git a/lib/std/math/complex/asinh.zig b/lib/std/math/complex/asinh.zig index 2dcfc9c2ac..4923caff50 100644 --- a/lib/std/math/complex/asinh.zig +++ b/lib/std/math/complex/asinh.zig @@ -12,12 +12,11 @@ pub fn asinh(z: anytype) Complex(@TypeOf(z.re, z.im)) { return Complex(T).init(r.im, -r.re); } -const epsilon = 0.0001; - test asinh { + const epsilon = math.floatEps(f32); const a = Complex(f32).init(5, 3); const c = asinh(a); - try testing.expect(math.approxEqAbs(f32, c.re, 2.459831, epsilon)); - try testing.expect(math.approxEqAbs(f32, c.im, 0.533999, epsilon)); + try testing.expectApproxEqAbs(2.4598298, c.re, epsilon); + try testing.expectApproxEqAbs(0.5339993, c.im, epsilon); } diff --git a/lib/std/math/complex/atan.zig b/lib/std/math/complex/atan.zig index 7269ea9ead..9e9e94622c 100644 --- a/lib/std/math/complex/atan.zig +++ b/lib/std/math/complex/atan.zig @@ -88,20 +88,20 @@ fn atan64(z: Complex(f64)) Complex(f64) { return Complex(f64).init(w, 0.25 * @log(a)); } -const epsilon = 0.0001; - test atan32 { + const epsilon = math.floatEps(f32); const a = Complex(f32).init(5, 3); const c = atan(a); - try testing.expect(math.approxEqAbs(f32, c.re, 1.423679, epsilon)); - try testing.expect(math.approxEqAbs(f32, c.im, 0.086569, epsilon)); + try testing.expectApproxEqAbs(1.423679, c.re, epsilon); + try testing.expectApproxEqAbs(0.086569, c.im, epsilon); } test atan64 { + const epsilon = math.floatEps(f64); const a = Complex(f64).init(5, 3); const c = atan(a); - try testing.expect(math.approxEqAbs(f64, c.re, 1.423679, epsilon)); - try testing.expect(math.approxEqAbs(f64, c.im, 0.086569, epsilon)); + try testing.expectApproxEqAbs(1.4236790442393028, c.re, epsilon); + try testing.expectApproxEqAbs(0.08656905917945844, c.im, epsilon); } diff --git a/lib/std/math/complex/atanh.zig b/lib/std/math/complex/atanh.zig index 54d21fc433..86d5b3df23 100644 --- a/lib/std/math/complex/atanh.zig +++ b/lib/std/math/complex/atanh.zig @@ -12,12 +12,11 @@ pub fn atanh(z: anytype) Complex(@TypeOf(z.re, z.im)) { return Complex(T).init(r.im, -r.re); } -const epsilon = 0.0001; - test atanh { + const epsilon = math.floatEps(f32); const a = Complex(f32).init(5, 3); const c = atanh(a); - try testing.expect(math.approxEqAbs(f32, c.re, 0.146947, epsilon)); - try testing.expect(math.approxEqAbs(f32, c.im, 1.480870, epsilon)); + try testing.expectApproxEqAbs(0.14694665, c.re, epsilon); + try testing.expectApproxEqAbs(1.4808695, c.im, epsilon); } diff --git a/lib/std/math/complex/conj.zig b/lib/std/math/complex/conj.zig index b5a4d063d3..4a271c9801 100644 --- a/lib/std/math/complex/conj.zig +++ b/lib/std/math/complex/conj.zig @@ -14,5 +14,6 @@ test conj { const a = Complex(f32).init(5, 3); const c = a.conjugate(); - try testing.expect(c.re == 5 and c.im == -3); + try testing.expectEqual(5, c.re); + try testing.expectEqual(-3, c.im); } diff --git a/lib/std/math/complex/cos.zig b/lib/std/math/complex/cos.zig index 389dd013e3..fee1a8c37a 100644 --- a/lib/std/math/complex/cos.zig +++ b/lib/std/math/complex/cos.zig @@ -11,12 +11,11 @@ pub fn cos(z: anytype) Complex(@TypeOf(z.re, z.im)) { return cmath.cosh(p); } -const epsilon = 0.0001; - test cos { + const epsilon = math.floatEps(f32); const a = Complex(f32).init(5, 3); const c = cos(a); - try testing.expect(math.approxEqAbs(f32, c.re, 2.855815, epsilon)); - try testing.expect(math.approxEqAbs(f32, c.im, 9.606383, epsilon)); + try testing.expectApproxEqAbs(2.8558152, c.re, epsilon); + try testing.expectApproxEqAbs(9.606383, c.im, epsilon); } diff --git a/lib/std/math/complex/cosh.zig b/lib/std/math/complex/cosh.zig index 18443da171..c672c71ece 100644 --- a/lib/std/math/complex/cosh.zig +++ b/lib/std/math/complex/cosh.zig @@ -153,20 +153,20 @@ fn cosh64(z: Complex(f64)) Complex(f64) { return Complex(f64).init((x * x) * (y - y), (x + x) * (y - y)); } -const epsilon = 0.0001; - test cosh32 { + const epsilon = math.floatEps(f32); const a = Complex(f32).init(5, 3); const c = cosh(a); - try testing.expect(math.approxEqAbs(f32, c.re, -73.467300, epsilon)); - try testing.expect(math.approxEqAbs(f32, c.im, 10.471557, epsilon)); + try testing.expectApproxEqAbs(-73.467300, c.re, epsilon); + try testing.expectApproxEqAbs(10.471557, c.im, epsilon); } test cosh64 { + const epsilon = math.floatEps(f64); const a = Complex(f64).init(5, 3); const c = cosh(a); - try testing.expect(math.approxEqAbs(f64, c.re, -73.467300, epsilon)); - try testing.expect(math.approxEqAbs(f64, c.im, 10.471557, epsilon)); + try testing.expectApproxEqAbs(-73.46729221264526, c.re, epsilon); + try testing.expectApproxEqAbs(10.471557674805572, c.im, epsilon); } diff --git a/lib/std/math/complex/log.zig b/lib/std/math/complex/log.zig index 65859c2dce..39174431f1 100644 --- a/lib/std/math/complex/log.zig +++ b/lib/std/math/complex/log.zig @@ -13,12 +13,11 @@ pub fn log(z: anytype) Complex(@TypeOf(z.re, z.im)) { return Complex(T).init(@log(r), phi); } -const epsilon = 0.0001; - test log { + const epsilon = math.floatEps(f32); const a = Complex(f32).init(5, 3); const c = log(a); - try testing.expect(math.approxEqAbs(f32, c.re, 1.763180, epsilon)); - try testing.expect(math.approxEqAbs(f32, c.im, 0.540419, epsilon)); + try testing.expectApproxEqAbs(1.7631803, c.re, epsilon); + try testing.expectApproxEqAbs(0.5404195, c.im, epsilon); } diff --git a/lib/std/math/complex/pow.zig b/lib/std/math/complex/pow.zig index 874e4037dc..6fcca6b864 100644 --- a/lib/std/math/complex/pow.zig +++ b/lib/std/math/complex/pow.zig @@ -9,13 +9,12 @@ pub fn pow(z: anytype, s: anytype) Complex(@TypeOf(z.re, z.im, s.re, s.im)) { return cmath.exp(cmath.log(z).mul(s)); } -const epsilon = 0.0001; - test pow { + const epsilon = math.floatEps(f32); const a = Complex(f32).init(5, 3); const b = Complex(f32).init(2.3, -1.3); const c = pow(a, b); - try testing.expect(math.approxEqAbs(f32, c.re, 58.049110, epsilon)); - try testing.expect(math.approxEqAbs(f32, c.im, -101.003433, epsilon)); + try testing.expectApproxEqAbs(58.049110, c.re, epsilon); + try testing.expectApproxEqAbs(-101.003433, c.im, epsilon); } diff --git a/lib/std/math/complex/proj.zig b/lib/std/math/complex/proj.zig index b7d3d58b12..243e5133c3 100644 --- a/lib/std/math/complex/proj.zig +++ b/lib/std/math/complex/proj.zig @@ -19,5 +19,6 @@ test proj { const a = Complex(f32).init(5, 3); const c = proj(a); - try testing.expect(c.re == 5 and c.im == 3); + try testing.expectEqual(5, c.re); + try testing.expectEqual(3, c.im); } diff --git a/lib/std/math/complex/sin.zig b/lib/std/math/complex/sin.zig index 3ca2419e43..5ce5b335f8 100644 --- a/lib/std/math/complex/sin.zig +++ b/lib/std/math/complex/sin.zig @@ -12,12 +12,11 @@ pub fn sin(z: anytype) Complex(@TypeOf(z.re, z.im)) { return Complex(T).init(q.im, -q.re); } -const epsilon = 0.0001; - test sin { + const epsilon = math.floatEps(f32); const a = Complex(f32).init(5, 3); const c = sin(a); - try testing.expect(math.approxEqAbs(f32, c.re, -9.654126, epsilon)); - try testing.expect(math.approxEqAbs(f32, c.im, 2.841692, epsilon)); + try testing.expectApproxEqAbs(-9.654126, c.re, epsilon); + try testing.expectApproxEqAbs(2.8416924, c.im, epsilon); } diff --git a/lib/std/math/complex/sinh.zig b/lib/std/math/complex/sinh.zig index 911587a9c5..ae8fadb9ab 100644 --- a/lib/std/math/complex/sinh.zig +++ b/lib/std/math/complex/sinh.zig @@ -152,20 +152,20 @@ fn sinh64(z: Complex(f64)) Complex(f64) { return Complex(f64).init((x * x) * (y - y), (x + x) * (y - y)); } -const epsilon = 0.0001; - test sinh32 { + const epsilon = math.floatEps(f32); const a = Complex(f32).init(5, 3); const c = sinh(a); - try testing.expect(math.approxEqAbs(f32, c.re, -73.460617, epsilon)); - try testing.expect(math.approxEqAbs(f32, c.im, 10.472508, epsilon)); + try testing.expectApproxEqAbs(-73.460617, c.re, epsilon); + try testing.expectApproxEqAbs(10.472508, c.im, epsilon); } test sinh64 { + const epsilon = math.floatEps(f64); const a = Complex(f64).init(5, 3); const c = sinh(a); - try testing.expect(math.approxEqAbs(f64, c.re, -73.460617, epsilon)); - try testing.expect(math.approxEqAbs(f64, c.im, 10.472508, epsilon)); + try testing.expectApproxEqAbs(-73.46062169567367, c.re, epsilon); + try testing.expectApproxEqAbs(10.472508533940392, c.im, epsilon); } diff --git a/lib/std/math/complex/sqrt.zig b/lib/std/math/complex/sqrt.zig index ef24cc7726..f8f7798ae9 100644 --- a/lib/std/math/complex/sqrt.zig +++ b/lib/std/math/complex/sqrt.zig @@ -127,20 +127,20 @@ fn sqrt64(z: Complex(f64)) Complex(f64) { return result; } -const epsilon = 0.0001; - test sqrt32 { + const epsilon = math.floatEps(f32); const a = Complex(f32).init(5, 3); const c = sqrt(a); - try testing.expect(math.approxEqAbs(f32, c.re, 2.327117, epsilon)); - try testing.expect(math.approxEqAbs(f32, c.im, 0.644574, epsilon)); + try testing.expectApproxEqAbs(2.3271174, c.re, epsilon); + try testing.expectApproxEqAbs(0.6445742, c.im, epsilon); } test sqrt64 { + const epsilon = math.floatEps(f64); const a = Complex(f64).init(5, 3); const c = sqrt(a); - try testing.expect(math.approxEqAbs(f64, c.re, 2.3271175190399496, epsilon)); - try testing.expect(math.approxEqAbs(f64, c.im, 0.6445742373246469, epsilon)); + try testing.expectApproxEqAbs(2.3271175190399496, c.re, epsilon); + try testing.expectApproxEqAbs(0.6445742373246469, c.im, epsilon); } diff --git a/lib/std/math/complex/tan.zig b/lib/std/math/complex/tan.zig index ad5b1b47b6..4c3629046a 100644 --- a/lib/std/math/complex/tan.zig +++ b/lib/std/math/complex/tan.zig @@ -12,12 +12,11 @@ pub fn tan(z: anytype) Complex(@TypeOf(z.re, z.im)) { return Complex(T).init(r.im, -r.re); } -const epsilon = 0.0001; - test tan { + const epsilon = math.floatEps(f32); const a = Complex(f32).init(5, 3); const c = tan(a); - try testing.expect(math.approxEqAbs(f32, c.re, -0.002708233, epsilon)); - try testing.expect(math.approxEqAbs(f32, c.im, 1.004165, epsilon)); + try testing.expectApproxEqAbs(-0.002708233, c.re, epsilon); + try testing.expectApproxEqAbs(1.0041647, c.im, epsilon); } diff --git a/lib/std/math/complex/tanh.zig b/lib/std/math/complex/tanh.zig index 8a1d46eca7..ad75479322 100644 --- a/lib/std/math/complex/tanh.zig +++ b/lib/std/math/complex/tanh.zig @@ -101,20 +101,20 @@ fn tanh64(z: Complex(f64)) Complex(f64) { return Complex(f64).init((beta * rho * s) / den, t / den); } -const epsilon = 0.0001; - test tanh32 { + const epsilon = math.floatEps(f32); const a = Complex(f32).init(5, 3); const c = tanh(a); - try testing.expect(math.approxEqAbs(f32, c.re, 0.999913, epsilon)); - try testing.expect(math.approxEqAbs(f32, c.im, -0.000025, epsilon)); + try testing.expectApproxEqAbs(0.99991274, c.re, epsilon); + try testing.expectApproxEqAbs(-0.00002536878, c.im, epsilon); } test tanh64 { + const epsilon = math.floatEps(f64); const a = Complex(f64).init(5, 3); const c = tanh(a); - try testing.expect(math.approxEqAbs(f64, c.re, 0.999913, epsilon)); - try testing.expect(math.approxEqAbs(f64, c.im, -0.000025, epsilon)); + try testing.expectApproxEqAbs(0.9999128201513536, c.re, epsilon); + try testing.expectApproxEqAbs(-0.00002536867620767604, c.im, epsilon); } From 1e9278d718f7eec0aaad4dc296757c70e7f0f68b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Tue, 30 Jul 2024 00:11:12 +0200 Subject: [PATCH 039/266] std.Target: Remove `spir`/`spir64` architecture tags. These were for very old OpenCL have been long abandoned in favor of SPIR-V. * https://github.com/KhronosGroup/SPIR * https://github.com/KhronosGroup/SPIR-Tools --- lib/compiler/aro/aro/target.zig | 7 ------- lib/std/Target.zig | 18 ++---------------- src/Type.zig | 2 -- src/Zcu.zig | 2 -- src/codegen/llvm.zig | 6 ------ src/target.zig | 2 -- 6 files changed, 2 insertions(+), 35 deletions(-) diff --git a/lib/compiler/aro/aro/target.zig b/lib/compiler/aro/aro/target.zig index 8a864eb0ad..5822801036 100644 --- a/lib/compiler/aro/aro/target.zig +++ b/lib/compiler/aro/aro/target.zig @@ -53,7 +53,6 @@ pub fn intPtrType(target: std.Target) Type { .xcore, .hexagon, .m68k, - .spir, .spirv32, .arc, .avr, @@ -473,7 +472,6 @@ pub fn get32BitArchVariant(target: std.Target) ?std.Target { .x86, .xcore, .nvptx, - .spir, .kalimba, .lanai, .wasm32, @@ -487,7 +485,6 @@ pub fn get32BitArchVariant(target: std.Target) ?std.Target { .aarch64_be => copy.cpu.arch = .armeb, .nvptx64 => copy.cpu.arch = .nvptx, .wasm64 => copy.cpu.arch = .wasm32, - .spir64 => copy.cpu.arch = .spir, .spirv64 => copy.cpu.arch = .spirv32, .loongarch64 => copy.cpu.arch = .loongarch32, .mips64 => copy.cpu.arch = .mips, @@ -526,7 +523,6 @@ pub fn get64BitArchVariant(target: std.Target) ?std.Target { .bpfel, .nvptx64, .wasm64, - .spir64, .spirv64, .loongarch64, .mips64, @@ -550,7 +546,6 @@ pub fn get64BitArchVariant(target: std.Target) ?std.Target { .powerpcle => copy.cpu.arch = .powerpc64le, .riscv32 => copy.cpu.arch = .riscv64, .sparc => copy.cpu.arch = .sparc64, - .spir => copy.cpu.arch = .spir64, .spirv32 => copy.cpu.arch = .spirv64, .thumb => copy.cpu.arch = .aarch64, .thumbeb => copy.cpu.arch = .aarch64_be, @@ -607,8 +602,6 @@ pub fn toLLVMTriple(target: std.Target, buf: []u8) []const u8 { .xtensa => "xtensa", .nvptx => "nvptx", .nvptx64 => "nvptx64", - .spir => "spir", - .spir64 => "spir64", .spirv32 => "spirv32", .spirv64 => "spirv64", .kalimba => "kalimba", diff --git a/lib/std/Target.zig b/lib/std/Target.zig index a69038fae4..d76eb885c0 100644 --- a/lib/std/Target.zig +++ b/lib/std/Target.zig @@ -1027,8 +1027,6 @@ pub const Cpu = struct { xtensa, nvptx, nvptx64, - spir, - spir64, spirv, spirv32, spirv64, @@ -1048,6 +1046,8 @@ pub const Cpu = struct { // - amdil64 // - hsail // - hsail64 + // - spir + // - spir64 // - shave // - renderscript32 // - renderscript64 @@ -1178,7 +1178,6 @@ pub const Cpu = struct { .xcore => .XCORE, .xtensa => .XTENSA, .nvptx => .NONE, - .spir => .NONE, .kalimba => .CSR_KALIMBA, .lanai => .LANAI, .wasm32 => .NONE, @@ -1191,7 +1190,6 @@ pub const Cpu = struct { .riscv64 => .RISCV, .x86_64 => .X86_64, .nvptx64 => .NONE, - .spir64 => .NONE, .wasm64 => .NONE, .amdgcn => .AMDGPU, .bpfel => .BPF, @@ -1231,7 +1229,6 @@ pub const Cpu = struct { .xcore => .Unknown, .xtensa => .Unknown, .nvptx => .Unknown, - .spir => .Unknown, .kalimba => .Unknown, .lanai => .Unknown, .wasm32 => .Unknown, @@ -1244,7 +1241,6 @@ pub const Cpu = struct { .riscv64 => .RISCV64, .x86_64 => .X64, .nvptx64 => .Unknown, - .spir64 => .Unknown, .wasm64 => .Unknown, .amdgcn => .Unknown, .bpfel => .Unknown, @@ -1289,8 +1285,6 @@ pub const Cpu = struct { .wasm64, .xcore, .thumb, - .spir, - .spir64, .ve, .spu_2, // GPU bitness is opaque. For now, assume little endian. @@ -1769,8 +1763,6 @@ pub const DynamicLinker = struct { .msp430, .amdgcn, .xcore, - .spir, - .spir64, .kalimba, .lanai, .ve, @@ -1868,7 +1860,6 @@ pub fn ptrBitWidth_cpu_abi(cpu: Cpu, abi: Abi) u16 { .x86, .xcore, .nvptx, - .spir, .kalimba, .lanai, .wasm32, @@ -1887,7 +1878,6 @@ pub fn ptrBitWidth_cpu_abi(cpu: Cpu, abi: Abi) u16 { .riscv64, .x86_64, .nvptx64, - .spir64, .wasm64, .amdgcn, .bpfel, @@ -2368,7 +2358,6 @@ pub fn c_type_alignment(target: Target, c_type: CType) u16 { .xcore, .dxil, .loongarch32, - .spir, .spirv32, .kalimba, .ve, @@ -2391,7 +2380,6 @@ pub fn c_type_alignment(target: Target, c_type: CType) u16 { .nvptx, .nvptx64, .s390x, - .spir64, .spirv64, => 8, @@ -2476,7 +2464,6 @@ pub fn c_type_preferred_alignment(target: Target, c_type: CType) u16 { .xcore, .dxil, .loongarch32, - .spir, .spirv32, .kalimba, .ve, @@ -2506,7 +2493,6 @@ pub fn c_type_preferred_alignment(target: Target, c_type: CType) u16 { .nvptx, .nvptx64, .s390x, - .spir64, .spirv64, => 8, diff --git a/src/Type.zig b/src/Type.zig index 5bcffed6b7..22e2931019 100644 --- a/src/Type.zig +++ b/src/Type.zig @@ -1647,11 +1647,9 @@ pub fn maxIntAlignment(target: std.Target, use_llvm: bool) u16 { .csky, .arc, .m68k, - .spir, .kalimba, .spirv, .spirv32, - .spir64, .ve, .spirv64, .dxil, diff --git a/src/Zcu.zig b/src/Zcu.zig index c644dcce64..bc7d2d2e49 100644 --- a/src/Zcu.zig +++ b/src/Zcu.zig @@ -3253,7 +3253,6 @@ pub fn atomicPtrAlignment( .thumbeb, .x86, .xcore, - .spir, .kalimba, .lanai, .wasm32, @@ -3275,7 +3274,6 @@ pub fn atomicPtrAlignment( .riscv64, .sparc64, .s390x, - .spir64, .wasm64, .ve, .spirv64, diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 23f423ab2c..fcbfa06bd2 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -79,8 +79,6 @@ pub fn targetTriple(allocator: Allocator, target: std.Target) ![]const u8 { .xtensa => "xtensa", .nvptx => "nvptx", .nvptx64 => "nvptx64", - .spir => "spir", - .spir64 => "spir64", .spirv => "spirv", .spirv32 => "spirv32", .spirv64 => "spirv64", @@ -292,8 +290,6 @@ pub fn targetArch(arch_tag: std.Target.Cpu.Arch) llvm.ArchType { .xtensa => .xtensa, .nvptx => .nvptx, .nvptx64 => .nvptx64, - .spir => .spir, - .spir64 => .spir64, .spirv => .spirv, .spirv32 => .spirv32, .spirv64 => .spirv64, @@ -12095,8 +12091,6 @@ pub fn initializeLLVMTarget(arch: std.Target.Cpu.Arch) void { }, // LLVM backends that have no initialization functions. - .spir, - .spir64, .spirv, .spirv32, .spirv64, diff --git a/src/target.zig b/src/target.zig index ccf3ceb626..e30743f65c 100644 --- a/src/target.zig +++ b/src/target.zig @@ -148,8 +148,6 @@ pub fn hasLlvmSupport(target: std.Target, ofmt: std.Target.ObjectFormat) bool { .xtensa, .nvptx, .nvptx64, - .spir, - .spir64, .spirv, .spirv32, .spirv64, From c8ca05e93a5ad482279c9dd95e330ed6c1027c3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Tue, 30 Jul 2024 02:31:25 +0200 Subject: [PATCH 040/266] std.Target: Remove `sparcel` architecture tag. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit What is `sparcel`, you might ask? Good question! If you take a peek in the SPARC v8 manual, §2.2, it is quite explicit that SPARC v8 is a big-endian architecture. No little-endian or mixed-endian support to be found here. On the other hand, the SPARC v9 manual, in §3.2.1.2, states that it has support for mixed-endian operation, with big-endian mode being the default. Ok, so `sparcel` must just be referring to SPARC v9 running in little-endian mode, surely? Nope: * https://github.com/llvm/llvm-project/blob/40b4fd7a3e81d32b29364a1b15337bcf817659c0/llvm/lib/Target/Sparc/SparcTargetMachine.cpp#L226 * https://github.com/llvm/llvm-project/blob/40b4fd7a3e81d32b29364a1b15337bcf817659c0/llvm/lib/Target/Sparc/SparcTargetMachine.cpp#L104 So, `sparcel` in LLVM is referring to some sort of fantastical little-endian SPARC v8 architecture. I've scoured the internet and I can find absolutely no evidence that such a thing exists or has ever existed. In fact, I can find no evidence that a little-endian implementation of SPARC v9 ever existed, either. Or any SPARC version, actually! The support was added here: https://reviews.llvm.org/D8741 Notably, there is no mention whatsoever of what CPU this might be referring to, and no justification given for the "but some are little" comment added in the patch. My best guess is that this might have been some private exercise in creating a little-endian version of SPARC that never saw the light of day. Given that SPARC v8 explicitly doesn't support little-endian operation (let alone little-endian instruction encoding!), and no CPU is known to be implemented as such, I think it's very reasonable for us to just remove this support. --- lib/compiler/aro/aro/Compilation.zig | 4 ++-- lib/compiler/aro/aro/Driver/GCCDetector.zig | 2 +- lib/compiler/aro/aro/target.zig | 9 +++---- lib/compiler/aro/aro/toolchains/Linux.zig | 1 - lib/compiler_rt/atomics.zig | 2 +- lib/compiler_rt/clear_cache.zig | 2 +- lib/std/Target.zig | 26 +++++++-------------- lib/std/atomic.zig | 1 - lib/std/builtin.zig | 2 +- lib/std/c.zig | 6 ++--- lib/std/os/linux.zig | 4 +--- lib/std/os/linux/ioctl.zig | 1 - src/Type.zig | 1 - src/Zcu.zig | 1 - src/codegen/llvm.zig | 5 +--- src/glibc.zig | 4 ++-- src/link/Elf.zig | 2 +- src/link/Plan9/aout.zig | 2 +- src/target.zig | 4 +--- test/behavior/align.zig | 1 - test/llvm_targets.zig | 2 -- 21 files changed, 27 insertions(+), 55 deletions(-) diff --git a/lib/compiler/aro/aro/Compilation.zig b/lib/compiler/aro/aro/Compilation.zig index d03f5dc997..21d6823253 100644 --- a/lib/compiler/aro/aro/Compilation.zig +++ b/lib/compiler/aro/aro/Compilation.zig @@ -363,7 +363,7 @@ fn generateSystemDefines(comp: *Compilation, w: anytype) !void { \\#define __sparc_v9__ 1 \\ ), - .sparc, .sparcel => try w.writeAll( + .sparc => try w.writeAll( \\#define __sparc__ 1 \\#define __sparc 1 \\ @@ -534,7 +534,7 @@ pub fn generateBuiltinMacros(comp: *Compilation, system_defines_mode: SystemDefi if (system_defines_mode == .include_system_defines) { try buf.appendSlice( - \\#define __VERSION__ "Aro + \\#define __VERSION__ "Aro ++ @import("../backend.zig").version_str ++ "\"\n" ++ \\#define __Aro__ \\ diff --git a/lib/compiler/aro/aro/Driver/GCCDetector.zig b/lib/compiler/aro/aro/Driver/GCCDetector.zig index d13a63985d..720254316e 100644 --- a/lib/compiler/aro/aro/Driver/GCCDetector.zig +++ b/lib/compiler/aro/aro/Driver/GCCDetector.zig @@ -376,7 +376,7 @@ fn collectLibDirsAndTriples( biarch_libdirs.appendSliceAssumeCapacity(&RISCV32LibDirs); biarch_triple_aliases.appendSliceAssumeCapacity(&RISCV32Triples); }, - .sparc, .sparcel => { + .sparc => { lib_dirs.appendSliceAssumeCapacity(&SPARCv8LibDirs); triple_aliases.appendSliceAssumeCapacity(&SPARCv8Triples); biarch_libdirs.appendSliceAssumeCapacity(&SPARCv9LibDirs); diff --git a/lib/compiler/aro/aro/target.zig b/lib/compiler/aro/aro/target.zig index 5822801036..1035bbaf7a 100644 --- a/lib/compiler/aro/aro/target.zig +++ b/lib/compiler/aro/aro/target.zig @@ -58,7 +58,7 @@ pub fn intPtrType(target: std.Target) Type { .avr, => return .{ .specifier = .int }, - .sparc, .sparcel => switch (target.os.tag) { + .sparc => switch (target.os.tag) { .netbsd, .openbsd => {}, else => return .{ .specifier = .int }, }, @@ -132,7 +132,7 @@ pub fn defaultFunctionAlignment(target: std.Target) u8 { return switch (target.cpu.arch) { .arm, .armeb => 4, .aarch64, .aarch64_be => 4, - .sparc, .sparcel, .sparc64 => 4, + .sparc, .sparc64 => 4, .riscv64 => 2, else => 1, }; @@ -426,7 +426,7 @@ pub fn ldEmulationOption(target: std.Target, arm_endianness: ?std.builtin.Endian .powerpc64le => "elf64lppc", .riscv32 => "elf32lriscv", .riscv64 => "elf64lriscv", - .sparc, .sparcel => "elf32_sparc", + .sparc => "elf32_sparc", .sparc64 => "elf64_sparc", .loongarch32 => "elf32loongarch", .loongarch64 => "elf64loongarch", @@ -466,7 +466,6 @@ pub fn get32BitArchVariant(target: std.Target) ?std.Target { .powerpcle, .riscv32, .sparc, - .sparcel, .thumb, .thumbeb, .x86, @@ -510,7 +509,6 @@ pub fn get64BitArchVariant(target: std.Target) ?std.Target { .lanai, .m68k, .msp430, - .sparcel, .spu_2, .xcore, .xtensa, @@ -592,7 +590,6 @@ pub fn toLLVMTriple(target: std.Target, buf: []u8) []const u8 { .riscv64 => "riscv64", .sparc => "sparc", .sparc64 => "sparc64", - .sparcel => "sparcel", .s390x => "s390x", .thumb => "thumb", .thumbeb => "thumbeb", diff --git a/lib/compiler/aro/aro/toolchains/Linux.zig b/lib/compiler/aro/aro/toolchains/Linux.zig index f166e9e683..36ab916b10 100644 --- a/lib/compiler/aro/aro/toolchains/Linux.zig +++ b/lib/compiler/aro/aro/toolchains/Linux.zig @@ -357,7 +357,6 @@ fn getOSLibDir(target: std.Target) []const u8 { .powerpc, .powerpcle, .sparc, - .sparcel, => return "lib32", else => {}, } diff --git a/lib/compiler_rt/atomics.zig b/lib/compiler_rt/atomics.zig index 77519ee9a7..e82b6ab055 100644 --- a/lib/compiler_rt/atomics.zig +++ b/lib/compiler_rt/atomics.zig @@ -30,7 +30,7 @@ const largest_atomic_size = switch (arch) { // On SPARC systems that lacks CAS and/or swap instructions, the only // available atomic operation is a test-and-set (`ldstub`), so we force // every atomic memory access to go through the lock. - .sparc, .sparcel => if (cpu.features.featureSetHas(.hasleoncasa)) @sizeOf(usize) else 0, + .sparc => if (cpu.features.featureSetHas(.hasleoncasa)) @sizeOf(usize) else 0, // XXX: On x86/x86_64 we could check the presence of cmpxchg8b/cmpxchg16b // and set this parameter accordingly. diff --git a/lib/compiler_rt/clear_cache.zig b/lib/compiler_rt/clear_cache.zig index a5740c63fe..5e5f202928 100644 --- a/lib/compiler_rt/clear_cache.zig +++ b/lib/compiler_rt/clear_cache.zig @@ -41,7 +41,7 @@ fn clear_cache(start: usize, end: usize) callconv(.C) void { else => false, }; const sparc = switch (arch) { - .sparc, .sparc64, .sparcel => true, + .sparc, .sparc64 => true, else => false, }; const apple = switch (os) { diff --git a/lib/std/Target.zig b/lib/std/Target.zig index d76eb885c0..729b7ff2ce 100644 --- a/lib/std/Target.zig +++ b/lib/std/Target.zig @@ -201,7 +201,7 @@ pub const Os = struct { .mips, .mipsel, .mips64, .mips64el => "mips", .powerpc, .powerpcle, .powerpc64, .powerpc64le => "powerpc", .riscv32, .riscv64 => "riscv", - .sparc, .sparcel, .sparc64 => "sparc", + .sparc, .sparc64 => "sparc", .x86, .x86_64 => "x86", else => @tagName(arch), }, @@ -1017,7 +1017,6 @@ pub const Cpu = struct { riscv64, sparc, sparc64, - sparcel, s390x, thumb, thumbeb, @@ -1040,6 +1039,7 @@ pub const Cpu = struct { // LLVM tags deliberately omitted: // - aarch64_32 // - r600 + // - sparcel // - le32 // - le64 // - amdil @@ -1121,7 +1121,7 @@ pub const Cpu = struct { pub inline fn isSPARC(arch: Arch) bool { return switch (arch) { - .sparc, .sparcel, .sparc64 => true, + .sparc, .sparc64 => true, else => false, }; } @@ -1171,7 +1171,6 @@ pub const Cpu = struct { .powerpc, .powerpcle => .PPC, .riscv32 => .RISCV, .sparc => .SPARC, - .sparcel => .SPARC, .thumb => .ARM, .thumbeb => .ARM, .x86 => .@"386", @@ -1222,7 +1221,6 @@ pub const Cpu = struct { .powerpc, .powerpcle => .POWERPC, .riscv32 => .RISCV32, .sparc => .Unknown, - .sparcel => .Unknown, .thumb => .Thumb, .thumbeb => .Thumb, .x86 => .I386, @@ -1274,7 +1272,6 @@ pub const Cpu = struct { .msp430, .nvptx, .nvptx64, - .sparcel, .powerpcle, .powerpc64le, .riscv32, @@ -1341,7 +1338,7 @@ pub const Cpu = struct { .powerpc, .powerpcle, .powerpc64, .powerpc64le => "powerpc", .amdgcn => "amdgpu", .riscv32, .riscv64 => "riscv", - .sparc, .sparc64, .sparcel => "sparc", + .sparc, .sparc64 => "sparc", .s390x => "s390x", .x86, .x86_64 => "x86", .nvptx, .nvptx64 => "nvptx", @@ -1368,7 +1365,7 @@ pub const Cpu = struct { .powerpc, .powerpcle, .powerpc64, .powerpc64le => &powerpc.all_features, .amdgcn => &amdgpu.all_features, .riscv32, .riscv64 => &riscv.all_features, - .sparc, .sparc64, .sparcel => &sparc.all_features, + .sparc, .sparc64 => &sparc.all_features, .spirv32, .spirv64 => &spirv.all_features, .s390x => &s390x.all_features, .x86, .x86_64 => &x86.all_features, @@ -1398,7 +1395,7 @@ pub const Cpu = struct { .powerpc, .powerpcle, .powerpc64, .powerpc64le => comptime allCpusFromDecls(powerpc.cpu), .amdgcn => comptime allCpusFromDecls(amdgpu.cpu), .riscv32, .riscv64 => comptime allCpusFromDecls(riscv.cpu), - .sparc, .sparc64, .sparcel => comptime allCpusFromDecls(sparc.cpu), + .sparc, .sparc64 => comptime allCpusFromDecls(sparc.cpu), .spirv32, .spirv64 => comptime allCpusFromDecls(spirv.cpu), .s390x => comptime allCpusFromDecls(s390x.cpu), .x86, .x86_64 => comptime allCpusFromDecls(x86.cpu), @@ -1490,7 +1487,7 @@ pub const Cpu = struct { .riscv32 => &riscv.cpu.generic_rv32, .riscv64 => &riscv.cpu.generic_rv64, .spirv32, .spirv64 => &spirv.cpu.generic, - .sparc, .sparcel => &sparc.cpu.generic, + .sparc => &sparc.cpu.generic, .sparc64 => &sparc.cpu.v9, // 64-bit SPARC needs v9 as the baseline .s390x => &s390x.cpu.generic, .x86 => &x86.cpu.i386, @@ -1511,7 +1508,7 @@ pub const Cpu = struct { .x86 => &x86.cpu.pentium4, .nvptx, .nvptx64 => &nvptx.cpu.sm_20, .s390x => &s390x.cpu.arch8, - .sparc, .sparcel => &sparc.cpu.v8, + .sparc => &sparc.cpu.v8, .loongarch64 => &loongarch.cpu.loongarch64, else => generic(arch), @@ -1696,7 +1693,6 @@ pub const DynamicLinker = struct { .linux => switch (cpu.arch) { .x86, .sparc, - .sparcel, => init("/lib/ld-linux.so.2"), .aarch64 => init("/lib/ld-linux-aarch64.so.1"), @@ -1854,7 +1850,6 @@ pub fn ptrBitWidth_cpu_abi(cpu: Cpu, abi: Abi) u16 { .powerpc, .powerpcle, .riscv32, - .sparcel, .thumb, .thumbeb, .x86, @@ -1914,7 +1909,6 @@ pub fn stackAlignment(target: Target) u16 { .mips, .mipsel, .sparc, - .sparcel, => 8, .aarch64, .aarch64_be, @@ -2075,7 +2069,6 @@ pub fn c_type_bit_size(target: Target, c_type: CType) u16 { .s390x, .sparc, .sparc64, - .sparcel, .wasm32, .wasm64, => return 128, @@ -2180,7 +2173,6 @@ pub fn c_type_bit_size(target: Target, c_type: CType) u16 { .mips64el, .sparc, .sparc64, - .sparcel, .wasm32, .wasm64, => return 128, @@ -2374,7 +2366,6 @@ pub fn c_type_alignment(target: Target, c_type: CType) u16 { .mips, .mipsel, .sparc, - .sparcel, .sparc64, .lanai, .nvptx, @@ -2487,7 +2478,6 @@ pub fn c_type_preferred_alignment(target: Target, c_type: CType) u16 { .mips, .mipsel, .sparc, - .sparcel, .sparc64, .lanai, .nvptx, diff --git a/lib/std/atomic.zig b/lib/std/atomic.zig index be7203c0cf..6de6d5437b 100644 --- a/lib/std/atomic.zig +++ b/lib/std/atomic.zig @@ -486,7 +486,6 @@ pub const cache_line = switch (builtin.cpu.arch) { .riscv32, .riscv64, .sparc, - .sparcel, .sparc64, => 32, diff --git a/lib/std/builtin.zig b/lib/std/builtin.zig index 86f8da6cd4..176b17dd07 100644 --- a/lib/std/builtin.zig +++ b/lib/std/builtin.zig @@ -615,7 +615,7 @@ pub const VaList = switch (builtin.cpu.arch) { else => VaListPowerPc, }, .powerpc64, .powerpc64le => *u8, - .sparc, .sparcel, .sparc64 => *anyopaque, + .sparc, .sparc64 => *anyopaque, .spirv32, .spirv64 => *anyopaque, .s390x => VaListS390x, .wasm32, .wasm64 => *anyopaque, diff --git a/lib/std/c.zig b/lib/std/c.zig index ff2c7dbd85..f6398720bf 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -5779,7 +5779,7 @@ pub const ucontext_t = switch (native_os) { .x86 => 4, .mips, .mipsel, .mips64, .mips64el => 14, .arm, .armeb, .thumb, .thumbeb => 1, - .sparc, .sparcel, .sparc64 => if (@sizeOf(usize) == 4) 43 else 8, + .sparc, .sparc64 => if (@sizeOf(usize) == 4) 43 else 8, else => 0, } ]u32, @@ -6821,7 +6821,7 @@ pub const pthread_key_t = switch (native_os) { pub const padded_pthread_spin_t = switch (native_os) { .netbsd => switch (builtin.cpu.arch) { .x86, .x86_64 => u32, - .sparc, .sparcel, .sparc64 => u32, + .sparc, .sparc64 => u32, else => pthread_spin_t, }, else => void, @@ -6834,7 +6834,7 @@ pub const pthread_spin_t = switch (native_os) { .powerpc, .powerpc64, .powerpc64le => i32, .x86, .x86_64 => u8, .arm, .armeb, .thumb, .thumbeb => i32, - .sparc, .sparcel, .sparc64 => u8, + .sparc, .sparc64 => u8, .riscv32, .riscv64 => u32, else => @compileError("undefined pthread_spin_t for this arch"), }, diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index 4f3db110b2..7bc8439370 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -2485,7 +2485,7 @@ pub const E = switch (native_arch) { pub const init = errnoFromSyscall; }, - .sparc, .sparcel, .sparc64 => enum(u16) { + .sparc, .sparc64 => enum(u16) { /// No error occurred. SUCCESS = 0, @@ -4832,7 +4832,6 @@ pub const MINSIGSTKSZ = switch (native_arch) { => 2048, .loongarch64, .sparc, - .sparcel, .sparc64, => 4096, .aarch64, @@ -4869,7 +4868,6 @@ pub const SIGSTKSZ = switch (native_arch) { .aarch64_be, .loongarch64, .sparc, - .sparcel, .sparc64, => 16384, .powerpc64, diff --git a/lib/std/os/linux/ioctl.zig b/lib/std/os/linux/ioctl.zig index 7f5d36b72d..8b7cc80af9 100644 --- a/lib/std/os/linux/ioctl.zig +++ b/lib/std/os/linux/ioctl.zig @@ -11,7 +11,6 @@ const bits = switch (@import("builtin").cpu.arch) { .powerpc64le, .sparc, .sparc64, - .sparcel, => .{ .size = 13, .dir = 3, .none = 1, .read = 2, .write = 4 }, else => .{ .size = 14, .dir = 2, .none = 0, .read = 2, .write = 1 }, }; diff --git a/src/Type.zig b/src/Type.zig index 22e2931019..52844f7ca5 100644 --- a/src/Type.zig +++ b/src/Type.zig @@ -1602,7 +1602,6 @@ pub fn maxIntAlignment(target: std.Target, use_llvm: bool) u16 { .amdgcn, .riscv32, .sparc, - .sparcel, .s390x, .lanai, .wasm32, diff --git a/src/Zcu.zig b/src/Zcu.zig index bc7d2d2e49..54faf34bf4 100644 --- a/src/Zcu.zig +++ b/src/Zcu.zig @@ -3248,7 +3248,6 @@ pub fn atomicPtrAlignment( .powerpcle, .riscv32, .sparc, - .sparcel, .thumb, .thumbeb, .x86, diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index fcbfa06bd2..4317c1b41f 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -69,7 +69,6 @@ pub fn targetTriple(allocator: Allocator, target: std.Target) ![]const u8 { .riscv64 => "riscv64", .sparc => "sparc", .sparc64 => "sparc64", - .sparcel => "sparcel", .s390x => "s390x", .thumb => "thumb", .thumbeb => "thumbeb", @@ -280,7 +279,6 @@ pub fn targetArch(arch_tag: std.Target.Cpu.Arch) llvm.ArchType { .riscv64 => .riscv64, .sparc => .sparc, .sparc64 => .sparcv9, // In LLVM, sparc64 == sparcv9. - .sparcel => .sparcel, .s390x => .systemz, .thumb => .thumb, .thumbeb => .thumbeb, @@ -469,7 +467,6 @@ const DataLayoutBuilder = struct { .powerpcle, .riscv32, .sparc, - .sparcel, .thumb, .thumbeb, .xtensa, @@ -12004,7 +12001,7 @@ pub fn initializeLLVMTarget(arch: std.Target.Cpu.Arch) void { llvm.LLVMInitializeRISCVAsmPrinter(); llvm.LLVMInitializeRISCVAsmParser(); }, - .sparc, .sparc64, .sparcel => { + .sparc, .sparc64 => { llvm.LLVMInitializeSparcTarget(); llvm.LLVMInitializeSparcTargetInfo(); llvm.LLVMInitializeSparcTargetMC(); diff --git a/src/glibc.zig b/src/glibc.zig index 6474a23dce..5214d0a977 100644 --- a/src/glibc.zig +++ b/src/glibc.zig @@ -394,7 +394,7 @@ fn start_asm_path(comp: *Compilation, arena: Allocator, basename: []const u8) ![ const arch = comp.getTarget().cpu.arch; const is_ppc = arch == .powerpc or arch == .powerpc64 or arch == .powerpc64le; const is_aarch64 = arch == .aarch64 or arch == .aarch64_be; - const is_sparc = arch == .sparc or arch == .sparcel or arch == .sparc64; + const is_sparc = arch == .sparc or arch == .sparc64; const is_64 = comp.getTarget().ptrBitWidth() == 64; const s = path.sep_str; @@ -532,7 +532,7 @@ fn add_include_dirs_arch( const is_x86 = arch == .x86 or arch == .x86_64; const is_aarch64 = arch == .aarch64 or arch == .aarch64_be; const is_ppc = arch == .powerpc or arch == .powerpc64 or arch == .powerpc64le; - const is_sparc = arch == .sparc or arch == .sparcel or arch == .sparc64; + const is_sparc = arch == .sparc or arch == .sparc64; const is_64 = target.ptrBitWidth() == 64; const s = path.sep_str; diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 5e5c05c1cd..70c28dfa35 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -5175,7 +5175,7 @@ fn getLDMOption(target: std.Target) ?[]const u8 { .powerpc => return "elf32ppclinux", .powerpc64 => return "elf64ppc", .powerpc64le => return "elf64lppc", - .sparc, .sparcel => return "elf32_sparc", + .sparc => return "elf32_sparc", .sparc64 => return "elf64_sparc", .mips => return "elf32btsmip", .mipsel => return "elf32ltsmip", diff --git a/src/link/Plan9/aout.zig b/src/link/Plan9/aout.zig index 12dfc45873..3d35bb9acb 100644 --- a/src/link/Plan9/aout.zig +++ b/src/link/Plan9/aout.zig @@ -110,7 +110,7 @@ pub const R_MAGIC = _MAGIC(HDR_MAGIC, 28); // arm64 pub fn magicFromArch(arch: std.Target.Cpu.Arch) !u32 { return switch (arch) { .x86 => I_MAGIC, - .sparc => K_MAGIC, // TODO should sparc64 and sparcel go here? + .sparc => K_MAGIC, // TODO should sparc64 go here? .mips => V_MAGIC, .arm => E_MAGIC, .aarch64 => R_MAGIC, diff --git a/src/target.zig b/src/target.zig index e30743f65c..62f3e52cb8 100644 --- a/src/target.zig +++ b/src/target.zig @@ -138,7 +138,6 @@ pub fn hasLlvmSupport(target: std.Target, ofmt: std.Target.ObjectFormat) bool { .riscv64, .sparc, .sparc64, - .sparcel, .s390x, .thumb, .thumbeb, @@ -408,7 +407,7 @@ pub fn defaultFunctionAlignment(target: std.Target) Alignment { return switch (target.cpu.arch) { .arm, .armeb => .@"4", .aarch64, .aarch64_be => .@"4", - .sparc, .sparcel, .sparc64 => .@"4", + .sparc, .sparc64 => .@"4", .riscv64 => .@"2", else => .@"1", }; @@ -423,7 +422,6 @@ pub fn minFunctionAlignment(target: std.Target) Alignment { .riscv32, .riscv64, .sparc, - .sparcel, .sparc64, => .@"2", else => .@"1", diff --git a/test/behavior/align.zig b/test/behavior/align.zig index 564e08bac1..2c3fd66412 100644 --- a/test/behavior/align.zig +++ b/test/behavior/align.zig @@ -95,7 +95,6 @@ test "alignment and size of structs with 128-bit fields" { .amdgcn, .riscv32, .sparc, - .sparcel, .s390x, .lanai, .wasm32, diff --git a/test/llvm_targets.zig b/test/llvm_targets.zig index 853ca15879..71dc9e30a3 100644 --- a/test/llvm_targets.zig +++ b/test/llvm_targets.zig @@ -87,8 +87,6 @@ const targets = [_]std.Target.Query{ .{ .cpu_arch = .sparc, .os_tag = .freestanding, .abi = .none }, .{ .cpu_arch = .sparc, .os_tag = .linux, .abi = .gnu }, .{ .cpu_arch = .sparc, .os_tag = .linux, .abi = .none }, - .{ .cpu_arch = .sparcel, .os_tag = .freestanding, .abi = .none }, - .{ .cpu_arch = .sparcel, .os_tag = .linux, .abi = .gnu }, .{ .cpu_arch = .sparc64, .os_tag = .freestanding, .abi = .none }, .{ .cpu_arch = .sparc64, .os_tag = .linux, .abi = .gnu }, //.{ .cpu_arch = .spirv32, .os_tag = .opencl, .abi = .none }, From ef06e4b6e41b44b6be7417f2e29d7546aefa7b5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Tue, 30 Jul 2024 02:15:39 +0200 Subject: [PATCH 041/266] std.Target: Remove `ananas` OS tag. This is a fairly small hobby OS that has not seen development in 2 years. Our current policy is that hobby OSs should use the `other` tag. https://github.com/zhmu/ananas --- lib/compiler/aro/aro/target.zig | 1 - lib/std/Target.zig | 7 ------- src/codegen/llvm.zig | 2 -- 3 files changed, 10 deletions(-) diff --git a/lib/compiler/aro/aro/target.zig b/lib/compiler/aro/aro/target.zig index 1035bbaf7a..f538721281 100644 --- a/lib/compiler/aro/aro/target.zig +++ b/lib/compiler/aro/aro/target.zig @@ -614,7 +614,6 @@ pub fn toLLVMTriple(target: std.Target, buf: []u8) []const u8 { const llvm_os = switch (target.os.tag) { .freestanding => "unknown", - .ananas => "ananas", .cloudabi => "cloudabi", .dragonfly => "dragonfly", .freebsd => "freebsd", diff --git a/lib/std/Target.zig b/lib/std/Target.zig index 729b7ff2ce..3557bc1c72 100644 --- a/lib/std/Target.zig +++ b/lib/std/Target.zig @@ -17,7 +17,6 @@ pub const Os = struct { pub const Tag = enum { freestanding, - ananas, cloudabi, dragonfly, freebsd, @@ -140,7 +139,6 @@ pub const Os = struct { pub inline fn getVersionRangeTag(tag: Tag) @typeInfo(TaggedVersionRange).Union.tag_type.? { return switch (tag) { .freestanding, - .ananas, .cloudabi, .fuchsia, .ps3, @@ -372,7 +370,6 @@ pub const Os = struct { pub fn default(tag: Tag, arch: Cpu.Arch) VersionRange { return switch (tag) { .freestanding, - .ananas, .cloudabi, .fuchsia, .ps3, @@ -559,7 +556,6 @@ pub const Os = struct { .linux, .windows, .freestanding, - .ananas, .cloudabi, .fuchsia, .ps3, @@ -666,7 +662,6 @@ pub const Abi = enum { pub fn default(arch: Cpu.Arch, os: Os) Abi { return if (arch.isWasm()) .musl else switch (os.tag) { .freestanding, - .ananas, .cloudabi, .dragonfly, .ps3, @@ -1796,7 +1791,6 @@ pub const DynamicLinker = struct { // TODO go over each item in this list and either move it to the above list, or // implement the standard dynamic linker path code for it. - .ananas, .cloudabi, .fuchsia, .ps3, @@ -2089,7 +2083,6 @@ pub fn c_type_bit_size(target: Target, c_type: CType) u16 { .solaris, .illumos, .haiku, - .ananas, .fuchsia, .minix, => switch (target.cpu.arch) { diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 4317c1b41f..8f042cf265 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -133,7 +133,6 @@ pub fn targetTriple(allocator: Allocator, target: std.Target) ![]const u8 { .opencl, .glsl450, .plan9, - .ananas, .cloudabi, .minix, .contiki, @@ -205,7 +204,6 @@ pub fn targetOs(os_tag: std.Target.Os.Tag) llvm.OSType { .opencl, .glsl450, .plan9, - .ananas, .cloudabi, .minix, .contiki, From b49b7501cf040349eb6d86b63041c5152f23e5e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Tue, 30 Jul 2024 02:17:00 +0200 Subject: [PATCH 042/266] std.Target: Remove `cloudabi` OS tag. It's discontinued in favor of WASI. https://github.com/NuxiNL/cloudlibc --- lib/compiler/aro/aro/target.zig | 1 - lib/std/Target.zig | 7 ------- src/codegen/llvm.zig | 2 -- 3 files changed, 10 deletions(-) diff --git a/lib/compiler/aro/aro/target.zig b/lib/compiler/aro/aro/target.zig index f538721281..2f46ebd605 100644 --- a/lib/compiler/aro/aro/target.zig +++ b/lib/compiler/aro/aro/target.zig @@ -614,7 +614,6 @@ pub fn toLLVMTriple(target: std.Target, buf: []u8) []const u8 { const llvm_os = switch (target.os.tag) { .freestanding => "unknown", - .cloudabi => "cloudabi", .dragonfly => "dragonfly", .freebsd => "freebsd", .fuchsia => "fuchsia", diff --git a/lib/std/Target.zig b/lib/std/Target.zig index 3557bc1c72..190a2e553f 100644 --- a/lib/std/Target.zig +++ b/lib/std/Target.zig @@ -17,7 +17,6 @@ pub const Os = struct { pub const Tag = enum { freestanding, - cloudabi, dragonfly, freebsd, fuchsia, @@ -139,7 +138,6 @@ pub const Os = struct { pub inline fn getVersionRangeTag(tag: Tag) @typeInfo(TaggedVersionRange).Union.tag_type.? { return switch (tag) { .freestanding, - .cloudabi, .fuchsia, .ps3, .zos, @@ -370,7 +368,6 @@ pub const Os = struct { pub fn default(tag: Tag, arch: Cpu.Arch) VersionRange { return switch (tag) { .freestanding, - .cloudabi, .fuchsia, .ps3, .zos, @@ -556,7 +553,6 @@ pub const Os = struct { .linux, .windows, .freestanding, - .cloudabi, .fuchsia, .ps3, .zos, @@ -662,7 +658,6 @@ pub const Abi = enum { pub fn default(arch: Cpu.Arch, os: Os) Abi { return if (arch.isWasm()) .musl else switch (os.tag) { .freestanding, - .cloudabi, .dragonfly, .ps3, .zos, @@ -1791,7 +1786,6 @@ pub const DynamicLinker = struct { // TODO go over each item in this list and either move it to the above list, or // implement the standard dynamic linker path code for it. - .cloudabi, .fuchsia, .ps3, .zos, @@ -2275,7 +2269,6 @@ pub fn c_type_bit_size(target: Target, c_type: CType) u16 { .longdouble => return 80, }, - .cloudabi, .ps3, .zos, .rtems, diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 8f042cf265..23e39caa98 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -133,7 +133,6 @@ pub fn targetTriple(allocator: Allocator, target: std.Target) ![]const u8 { .opencl, .glsl450, .plan9, - .cloudabi, .minix, .contiki, .other, @@ -204,7 +203,6 @@ pub fn targetOs(os_tag: std.Target.Os.Tag) llvm.OSType { .opencl, .glsl450, .plan9, - .cloudabi, .minix, .contiki, => .UnknownOS, From c377316420be53f5db36f2fd2848d2abbfe34007 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Tue, 30 Jul 2024 03:29:37 +0200 Subject: [PATCH 043/266] std.Target: Add `tce`/`tcele` to the comment listing omitted architectures. --- lib/std/Target.zig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/std/Target.zig b/lib/std/Target.zig index 190a2e553f..1165bebf08 100644 --- a/lib/std/Target.zig +++ b/lib/std/Target.zig @@ -1030,6 +1030,8 @@ pub const Cpu = struct { // - aarch64_32 // - r600 // - sparcel + // - tce + // - tcele // - le32 // - le64 // - amdil From 62a01851d9c433ea3f2e98cc986e75d32aece443 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Tue, 30 Jul 2024 04:36:43 +0200 Subject: [PATCH 044/266] glibc: Add a temporary hack in abilists loading due to sparcel removal. Revert this commit on the next glibc abilists update. --- src/glibc.zig | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/glibc.zig b/src/glibc.zig index 5214d0a977..85f1828ad4 100644 --- a/src/glibc.zig +++ b/src/glibc.zig @@ -122,6 +122,17 @@ pub fn loadMetaData(gpa: Allocator, contents: []const u8) LoadMetaDataError!*ABI return error.ZigInstallationCorrupt; }; const arch_tag = std.meta.stringToEnum(std.Target.Cpu.Arch, arch_name) orelse { + // TODO: Remove this on the next glibc abilists update. + if (mem.eql(u8, arch_name, "sparcel")) { + targets[i] = .{ + .arch = .sparc, + .os = .linux, + .abi = .gnu, + }; + + continue; + } + log.err("abilists: unrecognized arch: '{s}'", .{arch_name}); return error.ZigInstallationCorrupt; }; From 733d25000bcb50104253d879734c89abde5e33b5 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 24 Jul 2024 06:42:43 +0200 Subject: [PATCH 045/266] elf: move ownership of input merge sections to Object --- src/link/Elf.zig | 20 ----------------- src/link/Elf/Object.zig | 48 ++++++++++++++++++++++++++++++----------- 2 files changed, 35 insertions(+), 33 deletions(-) diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 5e5c05c1cd..5c06ccc00c 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -215,8 +215,6 @@ merge_sections: std.ArrayListUnmanaged(MergeSection) = .{}, /// List of output merge subsections. /// Each subsection is akin to Atom but belongs to a MergeSection. merge_subsections: std.ArrayListUnmanaged(MergeSubsection) = .{}, -/// List of input merge sections as parsed from input relocatables. -merge_input_sections: std.ArrayListUnmanaged(InputMergeSection) = .{}, /// Table of last atom index in a section and matching atom free list if any. last_atom_and_free_list_table: LastAtomAndFreeListTable = .{}, @@ -390,8 +388,6 @@ pub fn createEmpty( _ = try self.addSection(.{ .name = "" }); // Append null symbol in output symtab try self.symtab.append(gpa, null_sym); - // Append null input merge section. - try self.merge_input_sections.append(gpa, .{}); if (!is_obj_or_ar) { try self.dynstrtab.append(gpa, 0); @@ -515,10 +511,6 @@ pub fn deinit(self: *Elf) void { } self.merge_sections.deinit(gpa); self.merge_subsections.deinit(gpa); - for (self.merge_input_sections.items) |*sect| { - sect.deinit(gpa); - } - self.merge_input_sections.deinit(gpa); for (self.last_atom_and_free_list_table.values()) |*value| { value.free_list.deinit(gpa); } @@ -5847,18 +5839,6 @@ pub fn comdatGroupOwner(self: *Elf, index: ComdatGroupOwner.Index) *ComdatGroupO return &self.comdat_groups_owners.items[index]; } -pub fn addInputMergeSection(self: *Elf) !InputMergeSection.Index { - const index: InputMergeSection.Index = @intCast(self.merge_input_sections.items.len); - const msec = try self.merge_input_sections.addOne(self.base.comp.gpa); - msec.* = .{}; - return index; -} - -pub fn inputMergeSection(self: *Elf, index: InputMergeSection.Index) ?*InputMergeSection { - if (index == 0) return null; - return &self.merge_input_sections.items[index]; -} - pub fn addMergeSubsection(self: *Elf) !MergeSubsection.Index { const index: MergeSubsection.Index = @intCast(self.merge_subsections.items.len); const msec = try self.merge_subsections.addOne(self.base.comp.gpa); diff --git a/src/link/Elf/Object.zig b/src/link/Elf/Object.zig index e24bc431e8..108abf32fb 100644 --- a/src/link/Elf/Object.zig +++ b/src/link/Elf/Object.zig @@ -15,7 +15,8 @@ comdat_groups: std.ArrayListUnmanaged(Elf.ComdatGroup.Index) = .{}, comdat_group_data: std.ArrayListUnmanaged(u32) = .{}, relocs: std.ArrayListUnmanaged(elf.Elf64_Rela) = .{}, -merge_sections: std.ArrayListUnmanaged(InputMergeSection.Index) = .{}, +input_merge_sections: std.ArrayListUnmanaged(InputMergeSection) = .{}, +input_merge_sections_indexes: std.ArrayListUnmanaged(InputMergeSection.Index) = .{}, fdes: std.ArrayListUnmanaged(Fde) = .{}, cies: std.ArrayListUnmanaged(Cie) = .{}, @@ -53,7 +54,11 @@ pub fn deinit(self: *Object, allocator: Allocator) void { self.fdes.deinit(allocator); self.cies.deinit(allocator); self.eh_frame_data.deinit(allocator); - self.merge_sections.deinit(allocator); + for (self.input_merge_sections.items) |*isec| { + isec.deinit(allocator); + } + self.input_merge_sections.deinit(allocator); + self.input_merge_sections_indexes.deinit(allocator); } pub fn parse(self: *Object, elf_file: *Elf) !void { @@ -62,6 +67,10 @@ pub fn parse(self: *Object, elf_file: *Elf) !void { const handle = elf_file.fileHandle(self.file_handle); try self.parseCommon(gpa, handle, elf_file); + + // Append null input merge section + try self.input_merge_sections.append(gpa, .{}); + try self.initAtoms(gpa, handle, elf_file); try self.initSymtab(gpa, elf_file); @@ -664,8 +673,9 @@ pub fn checkDuplicates(self: *Object, dupes: anytype, elf_file: *Elf) error{OutO pub fn initMergeSections(self: *Object, elf_file: *Elf) !void { const gpa = elf_file.base.comp.gpa; - try self.merge_sections.resize(gpa, self.shdrs.items.len); - @memset(self.merge_sections.items, 0); + try self.input_merge_sections.ensureUnusedCapacity(gpa, self.shdrs.items.len); + try self.input_merge_sections_indexes.resize(gpa, self.shdrs.items.len); + @memset(self.input_merge_sections_indexes.items, 0); for (self.shdrs.items, 0..) |shdr, shndx| { if (shdr.sh_flags & elf.SHF_MERGE == 0) continue; @@ -675,9 +685,9 @@ pub fn initMergeSections(self: *Object, elf_file: *Elf) !void { if (!atom_ptr.flags.alive) continue; if (atom_ptr.relocs(elf_file).len > 0) continue; - const imsec_idx = try elf_file.addInputMergeSection(); - const imsec = elf_file.inputMergeSection(imsec_idx).?; - self.merge_sections.items[shndx] = imsec_idx; + const imsec_idx = try self.addInputMergeSection(gpa); + const imsec = self.inputMergeSection(imsec_idx).?; + self.input_merge_sections_indexes.items[shndx] = imsec_idx; imsec.merge_section_index = try elf_file.getOrCreateMergeSection(atom_ptr.name(elf_file), shdr.sh_flags, shdr.sh_type); imsec.atom_index = atom_index; @@ -741,8 +751,8 @@ pub fn initMergeSections(self: *Object, elf_file: *Elf) !void { pub fn resolveMergeSubsections(self: *Object, elf_file: *Elf) !void { const gpa = elf_file.base.comp.gpa; - for (self.merge_sections.items) |index| { - const imsec = elf_file.inputMergeSection(index) orelse continue; + for (self.input_merge_sections_indexes.items) |index| { + const imsec = self.inputMergeSection(index) orelse continue; if (imsec.offsets.items.len == 0) continue; const msec = elf_file.mergeSection(imsec.merge_section_index); const atom_ptr = elf_file.atom(imsec.atom_index).?; @@ -776,8 +786,8 @@ pub fn resolveMergeSubsections(self: *Object, elf_file: *Elf) !void { if (esym.st_shndx == elf.SHN_COMMON or esym.st_shndx == elf.SHN_UNDEF or esym.st_shndx == elf.SHN_ABS) continue; - const imsec_index = self.merge_sections.items[esym.st_shndx]; - const imsec = elf_file.inputMergeSection(imsec_index) orelse continue; + const imsec_index = self.input_merge_sections_indexes.items[esym.st_shndx]; + const imsec = self.inputMergeSection(imsec_index) orelse continue; if (imsec.offsets.items.len == 0) continue; const msub_index, const offset = imsec.findSubsection(@intCast(esym.st_value)) orelse { var err = try elf_file.base.addErrorWithNotes(2); @@ -801,8 +811,8 @@ pub fn resolveMergeSubsections(self: *Object, elf_file: *Elf) !void { const esym = self.symtab.items[rel.r_sym()]; if (esym.st_type() != elf.STT_SECTION) continue; - const imsec_index = self.merge_sections.items[esym.st_shndx]; - const imsec = elf_file.inputMergeSection(imsec_index) orelse continue; + const imsec_index = self.input_merge_sections_indexes.items[esym.st_shndx]; + const imsec = self.inputMergeSection(imsec_index) orelse continue; if (imsec.offsets.items.len == 0) continue; const msub_index, const offset = imsec.findSubsection(@intCast(@as(i64, @intCast(esym.st_value)) + rel.r_addend)) orelse { var err = try elf_file.base.addErrorWithNotes(1); @@ -1184,6 +1194,18 @@ fn preadRelocsAlloc(self: Object, allocator: Allocator, handle: std.fs.File, shn return @as([*]align(1) const elf.Elf64_Rela, @ptrCast(raw.ptr))[0..num]; } +fn addInputMergeSection(self: *Object, allocator: Allocator) !InputMergeSection.Index { + const index: InputMergeSection.Index = @intCast(self.input_merge_sections.items.len); + const msec = try self.input_merge_sections.addOne(allocator); + msec.* = .{}; + return index; +} + +fn inputMergeSection(self: *Object, index: InputMergeSection.Index) ?*InputMergeSection { + if (index == 0) return null; + return &self.input_merge_sections.items[index]; +} + pub fn format( self: *Object, comptime unused_fmt_string: []const u8, From f1fedb3a51d4d9a37e4568ce1a80149bb5a610f7 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 24 Jul 2024 10:15:09 +0200 Subject: [PATCH 046/266] elf: move ownership of comdat groups to Object --- src/link/Elf.zig | 38 ++++++++++++++--------------- src/link/Elf/Object.zig | 24 ++++++++++++------ src/link/Elf/relocatable.zig | 5 ++-- src/link/Elf/synthetic_sections.zig | 21 ++++++++++------ 4 files changed, 51 insertions(+), 37 deletions(-) diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 5c06ccc00c..0ee8edf379 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -219,7 +219,6 @@ merge_subsections: std.ArrayListUnmanaged(MergeSubsection) = .{}, /// Table of last atom index in a section and matching atom free list if any. last_atom_and_free_list_table: LastAtomAndFreeListTable = .{}, -comdat_groups: std.ArrayListUnmanaged(ComdatGroup) = .{}, comdat_groups_owners: std.ArrayListUnmanaged(ComdatGroupOwner) = .{}, comdat_groups_table: std.AutoHashMapUnmanaged(u32, ComdatGroupOwner.Index) = .{}, @@ -516,7 +515,6 @@ pub fn deinit(self: *Elf) void { } self.last_atom_and_free_list_table.deinit(gpa); - self.comdat_groups.deinit(gpa); self.comdat_groups_owners.deinit(gpa); self.comdat_groups_table.deinit(gpa); self.strings.deinit(gpa); @@ -1998,8 +1996,7 @@ pub fn resolveSymbols(self: *Elf) void { // Dedup comdat groups. for (self.objects.items) |index| { const object = self.file(index).?.object; - for (object.comdat_groups.items) |cg_index| { - const cg = self.comdatGroup(cg_index); + for (object.comdat_groups.items) |cg| { const cg_owner = self.comdatGroupOwner(cg.owner); const owner_file_index = if (self.file(cg_owner.file)) |file_ptr| file_ptr.object.index @@ -2011,8 +2008,7 @@ pub fn resolveSymbols(self: *Elf) void { for (self.objects.items) |index| { const object = self.file(index).?.object; - for (object.comdat_groups.items) |cg_index| { - const cg = self.comdatGroup(cg_index); + for (object.comdat_groups.items) |cg| { const cg_owner = self.comdatGroupOwner(cg.owner); if (cg_owner.file != index) { for (cg.comdatGroupMembers(self)) |shndx| { @@ -5822,18 +5818,6 @@ pub fn getOrCreateComdatGroupOwner(self: *Elf, name: [:0]const u8) !GetOrCreateC }; } -pub fn addComdatGroup(self: *Elf) !ComdatGroup.Index { - const gpa = self.base.comp.gpa; - const index = @as(ComdatGroup.Index, @intCast(self.comdat_groups.items.len)); - _ = try self.comdat_groups.addOne(gpa); - return index; -} - -pub fn comdatGroup(self: *Elf, index: ComdatGroup.Index) *ComdatGroup { - assert(index < self.comdat_groups.items.len); - return &self.comdat_groups.items[index]; -} - pub fn comdatGroupOwner(self: *Elf, index: ComdatGroupOwner.Index) *ComdatGroupOwner { assert(index < self.comdat_groups_owners.items.len); return &self.comdat_groups_owners.items[index]; @@ -6233,7 +6217,7 @@ fn fmtDumpState( try writer.writeAll("Output COMDAT groups\n"); for (self.comdat_group_sections.items) |cg| { - try writer.print(" shdr({d}) : COMDAT({d})\n", .{ cg.shndx, cg.cg_index }); + try writer.print(" shdr({d}) : COMDAT({})\n", .{ cg.shndx, cg.cg_ref }); } try writer.writeAll("\nOutput merge sections\n"); @@ -6376,6 +6360,22 @@ pub const SystemLib = struct { path: []const u8, }; +pub const Ref = struct { + index: u32, + file: u32, + + pub fn format( + ref: Ref, + comptime unused_fmt_string: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, + ) !void { + _ = unused_fmt_string; + _ = options; + try writer.print("ref({},{})", .{ ref.index, ref.file }); + } +}; + const LastAtomAndFreeList = struct { /// Index of the last allocated atom in this section. last_atom_index: Atom.Index = 0, diff --git a/src/link/Elf/Object.zig b/src/link/Elf/Object.zig index 108abf32fb..c7f1186c4b 100644 --- a/src/link/Elf/Object.zig +++ b/src/link/Elf/Object.zig @@ -11,10 +11,11 @@ strtab: std.ArrayListUnmanaged(u8) = .{}, first_global: ?Symbol.Index = null, symbols: std.ArrayListUnmanaged(Symbol.Index) = .{}, atoms: std.ArrayListUnmanaged(Atom.Index) = .{}, -comdat_groups: std.ArrayListUnmanaged(Elf.ComdatGroup.Index) = .{}, -comdat_group_data: std.ArrayListUnmanaged(u32) = .{}, relocs: std.ArrayListUnmanaged(elf.Elf64_Rela) = .{}, +comdat_groups: std.ArrayListUnmanaged(Elf.ComdatGroup) = .{}, +comdat_group_data: std.ArrayListUnmanaged(u32) = .{}, + input_merge_sections: std.ArrayListUnmanaged(InputMergeSection) = .{}, input_merge_sections_indexes: std.ArrayListUnmanaged(InputMergeSection.Index) = .{}, @@ -218,8 +219,8 @@ fn initAtoms(self: *Object, allocator: Allocator, handle: std.fs.File, elf_file: try self.comdat_group_data.appendUnalignedSlice(allocator, group_members[1..]); const gop = try elf_file.getOrCreateComdatGroupOwner(group_signature); - const comdat_group_index = try elf_file.addComdatGroup(); - const comdat_group = elf_file.comdatGroup(comdat_group_index); + const comdat_group_index = try self.addComdatGroup(allocator); + const comdat_group = self.comdatGroup(comdat_group_index); comdat_group.* = .{ .owner = gop.index, .file = self.index, @@ -227,7 +228,6 @@ fn initAtoms(self: *Object, allocator: Allocator, handle: std.fs.File, elf_file: .members_start = group_start, .members_len = @intCast(group_nmembers - 1), }; - try self.comdat_groups.append(allocator, comdat_group_index); }, elf.SHT_SYMTAB_SHNDX => @panic("TODO SHT_SYMTAB_SHNDX"), @@ -1206,6 +1206,17 @@ fn inputMergeSection(self: *Object, index: InputMergeSection.Index) ?*InputMerge return &self.input_merge_sections.items[index]; } +fn addComdatGroup(self: *Object, allocator: Allocator) !Elf.ComdatGroup.Index { + const index = @as(Elf.ComdatGroup.Index, @intCast(self.comdat_groups.items.len)); + _ = try self.comdat_groups.addOne(allocator); + return index; +} + +pub fn comdatGroup(self: *Object, index: Elf.ComdatGroup.Index) *Elf.ComdatGroup { + assert(index < self.comdat_groups.items.len); + return &self.comdat_groups.items[index]; +} + pub fn format( self: *Object, comptime unused_fmt_string: []const u8, @@ -1337,8 +1348,7 @@ fn formatComdatGroups( const object = ctx.object; const elf_file = ctx.elf_file; try writer.writeAll(" COMDAT groups\n"); - for (object.comdat_groups.items) |cg_index| { - const cg = elf_file.comdatGroup(cg_index); + for (object.comdat_groups.items, 0..) |cg, cg_index| { const cg_owner = elf_file.comdatGroupOwner(cg.owner); if (cg_owner.file != object.index) continue; try writer.print(" COMDAT({d})\n", .{cg_index}); diff --git a/src/link/Elf/relocatable.zig b/src/link/Elf/relocatable.zig index 1bbb320c98..d967b089ba 100644 --- a/src/link/Elf/relocatable.zig +++ b/src/link/Elf/relocatable.zig @@ -319,8 +319,7 @@ fn initComdatGroups(elf_file: *Elf) !void { for (elf_file.objects.items) |index| { const object = elf_file.file(index).?.object; - for (object.comdat_groups.items) |cg_index| { - const cg = elf_file.comdatGroup(cg_index); + for (object.comdat_groups.items, 0..) |cg, cg_index| { const cg_owner = elf_file.comdatGroupOwner(cg.owner); if (cg_owner.file != index) continue; @@ -333,7 +332,7 @@ fn initComdatGroups(elf_file: *Elf) !void { .addralign = @alignOf(u32), .offset = std.math.maxInt(u64), }), - .cg_index = cg_index, + .cg_ref = .{ .index = @intCast(cg_index), .file = index }, }; } } diff --git a/src/link/Elf/synthetic_sections.zig b/src/link/Elf/synthetic_sections.zig index 5a96650bc5..c483cfd749 100644 --- a/src/link/Elf/synthetic_sections.zig +++ b/src/link/Elf/synthetic_sections.zig @@ -1670,30 +1670,35 @@ pub const VerneedSection = struct { pub const ComdatGroupSection = struct { shndx: u32, - cg_index: u32, + cg_ref: Elf.Ref, - fn file(cgs: ComdatGroupSection, elf_file: *Elf) ?File { - const cg = elf_file.comdatGroup(cgs.cg_index); + fn ownerFile(cgs: ComdatGroupSection, elf_file: *Elf) ?File { + const cg = cgs.comdatGroup(elf_file); const cg_owner = elf_file.comdatGroupOwner(cg.owner); return elf_file.file(cg_owner.file); } + fn comdatGroup(cgs: ComdatGroupSection, elf_file: *Elf) *Elf.ComdatGroup { + const cg_file = elf_file.file(cgs.cg_ref.file).?; + return cg_file.object.comdatGroup(cgs.cg_ref.index); + } + pub fn symbol(cgs: ComdatGroupSection, elf_file: *Elf) Symbol.Index { - const cg = elf_file.comdatGroup(cgs.cg_index); - const object = cgs.file(elf_file).?.object; + const cg = cgs.comdatGroup(elf_file); + const object = cgs.ownerFile(elf_file).?.object; const shdr = object.shdrs.items[cg.shndx]; return object.symbols.items[shdr.sh_info]; } pub fn size(cgs: ComdatGroupSection, elf_file: *Elf) usize { - const cg = elf_file.comdatGroup(cgs.cg_index); + const cg = cgs.comdatGroup(elf_file); const members = cg.comdatGroupMembers(elf_file); return (members.len + 1) * @sizeOf(u32); } pub fn write(cgs: ComdatGroupSection, elf_file: *Elf, writer: anytype) !void { - const cg = elf_file.comdatGroup(cgs.cg_index); - const object = cgs.file(elf_file).?.object; + const cg = cgs.comdatGroup(elf_file); + const object = cgs.ownerFile(elf_file).?.object; const members = cg.comdatGroupMembers(elf_file); try writer.writeInt(u32, elf.GRP_COMDAT, .little); for (members) |shndx| { From 669f28594393e90e4d1aacd0d28f67ebe015b922 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 24 Jul 2024 16:03:29 +0200 Subject: [PATCH 047/266] elf: move ownership of atoms into objects --- src/link/Elf.zig | 138 ++++-------- src/link/Elf/Atom.zig | 56 ++--- src/link/Elf/LinkerDefined.zig | 9 +- src/link/Elf/Object.zig | 322 +++++++++++++++++----------- src/link/Elf/SharedObject.zig | 2 +- src/link/Elf/Symbol.zig | 8 +- src/link/Elf/ZigObject.zig | 185 ++++++++++------ src/link/Elf/eh_frame.zig | 4 +- src/link/Elf/file.zig | 29 ++- src/link/Elf/gc.zig | 12 +- src/link/Elf/relocatable.zig | 24 +-- src/link/Elf/synthetic_sections.zig | 8 +- src/link/Elf/thunks.zig | 15 +- src/link/MachO/InternalObject.zig | 3 +- src/link/MachO/ZigObject.zig | 2 +- 15 files changed, 464 insertions(+), 353 deletions(-) diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 0ee8edf379..d140b0a6e3 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -54,7 +54,7 @@ phdr_to_shdr_table: std.AutoHashMapUnmanaged(u32, u32) = .{}, shdr_table_offset: ?u64 = null, /// Table of lists of atoms per output section. /// This table is not used to track incrementally generated atoms. -output_sections: std.AutoArrayHashMapUnmanaged(u32, std.ArrayListUnmanaged(Atom.Index)) = .{}, +output_sections: std.AutoArrayHashMapUnmanaged(u32, std.ArrayListUnmanaged(Ref)) = .{}, output_rela_sections: std.AutoArrayHashMapUnmanaged(u32, RelaSection) = .{}, /// Stored in native-endian format, depending on target endianness needs to be bswapped on read/write. @@ -203,10 +203,6 @@ resolver: std.AutoArrayHashMapUnmanaged(u32, Symbol.Index) = .{}, has_text_reloc: bool = false, num_ifunc_dynrelocs: usize = 0, -/// List of atoms that are owned directly by the linker. -atoms: std.ArrayListUnmanaged(Atom) = .{}, -atoms_extra: std.ArrayListUnmanaged(u32) = .{}, - /// List of range extension thunks. thunks: std.ArrayListUnmanaged(Thunk) = .{}, @@ -375,9 +371,6 @@ pub fn createEmpty( try self.symbols.append(gpa, .{}); // Index 0 is always a null symbol. try self.symbols_extra.append(gpa, 0); - // Allocate atom index 0 to null atom - try self.atoms.append(gpa, .{}); - try self.atoms_extra.append(gpa, 0); // Append null file at index 0 try self.files.append(gpa, .null); // Append null byte to string tables @@ -499,8 +492,6 @@ pub fn deinit(self: *Elf) void { self.resolver.deinit(gpa); self.start_stop_indexes.deinit(gpa); - self.atoms.deinit(gpa); - self.atoms_extra.deinit(gpa); for (self.thunks.items) |*th| { th.deinit(gpa); } @@ -1305,6 +1296,8 @@ pub fn flushModule(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_nod const index = @as(File.Index, @intCast(try self.files.addOne(gpa))); self.files.set(index, .{ .linker_defined = .{ .index = index } }); self.linker_defined_index = index; + const object = self.file(index).?.linker_defined; + try object.init(gpa); } // Now, we are ready to resolve the symbols across all input files. @@ -1379,15 +1372,15 @@ pub fn flushModule(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_nod // Beyond this point, everything has been allocated a virtual address and we can resolve // the relocations, and commit objects to file. - if (self.zigObjectPtr()) |zig_object| { + if (self.zigObjectPtr()) |zo| { var has_reloc_errors = false; - for (zig_object.atoms.items) |atom_index| { - const atom_ptr = self.atom(atom_index) orelse continue; + for (zo.atoms_indexes.items) |atom_index| { + const atom_ptr = zo.atom(atom_index) orelse continue; if (!atom_ptr.flags.alive) continue; const out_shndx = atom_ptr.outputShndx() orelse continue; const shdr = &self.shdrs.items[out_shndx]; if (shdr.sh_type == elf.SHT_NOBITS) continue; - const code = try zig_object.codeAlloc(self, atom_index); + const code = try zo.codeAlloc(self, atom_index); defer gpa.free(code); const file_offset = shdr.sh_offset + @as(u64, @intCast(atom_ptr.value)); atom_ptr.resolveRelocsAlloc(self, code) catch |err| switch (err) { @@ -2012,8 +2005,8 @@ pub fn resolveSymbols(self: *Elf) void { const cg_owner = self.comdatGroupOwner(cg.owner); if (cg_owner.file != index) { for (cg.comdatGroupMembers(self)) |shndx| { - const atom_index = object.atoms.items[shndx]; - if (self.atom(atom_index)) |atom_ptr| { + const atom_index = object.atoms_indexes.items[shndx]; + if (object.atom(atom_index)) |atom_ptr| { atom_ptr.flags.alive = false; atom_ptr.markFdesDead(self); } @@ -2117,7 +2110,7 @@ fn claimUnresolved(self: *Elf) void { fn scanRelocs(self: *Elf) !void { const gpa = self.base.comp.gpa; - var undefs = std.AutoHashMap(Symbol.Index, std.ArrayList(Atom.Index)).init(gpa); + var undefs = std.AutoHashMap(Symbol.Index, std.ArrayList(Ref)).init(gpa); defer { var it = undefs.iterator(); while (it.next()) |entry| { @@ -3721,11 +3714,11 @@ fn sortInitFini(self: *Elf) !void { const Entry = struct { priority: i32, - atom_index: Atom.Index, + atom_ref: Ref, pub fn lessThan(ctx: *Elf, lhs: @This(), rhs: @This()) bool { if (lhs.priority == rhs.priority) { - return ctx.atom(lhs.atom_index).?.priority(ctx) < ctx.atom(rhs.atom_index).?.priority(ctx); + return ctx.atom(lhs.atom_ref).?.priority(ctx) < ctx.atom(rhs.atom_ref).?.priority(ctx); } return lhs.priority < rhs.priority; } @@ -3756,8 +3749,8 @@ fn sortInitFini(self: *Elf) !void { try entries.ensureTotalCapacityPrecise(atom_list.items.len); defer entries.deinit(); - for (atom_list.items) |atom_index| { - const atom_ptr = self.atom(atom_index).?; + for (atom_list.items) |ref| { + const atom_ptr = self.atom(ref).?; const object = atom_ptr.file(self).?.object; const priority = blk: { if (is_ctor_dtor) { @@ -3770,14 +3763,14 @@ fn sortInitFini(self: *Elf) !void { const priority = std.fmt.parseUnsigned(u16, it.first(), 10) catch default; break :blk priority; }; - entries.appendAssumeCapacity(.{ .priority = priority, .atom_index = atom_index }); + entries.appendAssumeCapacity(.{ .priority = priority, .atom_ref = ref }); } mem.sort(Entry, entries.items, self, Entry.lessThan); atom_list.clearRetainingCapacity(); for (entries.items) |entry| { - atom_list.appendAssumeCapacity(entry.atom_index); + atom_list.appendAssumeCapacity(entry.atom_ref); } } } @@ -4143,23 +4136,23 @@ fn resetShdrIndexes(self: *Elf, backlinks: []const u32) !void { } } - if (self.zigObjectPtr()) |zig_object| { - for (zig_object.atoms.items) |atom_index| { - const atom_ptr = self.atom(atom_index) orelse continue; + if (self.zigObjectPtr()) |zo| { + for (zo.atoms_indexes.items) |atom_index| { + const atom_ptr = zo.atom(atom_index) orelse continue; atom_ptr.output_section_index = backlinks[atom_ptr.output_section_index]; } - for (zig_object.locals()) |local_index| { + for (zo.locals()) |local_index| { const local = self.symbol(local_index); local.output_section_index = backlinks[local.output_section_index]; } - for (zig_object.globals()) |global_index| { + for (zo.globals()) |global_index| { const global = self.symbol(global_index); const atom_ptr = global.atom(self) orelse continue; if (!atom_ptr.flags.alive) continue; // TODO claim unresolved for objects - if (global.file(self).?.index() != zig_object.index) continue; + if (global.file(self).?.index() != zo.index) continue; const out_shndx = global.outputShndx() orelse continue; global.output_section_index = backlinks[out_shndx]; } @@ -4182,8 +4175,8 @@ fn updateSectionSizes(self: *Elf) !void { const shdr = &self.shdrs.items[shndx]; if (atom_list.items.len == 0) continue; if (self.requiresThunks() and shdr.sh_flags & elf.SHF_EXECINSTR != 0) continue; - for (atom_list.items) |atom_index| { - const atom_ptr = self.atom(atom_index) orelse continue; + for (atom_list.items) |ref| { + const atom_ptr = self.atom(ref) orelse continue; if (!atom_ptr.flags.alive) continue; const offset = atom_ptr.alignment.forward(shdr.sh_size); const padding = offset - shdr.sh_size; @@ -4618,7 +4611,7 @@ fn allocateSpecialPhdrs(self: *Elf) void { fn writeAtoms(self: *Elf) !void { const gpa = self.base.comp.gpa; - var undefs = std.AutoHashMap(Symbol.Index, std.ArrayList(Atom.Index)).init(gpa); + var undefs = std.AutoHashMap(Symbol.Index, std.ArrayList(Ref)).init(gpa); defer { var it = undefs.iterator(); while (it.next()) |entry| { @@ -4666,21 +4659,21 @@ fn writeAtoms(self: *Elf) !void { 0; @memset(buffer, padding_byte); - for (atom_list.items) |atom_index| { - const atom_ptr = self.atom(atom_index).?; + for (atom_list.items) |ref| { + const atom_ptr = self.atom(ref).?; assert(atom_ptr.flags.alive); const offset = math.cast(usize, atom_ptr.value - @as(i64, @intCast(base_offset))) orelse return error.Overflow; const size = math.cast(usize, atom_ptr.size) orelse return error.Overflow; - log.debug("writing atom({d}) at 0x{x}", .{ atom_index, sh_offset + offset }); + log.debug("writing atom({}) at 0x{x}", .{ ref, sh_offset + offset }); // TODO decompress directly into provided buffer const out_code = buffer[offset..][0..size]; const in_code = switch (atom_ptr.file(self).?) { - .object => |x| try x.codeDecompressAlloc(self, atom_index), - .zig_object => |x| try x.codeAlloc(self, atom_index), + .object => |x| try x.codeDecompressAlloc(self, ref.index), + .zig_object => |x| try x.codeAlloc(self, ref.index), else => unreachable, }; defer gpa.free(in_code); @@ -5598,64 +5591,6 @@ fn getStartStopBasename(self: *Elf, shdr: elf.Elf64_Shdr) ?[]const u8 { return null; } -pub fn atom(self: *Elf, atom_index: Atom.Index) ?*Atom { - if (atom_index == 0) return null; - assert(atom_index < self.atoms.items.len); - return &self.atoms.items[atom_index]; -} - -pub fn addAtom(self: *Elf) !Atom.Index { - const gpa = self.base.comp.gpa; - const index = @as(Atom.Index, @intCast(self.atoms.items.len)); - const atom_ptr = try self.atoms.addOne(gpa); - atom_ptr.* = .{ .atom_index = index }; - return index; -} - -pub fn addAtomExtra(self: *Elf, extra: Atom.Extra) !u32 { - const fields = @typeInfo(Atom.Extra).Struct.fields; - try self.atoms_extra.ensureUnusedCapacity(self.base.comp.gpa, fields.len); - return self.addAtomExtraAssumeCapacity(extra); -} - -pub fn addAtomExtraAssumeCapacity(self: *Elf, extra: Atom.Extra) u32 { - const index = @as(u32, @intCast(self.atoms_extra.items.len)); - const fields = @typeInfo(Atom.Extra).Struct.fields; - inline for (fields) |field| { - self.atoms_extra.appendAssumeCapacity(switch (field.type) { - u32 => @field(extra, field.name), - else => @compileError("bad field type"), - }); - } - return index; -} - -pub fn atomExtra(self: *Elf, index: u32) ?Atom.Extra { - if (index == 0) return null; - const fields = @typeInfo(Atom.Extra).Struct.fields; - var i: usize = index; - var result: Atom.Extra = undefined; - inline for (fields) |field| { - @field(result, field.name) = switch (field.type) { - u32 => self.atoms_extra.items[i], - else => @compileError("bad field type"), - }; - i += 1; - } - return result; -} - -pub fn setAtomExtra(self: *Elf, index: u32, extra: Atom.Extra) void { - assert(index > 0); - const fields = @typeInfo(Atom.Extra).Struct.fields; - inline for (fields, 0..) |field, i| { - self.atoms_extra.items[index + i] = switch (field.type) { - u32 => @field(extra, field.name), - else => @compileError("bad field type"), - }; - } -} - pub fn addThunk(self: *Elf) !Thunk.Index { const index = @as(Thunk.Index, @intCast(self.thunks.items.len)); const th = try self.thunks.addOne(self.base.comp.gpa); @@ -5692,6 +5627,11 @@ pub fn fileHandle(self: Elf, index: File.HandleIndex) File.Handle { return self.file_handles.items[index]; } +pub fn atom(self: *Elf, ref: Ref) ?*Atom { + const file_ptr = self.file(ref.file) orelse return null; + return file_ptr.atom(ref.index); +} + /// Returns pointer-to-symbol described at sym_index. pub fn symbol(self: *Elf, sym_index: Symbol.Index) *Symbol { return &self.symbols.items[sym_index]; @@ -5938,9 +5878,9 @@ fn reportUndefinedSymbols(self: *Elf, undefs: anytype) !void { var err = try self.base.addErrorWithNotesAssumeCapacity(nnotes); try err.addMsg("undefined symbol: {s}", .{self.symbol(undef_index).name(self)}); - for (atoms[0..natoms]) |atom_index| { - const atom_ptr = self.atom(atom_index).?; - const file_ptr = self.file(atom_ptr.file_index).?; + for (atoms[0..natoms]) |ref| { + const atom_ptr = self.atom(ref).?; + const file_ptr = self.file(ref.file).?; try err.addNote("referenced by {s}:{s}", .{ file_ptr.fmtPath(), atom_ptr.name(self) }); } @@ -6401,7 +6341,7 @@ const LastAtomAndFreeListTable = std.AutoArrayHashMapUnmanaged(u32, LastAtomAndF const RelaSection = struct { shndx: u32, - atom_list: std.ArrayListUnmanaged(Atom.Index) = .{}, + atom_list: std.ArrayListUnmanaged(Ref) = .{}, }; const RelaSectionTable = std.AutoArrayHashMapUnmanaged(u32, RelaSection); diff --git a/src/link/Elf/Atom.zig b/src/link/Elf/Atom.zig index bf2de7ccd9..743603fd47 100644 --- a/src/link/Elf/Atom.zig +++ b/src/link/Elf/Atom.zig @@ -68,7 +68,7 @@ pub fn file(self: Atom, elf_file: *Elf) ?File { pub fn thunk(self: Atom, elf_file: *Elf) *Thunk { assert(self.flags.thunk); - const extras = self.extra(elf_file).?; + const extras = self.extra(elf_file); return elf_file.thunk(extras.thunk); } @@ -99,7 +99,8 @@ pub fn priority(self: Atom, elf_file: *Elf) u64 { /// File offset relocation happens transparently, so it is not included in /// this calculation. pub fn capacity(self: Atom, elf_file: *Elf) u64 { - const next_addr = if (elf_file.atom(self.next_index)) |next| + const zo = elf_file.zigObjectPtr().?; + const next_addr = if (zo.atom(self.next_index)) |next| next.address(elf_file) else std.math.maxInt(u32); @@ -107,8 +108,9 @@ pub fn capacity(self: Atom, elf_file: *Elf) u64 { } pub fn freeListEligible(self: Atom, elf_file: *Elf) bool { + const zo = elf_file.zigObjectPtr().?; // No need to keep a free list node for the last block. - const next = elf_file.atom(self.next_index) orelse return false; + const next = zo.atom(self.next_index) orelse return false; const cap: u64 = @intCast(next.address(elf_file) - self.address(elf_file)); const ideal_cap = Elf.padToIdeal(self.size); if (cap <= ideal_cap) return false; @@ -117,6 +119,7 @@ pub fn freeListEligible(self: Atom, elf_file: *Elf) bool { } pub fn allocate(self: *Atom, elf_file: *Elf) !void { + const zo = elf_file.zigObjectPtr().?; const shdr = &elf_file.shdrs.items[self.outputShndx().?]; const meta = elf_file.last_atom_and_free_list_table.getPtr(self.outputShndx().?).?; const free_list = &meta.free_list; @@ -137,7 +140,7 @@ pub fn allocate(self: *Atom, elf_file: *Elf) !void { var i: usize = if (elf_file.base.child_pid == null) 0 else free_list.items.len; while (i < free_list.items.len) { const big_atom_index = free_list.items[i]; - const big_atom = elf_file.atom(big_atom_index).?; + const big_atom = zo.atom(big_atom_index).?; // We now have a pointer to a live atom that has too much capacity. // Is it enough that we could fit this new atom? const cap = big_atom.capacity(elf_file); @@ -169,7 +172,7 @@ pub fn allocate(self: *Atom, elf_file: *Elf) !void { free_list_removal = i; } break :blk @intCast(new_start_vaddr); - } else if (elf_file.atom(last_atom_index.*)) |last| { + } else if (zo.atom(last_atom_index.*)) |last| { const ideal_capacity = Elf.padToIdeal(last.size); const ideal_capacity_end_vaddr = @as(u64, @intCast(last.value)) + ideal_capacity; const new_start_vaddr = self.alignment.forward(ideal_capacity_end_vaddr); @@ -189,7 +192,7 @@ pub fn allocate(self: *Atom, elf_file: *Elf) !void { }); const expand_section = if (atom_placement) |placement_index| - elf_file.atom(placement_index).?.next_index == 0 + zo.atom(placement_index).?.next_index == 0 else true; if (expand_section) { @@ -214,15 +217,15 @@ pub fn allocate(self: *Atom, elf_file: *Elf) !void { // This function can also reallocate an atom. // In this case we need to "unplug" it from its previous location before // plugging it in to its new location. - if (elf_file.atom(self.prev_index)) |prev| { + if (zo.atom(self.prev_index)) |prev| { prev.next_index = self.next_index; } - if (elf_file.atom(self.next_index)) |next| { + if (zo.atom(self.next_index)) |next| { next.prev_index = self.prev_index; } if (atom_placement) |big_atom_index| { - const big_atom = elf_file.atom(big_atom_index).?; + const big_atom = zo.atom(big_atom_index).?; self.prev_index = big_atom_index; self.next_index = big_atom.next_index; big_atom.next_index = self.atom_index; @@ -250,6 +253,7 @@ pub fn grow(self: *Atom, elf_file: *Elf) !void { pub fn free(self: *Atom, elf_file: *Elf) void { log.debug("freeAtom {d} ({s})", .{ self.atom_index, self.name(elf_file) }); + const zo = elf_file.zigObjectPtr().?; const comp = elf_file.base.comp; const gpa = comp.gpa; const shndx = self.outputShndx().?; @@ -272,9 +276,9 @@ pub fn free(self: *Atom, elf_file: *Elf) void { } } - if (elf_file.atom(last_atom_index.*)) |last_atom| { + if (zo.atom(last_atom_index.*)) |last_atom| { if (last_atom.atom_index == self.atom_index) { - if (elf_file.atom(self.prev_index)) |_| { + if (zo.atom(self.prev_index)) |_| { // TODO shrink the section size here last_atom_index.* = self.prev_index; } else { @@ -283,7 +287,7 @@ pub fn free(self: *Atom, elf_file: *Elf) void { } } - if (elf_file.atom(self.prev_index)) |prev| { + if (zo.atom(self.prev_index)) |prev| { prev.next_index = self.next_index; if (!already_have_free_list_node and prev.*.freeListEligible(elf_file)) { // The free list is heuristics, it doesn't have to be perfect, so we can @@ -294,7 +298,7 @@ pub fn free(self: *Atom, elf_file: *Elf) void { self.prev_index = 0; } - if (elf_file.atom(self.next_index)) |next| { + if (zo.atom(self.next_index)) |next| { next.prev_index = self.prev_index; } else { self.next_index = 0; @@ -313,7 +317,7 @@ pub fn relocs(self: Atom, elf_file: *Elf) []const elf.Elf64_Rela { switch (self.file(elf_file).?) { .zig_object => |x| return x.relocs.items[shndx].items, .object => |x| { - const extras = self.extra(elf_file).?; + const extras = self.extra(elf_file); return x.relocs.items[extras.rel_index..][0..extras.rel_count]; }, else => unreachable, @@ -367,7 +371,7 @@ pub fn writeRelocs(self: Atom, elf_file: *Elf, out_relocs: *std.ArrayList(elf.El pub fn fdes(self: Atom, elf_file: *Elf) []Fde { if (!self.flags.fde) return &[0]Fde{}; - const extras = self.extra(elf_file).?; + const extras = self.extra(elf_file); const object = self.file(elf_file).?.object; return object.fdes.items[extras.fde_start..][0..extras.fde_count]; } @@ -712,9 +716,9 @@ fn reportUndefined( { const gop = try undefs.getOrPut(sym_index); if (!gop.found_existing) { - gop.value_ptr.* = std.ArrayList(Atom.Index).init(gpa); + gop.value_ptr.* = std.ArrayList(Elf.Ref).init(gpa); } - try gop.value_ptr.append(self.atom_index); + try gop.value_ptr.append(.{ .index = self.atom_index, .file = self.file_index }); return true; } @@ -1001,25 +1005,23 @@ const AddExtraOpts = struct { rel_count: ?u32 = null, }; -pub fn addExtra(atom: *Atom, opts: AddExtraOpts, elf_file: *Elf) !void { - if (atom.extra(elf_file) == null) { - atom.extra_index = try elf_file.addAtomExtra(.{}); - } - var extras = atom.extra(elf_file).?; +pub fn addExtra(atom: *Atom, opts: AddExtraOpts, elf_file: *Elf) void { + const file_ptr = atom.file(elf_file).?; + var extras = file_ptr.atomExtra(atom.extra_index); inline for (@typeInfo(@TypeOf(opts)).Struct.fields) |field| { if (@field(opts, field.name)) |x| { @field(extras, field.name) = x; } } - atom.setExtra(extras, elf_file); + file_ptr.setAtomExtra(atom.extra_index, extras); } -pub inline fn extra(atom: Atom, elf_file: *Elf) ?Extra { - return elf_file.atomExtra(atom.extra_index); +pub inline fn extra(atom: Atom, elf_file: *Elf) Extra { + return atom.file(elf_file).?.atomExtra(atom.extra_index); } pub inline fn setExtra(atom: Atom, extras: Extra, elf_file: *Elf) void { - elf_file.setAtomExtra(atom.extra_index, extras); + atom.file(elf_file).?.setAtomExtra(atom.extra_index, extras); } pub fn format( @@ -1063,7 +1065,7 @@ fn format2( }); if (atom.flags.fde) { try writer.writeAll(" : fdes{ "); - const extras = atom.extra(elf_file).?; + const extras = atom.extra(elf_file); for (atom.fdes(elf_file), extras.fde_start..) |fde, i| { try writer.print("{d}", .{i}); if (!fde.alive) try writer.writeAll("([*])"); diff --git a/src/link/Elf/LinkerDefined.zig b/src/link/Elf/LinkerDefined.zig index 97edd34791..0e4a7f8f50 100644 --- a/src/link/Elf/LinkerDefined.zig +++ b/src/link/Elf/LinkerDefined.zig @@ -1,4 +1,5 @@ index: File.Index, + symtab: std.ArrayListUnmanaged(elf.Elf64_Sym) = .{}, strtab: std.ArrayListUnmanaged(u8) = .{}, symbols: std.ArrayListUnmanaged(Symbol.Index) = .{}, @@ -11,6 +12,11 @@ pub fn deinit(self: *LinkerDefined, allocator: Allocator) void { self.symbols.deinit(allocator); } +pub fn init(self: *LinkerDefined, allocator: Allocator) !void { + // Null byte in strtab + try self.strtab.append(allocator, 0); +} + pub fn addGlobal(self: *LinkerDefined, name: [:0]const u8, elf_file: *Elf) !u32 { const comp = elf_file.base.comp; const gpa = comp.gpa; @@ -41,7 +47,7 @@ pub fn resolveSymbols(self: *LinkerDefined, elf_file: *Elf) void { const global = elf_file.symbol(index); if (self.asFile().symbolRank(this_sym, false) < global.symbolRank(elf_file)) { global.value = 0; - global.atom_index = 0; + global.atom_ref = .{ .index = 0, .file = 0 }; global.file_index = self.index; global.esym_index = sym_idx; global.version_index = elf_file.default_sym_version; @@ -127,6 +133,7 @@ const mem = std.mem; const std = @import("std"); const Allocator = mem.Allocator; +const Atom = @import("Atom.zig"); const Elf = @import("../Elf.zig"); const File = @import("file.zig").File; const LinkerDefined = @This(); diff --git a/src/link/Elf/Object.zig b/src/link/Elf/Object.zig index c7f1186c4b..08ad4e8378 100644 --- a/src/link/Elf/Object.zig +++ b/src/link/Elf/Object.zig @@ -10,9 +10,12 @@ symtab: std.ArrayListUnmanaged(elf.Elf64_Sym) = .{}, strtab: std.ArrayListUnmanaged(u8) = .{}, first_global: ?Symbol.Index = null, symbols: std.ArrayListUnmanaged(Symbol.Index) = .{}, -atoms: std.ArrayListUnmanaged(Atom.Index) = .{}, relocs: std.ArrayListUnmanaged(elf.Elf64_Rela) = .{}, +atoms: std.ArrayListUnmanaged(Atom) = .{}, +atoms_indexes: std.ArrayListUnmanaged(Atom.Index) = .{}, +atoms_extra: std.ArrayListUnmanaged(u32) = .{}, + comdat_groups: std.ArrayListUnmanaged(Elf.ComdatGroup) = .{}, comdat_group_data: std.ArrayListUnmanaged(u32) = .{}, @@ -49,6 +52,8 @@ pub fn deinit(self: *Object, allocator: Allocator) void { self.strtab.deinit(allocator); self.symbols.deinit(allocator); self.atoms.deinit(allocator); + self.atoms_indexes.deinit(allocator); + self.atoms_extra.deinit(allocator); self.comdat_groups.deinit(allocator); self.comdat_group_data.deinit(allocator); self.relocs.deinit(allocator); @@ -71,15 +76,17 @@ pub fn parse(self: *Object, elf_file: *Elf) !void { // Append null input merge section try self.input_merge_sections.append(gpa, .{}); + // Allocate atom index 0 to null atom + try self.atoms.append(gpa, .{ .extra_index = try self.addAtomExtra(gpa, .{}) }); try self.initAtoms(gpa, handle, elf_file); try self.initSymtab(gpa, elf_file); for (self.shdrs.items, 0..) |shdr, i| { - const atom = elf_file.atom(self.atoms.items[i]) orelse continue; - if (!atom.flags.alive) continue; + const atom_ptr = self.atom(self.atoms_indexes.items[i]) orelse continue; + if (!atom_ptr.flags.alive) continue; if ((cpu_arch == .x86_64 and shdr.sh_type == elf.SHT_X86_64_UNWIND) or - mem.eql(u8, atom.name(elf_file), ".eh_frame")) + mem.eql(u8, atom_ptr.name(elf_file), ".eh_frame")) { try self.parseEhFrame(gpa, handle, @as(u32, @intCast(i)), elf_file); } @@ -179,8 +186,11 @@ fn parseCommon(self: *Object, allocator: Allocator, handle: std.fs.File, elf_fil fn initAtoms(self: *Object, allocator: Allocator, handle: std.fs.File, elf_file: *Elf) !void { const shdrs = self.shdrs.items; - try self.atoms.resize(allocator, shdrs.len); - @memset(self.atoms.items, 0); + try self.atoms.ensureTotalCapacityPrecise(allocator, shdrs.len); + try self.atoms_extra.ensureTotalCapacityPrecise(allocator, shdrs.len * @sizeOf(Atom.Extra)); + try self.atoms_indexes.ensureTotalCapacityPrecise(allocator, shdrs.len); + try self.atoms_indexes.resize(allocator, shdrs.len); + @memset(self.atoms_indexes.items, 0); for (shdrs, 0..) |shdr, i| { if (shdr.sh_flags & elf.SHF_EXCLUDE != 0 and @@ -242,7 +252,19 @@ fn initAtoms(self: *Object, allocator: Allocator, handle: std.fs.File, elf_file: else => { const shndx = @as(u32, @intCast(i)); if (self.skipShdr(shndx, elf_file)) continue; - try self.addAtom(allocator, handle, shdr, shndx, elf_file); + const size, const alignment = if (shdr.sh_flags & elf.SHF_COMPRESSED != 0) blk: { + const data = try self.preadShdrContentsAlloc(allocator, handle, shndx); + defer allocator.free(data); + const chdr = @as(*align(1) const elf.Elf64_Chdr, @ptrCast(data.ptr)).*; + break :blk .{ chdr.ch_size, Alignment.fromNonzeroByteUnits(chdr.ch_addralign) }; + } else .{ shdr.sh_size, Alignment.fromNonzeroByteUnits(shdr.sh_addralign) }; + const atom_index = self.addAtomAssumeCapacity(.{ + .name = shdr.sh_name, + .shndx = shndx, + .size = size, + .alignment = alignment, + }); + self.atoms_indexes.items[shndx] = atom_index; }, } } @@ -250,14 +272,14 @@ fn initAtoms(self: *Object, allocator: Allocator, handle: std.fs.File, elf_file: // Parse relocs sections if any. for (shdrs, 0..) |shdr, i| switch (shdr.sh_type) { elf.SHT_REL, elf.SHT_RELA => { - const atom_index = self.atoms.items[shdr.sh_info]; - if (elf_file.atom(atom_index)) |atom| { + const atom_index = self.atoms_indexes.items[shdr.sh_info]; + if (self.atom(atom_index)) |atom_ptr| { const relocs = try self.preadRelocsAlloc(allocator, handle, @intCast(i)); defer allocator.free(relocs); - atom.relocs_section_index = @intCast(i); + atom_ptr.relocs_section_index = @intCast(i); const rel_index: u32 = @intCast(self.relocs.items.len); const rel_count: u32 = @intCast(relocs.len); - try atom.addExtra(.{ .rel_index = rel_index, .rel_count = rel_count }, elf_file); + atom_ptr.addExtra(.{ .rel_index = rel_index, .rel_count = rel_count }, elf_file); try self.relocs.appendUnalignedSlice(allocator, relocs); if (elf_file.getTarget().cpu.arch == .riscv64) { sortRelocs(self.relocs.items[rel_index..][0..rel_count]); @@ -268,27 +290,6 @@ fn initAtoms(self: *Object, allocator: Allocator, handle: std.fs.File, elf_file: }; } -fn addAtom(self: *Object, allocator: Allocator, handle: std.fs.File, shdr: elf.Elf64_Shdr, shndx: u32, elf_file: *Elf) !void { - const atom_index = try elf_file.addAtom(); - const atom = elf_file.atom(atom_index).?; - atom.atom_index = atom_index; - atom.name_offset = shdr.sh_name; - atom.file_index = self.index; - atom.input_section_index = shndx; - self.atoms.items[shndx] = atom_index; - - if (shdr.sh_flags & elf.SHF_COMPRESSED != 0) { - const data = try self.preadShdrContentsAlloc(allocator, handle, shndx); - defer allocator.free(data); - const chdr = @as(*align(1) const elf.Elf64_Chdr, @ptrCast(data.ptr)).*; - atom.size = chdr.ch_size; - atom.alignment = Alignment.fromNonzeroByteUnits(chdr.ch_addralign); - } else { - atom.size = shdr.sh_size; - atom.alignment = Alignment.fromNonzeroByteUnits(shdr.sh_addralign); - } -} - fn initOutputSection(self: Object, elf_file: *Elf, shdr: elf.Elf64_Shdr) error{OutOfMemory}!u32 { const name = blk: { const name = self.getString(shdr.sh_name); @@ -368,8 +369,10 @@ fn initSymtab(self: *Object, allocator: Allocator, elf_file: *Elf) !void { sym_ptr.value = @intCast(sym.st_value); sym_ptr.name_offset = sym.st_name; sym_ptr.esym_index = @as(u32, @intCast(i)); - sym_ptr.atom_index = if (sym.st_shndx == elf.SHN_ABS) 0 else self.atoms.items[sym.st_shndx]; sym_ptr.file_index = self.index; + if (sym.st_shndx != elf.SHN_ABS) { + sym_ptr.atom_ref = .{ .index = self.atoms_indexes.items[sym.st_shndx], .file = self.index }; + } } for (self.symtab.items[first_global..]) |sym| { @@ -456,15 +459,15 @@ fn parseEhFrame(self: *Object, allocator: Allocator, handle: std.fs.File, shndx: var i: u32 = @as(u32, @intCast(fdes_start)); while (i < self.fdes.items.len) { const fde = self.fdes.items[i]; - const atom = fde.atom(elf_file); + const atom_ptr = fde.atom(elf_file); const start = i; i += 1; while (i < self.fdes.items.len) : (i += 1) { const next_fde = self.fdes.items[i]; - if (atom.atom_index != next_fde.atom(elf_file).atom_index) break; + if (atom_ptr.atom_index != next_fde.atom(elf_file).atom_index) break; } - try atom.addExtra(.{ .fde_start = start, .fde_count = i - start }, elf_file); - atom.flags.fde = true; + atom_ptr.addExtra(.{ .fde_start = start, .fde_count = i - start }, elf_file); + atom_ptr.flags.fde = true; } } @@ -507,19 +510,19 @@ fn filterRelocs( pub fn scanRelocs(self: *Object, elf_file: *Elf, undefs: anytype) !void { const comp = elf_file.base.comp; const gpa = comp.gpa; - for (self.atoms.items) |atom_index| { - const atom = elf_file.atom(atom_index) orelse continue; - if (!atom.flags.alive) continue; - const shdr = atom.inputShdr(elf_file); + for (self.atoms_indexes.items) |atom_index| { + const atom_ptr = self.atom(atom_index) orelse continue; + if (!atom_ptr.flags.alive) continue; + const shdr = atom_ptr.inputShdr(elf_file); if (shdr.sh_flags & elf.SHF_ALLOC == 0) continue; if (shdr.sh_type == elf.SHT_NOBITS) continue; - if (atom.scanRelocsRequiresCode(elf_file)) { + if (atom_ptr.scanRelocsRequiresCode(elf_file)) { // TODO ideally, we don't have to decompress at this stage (should already be done) // and we just fetch the code slice. const code = try self.codeDecompressAlloc(elf_file, atom_index); defer gpa.free(code); - try atom.scanRelocs(elf_file, code, undefs); - } else try atom.scanRelocs(elf_file, null, undefs); + try atom_ptr.scanRelocs(elf_file, code, undefs); + } else try atom_ptr.scanRelocs(elf_file, null, undefs); } for (self.cies.items) |cie| { @@ -547,19 +550,21 @@ pub fn resolveSymbols(self: *Object, elf_file: *Elf) void { if (esym.st_shndx == elf.SHN_UNDEF) continue; if (esym.st_shndx != elf.SHN_ABS and esym.st_shndx != elf.SHN_COMMON) { - const atom_index = self.atoms.items[esym.st_shndx]; - const atom = elf_file.atom(atom_index) orelse continue; - if (!atom.flags.alive) continue; + const atom_index = self.atoms_indexes.items[esym.st_shndx]; + const atom_ptr = self.atom(atom_index) orelse continue; + if (!atom_ptr.flags.alive) continue; } const global = elf_file.symbol(index); if (self.asFile().symbolRank(esym, !self.alive) < global.symbolRank(elf_file)) { - const atom_index = switch (esym.st_shndx) { - elf.SHN_ABS, elf.SHN_COMMON => 0, - else => self.atoms.items[esym.st_shndx], - }; + switch (esym.st_shndx) { + elf.SHN_ABS, elf.SHN_COMMON => {}, + else => global.atom_ref = .{ + .index = self.atoms_indexes.items[esym.st_shndx], + .file = self.index, + }, + } global.value = @intCast(esym.st_value); - global.atom_index = atom_index; global.esym_index = esym_index; global.file_index = self.index; global.version_index = elf_file.default_sym_version; @@ -588,7 +593,7 @@ pub fn claimUnresolved(self: *Object, elf_file: *Elf) void { }; global.value = 0; - global.atom_index = 0; + global.atom_ref = .{ .index = 0, .file = 0 }; global.esym_index = esym_index; global.file_index = self.index; global.version_index = if (is_import) elf.VER_NDX_LOCAL else elf_file.default_sym_version; @@ -609,7 +614,7 @@ pub fn claimUnresolvedObject(self: *Object, elf_file: *Elf) void { } global.value = 0; - global.atom_index = 0; + global.atom_ref = .{ .index = 0, .file = 0 }; global.esym_index = esym_index; global.file_index = self.index; } @@ -633,13 +638,13 @@ pub fn markLive(self: *Object, elf_file: *Elf) void { } } -pub fn markEhFrameAtomsDead(self: Object, elf_file: *Elf) void { +pub fn markEhFrameAtomsDead(self: *Object, elf_file: *Elf) void { const cpu_arch = elf_file.getTarget().cpu.arch; - for (self.atoms.items) |atom_index| { - const atom = elf_file.atom(atom_index) orelse continue; - const is_eh_frame = (cpu_arch == .x86_64 and atom.inputShdr(elf_file).sh_type == elf.SHT_X86_64_UNWIND) or - mem.eql(u8, atom.name(elf_file), ".eh_frame"); - if (atom.flags.alive and is_eh_frame) atom.flags.alive = false; + for (self.atoms_indexes.items) |atom_index| { + const atom_ptr = self.atom(atom_index) orelse continue; + const is_eh_frame = (cpu_arch == .x86_64 and atom_ptr.inputShdr(elf_file).sh_type == elf.SHT_X86_64_UNWIND) or + mem.eql(u8, atom_ptr.name(elf_file), ".eh_frame"); + if (atom_ptr.flags.alive and is_eh_frame) atom_ptr.flags.alive = false; } } @@ -657,9 +662,9 @@ pub fn checkDuplicates(self: *Object, dupes: anytype, elf_file: *Elf) error{OutO sym.st_shndx == elf.SHN_COMMON) continue; if (sym.st_shndx != elf.SHN_ABS) { - const atom_index = self.atoms.items[sym.st_shndx]; - const atom = elf_file.atom(atom_index) orelse continue; - if (!atom.flags.alive) continue; + const atom_index = self.atoms_indexes.items[sym.st_shndx]; + const atom_ptr = self.atom(atom_index) orelse continue; + if (!atom_ptr.flags.alive) continue; } const gop = try dupes.getOrPut(index); @@ -680,8 +685,8 @@ pub fn initMergeSections(self: *Object, elf_file: *Elf) !void { for (self.shdrs.items, 0..) |shdr, shndx| { if (shdr.sh_flags & elf.SHF_MERGE == 0) continue; - const atom_index = self.atoms.items[shndx]; - const atom_ptr = elf_file.atom(atom_index) orelse continue; + const atom_index = self.atoms_indexes.items[shndx]; + const atom_ptr = self.atom(atom_index) orelse continue; if (!atom_ptr.flags.alive) continue; if (atom_ptr.relocs(elf_file).len > 0) continue; @@ -755,7 +760,7 @@ pub fn resolveMergeSubsections(self: *Object, elf_file: *Elf) !void { const imsec = self.inputMergeSection(index) orelse continue; if (imsec.offsets.items.len == 0) continue; const msec = elf_file.mergeSection(imsec.merge_section_index); - const atom_ptr = elf_file.atom(imsec.atom_index).?; + const atom_ptr = self.atom(imsec.atom_index).?; const isec = atom_ptr.inputShdr(elf_file); try imsec.subsections.resize(gpa, imsec.strings.items.len); @@ -802,10 +807,10 @@ pub fn resolveMergeSubsections(self: *Object, elf_file: *Elf) !void { sym.value = offset; } - for (self.atoms.items) |atom_index| { - const atom_ptr = elf_file.atom(atom_index) orelse continue; + for (self.atoms_indexes.items) |atom_index| { + const atom_ptr = self.atom(atom_index) orelse continue; if (!atom_ptr.flags.alive) continue; - const extras = atom_ptr.extra(elf_file) orelse continue; + const extras = atom_ptr.extra(elf_file); const relocs = self.relocs.items[extras.rel_index..][0..extras.rel_count]; for (relocs) |*rel| { const esym = self.symtab.items[rel.r_sym()]; @@ -867,21 +872,10 @@ pub fn convertCommonSymbols(self: *Object, elf_file: *Elf) !void { const comp = elf_file.base.comp; const gpa = comp.gpa; - const atom_index = try elf_file.addAtom(); - try self.atoms.append(gpa, atom_index); - const is_tls = global.type(elf_file) == elf.STT_TLS; const name = if (is_tls) ".tls_common" else ".common"; - - const atom = elf_file.atom(atom_index).?; const name_offset = @as(u32, @intCast(self.strtab.items.len)); try self.strtab.writer(gpa).print("{s}\x00", .{name}); - atom.atom_index = atom_index; - atom.name_offset = name_offset; - atom.file_index = self.index; - atom.size = this_sym.st_size; - const alignment = this_sym.st_value; - atom.alignment = Alignment.fromNonzeroByteUnits(alignment); var sh_flags: u32 = elf.SHF_ALLOC | elf.SHF_WRITE; if (is_tls) sh_flags |= elf.SHF_TLS; @@ -897,38 +891,45 @@ pub fn convertCommonSymbols(self: *Object, elf_file: *Elf) !void { .sh_size = sh_size, .sh_link = 0, .sh_info = 0, - .sh_addralign = alignment, + .sh_addralign = this_sym.st_value, .sh_entsize = 0, }; - atom.input_section_index = shndx; + + const atom_index = try self.addAtom(gpa, .{ + .name = name_offset, + .shndx = shndx, + .size = this_sym.st_size, + .alignment = Alignment.fromNonzeroByteUnits(this_sym.st_value), + }); + try self.atoms_indexes.append(gpa, atom_index); global.value = 0; - global.atom_index = atom_index; + global.atom_ref = .{ .index = atom_index, .file = self.index }; global.flags.weak = false; } } -pub fn initOutputSections(self: Object, elf_file: *Elf) !void { - for (self.atoms.items) |atom_index| { - const atom = elf_file.atom(atom_index) orelse continue; - if (!atom.flags.alive) continue; - const shdr = atom.inputShdr(elf_file); +pub fn initOutputSections(self: *Object, elf_file: *Elf) !void { + for (self.atoms_indexes.items) |atom_index| { + const atom_ptr = self.atom(atom_index) orelse continue; + if (!atom_ptr.flags.alive) continue; + const shdr = atom_ptr.inputShdr(elf_file); _ = try self.initOutputSection(elf_file, shdr); } } pub fn addAtomsToOutputSections(self: *Object, elf_file: *Elf) !void { - for (self.atoms.items) |atom_index| { - const atom = elf_file.atom(atom_index) orelse continue; - if (!atom.flags.alive) continue; - const shdr = atom.inputShdr(elf_file); - atom.output_section_index = self.initOutputSection(elf_file, shdr) catch unreachable; + for (self.atoms_indexes.items) |atom_index| { + const atom_ptr = self.atom(atom_index) orelse continue; + if (!atom_ptr.flags.alive) continue; + const shdr = atom_ptr.inputShdr(elf_file); + atom_ptr.output_section_index = self.initOutputSection(elf_file, shdr) catch unreachable; const comp = elf_file.base.comp; const gpa = comp.gpa; - const gop = try elf_file.output_sections.getOrPut(gpa, atom.output_section_index); + const gop = try elf_file.output_sections.getOrPut(gpa, atom_ptr.output_section_index); if (!gop.found_existing) gop.value_ptr.* = .{}; - try gop.value_ptr.append(gpa, atom_index); + try gop.value_ptr.append(gpa, .{ .index = atom_index, .file = self.index }); } for (self.locals()) |local_index| { @@ -938,9 +939,9 @@ pub fn addAtomsToOutputSections(self: *Object, elf_file: *Elf) !void { local.output_section_index = msub.mergeSection(elf_file).output_section_index; continue; } - const atom = local.atom(elf_file) orelse continue; - if (!atom.flags.alive) continue; - local.output_section_index = atom.output_section_index; + const atom_ptr = local.atom(elf_file) orelse continue; + if (!atom_ptr.flags.alive) continue; + local.output_section_index = atom_ptr.output_section_index; } for (self.globals()) |global_index| { @@ -951,9 +952,9 @@ pub fn addAtomsToOutputSections(self: *Object, elf_file: *Elf) !void { global.output_section_index = msub.mergeSection(elf_file).output_section_index; continue; } - const atom = global.atom(elf_file) orelse continue; - if (!atom.flags.alive) continue; - global.output_section_index = atom.output_section_index; + const atom_ptr = global.atom(elf_file) orelse continue; + if (!atom_ptr.flags.alive) continue; + global.output_section_index = atom_ptr.output_section_index; } for (self.symbols.items[self.symtab.items.len..]) |local_index| { @@ -964,11 +965,11 @@ pub fn addAtomsToOutputSections(self: *Object, elf_file: *Elf) !void { } } -pub fn initRelaSections(self: Object, elf_file: *Elf) !void { - for (self.atoms.items) |atom_index| { - const atom = elf_file.atom(atom_index) orelse continue; - if (!atom.flags.alive) continue; - const shndx = atom.relocsShndx() orelse continue; +pub fn initRelaSections(self: *Object, elf_file: *Elf) !void { + for (self.atoms_indexes.items) |atom_index| { + const atom_ptr = self.atom(atom_index) orelse continue; + if (!atom_ptr.flags.alive) continue; + const shndx = atom_ptr.relocsShndx() orelse continue; const shdr = self.shdrs.items[shndx]; const out_shndx = try self.initOutputSection(elf_file, shdr); const out_shdr = &elf_file.shdrs.items[out_shndx]; @@ -978,24 +979,24 @@ pub fn initRelaSections(self: Object, elf_file: *Elf) !void { } } -pub fn addAtomsToRelaSections(self: Object, elf_file: *Elf) !void { - for (self.atoms.items) |atom_index| { - const atom = elf_file.atom(atom_index) orelse continue; - if (!atom.flags.alive) continue; +pub fn addAtomsToRelaSections(self: *Object, elf_file: *Elf) !void { + for (self.atoms_indexes.items) |atom_index| { + const atom_ptr = self.atom(atom_index) orelse continue; + if (!atom_ptr.flags.alive) continue; const shndx = blk: { - const shndx = atom.relocsShndx() orelse continue; + const shndx = atom_ptr.relocsShndx() orelse continue; const shdr = self.shdrs.items[shndx]; break :blk self.initOutputSection(elf_file, shdr) catch unreachable; }; const shdr = &elf_file.shdrs.items[shndx]; - shdr.sh_info = atom.outputShndx().?; + shdr.sh_info = atom_ptr.outputShndx().?; shdr.sh_link = elf_file.symtab_section_index.?; const comp = elf_file.base.comp; const gpa = comp.gpa; - const gop = try elf_file.output_rela_sections.getOrPut(gpa, atom.outputShndx().?); + const gop = try elf_file.output_rela_sections.getOrPut(gpa, atom_ptr.outputShndx().?); if (!gop.found_existing) gop.value_ptr.* = .{ .shndx = shndx }; - try gop.value_ptr.atom_list.append(gpa, atom_index); + try gop.value_ptr.atom_list.append(gpa, .{ .index = atom_index, .file = self.index }); } } @@ -1129,11 +1130,10 @@ pub fn globals(self: Object) []const Symbol.Index { /// Returns atom's code and optionally uncompresses data if required (for compressed sections). /// Caller owns the memory. -pub fn codeDecompressAlloc(self: Object, elf_file: *Elf, atom_index: Atom.Index) ![]u8 { +pub fn codeDecompressAlloc(self: *Object, elf_file: *Elf, atom_index: Atom.Index) ![]u8 { const comp = elf_file.base.comp; const gpa = comp.gpa; - const atom_ptr = elf_file.atom(atom_index).?; - assert(atom_ptr.file_index == self.index); + const atom_ptr = self.atom(atom_index).?; const shdr = atom_ptr.inputShdr(elf_file); const handle = elf_file.fileHandle(self.file_handle); const data = try self.preadShdrContentsAlloc(gpa, handle, atom_ptr.input_section_index); @@ -1194,6 +1194,82 @@ fn preadRelocsAlloc(self: Object, allocator: Allocator, handle: std.fs.File, shn return @as([*]align(1) const elf.Elf64_Rela, @ptrCast(raw.ptr))[0..num]; } +const AddAtomArgs = struct { + name: u32, + shndx: u32, + size: u64, + alignment: Alignment, +}; + +fn addAtom(self: *Object, allocator: Allocator, args: AddAtomArgs) !Atom.Index { + try self.atoms.ensureUnusedCapacity(allocator, 1); + try self.atoms_extra.ensureUnusedCapacity(allocator, @sizeOf(Atom.Extra)); + return self.addAtomAssumeCapacity(args); +} + +fn addAtomAssumeCapacity(self: *Object, args: AddAtomArgs) Atom.Index { + const atom_index: Atom.Index = @intCast(self.atoms.items.len); + const atom_ptr = self.atoms.addOneAssumeCapacity(); + atom_ptr.* = .{ + .atom_index = atom_index, + .name_offset = args.name, + .file_index = self.index, + .input_section_index = args.shndx, + .extra_index = self.addAtomExtraAssumeCapacity(.{}), + .size = args.size, + .alignment = args.alignment, + }; + return atom_index; +} + +pub fn atom(self: *Object, atom_index: Atom.Index) ?*Atom { + if (atom_index == 0) return null; + assert(atom_index < self.atoms.items.len); + return &self.atoms.items[atom_index]; +} + +pub fn addAtomExtra(self: *Object, allocator: Allocator, extra: Atom.Extra) !u32 { + const fields = @typeInfo(Atom.Extra).Struct.fields; + try self.atoms_extra.ensureUnusedCapacity(allocator, fields.len); + return self.addAtomExtraAssumeCapacity(extra); +} + +pub fn addAtomExtraAssumeCapacity(self: *Object, extra: Atom.Extra) u32 { + const index = @as(u32, @intCast(self.atoms_extra.items.len)); + const fields = @typeInfo(Atom.Extra).Struct.fields; + inline for (fields) |field| { + self.atoms_extra.appendAssumeCapacity(switch (field.type) { + u32 => @field(extra, field.name), + else => @compileError("bad field type"), + }); + } + return index; +} + +pub fn atomExtra(self: *Object, index: u32) Atom.Extra { + const fields = @typeInfo(Atom.Extra).Struct.fields; + var i: usize = index; + var result: Atom.Extra = undefined; + inline for (fields) |field| { + @field(result, field.name) = switch (field.type) { + u32 => self.atoms_extra.items[i], + else => @compileError("bad field type"), + }; + i += 1; + } + return result; +} + +pub fn setAtomExtra(self: *Object, index: u32, extra: Atom.Extra) void { + const fields = @typeInfo(Atom.Extra).Struct.fields; + inline for (fields, 0..) |field, i| { + self.atoms_extra.items[index + i] = switch (field.type) { + u32 => @field(extra, field.name), + else => @compileError("bad field type"), + }; + } +} + fn addInputMergeSection(self: *Object, allocator: Allocator) !InputMergeSection.Index { const index: InputMergeSection.Index = @intCast(self.input_merge_sections.items.len); const msec = try self.input_merge_sections.addOne(allocator); @@ -1280,9 +1356,9 @@ fn formatAtoms( _ = options; const object = ctx.object; try writer.writeAll(" atoms\n"); - for (object.atoms.items) |atom_index| { - const atom = ctx.elf_file.atom(atom_index) orelse continue; - try writer.print(" {}\n", .{atom.fmt(ctx.elf_file)}); + for (object.atoms_indexes.items) |atom_index| { + const atom_ptr = object.atom(atom_index) orelse continue; + try writer.print(" {}\n", .{atom_ptr.fmt(ctx.elf_file)}); } } @@ -1354,9 +1430,9 @@ fn formatComdatGroups( try writer.print(" COMDAT({d})\n", .{cg_index}); const cg_members = cg.comdatGroupMembers(elf_file); for (cg_members) |shndx| { - const atom_index = object.atoms.items[shndx]; - const atom = elf_file.atom(atom_index) orelse continue; - try writer.print(" atom({d}) : {s}\n", .{ atom_index, atom.name(elf_file) }); + const atom_index = object.atoms_indexes.items[shndx]; + const atom_ptr = object.atom(atom_index) orelse continue; + try writer.print(" atom({d}) : {s}\n", .{ atom_index, atom_ptr.name(elf_file) }); } } } diff --git a/src/link/Elf/SharedObject.zig b/src/link/Elf/SharedObject.zig index 2ec5b211e8..ef65f3abaa 100644 --- a/src/link/Elf/SharedObject.zig +++ b/src/link/Elf/SharedObject.zig @@ -232,7 +232,7 @@ pub fn resolveSymbols(self: *SharedObject, elf_file: *Elf) void { const global = elf_file.symbol(index); if (self.asFile().symbolRank(this_sym, false) < global.symbolRank(elf_file)) { global.value = @intCast(this_sym.st_value); - global.atom_index = 0; + global.atom_ref = .{ .index = 0, .file = 0 }; global.esym_index = esym_index; global.version_index = self.versyms.items[esym_index]; global.file_index = self.index; diff --git a/src/link/Elf/Symbol.zig b/src/link/Elf/Symbol.zig index 21061f036b..6a54e9ad99 100644 --- a/src/link/Elf/Symbol.zig +++ b/src/link/Elf/Symbol.zig @@ -9,10 +9,9 @@ name_offset: u32 = 0, /// Index of file where this symbol is defined. file_index: File.Index = 0, -/// Index of atom containing this symbol. -/// Index of 0 means there is no associated atom with this symbol. +/// Reference to Atom containing this symbol if any. /// Use `atom` to get the pointer to the atom. -atom_index: Atom.Index = 0, +atom_ref: Elf.Ref = .{ .index = 0, .file = 0 }, /// Assigned output section index for this symbol. output_section_index: u32 = 0, @@ -68,7 +67,8 @@ pub fn name(symbol: Symbol, elf_file: *Elf) [:0]const u8 { } pub fn atom(symbol: Symbol, elf_file: *Elf) ?*Atom { - return elf_file.atom(symbol.atom_index); + const file_ptr = elf_file.file(symbol.atom_ref.file) orelse return null; + return file_ptr.atom(symbol.atom_ref.index); } pub fn mergeSubsection(symbol: Symbol, elf_file: *Elf) ?*MergeSubsection { diff --git a/src/link/Elf/ZigObject.zig b/src/link/Elf/ZigObject.zig index 8fee0d64df..ca5f3af021 100644 --- a/src/link/Elf/ZigObject.zig +++ b/src/link/Elf/ZigObject.zig @@ -15,7 +15,9 @@ local_symbols: std.ArrayListUnmanaged(Symbol.Index) = .{}, global_symbols: std.ArrayListUnmanaged(Symbol.Index) = .{}, globals_lookup: std.AutoHashMapUnmanaged(u32, Symbol.Index) = .{}, -atoms: std.ArrayListUnmanaged(Atom.Index) = .{}, +atoms: std.ArrayListUnmanaged(Atom) = .{}, +atoms_indexes: std.ArrayListUnmanaged(Atom.Index) = .{}, +atoms_extra: std.ArrayListUnmanaged(u32) = .{}, relocs: std.ArrayListUnmanaged(std.ArrayListUnmanaged(elf.Elf64_Rela)) = .{}, num_dynrelocs: u32 = 0, @@ -80,7 +82,7 @@ pub fn init(self: *ZigObject, elf_file: *Elf) !void { const comp = elf_file.base.comp; const gpa = comp.gpa; - try self.atoms.append(gpa, 0); // null input section + try self.atoms.append(gpa, .{ .extra_index = try self.addAtomExtra(gpa, .{}) }); // null input section try self.relocs.append(gpa, .{}); // null relocs section try self.strtab.buffer.append(gpa, 0); @@ -117,6 +119,8 @@ pub fn deinit(self: *ZigObject, allocator: Allocator) void { self.global_symbols.deinit(allocator); self.globals_lookup.deinit(allocator); self.atoms.deinit(allocator); + self.atoms_indexes.deinit(allocator); + self.atoms_extra.deinit(allocator); for (self.relocs.items) |*list| { list.deinit(allocator); } @@ -276,24 +280,20 @@ pub fn addGlobalEsym(self: *ZigObject, allocator: Allocator) !Symbol.Index { return index | global_symbol_bit; } -pub fn addAtom(self: *ZigObject, elf_file: *Elf) !Symbol.Index { +pub fn newAtom(self: *ZigObject, elf_file: *Elf) !Symbol.Index { const gpa = elf_file.base.comp.gpa; - const atom_index = try elf_file.addAtom(); + const atom_index = try self.addAtom(gpa); const symbol_index = try elf_file.addSymbol(); const esym_index = try self.addLocalEsym(gpa); - const shndx = @as(u32, @intCast(self.atoms.items.len)); - try self.atoms.append(gpa, atom_index); + try self.atoms_indexes.append(gpa, atom_index); try self.local_symbols.append(gpa, symbol_index); - const atom_ptr = elf_file.atom(atom_index).?; - atom_ptr.file_index = self.index; - const symbol_ptr = elf_file.symbol(symbol_index); symbol_ptr.file_index = self.index; - symbol_ptr.atom_index = atom_index; + symbol_ptr.atom_ref = .{ .index = atom_index, .file = self.index }; - self.local_esyms.items(.shndx)[esym_index] = shndx; + self.local_esyms.items(.shndx)[esym_index] = atom_index; self.local_esyms.items(.elf_sym)[esym_index].st_shndx = SHN_ATOM; symbol_ptr.esym_index = esym_index; @@ -301,21 +301,22 @@ pub fn addAtom(self: *ZigObject, elf_file: *Elf) !Symbol.Index { const relocs_index = @as(u32, @intCast(self.relocs.items.len)); const relocs = try self.relocs.addOne(gpa); relocs.* = .{}; + + const atom_ptr = self.atom(atom_index).?; atom_ptr.relocs_section_index = relocs_index; return symbol_index; } /// TODO actually create fake input shdrs and return that instead. -pub fn inputShdr(self: ZigObject, atom_index: Atom.Index, elf_file: *Elf) elf.Elf64_Shdr { - _ = self; - const atom = elf_file.atom(atom_index) orelse return Elf.null_shdr; - const shndx = atom.outputShndx() orelse return Elf.null_shdr; +pub fn inputShdr(self: *ZigObject, atom_index: Atom.Index, elf_file: *Elf) elf.Elf64_Shdr { + const atom_ptr = self.atom(atom_index) orelse return Elf.null_shdr; + const shndx = atom_ptr.outputShndx() orelse return Elf.null_shdr; var shdr = elf_file.shdrs.items[shndx]; shdr.sh_addr = 0; shdr.sh_offset = 0; - shdr.sh_size = atom.size; - shdr.sh_addralign = atom.alignment.toByteUnits() orelse 1; + shdr.sh_size = atom_ptr.size; + shdr.sh_addralign = atom_ptr.alignment.toByteUnits() orelse 1; return shdr; } @@ -329,24 +330,23 @@ pub fn resolveSymbols(self: *ZigObject, elf_file: *Elf) void { if (esym.st_shndx != elf.SHN_ABS and esym.st_shndx != elf.SHN_COMMON) { assert(esym.st_shndx == SHN_ATOM); - const atom_index = self.atoms.items[shndx]; - const atom = elf_file.atom(atom_index) orelse continue; - if (!atom.flags.alive) continue; + const atom_ptr = self.atom(shndx) orelse continue; + if (!atom_ptr.flags.alive) continue; } const global = elf_file.symbol(index); if (self.asFile().symbolRank(esym, false) < global.symbolRank(elf_file)) { const atom_index = switch (esym.st_shndx) { elf.SHN_ABS, elf.SHN_COMMON => 0, - SHN_ATOM => self.atoms.items[shndx], + SHN_ATOM => shndx, else => unreachable, }; - const output_section_index = if (elf_file.atom(atom_index)) |atom| - atom.outputShndx().? + const output_section_index = if (self.atom(atom_index)) |atom_ptr| + atom_ptr.outputShndx().? else elf.SHN_UNDEF; global.value = @intCast(esym.st_value); - global.atom_index = atom_index; + global.atom_ref = .{ .index = atom_index, .file = self.index }; global.esym_index = esym_index; global.file_index = self.index; global.output_section_index = output_section_index; @@ -376,7 +376,7 @@ pub fn claimUnresolved(self: ZigObject, elf_file: *Elf) void { }; global.value = 0; - global.atom_index = 0; + global.atom_ref = .{ .index = 0, .file = 0 }; global.esym_index = esym_index; global.file_index = self.index; global.version_index = if (is_import) elf.VER_NDX_LOCAL else elf_file.default_sym_version; @@ -397,7 +397,7 @@ pub fn claimUnresolvedObject(self: ZigObject, elf_file: *Elf) void { } global.value = 0; - global.atom_index = 0; + global.atom_ref = .{ .index = 0, .file = 0 }; global.esym_index = esym_index; global.file_index = self.index; } @@ -405,19 +405,19 @@ pub fn claimUnresolvedObject(self: ZigObject, elf_file: *Elf) void { pub fn scanRelocs(self: *ZigObject, elf_file: *Elf, undefs: anytype) !void { const gpa = elf_file.base.comp.gpa; - for (self.atoms.items) |atom_index| { - const atom = elf_file.atom(atom_index) orelse continue; - if (!atom.flags.alive) continue; - const shdr = atom.inputShdr(elf_file); + for (self.atoms_indexes.items) |atom_index| { + const atom_ptr = self.atom(atom_index) orelse continue; + if (!atom_ptr.flags.alive) continue; + const shdr = atom_ptr.inputShdr(elf_file); if (shdr.sh_type == elf.SHT_NOBITS) continue; - if (atom.scanRelocsRequiresCode(elf_file)) { + if (atom_ptr.scanRelocsRequiresCode(elf_file)) { // TODO ideally we don't have to fetch the code here. // Perhaps it would make sense to save the code until flushModule where we // would free all of generated code? const code = try self.codeAlloc(elf_file, atom_index); defer gpa.free(code); - try atom.scanRelocs(elf_file, code, undefs); - } else try atom.scanRelocs(elf_file, null, undefs); + try atom_ptr.scanRelocs(elf_file, code, undefs); + } else try atom_ptr.scanRelocs(elf_file, null, undefs); } } @@ -450,9 +450,8 @@ pub fn checkDuplicates(self: *ZigObject, dupes: anytype, elf_file: *Elf) error{O esym.st_shndx == elf.SHN_COMMON) continue; if (esym.st_shndx == SHN_ATOM) { - const atom_index = self.atoms.items[shndx]; - const atom = elf_file.atom(atom_index) orelse continue; - if (!atom.flags.alive) continue; + const atom_ptr = self.atom(shndx) orelse continue; + if (!atom_ptr.flags.alive) continue; } const gop = try dupes.getOrPut(index); @@ -517,20 +516,20 @@ pub fn writeAr(self: ZigObject, writer: anytype) !void { try writer.writeAll(self.data.items); } -pub fn addAtomsToRelaSections(self: ZigObject, elf_file: *Elf) !void { - for (self.atoms.items) |atom_index| { - const atom = elf_file.atom(atom_index) orelse continue; - if (!atom.flags.alive) continue; - const rela_shndx = atom.relocsShndx() orelse continue; +pub fn addAtomsToRelaSections(self: *ZigObject, elf_file: *Elf) !void { + for (self.atoms_indexes.items) |atom_index| { + const atom_ptr = self.atom(atom_index) orelse continue; + if (!atom_ptr.flags.alive) continue; + const rela_shndx = atom_ptr.relocsShndx() orelse continue; // TODO this check will become obsolete when we rework our relocs mechanism at the ZigObject level if (self.relocs.items[rela_shndx].items.len == 0) continue; - const out_shndx = atom.outputShndx().?; + const out_shndx = atom_ptr.outputShndx().?; const out_shdr = elf_file.shdrs.items[out_shndx]; if (out_shdr.sh_type == elf.SHT_NOBITS) continue; const gpa = elf_file.base.comp.gpa; const sec = elf_file.output_rela_sections.getPtr(out_shndx).?; - try sec.atom_list.append(gpa, atom_index); + try sec.atom_list.append(gpa, .{ .index = atom_index, .file = self.index }); } } @@ -561,7 +560,7 @@ pub fn globals(self: ZigObject) []const Symbol.Index { pub fn updateSymtabSize(self: *ZigObject, elf_file: *Elf) !void { for (self.locals()) |local_index| { const local = elf_file.symbol(local_index); - if (local.atom(elf_file)) |atom| if (!atom.flags.alive) continue; + if (local.atom(elf_file)) |atom_ptr| if (!atom_ptr.flags.alive) continue; const esym = local.elfSym(elf_file); switch (esym.st_type()) { elf.STT_SECTION, elf.STT_NOTYPE => continue, @@ -577,7 +576,7 @@ pub fn updateSymtabSize(self: *ZigObject, elf_file: *Elf) !void { const global = elf_file.symbol(global_index); const file_ptr = global.file(elf_file) orelse continue; if (file_ptr.index() != self.index) continue; - if (global.atom(elf_file)) |atom| if (!atom.flags.alive) continue; + if (global.atom(elf_file)) |atom_ptr| if (!atom_ptr.flags.alive) continue; global.flags.output_symtab = true; if (global.isLocal(elf_file)) { try global.addExtra(.{ .symtab = self.output_symtab_ctx.nlocals }, elf_file); @@ -621,11 +620,10 @@ pub fn asFile(self: *ZigObject) File { /// Returns atom's code. /// Caller owns the memory. -pub fn codeAlloc(self: ZigObject, elf_file: *Elf, atom_index: Atom.Index) ![]u8 { +pub fn codeAlloc(self: *ZigObject, elf_file: *Elf, atom_index: Atom.Index) ![]u8 { const gpa = elf_file.base.comp.gpa; - const atom = elf_file.atom(atom_index).?; - assert(atom.file_index == self.index); - const shdr = &elf_file.shdrs.items[atom.outputShndx().?]; + const atom_ptr = self.atom(atom_index).?; + const shdr = &elf_file.shdrs.items[atom_ptr.outputShndx().?]; if (shdr.sh_flags & elf.SHF_TLS != 0) { const tlv = self.tls_variables.get(atom_index).?; @@ -633,13 +631,13 @@ pub fn codeAlloc(self: ZigObject, elf_file: *Elf, atom_index: Atom.Index) ![]u8 return code; } - const file_offset = shdr.sh_offset + @as(u64, @intCast(atom.value)); - const size = std.math.cast(usize, atom.size) orelse return error.Overflow; + const file_offset = shdr.sh_offset + @as(u64, @intCast(atom_ptr.value)); + const size = std.math.cast(usize, atom_ptr.size) orelse return error.Overflow; const code = try gpa.alloc(u8, size); errdefer gpa.free(code); const amt = try elf_file.base.file.?.preadAll(code, file_offset); if (amt != code.len) { - log.err("fetching code for {s} failed", .{atom.name(elf_file)}); + log.err("fetching code for {s} failed", .{atom_ptr.name(elf_file)}); return error.InputOutput; } return code; @@ -760,7 +758,7 @@ pub fn getOrCreateMetadataForLazySymbol( }; switch (metadata.state.*) { .unused => { - const symbol_index = try self.addAtom(elf_file); + const symbol_index = try self.newAtom(elf_file); const sym = elf_file.symbol(symbol_index); sym.flags.needs_zig_got = true; metadata.symbol_index.* = symbol_index; @@ -824,7 +822,7 @@ pub fn getOrCreateMetadataForDecl( const gop = try self.decls.getOrPut(gpa, decl_index); if (!gop.found_existing) { const any_non_single_threaded = elf_file.base.comp.config.any_non_single_threaded; - const symbol_index = try self.addAtom(elf_file); + const symbol_index = try self.newAtom(elf_file); const mod = elf_file.base.comp.module.?; const decl = mod.declPtr(decl_index); const sym = elf_file.symbol(symbol_index); @@ -1048,7 +1046,7 @@ fn updateTlv( { const gop = try elf_file.output_sections.getOrPut(gpa, atom_ptr.output_section_index); if (!gop.found_existing) gop.value_ptr.* = .{}; - try gop.value_ptr.append(gpa, atom_ptr.atom_index); + try gop.value_ptr.append(gpa, .{ .index = atom_ptr.atom_index, .file = self.index }); } } @@ -1307,8 +1305,7 @@ pub fn lowerUnnamedConst( return error.CodegenFail; }, }; - const sym = elf_file.symbol(sym_index); - try unnamed_consts.append(gpa, sym.atom_index); + try unnamed_consts.append(gpa, sym_index); return sym_index; } @@ -1332,7 +1329,7 @@ fn lowerConst( var code_buffer = std.ArrayList(u8).init(gpa); defer code_buffer.deinit(); - const sym_index = try self.addAtom(elf_file); + const sym_index = try self.newAtom(elf_file); const res = try codegen.generateSymbol( &elf_file.base, @@ -1530,6 +1527,72 @@ pub fn getString(self: ZigObject, off: u32) [:0]const u8 { return self.strtab.getAssumeExists(off); } +fn addAtom(self: *ZigObject, allocator: Allocator) !Atom.Index { + try self.atoms.ensureUnusedCapacity(allocator, 1); + try self.atoms_extra.ensureUnusedCapacity(allocator, @sizeOf(Atom.Extra)); + return self.addAtomAssumeCapacity(); +} + +fn addAtomAssumeCapacity(self: *ZigObject) Atom.Index { + const atom_index: Atom.Index = @intCast(self.atoms.items.len); + const atom_ptr = self.atoms.addOneAssumeCapacity(); + atom_ptr.* = .{ + .file_index = self.index, + .atom_index = atom_index, + .extra_index = self.addAtomExtraAssumeCapacity(.{}), + }; + return atom_index; +} + +pub fn atom(self: *ZigObject, atom_index: Atom.Index) ?*Atom { + if (atom_index == 0) return null; + assert(atom_index < self.atoms.items.len); + return &self.atoms.items[atom_index]; +} + +fn addAtomExtra(self: *ZigObject, allocator: Allocator, extra: Atom.Extra) !u32 { + const fields = @typeInfo(Atom.Extra).Struct.fields; + try self.atoms_extra.ensureUnusedCapacity(allocator, fields.len); + return self.addAtomExtraAssumeCapacity(extra); +} + +fn addAtomExtraAssumeCapacity(self: *ZigObject, extra: Atom.Extra) u32 { + const index = @as(u32, @intCast(self.atoms_extra.items.len)); + const fields = @typeInfo(Atom.Extra).Struct.fields; + inline for (fields) |field| { + self.atoms_extra.appendAssumeCapacity(switch (field.type) { + u32 => @field(extra, field.name), + else => @compileError("bad field type"), + }); + } + return index; +} + +pub fn atomExtra(self: ZigObject, index: u32) Atom.Extra { + const fields = @typeInfo(Atom.Extra).Struct.fields; + var i: usize = index; + var result: Atom.Extra = undefined; + inline for (fields) |field| { + @field(result, field.name) = switch (field.type) { + u32 => self.atoms_extra.items[i], + else => @compileError("bad field type"), + }; + i += 1; + } + return result; +} + +pub fn setAtomExtra(self: *ZigObject, index: u32, extra: Atom.Extra) void { + assert(index > 0); + const fields = @typeInfo(Atom.Extra).Struct.fields; + inline for (fields, 0..) |field, i| { + self.atoms_extra.items[index + i] = switch (field.type) { + u32 => @field(extra, field.name), + else => @compileError("bad field type"), + }; + } +} + pub fn fmtSymtab(self: *ZigObject, elf_file: *Elf) std.fmt.Formatter(formatSymtab) { return .{ .data = .{ .self = self, @@ -1578,9 +1641,9 @@ fn formatAtoms( _ = unused_fmt_string; _ = options; try writer.writeAll(" atoms\n"); - for (ctx.self.atoms.items) |atom_index| { - const atom = ctx.elf_file.atom(atom_index) orelse continue; - try writer.print(" {}\n", .{atom.fmt(ctx.elf_file)}); + for (ctx.self.atoms_indexes.items) |atom_index| { + const atom_ptr = ctx.self.atom(atom_index) orelse continue; + try writer.print(" {}\n", .{atom_ptr.fmt(ctx.elf_file)}); } } diff --git a/src/link/Elf/eh_frame.zig b/src/link/Elf/eh_frame.zig index 7f227a06e3..bc0e3b90c8 100644 --- a/src/link/Elf/eh_frame.zig +++ b/src/link/Elf/eh_frame.zig @@ -42,8 +42,8 @@ pub const Fde = struct { const object = elf_file.file(fde.file_index).?.object; const rel = fde.relocs(elf_file)[0]; const sym = object.symtab.items[rel.r_sym()]; - const atom_index = object.atoms.items[sym.st_shndx]; - return elf_file.atom(atom_index).?; + const atom_index = object.atoms_indexes.items[sym.st_shndx]; + return object.atom(atom_index).?; } pub fn relocs(fde: Fde, elf_file: *Elf) []align(1) const elf.Elf64_Rela { diff --git a/src/link/Elf/file.zig b/src/link/Elf/file.zig index cf1f0db2e7..687013b04e 100644 --- a/src/link/Elf/file.zig +++ b/src/link/Elf/file.zig @@ -98,11 +98,34 @@ pub const File = union(enum) { } } + pub fn atom(file: File, atom_index: Atom.Index) ?*Atom { + return switch (file) { + .shared_object => unreachable, + .linker_defined => null, + inline else => |x| x.atom(atom_index), + }; + } + pub fn atoms(file: File) []const Atom.Index { return switch (file) { - .linker_defined, .shared_object => &[0]Atom.Index{}, - .zig_object => |x| x.atoms.items, - .object => |x| x.atoms.items, + .shared_object => unreachable, + .linker_defined => &[0]Atom.Index{}, + .zig_object => |x| x.atoms_indexes.items, + .object => |x| x.atoms_indexes.items, + }; + } + + pub fn atomExtra(file: File, extra_index: u32) Atom.Extra { + return switch (file) { + .shared_object, .linker_defined => unreachable, + inline else => |x| x.atomExtra(extra_index), + }; + } + + pub fn setAtomExtra(file: File, extra_index: u32, extra: Atom.Extra) void { + return switch (file) { + .shared_object, .linker_defined => unreachable, + inline else => |x| x.setAtomExtra(extra_index, extra), }; } diff --git a/src/link/Elf/gc.zig b/src/link/Elf/gc.zig index 49b5664dec..539caaa3da 100644 --- a/src/link/Elf/gc.zig +++ b/src/link/Elf/gc.zig @@ -35,7 +35,7 @@ fn collectRoots(roots: *std.ArrayList(*Atom), files: []const File.Index, elf_fil const file = elf_file.file(index).?; for (file.atoms()) |atom_index| { - const atom = elf_file.atom(atom_index) orelse continue; + const atom = file.atom(atom_index) orelse continue; if (!atom.flags.alive) continue; const shdr = atom.inputShdr(elf_file); @@ -120,8 +120,9 @@ fn mark(roots: std.ArrayList(*Atom), elf_file: *Elf) void { fn prune(files: []const File.Index, elf_file: *Elf) void { for (files) |index| { - for (elf_file.file(index).?.atoms()) |atom_index| { - const atom = elf_file.atom(atom_index) orelse continue; + const file = elf_file.file(index).?; + for (file.atoms()) |atom_index| { + const atom = file.atom(atom_index) orelse continue; if (atom.flags.alive and !atom.flags.visited) { atom.flags.alive = false; atom.markFdesDead(elf_file); @@ -133,8 +134,9 @@ fn prune(files: []const File.Index, elf_file: *Elf) void { pub fn dumpPrunedAtoms(elf_file: *Elf) !void { const stderr = std.io.getStdErr().writer(); for (elf_file.objects.items) |index| { - for (elf_file.file(index).?.object.atoms.items) |atom_index| { - const atom = elf_file.atom(atom_index) orelse continue; + const file = elf_file.file(index).?; + for (file.atoms()) |atom_index| { + const atom = file.atom(atom_index) orelse continue; if (!atom.flags.alive) // TODO should we simply print to stderr? try stderr.print("link: removing unused section '{s}' in file '{}'\n", .{ diff --git a/src/link/Elf/relocatable.zig b/src/link/Elf/relocatable.zig index d967b089ba..1776faa9ca 100644 --- a/src/link/Elf/relocatable.zig +++ b/src/link/Elf/relocatable.zig @@ -341,8 +341,8 @@ fn initComdatGroups(elf_file: *Elf) !void { fn updateSectionSizes(elf_file: *Elf) !void { for (elf_file.output_sections.keys(), elf_file.output_sections.values()) |shndx, atom_list| { const shdr = &elf_file.shdrs.items[shndx]; - for (atom_list.items) |atom_index| { - const atom_ptr = elf_file.atom(atom_index) orelse continue; + for (atom_list.items) |ref| { + const atom_ptr = elf_file.atom(ref) orelse continue; if (!atom_ptr.flags.alive) continue; const offset = atom_ptr.alignment.forward(shdr.sh_size); const padding = offset - shdr.sh_size; @@ -354,8 +354,8 @@ fn updateSectionSizes(elf_file: *Elf) !void { for (elf_file.output_rela_sections.values()) |sec| { const shdr = &elf_file.shdrs.items[sec.shndx]; - for (sec.atom_list.items) |atom_index| { - const atom_ptr = elf_file.atom(atom_index) orelse continue; + for (sec.atom_list.items) |ref| { + const atom_ptr = elf_file.atom(ref) orelse continue; if (!atom_ptr.flags.alive) continue; const relocs = atom_ptr.relocs(elf_file); shdr.sh_size += shdr.sh_entsize * relocs.len; @@ -448,16 +448,16 @@ fn writeAtoms(elf_file: *Elf) !void { 0; @memset(buffer, padding_byte); - for (atom_list.items) |atom_index| { - const atom_ptr = elf_file.atom(atom_index).?; + for (atom_list.items) |ref| { + const atom_ptr = elf_file.atom(ref).?; assert(atom_ptr.flags.alive); const offset = math.cast(usize, atom_ptr.value - @as(i64, @intCast(shdr.sh_addr - base_offset))) orelse return error.Overflow; const size = math.cast(usize, atom_ptr.size) orelse return error.Overflow; - log.debug("writing atom({d}) from 0x{x} to 0x{x}", .{ - atom_index, + log.debug("writing atom({}) from 0x{x} to 0x{x}", .{ + ref, sh_offset + offset, sh_offset + offset + size, }); @@ -465,8 +465,8 @@ fn writeAtoms(elf_file: *Elf) !void { // TODO decompress directly into provided buffer const out_code = buffer[offset..][0..size]; const in_code = switch (atom_ptr.file(elf_file).?) { - .object => |x| try x.codeDecompressAlloc(elf_file, atom_index), - .zig_object => |x| try x.codeAlloc(elf_file, atom_index), + .object => |x| try x.codeDecompressAlloc(elf_file, ref.index), + .zig_object => |x| try x.codeAlloc(elf_file, ref.index), else => unreachable, }; defer gpa.free(in_code); @@ -490,8 +490,8 @@ fn writeSyntheticSections(elf_file: *Elf) !void { var relocs = try std.ArrayList(elf.Elf64_Rela).initCapacity(gpa, num_relocs); defer relocs.deinit(); - for (sec.atom_list.items) |atom_index| { - const atom_ptr = elf_file.atom(atom_index) orelse continue; + for (sec.atom_list.items) |ref| { + const atom_ptr = elf_file.atom(ref) orelse continue; if (!atom_ptr.flags.alive) continue; try atom_ptr.writeRelocs(elf_file, &relocs); } diff --git a/src/link/Elf/synthetic_sections.zig b/src/link/Elf/synthetic_sections.zig index c483cfd749..25b6833469 100644 --- a/src/link/Elf/synthetic_sections.zig +++ b/src/link/Elf/synthetic_sections.zig @@ -1705,14 +1705,14 @@ pub const ComdatGroupSection = struct { const shdr = object.shdrs.items[shndx]; switch (shdr.sh_type) { elf.SHT_RELA => { - const atom_index = object.atoms.items[shdr.sh_info]; - const atom = elf_file.atom(atom_index).?; + const atom_index = object.atoms_indexes.items[shdr.sh_info]; + const atom = object.atom(atom_index).?; const rela = elf_file.output_rela_sections.get(atom.outputShndx().?).?; try writer.writeInt(u32, rela.shndx, .little); }, else => { - const atom_index = object.atoms.items[shndx]; - const atom = elf_file.atom(atom_index).?; + const atom_index = object.atoms_indexes.items[shndx]; + const atom = object.atom(atom_index).?; try writer.writeInt(u32, atom.outputShndx().?, .little); }, } diff --git a/src/link/Elf/thunks.zig b/src/link/Elf/thunks.zig index 48694416c9..095430dbce 100644 --- a/src/link/Elf/thunks.zig +++ b/src/link/Elf/thunks.zig @@ -6,8 +6,8 @@ pub fn createThunks(shndx: u32, elf_file: *Elf) !void { const atoms = elf_file.output_sections.get(shndx).?.items; assert(atoms.len > 0); - for (atoms) |atom_index| { - elf_file.atom(atom_index).?.value = -1; + for (atoms) |ref| { + elf_file.atom(ref).?.value = -1; } var i: usize = 0; @@ -19,8 +19,7 @@ pub fn createThunks(shndx: u32, elf_file: *Elf) !void { i += 1; while (i < atoms.len) : (i += 1) { - const atom_index = atoms[i]; - const atom = elf_file.atom(atom_index).?; + const atom = elf_file.atom(atoms[i]).?; assert(atom.flags.alive); if (@as(i64, @intCast(atom.alignment.forward(shdr.sh_size))) - start_atom.value >= max_distance) break; @@ -33,10 +32,10 @@ pub fn createThunks(shndx: u32, elf_file: *Elf) !void { thunk.output_section_index = shndx; // Scan relocs in the group and create trampolines for any unreachable callsite - for (atoms[start..i]) |atom_index| { - const atom = elf_file.atom(atom_index).?; + for (atoms[start..i]) |ref| { + const atom = elf_file.atom(ref).?; const file = atom.file(elf_file).?; - log.debug("atom({d}) {s}", .{ atom_index, atom.name(elf_file) }); + log.debug("atom({}) {s}", .{ ref, atom.name(elf_file) }); for (atom.relocs(elf_file)) |rel| { const is_reachable = switch (cpu_arch) { .aarch64 => aarch64.isReachable(atom, rel, elf_file), @@ -51,7 +50,7 @@ pub fn createThunks(shndx: u32, elf_file: *Elf) !void { }; try thunk.symbols.put(gpa, target, {}); } - try atom.addExtra(.{ .thunk = thunk_index }, elf_file); + atom.addExtra(.{ .thunk = thunk_index }, elf_file); atom.flags.thunk = true; } diff --git a/src/link/MachO/InternalObject.zig b/src/link/MachO/InternalObject.zig index 9720251f97..7e0f375ec1 100644 --- a/src/link/MachO/InternalObject.zig +++ b/src/link/MachO/InternalObject.zig @@ -45,8 +45,7 @@ pub fn deinit(self: *InternalObject, allocator: Allocator) void { pub fn init(self: *InternalObject, allocator: Allocator) !void { // Atom at index 0 is reserved as null atom. - try self.atoms.append(allocator, .{}); - try self.atoms_extra.append(allocator, 0); + try self.atoms.append(allocator, .{ .extra = try self.addAtomExtra(allocator, .{}) }); // Null byte in strtab try self.strtab.append(allocator, 0); } diff --git a/src/link/MachO/ZigObject.zig b/src/link/MachO/ZigObject.zig index 4e939008a5..eb240017ea 100644 --- a/src/link/MachO/ZigObject.zig +++ b/src/link/MachO/ZigObject.zig @@ -1634,7 +1634,7 @@ fn isThreadlocal(macho_file: *MachO, decl_index: InternPool.DeclIndex) bool { fn addAtom(self: *ZigObject, allocator: Allocator) !Atom.Index { try self.atoms.ensureUnusedCapacity(allocator, 1); - try self.atoms_extra.ensureUnusedCapacity(allocator, 1); + try self.atoms_extra.ensureUnusedCapacity(allocator, @sizeOf(Atom.Extra)); return self.addAtomAssumeCapacity(); } From 494ae149e0a52c4afd71f6741379d4a7a9afe3f3 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 24 Jul 2024 22:06:12 +0200 Subject: [PATCH 048/266] elf: skip storing comdat group signature globally --- src/link/Elf.zig | 34 ++++++++++++++++++++++++++++++---- src/link/Elf/Object.zig | 9 ++++++--- src/link/Elf/file.zig | 6 ++++++ 3 files changed, 42 insertions(+), 7 deletions(-) diff --git a/src/link/Elf.zig b/src/link/Elf.zig index d140b0a6e3..d8d62eba10 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -216,7 +216,7 @@ merge_subsections: std.ArrayListUnmanaged(MergeSubsection) = .{}, last_atom_and_free_list_table: LastAtomAndFreeListTable = .{}, comdat_groups_owners: std.ArrayListUnmanaged(ComdatGroupOwner) = .{}, -comdat_groups_table: std.AutoHashMapUnmanaged(u32, ComdatGroupOwner.Index) = .{}, +comdat_groups_table: std.ArrayHashMapUnmanaged(ComdatGroupKey, ComdatGroupOwner.Index, ComdatGroupContext, false) = .{}, /// Global string table used to provide quick access to global symbol resolvers /// such as `resolver` and `comdat_groups_table`. @@ -5742,10 +5742,9 @@ const GetOrCreateComdatGroupOwnerResult = struct { index: ComdatGroupOwner.Index, }; -pub fn getOrCreateComdatGroupOwner(self: *Elf, name: [:0]const u8) !GetOrCreateComdatGroupOwnerResult { +pub fn getOrCreateComdatGroupOwner(self: *Elf, key: ComdatGroupKey) !GetOrCreateComdatGroupOwnerResult { const gpa = self.base.comp.gpa; - const off = try self.strings.insert(gpa, name); - const gop = try self.comdat_groups_table.getOrPut(gpa, off); + const gop = try self.comdat_groups_table.getOrPutContext(gpa, key, .{ .elf_file = self }); if (!gop.found_existing) { const index: ComdatGroupOwner.Index = @intCast(self.comdat_groups_owners.items.len); const owner = try self.comdat_groups_owners.addOne(gpa); @@ -6244,6 +6243,33 @@ const default_entry_addr = 0x8000000; pub const base_tag: link.File.Tag = .elf; +const ComdatGroupKey = struct { + /// String table offset. + off: u32, + + /// File index. + file_index: File.Index, + + pub fn get(key: ComdatGroupKey, elf_file: *Elf) [:0]const u8 { + const file_ptr = elf_file.file(key.file_index).?; + return file_ptr.getString(key.off); + } +}; + +const ComdatGroupContext = struct { + elf_file: *Elf, + + pub fn eql(ctx: ComdatGroupContext, a: ComdatGroupKey, b: ComdatGroupKey, b_index: usize) bool { + _ = b_index; + const elf_file = ctx.elf_file; + return mem.eql(u8, a.get(elf_file), b.get(elf_file)); + } + + pub fn hash(ctx: ComdatGroupContext, a: ComdatGroupKey) u32 { + return std.array_hash_map.hashString(a.get(ctx.elf_file)); + } +}; + const ComdatGroupOwner = struct { file: File.Index = 0, diff --git a/src/link/Elf/Object.zig b/src/link/Elf/Object.zig index 08ad4e8378..649c6e0354 100644 --- a/src/link/Elf/Object.zig +++ b/src/link/Elf/Object.zig @@ -208,9 +208,9 @@ fn initAtoms(self: *Object, allocator: Allocator, handle: std.fs.File, elf_file: const group_signature = blk: { if (group_info_sym.st_name == 0 and group_info_sym.st_type() == elf.STT_SECTION) { const sym_shdr = shdrs[group_info_sym.st_shndx]; - break :blk self.getString(sym_shdr.sh_name); + break :blk sym_shdr.sh_name; } - break :blk self.getString(group_info_sym.st_name); + break :blk group_info_sym.st_name; }; const shndx = @as(u32, @intCast(i)); @@ -228,7 +228,10 @@ fn initAtoms(self: *Object, allocator: Allocator, handle: std.fs.File, elf_file: const group_start = @as(u32, @intCast(self.comdat_group_data.items.len)); try self.comdat_group_data.appendUnalignedSlice(allocator, group_members[1..]); - const gop = try elf_file.getOrCreateComdatGroupOwner(group_signature); + const gop = try elf_file.getOrCreateComdatGroupOwner(.{ + .off = group_signature, + .file_index = self.index, + }); const comdat_group_index = try self.addComdatGroup(allocator); const comdat_group = self.comdatGroup(comdat_group_index); comdat_group.* = .{ diff --git a/src/link/Elf/file.zig b/src/link/Elf/file.zig index 687013b04e..df2c59a0f2 100644 --- a/src/link/Elf/file.zig +++ b/src/link/Elf/file.zig @@ -157,6 +157,12 @@ pub const File = union(enum) { }; } + pub fn getString(file: File, off: u32) [:0]const u8 { + return switch (file) { + inline else => |x| x.getString(off), + }; + } + pub fn updateSymtabSize(file: File, elf_file: *Elf) !void { return switch (file) { inline else => |x| x.updateSymtabSize(elf_file), From fa09276510b03292ace9b8cc72064341530a1940 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Thu, 25 Jul 2024 11:53:39 +0200 Subject: [PATCH 049/266] test/link/elf: test COMDAT elimination --- test/link/elf.zig | 92 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/test/link/elf.zig b/test/link/elf.zig index d97afd4d8f..49291be022 100644 --- a/test/link/elf.zig +++ b/test/link/elf.zig @@ -59,6 +59,7 @@ pub fn testAll(b: *Build, build_opts: BuildOptions) *Step { // Exercise linker with LLVM backend // musl tests elf_step.dependOn(testAbsSymbols(b, .{ .target = musl_target })); + elf_step.dependOn(testComdatElimination(b, .{ .target = musl_target })); elf_step.dependOn(testCommonSymbols(b, .{ .target = musl_target })); elf_step.dependOn(testCommonSymbolsInArchive(b, .{ .target = musl_target })); elf_step.dependOn(testCommentString(b, .{ .target = musl_target })); @@ -368,6 +369,97 @@ fn testCanonicalPlt(b: *Build, opts: Options) *Step { return test_step; } +fn testComdatElimination(b: *Build, opts: Options) *Step { + const test_step = addTestStep(b, "comdat-elimination", opts); + + const a_o = addObject(b, opts, .{ + .name = "a", + .cpp_source_bytes = + \\#include + \\inline void foo() { + \\ printf("calling foo in a\n"); + \\} + \\void hello() { + \\ foo(); + \\} + , + }); + a_o.linkLibCpp(); + + const main_o = addObject(b, opts, .{ + .name = "main", + .cpp_source_bytes = + \\#include + \\inline void foo() { + \\ printf("calling foo in main\n"); + \\} + \\void hello(); + \\int main() { + \\ foo(); + \\ hello(); + \\ return 0; + \\} + , + }); + main_o.linkLibCpp(); + + { + const exe = addExecutable(b, opts, .{ + .name = "main1", + }); + exe.addObject(a_o); + exe.addObject(main_o); + exe.linkLibCpp(); + + const run = addRunArtifact(exe); + run.expectStdOutEqual( + \\calling foo in a + \\calling foo in a + \\ + ); + test_step.dependOn(&run.step); + + const check = exe.checkObject(); + check.checkInSymtab(); + // This weird looking double assertion uses the fact that once we find the symbol in + // the symtab, we do not reset the cursor and do subsequent checks from that point onwards. + // If this is the case, and COMDAT elimination works correctly we should only have one instance + // of foo() function. + check.checkContains("_Z3foov"); + check.checkNotPresent("_Z3foov"); + test_step.dependOn(&check.step); + } + + { + const exe = addExecutable(b, opts, .{ + .name = "main2", + }); + exe.addObject(main_o); + exe.addObject(a_o); + exe.linkLibCpp(); + + const run = addRunArtifact(exe); + run.expectStdOutEqual( + \\calling foo in main + \\calling foo in main + \\ + ); + test_step.dependOn(&run.step); + + const check = exe.checkObject(); + check.checkInSymtab(); + // This weird looking double assertion uses the fact that once we find the symbol in + // the symtab, we do not reset the cursor and do subsequent checks from that point onwards. + // If this is the case, and COMDAT elimination works correctly we should only have one instance + // of foo() function. + check.checkContains("_Z3foov"); + check.checkNotPresent("_Z3foov"); + test_step.dependOn(&check.step); + } + + return test_step; +} + fn testCommentString(b: *Build, opts: Options) *Step { const test_step = addTestStep(b, "comment-string", opts); From c575e3daa4cdb39e38cc0b32fc8fe4a917947d34 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Thu, 25 Jul 2024 16:47:43 +0200 Subject: [PATCH 050/266] elf: resolve COMDATs in more parallel-friendly way --- src/link/Elf.zig | 123 +++++++--------------------- src/link/Elf/Atom.zig | 5 +- src/link/Elf/Object.zig | 71 +++++++++++++--- src/link/Elf/file.zig | 7 ++ src/link/Elf/relocatable.zig | 7 +- src/link/Elf/synthetic_sections.zig | 10 +-- test/link/elf.zig | 44 ++++++++-- 7 files changed, 140 insertions(+), 127 deletions(-) diff --git a/src/link/Elf.zig b/src/link/Elf.zig index d8d62eba10..b3cc9bc601 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -215,11 +215,8 @@ merge_subsections: std.ArrayListUnmanaged(MergeSubsection) = .{}, /// Table of last atom index in a section and matching atom free list if any. last_atom_and_free_list_table: LastAtomAndFreeListTable = .{}, -comdat_groups_owners: std.ArrayListUnmanaged(ComdatGroupOwner) = .{}, -comdat_groups_table: std.ArrayHashMapUnmanaged(ComdatGroupKey, ComdatGroupOwner.Index, ComdatGroupContext, false) = .{}, - /// Global string table used to provide quick access to global symbol resolvers -/// such as `resolver` and `comdat_groups_table`. +/// such as `resolver`. strings: StringTable = .{}, first_eflags: ?elf.Elf64_Word = null, @@ -506,8 +503,6 @@ pub fn deinit(self: *Elf) void { } self.last_atom_and_free_list_table.deinit(gpa); - self.comdat_groups_owners.deinit(gpa); - self.comdat_groups_table.deinit(gpa); self.strings.deinit(gpa); self.got.deinit(gpa); @@ -1305,7 +1300,7 @@ pub fn flushModule(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_nod // input Object files. // Any qualifing unresolved symbol will be upgraded to an absolute, weak // symbol for potential resolution at load-time. - self.resolveSymbols(); + try self.resolveSymbols(); self.markEhFrameAtomsDead(); try self.resolveMergeSections(); @@ -1955,7 +1950,7 @@ fn accessLibPath( /// 4. Reset state of all resolved globals since we will redo this bit on the pruned set. /// 5. Remove references to dead objects/shared objects /// 6. Re-run symbol resolution on pruned objects and shared objects sets. -pub fn resolveSymbols(self: *Elf) void { +pub fn resolveSymbols(self: *Elf) !void { // Resolve symbols in the ZigObject. For now, we assume that it's always live. if (self.zigObjectPtr()) |zig_object| zig_object.asFile().resolveSymbols(self); // Resolve symbols on the set of all objects and shared objects (even if some are unneeded). @@ -1986,32 +1981,17 @@ pub fn resolveSymbols(self: *Elf) void { } else i += 1; } - // Dedup comdat groups. - for (self.objects.items) |index| { - const object = self.file(index).?.object; - for (object.comdat_groups.items) |cg| { - const cg_owner = self.comdatGroupOwner(cg.owner); - const owner_file_index = if (self.file(cg_owner.file)) |file_ptr| - file_ptr.object.index - else - std.math.maxInt(File.Index); - cg_owner.file = @min(owner_file_index, index); - } - } + { + // Dedup comdat groups. + var table = std.StringHashMap(Ref).init(self.base.comp.gpa); + defer table.deinit(); - for (self.objects.items) |index| { - const object = self.file(index).?.object; - for (object.comdat_groups.items) |cg| { - const cg_owner = self.comdatGroupOwner(cg.owner); - if (cg_owner.file != index) { - for (cg.comdatGroupMembers(self)) |shndx| { - const atom_index = object.atoms_indexes.items[shndx]; - if (object.atom(atom_index)) |atom_ptr| { - atom_ptr.flags.alive = false; - atom_ptr.markFdesDead(self); - } - } - } + for (self.objects.items) |index| { + try self.file(index).?.object.resolveComdatGroups(self, &table); + } + + for (self.objects.items) |index| { + self.file(index).?.object.markComdatGroupsDead(self); } } @@ -5632,6 +5612,10 @@ pub fn atom(self: *Elf, ref: Ref) ?*Atom { return file_ptr.atom(ref.index); } +pub fn comdatGroup(self: *Elf, ref: Ref) *ComdatGroup { + return self.file(ref.file).?.comdatGroup(ref.index); +} + /// Returns pointer-to-symbol described at sym_index. pub fn symbol(self: *Elf, sym_index: Symbol.Index) *Symbol { return &self.symbols.items[sym_index]; @@ -5737,31 +5721,6 @@ pub fn zigObjectPtr(self: *Elf) ?*ZigObject { return self.file(index).?.zig_object; } -const GetOrCreateComdatGroupOwnerResult = struct { - found_existing: bool, - index: ComdatGroupOwner.Index, -}; - -pub fn getOrCreateComdatGroupOwner(self: *Elf, key: ComdatGroupKey) !GetOrCreateComdatGroupOwnerResult { - const gpa = self.base.comp.gpa; - const gop = try self.comdat_groups_table.getOrPutContext(gpa, key, .{ .elf_file = self }); - if (!gop.found_existing) { - const index: ComdatGroupOwner.Index = @intCast(self.comdat_groups_owners.items.len); - const owner = try self.comdat_groups_owners.addOne(gpa); - owner.* = .{}; - gop.value_ptr.* = index; - } - return .{ - .found_existing = gop.found_existing, - .index = gop.value_ptr.*, - }; -} - -pub fn comdatGroupOwner(self: *Elf, index: ComdatGroupOwner.Index) *ComdatGroupOwner { - assert(index < self.comdat_groups_owners.items.len); - return &self.comdat_groups_owners.items[index]; -} - pub fn addMergeSubsection(self: *Elf) !MergeSubsection.Index { const index: MergeSubsection.Index = @intCast(self.merge_subsections.items.len); const msec = try self.merge_subsections.addOne(self.base.comp.gpa); @@ -6243,48 +6202,24 @@ const default_entry_addr = 0x8000000; pub const base_tag: link.File.Tag = .elf; -const ComdatGroupKey = struct { - /// String table offset. - off: u32, - - /// File index. - file_index: File.Index, - - pub fn get(key: ComdatGroupKey, elf_file: *Elf) [:0]const u8 { - const file_ptr = elf_file.file(key.file_index).?; - return file_ptr.getString(key.off); - } -}; - -const ComdatGroupContext = struct { - elf_file: *Elf, - - pub fn eql(ctx: ComdatGroupContext, a: ComdatGroupKey, b: ComdatGroupKey, b_index: usize) bool { - _ = b_index; - const elf_file = ctx.elf_file; - return mem.eql(u8, a.get(elf_file), b.get(elf_file)); - } - - pub fn hash(ctx: ComdatGroupContext, a: ComdatGroupKey) u32 { - return std.array_hash_map.hashString(a.get(ctx.elf_file)); - } -}; - -const ComdatGroupOwner = struct { - file: File.Index = 0, - - const Index = u32; -}; - pub const ComdatGroup = struct { - owner: ComdatGroupOwner.Index, - file: File.Index, + signature_off: u32, + file_index: File.Index, shndx: u32, members_start: u32, members_len: u32, + alive: bool = true, + + pub fn file(cg: ComdatGroup, elf_file: *Elf) File { + return elf_file.file(cg.file_index).?; + } + + pub fn signature(cg: ComdatGroup, elf_file: *Elf) [:0]const u8 { + return cg.file(elf_file).object.getString(cg.signature_off); + } pub fn comdatGroupMembers(cg: ComdatGroup, elf_file: *Elf) []const u32 { - const object = elf_file.file(cg.file).?.object; + const object = cg.file(elf_file).object; return object.comdat_group_data.items[cg.members_start..][0..cg.members_len]; } diff --git a/src/link/Elf/Atom.zig b/src/link/Elf/Atom.zig index 743603fd47..e08bada260 100644 --- a/src/link/Elf/Atom.zig +++ b/src/link/Elf/Atom.zig @@ -346,7 +346,10 @@ pub fn writeRelocs(self: Atom, elf_file: *Elf, out_relocs: *std.ArrayList(elf.El r_sym = elf_file.sectionSymbolOutputSymtabIndex(msub.mergeSection(elf_file).output_section_index); } else { r_addend += @intCast(target.address(.{}, elf_file)); - r_sym = elf_file.sectionSymbolOutputSymtabIndex(target.outputShndx().?); + r_sym = if (target.outputShndx()) |osec| + elf_file.sectionSymbolOutputSymtabIndex(osec) + else + 0; }, else => { r_sym = target.outputSymtabIndex(elf_file) orelse 0; diff --git a/src/link/Elf/Object.zig b/src/link/Elf/Object.zig index 649c6e0354..1d5a575457 100644 --- a/src/link/Elf/Object.zig +++ b/src/link/Elf/Object.zig @@ -216,27 +216,41 @@ fn initAtoms(self: *Object, allocator: Allocator, handle: std.fs.File, elf_file: const shndx = @as(u32, @intCast(i)); const group_raw_data = try self.preadShdrContentsAlloc(allocator, handle, shndx); defer allocator.free(group_raw_data); - const group_nmembers = @divExact(group_raw_data.len, @sizeOf(u32)); + const group_nmembers = math.divExact(usize, group_raw_data.len, @sizeOf(u32)) catch { + try elf_file.reportParseError2( + self.index, + "corrupt section group: not evenly divisible ", + .{}, + ); + return error.MalformedObject; + }; + if (group_nmembers == 0) { + try elf_file.reportParseError2( + self.index, + "corrupt section group: empty section", + .{}, + ); + return error.MalformedObject; + } const group_members = @as([*]align(1) const u32, @ptrCast(group_raw_data.ptr))[0..group_nmembers]; if (group_members[0] != elf.GRP_COMDAT) { - // TODO convert into an error - log.debug("{}: unknown SHT_GROUP format", .{self.fmtPath()}); - continue; + try elf_file.reportParseError2( + self.index, + "corrupt section group: unknown SHT_GROUP format", + .{}, + ); + return error.MalformedObject; } const group_start = @as(u32, @intCast(self.comdat_group_data.items.len)); try self.comdat_group_data.appendUnalignedSlice(allocator, group_members[1..]); - const gop = try elf_file.getOrCreateComdatGroupOwner(.{ - .off = group_signature, - .file_index = self.index, - }); const comdat_group_index = try self.addComdatGroup(allocator); const comdat_group = self.comdatGroup(comdat_group_index); comdat_group.* = .{ - .owner = gop.index, - .file = self.index, + .signature_off = group_signature, + .file_index = self.index, .shndx = shndx, .members_start = group_start, .members_len = @intCast(group_nmembers - 1), @@ -912,6 +926,37 @@ pub fn convertCommonSymbols(self: *Object, elf_file: *Elf) !void { } } +pub fn resolveComdatGroups(self: *Object, elf_file: *Elf, table: anytype) !void { + for (self.comdat_groups.items, 0..) |*cg, cgi| { + const signature = cg.signature(elf_file); + const gop = try table.getOrPut(signature); + if (!gop.found_existing) { + gop.value_ptr.* = .{ .index = @intCast(cgi), .file = self.index }; + continue; + } + const current = elf_file.comdatGroup(gop.value_ptr.*); + cg.alive = false; + if (self.index < current.file_index) { + current.alive = false; + cg.alive = true; + gop.value_ptr.* = .{ .index = @intCast(cgi), .file = self.index }; + } + } +} + +pub fn markComdatGroupsDead(self: *Object, elf_file: *Elf) void { + for (self.comdat_groups.items) |cg| { + if (cg.alive) continue; + for (cg.comdatGroupMembers(elf_file)) |shndx| { + const atom_index = self.atoms_indexes.items[shndx]; + if (self.atom(atom_index)) |atom_ptr| { + atom_ptr.flags.alive = false; + atom_ptr.markFdesDead(elf_file); + } + } + } +} + pub fn initOutputSections(self: *Object, elf_file: *Elf) !void { for (self.atoms_indexes.items) |atom_index| { const atom_ptr = self.atom(atom_index) orelse continue; @@ -1428,9 +1473,9 @@ fn formatComdatGroups( const elf_file = ctx.elf_file; try writer.writeAll(" COMDAT groups\n"); for (object.comdat_groups.items, 0..) |cg, cg_index| { - const cg_owner = elf_file.comdatGroupOwner(cg.owner); - if (cg_owner.file != object.index) continue; - try writer.print(" COMDAT({d})\n", .{cg_index}); + try writer.print(" COMDAT({d})", .{cg_index}); + if (!cg.alive) try writer.writeAll(" : [*]"); + try writer.writeByte('\n'); const cg_members = cg.comdatGroupMembers(elf_file); for (cg_members) |shndx| { const atom_index = object.atoms_indexes.items[shndx]; diff --git a/src/link/Elf/file.zig b/src/link/Elf/file.zig index df2c59a0f2..9d2560e11b 100644 --- a/src/link/Elf/file.zig +++ b/src/link/Elf/file.zig @@ -137,6 +137,13 @@ pub const File = union(enum) { }; } + pub fn comdatGroup(file: File, ind: Elf.ComdatGroup.Index) *Elf.ComdatGroup { + return switch (file) { + .linker_defined, .shared_object, .zig_object => unreachable, + .object => |x| x.comdatGroup(ind), + }; + } + pub fn symbol(file: File, ind: Symbol.Index) Symbol.Index { return switch (file) { .zig_object => |x| x.symbol(ind), diff --git a/src/link/Elf/relocatable.zig b/src/link/Elf/relocatable.zig index 1776faa9ca..b3c7146169 100644 --- a/src/link/Elf/relocatable.zig +++ b/src/link/Elf/relocatable.zig @@ -190,7 +190,7 @@ pub fn flushObject(elf_file: *Elf, comp: *Compilation, module_obj_path: ?[]const // Now, we are ready to resolve the symbols across all input files. // We will first resolve the files in the ZigObject, next in the parsed // input Object files. - elf_file.resolveSymbols(); + try elf_file.resolveSymbols(); elf_file.markEhFrameAtomsDead(); try elf_file.resolveMergeSections(); try elf_file.addCommentString(); @@ -318,11 +318,8 @@ fn initComdatGroups(elf_file: *Elf) !void { for (elf_file.objects.items) |index| { const object = elf_file.file(index).?.object; - for (object.comdat_groups.items, 0..) |cg, cg_index| { - const cg_owner = elf_file.comdatGroupOwner(cg.owner); - if (cg_owner.file != index) continue; - + if (!cg.alive) continue; const cg_sec = try elf_file.comdat_group_sections.addOne(gpa); cg_sec.* = .{ .shndx = try elf_file.addSection(.{ diff --git a/src/link/Elf/synthetic_sections.zig b/src/link/Elf/synthetic_sections.zig index 25b6833469..78bdd08fed 100644 --- a/src/link/Elf/synthetic_sections.zig +++ b/src/link/Elf/synthetic_sections.zig @@ -1672,12 +1672,6 @@ pub const ComdatGroupSection = struct { shndx: u32, cg_ref: Elf.Ref, - fn ownerFile(cgs: ComdatGroupSection, elf_file: *Elf) ?File { - const cg = cgs.comdatGroup(elf_file); - const cg_owner = elf_file.comdatGroupOwner(cg.owner); - return elf_file.file(cg_owner.file); - } - fn comdatGroup(cgs: ComdatGroupSection, elf_file: *Elf) *Elf.ComdatGroup { const cg_file = elf_file.file(cgs.cg_ref.file).?; return cg_file.object.comdatGroup(cgs.cg_ref.index); @@ -1685,7 +1679,7 @@ pub const ComdatGroupSection = struct { pub fn symbol(cgs: ComdatGroupSection, elf_file: *Elf) Symbol.Index { const cg = cgs.comdatGroup(elf_file); - const object = cgs.ownerFile(elf_file).?.object; + const object = cg.file(elf_file).object; const shdr = object.shdrs.items[cg.shndx]; return object.symbols.items[shdr.sh_info]; } @@ -1698,7 +1692,7 @@ pub const ComdatGroupSection = struct { pub fn write(cgs: ComdatGroupSection, elf_file: *Elf, writer: anytype) !void { const cg = cgs.comdatGroup(elf_file); - const object = cgs.ownerFile(elf_file).?.object; + const object = cg.file(elf_file).object; const members = cg.comdatGroupMembers(elf_file); try writer.writeInt(u32, elf.GRP_COMDAT, .little); for (members) |shndx| { diff --git a/test/link/elf.zig b/test/link/elf.zig index 49291be022..2dc36c6dc2 100644 --- a/test/link/elf.zig +++ b/test/link/elf.zig @@ -404,9 +404,7 @@ fn testComdatElimination(b: *Build, opts: Options) *Step { main_o.linkLibCpp(); { - const exe = addExecutable(b, opts, .{ - .name = "main1", - }); + const exe = addExecutable(b, opts, .{ .name = "main1" }); exe.addObject(a_o); exe.addObject(main_o); exe.linkLibCpp(); @@ -431,9 +429,7 @@ fn testComdatElimination(b: *Build, opts: Options) *Step { } { - const exe = addExecutable(b, opts, .{ - .name = "main2", - }); + const exe = addExecutable(b, opts, .{ .name = "main2" }); exe.addObject(main_o); exe.addObject(a_o); exe.linkLibCpp(); @@ -457,6 +453,42 @@ fn testComdatElimination(b: *Build, opts: Options) *Step { test_step.dependOn(&check.step); } + { + const c_o = addObject(b, opts, .{ .name = "c" }); + c_o.addObject(main_o); + c_o.addObject(a_o); + + const exe = addExecutable(b, opts, .{ .name = "main3" }); + exe.addObject(c_o); + exe.linkLibCpp(); + + const run = addRunArtifact(exe); + run.expectStdOutEqual( + \\calling foo in main + \\calling foo in main + \\ + ); + test_step.dependOn(&run.step); + } + + { + const d_o = addObject(b, opts, .{ .name = "d" }); + d_o.addObject(a_o); + d_o.addObject(main_o); + + const exe = addExecutable(b, opts, .{ .name = "main4" }); + exe.addObject(d_o); + exe.linkLibCpp(); + + const run = addRunArtifact(exe); + run.expectStdOutEqual( + \\calling foo in a + \\calling foo in a + \\ + ); + test_step.dependOn(&run.step); + } + return test_step; } From 96c20adeee84dbd86a7036cce49ef0c58870bdce Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Fri, 26 Jul 2024 14:06:21 +0200 Subject: [PATCH 051/266] elf: remove obsolete flags from atom --- src/link/Elf.zig | 8 ++++---- src/link/Elf/Atom.zig | 38 +++++++++++++----------------------- src/link/Elf/Object.zig | 33 +++++++++++++++---------------- src/link/Elf/Symbol.zig | 2 +- src/link/Elf/ZigObject.zig | 20 +++++++++---------- src/link/Elf/gc.zig | 22 ++++++++++----------- src/link/Elf/relocatable.zig | 8 ++++---- src/link/Elf/thunks.zig | 5 ++--- 8 files changed, 62 insertions(+), 74 deletions(-) diff --git a/src/link/Elf.zig b/src/link/Elf.zig index b3cc9bc601..c0b24e7846 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -1371,7 +1371,7 @@ pub fn flushModule(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_nod var has_reloc_errors = false; for (zo.atoms_indexes.items) |atom_index| { const atom_ptr = zo.atom(atom_index) orelse continue; - if (!atom_ptr.flags.alive) continue; + if (!atom_ptr.alive) continue; const out_shndx = atom_ptr.outputShndx() orelse continue; const shdr = &self.shdrs.items[out_shndx]; if (shdr.sh_type == elf.SHT_NOBITS) continue; @@ -4130,7 +4130,7 @@ fn resetShdrIndexes(self: *Elf, backlinks: []const u32) !void { for (zo.globals()) |global_index| { const global = self.symbol(global_index); const atom_ptr = global.atom(self) orelse continue; - if (!atom_ptr.flags.alive) continue; + if (!atom_ptr.alive) continue; // TODO claim unresolved for objects if (global.file(self).?.index() != zo.index) continue; const out_shndx = global.outputShndx() orelse continue; @@ -4157,7 +4157,7 @@ fn updateSectionSizes(self: *Elf) !void { if (self.requiresThunks() and shdr.sh_flags & elf.SHF_EXECINSTR != 0) continue; for (atom_list.items) |ref| { const atom_ptr = self.atom(ref) orelse continue; - if (!atom_ptr.flags.alive) continue; + if (!atom_ptr.alive) continue; const offset = atom_ptr.alignment.forward(shdr.sh_size); const padding = offset - shdr.sh_size; atom_ptr.value = @intCast(offset); @@ -4641,7 +4641,7 @@ fn writeAtoms(self: *Elf) !void { for (atom_list.items) |ref| { const atom_ptr = self.atom(ref).?; - assert(atom_ptr.flags.alive); + assert(atom_ptr.alive); const offset = math.cast(usize, atom_ptr.value - @as(i64, @intCast(base_offset))) orelse return error.Overflow; diff --git a/src/link/Elf/Atom.zig b/src/link/Elf/Atom.zig index e08bada260..5f90ab86f5 100644 --- a/src/link/Elf/Atom.zig +++ b/src/link/Elf/Atom.zig @@ -30,8 +30,11 @@ atom_index: Index = 0, prev_index: Index = 0, next_index: Index = 0, -/// Flags we use for state tracking. -flags: Flags = .{}, +/// Specifies whether this atom is alive or has been garbage collected. +alive: bool = true, + +/// Specifies if the atom has been visited during garbage collection. +visited: bool = false, extra_index: u32 = 0, @@ -55,7 +58,7 @@ pub fn debugTombstoneValue(self: Atom, target: Symbol, elf_file: *Elf) ?u64 { if (msub.alive) return null; } if (target.atom(elf_file)) |atom_ptr| { - if (atom_ptr.flags.alive) return null; + if (atom_ptr.alive) return null; } const atom_name = self.name(elf_file); if (!mem.startsWith(u8, atom_name, ".debug")) return null; @@ -67,7 +70,6 @@ pub fn file(self: Atom, elf_file: *Elf) ?File { } pub fn thunk(self: Atom, elf_file: *Elf) *Thunk { - assert(self.flags.thunk); const extras = self.extra(elf_file); return elf_file.thunk(extras.thunk); } @@ -237,7 +239,7 @@ pub fn allocate(self: *Atom, elf_file: *Elf) !void { _ = free_list.swapRemove(i); } - self.flags.alive = true; + self.alive = true; } pub fn shrink(self: *Atom, elf_file: *Elf) void { @@ -373,10 +375,12 @@ pub fn writeRelocs(self: Atom, elf_file: *Elf, out_relocs: *std.ArrayList(elf.El } pub fn fdes(self: Atom, elf_file: *Elf) []Fde { - if (!self.flags.fde) return &[0]Fde{}; const extras = self.extra(elf_file); - const object = self.file(elf_file).?.object; - return object.fdes.items[extras.fde_start..][0..extras.fde_count]; + return switch (self.file(elf_file).?) { + .shared_object => unreachable, + .linker_defined, .zig_object => &[0]Fde{}, + .object => |x| x.fdes.items[extras.fde_start..][0..extras.fde_count], + }; } pub fn markFdesDead(self: Atom, elf_file: *Elf) void { @@ -1066,7 +1070,7 @@ fn format2( atom.atom_index, atom.name(elf_file), atom.address(elf_file), atom.output_section_index, atom.alignment, atom.size, }); - if (atom.flags.fde) { + if (atom.fdes(elf_file).len > 0) { try writer.writeAll(" : fdes{ "); const extras = atom.extra(elf_file); for (atom.fdes(elf_file), extras.fde_start..) |fde, i| { @@ -1076,27 +1080,13 @@ fn format2( } try writer.writeAll(" }"); } - if (!atom.flags.alive) { + if (!atom.alive) { try writer.writeAll(" : [*]"); } } pub const Index = u32; -pub const Flags = packed struct { - /// Specifies whether this atom is alive or has been garbage collected. - alive: bool = true, - - /// Specifies if the atom has been visited during garbage collection. - visited: bool = false, - - /// Whether this atom has a range extension thunk. - thunk: bool = false, - - /// Whether this atom has FDE records. - fde: bool = false, -}; - const x86_64 = struct { fn scanReloc( atom: Atom, diff --git a/src/link/Elf/Object.zig b/src/link/Elf/Object.zig index 1d5a575457..f0632bb509 100644 --- a/src/link/Elf/Object.zig +++ b/src/link/Elf/Object.zig @@ -84,7 +84,7 @@ pub fn parse(self: *Object, elf_file: *Elf) !void { for (self.shdrs.items, 0..) |shdr, i| { const atom_ptr = self.atom(self.atoms_indexes.items[i]) orelse continue; - if (!atom_ptr.flags.alive) continue; + if (!atom_ptr.alive) continue; if ((cpu_arch == .x86_64 and shdr.sh_type == elf.SHT_X86_64_UNWIND) or mem.eql(u8, atom_ptr.name(elf_file), ".eh_frame")) { @@ -484,7 +484,6 @@ fn parseEhFrame(self: *Object, allocator: Allocator, handle: std.fs.File, shndx: if (atom_ptr.atom_index != next_fde.atom(elf_file).atom_index) break; } atom_ptr.addExtra(.{ .fde_start = start, .fde_count = i - start }, elf_file); - atom_ptr.flags.fde = true; } } @@ -529,7 +528,7 @@ pub fn scanRelocs(self: *Object, elf_file: *Elf, undefs: anytype) !void { const gpa = comp.gpa; for (self.atoms_indexes.items) |atom_index| { const atom_ptr = self.atom(atom_index) orelse continue; - if (!atom_ptr.flags.alive) continue; + if (!atom_ptr.alive) continue; const shdr = atom_ptr.inputShdr(elf_file); if (shdr.sh_flags & elf.SHF_ALLOC == 0) continue; if (shdr.sh_type == elf.SHT_NOBITS) continue; @@ -569,7 +568,7 @@ pub fn resolveSymbols(self: *Object, elf_file: *Elf) void { if (esym.st_shndx != elf.SHN_ABS and esym.st_shndx != elf.SHN_COMMON) { const atom_index = self.atoms_indexes.items[esym.st_shndx]; const atom_ptr = self.atom(atom_index) orelse continue; - if (!atom_ptr.flags.alive) continue; + if (!atom_ptr.alive) continue; } const global = elf_file.symbol(index); @@ -661,7 +660,7 @@ pub fn markEhFrameAtomsDead(self: *Object, elf_file: *Elf) void { const atom_ptr = self.atom(atom_index) orelse continue; const is_eh_frame = (cpu_arch == .x86_64 and atom_ptr.inputShdr(elf_file).sh_type == elf.SHT_X86_64_UNWIND) or mem.eql(u8, atom_ptr.name(elf_file), ".eh_frame"); - if (atom_ptr.flags.alive and is_eh_frame) atom_ptr.flags.alive = false; + if (atom_ptr.alive and is_eh_frame) atom_ptr.alive = false; } } @@ -681,7 +680,7 @@ pub fn checkDuplicates(self: *Object, dupes: anytype, elf_file: *Elf) error{OutO if (sym.st_shndx != elf.SHN_ABS) { const atom_index = self.atoms_indexes.items[sym.st_shndx]; const atom_ptr = self.atom(atom_index) orelse continue; - if (!atom_ptr.flags.alive) continue; + if (!atom_ptr.alive) continue; } const gop = try dupes.getOrPut(index); @@ -704,7 +703,7 @@ pub fn initMergeSections(self: *Object, elf_file: *Elf) !void { const atom_index = self.atoms_indexes.items[shndx]; const atom_ptr = self.atom(atom_index) orelse continue; - if (!atom_ptr.flags.alive) continue; + if (!atom_ptr.alive) continue; if (atom_ptr.relocs(elf_file).len > 0) continue; const imsec_idx = try self.addInputMergeSection(gpa); @@ -766,7 +765,7 @@ pub fn initMergeSections(self: *Object, elf_file: *Elf) !void { } } - atom_ptr.flags.alive = false; + atom_ptr.alive = false; } } @@ -826,7 +825,7 @@ pub fn resolveMergeSubsections(self: *Object, elf_file: *Elf) !void { for (self.atoms_indexes.items) |atom_index| { const atom_ptr = self.atom(atom_index) orelse continue; - if (!atom_ptr.flags.alive) continue; + if (!atom_ptr.alive) continue; const extras = atom_ptr.extra(elf_file); const relocs = self.relocs.items[extras.rel_index..][0..extras.rel_count]; for (relocs) |*rel| { @@ -950,7 +949,7 @@ pub fn markComdatGroupsDead(self: *Object, elf_file: *Elf) void { for (cg.comdatGroupMembers(elf_file)) |shndx| { const atom_index = self.atoms_indexes.items[shndx]; if (self.atom(atom_index)) |atom_ptr| { - atom_ptr.flags.alive = false; + atom_ptr.alive = false; atom_ptr.markFdesDead(elf_file); } } @@ -960,7 +959,7 @@ pub fn markComdatGroupsDead(self: *Object, elf_file: *Elf) void { pub fn initOutputSections(self: *Object, elf_file: *Elf) !void { for (self.atoms_indexes.items) |atom_index| { const atom_ptr = self.atom(atom_index) orelse continue; - if (!atom_ptr.flags.alive) continue; + if (!atom_ptr.alive) continue; const shdr = atom_ptr.inputShdr(elf_file); _ = try self.initOutputSection(elf_file, shdr); } @@ -969,7 +968,7 @@ pub fn initOutputSections(self: *Object, elf_file: *Elf) !void { pub fn addAtomsToOutputSections(self: *Object, elf_file: *Elf) !void { for (self.atoms_indexes.items) |atom_index| { const atom_ptr = self.atom(atom_index) orelse continue; - if (!atom_ptr.flags.alive) continue; + if (!atom_ptr.alive) continue; const shdr = atom_ptr.inputShdr(elf_file); atom_ptr.output_section_index = self.initOutputSection(elf_file, shdr) catch unreachable; @@ -988,7 +987,7 @@ pub fn addAtomsToOutputSections(self: *Object, elf_file: *Elf) !void { continue; } const atom_ptr = local.atom(elf_file) orelse continue; - if (!atom_ptr.flags.alive) continue; + if (!atom_ptr.alive) continue; local.output_section_index = atom_ptr.output_section_index; } @@ -1001,7 +1000,7 @@ pub fn addAtomsToOutputSections(self: *Object, elf_file: *Elf) !void { continue; } const atom_ptr = global.atom(elf_file) orelse continue; - if (!atom_ptr.flags.alive) continue; + if (!atom_ptr.alive) continue; global.output_section_index = atom_ptr.output_section_index; } @@ -1016,7 +1015,7 @@ pub fn addAtomsToOutputSections(self: *Object, elf_file: *Elf) !void { pub fn initRelaSections(self: *Object, elf_file: *Elf) !void { for (self.atoms_indexes.items) |atom_index| { const atom_ptr = self.atom(atom_index) orelse continue; - if (!atom_ptr.flags.alive) continue; + if (!atom_ptr.alive) continue; const shndx = atom_ptr.relocsShndx() orelse continue; const shdr = self.shdrs.items[shndx]; const out_shndx = try self.initOutputSection(elf_file, shdr); @@ -1030,7 +1029,7 @@ pub fn initRelaSections(self: *Object, elf_file: *Elf) !void { pub fn addAtomsToRelaSections(self: *Object, elf_file: *Elf) !void { for (self.atoms_indexes.items) |atom_index| { const atom_ptr = self.atom(atom_index) orelse continue; - if (!atom_ptr.flags.alive) continue; + if (!atom_ptr.alive) continue; const shndx = blk: { const shndx = atom_ptr.relocsShndx() orelse continue; const shdr = self.shdrs.items[shndx]; @@ -1100,7 +1099,7 @@ pub fn updateSymtabSize(self: *Object, elf_file: *Elf) !void { const isAlive = struct { fn isAlive(sym: *const Symbol, ctx: *Elf) bool { if (sym.mergeSubsection(ctx)) |msub| return msub.alive; - if (sym.atom(ctx)) |atom_ptr| return atom_ptr.flags.alive; + if (sym.atom(ctx)) |atom_ptr| return atom_ptr.alive; return true; } }.isAlive; diff --git a/src/link/Elf/Symbol.zig b/src/link/Elf/Symbol.zig index 6a54e9ad99..9ddc534b92 100644 --- a/src/link/Elf/Symbol.zig +++ b/src/link/Elf/Symbol.zig @@ -116,7 +116,7 @@ pub fn address(symbol: Symbol, opts: struct { plt: bool = true }, elf_file: *Elf return symbol.pltAddress(elf_file); } if (symbol.atom(elf_file)) |atom_ptr| { - if (!atom_ptr.flags.alive) { + if (!atom_ptr.alive) { if (mem.eql(u8, atom_ptr.name(elf_file), ".eh_frame")) { const sym_name = symbol.name(elf_file); const sh_addr, const sh_size = blk: { diff --git a/src/link/Elf/ZigObject.zig b/src/link/Elf/ZigObject.zig index ca5f3af021..f1efca5a79 100644 --- a/src/link/Elf/ZigObject.zig +++ b/src/link/Elf/ZigObject.zig @@ -331,7 +331,7 @@ pub fn resolveSymbols(self: *ZigObject, elf_file: *Elf) void { if (esym.st_shndx != elf.SHN_ABS and esym.st_shndx != elf.SHN_COMMON) { assert(esym.st_shndx == SHN_ATOM); const atom_ptr = self.atom(shndx) orelse continue; - if (!atom_ptr.flags.alive) continue; + if (!atom_ptr.alive) continue; } const global = elf_file.symbol(index); @@ -407,7 +407,7 @@ pub fn scanRelocs(self: *ZigObject, elf_file: *Elf, undefs: anytype) !void { const gpa = elf_file.base.comp.gpa; for (self.atoms_indexes.items) |atom_index| { const atom_ptr = self.atom(atom_index) orelse continue; - if (!atom_ptr.flags.alive) continue; + if (!atom_ptr.alive) continue; const shdr = atom_ptr.inputShdr(elf_file); if (shdr.sh_type == elf.SHT_NOBITS) continue; if (atom_ptr.scanRelocsRequiresCode(elf_file)) { @@ -451,7 +451,7 @@ pub fn checkDuplicates(self: *ZigObject, dupes: anytype, elf_file: *Elf) error{O if (esym.st_shndx == SHN_ATOM) { const atom_ptr = self.atom(shndx) orelse continue; - if (!atom_ptr.flags.alive) continue; + if (!atom_ptr.alive) continue; } const gop = try dupes.getOrPut(index); @@ -519,7 +519,7 @@ pub fn writeAr(self: ZigObject, writer: anytype) !void { pub fn addAtomsToRelaSections(self: *ZigObject, elf_file: *Elf) !void { for (self.atoms_indexes.items) |atom_index| { const atom_ptr = self.atom(atom_index) orelse continue; - if (!atom_ptr.flags.alive) continue; + if (!atom_ptr.alive) continue; const rela_shndx = atom_ptr.relocsShndx() orelse continue; // TODO this check will become obsolete when we rework our relocs mechanism at the ZigObject level if (self.relocs.items[rela_shndx].items.len == 0) continue; @@ -560,7 +560,7 @@ pub fn globals(self: ZigObject) []const Symbol.Index { pub fn updateSymtabSize(self: *ZigObject, elf_file: *Elf) !void { for (self.locals()) |local_index| { const local = elf_file.symbol(local_index); - if (local.atom(elf_file)) |atom_ptr| if (!atom_ptr.flags.alive) continue; + if (local.atom(elf_file)) |atom_ptr| if (!atom_ptr.alive) continue; const esym = local.elfSym(elf_file); switch (esym.st_type()) { elf.STT_SECTION, elf.STT_NOTYPE => continue, @@ -576,7 +576,7 @@ pub fn updateSymtabSize(self: *ZigObject, elf_file: *Elf) !void { const global = elf_file.symbol(global_index); const file_ptr = global.file(elf_file) orelse continue; if (file_ptr.index() != self.index) continue; - if (global.atom(elf_file)) |atom_ptr| if (!atom_ptr.flags.alive) continue; + if (global.atom(elf_file)) |atom_ptr| if (!atom_ptr.alive) continue; global.flags.output_symtab = true; if (global.isLocal(elf_file)) { try global.addExtra(.{ .symtab = self.output_symtab_ctx.nlocals }, elf_file); @@ -922,7 +922,7 @@ fn updateDeclCode( atom_ptr.output_section_index = shdr_index; sym.name_offset = try self.strtab.insert(gpa, decl.fqn.toSlice(ip)); - atom_ptr.flags.alive = true; + atom_ptr.alive = true; atom_ptr.name_offset = sym.name_offset; esym.st_name = sym.name_offset; esym.st_info |= stt_bits; @@ -1022,7 +1022,7 @@ fn updateTlv( atom_ptr.output_section_index = shndx; sym.name_offset = try self.strtab.insert(gpa, decl.fqn.toSlice(ip)); - atom_ptr.flags.alive = true; + atom_ptr.alive = true; atom_ptr.name_offset = sym.name_offset; esym.st_value = 0; esym.st_name = sym.name_offset; @@ -1246,7 +1246,7 @@ fn updateLazySymbol( local_esym.st_info |= elf.STT_OBJECT; local_esym.st_size = code.len; const atom_ptr = local_sym.atom(elf_file).?; - atom_ptr.flags.alive = true; + atom_ptr.alive = true; atom_ptr.name_offset = name_str_index; atom_ptr.alignment = required_alignment; atom_ptr.size = code.len; @@ -1354,7 +1354,7 @@ fn lowerConst( local_esym.st_info |= elf.STT_OBJECT; local_esym.st_size = code.len; const atom_ptr = local_sym.atom(elf_file).?; - atom_ptr.flags.alive = true; + atom_ptr.alive = true; atom_ptr.name_offset = name_str_index; atom_ptr.alignment = required_alignment; atom_ptr.size = code.len; diff --git a/src/link/Elf/gc.zig b/src/link/Elf/gc.zig index 539caaa3da..79a632d07e 100644 --- a/src/link/Elf/gc.zig +++ b/src/link/Elf/gc.zig @@ -36,7 +36,7 @@ fn collectRoots(roots: *std.ArrayList(*Atom), files: []const File.Index, elf_fil for (file.atoms()) |atom_index| { const atom = file.atom(atom_index) orelse continue; - if (!atom.flags.alive) continue; + if (!atom.alive) continue; const shdr = atom.inputShdr(elf_file); const name = atom.name(elf_file); @@ -54,7 +54,7 @@ fn collectRoots(roots: *std.ArrayList(*Atom), files: []const File.Index, elf_fil break :blk false; }; if (is_gc_root and markAtom(atom)) try roots.append(atom); - if (shdr.sh_flags & elf.SHF_ALLOC == 0) atom.flags.visited = true; + if (shdr.sh_flags & elf.SHF_ALLOC == 0) atom.visited = true; } // Mark every atom referenced by CIE as alive. @@ -77,22 +77,22 @@ fn markSymbol(sym: *Symbol, roots: *std.ArrayList(*Atom), elf_file: *Elf) !void } fn markAtom(atom: *Atom) bool { - const already_visited = atom.flags.visited; - atom.flags.visited = true; - return atom.flags.alive and !already_visited; + const already_visited = atom.visited; + atom.visited = true; + return atom.alive and !already_visited; } fn markLive(atom: *Atom, elf_file: *Elf) void { if (@import("build_options").enable_logging) track_live_level.incr(); - assert(atom.flags.visited); + assert(atom.visited); const file = atom.file(elf_file).?; for (atom.fdes(elf_file)) |fde| { for (fde.relocs(elf_file)[1..]) |rel| { const target_sym = elf_file.symbol(file.symbol(rel.r_sym())); const target_atom = target_sym.atom(elf_file) orelse continue; - target_atom.flags.alive = true; + target_atom.alive = true; gc_track_live_log.debug("{}marking live atom({d})", .{ track_live_level, target_atom.atom_index }); if (markAtom(target_atom)) markLive(target_atom, elf_file); } @@ -105,7 +105,7 @@ fn markLive(atom: *Atom, elf_file: *Elf) void { continue; } const target_atom = target_sym.atom(elf_file) orelse continue; - target_atom.flags.alive = true; + target_atom.alive = true; gc_track_live_log.debug("{}marking live atom({d})", .{ track_live_level, target_atom.atom_index }); if (markAtom(target_atom)) markLive(target_atom, elf_file); } @@ -123,8 +123,8 @@ fn prune(files: []const File.Index, elf_file: *Elf) void { const file = elf_file.file(index).?; for (file.atoms()) |atom_index| { const atom = file.atom(atom_index) orelse continue; - if (atom.flags.alive and !atom.flags.visited) { - atom.flags.alive = false; + if (atom.alive and !atom.visited) { + atom.alive = false; atom.markFdesDead(elf_file); } } @@ -137,7 +137,7 @@ pub fn dumpPrunedAtoms(elf_file: *Elf) !void { const file = elf_file.file(index).?; for (file.atoms()) |atom_index| { const atom = file.atom(atom_index) orelse continue; - if (!atom.flags.alive) + if (!atom.alive) // TODO should we simply print to stderr? try stderr.print("link: removing unused section '{s}' in file '{}'\n", .{ atom.name(elf_file), diff --git a/src/link/Elf/relocatable.zig b/src/link/Elf/relocatable.zig index b3c7146169..f6b32e2ef4 100644 --- a/src/link/Elf/relocatable.zig +++ b/src/link/Elf/relocatable.zig @@ -340,7 +340,7 @@ fn updateSectionSizes(elf_file: *Elf) !void { const shdr = &elf_file.shdrs.items[shndx]; for (atom_list.items) |ref| { const atom_ptr = elf_file.atom(ref) orelse continue; - if (!atom_ptr.flags.alive) continue; + if (!atom_ptr.alive) continue; const offset = atom_ptr.alignment.forward(shdr.sh_size); const padding = offset - shdr.sh_size; atom_ptr.value = @intCast(offset); @@ -353,7 +353,7 @@ fn updateSectionSizes(elf_file: *Elf) !void { const shdr = &elf_file.shdrs.items[sec.shndx]; for (sec.atom_list.items) |ref| { const atom_ptr = elf_file.atom(ref) orelse continue; - if (!atom_ptr.flags.alive) continue; + if (!atom_ptr.alive) continue; const relocs = atom_ptr.relocs(elf_file); shdr.sh_size += shdr.sh_entsize * relocs.len; } @@ -447,7 +447,7 @@ fn writeAtoms(elf_file: *Elf) !void { for (atom_list.items) |ref| { const atom_ptr = elf_file.atom(ref).?; - assert(atom_ptr.flags.alive); + assert(atom_ptr.alive); const offset = math.cast(usize, atom_ptr.value - @as(i64, @intCast(shdr.sh_addr - base_offset))) orelse return error.Overflow; @@ -489,7 +489,7 @@ fn writeSyntheticSections(elf_file: *Elf) !void { for (sec.atom_list.items) |ref| { const atom_ptr = elf_file.atom(ref) orelse continue; - if (!atom_ptr.flags.alive) continue; + if (!atom_ptr.alive) continue; try atom_ptr.writeRelocs(elf_file, &relocs); } assert(relocs.items.len == num_relocs); diff --git a/src/link/Elf/thunks.zig b/src/link/Elf/thunks.zig index 095430dbce..8353eea98a 100644 --- a/src/link/Elf/thunks.zig +++ b/src/link/Elf/thunks.zig @@ -14,13 +14,13 @@ pub fn createThunks(shndx: u32, elf_file: *Elf) !void { while (i < atoms.len) { const start = i; const start_atom = elf_file.atom(atoms[start]).?; - assert(start_atom.flags.alive); + assert(start_atom.alive); start_atom.value = try advance(shdr, start_atom.size, start_atom.alignment); i += 1; while (i < atoms.len) : (i += 1) { const atom = elf_file.atom(atoms[i]).?; - assert(atom.flags.alive); + assert(atom.alive); if (@as(i64, @intCast(atom.alignment.forward(shdr.sh_size))) - start_atom.value >= max_distance) break; atom.value = try advance(shdr, atom.size, atom.alignment); @@ -51,7 +51,6 @@ pub fn createThunks(shndx: u32, elf_file: *Elf) !void { try thunk.symbols.put(gpa, target, {}); } atom.addExtra(.{ .thunk = thunk_index }, elf_file); - atom.flags.thunk = true; } thunk.value = try advance(shdr, thunk.size(elf_file), Atom.Alignment.fromNonzeroByteUnits(2)); From e8d008a8a83f10b8400691bef216147e08ac2daf Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Fri, 26 Jul 2024 14:49:15 +0200 Subject: [PATCH 052/266] elf: atom is always assigned output section index --- src/link/Elf.zig | 2 +- src/link/Elf/Atom.zig | 16 +++++----------- src/link/Elf/Object.zig | 4 ++-- src/link/Elf/ZigObject.zig | 8 ++++---- src/link/Elf/synthetic_sections.zig | 4 ++-- 5 files changed, 14 insertions(+), 20 deletions(-) diff --git a/src/link/Elf.zig b/src/link/Elf.zig index c0b24e7846..a7e74aad64 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -1372,7 +1372,7 @@ pub fn flushModule(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_nod for (zo.atoms_indexes.items) |atom_index| { const atom_ptr = zo.atom(atom_index) orelse continue; if (!atom_ptr.alive) continue; - const out_shndx = atom_ptr.outputShndx() orelse continue; + const out_shndx = atom_ptr.output_section_index; const shdr = &self.shdrs.items[out_shndx]; if (shdr.sh_type == elf.SHT_NOBITS) continue; const code = try zo.codeAlloc(self, atom_index); diff --git a/src/link/Elf/Atom.zig b/src/link/Elf/Atom.zig index 5f90ab86f5..055d8598b2 100644 --- a/src/link/Elf/Atom.zig +++ b/src/link/Elf/Atom.zig @@ -48,8 +48,7 @@ pub fn name(self: Atom, elf_file: *Elf) []const u8 { } pub fn address(self: Atom, elf_file: *Elf) i64 { - const shndx = self.outputShndx() orelse return self.value; - const shdr = elf_file.shdrs.items[shndx]; + const shdr = elf_file.shdrs.items[self.output_section_index]; return @as(i64, @intCast(shdr.sh_addr)) + self.value; } @@ -87,11 +86,6 @@ pub fn relocsShndx(self: Atom) ?u32 { return self.relocs_section_index; } -pub fn outputShndx(self: Atom) ?u32 { - if (self.output_section_index == 0) return null; - return self.output_section_index; -} - pub fn priority(self: Atom, elf_file: *Elf) u64 { const index = self.file(elf_file).?.index(); return (@as(u64, @intCast(index)) << 32) | @as(u64, @intCast(self.input_section_index)); @@ -122,8 +116,8 @@ pub fn freeListEligible(self: Atom, elf_file: *Elf) bool { pub fn allocate(self: *Atom, elf_file: *Elf) !void { const zo = elf_file.zigObjectPtr().?; - const shdr = &elf_file.shdrs.items[self.outputShndx().?]; - const meta = elf_file.last_atom_and_free_list_table.getPtr(self.outputShndx().?).?; + const shdr = &elf_file.shdrs.items[self.output_section_index]; + const meta = elf_file.last_atom_and_free_list_table.getPtr(self.output_section_index).?; const free_list = &meta.free_list; const last_atom_index = &meta.last_atom_index; const new_atom_ideal_capacity = Elf.padToIdeal(self.size); @@ -199,7 +193,7 @@ pub fn allocate(self: *Atom, elf_file: *Elf) !void { true; if (expand_section) { const needed_size: u64 = @intCast(self.value + @as(i64, @intCast(self.size))); - try elf_file.growAllocSection(self.outputShndx().?, needed_size); + try elf_file.growAllocSection(self.output_section_index, needed_size); last_atom_index.* = self.atom_index; const zig_object = elf_file.zigObjectPtr().?; @@ -258,7 +252,7 @@ pub fn free(self: *Atom, elf_file: *Elf) void { const zo = elf_file.zigObjectPtr().?; const comp = elf_file.base.comp; const gpa = comp.gpa; - const shndx = self.outputShndx().?; + const shndx = self.output_section_index; const meta = elf_file.last_atom_and_free_list_table.getPtr(shndx).?; const free_list = &meta.free_list; const last_atom_index = &meta.last_atom_index; diff --git a/src/link/Elf/Object.zig b/src/link/Elf/Object.zig index f0632bb509..121c1d9448 100644 --- a/src/link/Elf/Object.zig +++ b/src/link/Elf/Object.zig @@ -1036,12 +1036,12 @@ pub fn addAtomsToRelaSections(self: *Object, elf_file: *Elf) !void { break :blk self.initOutputSection(elf_file, shdr) catch unreachable; }; const shdr = &elf_file.shdrs.items[shndx]; - shdr.sh_info = atom_ptr.outputShndx().?; + shdr.sh_info = atom_ptr.output_section_index; shdr.sh_link = elf_file.symtab_section_index.?; const comp = elf_file.base.comp; const gpa = comp.gpa; - const gop = try elf_file.output_rela_sections.getOrPut(gpa, atom_ptr.outputShndx().?); + const gop = try elf_file.output_rela_sections.getOrPut(gpa, atom_ptr.output_section_index); if (!gop.found_existing) gop.value_ptr.* = .{ .shndx = shndx }; try gop.value_ptr.atom_list.append(gpa, .{ .index = atom_index, .file = self.index }); } diff --git a/src/link/Elf/ZigObject.zig b/src/link/Elf/ZigObject.zig index f1efca5a79..18e30475f6 100644 --- a/src/link/Elf/ZigObject.zig +++ b/src/link/Elf/ZigObject.zig @@ -311,7 +311,7 @@ pub fn newAtom(self: *ZigObject, elf_file: *Elf) !Symbol.Index { /// TODO actually create fake input shdrs and return that instead. pub fn inputShdr(self: *ZigObject, atom_index: Atom.Index, elf_file: *Elf) elf.Elf64_Shdr { const atom_ptr = self.atom(atom_index) orelse return Elf.null_shdr; - const shndx = atom_ptr.outputShndx() orelse return Elf.null_shdr; + const shndx = atom_ptr.output_section_index; var shdr = elf_file.shdrs.items[shndx]; shdr.sh_addr = 0; shdr.sh_offset = 0; @@ -342,7 +342,7 @@ pub fn resolveSymbols(self: *ZigObject, elf_file: *Elf) void { else => unreachable, }; const output_section_index = if (self.atom(atom_index)) |atom_ptr| - atom_ptr.outputShndx().? + atom_ptr.output_section_index else elf.SHN_UNDEF; global.value = @intCast(esym.st_value); @@ -523,7 +523,7 @@ pub fn addAtomsToRelaSections(self: *ZigObject, elf_file: *Elf) !void { const rela_shndx = atom_ptr.relocsShndx() orelse continue; // TODO this check will become obsolete when we rework our relocs mechanism at the ZigObject level if (self.relocs.items[rela_shndx].items.len == 0) continue; - const out_shndx = atom_ptr.outputShndx().?; + const out_shndx = atom_ptr.output_section_index; const out_shdr = elf_file.shdrs.items[out_shndx]; if (out_shdr.sh_type == elf.SHT_NOBITS) continue; @@ -623,7 +623,7 @@ pub fn asFile(self: *ZigObject) File { pub fn codeAlloc(self: *ZigObject, elf_file: *Elf, atom_index: Atom.Index) ![]u8 { const gpa = elf_file.base.comp.gpa; const atom_ptr = self.atom(atom_index).?; - const shdr = &elf_file.shdrs.items[atom_ptr.outputShndx().?]; + const shdr = &elf_file.shdrs.items[atom_ptr.output_section_index]; if (shdr.sh_flags & elf.SHF_TLS != 0) { const tlv = self.tls_variables.get(atom_index).?; diff --git a/src/link/Elf/synthetic_sections.zig b/src/link/Elf/synthetic_sections.zig index 78bdd08fed..872356be82 100644 --- a/src/link/Elf/synthetic_sections.zig +++ b/src/link/Elf/synthetic_sections.zig @@ -1701,13 +1701,13 @@ pub const ComdatGroupSection = struct { elf.SHT_RELA => { const atom_index = object.atoms_indexes.items[shdr.sh_info]; const atom = object.atom(atom_index).?; - const rela = elf_file.output_rela_sections.get(atom.outputShndx().?).?; + const rela = elf_file.output_rela_sections.get(atom.output_section_index).?; try writer.writeInt(u32, rela.shndx, .little); }, else => { const atom_index = object.atoms_indexes.items[shndx]; const atom = object.atom(atom_index).?; - try writer.writeInt(u32, atom.outputShndx().?, .little); + try writer.writeInt(u32, atom.output_section_index, .little); }, } } From 24126f5382c09f54d8344208e6e38df632d35a44 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Fri, 26 Jul 2024 16:23:38 +0200 Subject: [PATCH 053/266] elf: simplify output section tracking for symbols --- src/link/Elf.zig | 15 --------------- src/link/Elf/Atom.zig | 7 ++----- src/link/Elf/Object.zig | 32 -------------------------------- src/link/Elf/Symbol.zig | 25 +++++++++++++++---------- src/link/Elf/ZigObject.zig | 16 +++------------- src/link/Elf/eh_frame.zig | 2 +- src/link/Elf/relocatable.zig | 2 +- test/link/elf.zig | 20 -------------------- 8 files changed, 22 insertions(+), 97 deletions(-) diff --git a/src/link/Elf.zig b/src/link/Elf.zig index a7e74aad64..1ca7ac9eca 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -4121,21 +4121,6 @@ fn resetShdrIndexes(self: *Elf, backlinks: []const u32) !void { const atom_ptr = zo.atom(atom_index) orelse continue; atom_ptr.output_section_index = backlinks[atom_ptr.output_section_index]; } - - for (zo.locals()) |local_index| { - const local = self.symbol(local_index); - local.output_section_index = backlinks[local.output_section_index]; - } - - for (zo.globals()) |global_index| { - const global = self.symbol(global_index); - const atom_ptr = global.atom(self) orelse continue; - if (!atom_ptr.alive) continue; - // TODO claim unresolved for objects - if (global.file(self).?.index() != zo.index) continue; - const out_shndx = global.outputShndx() orelse continue; - global.output_section_index = backlinks[out_shndx]; - } } for (self.output_rela_sections.keys(), self.output_rela_sections.values()) |shndx, sec| { diff --git a/src/link/Elf/Atom.zig b/src/link/Elf/Atom.zig index 055d8598b2..1763dafa57 100644 --- a/src/link/Elf/Atom.zig +++ b/src/link/Elf/Atom.zig @@ -337,12 +337,9 @@ pub fn writeRelocs(self: Atom, elf_file: *Elf, out_relocs: *std.ArrayList(elf.El var r_addend = rel.r_addend; var r_sym: u32 = 0; switch (target.type(elf_file)) { - elf.STT_SECTION => if (target.mergeSubsection(elf_file)) |msub| { + elf.STT_SECTION => { r_addend += @intCast(target.address(.{}, elf_file)); - r_sym = elf_file.sectionSymbolOutputSymtabIndex(msub.mergeSection(elf_file).output_section_index); - } else { - r_addend += @intCast(target.address(.{}, elf_file)); - r_sym = if (target.outputShndx()) |osec| + r_sym = if (target.outputShndx(elf_file)) |osec| elf_file.sectionSymbolOutputSymtabIndex(osec) else 0; diff --git a/src/link/Elf/Object.zig b/src/link/Elf/Object.zig index 121c1d9448..fb8da7dfcc 100644 --- a/src/link/Elf/Object.zig +++ b/src/link/Elf/Object.zig @@ -978,38 +978,6 @@ pub fn addAtomsToOutputSections(self: *Object, elf_file: *Elf) !void { if (!gop.found_existing) gop.value_ptr.* = .{}; try gop.value_ptr.append(gpa, .{ .index = atom_index, .file = self.index }); } - - for (self.locals()) |local_index| { - const local = elf_file.symbol(local_index); - if (local.mergeSubsection(elf_file)) |msub| { - if (!msub.alive) continue; - local.output_section_index = msub.mergeSection(elf_file).output_section_index; - continue; - } - const atom_ptr = local.atom(elf_file) orelse continue; - if (!atom_ptr.alive) continue; - local.output_section_index = atom_ptr.output_section_index; - } - - for (self.globals()) |global_index| { - const global = elf_file.symbol(global_index); - if (global.file(elf_file).?.index() != self.index) continue; - if (global.mergeSubsection(elf_file)) |msub| { - if (!msub.alive) continue; - global.output_section_index = msub.mergeSection(elf_file).output_section_index; - continue; - } - const atom_ptr = global.atom(elf_file) orelse continue; - if (!atom_ptr.alive) continue; - global.output_section_index = atom_ptr.output_section_index; - } - - for (self.symbols.items[self.symtab.items.len..]) |local_index| { - const local = elf_file.symbol(local_index); - const msub = local.mergeSubsection(elf_file).?; - if (!msub.alive) continue; - local.output_section_index = msub.mergeSection(elf_file).output_section_index; - } } pub fn initRelaSections(self: *Object, elf_file: *Elf) !void { diff --git a/src/link/Elf/Symbol.zig b/src/link/Elf/Symbol.zig index 9ddc534b92..3fcecd5c09 100644 --- a/src/link/Elf/Symbol.zig +++ b/src/link/Elf/Symbol.zig @@ -33,11 +33,15 @@ pub fn isAbs(symbol: Symbol, elf_file: *Elf) bool { const file_ptr = symbol.file(elf_file).?; if (file_ptr == .shared_object) return symbol.elfSym(elf_file).st_shndx == elf.SHN_ABS; return !symbol.flags.import and symbol.atom(elf_file) == null and - symbol.mergeSubsection(elf_file) == null and symbol.outputShndx() == null and + symbol.mergeSubsection(elf_file) == null and symbol.outputShndx(elf_file) == null and file_ptr != .linker_defined; } -pub fn outputShndx(symbol: Symbol) ?u32 { +pub fn outputShndx(symbol: Symbol, elf_file: *Elf) ?u32 { + if (symbol.mergeSubsection(elf_file)) |msub| + return if (msub.alive) msub.mergeSection(elf_file).output_section_index else null; + if (symbol.atom(elf_file)) |atom_ptr| + return if (atom_ptr.alive) atom_ptr.output_section_index else null; if (symbol.output_section_index == 0) return null; return symbol.output_section_index; } @@ -298,7 +302,7 @@ pub fn setOutputSym(symbol: Symbol, elf_file: *Elf, out: *elf.Elf64_Sym) void { if (elf_file.base.isRelocatable() and esym.st_shndx == elf.SHN_COMMON) break :blk elf.SHN_COMMON; if (symbol.mergeSubsection(elf_file)) |msub| break :blk @intCast(msub.mergeSection(elf_file).output_section_index); if (symbol.atom(elf_file) == null and file_ptr != .linker_defined) break :blk elf.SHN_ABS; - break :blk @intCast(symbol.outputShndx() orelse elf.SHN_UNDEF); + break :blk @intCast(symbol.outputShndx(elf_file) orelse elf.SHN_UNDEF); }; const st_value = blk: { if (symbol.flags.has_copy_rel) break :blk symbol.address(.{}, elf_file); @@ -382,22 +386,23 @@ fn format2( _ = options; _ = unused_fmt_string; const symbol = ctx.symbol; + const elf_file = ctx.elf_file; try writer.print("%{d} : {s} : @{x}", .{ symbol.esym_index, - symbol.fmtName(ctx.elf_file), - symbol.address(.{}, ctx.elf_file), + symbol.fmtName(elf_file), + symbol.address(.{}, elf_file), }); - if (symbol.file(ctx.elf_file)) |file_ptr| { - if (symbol.isAbs(ctx.elf_file)) { - if (symbol.elfSym(ctx.elf_file).st_shndx == elf.SHN_UNDEF) { + if (symbol.file(elf_file)) |file_ptr| { + if (symbol.isAbs(elf_file)) { + if (symbol.elfSym(elf_file).st_shndx == elf.SHN_UNDEF) { try writer.writeAll(" : undef"); } else { try writer.writeAll(" : absolute"); } - } else if (symbol.outputShndx()) |shndx| { + } else if (symbol.outputShndx(elf_file)) |shndx| { try writer.print(" : shdr({d})", .{shndx}); } - if (symbol.atom(ctx.elf_file)) |atom_ptr| { + if (symbol.atom(elf_file)) |atom_ptr| { try writer.print(" : atom({d})", .{atom_ptr.atom_index}); } var buf: [2]u8 = .{'_'} ** 2; diff --git a/src/link/Elf/ZigObject.zig b/src/link/Elf/ZigObject.zig index 18e30475f6..9c4baa4aff 100644 --- a/src/link/Elf/ZigObject.zig +++ b/src/link/Elf/ZigObject.zig @@ -341,15 +341,10 @@ pub fn resolveSymbols(self: *ZigObject, elf_file: *Elf) void { SHN_ATOM => shndx, else => unreachable, }; - const output_section_index = if (self.atom(atom_index)) |atom_ptr| - atom_ptr.output_section_index - else - elf.SHN_UNDEF; global.value = @intCast(esym.st_value); global.atom_ref = .{ .index = atom_index, .file = self.index }; global.esym_index = esym_index; global.file_index = self.index; - global.output_section_index = output_section_index; global.version_index = elf_file.default_sym_version; if (esym.st_bind() == elf.STB_WEAK) global.flags.weak = true; } @@ -492,7 +487,7 @@ pub fn updateArSymtab(self: ZigObject, ar_symtab: *Archive.ArSymtab, elf_file: * const global = elf_file.symbol(global_index); const file_ptr = global.file(elf_file).?; assert(file_ptr.index() == self.index); - if (global.outputShndx() == null) continue; + if (global.outputShndx(elf_file) == null) continue; const off = try ar_symtab.strtab.insert(gpa, global.name(elf_file)); ar_symtab.symtab.appendAssumeCapacity(.{ .off = off, .file_index = self.index }); @@ -918,12 +913,10 @@ fn updateDeclCode( const esym = &self.local_esyms.items(.elf_sym)[sym.esym_index]; const atom_ptr = sym.atom(elf_file).?; - sym.output_section_index = shdr_index; - atom_ptr.output_section_index = shdr_index; - - sym.name_offset = try self.strtab.insert(gpa, decl.fqn.toSlice(ip)); atom_ptr.alive = true; atom_ptr.name_offset = sym.name_offset; + atom_ptr.output_section_index = shdr_index; + sym.name_offset = try self.strtab.insert(gpa, decl.fqn.toSlice(ip)); esym.st_name = sym.name_offset; esym.st_info |= stt_bits; esym.st_size = code.len; @@ -1018,7 +1011,6 @@ fn updateTlv( const atom_ptr = sym.atom(elf_file).?; sym.value = 0; - sym.output_section_index = shndx; atom_ptr.output_section_index = shndx; sym.name_offset = try self.strtab.insert(gpa, decl.fqn.toSlice(ip)); @@ -1240,7 +1232,6 @@ fn updateLazySymbol( }; const local_sym = elf_file.symbol(symbol_index); local_sym.name_offset = name_str_index; - local_sym.output_section_index = output_section_index; const local_esym = &self.local_esyms.items(.elf_sym)[local_sym.esym_index]; local_esym.st_name = name_str_index; local_esym.st_info |= elf.STT_OBJECT; @@ -1348,7 +1339,6 @@ fn lowerConst( const local_sym = elf_file.symbol(sym_index); const name_str_index = try self.strtab.insert(gpa, name); local_sym.name_offset = name_str_index; - local_sym.output_section_index = output_section_index; const local_esym = &self.local_esyms.items(.elf_sym)[local_sym.esym_index]; local_esym.st_name = name_str_index; local_esym.st_info |= elf.STT_OBJECT; diff --git a/src/link/Elf/eh_frame.zig b/src/link/Elf/eh_frame.zig index bc0e3b90c8..1ca1b90906 100644 --- a/src/link/Elf/eh_frame.zig +++ b/src/link/Elf/eh_frame.zig @@ -421,7 +421,7 @@ fn emitReloc(elf_file: *Elf, rec: anytype, sym: *const Symbol, rel: elf.Elf64_Re switch (sym.type(elf_file)) { elf.STT_SECTION => { r_addend += @intCast(sym.address(.{}, elf_file)); - r_sym = elf_file.sectionSymbolOutputSymtabIndex(sym.outputShndx().?); + r_sym = elf_file.sectionSymbolOutputSymtabIndex(sym.outputShndx(elf_file).?); }, else => { r_sym = sym.outputSymtabIndex(elf_file) orelse 0; diff --git a/src/link/Elf/relocatable.zig b/src/link/Elf/relocatable.zig index f6b32e2ef4..c5834fc2ae 100644 --- a/src/link/Elf/relocatable.zig +++ b/src/link/Elf/relocatable.zig @@ -382,7 +382,7 @@ fn updateComdatGroupsSizes(elf_file: *Elf) void { const sym = elf_file.symbol(cg.symbol(elf_file)); shdr.sh_info = sym.outputSymtabIndex(elf_file) orelse - elf_file.sectionSymbolOutputSymtabIndex(sym.outputShndx().?); + elf_file.sectionSymbolOutputSymtabIndex(sym.outputShndx(elf_file).?); } } diff --git a/test/link/elf.zig b/test/link/elf.zig index 2dc36c6dc2..71b722a9f9 100644 --- a/test/link/elf.zig +++ b/test/link/elf.zig @@ -416,16 +416,6 @@ fn testComdatElimination(b: *Build, opts: Options) *Step { \\ ); test_step.dependOn(&run.step); - - const check = exe.checkObject(); - check.checkInSymtab(); - // This weird looking double assertion uses the fact that once we find the symbol in - // the symtab, we do not reset the cursor and do subsequent checks from that point onwards. - // If this is the case, and COMDAT elimination works correctly we should only have one instance - // of foo() function. - check.checkContains("_Z3foov"); - check.checkNotPresent("_Z3foov"); - test_step.dependOn(&check.step); } { @@ -441,16 +431,6 @@ fn testComdatElimination(b: *Build, opts: Options) *Step { \\ ); test_step.dependOn(&run.step); - - const check = exe.checkObject(); - check.checkInSymtab(); - // This weird looking double assertion uses the fact that once we find the symbol in - // the symtab, we do not reset the cursor and do subsequent checks from that point onwards. - // If this is the case, and COMDAT elimination works correctly we should only have one instance - // of foo() function. - check.checkContains("_Z3foov"); - check.checkNotPresent("_Z3foov"); - test_step.dependOn(&check.step); } { From 0701646beb8421464346ce6e8993394d8d2c7d5c Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sun, 28 Jul 2024 10:24:26 +0200 Subject: [PATCH 054/266] elf: move merge subsections ownership into merge sections --- src/link/Elf.zig | 41 +++++++++------------------- src/link/Elf/LinkerDefined.zig | 2 +- src/link/Elf/Object.zig | 31 ++++++++++----------- src/link/Elf/SharedObject.zig | 2 +- src/link/Elf/Symbol.zig | 18 ++++++------ src/link/Elf/ZigObject.zig | 8 +++--- src/link/Elf/merge_section.zig | 50 +++++++++++++++++++++++++--------- 7 files changed, 80 insertions(+), 72 deletions(-) diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 1ca7ac9eca..4da9ffeb33 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -208,9 +208,6 @@ thunks: std.ArrayListUnmanaged(Thunk) = .{}, /// List of output merge sections with deduped contents. merge_sections: std.ArrayListUnmanaged(MergeSection) = .{}, -/// List of output merge subsections. -/// Each subsection is akin to Atom but belongs to a MergeSection. -merge_subsections: std.ArrayListUnmanaged(MergeSubsection) = .{}, /// Table of last atom index in a section and matching atom free list if any. last_atom_and_free_list_table: LastAtomAndFreeListTable = .{}, @@ -497,7 +494,6 @@ pub fn deinit(self: *Elf) void { sect.deinit(gpa); } self.merge_sections.deinit(gpa); - self.merge_subsections.deinit(gpa); for (self.last_atom_and_free_list_table.values()) |*value| { value.free_list.deinit(gpa); } @@ -3288,12 +3284,13 @@ fn checkDuplicates(self: *Elf) !void { } pub fn addCommentString(self: *Elf) !void { + const gpa = self.base.comp.gpa; const msec_index = try self.getOrCreateMergeSection(".comment", elf.SHF_MERGE | elf.SHF_STRINGS, elf.SHT_PROGBITS); const msec = self.mergeSection(msec_index); - const res = try msec.insertZ(self.base.comp.gpa, "zig " ++ builtin.zig_version_string); + const res = try msec.insertZ(gpa, "zig " ++ builtin.zig_version_string); if (res.found_existing) return; - const msub_index = try self.addMergeSubsection(); - const msub = self.mergeSubsection(msub_index); + const msub_index = try msec.addMergeSubsection(gpa); + const msub = msec.mergeSubsection(msub_index); msub.merge_section_index = msec_index; msub.string_index = res.key.pos; msub.alignment = .@"1"; @@ -3340,8 +3337,8 @@ pub fn finalizeMergeSections(self: *Elf) !void { pub fn updateMergeSectionSizes(self: *Elf) !void { for (self.merge_sections.items) |*msec| { const shdr = &self.shdrs.items[msec.output_section_index]; - for (msec.subsections.items) |msub_index| { - const msub = self.mergeSubsection(msub_index); + for (msec.finalized_subsections.items) |msub_index| { + const msub = msec.mergeSubsection(msub_index); assert(msub.alive); const offset = msub.alignment.forward(shdr.sh_size); const padding = offset - shdr.sh_size; @@ -3357,14 +3354,14 @@ pub fn writeMergeSections(self: *Elf) !void { var buffer = std.ArrayList(u8).init(gpa); defer buffer.deinit(); - for (self.merge_sections.items) |msec| { + for (self.merge_sections.items) |*msec| { const shdr = self.shdrs.items[msec.output_section_index]; const size = math.cast(usize, shdr.sh_size) orelse return error.Overflow; try buffer.ensureTotalCapacity(size); buffer.appendNTimesAssumeCapacity(0, size); - for (msec.subsections.items) |msub_index| { - const msub = self.mergeSubsection(msub_index); + for (msec.finalized_subsections.items) |msub_index| { + const msub = msec.mergeSubsection(msub_index); assert(msub.alive); const string = msub.getString(self); const off = math.cast(usize, msub.value) orelse return error.Overflow; @@ -3384,7 +3381,7 @@ fn initOutputSections(self: *Elf) !void { pub fn initMergeSections(self: *Elf) !void { for (self.merge_sections.items) |*msec| { - if (msec.subsections.items.len == 0) continue; + if (msec.finalized_subsections.items.len == 0) continue; const name = msec.name(self); const shndx = self.sectionByName(name) orelse try self.addSection(.{ .name = name, @@ -3393,9 +3390,9 @@ pub fn initMergeSections(self: *Elf) !void { }); msec.output_section_index = shndx; - var entsize = self.mergeSubsection(msec.subsections.items[0]).entsize; - for (msec.subsections.items) |index| { - const msub = self.mergeSubsection(index); + var entsize = msec.mergeSubsection(msec.finalized_subsections.items[0]).entsize; + for (msec.finalized_subsections.items) |msub_index| { + const msub = msec.mergeSubsection(msub_index); entsize = @min(entsize, msub.entsize); } const shdr = &self.shdrs.items[shndx]; @@ -5706,18 +5703,6 @@ pub fn zigObjectPtr(self: *Elf) ?*ZigObject { return self.file(index).?.zig_object; } -pub fn addMergeSubsection(self: *Elf) !MergeSubsection.Index { - const index: MergeSubsection.Index = @intCast(self.merge_subsections.items.len); - const msec = try self.merge_subsections.addOne(self.base.comp.gpa); - msec.* = .{}; - return index; -} - -pub fn mergeSubsection(self: *Elf, index: MergeSubsection.Index) *MergeSubsection { - assert(index < self.merge_subsections.items.len); - return &self.merge_subsections.items[index]; -} - pub fn getOrCreateMergeSection(self: *Elf, name: []const u8, flags: u64, @"type": u32) !MergeSection.Index { const gpa = self.base.comp.gpa; const out_name = name: { diff --git a/src/link/Elf/LinkerDefined.zig b/src/link/Elf/LinkerDefined.zig index 0e4a7f8f50..6009f446a9 100644 --- a/src/link/Elf/LinkerDefined.zig +++ b/src/link/Elf/LinkerDefined.zig @@ -47,7 +47,7 @@ pub fn resolveSymbols(self: *LinkerDefined, elf_file: *Elf) void { const global = elf_file.symbol(index); if (self.asFile().symbolRank(this_sym, false) < global.symbolRank(elf_file)) { global.value = 0; - global.atom_ref = .{ .index = 0, .file = 0 }; + global.ref = .{ .index = 0, .file = 0 }; global.file_index = self.index; global.esym_index = sym_idx; global.version_index = elf_file.default_sym_version; diff --git a/src/link/Elf/Object.zig b/src/link/Elf/Object.zig index fb8da7dfcc..40c1896282 100644 --- a/src/link/Elf/Object.zig +++ b/src/link/Elf/Object.zig @@ -388,7 +388,7 @@ fn initSymtab(self: *Object, allocator: Allocator, elf_file: *Elf) !void { sym_ptr.esym_index = @as(u32, @intCast(i)); sym_ptr.file_index = self.index; if (sym.st_shndx != elf.SHN_ABS) { - sym_ptr.atom_ref = .{ .index = self.atoms_indexes.items[sym.st_shndx], .file = self.index }; + sym_ptr.ref = .{ .index = self.atoms_indexes.items[sym.st_shndx], .file = self.index }; } } @@ -575,7 +575,7 @@ pub fn resolveSymbols(self: *Object, elf_file: *Elf) void { if (self.asFile().symbolRank(esym, !self.alive) < global.symbolRank(elf_file)) { switch (esym.st_shndx) { elf.SHN_ABS, elf.SHN_COMMON => {}, - else => global.atom_ref = .{ + else => global.ref = .{ .index = self.atoms_indexes.items[esym.st_shndx], .file = self.index, }, @@ -609,7 +609,7 @@ pub fn claimUnresolved(self: *Object, elf_file: *Elf) void { }; global.value = 0; - global.atom_ref = .{ .index = 0, .file = 0 }; + global.ref = .{ .index = 0, .file = 0 }; global.esym_index = esym_index; global.file_index = self.index; global.version_index = if (is_import) elf.VER_NDX_LOCAL else elf_file.default_sym_version; @@ -630,7 +630,7 @@ pub fn claimUnresolvedObject(self: *Object, elf_file: *Elf) void { } global.value = 0; - global.atom_ref = .{ .index = 0, .file = 0 }; + global.ref = .{ .index = 0, .file = 0 }; global.esym_index = esym_index; global.file_index = self.index; } @@ -785,8 +785,8 @@ pub fn resolveMergeSubsections(self: *Object, elf_file: *Elf) !void { const string = imsec.bytes.items[str.pos..][0..str.len]; const res = try msec.insert(gpa, string); if (!res.found_existing) { - const msub_index = try elf_file.addMergeSubsection(); - const msub = elf_file.mergeSubsection(msub_index); + const msub_index = try msec.addMergeSubsection(gpa); + const msub = msec.mergeSubsection(msub_index); msub.merge_section_index = imsec.merge_section_index; msub.string_index = res.key.pos; msub.alignment = atom_ptr.alignment; @@ -810,7 +810,7 @@ pub fn resolveMergeSubsections(self: *Object, elf_file: *Elf) !void { const imsec_index = self.input_merge_sections_indexes.items[esym.st_shndx]; const imsec = self.inputMergeSection(imsec_index) orelse continue; if (imsec.offsets.items.len == 0) continue; - const msub_index, const offset = imsec.findSubsection(@intCast(esym.st_value)) orelse { + const res = imsec.findSubsection(@intCast(esym.st_value)) orelse { var err = try elf_file.base.addErrorWithNotes(2); try err.addMsg("invalid symbol value: {x}", .{esym.st_value}); try err.addNote("for symbol {s}", .{sym.name(elf_file)}); @@ -818,9 +818,9 @@ pub fn resolveMergeSubsections(self: *Object, elf_file: *Elf) !void { return error.MalformedObject; }; - try sym.addExtra(.{ .subsection = msub_index }, elf_file); + sym.ref = .{ .index = res.msub_index, .file = imsec.merge_section_index }; sym.flags.merge_subsection = true; - sym.value = offset; + sym.value = res.offset; } for (self.atoms_indexes.items) |atom_index| { @@ -835,28 +835,27 @@ pub fn resolveMergeSubsections(self: *Object, elf_file: *Elf) !void { const imsec_index = self.input_merge_sections_indexes.items[esym.st_shndx]; const imsec = self.inputMergeSection(imsec_index) orelse continue; if (imsec.offsets.items.len == 0) continue; - const msub_index, const offset = imsec.findSubsection(@intCast(@as(i64, @intCast(esym.st_value)) + rel.r_addend)) orelse { + const msec = elf_file.mergeSection(imsec.merge_section_index); + const res = imsec.findSubsection(@intCast(@as(i64, @intCast(esym.st_value)) + rel.r_addend)) orelse { var err = try elf_file.base.addErrorWithNotes(1); try err.addMsg("invalid relocation at offset 0x{x}", .{rel.r_offset}); try err.addNote("in {}:{s}", .{ self.fmtPath(), atom_ptr.name(elf_file) }); return error.MalformedObject; }; - const msub = elf_file.mergeSubsection(msub_index); - const msec = msub.mergeSection(elf_file); const out_sym_idx: u64 = @intCast(self.symbols.items.len); try self.symbols.ensureUnusedCapacity(gpa, 1); - const name = try std.fmt.allocPrint(gpa, "{s}$subsection{d}", .{ msec.name(elf_file), msub_index }); + const name = try std.fmt.allocPrint(gpa, "{s}$subsection{d}", .{ msec.name(elf_file), res.msub_index }); defer gpa.free(name); const sym_index = try elf_file.addSymbol(); const sym = elf_file.symbol(sym_index); sym.* = .{ - .value = @bitCast(@as(i64, @intCast(offset)) - rel.r_addend), + .value = @bitCast(@as(i64, @intCast(res.offset)) - rel.r_addend), .name_offset = try self.addString(gpa, name), .esym_index = rel.r_sym(), .file_index = self.index, }; - try sym.addExtra(.{ .subsection = msub_index }, elf_file); + sym.ref = .{ .index = res.msub_index, .file = imsec.merge_section_index }; sym.flags.merge_subsection = true; self.symbols.addOneAssumeCapacity().* = sym_index; rel.r_info = (out_sym_idx << 32) | rel.r_type(); @@ -920,7 +919,7 @@ pub fn convertCommonSymbols(self: *Object, elf_file: *Elf) !void { try self.atoms_indexes.append(gpa, atom_index); global.value = 0; - global.atom_ref = .{ .index = atom_index, .file = self.index }; + global.ref = .{ .index = atom_index, .file = self.index }; global.flags.weak = false; } } diff --git a/src/link/Elf/SharedObject.zig b/src/link/Elf/SharedObject.zig index ef65f3abaa..461a56ae39 100644 --- a/src/link/Elf/SharedObject.zig +++ b/src/link/Elf/SharedObject.zig @@ -232,7 +232,7 @@ pub fn resolveSymbols(self: *SharedObject, elf_file: *Elf) void { const global = elf_file.symbol(index); if (self.asFile().symbolRank(this_sym, false) < global.symbolRank(elf_file)) { global.value = @intCast(this_sym.st_value); - global.atom_ref = .{ .index = 0, .file = 0 }; + global.ref = .{ .index = 0, .file = 0 }; global.esym_index = esym_index; global.version_index = self.versyms.items[esym_index]; global.file_index = self.index; diff --git a/src/link/Elf/Symbol.zig b/src/link/Elf/Symbol.zig index 3fcecd5c09..68d23cb74d 100644 --- a/src/link/Elf/Symbol.zig +++ b/src/link/Elf/Symbol.zig @@ -9,9 +9,9 @@ name_offset: u32 = 0, /// Index of file where this symbol is defined. file_index: File.Index = 0, -/// Reference to Atom containing this symbol if any. -/// Use `atom` to get the pointer to the atom. -atom_ref: Elf.Ref = .{ .index = 0, .file = 0 }, +/// Reference to Atom or merge subsection containing this symbol if any. +/// Use `atom` or `mergeSubsection` to get the pointer to the atom. +ref: Elf.Ref = .{ .index = 0, .file = 0 }, /// Assigned output section index for this symbol. output_section_index: u32 = 0, @@ -71,14 +71,15 @@ pub fn name(symbol: Symbol, elf_file: *Elf) [:0]const u8 { } pub fn atom(symbol: Symbol, elf_file: *Elf) ?*Atom { - const file_ptr = elf_file.file(symbol.atom_ref.file) orelse return null; - return file_ptr.atom(symbol.atom_ref.index); + if (symbol.flags.merge_subsection) return null; + const file_ptr = elf_file.file(symbol.ref.file) orelse return null; + return file_ptr.atom(symbol.ref.index); } pub fn mergeSubsection(symbol: Symbol, elf_file: *Elf) ?*MergeSubsection { if (!symbol.flags.merge_subsection) return null; - const extras = symbol.extra(elf_file).?; - return elf_file.mergeSubsection(extras.subsection); + const msec = elf_file.mergeSection(symbol.ref.file); + return msec.mergeSubsection(symbol.ref.index); } pub fn file(symbol: Symbol, elf_file: *Elf) ?File { @@ -262,7 +263,6 @@ const AddExtraOpts = struct { gottp: ?u32 = null, tlsdesc: ?u32 = null, zig_got: ?u32 = null, - subsection: ?u32 = null, }; pub fn addExtra(symbol: *Symbol, opts: AddExtraOpts, elf_file: *Elf) !void { @@ -488,7 +488,7 @@ pub const Extra = struct { gottp: u32 = 0, tlsdesc: u32 = 0, zig_got: u32 = 0, - subsection: u32 = 0, + merge_section: u32 = 0, }; pub const Index = u32; diff --git a/src/link/Elf/ZigObject.zig b/src/link/Elf/ZigObject.zig index 9c4baa4aff..1771e3cae1 100644 --- a/src/link/Elf/ZigObject.zig +++ b/src/link/Elf/ZigObject.zig @@ -291,7 +291,7 @@ pub fn newAtom(self: *ZigObject, elf_file: *Elf) !Symbol.Index { const symbol_ptr = elf_file.symbol(symbol_index); symbol_ptr.file_index = self.index; - symbol_ptr.atom_ref = .{ .index = atom_index, .file = self.index }; + symbol_ptr.ref = .{ .index = atom_index, .file = self.index }; self.local_esyms.items(.shndx)[esym_index] = atom_index; self.local_esyms.items(.elf_sym)[esym_index].st_shndx = SHN_ATOM; @@ -342,7 +342,7 @@ pub fn resolveSymbols(self: *ZigObject, elf_file: *Elf) void { else => unreachable, }; global.value = @intCast(esym.st_value); - global.atom_ref = .{ .index = atom_index, .file = self.index }; + global.ref = .{ .index = atom_index, .file = self.index }; global.esym_index = esym_index; global.file_index = self.index; global.version_index = elf_file.default_sym_version; @@ -371,7 +371,7 @@ pub fn claimUnresolved(self: ZigObject, elf_file: *Elf) void { }; global.value = 0; - global.atom_ref = .{ .index = 0, .file = 0 }; + global.ref = .{ .index = 0, .file = 0 }; global.esym_index = esym_index; global.file_index = self.index; global.version_index = if (is_import) elf.VER_NDX_LOCAL else elf_file.default_sym_version; @@ -392,7 +392,7 @@ pub fn claimUnresolvedObject(self: ZigObject, elf_file: *Elf) void { } global.value = 0; - global.atom_ref = .{ .index = 0, .file = 0 }; + global.ref = .{ .index = 0, .file = 0 }; global.esym_index = esym_index; global.file_index = self.index; } diff --git a/src/link/Elf/merge_section.zig b/src/link/Elf/merge_section.zig index ab716794fc..3dabe8eda3 100644 --- a/src/link/Elf/merge_section.zig +++ b/src/link/Elf/merge_section.zig @@ -10,12 +10,14 @@ pub const MergeSection = struct { IndexContext, std.hash_map.default_max_load_percentage, ) = .{}, - subsections: std.ArrayListUnmanaged(MergeSubsection.Index) = .{}, + subsections: std.ArrayListUnmanaged(MergeSubsection) = .{}, + finalized_subsections: std.ArrayListUnmanaged(MergeSubsection.Index) = .{}, pub fn deinit(msec: *MergeSection, allocator: Allocator) void { msec.bytes.deinit(allocator); msec.table.deinit(allocator); msec.subsections.deinit(allocator); + msec.finalized_subsections.deinit(allocator); } pub fn name(msec: MergeSection, elf_file: *Elf) [:0]const u8 { @@ -60,23 +62,25 @@ pub const MergeSection = struct { /// Sorts all owned subsections. pub fn finalize(msec: *MergeSection, elf_file: *Elf) !void { const gpa = elf_file.base.comp.gpa; - try msec.subsections.ensureTotalCapacityPrecise(gpa, msec.table.count()); + try msec.finalized_subsections.ensureTotalCapacityPrecise(gpa, msec.subsections.items.len); var it = msec.table.iterator(); while (it.next()) |entry| { - const msub = elf_file.mergeSubsection(entry.value_ptr.*); + const msub = msec.mergeSubsection(entry.value_ptr.*); if (!msub.alive) continue; - msec.subsections.appendAssumeCapacity(entry.value_ptr.*); + msec.finalized_subsections.appendAssumeCapacity(entry.value_ptr.*); } msec.table.clearAndFree(gpa); const sortFn = struct { - pub fn sortFn(ctx: *Elf, lhs: MergeSubsection.Index, rhs: MergeSubsection.Index) bool { + pub fn sortFn(ctx: *MergeSection, lhs: MergeSubsection.Index, rhs: MergeSubsection.Index) bool { const lhs_msub = ctx.mergeSubsection(lhs); const rhs_msub = ctx.mergeSubsection(rhs); if (lhs_msub.alignment.compareStrict(.eq, rhs_msub.alignment)) { if (lhs_msub.size == rhs_msub.size) { - return mem.order(u8, lhs_msub.getString(ctx), rhs_msub.getString(ctx)) == .lt; + const lhs_string = ctx.bytes.items[lhs_msub.string_index..][0..lhs_msub.size]; + const rhs_string = ctx.bytes.items[rhs_msub.string_index..][0..rhs_msub.size]; + return mem.order(u8, lhs_string, rhs_string) == .lt; } return lhs_msub.size < rhs_msub.size; } @@ -84,7 +88,19 @@ pub const MergeSection = struct { } }.sortFn; - std.mem.sort(MergeSubsection.Index, msec.subsections.items, elf_file, sortFn); + std.mem.sort(MergeSubsection.Index, msec.finalized_subsections.items, msec, sortFn); + } + + pub fn addMergeSubsection(msec: *MergeSection, allocator: Allocator) !MergeSubsection.Index { + const index: MergeSubsection.Index = @intCast(msec.subsections.items.len); + const msub = try msec.subsections.addOne(allocator); + msub.* = .{}; + return index; + } + + pub fn mergeSubsection(msec: *MergeSection, index: MergeSubsection.Index) *MergeSubsection { + assert(index < msec.subsections.items.len); + return &msec.subsections.items[index]; } pub const IndexContext = struct { @@ -154,8 +170,8 @@ pub const MergeSection = struct { msec.type, msec.flags, }); - for (msec.subsections.items) |index| { - try writer.print(" {}\n", .{elf_file.mergeSubsection(index).fmt(elf_file)}); + for (msec.subsections.items) |msub| { + try writer.print(" {}\n", .{msub.fmt(elf_file)}); } } @@ -250,18 +266,26 @@ pub const InputMergeSection = struct { // TODO: imsec.strings.clearAndFree(allocator); } - pub fn findSubsection(imsec: InputMergeSection, offset: u32) ?struct { MergeSubsection.Index, u32 } { + const FindSubsectionResult = struct { + msub_index: MergeSubsection.Index, + offset: u32, + }; + + pub fn findSubsection(imsec: InputMergeSection, offset: u32) ?FindSubsectionResult { // TODO: binary search for (imsec.offsets.items, 0..) |off, index| { if (offset < off) return .{ - imsec.subsections.items[index - 1], - offset - imsec.offsets.items[index - 1], + .msub_index = imsec.subsections.items[index - 1], + .offset = offset - imsec.offsets.items[index - 1], }; } const last = imsec.offsets.items.len - 1; const last_off = imsec.offsets.items[last]; const last_len = imsec.strings.items[last].len; - if (offset < last_off + last_len) return .{ imsec.subsections.items[last], offset - last_off }; + if (offset < last_off + last_len) return .{ + .msub_index = imsec.subsections.items[last], + .offset = offset - last_off, + }; return null; } From 801c37209889883ad2d7b9cfb232fd8d9e0ed66c Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sun, 28 Jul 2024 11:36:51 +0200 Subject: [PATCH 055/266] elf: init output merge sections in a separate step --- src/link/Elf.zig | 10 ++++++++-- src/link/Elf/Object.zig | 17 ++++++++++++++--- src/link/Elf/merge_section.zig | 7 +++---- 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 4da9ffeb33..030c0499cc 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -3308,7 +3308,7 @@ pub fn resolveMergeSections(self: *Elf) !void { for (self.objects.items) |index| { const file_ptr = self.file(index).?; if (!file_ptr.isAlive()) continue; - file_ptr.object.initMergeSections(self) catch |err| switch (err) { + file_ptr.object.initInputMergeSections(self) catch |err| switch (err) { error.MalformedObject => has_errors = true, else => |e| return e, }; @@ -3316,6 +3316,12 @@ pub fn resolveMergeSections(self: *Elf) !void { if (has_errors) return error.FlushFailure; + for (self.objects.items) |index| { + const file_ptr = self.file(index).?; + if (!file_ptr.isAlive()) continue; + try file_ptr.object.initOutputMergeSections(self); + } + for (self.objects.items) |index| { const file_ptr = self.file(index).?; if (!file_ptr.isAlive()) continue; @@ -3330,7 +3336,7 @@ pub fn resolveMergeSections(self: *Elf) !void { pub fn finalizeMergeSections(self: *Elf) !void { for (self.merge_sections.items) |*msec| { - try msec.finalize(self); + try msec.finalize(self.base.comp.gpa); } } diff --git a/src/link/Elf/Object.zig b/src/link/Elf/Object.zig index 40c1896282..4944ef904e 100644 --- a/src/link/Elf/Object.zig +++ b/src/link/Elf/Object.zig @@ -691,7 +691,7 @@ pub fn checkDuplicates(self: *Object, dupes: anytype, elf_file: *Elf) error{OutO } } -pub fn initMergeSections(self: *Object, elf_file: *Elf) !void { +pub fn initInputMergeSections(self: *Object, elf_file: *Elf) !void { const gpa = elf_file.base.comp.gpa; try self.input_merge_sections.ensureUnusedCapacity(gpa, self.shdrs.items.len); @@ -709,8 +709,6 @@ pub fn initMergeSections(self: *Object, elf_file: *Elf) !void { const imsec_idx = try self.addInputMergeSection(gpa); const imsec = self.inputMergeSection(imsec_idx).?; self.input_merge_sections_indexes.items[shndx] = imsec_idx; - - imsec.merge_section_index = try elf_file.getOrCreateMergeSection(atom_ptr.name(elf_file), shdr.sh_flags, shdr.sh_type); imsec.atom_index = atom_index; const data = try self.codeDecompressAlloc(elf_file, atom_index); @@ -769,6 +767,19 @@ pub fn initMergeSections(self: *Object, elf_file: *Elf) !void { } } +pub fn initOutputMergeSections(self: *Object, elf_file: *Elf) !void { + for (self.input_merge_sections_indexes.items) |index| { + const imsec = self.inputMergeSection(index) orelse continue; + const atom_ptr = self.atom(imsec.atom_index).?; + const shdr = atom_ptr.inputShdr(elf_file); + imsec.merge_section_index = try elf_file.getOrCreateMergeSection( + atom_ptr.name(elf_file), + shdr.sh_flags, + shdr.sh_type, + ); + } +} + pub fn resolveMergeSubsections(self: *Object, elf_file: *Elf) !void { const gpa = elf_file.base.comp.gpa; diff --git a/src/link/Elf/merge_section.zig b/src/link/Elf/merge_section.zig index 3dabe8eda3..b4b670062c 100644 --- a/src/link/Elf/merge_section.zig +++ b/src/link/Elf/merge_section.zig @@ -60,9 +60,8 @@ pub const MergeSection = struct { /// Finalizes the merge section and clears hash table. /// Sorts all owned subsections. - pub fn finalize(msec: *MergeSection, elf_file: *Elf) !void { - const gpa = elf_file.base.comp.gpa; - try msec.finalized_subsections.ensureTotalCapacityPrecise(gpa, msec.subsections.items.len); + pub fn finalize(msec: *MergeSection, allocator: Allocator) !void { + try msec.finalized_subsections.ensureTotalCapacityPrecise(allocator, msec.subsections.items.len); var it = msec.table.iterator(); while (it.next()) |entry| { @@ -70,7 +69,7 @@ pub const MergeSection = struct { if (!msub.alive) continue; msec.finalized_subsections.appendAssumeCapacity(entry.value_ptr.*); } - msec.table.clearAndFree(gpa); + msec.table.clearAndFree(allocator); const sortFn = struct { pub fn sortFn(ctx: *MergeSection, lhs: MergeSubsection.Index, rhs: MergeSubsection.Index) bool { From ef7bbcd80fec680b527795dd85bc294b3bf6cbbd Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sun, 28 Jul 2024 14:46:50 +0200 Subject: [PATCH 056/266] elf: do not store merge section output section name in strings buffer --- src/link/Elf.zig | 84 +++++++++++++++++----------------- src/link/Elf/Atom.zig | 2 +- src/link/Elf/Object.zig | 2 +- src/link/Elf/ZigObject.zig | 4 +- src/link/Elf/merge_section.zig | 2 +- src/link/Elf/relocatable.zig | 9 ++-- 6 files changed, 53 insertions(+), 50 deletions(-) diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 030c0499cc..de4085eec3 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -371,7 +371,7 @@ pub fn createEmpty( try self.shstrtab.append(gpa, 0); try self.strtab.append(gpa, 0); // There must always be a null shdr in index 0 - _ = try self.addSection(.{ .name = "" }); + _ = try self.addSection(.{}); // Append null symbol in output symtab try self.symtab.append(gpa, null_sym); @@ -716,7 +716,7 @@ pub fn initMetadata(self: *Elf, options: InitMetadataOptions) !void { if (self.zig_text_section_index == null) { self.zig_text_section_index = try self.addSection(.{ - .name = ".text.zig", + .name = try self.insertShString(".text.zig"), .type = elf.SHT_PROGBITS, .flags = elf.SHF_ALLOC | elf.SHF_EXECINSTR, .addralign = 1, @@ -725,7 +725,7 @@ pub fn initMetadata(self: *Elf, options: InitMetadataOptions) !void { const shdr = &self.shdrs.items[self.zig_text_section_index.?]; fillSection(self, shdr, options.program_code_size_hint, self.phdr_zig_load_re_index); if (self.base.isRelocatable()) { - const rela_shndx = try self.addRelaShdr(".rela.text.zig", self.zig_text_section_index.?); + const rela_shndx = try self.addRelaShdr(try self.insertShString(".rela.text.zig"), self.zig_text_section_index.?); try self.output_rela_sections.putNoClobber(gpa, self.zig_text_section_index.?, .{ .shndx = rela_shndx, }); @@ -742,7 +742,7 @@ pub fn initMetadata(self: *Elf, options: InitMetadataOptions) !void { if (self.zig_got_section_index == null and !self.base.isRelocatable()) { self.zig_got_section_index = try self.addSection(.{ - .name = ".got.zig", + .name = try self.insertShString(".got.zig"), .type = elf.SHT_PROGBITS, .addralign = ptr_size, .flags = elf.SHF_ALLOC | elf.SHF_WRITE, @@ -763,7 +763,7 @@ pub fn initMetadata(self: *Elf, options: InitMetadataOptions) !void { if (self.zig_data_rel_ro_section_index == null) { self.zig_data_rel_ro_section_index = try self.addSection(.{ - .name = ".data.rel.ro.zig", + .name = try self.insertShString(".data.rel.ro.zig"), .type = elf.SHT_PROGBITS, .addralign = 1, .flags = elf.SHF_ALLOC | elf.SHF_WRITE, @@ -773,7 +773,7 @@ pub fn initMetadata(self: *Elf, options: InitMetadataOptions) !void { fillSection(self, shdr, 1024, self.phdr_zig_load_ro_index); if (self.base.isRelocatable()) { const rela_shndx = try self.addRelaShdr( - ".rela.data.rel.ro.zig", + try self.insertShString(".rela.data.rel.ro.zig"), self.zig_data_rel_ro_section_index.?, ); try self.output_rela_sections.putNoClobber(gpa, self.zig_data_rel_ro_section_index.?, .{ @@ -792,7 +792,7 @@ pub fn initMetadata(self: *Elf, options: InitMetadataOptions) !void { if (self.zig_data_section_index == null) { self.zig_data_section_index = try self.addSection(.{ - .name = ".data.zig", + .name = try self.insertShString(".data.zig"), .type = elf.SHT_PROGBITS, .addralign = ptr_size, .flags = elf.SHF_ALLOC | elf.SHF_WRITE, @@ -802,7 +802,7 @@ pub fn initMetadata(self: *Elf, options: InitMetadataOptions) !void { fillSection(self, shdr, 1024, self.phdr_zig_load_rw_index); if (self.base.isRelocatable()) { const rela_shndx = try self.addRelaShdr( - ".rela.data.zig", + try self.insertShString(".rela.data.zig"), self.zig_data_section_index.?, ); try self.output_rela_sections.putNoClobber(gpa, self.zig_data_section_index.?, .{ @@ -821,7 +821,7 @@ pub fn initMetadata(self: *Elf, options: InitMetadataOptions) !void { if (self.zig_bss_section_index == null) { self.zig_bss_section_index = try self.addSection(.{ - .name = ".bss.zig", + .name = try self.insertShString(".bss.zig"), .type = elf.SHT_NOBITS, .addralign = ptr_size, .flags = elf.SHF_ALLOC | elf.SHF_WRITE, @@ -845,7 +845,7 @@ pub fn initMetadata(self: *Elf, options: InitMetadataOptions) !void { assert(dw.strtab.buffer.items.len == 0); try dw.strtab.buffer.append(gpa, 0); self.debug_str_section_index = try self.addSection(.{ - .name = ".debug_str", + .name = try self.insertShString(".debug_str"), .flags = elf.SHF_MERGE | elf.SHF_STRINGS, .entsize = 1, .type = elf.SHT_PROGBITS, @@ -863,7 +863,7 @@ pub fn initMetadata(self: *Elf, options: InitMetadataOptions) !void { if (self.debug_info_section_index == null) { self.debug_info_section_index = try self.addSection(.{ - .name = ".debug_info", + .name = try self.insertShString(".debug_info"), .type = elf.SHT_PROGBITS, .addralign = 1, .offset = std.math.maxInt(u64), @@ -879,7 +879,7 @@ pub fn initMetadata(self: *Elf, options: InitMetadataOptions) !void { if (self.debug_abbrev_section_index == null) { self.debug_abbrev_section_index = try self.addSection(.{ - .name = ".debug_abbrev", + .name = try self.insertShString(".debug_abbrev"), .type = elf.SHT_PROGBITS, .addralign = 1, .offset = std.math.maxInt(u64), @@ -895,7 +895,7 @@ pub fn initMetadata(self: *Elf, options: InitMetadataOptions) !void { if (self.debug_aranges_section_index == null) { self.debug_aranges_section_index = try self.addSection(.{ - .name = ".debug_aranges", + .name = try self.insertShString(".debug_aranges"), .type = elf.SHT_PROGBITS, .addralign = 16, .offset = std.math.maxInt(u64), @@ -911,7 +911,7 @@ pub fn initMetadata(self: *Elf, options: InitMetadataOptions) !void { if (self.debug_line_section_index == null) { self.debug_line_section_index = try self.addSection(.{ - .name = ".debug_line", + .name = try self.insertShString(".debug_line"), .type = elf.SHT_PROGBITS, .addralign = 1, .offset = std.math.maxInt(u64), @@ -3390,7 +3390,7 @@ pub fn initMergeSections(self: *Elf) !void { if (msec.finalized_subsections.items.len == 0) continue; const name = msec.name(self); const shndx = self.sectionByName(name) orelse try self.addSection(.{ - .name = name, + .name = msec.name_offset, .type = msec.type, .flags = msec.flags, }); @@ -3416,7 +3416,7 @@ fn initSyntheticSections(self: *Elf) !void { } else false; if (needs_eh_frame) { self.eh_frame_section_index = try self.addSection(.{ - .name = ".eh_frame", + .name = try self.insertShString(".eh_frame"), .type = elf.SHT_PROGBITS, .flags = elf.SHF_ALLOC, .addralign = ptr_size, @@ -3425,7 +3425,7 @@ fn initSyntheticSections(self: *Elf) !void { if (comp.link_eh_frame_hdr) { self.eh_frame_hdr_section_index = try self.addSection(.{ - .name = ".eh_frame_hdr", + .name = try self.insertShString(".eh_frame_hdr"), .type = elf.SHT_PROGBITS, .flags = elf.SHF_ALLOC, .addralign = 4, @@ -3436,7 +3436,7 @@ fn initSyntheticSections(self: *Elf) !void { if (self.got.entries.items.len > 0) { self.got_section_index = try self.addSection(.{ - .name = ".got", + .name = try self.insertShString(".got"), .type = elf.SHT_PROGBITS, .flags = elf.SHF_ALLOC | elf.SHF_WRITE, .addralign = ptr_size, @@ -3445,7 +3445,7 @@ fn initSyntheticSections(self: *Elf) !void { } self.got_plt_section_index = try self.addSection(.{ - .name = ".got.plt", + .name = try self.insertShString(".got.plt"), .type = elf.SHT_PROGBITS, .flags = elf.SHF_ALLOC | elf.SHF_WRITE, .addralign = @alignOf(u64), @@ -3465,7 +3465,7 @@ fn initSyntheticSections(self: *Elf) !void { }; if (needs_rela_dyn) { self.rela_dyn_section_index = try self.addSection(.{ - .name = ".rela.dyn", + .name = try self.insertShString(".rela.dyn"), .type = elf.SHT_RELA, .flags = elf.SHF_ALLOC, .addralign = @alignOf(elf.Elf64_Rela), @@ -3476,14 +3476,14 @@ fn initSyntheticSections(self: *Elf) !void { if (self.plt.symbols.items.len > 0) { self.plt_section_index = try self.addSection(.{ - .name = ".plt", + .name = try self.insertShString(".plt"), .type = elf.SHT_PROGBITS, .flags = elf.SHF_ALLOC | elf.SHF_EXECINSTR, .addralign = 16, .offset = std.math.maxInt(u64), }); self.rela_plt_section_index = try self.addSection(.{ - .name = ".rela.plt", + .name = try self.insertShString(".rela.plt"), .type = elf.SHT_RELA, .flags = elf.SHF_ALLOC, .addralign = @alignOf(elf.Elf64_Rela), @@ -3494,7 +3494,7 @@ fn initSyntheticSections(self: *Elf) !void { if (self.plt_got.symbols.items.len > 0) { self.plt_got_section_index = try self.addSection(.{ - .name = ".plt.got", + .name = try self.insertShString(".plt.got"), .type = elf.SHT_PROGBITS, .flags = elf.SHF_ALLOC | elf.SHF_EXECINSTR, .addralign = 16, @@ -3504,7 +3504,7 @@ fn initSyntheticSections(self: *Elf) !void { if (self.copy_rel.symbols.items.len > 0) { self.copy_rel_section_index = try self.addSection(.{ - .name = ".copyrel", + .name = try self.insertShString(".copyrel"), .type = elf.SHT_NOBITS, .flags = elf.SHF_ALLOC | elf.SHF_WRITE, .offset = std.math.maxInt(u64), @@ -3522,7 +3522,7 @@ fn initSyntheticSections(self: *Elf) !void { }; if (needs_interp) { self.interp_section_index = try self.addSection(.{ - .name = ".interp", + .name = try self.insertShString(".interp"), .type = elf.SHT_PROGBITS, .flags = elf.SHF_ALLOC, .addralign = 1, @@ -3532,7 +3532,7 @@ fn initSyntheticSections(self: *Elf) !void { if (self.isEffectivelyDynLib() or self.shared_objects.items.len > 0 or comp.config.pie) { self.dynstrtab_section_index = try self.addSection(.{ - .name = ".dynstr", + .name = try self.insertShString(".dynstr"), .flags = elf.SHF_ALLOC, .type = elf.SHT_STRTAB, .entsize = 1, @@ -3540,7 +3540,7 @@ fn initSyntheticSections(self: *Elf) !void { .offset = std.math.maxInt(u64), }); self.dynamic_section_index = try self.addSection(.{ - .name = ".dynamic", + .name = try self.insertShString(".dynamic"), .flags = elf.SHF_ALLOC | elf.SHF_WRITE, .type = elf.SHT_DYNAMIC, .entsize = @sizeOf(elf.Elf64_Dyn), @@ -3548,7 +3548,7 @@ fn initSyntheticSections(self: *Elf) !void { .offset = std.math.maxInt(u64), }); self.dynsymtab_section_index = try self.addSection(.{ - .name = ".dynsym", + .name = try self.insertShString(".dynsym"), .flags = elf.SHF_ALLOC, .type = elf.SHT_DYNSYM, .addralign = @alignOf(elf.Elf64_Sym), @@ -3557,7 +3557,7 @@ fn initSyntheticSections(self: *Elf) !void { .offset = std.math.maxInt(u64), }); self.hash_section_index = try self.addSection(.{ - .name = ".hash", + .name = try self.insertShString(".hash"), .flags = elf.SHF_ALLOC, .type = elf.SHT_HASH, .addralign = 4, @@ -3565,7 +3565,7 @@ fn initSyntheticSections(self: *Elf) !void { .offset = std.math.maxInt(u64), }); self.gnu_hash_section_index = try self.addSection(.{ - .name = ".gnu.hash", + .name = try self.insertShString(".gnu.hash"), .flags = elf.SHF_ALLOC, .type = elf.SHT_GNU_HASH, .addralign = 8, @@ -3578,7 +3578,7 @@ fn initSyntheticSections(self: *Elf) !void { } else false; if (needs_versions) { self.versym_section_index = try self.addSection(.{ - .name = ".gnu.version", + .name = try self.insertShString(".gnu.version"), .flags = elf.SHF_ALLOC, .type = elf.SHT_GNU_VERSYM, .addralign = @alignOf(elf.Elf64_Versym), @@ -3586,7 +3586,7 @@ fn initSyntheticSections(self: *Elf) !void { .offset = std.math.maxInt(u64), }); self.verneed_section_index = try self.addSection(.{ - .name = ".gnu.version_r", + .name = try self.insertShString(".gnu.version_r"), .flags = elf.SHF_ALLOC, .type = elf.SHT_GNU_VERNEED, .addralign = @alignOf(elf.Elf64_Verneed), @@ -3606,7 +3606,7 @@ pub fn initSymtab(self: *Elf) !void { }; if (self.symtab_section_index == null) { self.symtab_section_index = try self.addSection(.{ - .name = ".symtab", + .name = try self.insertShString(".symtab"), .type = elf.SHT_SYMTAB, .addralign = if (small_ptr) @alignOf(elf.Elf32_Sym) else @alignOf(elf.Elf64_Sym), .entsize = if (small_ptr) @sizeOf(elf.Elf32_Sym) else @sizeOf(elf.Elf64_Sym), @@ -3615,7 +3615,7 @@ pub fn initSymtab(self: *Elf) !void { } if (self.strtab_section_index == null) { self.strtab_section_index = try self.addSection(.{ - .name = ".strtab", + .name = try self.insertShString(".strtab"), .type = elf.SHT_STRTAB, .entsize = 1, .addralign = 1, @@ -3627,7 +3627,7 @@ pub fn initSymtab(self: *Elf) !void { pub fn initShStrtab(self: *Elf) !void { if (self.shstrtab_section_index == null) { self.shstrtab_section_index = try self.addSection(.{ - .name = ".shstrtab", + .name = try self.insertShString(".shstrtab"), .type = elf.SHT_STRTAB, .entsize = 1, .addralign = 1, @@ -5428,7 +5428,7 @@ fn addPhdr(self: *Elf, opts: struct { return index; } -pub fn addRelaShdr(self: *Elf, name: [:0]const u8, shndx: u32) !u32 { +pub fn addRelaShdr(self: *Elf, name: u32, shndx: u32) !u32 { const entsize: u64 = switch (self.ptr_width) { .p32 => @sizeOf(elf.Elf32_Rela), .p64 => @sizeOf(elf.Elf64_Rela), @@ -5449,7 +5449,7 @@ pub fn addRelaShdr(self: *Elf, name: [:0]const u8, shndx: u32) !u32 { } pub const AddSectionOpts = struct { - name: [:0]const u8, + name: u32 = 0, type: u32 = elf.SHT_NULL, flags: u64 = 0, link: u32 = 0, @@ -5464,7 +5464,7 @@ pub fn addSection(self: *Elf, opts: AddSectionOpts) !u32 { const index = @as(u32, @intCast(self.shdrs.items.len)); const shdr = try self.shdrs.addOne(gpa); shdr.* = .{ - .sh_name = try self.insertShString(opts.name), + .sh_name = opts.name, .sh_type = opts.type, .sh_flags = opts.flags, .sh_addr = 0, @@ -5709,7 +5709,7 @@ pub fn zigObjectPtr(self: *Elf) ?*ZigObject { return self.file(index).?.zig_object; } -pub fn getOrCreateMergeSection(self: *Elf, name: []const u8, flags: u64, @"type": u32) !MergeSection.Index { +pub fn getOrCreateMergeSection(self: *Elf, name: [:0]const u8, flags: u64, @"type": u32) !MergeSection.Index { const gpa = self.base.comp.gpa; const out_name = name: { if (self.base.isRelocatable()) break :name name; @@ -5717,11 +5717,11 @@ pub fn getOrCreateMergeSection(self: *Elf, name: []const u8, flags: u64, @"type" break :name if (flags & elf.SHF_STRINGS != 0) ".rodata.str" else ".rodata.cst"; break :name name; }; - const out_off = try self.strings.insert(gpa, out_name); - const out_flags = flags & ~@as(u64, elf.SHF_COMPRESSED | elf.SHF_GROUP); for (self.merge_sections.items, 0..) |msec, index| { - if (msec.name_offset == out_off) return @intCast(index); + if (mem.eql(u8, msec.name(self), out_name)) return @intCast(index); } + const out_off = try self.insertShString(out_name); + const out_flags = flags & ~@as(u64, elf.SHF_COMPRESSED | elf.SHF_GROUP); const index = @as(MergeSection.Index, @intCast(self.merge_sections.items.len)); const msec = try self.merge_sections.addOne(gpa); msec.* = .{ diff --git a/src/link/Elf/Atom.zig b/src/link/Elf/Atom.zig index 1763dafa57..30285f204a 100644 --- a/src/link/Elf/Atom.zig +++ b/src/link/Elf/Atom.zig @@ -40,7 +40,7 @@ extra_index: u32 = 0, pub const Alignment = @import("../../InternPool.zig").Alignment; -pub fn name(self: Atom, elf_file: *Elf) []const u8 { +pub fn name(self: Atom, elf_file: *Elf) [:0]const u8 { const file_ptr = self.file(elf_file).?; return switch (file_ptr) { inline else => |x| x.getString(self.name_offset), diff --git a/src/link/Elf/Object.zig b/src/link/Elf/Object.zig index 4944ef904e..9185ca0ef7 100644 --- a/src/link/Elf/Object.zig +++ b/src/link/Elf/Object.zig @@ -354,7 +354,7 @@ fn initOutputSection(self: Object, elf_file: *Elf, shdr: elf.Elf64_Shdr) error{O const out_shndx = elf_file.sectionByName(name) orelse try elf_file.addSection(.{ .type = @"type", .flags = flags, - .name = name, + .name = try elf_file.insertShString(name), }); return out_shndx; } diff --git a/src/link/Elf/ZigObject.zig b/src/link/Elf/ZigObject.zig index 1771e3cae1..66276761e0 100644 --- a/src/link/Elf/ZigObject.zig +++ b/src/link/Elf/ZigObject.zig @@ -854,14 +854,14 @@ fn getDeclShdrIndex( if (is_all_zeroes) break :blk elf_file.sectionByName(".tbss") orelse try elf_file.addSection(.{ .type = elf.SHT_NOBITS, .flags = elf.SHF_ALLOC | elf.SHF_WRITE | elf.SHF_TLS, - .name = ".tbss", + .name = try elf_file.insertShString(".tbss"), .offset = std.math.maxInt(u64), }); break :blk elf_file.sectionByName(".tdata") orelse try elf_file.addSection(.{ .type = elf.SHT_PROGBITS, .flags = elf.SHF_ALLOC | elf.SHF_WRITE | elf.SHF_TLS, - .name = ".tdata", + .name = try elf_file.insertShString(".tdata"), .offset = std.math.maxInt(u64), }); } diff --git a/src/link/Elf/merge_section.zig b/src/link/Elf/merge_section.zig index b4b670062c..baec316c3d 100644 --- a/src/link/Elf/merge_section.zig +++ b/src/link/Elf/merge_section.zig @@ -21,7 +21,7 @@ pub const MergeSection = struct { } pub fn name(msec: MergeSection, elf_file: *Elf) [:0]const u8 { - return elf_file.strings.getAssumeExists(msec.name_offset); + return elf_file.getShString(msec.name_offset); } pub fn address(msec: MergeSection, elf_file: *Elf) i64 { diff --git a/src/link/Elf/relocatable.zig b/src/link/Elf/relocatable.zig index c5834fc2ae..ce65b8eb72 100644 --- a/src/link/Elf/relocatable.zig +++ b/src/link/Elf/relocatable.zig @@ -299,13 +299,16 @@ fn initSections(elf_file: *Elf) !void { } else false; if (needs_eh_frame) { elf_file.eh_frame_section_index = try elf_file.addSection(.{ - .name = ".eh_frame", + .name = try elf_file.insertShString(".eh_frame"), .type = elf.SHT_PROGBITS, .flags = elf.SHF_ALLOC, .addralign = ptr_size, .offset = std.math.maxInt(u64), }); - elf_file.eh_frame_rela_section_index = try elf_file.addRelaShdr(".rela.eh_frame", elf_file.eh_frame_section_index.?); + elf_file.eh_frame_rela_section_index = try elf_file.addRelaShdr( + try elf_file.insertShString(".rela.eh_frame"), + elf_file.eh_frame_section_index.?, + ); } try initComdatGroups(elf_file); @@ -323,7 +326,7 @@ fn initComdatGroups(elf_file: *Elf) !void { const cg_sec = try elf_file.comdat_group_sections.addOne(gpa); cg_sec.* = .{ .shndx = try elf_file.addSection(.{ - .name = ".group", + .name = try elf_file.insertShString(".group"), .type = elf.SHT_GROUP, .entsize = @sizeOf(u32), .addralign = @alignOf(u32), From 5ceac8ebbae5fedc56c74e2b3d4918742089ec45 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sun, 28 Jul 2024 23:19:35 +0200 Subject: [PATCH 057/266] elf: move initializing and allocating linker-defined symbols into LinkerDefined --- src/link/Elf.zig | 243 ++-------------------------- src/link/Elf/LinkerDefined.zig | 217 ++++++++++++++++++++++++- src/link/Elf/synthetic_sections.zig | 2 +- 3 files changed, 233 insertions(+), 229 deletions(-) diff --git a/src/link/Elf.zig b/src/link/Elf.zig index de4085eec3..5008834362 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -174,25 +174,6 @@ shstrtab_section_index: ?u32 = null, strtab_section_index: ?u32 = null, symtab_section_index: ?u32 = null, -// Linker-defined symbols -dynamic_index: ?Symbol.Index = null, -ehdr_start_index: ?Symbol.Index = null, -init_array_start_index: ?Symbol.Index = null, -init_array_end_index: ?Symbol.Index = null, -fini_array_start_index: ?Symbol.Index = null, -fini_array_end_index: ?Symbol.Index = null, -preinit_array_start_index: ?Symbol.Index = null, -preinit_array_end_index: ?Symbol.Index = null, -got_index: ?Symbol.Index = null, -plt_index: ?Symbol.Index = null, -end_index: ?Symbol.Index = null, -gnu_eh_frame_hdr_index: ?Symbol.Index = null, -dso_handle_index: ?Symbol.Index = null, -rela_iplt_start_index: ?Symbol.Index = null, -rela_iplt_end_index: ?Symbol.Index = null, -global_pointer_index: ?Symbol.Index = null, -start_stop_indexes: std.ArrayListUnmanaged(u32) = .{}, - /// An array of symbols parsed across all input files. symbols: std.ArrayListUnmanaged(Symbol) = .{}, symbols_extra: std.ArrayListUnmanaged(u32) = .{}, @@ -484,7 +465,6 @@ pub fn deinit(self: *Elf) void { self.symbols_extra.deinit(gpa); self.symbols_free_list.deinit(gpa); self.resolver.deinit(gpa); - self.start_stop_indexes.deinit(gpa); for (self.thunks.items) |*th| { th.deinit(gpa); @@ -1287,7 +1267,7 @@ pub fn flushModule(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_nod const index = @as(File.Index, @intCast(try self.files.addOne(gpa))); self.files.set(index, .{ .linker_defined = .{ .index = index } }); self.linker_defined_index = index; - const object = self.file(index).?.linker_defined; + const object = self.linkerDefinedPtr().?; try object.init(gpa); } @@ -1327,7 +1307,9 @@ pub fn flushModule(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_nod try self.finalizeMergeSections(); try self.initOutputSections(); try self.initMergeSections(); - try self.addLinkerDefinedSymbols(); + if (self.linkerDefinedPtr()) |obj| { + try obj.initSymbols(self); + } self.claimUnresolved(); // Scan and create missing synthetic entries such as GOT indirection. @@ -1353,7 +1335,9 @@ pub fn flushModule(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_nod try self.sortPhdrs(); try self.allocateNonAllocSections(); self.allocateSpecialPhdrs(); - self.allocateLinkerDefinedSymbols(); + if (self.linkerDefinedPtr()) |obj| { + obj.allocateSymbols(self); + } // Dump the state for easy debugging. // State can be dumped via `--debug-log link_state`. @@ -3063,205 +3047,6 @@ pub fn deleteExport( return self.zigObjectPtr().?.deleteExport(self, exported, name); } -fn addLinkerDefinedSymbols(self: *Elf) !void { - const comp = self.base.comp; - const gpa = comp.gpa; - - const linker_defined_index = self.linker_defined_index orelse return; - const linker_defined = self.file(linker_defined_index).?.linker_defined; - self.dynamic_index = try linker_defined.addGlobal("_DYNAMIC", self); - self.ehdr_start_index = try linker_defined.addGlobal("__ehdr_start", self); - self.init_array_start_index = try linker_defined.addGlobal("__init_array_start", self); - self.init_array_end_index = try linker_defined.addGlobal("__init_array_end", self); - self.fini_array_start_index = try linker_defined.addGlobal("__fini_array_start", self); - self.fini_array_end_index = try linker_defined.addGlobal("__fini_array_end", self); - self.preinit_array_start_index = try linker_defined.addGlobal("__preinit_array_start", self); - self.preinit_array_end_index = try linker_defined.addGlobal("__preinit_array_end", self); - self.got_index = try linker_defined.addGlobal("_GLOBAL_OFFSET_TABLE_", self); - self.plt_index = try linker_defined.addGlobal("_PROCEDURE_LINKAGE_TABLE_", self); - self.end_index = try linker_defined.addGlobal("_end", self); - - if (comp.link_eh_frame_hdr) { - self.gnu_eh_frame_hdr_index = try linker_defined.addGlobal("__GNU_EH_FRAME_HDR", self); - } - - if (self.globalByName("__dso_handle")) |index| { - if (self.symbol(index).file(self) == null) - self.dso_handle_index = try linker_defined.addGlobal("__dso_handle", self); - } - - self.rela_iplt_start_index = try linker_defined.addGlobal("__rela_iplt_start", self); - self.rela_iplt_end_index = try linker_defined.addGlobal("__rela_iplt_end", self); - - for (self.shdrs.items) |shdr| { - if (self.getStartStopBasename(shdr)) |name| { - try self.start_stop_indexes.ensureUnusedCapacity(gpa, 2); - - const start = try std.fmt.allocPrintZ(gpa, "__start_{s}", .{name}); - defer gpa.free(start); - const stop = try std.fmt.allocPrintZ(gpa, "__stop_{s}", .{name}); - defer gpa.free(stop); - - self.start_stop_indexes.appendAssumeCapacity(try linker_defined.addGlobal(start, self)); - self.start_stop_indexes.appendAssumeCapacity(try linker_defined.addGlobal(stop, self)); - } - } - - if (self.getTarget().cpu.arch.isRISCV() and self.isEffectivelyDynLib()) { - self.global_pointer_index = try linker_defined.addGlobal("__global_pointer$", self); - } - - linker_defined.resolveSymbols(self); -} - -fn allocateLinkerDefinedSymbols(self: *Elf) void { - const comp = self.base.comp; - const link_mode = comp.config.link_mode; - - // _DYNAMIC - if (self.dynamic_section_index) |shndx| { - const shdr = &self.shdrs.items[shndx]; - const symbol_ptr = self.symbol(self.dynamic_index.?); - symbol_ptr.value = @intCast(shdr.sh_addr); - symbol_ptr.output_section_index = shndx; - } - - // __ehdr_start - { - const symbol_ptr = self.symbol(self.ehdr_start_index.?); - symbol_ptr.value = @intCast(self.image_base); - symbol_ptr.output_section_index = 1; - } - - // __init_array_start, __init_array_end - if (self.sectionByName(".init_array")) |shndx| { - const start_sym = self.symbol(self.init_array_start_index.?); - const end_sym = self.symbol(self.init_array_end_index.?); - const shdr = &self.shdrs.items[shndx]; - start_sym.output_section_index = shndx; - start_sym.value = @intCast(shdr.sh_addr); - end_sym.output_section_index = shndx; - end_sym.value = @intCast(shdr.sh_addr + shdr.sh_size); - } - - // __fini_array_start, __fini_array_end - if (self.sectionByName(".fini_array")) |shndx| { - const start_sym = self.symbol(self.fini_array_start_index.?); - const end_sym = self.symbol(self.fini_array_end_index.?); - const shdr = &self.shdrs.items[shndx]; - start_sym.output_section_index = shndx; - start_sym.value = @intCast(shdr.sh_addr); - end_sym.output_section_index = shndx; - end_sym.value = @intCast(shdr.sh_addr + shdr.sh_size); - } - - // __preinit_array_start, __preinit_array_end - if (self.sectionByName(".preinit_array")) |shndx| { - const start_sym = self.symbol(self.preinit_array_start_index.?); - const end_sym = self.symbol(self.preinit_array_end_index.?); - const shdr = &self.shdrs.items[shndx]; - start_sym.output_section_index = shndx; - start_sym.value = @intCast(shdr.sh_addr); - end_sym.output_section_index = shndx; - end_sym.value = @intCast(shdr.sh_addr + shdr.sh_size); - } - - // _GLOBAL_OFFSET_TABLE_ - if (self.getTarget().cpu.arch == .x86_64) { - if (self.got_plt_section_index) |shndx| { - const shdr = self.shdrs.items[shndx]; - const sym = self.symbol(self.got_index.?); - sym.value = @intCast(shdr.sh_addr); - sym.output_section_index = shndx; - } - } else { - if (self.got_section_index) |shndx| { - const shdr = self.shdrs.items[shndx]; - const sym = self.symbol(self.got_index.?); - sym.value = @intCast(shdr.sh_addr); - sym.output_section_index = shndx; - } - } - - // _PROCEDURE_LINKAGE_TABLE_ - if (self.plt_section_index) |shndx| { - const shdr = &self.shdrs.items[shndx]; - const symbol_ptr = self.symbol(self.plt_index.?); - symbol_ptr.value = @intCast(shdr.sh_addr); - symbol_ptr.output_section_index = shndx; - } - - // __dso_handle - if (self.dso_handle_index) |index| { - const shdr = &self.shdrs.items[1]; - const symbol_ptr = self.symbol(index); - symbol_ptr.value = @intCast(shdr.sh_addr); - symbol_ptr.output_section_index = 0; - } - - // __GNU_EH_FRAME_HDR - if (self.eh_frame_hdr_section_index) |shndx| { - const shdr = &self.shdrs.items[shndx]; - const symbol_ptr = self.symbol(self.gnu_eh_frame_hdr_index.?); - symbol_ptr.value = @intCast(shdr.sh_addr); - symbol_ptr.output_section_index = shndx; - } - - // __rela_iplt_start, __rela_iplt_end - if (self.rela_dyn_section_index) |shndx| blk: { - if (link_mode != .static or comp.config.pie) break :blk; - const shdr = &self.shdrs.items[shndx]; - const end_addr = shdr.sh_addr + shdr.sh_size; - const start_addr = end_addr - self.calcNumIRelativeRelocs() * @sizeOf(elf.Elf64_Rela); - const start_sym = self.symbol(self.rela_iplt_start_index.?); - const end_sym = self.symbol(self.rela_iplt_end_index.?); - start_sym.value = @intCast(start_addr); - start_sym.output_section_index = shndx; - end_sym.value = @intCast(end_addr); - end_sym.output_section_index = shndx; - } - - // _end - { - const end_symbol = self.symbol(self.end_index.?); - for (self.shdrs.items, 0..) |shdr, shndx| { - if (shdr.sh_flags & elf.SHF_ALLOC != 0) { - end_symbol.value = @intCast(shdr.sh_addr + shdr.sh_size); - end_symbol.output_section_index = @intCast(shndx); - } - } - } - - // __start_*, __stop_* - { - var index: usize = 0; - while (index < self.start_stop_indexes.items.len) : (index += 2) { - const start = self.symbol(self.start_stop_indexes.items[index]); - const name = start.name(self); - const stop = self.symbol(self.start_stop_indexes.items[index + 1]); - const shndx = self.sectionByName(name["__start_".len..]).?; - const shdr = &self.shdrs.items[shndx]; - start.value = @intCast(shdr.sh_addr); - start.output_section_index = shndx; - stop.value = @intCast(shdr.sh_addr + shdr.sh_size); - stop.output_section_index = shndx; - } - } - - // __global_pointer$ - if (self.global_pointer_index) |index| { - const sym = self.symbol(index); - if (self.sectionByName(".sdata")) |shndx| { - const shdr = self.shdrs.items[shndx]; - sym.value = @intCast(shdr.sh_addr + 0x800); - sym.output_section_index = shndx; - } else { - sym.value = 0; - sym.output_section_index = 0; - } - } -} - fn checkDuplicates(self: *Elf) !void { const gpa = self.base.comp.gpa; @@ -4964,9 +4749,8 @@ pub fn writeSymtab(self: *Elf) !void { file_ptr.writeSymtab(self); } - if (self.linker_defined_index) |index| { - const file_ptr = self.file(index).?; - file_ptr.writeSymtab(self); + if (self.linkerDefinedPtr()) |obj| { + obj.asFile().writeSymtab(self); } if (self.zig_got_section_index) |_| { @@ -5529,7 +5313,7 @@ fn sortRelaDyn(self: *Elf) void { mem.sort(elf.Elf64_Rela, self.rela_dyn.items, self, Sort.lessThan); } -fn calcNumIRelativeRelocs(self: *Elf) usize { +pub fn calcNumIRelativeRelocs(self: *Elf) usize { var count: usize = self.num_ifunc_dynrelocs; for (self.got.entries.items) |entry| { @@ -5551,7 +5335,7 @@ pub fn isCIdentifier(name: []const u8) bool { return true; } -fn getStartStopBasename(self: *Elf, shdr: elf.Elf64_Shdr) ?[]const u8 { +pub fn getStartStopBasename(self: *Elf, shdr: elf.Elf64_Shdr) ?[]const u8 { const name = self.getShString(shdr.sh_name); if (shdr.sh_flags & elf.SHF_ALLOC != 0 and name.len > 0) { if (isCIdentifier(name)) return name; @@ -5709,6 +5493,11 @@ pub fn zigObjectPtr(self: *Elf) ?*ZigObject { return self.file(index).?.zig_object; } +pub fn linkerDefinedPtr(self: *Elf) ?*LinkerDefined { + const index = self.linker_defined_index orelse return null; + return self.file(index).?.linker_defined; +} + pub fn getOrCreateMergeSection(self: *Elf, name: [:0]const u8, flags: u64, @"type": u32) !MergeSection.Index { const gpa = self.base.comp.gpa; const out_name = name: { diff --git a/src/link/Elf/LinkerDefined.zig b/src/link/Elf/LinkerDefined.zig index 6009f446a9..dbca64d325 100644 --- a/src/link/Elf/LinkerDefined.zig +++ b/src/link/Elf/LinkerDefined.zig @@ -4,12 +4,31 @@ symtab: std.ArrayListUnmanaged(elf.Elf64_Sym) = .{}, strtab: std.ArrayListUnmanaged(u8) = .{}, symbols: std.ArrayListUnmanaged(Symbol.Index) = .{}, +dynamic_index: ?Symbol.Index = null, +ehdr_start_index: ?Symbol.Index = null, +init_array_start_index: ?Symbol.Index = null, +init_array_end_index: ?Symbol.Index = null, +fini_array_start_index: ?Symbol.Index = null, +fini_array_end_index: ?Symbol.Index = null, +preinit_array_start_index: ?Symbol.Index = null, +preinit_array_end_index: ?Symbol.Index = null, +got_index: ?Symbol.Index = null, +plt_index: ?Symbol.Index = null, +end_index: ?Symbol.Index = null, +gnu_eh_frame_hdr_index: ?Symbol.Index = null, +dso_handle_index: ?Symbol.Index = null, +rela_iplt_start_index: ?Symbol.Index = null, +rela_iplt_end_index: ?Symbol.Index = null, +global_pointer_index: ?Symbol.Index = null, +start_stop_indexes: std.ArrayListUnmanaged(u32) = .{}, + output_symtab_ctx: Elf.SymtabCtx = .{}, pub fn deinit(self: *LinkerDefined, allocator: Allocator) void { self.symtab.deinit(allocator); self.strtab.deinit(allocator); self.symbols.deinit(allocator); + self.start_stop_indexes.deinit(allocator); } pub fn init(self: *LinkerDefined, allocator: Allocator) !void { @@ -17,7 +36,55 @@ pub fn init(self: *LinkerDefined, allocator: Allocator) !void { try self.strtab.append(allocator, 0); } -pub fn addGlobal(self: *LinkerDefined, name: [:0]const u8, elf_file: *Elf) !u32 { +pub fn initSymbols(self: *LinkerDefined, elf_file: *Elf) !void { + const gpa = elf_file.base.comp.gpa; + + self.dynamic_index = try self.addGlobal("_DYNAMIC", elf_file); + self.ehdr_start_index = try self.addGlobal("__ehdr_start", elf_file); + self.init_array_start_index = try self.addGlobal("__init_array_start", elf_file); + self.init_array_end_index = try self.addGlobal("__init_array_end", elf_file); + self.fini_array_start_index = try self.addGlobal("__fini_array_start", elf_file); + self.fini_array_end_index = try self.addGlobal("__fini_array_end", elf_file); + self.preinit_array_start_index = try self.addGlobal("__preinit_array_start", elf_file); + self.preinit_array_end_index = try self.addGlobal("__preinit_array_end", elf_file); + self.got_index = try self.addGlobal("_GLOBAL_OFFSET_TABLE_", elf_file); + self.plt_index = try self.addGlobal("_PROCEDURE_LINKAGE_TABLE_", elf_file); + self.end_index = try self.addGlobal("_end", elf_file); + + if (elf_file.base.comp.link_eh_frame_hdr) { + self.gnu_eh_frame_hdr_index = try self.addGlobal("__GNU_EH_FRAME_HDR", elf_file); + } + + if (elf_file.globalByName("__dso_handle")) |index| { + if (elf_file.symbol(index).file(elf_file) == null) + self.dso_handle_index = try self.addGlobal("__dso_handle", elf_file); + } + + self.rela_iplt_start_index = try self.addGlobal("__rela_iplt_start", elf_file); + self.rela_iplt_end_index = try self.addGlobal("__rela_iplt_end", elf_file); + + for (elf_file.shdrs.items) |shdr| { + if (elf_file.getStartStopBasename(shdr)) |name| { + try self.start_stop_indexes.ensureUnusedCapacity(gpa, 2); + + const start = try std.fmt.allocPrintZ(gpa, "__start_{s}", .{name}); + defer gpa.free(start); + const stop = try std.fmt.allocPrintZ(gpa, "__stop_{s}", .{name}); + defer gpa.free(stop); + + self.start_stop_indexes.appendAssumeCapacity(try self.addGlobal(start, elf_file)); + self.start_stop_indexes.appendAssumeCapacity(try self.addGlobal(stop, elf_file)); + } + } + + if (elf_file.getTarget().cpu.arch.isRISCV() and elf_file.isEffectivelyDynLib()) { + self.global_pointer_index = try self.addGlobal("__global_pointer$", elf_file); + } + + self.resolveSymbols(elf_file); +} + +fn addGlobal(self: *LinkerDefined, name: [:0]const u8, elf_file: *Elf) !u32 { const comp = elf_file.base.comp; const gpa = comp.gpa; try self.symtab.ensureUnusedCapacity(gpa, 1); @@ -55,6 +122,154 @@ pub fn resolveSymbols(self: *LinkerDefined, elf_file: *Elf) void { } } +pub fn allocateSymbols(self: *LinkerDefined, elf_file: *Elf) void { + const comp = elf_file.base.comp; + const link_mode = comp.config.link_mode; + + // _DYNAMIC + if (elf_file.dynamic_section_index) |shndx| { + const shdr = &elf_file.shdrs.items[shndx]; + const symbol_ptr = elf_file.symbol(self.dynamic_index.?); + symbol_ptr.value = @intCast(shdr.sh_addr); + symbol_ptr.output_section_index = shndx; + } + + // __ehdr_start + { + const symbol_ptr = elf_file.symbol(self.ehdr_start_index.?); + symbol_ptr.value = @intCast(elf_file.image_base); + symbol_ptr.output_section_index = 1; + } + + // __init_array_start, __init_array_end + if (elf_file.sectionByName(".init_array")) |shndx| { + const start_sym = elf_file.symbol(self.init_array_start_index.?); + const end_sym = elf_file.symbol(self.init_array_end_index.?); + const shdr = &elf_file.shdrs.items[shndx]; + start_sym.output_section_index = shndx; + start_sym.value = @intCast(shdr.sh_addr); + end_sym.output_section_index = shndx; + end_sym.value = @intCast(shdr.sh_addr + shdr.sh_size); + } + + // __fini_array_start, __fini_array_end + if (elf_file.sectionByName(".fini_array")) |shndx| { + const start_sym = elf_file.symbol(self.fini_array_start_index.?); + const end_sym = elf_file.symbol(self.fini_array_end_index.?); + const shdr = &elf_file.shdrs.items[shndx]; + start_sym.output_section_index = shndx; + start_sym.value = @intCast(shdr.sh_addr); + end_sym.output_section_index = shndx; + end_sym.value = @intCast(shdr.sh_addr + shdr.sh_size); + } + + // __preinit_array_start, __preinit_array_end + if (elf_file.sectionByName(".preinit_array")) |shndx| { + const start_sym = elf_file.symbol(self.preinit_array_start_index.?); + const end_sym = elf_file.symbol(self.preinit_array_end_index.?); + const shdr = &elf_file.shdrs.items[shndx]; + start_sym.output_section_index = shndx; + start_sym.value = @intCast(shdr.sh_addr); + end_sym.output_section_index = shndx; + end_sym.value = @intCast(shdr.sh_addr + shdr.sh_size); + } + + // _GLOBAL_OFFSET_TABLE_ + if (elf_file.getTarget().cpu.arch == .x86_64) { + if (elf_file.got_plt_section_index) |shndx| { + const shdr = elf_file.shdrs.items[shndx]; + const sym = elf_file.symbol(self.got_index.?); + sym.value = @intCast(shdr.sh_addr); + sym.output_section_index = shndx; + } + } else { + if (elf_file.got_section_index) |shndx| { + const shdr = elf_file.shdrs.items[shndx]; + const sym = elf_file.symbol(self.got_index.?); + sym.value = @intCast(shdr.sh_addr); + sym.output_section_index = shndx; + } + } + + // _PROCEDURE_LINKAGE_TABLE_ + if (elf_file.plt_section_index) |shndx| { + const shdr = &elf_file.shdrs.items[shndx]; + const symbol_ptr = elf_file.symbol(self.plt_index.?); + symbol_ptr.value = @intCast(shdr.sh_addr); + symbol_ptr.output_section_index = shndx; + } + + // __dso_handle + if (self.dso_handle_index) |index| { + const shdr = &elf_file.shdrs.items[1]; + const symbol_ptr = elf_file.symbol(index); + symbol_ptr.value = @intCast(shdr.sh_addr); + symbol_ptr.output_section_index = 0; + } + + // __GNU_EH_FRAME_HDR + if (elf_file.eh_frame_hdr_section_index) |shndx| { + const shdr = &elf_file.shdrs.items[shndx]; + const symbol_ptr = elf_file.symbol(self.gnu_eh_frame_hdr_index.?); + symbol_ptr.value = @intCast(shdr.sh_addr); + symbol_ptr.output_section_index = shndx; + } + + // __rela_iplt_start, __rela_iplt_end + if (elf_file.rela_dyn_section_index) |shndx| blk: { + if (link_mode != .static or comp.config.pie) break :blk; + const shdr = &elf_file.shdrs.items[shndx]; + const end_addr = shdr.sh_addr + shdr.sh_size; + const start_addr = end_addr - elf_file.calcNumIRelativeRelocs() * @sizeOf(elf.Elf64_Rela); + const start_sym = elf_file.symbol(self.rela_iplt_start_index.?); + const end_sym = elf_file.symbol(self.rela_iplt_end_index.?); + start_sym.value = @intCast(start_addr); + start_sym.output_section_index = shndx; + end_sym.value = @intCast(end_addr); + end_sym.output_section_index = shndx; + } + + // _end + { + const end_symbol = elf_file.symbol(self.end_index.?); + for (elf_file.shdrs.items, 0..) |shdr, shndx| { + if (shdr.sh_flags & elf.SHF_ALLOC != 0) { + end_symbol.value = @intCast(shdr.sh_addr + shdr.sh_size); + end_symbol.output_section_index = @intCast(shndx); + } + } + } + + // __start_*, __stop_* + { + var index: usize = 0; + while (index < self.start_stop_indexes.items.len) : (index += 2) { + const start = elf_file.symbol(self.start_stop_indexes.items[index]); + const name = start.name(elf_file); + const stop = elf_file.symbol(self.start_stop_indexes.items[index + 1]); + const shndx = elf_file.sectionByName(name["__start_".len..]).?; + const shdr = &elf_file.shdrs.items[shndx]; + start.value = @intCast(shdr.sh_addr); + start.output_section_index = shndx; + stop.value = @intCast(shdr.sh_addr + shdr.sh_size); + stop.output_section_index = shndx; + } + } + + // __global_pointer$ + if (self.global_pointer_index) |index| { + const sym = elf_file.symbol(index); + if (elf_file.sectionByName(".sdata")) |shndx| { + const shdr = elf_file.shdrs.items[shndx]; + sym.value = @intCast(shdr.sh_addr + 0x800); + sym.output_section_index = shndx; + } else { + sym.value = 0; + sym.output_section_index = 0; + } + } +} + pub fn globals(self: LinkerDefined) []const Symbol.Index { return self.symbols.items; } diff --git a/src/link/Elf/synthetic_sections.zig b/src/link/Elf/synthetic_sections.zig index 872356be82..c5c2359890 100644 --- a/src/link/Elf/synthetic_sections.zig +++ b/src/link/Elf/synthetic_sections.zig @@ -1075,7 +1075,7 @@ pub const GotPltSection = struct { _ = got_plt; { // [0]: _DYNAMIC - const symbol = elf_file.symbol(elf_file.dynamic_index.?); + const symbol = elf_file.symbol(elf_file.linkerDefinedPtr().?.dynamic_index.?); try writer.writeInt(u64, @intCast(symbol.address(.{}, elf_file)), .little); } // [1]: 0x0 From 3618824ea36e22fda19c4adcf0cec3ce3254df4c Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 29 Jul 2024 18:08:10 +0200 Subject: [PATCH 058/266] elf: move entry tracking into LinkerDefined --- src/link/Elf.zig | 24 ++++++++---------------- src/link/Elf/LinkerDefined.zig | 7 +++++++ src/link/Elf/gc.zig | 8 +++++--- 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 5008834362..97812e9c19 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -93,7 +93,6 @@ phdr_gnu_stack_index: ?u16 = null, /// TODO I think ELF permits multiple TLS segments but for now, assume one per file. phdr_tls_index: ?u16 = null, -entry_index: ?Symbol.Index = null, page_size: u32, default_sym_version: elf.Elf64_Versym, @@ -1277,19 +1276,15 @@ pub fn flushModule(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_nod // Any qualifing unresolved symbol will be upgraded to an absolute, weak // symbol for potential resolution at load-time. try self.resolveSymbols(); + if (self.linkerDefinedPtr()) |obj| { + try obj.initSymbols(self); + } self.markEhFrameAtomsDead(); try self.resolveMergeSections(); try self.convertCommonSymbols(); self.markImportsExports(); - // Look for entry address in objects if not set by the incremental compiler. - if (self.entry_index == null) { - if (self.entry_name) |name| { - self.entry_index = self.globalByName(name); - } - } - if (self.base.gc_sections) { try gc.gcAtoms(self); @@ -1307,9 +1302,6 @@ pub fn flushModule(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_nod try self.finalizeMergeSections(); try self.initOutputSections(); try self.initMergeSections(); - if (self.linkerDefinedPtr()) |obj| { - try obj.initSymbols(self); - } self.claimUnresolved(); // Scan and create missing synthetic entries such as GOT indirection. @@ -1385,7 +1377,7 @@ pub fn flushModule(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_nod else => |e| return e, }; - if (self.entry_index == null and self.base.isExe()) { + if (self.base.isExe() and self.linkerDefinedPtr().?.entry_index == null) { log.debug("flushing. no_entry_point_found = true", .{}); comp.link_error_flags.no_entry_point_found = true; } else { @@ -2917,10 +2909,10 @@ pub fn writeElfHeader(self: *Elf) !void { mem.writeInt(u32, hdr_buf[index..][0..4], 1, endian); index += 4; - const e_entry = if (self.entry_index) |entry_index| - @as(u64, @intCast(self.symbol(entry_index).address(.{}, self))) - else - 0; + const e_entry: u64 = if (self.linkerDefinedPtr()) |obj| blk: { + const entry_index = obj.entry_index orelse break :blk 0; + break :blk @intCast(self.symbol(entry_index).address(.{}, self)); + } else 0; const phdr_table_offset = if (self.phdr_table_index) |phndx| self.phdrs.items[phndx].p_offset else 0; switch (self.ptr_width) { .p32 => { diff --git a/src/link/Elf/LinkerDefined.zig b/src/link/Elf/LinkerDefined.zig index dbca64d325..b937005f04 100644 --- a/src/link/Elf/LinkerDefined.zig +++ b/src/link/Elf/LinkerDefined.zig @@ -4,6 +4,7 @@ symtab: std.ArrayListUnmanaged(elf.Elf64_Sym) = .{}, strtab: std.ArrayListUnmanaged(u8) = .{}, symbols: std.ArrayListUnmanaged(Symbol.Index) = .{}, +entry_index: ?Symbol.Index = null, dynamic_index: ?Symbol.Index = null, ehdr_start_index: ?Symbol.Index = null, init_array_start_index: ?Symbol.Index = null, @@ -39,6 +40,12 @@ pub fn init(self: *LinkerDefined, allocator: Allocator) !void { pub fn initSymbols(self: *LinkerDefined, elf_file: *Elf) !void { const gpa = elf_file.base.comp.gpa; + // Look for entry address in objects if not set by the incremental compiler. + if (self.entry_index == null) { + if (elf_file.entry_name) |name| { + self.entry_index = elf_file.globalByName(name); + } + } self.dynamic_index = try self.addGlobal("_DYNAMIC", elf_file); self.ehdr_start_index = try self.addGlobal("__ehdr_start", elf_file); self.init_array_start_index = try self.addGlobal("__init_array_start", elf_file); diff --git a/src/link/Elf/gc.zig b/src/link/Elf/gc.zig index 79a632d07e..de2d43f738 100644 --- a/src/link/Elf/gc.zig +++ b/src/link/Elf/gc.zig @@ -16,9 +16,11 @@ pub fn gcAtoms(elf_file: *Elf) !void { } fn collectRoots(roots: *std.ArrayList(*Atom), files: []const File.Index, elf_file: *Elf) !void { - if (elf_file.entry_index) |index| { - const global = elf_file.symbol(index); - try markSymbol(global, roots, elf_file); + if (elf_file.linkerDefinedPtr()) |obj| { + if (obj.entry_index) |index| { + const global = elf_file.symbol(index); + try markSymbol(global, roots, elf_file); + } } for (files) |index| { From a76ad907d0869d959fd954ff6edeff15942c3bc0 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 29 Jul 2024 22:14:17 +0200 Subject: [PATCH 059/266] elf: include LinkerDefined in symbol resolution --- src/link/Elf.zig | 9 +++++---- src/link/Elf/LinkerDefined.zig | 18 +++++------------- 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 97812e9c19..5ca4ec7ff2 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -1268,6 +1268,7 @@ pub fn flushModule(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_nod self.linker_defined_index = index; const object = self.linkerDefinedPtr().?; try object.init(gpa); + try object.initSymbols(self); } // Now, we are ready to resolve the symbols across all input files. @@ -1276,9 +1277,6 @@ pub fn flushModule(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_nod // Any qualifing unresolved symbol will be upgraded to an absolute, weak // symbol for potential resolution at load-time. try self.resolveSymbols(); - if (self.linkerDefinedPtr()) |obj| { - try obj.initSymbols(self); - } self.markEhFrameAtomsDead(); try self.resolveMergeSections(); @@ -1928,6 +1926,7 @@ pub fn resolveSymbols(self: *Elf) !void { // Resolve symbols on the set of all objects and shared objects (even if some are unneeded). for (self.objects.items) |index| self.file(index).?.resolveSymbols(self); for (self.shared_objects.items) |index| self.file(index).?.resolveSymbols(self); + if (self.linkerDefinedPtr()) |obj| obj.asFile().resolveSymbols(self); // Mark live objects. self.markLive(); @@ -1936,6 +1935,7 @@ pub fn resolveSymbols(self: *Elf) !void { if (self.zigObjectPtr()) |zig_object| zig_object.asFile().resetGlobals(self); for (self.objects.items) |index| self.file(index).?.resetGlobals(self); for (self.shared_objects.items) |index| self.file(index).?.resetGlobals(self); + if (self.linkerDefinedPtr()) |obj| obj.asFile().resetGlobals(self); // Prune dead objects and shared objects. var i: usize = 0; @@ -1968,9 +1968,10 @@ pub fn resolveSymbols(self: *Elf) !void { } // Re-resolve the symbols. - if (self.zigObjectPtr()) |zig_object| zig_object.resolveSymbols(self); + if (self.zigObjectPtr()) |zig_object| zig_object.asFile().resolveSymbols(self); for (self.objects.items) |index| self.file(index).?.resolveSymbols(self); for (self.shared_objects.items) |index| self.file(index).?.resolveSymbols(self); + if (self.linkerDefinedPtr()) |obj| obj.asFile().resolveSymbols(self); } /// Traverses all objects and shared objects marking any object referenced by diff --git a/src/link/Elf/LinkerDefined.zig b/src/link/Elf/LinkerDefined.zig index b937005f04..676c49cc7d 100644 --- a/src/link/Elf/LinkerDefined.zig +++ b/src/link/Elf/LinkerDefined.zig @@ -40,12 +40,10 @@ pub fn init(self: *LinkerDefined, allocator: Allocator) !void { pub fn initSymbols(self: *LinkerDefined, elf_file: *Elf) !void { const gpa = elf_file.base.comp.gpa; - // Look for entry address in objects if not set by the incremental compiler. - if (self.entry_index == null) { - if (elf_file.entry_name) |name| { - self.entry_index = elf_file.globalByName(name); - } + if (elf_file.entry_name) |name| { + self.entry_index = try self.addGlobal(name, elf_file); } + self.dynamic_index = try self.addGlobal("_DYNAMIC", elf_file); self.ehdr_start_index = try self.addGlobal("__ehdr_start", elf_file); self.init_array_start_index = try self.addGlobal("__init_array_start", elf_file); @@ -62,11 +60,7 @@ pub fn initSymbols(self: *LinkerDefined, elf_file: *Elf) !void { self.gnu_eh_frame_hdr_index = try self.addGlobal("__GNU_EH_FRAME_HDR", elf_file); } - if (elf_file.globalByName("__dso_handle")) |index| { - if (elf_file.symbol(index).file(elf_file) == null) - self.dso_handle_index = try self.addGlobal("__dso_handle", elf_file); - } - + self.dso_handle_index = try self.addGlobal("__dso_handle", elf_file); self.rela_iplt_start_index = try self.addGlobal("__rela_iplt_start", elf_file); self.rela_iplt_end_index = try self.addGlobal("__rela_iplt_end", elf_file); @@ -87,11 +81,9 @@ pub fn initSymbols(self: *LinkerDefined, elf_file: *Elf) !void { if (elf_file.getTarget().cpu.arch.isRISCV() and elf_file.isEffectivelyDynLib()) { self.global_pointer_index = try self.addGlobal("__global_pointer$", elf_file); } - - self.resolveSymbols(elf_file); } -fn addGlobal(self: *LinkerDefined, name: [:0]const u8, elf_file: *Elf) !u32 { +fn addGlobal(self: *LinkerDefined, name: []const u8, elf_file: *Elf) !u32 { const comp = elf_file.base.comp; const gpa = comp.gpa; try self.symtab.ensureUnusedCapacity(gpa, 1); From e0c475b6b79598022667e8f74c50264ddfd3e81c Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 29 Jul 2024 23:04:33 +0200 Subject: [PATCH 060/266] elf: remove now unused globalByName --- src/link/Elf.zig | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 5ca4ec7ff2..982015f61f 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -5472,11 +5472,6 @@ pub fn getOrPutGlobal(self: *Elf, name: []const u8) !GetOrPutGlobalResult { }; } -pub fn globalByName(self: *Elf, name: []const u8) ?Symbol.Index { - const name_off = self.strings.getOffset(name) orelse return null; - return self.resolver.get(name_off); -} - pub fn getGlobalSymbol(self: *Elf, name: []const u8, lib_name: ?[]const u8) !u32 { return self.zigObjectPtr().?.getGlobalSymbol(self, name, lib_name); } From 843885512dd69e083ec9163e6f57822487b46639 Mon Sep 17 00:00:00 2001 From: Marc Tiehuis Date: Tue, 30 Jul 2024 20:31:22 +1200 Subject: [PATCH 061/266] std.math.complex: fix cosh/tanh --- lib/std/math/complex/cosh.zig | 11 ++++++++++- lib/std/math/complex/tanh.zig | 11 ++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/lib/std/math/complex/cosh.zig b/lib/std/math/complex/cosh.zig index c672c71ece..d41b3fb06a 100644 --- a/lib/std/math/complex/cosh.zig +++ b/lib/std/math/complex/cosh.zig @@ -123,7 +123,7 @@ fn cosh64(z: Complex(f64)) Complex(f64) { } // x >= 1455: result always overflows else { - const h = 0x1p1023; + const h = 0x1p1023 * x; return Complex(f64).init(h * h * @cos(y), h * @sin(y)); } } @@ -170,3 +170,12 @@ test cosh64 { try testing.expectApproxEqAbs(-73.46729221264526, c.re, epsilon); try testing.expectApproxEqAbs(10.471557674805572, c.im, epsilon); } + +test "cosh64 musl" { + const epsilon = math.floatEps(f64); + const a = Complex(f64).init(7.44648873421389e17, 1.6008058402057622e19); + const c = cosh(a); + + try testing.expectApproxEqAbs(std.math.inf(f64), c.re, epsilon); + try testing.expectApproxEqAbs(std.math.inf(f64), c.im, epsilon); +} diff --git a/lib/std/math/complex/tanh.zig b/lib/std/math/complex/tanh.zig index ad75479322..af2ed4f061 100644 --- a/lib/std/math/complex/tanh.zig +++ b/lib/std/math/complex/tanh.zig @@ -70,7 +70,7 @@ fn tanh64(z: Complex(f64)) Complex(f64) { const ix = hx & 0x7fffffff; if (ix >= 0x7ff00000) { - if ((ix & 0x7fffff) | lx != 0) { + if ((ix & 0xfffff) | lx != 0) { const r = if (y == 0) y else x * y; return Complex(f64).init(x, r); } @@ -118,3 +118,12 @@ test tanh64 { try testing.expectApproxEqAbs(0.9999128201513536, c.re, epsilon); try testing.expectApproxEqAbs(-0.00002536867620767604, c.im, epsilon); } + +test "tanh64 musl" { + const epsilon = math.floatEps(f64); + const a = Complex(f64).init(std.math.inf(f64), std.math.inf(f64)); + const c = tanh(a); + + try testing.expectApproxEqAbs(1, c.re, epsilon); + try testing.expectApproxEqAbs(0, c.im, epsilon); +} From 3f10217a47ff259ca3671dd8bb89a04417bccdc2 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 30 Jul 2024 12:54:30 +0200 Subject: [PATCH 062/266] elf: fix a typo in setting atom name before it's been allocated --- src/link/Elf/ZigObject.zig | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/link/Elf/ZigObject.zig b/src/link/Elf/ZigObject.zig index 66276761e0..454646bfeb 100644 --- a/src/link/Elf/ZigObject.zig +++ b/src/link/Elf/ZigObject.zig @@ -912,12 +912,13 @@ fn updateDeclCode( const sym = elf_file.symbol(sym_index); const esym = &self.local_esyms.items(.elf_sym)[sym.esym_index]; const atom_ptr = sym.atom(elf_file).?; + const name_offset = try self.strtab.insert(gpa, decl.fqn.toSlice(ip)); atom_ptr.alive = true; - atom_ptr.name_offset = sym.name_offset; + atom_ptr.name_offset = name_offset; atom_ptr.output_section_index = shdr_index; - sym.name_offset = try self.strtab.insert(gpa, decl.fqn.toSlice(ip)); - esym.st_name = sym.name_offset; + sym.name_offset = name_offset; + esym.st_name = name_offset; esym.st_info |= stt_bits; esym.st_size = code.len; @@ -1009,15 +1010,17 @@ fn updateTlv( const sym = elf_file.symbol(sym_index); const esym = &self.local_esyms.items(.elf_sym)[sym.esym_index]; const atom_ptr = sym.atom(elf_file).?; + const name_offset = try self.strtab.insert(gpa, decl.fqn.toSlice(ip)); sym.value = 0; - atom_ptr.output_section_index = shndx; + sym.name_offset = name_offset; - sym.name_offset = try self.strtab.insert(gpa, decl.fqn.toSlice(ip)); + atom_ptr.output_section_index = shndx; atom_ptr.alive = true; - atom_ptr.name_offset = sym.name_offset; + atom_ptr.name_offset = name_offset; + esym.st_value = 0; - esym.st_name = sym.name_offset; + esym.st_name = name_offset; esym.st_info = elf.STT_TLS; esym.st_size = code.len; From 108c682df0ec6908695e55d3c3f50fe158a5d502 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Wed, 24 Jul 2024 23:25:19 +0200 Subject: [PATCH 063/266] start: Add POSIX csky support. --- lib/std/start.zig | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/lib/std/start.zig b/lib/std/start.zig index 74e177ce0b..76789bc5fc 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -316,6 +316,24 @@ fn _start() callconv(.Naked) noreturn { \\ and sp, #-16 \\ b %[posixCallMainAndExit] , + // zig fmt: off + .csky => + if (builtin.position_independent_code) + // The CSKY ABI assumes that `gb` is set to the address of the GOT in order for + // position-independent code to work. We depend on this in `std.os.linux.start_pie` + // to locate `_DYNAMIC` as well. + \\ grs t0, 1f + \\ 1: + \\ lrw gb, 1b@GOTPC + \\ addu gb, t0 + else "" + ++ + \\ movi lr, 0 + \\ mov a0, sp + \\ andi sp, sp, -8 + \\ jmpi %[posixCallMainAndExit] + , + // zig fmt: on .hexagon => // r29 = SP, r30 = FP \\ r30 = #0 From 78e581b86f11bfc8da277cbad7b7a30941e55e95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Tue, 30 Jul 2024 04:02:32 +0200 Subject: [PATCH 064/266] start: Copy the mips64 comment about gp to the mips32 code. Also, don't incorrectly claim that it's only needed for dynamic linking/PIC. --- lib/std/start.zig | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/std/start.zig b/lib/std/start.zig index 2a61be46e4..bca0922fb3 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -293,6 +293,7 @@ fn _start() callconv(.Naked) noreturn { \\ .gpword . \\ .gpword %[posixCallMainAndExit] \\ 1: + // The `gp` register on MIPS serves a similar purpose to `r2` (ToC pointer) on PPC64. \\ lw $gp, 0($ra) \\ subu $gp, $ra, $gp \\ lw $25, 4($ra) @@ -314,8 +315,6 @@ fn _start() callconv(.Naked) noreturn { \\ .gpdword %[posixCallMainAndExit] \\ 1: // The `gp` register on MIPS serves a similar purpose to `r2` (ToC pointer) on PPC64. - // We need to set it up in order for dynamically-linked / position-independent code to - // work. \\ ld $gp, 0($ra) \\ dsubu $gp, $ra, $gp \\ ld $25, 8($ra) From 52519f79e0b3659e43eb8e765a9e2b584fc8f0f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Thu, 25 Jul 2024 23:55:42 +0200 Subject: [PATCH 065/266] std.os.linux.start_pie: Apply MIPS local GOT relocations. --- lib/std/os/linux/start_pie.zig | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/lib/std/os/linux/start_pie.zig b/lib/std/os/linux/start_pie.zig index b5cc06f429..ab4e2f4fe9 100644 --- a/lib/std/os/linux/start_pie.zig +++ b/lib/std/os/linux/start_pie.zig @@ -222,6 +222,24 @@ pub fn relocate(phdrs: []elf.Phdr) void { } } + // Deal with the GOT relocations that MIPS uses first. + if (builtin.cpu.arch.isMIPS()) { + const count: elf.Addr = blk: { + // This is an architecture-specific tag, so not part of `sorted_dynv`. + var i: usize = 0; + while (dynv[i].d_tag != elf.DT_NULL) : (i += 1) { + if (dynv[i].d_tag == elf.DT_MIPS_LOCAL_GOTNO) break :blk dynv[i].d_val; + } + + break :blk 0; + }; + + const got: [*]usize = @ptrFromInt(base_addr + sorted_dynv[elf.DT_PLTGOT]); + + for (0..count) |i| { + got[i] += base_addr; + } + } // Apply normal relocations. From 2e719f32397c1ec87a9f5a5b6947dbc553471707 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Sat, 27 Jul 2024 10:21:28 +0200 Subject: [PATCH 066/266] std.os.linux.start_pie: Use a 64-bit displacement for s390x. Not likely to be necessary ever, but might as well be 100% correct. --- lib/std/os/linux/start_pie.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/std/os/linux/start_pie.zig b/lib/std/os/linux/start_pie.zig index ab4e2f4fe9..7b086250d6 100644 --- a/lib/std/os/linux/start_pie.zig +++ b/lib/std/os/linux/start_pie.zig @@ -176,9 +176,9 @@ inline fn getDynamicSymbol() [*]elf.Dyn { \\ .weak _DYNAMIC \\ .hidden _DYNAMIC \\ larl %[ret], 1f - \\ agf %[ret], 0(%[ret]) + \\ ag %[ret], 0(%[ret]) \\ b 2f - \\ 1: .long _DYNAMIC - . + \\ 1: .quad _DYNAMIC - . \\ 2: : [ret] "=r" (-> [*]elf.Dyn), ), From 2fb813c61f7193c8f765021b9eecfa068eb2760c Mon Sep 17 00:00:00 2001 From: YANG Xudong Date: Wed, 31 Jul 2024 06:20:56 +0800 Subject: [PATCH 067/266] std: set standard dynamic linker path for loongarch64 on linux. (#20726) --- lib/std/Target.zig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/std/Target.zig b/lib/std/Target.zig index a69038fae4..fe4f019ba0 100644 --- a/lib/std/Target.zig +++ b/lib/std/Target.zig @@ -1717,6 +1717,8 @@ pub const DynamicLinker = struct { else => "", }}) catch unreachable, + .loongarch64 => init("/lib64/ld-linux-loongarch-lp64d.so.1"), + .mips, .mipsel, .mips64, @@ -1776,7 +1778,6 @@ pub const DynamicLinker = struct { .ve, .dxil, .loongarch32, - .loongarch64, .xtensa, => none, }, From a69d403cb2c82ce6257bfa1ee7eba52f895c14e7 Mon Sep 17 00:00:00 2001 From: YANG Xudong Date: Mon, 22 Jul 2024 10:56:14 +0800 Subject: [PATCH 068/266] std: fix long double size for loongarch. --- lib/std/Target.zig | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/std/Target.zig b/lib/std/Target.zig index fe4f019ba0..42552f9953 100644 --- a/lib/std/Target.zig +++ b/lib/std/Target.zig @@ -2089,6 +2089,8 @@ pub fn c_type_bit_size(target: Target, c_type: CType) u16 { .sparcel, .wasm32, .wasm64, + .loongarch32, + .loongarch64, => return 128, else => return 64, @@ -2194,6 +2196,8 @@ pub fn c_type_bit_size(target: Target, c_type: CType) u16 { .sparcel, .wasm32, .wasm64, + .loongarch32, + .loongarch64, => return 128, else => return 64, From 982510f8d565895cd6b467da092d2feb851aabe7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Tue, 30 Jul 2024 04:02:49 +0200 Subject: [PATCH 069/266] start: Initialize gp to __global_pointer$ on riscv. --- lib/std/start.zig | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/std/start.zig b/lib/std/start.zig index bca0922fb3..7b68f99b9f 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -273,6 +273,14 @@ fn _start() callconv(.Naked) noreturn { \\ b %[posixCallMainAndExit] , .riscv32, .riscv64 => + // The RISC-V ELF ABI assumes that `gp` is set to the value of `__global_pointer$` at + // startup in order for GP relaxation to work, even in static builds. + \\ .weak __global_pointer$ + \\ .hidden __global_pointer$ + \\ .option push + \\ .option norelax + \\ lla gp, __global_pointer$ + \\ .option pop \\ li s0, 0 \\ li ra, 0 \\ mv a0, sp From 653eb7535514d06aca1eaaf777f82e1ecd968086 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Tue, 30 Jul 2024 17:42:11 +0200 Subject: [PATCH 070/266] start: Disable the gp initialization code for the self-hosted riscv64 backend. --- lib/std/start.zig | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/lib/std/start.zig b/lib/std/start.zig index 7b68f99b9f..2f92ded569 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -272,21 +272,27 @@ fn _start() callconv(.Naked) noreturn { \\ bstrins.d $sp, $zero, 3, 0 \\ b %[posixCallMainAndExit] , + // zig fmt: off .riscv32, .riscv64 => - // The RISC-V ELF ABI assumes that `gp` is set to the value of `__global_pointer$` at - // startup in order for GP relaxation to work, even in static builds. - \\ .weak __global_pointer$ - \\ .hidden __global_pointer$ - \\ .option push - \\ .option norelax - \\ lla gp, __global_pointer$ - \\ .option pop + // The self-hosted riscv64 backend is not able to assemble this yet. + if (builtin.zig_backend != .stage2_riscv64) + // The RISC-V ELF ABI assumes that `gp` is set to the value of `__global_pointer$` at + // startup in order for GP relaxation to work, even in static builds. + \\ .weak __global_pointer$ + \\ .hidden __global_pointer$ + \\ .option push + \\ .option norelax + \\ lla gp, __global_pointer$ + \\ .option pop + else "" + ++ \\ li s0, 0 \\ li ra, 0 \\ mv a0, sp \\ andi sp, sp, -16 \\ tail %[posixCallMainAndExit]@plt , + // zig fmt: off .m68k => // Note that the - 8 is needed because pc in the jsr instruction points into the middle // of the jsr instruction. (The lea is 6 bytes, the jsr is 4 bytes.) From 236567de8d7706b7a6c529670a1a15f4c8b9f95e Mon Sep 17 00:00:00 2001 From: Evan Haas Date: Tue, 30 Jul 2024 10:52:33 -0700 Subject: [PATCH 071/266] aro_translate_c: Emit compile errors instead of panicking for var decls --- lib/compiler/aro_translate_c.zig | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/compiler/aro_translate_c.zig b/lib/compiler/aro_translate_c.zig index 02c7547ce5..2e20918d4b 100644 --- a/lib/compiler/aro_translate_c.zig +++ b/lib/compiler/aro_translate_c.zig @@ -304,7 +304,7 @@ fn transDecl(c: *Context, scope: *Scope, decl: NodeIndex) !void { .threadlocal_extern_var, .threadlocal_static_var, => { - try transVarDecl(c, decl, null); + try transVarDecl(c, decl); }, .static_assert => try warn(c, &c.global_scope.base, 0, "ignoring _Static_assert declaration", .{}), else => unreachable, @@ -566,8 +566,10 @@ fn transFnDecl(c: *Context, fn_decl: NodeIndex) Error!void { return addTopLevelDecl(c, fn_name, proto_node); } -fn transVarDecl(_: *Context, _: NodeIndex, _: ?usize) Error!void { - @panic("TODO"); +fn transVarDecl(c: *Context, node: NodeIndex) Error!void { + const data = c.tree.nodes.items(.data)[@intFromEnum(node)]; + const name = c.tree.tokSlice(data.decl.name); + return failDecl(c, data.decl.name, name, "unable to translate variable declaration", .{}); } fn transEnumDecl(c: *Context, scope: *Scope, enum_decl: NodeIndex, field_nodes: []const NodeIndex) Error!void { From da3822f4c2b6a9e0703079c80a43c3e2a5f01aec Mon Sep 17 00:00:00 2001 From: Evan Haas Date: Tue, 30 Jul 2024 11:25:15 -0700 Subject: [PATCH 072/266] aro_translate_c: Translate array types --- lib/compiler/aro_translate_c.zig | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/compiler/aro_translate_c.zig b/lib/compiler/aro_translate_c.zig index 2e20918d4b..1963b8f84f 100644 --- a/lib/compiler/aro_translate_c.zig +++ b/lib/compiler/aro_translate_c.zig @@ -681,12 +681,15 @@ fn transType(c: *Context, scope: *Scope, raw_ty: Type, qual_handling: Type.QualH .float80 => return ZigTag.type.create(c.arena, "f80"), .float128 => return ZigTag.type.create(c.arena, "f128"), .@"enum" => @panic("TODO"), - .pointer, + .pointer, .incomplete_array => @panic("todo"), .unspecified_variable_len_array, .array, .static_array, - .incomplete_array, - => @panic("TODO"), + => { + const size = ty.arrayLen().?; + const elem_type = try transType(c, scope, ty.elemType(), qual_handling, source_loc); + return ZigTag.array_type.create(c.arena, .{ .len = size, .elem_type = elem_type }); + }, .func, .var_args_func, .old_style_func, From e32cde256811c0de68c507014d901b1a88c8e7f9 Mon Sep 17 00:00:00 2001 From: Evan Haas Date: Tue, 30 Jul 2024 11:31:30 -0700 Subject: [PATCH 073/266] aro_translate_c: translate incomplete arrays --- lib/compiler/aro_translate_c.zig | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/compiler/aro_translate_c.zig b/lib/compiler/aro_translate_c.zig index 1963b8f84f..a183df4fb9 100644 --- a/lib/compiler/aro_translate_c.zig +++ b/lib/compiler/aro_translate_c.zig @@ -681,8 +681,16 @@ fn transType(c: *Context, scope: *Scope, raw_ty: Type, qual_handling: Type.QualH .float80 => return ZigTag.type.create(c.arena, "f80"), .float128 => return ZigTag.type.create(c.arena, "f128"), .@"enum" => @panic("TODO"), - .pointer, .incomplete_array => @panic("todo"), + .pointer => @panic("todo"), .unspecified_variable_len_array, + .incomplete_array => { + const child_type = ty.elemType(); + const is_const = child_type.qual.@"const"; + const is_volatile = child_type.qual.@"volatile"; + const elem_type = try transType(c, scope, child_type, qual_handling, source_loc); + + return ZigTag.c_pointer.create(c.arena, .{ .is_const = is_const, .is_volatile = is_volatile, .elem_type = elem_type }); + }, .array, .static_array, => { From 2f2f35105ec76b3543b3198071fef35f4cbf71e2 Mon Sep 17 00:00:00 2001 From: Evan Haas Date: Tue, 30 Jul 2024 12:19:26 -0700 Subject: [PATCH 074/266] aro_translate_c: translate pointer types --- lib/compiler/aro_translate_c.zig | 65 ++++++++++++++++++++++++++++++-- 1 file changed, 62 insertions(+), 3 deletions(-) diff --git a/lib/compiler/aro_translate_c.zig b/lib/compiler/aro_translate_c.zig index a183df4fb9..9ba092ab9c 100644 --- a/lib/compiler/aro_translate_c.zig +++ b/lib/compiler/aro_translate_c.zig @@ -681,9 +681,29 @@ fn transType(c: *Context, scope: *Scope, raw_ty: Type, qual_handling: Type.QualH .float80 => return ZigTag.type.create(c.arena, "f80"), .float128 => return ZigTag.type.create(c.arena, "f128"), .@"enum" => @panic("TODO"), - .pointer => @panic("todo"), - .unspecified_variable_len_array, - .incomplete_array => { + .pointer => { + const child_type = ty.elemType(); + + const is_fn_proto = child_type.isFunc(); + const is_const = is_fn_proto or child_type.isConst(); + const is_volatile = child_type.qual.@"volatile"; + const elem_type = try transType(c, scope, child_type, qual_handling, source_loc); + const ptr_info = .{ + .is_const = is_const, + .is_volatile = is_volatile, + .elem_type = elem_type, + }; + if (is_fn_proto or + typeIsOpaque(c, child_type) or + typeWasDemotedToOpaque(c, child_type)) + { + const ptr = try ZigTag.single_pointer.create(c.arena, ptr_info); + return ZigTag.optional_type.create(c.arena, ptr); + } + + return ZigTag.c_pointer.create(c.arena, ptr_info); + }, + .unspecified_variable_len_array, .incomplete_array => { const child_type = ty.elemType(); const is_const = child_type.qual.@"const"; const is_volatile = child_type.qual.@"volatile"; @@ -965,6 +985,45 @@ fn transCompoundStmtInline(c: *Context, compound: NodeIndex, block: *Scope.Block } } +fn recordHasBitfield(record: *const Type.Record) bool { + if (record.isIncomplete()) return false; + for (record.fields) |field| { + if (!field.isRegularField()) return true; + } + return false; +} + +fn typeIsOpaque(c: *Context, ty: Type) bool { + return switch (ty.specifier) { + .void => true, + .@"struct", .@"union" => recordHasBitfield(ty.getRecord().?), + .typeof_type => typeIsOpaque(c, ty.data.sub_type.*), + .typeof_expr => typeIsOpaque(c, ty.data.expr.ty), + .attributed => typeIsOpaque(c, ty.data.attributed.base), + else => false, + }; +} + +fn typeWasDemotedToOpaque(c: *Context, ty: Type) bool { + switch (ty.specifier) { + .@"struct", .@"union" => { + const record = ty.getRecord().?; + if (c.opaque_demotes.contains(@intFromPtr(record))) return true; + for (record.fields) |field| { + if (typeWasDemotedToOpaque(c, field.ty)) return true; + } + return false; + }, + + .@"enum" => return c.opaque_demotes.contains(@intFromPtr(ty.data.@"enum")), + + .typeof_type => return typeWasDemotedToOpaque(c, ty.data.sub_type.*), + .typeof_expr => return typeWasDemotedToOpaque(c, ty.data.expr.ty), + .attributed => return typeWasDemotedToOpaque(c, ty.data.attributed.base), + else => return false, + } +} + fn transCompoundStmt(c: *Context, scope: *Scope, compound: NodeIndex) TransError!ZigNode { var block_scope = try Scope.Block.init(c, scope, false); defer block_scope.deinit(); From 5cc9e18277e1b166be0898255448f3c642759bbc Mon Sep 17 00:00:00 2001 From: Evan Haas Date: Tue, 30 Jul 2024 13:37:37 -0700 Subject: [PATCH 075/266] aro_translate_c: Translate enum types --- lib/compiler/aro_translate_c.zig | 38 ++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/lib/compiler/aro_translate_c.zig b/lib/compiler/aro_translate_c.zig index 9ba092ab9c..5c9e9771ba 100644 --- a/lib/compiler/aro_translate_c.zig +++ b/lib/compiler/aro_translate_c.zig @@ -243,6 +243,7 @@ fn transTopLevelDecls(c: *Context) !void { fn transDecl(c: *Context, scope: *Scope, decl: NodeIndex) !void { const node_tags = c.tree.nodes.items(.tag); const node_data = c.tree.nodes.items(.data); + const node_ty = c.tree.nodes.items(.ty); const data = node_data[@intFromEnum(decl)]; switch (node_tags[@intFromEnum(decl)]) { .typedef => { @@ -270,11 +271,13 @@ fn transDecl(c: *Context, scope: *Scope, decl: NodeIndex) !void { var field_count: u8 = 0; if (fields[0] != .none) field_count += 1; if (fields[1] != .none) field_count += 1; - try transEnumDecl(c, scope, decl, fields[0..field_count]); + const enum_decl = node_ty[@intFromEnum(decl)].canonicalize(.standard).data.@"enum"; + try transEnumDecl(c, scope, enum_decl, fields[0..field_count]); }, .enum_decl => { const fields = c.tree.data[data.range.start..data.range.end]; - try transEnumDecl(c, scope, decl, fields); + const enum_decl = node_ty[@intFromEnum(decl)].canonicalize(.standard).data.@"enum"; + try transEnumDecl(c, scope, enum_decl, fields); }, .enum_field_decl, @@ -572,18 +575,16 @@ fn transVarDecl(c: *Context, node: NodeIndex) Error!void { return failDecl(c, data.decl.name, name, "unable to translate variable declaration", .{}); } -fn transEnumDecl(c: *Context, scope: *Scope, enum_decl: NodeIndex, field_nodes: []const NodeIndex) Error!void { - const node_types = c.tree.nodes.items(.ty); - const ty = node_types[@intFromEnum(enum_decl)]; - if (c.decl_table.get(@intFromPtr(ty.data.@"enum"))) |_| +fn transEnumDecl(c: *Context, scope: *Scope, enum_decl: *const Type.Enum, field_nodes: []const NodeIndex) Error!void { + if (c.decl_table.get(@intFromPtr(enum_decl))) |_| return; // Avoid processing this decl twice const toplevel = scope.id == .root; const bs: *Scope.Block = if (!toplevel) try scope.findBlockScope(c) else undefined; var is_unnamed = false; - var bare_name: []const u8 = c.mapper.lookup(ty.data.@"enum".name); + var bare_name: []const u8 = c.mapper.lookup(enum_decl.name); var name = bare_name; - if (c.unnamed_typedefs.get(@intFromPtr(ty.data.@"enum"))) |typedef_name| { + if (c.unnamed_typedefs.get(@intFromPtr(enum_decl))) |typedef_name| { bare_name = typedef_name; name = typedef_name; } else { @@ -594,10 +595,10 @@ fn transEnumDecl(c: *Context, scope: *Scope, enum_decl: NodeIndex, field_nodes: name = try std.fmt.allocPrint(c.arena, "enum_{s}", .{bare_name}); } if (!toplevel) name = try bs.makeMangledName(c, name); - try c.decl_table.putNoClobber(c.gpa, @intFromPtr(ty.data.@"enum"), name); + try c.decl_table.putNoClobber(c.gpa, @intFromPtr(enum_decl), name); - const enum_type_node = if (!ty.data.@"enum".isIncomplete()) blk: { - for (ty.data.@"enum".fields, field_nodes) |field, field_node| { + const enum_type_node = if (!enum_decl.isIncomplete()) blk: { + for (enum_decl.fields, field_nodes) |field, field_node| { var enum_val_name: []const u8 = c.mapper.lookup(field.name); if (!toplevel) { enum_val_name = try bs.makeMangledName(c, enum_val_name); @@ -623,14 +624,14 @@ fn transEnumDecl(c: *Context, scope: *Scope, enum_decl: NodeIndex, field_nodes: } } - break :blk transType(c, scope, ty.data.@"enum".tag_ty, .standard, 0) catch |err| switch (err) { + break :blk transType(c, scope, enum_decl.tag_ty, .standard, 0) catch |err| switch (err) { error.UnsupportedType => { return failDecl(c, 0, name, "unable to translate enum integer type", .{}); }, else => |e| return e, }; } else blk: { - try c.opaque_demotes.put(c.gpa, @intFromPtr(ty.data.@"enum"), {}); + try c.opaque_demotes.put(c.gpa, @intFromPtr(enum_decl), {}); break :blk ZigTag.opaque_literal.init(); }; @@ -680,7 +681,16 @@ fn transType(c: *Context, scope: *Scope, raw_ty: Type, qual_handling: Type.QualH .long_double => return ZigTag.type.create(c.arena, "c_longdouble"), .float80 => return ZigTag.type.create(c.arena, "f80"), .float128 => return ZigTag.type.create(c.arena, "f128"), - .@"enum" => @panic("TODO"), + .@"enum" => { + const enum_decl = ty.data.@"enum"; + var trans_scope = scope; + if (enum_decl.name != .empty) { + const decl_name = c.mapper.lookup(enum_decl.name); + if (c.weak_global_names.contains(decl_name)) trans_scope = &c.global_scope.base; + } + try transEnumDecl(c, trans_scope, enum_decl, &.{}); + return ZigTag.identifier.create(c.arena, c.decl_table.get(@intFromPtr(enum_decl)).?); + }, .pointer => { const child_type = ty.elemType(); From 4300a9c417cb9344d65e74335c9ce09cbf9bfc17 Mon Sep 17 00:00:00 2001 From: Evan Haas Date: Tue, 30 Jul 2024 16:47:10 -0700 Subject: [PATCH 076/266] aro_translate_c: Make function decls public --- lib/compiler/aro_translate_c.zig | 5 +++-- .../translate_c/function prototype with parenthesis.c | 10 ++++++++++ test/translate_c.zig | 10 ---------- 3 files changed, 13 insertions(+), 12 deletions(-) create mode 100644 test/cases/translate_c/function prototype with parenthesis.c diff --git a/lib/compiler/aro_translate_c.zig b/lib/compiler/aro_translate_c.zig index 5c9e9771ba..dd8ca54819 100644 --- a/lib/compiler/aro_translate_c.zig +++ b/lib/compiler/aro_translate_c.zig @@ -297,7 +297,7 @@ fn transDecl(c: *Context, scope: *Scope, decl: NodeIndex) !void { .inline_fn_def, .inline_static_fn_def, => { - try transFnDecl(c, decl); + try transFnDecl(c, decl, true); }, .@"var", @@ -476,7 +476,7 @@ fn transRecordDecl(c: *Context, scope: *Scope, record_node: NodeIndex, field_nod } } -fn transFnDecl(c: *Context, fn_decl: NodeIndex) Error!void { +fn transFnDecl(c: *Context, fn_decl: NodeIndex, is_pub: bool) Error!void { const raw_ty = c.tree.nodes.items(.ty)[@intFromEnum(fn_decl)]; const fn_ty = raw_ty.canonicalize(.standard); const node_data = c.tree.nodes.items(.data)[@intFromEnum(fn_decl)]; @@ -501,6 +501,7 @@ fn transFnDecl(c: *Context, fn_decl: NodeIndex) Error!void { else => unreachable, }, + .is_pub = is_pub, }; const proto_node = transFnType(c, &c.global_scope.base, raw_ty, fn_ty, fn_decl_loc, proto_ctx) catch |err| switch (err) { diff --git a/test/cases/translate_c/function prototype with parenthesis.c b/test/cases/translate_c/function prototype with parenthesis.c new file mode 100644 index 0000000000..7b93e3fa93 --- /dev/null +++ b/test/cases/translate_c/function prototype with parenthesis.c @@ -0,0 +1,10 @@ +void (f0) (void *L); +void ((f1)) (void *L); +void (((f2))) (void *L); + +// translate-c +// c_frontend=clang,aro +// +// pub extern fn f0(L: ?*anyopaque) void; +// pub extern fn f1(L: ?*anyopaque) void; +// pub extern fn f2(L: ?*anyopaque) void; diff --git a/test/translate_c.zig b/test/translate_c.zig index c07b29f772..31bb577f00 100644 --- a/test/translate_c.zig +++ b/test/translate_c.zig @@ -494,16 +494,6 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\}; }); - cases.add("function prototype with parenthesis", - \\void (f0) (void *L); - \\void ((f1)) (void *L); - \\void (((f2))) (void *L); - , &[_][]const u8{ - \\pub extern fn f0(L: ?*anyopaque) void; - \\pub extern fn f1(L: ?*anyopaque) void; - \\pub extern fn f2(L: ?*anyopaque) void; - }); - cases.add("array initializer w/ typedef", \\typedef unsigned char uuid_t[16]; \\static const uuid_t UUID_NULL __attribute__ ((unused)) = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; From 6997f82e028642a727cc8762a9077c473066e265 Mon Sep 17 00:00:00 2001 From: Evan Haas Date: Tue, 30 Jul 2024 16:49:59 -0700 Subject: [PATCH 077/266] translate_c: move empty declaration test to test manifest --- test/cases/translate_c/empty declaration.c | 6 ++++++ test/translate_c.zig | 4 ---- 2 files changed, 6 insertions(+), 4 deletions(-) create mode 100644 test/cases/translate_c/empty declaration.c diff --git a/test/cases/translate_c/empty declaration.c b/test/cases/translate_c/empty declaration.c new file mode 100644 index 0000000000..5f19328acf --- /dev/null +++ b/test/cases/translate_c/empty declaration.c @@ -0,0 +1,6 @@ +; + +// translate-c +// c_frontend=clang,aro +// +// \ No newline at end of file diff --git a/test/translate_c.zig b/test/translate_c.zig index 31bb577f00..772129be9b 100644 --- a/test/translate_c.zig +++ b/test/translate_c.zig @@ -519,10 +519,6 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\}; }); - cases.add("empty declaration", - \\; - , &[_][]const u8{""}); - cases.add("#define hex literal with capital X", \\#define VAL 0XF00D , &[_][]const u8{ From c57fcd1db536591ecdee8c9ec497c8ea667c57f0 Mon Sep 17 00:00:00 2001 From: Evan Haas Date: Tue, 30 Jul 2024 17:20:09 -0700 Subject: [PATCH 078/266] aro_translate_c: demote functions with bodies to extern Translating statements is currently not supported; demoting to extern is better than crashing. --- lib/compiler/aro_translate_c.zig | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/compiler/aro_translate_c.zig b/lib/compiler/aro_translate_c.zig index dd8ca54819..39cdbcba1e 100644 --- a/lib/compiler/aro_translate_c.zig +++ b/lib/compiler/aro_translate_c.zig @@ -971,7 +971,9 @@ fn transFnType( } fn transStmt(c: *Context, node: NodeIndex) TransError!ZigNode { - return transExpr(c, node, .unused); + _ = c; + _ = node; + return error.UnsupportedTranslation; } fn transCompoundStmtInline(c: *Context, compound: NodeIndex, block: *Scope.Block) TransError!void { From 055077f9dd0091a0e071243e10e4d2500038d0be Mon Sep 17 00:00:00 2001 From: Evan Haas Date: Tue, 30 Jul 2024 18:24:16 -0700 Subject: [PATCH 079/266] aro_translate_c: improve record translation Move field record decl translation into `transType` instead of `transDecl` --- lib/compiler/aro_translate_c.zig | 31 +++++++++---------------------- 1 file changed, 9 insertions(+), 22 deletions(-) diff --git a/lib/compiler/aro_translate_c.zig b/lib/compiler/aro_translate_c.zig index 39cdbcba1e..5bacf73226 100644 --- a/lib/compiler/aro_translate_c.zig +++ b/lib/compiler/aro_translate_c.zig @@ -253,17 +253,12 @@ fn transDecl(c: *Context, scope: *Scope, decl: NodeIndex) !void { .struct_decl_two, .union_decl_two, => { - var fields = [2]NodeIndex{ data.bin.lhs, data.bin.rhs }; - var field_count: u2 = 0; - if (fields[0] != .none) field_count += 1; - if (fields[1] != .none) field_count += 1; - try transRecordDecl(c, scope, decl, fields[0..field_count]); + try transRecordDecl(c, scope, node_ty[@intFromEnum(decl)]); }, .struct_decl, .union_decl, => { - const fields = c.tree.data[data.range.start..data.range.end]; - try transRecordDecl(c, scope, decl, fields); + try transRecordDecl(c, scope, node_ty[@intFromEnum(decl)]); }, .enum_decl_two => { @@ -333,16 +328,14 @@ fn mangleWeakGlobalName(c: *Context, want_name: []const u8) ![]const u8 { return cur_name; } -fn transRecordDecl(c: *Context, scope: *Scope, record_node: NodeIndex, field_nodes: []const NodeIndex) Error!void { - const node_types = c.tree.nodes.items(.ty); - const raw_record_ty = node_types[@intFromEnum(record_node)]; - const record_decl = raw_record_ty.getRecord().?; +fn transRecordDecl(c: *Context, scope: *Scope, record_ty: Type) Error!void { + const record_decl = record_ty.getRecord().?; if (c.decl_table.get(@intFromPtr(record_decl))) |_| return; // Avoid processing this decl twice const toplevel = scope.id == .root; const bs: *Scope.Block = if (!toplevel) try scope.findBlockScope(c) else undefined; - const container_kind: ZigTag = if (raw_record_ty.is(.@"union")) .@"union" else .@"struct"; + const container_kind: ZigTag = if (record_ty.is(.@"union")) .@"union" else .@"struct"; const container_kind_name: []const u8 = @tagName(container_kind); var is_unnamed = false; @@ -353,7 +346,7 @@ fn transRecordDecl(c: *Context, scope: *Scope, record_node: NodeIndex, field_nod bare_name = typedef_name; name = typedef_name; } else { - if (raw_record_ty.isAnonymousRecord(c.comp)) { + if (record_ty.isAnonymousRecord(c.comp)) { bare_name = try std.fmt.allocPrint(c.arena, "unnamed_{d}", .{c.getMangle()}); is_unnamed = true; } @@ -380,17 +373,10 @@ fn transRecordDecl(c: *Context, scope: *Scope, record_node: NodeIndex, field_nod // layout, then we can just use a simple `extern` type. If it does have attributes, // then we need to inspect the layout and assign an `align` value for each field. const has_alignment_attributes = record_decl.field_attributes != null or - raw_record_ty.hasAttribute(.@"packed") or - raw_record_ty.hasAttribute(.aligned); + record_ty.hasAttribute(.@"packed") or + record_ty.hasAttribute(.aligned); const head_field_alignment: ?c_uint = if (has_alignment_attributes) headFieldAlignment(record_decl) else null; - // Iterate over field nodes so that we translate any type decls included in this record decl. - // TODO: Move this logic into `fn transType()` instead of handling decl translation here. - for (field_nodes) |field_node| { - const field_raw_ty = node_types[@intFromEnum(field_node)]; - if (field_raw_ty.isEnumOrRecord()) try transDecl(c, scope, field_node); - } - for (record_decl.fields, 0..) |field, field_index| { const field_loc = field.name_tok; @@ -742,6 +728,7 @@ fn transType(c: *Context, scope: *Scope, raw_ty: Type, qual_handling: Type.QualH const name_id = c.mapper.lookup(record_decl.name); if (c.weak_global_names.contains(name_id)) trans_scope = &c.global_scope.base; } + try transRecordDecl(c, trans_scope, ty); const name = c.decl_table.get(@intFromPtr(ty.data.record)).?; return ZigTag.identifier.create(c.arena, name); }, From 6a103d87f650ed7cac79866033e3136433259f67 Mon Sep 17 00:00:00 2001 From: Evan Haas Date: Tue, 30 Jul 2024 21:27:08 -0700 Subject: [PATCH 080/266] aro_translate_c: basic typedef support --- lib/compiler/aro_translate_c.zig | 44 +++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/lib/compiler/aro_translate_c.zig b/lib/compiler/aro_translate_c.zig index 5bacf73226..5665c3b2e6 100644 --- a/lib/compiler/aro_translate_c.zig +++ b/lib/compiler/aro_translate_c.zig @@ -185,7 +185,7 @@ fn prepopulateGlobalNameTable(c: *Context) !void { for (c.tree.root_decls) |node| { const data = node_data[@intFromEnum(node)]; switch (node_tags[@intFromEnum(node)]) { - .typedef => @panic("TODO"), + .typedef => {}, .struct_decl_two, .union_decl_two, @@ -309,8 +309,46 @@ fn transDecl(c: *Context, scope: *Scope, decl: NodeIndex) !void { } } -fn transTypeDef(_: *Context, _: *Scope, _: NodeIndex) Error!void { - @panic("TODO"); +fn transTypeDef(c: *Context, scope: *Scope, typedef_decl: NodeIndex) Error!void { + const ty = c.tree.nodes.items(.ty)[@intFromEnum(typedef_decl)]; + const data = c.tree.nodes.items(.data)[@intFromEnum(typedef_decl)]; + + const toplevel = scope.id == .root; + const bs: *Scope.Block = if (!toplevel) try scope.findBlockScope(c) else undefined; + + var name: []const u8 = c.tree.tokSlice(data.decl.name); + try c.typedefs.put(c.gpa, name, {}); + + if (!toplevel) name = try bs.makeMangledName(c, name); + + const typedef_loc = data.decl.name; + const init_node = transType(c, scope, ty, .standard, typedef_loc) catch |err| switch (err) { + error.UnsupportedType => { + return failDecl(c, typedef_loc, name, "unable to resolve typedef child type", .{}); + }, + error.OutOfMemory => |e| return e, + }; + + const payload = try c.arena.create(ast.Payload.SimpleVarDecl); + payload.* = .{ + .base = .{ .tag = ([2]ZigTag{ .var_simple, .pub_var_simple })[@intFromBool(toplevel)] }, + .data = .{ + .name = name, + .init = init_node, + }, + }; + const node = ZigNode.initPayload(&payload.base); + + if (toplevel) { + try addTopLevelDecl(c, name, node); + } else { + try scope.appendNode(node); + if (node.tag() != .pub_var_simple) { + try bs.discardVariable(c, name); + } + } + + } fn mangleWeakGlobalName(c: *Context, want_name: []const u8) ![]const u8 { From 93a502cb2f5a38d0a94ebccd1be523bc71f6a00b Mon Sep 17 00:00:00 2001 From: Evan Haas Date: Tue, 30 Jul 2024 21:31:34 -0700 Subject: [PATCH 081/266] aro_translate_c: move simple function prototype test to manifest --- test/cases/translate_c/simple function prototypes.c | 8 ++++++++ test/translate_c.zig | 8 -------- 2 files changed, 8 insertions(+), 8 deletions(-) create mode 100644 test/cases/translate_c/simple function prototypes.c diff --git a/test/cases/translate_c/simple function prototypes.c b/test/cases/translate_c/simple function prototypes.c new file mode 100644 index 0000000000..ee1e2bad32 --- /dev/null +++ b/test/cases/translate_c/simple function prototypes.c @@ -0,0 +1,8 @@ +void __attribute__((noreturn)) foo(void); +int bar(void); + +// translate-c +// c_frontend=clang,aro +// +// pub extern fn foo() noreturn; +// pub extern fn bar() c_int; diff --git a/test/translate_c.zig b/test/translate_c.zig index 772129be9b..d2ed936434 100644 --- a/test/translate_c.zig +++ b/test/translate_c.zig @@ -644,14 +644,6 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\pub export fn my_fn() linksection("NEAR,.data") void {} }); - cases.add("simple function prototypes", - \\void __attribute__((noreturn)) foo(void); - \\int bar(void); - , &[_][]const u8{ - \\pub extern fn foo() noreturn; - \\pub extern fn bar() c_int; - }); - cases.add("simple var decls", \\void foo(void) { \\ int a; From b3f576993087f90a249b75f8e72cb95974633eb4 Mon Sep 17 00:00:00 2001 From: Evan Haas Date: Tue, 30 Jul 2024 23:30:10 -0700 Subject: [PATCH 082/266] aro_translate_c: handle opaque struct defs in prototypes --- lib/compiler/aro_translate_c.zig | 5 +++++ .../cases/translate_c/struct prototype used in func.c | 10 ++++++++++ test/translate_c.zig | 11 ----------- 3 files changed, 15 insertions(+), 11 deletions(-) create mode 100644 test/cases/translate_c/struct prototype used in func.c diff --git a/lib/compiler/aro_translate_c.zig b/lib/compiler/aro_translate_c.zig index 5665c3b2e6..cec10a301a 100644 --- a/lib/compiler/aro_translate_c.zig +++ b/lib/compiler/aro_translate_c.zig @@ -398,6 +398,11 @@ fn transRecordDecl(c: *Context, scope: *Scope, record_ty: Type) Error!void { const is_pub = toplevel and !is_unnamed; const init_node = blk: { + if (record_decl.isIncomplete()) { + try c.opaque_demotes.put(c.gpa, @intFromPtr(record_decl), {}); + break :blk ZigTag.opaque_literal.init(); + } + var fields = try std.ArrayList(ast.Payload.Record.Field).initCapacity(c.gpa, record_decl.fields.len); defer fields.deinit(); diff --git a/test/cases/translate_c/struct prototype used in func.c b/test/cases/translate_c/struct prototype used in func.c new file mode 100644 index 0000000000..b688e6e742 --- /dev/null +++ b/test/cases/translate_c/struct prototype used in func.c @@ -0,0 +1,10 @@ +struct Foo; +struct Foo *some_func(struct Foo *foo, int x); + +// translate-c +// c_frontend=clang,aro +// +// pub const struct_Foo = opaque {}; +// pub extern fn some_func(foo: ?*struct_Foo, x: c_int) ?*struct_Foo; +// +// pub const Foo = struct_Foo; diff --git a/test/translate_c.zig b/test/translate_c.zig index d2ed936434..5c74110749 100644 --- a/test/translate_c.zig +++ b/test/translate_c.zig @@ -879,17 +879,6 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\pub const Foo = struct_Foo; }); - cases.add("struct prototype used in func", - \\struct Foo; - \\struct Foo *some_func(struct Foo *foo, int x); - , &[_][]const u8{ - \\pub const struct_Foo = opaque {}; - , - \\pub extern fn some_func(foo: ?*struct_Foo, x: c_int) ?*struct_Foo; - , - \\pub const Foo = struct_Foo; - }); - cases.add("#define an unsigned integer literal", \\#define CHANNEL_COUNT 24 , &[_][]const u8{ From 6c632d52f903b97d28210a6a4155eee8e6704d33 Mon Sep 17 00:00:00 2001 From: Evan Haas Date: Tue, 30 Jul 2024 23:30:25 -0700 Subject: [PATCH 083/266] aro_translate_c: move noreturn test to manifest --- test/cases/translate_c/noreturn attribute.c | 6 ++++++ test/translate_c.zig | 6 ------ 2 files changed, 6 insertions(+), 6 deletions(-) create mode 100644 test/cases/translate_c/noreturn attribute.c diff --git a/test/cases/translate_c/noreturn attribute.c b/test/cases/translate_c/noreturn attribute.c new file mode 100644 index 0000000000..9564fd0092 --- /dev/null +++ b/test/cases/translate_c/noreturn attribute.c @@ -0,0 +1,6 @@ +void foo(void) __attribute__((noreturn)); + +// translate-c +// c_frontend=aro,clang +// +// pub extern fn foo() noreturn; diff --git a/test/translate_c.zig b/test/translate_c.zig index 5c74110749..3b4b80920e 100644 --- a/test/translate_c.zig +++ b/test/translate_c.zig @@ -774,12 +774,6 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\} }); - cases.add("noreturn attribute", - \\void foo(void) __attribute__((noreturn)); - , &[_][]const u8{ - \\pub extern fn foo() noreturn; - }); - cases.add("always_inline attribute", \\__attribute__((always_inline)) int foo() { \\ return 5; From aa5a1105e838fbf2f9f918c2a544be6fa0c35e22 Mon Sep 17 00:00:00 2001 From: Evan Haas Date: Wed, 31 Jul 2024 09:22:25 -0700 Subject: [PATCH 084/266] aro_translate_c: do not translate atomic types --- lib/compiler/aro_translate_c.zig | 24 ++++++++++++++++++++++++ test/cases/translate_c/atomic types.c | 8 ++++++++ 2 files changed, 32 insertions(+) create mode 100644 test/cases/translate_c/atomic types.c diff --git a/lib/compiler/aro_translate_c.zig b/lib/compiler/aro_translate_c.zig index cec10a301a..9de4ec52d4 100644 --- a/lib/compiler/aro_translate_c.zig +++ b/lib/compiler/aro_translate_c.zig @@ -78,6 +78,17 @@ fn addTopLevelDecl(c: *Context, name: []const u8, decl_node: ZigNode) !void { } } +fn fail( + c: *Context, + err: anytype, + source_loc: TokenIndex, + comptime format: []const u8, + args: anytype, +) (@TypeOf(err) || error{OutOfMemory}) { + try warn(c, &c.global_scope.base, source_loc, format, args); + return err; +} + fn failDecl(c: *Context, loc: TokenIndex, name: []const u8, comptime format: []const u8, args: anytype) Error!void { // location // pub const name = @compileError(msg); @@ -687,8 +698,21 @@ fn transEnumDecl(c: *Context, scope: *Scope, enum_decl: *const Type.Enum, field_ } } +fn getTypeStr(c: *Context, ty: Type) ![]const u8 { + var buf: std.ArrayListUnmanaged(u8) = .{}; + defer buf.deinit(c.gpa); + const w = buf.writer(c.gpa); + try ty.print(c.mapper, c.comp.langopts, w); + return c.arena.dupe(u8, buf.items); +} + fn transType(c: *Context, scope: *Scope, raw_ty: Type, qual_handling: Type.QualHandling, source_loc: TokenIndex) TypeError!ZigNode { const ty = raw_ty.canonicalize(qual_handling); + if (ty.qual.atomic) { + const type_name = try getTypeStr(c, ty); + return fail(c, error.UnsupportedType, source_loc, "unsupported type: '{s}'", .{type_name}); + } + switch (ty.specifier) { .void => return ZigTag.type.create(c.arena, "anyopaque"), .bool => return ZigTag.type.create(c.arena, "bool"), diff --git a/test/cases/translate_c/atomic types.c b/test/cases/translate_c/atomic types.c new file mode 100644 index 0000000000..ad1af598c4 --- /dev/null +++ b/test/cases/translate_c/atomic types.c @@ -0,0 +1,8 @@ +typedef _Atomic(int) AtomicInt; + +// translate-c +// target=x86_64-linux +// c_frontend=aro +// +// tmp.c:1:22: warning: unsupported type: '_Atomic(int)' +// pub const AtomicInt = @compileError("unable to resolve typedef child type"); From 1cc74f3cae32bc5c002868d7d53af4e14f5a9ce6 Mon Sep 17 00:00:00 2001 From: Evan Haas Date: Wed, 31 Jul 2024 10:33:44 -0700 Subject: [PATCH 085/266] aro_translate_c: fix formatting --- lib/compiler/aro_translate_c.zig | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/compiler/aro_translate_c.zig b/lib/compiler/aro_translate_c.zig index 9de4ec52d4..ad7584c726 100644 --- a/lib/compiler/aro_translate_c.zig +++ b/lib/compiler/aro_translate_c.zig @@ -358,8 +358,6 @@ fn transTypeDef(c: *Context, scope: *Scope, typedef_decl: NodeIndex) Error!void try bs.discardVariable(c, name); } } - - } fn mangleWeakGlobalName(c: *Context, want_name: []const u8) ![]const u8 { From 00089347458b4c2544871666cc09570642b0d5b1 Mon Sep 17 00:00:00 2001 From: David Rubin Date: Mon, 29 Jul 2024 03:28:30 -0700 Subject: [PATCH 086/266] riscv: implement `@divExact` --- src/arch/riscv64/CodeGen.zig | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/arch/riscv64/CodeGen.zig b/src/arch/riscv64/CodeGen.zig index 4fd47a43ff..4815daf5eb 100644 --- a/src/arch/riscv64/CodeGen.zig +++ b/src/arch/riscv64/CodeGen.zig @@ -1510,6 +1510,7 @@ fn genBody(func: *Func, body: []const Air.Inst.Index) InnerError!void { .mul, .mul_wrap, .div_trunc, + .div_exact, .rem, .shl, .shl_exact, @@ -1533,7 +1534,6 @@ fn genBody(func: *Func, body: []const Air.Inst.Index) InnerError!void { .mod, .div_float, .div_floor, - .div_exact, => return func.fail("TODO: {s}", .{@tagName(tag)}), .sqrt, @@ -2563,10 +2563,12 @@ fn genBinOp( .mul_wrap, .rem, .div_trunc, + .div_exact, => { switch (tag) { .rem, .div_trunc, + .div_exact, => { if (!math.isPowerOfTwo(bit_size)) { try func.truncateRegister(lhs_ty, lhs_reg); @@ -2576,7 +2578,7 @@ fn genBinOp( else => { if (!math.isPowerOfTwo(bit_size)) return func.fail( - "TODO: genBinOp verify {s} non-pow 2, found {}", + "TODO: genBinOp verify if needs to truncate {s} non-pow 2, found {}", .{ @tagName(tag), bit_size }, ); }, @@ -2604,7 +2606,7 @@ fn genBinOp( 8, 16, 32 => if (is_unsigned) .remuw else .remw, else => if (is_unsigned) .remu else .rem, }, - .div_trunc => switch (bit_size) { + .div_trunc, .div_exact => switch (bit_size) { 8, 16, 32 => if (is_unsigned) .divuw else .divw, else => if (is_unsigned) .divu else .div, }, From 9c7aade488c94ddd84477a6d29162ba6b03ce800 Mon Sep 17 00:00:00 2001 From: David Rubin Date: Wed, 31 Jul 2024 11:43:47 -0700 Subject: [PATCH 087/266] riscv: fix `.got` symbol loading --- src/arch/riscv64/Emit.zig | 3 +++ src/link/Elf.zig | 31 ++++++++++++++++++++++++++++++- src/link/Elf/Atom.zig | 20 ++++++++++++++++++-- src/link/Elf/relocation.zig | 4 ++++ 4 files changed, 55 insertions(+), 3 deletions(-) diff --git a/src/arch/riscv64/Emit.zig b/src/arch/riscv64/Emit.zig index 6c04988e78..beb232b776 100644 --- a/src/arch/riscv64/Emit.zig +++ b/src/arch/riscv64/Emit.zig @@ -63,6 +63,9 @@ pub fn emitMir(emit: *Emit) Error!void { hi_r_type = Elf.R_ZIG_GOT_HI20; lo_r_type = Elf.R_ZIG_GOT_LO12; + } else if (sym.flags.needs_got) { + hi_r_type = Elf.R_GOT_HI20_STATIC; // TODO: rework this #20887 + lo_r_type = Elf.R_GOT_LO12_I_STATIC; // TODO: rework this #20887 } try atom_ptr.addReloc(elf_file, .{ diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 982015f61f..469e8da345 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -6059,11 +6059,40 @@ const RelaSection = struct { }; const RelaSectionTable = std.AutoArrayHashMapUnmanaged(u32, RelaSection); -// TODO: add comptime check we don't clobber any reloc for any ISA pub const R_ZIG_GOT32: u32 = 0xff00; pub const R_ZIG_GOTPCREL: u32 = 0xff01; pub const R_ZIG_GOT_HI20: u32 = 0xff02; pub const R_ZIG_GOT_LO12: u32 = 0xff03; +pub const R_GOT_HI20_STATIC: u32 = 0xff04; +pub const R_GOT_LO12_I_STATIC: u32 = 0xff05; + +// Comptime asserts that no Zig relocs overlap with another ISA's reloc number +comptime { + const zig_relocs = .{ + R_ZIG_GOT32, + R_ZIG_GOT_HI20, + R_ZIG_GOT_LO12, + R_ZIG_GOTPCREL, + R_GOT_HI20_STATIC, + R_GOT_LO12_I_STATIC, + }; + + const other_relocs = .{ + elf.R_X86_64, + elf.R_AARCH64, + elf.R_RISCV, + elf.R_PPC64, + }; + + @setEvalBranchQuota(@min(other_relocs.len * zig_relocs.len * 256, 6200)); + for (other_relocs) |relocs| { + for (@typeInfo(relocs).Enum.fields) |reloc| { + for (zig_relocs) |zig_reloc| { + assert(reloc.value != zig_reloc); + } + } + } +} fn defaultEntrySymbolName(cpu_arch: std.Target.Cpu.Arch) []const u8 { return switch (cpu_arch) { diff --git a/src/link/Elf/Atom.zig b/src/link/Elf/Atom.zig index 30285f204a..b91b546103 100644 --- a/src/link/Elf/Atom.zig +++ b/src/link/Elf/Atom.zig @@ -2016,6 +2016,10 @@ const riscv = struct { assert(symbol.flags.has_zig_got); }, + Elf.R_GOT_HI20_STATIC, + Elf.R_GOT_LO12_I_STATIC, + => symbol.flags.needs_got = true, + else => try atom.reportUnhandledRelocError(rel, elf_file), }, } @@ -2161,16 +2165,28 @@ const riscv = struct { // Zig custom relocations Elf.R_ZIG_GOT_HI20 => { assert(target.flags.has_zig_got); - const disp: u32 = @bitCast(math.cast(i32, G + ZIG_GOT + A) orelse return error.Overflow); + const disp: u32 = @bitCast(math.cast(i32, ZIG_GOT + A) orelse return error.Overflow); riscv_util.writeInstU(code[r_offset..][0..4], disp); }, Elf.R_ZIG_GOT_LO12 => { assert(target.flags.has_zig_got); - const value: u32 = @bitCast(math.cast(i32, G + ZIG_GOT + A) orelse return error.Overflow); + const value: u32 = @bitCast(math.cast(i32, ZIG_GOT + A) orelse return error.Overflow); riscv_util.writeInstI(code[r_offset..][0..4], value); }, + Elf.R_GOT_HI20_STATIC => { + assert(target.flags.has_got); + const disp: u32 = @bitCast(math.cast(i32, G + GOT + A) orelse return error.Overflow); + riscv_util.writeInstU(code[r_offset..][0..4], disp); + }, + + Elf.R_GOT_LO12_I_STATIC => { + assert(target.flags.has_got); + const disp: u32 = @bitCast(math.cast(i32, G + GOT + A) orelse return error.Overflow); + riscv_util.writeInstI(code[r_offset..][0..4], disp); + }, + else => try atom.reportUnhandledRelocError(rel, elf_file), }, } diff --git a/src/link/Elf/relocation.zig b/src/link/Elf/relocation.zig index 3c8afa3c12..5f6810d6f9 100644 --- a/src/link/Elf/relocation.zig +++ b/src/link/Elf/relocation.zig @@ -115,6 +115,10 @@ fn formatRelocType( switch (r_type) { Elf.R_ZIG_GOT32 => try writer.writeAll("R_ZIG_GOT32"), Elf.R_ZIG_GOTPCREL => try writer.writeAll("R_ZIG_GOTPCREL"), + Elf.R_ZIG_GOT_HI20 => try writer.writeAll("R_ZIG_GOT_HI20"), + Elf.R_ZIG_GOT_LO12 => try writer.writeAll("R_ZIG_GOT_LO12"), + Elf.R_GOT_HI20_STATIC => try writer.writeAll("R_GOT_HI20_STATIC"), + Elf.R_GOT_LO12_I_STATIC => try writer.writeAll("R_GOT_LO12_I_STATIC"), else => switch (ctx.cpu_arch) { .x86_64 => try writer.print("R_X86_64_{s}", .{@tagName(@as(elf.R_X86_64, @enumFromInt(r_type)))}), .aarch64 => try writer.print("R_AARCH64_{s}", .{@tagName(@as(elf.R_AARCH64, @enumFromInt(r_type)))}), From 2b8a71489a24649342e797f609fc6bb1b141a422 Mon Sep 17 00:00:00 2001 From: David Rubin Date: Wed, 31 Jul 2024 11:46:01 -0700 Subject: [PATCH 088/266] start: remove riscv condition --- lib/std/start.zig | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/lib/std/start.zig b/lib/std/start.zig index 8d55c178e4..61e41370b4 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -465,21 +465,18 @@ fn posixCallMainAndExit(argc_argv_ptr: [*]usize) callconv(.C) noreturn { // to ask for more stack space. expandStackSize(phdrs); - // Disabled with the riscv backend because it cannot handle this code yet. - if (builtin.zig_backend != .stage2_riscv64) { - const opt_init_array_start = @extern([*]*const fn () callconv(.C) void, .{ - .name = "__init_array_start", - .linkage = .weak, - }); - const opt_init_array_end = @extern([*]*const fn () callconv(.C) void, .{ - .name = "__init_array_end", - .linkage = .weak, - }); - if (opt_init_array_start) |init_array_start| { - const init_array_end = opt_init_array_end.?; - const slice = init_array_start[0 .. init_array_end - init_array_start]; - for (slice) |func| func(); - } + const opt_init_array_start = @extern([*]*const fn () callconv(.C) void, .{ + .name = "__init_array_start", + .linkage = .weak, + }); + const opt_init_array_end = @extern([*]*const fn () callconv(.C) void, .{ + .name = "__init_array_end", + .linkage = .weak, + }); + if (opt_init_array_start) |init_array_start| { + const init_array_end = opt_init_array_end.?; + const slice = init_array_start[0 .. init_array_end - init_array_start]; + for (slice) |func| func(); } } From c08effc20abe2595ba5c83e25b78c274ec9c58ec Mon Sep 17 00:00:00 2001 From: David Rubin Date: Wed, 31 Jul 2024 14:00:46 -0700 Subject: [PATCH 089/266] riscv: implement non-pow2 indirect loads --- src/arch/riscv64/CodeGen.zig | 8 ++++---- test/behavior/defer.zig | 1 - 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/arch/riscv64/CodeGen.zig b/src/arch/riscv64/CodeGen.zig index 4fd47a43ff..46140b4727 100644 --- a/src/arch/riscv64/CodeGen.zig +++ b/src/arch/riscv64/CodeGen.zig @@ -6855,10 +6855,10 @@ fn genSetReg(func: *Func, ty: Type, reg: Register, src_mcv: MCValue) InnerError! else => return std.debug.panic("TODO: genSetReg for float size {d}", .{abi_size}), }, .int => switch (abi_size) { - 1 => .lb, - 2 => .lh, - 4 => .lw, - 8 => .ld, + 1...1 => .lb, + 2...2 => .lh, + 3...4 => .lw, + 5...8 => .ld, else => return std.debug.panic("TODO: genSetReg for int size {d}", .{abi_size}), }, .vector => { diff --git a/test/behavior/defer.zig b/test/behavior/defer.zig index 219e88b554..64bd1a5e0d 100644 --- a/test/behavior/defer.zig +++ b/test/behavior/defer.zig @@ -94,7 +94,6 @@ test "mixing normal and error defers" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; try expect(runSomeErrorDefers(true) catch unreachable); try expect(result[0] == 'c'); From 377e8579f9539c56fc5988c9a452a01438af89f3 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 31 Jul 2024 11:51:19 -0700 Subject: [PATCH 090/266] std.zig.tokenizer: simplify I pointed a fuzzer at the tokenizer and it crashed immediately. Upon inspection, I was dissatisfied with the implementation. This commit removes several mechanisms: * Removes the "invalid byte" compile error note. * Dramatically simplifies tokenizer recovery by making recovery always occur at newlines, and never otherwise. * Removes UTF-8 validation. * Moves some character validation logic to `std.zig.parseCharLiteral`. Removing UTF-8 validation is a regression of #663, however, the existing implementation was already buggy. When adding this functionality back, it must be fuzz-tested while checking the property that it matches an independent Unicode validation implementation on the same file. While we're at it, fuzzing should check the other properties of that proposal, such as no ASCII control characters existing inside the source code. Other changes included in this commit: * Deprecate `std.unicode.utf8Decode` and its WTF-8 counterpart. This function has an awkward API that is too easy to misuse. * Make `utf8Decode2` and friends use arrays as parameters, eliminating a runtime assertion in favor of using the type system. After this commit, the crash found by fuzzing, which was "\x07\xd5\x80\xc3=o\xda|a\xfc{\x9a\xec\x91\xdf\x0f\\\x1a^\xbe;\x8c\xbf\xee\xea" no longer causes a crash. However, I did not feel the need to add this test case because the simplified logic eradicates most crashes of this nature. --- lib/std/unicode.zig | 33 +- lib/std/zig/Ast.zig | 2 +- lib/std/zig/AstGen.zig | 15 +- lib/std/zig/parser_test.zig | 1 - lib/std/zig/string_literal.zig | 24 +- lib/std/zig/tokenizer.zig | 536 ++++++------------ src/Package/Manifest.zig | 3 + test/cases/compile_errors/empty_char_lit.zig | 9 + .../invalid_legacy_unicode_escape.zig | 3 +- .../compile_errors/invalid_unicode_escape.zig | 3 +- .../normal_string_with_newline.zig | 3 +- test/compile_errors.zig | 18 +- 12 files changed, 246 insertions(+), 404 deletions(-) create mode 100644 test/cases/compile_errors/empty_char_lit.zig diff --git a/lib/std/unicode.zig b/lib/std/unicode.zig index bab075114d..a8fa1454a5 100644 --- a/lib/std/unicode.zig +++ b/lib/std/unicode.zig @@ -95,16 +95,13 @@ pub inline fn utf8EncodeComptime(comptime c: u21) [ const Utf8DecodeError = Utf8Decode2Error || Utf8Decode3Error || Utf8Decode4Error; -/// Decodes the UTF-8 codepoint encoded in the given slice of bytes. -/// bytes.len must be equal to utf8ByteSequenceLength(bytes[0]) catch unreachable. -/// If you already know the length at comptime, you can call one of -/// utf8Decode2,utf8Decode3,utf8Decode4 directly instead of this function. +/// Deprecated. This function has an awkward API that is too easy to use incorrectly. pub fn utf8Decode(bytes: []const u8) Utf8DecodeError!u21 { return switch (bytes.len) { - 1 => @as(u21, bytes[0]), - 2 => utf8Decode2(bytes), - 3 => utf8Decode3(bytes), - 4 => utf8Decode4(bytes), + 1 => bytes[0], + 2 => utf8Decode2(bytes[0..2].*), + 3 => utf8Decode3(bytes[0..3].*), + 4 => utf8Decode4(bytes[0..4].*), else => unreachable, }; } @@ -113,8 +110,7 @@ const Utf8Decode2Error = error{ Utf8ExpectedContinuation, Utf8OverlongEncoding, }; -pub fn utf8Decode2(bytes: []const u8) Utf8Decode2Error!u21 { - assert(bytes.len == 2); +pub fn utf8Decode2(bytes: [2]u8) Utf8Decode2Error!u21 { assert(bytes[0] & 0b11100000 == 0b11000000); var value: u21 = bytes[0] & 0b00011111; @@ -130,7 +126,7 @@ pub fn utf8Decode2(bytes: []const u8) Utf8Decode2Error!u21 { const Utf8Decode3Error = Utf8Decode3AllowSurrogateHalfError || error{ Utf8EncodesSurrogateHalf, }; -pub fn utf8Decode3(bytes: []const u8) Utf8Decode3Error!u21 { +pub fn utf8Decode3(bytes: [3]u8) Utf8Decode3Error!u21 { const value = try utf8Decode3AllowSurrogateHalf(bytes); if (0xd800 <= value and value <= 0xdfff) return error.Utf8EncodesSurrogateHalf; @@ -142,8 +138,7 @@ const Utf8Decode3AllowSurrogateHalfError = error{ Utf8ExpectedContinuation, Utf8OverlongEncoding, }; -pub fn utf8Decode3AllowSurrogateHalf(bytes: []const u8) Utf8Decode3AllowSurrogateHalfError!u21 { - assert(bytes.len == 3); +pub fn utf8Decode3AllowSurrogateHalf(bytes: [3]u8) Utf8Decode3AllowSurrogateHalfError!u21 { assert(bytes[0] & 0b11110000 == 0b11100000); var value: u21 = bytes[0] & 0b00001111; @@ -165,8 +160,7 @@ const Utf8Decode4Error = error{ Utf8OverlongEncoding, Utf8CodepointTooLarge, }; -pub fn utf8Decode4(bytes: []const u8) Utf8Decode4Error!u21 { - assert(bytes.len == 4); +pub fn utf8Decode4(bytes: [4]u8) Utf8Decode4Error!u21 { assert(bytes[0] & 0b11111000 == 0b11110000); var value: u21 = bytes[0] & 0b00000111; @@ -1637,12 +1631,13 @@ pub fn wtf8Encode(c: u21, out: []u8) error{CodepointTooLarge}!u3 { const Wtf8DecodeError = Utf8Decode2Error || Utf8Decode3AllowSurrogateHalfError || Utf8Decode4Error; +/// Deprecated. This function has an awkward API that is too easy to use incorrectly. pub fn wtf8Decode(bytes: []const u8) Wtf8DecodeError!u21 { return switch (bytes.len) { - 1 => @as(u21, bytes[0]), - 2 => utf8Decode2(bytes), - 3 => utf8Decode3AllowSurrogateHalf(bytes), - 4 => utf8Decode4(bytes), + 1 => bytes[0], + 2 => utf8Decode2(bytes[0..2].*), + 3 => utf8Decode3AllowSurrogateHalf(bytes[0..3].*), + 4 => utf8Decode4(bytes[0..4].*), else => unreachable, }; } diff --git a/lib/std/zig/Ast.zig b/lib/std/zig/Ast.zig index f55d78b6ce..1f734cef63 100644 --- a/lib/std/zig/Ast.zig +++ b/lib/std/zig/Ast.zig @@ -69,7 +69,7 @@ pub fn parse(gpa: Allocator, source: [:0]const u8, mode: Mode) Allocator.Error!A const token = tokenizer.next(); try tokens.append(gpa, .{ .tag = token.tag, - .start = @as(u32, @intCast(token.loc.start)), + .start = @intCast(token.loc.start), }); if (token.tag == .eof) break; } diff --git a/lib/std/zig/AstGen.zig b/lib/std/zig/AstGen.zig index a6be743c2b..c24aa6d063 100644 --- a/lib/std/zig/AstGen.zig +++ b/lib/std/zig/AstGen.zig @@ -11351,6 +11351,9 @@ fn failWithStrLitError(astgen: *AstGen, err: std.zig.string_literal.Error, token .{raw_string[bad_index]}, ); }, + .empty_char_literal => { + return astgen.failOff(token, offset, "empty character literal", .{}); + }, } } @@ -13820,21 +13823,9 @@ fn lowerAstErrors(astgen: *AstGen) !void { var msg: std.ArrayListUnmanaged(u8) = .{}; defer msg.deinit(gpa); - const token_starts = tree.tokens.items(.start); - const token_tags = tree.tokens.items(.tag); - var notes: std.ArrayListUnmanaged(u32) = .{}; defer notes.deinit(gpa); - const tok = parse_err.token + @intFromBool(parse_err.token_is_prev); - if (token_tags[tok] == .invalid) { - const bad_off: u32 = @intCast(tree.tokenSlice(tok).len); - const byte_abs = token_starts[tok] + bad_off; - try notes.append(gpa, try astgen.errNoteTokOff(tok, bad_off, "invalid byte: '{'}'", .{ - std.zig.fmtEscapes(tree.source[byte_abs..][0..1]), - })); - } - for (tree.errors[1..]) |note| { if (!note.is_note) break; diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig index 5130ce4037..530aa924d0 100644 --- a/lib/std/zig/parser_test.zig +++ b/lib/std/zig/parser_test.zig @@ -6061,7 +6061,6 @@ test "recovery: invalid container members" { , &[_]Error{ .expected_expr, .expected_comma_after_field, - .expected_type_expr, .expected_semi_after_stmt, }); } diff --git a/lib/std/zig/string_literal.zig b/lib/std/zig/string_literal.zig index c160d16b07..6917809837 100644 --- a/lib/std/zig/string_literal.zig +++ b/lib/std/zig/string_literal.zig @@ -1,6 +1,5 @@ const std = @import("../std.zig"); const assert = std.debug.assert; -const utf8Decode = std.unicode.utf8Decode; const utf8Encode = std.unicode.utf8Encode; pub const ParseError = error{ @@ -37,12 +36,16 @@ pub const Error = union(enum) { expected_single_quote: usize, /// The character at this index cannot be represented without an escape sequence. invalid_character: usize, + /// `''`. Not returned for string literals. + empty_char_literal, }; -/// Only validates escape sequence characters. -/// Slice must be valid utf8 starting and ending with "'" and exactly one codepoint in between. +/// Asserts the slice starts and ends with single-quotes. +/// Returns an error if there is not exactly one UTF-8 codepoint in between. pub fn parseCharLiteral(slice: []const u8) ParsedCharLiteral { - assert(slice.len >= 3 and slice[0] == '\'' and slice[slice.len - 1] == '\''); + if (slice.len < 3) return .{ .failure = .empty_char_literal }; + assert(slice[0] == '\''); + assert(slice[slice.len - 1] == '\''); switch (slice[1]) { '\\' => { @@ -55,7 +58,18 @@ pub fn parseCharLiteral(slice: []const u8) ParsedCharLiteral { }, 0 => return .{ .failure = .{ .invalid_character = 1 } }, else => { - const codepoint = utf8Decode(slice[1 .. slice.len - 1]) catch unreachable; + const inner = slice[1 .. slice.len - 1]; + const n = std.unicode.utf8ByteSequenceLength(inner[0]) catch return .{ + .failure = .{ .invalid_unicode_codepoint = 1 }, + }; + if (inner.len > n) return .{ .failure = .{ .expected_single_quote = 1 + n } }; + const codepoint = switch (n) { + 1 => inner[0], + 2 => std.unicode.utf8Decode2(inner[0..2].*), + 3 => std.unicode.utf8Decode3(inner[0..3].*), + 4 => std.unicode.utf8Decode4(inner[0..4].*), + else => unreachable, + } catch return .{ .failure = .{ .invalid_unicode_codepoint = 1 } }; return .{ .success = codepoint }; }, } diff --git a/lib/std/zig/tokenizer.zig b/lib/std/zig/tokenizer.zig index 36cbf9a856..32e11b1b9a 100644 --- a/lib/std/zig/tokenizer.zig +++ b/lib/std/zig/tokenizer.zig @@ -320,7 +320,7 @@ pub const Token = struct { pub fn symbol(tag: Tag) []const u8 { return tag.lexeme() orelse switch (tag) { - .invalid => "invalid bytes", + .invalid => "invalid token", .identifier => "an identifier", .string_literal, .multiline_string_literal_line => "a string literal", .char_literal => "a character literal", @@ -338,22 +338,22 @@ pub const Tokenizer = struct { buffer: [:0]const u8, index: usize, - /// For debugging purposes + /// For debugging purposes. pub fn dump(self: *Tokenizer, token: *const Token) void { std.debug.print("{s} \"{s}\"\n", .{ @tagName(token.tag), self.buffer[token.loc.start..token.loc.end] }); } pub fn init(buffer: [:0]const u8) Tokenizer { - // Skip the UTF-8 BOM if present - const src_start: usize = if (std.mem.startsWith(u8, buffer, "\xEF\xBB\xBF")) 3 else 0; - return Tokenizer{ + // Skip the UTF-8 BOM if present. + return .{ .buffer = buffer, - .index = src_start, + .index = if (std.mem.startsWith(u8, buffer, "\xEF\xBB\xBF")) 3 else 0, }; } const State = enum { start, + expect_newline, identifier, builtin, string_literal, @@ -361,10 +361,6 @@ pub const Tokenizer = struct { multiline_string_literal_line, char_literal, char_literal_backslash, - char_literal_hex_escape, - char_literal_unicode_escape_saw_u, - char_literal_unicode_escape, - char_literal_end, backslash, equal, bang, @@ -400,32 +396,38 @@ pub const Tokenizer = struct { period_2, period_asterisk, saw_at_sign, + invalid, }; + /// After this returns invalid, it will reset on the next newline, returning tokens starting from there. + /// An eof token will always be returned at the end. pub fn next(self: *Tokenizer) Token { var state: State = .start; - var result = Token{ - .tag = .eof, + var result: Token = .{ + .tag = undefined, .loc = .{ .start = self.index, .end = undefined, }, }; - var seen_escape_digits: usize = undefined; while (true) : (self.index += 1) { const c = self.buffer[self.index]; switch (state) { .start => switch (c) { 0 => { - if (self.index != self.buffer.len) { - result.tag = .invalid; - result.loc.end = self.index; - self.index += 1; - return result; - } - break; + if (self.index == self.buffer.len) return .{ + .tag = .eof, + .loc = .{ + .start = self.index, + .end = self.index, + }, + }; + state = .invalid; }, - ' ', '\n', '\t', '\r' => { + '\r' => { + state = .expect_newline; + }, + ' ', '\n', '\t' => { result.loc.start = self.index + 1; }, '"' => { @@ -434,6 +436,7 @@ pub const Tokenizer = struct { }, '\'' => { state = .char_literal; + result.tag = .char_literal; }, 'a'...'z', 'A'...'Z', '_' => { state = .identifier; @@ -545,14 +548,37 @@ pub const Tokenizer = struct { result.tag = .number_literal; }, else => { - result.tag = .invalid; - result.loc.end = self.index; - self.index += std.unicode.utf8ByteSequenceLength(c) catch 1; - return result; + state = .invalid; }, }, + .expect_newline => switch (c) { + '\n' => { + result.loc.start = self.index + 1; + state = .start; + }, + else => { + state = .invalid; + }, + }, + + .invalid => switch (c) { + 0 => if (self.index == self.buffer.len) { + result.tag = .invalid; + break; + }, + '\n' => { + result.tag = .invalid; + break; + }, + else => continue, + }, + .saw_at_sign => switch (c) { + 0, '\n' => { + result.tag = .invalid; + break; + }, '"' => { result.tag = .identifier; state = .string_literal; @@ -562,8 +588,7 @@ pub const Tokenizer = struct { result.tag = .builtin; }, else => { - result.tag = .invalid; - break; + state = .invalid; }, }, @@ -698,7 +723,7 @@ pub const Tokenizer = struct { }, .identifier => switch (c) { - 'a'...'z', 'A'...'Z', '_', '0'...'9' => {}, + 'a'...'z', 'A'...'Z', '_', '0'...'9' => continue, else => { if (Token.getKeyword(self.buffer[result.loc.start..self.index])) |tag| { result.tag = tag; @@ -707,26 +732,37 @@ pub const Tokenizer = struct { }, }, .builtin => switch (c) { - 'a'...'z', 'A'...'Z', '_', '0'...'9' => {}, + 'a'...'z', 'A'...'Z', '_', '0'...'9' => continue, else => break, }, .backslash => switch (c) { - '\\' => { - state = .multiline_string_literal_line; - }, - else => { + 0 => { result.tag = .invalid; break; }, + '\\' => { + state = .multiline_string_literal_line; + }, + '\n' => { + result.tag = .invalid; + break; + }, + else => { + state = .invalid; + }, }, .string_literal => switch (c) { - 0, '\n' => { - result.tag = .invalid; - result.loc.end = self.index; + 0 => { if (self.index != self.buffer.len) { - self.index += 1; + state = .invalid; + continue; } - return result; + result.tag = .invalid; + break; + }, + '\n' => { + result.tag = .invalid; + break; }, '\\' => { state = .string_literal_backslash; @@ -735,150 +771,74 @@ pub const Tokenizer = struct { self.index += 1; break; }, - else => { - if (self.invalidCharacterLength()) |len| { - result.tag = .invalid; - result.loc.end = self.index; - self.index += len; - return result; - } - - self.index += (std.unicode.utf8ByteSequenceLength(c) catch unreachable) - 1; + 0x01...0x09, 0x0b...0x1f, 0x7f => { + state = .invalid; }, + else => continue, }, .string_literal_backslash => switch (c) { 0, '\n' => { result.tag = .invalid; - result.loc.end = self.index; - if (self.index != self.buffer.len) { - self.index += 1; - } - return result; + break; }, else => { state = .string_literal; - - if (self.invalidCharacterLength()) |len| { - result.tag = .invalid; - result.loc.end = self.index; - self.index += len; - return result; - } - - self.index += (std.unicode.utf8ByteSequenceLength(c) catch unreachable) - 1; }, }, .char_literal => switch (c) { - 0, '\n', '\'' => { - result.tag = .invalid; - result.loc.end = self.index; + 0 => { if (self.index != self.buffer.len) { - self.index += 1; + state = .invalid; + continue; } - return result; + result.tag = .invalid; + break; + }, + '\n' => { + result.tag = .invalid; + break; }, '\\' => { state = .char_literal_backslash; }, - else => { - state = .char_literal_end; - - if (self.invalidCharacterLength()) |len| { - result.tag = .invalid; - result.loc.end = self.index; - self.index += len; - return result; - } - - self.index += (std.unicode.utf8ByteSequenceLength(c) catch unreachable) - 1; - }, - }, - - .char_literal_backslash => switch (c) { - 0, '\n' => { - result.tag = .invalid; - result.loc.end = self.index; - if (self.index != self.buffer.len) { - self.index += 1; - } - return result; - }, - 'x' => { - state = .char_literal_hex_escape; - seen_escape_digits = 0; - }, - 'u' => { - state = .char_literal_unicode_escape_saw_u; - }, - else => { - state = .char_literal_end; - - if (self.invalidCharacterLength()) |len| { - result.tag = .invalid; - result.loc.end = self.index; - self.index += len; - return result; - } - - self.index += (std.unicode.utf8ByteSequenceLength(c) catch unreachable) - 1; - }, - }, - - .char_literal_hex_escape => switch (c) { - '0'...'9', 'a'...'f', 'A'...'F' => { - seen_escape_digits += 1; - if (seen_escape_digits == 2) { - state = .char_literal_end; - } - }, - else => { - result.tag = .invalid; - break; - }, - }, - - .char_literal_unicode_escape_saw_u => switch (c) { - '{' => { - state = .char_literal_unicode_escape; - }, - else => { - result.tag = .invalid; - break; - }, - }, - - .char_literal_unicode_escape => switch (c) { - '0'...'9', 'a'...'f', 'A'...'F' => {}, - '}' => { - state = .char_literal_end; // too many/few digits handled later - }, - else => { - result.tag = .invalid; - break; - }, - }, - - .char_literal_end => switch (c) { '\'' => { - result.tag = .char_literal; self.index += 1; break; }, - else => { + 0x01...0x09, 0x0b...0x1f, 0x7f => { + state = .invalid; + }, + else => continue, + }, + + .char_literal_backslash => switch (c) { + 0 => { + if (self.index != self.buffer.len) { + state = .invalid; + continue; + } result.tag = .invalid; break; }, + '\n' => { + result.tag = .invalid; + break; + }, + 0x01...0x09, 0x0b...0x1f, 0x7f => { + state = .invalid; + }, + else => { + state = .char_literal; + }, }, .multiline_string_literal_line => switch (c) { 0 => { if (self.index != self.buffer.len) { - result.tag = .invalid; - result.loc.end = self.index; - self.index += 1; - return result; + state = .invalid; + continue; } break; }, @@ -886,17 +846,10 @@ pub const Tokenizer = struct { self.index += 1; break; }, - '\t' => {}, - else => { - if (self.invalidCharacterLength()) |len| { - result.tag = .invalid; - result.loc.end = self.index; - self.index += len; - return result; - } - - self.index += (std.unicode.utf8ByteSequenceLength(c) catch unreachable) - 1; + 0x01...0x08, 0x0b...0x1f, 0x7f => { + state = .invalid; }, + else => continue, }, .bang => switch (c) { @@ -1113,12 +1066,16 @@ pub const Tokenizer = struct { .line_comment_start => switch (c) { 0 => { if (self.index != self.buffer.len) { - result.tag = .invalid; - result.loc.end = self.index; - self.index += 1; - return result; + state = .invalid; + continue; } - break; + return .{ + .tag = .eof, + .loc = .{ + .start = self.index, + .end = self.index, + }, + }; }, '/' => { state = .doc_comment_start; @@ -1127,105 +1084,74 @@ pub const Tokenizer = struct { result.tag = .container_doc_comment; state = .doc_comment; }, + '\r' => { + state = .expect_newline; + }, '\n' => { state = .start; result.loc.start = self.index + 1; }, - '\t' => { - state = .line_comment; + 0x01...0x08, 0x0b...0x0c, 0x0e...0x1f, 0x7f => { + state = .invalid; }, else => { state = .line_comment; - - if (self.invalidCharacterLength()) |len| { - result.tag = .invalid; - result.loc.end = self.index; - self.index += len; - return result; - } - - self.index += (std.unicode.utf8ByteSequenceLength(c) catch unreachable) - 1; }, }, .doc_comment_start => switch (c) { + 0, '\n', '\r' => { + result.tag = .doc_comment; + break; + }, '/' => { state = .line_comment; }, - 0 => { - if (self.index != self.buffer.len) { - result.tag = .invalid; - result.loc.end = self.index; - self.index += 1; - return result; - } - result.tag = .doc_comment; - break; - }, - '\n' => { - result.tag = .doc_comment; - break; - }, - '\t' => { - state = .doc_comment; - result.tag = .doc_comment; + 0x01...0x08, 0x0b...0x0c, 0x0e...0x1f, 0x7f => { + state = .invalid; }, else => { state = .doc_comment; result.tag = .doc_comment; - - if (self.invalidCharacterLength()) |len| { - result.tag = .invalid; - result.loc.end = self.index; - self.index += len; - return result; - } - - self.index += (std.unicode.utf8ByteSequenceLength(c) catch unreachable) - 1; }, }, .line_comment => switch (c) { 0 => { if (self.index != self.buffer.len) { - result.tag = .invalid; - result.loc.end = self.index; - self.index += 1; - return result; + state = .invalid; + continue; } - break; + return .{ + .tag = .eof, + .loc = .{ + .start = self.index, + .end = self.index, + }, + }; + }, + '\r' => { + state = .expect_newline; }, '\n' => { state = .start; result.loc.start = self.index + 1; }, - '\t' => {}, - else => { - if (self.invalidCharacterLength()) |len| { - result.tag = .invalid; - result.loc.end = self.index; - self.index += len; - return result; - } - - self.index += (std.unicode.utf8ByteSequenceLength(c) catch unreachable) - 1; + 0x01...0x08, 0x0b...0x0c, 0x0e...0x1f, 0x7f => { + state = .invalid; }, + else => continue, }, .doc_comment => switch (c) { - 0, '\n' => break, - '\t' => {}, - else => { - if (self.invalidCharacterLength()) |len| { - result.tag = .invalid; - result.loc.end = self.index; - self.index += len; - return result; - } - - self.index += (std.unicode.utf8ByteSequenceLength(c) catch unreachable) - 1; + 0, '\n', '\r' => { + break; }, + 0x01...0x08, 0x0b...0x0c, 0x0e...0x1f, 0x7f => { + state = .invalid; + }, + else => continue, }, .int => switch (c) { '.' => state = .int_period, - '_', 'a'...'d', 'f'...'o', 'q'...'z', 'A'...'D', 'F'...'O', 'Q'...'Z', '0'...'9' => {}, + '_', 'a'...'d', 'f'...'o', 'q'...'z', 'A'...'D', 'F'...'O', 'Q'...'Z', '0'...'9' => continue, 'e', 'E', 'p', 'P' => state = .int_exponent, else => break, }, @@ -1249,7 +1175,7 @@ pub const Tokenizer = struct { }, }, .float => switch (c) { - '_', 'a'...'d', 'f'...'o', 'q'...'z', 'A'...'D', 'F'...'O', 'Q'...'Z', '0'...'9' => {}, + '_', 'a'...'d', 'f'...'o', 'q'...'z', 'A'...'D', 'F'...'O', 'Q'...'Z', '0'...'9' => continue, 'e', 'E', 'p', 'P' => state = .float_exponent, else => break, }, @@ -1263,57 +1189,9 @@ pub const Tokenizer = struct { } } - if (result.tag == .eof) { - result.loc.start = self.index; - } - result.loc.end = self.index; return result; } - - fn invalidCharacterLength(self: *Tokenizer) ?u3 { - const c0 = self.buffer[self.index]; - if (std.ascii.isAscii(c0)) { - if (c0 == '\r') { - if (self.index + 1 < self.buffer.len and self.buffer[self.index + 1] == '\n') { - // Carriage returns are *only* allowed just before a linefeed as part of a CRLF pair, otherwise - // they constitute an illegal byte! - return null; - } else { - return 1; - } - } else if (std.ascii.isControl(c0)) { - // ascii control codes are never allowed - // (note that \n was checked before we got here) - return 1; - } - // looks fine to me. - return null; - } else { - // check utf8-encoded character. - const length = std.unicode.utf8ByteSequenceLength(c0) catch return 1; - if (self.index + length > self.buffer.len) { - return @as(u3, @intCast(self.buffer.len - self.index)); - } - const bytes = self.buffer[self.index .. self.index + length]; - switch (length) { - 2 => { - const value = std.unicode.utf8Decode2(bytes) catch return length; - if (value == 0x85) return length; // U+0085 (NEL) - }, - 3 => { - const value = std.unicode.utf8Decode3(bytes) catch return length; - if (value == 0x2028) return length; // U+2028 (LS) - if (value == 0x2029) return length; // U+2029 (PS) - }, - 4 => { - _ = std.unicode.utf8Decode4(bytes) catch return length; - }, - else => unreachable, - } - return null; - } - } }; test "keywords" { @@ -1355,7 +1233,7 @@ test "code point literal with hex escape" { , &.{.char_literal}); try testTokenize( \\'\x1' - , &.{ .invalid, .invalid }); + , &.{.char_literal}); } test "newline in char literal" { @@ -1396,40 +1274,30 @@ test "code point literal with unicode escapes" { // Invalid unicode escapes try testTokenize( \\'\u' - , &.{ .invalid, .invalid }); + , &.{.char_literal}); try testTokenize( \\'\u{{' - , &.{ .invalid, .l_brace, .invalid }); + , &.{.char_literal}); try testTokenize( \\'\u{}' , &.{.char_literal}); try testTokenize( \\'\u{s}' - , &.{ - .invalid, - .identifier, - .r_brace, - .invalid, - }); + , &.{.char_literal}); try testTokenize( \\'\u{2z}' - , &.{ - .invalid, - .identifier, - .r_brace, - .invalid, - }); + , &.{.char_literal}); try testTokenize( \\'\u{4a' - , &.{ .invalid, .invalid }); // 4a is valid + , &.{.char_literal}); // Test old-style unicode literals try testTokenize( \\'\u0333' - , &.{ .invalid, .number_literal, .invalid }); + , &.{.char_literal}); try testTokenize( \\'\U0333' - , &.{ .invalid, .number_literal, .invalid }); + , &.{.char_literal}); } test "code point literal with unicode code point" { @@ -1465,24 +1333,15 @@ test "invalid token characters" { try testTokenize("`", &.{.invalid}); try testTokenize("'c", &.{.invalid}); try testTokenize("'", &.{.invalid}); - try testTokenize("''", &.{.invalid}); + try testTokenize("''", &.{.char_literal}); try testTokenize("'\n'", &.{ .invalid, .invalid }); } test "invalid literal/comment characters" { - try testTokenize("\"\x00\"", &.{ - .invalid, - .invalid, // Incomplete string literal starting after invalid - }); - try testTokenize("//\x00", &.{ - .invalid, - }); - try testTokenize("//\x1f", &.{ - .invalid, - }); - try testTokenize("//\x7f", &.{ - .invalid, - }); + try testTokenize("\"\x00\"", &.{.invalid}); + try testTokenize("//\x00", &.{.invalid}); + try testTokenize("//\x1f", &.{.invalid}); + try testTokenize("//\x7f", &.{.invalid}); } test "utf8" { @@ -1491,46 +1350,24 @@ test "utf8" { } test "invalid utf8" { - try testTokenize("//\x80", &.{ - .invalid, - }); - try testTokenize("//\xbf", &.{ - .invalid, - }); - try testTokenize("//\xf8", &.{ - .invalid, - }); - try testTokenize("//\xff", &.{ - .invalid, - }); - try testTokenize("//\xc2\xc0", &.{ - .invalid, - }); - try testTokenize("//\xe0", &.{ - .invalid, - }); - try testTokenize("//\xf0", &.{ - .invalid, - }); - try testTokenize("//\xf0\x90\x80\xc0", &.{ - .invalid, - }); + try testTokenize("//\x80", &.{}); + try testTokenize("//\xbf", &.{}); + try testTokenize("//\xf8", &.{}); + try testTokenize("//\xff", &.{}); + try testTokenize("//\xc2\xc0", &.{}); + try testTokenize("//\xe0", &.{}); + try testTokenize("//\xf0", &.{}); + try testTokenize("//\xf0\x90\x80\xc0", &.{}); } test "illegal unicode codepoints" { // unicode newline characters.U+0085, U+2028, U+2029 try testTokenize("//\xc2\x84", &.{}); - try testTokenize("//\xc2\x85", &.{ - .invalid, - }); + try testTokenize("//\xc2\x85", &.{}); try testTokenize("//\xc2\x86", &.{}); try testTokenize("//\xe2\x80\xa7", &.{}); - try testTokenize("//\xe2\x80\xa8", &.{ - .invalid, - }); - try testTokenize("//\xe2\x80\xa9", &.{ - .invalid, - }); + try testTokenize("//\xe2\x80\xa8", &.{}); + try testTokenize("//\xe2\x80\xa9", &.{}); try testTokenize("//\xe2\x80\xaa", &.{}); } @@ -1892,8 +1729,8 @@ test "multi line string literal with only 1 backslash" { } test "invalid builtin identifiers" { - try testTokenize("@()", &.{ .invalid, .l_paren, .r_paren }); - try testTokenize("@0()", &.{ .invalid, .number_literal, .l_paren, .r_paren }); + try testTokenize("@()", &.{.invalid}); + try testTokenize("@0()", &.{.invalid}); } test "invalid token with unfinished escape right before eof" { @@ -1921,12 +1758,12 @@ test "saturating operators" { } test "null byte before eof" { - try testTokenize("123 \x00 456", &.{ .number_literal, .invalid, .number_literal }); + try testTokenize("123 \x00 456", &.{ .number_literal, .invalid }); try testTokenize("//\x00", &.{.invalid}); try testTokenize("\\\\\x00", &.{.invalid}); try testTokenize("\x00", &.{.invalid}); try testTokenize("// NUL\x00\n", &.{.invalid}); - try testTokenize("///\x00\n", &.{.invalid}); + try testTokenize("///\x00\n", &.{ .doc_comment, .invalid }); try testTokenize("/// NUL\x00\n", &.{ .doc_comment, .invalid }); } @@ -1936,6 +1773,9 @@ fn testTokenize(source: [:0]const u8, expected_token_tags: []const Token.Tag) !v const token = tokenizer.next(); try std.testing.expectEqual(expected_token_tag, token.tag); } + // Last token should always be eof, even when the last token was invalid, + // in which case the tokenizer is in an invalid state, which can only be + // recovered by opinionated means outside the scope of this implementation. const last_token = tokenizer.next(); try std.testing.expectEqual(Token.Tag.eof, last_token.tag); try std.testing.expectEqual(source.len, last_token.loc.start); diff --git a/src/Package/Manifest.zig b/src/Package/Manifest.zig index d9c39f5ab3..eb82ef039d 100644 --- a/src/Package/Manifest.zig +++ b/src/Package/Manifest.zig @@ -549,6 +549,9 @@ const Parse = struct { .{raw_string[bad_index]}, ); }, + .empty_char_literal => { + try p.appendErrorOff(token, offset, "empty character literal", .{}); + }, } } diff --git a/test/cases/compile_errors/empty_char_lit.zig b/test/cases/compile_errors/empty_char_lit.zig new file mode 100644 index 0000000000..99d80778b1 --- /dev/null +++ b/test/cases/compile_errors/empty_char_lit.zig @@ -0,0 +1,9 @@ +export fn entry() u8 { + return ''; +} + +// error +// backend=stage2 +// target=native +// +// :2:12: error: empty character literal diff --git a/test/cases/compile_errors/invalid_legacy_unicode_escape.zig b/test/cases/compile_errors/invalid_legacy_unicode_escape.zig index cc4e78f6e4..c22d70f3eb 100644 --- a/test/cases/compile_errors/invalid_legacy_unicode_escape.zig +++ b/test/cases/compile_errors/invalid_legacy_unicode_escape.zig @@ -6,5 +6,4 @@ export fn entry() void { // backend=stage2 // target=native // -// :2:15: error: expected expression, found 'invalid bytes' -// :2:18: note: invalid byte: '1' +// :2:17: error: invalid escape character: 'U' diff --git a/test/cases/compile_errors/invalid_unicode_escape.zig b/test/cases/compile_errors/invalid_unicode_escape.zig index 1555f2be80..956b4a37a2 100644 --- a/test/cases/compile_errors/invalid_unicode_escape.zig +++ b/test/cases/compile_errors/invalid_unicode_escape.zig @@ -6,6 +6,5 @@ export fn entry() void { // backend=stage2 // target=native // -// :2:15: error: expected expression, found 'invalid bytes' -// :2:21: note: invalid byte: 'z' +// :2:21: error: expected hex digit or '}', found 'z' diff --git a/test/cases/compile_errors/normal_string_with_newline.zig b/test/cases/compile_errors/normal_string_with_newline.zig index 19e15133ee..f19ce59ec8 100644 --- a/test/cases/compile_errors/normal_string_with_newline.zig +++ b/test/cases/compile_errors/normal_string_with_newline.zig @@ -5,5 +5,4 @@ b"; // backend=stage2 // target=native // -// :1:13: error: expected expression, found 'invalid bytes' -// :1:15: note: invalid byte: '\n' +// :1:13: error: expected expression, found 'invalid token' diff --git a/test/compile_errors.zig b/test/compile_errors.zig index 5c5a574caf..c7a3be8f9f 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -42,8 +42,7 @@ pub fn addCases(ctx: *Cases, b: *std.Build) !void { const case = ctx.obj("isolated carriage return in multiline string literal", b.graph.host); case.addError("const foo = \\\\\test\r\r rogue carriage return\n;", &[_][]const u8{ - ":1:13: error: expected expression, found 'invalid bytes'", - ":1:19: note: invalid byte: '\\r'", + ":1:13: error: expected expression, found 'invalid token'", }); } @@ -179,8 +178,7 @@ pub fn addCases(ctx: *Cases, b: *std.Build) !void { \\ return true; \\} , &[_][]const u8{ - ":1:1: error: expected type expression, found 'invalid bytes'", - ":1:1: note: invalid byte: '\\xff'", + ":1:1: error: expected type expression, found 'invalid token'", }); } @@ -222,8 +220,7 @@ pub fn addCases(ctx: *Cases, b: *std.Build) !void { const case = ctx.obj("invalid byte in string", b.graph.host); case.addError("_ = \"\x01Q\";", &[_][]const u8{ - ":1:5: error: expected expression, found 'invalid bytes'", - ":1:6: note: invalid byte: '\\x01'", + ":1:5: error: expected expression, found 'invalid token'", }); } @@ -231,8 +228,7 @@ pub fn addCases(ctx: *Cases, b: *std.Build) !void { const case = ctx.obj("invalid byte in comment", b.graph.host); case.addError("//\x01Q", &[_][]const u8{ - ":1:1: error: expected type expression, found 'invalid bytes'", - ":1:3: note: invalid byte: '\\x01'", + ":1:1: error: expected type expression, found 'invalid token'", }); } @@ -240,8 +236,7 @@ pub fn addCases(ctx: *Cases, b: *std.Build) !void { const case = ctx.obj("control character in character literal", b.graph.host); case.addError("const c = '\x01';", &[_][]const u8{ - ":1:11: error: expected expression, found 'invalid bytes'", - ":1:12: note: invalid byte: '\\x01'", + ":1:11: error: expected expression, found 'invalid token'", }); } @@ -249,8 +244,7 @@ pub fn addCases(ctx: *Cases, b: *std.Build) !void { const case = ctx.obj("invalid byte at start of token", b.graph.host); case.addError("x = \x00Q", &[_][]const u8{ - ":1:5: error: expected expression, found 'invalid bytes'", - ":1:5: note: invalid byte: '\\x00'", + ":1:5: error: expected expression, found 'invalid token'", }); } } From a7029496d153bd2ba4e91ef561a399cad6d77307 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 31 Jul 2024 14:03:20 -0700 Subject: [PATCH 091/266] remove hard tabs from source code these are illegal according to the spec --- lib/std/crypto/ml_kem.zig | 6 +- lib/std/macho.zig | 9 ++- lib/std/zig/system/darwin/macos.zig | 92 ++++++++++++++--------------- lib/std/zig/system/linux.zig | 60 +++++++++---------- src/link/tapi/yaml/test.zig | 5 +- test/run_translated_c.zig | 12 ++-- test/translate_c.zig | 34 +++++------ 7 files changed, 107 insertions(+), 111 deletions(-) diff --git a/lib/std/crypto/ml_kem.zig b/lib/std/crypto/ml_kem.zig index c3cb5805ee..23ea7bb8ec 100644 --- a/lib/std/crypto/ml_kem.zig +++ b/lib/std/crypto/ml_kem.zig @@ -677,10 +677,10 @@ fn montReduce(x: i32) i16 { // Note gcd(2¹⁶, q) = 1 as q is prime. Write q' := 62209 = q⁻¹ mod R. // First we compute // - // m := ((x mod R) q') mod R + // m := ((x mod R) q') mod R // = x q' mod R - // = int16(x q') - // = int16(int32(x) * int32(q')) + // = int16(x q') + // = int16(int32(x) * int32(q')) // // Note that x q' might be as big as 2³² and could overflow the int32 // multiplication in the last line. However for any int32s a and b, diff --git a/lib/std/macho.zig b/lib/std/macho.zig index d477befbc4..75aa91e536 100644 --- a/lib/std/macho.zig +++ b/lib/std/macho.zig @@ -203,8 +203,7 @@ pub const symtab_command = extern struct { /// local symbols (static and debugging symbols) - grouped by module /// defined external symbols - grouped by module (sorted by name if not lib) /// undefined external symbols (sorted by name if MH_BINDATLOAD is not set, -/// and in order the were seen by the static -/// linker if MH_BINDATLOAD is set) +/// and in order the were seen by the static linker if MH_BINDATLOAD is set) /// In this load command there are offsets and counts to each of the three groups /// of symbols. /// @@ -219,9 +218,9 @@ pub const symtab_command = extern struct { /// shared library. For executable and object modules, which are files /// containing only one module, the information that would be in these three /// tables is determined as follows: -/// table of contents - the defined external symbols are sorted by name -/// module table - the file contains only one module so everything in the -/// file is part of the module. +/// table of contents - the defined external symbols are sorted by name +/// module table - the file contains only one module so everything in the file +/// is part of the module. /// reference symbol table - is the defined and undefined external symbols /// /// For dynamically linked shared library files this load command also contains diff --git a/lib/std/zig/system/darwin/macos.zig b/lib/std/zig/system/darwin/macos.zig index bf26079e5a..0bc08b319c 100644 --- a/lib/std/zig/system/darwin/macos.zig +++ b/lib/std/zig/system/darwin/macos.zig @@ -303,16 +303,16 @@ test "detect" { \\ \\ \\ - \\ ProductBuildVersion - \\ 7W98 - \\ ProductCopyright - \\ Apple Computer, Inc. 1983-2004 - \\ ProductName - \\ Mac OS X - \\ ProductUserVisibleVersion - \\ 10.3.9 - \\ ProductVersion - \\ 10.3.9 + \\ ProductBuildVersion + \\ 7W98 + \\ ProductCopyright + \\ Apple Computer, Inc. 1983-2004 + \\ ProductName + \\ Mac OS X + \\ ProductUserVisibleVersion + \\ 10.3.9 + \\ ProductVersion + \\ 10.3.9 \\ \\ , @@ -323,18 +323,18 @@ test "detect" { \\ \\ \\ - \\ ProductBuildVersion - \\ 19G68 - \\ ProductCopyright - \\ 1983-2020 Apple Inc. - \\ ProductName - \\ Mac OS X - \\ ProductUserVisibleVersion - \\ 10.15.6 - \\ ProductVersion - \\ 10.15.6 - \\ iOSSupportVersion - \\ 13.6 + \\ ProductBuildVersion + \\ 19G68 + \\ ProductCopyright + \\ 1983-2020 Apple Inc. + \\ ProductName + \\ Mac OS X + \\ ProductUserVisibleVersion + \\ 10.15.6 + \\ ProductVersion + \\ 10.15.6 + \\ iOSSupportVersion + \\ 13.6 \\ \\ , @@ -345,18 +345,18 @@ test "detect" { \\ \\ \\ - \\ ProductBuildVersion - \\ 20A2408 - \\ ProductCopyright - \\ 1983-2020 Apple Inc. - \\ ProductName - \\ macOS - \\ ProductUserVisibleVersion - \\ 11.0 - \\ ProductVersion - \\ 11.0 - \\ iOSSupportVersion - \\ 14.2 + \\ ProductBuildVersion + \\ 20A2408 + \\ ProductCopyright + \\ 1983-2020 Apple Inc. + \\ ProductName + \\ macOS + \\ ProductUserVisibleVersion + \\ 11.0 + \\ ProductVersion + \\ 11.0 + \\ iOSSupportVersion + \\ 14.2 \\ \\ , @@ -367,18 +367,18 @@ test "detect" { \\ \\ \\ - \\ ProductBuildVersion - \\ 20C63 - \\ ProductCopyright - \\ 1983-2020 Apple Inc. - \\ ProductName - \\ macOS - \\ ProductUserVisibleVersion - \\ 11.1 - \\ ProductVersion - \\ 11.1 - \\ iOSSupportVersion - \\ 14.3 + \\ ProductBuildVersion + \\ 20C63 + \\ ProductCopyright + \\ 1983-2020 Apple Inc. + \\ ProductName + \\ macOS + \\ ProductUserVisibleVersion + \\ 11.1 + \\ ProductVersion + \\ 11.1 + \\ iOSSupportVersion + \\ 14.3 \\ \\ , diff --git a/lib/std/zig/system/linux.zig b/lib/std/zig/system/linux.zig index 5f58d6d926..7e37185f3e 100644 --- a/lib/std/zig/system/linux.zig +++ b/lib/std/zig/system/linux.zig @@ -109,12 +109,12 @@ const RiscvCpuinfoParser = CpuinfoParser(RiscvCpuinfoImpl); test "cpuinfo: RISC-V" { try testParser(RiscvCpuinfoParser, .riscv64, &Target.riscv.cpu.sifive_u74, - \\processor : 0 - \\hart : 1 - \\isa : rv64imafdc - \\mmu : sv39 - \\isa-ext : - \\uarch : sifive,u74-mc + \\processor : 0 + \\hart : 1 + \\isa : rv64imafdc + \\mmu : sv39 + \\isa-ext : + \\uarch : sifive,u74-mc ); } @@ -177,16 +177,16 @@ const PowerpcCpuinfoParser = CpuinfoParser(PowerpcCpuinfoImpl); test "cpuinfo: PowerPC" { try testParser(PowerpcCpuinfoParser, .powerpc, &Target.powerpc.cpu.@"970", - \\processor : 0 - \\cpu : PPC970MP, altivec supported - \\clock : 1250.000000MHz - \\revision : 1.1 (pvr 0044 0101) + \\processor : 0 + \\cpu : PPC970MP, altivec supported + \\clock : 1250.000000MHz + \\revision : 1.1 (pvr 0044 0101) ); try testParser(PowerpcCpuinfoParser, .powerpc64le, &Target.powerpc.cpu.pwr8, - \\processor : 0 - \\cpu : POWER8 (raw), altivec supported - \\clock : 2926.000000MHz - \\revision : 2.0 (pvr 004d 0200) + \\processor : 0 + \\cpu : POWER8 (raw), altivec supported + \\clock : 2926.000000MHz + \\revision : 2.0 (pvr 004d 0200) ); } @@ -304,25 +304,25 @@ test "cpuinfo: ARM" { \\CPU revision : 7 ); try testParser(ArmCpuinfoParser, .arm, &Target.arm.cpu.cortex_a7, - \\processor : 0 - \\model name : ARMv7 Processor rev 3 (v7l) - \\BogoMIPS : 18.00 - \\Features : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae - \\CPU implementer : 0x41 + \\processor : 0 + \\model name : ARMv7 Processor rev 3 (v7l) + \\BogoMIPS : 18.00 + \\Features : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae + \\CPU implementer : 0x41 \\CPU architecture: 7 - \\CPU variant : 0x0 - \\CPU part : 0xc07 - \\CPU revision : 3 + \\CPU variant : 0x0 + \\CPU part : 0xc07 + \\CPU revision : 3 \\ - \\processor : 4 - \\model name : ARMv7 Processor rev 3 (v7l) - \\BogoMIPS : 90.00 - \\Features : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae - \\CPU implementer : 0x41 + \\processor : 4 + \\model name : ARMv7 Processor rev 3 (v7l) + \\BogoMIPS : 90.00 + \\Features : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae + \\CPU implementer : 0x41 \\CPU architecture: 7 - \\CPU variant : 0x2 - \\CPU part : 0xc0f - \\CPU revision : 3 + \\CPU variant : 0x2 + \\CPU part : 0xc0f + \\CPU revision : 3 ); try testParser(ArmCpuinfoParser, .aarch64, &Target.aarch64.cpu.cortex_a72, \\processor : 0 diff --git a/src/link/tapi/yaml/test.zig b/src/link/tapi/yaml/test.zig index 8db9435885..d354a94b1d 100644 --- a/src/link/tapi/yaml/test.zig +++ b/src/link/tapi/yaml/test.zig @@ -237,10 +237,7 @@ test "double quoted string" { try testing.expectEqualStrings( \\"here" are some escaped quotes , arr[1]); - try testing.expectEqualStrings( - \\newlines and tabs - \\are supported - , arr[2]); + try testing.expectEqualStrings("newlines and tabs\nare\tsupported", arr[2]); try testing.expectEqualStrings( \\let's have \\some fun! diff --git a/test/run_translated_c.zig b/test/run_translated_c.zig index 744d08f27a..73aa7d01f4 100644 --- a/test/run_translated_c.zig +++ b/test/run_translated_c.zig @@ -26,17 +26,17 @@ pub fn addCases(cases: *tests.RunTranslatedCContext) void { \\void baz(void); \\struct foo { int x; }; \\void bar() { - \\ struct foo tmp; + \\ struct foo tmp; \\} \\ \\void baz() { - \\ struct foo tmp; + \\ struct foo tmp; \\} \\ \\int main(void) { - \\ bar(); - \\ baz(); - \\ return 0; + \\ bar(); + \\ baz(); + \\ return 0; \\} , ""); @@ -53,7 +53,7 @@ pub fn addCases(cases: *tests.RunTranslatedCContext) void { cases.add("parenthesized string literal", \\void foo(const char *s) {} \\int main(void) { - \\ foo(("bar")); + \\ foo(("bar")); \\} , ""); diff --git a/test/translate_c.zig b/test/translate_c.zig index c07b29f772..2100e23ea3 100644 --- a/test/translate_c.zig +++ b/test/translate_c.zig @@ -133,20 +133,20 @@ pub fn addCases(cases: *tests.TranslateCContext) void { cases.add("scoped typedef", \\void foo() { - \\ typedef union { - \\ int A; - \\ int B; - \\ int C; - \\ } Foo; - \\ Foo a = {0}; - \\ { - \\ typedef union { - \\ int A; - \\ int B; - \\ int C; - \\ } Foo; - \\ Foo a = {0}; - \\ } + \\ typedef union { + \\ int A; + \\ int B; + \\ int C; + \\ } Foo; + \\ Foo a = {0}; + \\ { + \\ typedef union { + \\ int A; + \\ int B; + \\ int C; + \\ } Foo; + \\ Foo a = {0}; + \\ } \\} , &[_][]const u8{ \\pub export fn foo() void { @@ -2043,18 +2043,18 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\ break; \\ } \\ case 4: - \\ case 5: + \\ case 5: \\ res = 69; \\ { \\ res = 5; - \\ return; + \\ return; \\ } \\ case 6: \\ switch (res) { \\ case 9: break; \\ } \\ res = 1; - \\ return; + \\ return; \\ } \\} , &[_][]const u8{ From c2b8afcac9e427102370dc5bac8c3d9621eee6d8 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 31 Jul 2024 14:04:15 -0700 Subject: [PATCH 092/266] tokenizer: tabs and carriage returns spec conformance --- lib/std/zig/tokenizer.zig | 129 +++++++++++++++++++++++++++----------- test/compile_errors.zig | 8 --- 2 files changed, 94 insertions(+), 43 deletions(-) diff --git a/lib/std/zig/tokenizer.zig b/lib/std/zig/tokenizer.zig index 32e11b1b9a..c375818770 100644 --- a/lib/std/zig/tokenizer.zig +++ b/lib/std/zig/tokenizer.zig @@ -424,10 +424,7 @@ pub const Tokenizer = struct { }; state = .invalid; }, - '\r' => { - state = .expect_newline; - }, - ' ', '\n', '\t' => { + ' ', '\n', '\t', '\r' => { result.loc.start = self.index + 1; }, '"' => { @@ -553,6 +550,13 @@ pub const Tokenizer = struct { }, .expect_newline => switch (c) { + 0 => { + if (self.index == self.buffer.len) { + result.tag = .invalid; + break; + } + state = .invalid; + }, '\n' => { result.loc.start = self.index + 1; state = .start; @@ -846,7 +850,15 @@ pub const Tokenizer = struct { self.index += 1; break; }, - 0x01...0x08, 0x0b...0x1f, 0x7f => { + '\r' => { + if (self.buffer[self.index + 1] == '\n') { + self.index += 2; + break; + } else { + state = .invalid; + } + }, + 0x01...0x09, 0x0b...0x0c, 0x0e...0x1f, 0x7f => { state = .invalid; }, else => continue, @@ -1091,7 +1103,7 @@ pub const Tokenizer = struct { state = .start; result.loc.start = self.index + 1; }, - 0x01...0x08, 0x0b...0x0c, 0x0e...0x1f, 0x7f => { + 0x01...0x09, 0x0b...0x0c, 0x0e...0x1f, 0x7f => { state = .invalid; }, else => { @@ -1099,14 +1111,23 @@ pub const Tokenizer = struct { }, }, .doc_comment_start => switch (c) { - 0, '\n', '\r' => { + 0, '\n' => { result.tag = .doc_comment; break; }, + '\r' => { + if (self.buffer[self.index + 1] == '\n') { + self.index += 1; + result.tag = .doc_comment; + break; + } else { + state = .invalid; + } + }, '/' => { state = .line_comment; }, - 0x01...0x08, 0x0b...0x0c, 0x0e...0x1f, 0x7f => { + 0x01...0x09, 0x0b...0x0c, 0x0e...0x1f, 0x7f => { state = .invalid; }, else => { @@ -1135,16 +1156,24 @@ pub const Tokenizer = struct { state = .start; result.loc.start = self.index + 1; }, - 0x01...0x08, 0x0b...0x0c, 0x0e...0x1f, 0x7f => { + 0x01...0x09, 0x0b...0x0c, 0x0e...0x1f, 0x7f => { state = .invalid; }, else => continue, }, .doc_comment => switch (c) { - 0, '\n', '\r' => { + 0, '\n' => { break; }, - 0x01...0x08, 0x0b...0x0c, 0x0e...0x1f, 0x7f => { + '\r' => { + if (self.buffer[self.index + 1] == '\n') { + self.index += 1; + break; + } else { + state = .invalid; + } + }, + 0x01...0x09, 0x0b...0x0c, 0x0e...0x1f, 0x7f => { state = .invalid; }, else => continue, @@ -1386,30 +1415,6 @@ test "string identifier and builtin fns" { }); } -test "multiline string literal with literal tab" { - try testTokenize( - \\\\foo bar - , &.{ - .multiline_string_literal_line, - }); -} - -test "comments with literal tab" { - try testTokenize( - \\//foo bar - \\//!foo bar - \\///foo bar - \\// foo - \\/// foo - \\/// /foo - , &.{ - .container_doc_comment, - .doc_comment, - .doc_comment, - .doc_comment, - }); -} - test "pipe and then invalid" { try testTokenize("||=", &.{ .pipe_pipe, @@ -1767,6 +1772,60 @@ test "null byte before eof" { try testTokenize("/// NUL\x00\n", &.{ .doc_comment, .invalid }); } +test "invalid tabs and carriage returns" { + // "Inside Line Comments and Documentation Comments, Any TAB is rejected by + // the grammar since it is ambiguous how it should be rendered." + // https://github.com/ziglang/zig-spec/issues/38 + try testTokenize("//\t", &.{.invalid}); + try testTokenize("// \t", &.{.invalid}); + try testTokenize("///\t", &.{.invalid}); + try testTokenize("/// \t", &.{.invalid}); + try testTokenize("//!\t", &.{.invalid}); + try testTokenize("//! \t", &.{.invalid}); + + // "Inside Line Comments and Documentation Comments, CR directly preceding + // NL is unambiguously part of the newline sequence. It is accepted by the + // grammar and removed by zig fmt, leaving only NL. CR anywhere else is + // rejected by the grammar." + // https://github.com/ziglang/zig-spec/issues/38 + try testTokenize("//\r", &.{.invalid}); + try testTokenize("// \r", &.{.invalid}); + try testTokenize("///\r", &.{.invalid}); + try testTokenize("/// \r", &.{.invalid}); + try testTokenize("//\r ", &.{.invalid}); + try testTokenize("// \r ", &.{.invalid}); + try testTokenize("///\r ", &.{.invalid}); + try testTokenize("/// \r ", &.{.invalid}); + try testTokenize("//\r\n", &.{}); + try testTokenize("// \r\n", &.{}); + try testTokenize("///\r\n", &.{.doc_comment}); + try testTokenize("/// \r\n", &.{.doc_comment}); + try testTokenize("//!\r", &.{.invalid}); + try testTokenize("//! \r", &.{.invalid}); + try testTokenize("//!\r ", &.{.invalid}); + try testTokenize("//! \r ", &.{.invalid}); + try testTokenize("//!\r\n", &.{.container_doc_comment}); + try testTokenize("//! \r\n", &.{.container_doc_comment}); + + // The control characters TAB and CR are rejected by the grammar inside multi-line string literals, + // except if CR is directly before NL. + // https://github.com/ziglang/zig-spec/issues/38 + try testTokenize("\\\\\r", &.{.invalid}); + try testTokenize("\\\\\r ", &.{.invalid}); + try testTokenize("\\\\ \r", &.{.invalid}); + try testTokenize("\\\\\t", &.{.invalid}); + try testTokenize("\\\\\t ", &.{.invalid}); + try testTokenize("\\\\ \t", &.{.invalid}); + try testTokenize("\\\\\r\n", &.{.multiline_string_literal_line}); + + // "TAB used as whitespace is...accepted by the grammar. CR used as + // whitespace, whether directly preceding NL or stray, is...accepted by the + // grammar." + // https://github.com/ziglang/zig-spec/issues/38 + try testTokenize("\tpub\tswitch\t", &.{ .keyword_pub, .keyword_switch }); + try testTokenize("\rpub\rswitch\r", &.{ .keyword_pub, .keyword_switch }); +} + fn testTokenize(source: [:0]const u8, expected_token_tags: []const Token.Tag) !void { var tokenizer = Tokenizer.init(source); for (expected_token_tags) |expected_token_tag| { diff --git a/test/compile_errors.zig b/test/compile_errors.zig index c7a3be8f9f..07ad178859 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -38,14 +38,6 @@ pub fn addCases(ctx: *Cases, b: *std.Build) !void { }); } - { - const case = ctx.obj("isolated carriage return in multiline string literal", b.graph.host); - - case.addError("const foo = \\\\\test\r\r rogue carriage return\n;", &[_][]const u8{ - ":1:13: error: expected expression, found 'invalid token'", - }); - } - { const case = ctx.obj("missing semicolon at EOF", b.graph.host); case.addError( From 604e87a95868094bdddbcc6a4579f8535ae5e2a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Fri, 26 Jul 2024 03:14:32 +0200 Subject: [PATCH 093/266] std.Target: Use hexagonv60 as the baseline CPU model for hexagon. --- lib/std/Target.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/std/Target.zig b/lib/std/Target.zig index 42552f9953..dbd1c6cb94 100644 --- a/lib/std/Target.zig +++ b/lib/std/Target.zig @@ -1512,6 +1512,7 @@ pub const Cpu = struct { pub fn baseline(arch: Arch) *const Model { return switch (arch) { .arm, .armeb, .thumb, .thumbeb => &arm.cpu.baseline, + .hexagon => &hexagon.cpu.hexagonv60, .riscv32 => &riscv.cpu.baseline_rv32, .riscv64 => &riscv.cpu.baseline_rv64, .x86 => &x86.cpu.pentium4, From f03d54f0692967872f6d5664597d3e349d79c2a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Mon, 29 Jul 2024 20:26:39 +0200 Subject: [PATCH 094/266] std.atomic: Don't lie to the compiler about memory clobbers in spinLoopHint(). --- lib/std/atomic.zig | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/lib/std/atomic.zig b/lib/std/atomic.zig index be7203c0cf..084d44b3e6 100644 --- a/lib/std/atomic.zig +++ b/lib/std/atomic.zig @@ -380,7 +380,7 @@ pub inline fn spinLoopHint() void { // https://software.intel.com/content/www/us/en/develop/articles/benefitting-power-and-performance-sleep-loops.html .x86, .x86_64, - => asm volatile ("pause" ::: "memory"), + => asm volatile ("pause"), // No-op instruction that serves as a hardware-thread resource yield hint. // https://stackoverflow.com/a/7588941 @@ -388,7 +388,7 @@ pub inline fn spinLoopHint() void { .powerpcle, .powerpc64, .powerpc64le, - => asm volatile ("or 27, 27, 27" ::: "memory"), + => asm volatile ("or 27, 27, 27"), // `isb` appears more reliable for releasing execution resources than `yield` // on common aarch64 CPUs. @@ -396,7 +396,7 @@ pub inline fn spinLoopHint() void { // https://bugs.mysql.com/bug.php?id=100664 .aarch64, .aarch64_be, - => asm volatile ("isb" ::: "memory"), + => asm volatile ("isb"), // `yield` was introduced in v6k but is also available on v6m. // https://www.keil.com/support/man/docs/armasm/armasm_dom1361289926796.htm @@ -409,30 +409,22 @@ pub inline fn spinLoopHint() void { .has_v6k, .has_v6m, }); if (can_yield) { - asm volatile ("yield" ::: "memory"); - } else { - asm volatile ("" ::: "memory"); + asm volatile ("yield"); } }, // The 8-bit immediate specifies the amount of cycles to pause for. We can't really be too // opinionated here. .hexagon, - => asm volatile ("pause(#1)" ::: "memory"), + => asm volatile ("pause(#1)"), .riscv32, .riscv64, - => { - if (comptime std.Target.riscv.featureSetHas(builtin.target.cpu.features, .zihintpause)) { - asm volatile ("pause" ::: "memory"); - } else { - asm volatile ("" ::: "memory"); - } + => if (comptime std.Target.riscv.featureSetHas(builtin.target.cpu.features, .zihintpause)) { + asm volatile ("pause"); }, - // Memory barrier to prevent the compiler from optimizing away the spin-loop - // even if no hint_instruction was provided. - else => asm volatile ("" ::: "memory"), + else => {}, } } From 8d5eaadb0593b3e2d217171b4b71f45d5bed2fcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Tue, 30 Jul 2024 00:09:31 +0200 Subject: [PATCH 095/266] std.Target: Handle loongarch in Os.Tag.archName(). --- lib/std/Target.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/std/Target.zig b/lib/std/Target.zig index dbd1c6cb94..7c222a9b53 100644 --- a/lib/std/Target.zig +++ b/lib/std/Target.zig @@ -198,6 +198,7 @@ pub const Os = struct { .linux => switch (arch) { .arm, .armeb, .thumb, .thumbeb => "arm", .aarch64, .aarch64_be => "aarch64", + .loongarch32, .loongarch64 => "loongarch", .mips, .mipsel, .mips64, .mips64el => "mips", .powerpc, .powerpcle, .powerpc64, .powerpc64le => "powerpc", .riscv32, .riscv64 => "riscv", From 624fa8523a2c4158ddc9fce231181a9e8583a633 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Thu, 1 Aug 2024 11:41:43 +0200 Subject: [PATCH 096/266] std.os.linux: Unbreak the build Happened because I wrote #20869 before #20870. --- lib/std/os/linux.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index 1ca8227781..198481c373 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -111,7 +111,7 @@ pub const SYS = switch (@import("builtin").cpu.arch) { .hexagon => syscalls.Hexagon, .riscv32 => syscalls.RiscV32, .riscv64 => syscalls.RiscV64, - .sparc, .sparcel => syscalls.Sparc, + .sparc => syscalls.Sparc, .sparc64 => syscalls.Sparc64, .m68k => syscalls.M68k, .mips, .mipsel => syscalls.MipsO32, From 5e08d00862fb10c72cd011bf29112e12ef38d576 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Tue, 30 Jul 2024 00:12:02 +0200 Subject: [PATCH 097/266] std.Target: Add Arch.isLoongArch() function. --- lib/std/Target.zig | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/std/Target.zig b/lib/std/Target.zig index 8d9f596072..5c2b5268a3 100644 --- a/lib/std/Target.zig +++ b/lib/std/Target.zig @@ -1084,6 +1084,13 @@ pub const Cpu = struct { }; } + pub inline fn isLoongArch(arch: Arch) bool { + return switch (arch) { + .loongarch32, .loongarch64 => true, + else => false, + }; + } + pub inline fn isRISCV(arch: Arch) bool { return switch (arch) { .riscv32, .riscv64 => true, From e5c75479c2c166c99219083fd24b70901d776367 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Tue, 30 Jul 2024 00:59:50 +0200 Subject: [PATCH 098/266] std.Target: Rework isPPC()/isPPC64() functions. * Rename isPPC() -> isPowerPC32(). * Rename isPPC64() -> isPowerPC64(). * Add new isPowerPC() function which covers both. There was confusion even in the standard library about what isPPC() meant. This change makes these functions work how I think most people actually expect them to work, and makes them consistent with isMIPS(), isSPARC(), etc. I chose to rename from PPC to PowerPC because 1) it's more consistent with the other functions, and 2) it'll cause loud rather than silent breakage for anyone who might have been depending on isPPC() while misunderstanding it. --- lib/compiler/aro/aro/Toolchain.zig | 2 +- lib/compiler/aro/aro/target.zig | 2 +- lib/compiler_rt/common.zig | 2 +- lib/std/Target.zig | 8 ++- lib/std/os/linux.zig | 16 ++--- lib/std/simd.zig | 2 +- src/codegen/llvm.zig | 4 +- test/behavior/union.zig | 2 +- test/c_abi/main.zig | 108 +++++++++++------------------ 9 files changed, 62 insertions(+), 84 deletions(-) diff --git a/lib/compiler/aro/aro/Toolchain.zig b/lib/compiler/aro/aro/Toolchain.zig index 1313675f6c..fca44e07ec 100644 --- a/lib/compiler/aro/aro/Toolchain.zig +++ b/lib/compiler/aro/aro/Toolchain.zig @@ -89,7 +89,7 @@ pub fn discover(tc: *Toolchain) !void { .{ .unknown = {} } // TODO else if (target.cpu.arch.isMIPS()) .{ .unknown = {} } // TODO - else if (target.cpu.arch.isPPC()) + else if (target.cpu.arch.isPowerPC()) .{ .unknown = {} } // TODO else if (target.cpu.arch == .ve) .{ .unknown = {} } // TODO diff --git a/lib/compiler/aro/aro/target.zig b/lib/compiler/aro/aro/target.zig index 2f46ebd605..a876723302 100644 --- a/lib/compiler/aro/aro/target.zig +++ b/lib/compiler/aro/aro/target.zig @@ -264,7 +264,7 @@ pub fn systemCompiler(target: std.Target) LangOpts.Compiler { pub fn hasFloat128(target: std.Target) bool { if (target.cpu.arch.isWasm()) return true; if (target.isDarwin()) return false; - if (target.cpu.arch.isPPC() or target.cpu.arch.isPPC64()) return std.Target.powerpc.featureSetHas(target.cpu.features, .float128); + if (target.cpu.arch.isPowerPC()) return std.Target.powerpc.featureSetHas(target.cpu.features, .float128); return switch (target.os.tag) { .dragonfly, .haiku, diff --git a/lib/compiler_rt/common.zig b/lib/compiler_rt/common.zig index 5b3d0324b5..552fc07efd 100644 --- a/lib/compiler_rt/common.zig +++ b/lib/compiler_rt/common.zig @@ -22,7 +22,7 @@ pub const want_aeabi = switch (builtin.abi) { }, else => false, }; -pub const want_ppc_abi = builtin.cpu.arch.isPPC() or builtin.cpu.arch.isPPC64(); +pub const want_ppc_abi = builtin.cpu.arch.isPowerPC(); pub const want_float_exceptions = !builtin.cpu.arch.isWasm(); diff --git a/lib/std/Target.zig b/lib/std/Target.zig index 5c2b5268a3..566dd45996 100644 --- a/lib/std/Target.zig +++ b/lib/std/Target.zig @@ -1105,14 +1105,18 @@ pub const Cpu = struct { }; } - pub inline fn isPPC(arch: Arch) bool { + pub inline fn isPowerPC(arch: Arch) bool { + return arch.isPowerPC32() or arch.isPowerPC64(); + } + + pub inline fn isPowerPC32(arch: Arch) bool { return switch (arch) { .powerpc, .powerpcle => true, else => false, }; } - pub inline fn isPPC64(arch: Arch) bool { + pub inline fn isPowerPC64(arch: Arch) bool { return switch (arch) { .powerpc64, .powerpc64le => true, else => false, diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index 198481c373..0a40b3a790 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -16,8 +16,7 @@ const native_arch = builtin.cpu.arch; const native_abi = builtin.abi; const native_endian = native_arch.endian(); const is_mips = native_arch.isMIPS(); -const is_ppc = native_arch.isPPC(); -const is_ppc64 = native_arch.isPPC64(); +const is_ppc = native_arch.isPowerPC(); const is_sparc = native_arch.isSPARC(); const iovec = std.posix.iovec; const iovec_const = std.posix.iovec_const; @@ -434,10 +433,9 @@ fn getauxvalImpl(index: usize) callconv(.C) usize { // Some architectures (and some syscalls) require 64bit parameters to be passed // in a even-aligned register pair. const require_aligned_register_pair = - builtin.cpu.arch.isPPC() or + builtin.cpu.arch.isPowerPC32() or builtin.cpu.arch.isMIPS() or - builtin.cpu.arch.isARM() or - builtin.cpu.arch.isThumb(); + builtin.cpu.arch.isArmOrThumb(); // Split a 64bit value into a {LSB,MSB} pair. // The LE/BE variants specify the endianness to assume. @@ -2228,7 +2226,7 @@ pub fn process_vm_writev(pid: pid_t, local: []const iovec_const, remote: []const } pub fn fadvise(fd: fd_t, offset: i64, len: i64, advice: usize) usize { - if (comptime native_arch.isARM() or native_arch.isPPC()) { + if (comptime native_arch.isARM() or native_arch.isPowerPC32()) { // These architectures reorder the arguments so that a register is not skipped to align the // register number that `offset` is passed in. @@ -3575,7 +3573,7 @@ pub const SO = if (is_mips) struct { pub const RCVTIMEO_NEW = 66; pub const SNDTIMEO_NEW = 67; pub const DETACH_REUSEPORT_BPF = 68; -} else if (is_ppc or is_ppc64) struct { +} else if (is_ppc) struct { pub const DEBUG = 1; pub const REUSEADDR = 2; pub const TYPE = 3; @@ -4023,8 +4021,8 @@ pub const T = struct { pub const IOCSPGRP = if (is_mips) 0x741d else 0x5410; pub const IOCOUTQ = if (is_mips) 0x7472 else 0x5411; pub const IOCSTI = if (is_mips) 0x5472 else 0x5412; - pub const IOCGWINSZ = if (is_mips or is_ppc64) 0x40087468 else 0x5413; - pub const IOCSWINSZ = if (is_mips or is_ppc64) 0x80087467 else 0x5414; + pub const IOCGWINSZ = if (is_mips or is_ppc) 0x40087468 else 0x5413; + pub const IOCSWINSZ = if (is_mips or is_ppc) 0x80087467 else 0x5414; pub const IOCMGET = if (is_mips) 0x741d else 0x5415; pub const IOCMBIS = if (is_mips) 0x741b else 0x5416; pub const IOCMBIC = if (is_mips) 0x741c else 0x5417; diff --git a/lib/std/simd.zig b/lib/std/simd.zig index f5eef1cf97..a0f84b1a1d 100644 --- a/lib/std/simd.zig +++ b/lib/std/simd.zig @@ -26,7 +26,7 @@ pub fn suggestVectorLengthForCpu(comptime T: type, comptime cpu: std.Target.Cpu) // TODO: Check on this return when bigger values are more common if (std.Target.aarch64.featureSetHas(cpu.features, .sve)) break :blk 128; if (std.Target.aarch64.featureSetHas(cpu.features, .neon)) break :blk 128; - } else if (cpu.arch.isPPC() or cpu.arch.isPPC64()) { + } else if (cpu.arch.isPowerPC()) { if (std.Target.powerpc.featureSetHas(cpu.features, .altivec)) break :blk 128; } else if (cpu.arch.isMIPS()) { if (std.Target.mips.featureSetHas(cpu.features, .msa)) break :blk 128; diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 23e39caa98..1ba1ed8b6f 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -399,7 +399,7 @@ const DataLayoutBuilder = struct { else if (self.target.cpu.arch == .powerpc64 and self.target.os.tag != .freebsd and self.target.abi != .musl) try writer.writeAll("-Fi64") - else if (self.target.cpu.arch.isPPC() or self.target.cpu.arch.isPPC64()) + else if (self.target.cpu.arch.isPowerPC()) try writer.writeAll("-Fn32"); if (self.target.cpu.arch != .hexagon) { if (self.target.cpu.arch == .arc or self.target.cpu.arch == .s390x) @@ -659,7 +659,7 @@ const DataLayoutBuilder = struct { 128 => abi = 64, else => {}, } - } else if ((self.target.cpu.arch.isPPC64() and self.target.os.tag == .linux and + } else if ((self.target.cpu.arch.isPowerPC64() and self.target.os.tag == .linux and (size == 256 or size == 512)) or (self.target.cpu.arch.isNvptx() and (size == 16 or size == 32))) { diff --git a/test/behavior/union.zig b/test/behavior/union.zig index b1c36c42cc..f5cd61f6f4 100644 --- a/test/behavior/union.zig +++ b/test/behavior/union.zig @@ -1903,7 +1903,7 @@ test "reinterpret packed union" { try comptime S.doTheTest(); if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO - if (builtin.cpu.arch.isPPC()) return error.SkipZigTest; // TODO + if (builtin.cpu.arch.isPowerPC32()) return error.SkipZigTest; // TODO if (builtin.cpu.arch.isWasm()) return error.SkipZigTest; // TODO try S.doTheTest(); } diff --git a/test/c_abi/main.zig b/test/c_abi/main.zig index 2bc93cc89e..86b9584a6e 100644 --- a/test/c_abi/main.zig +++ b/test/c_abi/main.zig @@ -11,7 +11,7 @@ const print = std.debug.print; const expect = std.testing.expect; const expectEqual = std.testing.expectEqual; const have_i128 = builtin.cpu.arch != .x86 and !builtin.cpu.arch.isARM() and - !builtin.cpu.arch.isMIPS() and !builtin.cpu.arch.isPPC(); + !builtin.cpu.arch.isMIPS() and !builtin.cpu.arch.isPowerPC32(); const have_f128 = builtin.cpu.arch.isX86() and !builtin.os.tag.isDarwin(); const have_f80 = builtin.cpu.arch.isX86(); @@ -123,8 +123,7 @@ test "C ABI floats" { } test "C ABI long double" { - if (builtin.cpu.arch.isPPC()) return error.SkipZigTest; - if (builtin.cpu.arch.isPPC64()) return error.SkipZigTest; + if (builtin.cpu.arch.isPowerPC()) return error.SkipZigTest; c_long_double(12.34); } @@ -182,7 +181,7 @@ extern fn c_cmultf(a: ComplexFloat, b: ComplexFloat) ComplexFloat; extern fn c_cmultd(a: ComplexDouble, b: ComplexDouble) ComplexDouble; const complex_abi_compatible = builtin.cpu.arch != .x86 and !builtin.cpu.arch.isMIPS() and - !builtin.cpu.arch.isARM() and !builtin.cpu.arch.isPPC() and !builtin.cpu.arch.isRISCV(); + !builtin.cpu.arch.isARM() and !builtin.cpu.arch.isPowerPC32() and !builtin.cpu.arch.isRISCV(); test "C ABI complex float" { if (!complex_abi_compatible) return error.SkipZigTest; @@ -324,7 +323,7 @@ extern fn c_struct_u64_u64_8(usize, usize, usize, usize, usize, usize, usize, us test "C ABI struct u64 u64" { if (builtin.cpu.arch.isMIPS()) return error.SkipZigTest; - if (builtin.cpu.arch.isPPC()) return error.SkipZigTest; + if (builtin.cpu.arch.isPowerPC32()) return error.SkipZigTest; const s = c_ret_struct_u64_u64(); try expect(s.a == 21); @@ -361,7 +360,7 @@ extern fn c_struct_f32f32_f32(Struct_f32f32_f32) void; test "C ABI struct {f32,f32} f32" { if (builtin.cpu.arch.isMIPS()) return error.SkipZigTest; - if (builtin.cpu.arch.isPPC()) return error.SkipZigTest; + if (builtin.cpu.arch.isPowerPC32()) return error.SkipZigTest; const s = c_ret_struct_f32f32_f32(); try expect(s.a.b == 1.0); @@ -391,7 +390,7 @@ extern fn c_struct_f32_f32f32(Struct_f32_f32f32) void; test "C ABI struct f32 {f32,f32}" { if (builtin.cpu.arch.isMIPS()) return error.SkipZigTest; - if (builtin.cpu.arch.isPPC()) return error.SkipZigTest; + if (builtin.cpu.arch.isPowerPC32()) return error.SkipZigTest; const s = c_ret_struct_f32_f32f32(); try expect(s.a == 1.0); @@ -426,8 +425,7 @@ extern fn c_struct_u32_union_u32_u32u32(Struct_u32_Union_u32_u32u32) void; test "C ABI struct{u32,union{u32,struct{u32,u32}}}" { if (builtin.cpu.arch.isMIPS()) return error.SkipZigTest; - if (builtin.cpu.arch.isPPC()) return error.SkipZigTest; - if (builtin.cpu.arch.isPPC64()) return error.SkipZigTest; + if (builtin.cpu.arch.isPowerPC()) return error.SkipZigTest; const s = c_ret_struct_u32_union_u32_u32u32(); try expect(s.a == 1); @@ -447,7 +445,7 @@ extern fn c_big_struct(BigStruct) void; test "C ABI big struct" { if (builtin.cpu.arch.isMIPS()) return error.SkipZigTest; - if (builtin.cpu.arch.isPPC()) return error.SkipZigTest; + if (builtin.cpu.arch.isPowerPC32()) return error.SkipZigTest; const s = BigStruct{ .a = 1, @@ -473,7 +471,7 @@ const BigUnion = extern union { extern fn c_big_union(BigUnion) void; test "C ABI big union" { - if (builtin.cpu.arch.isPPC()) return error.SkipZigTest; + if (builtin.cpu.arch.isPowerPC32()) return error.SkipZigTest; const x = BigUnion{ .a = BigStruct{ @@ -506,8 +504,7 @@ extern fn c_ret_med_struct_mixed() MedStructMixed; test "C ABI medium struct of ints and floats" { if (builtin.cpu.arch.isMIPS()) return error.SkipZigTest; - if (builtin.cpu.arch.isPPC()) return error.SkipZigTest; - if (builtin.cpu.arch.isPPC64()) return error.SkipZigTest; + if (builtin.cpu.arch.isPowerPC()) return error.SkipZigTest; const s = MedStructMixed{ .a = 1234, @@ -539,8 +536,7 @@ extern fn c_ret_small_struct_ints() SmallStructInts; test "C ABI small struct of ints" { if (builtin.cpu.arch == .x86) return error.SkipZigTest; if (builtin.cpu.arch.isMIPS()) return error.SkipZigTest; - if (builtin.cpu.arch.isPPC()) return error.SkipZigTest; - if (builtin.cpu.arch.isPPC64()) return error.SkipZigTest; + if (builtin.cpu.arch.isPowerPC()) return error.SkipZigTest; const s = SmallStructInts{ .a = 1, @@ -573,8 +569,7 @@ extern fn c_ret_med_struct_ints() MedStructInts; test "C ABI medium struct of ints" { if (builtin.cpu.arch.isMIPS()) return error.SkipZigTest; - if (builtin.cpu.arch.isPPC()) return error.SkipZigTest; - if (builtin.cpu.arch.isPPC64()) return error.SkipZigTest; + if (builtin.cpu.arch.isPowerPC()) return error.SkipZigTest; const s = MedStructInts{ .x = 1, @@ -652,8 +647,7 @@ extern fn c_split_struct_ints(SplitStructInt) void; test "C ABI split struct of ints" { if (builtin.cpu.arch == .x86) return error.SkipZigTest; if (builtin.cpu.arch.isMIPS()) return error.SkipZigTest; - if (builtin.cpu.arch.isPPC()) return error.SkipZigTest; - if (builtin.cpu.arch.isPPC64()) return error.SkipZigTest; + if (builtin.cpu.arch.isPowerPC()) return error.SkipZigTest; const s = SplitStructInt{ .a = 1234, @@ -680,8 +674,7 @@ extern fn c_ret_split_struct_mixed() SplitStructMixed; test "C ABI split struct of ints and floats" { if (builtin.cpu.arch == .x86) return error.SkipZigTest; if (builtin.cpu.arch.isMIPS()) return error.SkipZigTest; - if (builtin.cpu.arch.isPPC()) return error.SkipZigTest; - if (builtin.cpu.arch.isPPC64()) return error.SkipZigTest; + if (builtin.cpu.arch.isPowerPC()) return error.SkipZigTest; const s = SplitStructMixed{ .a = 1234, @@ -708,7 +701,7 @@ extern fn c_multiple_struct_floats(FloatRect, FloatRect) void; test "C ABI sret and byval together" { if (builtin.cpu.arch.isMIPS()) return error.SkipZigTest; - if (builtin.cpu.arch.isPPC()) return error.SkipZigTest; + if (builtin.cpu.arch.isPowerPC32()) return error.SkipZigTest; const s = BigStruct{ .a = 1, @@ -760,8 +753,7 @@ extern fn c_big_struct_floats(Vector5) void; test "C ABI structs of floats as parameter" { if (builtin.cpu.arch.isMIPS()) return error.SkipZigTest; - if (builtin.cpu.arch.isPPC()) return error.SkipZigTest; - if (builtin.cpu.arch.isPPC64()) return error.SkipZigTest; + if (builtin.cpu.arch.isPowerPC()) return error.SkipZigTest; const v3 = Vector3{ .x = 3.0, @@ -800,8 +792,7 @@ export fn zig_multiple_struct_ints(x: Rect, y: Rect) void { } test "C ABI structs of ints as multiple parameters" { - if (builtin.cpu.arch.isPPC()) return error.SkipZigTest; - if (builtin.cpu.arch.isPPC64()) return error.SkipZigTest; + if (builtin.cpu.arch.isPowerPC()) return error.SkipZigTest; const r1 = Rect{ .left = 1, @@ -838,7 +829,7 @@ export fn zig_multiple_struct_floats(x: FloatRect, y: FloatRect) void { test "C ABI structs of floats as multiple parameters" { if (builtin.cpu.arch.isMIPS()) return error.SkipZigTest; - if (builtin.cpu.arch.isPPC()) return error.SkipZigTest; + if (builtin.cpu.arch.isPowerPC32()) return error.SkipZigTest; const r1 = FloatRect{ .left = 1, @@ -951,8 +942,7 @@ extern fn c_ret_struct_with_array() StructWithArray; test "Struct with array as padding." { if (builtin.cpu.arch == .x86) return error.SkipZigTest; if (builtin.cpu.arch.isMIPS()) return error.SkipZigTest; - if (builtin.cpu.arch.isPPC()) return error.SkipZigTest; - if (builtin.cpu.arch.isPPC64()) return error.SkipZigTest; + if (builtin.cpu.arch.isPowerPC()) return error.SkipZigTest; c_struct_with_array(.{ .a = 1, .padding = undefined, .b = 2 }); @@ -977,7 +967,7 @@ extern fn c_ret_float_array_struct() FloatArrayStruct; test "Float array like struct" { if (builtin.cpu.arch.isMIPS()) return error.SkipZigTest; - if (builtin.cpu.arch.isPPC()) return error.SkipZigTest; + if (builtin.cpu.arch.isPowerPC32()) return error.SkipZigTest; c_float_array_struct(.{ .origin = .{ @@ -1004,7 +994,7 @@ extern fn c_ret_small_vec() SmallVec; test "small simd vector" { if (builtin.cpu.arch == .x86) return error.SkipZigTest; - if (builtin.cpu.arch.isPPC64()) return error.SkipZigTest; + if (builtin.cpu.arch.isPowerPC64()) return error.SkipZigTest; c_small_vec(.{ 1, 2 }); @@ -1022,7 +1012,7 @@ test "medium simd vector" { if (builtin.zig_backend == .stage2_x86_64 and !comptime std.Target.x86.featureSetHas(builtin.cpu.features, .avx)) return error.SkipZigTest; - if (builtin.cpu.arch.isPPC64()) return error.SkipZigTest; + if (builtin.cpu.arch.isPowerPC64()) return error.SkipZigTest; c_medium_vec(.{ 1, 2, 3, 4 }); @@ -1042,7 +1032,7 @@ test "big simd vector" { if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.cpu.arch.isMIPS() and builtin.mode != .Debug) return error.SkipZigTest; - if (builtin.cpu.arch.isPPC64()) return error.SkipZigTest; + if (builtin.cpu.arch.isPowerPC64()) return error.SkipZigTest; if (builtin.zig_backend == .stage2_llvm and builtin.cpu.arch == .x86_64 and builtin.os.tag == .macos and builtin.mode != .Debug) return error.SkipZigTest; c_big_vec(.{ 1, 2, 3, 4, 5, 6, 7, 8 }); @@ -5352,7 +5342,7 @@ extern fn c_ret_ptr_size_float_struct() Vector2; test "C ABI pointer sized float struct" { if (builtin.cpu.arch.isMIPS()) return error.SkipZigTest; if (builtin.cpu.arch.isRISCV()) return error.SkipZigTest; - if (builtin.cpu.arch.isPPC()) return error.SkipZigTest; + if (builtin.cpu.arch.isPowerPC32()) return error.SkipZigTest; c_ptr_size_float_struct(.{ .x = 1, .y = 2 }); @@ -5374,29 +5364,25 @@ const DC = extern struct { v1: f64, v2: u8 }; test "DC: Zig passes to C" { if (builtin.cpu.arch.isMIPS()) return error.SkipZigTest; if (builtin.cpu.arch.isRISCV()) return error.SkipZigTest; - if (builtin.cpu.arch.isPPC()) return error.SkipZigTest; - if (builtin.cpu.arch.isPPC64()) return error.SkipZigTest; + if (builtin.cpu.arch.isPowerPC()) return error.SkipZigTest; try expectOk(c_assert_DC(.{ .v1 = -0.25, .v2 = 15 })); } test "DC: Zig returns to C" { if (builtin.cpu.arch.isMIPS() and builtin.mode != .Debug) return error.SkipZigTest; if (builtin.cpu.arch.isRISCV()) return error.SkipZigTest; - if (builtin.cpu.arch.isPPC()) return error.SkipZigTest; - if (builtin.cpu.arch.isPPC64()) return error.SkipZigTest; + if (builtin.cpu.arch.isPowerPC()) return error.SkipZigTest; try expectOk(c_assert_ret_DC()); } test "DC: C passes to Zig" { if (builtin.cpu.arch.isMIPS()) return error.SkipZigTest; if (builtin.cpu.arch.isRISCV()) return error.SkipZigTest; - if (builtin.cpu.arch.isPPC()) return error.SkipZigTest; - if (builtin.cpu.arch.isPPC64()) return error.SkipZigTest; + if (builtin.cpu.arch.isPowerPC()) return error.SkipZigTest; try expectOk(c_send_DC()); } test "DC: C returns to Zig" { if (builtin.cpu.arch.isMIPS() and builtin.mode != .Debug) return error.SkipZigTest; if (builtin.cpu.arch.isRISCV()) return error.SkipZigTest; - if (builtin.cpu.arch.isPPC()) return error.SkipZigTest; - if (builtin.cpu.arch.isPPC64()) return error.SkipZigTest; + if (builtin.cpu.arch.isPowerPC()) return error.SkipZigTest; try expectEqual(DC{ .v1 = -0.25, .v2 = 15 }, c_ret_DC()); } @@ -5421,14 +5407,12 @@ const CFF = extern struct { v1: u8, v2: f32, v3: f32 }; test "CFF: Zig passes to C" { if (builtin.target.cpu.arch == .x86) return error.SkipZigTest; if (builtin.cpu.arch.isMIPS()) return error.SkipZigTest; - if (builtin.cpu.arch.isPPC()) return error.SkipZigTest; - if (builtin.cpu.arch.isPPC64()) return error.SkipZigTest; + if (builtin.cpu.arch.isPowerPC()) return error.SkipZigTest; try expectOk(c_assert_CFF(.{ .v1 = 39, .v2 = 0.875, .v3 = 1.0 })); } test "CFF: Zig returns to C" { if (builtin.cpu.arch.isMIPS()) return error.SkipZigTest; - if (builtin.cpu.arch.isPPC()) return error.SkipZigTest; - if (builtin.cpu.arch.isPPC64()) return error.SkipZigTest; + if (builtin.cpu.arch.isPowerPC()) return error.SkipZigTest; try expectOk(c_assert_ret_CFF()); } test "CFF: C passes to Zig" { @@ -5436,8 +5420,7 @@ test "CFF: C passes to Zig" { if (builtin.cpu.arch.isRISCV() and builtin.mode != .Debug) return error.SkipZigTest; if (builtin.cpu.arch == .aarch64 and builtin.mode != .Debug) return error.SkipZigTest; if (builtin.cpu.arch.isMIPS()) return error.SkipZigTest; - if (builtin.cpu.arch.isPPC()) return error.SkipZigTest; - if (builtin.cpu.arch.isPPC64()) return error.SkipZigTest; + if (builtin.cpu.arch.isPowerPC()) return error.SkipZigTest; try expectOk(c_send_CFF()); } @@ -5445,8 +5428,7 @@ test "CFF: C returns to Zig" { if (builtin.cpu.arch == .aarch64 and builtin.mode != .Debug) return error.SkipZigTest; if (builtin.cpu.arch.isRISCV() and builtin.mode != .Debug) return error.SkipZigTest; if (builtin.cpu.arch.isMIPS()) return error.SkipZigTest; - if (builtin.cpu.arch.isPPC()) return error.SkipZigTest; - if (builtin.cpu.arch.isPPC64()) return error.SkipZigTest; + if (builtin.cpu.arch.isPowerPC()) return error.SkipZigTest; try expectEqual(CFF{ .v1 = 39, .v2 = 0.875, .v3 = 1.0 }, c_ret_CFF()); } pub extern fn c_assert_CFF(lv: CFF) c_int; @@ -5470,26 +5452,22 @@ const PD = extern struct { v1: ?*anyopaque, v2: f64 }; test "PD: Zig passes to C" { if (builtin.cpu.arch.isMIPS()) return error.SkipZigTest; - if (builtin.cpu.arch.isPPC()) return error.SkipZigTest; - if (builtin.cpu.arch.isPPC64()) return error.SkipZigTest; + if (builtin.cpu.arch.isPowerPC()) return error.SkipZigTest; try expectOk(c_assert_PD(.{ .v1 = null, .v2 = 0.5 })); } test "PD: Zig returns to C" { if (builtin.cpu.arch.isMIPS() and builtin.mode != .Debug) return error.SkipZigTest; - if (builtin.cpu.arch.isPPC()) return error.SkipZigTest; - if (builtin.cpu.arch.isPPC64()) return error.SkipZigTest; + if (builtin.cpu.arch.isPowerPC()) return error.SkipZigTest; try expectOk(c_assert_ret_PD()); } test "PD: C passes to Zig" { if (builtin.cpu.arch.isMIPS()) return error.SkipZigTest; - if (builtin.cpu.arch.isPPC()) return error.SkipZigTest; - if (builtin.cpu.arch.isPPC64()) return error.SkipZigTest; + if (builtin.cpu.arch.isPowerPC()) return error.SkipZigTest; try expectOk(c_send_PD()); } test "PD: C returns to Zig" { if (builtin.cpu.arch.isMIPS() and builtin.mode != .Debug) return error.SkipZigTest; - if (builtin.cpu.arch.isPPC()) return error.SkipZigTest; - if (builtin.cpu.arch.isPPC64()) return error.SkipZigTest; + if (builtin.cpu.arch.isPowerPC()) return error.SkipZigTest; try expectEqual(PD{ .v1 = null, .v2 = 0.5 }, c_ret_PD()); } pub extern fn c_assert_PD(lv: PD) c_int; @@ -5521,7 +5499,7 @@ const ByRef = extern struct { extern fn c_modify_by_ref_param(ByRef) ByRef; test "C function modifies by ref param" { - if (builtin.cpu.arch.isPPC()) return error.SkipZigTest; + if (builtin.cpu.arch.isPowerPC32()) return error.SkipZigTest; const res = c_modify_by_ref_param(.{ .val = 1, .arr = undefined }); try expect(res.val == 42); @@ -5543,7 +5521,7 @@ const ByVal = extern struct { extern fn c_func_ptr_byval(*anyopaque, *anyopaque, ByVal, c_ulong, *anyopaque, c_ulong) void; test "C function that takes byval struct called via function pointer" { if (builtin.cpu.arch.isMIPS() and builtin.mode != .Debug) return error.SkipZigTest; - if (builtin.cpu.arch.isPPC()) return error.SkipZigTest; + if (builtin.cpu.arch.isPowerPC32()) return error.SkipZigTest; var fn_ptr = &c_func_ptr_byval; _ = &fn_ptr; @@ -5574,8 +5552,7 @@ const f16_struct = extern struct { extern fn c_f16_struct(f16_struct) f16_struct; test "f16 struct" { if (builtin.target.cpu.arch.isMIPS()) return error.SkipZigTest; - if (builtin.target.cpu.arch.isPPC()) return error.SkipZigTest; - if (builtin.target.cpu.arch.isPPC()) return error.SkipZigTest; + if (builtin.target.cpu.arch.isPowerPC32()) return error.SkipZigTest; if (builtin.cpu.arch.isARM() and builtin.mode != .Debug) return error.SkipZigTest; const a = c_f16_struct(.{ .a = 12 }); @@ -5690,8 +5667,7 @@ const Coord2 = extern struct { extern fn stdcall_coord2(Coord2, Coord2, Coord2) callconv(stdcall_callconv) Coord2; test "Stdcall ABI structs" { if (builtin.cpu.arch.isMIPS()) return error.SkipZigTest; - if (builtin.cpu.arch.isPPC()) return error.SkipZigTest; - if (builtin.cpu.arch.isPPC64()) return error.SkipZigTest; + if (builtin.cpu.arch.isPowerPC()) return error.SkipZigTest; const res = stdcall_coord2( .{ .x = 0x1111, .y = 0x2222 }, @@ -5704,7 +5680,7 @@ test "Stdcall ABI structs" { extern fn stdcall_big_union(BigUnion) callconv(stdcall_callconv) void; test "Stdcall ABI big union" { - if (builtin.cpu.arch.isPPC()) return error.SkipZigTest; + if (builtin.cpu.arch.isPowerPC32()) return error.SkipZigTest; const x = BigUnion{ .a = BigStruct{ @@ -5776,7 +5752,7 @@ const byval_tail_callsite_attr = struct { test "byval tail callsite attribute" { if (builtin.cpu.arch.isMIPS()) return error.SkipZigTest; - if (builtin.cpu.arch.isPPC()) return error.SkipZigTest; + if (builtin.cpu.arch.isPowerPC32()) return error.SkipZigTest; // Originally reported at https://github.com/ziglang/zig/issues/16290 // the bug was that the extern function had the byval attribute, but From 5a97045644b91cdc5fb6e684eeabd596d5fb671e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Tue, 30 Jul 2024 01:06:05 +0200 Subject: [PATCH 099/266] std.Target: Add isMIPS32()/isMIPS64() functions. --- lib/std/Target.zig | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/std/Target.zig b/lib/std/Target.zig index 566dd45996..bc8f7b637c 100644 --- a/lib/std/Target.zig +++ b/lib/std/Target.zig @@ -1099,8 +1099,19 @@ pub const Cpu = struct { } pub inline fn isMIPS(arch: Arch) bool { + return arch.isMIPS32() or arch.isMIPS64(); + } + + pub inline fn isMIPS32(arch: Arch) bool { return switch (arch) { - .mips, .mipsel, .mips64, .mips64el => true, + .mips, .mipsel => true, + else => false, + }; + } + + pub inline fn isMIPS64(arch: Arch) bool { + return switch (arch) { + .mips64, .mips64el => true, else => false, }; } From a2868147b12985a660fa8f642b08385acc6d41df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Tue, 30 Jul 2024 01:57:56 +0200 Subject: [PATCH 100/266] std.os.linux: Fix require_aligned_register_pair to check isMIPS32(). Only 32-bit architectures need this. --- lib/std/os/linux.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index 0a40b3a790..7d1f0439f0 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -434,7 +434,7 @@ fn getauxvalImpl(index: usize) callconv(.C) usize { // in a even-aligned register pair. const require_aligned_register_pair = builtin.cpu.arch.isPowerPC32() or - builtin.cpu.arch.isMIPS() or + builtin.cpu.arch.isMIPS32() or builtin.cpu.arch.isArmOrThumb(); // Split a 64bit value into a {LSB,MSB} pair. From f7d07b44d1c6eb05e9695f8ae28a0c64fbd14dd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Tue, 30 Jul 2024 02:00:44 +0200 Subject: [PATCH 101/266] std.os.linux: Fix arm check in fadvise() to also include thumb. --- lib/std/os/linux.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index 7d1f0439f0..4e1dd8cda3 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -2226,7 +2226,7 @@ pub fn process_vm_writev(pid: pid_t, local: []const iovec_const, remote: []const } pub fn fadvise(fd: fd_t, offset: i64, len: i64, advice: usize) usize { - if (comptime native_arch.isARM() or native_arch.isPowerPC32()) { + if (comptime native_arch.isArmOrThumb() or native_arch.isPowerPC32()) { // These architectures reorder the arguments so that a register is not skipped to align the // register number that `offset` is passed in. From 7cc257957e6229a1abe675128f1fdbb7078eb342 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Tue, 30 Jul 2024 02:38:24 +0200 Subject: [PATCH 102/266] glibc: Fix some target architecture checks to include thumb and powerpcle. --- src/glibc.zig | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/glibc.zig b/src/glibc.zig index 85f1828ad4..576e7f8137 100644 --- a/src/glibc.zig +++ b/src/glibc.zig @@ -403,9 +403,9 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile, prog_node: std.Progre fn start_asm_path(comp: *Compilation, arena: Allocator, basename: []const u8) ![]const u8 { const arch = comp.getTarget().cpu.arch; - const is_ppc = arch == .powerpc or arch == .powerpc64 or arch == .powerpc64le; - const is_aarch64 = arch == .aarch64 or arch == .aarch64_be; - const is_sparc = arch == .sparc or arch == .sparc64; + const is_ppc = arch.isPowerPC(); + const is_aarch64 = arch.isAARCH64(); + const is_sparc = arch.isSPARC(); const is_64 = comp.getTarget().ptrBitWidth() == 64; const s = path.sep_str; @@ -423,7 +423,7 @@ fn start_asm_path(comp: *Compilation, arena: Allocator, basename: []const u8) ![ try result.appendSlice("sparc" ++ s ++ "sparc32"); } } - } else if (arch.isARM()) { + } else if (arch.isArmOrThumb()) { try result.appendSlice("arm"); } else if (arch.isMIPS()) { if (!mem.eql(u8, basename, "crti.S") and !mem.eql(u8, basename, "crtn.S")) { @@ -540,10 +540,10 @@ fn add_include_dirs_arch( dir: []const u8, ) error{OutOfMemory}!void { const arch = target.cpu.arch; - const is_x86 = arch == .x86 or arch == .x86_64; - const is_aarch64 = arch == .aarch64 or arch == .aarch64_be; - const is_ppc = arch == .powerpc or arch == .powerpc64 or arch == .powerpc64le; - const is_sparc = arch == .sparc or arch == .sparc64; + const is_x86 = arch.isX86(); + const is_aarch64 = arch.isAARCH64(); + const is_ppc = arch.isPowerPC(); + const is_sparc = arch.isSPARC(); const is_64 = target.ptrBitWidth() == 64; const s = path.sep_str; @@ -573,7 +573,7 @@ fn add_include_dirs_arch( try args.append("-I"); try args.append(try path.join(arena, &[_][]const u8{ dir, "x86" })); } - } else if (arch.isARM()) { + } else if (arch.isArmOrThumb()) { if (opt_nptl) |nptl| { try args.append("-I"); try args.append(try path.join(arena, &[_][]const u8{ dir, "arm", nptl })); From 5cd92a6b51f37d91a5063c5ee36167e96f030526 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Tue, 30 Jul 2024 02:57:43 +0200 Subject: [PATCH 103/266] libunwind: Fix an isARM() check to use isArmOrThumb() instead. --- src/libunwind.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libunwind.zig b/src/libunwind.zig index 7783876827..dd2ed39130 100644 --- a/src/libunwind.zig +++ b/src/libunwind.zig @@ -131,7 +131,7 @@ pub fn buildStaticLib(comp: *Compilation, prog_node: std.Progress.Node) BuildErr if (!comp.config.any_non_single_threaded) { try cflags.append("-D_LIBUNWIND_HAS_NO_THREADS"); } - if (target.cpu.arch.isARM() and target.abi.floatAbi() == .hard) { + if (target.cpu.arch.isArmOrThumb() and target.abi.floatAbi() == .hard) { try cflags.append("-DCOMPILER_RT_ARMHF_TARGET"); } try cflags.append("-Wno-bitwise-conditional-parentheses"); From a9c78185120fd1dd731c00f1bd961f0b2e2b2c83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Tue, 30 Jul 2024 02:56:29 +0200 Subject: [PATCH 104/266] start: Fix an isARM() check to use isArmOrThumb() instead. --- lib/std/start.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/start.zig b/lib/std/start.zig index fd8661660b..c975c888af 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -495,7 +495,7 @@ fn posixCallMainAndExit(argc_argv_ptr: [*]usize) callconv(.C) noreturn { // ARMv6 targets (and earlier) have no support for TLS in hardware. // FIXME: Elide the check for targets >= ARMv7 when the target feature API // becomes less verbose (and more usable). - if (comptime native_arch.isARM()) { + if (comptime native_arch.isArmOrThumb()) { if (at_hwcap & std.os.linux.HWCAP.TLS == 0) { // FIXME: Make __aeabi_read_tp call the kernel helper kuser_get_tls // For the time being use a simple trap instead of a @panic call to From 7d88bd0b9c4ca12235769a04c45842dab3b54827 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Tue, 30 Jul 2024 02:55:49 +0200 Subject: [PATCH 105/266] std.simd: Fix an isARM() check to use isArmOrThumb() instead. Thumb can have NEON instructions too. --- lib/std/simd.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/simd.zig b/lib/std/simd.zig index a0f84b1a1d..236b9fbab8 100644 --- a/lib/std/simd.zig +++ b/lib/std/simd.zig @@ -18,7 +18,7 @@ pub fn suggestVectorLengthForCpu(comptime T: type, comptime cpu: std.Target.Cpu) if (std.Target.x86.featureSetHasAny(cpu.features, .{ .prefer_256_bit, .avx2 }) and !std.Target.x86.featureSetHas(cpu.features, .prefer_128_bit)) break :blk 256; if (std.Target.x86.featureSetHas(cpu.features, .sse)) break :blk 128; if (std.Target.x86.featureSetHasAny(cpu.features, .{ .mmx, .@"3dnow" })) break :blk 64; - } else if (cpu.arch.isARM()) { + } else if (cpu.arch.isArmOrThumb()) { if (std.Target.arm.featureSetHas(cpu.features, .neon)) break :blk 128; } else if (cpu.arch.isAARCH64()) { // SVE allows up to 2048 bits in the specification, as of 2022 the most powerful machine has implemented 512-bit From 5c6f5e6cf2d6fdce2083e1f946a1f5ee407dbdf9 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 26 Jul 2024 15:38:12 -0700 Subject: [PATCH 106/266] test runner: avoid spawning progress thread when instrumented because it causes unwanted concurrent accesses to pc tracking --- lib/compiler/test_runner.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/compiler/test_runner.zig b/lib/compiler/test_runner.zig index 3e97062982..be793376a5 100644 --- a/lib/compiler/test_runner.zig +++ b/lib/compiler/test_runner.zig @@ -166,7 +166,7 @@ fn mainTerminal() void { var skip_count: usize = 0; var fail_count: usize = 0; var fuzz_count: usize = 0; - const root_node = std.Progress.start(.{ + const root_node = if (builtin.fuzz) std.Progress.Node.none else std.Progress.start(.{ .root_name = "Test", .estimated_total_items = test_fn_list.len, }); From 12d0c9a2fc1220ec5cba62f1923c502cd5607bf4 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 29 Jul 2024 21:43:46 -0700 Subject: [PATCH 107/266] add std.fs.Dir.Handle mirrors std.fs.File.Handle --- lib/std/fs/Dir.zig | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/std/fs/Dir.zig b/lib/std/fs/Dir.zig index 34967301e1..d4ea8a0109 100644 --- a/lib/std/fs/Dir.zig +++ b/lib/std/fs/Dir.zig @@ -1,4 +1,6 @@ -fd: posix.fd_t, +fd: Handle, + +pub const Handle = posix.fd_t; pub const default_mode = 0o755; From 9c84b5cc18b6f78d1eec25432c3cd2988edb83ed Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 29 Jul 2024 23:06:39 -0700 Subject: [PATCH 108/266] build.zig: fix -Dskip-non-native now it actually does what it says on the tin --- test/tests.zig | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/test/tests.zig b/test/tests.zig index b4c720a1ff..e56164366d 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -992,11 +992,7 @@ pub fn addModuleTests(b: *std.Build, options: ModuleTestOptions) *Step { const step = b.step(b.fmt("test-{s}", .{options.name}), options.desc); for (test_targets) |test_target| { - const is_native = test_target.target.isNative() or - (test_target.target.os_tag == builtin.os.tag and - test_target.target.cpu_arch == builtin.cpu.arch); - - if (options.skip_non_native and !is_native) + if (options.skip_non_native and !test_target.target.isNative()) continue; const resolved_target = b.resolveTargetQuery(test_target.target); From 377274ee9ab270886cce1ceaf5ffeddaefd9c239 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 1 Aug 2024 11:58:01 -0700 Subject: [PATCH 109/266] std.debug.DebugInfo: rename to std.debug.Info avoiding redundancy in the fully qualified namespace --- lib/std/debug.zig | 60 +++++++++++++++++++++++------------------------ 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/lib/std/debug.zig b/lib/std/debug.zig index be8b030769..6313f4bcc7 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -102,9 +102,9 @@ pub fn getStderrMutex() *std.Thread.Mutex { } /// TODO multithreaded awareness -var self_debug_info: ?DebugInfo = null; +var self_debug_info: ?Info = null; -pub fn getSelfDebugInfo() !*DebugInfo { +pub fn getSelfDebugInfo() !*Info { if (self_debug_info) |*info| { return info; } else { @@ -346,7 +346,7 @@ pub fn captureStackTrace(first_address: ?usize, stack_trace: *std.builtin.StackT stack_trace.index = slice.len; } else { // TODO: This should use the DWARF unwinder if .eh_frame_hdr is available (so that full debug info parsing isn't required). - // A new path for loading DebugInfo needs to be created which will only attempt to parse in-memory sections, because + // A new path for loading Info needs to be created which will only attempt to parse in-memory sections, because // stopping to load other debug info (ie. source line info) from disk here is not required for unwinding. var it = StackIterator.init(first_address, null); defer it.deinit(); @@ -524,7 +524,7 @@ pub fn writeStackTrace( stack_trace: std.builtin.StackTrace, out_stream: anytype, allocator: mem.Allocator, - debug_info: *DebugInfo, + debug_info: *Info, tty_config: io.tty.Config, ) !void { _ = allocator; @@ -561,11 +561,11 @@ pub const StackIterator = struct { fp: usize, ma: MemoryAccessor = MemoryAccessor.init, - // When DebugInfo and a register context is available, this iterator can unwind + // When Info and a register context is available, this iterator can unwind // stacks with frames that don't use a frame pointer (ie. -fomit-frame-pointer), // using DWARF and MachO unwind info. unwind_state: if (have_ucontext) ?struct { - debug_info: *DebugInfo, + debug_info: *Info, dwarf_context: DW.UnwindContext, last_error: ?UnwindError = null, failed: bool = false, @@ -590,7 +590,7 @@ pub const StackIterator = struct { }; } - pub fn initWithContext(first_address: ?usize, debug_info: *DebugInfo, context: *const posix.ucontext_t) !StackIterator { + pub fn initWithContext(first_address: ?usize, debug_info: *Info, context: *const posix.ucontext_t) !StackIterator { // The implementation of DWARF unwinding on aarch64-macos is not complete. However, Apple mandates that // the frame pointer register is always used, so on this platform we can safely use the FP-based unwinder. if (comptime builtin.target.isDarwin() and native_arch == .aarch64) { @@ -850,7 +850,7 @@ const have_msync = switch (native_os) { pub fn writeCurrentStackTrace( out_stream: anytype, - debug_info: *DebugInfo, + debug_info: *Info, tty_config: io.tty.Config, start_addr: ?usize, ) !void { @@ -936,7 +936,7 @@ pub noinline fn walkStackWindows(addresses: []usize, existing_context: ?*const w pub fn writeStackTraceWindows( out_stream: anytype, - debug_info: *DebugInfo, + debug_info: *Info, tty_config: io.tty.Config, context: *const windows.CONTEXT, start_addr: ?usize, @@ -1000,7 +1000,7 @@ test machoSearchSymbols { try testing.expectEqual(&symbols[2], machoSearchSymbols(&symbols, 5000).?); } -fn printUnknownSource(debug_info: *DebugInfo, out_stream: anytype, address: usize, tty_config: io.tty.Config) !void { +fn printUnknownSource(debug_info: *Info, out_stream: anytype, address: usize, tty_config: io.tty.Config) !void { const module_name = debug_info.getModuleNameForAddress(address); return printLineInfo( out_stream, @@ -1013,14 +1013,14 @@ fn printUnknownSource(debug_info: *DebugInfo, out_stream: anytype, address: usiz ); } -fn printLastUnwindError(it: *StackIterator, debug_info: *DebugInfo, out_stream: anytype, tty_config: io.tty.Config) void { +fn printLastUnwindError(it: *StackIterator, debug_info: *Info, out_stream: anytype, tty_config: io.tty.Config) void { if (!have_ucontext) return; if (it.getLastError()) |unwind_error| { printUnwindError(debug_info, out_stream, unwind_error.address, unwind_error.err, tty_config) catch {}; } } -fn printUnwindError(debug_info: *DebugInfo, out_stream: anytype, address: usize, err: UnwindError, tty_config: io.tty.Config) !void { +fn printUnwindError(debug_info: *Info, out_stream: anytype, address: usize, err: UnwindError, tty_config: io.tty.Config) !void { const module_name = debug_info.getModuleNameForAddress(address) orelse "???"; try tty_config.setColor(out_stream, .dim); if (err == error.MissingDebugInfo) { @@ -1031,7 +1031,7 @@ fn printUnwindError(debug_info: *DebugInfo, out_stream: anytype, address: usize, try tty_config.setColor(out_stream, .reset); } -pub fn printSourceAtAddress(debug_info: *DebugInfo, out_stream: anytype, address: usize, tty_config: io.tty.Config) !void { +pub fn printSourceAtAddress(debug_info: *Info, out_stream: anytype, address: usize, tty_config: io.tty.Config) !void { const module = debug_info.getModuleForAddress(address) catch |err| switch (err) { error.MissingDebugInfo, error.InvalidDebugInfo => return printUnknownSource(debug_info, out_stream, address, tty_config), else => return err, @@ -1105,9 +1105,9 @@ fn printLineInfo( pub const OpenSelfDebugInfoError = error{ MissingDebugInfo, UnsupportedOperatingSystem, -} || @typeInfo(@typeInfo(@TypeOf(DebugInfo.init)).Fn.return_type.?).ErrorUnion.error_set; +} || @typeInfo(@typeInfo(@TypeOf(Info.init)).Fn.return_type.?).ErrorUnion.error_set; -pub fn openSelfDebugInfo(allocator: mem.Allocator) OpenSelfDebugInfoError!DebugInfo { +pub fn openSelfDebugInfo(allocator: mem.Allocator) OpenSelfDebugInfoError!Info { nosuspend { if (builtin.strip_debug_info) return error.MissingDebugInfo; @@ -1124,7 +1124,7 @@ pub fn openSelfDebugInfo(allocator: mem.Allocator) OpenSelfDebugInfoError!DebugI .solaris, .illumos, .windows, - => return try DebugInfo.init(allocator), + => return try Info.init(allocator), else => return error.UnsupportedOperatingSystem, } } @@ -1759,13 +1759,13 @@ pub const WindowsModuleInfo = struct { } = null, }; -pub const DebugInfo = struct { +pub const Info = struct { allocator: mem.Allocator, address_map: std.AutoHashMap(usize, *ModuleDebugInfo), modules: if (native_os == .windows) std.ArrayListUnmanaged(WindowsModuleInfo) else void, - pub fn init(allocator: mem.Allocator) !DebugInfo { - var debug_info = DebugInfo{ + pub fn init(allocator: mem.Allocator) !Info { + var debug_info = Info{ .allocator = allocator, .address_map = std.AutoHashMap(usize, *ModuleDebugInfo).init(allocator), .modules = if (native_os == .windows) .{} else {}, @@ -1808,7 +1808,7 @@ pub const DebugInfo = struct { return debug_info; } - pub fn deinit(self: *DebugInfo) void { + pub fn deinit(self: *Info) void { var it = self.address_map.iterator(); while (it.next()) |entry| { const mdi = entry.value_ptr.*; @@ -1825,7 +1825,7 @@ pub const DebugInfo = struct { } } - pub fn getModuleForAddress(self: *DebugInfo, address: usize) !*ModuleDebugInfo { + pub fn getModuleForAddress(self: *Info, address: usize) !*ModuleDebugInfo { if (comptime builtin.target.isDarwin()) { return self.lookupModuleDyld(address); } else if (native_os == .windows) { @@ -1842,7 +1842,7 @@ pub const DebugInfo = struct { // Returns the module name for a given address. // This can be called when getModuleForAddress fails, so implementations should provide // a path that doesn't rely on any side-effects of a prior successful module lookup. - pub fn getModuleNameForAddress(self: *DebugInfo, address: usize) ?[]const u8 { + pub fn getModuleNameForAddress(self: *Info, address: usize) ?[]const u8 { if (comptime builtin.target.isDarwin()) { return self.lookupModuleNameDyld(address); } else if (native_os == .windows) { @@ -1856,7 +1856,7 @@ pub const DebugInfo = struct { } } - fn lookupModuleDyld(self: *DebugInfo, address: usize) !*ModuleDebugInfo { + fn lookupModuleDyld(self: *Info, address: usize) !*ModuleDebugInfo { const image_count = std.c._dyld_image_count(); var i: u32 = 0; @@ -1922,7 +1922,7 @@ pub const DebugInfo = struct { return error.MissingDebugInfo; } - fn lookupModuleNameDyld(self: *DebugInfo, address: usize) ?[]const u8 { + fn lookupModuleNameDyld(self: *Info, address: usize) ?[]const u8 { _ = self; const image_count = std.c._dyld_image_count(); @@ -1960,7 +1960,7 @@ pub const DebugInfo = struct { return null; } - fn lookupModuleWin32(self: *DebugInfo, address: usize) !*ModuleDebugInfo { + fn lookupModuleWin32(self: *Info, address: usize) !*ModuleDebugInfo { for (self.modules.items) |*module| { if (address >= module.base_address and address < module.base_address + module.size) { if (self.address_map.get(module.base_address)) |obj_di| { @@ -2050,7 +2050,7 @@ pub const DebugInfo = struct { return error.MissingDebugInfo; } - fn lookupModuleNameWin32(self: *DebugInfo, address: usize) ?[]const u8 { + fn lookupModuleNameWin32(self: *Info, address: usize) ?[]const u8 { for (self.modules.items) |module| { if (address >= module.base_address and address < module.base_address + module.size) { return module.name; @@ -2059,7 +2059,7 @@ pub const DebugInfo = struct { return null; } - fn lookupModuleNameDl(self: *DebugInfo, address: usize) ?[]const u8 { + fn lookupModuleNameDl(self: *Info, address: usize) ?[]const u8 { _ = self; var ctx: struct { @@ -2097,7 +2097,7 @@ pub const DebugInfo = struct { return null; } - fn lookupModuleDl(self: *DebugInfo, address: usize) !*ModuleDebugInfo { + fn lookupModuleDl(self: *Info, address: usize) !*ModuleDebugInfo { var ctx: struct { // Input address: usize, @@ -2191,13 +2191,13 @@ pub const DebugInfo = struct { return obj_di; } - fn lookupModuleHaiku(self: *DebugInfo, address: usize) !*ModuleDebugInfo { + fn lookupModuleHaiku(self: *Info, address: usize) !*ModuleDebugInfo { _ = self; _ = address; @panic("TODO implement lookup module for Haiku"); } - fn lookupModuleWasm(self: *DebugInfo, address: usize) !*ModuleDebugInfo { + fn lookupModuleWasm(self: *Info, address: usize) !*ModuleDebugInfo { _ = self; _ = address; @panic("TODO implement lookup module for Wasm"); From e5b46eab3b16a6bf7924ef837fcd6552f430bd58 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 1 Aug 2024 13:40:47 -0700 Subject: [PATCH 110/266] std: dwarf namespace reorg std.debug.Dwarf is the parsing/decoding logic. std.dwarf remains the unopinionated types and bits alone. If you look at this diff you can see a lot less redundancy in namespaces. --- lib/std/debug.zig | 76 +- lib/std/debug/Dwarf.zig | 2709 +++++++++++++++++ lib/std/{dwarf => debug/Dwarf}/abi.zig | 4 +- lib/std/{dwarf => debug/Dwarf}/call_frame.zig | 24 +- .../Dwarf/expression.zig} | 60 +- lib/std/dwarf.zig | 2702 +--------------- src/target.zig | 2 +- 7 files changed, 2793 insertions(+), 2784 deletions(-) create mode 100644 lib/std/debug/Dwarf.zig rename lib/std/{dwarf => debug/Dwarf}/abi.zig (99%) rename lib/std/{dwarf => debug/Dwarf}/call_frame.zig (97%) rename lib/std/{dwarf/expressions.zig => debug/Dwarf/expression.zig} (98%) diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 6313f4bcc7..22a7e551ec 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -18,6 +18,8 @@ const native_arch = builtin.cpu.arch; const native_os = builtin.os.tag; const native_endian = native_arch.endian(); +pub const Dwarf = @import("debug/Dwarf.zig"); + pub const runtime_safety = switch (builtin.mode) { .Debug, .ReleaseSafe => true, .ReleaseFast, .ReleaseSmall => false, @@ -67,7 +69,7 @@ pub const SymbolInfo = struct { }; const PdbOrDwarf = union(enum) { pdb: pdb.Pdb, - dwarf: DW.DwarfInfo, + dwarf: Dwarf, fn deinit(self: *PdbOrDwarf, allocator: mem.Allocator) void { switch (self.*) { @@ -566,7 +568,7 @@ pub const StackIterator = struct { // using DWARF and MachO unwind info. unwind_state: if (have_ucontext) ?struct { debug_info: *Info, - dwarf_context: DW.UnwindContext, + dwarf_context: Dwarf.UnwindContext, last_error: ?UnwindError = null, failed: bool = false, } else void = if (have_ucontext) null else {}, @@ -599,7 +601,7 @@ pub const StackIterator = struct { var iterator = init(first_address, null); iterator.unwind_state = .{ .debug_info = debug_info, - .dwarf_context = try DW.UnwindContext.init(debug_info.allocator, context), + .dwarf_context = try Dwarf.UnwindContext.init(debug_info.allocator, context), }; return iterator; @@ -783,7 +785,7 @@ pub const StackIterator = struct { // __unwind_info is a requirement for unwinding on Darwin. It may fall back to DWARF, but unwinding // via DWARF before attempting to use the compact unwind info will produce incorrect results. if (module.unwind_info) |unwind_info| { - if (DW.unwindFrameMachO(&unwind_state.dwarf_context, &it.ma, unwind_info, module.eh_frame, module.base_address)) |return_address| { + if (Dwarf.unwindFrameMachO(&unwind_state.dwarf_context, &it.ma, unwind_info, module.eh_frame, module.base_address)) |return_address| { return return_address; } else |err| { if (err != error.RequiresDWARFUnwind) return err; @@ -1140,10 +1142,10 @@ fn readCoffDebugInfo(allocator: mem.Allocator, coff_obj: *coff.Coff) !ModuleDebu if (coff_obj.getSectionByName(".debug_info")) |_| { // This coff file has embedded DWARF debug info - var sections: DW.DwarfInfo.SectionArray = DW.DwarfInfo.null_section_array; + var sections: Dwarf.SectionArray = Dwarf.null_section_array; errdefer for (sections) |section| if (section) |s| if (s.owned) allocator.free(s.data); - inline for (@typeInfo(DW.DwarfSection).Enum.fields, 0..) |section, i| { + inline for (@typeInfo(Dwarf.Section.Id).Enum.fields, 0..) |section, i| { sections[i] = if (coff_obj.getSectionByName("." ++ section.name)) |section_header| blk: { break :blk .{ .data = try coff_obj.getSectionDataAlloc(section_header, allocator), @@ -1153,13 +1155,13 @@ fn readCoffDebugInfo(allocator: mem.Allocator, coff_obj: *coff.Coff) !ModuleDebu } else null; } - var dwarf = DW.DwarfInfo{ + var dwarf = Dwarf{ .endian = native_endian, .sections = sections, .is_macho = false, }; - try DW.openDwarfDebugInfo(&dwarf, allocator); + try Dwarf.open(&dwarf, allocator); di.dwarf = dwarf; } @@ -1211,7 +1213,7 @@ pub fn readElfDebugInfo( elf_filename: ?[]const u8, build_id: ?[]const u8, expected_crc: ?u32, - parent_sections: *DW.DwarfInfo.SectionArray, + parent_sections: *Dwarf.SectionArray, parent_mapped_mem: ?[]align(mem.page_size) const u8, ) !ModuleDebugInfo { nosuspend { @@ -1245,7 +1247,7 @@ pub fn readElfDebugInfo( @ptrCast(@alignCast(&mapped_mem[shoff])), )[0..hdr.e_shnum]; - var sections: DW.DwarfInfo.SectionArray = DW.DwarfInfo.null_section_array; + var sections: Dwarf.SectionArray = Dwarf.null_section_array; // Combine section list. This takes ownership over any owned sections from the parent scope. for (parent_sections, §ions) |*parent, *section| { @@ -1274,7 +1276,7 @@ pub fn readElfDebugInfo( } var section_index: ?usize = null; - inline for (@typeInfo(DW.DwarfSection).Enum.fields, 0..) |section, i| { + inline for (@typeInfo(Dwarf.Section.Id).Enum.fields, 0..) |section, i| { if (mem.eql(u8, "." ++ section.name, name)) section_index = i; } if (section_index == null) continue; @@ -1308,10 +1310,10 @@ pub fn readElfDebugInfo( } const missing_debug_info = - sections[@intFromEnum(DW.DwarfSection.debug_info)] == null or - sections[@intFromEnum(DW.DwarfSection.debug_abbrev)] == null or - sections[@intFromEnum(DW.DwarfSection.debug_str)] == null or - sections[@intFromEnum(DW.DwarfSection.debug_line)] == null; + sections[@intFromEnum(Dwarf.Section.Id.debug_info)] == null or + sections[@intFromEnum(Dwarf.Section.Id.debug_abbrev)] == null or + sections[@intFromEnum(Dwarf.Section.Id.debug_str)] == null or + sections[@intFromEnum(Dwarf.Section.Id.debug_line)] == null; // Attempt to load debug info from an external file // See: https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html @@ -1379,13 +1381,13 @@ pub fn readElfDebugInfo( return error.MissingDebugInfo; } - var di = DW.DwarfInfo{ + var di = Dwarf{ .endian = endian, .sections = sections, .is_macho = false, }; - try DW.openDwarfDebugInfo(&di, allocator); + try Dwarf.open(&di, allocator); return ModuleDebugInfo{ .base_address = undefined, @@ -2168,13 +2170,13 @@ pub const Info = struct { const obj_di = try self.allocator.create(ModuleDebugInfo); errdefer self.allocator.destroy(obj_di); - var sections: DW.DwarfInfo.SectionArray = DW.DwarfInfo.null_section_array; + var sections: Dwarf.SectionArray = Dwarf.null_section_array; if (ctx.gnu_eh_frame) |eh_frame_hdr| { // This is a special case - pointer offsets inside .eh_frame_hdr // are encoded relative to its base address, so we must use the // version that is already memory mapped, and not the one that // will be mapped separately from the ELF file. - sections[@intFromEnum(DW.DwarfSection.eh_frame_hdr)] = .{ + sections[@intFromEnum(Dwarf.Section.Id.eh_frame_hdr)] = .{ .data = eh_frame_hdr, .owned = false, }; @@ -2219,7 +2221,7 @@ pub const ModuleDebugInfo = switch (native_os) { const OFileTable = std.StringHashMap(OFileInfo); const OFileInfo = struct { - di: DW.DwarfInfo, + di: Dwarf, addr_table: std.StringHashMap(u64), }; @@ -2278,8 +2280,8 @@ pub const ModuleDebugInfo = switch (native_os) { addr_table.putAssumeCapacityNoClobber(sym_name, sym.n_value); } - var sections: DW.DwarfInfo.SectionArray = DW.DwarfInfo.null_section_array; - if (self.eh_frame) |eh_frame| sections[@intFromEnum(DW.DwarfSection.eh_frame)] = .{ + var sections: Dwarf.SectionArray = Dwarf.null_section_array; + if (self.eh_frame) |eh_frame| sections[@intFromEnum(Dwarf.Section.Id.eh_frame)] = .{ .data = eh_frame, .owned = false, }; @@ -2288,7 +2290,7 @@ pub const ModuleDebugInfo = switch (native_os) { if (!std.mem.eql(u8, "__DWARF", sect.segName())) continue; var section_index: ?usize = null; - inline for (@typeInfo(DW.DwarfSection).Enum.fields, 0..) |section, i| { + inline for (@typeInfo(Dwarf.Section.Id).Enum.fields, 0..) |section, i| { if (mem.eql(u8, "__" ++ section.name, sect.sectName())) section_index = i; } if (section_index == null) continue; @@ -2302,19 +2304,19 @@ pub const ModuleDebugInfo = switch (native_os) { } const missing_debug_info = - sections[@intFromEnum(DW.DwarfSection.debug_info)] == null or - sections[@intFromEnum(DW.DwarfSection.debug_abbrev)] == null or - sections[@intFromEnum(DW.DwarfSection.debug_str)] == null or - sections[@intFromEnum(DW.DwarfSection.debug_line)] == null; + sections[@intFromEnum(Dwarf.Section.Id.debug_info)] == null or + sections[@intFromEnum(Dwarf.Section.Id.debug_abbrev)] == null or + sections[@intFromEnum(Dwarf.Section.Id.debug_str)] == null or + sections[@intFromEnum(Dwarf.Section.Id.debug_line)] == null; if (missing_debug_info) return error.MissingDebugInfo; - var di = DW.DwarfInfo{ + var di = Dwarf{ .endian = .little, .sections = sections, .is_macho = true, }; - try DW.openDwarfDebugInfo(&di, allocator); + try Dwarf.open(&di, allocator); const info = OFileInfo{ .di = di, .addr_table = addr_table, @@ -2411,14 +2413,14 @@ pub const ModuleDebugInfo = switch (native_os) { } } - pub fn getDwarfInfoForAddress(self: *@This(), allocator: mem.Allocator, address: usize) !?*const DW.DwarfInfo { + pub fn getDwarfInfoForAddress(self: *@This(), allocator: mem.Allocator, address: usize) !?*const Dwarf { return if ((try self.getOFileInfoForAddress(allocator, address)).o_file_info) |o_file_info| &o_file_info.di else null; } }, .uefi, .windows => struct { base_address: usize, pdb: ?pdb.Pdb = null, - dwarf: ?DW.DwarfInfo = null, + dwarf: ?Dwarf = null, coff_image_base: u64, /// Only used if pdb is non-null @@ -2488,7 +2490,7 @@ pub const ModuleDebugInfo = switch (native_os) { return SymbolInfo{}; } - pub fn getDwarfInfoForAddress(self: *@This(), allocator: mem.Allocator, address: usize) !?*const DW.DwarfInfo { + pub fn getDwarfInfoForAddress(self: *@This(), allocator: mem.Allocator, address: usize) !?*const Dwarf { _ = allocator; _ = address; @@ -2500,7 +2502,7 @@ pub const ModuleDebugInfo = switch (native_os) { }, .linux, .netbsd, .freebsd, .dragonfly, .openbsd, .haiku, .solaris, .illumos => struct { base_address: usize, - dwarf: DW.DwarfInfo, + dwarf: Dwarf, mapped_memory: []align(mem.page_size) const u8, external_mapped_memory: ?[]align(mem.page_size) const u8, @@ -2516,7 +2518,7 @@ pub const ModuleDebugInfo = switch (native_os) { return getSymbolFromDwarf(allocator, relocated_address, &self.dwarf); } - pub fn getDwarfInfoForAddress(self: *@This(), allocator: mem.Allocator, address: usize) !?*const DW.DwarfInfo { + pub fn getDwarfInfoForAddress(self: *@This(), allocator: mem.Allocator, address: usize) !?*const Dwarf { _ = allocator; _ = address; return &self.dwarf; @@ -2535,17 +2537,17 @@ pub const ModuleDebugInfo = switch (native_os) { return SymbolInfo{}; } - pub fn getDwarfInfoForAddress(self: *@This(), allocator: mem.Allocator, address: usize) !?*const DW.DwarfInfo { + pub fn getDwarfInfoForAddress(self: *@This(), allocator: mem.Allocator, address: usize) !?*const Dwarf { _ = self; _ = allocator; _ = address; return null; } }, - else => DW.DwarfInfo, + else => Dwarf, }; -fn getSymbolFromDwarf(allocator: mem.Allocator, address: u64, di: *DW.DwarfInfo) !SymbolInfo { +fn getSymbolFromDwarf(allocator: mem.Allocator, address: u64, di: *Dwarf) !SymbolInfo { if (nosuspend di.findCompileUnit(address)) |compile_unit| { return SymbolInfo{ .symbol_name = nosuspend di.getSymbolName(address) orelse "???", diff --git a/lib/std/debug/Dwarf.zig b/lib/std/debug/Dwarf.zig new file mode 100644 index 0000000000..f17fd737a1 --- /dev/null +++ b/lib/std/debug/Dwarf.zig @@ -0,0 +1,2709 @@ +//! Implements parsing, decoding, and caching of DWARF information. +//! +//! For unopinionated types and bits, see `std.dwarf`. + +const builtin = @import("builtin"); +const std = @import("../std.zig"); +const AT = DW.AT; +const Allocator = std.mem.Allocator; +const DW = std.dwarf; +const EH = DW.EH; +const FORM = DW.FORM; +const Format = DW.Format; +const RLE = DW.RLE; +const StackIterator = std.debug.StackIterator; +const UT = DW.UT; +const assert = std.debug.assert; +const cast = std.math.cast; +const maxInt = std.math.maxInt; +const native_endian = builtin.cpu.arch.endian(); +const readInt = std.mem.readInt; + +const Dwarf = @This(); + +pub const expression = @import("Dwarf/expression.zig"); +pub const abi = @import("Dwarf/abi.zig"); +pub const call_frame = @import("Dwarf/call_frame.zig"); + +endian: std.builtin.Endian, +sections: SectionArray = null_section_array, +is_macho: bool, + +// Filled later by the initializer +abbrev_table_list: std.ArrayListUnmanaged(Abbrev.Table) = .{}, +compile_unit_list: std.ArrayListUnmanaged(CompileUnit) = .{}, +func_list: std.ArrayListUnmanaged(Func) = .{}, + +eh_frame_hdr: ?ExceptionFrameHeader = null, +// These lookup tables are only used if `eh_frame_hdr` is null +cie_map: std.AutoArrayHashMapUnmanaged(u64, CommonInformationEntry) = .{}, +// Sorted by start_pc +fde_list: std.ArrayListUnmanaged(FrameDescriptionEntry) = .{}, + +pub const Section = struct { + data: []const u8, + // Module-relative virtual address. + // Only set if the section data was loaded from disk. + virtual_address: ?usize = null, + // If `data` is owned by this Dwarf. + owned: bool, + + pub const Id = enum { + debug_info, + debug_abbrev, + debug_str, + debug_str_offsets, + debug_line, + debug_line_str, + debug_ranges, + debug_loclists, + debug_rnglists, + debug_addr, + debug_names, + debug_frame, + eh_frame, + eh_frame_hdr, + }; + + // For sections that are not memory mapped by the loader, this is an offset + // from `data.ptr` to where the section would have been mapped. Otherwise, + // `data` is directly backed by the section and the offset is zero. + pub fn virtualOffset(self: Section, base_address: usize) i64 { + return if (self.virtual_address) |va| + @as(i64, @intCast(base_address + va)) - + @as(i64, @intCast(@intFromPtr(self.data.ptr))) + else + 0; + } +}; + +pub const Abbrev = struct { + code: u64, + tag_id: u64, + has_children: bool, + attrs: []Attr, + + fn deinit(abbrev: *Abbrev, allocator: Allocator) void { + allocator.free(abbrev.attrs); + abbrev.* = undefined; + } + + const Attr = struct { + id: u64, + form_id: u64, + /// Only valid if form_id is .implicit_const + payload: i64, + }; + + const Table = struct { + // offset from .debug_abbrev + offset: u64, + abbrevs: []Abbrev, + + fn deinit(table: *Table, allocator: Allocator) void { + for (table.abbrevs) |*abbrev| { + abbrev.deinit(allocator); + } + allocator.free(table.abbrevs); + table.* = undefined; + } + + fn get(table: *const Table, abbrev_code: u64) ?*const Abbrev { + return for (table.abbrevs) |*abbrev| { + if (abbrev.code == abbrev_code) break abbrev; + } else null; + } + }; +}; + +pub const CompileUnit = struct { + version: u16, + format: Format, + die: Die, + pc_range: ?PcRange, + + str_offsets_base: usize, + addr_base: usize, + rnglists_base: usize, + loclists_base: usize, + frame_base: ?*const FormValue, +}; + +pub const FormValue = union(enum) { + addr: u64, + addrx: usize, + block: []const u8, + udata: u64, + data16: *const [16]u8, + sdata: i64, + exprloc: []const u8, + flag: bool, + sec_offset: u64, + ref: u64, + ref_addr: u64, + string: [:0]const u8, + strp: u64, + strx: usize, + line_strp: u64, + loclistx: u64, + rnglistx: u64, + + fn getString(fv: FormValue, di: Dwarf) ![:0]const u8 { + switch (fv) { + .string => |s| return s, + .strp => |off| return di.getString(off), + .line_strp => |off| return di.getLineString(off), + else => return badDwarf(), + } + } + + fn getUInt(fv: FormValue, comptime U: type) !U { + return switch (fv) { + inline .udata, + .sdata, + .sec_offset, + => |c| cast(U, c) orelse badDwarf(), + else => badDwarf(), + }; + } +}; + +pub const Die = struct { + tag_id: u64, + has_children: bool, + attrs: []Attr, + + const Attr = struct { + id: u64, + value: FormValue, + }; + + fn deinit(self: *Die, allocator: Allocator) void { + allocator.free(self.attrs); + self.* = undefined; + } + + fn getAttr(self: *const Die, id: u64) ?*const FormValue { + for (self.attrs) |*attr| { + if (attr.id == id) return &attr.value; + } + return null; + } + + fn getAttrAddr( + self: *const Die, + di: *const Dwarf, + id: u64, + compile_unit: CompileUnit, + ) error{ InvalidDebugInfo, MissingDebugInfo }!u64 { + const form_value = self.getAttr(id) orelse return error.MissingDebugInfo; + return switch (form_value.*) { + .addr => |value| value, + .addrx => |index| di.readDebugAddr(compile_unit, index), + else => error.InvalidDebugInfo, + }; + } + + fn getAttrSecOffset(self: *const Die, id: u64) !u64 { + const form_value = self.getAttr(id) orelse return error.MissingDebugInfo; + return form_value.getUInt(u64); + } + + fn getAttrUnsignedLe(self: *const Die, id: u64) !u64 { + const form_value = self.getAttr(id) orelse return error.MissingDebugInfo; + return switch (form_value.*) { + .Const => |value| value.asUnsignedLe(), + else => error.InvalidDebugInfo, + }; + } + + fn getAttrRef(self: *const Die, id: u64) !u64 { + const form_value = self.getAttr(id) orelse return error.MissingDebugInfo; + return switch (form_value.*) { + .ref => |value| value, + else => error.InvalidDebugInfo, + }; + } + + pub fn getAttrString( + self: *const Die, + di: *Dwarf, + id: u64, + opt_str: ?[]const u8, + compile_unit: CompileUnit, + ) error{ InvalidDebugInfo, MissingDebugInfo }![]const u8 { + const form_value = self.getAttr(id) orelse return error.MissingDebugInfo; + switch (form_value.*) { + .string => |value| return value, + .strp => |offset| return di.getString(offset), + .strx => |index| { + const debug_str_offsets = di.section(.debug_str_offsets) orelse return badDwarf(); + if (compile_unit.str_offsets_base == 0) return badDwarf(); + switch (compile_unit.format) { + .@"32" => { + const byte_offset = compile_unit.str_offsets_base + 4 * index; + if (byte_offset + 4 > debug_str_offsets.len) return badDwarf(); + const offset = readInt(u32, debug_str_offsets[byte_offset..][0..4], di.endian); + return getStringGeneric(opt_str, offset); + }, + .@"64" => { + const byte_offset = compile_unit.str_offsets_base + 8 * index; + if (byte_offset + 8 > debug_str_offsets.len) return badDwarf(); + const offset = readInt(u64, debug_str_offsets[byte_offset..][0..8], di.endian); + return getStringGeneric(opt_str, offset); + }, + } + }, + .line_strp => |offset| return di.getLineString(offset), + else => return badDwarf(), + } + } +}; + +/// This represents the decoded .eh_frame_hdr header +pub const ExceptionFrameHeader = struct { + eh_frame_ptr: usize, + table_enc: u8, + fde_count: usize, + entries: []const u8, + + pub fn entrySize(table_enc: u8) !u8 { + return switch (table_enc & EH.PE.type_mask) { + EH.PE.udata2, + EH.PE.sdata2, + => 4, + EH.PE.udata4, + EH.PE.sdata4, + => 8, + EH.PE.udata8, + EH.PE.sdata8, + => 16, + // This is a binary search table, so all entries must be the same length + else => return badDwarf(), + }; + } + + fn isValidPtr( + self: ExceptionFrameHeader, + comptime T: type, + ptr: usize, + ma: *StackIterator.MemoryAccessor, + eh_frame_len: ?usize, + ) bool { + if (eh_frame_len) |len| { + return ptr >= self.eh_frame_ptr and ptr <= self.eh_frame_ptr + len - @sizeOf(T); + } else { + return ma.load(T, ptr) != null; + } + } + + /// Find an entry by binary searching the eh_frame_hdr section. + /// + /// Since the length of the eh_frame section (`eh_frame_len`) may not be known by the caller, + /// MemoryAccessor will be used to verify readability of the header entries. + /// If `eh_frame_len` is provided, then these checks can be skipped. + pub fn findEntry( + self: ExceptionFrameHeader, + ma: *StackIterator.MemoryAccessor, + eh_frame_len: ?usize, + eh_frame_hdr_ptr: usize, + pc: usize, + cie: *CommonInformationEntry, + fde: *FrameDescriptionEntry, + ) !void { + const entry_size = try entrySize(self.table_enc); + + var left: usize = 0; + var len: usize = self.fde_count; + + var fbr: FixedBufferReader = .{ .buf = self.entries, .endian = native_endian }; + + while (len > 1) { + const mid = left + len / 2; + + fbr.pos = mid * entry_size; + const pc_begin = try readEhPointer(&fbr, self.table_enc, @sizeOf(usize), .{ + .pc_rel_base = @intFromPtr(&self.entries[fbr.pos]), + .follow_indirect = true, + .data_rel_base = eh_frame_hdr_ptr, + }) orelse return badDwarf(); + + if (pc < pc_begin) { + len /= 2; + } else { + left = mid; + if (pc == pc_begin) break; + len -= len / 2; + } + } + + if (len == 0) return badDwarf(); + fbr.pos = left * entry_size; + + // Read past the pc_begin field of the entry + _ = try readEhPointer(&fbr, self.table_enc, @sizeOf(usize), .{ + .pc_rel_base = @intFromPtr(&self.entries[fbr.pos]), + .follow_indirect = true, + .data_rel_base = eh_frame_hdr_ptr, + }) orelse return badDwarf(); + + const fde_ptr = cast(usize, try readEhPointer(&fbr, self.table_enc, @sizeOf(usize), .{ + .pc_rel_base = @intFromPtr(&self.entries[fbr.pos]), + .follow_indirect = true, + .data_rel_base = eh_frame_hdr_ptr, + }) orelse return badDwarf()) orelse return badDwarf(); + + if (fde_ptr < self.eh_frame_ptr) return badDwarf(); + + // Even if eh_frame_len is not specified, all ranges accssed are checked via MemoryAccessor + const eh_frame = @as([*]const u8, @ptrFromInt(self.eh_frame_ptr))[0 .. eh_frame_len orelse maxInt(u32)]; + + const fde_offset = fde_ptr - self.eh_frame_ptr; + var eh_frame_fbr: FixedBufferReader = .{ + .buf = eh_frame, + .pos = fde_offset, + .endian = native_endian, + }; + + const fde_entry_header = try EntryHeader.read(&eh_frame_fbr, if (eh_frame_len == null) ma else null, .eh_frame); + if (!self.isValidPtr(u8, @intFromPtr(&fde_entry_header.entry_bytes[fde_entry_header.entry_bytes.len - 1]), ma, eh_frame_len)) return badDwarf(); + if (fde_entry_header.type != .fde) return badDwarf(); + + // CIEs always come before FDEs (the offset is a subtraction), so we can assume this memory is readable + const cie_offset = fde_entry_header.type.fde; + try eh_frame_fbr.seekTo(cie_offset); + const cie_entry_header = try EntryHeader.read(&eh_frame_fbr, if (eh_frame_len == null) ma else null, .eh_frame); + if (!self.isValidPtr(u8, @intFromPtr(&cie_entry_header.entry_bytes[cie_entry_header.entry_bytes.len - 1]), ma, eh_frame_len)) return badDwarf(); + if (cie_entry_header.type != .cie) return badDwarf(); + + cie.* = try CommonInformationEntry.parse( + cie_entry_header.entry_bytes, + 0, + true, + cie_entry_header.format, + .eh_frame, + cie_entry_header.length_offset, + @sizeOf(usize), + native_endian, + ); + + fde.* = try FrameDescriptionEntry.parse( + fde_entry_header.entry_bytes, + 0, + true, + cie.*, + @sizeOf(usize), + native_endian, + ); + } +}; + +pub const EntryHeader = struct { + /// Offset of the length field in the backing buffer + length_offset: usize, + format: Format, + type: union(enum) { + cie, + /// Value is the offset of the corresponding CIE + fde: u64, + terminator, + }, + /// The entry's contents, not including the ID field + entry_bytes: []const u8, + + /// The length of the entry including the ID field, but not the length field itself + pub fn entryLength(self: EntryHeader) usize { + return self.entry_bytes.len + @as(u8, if (self.format == .@"64") 8 else 4); + } + + /// Reads a header for either an FDE or a CIE, then advances the fbr to the position after the trailing structure. + /// `fbr` must be a FixedBufferReader backed by either the .eh_frame or .debug_frame sections. + pub fn read( + fbr: *FixedBufferReader, + opt_ma: ?*StackIterator.MemoryAccessor, + dwarf_section: Section.Id, + ) !EntryHeader { + assert(dwarf_section == .eh_frame or dwarf_section == .debug_frame); + + const length_offset = fbr.pos; + const unit_header = try readUnitHeader(fbr, opt_ma); + const unit_length = cast(usize, unit_header.unit_length) orelse return badDwarf(); + if (unit_length == 0) return .{ + .length_offset = length_offset, + .format = unit_header.format, + .type = .terminator, + .entry_bytes = &.{}, + }; + const start_offset = fbr.pos; + const end_offset = start_offset + unit_length; + defer fbr.pos = end_offset; + + const id = try if (opt_ma) |ma| + fbr.readAddressChecked(unit_header.format, ma) + else + fbr.readAddress(unit_header.format); + const entry_bytes = fbr.buf[fbr.pos..end_offset]; + const cie_id: u64 = switch (dwarf_section) { + .eh_frame => CommonInformationEntry.eh_id, + .debug_frame => switch (unit_header.format) { + .@"32" => CommonInformationEntry.dwarf32_id, + .@"64" => CommonInformationEntry.dwarf64_id, + }, + else => unreachable, + }; + + return .{ + .length_offset = length_offset, + .format = unit_header.format, + .type = if (id == cie_id) .cie else .{ .fde = switch (dwarf_section) { + .eh_frame => try std.math.sub(u64, start_offset, id), + .debug_frame => id, + else => unreachable, + } }, + .entry_bytes = entry_bytes, + }; + } +}; + +pub const CommonInformationEntry = struct { + // Used in .eh_frame + pub const eh_id = 0; + + // Used in .debug_frame (DWARF32) + pub const dwarf32_id = maxInt(u32); + + // Used in .debug_frame (DWARF64) + pub const dwarf64_id = maxInt(u64); + + // Offset of the length field of this entry in the eh_frame section. + // This is the key that FDEs use to reference CIEs. + length_offset: u64, + version: u8, + address_size: u8, + format: Format, + + // Only present in version 4 + segment_selector_size: ?u8, + + code_alignment_factor: u32, + data_alignment_factor: i32, + return_address_register: u8, + + aug_str: []const u8, + aug_data: []const u8, + lsda_pointer_enc: u8, + personality_enc: ?u8, + personality_routine_pointer: ?u64, + fde_pointer_enc: u8, + initial_instructions: []const u8, + + pub fn isSignalFrame(self: CommonInformationEntry) bool { + for (self.aug_str) |c| if (c == 'S') return true; + return false; + } + + pub fn addressesSignedWithBKey(self: CommonInformationEntry) bool { + for (self.aug_str) |c| if (c == 'B') return true; + return false; + } + + pub fn mteTaggedFrame(self: CommonInformationEntry) bool { + for (self.aug_str) |c| if (c == 'G') return true; + return false; + } + + /// This function expects to read the CIE starting with the version field. + /// The returned struct references memory backed by cie_bytes. + /// + /// See the FrameDescriptionEntry.parse documentation for the description + /// of `pc_rel_offset` and `is_runtime`. + /// + /// `length_offset` specifies the offset of this CIE's length field in the + /// .eh_frame / .debug_frame section. + pub fn parse( + cie_bytes: []const u8, + pc_rel_offset: i64, + is_runtime: bool, + format: Format, + dwarf_section: Section.Id, + length_offset: u64, + addr_size_bytes: u8, + endian: std.builtin.Endian, + ) !CommonInformationEntry { + if (addr_size_bytes > 8) return error.UnsupportedAddrSize; + + var fbr: FixedBufferReader = .{ .buf = cie_bytes, .endian = endian }; + + const version = try fbr.readByte(); + switch (dwarf_section) { + .eh_frame => if (version != 1 and version != 3) return error.UnsupportedDwarfVersion, + .debug_frame => if (version != 4) return error.UnsupportedDwarfVersion, + else => return error.UnsupportedDwarfSection, + } + + var has_eh_data = false; + var has_aug_data = false; + + var aug_str_len: usize = 0; + const aug_str_start = fbr.pos; + var aug_byte = try fbr.readByte(); + while (aug_byte != 0) : (aug_byte = try fbr.readByte()) { + switch (aug_byte) { + 'z' => { + if (aug_str_len != 0) return badDwarf(); + has_aug_data = true; + }, + 'e' => { + if (has_aug_data or aug_str_len != 0) return badDwarf(); + if (try fbr.readByte() != 'h') return badDwarf(); + has_eh_data = true; + }, + else => if (has_eh_data) return badDwarf(), + } + + aug_str_len += 1; + } + + if (has_eh_data) { + // legacy data created by older versions of gcc - unsupported here + for (0..addr_size_bytes) |_| _ = try fbr.readByte(); + } + + const address_size = if (version == 4) try fbr.readByte() else addr_size_bytes; + const segment_selector_size = if (version == 4) try fbr.readByte() else null; + + const code_alignment_factor = try fbr.readUleb128(u32); + const data_alignment_factor = try fbr.readIleb128(i32); + const return_address_register = if (version == 1) try fbr.readByte() else try fbr.readUleb128(u8); + + var lsda_pointer_enc: u8 = EH.PE.omit; + var personality_enc: ?u8 = null; + var personality_routine_pointer: ?u64 = null; + var fde_pointer_enc: u8 = EH.PE.absptr; + + var aug_data: []const u8 = &[_]u8{}; + const aug_str = if (has_aug_data) blk: { + const aug_data_len = try fbr.readUleb128(usize); + const aug_data_start = fbr.pos; + aug_data = cie_bytes[aug_data_start..][0..aug_data_len]; + + const aug_str = cie_bytes[aug_str_start..][0..aug_str_len]; + for (aug_str[1..]) |byte| { + switch (byte) { + 'L' => { + lsda_pointer_enc = try fbr.readByte(); + }, + 'P' => { + personality_enc = try fbr.readByte(); + personality_routine_pointer = try readEhPointer(&fbr, personality_enc.?, addr_size_bytes, .{ + .pc_rel_base = try pcRelBase(@intFromPtr(&cie_bytes[fbr.pos]), pc_rel_offset), + .follow_indirect = is_runtime, + }); + }, + 'R' => { + fde_pointer_enc = try fbr.readByte(); + }, + 'S', 'B', 'G' => {}, + else => return badDwarf(), + } + } + + // aug_data_len can include padding so the CIE ends on an address boundary + fbr.pos = aug_data_start + aug_data_len; + break :blk aug_str; + } else &[_]u8{}; + + const initial_instructions = cie_bytes[fbr.pos..]; + return .{ + .length_offset = length_offset, + .version = version, + .address_size = address_size, + .format = format, + .segment_selector_size = segment_selector_size, + .code_alignment_factor = code_alignment_factor, + .data_alignment_factor = data_alignment_factor, + .return_address_register = return_address_register, + .aug_str = aug_str, + .aug_data = aug_data, + .lsda_pointer_enc = lsda_pointer_enc, + .personality_enc = personality_enc, + .personality_routine_pointer = personality_routine_pointer, + .fde_pointer_enc = fde_pointer_enc, + .initial_instructions = initial_instructions, + }; + } +}; + +pub const FrameDescriptionEntry = struct { + // Offset into eh_frame where the CIE for this FDE is stored + cie_length_offset: u64, + + pc_begin: u64, + pc_range: u64, + lsda_pointer: ?u64, + aug_data: []const u8, + instructions: []const u8, + + /// This function expects to read the FDE starting at the PC Begin field. + /// The returned struct references memory backed by `fde_bytes`. + /// + /// `pc_rel_offset` specifies an offset to be applied to pc_rel_base values + /// used when decoding pointers. This should be set to zero if fde_bytes is + /// backed by the memory of a .eh_frame / .debug_frame section in the running executable. + /// Otherwise, it should be the relative offset to translate addresses from + /// where the section is currently stored in memory, to where it *would* be + /// stored at runtime: section base addr - backing data base ptr. + /// + /// Similarly, `is_runtime` specifies this function is being called on a runtime + /// section, and so indirect pointers can be followed. + pub fn parse( + fde_bytes: []const u8, + pc_rel_offset: i64, + is_runtime: bool, + cie: CommonInformationEntry, + addr_size_bytes: u8, + endian: std.builtin.Endian, + ) !FrameDescriptionEntry { + if (addr_size_bytes > 8) return error.InvalidAddrSize; + + var fbr: FixedBufferReader = .{ .buf = fde_bytes, .endian = endian }; + + const pc_begin = try readEhPointer(&fbr, cie.fde_pointer_enc, addr_size_bytes, .{ + .pc_rel_base = try pcRelBase(@intFromPtr(&fde_bytes[fbr.pos]), pc_rel_offset), + .follow_indirect = is_runtime, + }) orelse return badDwarf(); + + const pc_range = try readEhPointer(&fbr, cie.fde_pointer_enc, addr_size_bytes, .{ + .pc_rel_base = 0, + .follow_indirect = false, + }) orelse return badDwarf(); + + var aug_data: []const u8 = &[_]u8{}; + const lsda_pointer = if (cie.aug_str.len > 0) blk: { + const aug_data_len = try fbr.readUleb128(usize); + const aug_data_start = fbr.pos; + aug_data = fde_bytes[aug_data_start..][0..aug_data_len]; + + const lsda_pointer = if (cie.lsda_pointer_enc != EH.PE.omit) + try readEhPointer(&fbr, cie.lsda_pointer_enc, addr_size_bytes, .{ + .pc_rel_base = try pcRelBase(@intFromPtr(&fde_bytes[fbr.pos]), pc_rel_offset), + .follow_indirect = is_runtime, + }) + else + null; + + fbr.pos = aug_data_start + aug_data_len; + break :blk lsda_pointer; + } else null; + + const instructions = fde_bytes[fbr.pos..]; + return .{ + .cie_length_offset = cie.length_offset, + .pc_begin = pc_begin, + .pc_range = pc_range, + .lsda_pointer = lsda_pointer, + .aug_data = aug_data, + .instructions = instructions, + }; + } +}; + +pub const UnwindContext = struct { + allocator: Allocator, + cfa: ?usize, + pc: usize, + thread_context: *std.debug.ThreadContext, + reg_context: abi.RegisterContext, + vm: call_frame.VirtualMachine, + stack_machine: expression.StackMachine(.{ .call_frame_context = true }), + + pub fn init( + allocator: Allocator, + thread_context: *const std.debug.ThreadContext, + ) !UnwindContext { + const pc = abi.stripInstructionPtrAuthCode( + (try abi.regValueNative( + usize, + thread_context, + abi.ipRegNum(), + null, + )).*, + ); + + const context_copy = try allocator.create(std.debug.ThreadContext); + std.debug.copyContext(thread_context, context_copy); + + return .{ + .allocator = allocator, + .cfa = null, + .pc = pc, + .thread_context = context_copy, + .reg_context = undefined, + .vm = .{}, + .stack_machine = .{}, + }; + } + + pub fn deinit(self: *UnwindContext) void { + self.vm.deinit(self.allocator); + self.stack_machine.deinit(self.allocator); + self.allocator.destroy(self.thread_context); + self.* = undefined; + } + + pub fn getFp(self: *const UnwindContext) !usize { + return (try abi.regValueNative(usize, self.thread_context, abi.fpRegNum(self.reg_context), self.reg_context)).*; + } +}; + +const num_sections = std.enums.directEnumArrayLen(Section.Id, 0); +pub const SectionArray = [num_sections]?Section; +pub const null_section_array = [_]?Section{null} ** num_sections; + +/// Initialize DWARF info. The caller has the responsibility to initialize most +/// the `Dwarf` fields before calling. `binary_mem` is the raw bytes of the +/// main binary file (not the secondary debug info file). +pub fn open(di: *Dwarf, allocator: Allocator) !void { + try di.scanAllFunctions(allocator); + try di.scanAllCompileUnits(allocator); +} + +const PcRange = struct { + start: u64, + end: u64, +}; + +const Func = struct { + pc_range: ?PcRange, + name: ?[]const u8, +}; + +pub fn section(di: Dwarf, dwarf_section: Section.Id) ?[]const u8 { + return if (di.sections[@intFromEnum(dwarf_section)]) |s| s.data else null; +} + +pub fn sectionVirtualOffset(di: Dwarf, dwarf_section: Section.Id, base_address: usize) ?i64 { + return if (di.sections[@intFromEnum(dwarf_section)]) |s| s.virtualOffset(base_address) else null; +} + +pub fn deinit(di: *Dwarf, allocator: Allocator) void { + for (di.sections) |opt_section| { + if (opt_section) |s| if (s.owned) allocator.free(s.data); + } + for (di.abbrev_table_list.items) |*abbrev| { + abbrev.deinit(allocator); + } + di.abbrev_table_list.deinit(allocator); + for (di.compile_unit_list.items) |*cu| { + cu.die.deinit(allocator); + } + di.compile_unit_list.deinit(allocator); + di.func_list.deinit(allocator); + di.cie_map.deinit(allocator); + di.fde_list.deinit(allocator); + di.* = undefined; +} + +pub fn getSymbolName(di: *Dwarf, address: u64) ?[]const u8 { + for (di.func_list.items) |*func| { + if (func.pc_range) |range| { + if (address >= range.start and address < range.end) { + return func.name; + } + } + } + + return null; +} + +fn scanAllFunctions(di: *Dwarf, allocator: Allocator) !void { + var fbr: FixedBufferReader = .{ .buf = di.section(.debug_info).?, .endian = di.endian }; + var this_unit_offset: u64 = 0; + + while (this_unit_offset < fbr.buf.len) { + try fbr.seekTo(this_unit_offset); + + const unit_header = try readUnitHeader(&fbr, null); + if (unit_header.unit_length == 0) return; + const next_offset = unit_header.header_length + unit_header.unit_length; + + const version = try fbr.readInt(u16); + if (version < 2 or version > 5) return badDwarf(); + + var address_size: u8 = undefined; + var debug_abbrev_offset: u64 = undefined; + if (version >= 5) { + const unit_type = try fbr.readInt(u8); + if (unit_type != DW.UT.compile) return badDwarf(); + address_size = try fbr.readByte(); + debug_abbrev_offset = try fbr.readAddress(unit_header.format); + } else { + debug_abbrev_offset = try fbr.readAddress(unit_header.format); + address_size = try fbr.readByte(); + } + if (address_size != @sizeOf(usize)) return badDwarf(); + + const abbrev_table = try di.getAbbrevTable(allocator, debug_abbrev_offset); + + var max_attrs: usize = 0; + var zig_padding_abbrev_code: u7 = 0; + for (abbrev_table.abbrevs) |abbrev| { + max_attrs = @max(max_attrs, abbrev.attrs.len); + if (cast(u7, abbrev.code)) |code| { + if (abbrev.tag_id == DW.TAG.ZIG_padding and + !abbrev.has_children and + abbrev.attrs.len == 0) + { + zig_padding_abbrev_code = code; + } + } + } + const attrs_buf = try allocator.alloc(Die.Attr, max_attrs * 3); + defer allocator.free(attrs_buf); + var attrs_bufs: [3][]Die.Attr = undefined; + for (&attrs_bufs, 0..) |*buf, index| buf.* = attrs_buf[index * max_attrs ..][0..max_attrs]; + + const next_unit_pos = this_unit_offset + next_offset; + + var compile_unit: CompileUnit = .{ + .version = version, + .format = unit_header.format, + .die = undefined, + .pc_range = null, + + .str_offsets_base = 0, + .addr_base = 0, + .rnglists_base = 0, + .loclists_base = 0, + .frame_base = null, + }; + + while (true) { + fbr.pos = std.mem.indexOfNonePos(u8, fbr.buf, fbr.pos, &.{ + zig_padding_abbrev_code, 0, + }) orelse fbr.buf.len; + if (fbr.pos >= next_unit_pos) break; + var die_obj = (try parseDie( + &fbr, + attrs_bufs[0], + abbrev_table, + unit_header.format, + )) orelse continue; + + switch (die_obj.tag_id) { + DW.TAG.compile_unit => { + compile_unit.die = die_obj; + compile_unit.die.attrs = attrs_bufs[1][0..die_obj.attrs.len]; + @memcpy(compile_unit.die.attrs, die_obj.attrs); + + compile_unit.str_offsets_base = if (die_obj.getAttr(AT.str_offsets_base)) |fv| try fv.getUInt(usize) else 0; + compile_unit.addr_base = if (die_obj.getAttr(AT.addr_base)) |fv| try fv.getUInt(usize) else 0; + compile_unit.rnglists_base = if (die_obj.getAttr(AT.rnglists_base)) |fv| try fv.getUInt(usize) else 0; + compile_unit.loclists_base = if (die_obj.getAttr(AT.loclists_base)) |fv| try fv.getUInt(usize) else 0; + compile_unit.frame_base = die_obj.getAttr(AT.frame_base); + }, + DW.TAG.subprogram, DW.TAG.inlined_subroutine, DW.TAG.subroutine, DW.TAG.entry_point => { + const fn_name = x: { + var this_die_obj = die_obj; + // Prevent endless loops + for (0..3) |_| { + if (this_die_obj.getAttr(AT.name)) |_| { + break :x try this_die_obj.getAttrString(di, AT.name, di.section(.debug_str), compile_unit); + } else if (this_die_obj.getAttr(AT.abstract_origin)) |_| { + const after_die_offset = fbr.pos; + defer fbr.pos = after_die_offset; + + // Follow the DIE it points to and repeat + const ref_offset = try this_die_obj.getAttrRef(AT.abstract_origin); + if (ref_offset > next_offset) return badDwarf(); + try fbr.seekTo(this_unit_offset + ref_offset); + this_die_obj = (try parseDie( + &fbr, + attrs_bufs[2], + abbrev_table, + unit_header.format, + )) orelse return badDwarf(); + } else if (this_die_obj.getAttr(AT.specification)) |_| { + const after_die_offset = fbr.pos; + defer fbr.pos = after_die_offset; + + // Follow the DIE it points to and repeat + const ref_offset = try this_die_obj.getAttrRef(AT.specification); + if (ref_offset > next_offset) return badDwarf(); + try fbr.seekTo(this_unit_offset + ref_offset); + this_die_obj = (try parseDie( + &fbr, + attrs_bufs[2], + abbrev_table, + unit_header.format, + )) orelse return badDwarf(); + } else { + break :x null; + } + } + + break :x null; + }; + + var range_added = if (die_obj.getAttrAddr(di, AT.low_pc, compile_unit)) |low_pc| blk: { + if (die_obj.getAttr(AT.high_pc)) |high_pc_value| { + const pc_end = switch (high_pc_value.*) { + .addr => |value| value, + .udata => |offset| low_pc + offset, + else => return badDwarf(), + }; + + try di.func_list.append(allocator, .{ + .name = fn_name, + .pc_range = .{ + .start = low_pc, + .end = pc_end, + }, + }); + + break :blk true; + } + + break :blk false; + } else |err| blk: { + if (err != error.MissingDebugInfo) return err; + break :blk false; + }; + + if (die_obj.getAttr(AT.ranges)) |ranges_value| blk: { + var iter = DebugRangeIterator.init(ranges_value, di, &compile_unit) catch |err| { + if (err != error.MissingDebugInfo) return err; + break :blk; + }; + + while (try iter.next()) |range| { + range_added = true; + try di.func_list.append(allocator, .{ + .name = fn_name, + .pc_range = .{ + .start = range.start_addr, + .end = range.end_addr, + }, + }); + } + } + + if (fn_name != null and !range_added) { + try di.func_list.append(allocator, .{ + .name = fn_name, + .pc_range = null, + }); + } + }, + else => {}, + } + } + + this_unit_offset += next_offset; + } +} + +fn scanAllCompileUnits(di: *Dwarf, allocator: Allocator) !void { + var fbr: FixedBufferReader = .{ .buf = di.section(.debug_info).?, .endian = di.endian }; + var this_unit_offset: u64 = 0; + + var attrs_buf = std.ArrayList(Die.Attr).init(allocator); + defer attrs_buf.deinit(); + + while (this_unit_offset < fbr.buf.len) { + try fbr.seekTo(this_unit_offset); + + const unit_header = try readUnitHeader(&fbr, null); + if (unit_header.unit_length == 0) return; + const next_offset = unit_header.header_length + unit_header.unit_length; + + const version = try fbr.readInt(u16); + if (version < 2 or version > 5) return badDwarf(); + + var address_size: u8 = undefined; + var debug_abbrev_offset: u64 = undefined; + if (version >= 5) { + const unit_type = try fbr.readInt(u8); + if (unit_type != UT.compile) return badDwarf(); + address_size = try fbr.readByte(); + debug_abbrev_offset = try fbr.readAddress(unit_header.format); + } else { + debug_abbrev_offset = try fbr.readAddress(unit_header.format); + address_size = try fbr.readByte(); + } + if (address_size != @sizeOf(usize)) return badDwarf(); + + const abbrev_table = try di.getAbbrevTable(allocator, debug_abbrev_offset); + + var max_attrs: usize = 0; + for (abbrev_table.abbrevs) |abbrev| { + max_attrs = @max(max_attrs, abbrev.attrs.len); + } + try attrs_buf.resize(max_attrs); + + var compile_unit_die = (try parseDie( + &fbr, + attrs_buf.items, + abbrev_table, + unit_header.format, + )) orelse return badDwarf(); + + if (compile_unit_die.tag_id != DW.TAG.compile_unit) return badDwarf(); + + compile_unit_die.attrs = try allocator.dupe(Die.Attr, compile_unit_die.attrs); + + var compile_unit: CompileUnit = .{ + .version = version, + .format = unit_header.format, + .pc_range = null, + .die = compile_unit_die, + .str_offsets_base = if (compile_unit_die.getAttr(AT.str_offsets_base)) |fv| try fv.getUInt(usize) else 0, + .addr_base = if (compile_unit_die.getAttr(AT.addr_base)) |fv| try fv.getUInt(usize) else 0, + .rnglists_base = if (compile_unit_die.getAttr(AT.rnglists_base)) |fv| try fv.getUInt(usize) else 0, + .loclists_base = if (compile_unit_die.getAttr(AT.loclists_base)) |fv| try fv.getUInt(usize) else 0, + .frame_base = compile_unit_die.getAttr(AT.frame_base), + }; + + compile_unit.pc_range = x: { + if (compile_unit_die.getAttrAddr(di, AT.low_pc, compile_unit)) |low_pc| { + if (compile_unit_die.getAttr(AT.high_pc)) |high_pc_value| { + const pc_end = switch (high_pc_value.*) { + .addr => |value| value, + .udata => |offset| low_pc + offset, + else => return badDwarf(), + }; + break :x PcRange{ + .start = low_pc, + .end = pc_end, + }; + } else { + break :x null; + } + } else |err| { + if (err != error.MissingDebugInfo) return err; + break :x null; + } + }; + + try di.compile_unit_list.append(allocator, compile_unit); + + this_unit_offset += next_offset; + } +} + +const DebugRangeIterator = struct { + base_address: u64, + section_type: Section.Id, + di: *const Dwarf, + compile_unit: *const CompileUnit, + fbr: FixedBufferReader, + + pub fn init(ranges_value: *const FormValue, di: *const Dwarf, compile_unit: *const CompileUnit) !@This() { + const section_type = if (compile_unit.version >= 5) Section.Id.debug_rnglists else Section.Id.debug_ranges; + const debug_ranges = di.section(section_type) orelse return error.MissingDebugInfo; + + const ranges_offset = switch (ranges_value.*) { + .sec_offset, .udata => |off| off, + .rnglistx => |idx| off: { + switch (compile_unit.format) { + .@"32" => { + const offset_loc = @as(usize, @intCast(compile_unit.rnglists_base + 4 * idx)); + if (offset_loc + 4 > debug_ranges.len) return badDwarf(); + const offset = readInt(u32, debug_ranges[offset_loc..][0..4], di.endian); + break :off compile_unit.rnglists_base + offset; + }, + .@"64" => { + const offset_loc = @as(usize, @intCast(compile_unit.rnglists_base + 8 * idx)); + if (offset_loc + 8 > debug_ranges.len) return badDwarf(); + const offset = readInt(u64, debug_ranges[offset_loc..][0..8], di.endian); + break :off compile_unit.rnglists_base + offset; + }, + } + }, + else => return badDwarf(), + }; + + // All the addresses in the list are relative to the value + // specified by DW_AT.low_pc or to some other value encoded + // in the list itself. + // If no starting value is specified use zero. + const base_address = compile_unit.die.getAttrAddr(di, AT.low_pc, compile_unit.*) catch |err| switch (err) { + error.MissingDebugInfo => 0, + else => return err, + }; + + return .{ + .base_address = base_address, + .section_type = section_type, + .di = di, + .compile_unit = compile_unit, + .fbr = .{ + .buf = debug_ranges, + .pos = cast(usize, ranges_offset) orelse return badDwarf(), + .endian = di.endian, + }, + }; + } + + // Returns the next range in the list, or null if the end was reached. + pub fn next(self: *@This()) !?struct { start_addr: u64, end_addr: u64 } { + switch (self.section_type) { + .debug_rnglists => { + const kind = try self.fbr.readByte(); + switch (kind) { + RLE.end_of_list => return null, + RLE.base_addressx => { + const index = try self.fbr.readUleb128(usize); + self.base_address = try self.di.readDebugAddr(self.compile_unit.*, index); + return try self.next(); + }, + RLE.startx_endx => { + const start_index = try self.fbr.readUleb128(usize); + const start_addr = try self.di.readDebugAddr(self.compile_unit.*, start_index); + + const end_index = try self.fbr.readUleb128(usize); + const end_addr = try self.di.readDebugAddr(self.compile_unit.*, end_index); + + return .{ + .start_addr = start_addr, + .end_addr = end_addr, + }; + }, + RLE.startx_length => { + const start_index = try self.fbr.readUleb128(usize); + const start_addr = try self.di.readDebugAddr(self.compile_unit.*, start_index); + + const len = try self.fbr.readUleb128(usize); + const end_addr = start_addr + len; + + return .{ + .start_addr = start_addr, + .end_addr = end_addr, + }; + }, + RLE.offset_pair => { + const start_addr = try self.fbr.readUleb128(usize); + const end_addr = try self.fbr.readUleb128(usize); + + // This is the only kind that uses the base address + return .{ + .start_addr = self.base_address + start_addr, + .end_addr = self.base_address + end_addr, + }; + }, + RLE.base_address => { + self.base_address = try self.fbr.readInt(usize); + return try self.next(); + }, + RLE.start_end => { + const start_addr = try self.fbr.readInt(usize); + const end_addr = try self.fbr.readInt(usize); + + return .{ + .start_addr = start_addr, + .end_addr = end_addr, + }; + }, + RLE.start_length => { + const start_addr = try self.fbr.readInt(usize); + const len = try self.fbr.readUleb128(usize); + const end_addr = start_addr + len; + + return .{ + .start_addr = start_addr, + .end_addr = end_addr, + }; + }, + else => return badDwarf(), + } + }, + .debug_ranges => { + const start_addr = try self.fbr.readInt(usize); + const end_addr = try self.fbr.readInt(usize); + if (start_addr == 0 and end_addr == 0) return null; + + // This entry selects a new value for the base address + if (start_addr == maxInt(usize)) { + self.base_address = end_addr; + return try self.next(); + } + + return .{ + .start_addr = self.base_address + start_addr, + .end_addr = self.base_address + end_addr, + }; + }, + else => unreachable, + } + } +}; + +pub fn findCompileUnit(di: *const Dwarf, target_address: u64) !*const CompileUnit { + for (di.compile_unit_list.items) |*compile_unit| { + if (compile_unit.pc_range) |range| { + if (target_address >= range.start and target_address < range.end) return compile_unit; + } + + const ranges_value = compile_unit.die.getAttr(AT.ranges) orelse continue; + var iter = DebugRangeIterator.init(ranges_value, di, compile_unit) catch continue; + while (try iter.next()) |range| { + if (target_address >= range.start_addr and target_address < range.end_addr) return compile_unit; + } + } + + return missingDwarf(); +} + +/// Gets an already existing AbbrevTable given the abbrev_offset, or if not found, +/// seeks in the stream and parses it. +fn getAbbrevTable(di: *Dwarf, allocator: Allocator, abbrev_offset: u64) !*const Abbrev.Table { + for (di.abbrev_table_list.items) |*table| { + if (table.offset == abbrev_offset) { + return table; + } + } + try di.abbrev_table_list.append( + allocator, + try di.parseAbbrevTable(allocator, abbrev_offset), + ); + return &di.abbrev_table_list.items[di.abbrev_table_list.items.len - 1]; +} + +fn parseAbbrevTable(di: *Dwarf, allocator: Allocator, offset: u64) !Abbrev.Table { + var fbr: FixedBufferReader = .{ + .buf = di.section(.debug_abbrev).?, + .pos = cast(usize, offset) orelse return badDwarf(), + .endian = di.endian, + }; + + var abbrevs = std.ArrayList(Abbrev).init(allocator); + defer { + for (abbrevs.items) |*abbrev| { + abbrev.deinit(allocator); + } + abbrevs.deinit(); + } + + var attrs = std.ArrayList(Abbrev.Attr).init(allocator); + defer attrs.deinit(); + + while (true) { + const code = try fbr.readUleb128(u64); + if (code == 0) break; + const tag_id = try fbr.readUleb128(u64); + const has_children = (try fbr.readByte()) == DW.CHILDREN.yes; + + while (true) { + const attr_id = try fbr.readUleb128(u64); + const form_id = try fbr.readUleb128(u64); + if (attr_id == 0 and form_id == 0) break; + try attrs.append(.{ + .id = attr_id, + .form_id = form_id, + .payload = switch (form_id) { + FORM.implicit_const => try fbr.readIleb128(i64), + else => undefined, + }, + }); + } + + try abbrevs.append(.{ + .code = code, + .tag_id = tag_id, + .has_children = has_children, + .attrs = try attrs.toOwnedSlice(), + }); + } + + return .{ + .offset = offset, + .abbrevs = try abbrevs.toOwnedSlice(), + }; +} + +fn parseDie( + fbr: *FixedBufferReader, + attrs_buf: []Die.Attr, + abbrev_table: *const Abbrev.Table, + format: Format, +) !?Die { + const abbrev_code = try fbr.readUleb128(u64); + if (abbrev_code == 0) return null; + const table_entry = abbrev_table.get(abbrev_code) orelse return badDwarf(); + + const attrs = attrs_buf[0..table_entry.attrs.len]; + for (attrs, table_entry.attrs) |*result_attr, attr| result_attr.* = Die.Attr{ + .id = attr.id, + .value = try parseFormValue( + fbr, + attr.form_id, + format, + attr.payload, + ), + }; + return .{ + .tag_id = table_entry.tag_id, + .has_children = table_entry.has_children, + .attrs = attrs, + }; +} + +pub fn getLineNumberInfo( + di: *Dwarf, + allocator: Allocator, + compile_unit: CompileUnit, + target_address: u64, +) !std.debug.LineInfo { + const compile_unit_cwd = try compile_unit.die.getAttrString(di, AT.comp_dir, di.section(.debug_line_str), compile_unit); + const line_info_offset = try compile_unit.die.getAttrSecOffset(AT.stmt_list); + + var fbr: FixedBufferReader = .{ .buf = di.section(.debug_line).?, .endian = di.endian }; + try fbr.seekTo(line_info_offset); + + const unit_header = try readUnitHeader(&fbr, null); + if (unit_header.unit_length == 0) return missingDwarf(); + const next_offset = unit_header.header_length + unit_header.unit_length; + + const version = try fbr.readInt(u16); + if (version < 2) return badDwarf(); + + var addr_size: u8 = switch (unit_header.format) { + .@"32" => 4, + .@"64" => 8, + }; + var seg_size: u8 = 0; + if (version >= 5) { + addr_size = try fbr.readByte(); + seg_size = try fbr.readByte(); + } + + const prologue_length = try fbr.readAddress(unit_header.format); + const prog_start_offset = fbr.pos + prologue_length; + + const minimum_instruction_length = try fbr.readByte(); + if (minimum_instruction_length == 0) return badDwarf(); + + if (version >= 4) { + // maximum_operations_per_instruction + _ = try fbr.readByte(); + } + + const default_is_stmt = (try fbr.readByte()) != 0; + const line_base = try fbr.readByteSigned(); + + const line_range = try fbr.readByte(); + if (line_range == 0) return badDwarf(); + + const opcode_base = try fbr.readByte(); + + const standard_opcode_lengths = try fbr.readBytes(opcode_base - 1); + + var include_directories = std.ArrayList(FileEntry).init(allocator); + defer include_directories.deinit(); + var file_entries = std.ArrayList(FileEntry).init(allocator); + defer file_entries.deinit(); + + if (version < 5) { + try include_directories.append(.{ .path = compile_unit_cwd }); + + while (true) { + const dir = try fbr.readBytesTo(0); + if (dir.len == 0) break; + try include_directories.append(.{ .path = dir }); + } + + while (true) { + const file_name = try fbr.readBytesTo(0); + if (file_name.len == 0) break; + const dir_index = try fbr.readUleb128(u32); + const mtime = try fbr.readUleb128(u64); + const size = try fbr.readUleb128(u64); + try file_entries.append(.{ + .path = file_name, + .dir_index = dir_index, + .mtime = mtime, + .size = size, + }); + } + } else { + const FileEntFmt = struct { + content_type_code: u8, + form_code: u16, + }; + { + var dir_ent_fmt_buf: [10]FileEntFmt = undefined; + const directory_entry_format_count = try fbr.readByte(); + if (directory_entry_format_count > dir_ent_fmt_buf.len) return badDwarf(); + for (dir_ent_fmt_buf[0..directory_entry_format_count]) |*ent_fmt| { + ent_fmt.* = .{ + .content_type_code = try fbr.readUleb128(u8), + .form_code = try fbr.readUleb128(u16), + }; + } + + const directories_count = try fbr.readUleb128(usize); + try include_directories.ensureUnusedCapacity(directories_count); + { + var i: usize = 0; + while (i < directories_count) : (i += 1) { + var e: FileEntry = .{ .path = &.{} }; + for (dir_ent_fmt_buf[0..directory_entry_format_count]) |ent_fmt| { + const form_value = try parseFormValue( + &fbr, + ent_fmt.form_code, + unit_header.format, + null, + ); + switch (ent_fmt.content_type_code) { + DW.LNCT.path => e.path = try form_value.getString(di.*), + DW.LNCT.directory_index => e.dir_index = try form_value.getUInt(u32), + DW.LNCT.timestamp => e.mtime = try form_value.getUInt(u64), + DW.LNCT.size => e.size = try form_value.getUInt(u64), + DW.LNCT.MD5 => e.md5 = switch (form_value) { + .data16 => |data16| data16.*, + else => return badDwarf(), + }, + else => continue, + } + } + include_directories.appendAssumeCapacity(e); + } + } + } + + var file_ent_fmt_buf: [10]FileEntFmt = undefined; + const file_name_entry_format_count = try fbr.readByte(); + if (file_name_entry_format_count > file_ent_fmt_buf.len) return badDwarf(); + for (file_ent_fmt_buf[0..file_name_entry_format_count]) |*ent_fmt| { + ent_fmt.* = .{ + .content_type_code = try fbr.readUleb128(u8), + .form_code = try fbr.readUleb128(u16), + }; + } + + const file_names_count = try fbr.readUleb128(usize); + try file_entries.ensureUnusedCapacity(file_names_count); + { + var i: usize = 0; + while (i < file_names_count) : (i += 1) { + var e: FileEntry = .{ .path = &.{} }; + for (file_ent_fmt_buf[0..file_name_entry_format_count]) |ent_fmt| { + const form_value = try parseFormValue( + &fbr, + ent_fmt.form_code, + unit_header.format, + null, + ); + switch (ent_fmt.content_type_code) { + DW.LNCT.path => e.path = try form_value.getString(di.*), + DW.LNCT.directory_index => e.dir_index = try form_value.getUInt(u32), + DW.LNCT.timestamp => e.mtime = try form_value.getUInt(u64), + DW.LNCT.size => e.size = try form_value.getUInt(u64), + DW.LNCT.MD5 => e.md5 = switch (form_value) { + .data16 => |data16| data16.*, + else => return badDwarf(), + }, + else => continue, + } + } + file_entries.appendAssumeCapacity(e); + } + } + } + + var prog = LineNumberProgram.init( + default_is_stmt, + include_directories.items, + target_address, + version, + ); + + try fbr.seekTo(prog_start_offset); + + const next_unit_pos = line_info_offset + next_offset; + + while (fbr.pos < next_unit_pos) { + const opcode = try fbr.readByte(); + + if (opcode == DW.LNS.extended_op) { + const op_size = try fbr.readUleb128(u64); + if (op_size < 1) return badDwarf(); + const sub_op = try fbr.readByte(); + switch (sub_op) { + DW.LNE.end_sequence => { + prog.end_sequence = true; + if (try prog.checkLineMatch(allocator, file_entries.items)) |info| return info; + prog.reset(); + }, + DW.LNE.set_address => { + const addr = try fbr.readInt(usize); + prog.address = addr; + }, + DW.LNE.define_file => { + const path = try fbr.readBytesTo(0); + const dir_index = try fbr.readUleb128(u32); + const mtime = try fbr.readUleb128(u64); + const size = try fbr.readUleb128(u64); + try file_entries.append(.{ + .path = path, + .dir_index = dir_index, + .mtime = mtime, + .size = size, + }); + }, + else => try fbr.seekForward(op_size - 1), + } + } else if (opcode >= opcode_base) { + // special opcodes + const adjusted_opcode = opcode - opcode_base; + const inc_addr = minimum_instruction_length * (adjusted_opcode / line_range); + const inc_line = @as(i32, line_base) + @as(i32, adjusted_opcode % line_range); + prog.line += inc_line; + prog.address += inc_addr; + if (try prog.checkLineMatch(allocator, file_entries.items)) |info| return info; + prog.basic_block = false; + } else { + switch (opcode) { + DW.LNS.copy => { + if (try prog.checkLineMatch(allocator, file_entries.items)) |info| return info; + prog.basic_block = false; + }, + DW.LNS.advance_pc => { + const arg = try fbr.readUleb128(usize); + prog.address += arg * minimum_instruction_length; + }, + DW.LNS.advance_line => { + const arg = try fbr.readIleb128(i64); + prog.line += arg; + }, + DW.LNS.set_file => { + const arg = try fbr.readUleb128(usize); + prog.file = arg; + }, + DW.LNS.set_column => { + const arg = try fbr.readUleb128(u64); + prog.column = arg; + }, + DW.LNS.negate_stmt => { + prog.is_stmt = !prog.is_stmt; + }, + DW.LNS.set_basic_block => { + prog.basic_block = true; + }, + DW.LNS.const_add_pc => { + const inc_addr = minimum_instruction_length * ((255 - opcode_base) / line_range); + prog.address += inc_addr; + }, + DW.LNS.fixed_advance_pc => { + const arg = try fbr.readInt(u16); + prog.address += arg; + }, + DW.LNS.set_prologue_end => {}, + else => { + if (opcode - 1 >= standard_opcode_lengths.len) return badDwarf(); + try fbr.seekForward(standard_opcode_lengths[opcode - 1]); + }, + } + } + } + + return missingDwarf(); +} + +fn getString(di: Dwarf, offset: u64) ![:0]const u8 { + return getStringGeneric(di.section(.debug_str), offset); +} + +fn getLineString(di: Dwarf, offset: u64) ![:0]const u8 { + return getStringGeneric(di.section(.debug_line_str), offset); +} + +fn readDebugAddr(di: Dwarf, compile_unit: CompileUnit, index: u64) !u64 { + const debug_addr = di.section(.debug_addr) orelse return badDwarf(); + + // addr_base points to the first item after the header, however we + // need to read the header to know the size of each item. Empirically, + // it may disagree with is_64 on the compile unit. + // The header is 8 or 12 bytes depending on is_64. + if (compile_unit.addr_base < 8) return badDwarf(); + + const version = readInt(u16, debug_addr[compile_unit.addr_base - 4 ..][0..2], di.endian); + if (version != 5) return badDwarf(); + + const addr_size = debug_addr[compile_unit.addr_base - 2]; + const seg_size = debug_addr[compile_unit.addr_base - 1]; + + const byte_offset = @as(usize, @intCast(compile_unit.addr_base + (addr_size + seg_size) * index)); + if (byte_offset + addr_size > debug_addr.len) return badDwarf(); + return switch (addr_size) { + 1 => debug_addr[byte_offset], + 2 => readInt(u16, debug_addr[byte_offset..][0..2], di.endian), + 4 => readInt(u32, debug_addr[byte_offset..][0..4], di.endian), + 8 => readInt(u64, debug_addr[byte_offset..][0..8], di.endian), + else => badDwarf(), + }; +} + +/// If .eh_frame_hdr is present, then only the header needs to be parsed. +/// +/// Otherwise, .eh_frame and .debug_frame are scanned and a sorted list +/// of FDEs is built for binary searching during unwinding. +pub fn scanAllUnwindInfo(di: *Dwarf, allocator: Allocator, base_address: usize) !void { + if (di.section(.eh_frame_hdr)) |eh_frame_hdr| blk: { + var fbr: FixedBufferReader = .{ .buf = eh_frame_hdr, .endian = native_endian }; + + const version = try fbr.readByte(); + if (version != 1) break :blk; + + const eh_frame_ptr_enc = try fbr.readByte(); + if (eh_frame_ptr_enc == EH.PE.omit) break :blk; + const fde_count_enc = try fbr.readByte(); + if (fde_count_enc == EH.PE.omit) break :blk; + const table_enc = try fbr.readByte(); + if (table_enc == EH.PE.omit) break :blk; + + const eh_frame_ptr = cast(usize, try readEhPointer(&fbr, eh_frame_ptr_enc, @sizeOf(usize), .{ + .pc_rel_base = @intFromPtr(&eh_frame_hdr[fbr.pos]), + .follow_indirect = true, + }) orelse return badDwarf()) orelse return badDwarf(); + + const fde_count = cast(usize, try readEhPointer(&fbr, fde_count_enc, @sizeOf(usize), .{ + .pc_rel_base = @intFromPtr(&eh_frame_hdr[fbr.pos]), + .follow_indirect = true, + }) orelse return badDwarf()) orelse return badDwarf(); + + const entry_size = try ExceptionFrameHeader.entrySize(table_enc); + const entries_len = fde_count * entry_size; + if (entries_len > eh_frame_hdr.len - fbr.pos) return badDwarf(); + + di.eh_frame_hdr = .{ + .eh_frame_ptr = eh_frame_ptr, + .table_enc = table_enc, + .fde_count = fde_count, + .entries = eh_frame_hdr[fbr.pos..][0..entries_len], + }; + + // No need to scan .eh_frame, we have a binary search table already + return; + } + + const frame_sections = [2]Section.Id{ .eh_frame, .debug_frame }; + for (frame_sections) |frame_section| { + if (di.section(frame_section)) |section_data| { + var fbr: FixedBufferReader = .{ .buf = section_data, .endian = di.endian }; + while (fbr.pos < fbr.buf.len) { + const entry_header = try EntryHeader.read(&fbr, null, frame_section); + switch (entry_header.type) { + .cie => { + const cie = try CommonInformationEntry.parse( + entry_header.entry_bytes, + di.sectionVirtualOffset(frame_section, base_address).?, + true, + entry_header.format, + frame_section, + entry_header.length_offset, + @sizeOf(usize), + di.endian, + ); + try di.cie_map.put(allocator, entry_header.length_offset, cie); + }, + .fde => |cie_offset| { + const cie = di.cie_map.get(cie_offset) orelse return badDwarf(); + const fde = try FrameDescriptionEntry.parse( + entry_header.entry_bytes, + di.sectionVirtualOffset(frame_section, base_address).?, + true, + cie, + @sizeOf(usize), + di.endian, + ); + try di.fde_list.append(allocator, fde); + }, + .terminator => break, + } + } + + std.mem.sortUnstable(FrameDescriptionEntry, di.fde_list.items, {}, struct { + fn lessThan(ctx: void, a: FrameDescriptionEntry, b: FrameDescriptionEntry) bool { + _ = ctx; + return a.pc_begin < b.pc_begin; + } + }.lessThan); + } + } +} + +/// Unwind a stack frame using DWARF unwinding info, updating the register context. +/// +/// If `.eh_frame_hdr` is available, it will be used to binary search for the FDE. +/// Otherwise, a linear scan of `.eh_frame` and `.debug_frame` is done to find the FDE. +/// +/// `explicit_fde_offset` is for cases where the FDE offset is known, such as when __unwind_info +/// defers unwinding to DWARF. This is an offset into the `.eh_frame` section. +pub fn unwindFrame(di: *const Dwarf, context: *UnwindContext, ma: *StackIterator.MemoryAccessor, explicit_fde_offset: ?usize) !usize { + if (!comptime abi.supportsUnwinding(builtin.target)) return error.UnsupportedCpuArchitecture; + if (context.pc == 0) return 0; + + // Find the FDE and CIE + var cie: CommonInformationEntry = undefined; + var fde: FrameDescriptionEntry = undefined; + + if (explicit_fde_offset) |fde_offset| { + const dwarf_section: Section.Id = .eh_frame; + const frame_section = di.section(dwarf_section) orelse return error.MissingFDE; + if (fde_offset >= frame_section.len) return error.MissingFDE; + + var fbr: FixedBufferReader = .{ + .buf = frame_section, + .pos = fde_offset, + .endian = di.endian, + }; + + const fde_entry_header = try EntryHeader.read(&fbr, null, dwarf_section); + if (fde_entry_header.type != .fde) return error.MissingFDE; + + const cie_offset = fde_entry_header.type.fde; + try fbr.seekTo(cie_offset); + + fbr.endian = native_endian; + const cie_entry_header = try EntryHeader.read(&fbr, null, dwarf_section); + if (cie_entry_header.type != .cie) return badDwarf(); + + cie = try CommonInformationEntry.parse( + cie_entry_header.entry_bytes, + 0, + true, + cie_entry_header.format, + dwarf_section, + cie_entry_header.length_offset, + @sizeOf(usize), + native_endian, + ); + + fde = try FrameDescriptionEntry.parse( + fde_entry_header.entry_bytes, + 0, + true, + cie, + @sizeOf(usize), + native_endian, + ); + } else if (di.eh_frame_hdr) |header| { + const eh_frame_len = if (di.section(.eh_frame)) |eh_frame| eh_frame.len else null; + try header.findEntry( + ma, + eh_frame_len, + @intFromPtr(di.section(.eh_frame_hdr).?.ptr), + context.pc, + &cie, + &fde, + ); + } else { + const index = std.sort.binarySearch(FrameDescriptionEntry, context.pc, di.fde_list.items, {}, struct { + pub fn compareFn(_: void, pc: usize, mid_item: FrameDescriptionEntry) std.math.Order { + if (pc < mid_item.pc_begin) return .lt; + + const range_end = mid_item.pc_begin + mid_item.pc_range; + if (pc < range_end) return .eq; + + return .gt; + } + }.compareFn); + + fde = if (index) |i| di.fde_list.items[i] else return error.MissingFDE; + cie = di.cie_map.get(fde.cie_length_offset) orelse return error.MissingCIE; + } + + var expression_context: expression.Context = .{ + .format = cie.format, + .memory_accessor = ma, + .compile_unit = di.findCompileUnit(fde.pc_begin) catch null, + .thread_context = context.thread_context, + .reg_context = context.reg_context, + .cfa = context.cfa, + }; + + context.vm.reset(); + context.reg_context.eh_frame = cie.version != 4; + context.reg_context.is_macho = di.is_macho; + + const row = try context.vm.runToNative(context.allocator, context.pc, cie, fde); + context.cfa = switch (row.cfa.rule) { + .val_offset => |offset| blk: { + const register = row.cfa.register orelse return error.InvalidCFARule; + const value = readInt(usize, (try abi.regBytes(context.thread_context, register, context.reg_context))[0..@sizeOf(usize)], native_endian); + break :blk try call_frame.applyOffset(value, offset); + }, + .expression => |expr| blk: { + context.stack_machine.reset(); + const value = try context.stack_machine.run( + expr, + context.allocator, + expression_context, + context.cfa, + ); + + if (value) |v| { + if (v != .generic) return error.InvalidExpressionValue; + break :blk v.generic; + } else return error.NoExpressionValue; + }, + else => return error.InvalidCFARule, + }; + + if (ma.load(usize, context.cfa.?) == null) return error.InvalidCFA; + expression_context.cfa = context.cfa; + + // Buffering the modifications is done because copying the thread context is not portable, + // some implementations (ie. darwin) use internal pointers to the mcontext. + var arena = std.heap.ArenaAllocator.init(context.allocator); + defer arena.deinit(); + const update_allocator = arena.allocator(); + + const RegisterUpdate = struct { + // Backed by thread_context + dest: []u8, + // Backed by arena + src: []const u8, + prev: ?*@This(), + }; + + var update_tail: ?*RegisterUpdate = null; + var has_return_address = true; + for (context.vm.rowColumns(row)) |column| { + if (column.register) |register| { + if (register == cie.return_address_register) { + has_return_address = column.rule != .undefined; + } + + const dest = try abi.regBytes(context.thread_context, register, context.reg_context); + const src = try update_allocator.alloc(u8, dest.len); + + const prev = update_tail; + update_tail = try update_allocator.create(RegisterUpdate); + update_tail.?.* = .{ + .dest = dest, + .src = src, + .prev = prev, + }; + + try column.resolveValue( + context, + expression_context, + ma, + src, + ); + } + } + + // On all implemented architectures, the CFA is defined as being the previous frame's SP + (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(context.reg_context), context.reg_context)).* = context.cfa.?; + + while (update_tail) |tail| { + @memcpy(tail.dest, tail.src); + update_tail = tail.prev; + } + + if (has_return_address) { + context.pc = abi.stripInstructionPtrAuthCode(readInt(usize, (try abi.regBytes( + context.thread_context, + cie.return_address_register, + context.reg_context, + ))[0..@sizeOf(usize)], native_endian)); + } else { + context.pc = 0; + } + + (try abi.regValueNative(usize, context.thread_context, abi.ipRegNum(), context.reg_context)).* = context.pc; + + // The call instruction will have pushed the address of the instruction that follows the call as the return address. + // This next instruction may be past the end of the function if the caller was `noreturn` (ie. the last instruction in + // the function was the call). If we were to look up an FDE entry using the return address directly, it could end up + // either not finding an FDE at all, or using the next FDE in the program, producing incorrect results. To prevent this, + // we subtract one so that the next lookup is guaranteed to land inside the + // + // The exception to this rule is signal frames, where we return execution would be returned to the instruction + // that triggered the handler. + const return_address = context.pc; + if (context.pc > 0 and !cie.isSignalFrame()) context.pc -= 1; + + return return_address; +} + +fn parseFormValue( + fbr: *FixedBufferReader, + form_id: u64, + format: Format, + implicit_const: ?i64, +) anyerror!FormValue { + return switch (form_id) { + FORM.addr => .{ .addr = try fbr.readAddress(switch (@bitSizeOf(usize)) { + 32 => .@"32", + 64 => .@"64", + else => @compileError("unsupported @sizeOf(usize)"), + }) }, + FORM.addrx1 => .{ .addrx = try fbr.readInt(u8) }, + FORM.addrx2 => .{ .addrx = try fbr.readInt(u16) }, + FORM.addrx3 => .{ .addrx = try fbr.readInt(u24) }, + FORM.addrx4 => .{ .addrx = try fbr.readInt(u32) }, + FORM.addrx => .{ .addrx = try fbr.readUleb128(usize) }, + + FORM.block1, + FORM.block2, + FORM.block4, + FORM.block, + => .{ .block = try fbr.readBytes(switch (form_id) { + FORM.block1 => try fbr.readInt(u8), + FORM.block2 => try fbr.readInt(u16), + FORM.block4 => try fbr.readInt(u32), + FORM.block => try fbr.readUleb128(usize), + else => unreachable, + }) }, + + FORM.data1 => .{ .udata = try fbr.readInt(u8) }, + FORM.data2 => .{ .udata = try fbr.readInt(u16) }, + FORM.data4 => .{ .udata = try fbr.readInt(u32) }, + FORM.data8 => .{ .udata = try fbr.readInt(u64) }, + FORM.data16 => .{ .data16 = (try fbr.readBytes(16))[0..16] }, + FORM.udata => .{ .udata = try fbr.readUleb128(u64) }, + FORM.sdata => .{ .sdata = try fbr.readIleb128(i64) }, + FORM.exprloc => .{ .exprloc = try fbr.readBytes(try fbr.readUleb128(usize)) }, + FORM.flag => .{ .flag = (try fbr.readByte()) != 0 }, + FORM.flag_present => .{ .flag = true }, + FORM.sec_offset => .{ .sec_offset = try fbr.readAddress(format) }, + + FORM.ref1 => .{ .ref = try fbr.readInt(u8) }, + FORM.ref2 => .{ .ref = try fbr.readInt(u16) }, + FORM.ref4 => .{ .ref = try fbr.readInt(u32) }, + FORM.ref8 => .{ .ref = try fbr.readInt(u64) }, + FORM.ref_udata => .{ .ref = try fbr.readUleb128(u64) }, + + FORM.ref_addr => .{ .ref_addr = try fbr.readAddress(format) }, + FORM.ref_sig8 => .{ .ref = try fbr.readInt(u64) }, + + FORM.string => .{ .string = try fbr.readBytesTo(0) }, + FORM.strp => .{ .strp = try fbr.readAddress(format) }, + FORM.strx1 => .{ .strx = try fbr.readInt(u8) }, + FORM.strx2 => .{ .strx = try fbr.readInt(u16) }, + FORM.strx3 => .{ .strx = try fbr.readInt(u24) }, + FORM.strx4 => .{ .strx = try fbr.readInt(u32) }, + FORM.strx => .{ .strx = try fbr.readUleb128(usize) }, + FORM.line_strp => .{ .line_strp = try fbr.readAddress(format) }, + FORM.indirect => parseFormValue(fbr, try fbr.readUleb128(u64), format, implicit_const), + FORM.implicit_const => .{ .sdata = implicit_const orelse return badDwarf() }, + FORM.loclistx => .{ .loclistx = try fbr.readUleb128(u64) }, + FORM.rnglistx => .{ .rnglistx = try fbr.readUleb128(u64) }, + else => { + //debug.print("unrecognized form id: {x}\n", .{form_id}); + return badDwarf(); + }, + }; +} + +const FileEntry = struct { + path: []const u8, + dir_index: u32 = 0, + mtime: u64 = 0, + size: u64 = 0, + md5: [16]u8 = [1]u8{0} ** 16, +}; + +const LineNumberProgram = struct { + address: u64, + file: usize, + line: i64, + column: u64, + version: u16, + is_stmt: bool, + basic_block: bool, + end_sequence: bool, + + default_is_stmt: bool, + target_address: u64, + include_dirs: []const FileEntry, + + prev_valid: bool, + prev_address: u64, + prev_file: usize, + prev_line: i64, + prev_column: u64, + prev_is_stmt: bool, + prev_basic_block: bool, + prev_end_sequence: bool, + + // Reset the state machine following the DWARF specification + pub fn reset(self: *LineNumberProgram) void { + self.address = 0; + self.file = 1; + self.line = 1; + self.column = 0; + self.is_stmt = self.default_is_stmt; + self.basic_block = false; + self.end_sequence = false; + // Invalidate all the remaining fields + self.prev_valid = false; + self.prev_address = 0; + self.prev_file = undefined; + self.prev_line = undefined; + self.prev_column = undefined; + self.prev_is_stmt = undefined; + self.prev_basic_block = undefined; + self.prev_end_sequence = undefined; + } + + pub fn init( + is_stmt: bool, + include_dirs: []const FileEntry, + target_address: u64, + version: u16, + ) LineNumberProgram { + return LineNumberProgram{ + .address = 0, + .file = 1, + .line = 1, + .column = 0, + .version = version, + .is_stmt = is_stmt, + .basic_block = false, + .end_sequence = false, + .include_dirs = include_dirs, + .default_is_stmt = is_stmt, + .target_address = target_address, + .prev_valid = false, + .prev_address = 0, + .prev_file = undefined, + .prev_line = undefined, + .prev_column = undefined, + .prev_is_stmt = undefined, + .prev_basic_block = undefined, + .prev_end_sequence = undefined, + }; + } + + pub fn checkLineMatch( + self: *LineNumberProgram, + allocator: Allocator, + file_entries: []const FileEntry, + ) !?std.debug.LineInfo { + if (self.prev_valid and + self.target_address >= self.prev_address and + self.target_address < self.address) + { + const file_index = if (self.version >= 5) self.prev_file else i: { + if (self.prev_file == 0) return missingDwarf(); + break :i self.prev_file - 1; + }; + + if (file_index >= file_entries.len) return badDwarf(); + const file_entry = &file_entries[file_index]; + + if (file_entry.dir_index >= self.include_dirs.len) return badDwarf(); + const dir_name = self.include_dirs[file_entry.dir_index].path; + + const file_name = try std.fs.path.join(allocator, &[_][]const u8{ + dir_name, file_entry.path, + }); + + return std.debug.LineInfo{ + .line = if (self.prev_line >= 0) @as(u64, @intCast(self.prev_line)) else 0, + .column = self.prev_column, + .file_name = file_name, + }; + } + + self.prev_valid = true; + self.prev_address = self.address; + self.prev_file = self.file; + self.prev_line = self.line; + self.prev_column = self.column; + self.prev_is_stmt = self.is_stmt; + self.prev_basic_block = self.basic_block; + self.prev_end_sequence = self.end_sequence; + return null; + } +}; + +const UnitHeader = struct { + format: Format, + header_length: u4, + unit_length: u64, +}; +fn readUnitHeader(fbr: *FixedBufferReader, opt_ma: ?*StackIterator.MemoryAccessor) !UnitHeader { + return switch (try if (opt_ma) |ma| fbr.readIntChecked(u32, ma) else fbr.readInt(u32)) { + 0...0xfffffff0 - 1 => |unit_length| .{ + .format = .@"32", + .header_length = 4, + .unit_length = unit_length, + }, + 0xfffffff0...0xffffffff - 1 => badDwarf(), + 0xffffffff => .{ + .format = .@"64", + .header_length = 12, + .unit_length = try if (opt_ma) |ma| fbr.readIntChecked(u64, ma) else fbr.readInt(u64), + }, + }; +} + +/// Returns the DWARF register number for an x86_64 register number found in compact unwind info +fn compactUnwindToDwarfRegNumber(unwind_reg_number: u3) !u8 { + return switch (unwind_reg_number) { + 1 => 3, // RBX + 2 => 12, // R12 + 3 => 13, // R13 + 4 => 14, // R14 + 5 => 15, // R15 + 6 => 6, // RBP + else => error.InvalidUnwindRegisterNumber, + }; +} + +/// This function is to make it handy to comment out the return and make it +/// into a crash when working on this file. +fn badDwarf() error{InvalidDebugInfo} { + //if (true) @panic("badDwarf"); // can be handy to uncomment when working on this file + return error.InvalidDebugInfo; +} + +fn missingDwarf() error{MissingDebugInfo} { + //if (true) @panic("missingDwarf"); // can be handy to uncomment when working on this file + return error.MissingDebugInfo; +} + +fn getStringGeneric(opt_str: ?[]const u8, offset: u64) ![:0]const u8 { + const str = opt_str orelse return badDwarf(); + if (offset > str.len) return badDwarf(); + const casted_offset = cast(usize, offset) orelse return badDwarf(); + // Valid strings always have a terminating zero byte + const last = std.mem.indexOfScalarPos(u8, str, casted_offset, 0) orelse return badDwarf(); + return str[casted_offset..last :0]; +} + +// Reading debug info needs to be fast, even when compiled in debug mode, +// so avoid using a `std.io.FixedBufferStream` which is too slow. +pub const FixedBufferReader = struct { + buf: []const u8, + pos: usize = 0, + endian: std.builtin.Endian, + + pub const Error = error{ EndOfBuffer, Overflow, InvalidBuffer }; + + fn seekTo(fbr: *FixedBufferReader, pos: u64) Error!void { + if (pos > fbr.buf.len) return error.EndOfBuffer; + fbr.pos = @intCast(pos); + } + + fn seekForward(fbr: *FixedBufferReader, amount: u64) Error!void { + if (fbr.buf.len - fbr.pos < amount) return error.EndOfBuffer; + fbr.pos += @intCast(amount); + } + + pub inline fn readByte(fbr: *FixedBufferReader) Error!u8 { + if (fbr.pos >= fbr.buf.len) return error.EndOfBuffer; + defer fbr.pos += 1; + return fbr.buf[fbr.pos]; + } + + fn readByteSigned(fbr: *FixedBufferReader) Error!i8 { + return @bitCast(try fbr.readByte()); + } + + fn readInt(fbr: *FixedBufferReader, comptime T: type) Error!T { + const size = @divExact(@typeInfo(T).Int.bits, 8); + if (fbr.buf.len - fbr.pos < size) return error.EndOfBuffer; + defer fbr.pos += size; + return std.mem.readInt(T, fbr.buf[fbr.pos..][0..size], fbr.endian); + } + + fn readIntChecked( + fbr: *FixedBufferReader, + comptime T: type, + ma: *std.debug.StackIterator.MemoryAccessor, + ) Error!T { + if (ma.load(T, @intFromPtr(fbr.buf[fbr.pos..].ptr)) == null) + return error.InvalidBuffer; + + return fbr.readInt(T); + } + + fn readUleb128(fbr: *FixedBufferReader, comptime T: type) Error!T { + return std.leb.readUleb128(T, fbr); + } + + fn readIleb128(fbr: *FixedBufferReader, comptime T: type) Error!T { + return std.leb.readIleb128(T, fbr); + } + + fn readAddress(fbr: *FixedBufferReader, format: Format) Error!u64 { + return switch (format) { + .@"32" => try fbr.readInt(u32), + .@"64" => try fbr.readInt(u64), + }; + } + + fn readAddressChecked( + fbr: *FixedBufferReader, + format: Format, + ma: *std.debug.StackIterator.MemoryAccessor, + ) Error!u64 { + return switch (format) { + .@"32" => try fbr.readIntChecked(u32, ma), + .@"64" => try fbr.readIntChecked(u64, ma), + }; + } + + fn readBytes(fbr: *FixedBufferReader, len: usize) Error![]const u8 { + if (fbr.buf.len - fbr.pos < len) return error.EndOfBuffer; + defer fbr.pos += len; + return fbr.buf[fbr.pos..][0..len]; + } + + fn readBytesTo(fbr: *FixedBufferReader, comptime sentinel: u8) Error![:sentinel]const u8 { + const end = @call(.always_inline, std.mem.indexOfScalarPos, .{ + u8, + fbr.buf, + fbr.pos, + sentinel, + }) orelse return error.EndOfBuffer; + defer fbr.pos = end + 1; + return fbr.buf[fbr.pos..end :sentinel]; + } +}; + +/// Unwind a frame using MachO compact unwind info (from __unwind_info). +/// If the compact encoding can't encode a way to unwind a frame, it will +/// defer unwinding to DWARF, in which case `.eh_frame` will be used if available. +pub fn unwindFrameMachO( + context: *UnwindContext, + ma: *StackIterator.MemoryAccessor, + unwind_info: []const u8, + eh_frame: ?[]const u8, + module_base_address: usize, +) !usize { + const macho = std.macho; + + const header = std.mem.bytesAsValue( + macho.unwind_info_section_header, + unwind_info[0..@sizeOf(macho.unwind_info_section_header)], + ); + const indices = std.mem.bytesAsSlice( + macho.unwind_info_section_header_index_entry, + unwind_info[header.indexSectionOffset..][0 .. header.indexCount * @sizeOf(macho.unwind_info_section_header_index_entry)], + ); + if (indices.len == 0) return error.MissingUnwindInfo; + + const mapped_pc = context.pc - module_base_address; + const second_level_index = blk: { + var left: usize = 0; + var len: usize = indices.len; + + while (len > 1) { + const mid = left + len / 2; + const offset = indices[mid].functionOffset; + if (mapped_pc < offset) { + len /= 2; + } else { + left = mid; + if (mapped_pc == offset) break; + len -= len / 2; + } + } + + // Last index is a sentinel containing the highest address as its functionOffset + if (indices[left].secondLevelPagesSectionOffset == 0) return error.MissingUnwindInfo; + break :blk &indices[left]; + }; + + const common_encodings = std.mem.bytesAsSlice( + macho.compact_unwind_encoding_t, + unwind_info[header.commonEncodingsArraySectionOffset..][0 .. header.commonEncodingsArrayCount * @sizeOf(macho.compact_unwind_encoding_t)], + ); + + const start_offset = second_level_index.secondLevelPagesSectionOffset; + const kind = std.mem.bytesAsValue( + macho.UNWIND_SECOND_LEVEL, + unwind_info[start_offset..][0..@sizeOf(macho.UNWIND_SECOND_LEVEL)], + ); + + const entry: struct { + function_offset: usize, + raw_encoding: u32, + } = switch (kind.*) { + .REGULAR => blk: { + const page_header = std.mem.bytesAsValue( + macho.unwind_info_regular_second_level_page_header, + unwind_info[start_offset..][0..@sizeOf(macho.unwind_info_regular_second_level_page_header)], + ); + + const entries = std.mem.bytesAsSlice( + macho.unwind_info_regular_second_level_entry, + unwind_info[start_offset + page_header.entryPageOffset ..][0 .. page_header.entryCount * @sizeOf(macho.unwind_info_regular_second_level_entry)], + ); + if (entries.len == 0) return error.InvalidUnwindInfo; + + var left: usize = 0; + var len: usize = entries.len; + while (len > 1) { + const mid = left + len / 2; + const offset = entries[mid].functionOffset; + if (mapped_pc < offset) { + len /= 2; + } else { + left = mid; + if (mapped_pc == offset) break; + len -= len / 2; + } + } + + break :blk .{ + .function_offset = entries[left].functionOffset, + .raw_encoding = entries[left].encoding, + }; + }, + .COMPRESSED => blk: { + const page_header = std.mem.bytesAsValue( + macho.unwind_info_compressed_second_level_page_header, + unwind_info[start_offset..][0..@sizeOf(macho.unwind_info_compressed_second_level_page_header)], + ); + + const entries = std.mem.bytesAsSlice( + macho.UnwindInfoCompressedEntry, + unwind_info[start_offset + page_header.entryPageOffset ..][0 .. page_header.entryCount * @sizeOf(macho.UnwindInfoCompressedEntry)], + ); + if (entries.len == 0) return error.InvalidUnwindInfo; + + var left: usize = 0; + var len: usize = entries.len; + while (len > 1) { + const mid = left + len / 2; + const offset = second_level_index.functionOffset + entries[mid].funcOffset; + if (mapped_pc < offset) { + len /= 2; + } else { + left = mid; + if (mapped_pc == offset) break; + len -= len / 2; + } + } + + const entry = entries[left]; + const function_offset = second_level_index.functionOffset + entry.funcOffset; + if (entry.encodingIndex < header.commonEncodingsArrayCount) { + if (entry.encodingIndex >= common_encodings.len) return error.InvalidUnwindInfo; + break :blk .{ + .function_offset = function_offset, + .raw_encoding = common_encodings[entry.encodingIndex], + }; + } else { + const local_index = try std.math.sub( + u8, + entry.encodingIndex, + cast(u8, header.commonEncodingsArrayCount) orelse return error.InvalidUnwindInfo, + ); + const local_encodings = std.mem.bytesAsSlice( + macho.compact_unwind_encoding_t, + unwind_info[start_offset + page_header.encodingsPageOffset ..][0 .. page_header.encodingsCount * @sizeOf(macho.compact_unwind_encoding_t)], + ); + if (local_index >= local_encodings.len) return error.InvalidUnwindInfo; + break :blk .{ + .function_offset = function_offset, + .raw_encoding = local_encodings[local_index], + }; + } + }, + else => return error.InvalidUnwindInfo, + }; + + if (entry.raw_encoding == 0) return error.NoUnwindInfo; + const reg_context = abi.RegisterContext{ + .eh_frame = false, + .is_macho = true, + }; + + const encoding: macho.CompactUnwindEncoding = @bitCast(entry.raw_encoding); + const new_ip = switch (builtin.cpu.arch) { + .x86_64 => switch (encoding.mode.x86_64) { + .OLD => return error.UnimplementedUnwindEncoding, + .RBP_FRAME => blk: { + const regs: [5]u3 = .{ + encoding.value.x86_64.frame.reg0, + encoding.value.x86_64.frame.reg1, + encoding.value.x86_64.frame.reg2, + encoding.value.x86_64.frame.reg3, + encoding.value.x86_64.frame.reg4, + }; + + const frame_offset = encoding.value.x86_64.frame.frame_offset * @sizeOf(usize); + var max_reg: usize = 0; + inline for (regs, 0..) |reg, i| { + if (reg > 0) max_reg = i; + } + + const fp = (try abi.regValueNative(usize, context.thread_context, abi.fpRegNum(reg_context), reg_context)).*; + const new_sp = fp + 2 * @sizeOf(usize); + + // Verify the stack range we're about to read register values from + if (ma.load(usize, new_sp) == null or ma.load(usize, fp - frame_offset + max_reg * @sizeOf(usize)) == null) return error.InvalidUnwindInfo; + + const ip_ptr = fp + @sizeOf(usize); + const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*; + const new_fp = @as(*const usize, @ptrFromInt(fp)).*; + + (try abi.regValueNative(usize, context.thread_context, abi.fpRegNum(reg_context), reg_context)).* = new_fp; + (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).* = new_sp; + (try abi.regValueNative(usize, context.thread_context, abi.ipRegNum(), reg_context)).* = new_ip; + + for (regs, 0..) |reg, i| { + if (reg == 0) continue; + const addr = fp - frame_offset + i * @sizeOf(usize); + const reg_number = try compactUnwindToDwarfRegNumber(reg); + (try abi.regValueNative(usize, context.thread_context, reg_number, reg_context)).* = @as(*const usize, @ptrFromInt(addr)).*; + } + + break :blk new_ip; + }, + .STACK_IMMD, + .STACK_IND, + => blk: { + const sp = (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).*; + const stack_size = if (encoding.mode.x86_64 == .STACK_IMMD) + @as(usize, encoding.value.x86_64.frameless.stack.direct.stack_size) * @sizeOf(usize) + else stack_size: { + // In .STACK_IND, the stack size is inferred from the subq instruction at the beginning of the function. + const sub_offset_addr = + module_base_address + + entry.function_offset + + encoding.value.x86_64.frameless.stack.indirect.sub_offset; + if (ma.load(usize, sub_offset_addr) == null) return error.InvalidUnwindInfo; + + // `sub_offset_addr` points to the offset of the literal within the instruction + const sub_operand = @as(*align(1) const u32, @ptrFromInt(sub_offset_addr)).*; + break :stack_size sub_operand + @sizeOf(usize) * @as(usize, encoding.value.x86_64.frameless.stack.indirect.stack_adjust); + }; + + // Decode the Lehmer-coded sequence of registers. + // For a description of the encoding see lib/libc/include/any-macos.13-any/mach-o/compact_unwind_encoding.h + + // Decode the variable-based permutation number into its digits. Each digit represents + // an index into the list of register numbers that weren't yet used in the sequence at + // the time the digit was added. + const reg_count = encoding.value.x86_64.frameless.stack_reg_count; + const ip_ptr = if (reg_count > 0) reg_blk: { + var digits: [6]u3 = undefined; + var accumulator: usize = encoding.value.x86_64.frameless.stack_reg_permutation; + var base: usize = 2; + for (0..reg_count) |i| { + const div = accumulator / base; + digits[digits.len - 1 - i] = @intCast(accumulator - base * div); + accumulator = div; + base += 1; + } + + const reg_numbers = [_]u3{ 1, 2, 3, 4, 5, 6 }; + var registers: [reg_numbers.len]u3 = undefined; + var used_indices = [_]bool{false} ** reg_numbers.len; + for (digits[digits.len - reg_count ..], 0..) |target_unused_index, i| { + var unused_count: u8 = 0; + const unused_index = for (used_indices, 0..) |used, index| { + if (!used) { + if (target_unused_index == unused_count) break index; + unused_count += 1; + } + } else unreachable; + + registers[i] = reg_numbers[unused_index]; + used_indices[unused_index] = true; + } + + var reg_addr = sp + stack_size - @sizeOf(usize) * @as(usize, reg_count + 1); + if (ma.load(usize, reg_addr) == null) return error.InvalidUnwindInfo; + for (0..reg_count) |i| { + const reg_number = try compactUnwindToDwarfRegNumber(registers[i]); + (try abi.regValueNative(usize, context.thread_context, reg_number, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*; + reg_addr += @sizeOf(usize); + } + + break :reg_blk reg_addr; + } else sp + stack_size - @sizeOf(usize); + + const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*; + const new_sp = ip_ptr + @sizeOf(usize); + if (ma.load(usize, new_sp) == null) return error.InvalidUnwindInfo; + + (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).* = new_sp; + (try abi.regValueNative(usize, context.thread_context, abi.ipRegNum(), reg_context)).* = new_ip; + + break :blk new_ip; + }, + .DWARF => { + return unwindFrameMachODwarf(context, ma, eh_frame orelse return error.MissingEhFrame, @intCast(encoding.value.x86_64.dwarf)); + }, + }, + .aarch64 => switch (encoding.mode.arm64) { + .OLD => return error.UnimplementedUnwindEncoding, + .FRAMELESS => blk: { + const sp = (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).*; + const new_sp = sp + encoding.value.arm64.frameless.stack_size * 16; + const new_ip = (try abi.regValueNative(usize, context.thread_context, 30, reg_context)).*; + if (ma.load(usize, new_sp) == null) return error.InvalidUnwindInfo; + (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).* = new_sp; + break :blk new_ip; + }, + .DWARF => { + return unwindFrameMachODwarf(context, ma, eh_frame orelse return error.MissingEhFrame, @intCast(encoding.value.arm64.dwarf)); + }, + .FRAME => blk: { + const fp = (try abi.regValueNative(usize, context.thread_context, abi.fpRegNum(reg_context), reg_context)).*; + const new_sp = fp + 16; + const ip_ptr = fp + @sizeOf(usize); + + const num_restored_pairs: usize = + @popCount(@as(u5, @bitCast(encoding.value.arm64.frame.x_reg_pairs))) + + @popCount(@as(u4, @bitCast(encoding.value.arm64.frame.d_reg_pairs))); + const min_reg_addr = fp - num_restored_pairs * 2 * @sizeOf(usize); + + if (ma.load(usize, new_sp) == null or ma.load(usize, min_reg_addr) == null) return error.InvalidUnwindInfo; + + var reg_addr = fp - @sizeOf(usize); + inline for (@typeInfo(@TypeOf(encoding.value.arm64.frame.x_reg_pairs)).Struct.fields, 0..) |field, i| { + if (@field(encoding.value.arm64.frame.x_reg_pairs, field.name) != 0) { + (try abi.regValueNative(usize, context.thread_context, 19 + i, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*; + reg_addr += @sizeOf(usize); + (try abi.regValueNative(usize, context.thread_context, 20 + i, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*; + reg_addr += @sizeOf(usize); + } + } + + inline for (@typeInfo(@TypeOf(encoding.value.arm64.frame.d_reg_pairs)).Struct.fields, 0..) |field, i| { + if (@field(encoding.value.arm64.frame.d_reg_pairs, field.name) != 0) { + // Only the lower half of the 128-bit V registers are restored during unwinding + @memcpy( + try abi.regBytes(context.thread_context, 64 + 8 + i, context.reg_context), + std.mem.asBytes(@as(*const usize, @ptrFromInt(reg_addr))), + ); + reg_addr += @sizeOf(usize); + @memcpy( + try abi.regBytes(context.thread_context, 64 + 9 + i, context.reg_context), + std.mem.asBytes(@as(*const usize, @ptrFromInt(reg_addr))), + ); + reg_addr += @sizeOf(usize); + } + } + + const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*; + const new_fp = @as(*const usize, @ptrFromInt(fp)).*; + + (try abi.regValueNative(usize, context.thread_context, abi.fpRegNum(reg_context), reg_context)).* = new_fp; + (try abi.regValueNative(usize, context.thread_context, abi.ipRegNum(), reg_context)).* = new_ip; + + break :blk new_ip; + }, + }, + else => return error.UnimplementedArch, + }; + + context.pc = abi.stripInstructionPtrAuthCode(new_ip); + if (context.pc > 0) context.pc -= 1; + return new_ip; +} + +fn unwindFrameMachODwarf( + context: *UnwindContext, + ma: *std.debug.StackIterator.MemoryAccessor, + eh_frame: []const u8, + fde_offset: usize, +) !usize { + var di = Dwarf{ + .endian = native_endian, + .is_macho = true, + }; + defer di.deinit(context.allocator); + + di.sections[@intFromEnum(Section.Id.eh_frame)] = .{ + .data = eh_frame, + .owned = false, + }; + + return di.unwindFrame(context, ma, fde_offset); +} + +const EhPointerContext = struct { + // The address of the pointer field itself + pc_rel_base: u64, + + // Whether or not to follow indirect pointers. This should only be + // used when decoding pointers at runtime using the current process's + // debug info + follow_indirect: bool, + + // These relative addressing modes are only used in specific cases, and + // might not be available / required in all parsing contexts + data_rel_base: ?u64 = null, + text_rel_base: ?u64 = null, + function_rel_base: ?u64 = null, +}; +fn readEhPointer(fbr: *FixedBufferReader, enc: u8, addr_size_bytes: u8, ctx: EhPointerContext) !?u64 { + if (enc == EH.PE.omit) return null; + + const value: union(enum) { + signed: i64, + unsigned: u64, + } = switch (enc & EH.PE.type_mask) { + EH.PE.absptr => .{ + .unsigned = switch (addr_size_bytes) { + 2 => try fbr.readInt(u16), + 4 => try fbr.readInt(u32), + 8 => try fbr.readInt(u64), + else => return error.InvalidAddrSize, + }, + }, + EH.PE.uleb128 => .{ .unsigned = try fbr.readUleb128(u64) }, + EH.PE.udata2 => .{ .unsigned = try fbr.readInt(u16) }, + EH.PE.udata4 => .{ .unsigned = try fbr.readInt(u32) }, + EH.PE.udata8 => .{ .unsigned = try fbr.readInt(u64) }, + EH.PE.sleb128 => .{ .signed = try fbr.readIleb128(i64) }, + EH.PE.sdata2 => .{ .signed = try fbr.readInt(i16) }, + EH.PE.sdata4 => .{ .signed = try fbr.readInt(i32) }, + EH.PE.sdata8 => .{ .signed = try fbr.readInt(i64) }, + else => return badDwarf(), + }; + + const base = switch (enc & EH.PE.rel_mask) { + EH.PE.pcrel => ctx.pc_rel_base, + EH.PE.textrel => ctx.text_rel_base orelse return error.PointerBaseNotSpecified, + EH.PE.datarel => ctx.data_rel_base orelse return error.PointerBaseNotSpecified, + EH.PE.funcrel => ctx.function_rel_base orelse return error.PointerBaseNotSpecified, + else => null, + }; + + const ptr: u64 = if (base) |b| switch (value) { + .signed => |s| @intCast(try std.math.add(i64, s, @as(i64, @intCast(b)))), + // absptr can actually contain signed values in some cases (aarch64 MachO) + .unsigned => |u| u +% b, + } else switch (value) { + .signed => |s| @as(u64, @intCast(s)), + .unsigned => |u| u, + }; + + if ((enc & EH.PE.indirect) > 0 and ctx.follow_indirect) { + if (@sizeOf(usize) != addr_size_bytes) { + // See the documentation for `follow_indirect` + return error.NonNativeIndirection; + } + + const native_ptr = cast(usize, ptr) orelse return error.PointerOverflow; + return switch (addr_size_bytes) { + 2, 4, 8 => return @as(*const usize, @ptrFromInt(native_ptr)).*, + else => return error.UnsupportedAddrSize, + }; + } else { + return ptr; + } +} + +fn pcRelBase(field_ptr: usize, pc_rel_offset: i64) !usize { + if (pc_rel_offset < 0) { + return std.math.sub(usize, field_ptr, @as(usize, @intCast(-pc_rel_offset))); + } else { + return std.math.add(usize, field_ptr, @as(usize, @intCast(pc_rel_offset))); + } +} diff --git a/lib/std/dwarf/abi.zig b/lib/std/debug/Dwarf/abi.zig similarity index 99% rename from lib/std/dwarf/abi.zig rename to lib/std/debug/Dwarf/abi.zig index 543a4b9ac1..1a47625ae7 100644 --- a/lib/std/dwarf/abi.zig +++ b/lib/std/debug/Dwarf/abi.zig @@ -1,5 +1,5 @@ const builtin = @import("builtin"); -const std = @import("../std.zig"); +const std = @import("../../std.zig"); const mem = std.mem; const native_os = builtin.os.tag; const posix = std.posix; @@ -392,7 +392,7 @@ pub fn regBytes( /// Returns the ABI-defined default value this register has in the unwinding table /// before running any of the CIE instructions. The DWARF spec defines these as having /// the .undefined rule by default, but allows ABI authors to override that. -pub fn getRegDefaultValue(reg_number: u8, context: *std.dwarf.UnwindContext, out: []u8) !void { +pub fn getRegDefaultValue(reg_number: u8, context: *std.debug.Dwarf.UnwindContext, out: []u8) !void { switch (builtin.cpu.arch) { .aarch64 => { // Callee-saved registers are initialized as if they had the .same_value rule diff --git a/lib/std/dwarf/call_frame.zig b/lib/std/debug/Dwarf/call_frame.zig similarity index 97% rename from lib/std/dwarf/call_frame.zig rename to lib/std/debug/Dwarf/call_frame.zig index 7aff897cea..73e00d3099 100644 --- a/lib/std/dwarf/call_frame.zig +++ b/lib/std/debug/Dwarf/call_frame.zig @@ -1,14 +1,14 @@ const builtin = @import("builtin"); -const std = @import("../std.zig"); +const std = @import("../../std.zig"); const mem = std.mem; const debug = std.debug; const leb = std.leb; -const dwarf = std.dwarf; -const abi = dwarf.abi; -const expressions = dwarf.expressions; +const DW = std.dwarf; +const abi = std.debug.Dwarf.abi; const assert = std.debug.assert; const native_endian = builtin.cpu.arch.endian(); +/// TODO merge with std.dwarf.CFA const Opcode = enum(u8) { advance_loc = 0x1 << 6, offset = 0x2 << 6, @@ -363,8 +363,8 @@ pub const VirtualMachine = struct { /// Resolves the register rule and places the result into `out` (see dwarf.abi.regBytes) pub fn resolveValue( self: Column, - context: *dwarf.UnwindContext, - expression_context: dwarf.expressions.ExpressionContext, + context: *std.debug.Dwarf.UnwindContext, + expression_context: std.debug.Dwarf.expression.Context, ma: *debug.StackIterator.MemoryAccessor, out: []u8, ) !void { @@ -483,8 +483,8 @@ pub const VirtualMachine = struct { self: *VirtualMachine, allocator: std.mem.Allocator, pc: u64, - cie: dwarf.CommonInformationEntry, - fde: dwarf.FrameDescriptionEntry, + cie: std.debug.Dwarf.CommonInformationEntry, + fde: std.debug.Dwarf.FrameDescriptionEntry, addr_size_bytes: u8, endian: std.builtin.Endian, ) !Row { @@ -502,7 +502,7 @@ pub const VirtualMachine = struct { for (&streams, 0..) |stream, i| { while (stream.pos < stream.buffer.len) { - const instruction = try dwarf.call_frame.Instruction.read(stream, addr_size_bytes, endian); + const instruction = try std.debug.Dwarf.call_frame.Instruction.read(stream, addr_size_bytes, endian); prev_row = try self.step(allocator, cie, i == 0, instruction); if (pc < fde.pc_begin + self.current_row.offset) return prev_row; } @@ -515,8 +515,8 @@ pub const VirtualMachine = struct { self: *VirtualMachine, allocator: std.mem.Allocator, pc: u64, - cie: dwarf.CommonInformationEntry, - fde: dwarf.FrameDescriptionEntry, + cie: std.debug.Dwarf.CommonInformationEntry, + fde: std.debug.Dwarf.FrameDescriptionEntry, ) !Row { return self.runTo(allocator, pc, cie, fde, @sizeOf(usize), builtin.target.cpu.arch.endian()); } @@ -538,7 +538,7 @@ pub const VirtualMachine = struct { pub fn step( self: *VirtualMachine, allocator: std.mem.Allocator, - cie: dwarf.CommonInformationEntry, + cie: std.debug.Dwarf.CommonInformationEntry, is_initial: bool, instruction: Instruction, ) !Row { diff --git a/lib/std/dwarf/expressions.zig b/lib/std/debug/Dwarf/expression.zig similarity index 98% rename from lib/std/dwarf/expressions.zig rename to lib/std/debug/Dwarf/expression.zig index f853c5fe5a..6243ea9717 100644 --- a/lib/std/dwarf/expressions.zig +++ b/lib/std/debug/Dwarf/expression.zig @@ -1,9 +1,8 @@ const std = @import("std"); const builtin = @import("builtin"); -const OP = @import("OP.zig"); const leb = std.leb; -const dwarf = std.dwarf; -const abi = dwarf.abi; +const OP = std.dwarf.OP; +const abi = std.debug.Dwarf.abi; const mem = std.mem; const assert = std.debug.assert; const native_endian = builtin.cpu.arch.endian(); @@ -11,46 +10,37 @@ const native_endian = builtin.cpu.arch.endian(); /// Expressions can be evaluated in different contexts, each requiring its own set of inputs. /// Callers should specify all the fields relevant to their context. If a field is required /// by the expression and it isn't in the context, error.IncompleteExpressionContext is returned. -pub const ExpressionContext = struct { +pub const Context = struct { /// The dwarf format of the section this expression is in - format: dwarf.Format = .@"32", - + format: std.dwarf.Format = .@"32", /// If specified, any addresses will pass through before being accessed memory_accessor: ?*std.debug.StackIterator.MemoryAccessor = null, - /// The compilation unit this expression relates to, if any - compile_unit: ?*const dwarf.CompileUnit = null, - + compile_unit: ?*const std.debug.Dwarf.CompileUnit = null, /// When evaluating a user-presented expression, this is the address of the object being evaluated object_address: ?*const anyopaque = null, - /// .debug_addr section debug_addr: ?[]const u8 = null, - /// Thread context thread_context: ?*std.debug.ThreadContext = null, reg_context: ?abi.RegisterContext = null, - /// Call frame address, if in a CFI context cfa: ?usize = null, - /// This expression is a sub-expression from an OP.entry_value instruction entry_value_context: bool = false, }; -pub const ExpressionOptions = struct { +pub const Options = struct { /// The address size of the target architecture addr_size: u8 = @sizeOf(usize), - /// Endianness of the target architecture endian: std.builtin.Endian = builtin.target.cpu.arch.endian(), - /// Restrict the stack machine to a subset of opcodes used in call frame instructions call_frame_context: bool = false, }; // Explicitly defined to support executing sub-expressions -pub const ExpressionError = error{ +pub const Error = error{ UnimplementedExpressionCall, UnimplementedOpcode, UnimplementedUserOpcode, @@ -75,7 +65,7 @@ pub const ExpressionError = error{ /// A stack machine that can decode and run DWARF expressions. /// Expressions can be decoded for non-native address size and endianness, /// but can only be executed if the current target matches the configuration. -pub fn StackMachine(comptime options: ExpressionOptions) type { +pub fn StackMachine(comptime options: Options) type { const addr_type = switch (options.addr_size) { 2 => u16, 4 => u32, @@ -186,7 +176,7 @@ pub fn StackMachine(comptime options: ExpressionOptions) type { } } - pub fn readOperand(stream: *std.io.FixedBufferStream([]const u8), opcode: u8, context: ExpressionContext) !?Operand { + pub fn readOperand(stream: *std.io.FixedBufferStream([]const u8), opcode: u8, context: Context) !?Operand { const reader = stream.reader(); return switch (opcode) { OP.addr => generic(try reader.readInt(addr_type, options.endian)), @@ -297,9 +287,9 @@ pub fn StackMachine(comptime options: ExpressionOptions) type { self: *Self, expression: []const u8, allocator: std.mem.Allocator, - context: ExpressionContext, + context: Context, initial_value: ?usize, - ) ExpressionError!?Value { + ) Error!?Value { if (initial_value) |i| try self.stack.append(allocator, .{ .generic = i }); var stream = std.io.fixedBufferStream(expression); while (try self.step(&stream, allocator, context)) {} @@ -312,8 +302,8 @@ pub fn StackMachine(comptime options: ExpressionOptions) type { self: *Self, stream: *std.io.FixedBufferStream([]const u8), allocator: std.mem.Allocator, - context: ExpressionContext, - ) ExpressionError!bool { + context: Context, + ) Error!bool { if (@sizeOf(usize) != @sizeOf(addr_type) or options.endian != comptime builtin.target.cpu.arch.endian()) @compileError("Execution of non-native address sizes / endianness is not supported"); @@ -792,7 +782,7 @@ pub fn StackMachine(comptime options: ExpressionOptions) type { }; } -pub fn Builder(comptime options: ExpressionOptions) type { +pub fn Builder(comptime options: Options) type { const addr_type = switch (options.addr_size) { 2 => u16, 4 => u32, @@ -1066,7 +1056,7 @@ const testing = std.testing; test "DWARF expressions" { const allocator = std.testing.allocator; - const options = ExpressionOptions{}; + const options = Options{}; var stack_machine = StackMachine(options){}; defer stack_machine.deinit(allocator); @@ -1079,7 +1069,7 @@ test "DWARF expressions" { // Literals { - const context = ExpressionContext{}; + const context = Context{}; for (0..32) |i| { try b.writeLiteral(writer, @intCast(i)); } @@ -1125,7 +1115,7 @@ test "DWARF expressions" { try b.writeConst(writer, i28, input[9]); try b.writeAddr(writer, input[10]); - var mock_compile_unit: dwarf.CompileUnit = undefined; + var mock_compile_unit: std.debug.Dwarf.CompileUnit = undefined; mock_compile_unit.addr_base = 1; var mock_debug_addr = std.ArrayList(u8).init(allocator); @@ -1135,7 +1125,7 @@ test "DWARF expressions" { try mock_debug_addr.writer().writeInt(usize, input[11], native_endian); try mock_debug_addr.writer().writeInt(usize, input[12], native_endian); - const context = ExpressionContext{ + const context = Context{ .compile_unit = &mock_compile_unit, .debug_addr = mock_debug_addr.items, }; @@ -1185,7 +1175,7 @@ test "DWARF expressions" { }; var thread_context: std.debug.ThreadContext = undefined; std.debug.relocateContext(&thread_context); - const context = ExpressionContext{ + const context = Context{ .thread_context = &thread_context, .reg_context = reg_context, }; @@ -1228,7 +1218,7 @@ test "DWARF expressions" { // Stack operations { - var context = ExpressionContext{}; + var context = Context{}; stack_machine.reset(); program.clearRetainingCapacity(); @@ -1359,7 +1349,7 @@ test "DWARF expressions" { // Arithmetic and Logical Operations { - const context = ExpressionContext{}; + const context = Context{}; stack_machine.reset(); program.clearRetainingCapacity(); @@ -1483,7 +1473,7 @@ test "DWARF expressions" { // Control Flow Operations { - const context = ExpressionContext{}; + const context = Context{}; const expected = .{ .{ OP.le, 1, 1, 0 }, .{ OP.ge, 1, 0, 1 }, @@ -1540,7 +1530,7 @@ test "DWARF expressions" { // Type conversions { - const context = ExpressionContext{}; + const context = Context{}; stack_machine.reset(); program.clearRetainingCapacity(); @@ -1588,7 +1578,7 @@ test "DWARF expressions" { // Special operations { - var context = ExpressionContext{}; + var context = Context{}; stack_machine.reset(); program.clearRetainingCapacity(); @@ -1617,7 +1607,7 @@ test "DWARF expressions" { }; var thread_context: std.debug.ThreadContext = undefined; std.debug.relocateContext(&thread_context); - context = ExpressionContext{ + context = Context{ .thread_context = &thread_context, .reg_context = reg_context, }; diff --git a/lib/std/dwarf.zig b/lib/std/dwarf.zig index 25171f51b9..6703574d4e 100644 --- a/lib/std/dwarf.zig +++ b/lib/std/dwarf.zig @@ -1,12 +1,8 @@ //! DWARF debugging data format. - -const builtin = @import("builtin"); -const std = @import("std.zig"); -const debug = std.debug; -const mem = std.mem; -const math = std.math; -const assert = debug.assert; -const native_endian = builtin.cpu.arch.endian(); +//! +//! This namespace contains unopinionated types and data definitions only. For +//! an implementation of parsing and caching DWARF information, see +//! `std.debug.Dwarf`. pub const TAG = @import("dwarf/TAG.zig"); pub const AT = @import("dwarf/AT.zig"); @@ -15,9 +11,7 @@ pub const LANG = @import("dwarf/LANG.zig"); pub const FORM = @import("dwarf/FORM.zig"); pub const ATE = @import("dwarf/ATE.zig"); pub const EH = @import("dwarf/EH.zig"); -pub const abi = @import("dwarf/abi.zig"); -pub const call_frame = @import("dwarf/call_frame.zig"); -pub const expressions = @import("dwarf/expressions.zig"); +pub const Format = enum { @"32", @"64" }; pub const LLE = struct { pub const end_of_list = 0x00; @@ -151,2689 +145,3 @@ pub const CC = enum(u8) { pub const lo_user = 0x40; pub const hi_user = 0xff; }; - -pub const Format = enum { @"32", @"64" }; - -const PcRange = struct { - start: u64, - end: u64, -}; - -const Func = struct { - pc_range: ?PcRange, - name: ?[]const u8, -}; - -pub const CompileUnit = struct { - version: u16, - format: Format, - die: Die, - pc_range: ?PcRange, - - str_offsets_base: usize, - addr_base: usize, - rnglists_base: usize, - loclists_base: usize, - frame_base: ?*const FormValue, -}; - -const Abbrev = struct { - code: u64, - tag_id: u64, - has_children: bool, - attrs: []Attr, - - fn deinit(abbrev: *Abbrev, allocator: mem.Allocator) void { - allocator.free(abbrev.attrs); - abbrev.* = undefined; - } - - const Attr = struct { - id: u64, - form_id: u64, - /// Only valid if form_id is .implicit_const - payload: i64, - }; - - const Table = struct { - // offset from .debug_abbrev - offset: u64, - abbrevs: []Abbrev, - - fn deinit(table: *Table, allocator: mem.Allocator) void { - for (table.abbrevs) |*abbrev| { - abbrev.deinit(allocator); - } - allocator.free(table.abbrevs); - table.* = undefined; - } - - fn get(table: *const Table, abbrev_code: u64) ?*const Abbrev { - return for (table.abbrevs) |*abbrev| { - if (abbrev.code == abbrev_code) break abbrev; - } else null; - } - }; -}; - -pub const FormValue = union(enum) { - addr: u64, - addrx: usize, - block: []const u8, - udata: u64, - data16: *const [16]u8, - sdata: i64, - exprloc: []const u8, - flag: bool, - sec_offset: u64, - ref: u64, - ref_addr: u64, - string: [:0]const u8, - strp: u64, - strx: usize, - line_strp: u64, - loclistx: u64, - rnglistx: u64, - - fn getString(fv: FormValue, di: DwarfInfo) ![:0]const u8 { - switch (fv) { - .string => |s| return s, - .strp => |off| return di.getString(off), - .line_strp => |off| return di.getLineString(off), - else => return badDwarf(), - } - } - - fn getUInt(fv: FormValue, comptime U: type) !U { - return switch (fv) { - inline .udata, - .sdata, - .sec_offset, - => |c| math.cast(U, c) orelse badDwarf(), - else => badDwarf(), - }; - } -}; - -const Die = struct { - tag_id: u64, - has_children: bool, - attrs: []Attr, - - const Attr = struct { - id: u64, - value: FormValue, - }; - - fn deinit(self: *Die, allocator: mem.Allocator) void { - allocator.free(self.attrs); - self.* = undefined; - } - - fn getAttr(self: *const Die, id: u64) ?*const FormValue { - for (self.attrs) |*attr| { - if (attr.id == id) return &attr.value; - } - return null; - } - - fn getAttrAddr( - self: *const Die, - di: *const DwarfInfo, - id: u64, - compile_unit: CompileUnit, - ) error{ InvalidDebugInfo, MissingDebugInfo }!u64 { - const form_value = self.getAttr(id) orelse return error.MissingDebugInfo; - return switch (form_value.*) { - .addr => |value| value, - .addrx => |index| di.readDebugAddr(compile_unit, index), - else => error.InvalidDebugInfo, - }; - } - - fn getAttrSecOffset(self: *const Die, id: u64) !u64 { - const form_value = self.getAttr(id) orelse return error.MissingDebugInfo; - return form_value.getUInt(u64); - } - - fn getAttrUnsignedLe(self: *const Die, id: u64) !u64 { - const form_value = self.getAttr(id) orelse return error.MissingDebugInfo; - return switch (form_value.*) { - .Const => |value| value.asUnsignedLe(), - else => error.InvalidDebugInfo, - }; - } - - fn getAttrRef(self: *const Die, id: u64) !u64 { - const form_value = self.getAttr(id) orelse return error.MissingDebugInfo; - return switch (form_value.*) { - .ref => |value| value, - else => error.InvalidDebugInfo, - }; - } - - pub fn getAttrString( - self: *const Die, - di: *DwarfInfo, - id: u64, - opt_str: ?[]const u8, - compile_unit: CompileUnit, - ) error{ InvalidDebugInfo, MissingDebugInfo }![]const u8 { - const form_value = self.getAttr(id) orelse return error.MissingDebugInfo; - switch (form_value.*) { - .string => |value| return value, - .strp => |offset| return di.getString(offset), - .strx => |index| { - const debug_str_offsets = di.section(.debug_str_offsets) orelse return badDwarf(); - if (compile_unit.str_offsets_base == 0) return badDwarf(); - switch (compile_unit.format) { - .@"32" => { - const byte_offset = compile_unit.str_offsets_base + 4 * index; - if (byte_offset + 4 > debug_str_offsets.len) return badDwarf(); - const offset = mem.readInt(u32, debug_str_offsets[byte_offset..][0..4], di.endian); - return getStringGeneric(opt_str, offset); - }, - .@"64" => { - const byte_offset = compile_unit.str_offsets_base + 8 * index; - if (byte_offset + 8 > debug_str_offsets.len) return badDwarf(); - const offset = mem.readInt(u64, debug_str_offsets[byte_offset..][0..8], di.endian); - return getStringGeneric(opt_str, offset); - }, - } - }, - .line_strp => |offset| return di.getLineString(offset), - else => return badDwarf(), - } - } -}; - -const FileEntry = struct { - path: []const u8, - dir_index: u32 = 0, - mtime: u64 = 0, - size: u64 = 0, - md5: [16]u8 = [1]u8{0} ** 16, -}; - -const LineNumberProgram = struct { - address: u64, - file: usize, - line: i64, - column: u64, - version: u16, - is_stmt: bool, - basic_block: bool, - end_sequence: bool, - - default_is_stmt: bool, - target_address: u64, - include_dirs: []const FileEntry, - - prev_valid: bool, - prev_address: u64, - prev_file: usize, - prev_line: i64, - prev_column: u64, - prev_is_stmt: bool, - prev_basic_block: bool, - prev_end_sequence: bool, - - // Reset the state machine following the DWARF specification - pub fn reset(self: *LineNumberProgram) void { - self.address = 0; - self.file = 1; - self.line = 1; - self.column = 0; - self.is_stmt = self.default_is_stmt; - self.basic_block = false; - self.end_sequence = false; - // Invalidate all the remaining fields - self.prev_valid = false; - self.prev_address = 0; - self.prev_file = undefined; - self.prev_line = undefined; - self.prev_column = undefined; - self.prev_is_stmt = undefined; - self.prev_basic_block = undefined; - self.prev_end_sequence = undefined; - } - - pub fn init( - is_stmt: bool, - include_dirs: []const FileEntry, - target_address: u64, - version: u16, - ) LineNumberProgram { - return LineNumberProgram{ - .address = 0, - .file = 1, - .line = 1, - .column = 0, - .version = version, - .is_stmt = is_stmt, - .basic_block = false, - .end_sequence = false, - .include_dirs = include_dirs, - .default_is_stmt = is_stmt, - .target_address = target_address, - .prev_valid = false, - .prev_address = 0, - .prev_file = undefined, - .prev_line = undefined, - .prev_column = undefined, - .prev_is_stmt = undefined, - .prev_basic_block = undefined, - .prev_end_sequence = undefined, - }; - } - - pub fn checkLineMatch( - self: *LineNumberProgram, - allocator: mem.Allocator, - file_entries: []const FileEntry, - ) !?debug.LineInfo { - if (self.prev_valid and - self.target_address >= self.prev_address and - self.target_address < self.address) - { - const file_index = if (self.version >= 5) self.prev_file else i: { - if (self.prev_file == 0) return missingDwarf(); - break :i self.prev_file - 1; - }; - - if (file_index >= file_entries.len) return badDwarf(); - const file_entry = &file_entries[file_index]; - - if (file_entry.dir_index >= self.include_dirs.len) return badDwarf(); - const dir_name = self.include_dirs[file_entry.dir_index].path; - - const file_name = try std.fs.path.join(allocator, &[_][]const u8{ - dir_name, file_entry.path, - }); - - return debug.LineInfo{ - .line = if (self.prev_line >= 0) @as(u64, @intCast(self.prev_line)) else 0, - .column = self.prev_column, - .file_name = file_name, - }; - } - - self.prev_valid = true; - self.prev_address = self.address; - self.prev_file = self.file; - self.prev_line = self.line; - self.prev_column = self.column; - self.prev_is_stmt = self.is_stmt; - self.prev_basic_block = self.basic_block; - self.prev_end_sequence = self.end_sequence; - return null; - } -}; - -const UnitHeader = struct { - format: Format, - header_length: u4, - unit_length: u64, -}; -fn readUnitHeader(fbr: *FixedBufferReader, opt_ma: ?*debug.StackIterator.MemoryAccessor) !UnitHeader { - return switch (try if (opt_ma) |ma| fbr.readIntChecked(u32, ma) else fbr.readInt(u32)) { - 0...0xfffffff0 - 1 => |unit_length| .{ - .format = .@"32", - .header_length = 4, - .unit_length = unit_length, - }, - 0xfffffff0...0xffffffff - 1 => badDwarf(), - 0xffffffff => .{ - .format = .@"64", - .header_length = 12, - .unit_length = try if (opt_ma) |ma| fbr.readIntChecked(u64, ma) else fbr.readInt(u64), - }, - }; -} - -fn parseFormValue( - fbr: *FixedBufferReader, - form_id: u64, - format: Format, - implicit_const: ?i64, -) anyerror!FormValue { - return switch (form_id) { - FORM.addr => .{ .addr = try fbr.readAddress(switch (@bitSizeOf(usize)) { - 32 => .@"32", - 64 => .@"64", - else => @compileError("unsupported @sizeOf(usize)"), - }) }, - FORM.addrx1 => .{ .addrx = try fbr.readInt(u8) }, - FORM.addrx2 => .{ .addrx = try fbr.readInt(u16) }, - FORM.addrx3 => .{ .addrx = try fbr.readInt(u24) }, - FORM.addrx4 => .{ .addrx = try fbr.readInt(u32) }, - FORM.addrx => .{ .addrx = try fbr.readUleb128(usize) }, - - FORM.block1, - FORM.block2, - FORM.block4, - FORM.block, - => .{ .block = try fbr.readBytes(switch (form_id) { - FORM.block1 => try fbr.readInt(u8), - FORM.block2 => try fbr.readInt(u16), - FORM.block4 => try fbr.readInt(u32), - FORM.block => try fbr.readUleb128(usize), - else => unreachable, - }) }, - - FORM.data1 => .{ .udata = try fbr.readInt(u8) }, - FORM.data2 => .{ .udata = try fbr.readInt(u16) }, - FORM.data4 => .{ .udata = try fbr.readInt(u32) }, - FORM.data8 => .{ .udata = try fbr.readInt(u64) }, - FORM.data16 => .{ .data16 = (try fbr.readBytes(16))[0..16] }, - FORM.udata => .{ .udata = try fbr.readUleb128(u64) }, - FORM.sdata => .{ .sdata = try fbr.readIleb128(i64) }, - FORM.exprloc => .{ .exprloc = try fbr.readBytes(try fbr.readUleb128(usize)) }, - FORM.flag => .{ .flag = (try fbr.readByte()) != 0 }, - FORM.flag_present => .{ .flag = true }, - FORM.sec_offset => .{ .sec_offset = try fbr.readAddress(format) }, - - FORM.ref1 => .{ .ref = try fbr.readInt(u8) }, - FORM.ref2 => .{ .ref = try fbr.readInt(u16) }, - FORM.ref4 => .{ .ref = try fbr.readInt(u32) }, - FORM.ref8 => .{ .ref = try fbr.readInt(u64) }, - FORM.ref_udata => .{ .ref = try fbr.readUleb128(u64) }, - - FORM.ref_addr => .{ .ref_addr = try fbr.readAddress(format) }, - FORM.ref_sig8 => .{ .ref = try fbr.readInt(u64) }, - - FORM.string => .{ .string = try fbr.readBytesTo(0) }, - FORM.strp => .{ .strp = try fbr.readAddress(format) }, - FORM.strx1 => .{ .strx = try fbr.readInt(u8) }, - FORM.strx2 => .{ .strx = try fbr.readInt(u16) }, - FORM.strx3 => .{ .strx = try fbr.readInt(u24) }, - FORM.strx4 => .{ .strx = try fbr.readInt(u32) }, - FORM.strx => .{ .strx = try fbr.readUleb128(usize) }, - FORM.line_strp => .{ .line_strp = try fbr.readAddress(format) }, - FORM.indirect => parseFormValue(fbr, try fbr.readUleb128(u64), format, implicit_const), - FORM.implicit_const => .{ .sdata = implicit_const orelse return badDwarf() }, - FORM.loclistx => .{ .loclistx = try fbr.readUleb128(u64) }, - FORM.rnglistx => .{ .rnglistx = try fbr.readUleb128(u64) }, - else => { - //debug.print("unrecognized form id: {x}\n", .{form_id}); - return badDwarf(); - }, - }; -} - -pub const DwarfSection = enum { - debug_info, - debug_abbrev, - debug_str, - debug_str_offsets, - debug_line, - debug_line_str, - debug_ranges, - debug_loclists, - debug_rnglists, - debug_addr, - debug_names, - debug_frame, - eh_frame, - eh_frame_hdr, -}; - -pub const DwarfInfo = struct { - pub const Section = struct { - data: []const u8, - // Module-relative virtual address. - // Only set if the section data was loaded from disk. - virtual_address: ?usize = null, - // If `data` is owned by this DwarfInfo. - owned: bool, - - // For sections that are not memory mapped by the loader, this is an offset - // from `data.ptr` to where the section would have been mapped. Otherwise, - // `data` is directly backed by the section and the offset is zero. - pub fn virtualOffset(self: Section, base_address: usize) i64 { - return if (self.virtual_address) |va| - @as(i64, @intCast(base_address + va)) - - @as(i64, @intCast(@intFromPtr(self.data.ptr))) - else - 0; - } - }; - - const num_sections = std.enums.directEnumArrayLen(DwarfSection, 0); - pub const SectionArray = [num_sections]?Section; - pub const null_section_array = [_]?Section{null} ** num_sections; - - endian: std.builtin.Endian, - sections: SectionArray = null_section_array, - is_macho: bool, - - // Filled later by the initializer - abbrev_table_list: std.ArrayListUnmanaged(Abbrev.Table) = .{}, - compile_unit_list: std.ArrayListUnmanaged(CompileUnit) = .{}, - func_list: std.ArrayListUnmanaged(Func) = .{}, - - eh_frame_hdr: ?ExceptionFrameHeader = null, - // These lookup tables are only used if `eh_frame_hdr` is null - cie_map: std.AutoArrayHashMapUnmanaged(u64, CommonInformationEntry) = .{}, - // Sorted by start_pc - fde_list: std.ArrayListUnmanaged(FrameDescriptionEntry) = .{}, - - pub fn section(di: DwarfInfo, dwarf_section: DwarfSection) ?[]const u8 { - return if (di.sections[@intFromEnum(dwarf_section)]) |s| s.data else null; - } - - pub fn sectionVirtualOffset(di: DwarfInfo, dwarf_section: DwarfSection, base_address: usize) ?i64 { - return if (di.sections[@intFromEnum(dwarf_section)]) |s| s.virtualOffset(base_address) else null; - } - - pub fn deinit(di: *DwarfInfo, allocator: mem.Allocator) void { - for (di.sections) |opt_section| { - if (opt_section) |s| if (s.owned) allocator.free(s.data); - } - for (di.abbrev_table_list.items) |*abbrev| { - abbrev.deinit(allocator); - } - di.abbrev_table_list.deinit(allocator); - for (di.compile_unit_list.items) |*cu| { - cu.die.deinit(allocator); - } - di.compile_unit_list.deinit(allocator); - di.func_list.deinit(allocator); - di.cie_map.deinit(allocator); - di.fde_list.deinit(allocator); - di.* = undefined; - } - - pub fn getSymbolName(di: *DwarfInfo, address: u64) ?[]const u8 { - for (di.func_list.items) |*func| { - if (func.pc_range) |range| { - if (address >= range.start and address < range.end) { - return func.name; - } - } - } - - return null; - } - - fn scanAllFunctions(di: *DwarfInfo, allocator: mem.Allocator) !void { - var fbr: FixedBufferReader = .{ .buf = di.section(.debug_info).?, .endian = di.endian }; - var this_unit_offset: u64 = 0; - - while (this_unit_offset < fbr.buf.len) { - try fbr.seekTo(this_unit_offset); - - const unit_header = try readUnitHeader(&fbr, null); - if (unit_header.unit_length == 0) return; - const next_offset = unit_header.header_length + unit_header.unit_length; - - const version = try fbr.readInt(u16); - if (version < 2 or version > 5) return badDwarf(); - - var address_size: u8 = undefined; - var debug_abbrev_offset: u64 = undefined; - if (version >= 5) { - const unit_type = try fbr.readInt(u8); - if (unit_type != UT.compile) return badDwarf(); - address_size = try fbr.readByte(); - debug_abbrev_offset = try fbr.readAddress(unit_header.format); - } else { - debug_abbrev_offset = try fbr.readAddress(unit_header.format); - address_size = try fbr.readByte(); - } - if (address_size != @sizeOf(usize)) return badDwarf(); - - const abbrev_table = try di.getAbbrevTable(allocator, debug_abbrev_offset); - - var max_attrs: usize = 0; - var zig_padding_abbrev_code: u7 = 0; - for (abbrev_table.abbrevs) |abbrev| { - max_attrs = @max(max_attrs, abbrev.attrs.len); - if (math.cast(u7, abbrev.code)) |code| { - if (abbrev.tag_id == TAG.ZIG_padding and - !abbrev.has_children and - abbrev.attrs.len == 0) - { - zig_padding_abbrev_code = code; - } - } - } - const attrs_buf = try allocator.alloc(Die.Attr, max_attrs * 3); - defer allocator.free(attrs_buf); - var attrs_bufs: [3][]Die.Attr = undefined; - for (&attrs_bufs, 0..) |*buf, index| buf.* = attrs_buf[index * max_attrs ..][0..max_attrs]; - - const next_unit_pos = this_unit_offset + next_offset; - - var compile_unit: CompileUnit = .{ - .version = version, - .format = unit_header.format, - .die = undefined, - .pc_range = null, - - .str_offsets_base = 0, - .addr_base = 0, - .rnglists_base = 0, - .loclists_base = 0, - .frame_base = null, - }; - - while (true) { - fbr.pos = mem.indexOfNonePos(u8, fbr.buf, fbr.pos, &.{ - zig_padding_abbrev_code, 0, - }) orelse fbr.buf.len; - if (fbr.pos >= next_unit_pos) break; - var die_obj = (try parseDie( - &fbr, - attrs_bufs[0], - abbrev_table, - unit_header.format, - )) orelse continue; - - switch (die_obj.tag_id) { - TAG.compile_unit => { - compile_unit.die = die_obj; - compile_unit.die.attrs = attrs_bufs[1][0..die_obj.attrs.len]; - @memcpy(compile_unit.die.attrs, die_obj.attrs); - - compile_unit.str_offsets_base = if (die_obj.getAttr(AT.str_offsets_base)) |fv| try fv.getUInt(usize) else 0; - compile_unit.addr_base = if (die_obj.getAttr(AT.addr_base)) |fv| try fv.getUInt(usize) else 0; - compile_unit.rnglists_base = if (die_obj.getAttr(AT.rnglists_base)) |fv| try fv.getUInt(usize) else 0; - compile_unit.loclists_base = if (die_obj.getAttr(AT.loclists_base)) |fv| try fv.getUInt(usize) else 0; - compile_unit.frame_base = die_obj.getAttr(AT.frame_base); - }, - TAG.subprogram, TAG.inlined_subroutine, TAG.subroutine, TAG.entry_point => { - const fn_name = x: { - var this_die_obj = die_obj; - // Prevent endless loops - for (0..3) |_| { - if (this_die_obj.getAttr(AT.name)) |_| { - break :x try this_die_obj.getAttrString(di, AT.name, di.section(.debug_str), compile_unit); - } else if (this_die_obj.getAttr(AT.abstract_origin)) |_| { - const after_die_offset = fbr.pos; - defer fbr.pos = after_die_offset; - - // Follow the DIE it points to and repeat - const ref_offset = try this_die_obj.getAttrRef(AT.abstract_origin); - if (ref_offset > next_offset) return badDwarf(); - try fbr.seekTo(this_unit_offset + ref_offset); - this_die_obj = (try parseDie( - &fbr, - attrs_bufs[2], - abbrev_table, - unit_header.format, - )) orelse return badDwarf(); - } else if (this_die_obj.getAttr(AT.specification)) |_| { - const after_die_offset = fbr.pos; - defer fbr.pos = after_die_offset; - - // Follow the DIE it points to and repeat - const ref_offset = try this_die_obj.getAttrRef(AT.specification); - if (ref_offset > next_offset) return badDwarf(); - try fbr.seekTo(this_unit_offset + ref_offset); - this_die_obj = (try parseDie( - &fbr, - attrs_bufs[2], - abbrev_table, - unit_header.format, - )) orelse return badDwarf(); - } else { - break :x null; - } - } - - break :x null; - }; - - var range_added = if (die_obj.getAttrAddr(di, AT.low_pc, compile_unit)) |low_pc| blk: { - if (die_obj.getAttr(AT.high_pc)) |high_pc_value| { - const pc_end = switch (high_pc_value.*) { - .addr => |value| value, - .udata => |offset| low_pc + offset, - else => return badDwarf(), - }; - - try di.func_list.append(allocator, .{ - .name = fn_name, - .pc_range = .{ - .start = low_pc, - .end = pc_end, - }, - }); - - break :blk true; - } - - break :blk false; - } else |err| blk: { - if (err != error.MissingDebugInfo) return err; - break :blk false; - }; - - if (die_obj.getAttr(AT.ranges)) |ranges_value| blk: { - var iter = DebugRangeIterator.init(ranges_value, di, &compile_unit) catch |err| { - if (err != error.MissingDebugInfo) return err; - break :blk; - }; - - while (try iter.next()) |range| { - range_added = true; - try di.func_list.append(allocator, .{ - .name = fn_name, - .pc_range = .{ - .start = range.start_addr, - .end = range.end_addr, - }, - }); - } - } - - if (fn_name != null and !range_added) { - try di.func_list.append(allocator, .{ - .name = fn_name, - .pc_range = null, - }); - } - }, - else => {}, - } - } - - this_unit_offset += next_offset; - } - } - - fn scanAllCompileUnits(di: *DwarfInfo, allocator: mem.Allocator) !void { - var fbr: FixedBufferReader = .{ .buf = di.section(.debug_info).?, .endian = di.endian }; - var this_unit_offset: u64 = 0; - - var attrs_buf = std.ArrayList(Die.Attr).init(allocator); - defer attrs_buf.deinit(); - - while (this_unit_offset < fbr.buf.len) { - try fbr.seekTo(this_unit_offset); - - const unit_header = try readUnitHeader(&fbr, null); - if (unit_header.unit_length == 0) return; - const next_offset = unit_header.header_length + unit_header.unit_length; - - const version = try fbr.readInt(u16); - if (version < 2 or version > 5) return badDwarf(); - - var address_size: u8 = undefined; - var debug_abbrev_offset: u64 = undefined; - if (version >= 5) { - const unit_type = try fbr.readInt(u8); - if (unit_type != UT.compile) return badDwarf(); - address_size = try fbr.readByte(); - debug_abbrev_offset = try fbr.readAddress(unit_header.format); - } else { - debug_abbrev_offset = try fbr.readAddress(unit_header.format); - address_size = try fbr.readByte(); - } - if (address_size != @sizeOf(usize)) return badDwarf(); - - const abbrev_table = try di.getAbbrevTable(allocator, debug_abbrev_offset); - - var max_attrs: usize = 0; - for (abbrev_table.abbrevs) |abbrev| { - max_attrs = @max(max_attrs, abbrev.attrs.len); - } - try attrs_buf.resize(max_attrs); - - var compile_unit_die = (try parseDie( - &fbr, - attrs_buf.items, - abbrev_table, - unit_header.format, - )) orelse return badDwarf(); - - if (compile_unit_die.tag_id != TAG.compile_unit) return badDwarf(); - - compile_unit_die.attrs = try allocator.dupe(Die.Attr, compile_unit_die.attrs); - - var compile_unit: CompileUnit = .{ - .version = version, - .format = unit_header.format, - .pc_range = null, - .die = compile_unit_die, - .str_offsets_base = if (compile_unit_die.getAttr(AT.str_offsets_base)) |fv| try fv.getUInt(usize) else 0, - .addr_base = if (compile_unit_die.getAttr(AT.addr_base)) |fv| try fv.getUInt(usize) else 0, - .rnglists_base = if (compile_unit_die.getAttr(AT.rnglists_base)) |fv| try fv.getUInt(usize) else 0, - .loclists_base = if (compile_unit_die.getAttr(AT.loclists_base)) |fv| try fv.getUInt(usize) else 0, - .frame_base = compile_unit_die.getAttr(AT.frame_base), - }; - - compile_unit.pc_range = x: { - if (compile_unit_die.getAttrAddr(di, AT.low_pc, compile_unit)) |low_pc| { - if (compile_unit_die.getAttr(AT.high_pc)) |high_pc_value| { - const pc_end = switch (high_pc_value.*) { - .addr => |value| value, - .udata => |offset| low_pc + offset, - else => return badDwarf(), - }; - break :x PcRange{ - .start = low_pc, - .end = pc_end, - }; - } else { - break :x null; - } - } else |err| { - if (err != error.MissingDebugInfo) return err; - break :x null; - } - }; - - try di.compile_unit_list.append(allocator, compile_unit); - - this_unit_offset += next_offset; - } - } - - const DebugRangeIterator = struct { - base_address: u64, - section_type: DwarfSection, - di: *const DwarfInfo, - compile_unit: *const CompileUnit, - fbr: FixedBufferReader, - - pub fn init(ranges_value: *const FormValue, di: *const DwarfInfo, compile_unit: *const CompileUnit) !@This() { - const section_type = if (compile_unit.version >= 5) DwarfSection.debug_rnglists else DwarfSection.debug_ranges; - const debug_ranges = di.section(section_type) orelse return error.MissingDebugInfo; - - const ranges_offset = switch (ranges_value.*) { - .sec_offset, .udata => |off| off, - .rnglistx => |idx| off: { - switch (compile_unit.format) { - .@"32" => { - const offset_loc = @as(usize, @intCast(compile_unit.rnglists_base + 4 * idx)); - if (offset_loc + 4 > debug_ranges.len) return badDwarf(); - const offset = mem.readInt(u32, debug_ranges[offset_loc..][0..4], di.endian); - break :off compile_unit.rnglists_base + offset; - }, - .@"64" => { - const offset_loc = @as(usize, @intCast(compile_unit.rnglists_base + 8 * idx)); - if (offset_loc + 8 > debug_ranges.len) return badDwarf(); - const offset = mem.readInt(u64, debug_ranges[offset_loc..][0..8], di.endian); - break :off compile_unit.rnglists_base + offset; - }, - } - }, - else => return badDwarf(), - }; - - // All the addresses in the list are relative to the value - // specified by DW_AT.low_pc or to some other value encoded - // in the list itself. - // If no starting value is specified use zero. - const base_address = compile_unit.die.getAttrAddr(di, AT.low_pc, compile_unit.*) catch |err| switch (err) { - error.MissingDebugInfo => 0, - else => return err, - }; - - return .{ - .base_address = base_address, - .section_type = section_type, - .di = di, - .compile_unit = compile_unit, - .fbr = .{ - .buf = debug_ranges, - .pos = math.cast(usize, ranges_offset) orelse return badDwarf(), - .endian = di.endian, - }, - }; - } - - // Returns the next range in the list, or null if the end was reached. - pub fn next(self: *@This()) !?struct { start_addr: u64, end_addr: u64 } { - switch (self.section_type) { - .debug_rnglists => { - const kind = try self.fbr.readByte(); - switch (kind) { - RLE.end_of_list => return null, - RLE.base_addressx => { - const index = try self.fbr.readUleb128(usize); - self.base_address = try self.di.readDebugAddr(self.compile_unit.*, index); - return try self.next(); - }, - RLE.startx_endx => { - const start_index = try self.fbr.readUleb128(usize); - const start_addr = try self.di.readDebugAddr(self.compile_unit.*, start_index); - - const end_index = try self.fbr.readUleb128(usize); - const end_addr = try self.di.readDebugAddr(self.compile_unit.*, end_index); - - return .{ - .start_addr = start_addr, - .end_addr = end_addr, - }; - }, - RLE.startx_length => { - const start_index = try self.fbr.readUleb128(usize); - const start_addr = try self.di.readDebugAddr(self.compile_unit.*, start_index); - - const len = try self.fbr.readUleb128(usize); - const end_addr = start_addr + len; - - return .{ - .start_addr = start_addr, - .end_addr = end_addr, - }; - }, - RLE.offset_pair => { - const start_addr = try self.fbr.readUleb128(usize); - const end_addr = try self.fbr.readUleb128(usize); - - // This is the only kind that uses the base address - return .{ - .start_addr = self.base_address + start_addr, - .end_addr = self.base_address + end_addr, - }; - }, - RLE.base_address => { - self.base_address = try self.fbr.readInt(usize); - return try self.next(); - }, - RLE.start_end => { - const start_addr = try self.fbr.readInt(usize); - const end_addr = try self.fbr.readInt(usize); - - return .{ - .start_addr = start_addr, - .end_addr = end_addr, - }; - }, - RLE.start_length => { - const start_addr = try self.fbr.readInt(usize); - const len = try self.fbr.readUleb128(usize); - const end_addr = start_addr + len; - - return .{ - .start_addr = start_addr, - .end_addr = end_addr, - }; - }, - else => return badDwarf(), - } - }, - .debug_ranges => { - const start_addr = try self.fbr.readInt(usize); - const end_addr = try self.fbr.readInt(usize); - if (start_addr == 0 and end_addr == 0) return null; - - // This entry selects a new value for the base address - if (start_addr == math.maxInt(usize)) { - self.base_address = end_addr; - return try self.next(); - } - - return .{ - .start_addr = self.base_address + start_addr, - .end_addr = self.base_address + end_addr, - }; - }, - else => unreachable, - } - } - }; - - pub fn findCompileUnit(di: *const DwarfInfo, target_address: u64) !*const CompileUnit { - for (di.compile_unit_list.items) |*compile_unit| { - if (compile_unit.pc_range) |range| { - if (target_address >= range.start and target_address < range.end) return compile_unit; - } - - const ranges_value = compile_unit.die.getAttr(AT.ranges) orelse continue; - var iter = DebugRangeIterator.init(ranges_value, di, compile_unit) catch continue; - while (try iter.next()) |range| { - if (target_address >= range.start_addr and target_address < range.end_addr) return compile_unit; - } - } - - return missingDwarf(); - } - - /// Gets an already existing AbbrevTable given the abbrev_offset, or if not found, - /// seeks in the stream and parses it. - fn getAbbrevTable(di: *DwarfInfo, allocator: mem.Allocator, abbrev_offset: u64) !*const Abbrev.Table { - for (di.abbrev_table_list.items) |*table| { - if (table.offset == abbrev_offset) { - return table; - } - } - try di.abbrev_table_list.append( - allocator, - try di.parseAbbrevTable(allocator, abbrev_offset), - ); - return &di.abbrev_table_list.items[di.abbrev_table_list.items.len - 1]; - } - - fn parseAbbrevTable(di: *DwarfInfo, allocator: mem.Allocator, offset: u64) !Abbrev.Table { - var fbr: FixedBufferReader = .{ - .buf = di.section(.debug_abbrev).?, - .pos = math.cast(usize, offset) orelse return badDwarf(), - .endian = di.endian, - }; - - var abbrevs = std.ArrayList(Abbrev).init(allocator); - defer { - for (abbrevs.items) |*abbrev| { - abbrev.deinit(allocator); - } - abbrevs.deinit(); - } - - var attrs = std.ArrayList(Abbrev.Attr).init(allocator); - defer attrs.deinit(); - - while (true) { - const code = try fbr.readUleb128(u64); - if (code == 0) break; - const tag_id = try fbr.readUleb128(u64); - const has_children = (try fbr.readByte()) == CHILDREN.yes; - - while (true) { - const attr_id = try fbr.readUleb128(u64); - const form_id = try fbr.readUleb128(u64); - if (attr_id == 0 and form_id == 0) break; - try attrs.append(.{ - .id = attr_id, - .form_id = form_id, - .payload = switch (form_id) { - FORM.implicit_const => try fbr.readIleb128(i64), - else => undefined, - }, - }); - } - - try abbrevs.append(.{ - .code = code, - .tag_id = tag_id, - .has_children = has_children, - .attrs = try attrs.toOwnedSlice(), - }); - } - - return .{ - .offset = offset, - .abbrevs = try abbrevs.toOwnedSlice(), - }; - } - - fn parseDie( - fbr: *FixedBufferReader, - attrs_buf: []Die.Attr, - abbrev_table: *const Abbrev.Table, - format: Format, - ) !?Die { - const abbrev_code = try fbr.readUleb128(u64); - if (abbrev_code == 0) return null; - const table_entry = abbrev_table.get(abbrev_code) orelse return badDwarf(); - - const attrs = attrs_buf[0..table_entry.attrs.len]; - for (attrs, table_entry.attrs) |*result_attr, attr| result_attr.* = Die.Attr{ - .id = attr.id, - .value = try parseFormValue( - fbr, - attr.form_id, - format, - attr.payload, - ), - }; - return .{ - .tag_id = table_entry.tag_id, - .has_children = table_entry.has_children, - .attrs = attrs, - }; - } - - pub fn getLineNumberInfo( - di: *DwarfInfo, - allocator: mem.Allocator, - compile_unit: CompileUnit, - target_address: u64, - ) !debug.LineInfo { - const compile_unit_cwd = try compile_unit.die.getAttrString(di, AT.comp_dir, di.section(.debug_line_str), compile_unit); - const line_info_offset = try compile_unit.die.getAttrSecOffset(AT.stmt_list); - - var fbr: FixedBufferReader = .{ .buf = di.section(.debug_line).?, .endian = di.endian }; - try fbr.seekTo(line_info_offset); - - const unit_header = try readUnitHeader(&fbr, null); - if (unit_header.unit_length == 0) return missingDwarf(); - const next_offset = unit_header.header_length + unit_header.unit_length; - - const version = try fbr.readInt(u16); - if (version < 2) return badDwarf(); - - var addr_size: u8 = switch (unit_header.format) { - .@"32" => 4, - .@"64" => 8, - }; - var seg_size: u8 = 0; - if (version >= 5) { - addr_size = try fbr.readByte(); - seg_size = try fbr.readByte(); - } - - const prologue_length = try fbr.readAddress(unit_header.format); - const prog_start_offset = fbr.pos + prologue_length; - - const minimum_instruction_length = try fbr.readByte(); - if (minimum_instruction_length == 0) return badDwarf(); - - if (version >= 4) { - // maximum_operations_per_instruction - _ = try fbr.readByte(); - } - - const default_is_stmt = (try fbr.readByte()) != 0; - const line_base = try fbr.readByteSigned(); - - const line_range = try fbr.readByte(); - if (line_range == 0) return badDwarf(); - - const opcode_base = try fbr.readByte(); - - const standard_opcode_lengths = try fbr.readBytes(opcode_base - 1); - - var include_directories = std.ArrayList(FileEntry).init(allocator); - defer include_directories.deinit(); - var file_entries = std.ArrayList(FileEntry).init(allocator); - defer file_entries.deinit(); - - if (version < 5) { - try include_directories.append(.{ .path = compile_unit_cwd }); - - while (true) { - const dir = try fbr.readBytesTo(0); - if (dir.len == 0) break; - try include_directories.append(.{ .path = dir }); - } - - while (true) { - const file_name = try fbr.readBytesTo(0); - if (file_name.len == 0) break; - const dir_index = try fbr.readUleb128(u32); - const mtime = try fbr.readUleb128(u64); - const size = try fbr.readUleb128(u64); - try file_entries.append(.{ - .path = file_name, - .dir_index = dir_index, - .mtime = mtime, - .size = size, - }); - } - } else { - const FileEntFmt = struct { - content_type_code: u8, - form_code: u16, - }; - { - var dir_ent_fmt_buf: [10]FileEntFmt = undefined; - const directory_entry_format_count = try fbr.readByte(); - if (directory_entry_format_count > dir_ent_fmt_buf.len) return badDwarf(); - for (dir_ent_fmt_buf[0..directory_entry_format_count]) |*ent_fmt| { - ent_fmt.* = .{ - .content_type_code = try fbr.readUleb128(u8), - .form_code = try fbr.readUleb128(u16), - }; - } - - const directories_count = try fbr.readUleb128(usize); - try include_directories.ensureUnusedCapacity(directories_count); - { - var i: usize = 0; - while (i < directories_count) : (i += 1) { - var e: FileEntry = .{ .path = &.{} }; - for (dir_ent_fmt_buf[0..directory_entry_format_count]) |ent_fmt| { - const form_value = try parseFormValue( - &fbr, - ent_fmt.form_code, - unit_header.format, - null, - ); - switch (ent_fmt.content_type_code) { - LNCT.path => e.path = try form_value.getString(di.*), - LNCT.directory_index => e.dir_index = try form_value.getUInt(u32), - LNCT.timestamp => e.mtime = try form_value.getUInt(u64), - LNCT.size => e.size = try form_value.getUInt(u64), - LNCT.MD5 => e.md5 = switch (form_value) { - .data16 => |data16| data16.*, - else => return badDwarf(), - }, - else => continue, - } - } - include_directories.appendAssumeCapacity(e); - } - } - } - - var file_ent_fmt_buf: [10]FileEntFmt = undefined; - const file_name_entry_format_count = try fbr.readByte(); - if (file_name_entry_format_count > file_ent_fmt_buf.len) return badDwarf(); - for (file_ent_fmt_buf[0..file_name_entry_format_count]) |*ent_fmt| { - ent_fmt.* = .{ - .content_type_code = try fbr.readUleb128(u8), - .form_code = try fbr.readUleb128(u16), - }; - } - - const file_names_count = try fbr.readUleb128(usize); - try file_entries.ensureUnusedCapacity(file_names_count); - { - var i: usize = 0; - while (i < file_names_count) : (i += 1) { - var e: FileEntry = .{ .path = &.{} }; - for (file_ent_fmt_buf[0..file_name_entry_format_count]) |ent_fmt| { - const form_value = try parseFormValue( - &fbr, - ent_fmt.form_code, - unit_header.format, - null, - ); - switch (ent_fmt.content_type_code) { - LNCT.path => e.path = try form_value.getString(di.*), - LNCT.directory_index => e.dir_index = try form_value.getUInt(u32), - LNCT.timestamp => e.mtime = try form_value.getUInt(u64), - LNCT.size => e.size = try form_value.getUInt(u64), - LNCT.MD5 => e.md5 = switch (form_value) { - .data16 => |data16| data16.*, - else => return badDwarf(), - }, - else => continue, - } - } - file_entries.appendAssumeCapacity(e); - } - } - } - - var prog = LineNumberProgram.init( - default_is_stmt, - include_directories.items, - target_address, - version, - ); - - try fbr.seekTo(prog_start_offset); - - const next_unit_pos = line_info_offset + next_offset; - - while (fbr.pos < next_unit_pos) { - const opcode = try fbr.readByte(); - - if (opcode == LNS.extended_op) { - const op_size = try fbr.readUleb128(u64); - if (op_size < 1) return badDwarf(); - const sub_op = try fbr.readByte(); - switch (sub_op) { - LNE.end_sequence => { - prog.end_sequence = true; - if (try prog.checkLineMatch(allocator, file_entries.items)) |info| return info; - prog.reset(); - }, - LNE.set_address => { - const addr = try fbr.readInt(usize); - prog.address = addr; - }, - LNE.define_file => { - const path = try fbr.readBytesTo(0); - const dir_index = try fbr.readUleb128(u32); - const mtime = try fbr.readUleb128(u64); - const size = try fbr.readUleb128(u64); - try file_entries.append(.{ - .path = path, - .dir_index = dir_index, - .mtime = mtime, - .size = size, - }); - }, - else => try fbr.seekForward(op_size - 1), - } - } else if (opcode >= opcode_base) { - // special opcodes - const adjusted_opcode = opcode - opcode_base; - const inc_addr = minimum_instruction_length * (adjusted_opcode / line_range); - const inc_line = @as(i32, line_base) + @as(i32, adjusted_opcode % line_range); - prog.line += inc_line; - prog.address += inc_addr; - if (try prog.checkLineMatch(allocator, file_entries.items)) |info| return info; - prog.basic_block = false; - } else { - switch (opcode) { - LNS.copy => { - if (try prog.checkLineMatch(allocator, file_entries.items)) |info| return info; - prog.basic_block = false; - }, - LNS.advance_pc => { - const arg = try fbr.readUleb128(usize); - prog.address += arg * minimum_instruction_length; - }, - LNS.advance_line => { - const arg = try fbr.readIleb128(i64); - prog.line += arg; - }, - LNS.set_file => { - const arg = try fbr.readUleb128(usize); - prog.file = arg; - }, - LNS.set_column => { - const arg = try fbr.readUleb128(u64); - prog.column = arg; - }, - LNS.negate_stmt => { - prog.is_stmt = !prog.is_stmt; - }, - LNS.set_basic_block => { - prog.basic_block = true; - }, - LNS.const_add_pc => { - const inc_addr = minimum_instruction_length * ((255 - opcode_base) / line_range); - prog.address += inc_addr; - }, - LNS.fixed_advance_pc => { - const arg = try fbr.readInt(u16); - prog.address += arg; - }, - LNS.set_prologue_end => {}, - else => { - if (opcode - 1 >= standard_opcode_lengths.len) return badDwarf(); - try fbr.seekForward(standard_opcode_lengths[opcode - 1]); - }, - } - } - } - - return missingDwarf(); - } - - fn getString(di: DwarfInfo, offset: u64) ![:0]const u8 { - return getStringGeneric(di.section(.debug_str), offset); - } - - fn getLineString(di: DwarfInfo, offset: u64) ![:0]const u8 { - return getStringGeneric(di.section(.debug_line_str), offset); - } - - fn readDebugAddr(di: DwarfInfo, compile_unit: CompileUnit, index: u64) !u64 { - const debug_addr = di.section(.debug_addr) orelse return badDwarf(); - - // addr_base points to the first item after the header, however we - // need to read the header to know the size of each item. Empirically, - // it may disagree with is_64 on the compile unit. - // The header is 8 or 12 bytes depending on is_64. - if (compile_unit.addr_base < 8) return badDwarf(); - - const version = mem.readInt(u16, debug_addr[compile_unit.addr_base - 4 ..][0..2], di.endian); - if (version != 5) return badDwarf(); - - const addr_size = debug_addr[compile_unit.addr_base - 2]; - const seg_size = debug_addr[compile_unit.addr_base - 1]; - - const byte_offset = @as(usize, @intCast(compile_unit.addr_base + (addr_size + seg_size) * index)); - if (byte_offset + addr_size > debug_addr.len) return badDwarf(); - return switch (addr_size) { - 1 => debug_addr[byte_offset], - 2 => mem.readInt(u16, debug_addr[byte_offset..][0..2], di.endian), - 4 => mem.readInt(u32, debug_addr[byte_offset..][0..4], di.endian), - 8 => mem.readInt(u64, debug_addr[byte_offset..][0..8], di.endian), - else => badDwarf(), - }; - } - - /// If .eh_frame_hdr is present, then only the header needs to be parsed. - /// - /// Otherwise, .eh_frame and .debug_frame are scanned and a sorted list - /// of FDEs is built for binary searching during unwinding. - pub fn scanAllUnwindInfo(di: *DwarfInfo, allocator: mem.Allocator, base_address: usize) !void { - if (di.section(.eh_frame_hdr)) |eh_frame_hdr| blk: { - var fbr: FixedBufferReader = .{ .buf = eh_frame_hdr, .endian = native_endian }; - - const version = try fbr.readByte(); - if (version != 1) break :blk; - - const eh_frame_ptr_enc = try fbr.readByte(); - if (eh_frame_ptr_enc == EH.PE.omit) break :blk; - const fde_count_enc = try fbr.readByte(); - if (fde_count_enc == EH.PE.omit) break :blk; - const table_enc = try fbr.readByte(); - if (table_enc == EH.PE.omit) break :blk; - - const eh_frame_ptr = math.cast(usize, try readEhPointer(&fbr, eh_frame_ptr_enc, @sizeOf(usize), .{ - .pc_rel_base = @intFromPtr(&eh_frame_hdr[fbr.pos]), - .follow_indirect = true, - }) orelse return badDwarf()) orelse return badDwarf(); - - const fde_count = math.cast(usize, try readEhPointer(&fbr, fde_count_enc, @sizeOf(usize), .{ - .pc_rel_base = @intFromPtr(&eh_frame_hdr[fbr.pos]), - .follow_indirect = true, - }) orelse return badDwarf()) orelse return badDwarf(); - - const entry_size = try ExceptionFrameHeader.entrySize(table_enc); - const entries_len = fde_count * entry_size; - if (entries_len > eh_frame_hdr.len - fbr.pos) return badDwarf(); - - di.eh_frame_hdr = .{ - .eh_frame_ptr = eh_frame_ptr, - .table_enc = table_enc, - .fde_count = fde_count, - .entries = eh_frame_hdr[fbr.pos..][0..entries_len], - }; - - // No need to scan .eh_frame, we have a binary search table already - return; - } - - const frame_sections = [2]DwarfSection{ .eh_frame, .debug_frame }; - for (frame_sections) |frame_section| { - if (di.section(frame_section)) |section_data| { - var fbr: FixedBufferReader = .{ .buf = section_data, .endian = di.endian }; - while (fbr.pos < fbr.buf.len) { - const entry_header = try EntryHeader.read(&fbr, null, frame_section); - switch (entry_header.type) { - .cie => { - const cie = try CommonInformationEntry.parse( - entry_header.entry_bytes, - di.sectionVirtualOffset(frame_section, base_address).?, - true, - entry_header.format, - frame_section, - entry_header.length_offset, - @sizeOf(usize), - di.endian, - ); - try di.cie_map.put(allocator, entry_header.length_offset, cie); - }, - .fde => |cie_offset| { - const cie = di.cie_map.get(cie_offset) orelse return badDwarf(); - const fde = try FrameDescriptionEntry.parse( - entry_header.entry_bytes, - di.sectionVirtualOffset(frame_section, base_address).?, - true, - cie, - @sizeOf(usize), - di.endian, - ); - try di.fde_list.append(allocator, fde); - }, - .terminator => break, - } - } - - mem.sortUnstable(FrameDescriptionEntry, di.fde_list.items, {}, struct { - fn lessThan(ctx: void, a: FrameDescriptionEntry, b: FrameDescriptionEntry) bool { - _ = ctx; - return a.pc_begin < b.pc_begin; - } - }.lessThan); - } - } - } - - /// Unwind a stack frame using DWARF unwinding info, updating the register context. - /// - /// If `.eh_frame_hdr` is available, it will be used to binary search for the FDE. - /// Otherwise, a linear scan of `.eh_frame` and `.debug_frame` is done to find the FDE. - /// - /// `explicit_fde_offset` is for cases where the FDE offset is known, such as when __unwind_info - /// defers unwinding to DWARF. This is an offset into the `.eh_frame` section. - pub fn unwindFrame(di: *const DwarfInfo, context: *UnwindContext, ma: *debug.StackIterator.MemoryAccessor, explicit_fde_offset: ?usize) !usize { - if (!comptime abi.supportsUnwinding(builtin.target)) return error.UnsupportedCpuArchitecture; - if (context.pc == 0) return 0; - - // Find the FDE and CIE - var cie: CommonInformationEntry = undefined; - var fde: FrameDescriptionEntry = undefined; - - if (explicit_fde_offset) |fde_offset| { - const dwarf_section: DwarfSection = .eh_frame; - const frame_section = di.section(dwarf_section) orelse return error.MissingFDE; - if (fde_offset >= frame_section.len) return error.MissingFDE; - - var fbr: FixedBufferReader = .{ - .buf = frame_section, - .pos = fde_offset, - .endian = di.endian, - }; - - const fde_entry_header = try EntryHeader.read(&fbr, null, dwarf_section); - if (fde_entry_header.type != .fde) return error.MissingFDE; - - const cie_offset = fde_entry_header.type.fde; - try fbr.seekTo(cie_offset); - - fbr.endian = native_endian; - const cie_entry_header = try EntryHeader.read(&fbr, null, dwarf_section); - if (cie_entry_header.type != .cie) return badDwarf(); - - cie = try CommonInformationEntry.parse( - cie_entry_header.entry_bytes, - 0, - true, - cie_entry_header.format, - dwarf_section, - cie_entry_header.length_offset, - @sizeOf(usize), - native_endian, - ); - - fde = try FrameDescriptionEntry.parse( - fde_entry_header.entry_bytes, - 0, - true, - cie, - @sizeOf(usize), - native_endian, - ); - } else if (di.eh_frame_hdr) |header| { - const eh_frame_len = if (di.section(.eh_frame)) |eh_frame| eh_frame.len else null; - try header.findEntry( - ma, - eh_frame_len, - @intFromPtr(di.section(.eh_frame_hdr).?.ptr), - context.pc, - &cie, - &fde, - ); - } else { - const index = std.sort.binarySearch(FrameDescriptionEntry, context.pc, di.fde_list.items, {}, struct { - pub fn compareFn(_: void, pc: usize, mid_item: FrameDescriptionEntry) math.Order { - if (pc < mid_item.pc_begin) return .lt; - - const range_end = mid_item.pc_begin + mid_item.pc_range; - if (pc < range_end) return .eq; - - return .gt; - } - }.compareFn); - - fde = if (index) |i| di.fde_list.items[i] else return error.MissingFDE; - cie = di.cie_map.get(fde.cie_length_offset) orelse return error.MissingCIE; - } - - var expression_context: expressions.ExpressionContext = .{ - .format = cie.format, - .memory_accessor = ma, - .compile_unit = di.findCompileUnit(fde.pc_begin) catch null, - .thread_context = context.thread_context, - .reg_context = context.reg_context, - .cfa = context.cfa, - }; - - context.vm.reset(); - context.reg_context.eh_frame = cie.version != 4; - context.reg_context.is_macho = di.is_macho; - - const row = try context.vm.runToNative(context.allocator, context.pc, cie, fde); - context.cfa = switch (row.cfa.rule) { - .val_offset => |offset| blk: { - const register = row.cfa.register orelse return error.InvalidCFARule; - const value = mem.readInt(usize, (try abi.regBytes(context.thread_context, register, context.reg_context))[0..@sizeOf(usize)], native_endian); - break :blk try call_frame.applyOffset(value, offset); - }, - .expression => |expression| blk: { - context.stack_machine.reset(); - const value = try context.stack_machine.run( - expression, - context.allocator, - expression_context, - context.cfa, - ); - - if (value) |v| { - if (v != .generic) return error.InvalidExpressionValue; - break :blk v.generic; - } else return error.NoExpressionValue; - }, - else => return error.InvalidCFARule, - }; - - if (ma.load(usize, context.cfa.?) == null) return error.InvalidCFA; - expression_context.cfa = context.cfa; - - // Buffering the modifications is done because copying the thread context is not portable, - // some implementations (ie. darwin) use internal pointers to the mcontext. - var arena = std.heap.ArenaAllocator.init(context.allocator); - defer arena.deinit(); - const update_allocator = arena.allocator(); - - const RegisterUpdate = struct { - // Backed by thread_context - dest: []u8, - // Backed by arena - src: []const u8, - prev: ?*@This(), - }; - - var update_tail: ?*RegisterUpdate = null; - var has_return_address = true; - for (context.vm.rowColumns(row)) |column| { - if (column.register) |register| { - if (register == cie.return_address_register) { - has_return_address = column.rule != .undefined; - } - - const dest = try abi.regBytes(context.thread_context, register, context.reg_context); - const src = try update_allocator.alloc(u8, dest.len); - - const prev = update_tail; - update_tail = try update_allocator.create(RegisterUpdate); - update_tail.?.* = .{ - .dest = dest, - .src = src, - .prev = prev, - }; - - try column.resolveValue( - context, - expression_context, - ma, - src, - ); - } - } - - // On all implemented architectures, the CFA is defined as being the previous frame's SP - (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(context.reg_context), context.reg_context)).* = context.cfa.?; - - while (update_tail) |tail| { - @memcpy(tail.dest, tail.src); - update_tail = tail.prev; - } - - if (has_return_address) { - context.pc = abi.stripInstructionPtrAuthCode(mem.readInt(usize, (try abi.regBytes( - context.thread_context, - cie.return_address_register, - context.reg_context, - ))[0..@sizeOf(usize)], native_endian)); - } else { - context.pc = 0; - } - - (try abi.regValueNative(usize, context.thread_context, abi.ipRegNum(), context.reg_context)).* = context.pc; - - // The call instruction will have pushed the address of the instruction that follows the call as the return address. - // This next instruction may be past the end of the function if the caller was `noreturn` (ie. the last instruction in - // the function was the call). If we were to look up an FDE entry using the return address directly, it could end up - // either not finding an FDE at all, or using the next FDE in the program, producing incorrect results. To prevent this, - // we subtract one so that the next lookup is guaranteed to land inside the - // - // The exception to this rule is signal frames, where we return execution would be returned to the instruction - // that triggered the handler. - const return_address = context.pc; - if (context.pc > 0 and !cie.isSignalFrame()) context.pc -= 1; - - return return_address; - } -}; - -/// Returns the DWARF register number for an x86_64 register number found in compact unwind info -fn compactUnwindToDwarfRegNumber(unwind_reg_number: u3) !u8 { - return switch (unwind_reg_number) { - 1 => 3, // RBX - 2 => 12, // R12 - 3 => 13, // R13 - 4 => 14, // R14 - 5 => 15, // R15 - 6 => 6, // RBP - else => error.InvalidUnwindRegisterNumber, - }; -} - -const macho = std.macho; - -/// Unwind a frame using MachO compact unwind info (from __unwind_info). -/// If the compact encoding can't encode a way to unwind a frame, it will -/// defer unwinding to DWARF, in which case `.eh_frame` will be used if available. -pub fn unwindFrameMachO( - context: *UnwindContext, - ma: *debug.StackIterator.MemoryAccessor, - unwind_info: []const u8, - eh_frame: ?[]const u8, - module_base_address: usize, -) !usize { - const header = mem.bytesAsValue( - macho.unwind_info_section_header, - unwind_info[0..@sizeOf(macho.unwind_info_section_header)], - ); - const indices = mem.bytesAsSlice( - macho.unwind_info_section_header_index_entry, - unwind_info[header.indexSectionOffset..][0 .. header.indexCount * @sizeOf(macho.unwind_info_section_header_index_entry)], - ); - if (indices.len == 0) return error.MissingUnwindInfo; - - const mapped_pc = context.pc - module_base_address; - const second_level_index = blk: { - var left: usize = 0; - var len: usize = indices.len; - - while (len > 1) { - const mid = left + len / 2; - const offset = indices[mid].functionOffset; - if (mapped_pc < offset) { - len /= 2; - } else { - left = mid; - if (mapped_pc == offset) break; - len -= len / 2; - } - } - - // Last index is a sentinel containing the highest address as its functionOffset - if (indices[left].secondLevelPagesSectionOffset == 0) return error.MissingUnwindInfo; - break :blk &indices[left]; - }; - - const common_encodings = mem.bytesAsSlice( - macho.compact_unwind_encoding_t, - unwind_info[header.commonEncodingsArraySectionOffset..][0 .. header.commonEncodingsArrayCount * @sizeOf(macho.compact_unwind_encoding_t)], - ); - - const start_offset = second_level_index.secondLevelPagesSectionOffset; - const kind = mem.bytesAsValue( - macho.UNWIND_SECOND_LEVEL, - unwind_info[start_offset..][0..@sizeOf(macho.UNWIND_SECOND_LEVEL)], - ); - - const entry: struct { - function_offset: usize, - raw_encoding: u32, - } = switch (kind.*) { - .REGULAR => blk: { - const page_header = mem.bytesAsValue( - macho.unwind_info_regular_second_level_page_header, - unwind_info[start_offset..][0..@sizeOf(macho.unwind_info_regular_second_level_page_header)], - ); - - const entries = mem.bytesAsSlice( - macho.unwind_info_regular_second_level_entry, - unwind_info[start_offset + page_header.entryPageOffset ..][0 .. page_header.entryCount * @sizeOf(macho.unwind_info_regular_second_level_entry)], - ); - if (entries.len == 0) return error.InvalidUnwindInfo; - - var left: usize = 0; - var len: usize = entries.len; - while (len > 1) { - const mid = left + len / 2; - const offset = entries[mid].functionOffset; - if (mapped_pc < offset) { - len /= 2; - } else { - left = mid; - if (mapped_pc == offset) break; - len -= len / 2; - } - } - - break :blk .{ - .function_offset = entries[left].functionOffset, - .raw_encoding = entries[left].encoding, - }; - }, - .COMPRESSED => blk: { - const page_header = mem.bytesAsValue( - macho.unwind_info_compressed_second_level_page_header, - unwind_info[start_offset..][0..@sizeOf(macho.unwind_info_compressed_second_level_page_header)], - ); - - const entries = mem.bytesAsSlice( - macho.UnwindInfoCompressedEntry, - unwind_info[start_offset + page_header.entryPageOffset ..][0 .. page_header.entryCount * @sizeOf(macho.UnwindInfoCompressedEntry)], - ); - if (entries.len == 0) return error.InvalidUnwindInfo; - - var left: usize = 0; - var len: usize = entries.len; - while (len > 1) { - const mid = left + len / 2; - const offset = second_level_index.functionOffset + entries[mid].funcOffset; - if (mapped_pc < offset) { - len /= 2; - } else { - left = mid; - if (mapped_pc == offset) break; - len -= len / 2; - } - } - - const entry = entries[left]; - const function_offset = second_level_index.functionOffset + entry.funcOffset; - if (entry.encodingIndex < header.commonEncodingsArrayCount) { - if (entry.encodingIndex >= common_encodings.len) return error.InvalidUnwindInfo; - break :blk .{ - .function_offset = function_offset, - .raw_encoding = common_encodings[entry.encodingIndex], - }; - } else { - const local_index = try math.sub( - u8, - entry.encodingIndex, - math.cast(u8, header.commonEncodingsArrayCount) orelse return error.InvalidUnwindInfo, - ); - const local_encodings = mem.bytesAsSlice( - macho.compact_unwind_encoding_t, - unwind_info[start_offset + page_header.encodingsPageOffset ..][0 .. page_header.encodingsCount * @sizeOf(macho.compact_unwind_encoding_t)], - ); - if (local_index >= local_encodings.len) return error.InvalidUnwindInfo; - break :blk .{ - .function_offset = function_offset, - .raw_encoding = local_encodings[local_index], - }; - } - }, - else => return error.InvalidUnwindInfo, - }; - - if (entry.raw_encoding == 0) return error.NoUnwindInfo; - const reg_context = abi.RegisterContext{ - .eh_frame = false, - .is_macho = true, - }; - - const encoding: macho.CompactUnwindEncoding = @bitCast(entry.raw_encoding); - const new_ip = switch (builtin.cpu.arch) { - .x86_64 => switch (encoding.mode.x86_64) { - .OLD => return error.UnimplementedUnwindEncoding, - .RBP_FRAME => blk: { - const regs: [5]u3 = .{ - encoding.value.x86_64.frame.reg0, - encoding.value.x86_64.frame.reg1, - encoding.value.x86_64.frame.reg2, - encoding.value.x86_64.frame.reg3, - encoding.value.x86_64.frame.reg4, - }; - - const frame_offset = encoding.value.x86_64.frame.frame_offset * @sizeOf(usize); - var max_reg: usize = 0; - inline for (regs, 0..) |reg, i| { - if (reg > 0) max_reg = i; - } - - const fp = (try abi.regValueNative(usize, context.thread_context, abi.fpRegNum(reg_context), reg_context)).*; - const new_sp = fp + 2 * @sizeOf(usize); - - // Verify the stack range we're about to read register values from - if (ma.load(usize, new_sp) == null or ma.load(usize, fp - frame_offset + max_reg * @sizeOf(usize)) == null) return error.InvalidUnwindInfo; - - const ip_ptr = fp + @sizeOf(usize); - const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*; - const new_fp = @as(*const usize, @ptrFromInt(fp)).*; - - (try abi.regValueNative(usize, context.thread_context, abi.fpRegNum(reg_context), reg_context)).* = new_fp; - (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).* = new_sp; - (try abi.regValueNative(usize, context.thread_context, abi.ipRegNum(), reg_context)).* = new_ip; - - for (regs, 0..) |reg, i| { - if (reg == 0) continue; - const addr = fp - frame_offset + i * @sizeOf(usize); - const reg_number = try compactUnwindToDwarfRegNumber(reg); - (try abi.regValueNative(usize, context.thread_context, reg_number, reg_context)).* = @as(*const usize, @ptrFromInt(addr)).*; - } - - break :blk new_ip; - }, - .STACK_IMMD, - .STACK_IND, - => blk: { - const sp = (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).*; - const stack_size = if (encoding.mode.x86_64 == .STACK_IMMD) - @as(usize, encoding.value.x86_64.frameless.stack.direct.stack_size) * @sizeOf(usize) - else stack_size: { - // In .STACK_IND, the stack size is inferred from the subq instruction at the beginning of the function. - const sub_offset_addr = - module_base_address + - entry.function_offset + - encoding.value.x86_64.frameless.stack.indirect.sub_offset; - if (ma.load(usize, sub_offset_addr) == null) return error.InvalidUnwindInfo; - - // `sub_offset_addr` points to the offset of the literal within the instruction - const sub_operand = @as(*align(1) const u32, @ptrFromInt(sub_offset_addr)).*; - break :stack_size sub_operand + @sizeOf(usize) * @as(usize, encoding.value.x86_64.frameless.stack.indirect.stack_adjust); - }; - - // Decode the Lehmer-coded sequence of registers. - // For a description of the encoding see lib/libc/include/any-macos.13-any/mach-o/compact_unwind_encoding.h - - // Decode the variable-based permutation number into its digits. Each digit represents - // an index into the list of register numbers that weren't yet used in the sequence at - // the time the digit was added. - const reg_count = encoding.value.x86_64.frameless.stack_reg_count; - const ip_ptr = if (reg_count > 0) reg_blk: { - var digits: [6]u3 = undefined; - var accumulator: usize = encoding.value.x86_64.frameless.stack_reg_permutation; - var base: usize = 2; - for (0..reg_count) |i| { - const div = accumulator / base; - digits[digits.len - 1 - i] = @intCast(accumulator - base * div); - accumulator = div; - base += 1; - } - - const reg_numbers = [_]u3{ 1, 2, 3, 4, 5, 6 }; - var registers: [reg_numbers.len]u3 = undefined; - var used_indices = [_]bool{false} ** reg_numbers.len; - for (digits[digits.len - reg_count ..], 0..) |target_unused_index, i| { - var unused_count: u8 = 0; - const unused_index = for (used_indices, 0..) |used, index| { - if (!used) { - if (target_unused_index == unused_count) break index; - unused_count += 1; - } - } else unreachable; - - registers[i] = reg_numbers[unused_index]; - used_indices[unused_index] = true; - } - - var reg_addr = sp + stack_size - @sizeOf(usize) * @as(usize, reg_count + 1); - if (ma.load(usize, reg_addr) == null) return error.InvalidUnwindInfo; - for (0..reg_count) |i| { - const reg_number = try compactUnwindToDwarfRegNumber(registers[i]); - (try abi.regValueNative(usize, context.thread_context, reg_number, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*; - reg_addr += @sizeOf(usize); - } - - break :reg_blk reg_addr; - } else sp + stack_size - @sizeOf(usize); - - const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*; - const new_sp = ip_ptr + @sizeOf(usize); - if (ma.load(usize, new_sp) == null) return error.InvalidUnwindInfo; - - (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).* = new_sp; - (try abi.regValueNative(usize, context.thread_context, abi.ipRegNum(), reg_context)).* = new_ip; - - break :blk new_ip; - }, - .DWARF => { - return unwindFrameMachODwarf(context, ma, eh_frame orelse return error.MissingEhFrame, @intCast(encoding.value.x86_64.dwarf)); - }, - }, - .aarch64 => switch (encoding.mode.arm64) { - .OLD => return error.UnimplementedUnwindEncoding, - .FRAMELESS => blk: { - const sp = (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).*; - const new_sp = sp + encoding.value.arm64.frameless.stack_size * 16; - const new_ip = (try abi.regValueNative(usize, context.thread_context, 30, reg_context)).*; - if (ma.load(usize, new_sp) == null) return error.InvalidUnwindInfo; - (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).* = new_sp; - break :blk new_ip; - }, - .DWARF => { - return unwindFrameMachODwarf(context, ma, eh_frame orelse return error.MissingEhFrame, @intCast(encoding.value.arm64.dwarf)); - }, - .FRAME => blk: { - const fp = (try abi.regValueNative(usize, context.thread_context, abi.fpRegNum(reg_context), reg_context)).*; - const new_sp = fp + 16; - const ip_ptr = fp + @sizeOf(usize); - - const num_restored_pairs: usize = - @popCount(@as(u5, @bitCast(encoding.value.arm64.frame.x_reg_pairs))) + - @popCount(@as(u4, @bitCast(encoding.value.arm64.frame.d_reg_pairs))); - const min_reg_addr = fp - num_restored_pairs * 2 * @sizeOf(usize); - - if (ma.load(usize, new_sp) == null or ma.load(usize, min_reg_addr) == null) return error.InvalidUnwindInfo; - - var reg_addr = fp - @sizeOf(usize); - inline for (@typeInfo(@TypeOf(encoding.value.arm64.frame.x_reg_pairs)).Struct.fields, 0..) |field, i| { - if (@field(encoding.value.arm64.frame.x_reg_pairs, field.name) != 0) { - (try abi.regValueNative(usize, context.thread_context, 19 + i, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*; - reg_addr += @sizeOf(usize); - (try abi.regValueNative(usize, context.thread_context, 20 + i, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*; - reg_addr += @sizeOf(usize); - } - } - - inline for (@typeInfo(@TypeOf(encoding.value.arm64.frame.d_reg_pairs)).Struct.fields, 0..) |field, i| { - if (@field(encoding.value.arm64.frame.d_reg_pairs, field.name) != 0) { - // Only the lower half of the 128-bit V registers are restored during unwinding - @memcpy( - try abi.regBytes(context.thread_context, 64 + 8 + i, context.reg_context), - mem.asBytes(@as(*const usize, @ptrFromInt(reg_addr))), - ); - reg_addr += @sizeOf(usize); - @memcpy( - try abi.regBytes(context.thread_context, 64 + 9 + i, context.reg_context), - mem.asBytes(@as(*const usize, @ptrFromInt(reg_addr))), - ); - reg_addr += @sizeOf(usize); - } - } - - const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*; - const new_fp = @as(*const usize, @ptrFromInt(fp)).*; - - (try abi.regValueNative(usize, context.thread_context, abi.fpRegNum(reg_context), reg_context)).* = new_fp; - (try abi.regValueNative(usize, context.thread_context, abi.ipRegNum(), reg_context)).* = new_ip; - - break :blk new_ip; - }, - }, - else => return error.UnimplementedArch, - }; - - context.pc = abi.stripInstructionPtrAuthCode(new_ip); - if (context.pc > 0) context.pc -= 1; - return new_ip; -} - -fn unwindFrameMachODwarf(context: *UnwindContext, ma: *debug.StackIterator.MemoryAccessor, eh_frame: []const u8, fde_offset: usize) !usize { - var di = DwarfInfo{ - .endian = native_endian, - .is_macho = true, - }; - defer di.deinit(context.allocator); - - di.sections[@intFromEnum(DwarfSection.eh_frame)] = .{ - .data = eh_frame, - .owned = false, - }; - - return di.unwindFrame(context, ma, fde_offset); -} - -pub const UnwindContext = struct { - allocator: mem.Allocator, - cfa: ?usize, - pc: usize, - thread_context: *debug.ThreadContext, - reg_context: abi.RegisterContext, - vm: call_frame.VirtualMachine, - stack_machine: expressions.StackMachine(.{ .call_frame_context = true }), - - pub fn init( - allocator: mem.Allocator, - thread_context: *const debug.ThreadContext, - ) !UnwindContext { - const pc = abi.stripInstructionPtrAuthCode( - (try abi.regValueNative( - usize, - thread_context, - abi.ipRegNum(), - null, - )).*, - ); - - const context_copy = try allocator.create(debug.ThreadContext); - debug.copyContext(thread_context, context_copy); - - return .{ - .allocator = allocator, - .cfa = null, - .pc = pc, - .thread_context = context_copy, - .reg_context = undefined, - .vm = .{}, - .stack_machine = .{}, - }; - } - - pub fn deinit(self: *UnwindContext) void { - self.vm.deinit(self.allocator); - self.stack_machine.deinit(self.allocator); - self.allocator.destroy(self.thread_context); - self.* = undefined; - } - - pub fn getFp(self: *const UnwindContext) !usize { - return (try abi.regValueNative(usize, self.thread_context, abi.fpRegNum(self.reg_context), self.reg_context)).*; - } -}; - -/// Initialize DWARF info. The caller has the responsibility to initialize most -/// the DwarfInfo fields before calling. `binary_mem` is the raw bytes of the -/// main binary file (not the secondary debug info file). -pub fn openDwarfDebugInfo(di: *DwarfInfo, allocator: mem.Allocator) !void { - try di.scanAllFunctions(allocator); - try di.scanAllCompileUnits(allocator); -} - -/// This function is to make it handy to comment out the return and make it -/// into a crash when working on this file. -fn badDwarf() error{InvalidDebugInfo} { - //if (true) @panic("badDwarf"); // can be handy to uncomment when working on this file - return error.InvalidDebugInfo; -} - -fn missingDwarf() error{MissingDebugInfo} { - //if (true) @panic("missingDwarf"); // can be handy to uncomment when working on this file - return error.MissingDebugInfo; -} - -fn getStringGeneric(opt_str: ?[]const u8, offset: u64) ![:0]const u8 { - const str = opt_str orelse return badDwarf(); - if (offset > str.len) return badDwarf(); - const casted_offset = math.cast(usize, offset) orelse return badDwarf(); - // Valid strings always have a terminating zero byte - const last = mem.indexOfScalarPos(u8, str, casted_offset, 0) orelse return badDwarf(); - return str[casted_offset..last :0]; -} - -const EhPointerContext = struct { - // The address of the pointer field itself - pc_rel_base: u64, - - // Whether or not to follow indirect pointers. This should only be - // used when decoding pointers at runtime using the current process's - // debug info - follow_indirect: bool, - - // These relative addressing modes are only used in specific cases, and - // might not be available / required in all parsing contexts - data_rel_base: ?u64 = null, - text_rel_base: ?u64 = null, - function_rel_base: ?u64 = null, -}; -fn readEhPointer(fbr: *FixedBufferReader, enc: u8, addr_size_bytes: u8, ctx: EhPointerContext) !?u64 { - if (enc == EH.PE.omit) return null; - - const value: union(enum) { - signed: i64, - unsigned: u64, - } = switch (enc & EH.PE.type_mask) { - EH.PE.absptr => .{ - .unsigned = switch (addr_size_bytes) { - 2 => try fbr.readInt(u16), - 4 => try fbr.readInt(u32), - 8 => try fbr.readInt(u64), - else => return error.InvalidAddrSize, - }, - }, - EH.PE.uleb128 => .{ .unsigned = try fbr.readUleb128(u64) }, - EH.PE.udata2 => .{ .unsigned = try fbr.readInt(u16) }, - EH.PE.udata4 => .{ .unsigned = try fbr.readInt(u32) }, - EH.PE.udata8 => .{ .unsigned = try fbr.readInt(u64) }, - EH.PE.sleb128 => .{ .signed = try fbr.readIleb128(i64) }, - EH.PE.sdata2 => .{ .signed = try fbr.readInt(i16) }, - EH.PE.sdata4 => .{ .signed = try fbr.readInt(i32) }, - EH.PE.sdata8 => .{ .signed = try fbr.readInt(i64) }, - else => return badDwarf(), - }; - - const base = switch (enc & EH.PE.rel_mask) { - EH.PE.pcrel => ctx.pc_rel_base, - EH.PE.textrel => ctx.text_rel_base orelse return error.PointerBaseNotSpecified, - EH.PE.datarel => ctx.data_rel_base orelse return error.PointerBaseNotSpecified, - EH.PE.funcrel => ctx.function_rel_base orelse return error.PointerBaseNotSpecified, - else => null, - }; - - const ptr: u64 = if (base) |b| switch (value) { - .signed => |s| @intCast(try math.add(i64, s, @as(i64, @intCast(b)))), - // absptr can actually contain signed values in some cases (aarch64 MachO) - .unsigned => |u| u +% b, - } else switch (value) { - .signed => |s| @as(u64, @intCast(s)), - .unsigned => |u| u, - }; - - if ((enc & EH.PE.indirect) > 0 and ctx.follow_indirect) { - if (@sizeOf(usize) != addr_size_bytes) { - // See the documentation for `follow_indirect` - return error.NonNativeIndirection; - } - - const native_ptr = math.cast(usize, ptr) orelse return error.PointerOverflow; - return switch (addr_size_bytes) { - 2, 4, 8 => return @as(*const usize, @ptrFromInt(native_ptr)).*, - else => return error.UnsupportedAddrSize, - }; - } else { - return ptr; - } -} - -/// This represents the decoded .eh_frame_hdr header -pub const ExceptionFrameHeader = struct { - eh_frame_ptr: usize, - table_enc: u8, - fde_count: usize, - entries: []const u8, - - pub fn entrySize(table_enc: u8) !u8 { - return switch (table_enc & EH.PE.type_mask) { - EH.PE.udata2, - EH.PE.sdata2, - => 4, - EH.PE.udata4, - EH.PE.sdata4, - => 8, - EH.PE.udata8, - EH.PE.sdata8, - => 16, - // This is a binary search table, so all entries must be the same length - else => return badDwarf(), - }; - } - - fn isValidPtr( - self: ExceptionFrameHeader, - comptime T: type, - ptr: usize, - ma: *debug.StackIterator.MemoryAccessor, - eh_frame_len: ?usize, - ) bool { - if (eh_frame_len) |len| { - return ptr >= self.eh_frame_ptr and ptr <= self.eh_frame_ptr + len - @sizeOf(T); - } else { - return ma.load(T, ptr) != null; - } - } - - /// Find an entry by binary searching the eh_frame_hdr section. - /// - /// Since the length of the eh_frame section (`eh_frame_len`) may not be known by the caller, - /// MemoryAccessor will be used to verify readability of the header entries. - /// If `eh_frame_len` is provided, then these checks can be skipped. - pub fn findEntry( - self: ExceptionFrameHeader, - ma: *debug.StackIterator.MemoryAccessor, - eh_frame_len: ?usize, - eh_frame_hdr_ptr: usize, - pc: usize, - cie: *CommonInformationEntry, - fde: *FrameDescriptionEntry, - ) !void { - const entry_size = try entrySize(self.table_enc); - - var left: usize = 0; - var len: usize = self.fde_count; - - var fbr: FixedBufferReader = .{ .buf = self.entries, .endian = native_endian }; - - while (len > 1) { - const mid = left + len / 2; - - fbr.pos = mid * entry_size; - const pc_begin = try readEhPointer(&fbr, self.table_enc, @sizeOf(usize), .{ - .pc_rel_base = @intFromPtr(&self.entries[fbr.pos]), - .follow_indirect = true, - .data_rel_base = eh_frame_hdr_ptr, - }) orelse return badDwarf(); - - if (pc < pc_begin) { - len /= 2; - } else { - left = mid; - if (pc == pc_begin) break; - len -= len / 2; - } - } - - if (len == 0) return badDwarf(); - fbr.pos = left * entry_size; - - // Read past the pc_begin field of the entry - _ = try readEhPointer(&fbr, self.table_enc, @sizeOf(usize), .{ - .pc_rel_base = @intFromPtr(&self.entries[fbr.pos]), - .follow_indirect = true, - .data_rel_base = eh_frame_hdr_ptr, - }) orelse return badDwarf(); - - const fde_ptr = math.cast(usize, try readEhPointer(&fbr, self.table_enc, @sizeOf(usize), .{ - .pc_rel_base = @intFromPtr(&self.entries[fbr.pos]), - .follow_indirect = true, - .data_rel_base = eh_frame_hdr_ptr, - }) orelse return badDwarf()) orelse return badDwarf(); - - if (fde_ptr < self.eh_frame_ptr) return badDwarf(); - - // Even if eh_frame_len is not specified, all ranges accssed are checked via MemoryAccessor - const eh_frame = @as([*]const u8, @ptrFromInt(self.eh_frame_ptr))[0 .. eh_frame_len orelse math.maxInt(u32)]; - - const fde_offset = fde_ptr - self.eh_frame_ptr; - var eh_frame_fbr: FixedBufferReader = .{ - .buf = eh_frame, - .pos = fde_offset, - .endian = native_endian, - }; - - const fde_entry_header = try EntryHeader.read(&eh_frame_fbr, if (eh_frame_len == null) ma else null, .eh_frame); - if (!self.isValidPtr(u8, @intFromPtr(&fde_entry_header.entry_bytes[fde_entry_header.entry_bytes.len - 1]), ma, eh_frame_len)) return badDwarf(); - if (fde_entry_header.type != .fde) return badDwarf(); - - // CIEs always come before FDEs (the offset is a subtraction), so we can assume this memory is readable - const cie_offset = fde_entry_header.type.fde; - try eh_frame_fbr.seekTo(cie_offset); - const cie_entry_header = try EntryHeader.read(&eh_frame_fbr, if (eh_frame_len == null) ma else null, .eh_frame); - if (!self.isValidPtr(u8, @intFromPtr(&cie_entry_header.entry_bytes[cie_entry_header.entry_bytes.len - 1]), ma, eh_frame_len)) return badDwarf(); - if (cie_entry_header.type != .cie) return badDwarf(); - - cie.* = try CommonInformationEntry.parse( - cie_entry_header.entry_bytes, - 0, - true, - cie_entry_header.format, - .eh_frame, - cie_entry_header.length_offset, - @sizeOf(usize), - native_endian, - ); - - fde.* = try FrameDescriptionEntry.parse( - fde_entry_header.entry_bytes, - 0, - true, - cie.*, - @sizeOf(usize), - native_endian, - ); - } -}; - -pub const EntryHeader = struct { - /// Offset of the length field in the backing buffer - length_offset: usize, - format: Format, - type: union(enum) { - cie, - /// Value is the offset of the corresponding CIE - fde: u64, - terminator, - }, - /// The entry's contents, not including the ID field - entry_bytes: []const u8, - - /// The length of the entry including the ID field, but not the length field itself - pub fn entryLength(self: EntryHeader) usize { - return self.entry_bytes.len + @as(u8, if (self.format == .@"64") 8 else 4); - } - - /// Reads a header for either an FDE or a CIE, then advances the fbr to the position after the trailing structure. - /// `fbr` must be a FixedBufferReader backed by either the .eh_frame or .debug_frame sections. - pub fn read( - fbr: *FixedBufferReader, - opt_ma: ?*debug.StackIterator.MemoryAccessor, - dwarf_section: DwarfSection, - ) !EntryHeader { - assert(dwarf_section == .eh_frame or dwarf_section == .debug_frame); - - const length_offset = fbr.pos; - const unit_header = try readUnitHeader(fbr, opt_ma); - const unit_length = math.cast(usize, unit_header.unit_length) orelse return badDwarf(); - if (unit_length == 0) return .{ - .length_offset = length_offset, - .format = unit_header.format, - .type = .terminator, - .entry_bytes = &.{}, - }; - const start_offset = fbr.pos; - const end_offset = start_offset + unit_length; - defer fbr.pos = end_offset; - - const id = try if (opt_ma) |ma| - fbr.readAddressChecked(unit_header.format, ma) - else - fbr.readAddress(unit_header.format); - const entry_bytes = fbr.buf[fbr.pos..end_offset]; - const cie_id: u64 = switch (dwarf_section) { - .eh_frame => CommonInformationEntry.eh_id, - .debug_frame => switch (unit_header.format) { - .@"32" => CommonInformationEntry.dwarf32_id, - .@"64" => CommonInformationEntry.dwarf64_id, - }, - else => unreachable, - }; - - return .{ - .length_offset = length_offset, - .format = unit_header.format, - .type = if (id == cie_id) .cie else .{ .fde = switch (dwarf_section) { - .eh_frame => try math.sub(u64, start_offset, id), - .debug_frame => id, - else => unreachable, - } }, - .entry_bytes = entry_bytes, - }; - } -}; - -pub const CommonInformationEntry = struct { - // Used in .eh_frame - pub const eh_id = 0; - - // Used in .debug_frame (DWARF32) - pub const dwarf32_id = math.maxInt(u32); - - // Used in .debug_frame (DWARF64) - pub const dwarf64_id = math.maxInt(u64); - - // Offset of the length field of this entry in the eh_frame section. - // This is the key that FDEs use to reference CIEs. - length_offset: u64, - version: u8, - address_size: u8, - format: Format, - - // Only present in version 4 - segment_selector_size: ?u8, - - code_alignment_factor: u32, - data_alignment_factor: i32, - return_address_register: u8, - - aug_str: []const u8, - aug_data: []const u8, - lsda_pointer_enc: u8, - personality_enc: ?u8, - personality_routine_pointer: ?u64, - fde_pointer_enc: u8, - initial_instructions: []const u8, - - pub fn isSignalFrame(self: CommonInformationEntry) bool { - for (self.aug_str) |c| if (c == 'S') return true; - return false; - } - - pub fn addressesSignedWithBKey(self: CommonInformationEntry) bool { - for (self.aug_str) |c| if (c == 'B') return true; - return false; - } - - pub fn mteTaggedFrame(self: CommonInformationEntry) bool { - for (self.aug_str) |c| if (c == 'G') return true; - return false; - } - - /// This function expects to read the CIE starting with the version field. - /// The returned struct references memory backed by cie_bytes. - /// - /// See the FrameDescriptionEntry.parse documentation for the description - /// of `pc_rel_offset` and `is_runtime`. - /// - /// `length_offset` specifies the offset of this CIE's length field in the - /// .eh_frame / .debug_frame section. - pub fn parse( - cie_bytes: []const u8, - pc_rel_offset: i64, - is_runtime: bool, - format: Format, - dwarf_section: DwarfSection, - length_offset: u64, - addr_size_bytes: u8, - endian: std.builtin.Endian, - ) !CommonInformationEntry { - if (addr_size_bytes > 8) return error.UnsupportedAddrSize; - - var fbr: FixedBufferReader = .{ .buf = cie_bytes, .endian = endian }; - - const version = try fbr.readByte(); - switch (dwarf_section) { - .eh_frame => if (version != 1 and version != 3) return error.UnsupportedDwarfVersion, - .debug_frame => if (version != 4) return error.UnsupportedDwarfVersion, - else => return error.UnsupportedDwarfSection, - } - - var has_eh_data = false; - var has_aug_data = false; - - var aug_str_len: usize = 0; - const aug_str_start = fbr.pos; - var aug_byte = try fbr.readByte(); - while (aug_byte != 0) : (aug_byte = try fbr.readByte()) { - switch (aug_byte) { - 'z' => { - if (aug_str_len != 0) return badDwarf(); - has_aug_data = true; - }, - 'e' => { - if (has_aug_data or aug_str_len != 0) return badDwarf(); - if (try fbr.readByte() != 'h') return badDwarf(); - has_eh_data = true; - }, - else => if (has_eh_data) return badDwarf(), - } - - aug_str_len += 1; - } - - if (has_eh_data) { - // legacy data created by older versions of gcc - unsupported here - for (0..addr_size_bytes) |_| _ = try fbr.readByte(); - } - - const address_size = if (version == 4) try fbr.readByte() else addr_size_bytes; - const segment_selector_size = if (version == 4) try fbr.readByte() else null; - - const code_alignment_factor = try fbr.readUleb128(u32); - const data_alignment_factor = try fbr.readIleb128(i32); - const return_address_register = if (version == 1) try fbr.readByte() else try fbr.readUleb128(u8); - - var lsda_pointer_enc: u8 = EH.PE.omit; - var personality_enc: ?u8 = null; - var personality_routine_pointer: ?u64 = null; - var fde_pointer_enc: u8 = EH.PE.absptr; - - var aug_data: []const u8 = &[_]u8{}; - const aug_str = if (has_aug_data) blk: { - const aug_data_len = try fbr.readUleb128(usize); - const aug_data_start = fbr.pos; - aug_data = cie_bytes[aug_data_start..][0..aug_data_len]; - - const aug_str = cie_bytes[aug_str_start..][0..aug_str_len]; - for (aug_str[1..]) |byte| { - switch (byte) { - 'L' => { - lsda_pointer_enc = try fbr.readByte(); - }, - 'P' => { - personality_enc = try fbr.readByte(); - personality_routine_pointer = try readEhPointer(&fbr, personality_enc.?, addr_size_bytes, .{ - .pc_rel_base = try pcRelBase(@intFromPtr(&cie_bytes[fbr.pos]), pc_rel_offset), - .follow_indirect = is_runtime, - }); - }, - 'R' => { - fde_pointer_enc = try fbr.readByte(); - }, - 'S', 'B', 'G' => {}, - else => return badDwarf(), - } - } - - // aug_data_len can include padding so the CIE ends on an address boundary - fbr.pos = aug_data_start + aug_data_len; - break :blk aug_str; - } else &[_]u8{}; - - const initial_instructions = cie_bytes[fbr.pos..]; - return .{ - .length_offset = length_offset, - .version = version, - .address_size = address_size, - .format = format, - .segment_selector_size = segment_selector_size, - .code_alignment_factor = code_alignment_factor, - .data_alignment_factor = data_alignment_factor, - .return_address_register = return_address_register, - .aug_str = aug_str, - .aug_data = aug_data, - .lsda_pointer_enc = lsda_pointer_enc, - .personality_enc = personality_enc, - .personality_routine_pointer = personality_routine_pointer, - .fde_pointer_enc = fde_pointer_enc, - .initial_instructions = initial_instructions, - }; - } -}; - -pub const FrameDescriptionEntry = struct { - // Offset into eh_frame where the CIE for this FDE is stored - cie_length_offset: u64, - - pc_begin: u64, - pc_range: u64, - lsda_pointer: ?u64, - aug_data: []const u8, - instructions: []const u8, - - /// This function expects to read the FDE starting at the PC Begin field. - /// The returned struct references memory backed by `fde_bytes`. - /// - /// `pc_rel_offset` specifies an offset to be applied to pc_rel_base values - /// used when decoding pointers. This should be set to zero if fde_bytes is - /// backed by the memory of a .eh_frame / .debug_frame section in the running executable. - /// Otherwise, it should be the relative offset to translate addresses from - /// where the section is currently stored in memory, to where it *would* be - /// stored at runtime: section base addr - backing data base ptr. - /// - /// Similarly, `is_runtime` specifies this function is being called on a runtime - /// section, and so indirect pointers can be followed. - pub fn parse( - fde_bytes: []const u8, - pc_rel_offset: i64, - is_runtime: bool, - cie: CommonInformationEntry, - addr_size_bytes: u8, - endian: std.builtin.Endian, - ) !FrameDescriptionEntry { - if (addr_size_bytes > 8) return error.InvalidAddrSize; - - var fbr: FixedBufferReader = .{ .buf = fde_bytes, .endian = endian }; - - const pc_begin = try readEhPointer(&fbr, cie.fde_pointer_enc, addr_size_bytes, .{ - .pc_rel_base = try pcRelBase(@intFromPtr(&fde_bytes[fbr.pos]), pc_rel_offset), - .follow_indirect = is_runtime, - }) orelse return badDwarf(); - - const pc_range = try readEhPointer(&fbr, cie.fde_pointer_enc, addr_size_bytes, .{ - .pc_rel_base = 0, - .follow_indirect = false, - }) orelse return badDwarf(); - - var aug_data: []const u8 = &[_]u8{}; - const lsda_pointer = if (cie.aug_str.len > 0) blk: { - const aug_data_len = try fbr.readUleb128(usize); - const aug_data_start = fbr.pos; - aug_data = fde_bytes[aug_data_start..][0..aug_data_len]; - - const lsda_pointer = if (cie.lsda_pointer_enc != EH.PE.omit) - try readEhPointer(&fbr, cie.lsda_pointer_enc, addr_size_bytes, .{ - .pc_rel_base = try pcRelBase(@intFromPtr(&fde_bytes[fbr.pos]), pc_rel_offset), - .follow_indirect = is_runtime, - }) - else - null; - - fbr.pos = aug_data_start + aug_data_len; - break :blk lsda_pointer; - } else null; - - const instructions = fde_bytes[fbr.pos..]; - return .{ - .cie_length_offset = cie.length_offset, - .pc_begin = pc_begin, - .pc_range = pc_range, - .lsda_pointer = lsda_pointer, - .aug_data = aug_data, - .instructions = instructions, - }; - } -}; - -fn pcRelBase(field_ptr: usize, pc_rel_offset: i64) !usize { - if (pc_rel_offset < 0) { - return math.sub(usize, field_ptr, @as(usize, @intCast(-pc_rel_offset))); - } else { - return math.add(usize, field_ptr, @as(usize, @intCast(pc_rel_offset))); - } -} - -// Reading debug info needs to be fast, even when compiled in debug mode, -// so avoid using a `std.io.FixedBufferStream` which is too slow. -pub const FixedBufferReader = struct { - buf: []const u8, - pos: usize = 0, - endian: std.builtin.Endian, - - pub const Error = error{ EndOfBuffer, Overflow, InvalidBuffer }; - - fn seekTo(fbr: *FixedBufferReader, pos: u64) Error!void { - if (pos > fbr.buf.len) return error.EndOfBuffer; - fbr.pos = @intCast(pos); - } - - fn seekForward(fbr: *FixedBufferReader, amount: u64) Error!void { - if (fbr.buf.len - fbr.pos < amount) return error.EndOfBuffer; - fbr.pos += @intCast(amount); - } - - pub inline fn readByte(fbr: *FixedBufferReader) Error!u8 { - if (fbr.pos >= fbr.buf.len) return error.EndOfBuffer; - defer fbr.pos += 1; - return fbr.buf[fbr.pos]; - } - - fn readByteSigned(fbr: *FixedBufferReader) Error!i8 { - return @bitCast(try fbr.readByte()); - } - - fn readInt(fbr: *FixedBufferReader, comptime T: type) Error!T { - const size = @divExact(@typeInfo(T).Int.bits, 8); - if (fbr.buf.len - fbr.pos < size) return error.EndOfBuffer; - defer fbr.pos += size; - return mem.readInt(T, fbr.buf[fbr.pos..][0..size], fbr.endian); - } - - fn readIntChecked( - fbr: *FixedBufferReader, - comptime T: type, - ma: *debug.StackIterator.MemoryAccessor, - ) Error!T { - if (ma.load(T, @intFromPtr(fbr.buf[fbr.pos..].ptr)) == null) - return error.InvalidBuffer; - - return readInt(fbr, T); - } - - fn readUleb128(fbr: *FixedBufferReader, comptime T: type) Error!T { - return std.leb.readUleb128(T, fbr); - } - - fn readIleb128(fbr: *FixedBufferReader, comptime T: type) Error!T { - return std.leb.readIleb128(T, fbr); - } - - fn readAddress(fbr: *FixedBufferReader, format: Format) Error!u64 { - return switch (format) { - .@"32" => try fbr.readInt(u32), - .@"64" => try fbr.readInt(u64), - }; - } - - fn readAddressChecked( - fbr: *FixedBufferReader, - format: Format, - ma: *debug.StackIterator.MemoryAccessor, - ) Error!u64 { - return switch (format) { - .@"32" => try fbr.readIntChecked(u32, ma), - .@"64" => try fbr.readIntChecked(u64, ma), - }; - } - - fn readBytes(fbr: *FixedBufferReader, len: usize) Error![]const u8 { - if (fbr.buf.len - fbr.pos < len) return error.EndOfBuffer; - defer fbr.pos += len; - return fbr.buf[fbr.pos..][0..len]; - } - - fn readBytesTo(fbr: *FixedBufferReader, comptime sentinel: u8) Error![:sentinel]const u8 { - const end = @call(.always_inline, mem.indexOfScalarPos, .{ - u8, - fbr.buf, - fbr.pos, - sentinel, - }) orelse return error.EndOfBuffer; - defer fbr.pos = end + 1; - return fbr.buf[fbr.pos..end :sentinel]; - } -}; - -test { - std.testing.refAllDecls(@This()); -} diff --git a/src/target.zig b/src/target.zig index 62f3e52cb8..768e4d957e 100644 --- a/src/target.zig +++ b/src/target.zig @@ -327,7 +327,7 @@ pub fn clangAssemblerSupportsMcpuArg(target: std.Target) bool { } pub fn needUnwindTables(target: std.Target) bool { - return target.os.tag == .windows or target.isDarwin() or std.dwarf.abi.supportsUnwinding(target); + return target.os.tag == .windows or target.isDarwin() or std.debug.Dwarf.abi.supportsUnwinding(target); } pub fn defaultAddressSpace( From 1ba6b56c817777c5f504a5975591da6de68dd361 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 1 Aug 2024 14:18:16 -0700 Subject: [PATCH 111/266] std.debug.Info: extract to separate file --- lib/std/debug.zig | 1377 +-------------------------------------- lib/std/debug/Dwarf.zig | 6 +- lib/std/debug/Info.zig | 1377 +++++++++++++++++++++++++++++++++++++++ lib/std/pdb.zig | 6 +- 4 files changed, 1390 insertions(+), 1376 deletions(-) create mode 100644 lib/std/debug/Info.zig diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 22a7e551ec..6dac92188e 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -1,5 +1,5 @@ -const std = @import("std.zig"); const builtin = @import("builtin"); +const std = @import("std.zig"); const math = std.math; const mem = std.mem; const io = std.io; @@ -19,6 +19,7 @@ const native_os = builtin.os.tag; const native_endian = native_arch.endian(); pub const Dwarf = @import("debug/Dwarf.zig"); +pub const Info = @import("debug/Info.zig"); pub const runtime_safety = switch (builtin.mode) { .Debug, .ReleaseSafe => true, @@ -46,39 +47,6 @@ pub const sys_can_stack_trace = switch (builtin.cpu.arch) { else => true, }; -pub const LineInfo = struct { - line: u64, - column: u64, - file_name: []const u8, - - pub fn deinit(self: LineInfo, allocator: mem.Allocator) void { - allocator.free(self.file_name); - } -}; - -pub const SymbolInfo = struct { - symbol_name: []const u8 = "???", - compile_unit_name: []const u8 = "???", - line_info: ?LineInfo = null, - - pub fn deinit(self: SymbolInfo, allocator: mem.Allocator) void { - if (self.line_info) |li| { - li.deinit(allocator); - } - } -}; -const PdbOrDwarf = union(enum) { - pdb: pdb.Pdb, - dwarf: Dwarf, - - fn deinit(self: *PdbOrDwarf, allocator: mem.Allocator) void { - switch (self.*) { - .pdb => |*inner| inner.deinit(), - .dwarf => |*inner| inner.deinit(allocator), - } - } -}; - /// Allows the caller to freely write to stderr until `unlockStdErr` is called. /// /// During the lock, any `std.Progress` information is cleared from the terminal. @@ -110,7 +78,7 @@ pub fn getSelfDebugInfo() !*Info { if (self_debug_info) |*info| { return info; } else { - self_debug_info = try openSelfDebugInfo(getDebugInfoAllocator()); + self_debug_info = try Info.openSelf(getDebugInfoAllocator()); return &self_debug_info.?; } } @@ -957,51 +925,6 @@ pub fn writeStackTraceWindows( } } -fn machoSearchSymbols(symbols: []const MachoSymbol, address: usize) ?*const MachoSymbol { - var min: usize = 0; - var max: usize = symbols.len - 1; - while (min < max) { - const mid = min + (max - min) / 2; - const curr = &symbols[mid]; - const next = &symbols[mid + 1]; - if (address >= next.address()) { - min = mid + 1; - } else if (address < curr.address()) { - max = mid; - } else { - return curr; - } - } - - const max_sym = &symbols[symbols.len - 1]; - if (address >= max_sym.address()) - return max_sym; - - return null; -} - -test machoSearchSymbols { - const symbols = [_]MachoSymbol{ - .{ .addr = 100, .strx = undefined, .size = undefined, .ofile = undefined }, - .{ .addr = 200, .strx = undefined, .size = undefined, .ofile = undefined }, - .{ .addr = 300, .strx = undefined, .size = undefined, .ofile = undefined }, - }; - - try testing.expectEqual(null, machoSearchSymbols(&symbols, 0)); - try testing.expectEqual(null, machoSearchSymbols(&symbols, 99)); - try testing.expectEqual(&symbols[0], machoSearchSymbols(&symbols, 100).?); - try testing.expectEqual(&symbols[0], machoSearchSymbols(&symbols, 150).?); - try testing.expectEqual(&symbols[0], machoSearchSymbols(&symbols, 199).?); - - try testing.expectEqual(&symbols[1], machoSearchSymbols(&symbols, 200).?); - try testing.expectEqual(&symbols[1], machoSearchSymbols(&symbols, 250).?); - try testing.expectEqual(&symbols[1], machoSearchSymbols(&symbols, 299).?); - - try testing.expectEqual(&symbols[2], machoSearchSymbols(&symbols, 300).?); - try testing.expectEqual(&symbols[2], machoSearchSymbols(&symbols, 301).?); - try testing.expectEqual(&symbols[2], machoSearchSymbols(&symbols, 5000).?); -} - fn printUnknownSource(debug_info: *Info, out_stream: anytype, address: usize, tty_config: io.tty.Config) !void { const module_name = debug_info.getModuleNameForAddress(address); return printLineInfo( @@ -1058,7 +981,7 @@ pub fn printSourceAtAddress(debug_info: *Info, out_stream: anytype, address: usi fn printLineInfo( out_stream: anytype, - line_info: ?LineInfo, + line_info: ?Info.SourceLocation, address: usize, symbol_name: []const u8, compile_unit_name: []const u8, @@ -1104,428 +1027,7 @@ fn printLineInfo( } } -pub const OpenSelfDebugInfoError = error{ - MissingDebugInfo, - UnsupportedOperatingSystem, -} || @typeInfo(@typeInfo(@TypeOf(Info.init)).Fn.return_type.?).ErrorUnion.error_set; - -pub fn openSelfDebugInfo(allocator: mem.Allocator) OpenSelfDebugInfoError!Info { - nosuspend { - if (builtin.strip_debug_info) - return error.MissingDebugInfo; - if (@hasDecl(root, "os") and @hasDecl(root.os, "debug") and @hasDecl(root.os.debug, "openSelfDebugInfo")) { - return root.os.debug.openSelfDebugInfo(allocator); - } - switch (native_os) { - .linux, - .freebsd, - .netbsd, - .dragonfly, - .openbsd, - .macos, - .solaris, - .illumos, - .windows, - => return try Info.init(allocator), - else => return error.UnsupportedOperatingSystem, - } - } -} - -fn readCoffDebugInfo(allocator: mem.Allocator, coff_obj: *coff.Coff) !ModuleDebugInfo { - nosuspend { - var di = ModuleDebugInfo{ - .base_address = undefined, - .coff_image_base = coff_obj.getImageBase(), - .coff_section_headers = undefined, - }; - - if (coff_obj.getSectionByName(".debug_info")) |_| { - // This coff file has embedded DWARF debug info - var sections: Dwarf.SectionArray = Dwarf.null_section_array; - errdefer for (sections) |section| if (section) |s| if (s.owned) allocator.free(s.data); - - inline for (@typeInfo(Dwarf.Section.Id).Enum.fields, 0..) |section, i| { - sections[i] = if (coff_obj.getSectionByName("." ++ section.name)) |section_header| blk: { - break :blk .{ - .data = try coff_obj.getSectionDataAlloc(section_header, allocator), - .virtual_address = section_header.virtual_address, - .owned = true, - }; - } else null; - } - - var dwarf = Dwarf{ - .endian = native_endian, - .sections = sections, - .is_macho = false, - }; - - try Dwarf.open(&dwarf, allocator); - di.dwarf = dwarf; - } - - const raw_path = try coff_obj.getPdbPath() orelse return di; - const path = blk: { - if (fs.path.isAbsolute(raw_path)) { - break :blk raw_path; - } else { - const self_dir = try fs.selfExeDirPathAlloc(allocator); - defer allocator.free(self_dir); - break :blk try fs.path.join(allocator, &.{ self_dir, raw_path }); - } - }; - defer if (path.ptr != raw_path.ptr) allocator.free(path); - - di.pdb = pdb.Pdb.init(allocator, path) catch |err| switch (err) { - error.FileNotFound, error.IsDir => { - if (di.dwarf == null) return error.MissingDebugInfo; - return di; - }, - else => return err, - }; - try di.pdb.?.parseInfoStream(); - try di.pdb.?.parseDbiStream(); - - if (!mem.eql(u8, &coff_obj.guid, &di.pdb.?.guid) or coff_obj.age != di.pdb.?.age) - return error.InvalidDebugInfo; - - // Only used by the pdb path - di.coff_section_headers = try coff_obj.getSectionHeadersAlloc(allocator); - errdefer allocator.free(di.coff_section_headers); - - return di; - } -} - -fn chopSlice(ptr: []const u8, offset: u64, size: u64) error{Overflow}![]const u8 { - const start = math.cast(usize, offset) orelse return error.Overflow; - const end = start + (math.cast(usize, size) orelse return error.Overflow); - return ptr[start..end]; -} - -/// Reads debug info from an ELF file, or the current binary if none in specified. -/// If the required sections aren't present but a reference to external debug info is, -/// then this this function will recurse to attempt to load the debug sections from -/// an external file. -pub fn readElfDebugInfo( - allocator: mem.Allocator, - elf_filename: ?[]const u8, - build_id: ?[]const u8, - expected_crc: ?u32, - parent_sections: *Dwarf.SectionArray, - parent_mapped_mem: ?[]align(mem.page_size) const u8, -) !ModuleDebugInfo { - nosuspend { - const elf_file = (if (elf_filename) |filename| blk: { - break :blk fs.cwd().openFile(filename, .{}); - } else fs.openSelfExe(.{})) catch |err| switch (err) { - error.FileNotFound => return error.MissingDebugInfo, - else => return err, - }; - - const mapped_mem = try mapWholeFile(elf_file); - if (expected_crc) |crc| if (crc != std.hash.crc.Crc32.hash(mapped_mem)) return error.InvalidDebugInfo; - - const hdr: *const elf.Ehdr = @ptrCast(&mapped_mem[0]); - if (!mem.eql(u8, hdr.e_ident[0..4], elf.MAGIC)) return error.InvalidElfMagic; - if (hdr.e_ident[elf.EI_VERSION] != 1) return error.InvalidElfVersion; - - const endian: std.builtin.Endian = switch (hdr.e_ident[elf.EI_DATA]) { - elf.ELFDATA2LSB => .little, - elf.ELFDATA2MSB => .big, - else => return error.InvalidElfEndian, - }; - assert(endian == native_endian); // this is our own debug info - - const shoff = hdr.e_shoff; - const str_section_off = shoff + @as(u64, hdr.e_shentsize) * @as(u64, hdr.e_shstrndx); - const str_shdr: *const elf.Shdr = @ptrCast(@alignCast(&mapped_mem[math.cast(usize, str_section_off) orelse return error.Overflow])); - const header_strings = mapped_mem[str_shdr.sh_offset..][0..str_shdr.sh_size]; - const shdrs = @as( - [*]const elf.Shdr, - @ptrCast(@alignCast(&mapped_mem[shoff])), - )[0..hdr.e_shnum]; - - var sections: Dwarf.SectionArray = Dwarf.null_section_array; - - // Combine section list. This takes ownership over any owned sections from the parent scope. - for (parent_sections, §ions) |*parent, *section| { - if (parent.*) |*p| { - section.* = p.*; - p.owned = false; - } - } - errdefer for (sections) |section| if (section) |s| if (s.owned) allocator.free(s.data); - - var separate_debug_filename: ?[]const u8 = null; - var separate_debug_crc: ?u32 = null; - - for (shdrs) |*shdr| { - if (shdr.sh_type == elf.SHT_NULL or shdr.sh_type == elf.SHT_NOBITS) continue; - const name = mem.sliceTo(header_strings[shdr.sh_name..], 0); - - if (mem.eql(u8, name, ".gnu_debuglink")) { - const gnu_debuglink = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size); - const debug_filename = mem.sliceTo(@as([*:0]const u8, @ptrCast(gnu_debuglink.ptr)), 0); - const crc_offset = mem.alignForward(usize, @intFromPtr(&debug_filename[debug_filename.len]) + 1, 4) - @intFromPtr(gnu_debuglink.ptr); - const crc_bytes = gnu_debuglink[crc_offset..][0..4]; - separate_debug_crc = mem.readInt(u32, crc_bytes, native_endian); - separate_debug_filename = debug_filename; - continue; - } - - var section_index: ?usize = null; - inline for (@typeInfo(Dwarf.Section.Id).Enum.fields, 0..) |section, i| { - if (mem.eql(u8, "." ++ section.name, name)) section_index = i; - } - if (section_index == null) continue; - if (sections[section_index.?] != null) continue; - - const section_bytes = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size); - sections[section_index.?] = if ((shdr.sh_flags & elf.SHF_COMPRESSED) > 0) blk: { - var section_stream = io.fixedBufferStream(section_bytes); - var section_reader = section_stream.reader(); - const chdr = section_reader.readStruct(elf.Chdr) catch continue; - if (chdr.ch_type != .ZLIB) continue; - - var zlib_stream = std.compress.zlib.decompressor(section_stream.reader()); - - const decompressed_section = try allocator.alloc(u8, chdr.ch_size); - errdefer allocator.free(decompressed_section); - - const read = zlib_stream.reader().readAll(decompressed_section) catch continue; - assert(read == decompressed_section.len); - - break :blk .{ - .data = decompressed_section, - .virtual_address = shdr.sh_addr, - .owned = true, - }; - } else .{ - .data = section_bytes, - .virtual_address = shdr.sh_addr, - .owned = false, - }; - } - - const missing_debug_info = - sections[@intFromEnum(Dwarf.Section.Id.debug_info)] == null or - sections[@intFromEnum(Dwarf.Section.Id.debug_abbrev)] == null or - sections[@intFromEnum(Dwarf.Section.Id.debug_str)] == null or - sections[@intFromEnum(Dwarf.Section.Id.debug_line)] == null; - - // Attempt to load debug info from an external file - // See: https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html - if (missing_debug_info) { - - // Only allow one level of debug info nesting - if (parent_mapped_mem) |_| { - return error.MissingDebugInfo; - } - - const global_debug_directories = [_][]const u8{ - "/usr/lib/debug", - }; - - // /.build-id/<2-character id prefix>/.debug - if (build_id) |id| blk: { - if (id.len < 3) break :blk; - - // Either md5 (16 bytes) or sha1 (20 bytes) are used here in practice - const extension = ".debug"; - var id_prefix_buf: [2]u8 = undefined; - var filename_buf: [38 + extension.len]u8 = undefined; - - _ = std.fmt.bufPrint(&id_prefix_buf, "{s}", .{std.fmt.fmtSliceHexLower(id[0..1])}) catch unreachable; - const filename = std.fmt.bufPrint( - &filename_buf, - "{s}" ++ extension, - .{std.fmt.fmtSliceHexLower(id[1..])}, - ) catch break :blk; - - for (global_debug_directories) |global_directory| { - const path = try fs.path.join(allocator, &.{ global_directory, ".build-id", &id_prefix_buf, filename }); - defer allocator.free(path); - - return readElfDebugInfo(allocator, path, null, separate_debug_crc, §ions, mapped_mem) catch continue; - } - } - - // use the path from .gnu_debuglink, in the same search order as gdb - if (separate_debug_filename) |separate_filename| blk: { - if (elf_filename != null and mem.eql(u8, elf_filename.?, separate_filename)) return error.MissingDebugInfo; - - // / - if (readElfDebugInfo(allocator, separate_filename, null, separate_debug_crc, §ions, mapped_mem)) |debug_info| return debug_info else |_| {} - - // /.debug/ - { - const path = try fs.path.join(allocator, &.{ ".debug", separate_filename }); - defer allocator.free(path); - - if (readElfDebugInfo(allocator, path, null, separate_debug_crc, §ions, mapped_mem)) |debug_info| return debug_info else |_| {} - } - - var cwd_buf: [fs.max_path_bytes]u8 = undefined; - const cwd_path = posix.realpath(".", &cwd_buf) catch break :blk; - - // // - for (global_debug_directories) |global_directory| { - const path = try fs.path.join(allocator, &.{ global_directory, cwd_path, separate_filename }); - defer allocator.free(path); - if (readElfDebugInfo(allocator, path, null, separate_debug_crc, §ions, mapped_mem)) |debug_info| return debug_info else |_| {} - } - } - - return error.MissingDebugInfo; - } - - var di = Dwarf{ - .endian = endian, - .sections = sections, - .is_macho = false, - }; - - try Dwarf.open(&di, allocator); - - return ModuleDebugInfo{ - .base_address = undefined, - .dwarf = di, - .mapped_memory = parent_mapped_mem orelse mapped_mem, - .external_mapped_memory = if (parent_mapped_mem != null) mapped_mem else null, - }; - } -} - -/// This takes ownership of macho_file: users of this function should not close -/// it themselves, even on error. -/// TODO it's weird to take ownership even on error, rework this code. -fn readMachODebugInfo(allocator: mem.Allocator, macho_file: File) !ModuleDebugInfo { - const mapped_mem = try mapWholeFile(macho_file); - - const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_mem.ptr)); - if (hdr.magic != macho.MH_MAGIC_64) - return error.InvalidDebugInfo; - - var it = macho.LoadCommandIterator{ - .ncmds = hdr.ncmds, - .buffer = mapped_mem[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds], - }; - const symtab = while (it.next()) |cmd| switch (cmd.cmd()) { - .SYMTAB => break cmd.cast(macho.symtab_command).?, - else => {}, - } else return error.MissingDebugInfo; - - const syms = @as( - [*]const macho.nlist_64, - @ptrCast(@alignCast(&mapped_mem[symtab.symoff])), - )[0..symtab.nsyms]; - const strings = mapped_mem[symtab.stroff..][0 .. symtab.strsize - 1 :0]; - - const symbols_buf = try allocator.alloc(MachoSymbol, syms.len); - - var ofile: u32 = undefined; - var last_sym: MachoSymbol = undefined; - var symbol_index: usize = 0; - var state: enum { - init, - oso_open, - oso_close, - bnsym, - fun_strx, - fun_size, - ensym, - } = .init; - - for (syms) |*sym| { - if (!sym.stab()) continue; - - // TODO handle globals N_GSYM, and statics N_STSYM - switch (sym.n_type) { - macho.N_OSO => { - switch (state) { - .init, .oso_close => { - state = .oso_open; - ofile = sym.n_strx; - }, - else => return error.InvalidDebugInfo, - } - }, - macho.N_BNSYM => { - switch (state) { - .oso_open, .ensym => { - state = .bnsym; - last_sym = .{ - .strx = 0, - .addr = sym.n_value, - .size = 0, - .ofile = ofile, - }; - }, - else => return error.InvalidDebugInfo, - } - }, - macho.N_FUN => { - switch (state) { - .bnsym => { - state = .fun_strx; - last_sym.strx = sym.n_strx; - }, - .fun_strx => { - state = .fun_size; - last_sym.size = @as(u32, @intCast(sym.n_value)); - }, - else => return error.InvalidDebugInfo, - } - }, - macho.N_ENSYM => { - switch (state) { - .fun_size => { - state = .ensym; - symbols_buf[symbol_index] = last_sym; - symbol_index += 1; - }, - else => return error.InvalidDebugInfo, - } - }, - macho.N_SO => { - switch (state) { - .init, .oso_close => {}, - .oso_open, .ensym => { - state = .oso_close; - }, - else => return error.InvalidDebugInfo, - } - }, - else => {}, - } - } - - switch (state) { - .init => return error.MissingDebugInfo, - .oso_close => {}, - else => return error.InvalidDebugInfo, - } - - const symbols = try allocator.realloc(symbols_buf, symbol_index); - - // Even though lld emits symbols in ascending order, this debug code - // should work for programs linked in any valid way. - // This sort is so that we can binary search later. - mem.sort(MachoSymbol, symbols, {}, MachoSymbol.addressLessThan); - - return ModuleDebugInfo{ - .base_address = undefined, - .vmaddr_slide = undefined, - .mapped_memory = mapped_mem, - .ofiles = ModuleDebugInfo.OFileTable.init(allocator), - .symbols = symbols, - .strings = strings, - }; -} - -fn printLineFromFileAnyOs(out_stream: anytype, line_info: LineInfo) !void { +fn printLineFromFileAnyOs(out_stream: anytype, line_info: Info.SourceLocation) !void { // Need this to always block even in async I/O mode, because this could potentially // be called from e.g. the event loop code crashing. var f = try fs.cwd().openFile(line_info.file_name, .{}); @@ -1591,7 +1093,7 @@ test printLineFromFileAnyOs { var test_dir = std.testing.tmpDir(.{}); defer test_dir.cleanup(); - // Relies on testing.tmpDir internals which is not ideal, but LineInfo requires paths. + // Relies on testing.tmpDir internals which is not ideal, but Info.SourceLocation requires paths. const test_dir_path = try join(allocator, &.{ ".zig-cache", "tmp", test_dir.sub_path[0..] }); defer allocator.free(test_dir_path); @@ -1702,871 +1204,6 @@ test printLineFromFileAnyOs { } } -const MachoSymbol = struct { - strx: u32, - addr: u64, - size: u32, - ofile: u32, - - /// Returns the address from the macho file - fn address(self: MachoSymbol) u64 { - return self.addr; - } - - fn addressLessThan(context: void, lhs: MachoSymbol, rhs: MachoSymbol) bool { - _ = context; - return lhs.addr < rhs.addr; - } -}; - -/// Takes ownership of file, even on error. -/// TODO it's weird to take ownership even on error, rework this code. -fn mapWholeFile(file: File) ![]align(mem.page_size) const u8 { - nosuspend { - defer file.close(); - - const file_len = math.cast(usize, try file.getEndPos()) orelse math.maxInt(usize); - const mapped_mem = try posix.mmap( - null, - file_len, - posix.PROT.READ, - .{ .TYPE = .SHARED }, - file.handle, - 0, - ); - errdefer posix.munmap(mapped_mem); - - return mapped_mem; - } -} - -pub const WindowsModuleInfo = struct { - base_address: usize, - size: u32, - name: []const u8, - handle: windows.HMODULE, - - // Set when the image file needed to be mapped from disk - mapped_file: ?struct { - file: File, - section_handle: windows.HANDLE, - section_view: []const u8, - - pub fn deinit(self: @This()) void { - const process_handle = windows.GetCurrentProcess(); - assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @constCast(@ptrCast(self.section_view.ptr))) == .SUCCESS); - windows.CloseHandle(self.section_handle); - self.file.close(); - } - } = null, -}; - -pub const Info = struct { - allocator: mem.Allocator, - address_map: std.AutoHashMap(usize, *ModuleDebugInfo), - modules: if (native_os == .windows) std.ArrayListUnmanaged(WindowsModuleInfo) else void, - - pub fn init(allocator: mem.Allocator) !Info { - var debug_info = Info{ - .allocator = allocator, - .address_map = std.AutoHashMap(usize, *ModuleDebugInfo).init(allocator), - .modules = if (native_os == .windows) .{} else {}, - }; - - if (native_os == .windows) { - errdefer debug_info.modules.deinit(allocator); - - const handle = windows.kernel32.CreateToolhelp32Snapshot(windows.TH32CS_SNAPMODULE | windows.TH32CS_SNAPMODULE32, 0); - if (handle == windows.INVALID_HANDLE_VALUE) { - switch (windows.GetLastError()) { - else => |err| return windows.unexpectedError(err), - } - } - defer windows.CloseHandle(handle); - - var module_entry: windows.MODULEENTRY32 = undefined; - module_entry.dwSize = @sizeOf(windows.MODULEENTRY32); - if (windows.kernel32.Module32First(handle, &module_entry) == 0) { - return error.MissingDebugInfo; - } - - var module_valid = true; - while (module_valid) { - const module_info = try debug_info.modules.addOne(allocator); - const name = allocator.dupe(u8, mem.sliceTo(&module_entry.szModule, 0)) catch &.{}; - errdefer allocator.free(name); - - module_info.* = .{ - .base_address = @intFromPtr(module_entry.modBaseAddr), - .size = module_entry.modBaseSize, - .name = name, - .handle = module_entry.hModule, - }; - - module_valid = windows.kernel32.Module32Next(handle, &module_entry) == 1; - } - } - - return debug_info; - } - - pub fn deinit(self: *Info) void { - var it = self.address_map.iterator(); - while (it.next()) |entry| { - const mdi = entry.value_ptr.*; - mdi.deinit(self.allocator); - self.allocator.destroy(mdi); - } - self.address_map.deinit(); - if (native_os == .windows) { - for (self.modules.items) |module| { - self.allocator.free(module.name); - if (module.mapped_file) |mapped_file| mapped_file.deinit(); - } - self.modules.deinit(self.allocator); - } - } - - pub fn getModuleForAddress(self: *Info, address: usize) !*ModuleDebugInfo { - if (comptime builtin.target.isDarwin()) { - return self.lookupModuleDyld(address); - } else if (native_os == .windows) { - return self.lookupModuleWin32(address); - } else if (native_os == .haiku) { - return self.lookupModuleHaiku(address); - } else if (comptime builtin.target.isWasm()) { - return self.lookupModuleWasm(address); - } else { - return self.lookupModuleDl(address); - } - } - - // Returns the module name for a given address. - // This can be called when getModuleForAddress fails, so implementations should provide - // a path that doesn't rely on any side-effects of a prior successful module lookup. - pub fn getModuleNameForAddress(self: *Info, address: usize) ?[]const u8 { - if (comptime builtin.target.isDarwin()) { - return self.lookupModuleNameDyld(address); - } else if (native_os == .windows) { - return self.lookupModuleNameWin32(address); - } else if (native_os == .haiku) { - return null; - } else if (comptime builtin.target.isWasm()) { - return null; - } else { - return self.lookupModuleNameDl(address); - } - } - - fn lookupModuleDyld(self: *Info, address: usize) !*ModuleDebugInfo { - const image_count = std.c._dyld_image_count(); - - var i: u32 = 0; - while (i < image_count) : (i += 1) { - const header = std.c._dyld_get_image_header(i) orelse continue; - const base_address = @intFromPtr(header); - if (address < base_address) continue; - const vmaddr_slide = std.c._dyld_get_image_vmaddr_slide(i); - - var it = macho.LoadCommandIterator{ - .ncmds = header.ncmds, - .buffer = @alignCast(@as( - [*]u8, - @ptrFromInt(@intFromPtr(header) + @sizeOf(macho.mach_header_64)), - )[0..header.sizeofcmds]), - }; - - var unwind_info: ?[]const u8 = null; - var eh_frame: ?[]const u8 = null; - while (it.next()) |cmd| switch (cmd.cmd()) { - .SEGMENT_64 => { - const segment_cmd = cmd.cast(macho.segment_command_64).?; - if (!mem.eql(u8, "__TEXT", segment_cmd.segName())) continue; - - const seg_start = segment_cmd.vmaddr + vmaddr_slide; - const seg_end = seg_start + segment_cmd.vmsize; - if (address >= seg_start and address < seg_end) { - if (self.address_map.get(base_address)) |obj_di| { - return obj_di; - } - - for (cmd.getSections()) |sect| { - if (mem.eql(u8, "__unwind_info", sect.sectName())) { - unwind_info = @as([*]const u8, @ptrFromInt(sect.addr + vmaddr_slide))[0..sect.size]; - } else if (mem.eql(u8, "__eh_frame", sect.sectName())) { - eh_frame = @as([*]const u8, @ptrFromInt(sect.addr + vmaddr_slide))[0..sect.size]; - } - } - - const obj_di = try self.allocator.create(ModuleDebugInfo); - errdefer self.allocator.destroy(obj_di); - - const macho_path = mem.sliceTo(std.c._dyld_get_image_name(i), 0); - const macho_file = fs.cwd().openFile(macho_path, .{}) catch |err| switch (err) { - error.FileNotFound => return error.MissingDebugInfo, - else => return err, - }; - obj_di.* = try readMachODebugInfo(self.allocator, macho_file); - obj_di.base_address = base_address; - obj_di.vmaddr_slide = vmaddr_slide; - obj_di.unwind_info = unwind_info; - obj_di.eh_frame = eh_frame; - - try self.address_map.putNoClobber(base_address, obj_di); - - return obj_di; - } - }, - else => {}, - }; - } - - return error.MissingDebugInfo; - } - - fn lookupModuleNameDyld(self: *Info, address: usize) ?[]const u8 { - _ = self; - const image_count = std.c._dyld_image_count(); - - var i: u32 = 0; - while (i < image_count) : (i += 1) { - const header = std.c._dyld_get_image_header(i) orelse continue; - const base_address = @intFromPtr(header); - if (address < base_address) continue; - const vmaddr_slide = std.c._dyld_get_image_vmaddr_slide(i); - - var it = macho.LoadCommandIterator{ - .ncmds = header.ncmds, - .buffer = @alignCast(@as( - [*]u8, - @ptrFromInt(@intFromPtr(header) + @sizeOf(macho.mach_header_64)), - )[0..header.sizeofcmds]), - }; - - while (it.next()) |cmd| switch (cmd.cmd()) { - .SEGMENT_64 => { - const segment_cmd = cmd.cast(macho.segment_command_64).?; - if (!mem.eql(u8, "__TEXT", segment_cmd.segName())) continue; - - const original_address = address - vmaddr_slide; - const seg_start = segment_cmd.vmaddr; - const seg_end = seg_start + segment_cmd.vmsize; - if (original_address >= seg_start and original_address < seg_end) { - return fs.path.basename(mem.sliceTo(std.c._dyld_get_image_name(i), 0)); - } - }, - else => {}, - }; - } - - return null; - } - - fn lookupModuleWin32(self: *Info, address: usize) !*ModuleDebugInfo { - for (self.modules.items) |*module| { - if (address >= module.base_address and address < module.base_address + module.size) { - if (self.address_map.get(module.base_address)) |obj_di| { - return obj_di; - } - - const obj_di = try self.allocator.create(ModuleDebugInfo); - errdefer self.allocator.destroy(obj_di); - - const mapped_module = @as([*]const u8, @ptrFromInt(module.base_address))[0..module.size]; - var coff_obj = try coff.Coff.init(mapped_module, true); - - // The string table is not mapped into memory by the loader, so if a section name is in the - // string table then we have to map the full image file from disk. This can happen when - // a binary is produced with -gdwarf, since the section names are longer than 8 bytes. - if (coff_obj.strtabRequired()) { - var name_buffer: [windows.PATH_MAX_WIDE + 4:0]u16 = undefined; - // openFileAbsoluteW requires the prefix to be present - @memcpy(name_buffer[0..4], &[_]u16{ '\\', '?', '?', '\\' }); - - const process_handle = windows.GetCurrentProcess(); - const len = windows.kernel32.GetModuleFileNameExW( - process_handle, - module.handle, - @ptrCast(&name_buffer[4]), - windows.PATH_MAX_WIDE, - ); - - if (len == 0) return error.MissingDebugInfo; - const coff_file = fs.openFileAbsoluteW(name_buffer[0 .. len + 4 :0], .{}) catch |err| switch (err) { - error.FileNotFound => return error.MissingDebugInfo, - else => return err, - }; - errdefer coff_file.close(); - - var section_handle: windows.HANDLE = undefined; - const create_section_rc = windows.ntdll.NtCreateSection( - §ion_handle, - windows.STANDARD_RIGHTS_REQUIRED | windows.SECTION_QUERY | windows.SECTION_MAP_READ, - null, - null, - windows.PAGE_READONLY, - // The documentation states that if no AllocationAttribute is specified, then SEC_COMMIT is the default. - // In practice, this isn't the case and specifying 0 will result in INVALID_PARAMETER_6. - windows.SEC_COMMIT, - coff_file.handle, - ); - if (create_section_rc != .SUCCESS) return error.MissingDebugInfo; - errdefer windows.CloseHandle(section_handle); - - var coff_len: usize = 0; - var base_ptr: usize = 0; - const map_section_rc = windows.ntdll.NtMapViewOfSection( - section_handle, - process_handle, - @ptrCast(&base_ptr), - null, - 0, - null, - &coff_len, - .ViewUnmap, - 0, - windows.PAGE_READONLY, - ); - if (map_section_rc != .SUCCESS) return error.MissingDebugInfo; - errdefer assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @ptrFromInt(base_ptr)) == .SUCCESS); - - const section_view = @as([*]const u8, @ptrFromInt(base_ptr))[0..coff_len]; - coff_obj = try coff.Coff.init(section_view, false); - - module.mapped_file = .{ - .file = coff_file, - .section_handle = section_handle, - .section_view = section_view, - }; - } - errdefer if (module.mapped_file) |mapped_file| mapped_file.deinit(); - - obj_di.* = try readCoffDebugInfo(self.allocator, &coff_obj); - obj_di.base_address = module.base_address; - - try self.address_map.putNoClobber(module.base_address, obj_di); - return obj_di; - } - } - - return error.MissingDebugInfo; - } - - fn lookupModuleNameWin32(self: *Info, address: usize) ?[]const u8 { - for (self.modules.items) |module| { - if (address >= module.base_address and address < module.base_address + module.size) { - return module.name; - } - } - return null; - } - - fn lookupModuleNameDl(self: *Info, address: usize) ?[]const u8 { - _ = self; - - var ctx: struct { - // Input - address: usize, - // Output - name: []const u8 = "", - } = .{ .address = address }; - const CtxTy = @TypeOf(ctx); - - if (posix.dl_iterate_phdr(&ctx, error{Found}, struct { - fn callback(info: *posix.dl_phdr_info, size: usize, context: *CtxTy) !void { - _ = size; - if (context.address < info.addr) return; - const phdrs = info.phdr[0..info.phnum]; - for (phdrs) |*phdr| { - if (phdr.p_type != elf.PT_LOAD) continue; - - const seg_start = info.addr +% phdr.p_vaddr; - const seg_end = seg_start + phdr.p_memsz; - if (context.address >= seg_start and context.address < seg_end) { - context.name = mem.sliceTo(info.name, 0) orelse ""; - break; - } - } else return; - - return error.Found; - } - }.callback)) { - return null; - } else |err| switch (err) { - error.Found => return fs.path.basename(ctx.name), - } - - return null; - } - - fn lookupModuleDl(self: *Info, address: usize) !*ModuleDebugInfo { - var ctx: struct { - // Input - address: usize, - // Output - base_address: usize = undefined, - name: []const u8 = undefined, - build_id: ?[]const u8 = null, - gnu_eh_frame: ?[]const u8 = null, - } = .{ .address = address }; - const CtxTy = @TypeOf(ctx); - - if (posix.dl_iterate_phdr(&ctx, error{Found}, struct { - fn callback(info: *posix.dl_phdr_info, size: usize, context: *CtxTy) !void { - _ = size; - // The base address is too high - if (context.address < info.addr) - return; - - const phdrs = info.phdr[0..info.phnum]; - for (phdrs) |*phdr| { - if (phdr.p_type != elf.PT_LOAD) continue; - - // Overflowing addition is used to handle the case of VSDOs having a p_vaddr = 0xffffffffff700000 - const seg_start = info.addr +% phdr.p_vaddr; - const seg_end = seg_start + phdr.p_memsz; - if (context.address >= seg_start and context.address < seg_end) { - // Android libc uses NULL instead of an empty string to mark the - // main program - context.name = mem.sliceTo(info.name, 0) orelse ""; - context.base_address = info.addr; - break; - } - } else return; - - for (info.phdr[0..info.phnum]) |phdr| { - switch (phdr.p_type) { - elf.PT_NOTE => { - // Look for .note.gnu.build-id - const note_bytes = @as([*]const u8, @ptrFromInt(info.addr + phdr.p_vaddr))[0..phdr.p_memsz]; - const name_size = mem.readInt(u32, note_bytes[0..4], native_endian); - if (name_size != 4) continue; - const desc_size = mem.readInt(u32, note_bytes[4..8], native_endian); - const note_type = mem.readInt(u32, note_bytes[8..12], native_endian); - if (note_type != elf.NT_GNU_BUILD_ID) continue; - if (!mem.eql(u8, "GNU\x00", note_bytes[12..16])) continue; - context.build_id = note_bytes[16..][0..desc_size]; - }, - elf.PT_GNU_EH_FRAME => { - context.gnu_eh_frame = @as([*]const u8, @ptrFromInt(info.addr + phdr.p_vaddr))[0..phdr.p_memsz]; - }, - else => {}, - } - } - - // Stop the iteration - return error.Found; - } - }.callback)) { - return error.MissingDebugInfo; - } else |err| switch (err) { - error.Found => {}, - } - - if (self.address_map.get(ctx.base_address)) |obj_di| { - return obj_di; - } - - const obj_di = try self.allocator.create(ModuleDebugInfo); - errdefer self.allocator.destroy(obj_di); - - var sections: Dwarf.SectionArray = Dwarf.null_section_array; - if (ctx.gnu_eh_frame) |eh_frame_hdr| { - // This is a special case - pointer offsets inside .eh_frame_hdr - // are encoded relative to its base address, so we must use the - // version that is already memory mapped, and not the one that - // will be mapped separately from the ELF file. - sections[@intFromEnum(Dwarf.Section.Id.eh_frame_hdr)] = .{ - .data = eh_frame_hdr, - .owned = false, - }; - } - - obj_di.* = try readElfDebugInfo(self.allocator, if (ctx.name.len > 0) ctx.name else null, ctx.build_id, null, §ions, null); - obj_di.base_address = ctx.base_address; - - // Missing unwind info isn't treated as a failure, as the unwinder will fall back to FP-based unwinding - obj_di.dwarf.scanAllUnwindInfo(self.allocator, ctx.base_address) catch {}; - - try self.address_map.putNoClobber(ctx.base_address, obj_di); - - return obj_di; - } - - fn lookupModuleHaiku(self: *Info, address: usize) !*ModuleDebugInfo { - _ = self; - _ = address; - @panic("TODO implement lookup module for Haiku"); - } - - fn lookupModuleWasm(self: *Info, address: usize) !*ModuleDebugInfo { - _ = self; - _ = address; - @panic("TODO implement lookup module for Wasm"); - } -}; - -pub const ModuleDebugInfo = switch (native_os) { - .macos, .ios, .watchos, .tvos, .visionos => struct { - base_address: usize, - vmaddr_slide: usize, - mapped_memory: []align(mem.page_size) const u8, - symbols: []const MachoSymbol, - strings: [:0]const u8, - ofiles: OFileTable, - - // Backed by the in-memory sections mapped by the loader - unwind_info: ?[]const u8 = null, - eh_frame: ?[]const u8 = null, - - const OFileTable = std.StringHashMap(OFileInfo); - const OFileInfo = struct { - di: Dwarf, - addr_table: std.StringHashMap(u64), - }; - - pub fn deinit(self: *@This(), allocator: mem.Allocator) void { - var it = self.ofiles.iterator(); - while (it.next()) |entry| { - const ofile = entry.value_ptr; - ofile.di.deinit(allocator); - ofile.addr_table.deinit(); - } - self.ofiles.deinit(); - allocator.free(self.symbols); - posix.munmap(self.mapped_memory); - } - - fn loadOFile(self: *@This(), allocator: mem.Allocator, o_file_path: []const u8) !*OFileInfo { - const o_file = try fs.cwd().openFile(o_file_path, .{}); - const mapped_mem = try mapWholeFile(o_file); - - const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_mem.ptr)); - if (hdr.magic != std.macho.MH_MAGIC_64) - return error.InvalidDebugInfo; - - var segcmd: ?macho.LoadCommandIterator.LoadCommand = null; - var symtabcmd: ?macho.symtab_command = null; - var it = macho.LoadCommandIterator{ - .ncmds = hdr.ncmds, - .buffer = mapped_mem[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds], - }; - while (it.next()) |cmd| switch (cmd.cmd()) { - .SEGMENT_64 => segcmd = cmd, - .SYMTAB => symtabcmd = cmd.cast(macho.symtab_command).?, - else => {}, - }; - - if (segcmd == null or symtabcmd == null) return error.MissingDebugInfo; - - // Parse symbols - const strtab = @as( - [*]const u8, - @ptrCast(&mapped_mem[symtabcmd.?.stroff]), - )[0 .. symtabcmd.?.strsize - 1 :0]; - const symtab = @as( - [*]const macho.nlist_64, - @ptrCast(@alignCast(&mapped_mem[symtabcmd.?.symoff])), - )[0..symtabcmd.?.nsyms]; - - // TODO handle tentative (common) symbols - var addr_table = std.StringHashMap(u64).init(allocator); - try addr_table.ensureTotalCapacity(@as(u32, @intCast(symtab.len))); - for (symtab) |sym| { - if (sym.n_strx == 0) continue; - if (sym.undf() or sym.tentative() or sym.abs()) continue; - const sym_name = mem.sliceTo(strtab[sym.n_strx..], 0); - // TODO is it possible to have a symbol collision? - addr_table.putAssumeCapacityNoClobber(sym_name, sym.n_value); - } - - var sections: Dwarf.SectionArray = Dwarf.null_section_array; - if (self.eh_frame) |eh_frame| sections[@intFromEnum(Dwarf.Section.Id.eh_frame)] = .{ - .data = eh_frame, - .owned = false, - }; - - for (segcmd.?.getSections()) |sect| { - if (!std.mem.eql(u8, "__DWARF", sect.segName())) continue; - - var section_index: ?usize = null; - inline for (@typeInfo(Dwarf.Section.Id).Enum.fields, 0..) |section, i| { - if (mem.eql(u8, "__" ++ section.name, sect.sectName())) section_index = i; - } - if (section_index == null) continue; - - const section_bytes = try chopSlice(mapped_mem, sect.offset, sect.size); - sections[section_index.?] = .{ - .data = section_bytes, - .virtual_address = sect.addr, - .owned = false, - }; - } - - const missing_debug_info = - sections[@intFromEnum(Dwarf.Section.Id.debug_info)] == null or - sections[@intFromEnum(Dwarf.Section.Id.debug_abbrev)] == null or - sections[@intFromEnum(Dwarf.Section.Id.debug_str)] == null or - sections[@intFromEnum(Dwarf.Section.Id.debug_line)] == null; - if (missing_debug_info) return error.MissingDebugInfo; - - var di = Dwarf{ - .endian = .little, - .sections = sections, - .is_macho = true, - }; - - try Dwarf.open(&di, allocator); - const info = OFileInfo{ - .di = di, - .addr_table = addr_table, - }; - - // Add the debug info to the cache - const result = try self.ofiles.getOrPut(o_file_path); - assert(!result.found_existing); - result.value_ptr.* = info; - - return result.value_ptr; - } - - pub fn getSymbolAtAddress(self: *@This(), allocator: mem.Allocator, address: usize) !SymbolInfo { - nosuspend { - const result = try self.getOFileInfoForAddress(allocator, address); - if (result.symbol == null) return .{}; - - // Take the symbol name from the N_FUN STAB entry, we're going to - // use it if we fail to find the DWARF infos - const stab_symbol = mem.sliceTo(self.strings[result.symbol.?.strx..], 0); - if (result.o_file_info == null) return .{ .symbol_name = stab_symbol }; - - // Translate again the address, this time into an address inside the - // .o file - const relocated_address_o = result.o_file_info.?.addr_table.get(stab_symbol) orelse return .{ - .symbol_name = "???", - }; - - const addr_off = result.relocated_address - result.symbol.?.addr; - const o_file_di = &result.o_file_info.?.di; - if (o_file_di.findCompileUnit(relocated_address_o)) |compile_unit| { - return SymbolInfo{ - .symbol_name = o_file_di.getSymbolName(relocated_address_o) orelse "???", - .compile_unit_name = compile_unit.die.getAttrString( - o_file_di, - DW.AT.name, - o_file_di.section(.debug_str), - compile_unit.*, - ) catch |err| switch (err) { - error.MissingDebugInfo, error.InvalidDebugInfo => "???", - }, - .line_info = o_file_di.getLineNumberInfo( - allocator, - compile_unit.*, - relocated_address_o + addr_off, - ) catch |err| switch (err) { - error.MissingDebugInfo, error.InvalidDebugInfo => null, - else => return err, - }, - }; - } else |err| switch (err) { - error.MissingDebugInfo, error.InvalidDebugInfo => { - return SymbolInfo{ .symbol_name = stab_symbol }; - }, - else => return err, - } - } - } - - pub fn getOFileInfoForAddress(self: *@This(), allocator: mem.Allocator, address: usize) !struct { - relocated_address: usize, - symbol: ?*const MachoSymbol = null, - o_file_info: ?*OFileInfo = null, - } { - nosuspend { - // Translate the VA into an address into this object - const relocated_address = address - self.vmaddr_slide; - - // Find the .o file where this symbol is defined - const symbol = machoSearchSymbols(self.symbols, relocated_address) orelse return .{ - .relocated_address = relocated_address, - }; - - // Check if its debug infos are already in the cache - const o_file_path = mem.sliceTo(self.strings[symbol.ofile..], 0); - const o_file_info = self.ofiles.getPtr(o_file_path) orelse - (self.loadOFile(allocator, o_file_path) catch |err| switch (err) { - error.FileNotFound, - error.MissingDebugInfo, - error.InvalidDebugInfo, - => return .{ - .relocated_address = relocated_address, - .symbol = symbol, - }, - else => return err, - }); - - return .{ - .relocated_address = relocated_address, - .symbol = symbol, - .o_file_info = o_file_info, - }; - } - } - - pub fn getDwarfInfoForAddress(self: *@This(), allocator: mem.Allocator, address: usize) !?*const Dwarf { - return if ((try self.getOFileInfoForAddress(allocator, address)).o_file_info) |o_file_info| &o_file_info.di else null; - } - }, - .uefi, .windows => struct { - base_address: usize, - pdb: ?pdb.Pdb = null, - dwarf: ?Dwarf = null, - coff_image_base: u64, - - /// Only used if pdb is non-null - coff_section_headers: []coff.SectionHeader, - - pub fn deinit(self: *@This(), allocator: mem.Allocator) void { - if (self.dwarf) |*dwarf| { - dwarf.deinit(allocator); - } - - if (self.pdb) |*p| { - p.deinit(); - allocator.free(self.coff_section_headers); - } - } - - fn getSymbolFromPdb(self: *@This(), relocated_address: usize) !?SymbolInfo { - var coff_section: *align(1) const coff.SectionHeader = undefined; - const mod_index = for (self.pdb.?.sect_contribs) |sect_contrib| { - if (sect_contrib.Section > self.coff_section_headers.len) continue; - // Remember that SectionContribEntry.Section is 1-based. - coff_section = &self.coff_section_headers[sect_contrib.Section - 1]; - - const vaddr_start = coff_section.virtual_address + sect_contrib.Offset; - const vaddr_end = vaddr_start + sect_contrib.Size; - if (relocated_address >= vaddr_start and relocated_address < vaddr_end) { - break sect_contrib.ModuleIndex; - } - } else { - // we have no information to add to the address - return null; - }; - - const module = (try self.pdb.?.getModule(mod_index)) orelse - return error.InvalidDebugInfo; - const obj_basename = fs.path.basename(module.obj_file_name); - - const symbol_name = self.pdb.?.getSymbolName( - module, - relocated_address - coff_section.virtual_address, - ) orelse "???"; - const opt_line_info = try self.pdb.?.getLineNumberInfo( - module, - relocated_address - coff_section.virtual_address, - ); - - return SymbolInfo{ - .symbol_name = symbol_name, - .compile_unit_name = obj_basename, - .line_info = opt_line_info, - }; - } - - pub fn getSymbolAtAddress(self: *@This(), allocator: mem.Allocator, address: usize) !SymbolInfo { - // Translate the VA into an address into this object - const relocated_address = address - self.base_address; - - if (self.pdb != null) { - if (try self.getSymbolFromPdb(relocated_address)) |symbol| return symbol; - } - - if (self.dwarf) |*dwarf| { - const dwarf_address = relocated_address + self.coff_image_base; - return getSymbolFromDwarf(allocator, dwarf_address, dwarf); - } - - return SymbolInfo{}; - } - - pub fn getDwarfInfoForAddress(self: *@This(), allocator: mem.Allocator, address: usize) !?*const Dwarf { - _ = allocator; - _ = address; - - return switch (self.debug_data) { - .dwarf => |*dwarf| dwarf, - else => null, - }; - } - }, - .linux, .netbsd, .freebsd, .dragonfly, .openbsd, .haiku, .solaris, .illumos => struct { - base_address: usize, - dwarf: Dwarf, - mapped_memory: []align(mem.page_size) const u8, - external_mapped_memory: ?[]align(mem.page_size) const u8, - - pub fn deinit(self: *@This(), allocator: mem.Allocator) void { - self.dwarf.deinit(allocator); - posix.munmap(self.mapped_memory); - if (self.external_mapped_memory) |m| posix.munmap(m); - } - - pub fn getSymbolAtAddress(self: *@This(), allocator: mem.Allocator, address: usize) !SymbolInfo { - // Translate the VA into an address into this object - const relocated_address = address - self.base_address; - return getSymbolFromDwarf(allocator, relocated_address, &self.dwarf); - } - - pub fn getDwarfInfoForAddress(self: *@This(), allocator: mem.Allocator, address: usize) !?*const Dwarf { - _ = allocator; - _ = address; - return &self.dwarf; - } - }, - .wasi, .emscripten => struct { - pub fn deinit(self: *@This(), allocator: mem.Allocator) void { - _ = self; - _ = allocator; - } - - pub fn getSymbolAtAddress(self: *@This(), allocator: mem.Allocator, address: usize) !SymbolInfo { - _ = self; - _ = allocator; - _ = address; - return SymbolInfo{}; - } - - pub fn getDwarfInfoForAddress(self: *@This(), allocator: mem.Allocator, address: usize) !?*const Dwarf { - _ = self; - _ = allocator; - _ = address; - return null; - } - }, - else => Dwarf, -}; - -fn getSymbolFromDwarf(allocator: mem.Allocator, address: u64, di: *Dwarf) !SymbolInfo { - if (nosuspend di.findCompileUnit(address)) |compile_unit| { - return SymbolInfo{ - .symbol_name = nosuspend di.getSymbolName(address) orelse "???", - .compile_unit_name = compile_unit.die.getAttrString(di, DW.AT.name, di.section(.debug_str), compile_unit.*) catch |err| switch (err) { - error.MissingDebugInfo, error.InvalidDebugInfo => "???", - }, - .line_info = nosuspend di.getLineNumberInfo(allocator, compile_unit.*, address) catch |err| switch (err) { - error.MissingDebugInfo, error.InvalidDebugInfo => null, - else => return err, - }, - }; - } else |err| switch (err) { - error.MissingDebugInfo, error.InvalidDebugInfo => { - return SymbolInfo{}; - }, - else => return err, - } -} - /// TODO multithreaded awareness var debug_info_allocator: ?mem.Allocator = null; var debug_info_arena_allocator: std.heap.ArenaAllocator = undefined; @@ -2802,7 +1439,7 @@ test "manage resources correctly" { } const writer = std.io.null_writer; - var di = try openSelfDebugInfo(testing.allocator); + var di = try Info.openSelf(testing.allocator); defer di.deinit(); try printSourceAtAddress(&di, writer, showMyTrace(), io.tty.detectConfig(std.io.getStdErr())); } diff --git a/lib/std/debug/Dwarf.zig b/lib/std/debug/Dwarf.zig index f17fd737a1..353c097471 100644 --- a/lib/std/debug/Dwarf.zig +++ b/lib/std/debug/Dwarf.zig @@ -1353,7 +1353,7 @@ pub fn getLineNumberInfo( allocator: Allocator, compile_unit: CompileUnit, target_address: u64, -) !std.debug.LineInfo { +) !std.debug.Info.SourceLocation { const compile_unit_cwd = try compile_unit.die.getAttrString(di, AT.comp_dir, di.section(.debug_line_str), compile_unit); const line_info_offset = try compile_unit.die.getAttrSecOffset(AT.stmt_list); @@ -2084,7 +2084,7 @@ const LineNumberProgram = struct { self: *LineNumberProgram, allocator: Allocator, file_entries: []const FileEntry, - ) !?std.debug.LineInfo { + ) !?std.debug.Info.SourceLocation { if (self.prev_valid and self.target_address >= self.prev_address and self.target_address < self.address) @@ -2104,7 +2104,7 @@ const LineNumberProgram = struct { dir_name, file_entry.path, }); - return std.debug.LineInfo{ + return std.debug.Info.SourceLocation{ .line = if (self.prev_line >= 0) @as(u64, @intCast(self.prev_line)) else 0, .column = self.prev_column, .file_name = file_name, diff --git a/lib/std/debug/Info.zig b/lib/std/debug/Info.zig new file mode 100644 index 0000000000..e863cb91de --- /dev/null +++ b/lib/std/debug/Info.zig @@ -0,0 +1,1377 @@ +//! Cross-platform abstraction for debug information. + +const builtin = @import("builtin"); +const native_os = builtin.os.tag; +const native_endian = native_arch.endian(); +const native_arch = builtin.cpu.arch; + +const std = @import("../std.zig"); +const mem = std.mem; +const Allocator = std.mem.Allocator; +const windows = std.os.windows; +const macho = std.macho; +const fs = std.fs; +const coff = std.coff; +const pdb = std.pdb; +const assert = std.debug.assert; +const posix = std.posix; +const elf = std.elf; +const Dwarf = std.debug.Dwarf; +const File = std.fs.File; +const math = std.math; +const testing = std.testing; + +const Info = @This(); + +const root = @import("root"); + +allocator: Allocator, +address_map: std.AutoHashMap(usize, *ModuleDebugInfo), +modules: if (native_os == .windows) std.ArrayListUnmanaged(WindowsModuleInfo) else void, + +pub const OpenSelfError = error{ + MissingDebugInfo, + UnsupportedOperatingSystem, +} || @typeInfo(@typeInfo(@TypeOf(Info.init)).Fn.return_type.?).ErrorUnion.error_set; + +pub fn openSelf(allocator: Allocator) OpenSelfError!Info { + nosuspend { + if (builtin.strip_debug_info) + return error.MissingDebugInfo; + if (@hasDecl(root, "os") and @hasDecl(root.os, "debug") and @hasDecl(root.os.debug, "openSelfDebugInfo")) { + return root.os.debug.openSelfDebugInfo(allocator); + } + switch (native_os) { + .linux, + .freebsd, + .netbsd, + .dragonfly, + .openbsd, + .macos, + .solaris, + .illumos, + .windows, + => return try Info.init(allocator), + else => return error.UnsupportedOperatingSystem, + } + } +} + +pub fn init(allocator: Allocator) !Info { + var debug_info = Info{ + .allocator = allocator, + .address_map = std.AutoHashMap(usize, *ModuleDebugInfo).init(allocator), + .modules = if (native_os == .windows) .{} else {}, + }; + + if (native_os == .windows) { + errdefer debug_info.modules.deinit(allocator); + + const handle = windows.kernel32.CreateToolhelp32Snapshot(windows.TH32CS_SNAPMODULE | windows.TH32CS_SNAPMODULE32, 0); + if (handle == windows.INVALID_HANDLE_VALUE) { + switch (windows.GetLastError()) { + else => |err| return windows.unexpectedError(err), + } + } + defer windows.CloseHandle(handle); + + var module_entry: windows.MODULEENTRY32 = undefined; + module_entry.dwSize = @sizeOf(windows.MODULEENTRY32); + if (windows.kernel32.Module32First(handle, &module_entry) == 0) { + return error.MissingDebugInfo; + } + + var module_valid = true; + while (module_valid) { + const module_info = try debug_info.modules.addOne(allocator); + const name = allocator.dupe(u8, mem.sliceTo(&module_entry.szModule, 0)) catch &.{}; + errdefer allocator.free(name); + + module_info.* = .{ + .base_address = @intFromPtr(module_entry.modBaseAddr), + .size = module_entry.modBaseSize, + .name = name, + .handle = module_entry.hModule, + }; + + module_valid = windows.kernel32.Module32Next(handle, &module_entry) == 1; + } + } + + return debug_info; +} + +pub fn deinit(self: *Info) void { + var it = self.address_map.iterator(); + while (it.next()) |entry| { + const mdi = entry.value_ptr.*; + mdi.deinit(self.allocator); + self.allocator.destroy(mdi); + } + self.address_map.deinit(); + if (native_os == .windows) { + for (self.modules.items) |module| { + self.allocator.free(module.name); + if (module.mapped_file) |mapped_file| mapped_file.deinit(); + } + self.modules.deinit(self.allocator); + } +} + +pub fn getModuleForAddress(self: *Info, address: usize) !*ModuleDebugInfo { + if (comptime builtin.target.isDarwin()) { + return self.lookupModuleDyld(address); + } else if (native_os == .windows) { + return self.lookupModuleWin32(address); + } else if (native_os == .haiku) { + return self.lookupModuleHaiku(address); + } else if (comptime builtin.target.isWasm()) { + return self.lookupModuleWasm(address); + } else { + return self.lookupModuleDl(address); + } +} + +// Returns the module name for a given address. +// This can be called when getModuleForAddress fails, so implementations should provide +// a path that doesn't rely on any side-effects of a prior successful module lookup. +pub fn getModuleNameForAddress(self: *Info, address: usize) ?[]const u8 { + if (comptime builtin.target.isDarwin()) { + return self.lookupModuleNameDyld(address); + } else if (native_os == .windows) { + return self.lookupModuleNameWin32(address); + } else if (native_os == .haiku) { + return null; + } else if (comptime builtin.target.isWasm()) { + return null; + } else { + return self.lookupModuleNameDl(address); + } +} + +fn lookupModuleDyld(self: *Info, address: usize) !*ModuleDebugInfo { + const image_count = std.c._dyld_image_count(); + + var i: u32 = 0; + while (i < image_count) : (i += 1) { + const header = std.c._dyld_get_image_header(i) orelse continue; + const base_address = @intFromPtr(header); + if (address < base_address) continue; + const vmaddr_slide = std.c._dyld_get_image_vmaddr_slide(i); + + var it = macho.LoadCommandIterator{ + .ncmds = header.ncmds, + .buffer = @alignCast(@as( + [*]u8, + @ptrFromInt(@intFromPtr(header) + @sizeOf(macho.mach_header_64)), + )[0..header.sizeofcmds]), + }; + + var unwind_info: ?[]const u8 = null; + var eh_frame: ?[]const u8 = null; + while (it.next()) |cmd| switch (cmd.cmd()) { + .SEGMENT_64 => { + const segment_cmd = cmd.cast(macho.segment_command_64).?; + if (!mem.eql(u8, "__TEXT", segment_cmd.segName())) continue; + + const seg_start = segment_cmd.vmaddr + vmaddr_slide; + const seg_end = seg_start + segment_cmd.vmsize; + if (address >= seg_start and address < seg_end) { + if (self.address_map.get(base_address)) |obj_di| { + return obj_di; + } + + for (cmd.getSections()) |sect| { + if (mem.eql(u8, "__unwind_info", sect.sectName())) { + unwind_info = @as([*]const u8, @ptrFromInt(sect.addr + vmaddr_slide))[0..sect.size]; + } else if (mem.eql(u8, "__eh_frame", sect.sectName())) { + eh_frame = @as([*]const u8, @ptrFromInt(sect.addr + vmaddr_slide))[0..sect.size]; + } + } + + const obj_di = try self.allocator.create(ModuleDebugInfo); + errdefer self.allocator.destroy(obj_di); + + const macho_path = mem.sliceTo(std.c._dyld_get_image_name(i), 0); + const macho_file = fs.cwd().openFile(macho_path, .{}) catch |err| switch (err) { + error.FileNotFound => return error.MissingDebugInfo, + else => return err, + }; + obj_di.* = try readMachODebugInfo(self.allocator, macho_file); + obj_di.base_address = base_address; + obj_di.vmaddr_slide = vmaddr_slide; + obj_di.unwind_info = unwind_info; + obj_di.eh_frame = eh_frame; + + try self.address_map.putNoClobber(base_address, obj_di); + + return obj_di; + } + }, + else => {}, + }; + } + + return error.MissingDebugInfo; +} + +fn lookupModuleNameDyld(self: *Info, address: usize) ?[]const u8 { + _ = self; + const image_count = std.c._dyld_image_count(); + + var i: u32 = 0; + while (i < image_count) : (i += 1) { + const header = std.c._dyld_get_image_header(i) orelse continue; + const base_address = @intFromPtr(header); + if (address < base_address) continue; + const vmaddr_slide = std.c._dyld_get_image_vmaddr_slide(i); + + var it = macho.LoadCommandIterator{ + .ncmds = header.ncmds, + .buffer = @alignCast(@as( + [*]u8, + @ptrFromInt(@intFromPtr(header) + @sizeOf(macho.mach_header_64)), + )[0..header.sizeofcmds]), + }; + + while (it.next()) |cmd| switch (cmd.cmd()) { + .SEGMENT_64 => { + const segment_cmd = cmd.cast(macho.segment_command_64).?; + if (!mem.eql(u8, "__TEXT", segment_cmd.segName())) continue; + + const original_address = address - vmaddr_slide; + const seg_start = segment_cmd.vmaddr; + const seg_end = seg_start + segment_cmd.vmsize; + if (original_address >= seg_start and original_address < seg_end) { + return fs.path.basename(mem.sliceTo(std.c._dyld_get_image_name(i), 0)); + } + }, + else => {}, + }; + } + + return null; +} + +fn lookupModuleWin32(self: *Info, address: usize) !*ModuleDebugInfo { + for (self.modules.items) |*module| { + if (address >= module.base_address and address < module.base_address + module.size) { + if (self.address_map.get(module.base_address)) |obj_di| { + return obj_di; + } + + const obj_di = try self.allocator.create(ModuleDebugInfo); + errdefer self.allocator.destroy(obj_di); + + const mapped_module = @as([*]const u8, @ptrFromInt(module.base_address))[0..module.size]; + var coff_obj = try coff.Coff.init(mapped_module, true); + + // The string table is not mapped into memory by the loader, so if a section name is in the + // string table then we have to map the full image file from disk. This can happen when + // a binary is produced with -gdwarf, since the section names are longer than 8 bytes. + if (coff_obj.strtabRequired()) { + var name_buffer: [windows.PATH_MAX_WIDE + 4:0]u16 = undefined; + // openFileAbsoluteW requires the prefix to be present + @memcpy(name_buffer[0..4], &[_]u16{ '\\', '?', '?', '\\' }); + + const process_handle = windows.GetCurrentProcess(); + const len = windows.kernel32.GetModuleFileNameExW( + process_handle, + module.handle, + @ptrCast(&name_buffer[4]), + windows.PATH_MAX_WIDE, + ); + + if (len == 0) return error.MissingDebugInfo; + const coff_file = fs.openFileAbsoluteW(name_buffer[0 .. len + 4 :0], .{}) catch |err| switch (err) { + error.FileNotFound => return error.MissingDebugInfo, + else => return err, + }; + errdefer coff_file.close(); + + var section_handle: windows.HANDLE = undefined; + const create_section_rc = windows.ntdll.NtCreateSection( + §ion_handle, + windows.STANDARD_RIGHTS_REQUIRED | windows.SECTION_QUERY | windows.SECTION_MAP_READ, + null, + null, + windows.PAGE_READONLY, + // The documentation states that if no AllocationAttribute is specified, then SEC_COMMIT is the default. + // In practice, this isn't the case and specifying 0 will result in INVALID_PARAMETER_6. + windows.SEC_COMMIT, + coff_file.handle, + ); + if (create_section_rc != .SUCCESS) return error.MissingDebugInfo; + errdefer windows.CloseHandle(section_handle); + + var coff_len: usize = 0; + var base_ptr: usize = 0; + const map_section_rc = windows.ntdll.NtMapViewOfSection( + section_handle, + process_handle, + @ptrCast(&base_ptr), + null, + 0, + null, + &coff_len, + .ViewUnmap, + 0, + windows.PAGE_READONLY, + ); + if (map_section_rc != .SUCCESS) return error.MissingDebugInfo; + errdefer assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @ptrFromInt(base_ptr)) == .SUCCESS); + + const section_view = @as([*]const u8, @ptrFromInt(base_ptr))[0..coff_len]; + coff_obj = try coff.Coff.init(section_view, false); + + module.mapped_file = .{ + .file = coff_file, + .section_handle = section_handle, + .section_view = section_view, + }; + } + errdefer if (module.mapped_file) |mapped_file| mapped_file.deinit(); + + obj_di.* = try readCoffDebugInfo(self.allocator, &coff_obj); + obj_di.base_address = module.base_address; + + try self.address_map.putNoClobber(module.base_address, obj_di); + return obj_di; + } + } + + return error.MissingDebugInfo; +} + +fn lookupModuleNameWin32(self: *Info, address: usize) ?[]const u8 { + for (self.modules.items) |module| { + if (address >= module.base_address and address < module.base_address + module.size) { + return module.name; + } + } + return null; +} + +fn lookupModuleNameDl(self: *Info, address: usize) ?[]const u8 { + _ = self; + + var ctx: struct { + // Input + address: usize, + // Output + name: []const u8 = "", + } = .{ .address = address }; + const CtxTy = @TypeOf(ctx); + + if (posix.dl_iterate_phdr(&ctx, error{Found}, struct { + fn callback(info: *posix.dl_phdr_info, size: usize, context: *CtxTy) !void { + _ = size; + if (context.address < info.addr) return; + const phdrs = info.phdr[0..info.phnum]; + for (phdrs) |*phdr| { + if (phdr.p_type != elf.PT_LOAD) continue; + + const seg_start = info.addr +% phdr.p_vaddr; + const seg_end = seg_start + phdr.p_memsz; + if (context.address >= seg_start and context.address < seg_end) { + context.name = mem.sliceTo(info.name, 0) orelse ""; + break; + } + } else return; + + return error.Found; + } + }.callback)) { + return null; + } else |err| switch (err) { + error.Found => return fs.path.basename(ctx.name), + } + + return null; +} + +fn lookupModuleDl(self: *Info, address: usize) !*ModuleDebugInfo { + var ctx: struct { + // Input + address: usize, + // Output + base_address: usize = undefined, + name: []const u8 = undefined, + build_id: ?[]const u8 = null, + gnu_eh_frame: ?[]const u8 = null, + } = .{ .address = address }; + const CtxTy = @TypeOf(ctx); + + if (posix.dl_iterate_phdr(&ctx, error{Found}, struct { + fn callback(info: *posix.dl_phdr_info, size: usize, context: *CtxTy) !void { + _ = size; + // The base address is too high + if (context.address < info.addr) + return; + + const phdrs = info.phdr[0..info.phnum]; + for (phdrs) |*phdr| { + if (phdr.p_type != elf.PT_LOAD) continue; + + // Overflowing addition is used to handle the case of VSDOs having a p_vaddr = 0xffffffffff700000 + const seg_start = info.addr +% phdr.p_vaddr; + const seg_end = seg_start + phdr.p_memsz; + if (context.address >= seg_start and context.address < seg_end) { + // Android libc uses NULL instead of an empty string to mark the + // main program + context.name = mem.sliceTo(info.name, 0) orelse ""; + context.base_address = info.addr; + break; + } + } else return; + + for (info.phdr[0..info.phnum]) |phdr| { + switch (phdr.p_type) { + elf.PT_NOTE => { + // Look for .note.gnu.build-id + const note_bytes = @as([*]const u8, @ptrFromInt(info.addr + phdr.p_vaddr))[0..phdr.p_memsz]; + const name_size = mem.readInt(u32, note_bytes[0..4], native_endian); + if (name_size != 4) continue; + const desc_size = mem.readInt(u32, note_bytes[4..8], native_endian); + const note_type = mem.readInt(u32, note_bytes[8..12], native_endian); + if (note_type != elf.NT_GNU_BUILD_ID) continue; + if (!mem.eql(u8, "GNU\x00", note_bytes[12..16])) continue; + context.build_id = note_bytes[16..][0..desc_size]; + }, + elf.PT_GNU_EH_FRAME => { + context.gnu_eh_frame = @as([*]const u8, @ptrFromInt(info.addr + phdr.p_vaddr))[0..phdr.p_memsz]; + }, + else => {}, + } + } + + // Stop the iteration + return error.Found; + } + }.callback)) { + return error.MissingDebugInfo; + } else |err| switch (err) { + error.Found => {}, + } + + if (self.address_map.get(ctx.base_address)) |obj_di| { + return obj_di; + } + + const obj_di = try self.allocator.create(ModuleDebugInfo); + errdefer self.allocator.destroy(obj_di); + + var sections: Dwarf.SectionArray = Dwarf.null_section_array; + if (ctx.gnu_eh_frame) |eh_frame_hdr| { + // This is a special case - pointer offsets inside .eh_frame_hdr + // are encoded relative to its base address, so we must use the + // version that is already memory mapped, and not the one that + // will be mapped separately from the ELF file. + sections[@intFromEnum(Dwarf.Section.Id.eh_frame_hdr)] = .{ + .data = eh_frame_hdr, + .owned = false, + }; + } + + obj_di.* = try readElfDebugInfo(self.allocator, if (ctx.name.len > 0) ctx.name else null, ctx.build_id, null, §ions, null); + obj_di.base_address = ctx.base_address; + + // Missing unwind info isn't treated as a failure, as the unwinder will fall back to FP-based unwinding + obj_di.dwarf.scanAllUnwindInfo(self.allocator, ctx.base_address) catch {}; + + try self.address_map.putNoClobber(ctx.base_address, obj_di); + + return obj_di; +} + +fn lookupModuleHaiku(self: *Info, address: usize) !*ModuleDebugInfo { + _ = self; + _ = address; + @panic("TODO implement lookup module for Haiku"); +} + +fn lookupModuleWasm(self: *Info, address: usize) !*ModuleDebugInfo { + _ = self; + _ = address; + @panic("TODO implement lookup module for Wasm"); +} + +pub const ModuleDebugInfo = switch (native_os) { + .macos, .ios, .watchos, .tvos, .visionos => struct { + base_address: usize, + vmaddr_slide: usize, + mapped_memory: []align(mem.page_size) const u8, + symbols: []const MachoSymbol, + strings: [:0]const u8, + ofiles: OFileTable, + + // Backed by the in-memory sections mapped by the loader + unwind_info: ?[]const u8 = null, + eh_frame: ?[]const u8 = null, + + const OFileTable = std.StringHashMap(OFileInfo); + const OFileInfo = struct { + di: Dwarf, + addr_table: std.StringHashMap(u64), + }; + + pub fn deinit(self: *@This(), allocator: Allocator) void { + var it = self.ofiles.iterator(); + while (it.next()) |entry| { + const ofile = entry.value_ptr; + ofile.di.deinit(allocator); + ofile.addr_table.deinit(); + } + self.ofiles.deinit(); + allocator.free(self.symbols); + posix.munmap(self.mapped_memory); + } + + fn loadOFile(self: *@This(), allocator: Allocator, o_file_path: []const u8) !*OFileInfo { + const o_file = try fs.cwd().openFile(o_file_path, .{}); + const mapped_mem = try mapWholeFile(o_file); + + const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_mem.ptr)); + if (hdr.magic != std.macho.MH_MAGIC_64) + return error.InvalidDebugInfo; + + var segcmd: ?macho.LoadCommandIterator.LoadCommand = null; + var symtabcmd: ?macho.symtab_command = null; + var it = macho.LoadCommandIterator{ + .ncmds = hdr.ncmds, + .buffer = mapped_mem[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds], + }; + while (it.next()) |cmd| switch (cmd.cmd()) { + .SEGMENT_64 => segcmd = cmd, + .SYMTAB => symtabcmd = cmd.cast(macho.symtab_command).?, + else => {}, + }; + + if (segcmd == null or symtabcmd == null) return error.MissingDebugInfo; + + // Parse symbols + const strtab = @as( + [*]const u8, + @ptrCast(&mapped_mem[symtabcmd.?.stroff]), + )[0 .. symtabcmd.?.strsize - 1 :0]; + const symtab = @as( + [*]const macho.nlist_64, + @ptrCast(@alignCast(&mapped_mem[symtabcmd.?.symoff])), + )[0..symtabcmd.?.nsyms]; + + // TODO handle tentative (common) symbols + var addr_table = std.StringHashMap(u64).init(allocator); + try addr_table.ensureTotalCapacity(@as(u32, @intCast(symtab.len))); + for (symtab) |sym| { + if (sym.n_strx == 0) continue; + if (sym.undf() or sym.tentative() or sym.abs()) continue; + const sym_name = mem.sliceTo(strtab[sym.n_strx..], 0); + // TODO is it possible to have a symbol collision? + addr_table.putAssumeCapacityNoClobber(sym_name, sym.n_value); + } + + var sections: Dwarf.SectionArray = Dwarf.null_section_array; + if (self.eh_frame) |eh_frame| sections[@intFromEnum(Dwarf.Section.Id.eh_frame)] = .{ + .data = eh_frame, + .owned = false, + }; + + for (segcmd.?.getSections()) |sect| { + if (!std.mem.eql(u8, "__DWARF", sect.segName())) continue; + + var section_index: ?usize = null; + inline for (@typeInfo(Dwarf.Section.Id).Enum.fields, 0..) |section, i| { + if (mem.eql(u8, "__" ++ section.name, sect.sectName())) section_index = i; + } + if (section_index == null) continue; + + const section_bytes = try chopSlice(mapped_mem, sect.offset, sect.size); + sections[section_index.?] = .{ + .data = section_bytes, + .virtual_address = sect.addr, + .owned = false, + }; + } + + const missing_debug_info = + sections[@intFromEnum(Dwarf.Section.Id.debug_info)] == null or + sections[@intFromEnum(Dwarf.Section.Id.debug_abbrev)] == null or + sections[@intFromEnum(Dwarf.Section.Id.debug_str)] == null or + sections[@intFromEnum(Dwarf.Section.Id.debug_line)] == null; + if (missing_debug_info) return error.MissingDebugInfo; + + var di = Dwarf{ + .endian = .little, + .sections = sections, + .is_macho = true, + }; + + try Dwarf.open(&di, allocator); + const info = OFileInfo{ + .di = di, + .addr_table = addr_table, + }; + + // Add the debug info to the cache + const result = try self.ofiles.getOrPut(o_file_path); + assert(!result.found_existing); + result.value_ptr.* = info; + + return result.value_ptr; + } + + pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !SymbolInfo { + nosuspend { + const result = try self.getOFileInfoForAddress(allocator, address); + if (result.symbol == null) return .{}; + + // Take the symbol name from the N_FUN STAB entry, we're going to + // use it if we fail to find the DWARF infos + const stab_symbol = mem.sliceTo(self.strings[result.symbol.?.strx..], 0); + if (result.o_file_info == null) return .{ .symbol_name = stab_symbol }; + + // Translate again the address, this time into an address inside the + // .o file + const relocated_address_o = result.o_file_info.?.addr_table.get(stab_symbol) orelse return .{ + .symbol_name = "???", + }; + + const addr_off = result.relocated_address - result.symbol.?.addr; + const o_file_di = &result.o_file_info.?.di; + if (o_file_di.findCompileUnit(relocated_address_o)) |compile_unit| { + return SymbolInfo{ + .symbol_name = o_file_di.getSymbolName(relocated_address_o) orelse "???", + .compile_unit_name = compile_unit.die.getAttrString( + o_file_di, + std.dwarf.AT.name, + o_file_di.section(.debug_str), + compile_unit.*, + ) catch |err| switch (err) { + error.MissingDebugInfo, error.InvalidDebugInfo => "???", + }, + .line_info = o_file_di.getLineNumberInfo( + allocator, + compile_unit.*, + relocated_address_o + addr_off, + ) catch |err| switch (err) { + error.MissingDebugInfo, error.InvalidDebugInfo => null, + else => return err, + }, + }; + } else |err| switch (err) { + error.MissingDebugInfo, error.InvalidDebugInfo => { + return SymbolInfo{ .symbol_name = stab_symbol }; + }, + else => return err, + } + } + } + + pub fn getOFileInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !struct { + relocated_address: usize, + symbol: ?*const MachoSymbol = null, + o_file_info: ?*OFileInfo = null, + } { + nosuspend { + // Translate the VA into an address into this object + const relocated_address = address - self.vmaddr_slide; + + // Find the .o file where this symbol is defined + const symbol = machoSearchSymbols(self.symbols, relocated_address) orelse return .{ + .relocated_address = relocated_address, + }; + + // Check if its debug infos are already in the cache + const o_file_path = mem.sliceTo(self.strings[symbol.ofile..], 0); + const o_file_info = self.ofiles.getPtr(o_file_path) orelse + (self.loadOFile(allocator, o_file_path) catch |err| switch (err) { + error.FileNotFound, + error.MissingDebugInfo, + error.InvalidDebugInfo, + => return .{ + .relocated_address = relocated_address, + .symbol = symbol, + }, + else => return err, + }); + + return .{ + .relocated_address = relocated_address, + .symbol = symbol, + .o_file_info = o_file_info, + }; + } + } + + pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*const Dwarf { + return if ((try self.getOFileInfoForAddress(allocator, address)).o_file_info) |o_file_info| &o_file_info.di else null; + } + }, + .uefi, .windows => struct { + base_address: usize, + pdb: ?pdb.Pdb = null, + dwarf: ?Dwarf = null, + coff_image_base: u64, + + /// Only used if pdb is non-null + coff_section_headers: []coff.SectionHeader, + + pub fn deinit(self: *@This(), allocator: Allocator) void { + if (self.dwarf) |*dwarf| { + dwarf.deinit(allocator); + } + + if (self.pdb) |*p| { + p.deinit(); + allocator.free(self.coff_section_headers); + } + } + + fn getSymbolFromPdb(self: *@This(), relocated_address: usize) !?SymbolInfo { + var coff_section: *align(1) const coff.SectionHeader = undefined; + const mod_index = for (self.pdb.?.sect_contribs) |sect_contrib| { + if (sect_contrib.Section > self.coff_section_headers.len) continue; + // Remember that SectionContribEntry.Section is 1-based. + coff_section = &self.coff_section_headers[sect_contrib.Section - 1]; + + const vaddr_start = coff_section.virtual_address + sect_contrib.Offset; + const vaddr_end = vaddr_start + sect_contrib.Size; + if (relocated_address >= vaddr_start and relocated_address < vaddr_end) { + break sect_contrib.ModuleIndex; + } + } else { + // we have no information to add to the address + return null; + }; + + const module = (try self.pdb.?.getModule(mod_index)) orelse + return error.InvalidDebugInfo; + const obj_basename = fs.path.basename(module.obj_file_name); + + const symbol_name = self.pdb.?.getSymbolName( + module, + relocated_address - coff_section.virtual_address, + ) orelse "???"; + const opt_line_info = try self.pdb.?.getLineNumberInfo( + module, + relocated_address - coff_section.virtual_address, + ); + + return SymbolInfo{ + .symbol_name = symbol_name, + .compile_unit_name = obj_basename, + .line_info = opt_line_info, + }; + } + + pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !SymbolInfo { + // Translate the VA into an address into this object + const relocated_address = address - self.base_address; + + if (self.pdb != null) { + if (try self.getSymbolFromPdb(relocated_address)) |symbol| return symbol; + } + + if (self.dwarf) |*dwarf| { + const dwarf_address = relocated_address + self.coff_image_base; + return getSymbolFromDwarf(allocator, dwarf_address, dwarf); + } + + return SymbolInfo{}; + } + + pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*const Dwarf { + _ = allocator; + _ = address; + + return switch (self.debug_data) { + .dwarf => |*dwarf| dwarf, + else => null, + }; + } + }, + .linux, .netbsd, .freebsd, .dragonfly, .openbsd, .haiku, .solaris, .illumos => struct { + base_address: usize, + dwarf: Dwarf, + mapped_memory: []align(mem.page_size) const u8, + external_mapped_memory: ?[]align(mem.page_size) const u8, + + pub fn deinit(self: *@This(), allocator: Allocator) void { + self.dwarf.deinit(allocator); + posix.munmap(self.mapped_memory); + if (self.external_mapped_memory) |m| posix.munmap(m); + } + + pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !SymbolInfo { + // Translate the VA into an address into this object + const relocated_address = address - self.base_address; + return getSymbolFromDwarf(allocator, relocated_address, &self.dwarf); + } + + pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*const Dwarf { + _ = allocator; + _ = address; + return &self.dwarf; + } + }, + .wasi, .emscripten => struct { + pub fn deinit(self: *@This(), allocator: Allocator) void { + _ = self; + _ = allocator; + } + + pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !SymbolInfo { + _ = self; + _ = allocator; + _ = address; + return SymbolInfo{}; + } + + pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*const Dwarf { + _ = self; + _ = allocator; + _ = address; + return null; + } + }, + else => Dwarf, +}; + +pub const WindowsModuleInfo = struct { + base_address: usize, + size: u32, + name: []const u8, + handle: windows.HMODULE, + + // Set when the image file needed to be mapped from disk + mapped_file: ?struct { + file: File, + section_handle: windows.HANDLE, + section_view: []const u8, + + pub fn deinit(self: @This()) void { + const process_handle = windows.GetCurrentProcess(); + assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @constCast(@ptrCast(self.section_view.ptr))) == .SUCCESS); + windows.CloseHandle(self.section_handle); + self.file.close(); + } + } = null, +}; + +/// This takes ownership of macho_file: users of this function should not close +/// it themselves, even on error. +/// TODO it's weird to take ownership even on error, rework this code. +fn readMachODebugInfo(allocator: Allocator, macho_file: File) !ModuleDebugInfo { + const mapped_mem = try mapWholeFile(macho_file); + + const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_mem.ptr)); + if (hdr.magic != macho.MH_MAGIC_64) + return error.InvalidDebugInfo; + + var it = macho.LoadCommandIterator{ + .ncmds = hdr.ncmds, + .buffer = mapped_mem[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds], + }; + const symtab = while (it.next()) |cmd| switch (cmd.cmd()) { + .SYMTAB => break cmd.cast(macho.symtab_command).?, + else => {}, + } else return error.MissingDebugInfo; + + const syms = @as( + [*]const macho.nlist_64, + @ptrCast(@alignCast(&mapped_mem[symtab.symoff])), + )[0..symtab.nsyms]; + const strings = mapped_mem[symtab.stroff..][0 .. symtab.strsize - 1 :0]; + + const symbols_buf = try allocator.alloc(MachoSymbol, syms.len); + + var ofile: u32 = undefined; + var last_sym: MachoSymbol = undefined; + var symbol_index: usize = 0; + var state: enum { + init, + oso_open, + oso_close, + bnsym, + fun_strx, + fun_size, + ensym, + } = .init; + + for (syms) |*sym| { + if (!sym.stab()) continue; + + // TODO handle globals N_GSYM, and statics N_STSYM + switch (sym.n_type) { + macho.N_OSO => { + switch (state) { + .init, .oso_close => { + state = .oso_open; + ofile = sym.n_strx; + }, + else => return error.InvalidDebugInfo, + } + }, + macho.N_BNSYM => { + switch (state) { + .oso_open, .ensym => { + state = .bnsym; + last_sym = .{ + .strx = 0, + .addr = sym.n_value, + .size = 0, + .ofile = ofile, + }; + }, + else => return error.InvalidDebugInfo, + } + }, + macho.N_FUN => { + switch (state) { + .bnsym => { + state = .fun_strx; + last_sym.strx = sym.n_strx; + }, + .fun_strx => { + state = .fun_size; + last_sym.size = @as(u32, @intCast(sym.n_value)); + }, + else => return error.InvalidDebugInfo, + } + }, + macho.N_ENSYM => { + switch (state) { + .fun_size => { + state = .ensym; + symbols_buf[symbol_index] = last_sym; + symbol_index += 1; + }, + else => return error.InvalidDebugInfo, + } + }, + macho.N_SO => { + switch (state) { + .init, .oso_close => {}, + .oso_open, .ensym => { + state = .oso_close; + }, + else => return error.InvalidDebugInfo, + } + }, + else => {}, + } + } + + switch (state) { + .init => return error.MissingDebugInfo, + .oso_close => {}, + else => return error.InvalidDebugInfo, + } + + const symbols = try allocator.realloc(symbols_buf, symbol_index); + + // Even though lld emits symbols in ascending order, this debug code + // should work for programs linked in any valid way. + // This sort is so that we can binary search later. + mem.sort(MachoSymbol, symbols, {}, MachoSymbol.addressLessThan); + + return ModuleDebugInfo{ + .base_address = undefined, + .vmaddr_slide = undefined, + .mapped_memory = mapped_mem, + .ofiles = ModuleDebugInfo.OFileTable.init(allocator), + .symbols = symbols, + .strings = strings, + }; +} + +fn readCoffDebugInfo(allocator: Allocator, coff_obj: *coff.Coff) !ModuleDebugInfo { + nosuspend { + var di = ModuleDebugInfo{ + .base_address = undefined, + .coff_image_base = coff_obj.getImageBase(), + .coff_section_headers = undefined, + }; + + if (coff_obj.getSectionByName(".debug_info")) |_| { + // This coff file has embedded DWARF debug info + var sections: Dwarf.SectionArray = Dwarf.null_section_array; + errdefer for (sections) |section| if (section) |s| if (s.owned) allocator.free(s.data); + + inline for (@typeInfo(Dwarf.Section.Id).Enum.fields, 0..) |section, i| { + sections[i] = if (coff_obj.getSectionByName("." ++ section.name)) |section_header| blk: { + break :blk .{ + .data = try coff_obj.getSectionDataAlloc(section_header, allocator), + .virtual_address = section_header.virtual_address, + .owned = true, + }; + } else null; + } + + var dwarf = Dwarf{ + .endian = native_endian, + .sections = sections, + .is_macho = false, + }; + + try Dwarf.open(&dwarf, allocator); + di.dwarf = dwarf; + } + + const raw_path = try coff_obj.getPdbPath() orelse return di; + const path = blk: { + if (fs.path.isAbsolute(raw_path)) { + break :blk raw_path; + } else { + const self_dir = try fs.selfExeDirPathAlloc(allocator); + defer allocator.free(self_dir); + break :blk try fs.path.join(allocator, &.{ self_dir, raw_path }); + } + }; + defer if (path.ptr != raw_path.ptr) allocator.free(path); + + di.pdb = pdb.Pdb.init(allocator, path) catch |err| switch (err) { + error.FileNotFound, error.IsDir => { + if (di.dwarf == null) return error.MissingDebugInfo; + return di; + }, + else => return err, + }; + try di.pdb.?.parseInfoStream(); + try di.pdb.?.parseDbiStream(); + + if (!mem.eql(u8, &coff_obj.guid, &di.pdb.?.guid) or coff_obj.age != di.pdb.?.age) + return error.InvalidDebugInfo; + + // Only used by the pdb path + di.coff_section_headers = try coff_obj.getSectionHeadersAlloc(allocator); + errdefer allocator.free(di.coff_section_headers); + + return di; + } +} + +/// Reads debug info from an ELF file, or the current binary if none in specified. +/// If the required sections aren't present but a reference to external debug info is, +/// then this this function will recurse to attempt to load the debug sections from +/// an external file. +pub fn readElfDebugInfo( + allocator: Allocator, + elf_filename: ?[]const u8, + build_id: ?[]const u8, + expected_crc: ?u32, + parent_sections: *Dwarf.SectionArray, + parent_mapped_mem: ?[]align(mem.page_size) const u8, +) !ModuleDebugInfo { + nosuspend { + const elf_file = (if (elf_filename) |filename| blk: { + break :blk fs.cwd().openFile(filename, .{}); + } else fs.openSelfExe(.{})) catch |err| switch (err) { + error.FileNotFound => return error.MissingDebugInfo, + else => return err, + }; + + const mapped_mem = try mapWholeFile(elf_file); + if (expected_crc) |crc| if (crc != std.hash.crc.Crc32.hash(mapped_mem)) return error.InvalidDebugInfo; + + const hdr: *const elf.Ehdr = @ptrCast(&mapped_mem[0]); + if (!mem.eql(u8, hdr.e_ident[0..4], elf.MAGIC)) return error.InvalidElfMagic; + if (hdr.e_ident[elf.EI_VERSION] != 1) return error.InvalidElfVersion; + + const endian: std.builtin.Endian = switch (hdr.e_ident[elf.EI_DATA]) { + elf.ELFDATA2LSB => .little, + elf.ELFDATA2MSB => .big, + else => return error.InvalidElfEndian, + }; + assert(endian == native_endian); // this is our own debug info + + const shoff = hdr.e_shoff; + const str_section_off = shoff + @as(u64, hdr.e_shentsize) * @as(u64, hdr.e_shstrndx); + const str_shdr: *const elf.Shdr = @ptrCast(@alignCast(&mapped_mem[math.cast(usize, str_section_off) orelse return error.Overflow])); + const header_strings = mapped_mem[str_shdr.sh_offset..][0..str_shdr.sh_size]; + const shdrs = @as( + [*]const elf.Shdr, + @ptrCast(@alignCast(&mapped_mem[shoff])), + )[0..hdr.e_shnum]; + + var sections: Dwarf.SectionArray = Dwarf.null_section_array; + + // Combine section list. This takes ownership over any owned sections from the parent scope. + for (parent_sections, §ions) |*parent, *section| { + if (parent.*) |*p| { + section.* = p.*; + p.owned = false; + } + } + errdefer for (sections) |section| if (section) |s| if (s.owned) allocator.free(s.data); + + var separate_debug_filename: ?[]const u8 = null; + var separate_debug_crc: ?u32 = null; + + for (shdrs) |*shdr| { + if (shdr.sh_type == elf.SHT_NULL or shdr.sh_type == elf.SHT_NOBITS) continue; + const name = mem.sliceTo(header_strings[shdr.sh_name..], 0); + + if (mem.eql(u8, name, ".gnu_debuglink")) { + const gnu_debuglink = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size); + const debug_filename = mem.sliceTo(@as([*:0]const u8, @ptrCast(gnu_debuglink.ptr)), 0); + const crc_offset = mem.alignForward(usize, @intFromPtr(&debug_filename[debug_filename.len]) + 1, 4) - @intFromPtr(gnu_debuglink.ptr); + const crc_bytes = gnu_debuglink[crc_offset..][0..4]; + separate_debug_crc = mem.readInt(u32, crc_bytes, native_endian); + separate_debug_filename = debug_filename; + continue; + } + + var section_index: ?usize = null; + inline for (@typeInfo(Dwarf.Section.Id).Enum.fields, 0..) |section, i| { + if (mem.eql(u8, "." ++ section.name, name)) section_index = i; + } + if (section_index == null) continue; + if (sections[section_index.?] != null) continue; + + const section_bytes = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size); + sections[section_index.?] = if ((shdr.sh_flags & elf.SHF_COMPRESSED) > 0) blk: { + var section_stream = std.io.fixedBufferStream(section_bytes); + var section_reader = section_stream.reader(); + const chdr = section_reader.readStruct(elf.Chdr) catch continue; + if (chdr.ch_type != .ZLIB) continue; + + var zlib_stream = std.compress.zlib.decompressor(section_stream.reader()); + + const decompressed_section = try allocator.alloc(u8, chdr.ch_size); + errdefer allocator.free(decompressed_section); + + const read = zlib_stream.reader().readAll(decompressed_section) catch continue; + assert(read == decompressed_section.len); + + break :blk .{ + .data = decompressed_section, + .virtual_address = shdr.sh_addr, + .owned = true, + }; + } else .{ + .data = section_bytes, + .virtual_address = shdr.sh_addr, + .owned = false, + }; + } + + const missing_debug_info = + sections[@intFromEnum(Dwarf.Section.Id.debug_info)] == null or + sections[@intFromEnum(Dwarf.Section.Id.debug_abbrev)] == null or + sections[@intFromEnum(Dwarf.Section.Id.debug_str)] == null or + sections[@intFromEnum(Dwarf.Section.Id.debug_line)] == null; + + // Attempt to load debug info from an external file + // See: https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html + if (missing_debug_info) { + + // Only allow one level of debug info nesting + if (parent_mapped_mem) |_| { + return error.MissingDebugInfo; + } + + const global_debug_directories = [_][]const u8{ + "/usr/lib/debug", + }; + + // /.build-id/<2-character id prefix>/.debug + if (build_id) |id| blk: { + if (id.len < 3) break :blk; + + // Either md5 (16 bytes) or sha1 (20 bytes) are used here in practice + const extension = ".debug"; + var id_prefix_buf: [2]u8 = undefined; + var filename_buf: [38 + extension.len]u8 = undefined; + + _ = std.fmt.bufPrint(&id_prefix_buf, "{s}", .{std.fmt.fmtSliceHexLower(id[0..1])}) catch unreachable; + const filename = std.fmt.bufPrint( + &filename_buf, + "{s}" ++ extension, + .{std.fmt.fmtSliceHexLower(id[1..])}, + ) catch break :blk; + + for (global_debug_directories) |global_directory| { + const path = try fs.path.join(allocator, &.{ global_directory, ".build-id", &id_prefix_buf, filename }); + defer allocator.free(path); + + return readElfDebugInfo(allocator, path, null, separate_debug_crc, §ions, mapped_mem) catch continue; + } + } + + // use the path from .gnu_debuglink, in the same search order as gdb + if (separate_debug_filename) |separate_filename| blk: { + if (elf_filename != null and mem.eql(u8, elf_filename.?, separate_filename)) return error.MissingDebugInfo; + + // / + if (readElfDebugInfo(allocator, separate_filename, null, separate_debug_crc, §ions, mapped_mem)) |debug_info| return debug_info else |_| {} + + // /.debug/ + { + const path = try fs.path.join(allocator, &.{ ".debug", separate_filename }); + defer allocator.free(path); + + if (readElfDebugInfo(allocator, path, null, separate_debug_crc, §ions, mapped_mem)) |debug_info| return debug_info else |_| {} + } + + var cwd_buf: [fs.max_path_bytes]u8 = undefined; + const cwd_path = posix.realpath(".", &cwd_buf) catch break :blk; + + // // + for (global_debug_directories) |global_directory| { + const path = try fs.path.join(allocator, &.{ global_directory, cwd_path, separate_filename }); + defer allocator.free(path); + if (readElfDebugInfo(allocator, path, null, separate_debug_crc, §ions, mapped_mem)) |debug_info| return debug_info else |_| {} + } + } + + return error.MissingDebugInfo; + } + + var di = Dwarf{ + .endian = endian, + .sections = sections, + .is_macho = false, + }; + + try Dwarf.open(&di, allocator); + + return ModuleDebugInfo{ + .base_address = undefined, + .dwarf = di, + .mapped_memory = parent_mapped_mem orelse mapped_mem, + .external_mapped_memory = if (parent_mapped_mem != null) mapped_mem else null, + }; + } +} + +const MachoSymbol = struct { + strx: u32, + addr: u64, + size: u32, + ofile: u32, + + /// Returns the address from the macho file + fn address(self: MachoSymbol) u64 { + return self.addr; + } + + fn addressLessThan(context: void, lhs: MachoSymbol, rhs: MachoSymbol) bool { + _ = context; + return lhs.addr < rhs.addr; + } +}; + +/// Takes ownership of file, even on error. +/// TODO it's weird to take ownership even on error, rework this code. +fn mapWholeFile(file: File) ![]align(mem.page_size) const u8 { + nosuspend { + defer file.close(); + + const file_len = math.cast(usize, try file.getEndPos()) orelse math.maxInt(usize); + const mapped_mem = try posix.mmap( + null, + file_len, + posix.PROT.READ, + .{ .TYPE = .SHARED }, + file.handle, + 0, + ); + errdefer posix.munmap(mapped_mem); + + return mapped_mem; + } +} + +fn chopSlice(ptr: []const u8, offset: u64, size: u64) error{Overflow}![]const u8 { + const start = math.cast(usize, offset) orelse return error.Overflow; + const end = start + (math.cast(usize, size) orelse return error.Overflow); + return ptr[start..end]; +} + +pub const SymbolInfo = struct { + symbol_name: []const u8 = "???", + compile_unit_name: []const u8 = "???", + line_info: ?SourceLocation = null, + + pub fn deinit(self: SymbolInfo, allocator: Allocator) void { + if (self.line_info) |li| { + li.deinit(allocator); + } + } +}; + +pub const SourceLocation = struct { + line: u64, + column: u64, + file_name: []const u8, + + pub fn deinit(self: SourceLocation, allocator: Allocator) void { + allocator.free(self.file_name); + } +}; + +fn machoSearchSymbols(symbols: []const MachoSymbol, address: usize) ?*const MachoSymbol { + var min: usize = 0; + var max: usize = symbols.len - 1; + while (min < max) { + const mid = min + (max - min) / 2; + const curr = &symbols[mid]; + const next = &symbols[mid + 1]; + if (address >= next.address()) { + min = mid + 1; + } else if (address < curr.address()) { + max = mid; + } else { + return curr; + } + } + + const max_sym = &symbols[symbols.len - 1]; + if (address >= max_sym.address()) + return max_sym; + + return null; +} + +test machoSearchSymbols { + const symbols = [_]MachoSymbol{ + .{ .addr = 100, .strx = undefined, .size = undefined, .ofile = undefined }, + .{ .addr = 200, .strx = undefined, .size = undefined, .ofile = undefined }, + .{ .addr = 300, .strx = undefined, .size = undefined, .ofile = undefined }, + }; + + try testing.expectEqual(null, machoSearchSymbols(&symbols, 0)); + try testing.expectEqual(null, machoSearchSymbols(&symbols, 99)); + try testing.expectEqual(&symbols[0], machoSearchSymbols(&symbols, 100).?); + try testing.expectEqual(&symbols[0], machoSearchSymbols(&symbols, 150).?); + try testing.expectEqual(&symbols[0], machoSearchSymbols(&symbols, 199).?); + + try testing.expectEqual(&symbols[1], machoSearchSymbols(&symbols, 200).?); + try testing.expectEqual(&symbols[1], machoSearchSymbols(&symbols, 250).?); + try testing.expectEqual(&symbols[1], machoSearchSymbols(&symbols, 299).?); + + try testing.expectEqual(&symbols[2], machoSearchSymbols(&symbols, 300).?); + try testing.expectEqual(&symbols[2], machoSearchSymbols(&symbols, 301).?); + try testing.expectEqual(&symbols[2], machoSearchSymbols(&symbols, 5000).?); +} + +fn getSymbolFromDwarf(allocator: Allocator, address: u64, di: *Dwarf) !SymbolInfo { + if (nosuspend di.findCompileUnit(address)) |compile_unit| { + return SymbolInfo{ + .symbol_name = nosuspend di.getSymbolName(address) orelse "???", + .compile_unit_name = compile_unit.die.getAttrString(di, std.dwarf.AT.name, di.section(.debug_str), compile_unit.*) catch |err| switch (err) { + error.MissingDebugInfo, error.InvalidDebugInfo => "???", + }, + .line_info = nosuspend di.getLineNumberInfo(allocator, compile_unit.*, address) catch |err| switch (err) { + error.MissingDebugInfo, error.InvalidDebugInfo => null, + else => return err, + }, + }; + } else |err| switch (err) { + error.MissingDebugInfo, error.InvalidDebugInfo => { + return SymbolInfo{}; + }, + else => return err, + } +} diff --git a/lib/std/pdb.zig b/lib/std/pdb.zig index ece1cc63dc..c96eb81fa9 100644 --- a/lib/std/pdb.zig +++ b/lib/std/pdb.zig @@ -706,7 +706,7 @@ pub const Pdb = struct { return null; } - pub fn getLineNumberInfo(self: *Pdb, module: *Module, address: u64) !debug.LineInfo { + pub fn getLineNumberInfo(self: *Pdb, module: *Module, address: u64) !debug.Info.SourceLocation { std.debug.assert(module.populated); const subsect_info = module.subsect_info; @@ -731,7 +731,7 @@ pub const Pdb = struct { if (address >= frag_vaddr_start and address < frag_vaddr_end) { // There is an unknown number of LineBlockFragmentHeaders (and their accompanying line and column records) - // from now on. We will iterate through them, and eventually find a LineInfo that we're interested in, + // from now on. We will iterate through them, and eventually find a SourceLocation that we're interested in, // breaking out to :subsections. If not, we will make sure to not read anything outside of this subsection. const subsection_end_index = sect_offset + subsect_hdr.Length; @@ -778,7 +778,7 @@ pub const Pdb = struct { const line_num_entry: *align(1) LineNumberEntry = @ptrCast(&subsect_info[found_line_index]); const flags: *align(1) LineNumberEntry.Flags = @ptrCast(&line_num_entry.Flags); - return debug.LineInfo{ + return debug.Info.SourceLocation{ .file_name = source_file_name, .line = flags.Start, .column = column, From ab0253f6620f5b87f06fdbddfc8876263c2a10a2 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 1 Aug 2024 15:39:27 -0700 Subject: [PATCH 112/266] std.debug.Info: rename ModuleDebugInfo to Module --- lib/std/debug/Info.zig | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/lib/std/debug/Info.zig b/lib/std/debug/Info.zig index e863cb91de..9d3074834b 100644 --- a/lib/std/debug/Info.zig +++ b/lib/std/debug/Info.zig @@ -26,7 +26,7 @@ const Info = @This(); const root = @import("root"); allocator: Allocator, -address_map: std.AutoHashMap(usize, *ModuleDebugInfo), +address_map: std.AutoHashMap(usize, *Module), modules: if (native_os == .windows) std.ArrayListUnmanaged(WindowsModuleInfo) else void, pub const OpenSelfError = error{ @@ -58,9 +58,9 @@ pub fn openSelf(allocator: Allocator) OpenSelfError!Info { } pub fn init(allocator: Allocator) !Info { - var debug_info = Info{ + var debug_info: Info = .{ .allocator = allocator, - .address_map = std.AutoHashMap(usize, *ModuleDebugInfo).init(allocator), + .address_map = std.AutoHashMap(usize, *Module).init(allocator), .modules = if (native_os == .windows) .{} else {}, }; @@ -118,7 +118,7 @@ pub fn deinit(self: *Info) void { } } -pub fn getModuleForAddress(self: *Info, address: usize) !*ModuleDebugInfo { +pub fn getModuleForAddress(self: *Info, address: usize) !*Module { if (comptime builtin.target.isDarwin()) { return self.lookupModuleDyld(address); } else if (native_os == .windows) { @@ -149,7 +149,7 @@ pub fn getModuleNameForAddress(self: *Info, address: usize) ?[]const u8 { } } -fn lookupModuleDyld(self: *Info, address: usize) !*ModuleDebugInfo { +fn lookupModuleDyld(self: *Info, address: usize) !*Module { const image_count = std.c._dyld_image_count(); var i: u32 = 0; @@ -189,7 +189,7 @@ fn lookupModuleDyld(self: *Info, address: usize) !*ModuleDebugInfo { } } - const obj_di = try self.allocator.create(ModuleDebugInfo); + const obj_di = try self.allocator.create(Module); errdefer self.allocator.destroy(obj_di); const macho_path = mem.sliceTo(std.c._dyld_get_image_name(i), 0); @@ -253,14 +253,14 @@ fn lookupModuleNameDyld(self: *Info, address: usize) ?[]const u8 { return null; } -fn lookupModuleWin32(self: *Info, address: usize) !*ModuleDebugInfo { +fn lookupModuleWin32(self: *Info, address: usize) !*Module { for (self.modules.items) |*module| { if (address >= module.base_address and address < module.base_address + module.size) { if (self.address_map.get(module.base_address)) |obj_di| { return obj_di; } - const obj_di = try self.allocator.create(ModuleDebugInfo); + const obj_di = try self.allocator.create(Module); errdefer self.allocator.destroy(obj_di); const mapped_module = @as([*]const u8, @ptrFromInt(module.base_address))[0..module.size]; @@ -390,7 +390,7 @@ fn lookupModuleNameDl(self: *Info, address: usize) ?[]const u8 { return null; } -fn lookupModuleDl(self: *Info, address: usize) !*ModuleDebugInfo { +fn lookupModuleDl(self: *Info, address: usize) !*Module { var ctx: struct { // Input address: usize, @@ -458,7 +458,7 @@ fn lookupModuleDl(self: *Info, address: usize) !*ModuleDebugInfo { return obj_di; } - const obj_di = try self.allocator.create(ModuleDebugInfo); + const obj_di = try self.allocator.create(Module); errdefer self.allocator.destroy(obj_di); var sections: Dwarf.SectionArray = Dwarf.null_section_array; @@ -484,19 +484,19 @@ fn lookupModuleDl(self: *Info, address: usize) !*ModuleDebugInfo { return obj_di; } -fn lookupModuleHaiku(self: *Info, address: usize) !*ModuleDebugInfo { +fn lookupModuleHaiku(self: *Info, address: usize) !*Module { _ = self; _ = address; @panic("TODO implement lookup module for Haiku"); } -fn lookupModuleWasm(self: *Info, address: usize) !*ModuleDebugInfo { +fn lookupModuleWasm(self: *Info, address: usize) !*Module { _ = self; _ = address; @panic("TODO implement lookup module for Wasm"); } -pub const ModuleDebugInfo = switch (native_os) { +pub const Module = switch (native_os) { .macos, .ios, .watchos, .tvos, .visionos => struct { base_address: usize, vmaddr_slide: usize, @@ -861,7 +861,7 @@ pub const WindowsModuleInfo = struct { /// This takes ownership of macho_file: users of this function should not close /// it themselves, even on error. /// TODO it's weird to take ownership even on error, rework this code. -fn readMachODebugInfo(allocator: Allocator, macho_file: File) !ModuleDebugInfo { +fn readMachODebugInfo(allocator: Allocator, macho_file: File) !Module { const mapped_mem = try mapWholeFile(macho_file); const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_mem.ptr)); @@ -975,19 +975,19 @@ fn readMachODebugInfo(allocator: Allocator, macho_file: File) !ModuleDebugInfo { // This sort is so that we can binary search later. mem.sort(MachoSymbol, symbols, {}, MachoSymbol.addressLessThan); - return ModuleDebugInfo{ + return .{ .base_address = undefined, .vmaddr_slide = undefined, .mapped_memory = mapped_mem, - .ofiles = ModuleDebugInfo.OFileTable.init(allocator), + .ofiles = Module.OFileTable.init(allocator), .symbols = symbols, .strings = strings, }; } -fn readCoffDebugInfo(allocator: Allocator, coff_obj: *coff.Coff) !ModuleDebugInfo { +fn readCoffDebugInfo(allocator: Allocator, coff_obj: *coff.Coff) !Module { nosuspend { - var di = ModuleDebugInfo{ + var di: Module = .{ .base_address = undefined, .coff_image_base = coff_obj.getImageBase(), .coff_section_headers = undefined, @@ -1062,7 +1062,7 @@ pub fn readElfDebugInfo( expected_crc: ?u32, parent_sections: *Dwarf.SectionArray, parent_mapped_mem: ?[]align(mem.page_size) const u8, -) !ModuleDebugInfo { +) !Module { nosuspend { const elf_file = (if (elf_filename) |filename| blk: { break :blk fs.cwd().openFile(filename, .{}); @@ -1236,7 +1236,7 @@ pub fn readElfDebugInfo( try Dwarf.open(&di, allocator); - return ModuleDebugInfo{ + return .{ .base_address = undefined, .dwarf = di, .mapped_memory = parent_mapped_mem orelse mapped_mem, From 290966c2497dc9d212bf9d4bd0fecee4988091a5 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 1 Aug 2024 16:31:03 -0700 Subject: [PATCH 113/266] std.debug: rename Info to SelfInfo This code has the hard-coded goal of supporting the executable's own debug information and makes design choices along that goal, such as memory-mapping the inputs, using dl_iterate_phdr, and doing conditional compilation on the host target. A more general-purpose implementation of debug information may be able to share code with this, but there are some fundamental incompatibilities. For example, the "SelfInfo" implementation wants to avoid bloating the binary with PDB on POSIX systems, and likewise DWARF on Windows systems, while a general-purpose implementation needs to support both PDB and DWARF from the same binary. It might, for example, inspect the debug information from a cross-compiled binary. `SourceLocation` now lives at `std.debug.SourceLocation` and is documented. Deprecate `std.debug.runtime_safety` because it returns the optimization mode of the standard library, when the caller probably wants to use the optimization mode of their own module. `std.pdb.Pdb` is moved to `std.debug.Pdb`, mirroring the recent extraction of `std.debug.Dwarf` from `std.dwarf`. I have no idea why we have both Module (with a Windows-specific definition) and WindowsModule. I left some passive aggressive doc comments to express my frustration. --- lib/std/debug.zig | 57 ++- lib/std/debug/Dwarf.zig | 6 +- lib/std/debug/Pdb.zig | 591 ++++++++++++++++++++++ lib/std/debug/{Info.zig => SelfInfo.zig} | 66 ++- lib/std/pdb.zig | 607 +---------------------- 5 files changed, 668 insertions(+), 659 deletions(-) create mode 100644 lib/std/debug/Pdb.zig rename lib/std/debug/{Info.zig => SelfInfo.zig} (96%) diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 6dac92188e..2753a0f52f 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -6,11 +6,6 @@ const io = std.io; const posix = std.posix; const fs = std.fs; const testing = std.testing; -const elf = std.elf; -const DW = std.dwarf; -const macho = std.macho; -const coff = std.coff; -const pdb = std.pdb; const root = @import("root"); const File = std.fs.File; const windows = std.os.windows; @@ -19,8 +14,22 @@ const native_os = builtin.os.tag; const native_endian = native_arch.endian(); pub const Dwarf = @import("debug/Dwarf.zig"); -pub const Info = @import("debug/Info.zig"); +pub const Pdb = @import("debug/Pdb.zig"); +pub const SelfInfo = @import("debug/SelfInfo.zig"); +/// Unresolved source locations can be represented with a single `usize` that +/// corresponds to a virtual memory address of the program counter. Combined +/// with debug information, those values can be converted into a resolved +/// source location, including file, line, and column. +pub const SourceLocation = struct { + line: u64, + column: u64, + file_name: []const u8, +}; + +/// Deprecated because it returns the optimization mode of the standard +/// library, when the caller probably wants to use the optimization mode of +/// their own module. pub const runtime_safety = switch (builtin.mode) { .Debug, .ReleaseSafe => true, .ReleaseFast, .ReleaseSmall => false, @@ -72,13 +81,13 @@ pub fn getStderrMutex() *std.Thread.Mutex { } /// TODO multithreaded awareness -var self_debug_info: ?Info = null; +var self_debug_info: ?SelfInfo = null; -pub fn getSelfDebugInfo() !*Info { +pub fn getSelfDebugInfo() !*SelfInfo { if (self_debug_info) |*info| { return info; } else { - self_debug_info = try Info.openSelf(getDebugInfoAllocator()); + self_debug_info = try SelfInfo.openSelf(getDebugInfoAllocator()); return &self_debug_info.?; } } @@ -316,7 +325,7 @@ pub fn captureStackTrace(first_address: ?usize, stack_trace: *std.builtin.StackT stack_trace.index = slice.len; } else { // TODO: This should use the DWARF unwinder if .eh_frame_hdr is available (so that full debug info parsing isn't required). - // A new path for loading Info needs to be created which will only attempt to parse in-memory sections, because + // A new path for loading SelfInfo needs to be created which will only attempt to parse in-memory sections, because // stopping to load other debug info (ie. source line info) from disk here is not required for unwinding. var it = StackIterator.init(first_address, null); defer it.deinit(); @@ -494,7 +503,7 @@ pub fn writeStackTrace( stack_trace: std.builtin.StackTrace, out_stream: anytype, allocator: mem.Allocator, - debug_info: *Info, + debug_info: *SelfInfo, tty_config: io.tty.Config, ) !void { _ = allocator; @@ -531,11 +540,11 @@ pub const StackIterator = struct { fp: usize, ma: MemoryAccessor = MemoryAccessor.init, - // When Info and a register context is available, this iterator can unwind + // When SelfInfo and a register context is available, this iterator can unwind // stacks with frames that don't use a frame pointer (ie. -fomit-frame-pointer), // using DWARF and MachO unwind info. unwind_state: if (have_ucontext) ?struct { - debug_info: *Info, + debug_info: *SelfInfo, dwarf_context: Dwarf.UnwindContext, last_error: ?UnwindError = null, failed: bool = false, @@ -560,7 +569,7 @@ pub const StackIterator = struct { }; } - pub fn initWithContext(first_address: ?usize, debug_info: *Info, context: *const posix.ucontext_t) !StackIterator { + pub fn initWithContext(first_address: ?usize, debug_info: *SelfInfo, context: *const posix.ucontext_t) !StackIterator { // The implementation of DWARF unwinding on aarch64-macos is not complete. However, Apple mandates that // the frame pointer register is always used, so on this platform we can safely use the FP-based unwinder. if (comptime builtin.target.isDarwin() and native_arch == .aarch64) { @@ -820,7 +829,7 @@ const have_msync = switch (native_os) { pub fn writeCurrentStackTrace( out_stream: anytype, - debug_info: *Info, + debug_info: *SelfInfo, tty_config: io.tty.Config, start_addr: ?usize, ) !void { @@ -906,7 +915,7 @@ pub noinline fn walkStackWindows(addresses: []usize, existing_context: ?*const w pub fn writeStackTraceWindows( out_stream: anytype, - debug_info: *Info, + debug_info: *SelfInfo, tty_config: io.tty.Config, context: *const windows.CONTEXT, start_addr: ?usize, @@ -925,7 +934,7 @@ pub fn writeStackTraceWindows( } } -fn printUnknownSource(debug_info: *Info, out_stream: anytype, address: usize, tty_config: io.tty.Config) !void { +fn printUnknownSource(debug_info: *SelfInfo, out_stream: anytype, address: usize, tty_config: io.tty.Config) !void { const module_name = debug_info.getModuleNameForAddress(address); return printLineInfo( out_stream, @@ -938,14 +947,14 @@ fn printUnknownSource(debug_info: *Info, out_stream: anytype, address: usize, tt ); } -fn printLastUnwindError(it: *StackIterator, debug_info: *Info, out_stream: anytype, tty_config: io.tty.Config) void { +fn printLastUnwindError(it: *StackIterator, debug_info: *SelfInfo, out_stream: anytype, tty_config: io.tty.Config) void { if (!have_ucontext) return; if (it.getLastError()) |unwind_error| { printUnwindError(debug_info, out_stream, unwind_error.address, unwind_error.err, tty_config) catch {}; } } -fn printUnwindError(debug_info: *Info, out_stream: anytype, address: usize, err: UnwindError, tty_config: io.tty.Config) !void { +fn printUnwindError(debug_info: *SelfInfo, out_stream: anytype, address: usize, err: UnwindError, tty_config: io.tty.Config) !void { const module_name = debug_info.getModuleNameForAddress(address) orelse "???"; try tty_config.setColor(out_stream, .dim); if (err == error.MissingDebugInfo) { @@ -956,7 +965,7 @@ fn printUnwindError(debug_info: *Info, out_stream: anytype, address: usize, err: try tty_config.setColor(out_stream, .reset); } -pub fn printSourceAtAddress(debug_info: *Info, out_stream: anytype, address: usize, tty_config: io.tty.Config) !void { +pub fn printSourceAtAddress(debug_info: *SelfInfo, out_stream: anytype, address: usize, tty_config: io.tty.Config) !void { const module = debug_info.getModuleForAddress(address) catch |err| switch (err) { error.MissingDebugInfo, error.InvalidDebugInfo => return printUnknownSource(debug_info, out_stream, address, tty_config), else => return err, @@ -981,7 +990,7 @@ pub fn printSourceAtAddress(debug_info: *Info, out_stream: anytype, address: usi fn printLineInfo( out_stream: anytype, - line_info: ?Info.SourceLocation, + line_info: ?SourceLocation, address: usize, symbol_name: []const u8, compile_unit_name: []const u8, @@ -1027,7 +1036,7 @@ fn printLineInfo( } } -fn printLineFromFileAnyOs(out_stream: anytype, line_info: Info.SourceLocation) !void { +fn printLineFromFileAnyOs(out_stream: anytype, line_info: SourceLocation) !void { // Need this to always block even in async I/O mode, because this could potentially // be called from e.g. the event loop code crashing. var f = try fs.cwd().openFile(line_info.file_name, .{}); @@ -1093,7 +1102,7 @@ test printLineFromFileAnyOs { var test_dir = std.testing.tmpDir(.{}); defer test_dir.cleanup(); - // Relies on testing.tmpDir internals which is not ideal, but Info.SourceLocation requires paths. + // Relies on testing.tmpDir internals which is not ideal, but SourceLocation requires paths. const test_dir_path = try join(allocator, &.{ ".zig-cache", "tmp", test_dir.sub_path[0..] }); defer allocator.free(test_dir_path); @@ -1439,7 +1448,7 @@ test "manage resources correctly" { } const writer = std.io.null_writer; - var di = try Info.openSelf(testing.allocator); + var di = try SelfInfo.openSelf(testing.allocator); defer di.deinit(); try printSourceAtAddress(&di, writer, showMyTrace(), io.tty.detectConfig(std.io.getStdErr())); } diff --git a/lib/std/debug/Dwarf.zig b/lib/std/debug/Dwarf.zig index 353c097471..4fff2562b2 100644 --- a/lib/std/debug/Dwarf.zig +++ b/lib/std/debug/Dwarf.zig @@ -1353,7 +1353,7 @@ pub fn getLineNumberInfo( allocator: Allocator, compile_unit: CompileUnit, target_address: u64, -) !std.debug.Info.SourceLocation { +) !std.debug.SourceLocation { const compile_unit_cwd = try compile_unit.die.getAttrString(di, AT.comp_dir, di.section(.debug_line_str), compile_unit); const line_info_offset = try compile_unit.die.getAttrSecOffset(AT.stmt_list); @@ -2084,7 +2084,7 @@ const LineNumberProgram = struct { self: *LineNumberProgram, allocator: Allocator, file_entries: []const FileEntry, - ) !?std.debug.Info.SourceLocation { + ) !?std.debug.SourceLocation { if (self.prev_valid and self.target_address >= self.prev_address and self.target_address < self.address) @@ -2104,7 +2104,7 @@ const LineNumberProgram = struct { dir_name, file_entry.path, }); - return std.debug.Info.SourceLocation{ + return std.debug.SourceLocation{ .line = if (self.prev_line >= 0) @as(u64, @intCast(self.prev_line)) else 0, .column = self.prev_column, .file_name = file_name, diff --git a/lib/std/debug/Pdb.zig b/lib/std/debug/Pdb.zig new file mode 100644 index 0000000000..bdcc108c1d --- /dev/null +++ b/lib/std/debug/Pdb.zig @@ -0,0 +1,591 @@ +const std = @import("../std.zig"); +const File = std.fs.File; +const Allocator = std.mem.Allocator; +const pdb = std.pdb; + +const Pdb = @This(); + +in_file: File, +msf: Msf, +allocator: Allocator, +string_table: ?*MsfStream, +dbi: ?*MsfStream, +modules: []Module, +sect_contribs: []pdb.SectionContribEntry, +guid: [16]u8, +age: u32, + +pub const Module = struct { + mod_info: pdb.ModInfo, + module_name: []u8, + obj_file_name: []u8, + // The fields below are filled on demand. + populated: bool, + symbols: []u8, + subsect_info: []u8, + checksum_offset: ?usize, + + pub fn deinit(self: *Module, allocator: Allocator) void { + allocator.free(self.module_name); + allocator.free(self.obj_file_name); + if (self.populated) { + allocator.free(self.symbols); + allocator.free(self.subsect_info); + } + } +}; + +pub fn init(allocator: Allocator, path: []const u8) !Pdb { + const file = try std.fs.cwd().openFile(path, .{}); + errdefer file.close(); + + return .{ + .in_file = file, + .allocator = allocator, + .string_table = null, + .dbi = null, + .msf = try Msf.init(allocator, file), + .modules = &[_]Module{}, + .sect_contribs = &[_]pdb.SectionContribEntry{}, + .guid = undefined, + .age = undefined, + }; +} + +pub fn deinit(self: *Pdb) void { + self.in_file.close(); + self.msf.deinit(self.allocator); + for (self.modules) |*module| { + module.deinit(self.allocator); + } + self.allocator.free(self.modules); + self.allocator.free(self.sect_contribs); +} + +pub fn parseDbiStream(self: *Pdb) !void { + var stream = self.getStream(pdb.StreamType.Dbi) orelse + return error.InvalidDebugInfo; + const reader = stream.reader(); + + const header = try reader.readStruct(std.pdb.DbiStreamHeader); + if (header.VersionHeader != 19990903) // V70, only value observed by LLVM team + return error.UnknownPDBVersion; + // if (header.Age != age) + // return error.UnmatchingPDB; + + const mod_info_size = header.ModInfoSize; + const section_contrib_size = header.SectionContributionSize; + + var modules = std.ArrayList(Module).init(self.allocator); + errdefer modules.deinit(); + + // Module Info Substream + var mod_info_offset: usize = 0; + while (mod_info_offset != mod_info_size) { + const mod_info = try reader.readStruct(pdb.ModInfo); + var this_record_len: usize = @sizeOf(pdb.ModInfo); + + const module_name = try reader.readUntilDelimiterAlloc(self.allocator, 0, 1024); + errdefer self.allocator.free(module_name); + this_record_len += module_name.len + 1; + + const obj_file_name = try reader.readUntilDelimiterAlloc(self.allocator, 0, 1024); + errdefer self.allocator.free(obj_file_name); + this_record_len += obj_file_name.len + 1; + + if (this_record_len % 4 != 0) { + const round_to_next_4 = (this_record_len | 0x3) + 1; + const march_forward_bytes = round_to_next_4 - this_record_len; + try stream.seekBy(@as(isize, @intCast(march_forward_bytes))); + this_record_len += march_forward_bytes; + } + + try modules.append(Module{ + .mod_info = mod_info, + .module_name = module_name, + .obj_file_name = obj_file_name, + + .populated = false, + .symbols = undefined, + .subsect_info = undefined, + .checksum_offset = null, + }); + + mod_info_offset += this_record_len; + if (mod_info_offset > mod_info_size) + return error.InvalidDebugInfo; + } + + // Section Contribution Substream + var sect_contribs = std.ArrayList(pdb.SectionContribEntry).init(self.allocator); + errdefer sect_contribs.deinit(); + + var sect_cont_offset: usize = 0; + if (section_contrib_size != 0) { + const version = reader.readEnum(std.pdb.SectionContrSubstreamVersion, .little) catch |err| switch (err) { + error.InvalidValue => return error.InvalidDebugInfo, + else => |e| return e, + }; + _ = version; + sect_cont_offset += @sizeOf(u32); + } + while (sect_cont_offset != section_contrib_size) { + const entry = try sect_contribs.addOne(); + entry.* = try reader.readStruct(pdb.SectionContribEntry); + sect_cont_offset += @sizeOf(pdb.SectionContribEntry); + + if (sect_cont_offset > section_contrib_size) + return error.InvalidDebugInfo; + } + + self.modules = try modules.toOwnedSlice(); + self.sect_contribs = try sect_contribs.toOwnedSlice(); +} + +pub fn parseInfoStream(self: *Pdb) !void { + var stream = self.getStream(pdb.StreamType.Pdb) orelse + return error.InvalidDebugInfo; + const reader = stream.reader(); + + // Parse the InfoStreamHeader. + const version = try reader.readInt(u32, .little); + const signature = try reader.readInt(u32, .little); + _ = signature; + const age = try reader.readInt(u32, .little); + const guid = try reader.readBytesNoEof(16); + + if (version != 20000404) // VC70, only value observed by LLVM team + return error.UnknownPDBVersion; + + self.guid = guid; + self.age = age; + + // Find the string table. + const string_table_index = str_tab_index: { + const name_bytes_len = try reader.readInt(u32, .little); + const name_bytes = try self.allocator.alloc(u8, name_bytes_len); + defer self.allocator.free(name_bytes); + try reader.readNoEof(name_bytes); + + const HashTableHeader = extern struct { + Size: u32, + Capacity: u32, + + fn maxLoad(cap: u32) u32 { + return cap * 2 / 3 + 1; + } + }; + const hash_tbl_hdr = try reader.readStruct(HashTableHeader); + if (hash_tbl_hdr.Capacity == 0) + return error.InvalidDebugInfo; + + if (hash_tbl_hdr.Size > HashTableHeader.maxLoad(hash_tbl_hdr.Capacity)) + return error.InvalidDebugInfo; + + const present = try readSparseBitVector(&reader, self.allocator); + defer self.allocator.free(present); + if (present.len != hash_tbl_hdr.Size) + return error.InvalidDebugInfo; + const deleted = try readSparseBitVector(&reader, self.allocator); + defer self.allocator.free(deleted); + + for (present) |_| { + const name_offset = try reader.readInt(u32, .little); + const name_index = try reader.readInt(u32, .little); + if (name_offset > name_bytes.len) + return error.InvalidDebugInfo; + const name = std.mem.sliceTo(name_bytes[name_offset..], 0); + if (std.mem.eql(u8, name, "/names")) { + break :str_tab_index name_index; + } + } + return error.MissingDebugInfo; + }; + + self.string_table = self.getStreamById(string_table_index) orelse + return error.MissingDebugInfo; +} + +pub fn getSymbolName(self: *Pdb, module: *Module, address: u64) ?[]const u8 { + _ = self; + std.debug.assert(module.populated); + + var symbol_i: usize = 0; + while (symbol_i != module.symbols.len) { + const prefix = @as(*align(1) pdb.RecordPrefix, @ptrCast(&module.symbols[symbol_i])); + if (prefix.RecordLen < 2) + return null; + switch (prefix.RecordKind) { + .S_LPROC32, .S_GPROC32 => { + const proc_sym = @as(*align(1) pdb.ProcSym, @ptrCast(&module.symbols[symbol_i + @sizeOf(pdb.RecordPrefix)])); + if (address >= proc_sym.CodeOffset and address < proc_sym.CodeOffset + proc_sym.CodeSize) { + return std.mem.sliceTo(@as([*:0]u8, @ptrCast(&proc_sym.Name[0])), 0); + } + }, + else => {}, + } + symbol_i += prefix.RecordLen + @sizeOf(u16); + } + + return null; +} + +pub fn getLineNumberInfo(self: *Pdb, module: *Module, address: u64) !std.debug.SourceLocation { + std.debug.assert(module.populated); + const subsect_info = module.subsect_info; + + var sect_offset: usize = 0; + var skip_len: usize = undefined; + const checksum_offset = module.checksum_offset orelse return error.MissingDebugInfo; + while (sect_offset != subsect_info.len) : (sect_offset += skip_len) { + const subsect_hdr = @as(*align(1) pdb.DebugSubsectionHeader, @ptrCast(&subsect_info[sect_offset])); + skip_len = subsect_hdr.Length; + sect_offset += @sizeOf(pdb.DebugSubsectionHeader); + + switch (subsect_hdr.Kind) { + .Lines => { + var line_index = sect_offset; + + const line_hdr = @as(*align(1) pdb.LineFragmentHeader, @ptrCast(&subsect_info[line_index])); + if (line_hdr.RelocSegment == 0) + return error.MissingDebugInfo; + line_index += @sizeOf(pdb.LineFragmentHeader); + const frag_vaddr_start = line_hdr.RelocOffset; + const frag_vaddr_end = frag_vaddr_start + line_hdr.CodeSize; + + if (address >= frag_vaddr_start and address < frag_vaddr_end) { + // There is an unknown number of LineBlockFragmentHeaders (and their accompanying line and column records) + // from now on. We will iterate through them, and eventually find a SourceLocation that we're interested in, + // breaking out to :subsections. If not, we will make sure to not read anything outside of this subsection. + const subsection_end_index = sect_offset + subsect_hdr.Length; + + while (line_index < subsection_end_index) { + const block_hdr = @as(*align(1) pdb.LineBlockFragmentHeader, @ptrCast(&subsect_info[line_index])); + line_index += @sizeOf(pdb.LineBlockFragmentHeader); + const start_line_index = line_index; + + const has_column = line_hdr.Flags.LF_HaveColumns; + + // All line entries are stored inside their line block by ascending start address. + // Heuristic: we want to find the last line entry + // that has a vaddr_start <= address. + // This is done with a simple linear search. + var line_i: u32 = 0; + while (line_i < block_hdr.NumLines) : (line_i += 1) { + const line_num_entry = @as(*align(1) pdb.LineNumberEntry, @ptrCast(&subsect_info[line_index])); + line_index += @sizeOf(pdb.LineNumberEntry); + + const vaddr_start = frag_vaddr_start + line_num_entry.Offset; + if (address < vaddr_start) { + break; + } + } + + // line_i == 0 would mean that no matching pdb.LineNumberEntry was found. + if (line_i > 0) { + const subsect_index = checksum_offset + block_hdr.NameIndex; + const chksum_hdr = @as(*align(1) pdb.FileChecksumEntryHeader, @ptrCast(&module.subsect_info[subsect_index])); + const strtab_offset = @sizeOf(pdb.StringTableHeader) + chksum_hdr.FileNameOffset; + try self.string_table.?.seekTo(strtab_offset); + const source_file_name = try self.string_table.?.reader().readUntilDelimiterAlloc(self.allocator, 0, 1024); + + const line_entry_idx = line_i - 1; + + const column = if (has_column) blk: { + const start_col_index = start_line_index + @sizeOf(pdb.LineNumberEntry) * block_hdr.NumLines; + const col_index = start_col_index + @sizeOf(pdb.ColumnNumberEntry) * line_entry_idx; + const col_num_entry = @as(*align(1) pdb.ColumnNumberEntry, @ptrCast(&subsect_info[col_index])); + break :blk col_num_entry.StartColumn; + } else 0; + + const found_line_index = start_line_index + line_entry_idx * @sizeOf(pdb.LineNumberEntry); + const line_num_entry: *align(1) pdb.LineNumberEntry = @ptrCast(&subsect_info[found_line_index]); + const flags: *align(1) pdb.LineNumberEntry.Flags = @ptrCast(&line_num_entry.Flags); + + return .{ + .file_name = source_file_name, + .line = flags.Start, + .column = column, + }; + } + } + + // Checking that we are not reading garbage after the (possibly) multiple block fragments. + if (line_index != subsection_end_index) { + return error.InvalidDebugInfo; + } + } + }, + else => {}, + } + + if (sect_offset > subsect_info.len) + return error.InvalidDebugInfo; + } + + return error.MissingDebugInfo; +} + +pub fn getModule(self: *Pdb, index: usize) !?*Module { + if (index >= self.modules.len) + return null; + + const mod = &self.modules[index]; + if (mod.populated) + return mod; + + // At most one can be non-zero. + if (mod.mod_info.C11ByteSize != 0 and mod.mod_info.C13ByteSize != 0) + return error.InvalidDebugInfo; + if (mod.mod_info.C13ByteSize == 0) + return error.InvalidDebugInfo; + + const stream = self.getStreamById(mod.mod_info.ModuleSymStream) orelse + return error.MissingDebugInfo; + const reader = stream.reader(); + + const signature = try reader.readInt(u32, .little); + if (signature != 4) + return error.InvalidDebugInfo; + + mod.symbols = try self.allocator.alloc(u8, mod.mod_info.SymByteSize - 4); + errdefer self.allocator.free(mod.symbols); + try reader.readNoEof(mod.symbols); + + mod.subsect_info = try self.allocator.alloc(u8, mod.mod_info.C13ByteSize); + errdefer self.allocator.free(mod.subsect_info); + try reader.readNoEof(mod.subsect_info); + + var sect_offset: usize = 0; + var skip_len: usize = undefined; + while (sect_offset != mod.subsect_info.len) : (sect_offset += skip_len) { + const subsect_hdr = @as(*align(1) pdb.DebugSubsectionHeader, @ptrCast(&mod.subsect_info[sect_offset])); + skip_len = subsect_hdr.Length; + sect_offset += @sizeOf(pdb.DebugSubsectionHeader); + + switch (subsect_hdr.Kind) { + .FileChecksums => { + mod.checksum_offset = sect_offset; + break; + }, + else => {}, + } + + if (sect_offset > mod.subsect_info.len) + return error.InvalidDebugInfo; + } + + mod.populated = true; + return mod; +} + +pub fn getStreamById(self: *Pdb, id: u32) ?*MsfStream { + if (id >= self.msf.streams.len) + return null; + return &self.msf.streams[id]; +} + +pub fn getStream(self: *Pdb, stream: pdb.StreamType) ?*MsfStream { + const id = @intFromEnum(stream); + return self.getStreamById(id); +} + +/// https://llvm.org/docs/PDB/MsfFile.html +const Msf = struct { + directory: MsfStream, + streams: []MsfStream, + + fn init(allocator: Allocator, file: File) !Msf { + const in = file.reader(); + + const superblock = try in.readStruct(pdb.SuperBlock); + + // Sanity checks + if (!std.mem.eql(u8, &superblock.FileMagic, pdb.SuperBlock.file_magic)) + return error.InvalidDebugInfo; + if (superblock.FreeBlockMapBlock != 1 and superblock.FreeBlockMapBlock != 2) + return error.InvalidDebugInfo; + const file_len = try file.getEndPos(); + if (superblock.NumBlocks * superblock.BlockSize != file_len) + return error.InvalidDebugInfo; + switch (superblock.BlockSize) { + // llvm only supports 4096 but we can handle any of these values + 512, 1024, 2048, 4096 => {}, + else => return error.InvalidDebugInfo, + } + + const dir_block_count = blockCountFromSize(superblock.NumDirectoryBytes, superblock.BlockSize); + if (dir_block_count > superblock.BlockSize / @sizeOf(u32)) + return error.UnhandledBigDirectoryStream; // cf. BlockMapAddr comment. + + try file.seekTo(superblock.BlockSize * superblock.BlockMapAddr); + const dir_blocks = try allocator.alloc(u32, dir_block_count); + for (dir_blocks) |*b| { + b.* = try in.readInt(u32, .little); + } + var directory = MsfStream.init( + superblock.BlockSize, + file, + dir_blocks, + ); + + const begin = directory.pos; + const stream_count = try directory.reader().readInt(u32, .little); + const stream_sizes = try allocator.alloc(u32, stream_count); + defer allocator.free(stream_sizes); + + // Microsoft's implementation uses @as(u32, -1) for inexistent streams. + // These streams are not used, but still participate in the file + // and must be taken into account when resolving stream indices. + const Nil = 0xFFFFFFFF; + for (stream_sizes) |*s| { + const size = try directory.reader().readInt(u32, .little); + s.* = if (size == Nil) 0 else blockCountFromSize(size, superblock.BlockSize); + } + + const streams = try allocator.alloc(MsfStream, stream_count); + for (streams, 0..) |*stream, i| { + const size = stream_sizes[i]; + if (size == 0) { + stream.* = MsfStream{ + .blocks = &[_]u32{}, + }; + } else { + var blocks = try allocator.alloc(u32, size); + var j: u32 = 0; + while (j < size) : (j += 1) { + const block_id = try directory.reader().readInt(u32, .little); + const n = (block_id % superblock.BlockSize); + // 0 is for pdb.SuperBlock, 1 and 2 for FPMs. + if (block_id == 0 or n == 1 or n == 2 or block_id * superblock.BlockSize > file_len) + return error.InvalidBlockIndex; + blocks[j] = block_id; + } + + stream.* = MsfStream.init( + superblock.BlockSize, + file, + blocks, + ); + } + } + + const end = directory.pos; + if (end - begin != superblock.NumDirectoryBytes) + return error.InvalidStreamDirectory; + + return Msf{ + .directory = directory, + .streams = streams, + }; + } + + fn deinit(self: *Msf, allocator: Allocator) void { + allocator.free(self.directory.blocks); + for (self.streams) |*stream| { + allocator.free(stream.blocks); + } + allocator.free(self.streams); + } +}; + +const MsfStream = struct { + in_file: File = undefined, + pos: u64 = undefined, + blocks: []u32 = undefined, + block_size: u32 = undefined, + + pub const Error = @typeInfo(@typeInfo(@TypeOf(read)).Fn.return_type.?).ErrorUnion.error_set; + + fn init(block_size: u32, file: File, blocks: []u32) MsfStream { + const stream = MsfStream{ + .in_file = file, + .pos = 0, + .blocks = blocks, + .block_size = block_size, + }; + + return stream; + } + + fn read(self: *MsfStream, buffer: []u8) !usize { + var block_id = @as(usize, @intCast(self.pos / self.block_size)); + if (block_id >= self.blocks.len) return 0; // End of Stream + var block = self.blocks[block_id]; + var offset = self.pos % self.block_size; + + try self.in_file.seekTo(block * self.block_size + offset); + const in = self.in_file.reader(); + + var size: usize = 0; + var rem_buffer = buffer; + while (size < buffer.len) { + const size_to_read = @min(self.block_size - offset, rem_buffer.len); + size += try in.read(rem_buffer[0..size_to_read]); + rem_buffer = buffer[size..]; + offset += size_to_read; + + // If we're at the end of a block, go to the next one. + if (offset == self.block_size) { + offset = 0; + block_id += 1; + if (block_id >= self.blocks.len) break; // End of Stream + block = self.blocks[block_id]; + try self.in_file.seekTo(block * self.block_size); + } + } + + self.pos += buffer.len; + return buffer.len; + } + + pub fn seekBy(self: *MsfStream, len: i64) !void { + self.pos = @as(u64, @intCast(@as(i64, @intCast(self.pos)) + len)); + if (self.pos >= self.blocks.len * self.block_size) + return error.EOF; + } + + pub fn seekTo(self: *MsfStream, len: u64) !void { + self.pos = len; + if (self.pos >= self.blocks.len * self.block_size) + return error.EOF; + } + + fn getSize(self: *const MsfStream) u64 { + return self.blocks.len * self.block_size; + } + + fn getFilePos(self: MsfStream) u64 { + const block_id = self.pos / self.block_size; + const block = self.blocks[block_id]; + const offset = self.pos % self.block_size; + + return block * self.block_size + offset; + } + + pub fn reader(self: *MsfStream) std.io.Reader(*MsfStream, Error, read) { + return .{ .context = self }; + } +}; + +fn readSparseBitVector(stream: anytype, allocator: Allocator) ![]u32 { + const num_words = try stream.readInt(u32, .little); + var list = std.ArrayList(u32).init(allocator); + errdefer list.deinit(); + var word_i: u32 = 0; + while (word_i != num_words) : (word_i += 1) { + const word = try stream.readInt(u32, .little); + var bit_i: u5 = 0; + while (true) : (bit_i += 1) { + if (word & (@as(u32, 1) << bit_i) != 0) { + try list.append(word_i * 32 + bit_i); + } + if (bit_i == std.math.maxInt(u5)) break; + } + } + return try list.toOwnedSlice(); +} + +fn blockCountFromSize(size: u32, block_size: u32) u32 { + return (size + block_size - 1) / block_size; +} diff --git a/lib/std/debug/Info.zig b/lib/std/debug/SelfInfo.zig similarity index 96% rename from lib/std/debug/Info.zig rename to lib/std/debug/SelfInfo.zig index 9d3074834b..58fe4b23b2 100644 --- a/lib/std/debug/Info.zig +++ b/lib/std/debug/SelfInfo.zig @@ -1,4 +1,5 @@ -//! Cross-platform abstraction for debug information. +//! Cross-platform abstraction for this binary's own debug information, with a +//! goal of minimal code bloat and compilation speed penalty. const builtin = @import("builtin"); const native_os = builtin.os.tag; @@ -17,24 +18,25 @@ const assert = std.debug.assert; const posix = std.posix; const elf = std.elf; const Dwarf = std.debug.Dwarf; +const Pdb = std.debug.Pdb; const File = std.fs.File; const math = std.math; const testing = std.testing; -const Info = @This(); +const SelfInfo = @This(); const root = @import("root"); allocator: Allocator, address_map: std.AutoHashMap(usize, *Module), -modules: if (native_os == .windows) std.ArrayListUnmanaged(WindowsModuleInfo) else void, +modules: if (native_os == .windows) std.ArrayListUnmanaged(WindowsModule) else void, pub const OpenSelfError = error{ MissingDebugInfo, UnsupportedOperatingSystem, -} || @typeInfo(@typeInfo(@TypeOf(Info.init)).Fn.return_type.?).ErrorUnion.error_set; +} || @typeInfo(@typeInfo(@TypeOf(SelfInfo.init)).Fn.return_type.?).ErrorUnion.error_set; -pub fn openSelf(allocator: Allocator) OpenSelfError!Info { +pub fn openSelf(allocator: Allocator) OpenSelfError!SelfInfo { nosuspend { if (builtin.strip_debug_info) return error.MissingDebugInfo; @@ -51,14 +53,14 @@ pub fn openSelf(allocator: Allocator) OpenSelfError!Info { .solaris, .illumos, .windows, - => return try Info.init(allocator), + => return try SelfInfo.init(allocator), else => return error.UnsupportedOperatingSystem, } } } -pub fn init(allocator: Allocator) !Info { - var debug_info: Info = .{ +pub fn init(allocator: Allocator) !SelfInfo { + var debug_info: SelfInfo = .{ .allocator = allocator, .address_map = std.AutoHashMap(usize, *Module).init(allocator), .modules = if (native_os == .windows) .{} else {}, @@ -101,7 +103,7 @@ pub fn init(allocator: Allocator) !Info { return debug_info; } -pub fn deinit(self: *Info) void { +pub fn deinit(self: *SelfInfo) void { var it = self.address_map.iterator(); while (it.next()) |entry| { const mdi = entry.value_ptr.*; @@ -118,7 +120,7 @@ pub fn deinit(self: *Info) void { } } -pub fn getModuleForAddress(self: *Info, address: usize) !*Module { +pub fn getModuleForAddress(self: *SelfInfo, address: usize) !*Module { if (comptime builtin.target.isDarwin()) { return self.lookupModuleDyld(address); } else if (native_os == .windows) { @@ -135,7 +137,7 @@ pub fn getModuleForAddress(self: *Info, address: usize) !*Module { // Returns the module name for a given address. // This can be called when getModuleForAddress fails, so implementations should provide // a path that doesn't rely on any side-effects of a prior successful module lookup. -pub fn getModuleNameForAddress(self: *Info, address: usize) ?[]const u8 { +pub fn getModuleNameForAddress(self: *SelfInfo, address: usize) ?[]const u8 { if (comptime builtin.target.isDarwin()) { return self.lookupModuleNameDyld(address); } else if (native_os == .windows) { @@ -149,7 +151,7 @@ pub fn getModuleNameForAddress(self: *Info, address: usize) ?[]const u8 { } } -fn lookupModuleDyld(self: *Info, address: usize) !*Module { +fn lookupModuleDyld(self: *SelfInfo, address: usize) !*Module { const image_count = std.c._dyld_image_count(); var i: u32 = 0; @@ -215,7 +217,7 @@ fn lookupModuleDyld(self: *Info, address: usize) !*Module { return error.MissingDebugInfo; } -fn lookupModuleNameDyld(self: *Info, address: usize) ?[]const u8 { +fn lookupModuleNameDyld(self: *SelfInfo, address: usize) ?[]const u8 { _ = self; const image_count = std.c._dyld_image_count(); @@ -253,7 +255,7 @@ fn lookupModuleNameDyld(self: *Info, address: usize) ?[]const u8 { return null; } -fn lookupModuleWin32(self: *Info, address: usize) !*Module { +fn lookupModuleWin32(self: *SelfInfo, address: usize) !*Module { for (self.modules.items) |*module| { if (address >= module.base_address and address < module.base_address + module.size) { if (self.address_map.get(module.base_address)) |obj_di| { @@ -343,7 +345,7 @@ fn lookupModuleWin32(self: *Info, address: usize) !*Module { return error.MissingDebugInfo; } -fn lookupModuleNameWin32(self: *Info, address: usize) ?[]const u8 { +fn lookupModuleNameWin32(self: *SelfInfo, address: usize) ?[]const u8 { for (self.modules.items) |module| { if (address >= module.base_address and address < module.base_address + module.size) { return module.name; @@ -352,7 +354,7 @@ fn lookupModuleNameWin32(self: *Info, address: usize) ?[]const u8 { return null; } -fn lookupModuleNameDl(self: *Info, address: usize) ?[]const u8 { +fn lookupModuleNameDl(self: *SelfInfo, address: usize) ?[]const u8 { _ = self; var ctx: struct { @@ -390,7 +392,7 @@ fn lookupModuleNameDl(self: *Info, address: usize) ?[]const u8 { return null; } -fn lookupModuleDl(self: *Info, address: usize) !*Module { +fn lookupModuleDl(self: *SelfInfo, address: usize) !*Module { var ctx: struct { // Input address: usize, @@ -484,13 +486,13 @@ fn lookupModuleDl(self: *Info, address: usize) !*Module { return obj_di; } -fn lookupModuleHaiku(self: *Info, address: usize) !*Module { +fn lookupModuleHaiku(self: *SelfInfo, address: usize) !*Module { _ = self; _ = address; @panic("TODO implement lookup module for Haiku"); } -fn lookupModuleWasm(self: *Info, address: usize) !*Module { +fn lookupModuleWasm(self: *SelfInfo, address: usize) !*Module { _ = self; _ = address; @panic("TODO implement lookup module for Wasm"); @@ -709,7 +711,7 @@ pub const Module = switch (native_os) { }, .uefi, .windows => struct { base_address: usize, - pdb: ?pdb.Pdb = null, + pdb: ?Pdb = null, dwarf: ?Dwarf = null, coff_image_base: u64, @@ -837,7 +839,11 @@ pub const Module = switch (native_os) { else => Dwarf, }; -pub const WindowsModuleInfo = struct { +/// How is this different than `Module` when the host is Windows? +/// Why are both stored in the `SelfInfo` struct? +/// Boy, it sure would be nice if someone added documentation comments for this +/// struct explaining it. +pub const WindowsModule = struct { base_address: usize, size: u32, name: []const u8, @@ -1030,7 +1036,7 @@ fn readCoffDebugInfo(allocator: Allocator, coff_obj: *coff.Coff) !Module { }; defer if (path.ptr != raw_path.ptr) allocator.free(path); - di.pdb = pdb.Pdb.init(allocator, path) catch |err| switch (err) { + di.pdb = Pdb.init(allocator, path) catch |err| switch (err) { error.FileNotFound, error.IsDir => { if (di.dwarf == null) return error.MissingDebugInfo; return di; @@ -1292,22 +1298,10 @@ fn chopSlice(ptr: []const u8, offset: u64, size: u64) error{Overflow}![]const u8 pub const SymbolInfo = struct { symbol_name: []const u8 = "???", compile_unit_name: []const u8 = "???", - line_info: ?SourceLocation = null, + line_info: ?std.debug.SourceLocation = null, pub fn deinit(self: SymbolInfo, allocator: Allocator) void { - if (self.line_info) |li| { - li.deinit(allocator); - } - } -}; - -pub const SourceLocation = struct { - line: u64, - column: u64, - file_name: []const u8, - - pub fn deinit(self: SourceLocation, allocator: Allocator) void { - allocator.free(self.file_name); + if (self.line_info) |li| allocator.free(li.file_name); } }; diff --git a/lib/std/pdb.zig b/lib/std/pdb.zig index c96eb81fa9..31ad02e945 100644 --- a/lib/std/pdb.zig +++ b/lib/std/pdb.zig @@ -1,3 +1,12 @@ +//! Program Data Base debugging information format. +//! +//! This namespace contains unopinionated types and data definitions only. For +//! an implementation of parsing and caching PDB information, see +//! `std.debug.Pdb`. +//! +//! Most of this is based on information gathered from LLVM source code, +//! documentation and/or contributors. + const std = @import("std.zig"); const io = std.io; const math = std.math; @@ -9,10 +18,7 @@ const debug = std.debug; const ArrayList = std.ArrayList; -// Note: most of this is based on information gathered from LLVM source code, -// documentation and/or contributors. - -// https://llvm.org/docs/PDB/DbiStream.html#stream-header +/// https://llvm.org/docs/PDB/DbiStream.html#stream-header pub const DbiStreamHeader = extern struct { VersionSignature: i32, VersionHeader: u32, @@ -415,10 +421,8 @@ pub const ColumnNumberEntry = extern struct { pub const FileChecksumEntryHeader = extern struct { /// Byte offset of filename in global string table. FileNameOffset: u32, - /// Number of bytes of checksum. ChecksumSize: u8, - /// FileChecksumKind ChecksumKind: u8, }; @@ -451,525 +455,15 @@ pub const DebugSubsectionHeader = extern struct { Length: u32, }; -pub const PDBStringTableHeader = extern struct { +pub const StringTableHeader = extern struct { /// PDBStringTableSignature Signature: u32, - /// 1 or 2 HashVersion: u32, - /// Number of bytes of names buffer. ByteSize: u32, }; -fn readSparseBitVector(stream: anytype, allocator: mem.Allocator) ![]u32 { - const num_words = try stream.readInt(u32, .little); - var list = ArrayList(u32).init(allocator); - errdefer list.deinit(); - var word_i: u32 = 0; - while (word_i != num_words) : (word_i += 1) { - const word = try stream.readInt(u32, .little); - var bit_i: u5 = 0; - while (true) : (bit_i += 1) { - if (word & (@as(u32, 1) << bit_i) != 0) { - try list.append(word_i * 32 + bit_i); - } - if (bit_i == std.math.maxInt(u5)) break; - } - } - return try list.toOwnedSlice(); -} - -pub const Pdb = struct { - in_file: File, - msf: Msf, - allocator: mem.Allocator, - string_table: ?*MsfStream, - dbi: ?*MsfStream, - modules: []Module, - sect_contribs: []SectionContribEntry, - guid: [16]u8, - age: u32, - - pub const Module = struct { - mod_info: ModInfo, - module_name: []u8, - obj_file_name: []u8, - // The fields below are filled on demand. - populated: bool, - symbols: []u8, - subsect_info: []u8, - checksum_offset: ?usize, - - pub fn deinit(self: *Module, allocator: mem.Allocator) void { - allocator.free(self.module_name); - allocator.free(self.obj_file_name); - if (self.populated) { - allocator.free(self.symbols); - allocator.free(self.subsect_info); - } - } - }; - - pub fn init(allocator: mem.Allocator, path: []const u8) !Pdb { - const file = try fs.cwd().openFile(path, .{}); - errdefer file.close(); - - return Pdb{ - .in_file = file, - .allocator = allocator, - .string_table = null, - .dbi = null, - .msf = try Msf.init(allocator, file), - .modules = &[_]Module{}, - .sect_contribs = &[_]SectionContribEntry{}, - .guid = undefined, - .age = undefined, - }; - } - - pub fn deinit(self: *Pdb) void { - self.in_file.close(); - self.msf.deinit(self.allocator); - for (self.modules) |*module| { - module.deinit(self.allocator); - } - self.allocator.free(self.modules); - self.allocator.free(self.sect_contribs); - } - - pub fn parseDbiStream(self: *Pdb) !void { - var stream = self.getStream(StreamType.Dbi) orelse - return error.InvalidDebugInfo; - const reader = stream.reader(); - - const header = try reader.readStruct(DbiStreamHeader); - if (header.VersionHeader != 19990903) // V70, only value observed by LLVM team - return error.UnknownPDBVersion; - // if (header.Age != age) - // return error.UnmatchingPDB; - - const mod_info_size = header.ModInfoSize; - const section_contrib_size = header.SectionContributionSize; - - var modules = ArrayList(Module).init(self.allocator); - errdefer modules.deinit(); - - // Module Info Substream - var mod_info_offset: usize = 0; - while (mod_info_offset != mod_info_size) { - const mod_info = try reader.readStruct(ModInfo); - var this_record_len: usize = @sizeOf(ModInfo); - - const module_name = try reader.readUntilDelimiterAlloc(self.allocator, 0, 1024); - errdefer self.allocator.free(module_name); - this_record_len += module_name.len + 1; - - const obj_file_name = try reader.readUntilDelimiterAlloc(self.allocator, 0, 1024); - errdefer self.allocator.free(obj_file_name); - this_record_len += obj_file_name.len + 1; - - if (this_record_len % 4 != 0) { - const round_to_next_4 = (this_record_len | 0x3) + 1; - const march_forward_bytes = round_to_next_4 - this_record_len; - try stream.seekBy(@as(isize, @intCast(march_forward_bytes))); - this_record_len += march_forward_bytes; - } - - try modules.append(Module{ - .mod_info = mod_info, - .module_name = module_name, - .obj_file_name = obj_file_name, - - .populated = false, - .symbols = undefined, - .subsect_info = undefined, - .checksum_offset = null, - }); - - mod_info_offset += this_record_len; - if (mod_info_offset > mod_info_size) - return error.InvalidDebugInfo; - } - - // Section Contribution Substream - var sect_contribs = ArrayList(SectionContribEntry).init(self.allocator); - errdefer sect_contribs.deinit(); - - var sect_cont_offset: usize = 0; - if (section_contrib_size != 0) { - const version = reader.readEnum(SectionContrSubstreamVersion, .little) catch |err| switch (err) { - error.InvalidValue => return error.InvalidDebugInfo, - else => |e| return e, - }; - _ = version; - sect_cont_offset += @sizeOf(u32); - } - while (sect_cont_offset != section_contrib_size) { - const entry = try sect_contribs.addOne(); - entry.* = try reader.readStruct(SectionContribEntry); - sect_cont_offset += @sizeOf(SectionContribEntry); - - if (sect_cont_offset > section_contrib_size) - return error.InvalidDebugInfo; - } - - self.modules = try modules.toOwnedSlice(); - self.sect_contribs = try sect_contribs.toOwnedSlice(); - } - - pub fn parseInfoStream(self: *Pdb) !void { - var stream = self.getStream(StreamType.Pdb) orelse - return error.InvalidDebugInfo; - const reader = stream.reader(); - - // Parse the InfoStreamHeader. - const version = try reader.readInt(u32, .little); - const signature = try reader.readInt(u32, .little); - _ = signature; - const age = try reader.readInt(u32, .little); - const guid = try reader.readBytesNoEof(16); - - if (version != 20000404) // VC70, only value observed by LLVM team - return error.UnknownPDBVersion; - - self.guid = guid; - self.age = age; - - // Find the string table. - const string_table_index = str_tab_index: { - const name_bytes_len = try reader.readInt(u32, .little); - const name_bytes = try self.allocator.alloc(u8, name_bytes_len); - defer self.allocator.free(name_bytes); - try reader.readNoEof(name_bytes); - - const HashTableHeader = extern struct { - Size: u32, - Capacity: u32, - - fn maxLoad(cap: u32) u32 { - return cap * 2 / 3 + 1; - } - }; - const hash_tbl_hdr = try reader.readStruct(HashTableHeader); - if (hash_tbl_hdr.Capacity == 0) - return error.InvalidDebugInfo; - - if (hash_tbl_hdr.Size > HashTableHeader.maxLoad(hash_tbl_hdr.Capacity)) - return error.InvalidDebugInfo; - - const present = try readSparseBitVector(&reader, self.allocator); - defer self.allocator.free(present); - if (present.len != hash_tbl_hdr.Size) - return error.InvalidDebugInfo; - const deleted = try readSparseBitVector(&reader, self.allocator); - defer self.allocator.free(deleted); - - for (present) |_| { - const name_offset = try reader.readInt(u32, .little); - const name_index = try reader.readInt(u32, .little); - if (name_offset > name_bytes.len) - return error.InvalidDebugInfo; - const name = mem.sliceTo(name_bytes[name_offset..], 0); - if (mem.eql(u8, name, "/names")) { - break :str_tab_index name_index; - } - } - return error.MissingDebugInfo; - }; - - self.string_table = self.getStreamById(string_table_index) orelse - return error.MissingDebugInfo; - } - - pub fn getSymbolName(self: *Pdb, module: *Module, address: u64) ?[]const u8 { - _ = self; - std.debug.assert(module.populated); - - var symbol_i: usize = 0; - while (symbol_i != module.symbols.len) { - const prefix = @as(*align(1) RecordPrefix, @ptrCast(&module.symbols[symbol_i])); - if (prefix.RecordLen < 2) - return null; - switch (prefix.RecordKind) { - .S_LPROC32, .S_GPROC32 => { - const proc_sym = @as(*align(1) ProcSym, @ptrCast(&module.symbols[symbol_i + @sizeOf(RecordPrefix)])); - if (address >= proc_sym.CodeOffset and address < proc_sym.CodeOffset + proc_sym.CodeSize) { - return mem.sliceTo(@as([*:0]u8, @ptrCast(&proc_sym.Name[0])), 0); - } - }, - else => {}, - } - symbol_i += prefix.RecordLen + @sizeOf(u16); - } - - return null; - } - - pub fn getLineNumberInfo(self: *Pdb, module: *Module, address: u64) !debug.Info.SourceLocation { - std.debug.assert(module.populated); - const subsect_info = module.subsect_info; - - var sect_offset: usize = 0; - var skip_len: usize = undefined; - const checksum_offset = module.checksum_offset orelse return error.MissingDebugInfo; - while (sect_offset != subsect_info.len) : (sect_offset += skip_len) { - const subsect_hdr = @as(*align(1) DebugSubsectionHeader, @ptrCast(&subsect_info[sect_offset])); - skip_len = subsect_hdr.Length; - sect_offset += @sizeOf(DebugSubsectionHeader); - - switch (subsect_hdr.Kind) { - .Lines => { - var line_index = sect_offset; - - const line_hdr = @as(*align(1) LineFragmentHeader, @ptrCast(&subsect_info[line_index])); - if (line_hdr.RelocSegment == 0) - return error.MissingDebugInfo; - line_index += @sizeOf(LineFragmentHeader); - const frag_vaddr_start = line_hdr.RelocOffset; - const frag_vaddr_end = frag_vaddr_start + line_hdr.CodeSize; - - if (address >= frag_vaddr_start and address < frag_vaddr_end) { - // There is an unknown number of LineBlockFragmentHeaders (and their accompanying line and column records) - // from now on. We will iterate through them, and eventually find a SourceLocation that we're interested in, - // breaking out to :subsections. If not, we will make sure to not read anything outside of this subsection. - const subsection_end_index = sect_offset + subsect_hdr.Length; - - while (line_index < subsection_end_index) { - const block_hdr = @as(*align(1) LineBlockFragmentHeader, @ptrCast(&subsect_info[line_index])); - line_index += @sizeOf(LineBlockFragmentHeader); - const start_line_index = line_index; - - const has_column = line_hdr.Flags.LF_HaveColumns; - - // All line entries are stored inside their line block by ascending start address. - // Heuristic: we want to find the last line entry - // that has a vaddr_start <= address. - // This is done with a simple linear search. - var line_i: u32 = 0; - while (line_i < block_hdr.NumLines) : (line_i += 1) { - const line_num_entry = @as(*align(1) LineNumberEntry, @ptrCast(&subsect_info[line_index])); - line_index += @sizeOf(LineNumberEntry); - - const vaddr_start = frag_vaddr_start + line_num_entry.Offset; - if (address < vaddr_start) { - break; - } - } - - // line_i == 0 would mean that no matching LineNumberEntry was found. - if (line_i > 0) { - const subsect_index = checksum_offset + block_hdr.NameIndex; - const chksum_hdr = @as(*align(1) FileChecksumEntryHeader, @ptrCast(&module.subsect_info[subsect_index])); - const strtab_offset = @sizeOf(PDBStringTableHeader) + chksum_hdr.FileNameOffset; - try self.string_table.?.seekTo(strtab_offset); - const source_file_name = try self.string_table.?.reader().readUntilDelimiterAlloc(self.allocator, 0, 1024); - - const line_entry_idx = line_i - 1; - - const column = if (has_column) blk: { - const start_col_index = start_line_index + @sizeOf(LineNumberEntry) * block_hdr.NumLines; - const col_index = start_col_index + @sizeOf(ColumnNumberEntry) * line_entry_idx; - const col_num_entry = @as(*align(1) ColumnNumberEntry, @ptrCast(&subsect_info[col_index])); - break :blk col_num_entry.StartColumn; - } else 0; - - const found_line_index = start_line_index + line_entry_idx * @sizeOf(LineNumberEntry); - const line_num_entry: *align(1) LineNumberEntry = @ptrCast(&subsect_info[found_line_index]); - const flags: *align(1) LineNumberEntry.Flags = @ptrCast(&line_num_entry.Flags); - - return debug.Info.SourceLocation{ - .file_name = source_file_name, - .line = flags.Start, - .column = column, - }; - } - } - - // Checking that we are not reading garbage after the (possibly) multiple block fragments. - if (line_index != subsection_end_index) { - return error.InvalidDebugInfo; - } - } - }, - else => {}, - } - - if (sect_offset > subsect_info.len) - return error.InvalidDebugInfo; - } - - return error.MissingDebugInfo; - } - - pub fn getModule(self: *Pdb, index: usize) !?*Module { - if (index >= self.modules.len) - return null; - - const mod = &self.modules[index]; - if (mod.populated) - return mod; - - // At most one can be non-zero. - if (mod.mod_info.C11ByteSize != 0 and mod.mod_info.C13ByteSize != 0) - return error.InvalidDebugInfo; - if (mod.mod_info.C13ByteSize == 0) - return error.InvalidDebugInfo; - - const stream = self.getStreamById(mod.mod_info.ModuleSymStream) orelse - return error.MissingDebugInfo; - const reader = stream.reader(); - - const signature = try reader.readInt(u32, .little); - if (signature != 4) - return error.InvalidDebugInfo; - - mod.symbols = try self.allocator.alloc(u8, mod.mod_info.SymByteSize - 4); - errdefer self.allocator.free(mod.symbols); - try reader.readNoEof(mod.symbols); - - mod.subsect_info = try self.allocator.alloc(u8, mod.mod_info.C13ByteSize); - errdefer self.allocator.free(mod.subsect_info); - try reader.readNoEof(mod.subsect_info); - - var sect_offset: usize = 0; - var skip_len: usize = undefined; - while (sect_offset != mod.subsect_info.len) : (sect_offset += skip_len) { - const subsect_hdr = @as(*align(1) DebugSubsectionHeader, @ptrCast(&mod.subsect_info[sect_offset])); - skip_len = subsect_hdr.Length; - sect_offset += @sizeOf(DebugSubsectionHeader); - - switch (subsect_hdr.Kind) { - .FileChecksums => { - mod.checksum_offset = sect_offset; - break; - }, - else => {}, - } - - if (sect_offset > mod.subsect_info.len) - return error.InvalidDebugInfo; - } - - mod.populated = true; - return mod; - } - - pub fn getStreamById(self: *Pdb, id: u32) ?*MsfStream { - if (id >= self.msf.streams.len) - return null; - return &self.msf.streams[id]; - } - - pub fn getStream(self: *Pdb, stream: StreamType) ?*MsfStream { - const id = @intFromEnum(stream); - return self.getStreamById(id); - } -}; - -// see https://llvm.org/docs/PDB/MsfFile.html -const Msf = struct { - directory: MsfStream, - streams: []MsfStream, - - fn init(allocator: mem.Allocator, file: File) !Msf { - const in = file.reader(); - - const superblock = try in.readStruct(SuperBlock); - - // Sanity checks - if (!mem.eql(u8, &superblock.FileMagic, SuperBlock.file_magic)) - return error.InvalidDebugInfo; - if (superblock.FreeBlockMapBlock != 1 and superblock.FreeBlockMapBlock != 2) - return error.InvalidDebugInfo; - const file_len = try file.getEndPos(); - if (superblock.NumBlocks * superblock.BlockSize != file_len) - return error.InvalidDebugInfo; - switch (superblock.BlockSize) { - // llvm only supports 4096 but we can handle any of these values - 512, 1024, 2048, 4096 => {}, - else => return error.InvalidDebugInfo, - } - - const dir_block_count = blockCountFromSize(superblock.NumDirectoryBytes, superblock.BlockSize); - if (dir_block_count > superblock.BlockSize / @sizeOf(u32)) - return error.UnhandledBigDirectoryStream; // cf. BlockMapAddr comment. - - try file.seekTo(superblock.BlockSize * superblock.BlockMapAddr); - const dir_blocks = try allocator.alloc(u32, dir_block_count); - for (dir_blocks) |*b| { - b.* = try in.readInt(u32, .little); - } - var directory = MsfStream.init( - superblock.BlockSize, - file, - dir_blocks, - ); - - const begin = directory.pos; - const stream_count = try directory.reader().readInt(u32, .little); - const stream_sizes = try allocator.alloc(u32, stream_count); - defer allocator.free(stream_sizes); - - // Microsoft's implementation uses @as(u32, -1) for inexistent streams. - // These streams are not used, but still participate in the file - // and must be taken into account when resolving stream indices. - const Nil = 0xFFFFFFFF; - for (stream_sizes) |*s| { - const size = try directory.reader().readInt(u32, .little); - s.* = if (size == Nil) 0 else blockCountFromSize(size, superblock.BlockSize); - } - - const streams = try allocator.alloc(MsfStream, stream_count); - for (streams, 0..) |*stream, i| { - const size = stream_sizes[i]; - if (size == 0) { - stream.* = MsfStream{ - .blocks = &[_]u32{}, - }; - } else { - var blocks = try allocator.alloc(u32, size); - var j: u32 = 0; - while (j < size) : (j += 1) { - const block_id = try directory.reader().readInt(u32, .little); - const n = (block_id % superblock.BlockSize); - // 0 is for SuperBlock, 1 and 2 for FPMs. - if (block_id == 0 or n == 1 or n == 2 or block_id * superblock.BlockSize > file_len) - return error.InvalidBlockIndex; - blocks[j] = block_id; - } - - stream.* = MsfStream.init( - superblock.BlockSize, - file, - blocks, - ); - } - } - - const end = directory.pos; - if (end - begin != superblock.NumDirectoryBytes) - return error.InvalidStreamDirectory; - - return Msf{ - .directory = directory, - .streams = streams, - }; - } - - fn deinit(self: *Msf, allocator: mem.Allocator) void { - allocator.free(self.directory.blocks); - for (self.streams) |*stream| { - allocator.free(stream.blocks); - } - allocator.free(self.streams); - } -}; - -fn blockCountFromSize(size: u32, block_size: u32) u32 { - return (size + block_size - 1) / block_size; -} - // https://llvm.org/docs/PDB/MsfFile.html#the-superblock pub const SuperBlock = extern struct { /// The LLVM docs list a space between C / C++ but empirically this is not the case. @@ -1016,82 +510,3 @@ pub const SuperBlock = extern struct { // implement it so we're kind of safe making this assumption for now. BlockMapAddr: u32, }; - -const MsfStream = struct { - in_file: File = undefined, - pos: u64 = undefined, - blocks: []u32 = undefined, - block_size: u32 = undefined, - - pub const Error = @typeInfo(@typeInfo(@TypeOf(read)).Fn.return_type.?).ErrorUnion.error_set; - - fn init(block_size: u32, file: File, blocks: []u32) MsfStream { - const stream = MsfStream{ - .in_file = file, - .pos = 0, - .blocks = blocks, - .block_size = block_size, - }; - - return stream; - } - - fn read(self: *MsfStream, buffer: []u8) !usize { - var block_id = @as(usize, @intCast(self.pos / self.block_size)); - if (block_id >= self.blocks.len) return 0; // End of Stream - var block = self.blocks[block_id]; - var offset = self.pos % self.block_size; - - try self.in_file.seekTo(block * self.block_size + offset); - const in = self.in_file.reader(); - - var size: usize = 0; - var rem_buffer = buffer; - while (size < buffer.len) { - const size_to_read = @min(self.block_size - offset, rem_buffer.len); - size += try in.read(rem_buffer[0..size_to_read]); - rem_buffer = buffer[size..]; - offset += size_to_read; - - // If we're at the end of a block, go to the next one. - if (offset == self.block_size) { - offset = 0; - block_id += 1; - if (block_id >= self.blocks.len) break; // End of Stream - block = self.blocks[block_id]; - try self.in_file.seekTo(block * self.block_size); - } - } - - self.pos += buffer.len; - return buffer.len; - } - - pub fn seekBy(self: *MsfStream, len: i64) !void { - self.pos = @as(u64, @intCast(@as(i64, @intCast(self.pos)) + len)); - if (self.pos >= self.blocks.len * self.block_size) - return error.EOF; - } - - pub fn seekTo(self: *MsfStream, len: u64) !void { - self.pos = len; - if (self.pos >= self.blocks.len * self.block_size) - return error.EOF; - } - - fn getSize(self: *const MsfStream) u64 { - return self.blocks.len * self.block_size; - } - - fn getFilePos(self: MsfStream) u64 { - const block_id = self.pos / self.block_size; - const block = self.blocks[block_id]; - const offset = self.pos % self.block_size; - - return block * self.block_size + offset; - } - - pub fn reader(self: *MsfStream) std.io.Reader(*MsfStream, Error, read) { - return .{ .context = self }; - } -}; From 48d584e3a33a76ef4ea643905a11d311e9ed8bbf Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 1 Aug 2024 22:04:40 -0700 Subject: [PATCH 114/266] std.debug: reorg and clarify API goals After this commit: `std.debug.SelfInfo` is a cross-platform abstraction for the current executable's own debug information, with a goal of minimal code bloat and compilation speed penalty. `std.debug.Dwarf` does not assume the current executable is itself the thing being debugged, however, it does assume the debug info has the same CPU architecture and OS as the current executable. It is planned to remove this limitation. --- lib/std/debug.zig | 233 +++---- lib/std/debug/Dwarf.zig | 916 ++++-------------------- lib/std/debug/Dwarf/abi.zig | 127 +--- lib/std/debug/Dwarf/call_frame.zig | 388 ----------- lib/std/debug/Dwarf/expression.zig | 26 +- lib/std/debug/MemoryAccessor.zig | 128 ++++ lib/std/debug/SelfInfo.zig | 1033 ++++++++++++++++++++++++++++ src/crash_report.zig | 2 +- 8 files changed, 1434 insertions(+), 1419 deletions(-) create mode 100644 lib/std/debug/MemoryAccessor.zig diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 2753a0f52f..0f10bada71 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -13,6 +13,7 @@ const native_arch = builtin.cpu.arch; const native_os = builtin.os.tag; const native_endian = native_arch.endian(); +pub const MemoryAccessor = @import("debug/MemoryAccessor.zig"); pub const Dwarf = @import("debug/Dwarf.zig"); pub const Pdb = @import("debug/Pdb.zig"); pub const SelfInfo = @import("debug/SelfInfo.zig"); @@ -243,7 +244,7 @@ pub inline fn getContext(context: *ThreadContext) bool { /// Tries to print the stack trace starting from the supplied base pointer to stderr, /// unbuffered, and ignores any error returned. /// TODO multithreaded awareness -pub fn dumpStackTraceFromBase(context: *const ThreadContext) void { +pub fn dumpStackTraceFromBase(context: *ThreadContext) void { nosuspend { if (comptime builtin.target.isWasm()) { if (native_os == .wasi) { @@ -545,7 +546,7 @@ pub const StackIterator = struct { // using DWARF and MachO unwind info. unwind_state: if (have_ucontext) ?struct { debug_info: *SelfInfo, - dwarf_context: Dwarf.UnwindContext, + dwarf_context: SelfInfo.UnwindContext, last_error: ?UnwindError = null, failed: bool = false, } else void = if (have_ucontext) null else {}, @@ -569,16 +570,16 @@ pub const StackIterator = struct { }; } - pub fn initWithContext(first_address: ?usize, debug_info: *SelfInfo, context: *const posix.ucontext_t) !StackIterator { + pub fn initWithContext(first_address: ?usize, debug_info: *SelfInfo, context: *posix.ucontext_t) !StackIterator { // The implementation of DWARF unwinding on aarch64-macos is not complete. However, Apple mandates that // the frame pointer register is always used, so on this platform we can safely use the FP-based unwinder. - if (comptime builtin.target.isDarwin() and native_arch == .aarch64) { + if (builtin.target.isDarwin() and native_arch == .aarch64) { return init(first_address, context.mcontext.ss.fp); } else { var iterator = init(first_address, null); iterator.unwind_state = .{ .debug_info = debug_info, - .dwarf_context = try Dwarf.UnwindContext.init(debug_info.allocator, context), + .dwarf_context = try SelfInfo.UnwindContext.init(debug_info.allocator, context), }; return iterator; @@ -644,116 +645,6 @@ pub const StackIterator = struct { return address; } - fn isValidMemory(address: usize) bool { - // We are unable to determine validity of memory for freestanding targets - if (native_os == .freestanding or native_os == .uefi) return true; - - const aligned_address = address & ~@as(usize, @intCast((mem.page_size - 1))); - if (aligned_address == 0) return false; - const aligned_memory = @as([*]align(mem.page_size) u8, @ptrFromInt(aligned_address))[0..mem.page_size]; - - if (native_os == .windows) { - var memory_info: windows.MEMORY_BASIC_INFORMATION = undefined; - - // The only error this function can throw is ERROR_INVALID_PARAMETER. - // supply an address that invalid i'll be thrown. - const rc = windows.VirtualQuery(aligned_memory, &memory_info, aligned_memory.len) catch { - return false; - }; - - // Result code has to be bigger than zero (number of bytes written) - if (rc == 0) { - return false; - } - - // Free pages cannot be read, they are unmapped - if (memory_info.State == windows.MEM_FREE) { - return false; - } - - return true; - } else if (have_msync) { - posix.msync(aligned_memory, posix.MSF.ASYNC) catch |err| { - switch (err) { - error.UnmappedMemory => return false, - else => unreachable, - } - }; - - return true; - } else { - // We are unable to determine validity of memory on this target. - return true; - } - } - - pub const MemoryAccessor = struct { - var cached_pid: posix.pid_t = -1; - - mem: switch (native_os) { - .linux => File, - else => void, - }, - - pub const init: MemoryAccessor = .{ - .mem = switch (native_os) { - .linux => .{ .handle = -1 }, - else => {}, - }, - }; - - fn read(ma: *MemoryAccessor, address: usize, buf: []u8) bool { - switch (native_os) { - .linux => while (true) switch (ma.mem.handle) { - -2 => break, - -1 => { - const linux = std.os.linux; - const pid = switch (@atomicLoad(posix.pid_t, &cached_pid, .monotonic)) { - -1 => pid: { - const pid = linux.getpid(); - @atomicStore(posix.pid_t, &cached_pid, pid, .monotonic); - break :pid pid; - }, - else => |pid| pid, - }; - const bytes_read = linux.process_vm_readv( - pid, - &.{.{ .base = buf.ptr, .len = buf.len }}, - &.{.{ .base = @ptrFromInt(address), .len = buf.len }}, - 0, - ); - switch (linux.E.init(bytes_read)) { - .SUCCESS => return bytes_read == buf.len, - .FAULT => return false, - .INVAL, .PERM, .SRCH => unreachable, // own pid is always valid - .NOMEM => {}, - .NOSYS => {}, // QEMU is known not to implement this syscall. - else => unreachable, // unexpected - } - var path_buf: [ - std.fmt.count("/proc/{d}/mem", .{math.minInt(posix.pid_t)}) - ]u8 = undefined; - const path = std.fmt.bufPrint(&path_buf, "/proc/{d}/mem", .{pid}) catch - unreachable; - ma.mem = std.fs.openFileAbsolute(path, .{}) catch { - ma.mem.handle = -2; - break; - }; - }, - else => return (ma.mem.pread(buf, address) catch return false) == buf.len, - }, - else => {}, - } - if (!isValidMemory(address)) return false; - @memcpy(buf, @as([*]const u8, @ptrFromInt(address))); - return true; - } - pub fn load(ma: *MemoryAccessor, comptime Type: type, address: usize) ?Type { - var result: Type = undefined; - return if (ma.read(address, std.mem.asBytes(&result))) result else null; - } - }; - fn next_unwind(it: *StackIterator) !usize { const unwind_state = &it.unwind_state.?; const module = try unwind_state.debug_info.getModuleForAddress(unwind_state.dwarf_context.pc); @@ -762,7 +653,13 @@ pub const StackIterator = struct { // __unwind_info is a requirement for unwinding on Darwin. It may fall back to DWARF, but unwinding // via DWARF before attempting to use the compact unwind info will produce incorrect results. if (module.unwind_info) |unwind_info| { - if (Dwarf.unwindFrameMachO(&unwind_state.dwarf_context, &it.ma, unwind_info, module.eh_frame, module.base_address)) |return_address| { + if (SelfInfo.unwindFrameMachO( + &unwind_state.dwarf_context, + &it.ma, + unwind_info, + module.eh_frame, + module.base_address, + )) |return_address| { return return_address; } else |err| { if (err != error.RequiresDWARFUnwind) return err; @@ -773,7 +670,7 @@ pub const StackIterator = struct { } if (try module.getDwarfInfoForAddress(unwind_state.debug_info.allocator, unwind_state.dwarf_context.pc)) |di| { - return di.unwindFrame(&unwind_state.dwarf_context, &it.ma, null); + return SelfInfo.unwindFrameDwarf(di, &unwind_state.dwarf_context, &it.ma, null); } else return error.MissingDebugInfo; } @@ -822,11 +719,6 @@ pub const StackIterator = struct { } }; -const have_msync = switch (native_os) { - .wasi, .emscripten, .windows => false, - else => true, -}; - pub fn writeCurrentStackTrace( out_stream: anytype, debug_info: *SelfInfo, @@ -1333,7 +1225,7 @@ fn handleSegfaultPosix(sig: i32, info: *const posix.siginfo_t, ctx_ptr: ?*anyopa posix.abort(); } -fn dumpSegfaultInfoPosix(sig: i32, code: i32, addr: usize, ctx_ptr: ?*const anyopaque) void { +fn dumpSegfaultInfoPosix(sig: i32, code: i32, addr: usize, ctx_ptr: ?*anyopaque) void { const stderr = io.getStdErr().writer(); _ = switch (sig) { posix.SIG.SEGV => if (native_arch == .x86_64 and native_os == .linux and code == 128) // SI_KERNEL @@ -1359,7 +1251,7 @@ fn dumpSegfaultInfoPosix(sig: i32, code: i32, addr: usize, ctx_ptr: ?*const anyo .arm, .aarch64, => { - const ctx: *const posix.ucontext_t = @ptrCast(@alignCast(ctx_ptr)); + const ctx: *posix.ucontext_t = @ptrCast(@alignCast(ctx_ptr)); dumpStackTraceFromBase(ctx); }, else => {}, @@ -1585,6 +1477,99 @@ pub const SafetyLock = struct { } }; +/// Deprecated. Don't use this, just read from your memory directly. +/// +/// This only exists because someone was too lazy to rework logic that used to +/// operate on an open file to operate on a memory buffer instead. +pub const DeprecatedFixedBufferReader = struct { + buf: []const u8, + pos: usize = 0, + endian: std.builtin.Endian, + + pub const Error = error{ EndOfBuffer, Overflow, InvalidBuffer }; + + pub fn seekTo(fbr: *DeprecatedFixedBufferReader, pos: u64) Error!void { + if (pos > fbr.buf.len) return error.EndOfBuffer; + fbr.pos = @intCast(pos); + } + + pub fn seekForward(fbr: *DeprecatedFixedBufferReader, amount: u64) Error!void { + if (fbr.buf.len - fbr.pos < amount) return error.EndOfBuffer; + fbr.pos += @intCast(amount); + } + + pub inline fn readByte(fbr: *DeprecatedFixedBufferReader) Error!u8 { + if (fbr.pos >= fbr.buf.len) return error.EndOfBuffer; + defer fbr.pos += 1; + return fbr.buf[fbr.pos]; + } + + pub fn readByteSigned(fbr: *DeprecatedFixedBufferReader) Error!i8 { + return @bitCast(try fbr.readByte()); + } + + pub fn readInt(fbr: *DeprecatedFixedBufferReader, comptime T: type) Error!T { + const size = @divExact(@typeInfo(T).Int.bits, 8); + if (fbr.buf.len - fbr.pos < size) return error.EndOfBuffer; + defer fbr.pos += size; + return std.mem.readInt(T, fbr.buf[fbr.pos..][0..size], fbr.endian); + } + + pub fn readIntChecked( + fbr: *DeprecatedFixedBufferReader, + comptime T: type, + ma: *MemoryAccessor, + ) Error!T { + if (ma.load(T, @intFromPtr(fbr.buf[fbr.pos..].ptr)) == null) + return error.InvalidBuffer; + + return fbr.readInt(T); + } + + pub fn readUleb128(fbr: *DeprecatedFixedBufferReader, comptime T: type) Error!T { + return std.leb.readUleb128(T, fbr); + } + + pub fn readIleb128(fbr: *DeprecatedFixedBufferReader, comptime T: type) Error!T { + return std.leb.readIleb128(T, fbr); + } + + pub fn readAddress(fbr: *DeprecatedFixedBufferReader, format: std.dwarf.Format) Error!u64 { + return switch (format) { + .@"32" => try fbr.readInt(u32), + .@"64" => try fbr.readInt(u64), + }; + } + + pub fn readAddressChecked( + fbr: *DeprecatedFixedBufferReader, + format: std.dwarf.Format, + ma: *MemoryAccessor, + ) Error!u64 { + return switch (format) { + .@"32" => try fbr.readIntChecked(u32, ma), + .@"64" => try fbr.readIntChecked(u64, ma), + }; + } + + pub fn readBytes(fbr: *DeprecatedFixedBufferReader, len: usize) Error![]const u8 { + if (fbr.buf.len - fbr.pos < len) return error.EndOfBuffer; + defer fbr.pos += len; + return fbr.buf[fbr.pos..][0..len]; + } + + pub fn readBytesTo(fbr: *DeprecatedFixedBufferReader, comptime sentinel: u8) Error![:sentinel]const u8 { + const end = @call(.always_inline, std.mem.indexOfScalarPos, .{ + u8, + fbr.buf, + fbr.pos, + sentinel, + }) orelse return error.EndOfBuffer; + defer fbr.pos = end + 1; + return fbr.buf[fbr.pos..end :sentinel]; + } +}; + /// Detect whether the program is being executed in the Valgrind virtual machine. /// /// When Valgrind integrations are disabled, this returns comptime-known false. diff --git a/lib/std/debug/Dwarf.zig b/lib/std/debug/Dwarf.zig index 4fff2562b2..991c731549 100644 --- a/lib/std/debug/Dwarf.zig +++ b/lib/std/debug/Dwarf.zig @@ -1,23 +1,32 @@ //! Implements parsing, decoding, and caching of DWARF information. //! +//! This API does not assume the current executable is itself the thing being +//! debugged, however, it does assume the debug info has the same CPU +//! architecture and OS as the current executable. It is planned to remove this +//! limitation. +//! //! For unopinionated types and bits, see `std.dwarf`. const builtin = @import("builtin"); +const native_endian = builtin.cpu.arch.endian(); + const std = @import("../std.zig"); -const AT = DW.AT; const Allocator = std.mem.Allocator; const DW = std.dwarf; +const AT = DW.AT; const EH = DW.EH; const FORM = DW.FORM; const Format = DW.Format; const RLE = DW.RLE; -const StackIterator = std.debug.StackIterator; const UT = DW.UT; const assert = std.debug.assert; const cast = std.math.cast; const maxInt = std.math.maxInt; -const native_endian = builtin.cpu.arch.endian(); const readInt = std.mem.readInt; +const MemoryAccessor = std.debug.MemoryAccessor; + +/// Did I mention this is deprecated? +const DeprecatedFixedBufferReader = std.debug.DeprecatedFixedBufferReader; const Dwarf = @This(); @@ -153,7 +162,7 @@ pub const FormValue = union(enum) { .string => |s| return s, .strp => |off| return di.getString(off), .line_strp => |off| return di.getLineString(off), - else => return badDwarf(), + else => return bad(), } } @@ -162,8 +171,8 @@ pub const FormValue = union(enum) { inline .udata, .sdata, .sec_offset, - => |c| cast(U, c) orelse badDwarf(), - else => badDwarf(), + => |c| cast(U, c) orelse bad(), + else => bad(), }; } }; @@ -237,25 +246,25 @@ pub const Die = struct { .string => |value| return value, .strp => |offset| return di.getString(offset), .strx => |index| { - const debug_str_offsets = di.section(.debug_str_offsets) orelse return badDwarf(); - if (compile_unit.str_offsets_base == 0) return badDwarf(); + const debug_str_offsets = di.section(.debug_str_offsets) orelse return bad(); + if (compile_unit.str_offsets_base == 0) return bad(); switch (compile_unit.format) { .@"32" => { const byte_offset = compile_unit.str_offsets_base + 4 * index; - if (byte_offset + 4 > debug_str_offsets.len) return badDwarf(); + if (byte_offset + 4 > debug_str_offsets.len) return bad(); const offset = readInt(u32, debug_str_offsets[byte_offset..][0..4], di.endian); return getStringGeneric(opt_str, offset); }, .@"64" => { const byte_offset = compile_unit.str_offsets_base + 8 * index; - if (byte_offset + 8 > debug_str_offsets.len) return badDwarf(); + if (byte_offset + 8 > debug_str_offsets.len) return bad(); const offset = readInt(u64, debug_str_offsets[byte_offset..][0..8], di.endian); return getStringGeneric(opt_str, offset); }, } }, .line_strp => |offset| return di.getLineString(offset), - else => return badDwarf(), + else => return bad(), } } }; @@ -279,7 +288,7 @@ pub const ExceptionFrameHeader = struct { EH.PE.sdata8, => 16, // This is a binary search table, so all entries must be the same length - else => return badDwarf(), + else => return bad(), }; } @@ -287,7 +296,7 @@ pub const ExceptionFrameHeader = struct { self: ExceptionFrameHeader, comptime T: type, ptr: usize, - ma: *StackIterator.MemoryAccessor, + ma: *MemoryAccessor, eh_frame_len: ?usize, ) bool { if (eh_frame_len) |len| { @@ -304,7 +313,7 @@ pub const ExceptionFrameHeader = struct { /// If `eh_frame_len` is provided, then these checks can be skipped. pub fn findEntry( self: ExceptionFrameHeader, - ma: *StackIterator.MemoryAccessor, + ma: *MemoryAccessor, eh_frame_len: ?usize, eh_frame_hdr_ptr: usize, pc: usize, @@ -316,7 +325,7 @@ pub const ExceptionFrameHeader = struct { var left: usize = 0; var len: usize = self.fde_count; - var fbr: FixedBufferReader = .{ .buf = self.entries, .endian = native_endian }; + var fbr: DeprecatedFixedBufferReader = .{ .buf = self.entries, .endian = native_endian }; while (len > 1) { const mid = left + len / 2; @@ -326,7 +335,7 @@ pub const ExceptionFrameHeader = struct { .pc_rel_base = @intFromPtr(&self.entries[fbr.pos]), .follow_indirect = true, .data_rel_base = eh_frame_hdr_ptr, - }) orelse return badDwarf(); + }) orelse return bad(); if (pc < pc_begin) { len /= 2; @@ -337,7 +346,7 @@ pub const ExceptionFrameHeader = struct { } } - if (len == 0) return badDwarf(); + if (len == 0) return bad(); fbr.pos = left * entry_size; // Read past the pc_begin field of the entry @@ -345,36 +354,36 @@ pub const ExceptionFrameHeader = struct { .pc_rel_base = @intFromPtr(&self.entries[fbr.pos]), .follow_indirect = true, .data_rel_base = eh_frame_hdr_ptr, - }) orelse return badDwarf(); + }) orelse return bad(); const fde_ptr = cast(usize, try readEhPointer(&fbr, self.table_enc, @sizeOf(usize), .{ .pc_rel_base = @intFromPtr(&self.entries[fbr.pos]), .follow_indirect = true, .data_rel_base = eh_frame_hdr_ptr, - }) orelse return badDwarf()) orelse return badDwarf(); + }) orelse return bad()) orelse return bad(); - if (fde_ptr < self.eh_frame_ptr) return badDwarf(); + if (fde_ptr < self.eh_frame_ptr) return bad(); // Even if eh_frame_len is not specified, all ranges accssed are checked via MemoryAccessor const eh_frame = @as([*]const u8, @ptrFromInt(self.eh_frame_ptr))[0 .. eh_frame_len orelse maxInt(u32)]; const fde_offset = fde_ptr - self.eh_frame_ptr; - var eh_frame_fbr: FixedBufferReader = .{ + var eh_frame_fbr: DeprecatedFixedBufferReader = .{ .buf = eh_frame, .pos = fde_offset, .endian = native_endian, }; const fde_entry_header = try EntryHeader.read(&eh_frame_fbr, if (eh_frame_len == null) ma else null, .eh_frame); - if (!self.isValidPtr(u8, @intFromPtr(&fde_entry_header.entry_bytes[fde_entry_header.entry_bytes.len - 1]), ma, eh_frame_len)) return badDwarf(); - if (fde_entry_header.type != .fde) return badDwarf(); + if (!self.isValidPtr(u8, @intFromPtr(&fde_entry_header.entry_bytes[fde_entry_header.entry_bytes.len - 1]), ma, eh_frame_len)) return bad(); + if (fde_entry_header.type != .fde) return bad(); // CIEs always come before FDEs (the offset is a subtraction), so we can assume this memory is readable const cie_offset = fde_entry_header.type.fde; try eh_frame_fbr.seekTo(cie_offset); const cie_entry_header = try EntryHeader.read(&eh_frame_fbr, if (eh_frame_len == null) ma else null, .eh_frame); - if (!self.isValidPtr(u8, @intFromPtr(&cie_entry_header.entry_bytes[cie_entry_header.entry_bytes.len - 1]), ma, eh_frame_len)) return badDwarf(); - if (cie_entry_header.type != .cie) return badDwarf(); + if (!self.isValidPtr(u8, @intFromPtr(&cie_entry_header.entry_bytes[cie_entry_header.entry_bytes.len - 1]), ma, eh_frame_len)) return bad(); + if (cie_entry_header.type != .cie) return bad(); cie.* = try CommonInformationEntry.parse( cie_entry_header.entry_bytes, @@ -417,17 +426,17 @@ pub const EntryHeader = struct { } /// Reads a header for either an FDE or a CIE, then advances the fbr to the position after the trailing structure. - /// `fbr` must be a FixedBufferReader backed by either the .eh_frame or .debug_frame sections. + /// `fbr` must be a DeprecatedFixedBufferReader backed by either the .eh_frame or .debug_frame sections. pub fn read( - fbr: *FixedBufferReader, - opt_ma: ?*StackIterator.MemoryAccessor, + fbr: *DeprecatedFixedBufferReader, + opt_ma: ?*MemoryAccessor, dwarf_section: Section.Id, ) !EntryHeader { assert(dwarf_section == .eh_frame or dwarf_section == .debug_frame); const length_offset = fbr.pos; const unit_header = try readUnitHeader(fbr, opt_ma); - const unit_length = cast(usize, unit_header.unit_length) orelse return badDwarf(); + const unit_length = cast(usize, unit_header.unit_length) orelse return bad(); if (unit_length == 0) return .{ .length_offset = length_offset, .format = unit_header.format, @@ -532,7 +541,7 @@ pub const CommonInformationEntry = struct { ) !CommonInformationEntry { if (addr_size_bytes > 8) return error.UnsupportedAddrSize; - var fbr: FixedBufferReader = .{ .buf = cie_bytes, .endian = endian }; + var fbr: DeprecatedFixedBufferReader = .{ .buf = cie_bytes, .endian = endian }; const version = try fbr.readByte(); switch (dwarf_section) { @@ -550,15 +559,15 @@ pub const CommonInformationEntry = struct { while (aug_byte != 0) : (aug_byte = try fbr.readByte()) { switch (aug_byte) { 'z' => { - if (aug_str_len != 0) return badDwarf(); + if (aug_str_len != 0) return bad(); has_aug_data = true; }, 'e' => { - if (has_aug_data or aug_str_len != 0) return badDwarf(); - if (try fbr.readByte() != 'h') return badDwarf(); + if (has_aug_data or aug_str_len != 0) return bad(); + if (try fbr.readByte() != 'h') return bad(); has_eh_data = true; }, - else => if (has_eh_data) return badDwarf(), + else => if (has_eh_data) return bad(), } aug_str_len += 1; @@ -604,7 +613,7 @@ pub const CommonInformationEntry = struct { fde_pointer_enc = try fbr.readByte(); }, 'S', 'B', 'G' => {}, - else => return badDwarf(), + else => return bad(), } } @@ -666,17 +675,17 @@ pub const FrameDescriptionEntry = struct { ) !FrameDescriptionEntry { if (addr_size_bytes > 8) return error.InvalidAddrSize; - var fbr: FixedBufferReader = .{ .buf = fde_bytes, .endian = endian }; + var fbr: DeprecatedFixedBufferReader = .{ .buf = fde_bytes, .endian = endian }; const pc_begin = try readEhPointer(&fbr, cie.fde_pointer_enc, addr_size_bytes, .{ .pc_rel_base = try pcRelBase(@intFromPtr(&fde_bytes[fbr.pos]), pc_rel_offset), .follow_indirect = is_runtime, - }) orelse return badDwarf(); + }) orelse return bad(); const pc_range = try readEhPointer(&fbr, cie.fde_pointer_enc, addr_size_bytes, .{ .pc_rel_base = 0, .follow_indirect = false, - }) orelse return badDwarf(); + }) orelse return bad(); var aug_data: []const u8 = &[_]u8{}; const lsda_pointer = if (cie.aug_str.len > 0) blk: { @@ -708,54 +717,6 @@ pub const FrameDescriptionEntry = struct { } }; -pub const UnwindContext = struct { - allocator: Allocator, - cfa: ?usize, - pc: usize, - thread_context: *std.debug.ThreadContext, - reg_context: abi.RegisterContext, - vm: call_frame.VirtualMachine, - stack_machine: expression.StackMachine(.{ .call_frame_context = true }), - - pub fn init( - allocator: Allocator, - thread_context: *const std.debug.ThreadContext, - ) !UnwindContext { - const pc = abi.stripInstructionPtrAuthCode( - (try abi.regValueNative( - usize, - thread_context, - abi.ipRegNum(), - null, - )).*, - ); - - const context_copy = try allocator.create(std.debug.ThreadContext); - std.debug.copyContext(thread_context, context_copy); - - return .{ - .allocator = allocator, - .cfa = null, - .pc = pc, - .thread_context = context_copy, - .reg_context = undefined, - .vm = .{}, - .stack_machine = .{}, - }; - } - - pub fn deinit(self: *UnwindContext) void { - self.vm.deinit(self.allocator); - self.stack_machine.deinit(self.allocator); - self.allocator.destroy(self.thread_context); - self.* = undefined; - } - - pub fn getFp(self: *const UnwindContext) !usize { - return (try abi.regValueNative(usize, self.thread_context, abi.fpRegNum(self.reg_context), self.reg_context)).*; - } -}; - const num_sections = std.enums.directEnumArrayLen(Section.Id, 0); pub const SectionArray = [num_sections]?Section; pub const null_section_array = [_]?Section{null} ** num_sections; @@ -817,7 +778,7 @@ pub fn getSymbolName(di: *Dwarf, address: u64) ?[]const u8 { } fn scanAllFunctions(di: *Dwarf, allocator: Allocator) !void { - var fbr: FixedBufferReader = .{ .buf = di.section(.debug_info).?, .endian = di.endian }; + var fbr: DeprecatedFixedBufferReader = .{ .buf = di.section(.debug_info).?, .endian = di.endian }; var this_unit_offset: u64 = 0; while (this_unit_offset < fbr.buf.len) { @@ -828,20 +789,20 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) !void { const next_offset = unit_header.header_length + unit_header.unit_length; const version = try fbr.readInt(u16); - if (version < 2 or version > 5) return badDwarf(); + if (version < 2 or version > 5) return bad(); var address_size: u8 = undefined; var debug_abbrev_offset: u64 = undefined; if (version >= 5) { const unit_type = try fbr.readInt(u8); - if (unit_type != DW.UT.compile) return badDwarf(); + if (unit_type != DW.UT.compile) return bad(); address_size = try fbr.readByte(); debug_abbrev_offset = try fbr.readAddress(unit_header.format); } else { debug_abbrev_offset = try fbr.readAddress(unit_header.format); address_size = try fbr.readByte(); } - if (address_size != @sizeOf(usize)) return badDwarf(); + if (address_size != @sizeOf(usize)) return bad(); const abbrev_table = try di.getAbbrevTable(allocator, debug_abbrev_offset); @@ -915,28 +876,28 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) !void { // Follow the DIE it points to and repeat const ref_offset = try this_die_obj.getAttrRef(AT.abstract_origin); - if (ref_offset > next_offset) return badDwarf(); + if (ref_offset > next_offset) return bad(); try fbr.seekTo(this_unit_offset + ref_offset); this_die_obj = (try parseDie( &fbr, attrs_bufs[2], abbrev_table, unit_header.format, - )) orelse return badDwarf(); + )) orelse return bad(); } else if (this_die_obj.getAttr(AT.specification)) |_| { const after_die_offset = fbr.pos; defer fbr.pos = after_die_offset; // Follow the DIE it points to and repeat const ref_offset = try this_die_obj.getAttrRef(AT.specification); - if (ref_offset > next_offset) return badDwarf(); + if (ref_offset > next_offset) return bad(); try fbr.seekTo(this_unit_offset + ref_offset); this_die_obj = (try parseDie( &fbr, attrs_bufs[2], abbrev_table, unit_header.format, - )) orelse return badDwarf(); + )) orelse return bad(); } else { break :x null; } @@ -950,7 +911,7 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) !void { const pc_end = switch (high_pc_value.*) { .addr => |value| value, .udata => |offset| low_pc + offset, - else => return badDwarf(), + else => return bad(), }; try di.func_list.append(allocator, .{ @@ -1004,7 +965,7 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) !void { } fn scanAllCompileUnits(di: *Dwarf, allocator: Allocator) !void { - var fbr: FixedBufferReader = .{ .buf = di.section(.debug_info).?, .endian = di.endian }; + var fbr: DeprecatedFixedBufferReader = .{ .buf = di.section(.debug_info).?, .endian = di.endian }; var this_unit_offset: u64 = 0; var attrs_buf = std.ArrayList(Die.Attr).init(allocator); @@ -1018,20 +979,20 @@ fn scanAllCompileUnits(di: *Dwarf, allocator: Allocator) !void { const next_offset = unit_header.header_length + unit_header.unit_length; const version = try fbr.readInt(u16); - if (version < 2 or version > 5) return badDwarf(); + if (version < 2 or version > 5) return bad(); var address_size: u8 = undefined; var debug_abbrev_offset: u64 = undefined; if (version >= 5) { const unit_type = try fbr.readInt(u8); - if (unit_type != UT.compile) return badDwarf(); + if (unit_type != UT.compile) return bad(); address_size = try fbr.readByte(); debug_abbrev_offset = try fbr.readAddress(unit_header.format); } else { debug_abbrev_offset = try fbr.readAddress(unit_header.format); address_size = try fbr.readByte(); } - if (address_size != @sizeOf(usize)) return badDwarf(); + if (address_size != @sizeOf(usize)) return bad(); const abbrev_table = try di.getAbbrevTable(allocator, debug_abbrev_offset); @@ -1046,9 +1007,9 @@ fn scanAllCompileUnits(di: *Dwarf, allocator: Allocator) !void { attrs_buf.items, abbrev_table, unit_header.format, - )) orelse return badDwarf(); + )) orelse return bad(); - if (compile_unit_die.tag_id != DW.TAG.compile_unit) return badDwarf(); + if (compile_unit_die.tag_id != DW.TAG.compile_unit) return bad(); compile_unit_die.attrs = try allocator.dupe(Die.Attr, compile_unit_die.attrs); @@ -1070,7 +1031,7 @@ fn scanAllCompileUnits(di: *Dwarf, allocator: Allocator) !void { const pc_end = switch (high_pc_value.*) { .addr => |value| value, .udata => |offset| low_pc + offset, - else => return badDwarf(), + else => return bad(), }; break :x PcRange{ .start = low_pc, @@ -1096,7 +1057,7 @@ const DebugRangeIterator = struct { section_type: Section.Id, di: *const Dwarf, compile_unit: *const CompileUnit, - fbr: FixedBufferReader, + fbr: DeprecatedFixedBufferReader, pub fn init(ranges_value: *const FormValue, di: *const Dwarf, compile_unit: *const CompileUnit) !@This() { const section_type = if (compile_unit.version >= 5) Section.Id.debug_rnglists else Section.Id.debug_ranges; @@ -1108,19 +1069,19 @@ const DebugRangeIterator = struct { switch (compile_unit.format) { .@"32" => { const offset_loc = @as(usize, @intCast(compile_unit.rnglists_base + 4 * idx)); - if (offset_loc + 4 > debug_ranges.len) return badDwarf(); + if (offset_loc + 4 > debug_ranges.len) return bad(); const offset = readInt(u32, debug_ranges[offset_loc..][0..4], di.endian); break :off compile_unit.rnglists_base + offset; }, .@"64" => { const offset_loc = @as(usize, @intCast(compile_unit.rnglists_base + 8 * idx)); - if (offset_loc + 8 > debug_ranges.len) return badDwarf(); + if (offset_loc + 8 > debug_ranges.len) return bad(); const offset = readInt(u64, debug_ranges[offset_loc..][0..8], di.endian); break :off compile_unit.rnglists_base + offset; }, } }, - else => return badDwarf(), + else => return bad(), }; // All the addresses in the list are relative to the value @@ -1139,7 +1100,7 @@ const DebugRangeIterator = struct { .compile_unit = compile_unit, .fbr = .{ .buf = debug_ranges, - .pos = cast(usize, ranges_offset) orelse return badDwarf(), + .pos = cast(usize, ranges_offset) orelse return bad(), .endian = di.endian, }, }; @@ -1214,7 +1175,7 @@ const DebugRangeIterator = struct { .end_addr = end_addr, }; }, - else => return badDwarf(), + else => return bad(), } }, .debug_ranges => { @@ -1251,7 +1212,7 @@ pub fn findCompileUnit(di: *const Dwarf, target_address: u64) !*const CompileUni } } - return missingDwarf(); + return missing(); } /// Gets an already existing AbbrevTable given the abbrev_offset, or if not found, @@ -1270,9 +1231,9 @@ fn getAbbrevTable(di: *Dwarf, allocator: Allocator, abbrev_offset: u64) !*const } fn parseAbbrevTable(di: *Dwarf, allocator: Allocator, offset: u64) !Abbrev.Table { - var fbr: FixedBufferReader = .{ + var fbr: DeprecatedFixedBufferReader = .{ .buf = di.section(.debug_abbrev).?, - .pos = cast(usize, offset) orelse return badDwarf(), + .pos = cast(usize, offset) orelse return bad(), .endian = di.endian, }; @@ -1322,14 +1283,14 @@ fn parseAbbrevTable(di: *Dwarf, allocator: Allocator, offset: u64) !Abbrev.Table } fn parseDie( - fbr: *FixedBufferReader, + fbr: *DeprecatedFixedBufferReader, attrs_buf: []Die.Attr, abbrev_table: *const Abbrev.Table, format: Format, ) !?Die { const abbrev_code = try fbr.readUleb128(u64); if (abbrev_code == 0) return null; - const table_entry = abbrev_table.get(abbrev_code) orelse return badDwarf(); + const table_entry = abbrev_table.get(abbrev_code) orelse return bad(); const attrs = attrs_buf[0..table_entry.attrs.len]; for (attrs, table_entry.attrs) |*result_attr, attr| result_attr.* = Die.Attr{ @@ -1357,15 +1318,15 @@ pub fn getLineNumberInfo( const compile_unit_cwd = try compile_unit.die.getAttrString(di, AT.comp_dir, di.section(.debug_line_str), compile_unit); const line_info_offset = try compile_unit.die.getAttrSecOffset(AT.stmt_list); - var fbr: FixedBufferReader = .{ .buf = di.section(.debug_line).?, .endian = di.endian }; + var fbr: DeprecatedFixedBufferReader = .{ .buf = di.section(.debug_line).?, .endian = di.endian }; try fbr.seekTo(line_info_offset); const unit_header = try readUnitHeader(&fbr, null); - if (unit_header.unit_length == 0) return missingDwarf(); + if (unit_header.unit_length == 0) return missing(); const next_offset = unit_header.header_length + unit_header.unit_length; const version = try fbr.readInt(u16); - if (version < 2) return badDwarf(); + if (version < 2) return bad(); var addr_size: u8 = switch (unit_header.format) { .@"32" => 4, @@ -1381,7 +1342,7 @@ pub fn getLineNumberInfo( const prog_start_offset = fbr.pos + prologue_length; const minimum_instruction_length = try fbr.readByte(); - if (minimum_instruction_length == 0) return badDwarf(); + if (minimum_instruction_length == 0) return bad(); if (version >= 4) { // maximum_operations_per_instruction @@ -1392,7 +1353,7 @@ pub fn getLineNumberInfo( const line_base = try fbr.readByteSigned(); const line_range = try fbr.readByte(); - if (line_range == 0) return badDwarf(); + if (line_range == 0) return bad(); const opcode_base = try fbr.readByte(); @@ -1433,7 +1394,7 @@ pub fn getLineNumberInfo( { var dir_ent_fmt_buf: [10]FileEntFmt = undefined; const directory_entry_format_count = try fbr.readByte(); - if (directory_entry_format_count > dir_ent_fmt_buf.len) return badDwarf(); + if (directory_entry_format_count > dir_ent_fmt_buf.len) return bad(); for (dir_ent_fmt_buf[0..directory_entry_format_count]) |*ent_fmt| { ent_fmt.* = .{ .content_type_code = try fbr.readUleb128(u8), @@ -1461,7 +1422,7 @@ pub fn getLineNumberInfo( DW.LNCT.size => e.size = try form_value.getUInt(u64), DW.LNCT.MD5 => e.md5 = switch (form_value) { .data16 => |data16| data16.*, - else => return badDwarf(), + else => return bad(), }, else => continue, } @@ -1473,7 +1434,7 @@ pub fn getLineNumberInfo( var file_ent_fmt_buf: [10]FileEntFmt = undefined; const file_name_entry_format_count = try fbr.readByte(); - if (file_name_entry_format_count > file_ent_fmt_buf.len) return badDwarf(); + if (file_name_entry_format_count > file_ent_fmt_buf.len) return bad(); for (file_ent_fmt_buf[0..file_name_entry_format_count]) |*ent_fmt| { ent_fmt.* = .{ .content_type_code = try fbr.readUleb128(u8), @@ -1501,7 +1462,7 @@ pub fn getLineNumberInfo( DW.LNCT.size => e.size = try form_value.getUInt(u64), DW.LNCT.MD5 => e.md5 = switch (form_value) { .data16 => |data16| data16.*, - else => return badDwarf(), + else => return bad(), }, else => continue, } @@ -1527,7 +1488,7 @@ pub fn getLineNumberInfo( if (opcode == DW.LNS.extended_op) { const op_size = try fbr.readUleb128(u64); - if (op_size < 1) return badDwarf(); + if (op_size < 1) return bad(); const sub_op = try fbr.readByte(); switch (sub_op) { DW.LNE.end_sequence => { @@ -1600,14 +1561,14 @@ pub fn getLineNumberInfo( }, DW.LNS.set_prologue_end => {}, else => { - if (opcode - 1 >= standard_opcode_lengths.len) return badDwarf(); + if (opcode - 1 >= standard_opcode_lengths.len) return bad(); try fbr.seekForward(standard_opcode_lengths[opcode - 1]); }, } } } - return missingDwarf(); + return missing(); } fn getString(di: Dwarf, offset: u64) ![:0]const u8 { @@ -1619,28 +1580,28 @@ fn getLineString(di: Dwarf, offset: u64) ![:0]const u8 { } fn readDebugAddr(di: Dwarf, compile_unit: CompileUnit, index: u64) !u64 { - const debug_addr = di.section(.debug_addr) orelse return badDwarf(); + const debug_addr = di.section(.debug_addr) orelse return bad(); // addr_base points to the first item after the header, however we // need to read the header to know the size of each item. Empirically, // it may disagree with is_64 on the compile unit. // The header is 8 or 12 bytes depending on is_64. - if (compile_unit.addr_base < 8) return badDwarf(); + if (compile_unit.addr_base < 8) return bad(); const version = readInt(u16, debug_addr[compile_unit.addr_base - 4 ..][0..2], di.endian); - if (version != 5) return badDwarf(); + if (version != 5) return bad(); const addr_size = debug_addr[compile_unit.addr_base - 2]; const seg_size = debug_addr[compile_unit.addr_base - 1]; const byte_offset = @as(usize, @intCast(compile_unit.addr_base + (addr_size + seg_size) * index)); - if (byte_offset + addr_size > debug_addr.len) return badDwarf(); + if (byte_offset + addr_size > debug_addr.len) return bad(); return switch (addr_size) { 1 => debug_addr[byte_offset], 2 => readInt(u16, debug_addr[byte_offset..][0..2], di.endian), 4 => readInt(u32, debug_addr[byte_offset..][0..4], di.endian), 8 => readInt(u64, debug_addr[byte_offset..][0..8], di.endian), - else => badDwarf(), + else => bad(), }; } @@ -1650,7 +1611,7 @@ fn readDebugAddr(di: Dwarf, compile_unit: CompileUnit, index: u64) !u64 { /// of FDEs is built for binary searching during unwinding. pub fn scanAllUnwindInfo(di: *Dwarf, allocator: Allocator, base_address: usize) !void { if (di.section(.eh_frame_hdr)) |eh_frame_hdr| blk: { - var fbr: FixedBufferReader = .{ .buf = eh_frame_hdr, .endian = native_endian }; + var fbr: DeprecatedFixedBufferReader = .{ .buf = eh_frame_hdr, .endian = native_endian }; const version = try fbr.readByte(); if (version != 1) break :blk; @@ -1665,16 +1626,16 @@ pub fn scanAllUnwindInfo(di: *Dwarf, allocator: Allocator, base_address: usize) const eh_frame_ptr = cast(usize, try readEhPointer(&fbr, eh_frame_ptr_enc, @sizeOf(usize), .{ .pc_rel_base = @intFromPtr(&eh_frame_hdr[fbr.pos]), .follow_indirect = true, - }) orelse return badDwarf()) orelse return badDwarf(); + }) orelse return bad()) orelse return bad(); const fde_count = cast(usize, try readEhPointer(&fbr, fde_count_enc, @sizeOf(usize), .{ .pc_rel_base = @intFromPtr(&eh_frame_hdr[fbr.pos]), .follow_indirect = true, - }) orelse return badDwarf()) orelse return badDwarf(); + }) orelse return bad()) orelse return bad(); const entry_size = try ExceptionFrameHeader.entrySize(table_enc); const entries_len = fde_count * entry_size; - if (entries_len > eh_frame_hdr.len - fbr.pos) return badDwarf(); + if (entries_len > eh_frame_hdr.len - fbr.pos) return bad(); di.eh_frame_hdr = .{ .eh_frame_ptr = eh_frame_ptr, @@ -1690,7 +1651,7 @@ pub fn scanAllUnwindInfo(di: *Dwarf, allocator: Allocator, base_address: usize) const frame_sections = [2]Section.Id{ .eh_frame, .debug_frame }; for (frame_sections) |frame_section| { if (di.section(frame_section)) |section_data| { - var fbr: FixedBufferReader = .{ .buf = section_data, .endian = di.endian }; + var fbr: DeprecatedFixedBufferReader = .{ .buf = section_data, .endian = di.endian }; while (fbr.pos < fbr.buf.len) { const entry_header = try EntryHeader.read(&fbr, null, frame_section); switch (entry_header.type) { @@ -1708,7 +1669,7 @@ pub fn scanAllUnwindInfo(di: *Dwarf, allocator: Allocator, base_address: usize) try di.cie_map.put(allocator, entry_header.length_offset, cie); }, .fde => |cie_offset| { - const cie = di.cie_map.get(cie_offset) orelse return badDwarf(); + const cie = di.cie_map.get(cie_offset) orelse return bad(); const fde = try FrameDescriptionEntry.parse( entry_header.entry_bytes, di.sectionVirtualOffset(frame_section, base_address).?, @@ -1733,205 +1694,8 @@ pub fn scanAllUnwindInfo(di: *Dwarf, allocator: Allocator, base_address: usize) } } -/// Unwind a stack frame using DWARF unwinding info, updating the register context. -/// -/// If `.eh_frame_hdr` is available, it will be used to binary search for the FDE. -/// Otherwise, a linear scan of `.eh_frame` and `.debug_frame` is done to find the FDE. -/// -/// `explicit_fde_offset` is for cases where the FDE offset is known, such as when __unwind_info -/// defers unwinding to DWARF. This is an offset into the `.eh_frame` section. -pub fn unwindFrame(di: *const Dwarf, context: *UnwindContext, ma: *StackIterator.MemoryAccessor, explicit_fde_offset: ?usize) !usize { - if (!comptime abi.supportsUnwinding(builtin.target)) return error.UnsupportedCpuArchitecture; - if (context.pc == 0) return 0; - - // Find the FDE and CIE - var cie: CommonInformationEntry = undefined; - var fde: FrameDescriptionEntry = undefined; - - if (explicit_fde_offset) |fde_offset| { - const dwarf_section: Section.Id = .eh_frame; - const frame_section = di.section(dwarf_section) orelse return error.MissingFDE; - if (fde_offset >= frame_section.len) return error.MissingFDE; - - var fbr: FixedBufferReader = .{ - .buf = frame_section, - .pos = fde_offset, - .endian = di.endian, - }; - - const fde_entry_header = try EntryHeader.read(&fbr, null, dwarf_section); - if (fde_entry_header.type != .fde) return error.MissingFDE; - - const cie_offset = fde_entry_header.type.fde; - try fbr.seekTo(cie_offset); - - fbr.endian = native_endian; - const cie_entry_header = try EntryHeader.read(&fbr, null, dwarf_section); - if (cie_entry_header.type != .cie) return badDwarf(); - - cie = try CommonInformationEntry.parse( - cie_entry_header.entry_bytes, - 0, - true, - cie_entry_header.format, - dwarf_section, - cie_entry_header.length_offset, - @sizeOf(usize), - native_endian, - ); - - fde = try FrameDescriptionEntry.parse( - fde_entry_header.entry_bytes, - 0, - true, - cie, - @sizeOf(usize), - native_endian, - ); - } else if (di.eh_frame_hdr) |header| { - const eh_frame_len = if (di.section(.eh_frame)) |eh_frame| eh_frame.len else null; - try header.findEntry( - ma, - eh_frame_len, - @intFromPtr(di.section(.eh_frame_hdr).?.ptr), - context.pc, - &cie, - &fde, - ); - } else { - const index = std.sort.binarySearch(FrameDescriptionEntry, context.pc, di.fde_list.items, {}, struct { - pub fn compareFn(_: void, pc: usize, mid_item: FrameDescriptionEntry) std.math.Order { - if (pc < mid_item.pc_begin) return .lt; - - const range_end = mid_item.pc_begin + mid_item.pc_range; - if (pc < range_end) return .eq; - - return .gt; - } - }.compareFn); - - fde = if (index) |i| di.fde_list.items[i] else return error.MissingFDE; - cie = di.cie_map.get(fde.cie_length_offset) orelse return error.MissingCIE; - } - - var expression_context: expression.Context = .{ - .format = cie.format, - .memory_accessor = ma, - .compile_unit = di.findCompileUnit(fde.pc_begin) catch null, - .thread_context = context.thread_context, - .reg_context = context.reg_context, - .cfa = context.cfa, - }; - - context.vm.reset(); - context.reg_context.eh_frame = cie.version != 4; - context.reg_context.is_macho = di.is_macho; - - const row = try context.vm.runToNative(context.allocator, context.pc, cie, fde); - context.cfa = switch (row.cfa.rule) { - .val_offset => |offset| blk: { - const register = row.cfa.register orelse return error.InvalidCFARule; - const value = readInt(usize, (try abi.regBytes(context.thread_context, register, context.reg_context))[0..@sizeOf(usize)], native_endian); - break :blk try call_frame.applyOffset(value, offset); - }, - .expression => |expr| blk: { - context.stack_machine.reset(); - const value = try context.stack_machine.run( - expr, - context.allocator, - expression_context, - context.cfa, - ); - - if (value) |v| { - if (v != .generic) return error.InvalidExpressionValue; - break :blk v.generic; - } else return error.NoExpressionValue; - }, - else => return error.InvalidCFARule, - }; - - if (ma.load(usize, context.cfa.?) == null) return error.InvalidCFA; - expression_context.cfa = context.cfa; - - // Buffering the modifications is done because copying the thread context is not portable, - // some implementations (ie. darwin) use internal pointers to the mcontext. - var arena = std.heap.ArenaAllocator.init(context.allocator); - defer arena.deinit(); - const update_allocator = arena.allocator(); - - const RegisterUpdate = struct { - // Backed by thread_context - dest: []u8, - // Backed by arena - src: []const u8, - prev: ?*@This(), - }; - - var update_tail: ?*RegisterUpdate = null; - var has_return_address = true; - for (context.vm.rowColumns(row)) |column| { - if (column.register) |register| { - if (register == cie.return_address_register) { - has_return_address = column.rule != .undefined; - } - - const dest = try abi.regBytes(context.thread_context, register, context.reg_context); - const src = try update_allocator.alloc(u8, dest.len); - - const prev = update_tail; - update_tail = try update_allocator.create(RegisterUpdate); - update_tail.?.* = .{ - .dest = dest, - .src = src, - .prev = prev, - }; - - try column.resolveValue( - context, - expression_context, - ma, - src, - ); - } - } - - // On all implemented architectures, the CFA is defined as being the previous frame's SP - (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(context.reg_context), context.reg_context)).* = context.cfa.?; - - while (update_tail) |tail| { - @memcpy(tail.dest, tail.src); - update_tail = tail.prev; - } - - if (has_return_address) { - context.pc = abi.stripInstructionPtrAuthCode(readInt(usize, (try abi.regBytes( - context.thread_context, - cie.return_address_register, - context.reg_context, - ))[0..@sizeOf(usize)], native_endian)); - } else { - context.pc = 0; - } - - (try abi.regValueNative(usize, context.thread_context, abi.ipRegNum(), context.reg_context)).* = context.pc; - - // The call instruction will have pushed the address of the instruction that follows the call as the return address. - // This next instruction may be past the end of the function if the caller was `noreturn` (ie. the last instruction in - // the function was the call). If we were to look up an FDE entry using the return address directly, it could end up - // either not finding an FDE at all, or using the next FDE in the program, producing incorrect results. To prevent this, - // we subtract one so that the next lookup is guaranteed to land inside the - // - // The exception to this rule is signal frames, where we return execution would be returned to the instruction - // that triggered the handler. - const return_address = context.pc; - if (context.pc > 0 and !cie.isSignalFrame()) context.pc -= 1; - - return return_address; -} - fn parseFormValue( - fbr: *FixedBufferReader, + fbr: *DeprecatedFixedBufferReader, form_id: u64, format: Format, implicit_const: ?i64, @@ -1990,12 +1754,12 @@ fn parseFormValue( FORM.strx => .{ .strx = try fbr.readUleb128(usize) }, FORM.line_strp => .{ .line_strp = try fbr.readAddress(format) }, FORM.indirect => parseFormValue(fbr, try fbr.readUleb128(u64), format, implicit_const), - FORM.implicit_const => .{ .sdata = implicit_const orelse return badDwarf() }, + FORM.implicit_const => .{ .sdata = implicit_const orelse return bad() }, FORM.loclistx => .{ .loclistx = try fbr.readUleb128(u64) }, FORM.rnglistx => .{ .rnglistx = try fbr.readUleb128(u64) }, else => { //debug.print("unrecognized form id: {x}\n", .{form_id}); - return badDwarf(); + return bad(); }, }; } @@ -2090,14 +1854,14 @@ const LineNumberProgram = struct { self.target_address < self.address) { const file_index = if (self.version >= 5) self.prev_file else i: { - if (self.prev_file == 0) return missingDwarf(); + if (self.prev_file == 0) return missing(); break :i self.prev_file - 1; }; - if (file_index >= file_entries.len) return badDwarf(); + if (file_index >= file_entries.len) return bad(); const file_entry = &file_entries[file_index]; - if (file_entry.dir_index >= self.include_dirs.len) return badDwarf(); + if (file_entry.dir_index >= self.include_dirs.len) return bad(); const dir_name = self.include_dirs[file_entry.dir_index].path; const file_name = try std.fs.path.join(allocator, &[_][]const u8{ @@ -2128,14 +1892,14 @@ const UnitHeader = struct { header_length: u4, unit_length: u64, }; -fn readUnitHeader(fbr: *FixedBufferReader, opt_ma: ?*StackIterator.MemoryAccessor) !UnitHeader { +fn readUnitHeader(fbr: *DeprecatedFixedBufferReader, opt_ma: ?*MemoryAccessor) !UnitHeader { return switch (try if (opt_ma) |ma| fbr.readIntChecked(u32, ma) else fbr.readInt(u32)) { 0...0xfffffff0 - 1 => |unit_length| .{ .format = .@"32", .header_length = 4, .unit_length = unit_length, }, - 0xfffffff0...0xffffffff - 1 => badDwarf(), + 0xfffffff0...0xffffffff - 1 => bad(), 0xffffffff => .{ .format = .@"64", .header_length = 12, @@ -2145,7 +1909,7 @@ fn readUnitHeader(fbr: *FixedBufferReader, opt_ma: ?*StackIterator.MemoryAccesso } /// Returns the DWARF register number for an x86_64 register number found in compact unwind info -fn compactUnwindToDwarfRegNumber(unwind_reg_number: u3) !u8 { +pub fn compactUnwindToDwarfRegNumber(unwind_reg_number: u3) !u8 { return switch (unwind_reg_number) { 1 => 3, // RBX 2 => 12, // R12 @@ -2159,473 +1923,25 @@ fn compactUnwindToDwarfRegNumber(unwind_reg_number: u3) !u8 { /// This function is to make it handy to comment out the return and make it /// into a crash when working on this file. -fn badDwarf() error{InvalidDebugInfo} { - //if (true) @panic("badDwarf"); // can be handy to uncomment when working on this file +pub fn bad() error{InvalidDebugInfo} { + //if (true) @panic("bad dwarf"); // can be handy to uncomment when working on this file return error.InvalidDebugInfo; } -fn missingDwarf() error{MissingDebugInfo} { - //if (true) @panic("missingDwarf"); // can be handy to uncomment when working on this file +fn missing() error{MissingDebugInfo} { + //if (true) @panic("missing dwarf"); // can be handy to uncomment when working on this file return error.MissingDebugInfo; } fn getStringGeneric(opt_str: ?[]const u8, offset: u64) ![:0]const u8 { - const str = opt_str orelse return badDwarf(); - if (offset > str.len) return badDwarf(); - const casted_offset = cast(usize, offset) orelse return badDwarf(); + const str = opt_str orelse return bad(); + if (offset > str.len) return bad(); + const casted_offset = cast(usize, offset) orelse return bad(); // Valid strings always have a terminating zero byte - const last = std.mem.indexOfScalarPos(u8, str, casted_offset, 0) orelse return badDwarf(); + const last = std.mem.indexOfScalarPos(u8, str, casted_offset, 0) orelse return bad(); return str[casted_offset..last :0]; } -// Reading debug info needs to be fast, even when compiled in debug mode, -// so avoid using a `std.io.FixedBufferStream` which is too slow. -pub const FixedBufferReader = struct { - buf: []const u8, - pos: usize = 0, - endian: std.builtin.Endian, - - pub const Error = error{ EndOfBuffer, Overflow, InvalidBuffer }; - - fn seekTo(fbr: *FixedBufferReader, pos: u64) Error!void { - if (pos > fbr.buf.len) return error.EndOfBuffer; - fbr.pos = @intCast(pos); - } - - fn seekForward(fbr: *FixedBufferReader, amount: u64) Error!void { - if (fbr.buf.len - fbr.pos < amount) return error.EndOfBuffer; - fbr.pos += @intCast(amount); - } - - pub inline fn readByte(fbr: *FixedBufferReader) Error!u8 { - if (fbr.pos >= fbr.buf.len) return error.EndOfBuffer; - defer fbr.pos += 1; - return fbr.buf[fbr.pos]; - } - - fn readByteSigned(fbr: *FixedBufferReader) Error!i8 { - return @bitCast(try fbr.readByte()); - } - - fn readInt(fbr: *FixedBufferReader, comptime T: type) Error!T { - const size = @divExact(@typeInfo(T).Int.bits, 8); - if (fbr.buf.len - fbr.pos < size) return error.EndOfBuffer; - defer fbr.pos += size; - return std.mem.readInt(T, fbr.buf[fbr.pos..][0..size], fbr.endian); - } - - fn readIntChecked( - fbr: *FixedBufferReader, - comptime T: type, - ma: *std.debug.StackIterator.MemoryAccessor, - ) Error!T { - if (ma.load(T, @intFromPtr(fbr.buf[fbr.pos..].ptr)) == null) - return error.InvalidBuffer; - - return fbr.readInt(T); - } - - fn readUleb128(fbr: *FixedBufferReader, comptime T: type) Error!T { - return std.leb.readUleb128(T, fbr); - } - - fn readIleb128(fbr: *FixedBufferReader, comptime T: type) Error!T { - return std.leb.readIleb128(T, fbr); - } - - fn readAddress(fbr: *FixedBufferReader, format: Format) Error!u64 { - return switch (format) { - .@"32" => try fbr.readInt(u32), - .@"64" => try fbr.readInt(u64), - }; - } - - fn readAddressChecked( - fbr: *FixedBufferReader, - format: Format, - ma: *std.debug.StackIterator.MemoryAccessor, - ) Error!u64 { - return switch (format) { - .@"32" => try fbr.readIntChecked(u32, ma), - .@"64" => try fbr.readIntChecked(u64, ma), - }; - } - - fn readBytes(fbr: *FixedBufferReader, len: usize) Error![]const u8 { - if (fbr.buf.len - fbr.pos < len) return error.EndOfBuffer; - defer fbr.pos += len; - return fbr.buf[fbr.pos..][0..len]; - } - - fn readBytesTo(fbr: *FixedBufferReader, comptime sentinel: u8) Error![:sentinel]const u8 { - const end = @call(.always_inline, std.mem.indexOfScalarPos, .{ - u8, - fbr.buf, - fbr.pos, - sentinel, - }) orelse return error.EndOfBuffer; - defer fbr.pos = end + 1; - return fbr.buf[fbr.pos..end :sentinel]; - } -}; - -/// Unwind a frame using MachO compact unwind info (from __unwind_info). -/// If the compact encoding can't encode a way to unwind a frame, it will -/// defer unwinding to DWARF, in which case `.eh_frame` will be used if available. -pub fn unwindFrameMachO( - context: *UnwindContext, - ma: *StackIterator.MemoryAccessor, - unwind_info: []const u8, - eh_frame: ?[]const u8, - module_base_address: usize, -) !usize { - const macho = std.macho; - - const header = std.mem.bytesAsValue( - macho.unwind_info_section_header, - unwind_info[0..@sizeOf(macho.unwind_info_section_header)], - ); - const indices = std.mem.bytesAsSlice( - macho.unwind_info_section_header_index_entry, - unwind_info[header.indexSectionOffset..][0 .. header.indexCount * @sizeOf(macho.unwind_info_section_header_index_entry)], - ); - if (indices.len == 0) return error.MissingUnwindInfo; - - const mapped_pc = context.pc - module_base_address; - const second_level_index = blk: { - var left: usize = 0; - var len: usize = indices.len; - - while (len > 1) { - const mid = left + len / 2; - const offset = indices[mid].functionOffset; - if (mapped_pc < offset) { - len /= 2; - } else { - left = mid; - if (mapped_pc == offset) break; - len -= len / 2; - } - } - - // Last index is a sentinel containing the highest address as its functionOffset - if (indices[left].secondLevelPagesSectionOffset == 0) return error.MissingUnwindInfo; - break :blk &indices[left]; - }; - - const common_encodings = std.mem.bytesAsSlice( - macho.compact_unwind_encoding_t, - unwind_info[header.commonEncodingsArraySectionOffset..][0 .. header.commonEncodingsArrayCount * @sizeOf(macho.compact_unwind_encoding_t)], - ); - - const start_offset = second_level_index.secondLevelPagesSectionOffset; - const kind = std.mem.bytesAsValue( - macho.UNWIND_SECOND_LEVEL, - unwind_info[start_offset..][0..@sizeOf(macho.UNWIND_SECOND_LEVEL)], - ); - - const entry: struct { - function_offset: usize, - raw_encoding: u32, - } = switch (kind.*) { - .REGULAR => blk: { - const page_header = std.mem.bytesAsValue( - macho.unwind_info_regular_second_level_page_header, - unwind_info[start_offset..][0..@sizeOf(macho.unwind_info_regular_second_level_page_header)], - ); - - const entries = std.mem.bytesAsSlice( - macho.unwind_info_regular_second_level_entry, - unwind_info[start_offset + page_header.entryPageOffset ..][0 .. page_header.entryCount * @sizeOf(macho.unwind_info_regular_second_level_entry)], - ); - if (entries.len == 0) return error.InvalidUnwindInfo; - - var left: usize = 0; - var len: usize = entries.len; - while (len > 1) { - const mid = left + len / 2; - const offset = entries[mid].functionOffset; - if (mapped_pc < offset) { - len /= 2; - } else { - left = mid; - if (mapped_pc == offset) break; - len -= len / 2; - } - } - - break :blk .{ - .function_offset = entries[left].functionOffset, - .raw_encoding = entries[left].encoding, - }; - }, - .COMPRESSED => blk: { - const page_header = std.mem.bytesAsValue( - macho.unwind_info_compressed_second_level_page_header, - unwind_info[start_offset..][0..@sizeOf(macho.unwind_info_compressed_second_level_page_header)], - ); - - const entries = std.mem.bytesAsSlice( - macho.UnwindInfoCompressedEntry, - unwind_info[start_offset + page_header.entryPageOffset ..][0 .. page_header.entryCount * @sizeOf(macho.UnwindInfoCompressedEntry)], - ); - if (entries.len == 0) return error.InvalidUnwindInfo; - - var left: usize = 0; - var len: usize = entries.len; - while (len > 1) { - const mid = left + len / 2; - const offset = second_level_index.functionOffset + entries[mid].funcOffset; - if (mapped_pc < offset) { - len /= 2; - } else { - left = mid; - if (mapped_pc == offset) break; - len -= len / 2; - } - } - - const entry = entries[left]; - const function_offset = second_level_index.functionOffset + entry.funcOffset; - if (entry.encodingIndex < header.commonEncodingsArrayCount) { - if (entry.encodingIndex >= common_encodings.len) return error.InvalidUnwindInfo; - break :blk .{ - .function_offset = function_offset, - .raw_encoding = common_encodings[entry.encodingIndex], - }; - } else { - const local_index = try std.math.sub( - u8, - entry.encodingIndex, - cast(u8, header.commonEncodingsArrayCount) orelse return error.InvalidUnwindInfo, - ); - const local_encodings = std.mem.bytesAsSlice( - macho.compact_unwind_encoding_t, - unwind_info[start_offset + page_header.encodingsPageOffset ..][0 .. page_header.encodingsCount * @sizeOf(macho.compact_unwind_encoding_t)], - ); - if (local_index >= local_encodings.len) return error.InvalidUnwindInfo; - break :blk .{ - .function_offset = function_offset, - .raw_encoding = local_encodings[local_index], - }; - } - }, - else => return error.InvalidUnwindInfo, - }; - - if (entry.raw_encoding == 0) return error.NoUnwindInfo; - const reg_context = abi.RegisterContext{ - .eh_frame = false, - .is_macho = true, - }; - - const encoding: macho.CompactUnwindEncoding = @bitCast(entry.raw_encoding); - const new_ip = switch (builtin.cpu.arch) { - .x86_64 => switch (encoding.mode.x86_64) { - .OLD => return error.UnimplementedUnwindEncoding, - .RBP_FRAME => blk: { - const regs: [5]u3 = .{ - encoding.value.x86_64.frame.reg0, - encoding.value.x86_64.frame.reg1, - encoding.value.x86_64.frame.reg2, - encoding.value.x86_64.frame.reg3, - encoding.value.x86_64.frame.reg4, - }; - - const frame_offset = encoding.value.x86_64.frame.frame_offset * @sizeOf(usize); - var max_reg: usize = 0; - inline for (regs, 0..) |reg, i| { - if (reg > 0) max_reg = i; - } - - const fp = (try abi.regValueNative(usize, context.thread_context, abi.fpRegNum(reg_context), reg_context)).*; - const new_sp = fp + 2 * @sizeOf(usize); - - // Verify the stack range we're about to read register values from - if (ma.load(usize, new_sp) == null or ma.load(usize, fp - frame_offset + max_reg * @sizeOf(usize)) == null) return error.InvalidUnwindInfo; - - const ip_ptr = fp + @sizeOf(usize); - const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*; - const new_fp = @as(*const usize, @ptrFromInt(fp)).*; - - (try abi.regValueNative(usize, context.thread_context, abi.fpRegNum(reg_context), reg_context)).* = new_fp; - (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).* = new_sp; - (try abi.regValueNative(usize, context.thread_context, abi.ipRegNum(), reg_context)).* = new_ip; - - for (regs, 0..) |reg, i| { - if (reg == 0) continue; - const addr = fp - frame_offset + i * @sizeOf(usize); - const reg_number = try compactUnwindToDwarfRegNumber(reg); - (try abi.regValueNative(usize, context.thread_context, reg_number, reg_context)).* = @as(*const usize, @ptrFromInt(addr)).*; - } - - break :blk new_ip; - }, - .STACK_IMMD, - .STACK_IND, - => blk: { - const sp = (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).*; - const stack_size = if (encoding.mode.x86_64 == .STACK_IMMD) - @as(usize, encoding.value.x86_64.frameless.stack.direct.stack_size) * @sizeOf(usize) - else stack_size: { - // In .STACK_IND, the stack size is inferred from the subq instruction at the beginning of the function. - const sub_offset_addr = - module_base_address + - entry.function_offset + - encoding.value.x86_64.frameless.stack.indirect.sub_offset; - if (ma.load(usize, sub_offset_addr) == null) return error.InvalidUnwindInfo; - - // `sub_offset_addr` points to the offset of the literal within the instruction - const sub_operand = @as(*align(1) const u32, @ptrFromInt(sub_offset_addr)).*; - break :stack_size sub_operand + @sizeOf(usize) * @as(usize, encoding.value.x86_64.frameless.stack.indirect.stack_adjust); - }; - - // Decode the Lehmer-coded sequence of registers. - // For a description of the encoding see lib/libc/include/any-macos.13-any/mach-o/compact_unwind_encoding.h - - // Decode the variable-based permutation number into its digits. Each digit represents - // an index into the list of register numbers that weren't yet used in the sequence at - // the time the digit was added. - const reg_count = encoding.value.x86_64.frameless.stack_reg_count; - const ip_ptr = if (reg_count > 0) reg_blk: { - var digits: [6]u3 = undefined; - var accumulator: usize = encoding.value.x86_64.frameless.stack_reg_permutation; - var base: usize = 2; - for (0..reg_count) |i| { - const div = accumulator / base; - digits[digits.len - 1 - i] = @intCast(accumulator - base * div); - accumulator = div; - base += 1; - } - - const reg_numbers = [_]u3{ 1, 2, 3, 4, 5, 6 }; - var registers: [reg_numbers.len]u3 = undefined; - var used_indices = [_]bool{false} ** reg_numbers.len; - for (digits[digits.len - reg_count ..], 0..) |target_unused_index, i| { - var unused_count: u8 = 0; - const unused_index = for (used_indices, 0..) |used, index| { - if (!used) { - if (target_unused_index == unused_count) break index; - unused_count += 1; - } - } else unreachable; - - registers[i] = reg_numbers[unused_index]; - used_indices[unused_index] = true; - } - - var reg_addr = sp + stack_size - @sizeOf(usize) * @as(usize, reg_count + 1); - if (ma.load(usize, reg_addr) == null) return error.InvalidUnwindInfo; - for (0..reg_count) |i| { - const reg_number = try compactUnwindToDwarfRegNumber(registers[i]); - (try abi.regValueNative(usize, context.thread_context, reg_number, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*; - reg_addr += @sizeOf(usize); - } - - break :reg_blk reg_addr; - } else sp + stack_size - @sizeOf(usize); - - const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*; - const new_sp = ip_ptr + @sizeOf(usize); - if (ma.load(usize, new_sp) == null) return error.InvalidUnwindInfo; - - (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).* = new_sp; - (try abi.regValueNative(usize, context.thread_context, abi.ipRegNum(), reg_context)).* = new_ip; - - break :blk new_ip; - }, - .DWARF => { - return unwindFrameMachODwarf(context, ma, eh_frame orelse return error.MissingEhFrame, @intCast(encoding.value.x86_64.dwarf)); - }, - }, - .aarch64 => switch (encoding.mode.arm64) { - .OLD => return error.UnimplementedUnwindEncoding, - .FRAMELESS => blk: { - const sp = (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).*; - const new_sp = sp + encoding.value.arm64.frameless.stack_size * 16; - const new_ip = (try abi.regValueNative(usize, context.thread_context, 30, reg_context)).*; - if (ma.load(usize, new_sp) == null) return error.InvalidUnwindInfo; - (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).* = new_sp; - break :blk new_ip; - }, - .DWARF => { - return unwindFrameMachODwarf(context, ma, eh_frame orelse return error.MissingEhFrame, @intCast(encoding.value.arm64.dwarf)); - }, - .FRAME => blk: { - const fp = (try abi.regValueNative(usize, context.thread_context, abi.fpRegNum(reg_context), reg_context)).*; - const new_sp = fp + 16; - const ip_ptr = fp + @sizeOf(usize); - - const num_restored_pairs: usize = - @popCount(@as(u5, @bitCast(encoding.value.arm64.frame.x_reg_pairs))) + - @popCount(@as(u4, @bitCast(encoding.value.arm64.frame.d_reg_pairs))); - const min_reg_addr = fp - num_restored_pairs * 2 * @sizeOf(usize); - - if (ma.load(usize, new_sp) == null or ma.load(usize, min_reg_addr) == null) return error.InvalidUnwindInfo; - - var reg_addr = fp - @sizeOf(usize); - inline for (@typeInfo(@TypeOf(encoding.value.arm64.frame.x_reg_pairs)).Struct.fields, 0..) |field, i| { - if (@field(encoding.value.arm64.frame.x_reg_pairs, field.name) != 0) { - (try abi.regValueNative(usize, context.thread_context, 19 + i, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*; - reg_addr += @sizeOf(usize); - (try abi.regValueNative(usize, context.thread_context, 20 + i, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*; - reg_addr += @sizeOf(usize); - } - } - - inline for (@typeInfo(@TypeOf(encoding.value.arm64.frame.d_reg_pairs)).Struct.fields, 0..) |field, i| { - if (@field(encoding.value.arm64.frame.d_reg_pairs, field.name) != 0) { - // Only the lower half of the 128-bit V registers are restored during unwinding - @memcpy( - try abi.regBytes(context.thread_context, 64 + 8 + i, context.reg_context), - std.mem.asBytes(@as(*const usize, @ptrFromInt(reg_addr))), - ); - reg_addr += @sizeOf(usize); - @memcpy( - try abi.regBytes(context.thread_context, 64 + 9 + i, context.reg_context), - std.mem.asBytes(@as(*const usize, @ptrFromInt(reg_addr))), - ); - reg_addr += @sizeOf(usize); - } - } - - const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*; - const new_fp = @as(*const usize, @ptrFromInt(fp)).*; - - (try abi.regValueNative(usize, context.thread_context, abi.fpRegNum(reg_context), reg_context)).* = new_fp; - (try abi.regValueNative(usize, context.thread_context, abi.ipRegNum(), reg_context)).* = new_ip; - - break :blk new_ip; - }, - }, - else => return error.UnimplementedArch, - }; - - context.pc = abi.stripInstructionPtrAuthCode(new_ip); - if (context.pc > 0) context.pc -= 1; - return new_ip; -} - -fn unwindFrameMachODwarf( - context: *UnwindContext, - ma: *std.debug.StackIterator.MemoryAccessor, - eh_frame: []const u8, - fde_offset: usize, -) !usize { - var di = Dwarf{ - .endian = native_endian, - .is_macho = true, - }; - defer di.deinit(context.allocator); - - di.sections[@intFromEnum(Section.Id.eh_frame)] = .{ - .data = eh_frame, - .owned = false, - }; - - return di.unwindFrame(context, ma, fde_offset); -} - const EhPointerContext = struct { // The address of the pointer field itself pc_rel_base: u64, @@ -2641,7 +1957,7 @@ const EhPointerContext = struct { text_rel_base: ?u64 = null, function_rel_base: ?u64 = null, }; -fn readEhPointer(fbr: *FixedBufferReader, enc: u8, addr_size_bytes: u8, ctx: EhPointerContext) !?u64 { +fn readEhPointer(fbr: *DeprecatedFixedBufferReader, enc: u8, addr_size_bytes: u8, ctx: EhPointerContext) !?u64 { if (enc == EH.PE.omit) return null; const value: union(enum) { @@ -2664,7 +1980,7 @@ fn readEhPointer(fbr: *FixedBufferReader, enc: u8, addr_size_bytes: u8, ctx: EhP EH.PE.sdata2 => .{ .signed = try fbr.readInt(i16) }, EH.PE.sdata4 => .{ .signed = try fbr.readInt(i32) }, EH.PE.sdata8 => .{ .signed = try fbr.readInt(i64) }, - else => return badDwarf(), + else => return bad(), }; const base = switch (enc & EH.PE.rel_mask) { diff --git a/lib/std/debug/Dwarf/abi.zig b/lib/std/debug/Dwarf/abi.zig index 1a47625ae7..e87f023d72 100644 --- a/lib/std/debug/Dwarf/abi.zig +++ b/lib/std/debug/Dwarf/abi.zig @@ -1,8 +1,9 @@ const builtin = @import("builtin"); + const std = @import("../../std.zig"); const mem = std.mem; -const native_os = builtin.os.tag; const posix = std.posix; +const Arch = std.Target.Cpu.Arch; pub fn supportsUnwinding(target: std.Target) bool { return switch (target.cpu.arch) { @@ -26,8 +27,8 @@ pub fn supportsUnwinding(target: std.Target) bool { }; } -pub fn ipRegNum() u8 { - return switch (builtin.cpu.arch) { +pub fn ipRegNum(arch: Arch) u8 { + return switch (arch) { .x86 => 8, .x86_64 => 16, .arm => 15, @@ -36,9 +37,10 @@ pub fn ipRegNum() u8 { }; } -pub fn fpRegNum(reg_context: RegisterContext) u8 { - return switch (builtin.cpu.arch) { - // GCC on OS X historically did the opposite of ELF for these registers (only in .eh_frame), and that is now the convention for MachO +pub fn fpRegNum(arch: Arch, reg_context: RegisterContext) u8 { + return switch (arch) { + // GCC on OS X historically did the opposite of ELF for these registers + // (only in .eh_frame), and that is now the convention for MachO .x86 => if (reg_context.eh_frame and reg_context.is_macho) 4 else 5, .x86_64 => 6, .arm => 11, @@ -47,8 +49,8 @@ pub fn fpRegNum(reg_context: RegisterContext) u8 { }; } -pub fn spRegNum(reg_context: RegisterContext) u8 { - return switch (builtin.cpu.arch) { +pub fn spRegNum(arch: Arch, reg_context: RegisterContext) u8 { + return switch (arch) { .x86 => if (reg_context.eh_frame and reg_context.is_macho) 5 else 4, .x86_64 => 7, .arm => 13, @@ -57,33 +59,12 @@ pub fn spRegNum(reg_context: RegisterContext) u8 { }; } -/// Some platforms use pointer authentication - the upper bits of instruction pointers contain a signature. -/// This function clears these signature bits to make the pointer usable. -pub inline fn stripInstructionPtrAuthCode(ptr: usize) usize { - if (builtin.cpu.arch == .aarch64) { - // `hint 0x07` maps to `xpaclri` (or `nop` if the hardware doesn't support it) - // The save / restore is because `xpaclri` operates on x30 (LR) - return asm ( - \\mov x16, x30 - \\mov x30, x15 - \\hint 0x07 - \\mov x15, x30 - \\mov x30, x16 - : [ret] "={x15}" (-> usize), - : [ptr] "{x15}" (ptr), - : "x16" - ); - } - - return ptr; -} - pub const RegisterContext = struct { eh_frame: bool, is_macho: bool, }; -pub const AbiError = error{ +pub const RegBytesError = error{ InvalidRegister, UnimplementedArch, UnimplementedOs, @@ -91,55 +72,21 @@ pub const AbiError = error{ ThreadContextNotSupported, }; -fn RegValueReturnType(comptime ContextPtrType: type, comptime T: type) type { - const reg_bytes_type = comptime RegBytesReturnType(ContextPtrType); - const info = @typeInfo(reg_bytes_type).Pointer; - return @Type(.{ - .Pointer = .{ - .size = .One, - .is_const = info.is_const, - .is_volatile = info.is_volatile, - .is_allowzero = info.is_allowzero, - .alignment = info.alignment, - .address_space = info.address_space, - .child = T, - .sentinel = null, - }, - }); -} - -/// Returns a pointer to a register stored in a ThreadContext, preserving the pointer attributes of the context. -pub fn regValueNative( - comptime T: type, - thread_context_ptr: anytype, - reg_number: u8, - reg_context: ?RegisterContext, -) !RegValueReturnType(@TypeOf(thread_context_ptr), T) { - const reg_bytes = try regBytes(thread_context_ptr, reg_number, reg_context); - if (@sizeOf(T) != reg_bytes.len) return error.IncompatibleRegisterSize; - return mem.bytesAsValue(T, reg_bytes[0..@sizeOf(T)]); -} - -fn RegBytesReturnType(comptime ContextPtrType: type) type { - const info = @typeInfo(ContextPtrType); - if (info != .Pointer or info.Pointer.child != std.debug.ThreadContext) { - @compileError("Expected a pointer to std.debug.ThreadContext, got " ++ @typeName(@TypeOf(ContextPtrType))); - } - - return if (info.Pointer.is_const) return []const u8 else []u8; -} - /// Returns a slice containing the backing storage for `reg_number`. /// +/// This function assumes the Dwarf information corresponds not necessarily to +/// the current executable, but at least with a matching CPU architecture and +/// OS. It is planned to lift this limitation with a future enhancement. +/// /// `reg_context` describes in what context the register number is used, as it can have different /// meanings depending on the DWARF container. It is only required when getting the stack or /// frame pointer register on some architectures. pub fn regBytes( - thread_context_ptr: anytype, + thread_context_ptr: *std.debug.ThreadContext, reg_number: u8, reg_context: ?RegisterContext, -) AbiError!RegBytesReturnType(@TypeOf(thread_context_ptr)) { - if (native_os == .windows) { +) RegBytesError![]u8 { + if (builtin.os.tag == .windows) { return switch (builtin.cpu.arch) { .x86 => switch (reg_number) { 0 => mem.asBytes(&thread_context_ptr.Eax), @@ -194,7 +141,7 @@ pub fn regBytes( const ucontext_ptr = thread_context_ptr; return switch (builtin.cpu.arch) { - .x86 => switch (native_os) { + .x86 => switch (builtin.os.tag) { .linux, .netbsd, .solaris, .illumos => switch (reg_number) { 0 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.EAX]), 1 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.ECX]), @@ -229,7 +176,7 @@ pub fn regBytes( }, else => error.UnimplementedOs, }, - .x86_64 => switch (native_os) { + .x86_64 => switch (builtin.os.tag) { .linux, .solaris, .illumos => switch (reg_number) { 0 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RAX]), 1 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RDX]), @@ -248,7 +195,7 @@ pub fn regBytes( 14 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.R14]), 15 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.R15]), 16 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RIP]), - 17...32 => |i| if (native_os.isSolarish()) + 17...32 => |i| if (builtin.os.tag.isSolarish()) mem.asBytes(&ucontext_ptr.mcontext.fpregs.chip_state.xmm[i - 17]) else mem.asBytes(&ucontext_ptr.mcontext.fpregs.xmm[i - 17]), @@ -318,7 +265,7 @@ pub fn regBytes( }, else => error.UnimplementedOs, }, - .arm => switch (native_os) { + .arm => switch (builtin.os.tag) { .linux => switch (reg_number) { 0 => mem.asBytes(&ucontext_ptr.mcontext.arm_r0), 1 => mem.asBytes(&ucontext_ptr.mcontext.arm_r1), @@ -341,7 +288,7 @@ pub fn regBytes( }, else => error.UnimplementedOs, }, - .aarch64 => switch (native_os) { + .aarch64 => switch (builtin.os.tag) { .macos, .ios => switch (reg_number) { 0...28 => mem.asBytes(&ucontext_ptr.mcontext.ss.regs[reg_number]), 29 => mem.asBytes(&ucontext_ptr.mcontext.ss.fp), @@ -389,22 +336,14 @@ pub fn regBytes( }; } -/// Returns the ABI-defined default value this register has in the unwinding table -/// before running any of the CIE instructions. The DWARF spec defines these as having -/// the .undefined rule by default, but allows ABI authors to override that. -pub fn getRegDefaultValue(reg_number: u8, context: *std.debug.Dwarf.UnwindContext, out: []u8) !void { - switch (builtin.cpu.arch) { - .aarch64 => { - // Callee-saved registers are initialized as if they had the .same_value rule - if (reg_number >= 19 and reg_number <= 28) { - const src = try regBytes(context.thread_context, reg_number, context.reg_context); - if (src.len != out.len) return error.RegisterSizeMismatch; - @memcpy(out, src); - return; - } - }, - else => {}, - } - - @memset(out, undefined); +/// Returns a pointer to a register stored in a ThreadContext, preserving the +/// pointer attributes of the context. +pub fn regValueNative( + thread_context_ptr: *std.debug.ThreadContext, + reg_number: u8, + reg_context: ?RegisterContext, +) !*align(1) usize { + const reg_bytes = try regBytes(thread_context_ptr, reg_number, reg_context); + if (@sizeOf(usize) != reg_bytes.len) return error.IncompatibleRegisterSize; + return mem.bytesAsValue(usize, reg_bytes[0..@sizeOf(usize)]); } diff --git a/lib/std/debug/Dwarf/call_frame.zig b/lib/std/debug/Dwarf/call_frame.zig index 73e00d3099..3e3d2585db 100644 --- a/lib/std/debug/Dwarf/call_frame.zig +++ b/lib/std/debug/Dwarf/call_frame.zig @@ -297,391 +297,3 @@ pub const Instruction = union(Opcode) { } } }; - -/// Since register rules are applied (usually) during a panic, -/// checked addition / subtraction is used so that we can return -/// an error and fall back to FP-based unwinding. -pub fn applyOffset(base: usize, offset: i64) !usize { - return if (offset >= 0) - try std.math.add(usize, base, @as(usize, @intCast(offset))) - else - try std.math.sub(usize, base, @as(usize, @intCast(-offset))); -} - -/// This is a virtual machine that runs DWARF call frame instructions. -pub const VirtualMachine = struct { - /// See section 6.4.1 of the DWARF5 specification for details on each - const RegisterRule = union(enum) { - // The spec says that the default rule for each column is the undefined rule. - // However, it also allows ABI / compiler authors to specify alternate defaults, so - // there is a distinction made here. - default: void, - - undefined: void, - same_value: void, - - // offset(N) - offset: i64, - - // val_offset(N) - val_offset: i64, - - // register(R) - register: u8, - - // expression(E) - expression: []const u8, - - // val_expression(E) - val_expression: []const u8, - - // Augmenter-defined rule - architectural: void, - }; - - /// Each row contains unwinding rules for a set of registers. - pub const Row = struct { - /// Offset from `FrameDescriptionEntry.pc_begin` - offset: u64 = 0, - - /// Special-case column that defines the CFA (Canonical Frame Address) rule. - /// The register field of this column defines the register that CFA is derived from. - cfa: Column = .{}, - - /// The register fields in these columns define the register the rule applies to. - columns: ColumnRange = .{}, - - /// Indicates that the next write to any column in this row needs to copy - /// the backing column storage first, as it may be referenced by previous rows. - copy_on_write: bool = false, - }; - - pub const Column = struct { - register: ?u8 = null, - rule: RegisterRule = .{ .default = {} }, - - /// Resolves the register rule and places the result into `out` (see dwarf.abi.regBytes) - pub fn resolveValue( - self: Column, - context: *std.debug.Dwarf.UnwindContext, - expression_context: std.debug.Dwarf.expression.Context, - ma: *debug.StackIterator.MemoryAccessor, - out: []u8, - ) !void { - switch (self.rule) { - .default => { - const register = self.register orelse return error.InvalidRegister; - try abi.getRegDefaultValue(register, context, out); - }, - .undefined => { - @memset(out, undefined); - }, - .same_value => { - // TODO: This copy could be eliminated if callers always copy the state then call this function to update it - const register = self.register orelse return error.InvalidRegister; - const src = try abi.regBytes(context.thread_context, register, context.reg_context); - if (src.len != out.len) return error.RegisterSizeMismatch; - @memcpy(out, src); - }, - .offset => |offset| { - if (context.cfa) |cfa| { - const addr = try applyOffset(cfa, offset); - if (ma.load(usize, addr) == null) return error.InvalidAddress; - const ptr: *const usize = @ptrFromInt(addr); - mem.writeInt(usize, out[0..@sizeOf(usize)], ptr.*, native_endian); - } else return error.InvalidCFA; - }, - .val_offset => |offset| { - if (context.cfa) |cfa| { - mem.writeInt(usize, out[0..@sizeOf(usize)], try applyOffset(cfa, offset), native_endian); - } else return error.InvalidCFA; - }, - .register => |register| { - const src = try abi.regBytes(context.thread_context, register, context.reg_context); - if (src.len != out.len) return error.RegisterSizeMismatch; - @memcpy(out, try abi.regBytes(context.thread_context, register, context.reg_context)); - }, - .expression => |expression| { - context.stack_machine.reset(); - const value = try context.stack_machine.run(expression, context.allocator, expression_context, context.cfa.?); - const addr = if (value) |v| blk: { - if (v != .generic) return error.InvalidExpressionValue; - break :blk v.generic; - } else return error.NoExpressionValue; - - if (ma.load(usize, addr) == null) return error.InvalidExpressionAddress; - const ptr: *usize = @ptrFromInt(addr); - mem.writeInt(usize, out[0..@sizeOf(usize)], ptr.*, native_endian); - }, - .val_expression => |expression| { - context.stack_machine.reset(); - const value = try context.stack_machine.run(expression, context.allocator, expression_context, context.cfa.?); - if (value) |v| { - if (v != .generic) return error.InvalidExpressionValue; - mem.writeInt(usize, out[0..@sizeOf(usize)], v.generic, native_endian); - } else return error.NoExpressionValue; - }, - .architectural => return error.UnimplementedRegisterRule, - } - } - }; - - const ColumnRange = struct { - /// Index into `columns` of the first column in this row. - start: usize = undefined, - len: u8 = 0, - }; - - columns: std.ArrayListUnmanaged(Column) = .{}, - stack: std.ArrayListUnmanaged(ColumnRange) = .{}, - current_row: Row = .{}, - - /// The result of executing the CIE's initial_instructions - cie_row: ?Row = null, - - pub fn deinit(self: *VirtualMachine, allocator: std.mem.Allocator) void { - self.stack.deinit(allocator); - self.columns.deinit(allocator); - self.* = undefined; - } - - pub fn reset(self: *VirtualMachine) void { - self.stack.clearRetainingCapacity(); - self.columns.clearRetainingCapacity(); - self.current_row = .{}; - self.cie_row = null; - } - - /// Return a slice backed by the row's non-CFA columns - pub fn rowColumns(self: VirtualMachine, row: Row) []Column { - if (row.columns.len == 0) return &.{}; - return self.columns.items[row.columns.start..][0..row.columns.len]; - } - - /// Either retrieves or adds a column for `register` (non-CFA) in the current row. - fn getOrAddColumn(self: *VirtualMachine, allocator: std.mem.Allocator, register: u8) !*Column { - for (self.rowColumns(self.current_row)) |*c| { - if (c.register == register) return c; - } - - if (self.current_row.columns.len == 0) { - self.current_row.columns.start = self.columns.items.len; - } - self.current_row.columns.len += 1; - - const column = try self.columns.addOne(allocator); - column.* = .{ - .register = register, - }; - - return column; - } - - /// Runs the CIE instructions, then the FDE instructions. Execution halts - /// once the row that corresponds to `pc` is known, and the row is returned. - pub fn runTo( - self: *VirtualMachine, - allocator: std.mem.Allocator, - pc: u64, - cie: std.debug.Dwarf.CommonInformationEntry, - fde: std.debug.Dwarf.FrameDescriptionEntry, - addr_size_bytes: u8, - endian: std.builtin.Endian, - ) !Row { - assert(self.cie_row == null); - if (pc < fde.pc_begin or pc >= fde.pc_begin + fde.pc_range) return error.AddressOutOfRange; - - var prev_row: Row = self.current_row; - - var cie_stream = std.io.fixedBufferStream(cie.initial_instructions); - var fde_stream = std.io.fixedBufferStream(fde.instructions); - var streams = [_]*std.io.FixedBufferStream([]const u8){ - &cie_stream, - &fde_stream, - }; - - for (&streams, 0..) |stream, i| { - while (stream.pos < stream.buffer.len) { - const instruction = try std.debug.Dwarf.call_frame.Instruction.read(stream, addr_size_bytes, endian); - prev_row = try self.step(allocator, cie, i == 0, instruction); - if (pc < fde.pc_begin + self.current_row.offset) return prev_row; - } - } - - return self.current_row; - } - - pub fn runToNative( - self: *VirtualMachine, - allocator: std.mem.Allocator, - pc: u64, - cie: std.debug.Dwarf.CommonInformationEntry, - fde: std.debug.Dwarf.FrameDescriptionEntry, - ) !Row { - return self.runTo(allocator, pc, cie, fde, @sizeOf(usize), builtin.target.cpu.arch.endian()); - } - - fn resolveCopyOnWrite(self: *VirtualMachine, allocator: std.mem.Allocator) !void { - if (!self.current_row.copy_on_write) return; - - const new_start = self.columns.items.len; - if (self.current_row.columns.len > 0) { - try self.columns.ensureUnusedCapacity(allocator, self.current_row.columns.len); - self.columns.appendSliceAssumeCapacity(self.rowColumns(self.current_row)); - self.current_row.columns.start = new_start; - } - } - - /// Executes a single instruction. - /// If this instruction is from the CIE, `is_initial` should be set. - /// Returns the value of `current_row` before executing this instruction. - pub fn step( - self: *VirtualMachine, - allocator: std.mem.Allocator, - cie: std.debug.Dwarf.CommonInformationEntry, - is_initial: bool, - instruction: Instruction, - ) !Row { - // CIE instructions must be run before FDE instructions - assert(!is_initial or self.cie_row == null); - if (!is_initial and self.cie_row == null) { - self.cie_row = self.current_row; - self.current_row.copy_on_write = true; - } - - const prev_row = self.current_row; - switch (instruction) { - .set_loc => |i| { - if (i.address <= self.current_row.offset) return error.InvalidOperation; - // TODO: Check cie.segment_selector_size != 0 for DWARFV4 - self.current_row.offset = i.address; - }, - inline .advance_loc, - .advance_loc1, - .advance_loc2, - .advance_loc4, - => |i| { - self.current_row.offset += i.delta * cie.code_alignment_factor; - self.current_row.copy_on_write = true; - }, - inline .offset, - .offset_extended, - .offset_extended_sf, - => |i| { - try self.resolveCopyOnWrite(allocator); - const column = try self.getOrAddColumn(allocator, i.register); - column.rule = .{ .offset = @as(i64, @intCast(i.offset)) * cie.data_alignment_factor }; - }, - inline .restore, - .restore_extended, - => |i| { - try self.resolveCopyOnWrite(allocator); - if (self.cie_row) |cie_row| { - const column = try self.getOrAddColumn(allocator, i.register); - column.rule = for (self.rowColumns(cie_row)) |cie_column| { - if (cie_column.register == i.register) break cie_column.rule; - } else .{ .default = {} }; - } else return error.InvalidOperation; - }, - .nop => {}, - .undefined => |i| { - try self.resolveCopyOnWrite(allocator); - const column = try self.getOrAddColumn(allocator, i.register); - column.rule = .{ .undefined = {} }; - }, - .same_value => |i| { - try self.resolveCopyOnWrite(allocator); - const column = try self.getOrAddColumn(allocator, i.register); - column.rule = .{ .same_value = {} }; - }, - .register => |i| { - try self.resolveCopyOnWrite(allocator); - const column = try self.getOrAddColumn(allocator, i.register); - column.rule = .{ .register = i.target_register }; - }, - .remember_state => { - try self.stack.append(allocator, self.current_row.columns); - self.current_row.copy_on_write = true; - }, - .restore_state => { - const restored_columns = self.stack.popOrNull() orelse return error.InvalidOperation; - self.columns.shrinkRetainingCapacity(self.columns.items.len - self.current_row.columns.len); - try self.columns.ensureUnusedCapacity(allocator, restored_columns.len); - - self.current_row.columns.start = self.columns.items.len; - self.current_row.columns.len = restored_columns.len; - self.columns.appendSliceAssumeCapacity(self.columns.items[restored_columns.start..][0..restored_columns.len]); - }, - .def_cfa => |i| { - try self.resolveCopyOnWrite(allocator); - self.current_row.cfa = .{ - .register = i.register, - .rule = .{ .val_offset = @intCast(i.offset) }, - }; - }, - .def_cfa_sf => |i| { - try self.resolveCopyOnWrite(allocator); - self.current_row.cfa = .{ - .register = i.register, - .rule = .{ .val_offset = i.offset * cie.data_alignment_factor }, - }; - }, - .def_cfa_register => |i| { - try self.resolveCopyOnWrite(allocator); - if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .val_offset) return error.InvalidOperation; - self.current_row.cfa.register = i.register; - }, - .def_cfa_offset => |i| { - try self.resolveCopyOnWrite(allocator); - if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .val_offset) return error.InvalidOperation; - self.current_row.cfa.rule = .{ - .val_offset = @intCast(i.offset), - }; - }, - .def_cfa_offset_sf => |i| { - try self.resolveCopyOnWrite(allocator); - if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .val_offset) return error.InvalidOperation; - self.current_row.cfa.rule = .{ - .val_offset = i.offset * cie.data_alignment_factor, - }; - }, - .def_cfa_expression => |i| { - try self.resolveCopyOnWrite(allocator); - self.current_row.cfa.register = undefined; - self.current_row.cfa.rule = .{ - .expression = i.block, - }; - }, - .expression => |i| { - try self.resolveCopyOnWrite(allocator); - const column = try self.getOrAddColumn(allocator, i.register); - column.rule = .{ - .expression = i.block, - }; - }, - .val_offset => |i| { - try self.resolveCopyOnWrite(allocator); - const column = try self.getOrAddColumn(allocator, i.register); - column.rule = .{ - .val_offset = @as(i64, @intCast(i.offset)) * cie.data_alignment_factor, - }; - }, - .val_offset_sf => |i| { - try self.resolveCopyOnWrite(allocator); - const column = try self.getOrAddColumn(allocator, i.register); - column.rule = .{ - .val_offset = i.offset * cie.data_alignment_factor, - }; - }, - .val_expression => |i| { - try self.resolveCopyOnWrite(allocator); - const column = try self.getOrAddColumn(allocator, i.register); - column.rule = .{ - .val_expression = i.block, - }; - }, - } - - return prev_row; - } -}; diff --git a/lib/std/debug/Dwarf/expression.zig b/lib/std/debug/Dwarf/expression.zig index 6243ea9717..7a3a4ed740 100644 --- a/lib/std/debug/Dwarf/expression.zig +++ b/lib/std/debug/Dwarf/expression.zig @@ -1,11 +1,13 @@ -const std = @import("std"); const builtin = @import("builtin"); +const native_arch = builtin.cpu.arch; +const native_endian = native_arch.endian(); + +const std = @import("std"); const leb = std.leb; const OP = std.dwarf.OP; const abi = std.debug.Dwarf.abi; const mem = std.mem; const assert = std.debug.assert; -const native_endian = builtin.cpu.arch.endian(); /// Expressions can be evaluated in different contexts, each requiring its own set of inputs. /// Callers should specify all the fields relevant to their context. If a field is required @@ -14,7 +16,7 @@ pub const Context = struct { /// The dwarf format of the section this expression is in format: std.dwarf.Format = .@"32", /// If specified, any addresses will pass through before being accessed - memory_accessor: ?*std.debug.StackIterator.MemoryAccessor = null, + memory_accessor: ?*std.debug.MemoryAccessor = null, /// The compilation unit this expression relates to, if any compile_unit: ?*const std.debug.Dwarf.CompileUnit = null, /// When evaluating a user-presented expression, this is the address of the object being evaluated @@ -34,7 +36,7 @@ pub const Options = struct { /// The address size of the target architecture addr_size: u8 = @sizeOf(usize), /// Endianness of the target architecture - endian: std.builtin.Endian = builtin.target.cpu.arch.endian(), + endian: std.builtin.Endian = native_endian, /// Restrict the stack machine to a subset of opcodes used in call frame instructions call_frame_context: bool = false, }; @@ -60,7 +62,7 @@ pub const Error = error{ InvalidTypeLength, TruncatedIntegralType, -} || abi.AbiError || error{ EndOfStream, Overflow, OutOfMemory, DivisionByZero }; +} || abi.RegBytesError || error{ EndOfStream, Overflow, OutOfMemory, DivisionByZero }; /// A stack machine that can decode and run DWARF expressions. /// Expressions can be decoded for non-native address size and endianness, @@ -304,7 +306,7 @@ pub fn StackMachine(comptime options: Options) type { allocator: std.mem.Allocator, context: Context, ) Error!bool { - if (@sizeOf(usize) != @sizeOf(addr_type) or options.endian != comptime builtin.target.cpu.arch.endian()) + if (@sizeOf(usize) != @sizeOf(addr_type) or options.endian != native_endian) @compileError("Execution of non-native address sizes / endianness is not supported"); const opcode = try stream.reader().readByte(); @@ -1186,13 +1188,13 @@ test "DWARF expressions" { // TODO: Test fbreg (once implemented): mock a DIE and point compile_unit.frame_base at it mem.writeInt(usize, reg_bytes[0..@sizeOf(usize)], 0xee, native_endian); - (try abi.regValueNative(usize, &thread_context, abi.fpRegNum(reg_context), reg_context)).* = 1; - (try abi.regValueNative(usize, &thread_context, abi.spRegNum(reg_context), reg_context)).* = 2; - (try abi.regValueNative(usize, &thread_context, abi.ipRegNum(), reg_context)).* = 3; + (try abi.regValueNative(&thread_context, abi.fpRegNum(native_arch, reg_context), reg_context)).* = 1; + (try abi.regValueNative(&thread_context, abi.spRegNum(native_arch, reg_context), reg_context)).* = 2; + (try abi.regValueNative(&thread_context, abi.ipRegNum(native_arch), reg_context)).* = 3; - try b.writeBreg(writer, abi.fpRegNum(reg_context), @as(usize, 100)); - try b.writeBreg(writer, abi.spRegNum(reg_context), @as(usize, 200)); - try b.writeBregx(writer, abi.ipRegNum(), @as(usize, 300)); + try b.writeBreg(writer, abi.fpRegNum(native_arch, reg_context), @as(usize, 100)); + try b.writeBreg(writer, abi.spRegNum(native_arch, reg_context), @as(usize, 200)); + try b.writeBregx(writer, abi.ipRegNum(native_arch), @as(usize, 300)); try b.writeRegvalType(writer, @as(u8, 0), @as(usize, 400)); _ = try stack_machine.run(program.items, allocator, context, 0); diff --git a/lib/std/debug/MemoryAccessor.zig b/lib/std/debug/MemoryAccessor.zig new file mode 100644 index 0000000000..bfdda609f6 --- /dev/null +++ b/lib/std/debug/MemoryAccessor.zig @@ -0,0 +1,128 @@ +//! Reads memory from any address of the current location using OS-specific +//! syscalls, bypassing memory page protection. Useful for stack unwinding. + +const builtin = @import("builtin"); +const native_os = builtin.os.tag; + +const std = @import("../std.zig"); +const posix = std.posix; +const File = std.fs.File; +const page_size = std.mem.page_size; + +const MemoryAccessor = @This(); + +var cached_pid: posix.pid_t = -1; + +mem: switch (native_os) { + .linux => File, + else => void, +}, + +pub const init: MemoryAccessor = .{ + .mem = switch (native_os) { + .linux => .{ .handle = -1 }, + else => {}, + }, +}; + +fn read(ma: *MemoryAccessor, address: usize, buf: []u8) bool { + switch (native_os) { + .linux => while (true) switch (ma.mem.handle) { + -2 => break, + -1 => { + const linux = std.os.linux; + const pid = switch (@atomicLoad(posix.pid_t, &cached_pid, .monotonic)) { + -1 => pid: { + const pid = linux.getpid(); + @atomicStore(posix.pid_t, &cached_pid, pid, .monotonic); + break :pid pid; + }, + else => |pid| pid, + }; + const bytes_read = linux.process_vm_readv( + pid, + &.{.{ .base = buf.ptr, .len = buf.len }}, + &.{.{ .base = @ptrFromInt(address), .len = buf.len }}, + 0, + ); + switch (linux.E.init(bytes_read)) { + .SUCCESS => return bytes_read == buf.len, + .FAULT => return false, + .INVAL, .PERM, .SRCH => unreachable, // own pid is always valid + .NOMEM => {}, + .NOSYS => {}, // QEMU is known not to implement this syscall. + else => unreachable, // unexpected + } + var path_buf: [ + std.fmt.count("/proc/{d}/mem", .{std.math.minInt(posix.pid_t)}) + ]u8 = undefined; + const path = std.fmt.bufPrint(&path_buf, "/proc/{d}/mem", .{pid}) catch + unreachable; + ma.mem = std.fs.openFileAbsolute(path, .{}) catch { + ma.mem.handle = -2; + break; + }; + }, + else => return (ma.mem.pread(buf, address) catch return false) == buf.len, + }, + else => {}, + } + if (!isValidMemory(address)) return false; + @memcpy(buf, @as([*]const u8, @ptrFromInt(address))); + return true; +} + +pub fn load(ma: *MemoryAccessor, comptime Type: type, address: usize) ?Type { + var result: Type = undefined; + return if (ma.read(address, std.mem.asBytes(&result))) result else null; +} + +pub fn isValidMemory(address: usize) bool { + // We are unable to determine validity of memory for freestanding targets + if (native_os == .freestanding or native_os == .uefi) return true; + + const aligned_address = address & ~@as(usize, @intCast((page_size - 1))); + if (aligned_address == 0) return false; + const aligned_memory = @as([*]align(page_size) u8, @ptrFromInt(aligned_address))[0..page_size]; + + if (native_os == .windows) { + const windows = std.os.windows; + + var memory_info: windows.MEMORY_BASIC_INFORMATION = undefined; + + // The only error this function can throw is ERROR_INVALID_PARAMETER. + // supply an address that invalid i'll be thrown. + const rc = windows.VirtualQuery(aligned_memory, &memory_info, aligned_memory.len) catch { + return false; + }; + + // Result code has to be bigger than zero (number of bytes written) + if (rc == 0) { + return false; + } + + // Free pages cannot be read, they are unmapped + if (memory_info.State == windows.MEM_FREE) { + return false; + } + + return true; + } else if (have_msync) { + posix.msync(aligned_memory, posix.MSF.ASYNC) catch |err| { + switch (err) { + error.UnmappedMemory => return false, + else => unreachable, + } + }; + + return true; + } else { + // We are unable to determine validity of memory on this target. + return true; + } +} + +const have_msync = switch (native_os) { + .wasi, .emscripten, .windows => false, + else => true, +}; diff --git a/lib/std/debug/SelfInfo.zig b/lib/std/debug/SelfInfo.zig index 58fe4b23b2..80a8cb4cd9 100644 --- a/lib/std/debug/SelfInfo.zig +++ b/lib/std/debug/SelfInfo.zig @@ -22,6 +22,9 @@ const Pdb = std.debug.Pdb; const File = std.fs.File; const math = std.math; const testing = std.testing; +const StackIterator = std.debug.StackIterator; +const regBytes = Dwarf.abi.regBytes; +const regValueNative = Dwarf.abi.regValueNative; const SelfInfo = @This(); @@ -1369,3 +1372,1033 @@ fn getSymbolFromDwarf(allocator: Allocator, address: u64, di: *Dwarf) !SymbolInf else => return err, } } + +/// Unwind a frame using MachO compact unwind info (from __unwind_info). +/// If the compact encoding can't encode a way to unwind a frame, it will +/// defer unwinding to DWARF, in which case `.eh_frame` will be used if available. +pub fn unwindFrameMachO( + context: *UnwindContext, + ma: *std.debug.MemoryAccessor, + unwind_info: []const u8, + eh_frame: ?[]const u8, + module_base_address: usize, +) !usize { + const header = std.mem.bytesAsValue( + macho.unwind_info_section_header, + unwind_info[0..@sizeOf(macho.unwind_info_section_header)], + ); + const indices = std.mem.bytesAsSlice( + macho.unwind_info_section_header_index_entry, + unwind_info[header.indexSectionOffset..][0 .. header.indexCount * @sizeOf(macho.unwind_info_section_header_index_entry)], + ); + if (indices.len == 0) return error.MissingUnwindInfo; + + const mapped_pc = context.pc - module_base_address; + const second_level_index = blk: { + var left: usize = 0; + var len: usize = indices.len; + + while (len > 1) { + const mid = left + len / 2; + const offset = indices[mid].functionOffset; + if (mapped_pc < offset) { + len /= 2; + } else { + left = mid; + if (mapped_pc == offset) break; + len -= len / 2; + } + } + + // Last index is a sentinel containing the highest address as its functionOffset + if (indices[left].secondLevelPagesSectionOffset == 0) return error.MissingUnwindInfo; + break :blk &indices[left]; + }; + + const common_encodings = std.mem.bytesAsSlice( + macho.compact_unwind_encoding_t, + unwind_info[header.commonEncodingsArraySectionOffset..][0 .. header.commonEncodingsArrayCount * @sizeOf(macho.compact_unwind_encoding_t)], + ); + + const start_offset = second_level_index.secondLevelPagesSectionOffset; + const kind = std.mem.bytesAsValue( + macho.UNWIND_SECOND_LEVEL, + unwind_info[start_offset..][0..@sizeOf(macho.UNWIND_SECOND_LEVEL)], + ); + + const entry: struct { + function_offset: usize, + raw_encoding: u32, + } = switch (kind.*) { + .REGULAR => blk: { + const page_header = std.mem.bytesAsValue( + macho.unwind_info_regular_second_level_page_header, + unwind_info[start_offset..][0..@sizeOf(macho.unwind_info_regular_second_level_page_header)], + ); + + const entries = std.mem.bytesAsSlice( + macho.unwind_info_regular_second_level_entry, + unwind_info[start_offset + page_header.entryPageOffset ..][0 .. page_header.entryCount * @sizeOf(macho.unwind_info_regular_second_level_entry)], + ); + if (entries.len == 0) return error.InvalidUnwindInfo; + + var left: usize = 0; + var len: usize = entries.len; + while (len > 1) { + const mid = left + len / 2; + const offset = entries[mid].functionOffset; + if (mapped_pc < offset) { + len /= 2; + } else { + left = mid; + if (mapped_pc == offset) break; + len -= len / 2; + } + } + + break :blk .{ + .function_offset = entries[left].functionOffset, + .raw_encoding = entries[left].encoding, + }; + }, + .COMPRESSED => blk: { + const page_header = std.mem.bytesAsValue( + macho.unwind_info_compressed_second_level_page_header, + unwind_info[start_offset..][0..@sizeOf(macho.unwind_info_compressed_second_level_page_header)], + ); + + const entries = std.mem.bytesAsSlice( + macho.UnwindInfoCompressedEntry, + unwind_info[start_offset + page_header.entryPageOffset ..][0 .. page_header.entryCount * @sizeOf(macho.UnwindInfoCompressedEntry)], + ); + if (entries.len == 0) return error.InvalidUnwindInfo; + + var left: usize = 0; + var len: usize = entries.len; + while (len > 1) { + const mid = left + len / 2; + const offset = second_level_index.functionOffset + entries[mid].funcOffset; + if (mapped_pc < offset) { + len /= 2; + } else { + left = mid; + if (mapped_pc == offset) break; + len -= len / 2; + } + } + + const entry = entries[left]; + const function_offset = second_level_index.functionOffset + entry.funcOffset; + if (entry.encodingIndex < header.commonEncodingsArrayCount) { + if (entry.encodingIndex >= common_encodings.len) return error.InvalidUnwindInfo; + break :blk .{ + .function_offset = function_offset, + .raw_encoding = common_encodings[entry.encodingIndex], + }; + } else { + const local_index = try math.sub( + u8, + entry.encodingIndex, + math.cast(u8, header.commonEncodingsArrayCount) orelse return error.InvalidUnwindInfo, + ); + const local_encodings = std.mem.bytesAsSlice( + macho.compact_unwind_encoding_t, + unwind_info[start_offset + page_header.encodingsPageOffset ..][0 .. page_header.encodingsCount * @sizeOf(macho.compact_unwind_encoding_t)], + ); + if (local_index >= local_encodings.len) return error.InvalidUnwindInfo; + break :blk .{ + .function_offset = function_offset, + .raw_encoding = local_encodings[local_index], + }; + } + }, + else => return error.InvalidUnwindInfo, + }; + + if (entry.raw_encoding == 0) return error.NoUnwindInfo; + const reg_context = Dwarf.abi.RegisterContext{ + .eh_frame = false, + .is_macho = true, + }; + + const encoding: macho.CompactUnwindEncoding = @bitCast(entry.raw_encoding); + const new_ip = switch (builtin.cpu.arch) { + .x86_64 => switch (encoding.mode.x86_64) { + .OLD => return error.UnimplementedUnwindEncoding, + .RBP_FRAME => blk: { + const regs: [5]u3 = .{ + encoding.value.x86_64.frame.reg0, + encoding.value.x86_64.frame.reg1, + encoding.value.x86_64.frame.reg2, + encoding.value.x86_64.frame.reg3, + encoding.value.x86_64.frame.reg4, + }; + + const frame_offset = encoding.value.x86_64.frame.frame_offset * @sizeOf(usize); + var max_reg: usize = 0; + inline for (regs, 0..) |reg, i| { + if (reg > 0) max_reg = i; + } + + const fp = (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).*; + const new_sp = fp + 2 * @sizeOf(usize); + + // Verify the stack range we're about to read register values from + if (ma.load(usize, new_sp) == null or ma.load(usize, fp - frame_offset + max_reg * @sizeOf(usize)) == null) return error.InvalidUnwindInfo; + + const ip_ptr = fp + @sizeOf(usize); + const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*; + const new_fp = @as(*const usize, @ptrFromInt(fp)).*; + + (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).* = new_fp; + (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).* = new_sp; + (try regValueNative(context.thread_context, ip_reg_num, reg_context)).* = new_ip; + + for (regs, 0..) |reg, i| { + if (reg == 0) continue; + const addr = fp - frame_offset + i * @sizeOf(usize); + const reg_number = try Dwarf.compactUnwindToDwarfRegNumber(reg); + (try regValueNative(context.thread_context, reg_number, reg_context)).* = @as(*const usize, @ptrFromInt(addr)).*; + } + + break :blk new_ip; + }, + .STACK_IMMD, + .STACK_IND, + => blk: { + const sp = (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).*; + const stack_size = if (encoding.mode.x86_64 == .STACK_IMMD) + @as(usize, encoding.value.x86_64.frameless.stack.direct.stack_size) * @sizeOf(usize) + else stack_size: { + // In .STACK_IND, the stack size is inferred from the subq instruction at the beginning of the function. + const sub_offset_addr = + module_base_address + + entry.function_offset + + encoding.value.x86_64.frameless.stack.indirect.sub_offset; + if (ma.load(usize, sub_offset_addr) == null) return error.InvalidUnwindInfo; + + // `sub_offset_addr` points to the offset of the literal within the instruction + const sub_operand = @as(*align(1) const u32, @ptrFromInt(sub_offset_addr)).*; + break :stack_size sub_operand + @sizeOf(usize) * @as(usize, encoding.value.x86_64.frameless.stack.indirect.stack_adjust); + }; + + // Decode the Lehmer-coded sequence of registers. + // For a description of the encoding see lib/libc/include/any-macos.13-any/mach-o/compact_unwind_encoding.h + + // Decode the variable-based permutation number into its digits. Each digit represents + // an index into the list of register numbers that weren't yet used in the sequence at + // the time the digit was added. + const reg_count = encoding.value.x86_64.frameless.stack_reg_count; + const ip_ptr = if (reg_count > 0) reg_blk: { + var digits: [6]u3 = undefined; + var accumulator: usize = encoding.value.x86_64.frameless.stack_reg_permutation; + var base: usize = 2; + for (0..reg_count) |i| { + const div = accumulator / base; + digits[digits.len - 1 - i] = @intCast(accumulator - base * div); + accumulator = div; + base += 1; + } + + const reg_numbers = [_]u3{ 1, 2, 3, 4, 5, 6 }; + var registers: [reg_numbers.len]u3 = undefined; + var used_indices = [_]bool{false} ** reg_numbers.len; + for (digits[digits.len - reg_count ..], 0..) |target_unused_index, i| { + var unused_count: u8 = 0; + const unused_index = for (used_indices, 0..) |used, index| { + if (!used) { + if (target_unused_index == unused_count) break index; + unused_count += 1; + } + } else unreachable; + + registers[i] = reg_numbers[unused_index]; + used_indices[unused_index] = true; + } + + var reg_addr = sp + stack_size - @sizeOf(usize) * @as(usize, reg_count + 1); + if (ma.load(usize, reg_addr) == null) return error.InvalidUnwindInfo; + for (0..reg_count) |i| { + const reg_number = try Dwarf.compactUnwindToDwarfRegNumber(registers[i]); + (try regValueNative(context.thread_context, reg_number, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*; + reg_addr += @sizeOf(usize); + } + + break :reg_blk reg_addr; + } else sp + stack_size - @sizeOf(usize); + + const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*; + const new_sp = ip_ptr + @sizeOf(usize); + if (ma.load(usize, new_sp) == null) return error.InvalidUnwindInfo; + + (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).* = new_sp; + (try regValueNative(context.thread_context, ip_reg_num, reg_context)).* = new_ip; + + break :blk new_ip; + }, + .DWARF => { + return unwindFrameMachODwarf(context, ma, eh_frame orelse return error.MissingEhFrame, @intCast(encoding.value.x86_64.dwarf)); + }, + }, + .aarch64 => switch (encoding.mode.arm64) { + .OLD => return error.UnimplementedUnwindEncoding, + .FRAMELESS => blk: { + const sp = (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).*; + const new_sp = sp + encoding.value.arm64.frameless.stack_size * 16; + const new_ip = (try regValueNative(context.thread_context, 30, reg_context)).*; + if (ma.load(usize, new_sp) == null) return error.InvalidUnwindInfo; + (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).* = new_sp; + break :blk new_ip; + }, + .DWARF => { + return unwindFrameMachODwarf(context, ma, eh_frame orelse return error.MissingEhFrame, @intCast(encoding.value.arm64.dwarf)); + }, + .FRAME => blk: { + const fp = (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).*; + const new_sp = fp + 16; + const ip_ptr = fp + @sizeOf(usize); + + const num_restored_pairs: usize = + @popCount(@as(u5, @bitCast(encoding.value.arm64.frame.x_reg_pairs))) + + @popCount(@as(u4, @bitCast(encoding.value.arm64.frame.d_reg_pairs))); + const min_reg_addr = fp - num_restored_pairs * 2 * @sizeOf(usize); + + if (ma.load(usize, new_sp) == null or ma.load(usize, min_reg_addr) == null) return error.InvalidUnwindInfo; + + var reg_addr = fp - @sizeOf(usize); + inline for (@typeInfo(@TypeOf(encoding.value.arm64.frame.x_reg_pairs)).Struct.fields, 0..) |field, i| { + if (@field(encoding.value.arm64.frame.x_reg_pairs, field.name) != 0) { + (try regValueNative(context.thread_context, 19 + i, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*; + reg_addr += @sizeOf(usize); + (try regValueNative(context.thread_context, 20 + i, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*; + reg_addr += @sizeOf(usize); + } + } + + inline for (@typeInfo(@TypeOf(encoding.value.arm64.frame.d_reg_pairs)).Struct.fields, 0..) |field, i| { + if (@field(encoding.value.arm64.frame.d_reg_pairs, field.name) != 0) { + // Only the lower half of the 128-bit V registers are restored during unwinding + @memcpy( + try regBytes(context.thread_context, 64 + 8 + i, context.reg_context), + std.mem.asBytes(@as(*const usize, @ptrFromInt(reg_addr))), + ); + reg_addr += @sizeOf(usize); + @memcpy( + try regBytes(context.thread_context, 64 + 9 + i, context.reg_context), + std.mem.asBytes(@as(*const usize, @ptrFromInt(reg_addr))), + ); + reg_addr += @sizeOf(usize); + } + } + + const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*; + const new_fp = @as(*const usize, @ptrFromInt(fp)).*; + + (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).* = new_fp; + (try regValueNative(context.thread_context, ip_reg_num, reg_context)).* = new_ip; + + break :blk new_ip; + }, + }, + else => return error.UnimplementedArch, + }; + + context.pc = stripInstructionPtrAuthCode(new_ip); + if (context.pc > 0) context.pc -= 1; + return new_ip; +} + +pub const UnwindContext = struct { + allocator: Allocator, + cfa: ?usize, + pc: usize, + thread_context: *std.debug.ThreadContext, + reg_context: Dwarf.abi.RegisterContext, + vm: VirtualMachine, + stack_machine: Dwarf.expression.StackMachine(.{ .call_frame_context = true }), + + pub fn init( + allocator: Allocator, + thread_context: *std.debug.ThreadContext, + ) !UnwindContext { + const pc = stripInstructionPtrAuthCode( + (try regValueNative(thread_context, ip_reg_num, null)).*, + ); + + const context_copy = try allocator.create(std.debug.ThreadContext); + std.debug.copyContext(thread_context, context_copy); + + return .{ + .allocator = allocator, + .cfa = null, + .pc = pc, + .thread_context = context_copy, + .reg_context = undefined, + .vm = .{}, + .stack_machine = .{}, + }; + } + + pub fn deinit(self: *UnwindContext) void { + self.vm.deinit(self.allocator); + self.stack_machine.deinit(self.allocator); + self.allocator.destroy(self.thread_context); + self.* = undefined; + } + + pub fn getFp(self: *const UnwindContext) !usize { + return (try regValueNative(self.thread_context, fpRegNum(self.reg_context), self.reg_context)).*; + } +}; + +/// Some platforms use pointer authentication - the upper bits of instruction pointers contain a signature. +/// This function clears these signature bits to make the pointer usable. +pub inline fn stripInstructionPtrAuthCode(ptr: usize) usize { + if (native_arch == .aarch64) { + // `hint 0x07` maps to `xpaclri` (or `nop` if the hardware doesn't support it) + // The save / restore is because `xpaclri` operates on x30 (LR) + return asm ( + \\mov x16, x30 + \\mov x30, x15 + \\hint 0x07 + \\mov x15, x30 + \\mov x30, x16 + : [ret] "={x15}" (-> usize), + : [ptr] "{x15}" (ptr), + : "x16" + ); + } + + return ptr; +} + +/// Unwind a stack frame using DWARF unwinding info, updating the register context. +/// +/// If `.eh_frame_hdr` is available, it will be used to binary search for the FDE. +/// Otherwise, a linear scan of `.eh_frame` and `.debug_frame` is done to find the FDE. +/// +/// `explicit_fde_offset` is for cases where the FDE offset is known, such as when __unwind_info +/// defers unwinding to DWARF. This is an offset into the `.eh_frame` section. +pub fn unwindFrameDwarf( + di: *const Dwarf, + context: *UnwindContext, + ma: *std.debug.MemoryAccessor, + explicit_fde_offset: ?usize, +) !usize { + if (!supports_unwinding) return error.UnsupportedCpuArchitecture; + if (context.pc == 0) return 0; + + // Find the FDE and CIE + var cie: Dwarf.CommonInformationEntry = undefined; + var fde: Dwarf.FrameDescriptionEntry = undefined; + + if (explicit_fde_offset) |fde_offset| { + const dwarf_section: Dwarf.Section.Id = .eh_frame; + const frame_section = di.section(dwarf_section) orelse return error.MissingFDE; + if (fde_offset >= frame_section.len) return error.MissingFDE; + + var fbr: std.debug.DeprecatedFixedBufferReader = .{ + .buf = frame_section, + .pos = fde_offset, + .endian = di.endian, + }; + + const fde_entry_header = try Dwarf.EntryHeader.read(&fbr, null, dwarf_section); + if (fde_entry_header.type != .fde) return error.MissingFDE; + + const cie_offset = fde_entry_header.type.fde; + try fbr.seekTo(cie_offset); + + fbr.endian = native_endian; + const cie_entry_header = try Dwarf.EntryHeader.read(&fbr, null, dwarf_section); + if (cie_entry_header.type != .cie) return Dwarf.bad(); + + cie = try Dwarf.CommonInformationEntry.parse( + cie_entry_header.entry_bytes, + 0, + true, + cie_entry_header.format, + dwarf_section, + cie_entry_header.length_offset, + @sizeOf(usize), + native_endian, + ); + + fde = try Dwarf.FrameDescriptionEntry.parse( + fde_entry_header.entry_bytes, + 0, + true, + cie, + @sizeOf(usize), + native_endian, + ); + } else if (di.eh_frame_hdr) |header| { + const eh_frame_len = if (di.section(.eh_frame)) |eh_frame| eh_frame.len else null; + try header.findEntry( + ma, + eh_frame_len, + @intFromPtr(di.section(.eh_frame_hdr).?.ptr), + context.pc, + &cie, + &fde, + ); + } else { + const index = std.sort.binarySearch(Dwarf.FrameDescriptionEntry, context.pc, di.fde_list.items, {}, struct { + pub fn compareFn(_: void, pc: usize, mid_item: Dwarf.FrameDescriptionEntry) std.math.Order { + if (pc < mid_item.pc_begin) return .lt; + + const range_end = mid_item.pc_begin + mid_item.pc_range; + if (pc < range_end) return .eq; + + return .gt; + } + }.compareFn); + + fde = if (index) |i| di.fde_list.items[i] else return error.MissingFDE; + cie = di.cie_map.get(fde.cie_length_offset) orelse return error.MissingCIE; + } + + var expression_context: Dwarf.expression.Context = .{ + .format = cie.format, + .memory_accessor = ma, + .compile_unit = di.findCompileUnit(fde.pc_begin) catch null, + .thread_context = context.thread_context, + .reg_context = context.reg_context, + .cfa = context.cfa, + }; + + context.vm.reset(); + context.reg_context.eh_frame = cie.version != 4; + context.reg_context.is_macho = di.is_macho; + + const row = try context.vm.runToNative(context.allocator, context.pc, cie, fde); + context.cfa = switch (row.cfa.rule) { + .val_offset => |offset| blk: { + const register = row.cfa.register orelse return error.InvalidCFARule; + const value = mem.readInt(usize, (try regBytes(context.thread_context, register, context.reg_context))[0..@sizeOf(usize)], native_endian); + break :blk try applyOffset(value, offset); + }, + .expression => |expr| blk: { + context.stack_machine.reset(); + const value = try context.stack_machine.run( + expr, + context.allocator, + expression_context, + context.cfa, + ); + + if (value) |v| { + if (v != .generic) return error.InvalidExpressionValue; + break :blk v.generic; + } else return error.NoExpressionValue; + }, + else => return error.InvalidCFARule, + }; + + if (ma.load(usize, context.cfa.?) == null) return error.InvalidCFA; + expression_context.cfa = context.cfa; + + // Buffering the modifications is done because copying the thread context is not portable, + // some implementations (ie. darwin) use internal pointers to the mcontext. + var arena = std.heap.ArenaAllocator.init(context.allocator); + defer arena.deinit(); + const update_allocator = arena.allocator(); + + const RegisterUpdate = struct { + // Backed by thread_context + dest: []u8, + // Backed by arena + src: []const u8, + prev: ?*@This(), + }; + + var update_tail: ?*RegisterUpdate = null; + var has_return_address = true; + for (context.vm.rowColumns(row)) |column| { + if (column.register) |register| { + if (register == cie.return_address_register) { + has_return_address = column.rule != .undefined; + } + + const dest = try regBytes(context.thread_context, register, context.reg_context); + const src = try update_allocator.alloc(u8, dest.len); + + const prev = update_tail; + update_tail = try update_allocator.create(RegisterUpdate); + update_tail.?.* = .{ + .dest = dest, + .src = src, + .prev = prev, + }; + + try column.resolveValue( + context, + expression_context, + ma, + src, + ); + } + } + + // On all implemented architectures, the CFA is defined as being the previous frame's SP + (try regValueNative(context.thread_context, spRegNum(context.reg_context), context.reg_context)).* = context.cfa.?; + + while (update_tail) |tail| { + @memcpy(tail.dest, tail.src); + update_tail = tail.prev; + } + + if (has_return_address) { + context.pc = stripInstructionPtrAuthCode(mem.readInt(usize, (try regBytes( + context.thread_context, + cie.return_address_register, + context.reg_context, + ))[0..@sizeOf(usize)], native_endian)); + } else { + context.pc = 0; + } + + (try regValueNative(context.thread_context, ip_reg_num, context.reg_context)).* = context.pc; + + // The call instruction will have pushed the address of the instruction that follows the call as the return address. + // This next instruction may be past the end of the function if the caller was `noreturn` (ie. the last instruction in + // the function was the call). If we were to look up an FDE entry using the return address directly, it could end up + // either not finding an FDE at all, or using the next FDE in the program, producing incorrect results. To prevent this, + // we subtract one so that the next lookup is guaranteed to land inside the + // + // The exception to this rule is signal frames, where we return execution would be returned to the instruction + // that triggered the handler. + const return_address = context.pc; + if (context.pc > 0 and !cie.isSignalFrame()) context.pc -= 1; + + return return_address; +} + +fn fpRegNum(reg_context: Dwarf.abi.RegisterContext) u8 { + return Dwarf.abi.fpRegNum(native_arch, reg_context); +} + +fn spRegNum(reg_context: Dwarf.abi.RegisterContext) u8 { + return Dwarf.abi.spRegNum(native_arch, reg_context); +} + +const ip_reg_num = Dwarf.abi.ipRegNum(native_arch); +const supports_unwinding = Dwarf.abi.supportsUnwinding(builtin.target); + +fn unwindFrameMachODwarf( + context: *UnwindContext, + ma: *std.debug.MemoryAccessor, + eh_frame: []const u8, + fde_offset: usize, +) !usize { + var di: Dwarf = .{ + .endian = native_endian, + .is_macho = true, + }; + defer di.deinit(context.allocator); + + di.sections[@intFromEnum(Dwarf.Section.Id.eh_frame)] = .{ + .data = eh_frame, + .owned = false, + }; + + return unwindFrameDwarf(&di, context, ma, fde_offset); +} + +/// This is a virtual machine that runs DWARF call frame instructions. +pub const VirtualMachine = struct { + /// See section 6.4.1 of the DWARF5 specification for details on each + const RegisterRule = union(enum) { + // The spec says that the default rule for each column is the undefined rule. + // However, it also allows ABI / compiler authors to specify alternate defaults, so + // there is a distinction made here. + default: void, + undefined: void, + same_value: void, + // offset(N) + offset: i64, + // val_offset(N) + val_offset: i64, + // register(R) + register: u8, + // expression(E) + expression: []const u8, + // val_expression(E) + val_expression: []const u8, + // Augmenter-defined rule + architectural: void, + }; + + /// Each row contains unwinding rules for a set of registers. + pub const Row = struct { + /// Offset from `FrameDescriptionEntry.pc_begin` + offset: u64 = 0, + /// Special-case column that defines the CFA (Canonical Frame Address) rule. + /// The register field of this column defines the register that CFA is derived from. + cfa: Column = .{}, + /// The register fields in these columns define the register the rule applies to. + columns: ColumnRange = .{}, + /// Indicates that the next write to any column in this row needs to copy + /// the backing column storage first, as it may be referenced by previous rows. + copy_on_write: bool = false, + }; + + pub const Column = struct { + register: ?u8 = null, + rule: RegisterRule = .{ .default = {} }, + + /// Resolves the register rule and places the result into `out` (see regBytes) + pub fn resolveValue( + self: Column, + context: *SelfInfo.UnwindContext, + expression_context: std.debug.Dwarf.expression.Context, + ma: *std.debug.MemoryAccessor, + out: []u8, + ) !void { + switch (self.rule) { + .default => { + const register = self.register orelse return error.InvalidRegister; + try getRegDefaultValue(register, context, out); + }, + .undefined => { + @memset(out, undefined); + }, + .same_value => { + // TODO: This copy could be eliminated if callers always copy the state then call this function to update it + const register = self.register orelse return error.InvalidRegister; + const src = try regBytes(context.thread_context, register, context.reg_context); + if (src.len != out.len) return error.RegisterSizeMismatch; + @memcpy(out, src); + }, + .offset => |offset| { + if (context.cfa) |cfa| { + const addr = try applyOffset(cfa, offset); + if (ma.load(usize, addr) == null) return error.InvalidAddress; + const ptr: *const usize = @ptrFromInt(addr); + mem.writeInt(usize, out[0..@sizeOf(usize)], ptr.*, native_endian); + } else return error.InvalidCFA; + }, + .val_offset => |offset| { + if (context.cfa) |cfa| { + mem.writeInt(usize, out[0..@sizeOf(usize)], try applyOffset(cfa, offset), native_endian); + } else return error.InvalidCFA; + }, + .register => |register| { + const src = try regBytes(context.thread_context, register, context.reg_context); + if (src.len != out.len) return error.RegisterSizeMismatch; + @memcpy(out, try regBytes(context.thread_context, register, context.reg_context)); + }, + .expression => |expression| { + context.stack_machine.reset(); + const value = try context.stack_machine.run(expression, context.allocator, expression_context, context.cfa.?); + const addr = if (value) |v| blk: { + if (v != .generic) return error.InvalidExpressionValue; + break :blk v.generic; + } else return error.NoExpressionValue; + + if (ma.load(usize, addr) == null) return error.InvalidExpressionAddress; + const ptr: *usize = @ptrFromInt(addr); + mem.writeInt(usize, out[0..@sizeOf(usize)], ptr.*, native_endian); + }, + .val_expression => |expression| { + context.stack_machine.reset(); + const value = try context.stack_machine.run(expression, context.allocator, expression_context, context.cfa.?); + if (value) |v| { + if (v != .generic) return error.InvalidExpressionValue; + mem.writeInt(usize, out[0..@sizeOf(usize)], v.generic, native_endian); + } else return error.NoExpressionValue; + }, + .architectural => return error.UnimplementedRegisterRule, + } + } + }; + + const ColumnRange = struct { + /// Index into `columns` of the first column in this row. + start: usize = undefined, + len: u8 = 0, + }; + + columns: std.ArrayListUnmanaged(Column) = .{}, + stack: std.ArrayListUnmanaged(ColumnRange) = .{}, + current_row: Row = .{}, + + /// The result of executing the CIE's initial_instructions + cie_row: ?Row = null, + + pub fn deinit(self: *VirtualMachine, allocator: std.mem.Allocator) void { + self.stack.deinit(allocator); + self.columns.deinit(allocator); + self.* = undefined; + } + + pub fn reset(self: *VirtualMachine) void { + self.stack.clearRetainingCapacity(); + self.columns.clearRetainingCapacity(); + self.current_row = .{}; + self.cie_row = null; + } + + /// Return a slice backed by the row's non-CFA columns + pub fn rowColumns(self: VirtualMachine, row: Row) []Column { + if (row.columns.len == 0) return &.{}; + return self.columns.items[row.columns.start..][0..row.columns.len]; + } + + /// Either retrieves or adds a column for `register` (non-CFA) in the current row. + fn getOrAddColumn(self: *VirtualMachine, allocator: std.mem.Allocator, register: u8) !*Column { + for (self.rowColumns(self.current_row)) |*c| { + if (c.register == register) return c; + } + + if (self.current_row.columns.len == 0) { + self.current_row.columns.start = self.columns.items.len; + } + self.current_row.columns.len += 1; + + const column = try self.columns.addOne(allocator); + column.* = .{ + .register = register, + }; + + return column; + } + + /// Runs the CIE instructions, then the FDE instructions. Execution halts + /// once the row that corresponds to `pc` is known, and the row is returned. + pub fn runTo( + self: *VirtualMachine, + allocator: std.mem.Allocator, + pc: u64, + cie: std.debug.Dwarf.CommonInformationEntry, + fde: std.debug.Dwarf.FrameDescriptionEntry, + addr_size_bytes: u8, + endian: std.builtin.Endian, + ) !Row { + assert(self.cie_row == null); + if (pc < fde.pc_begin or pc >= fde.pc_begin + fde.pc_range) return error.AddressOutOfRange; + + var prev_row: Row = self.current_row; + + var cie_stream = std.io.fixedBufferStream(cie.initial_instructions); + var fde_stream = std.io.fixedBufferStream(fde.instructions); + var streams = [_]*std.io.FixedBufferStream([]const u8){ + &cie_stream, + &fde_stream, + }; + + for (&streams, 0..) |stream, i| { + while (stream.pos < stream.buffer.len) { + const instruction = try std.debug.Dwarf.call_frame.Instruction.read(stream, addr_size_bytes, endian); + prev_row = try self.step(allocator, cie, i == 0, instruction); + if (pc < fde.pc_begin + self.current_row.offset) return prev_row; + } + } + + return self.current_row; + } + + pub fn runToNative( + self: *VirtualMachine, + allocator: std.mem.Allocator, + pc: u64, + cie: std.debug.Dwarf.CommonInformationEntry, + fde: std.debug.Dwarf.FrameDescriptionEntry, + ) !Row { + return self.runTo(allocator, pc, cie, fde, @sizeOf(usize), native_endian); + } + + fn resolveCopyOnWrite(self: *VirtualMachine, allocator: std.mem.Allocator) !void { + if (!self.current_row.copy_on_write) return; + + const new_start = self.columns.items.len; + if (self.current_row.columns.len > 0) { + try self.columns.ensureUnusedCapacity(allocator, self.current_row.columns.len); + self.columns.appendSliceAssumeCapacity(self.rowColumns(self.current_row)); + self.current_row.columns.start = new_start; + } + } + + /// Executes a single instruction. + /// If this instruction is from the CIE, `is_initial` should be set. + /// Returns the value of `current_row` before executing this instruction. + pub fn step( + self: *VirtualMachine, + allocator: std.mem.Allocator, + cie: std.debug.Dwarf.CommonInformationEntry, + is_initial: bool, + instruction: Dwarf.call_frame.Instruction, + ) !Row { + // CIE instructions must be run before FDE instructions + assert(!is_initial or self.cie_row == null); + if (!is_initial and self.cie_row == null) { + self.cie_row = self.current_row; + self.current_row.copy_on_write = true; + } + + const prev_row = self.current_row; + switch (instruction) { + .set_loc => |i| { + if (i.address <= self.current_row.offset) return error.InvalidOperation; + // TODO: Check cie.segment_selector_size != 0 for DWARFV4 + self.current_row.offset = i.address; + }, + inline .advance_loc, + .advance_loc1, + .advance_loc2, + .advance_loc4, + => |i| { + self.current_row.offset += i.delta * cie.code_alignment_factor; + self.current_row.copy_on_write = true; + }, + inline .offset, + .offset_extended, + .offset_extended_sf, + => |i| { + try self.resolveCopyOnWrite(allocator); + const column = try self.getOrAddColumn(allocator, i.register); + column.rule = .{ .offset = @as(i64, @intCast(i.offset)) * cie.data_alignment_factor }; + }, + inline .restore, + .restore_extended, + => |i| { + try self.resolveCopyOnWrite(allocator); + if (self.cie_row) |cie_row| { + const column = try self.getOrAddColumn(allocator, i.register); + column.rule = for (self.rowColumns(cie_row)) |cie_column| { + if (cie_column.register == i.register) break cie_column.rule; + } else .{ .default = {} }; + } else return error.InvalidOperation; + }, + .nop => {}, + .undefined => |i| { + try self.resolveCopyOnWrite(allocator); + const column = try self.getOrAddColumn(allocator, i.register); + column.rule = .{ .undefined = {} }; + }, + .same_value => |i| { + try self.resolveCopyOnWrite(allocator); + const column = try self.getOrAddColumn(allocator, i.register); + column.rule = .{ .same_value = {} }; + }, + .register => |i| { + try self.resolveCopyOnWrite(allocator); + const column = try self.getOrAddColumn(allocator, i.register); + column.rule = .{ .register = i.target_register }; + }, + .remember_state => { + try self.stack.append(allocator, self.current_row.columns); + self.current_row.copy_on_write = true; + }, + .restore_state => { + const restored_columns = self.stack.popOrNull() orelse return error.InvalidOperation; + self.columns.shrinkRetainingCapacity(self.columns.items.len - self.current_row.columns.len); + try self.columns.ensureUnusedCapacity(allocator, restored_columns.len); + + self.current_row.columns.start = self.columns.items.len; + self.current_row.columns.len = restored_columns.len; + self.columns.appendSliceAssumeCapacity(self.columns.items[restored_columns.start..][0..restored_columns.len]); + }, + .def_cfa => |i| { + try self.resolveCopyOnWrite(allocator); + self.current_row.cfa = .{ + .register = i.register, + .rule = .{ .val_offset = @intCast(i.offset) }, + }; + }, + .def_cfa_sf => |i| { + try self.resolveCopyOnWrite(allocator); + self.current_row.cfa = .{ + .register = i.register, + .rule = .{ .val_offset = i.offset * cie.data_alignment_factor }, + }; + }, + .def_cfa_register => |i| { + try self.resolveCopyOnWrite(allocator); + if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .val_offset) return error.InvalidOperation; + self.current_row.cfa.register = i.register; + }, + .def_cfa_offset => |i| { + try self.resolveCopyOnWrite(allocator); + if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .val_offset) return error.InvalidOperation; + self.current_row.cfa.rule = .{ + .val_offset = @intCast(i.offset), + }; + }, + .def_cfa_offset_sf => |i| { + try self.resolveCopyOnWrite(allocator); + if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .val_offset) return error.InvalidOperation; + self.current_row.cfa.rule = .{ + .val_offset = i.offset * cie.data_alignment_factor, + }; + }, + .def_cfa_expression => |i| { + try self.resolveCopyOnWrite(allocator); + self.current_row.cfa.register = undefined; + self.current_row.cfa.rule = .{ + .expression = i.block, + }; + }, + .expression => |i| { + try self.resolveCopyOnWrite(allocator); + const column = try self.getOrAddColumn(allocator, i.register); + column.rule = .{ + .expression = i.block, + }; + }, + .val_offset => |i| { + try self.resolveCopyOnWrite(allocator); + const column = try self.getOrAddColumn(allocator, i.register); + column.rule = .{ + .val_offset = @as(i64, @intCast(i.offset)) * cie.data_alignment_factor, + }; + }, + .val_offset_sf => |i| { + try self.resolveCopyOnWrite(allocator); + const column = try self.getOrAddColumn(allocator, i.register); + column.rule = .{ + .val_offset = i.offset * cie.data_alignment_factor, + }; + }, + .val_expression => |i| { + try self.resolveCopyOnWrite(allocator); + const column = try self.getOrAddColumn(allocator, i.register); + column.rule = .{ + .val_expression = i.block, + }; + }, + } + + return prev_row; + } +}; + +/// Returns the ABI-defined default value this register has in the unwinding table +/// before running any of the CIE instructions. The DWARF spec defines these as having +/// the .undefined rule by default, but allows ABI authors to override that. +fn getRegDefaultValue(reg_number: u8, context: *UnwindContext, out: []u8) !void { + switch (builtin.cpu.arch) { + .aarch64 => { + // Callee-saved registers are initialized as if they had the .same_value rule + if (reg_number >= 19 and reg_number <= 28) { + const src = try regBytes(context.thread_context, reg_number, context.reg_context); + if (src.len != out.len) return error.RegisterSizeMismatch; + @memcpy(out, src); + return; + } + }, + else => {}, + } + + @memset(out, undefined); +} + +/// Since register rules are applied (usually) during a panic, +/// checked addition / subtraction is used so that we can return +/// an error and fall back to FP-based unwinding. +fn applyOffset(base: usize, offset: i64) !usize { + return if (offset >= 0) + try std.math.add(usize, base, @as(usize, @intCast(offset))) + else + try std.math.sub(usize, base, @as(usize, @intCast(-offset))); +} diff --git a/src/crash_report.zig b/src/crash_report.zig index 0f9e73a3cb..d4fe72a8e8 100644 --- a/src/crash_report.zig +++ b/src/crash_report.zig @@ -256,7 +256,7 @@ const StackContext = union(enum) { current: struct { ret_addr: ?usize, }, - exception: *const debug.ThreadContext, + exception: *debug.ThreadContext, not_supported: void, pub fn dumpStackTrace(ctx: @This()) void { From c339aa655e58a3ecc22f21354b2d6584509a7dbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Fri, 2 Aug 2024 09:22:57 +0200 Subject: [PATCH 115/266] main: Give a more helpful message when we have a minimum glibc version. --- src/main.zig | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main.zig b/src/main.zig index d7f7e006db..a023e7d8ec 100644 --- a/src/main.zig +++ b/src/main.zig @@ -3412,6 +3412,14 @@ fn buildOutputType( std.log.info("zig can provide libc for related target {s}-{s}.{d}-{s}", .{ @tagName(t.arch), @tagName(t.os), os_ver.major, @tagName(t.abi), }); + } else if (t.glibc_min) |glibc_min| { + std.log.info("zig can provide libc for related target {s}-{s}-{s}.{d}.{d}", .{ + @tagName(t.arch), + @tagName(t.os), + @tagName(t.abi), + glibc_min.major, + glibc_min.minor, + }); } else { std.log.info("zig can provide libc for related target {s}-{s}-{s}", .{ @tagName(t.arch), @tagName(t.os), @tagName(t.abi), From 948bc91d12e147b46d54d82af5e9a9bc2ce25573 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Fri, 2 Aug 2024 09:52:19 +0200 Subject: [PATCH 116/266] std.Target.VersionRange: Make default() respect the minimum glibc version. --- lib/std/Target.zig | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/std/Target.zig b/lib/std/Target.zig index 8d9f596072..87bdffa5a0 100644 --- a/lib/std/Target.zig +++ b/lib/std/Target.zig @@ -483,7 +483,21 @@ pub const Os = struct { .min = .{ .major = 4, .minor = 19, .patch = 0 }, .max = .{ .major = 6, .minor = 5, .patch = 7 }, }, - .glibc = .{ .major = 2, .minor = 28, .patch = 0 }, + .glibc = blk: { + const default_min = .{ .major = 2, .minor = 28, .patch = 0 }; + + for (std.zig.target.available_libcs) |libc| { + // We don't know the ABI here. We can get away with not checking it + // for now, but that may not always remain true. + if (libc.os != tag or libc.arch != arch) continue; + + if (libc.glibc_min) |min| { + if (min.order(default_min) == .gt) break :blk min; + } + } + + break :blk default_min; + }, }, }, From 3c2733de6e20926b5113236296177e1b2a576423 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Fri, 2 Aug 2024 08:44:09 +0200 Subject: [PATCH 117/266] std.Target: Use a saner default dynamic linker path for riscv. The vast majority of Linux systems will be running these ABIs. --- lib/std/Target.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/std/Target.zig b/lib/std/Target.zig index 87bdffa5a0..de9729a6bc 100644 --- a/lib/std/Target.zig +++ b/lib/std/Target.zig @@ -1742,8 +1742,8 @@ pub const DynamicLinker = struct { else => "/lib64/ld-linux-x86-64.so.2", }), - .riscv32 => init("/lib/ld-linux-riscv32-ilp32.so.1"), - .riscv64 => init("/lib/ld-linux-riscv64-lp64.so.1"), + .riscv32 => init("/lib/ld-linux-riscv32-ilp32d.so.1"), + .riscv64 => init("/lib/ld-linux-riscv64-lp64d.so.1"), // Architectures in this list have been verified as not having a standard // dynamic linker path. From 635a3d87de03193f28bb840957e387981365566f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Fri, 2 Aug 2024 08:13:00 +0200 Subject: [PATCH 118/266] glibc: Change riscv32-linux-gnuilp32 target triple to riscv32-linux-gnu. This target triple was weird on multiple levels: * The `ilp32` ABI is the soft float ABI. This is not the main ABI we want to support on RISC-V; rather, we want `ilp32d`. * `gnuilp32` is a bespoke tag that was introduced in Zig. The rest of the world just uses `gnu` for RISC-V target triples. * `gnu_ilp32` is already the name of an ILP32 ABI used on AArch64. `gnuilp32` is too easy to confuse with this. * We don't use this convention for `riscv64-linux-gnu`. * Supporting all RISC-V ABIs with this convention will result in combinatorial explosion; see #20690. --- .../bits/endianness.h | 0 .../bits/environments.h | 0 .../{riscv32-linux-gnuilp32 => riscv32-linux-gnu}/bits/fcntl.h | 0 .../{riscv32-linux-gnuilp32 => riscv32-linux-gnu}/bits/fenv.h | 0 .../{riscv32-linux-gnuilp32 => riscv32-linux-gnu}/bits/floatn.h | 0 .../{riscv32-linux-gnuilp32 => riscv32-linux-gnu}/bits/link.h | 0 .../bits/long-double.h | 0 .../{riscv32-linux-gnuilp32 => riscv32-linux-gnu}/bits/procfs.h | 0 .../bits/pthreadtypes-arch.h | 0 .../{riscv32-linux-gnuilp32 => riscv32-linux-gnu}/bits/rseq.h | 0 .../{riscv32-linux-gnuilp32 => riscv32-linux-gnu}/bits/setjmp.h | 0 .../bits/sigcontext.h | 0 .../bits/struct_rwlock.h | 0 .../bits/struct_stat.h | 0 .../{riscv32-linux-gnuilp32 => riscv32-linux-gnu}/bits/time64.h | 0 .../bits/timesize.h | 0 .../bits/typesizes.h | 0 .../bits/wordsize.h | 0 .../{riscv32-linux-gnuilp32 => riscv32-linux-gnu}/fpu_control.h | 0 .../gnu/lib-names-ilp32.h | 0 .../gnu/lib-names.h | 0 .../gnu/stubs-ilp32.h | 0 .../{riscv32-linux-gnuilp32 => riscv32-linux-gnu}/gnu/stubs.h | 0 .../{riscv32-linux-gnuilp32 => riscv32-linux-gnu}/ieee754.h | 0 .../{riscv32-linux-gnuilp32 => riscv32-linux-gnu}/sys/asm.h | 0 .../sys/cachectl.h | 0 .../sys/ucontext.h | 0 .../{riscv32-linux-gnuilp32 => riscv32-linux-gnu}/sys/user.h | 0 lib/std/zig/target.zig | 2 +- src/target.zig | 1 - tools/process_headers.zig | 2 +- 31 files changed, 2 insertions(+), 3 deletions(-) rename lib/libc/include/{riscv32-linux-gnuilp32 => riscv32-linux-gnu}/bits/endianness.h (100%) rename lib/libc/include/{riscv32-linux-gnuilp32 => riscv32-linux-gnu}/bits/environments.h (100%) rename lib/libc/include/{riscv32-linux-gnuilp32 => riscv32-linux-gnu}/bits/fcntl.h (100%) rename lib/libc/include/{riscv32-linux-gnuilp32 => riscv32-linux-gnu}/bits/fenv.h (100%) rename lib/libc/include/{riscv32-linux-gnuilp32 => riscv32-linux-gnu}/bits/floatn.h (100%) rename lib/libc/include/{riscv32-linux-gnuilp32 => riscv32-linux-gnu}/bits/link.h (100%) rename lib/libc/include/{riscv32-linux-gnuilp32 => riscv32-linux-gnu}/bits/long-double.h (100%) rename lib/libc/include/{riscv32-linux-gnuilp32 => riscv32-linux-gnu}/bits/procfs.h (100%) rename lib/libc/include/{riscv32-linux-gnuilp32 => riscv32-linux-gnu}/bits/pthreadtypes-arch.h (100%) rename lib/libc/include/{riscv32-linux-gnuilp32 => riscv32-linux-gnu}/bits/rseq.h (100%) rename lib/libc/include/{riscv32-linux-gnuilp32 => riscv32-linux-gnu}/bits/setjmp.h (100%) rename lib/libc/include/{riscv32-linux-gnuilp32 => riscv32-linux-gnu}/bits/sigcontext.h (100%) rename lib/libc/include/{riscv32-linux-gnuilp32 => riscv32-linux-gnu}/bits/struct_rwlock.h (100%) rename lib/libc/include/{riscv32-linux-gnuilp32 => riscv32-linux-gnu}/bits/struct_stat.h (100%) rename lib/libc/include/{riscv32-linux-gnuilp32 => riscv32-linux-gnu}/bits/time64.h (100%) rename lib/libc/include/{riscv32-linux-gnuilp32 => riscv32-linux-gnu}/bits/timesize.h (100%) rename lib/libc/include/{riscv32-linux-gnuilp32 => riscv32-linux-gnu}/bits/typesizes.h (100%) rename lib/libc/include/{riscv32-linux-gnuilp32 => riscv32-linux-gnu}/bits/wordsize.h (100%) rename lib/libc/include/{riscv32-linux-gnuilp32 => riscv32-linux-gnu}/fpu_control.h (100%) rename lib/libc/include/{riscv32-linux-gnuilp32 => riscv32-linux-gnu}/gnu/lib-names-ilp32.h (100%) rename lib/libc/include/{riscv32-linux-gnuilp32 => riscv32-linux-gnu}/gnu/lib-names.h (100%) rename lib/libc/include/{riscv32-linux-gnuilp32 => riscv32-linux-gnu}/gnu/stubs-ilp32.h (100%) rename lib/libc/include/{riscv32-linux-gnuilp32 => riscv32-linux-gnu}/gnu/stubs.h (100%) rename lib/libc/include/{riscv32-linux-gnuilp32 => riscv32-linux-gnu}/ieee754.h (100%) rename lib/libc/include/{riscv32-linux-gnuilp32 => riscv32-linux-gnu}/sys/asm.h (100%) rename lib/libc/include/{riscv32-linux-gnuilp32 => riscv32-linux-gnu}/sys/cachectl.h (100%) rename lib/libc/include/{riscv32-linux-gnuilp32 => riscv32-linux-gnu}/sys/ucontext.h (100%) rename lib/libc/include/{riscv32-linux-gnuilp32 => riscv32-linux-gnu}/sys/user.h (100%) diff --git a/lib/libc/include/riscv32-linux-gnuilp32/bits/endianness.h b/lib/libc/include/riscv32-linux-gnu/bits/endianness.h similarity index 100% rename from lib/libc/include/riscv32-linux-gnuilp32/bits/endianness.h rename to lib/libc/include/riscv32-linux-gnu/bits/endianness.h diff --git a/lib/libc/include/riscv32-linux-gnuilp32/bits/environments.h b/lib/libc/include/riscv32-linux-gnu/bits/environments.h similarity index 100% rename from lib/libc/include/riscv32-linux-gnuilp32/bits/environments.h rename to lib/libc/include/riscv32-linux-gnu/bits/environments.h diff --git a/lib/libc/include/riscv32-linux-gnuilp32/bits/fcntl.h b/lib/libc/include/riscv32-linux-gnu/bits/fcntl.h similarity index 100% rename from lib/libc/include/riscv32-linux-gnuilp32/bits/fcntl.h rename to lib/libc/include/riscv32-linux-gnu/bits/fcntl.h diff --git a/lib/libc/include/riscv32-linux-gnuilp32/bits/fenv.h b/lib/libc/include/riscv32-linux-gnu/bits/fenv.h similarity index 100% rename from lib/libc/include/riscv32-linux-gnuilp32/bits/fenv.h rename to lib/libc/include/riscv32-linux-gnu/bits/fenv.h diff --git a/lib/libc/include/riscv32-linux-gnuilp32/bits/floatn.h b/lib/libc/include/riscv32-linux-gnu/bits/floatn.h similarity index 100% rename from lib/libc/include/riscv32-linux-gnuilp32/bits/floatn.h rename to lib/libc/include/riscv32-linux-gnu/bits/floatn.h diff --git a/lib/libc/include/riscv32-linux-gnuilp32/bits/link.h b/lib/libc/include/riscv32-linux-gnu/bits/link.h similarity index 100% rename from lib/libc/include/riscv32-linux-gnuilp32/bits/link.h rename to lib/libc/include/riscv32-linux-gnu/bits/link.h diff --git a/lib/libc/include/riscv32-linux-gnuilp32/bits/long-double.h b/lib/libc/include/riscv32-linux-gnu/bits/long-double.h similarity index 100% rename from lib/libc/include/riscv32-linux-gnuilp32/bits/long-double.h rename to lib/libc/include/riscv32-linux-gnu/bits/long-double.h diff --git a/lib/libc/include/riscv32-linux-gnuilp32/bits/procfs.h b/lib/libc/include/riscv32-linux-gnu/bits/procfs.h similarity index 100% rename from lib/libc/include/riscv32-linux-gnuilp32/bits/procfs.h rename to lib/libc/include/riscv32-linux-gnu/bits/procfs.h diff --git a/lib/libc/include/riscv32-linux-gnuilp32/bits/pthreadtypes-arch.h b/lib/libc/include/riscv32-linux-gnu/bits/pthreadtypes-arch.h similarity index 100% rename from lib/libc/include/riscv32-linux-gnuilp32/bits/pthreadtypes-arch.h rename to lib/libc/include/riscv32-linux-gnu/bits/pthreadtypes-arch.h diff --git a/lib/libc/include/riscv32-linux-gnuilp32/bits/rseq.h b/lib/libc/include/riscv32-linux-gnu/bits/rseq.h similarity index 100% rename from lib/libc/include/riscv32-linux-gnuilp32/bits/rseq.h rename to lib/libc/include/riscv32-linux-gnu/bits/rseq.h diff --git a/lib/libc/include/riscv32-linux-gnuilp32/bits/setjmp.h b/lib/libc/include/riscv32-linux-gnu/bits/setjmp.h similarity index 100% rename from lib/libc/include/riscv32-linux-gnuilp32/bits/setjmp.h rename to lib/libc/include/riscv32-linux-gnu/bits/setjmp.h diff --git a/lib/libc/include/riscv32-linux-gnuilp32/bits/sigcontext.h b/lib/libc/include/riscv32-linux-gnu/bits/sigcontext.h similarity index 100% rename from lib/libc/include/riscv32-linux-gnuilp32/bits/sigcontext.h rename to lib/libc/include/riscv32-linux-gnu/bits/sigcontext.h diff --git a/lib/libc/include/riscv32-linux-gnuilp32/bits/struct_rwlock.h b/lib/libc/include/riscv32-linux-gnu/bits/struct_rwlock.h similarity index 100% rename from lib/libc/include/riscv32-linux-gnuilp32/bits/struct_rwlock.h rename to lib/libc/include/riscv32-linux-gnu/bits/struct_rwlock.h diff --git a/lib/libc/include/riscv32-linux-gnuilp32/bits/struct_stat.h b/lib/libc/include/riscv32-linux-gnu/bits/struct_stat.h similarity index 100% rename from lib/libc/include/riscv32-linux-gnuilp32/bits/struct_stat.h rename to lib/libc/include/riscv32-linux-gnu/bits/struct_stat.h diff --git a/lib/libc/include/riscv32-linux-gnuilp32/bits/time64.h b/lib/libc/include/riscv32-linux-gnu/bits/time64.h similarity index 100% rename from lib/libc/include/riscv32-linux-gnuilp32/bits/time64.h rename to lib/libc/include/riscv32-linux-gnu/bits/time64.h diff --git a/lib/libc/include/riscv32-linux-gnuilp32/bits/timesize.h b/lib/libc/include/riscv32-linux-gnu/bits/timesize.h similarity index 100% rename from lib/libc/include/riscv32-linux-gnuilp32/bits/timesize.h rename to lib/libc/include/riscv32-linux-gnu/bits/timesize.h diff --git a/lib/libc/include/riscv32-linux-gnuilp32/bits/typesizes.h b/lib/libc/include/riscv32-linux-gnu/bits/typesizes.h similarity index 100% rename from lib/libc/include/riscv32-linux-gnuilp32/bits/typesizes.h rename to lib/libc/include/riscv32-linux-gnu/bits/typesizes.h diff --git a/lib/libc/include/riscv32-linux-gnuilp32/bits/wordsize.h b/lib/libc/include/riscv32-linux-gnu/bits/wordsize.h similarity index 100% rename from lib/libc/include/riscv32-linux-gnuilp32/bits/wordsize.h rename to lib/libc/include/riscv32-linux-gnu/bits/wordsize.h diff --git a/lib/libc/include/riscv32-linux-gnuilp32/fpu_control.h b/lib/libc/include/riscv32-linux-gnu/fpu_control.h similarity index 100% rename from lib/libc/include/riscv32-linux-gnuilp32/fpu_control.h rename to lib/libc/include/riscv32-linux-gnu/fpu_control.h diff --git a/lib/libc/include/riscv32-linux-gnuilp32/gnu/lib-names-ilp32.h b/lib/libc/include/riscv32-linux-gnu/gnu/lib-names-ilp32.h similarity index 100% rename from lib/libc/include/riscv32-linux-gnuilp32/gnu/lib-names-ilp32.h rename to lib/libc/include/riscv32-linux-gnu/gnu/lib-names-ilp32.h diff --git a/lib/libc/include/riscv32-linux-gnuilp32/gnu/lib-names.h b/lib/libc/include/riscv32-linux-gnu/gnu/lib-names.h similarity index 100% rename from lib/libc/include/riscv32-linux-gnuilp32/gnu/lib-names.h rename to lib/libc/include/riscv32-linux-gnu/gnu/lib-names.h diff --git a/lib/libc/include/riscv32-linux-gnuilp32/gnu/stubs-ilp32.h b/lib/libc/include/riscv32-linux-gnu/gnu/stubs-ilp32.h similarity index 100% rename from lib/libc/include/riscv32-linux-gnuilp32/gnu/stubs-ilp32.h rename to lib/libc/include/riscv32-linux-gnu/gnu/stubs-ilp32.h diff --git a/lib/libc/include/riscv32-linux-gnuilp32/gnu/stubs.h b/lib/libc/include/riscv32-linux-gnu/gnu/stubs.h similarity index 100% rename from lib/libc/include/riscv32-linux-gnuilp32/gnu/stubs.h rename to lib/libc/include/riscv32-linux-gnu/gnu/stubs.h diff --git a/lib/libc/include/riscv32-linux-gnuilp32/ieee754.h b/lib/libc/include/riscv32-linux-gnu/ieee754.h similarity index 100% rename from lib/libc/include/riscv32-linux-gnuilp32/ieee754.h rename to lib/libc/include/riscv32-linux-gnu/ieee754.h diff --git a/lib/libc/include/riscv32-linux-gnuilp32/sys/asm.h b/lib/libc/include/riscv32-linux-gnu/sys/asm.h similarity index 100% rename from lib/libc/include/riscv32-linux-gnuilp32/sys/asm.h rename to lib/libc/include/riscv32-linux-gnu/sys/asm.h diff --git a/lib/libc/include/riscv32-linux-gnuilp32/sys/cachectl.h b/lib/libc/include/riscv32-linux-gnu/sys/cachectl.h similarity index 100% rename from lib/libc/include/riscv32-linux-gnuilp32/sys/cachectl.h rename to lib/libc/include/riscv32-linux-gnu/sys/cachectl.h diff --git a/lib/libc/include/riscv32-linux-gnuilp32/sys/ucontext.h b/lib/libc/include/riscv32-linux-gnu/sys/ucontext.h similarity index 100% rename from lib/libc/include/riscv32-linux-gnuilp32/sys/ucontext.h rename to lib/libc/include/riscv32-linux-gnu/sys/ucontext.h diff --git a/lib/libc/include/riscv32-linux-gnuilp32/sys/user.h b/lib/libc/include/riscv32-linux-gnu/sys/user.h similarity index 100% rename from lib/libc/include/riscv32-linux-gnuilp32/sys/user.h rename to lib/libc/include/riscv32-linux-gnu/sys/user.h diff --git a/lib/std/zig/target.zig b/lib/std/zig/target.zig index d86d47ba59..4974e0f22b 100644 --- a/lib/std/zig/target.zig +++ b/lib/std/zig/target.zig @@ -56,7 +56,7 @@ pub const available_libcs = [_]ArchOsAbi{ .{ .arch = .powerpc, .os = .linux, .abi = .gnueabi }, .{ .arch = .powerpc, .os = .linux, .abi = .gnueabihf }, .{ .arch = .powerpc, .os = .linux, .abi = .musl }, - .{ .arch = .riscv32, .os = .linux, .abi = .gnuilp32, .glibc_min = .{ .major = 2, .minor = 33, .patch = 0 } }, + .{ .arch = .riscv32, .os = .linux, .abi = .gnu, .glibc_min = .{ .major = 2, .minor = 33, .patch = 0 } }, .{ .arch = .riscv32, .os = .linux, .abi = .musl }, .{ .arch = .riscv64, .os = .linux, .abi = .gnu, .glibc_min = .{ .major = 2, .minor = 27, .patch = 0 } }, .{ .arch = .riscv64, .os = .linux, .abi = .musl }, diff --git a/src/target.zig b/src/target.zig index 768e4d957e..6cd5967d0e 100644 --- a/src/target.zig +++ b/src/target.zig @@ -369,7 +369,6 @@ pub fn addrSpaceCastIsValid( pub fn llvmMachineAbi(target: std.Target) ?[:0]const u8 { const have_float = switch (target.abi) { - .gnuilp32 => return "ilp32", .gnueabihf, .musleabihf, .eabihf => true, else => false, }; diff --git a/tools/process_headers.zig b/tools/process_headers.zig index a15481afa2..7bed1a91c7 100644 --- a/tools/process_headers.zig +++ b/tools/process_headers.zig @@ -162,7 +162,7 @@ const glibc_targets = [_]LibCTarget{ LibCTarget{ .name = "riscv32-linux-gnu-rv32imac-ilp32", .arch = MultiArch{ .specific = Arch.riscv32 }, - .abi = MultiAbi{ .specific = Abi.gnuilp32 }, + .abi = MultiAbi{ .specific = Abi.gnu }, }, LibCTarget{ .name = "riscv64-linux-gnu-rv64imac-lp64", From 3ebb8806635b8fb707dc0f260be902a9ae592ee0 Mon Sep 17 00:00:00 2001 From: Tristan Ross Date: Fri, 2 Aug 2024 08:35:05 +0200 Subject: [PATCH 119/266] glibc: Replace ilp32/lp64 headers with ilp32d/lp64d. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The former are soft float; the latter are hard float. We primarily care about hard float here. Signed-off-by: Alex Rønne Petersen --- .../gnu/lib-names-ilp32d.h} | 8 ++++---- .../gnu/{stubs-ilp32.h => stubs-ilp32d.h} | 16 +--------------- .../gnu/lib-names-lp64d.h} | 8 ++++---- .../gnu/{stubs-lp64.h => stubs-lp64d.h} | 16 +--------------- 4 files changed, 10 insertions(+), 38 deletions(-) rename lib/libc/include/{riscv64-linux-gnu/gnu/lib-names-lp64.h => riscv32-linux-gnu/gnu/lib-names-ilp32d.h} (80%) rename lib/libc/include/riscv32-linux-gnu/gnu/{stubs-ilp32.h => stubs-ilp32d.h} (63%) rename lib/libc/include/{riscv32-linux-gnu/gnu/lib-names-ilp32.h => riscv64-linux-gnu/gnu/lib-names-lp64d.h} (82%) rename lib/libc/include/riscv64-linux-gnu/gnu/{stubs-lp64.h => stubs-lp64d.h} (63%) diff --git a/lib/libc/include/riscv64-linux-gnu/gnu/lib-names-lp64.h b/lib/libc/include/riscv32-linux-gnu/gnu/lib-names-ilp32d.h similarity index 80% rename from lib/libc/include/riscv64-linux-gnu/gnu/lib-names-lp64.h rename to lib/libc/include/riscv32-linux-gnu/gnu/lib-names-ilp32d.h index facadbf088..5d0f5d7d66 100644 --- a/lib/libc/include/riscv64-linux-gnu/gnu/lib-names-lp64.h +++ b/lib/libc/include/riscv32-linux-gnu/gnu/lib-names-ilp32d.h @@ -1,10 +1,10 @@ /* This file is automatically generated. */ #ifndef __GNU_LIB_NAMES_H -# error "Never use directly; include instead." +# error "Never use directly; include instead." #endif -#define LD_LINUX_RISCV64_LP64_SO "ld-linux-riscv64-lp64.so.1" -#define LD_SO "ld-linux-riscv64-lp64.so.1" +#define LD_LINUX_RISCV32_ILP32D_SO "ld-linux-riscv32-ilp32d.so.1" +#define LD_SO "ld-linux-riscv32-ilp32d.so.1" #define LIBANL_SO "libanl.so.1" #define LIBBROKENLOCALE_SO "libBrokenLocale.so.1" #define LIBC_MALLOC_DEBUG_SO "libc_malloc_debug.so.0" @@ -24,4 +24,4 @@ #define LIBRESOLV_SO "libresolv.so.2" #define LIBRT_SO "librt.so.1" #define LIBTHREAD_DB_SO "libthread_db.so.1" -#define LIBUTIL_SO "libutil.so.1" \ No newline at end of file +#define LIBUTIL_SO "libutil.so.1" diff --git a/lib/libc/include/riscv32-linux-gnu/gnu/stubs-ilp32.h b/lib/libc/include/riscv32-linux-gnu/gnu/stubs-ilp32d.h similarity index 63% rename from lib/libc/include/riscv32-linux-gnu/gnu/stubs-ilp32.h rename to lib/libc/include/riscv32-linux-gnu/gnu/stubs-ilp32d.h index 6ce02418e6..4d4c0d146a 100644 --- a/lib/libc/include/riscv32-linux-gnu/gnu/stubs-ilp32.h +++ b/lib/libc/include/riscv32-linux-gnu/gnu/stubs-ilp32d.h @@ -14,25 +14,11 @@ #define __stub___compat_uselib #define __stub_chflags #define __stub_fchflags -#define __stub_feclearexcept #define __stub_fedisableexcept #define __stub_feenableexcept -#define __stub_fegetenv #define __stub_fegetexcept -#define __stub_fegetexceptflag -#define __stub_fegetmode -#define __stub_fegetround -#define __stub_feholdexcept -#define __stub_feraiseexcept -#define __stub_fesetenv -#define __stub_fesetexcept -#define __stub_fesetexceptflag -#define __stub_fesetmode -#define __stub_fesetround -#define __stub_fetestexcept -#define __stub_feupdateenv #define __stub_gtty #define __stub_revoke #define __stub_setlogin #define __stub_sigreturn -#define __stub_stty \ No newline at end of file +#define __stub_stty diff --git a/lib/libc/include/riscv32-linux-gnu/gnu/lib-names-ilp32.h b/lib/libc/include/riscv64-linux-gnu/gnu/lib-names-lp64d.h similarity index 82% rename from lib/libc/include/riscv32-linux-gnu/gnu/lib-names-ilp32.h rename to lib/libc/include/riscv64-linux-gnu/gnu/lib-names-lp64d.h index f7cf1ab8a2..e16743eb16 100644 --- a/lib/libc/include/riscv32-linux-gnu/gnu/lib-names-ilp32.h +++ b/lib/libc/include/riscv64-linux-gnu/gnu/lib-names-lp64d.h @@ -1,10 +1,10 @@ /* This file is automatically generated. */ #ifndef __GNU_LIB_NAMES_H -# error "Never use directly; include instead." +# error "Never use directly; include instead." #endif -#define LD_LINUX_RISCV32_ILP32_SO "ld-linux-riscv32-ilp32.so.1" -#define LD_SO "ld-linux-riscv32-ilp32.so.1" +#define LD_LINUX_RISCV64_LP64D_SO "ld-linux-riscv64-lp64d.so.1" +#define LD_SO "ld-linux-riscv64-lp64d.so.1" #define LIBANL_SO "libanl.so.1" #define LIBBROKENLOCALE_SO "libBrokenLocale.so.1" #define LIBC_MALLOC_DEBUG_SO "libc_malloc_debug.so.0" @@ -24,4 +24,4 @@ #define LIBRESOLV_SO "libresolv.so.2" #define LIBRT_SO "librt.so.1" #define LIBTHREAD_DB_SO "libthread_db.so.1" -#define LIBUTIL_SO "libutil.so.1" \ No newline at end of file +#define LIBUTIL_SO "libutil.so.1" diff --git a/lib/libc/include/riscv64-linux-gnu/gnu/stubs-lp64.h b/lib/libc/include/riscv64-linux-gnu/gnu/stubs-lp64d.h similarity index 63% rename from lib/libc/include/riscv64-linux-gnu/gnu/stubs-lp64.h rename to lib/libc/include/riscv64-linux-gnu/gnu/stubs-lp64d.h index 6ce02418e6..4d4c0d146a 100644 --- a/lib/libc/include/riscv64-linux-gnu/gnu/stubs-lp64.h +++ b/lib/libc/include/riscv64-linux-gnu/gnu/stubs-lp64d.h @@ -14,25 +14,11 @@ #define __stub___compat_uselib #define __stub_chflags #define __stub_fchflags -#define __stub_feclearexcept #define __stub_fedisableexcept #define __stub_feenableexcept -#define __stub_fegetenv #define __stub_fegetexcept -#define __stub_fegetexceptflag -#define __stub_fegetmode -#define __stub_fegetround -#define __stub_feholdexcept -#define __stub_feraiseexcept -#define __stub_fesetenv -#define __stub_fesetexcept -#define __stub_fesetexceptflag -#define __stub_fesetmode -#define __stub_fesetround -#define __stub_fetestexcept -#define __stub_feupdateenv #define __stub_gtty #define __stub_revoke #define __stub_setlogin #define __stub_sigreturn -#define __stub_stty \ No newline at end of file +#define __stub_stty From 923f114bdb4e1808d7f6f424a292c9775ada6f25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Fri, 2 Aug 2024 09:09:48 +0200 Subject: [PATCH 120/266] glibc: Define NO_INITFINI for non-legacy architectures. --- src/glibc.zig | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/glibc.zig b/src/glibc.zig index 85f1828ad4..f61c8e5757 100644 --- a/src/glibc.zig +++ b/src/glibc.zig @@ -164,6 +164,21 @@ pub fn loadMetaData(gpa: Allocator, contents: []const u8) LoadMetaDataError!*ABI return abi; } +fn useElfInitFini(target: std.Target) bool { + // Legacy architectures use _init/_fini. + return switch (target.cpu.arch) { + .arm, .armeb, .thumb, .thumbeb => true, + .aarch64, .aarch64_be => true, + .m68k => true, + .mips, .mipsel, .mips64, .mips64el => true, + .powerpc, .powerpcle, .powerpc64, .powerpc64le => true, + .s390x => true, + .sparc, .sparc64 => true, + .x86, .x86_64 => true, + else => false, + }; +} + pub const CRTFile = enum { crti_o, crtn_o, @@ -369,6 +384,10 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile, prog_node: std.Progre }); try add_include_dirs(comp, arena, &args); + if (!useElfInitFini(target)) { + try args.append("-DNO_INITFINI"); + } + if (target.cpu.arch == .x86) { // This prevents i386/sysdep.h from trying to do some // silly and unnecessary inline asm hack that uses weird From f407778e83b46787b6337a8015d93f3d66334907 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Fri, 2 Aug 2024 08:20:53 +0200 Subject: [PATCH 121/266] glibc: Set -frounding-math like upstream. ~Same thing as b03a04c7fca587af0f1caf59881def3c91596728. --- src/glibc.zig | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/glibc.zig b/src/glibc.zig index f61c8e5757..2b4b3402d6 100644 --- a/src/glibc.zig +++ b/src/glibc.zig @@ -374,8 +374,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile, prog_node: std.Progre "-std=gnu11", "-fgnu89-inline", "-fmerge-all-constants", - // glibc sets this flag but clang does not support it. - // "-frounding-math", + "-frounding-math", "-fno-stack-protector", "-fno-common", "-fmath-errno", From f43c05690ed7f5a5f2db7dede06bba50016fa857 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Fri, 2 Aug 2024 09:11:16 +0200 Subject: [PATCH 122/266] process_headers: Replace ilp32/lp64 with ilp32d/lp64d. The former are soft float; the latter are hard float. We primarily care about hard float here. --- tools/process_headers.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/process_headers.zig b/tools/process_headers.zig index 7bed1a91c7..81333db1f1 100644 --- a/tools/process_headers.zig +++ b/tools/process_headers.zig @@ -160,12 +160,12 @@ const glibc_targets = [_]LibCTarget{ .abi = MultiAbi{ .specific = Abi.gnueabi }, }, LibCTarget{ - .name = "riscv32-linux-gnu-rv32imac-ilp32", + .name = "riscv32-linux-gnu-rv32imafdc-ilp32d", .arch = MultiArch{ .specific = Arch.riscv32 }, .abi = MultiAbi{ .specific = Abi.gnu }, }, LibCTarget{ - .name = "riscv64-linux-gnu-rv64imac-lp64", + .name = "riscv64-linux-gnu-rv64imafdc-lp64d", .arch = MultiArch{ .specific = Arch.riscv64 }, .abi = MultiAbi{ .specific = Abi.gnu }, }, From 6def9cee009a0ce300c8625d1d11a54e4e35c3f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Fri, 2 Aug 2024 00:54:05 +0200 Subject: [PATCH 123/266] ci: Add stage3/4 diff to (aarch64,x86_64)-windows-release scripts. --- ci/aarch64-windows.ps1 | 19 +++++++++++++++++++ ci/x86_64-windows-release.ps1 | 19 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/ci/aarch64-windows.ps1 b/ci/aarch64-windows.ps1 index 8365ff285d..bf68a6443c 100644 --- a/ci/aarch64-windows.ps1 +++ b/ci/aarch64-windows.ps1 @@ -69,3 +69,22 @@ Write-Output "Main test suite..." -Dskip-non-native ` -Denable-symlinks-windows CheckLastExitCode + +# Ensure that stage3 and stage4 are byte-for-byte identical. +Write-Output "Build and compare stage4..." +& "stage3-release\bin\zig.exe" build ` + --prefix stage4-release ` + -Denable-llvm ` + -Dno-lib ` + -Doptimize=ReleaseFast ` + -Dstrip ` + -Dtarget="$TARGET" ` + -Duse-zig-libcxx ` + -Dversion-string="$(stage3-release\bin\zig version)" +CheckLastExitCode + +# Compare-Object returns an error code if the files differ. +Write-Output "If the following command fails, it means nondeterminism has been" +Write-Output "introduced, making stage3 and stage4 no longer byte-for-byte identical." +Compare-Object (Get-Content stage3-release\bin\zig.exe) (Get-Content stage4-release\bin\zig.exe) +CheckLastExitCode diff --git a/ci/x86_64-windows-release.ps1 b/ci/x86_64-windows-release.ps1 index d2826f242d..3d8938ce19 100644 --- a/ci/x86_64-windows-release.ps1 +++ b/ci/x86_64-windows-release.ps1 @@ -70,6 +70,25 @@ Write-Output "Main test suite..." -Denable-symlinks-windows CheckLastExitCode +# Ensure that stage3 and stage4 are byte-for-byte identical. +Write-Output "Build and compare stage4..." +& "stage3-release\bin\zig.exe" build ` + --prefix stage4-release ` + -Denable-llvm ` + -Dno-lib ` + -Doptimize=ReleaseFast ` + -Dstrip ` + -Dtarget="$TARGET" ` + -Duse-zig-libcxx ` + -Dversion-string="$(stage3-release\bin\zig version)" +CheckLastExitCode + +# Compare-Object returns an error code if the files differ. +Write-Output "If the following command fails, it means nondeterminism has been" +Write-Output "introduced, making stage3 and stage4 no longer byte-for-byte identical." +Compare-Object (Get-Content stage3-release\bin\zig.exe) (Get-Content stage4-release\bin\zig.exe) +CheckLastExitCode + Write-Output "Build x86_64-windows-msvc behavior tests using the C backend..." & "stage3-release\bin\zig.exe" test ` ..\test\behavior.zig ` From 76ebae2ee0b3322a9bdba30f14f87d00a22dbadb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Fri, 2 Aug 2024 09:56:13 +0200 Subject: [PATCH 124/266] ci: Add stage3/4 diff to aarch64-linux-release script. --- ci/aarch64-linux-release.sh | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/ci/aarch64-linux-release.sh b/ci/aarch64-linux-release.sh index ee8ecbf470..947694ce39 100644 --- a/ci/aarch64-linux-release.sh +++ b/ci/aarch64-linux-release.sh @@ -64,6 +64,22 @@ stage3-release/bin/zig build test docs \ --zig-lib-dir "$PWD/../lib" \ -Denable-tidy +# Ensure that stage3 and stage4 are byte-for-byte identical. +stage3-release/bin/zig build \ + --prefix stage4-release \ + -Denable-llvm \ + -Dno-lib \ + -Doptimize=ReleaseFast \ + -Dstrip \ + -Dtarget=$TARGET \ + -Duse-zig-libcxx \ + -Dversion-string="$(stage3-release/bin/zig version)" + +# diff returns an error code if the files differ. +echo "If the following command fails, it means nondeterminism has been" +echo "introduced, making stage3 and stage4 no longer byte-for-byte identical." +diff stage3-release/bin/zig stage4-release/bin/zig + # Ensure that updating the wasm binary from this commit will result in a viable build. stage3-release/bin/zig build update-zig1 From 975c185b92a7d470ea705b28f46a8004bdda3c60 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 2 Aug 2024 12:00:08 -0700 Subject: [PATCH 125/266] fix compilation on powerpc GNU systems ...which have a ucontext_t but not a PC register. The current stack unwinding implementation does not yet support this architecture. Also fix name of `std.debug.SelfInfo.openSelf` to remove redundancy. Also removed this hook into root providing an "openSelfDebugInfo" function. Sorry, this debugging code is not of sufficient quality to offer a plugin API right now. --- lib/std/debug.zig | 22 +++++++++++++++------- lib/std/debug/Dwarf.zig | 24 ++++++++++++++++++++++++ lib/std/debug/Dwarf/abi.zig | 27 +++------------------------ lib/std/debug/Dwarf/expression.zig | 4 ++-- lib/std/debug/SelfInfo.zig | 13 ++++++------- test/standalone/coff_dwarf/main.zig | 2 +- 6 files changed, 51 insertions(+), 41 deletions(-) diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 0f10bada71..4d3437f665 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -88,7 +88,7 @@ pub fn getSelfDebugInfo() !*SelfInfo { if (self_debug_info) |*info| { return info; } else { - self_debug_info = try SelfInfo.openSelf(getDebugInfoAllocator()); + self_debug_info = try SelfInfo.open(getDebugInfoAllocator()); return &self_debug_info.?; } } @@ -573,17 +573,19 @@ pub const StackIterator = struct { pub fn initWithContext(first_address: ?usize, debug_info: *SelfInfo, context: *posix.ucontext_t) !StackIterator { // The implementation of DWARF unwinding on aarch64-macos is not complete. However, Apple mandates that // the frame pointer register is always used, so on this platform we can safely use the FP-based unwinder. - if (builtin.target.isDarwin() and native_arch == .aarch64) { + if (builtin.target.isDarwin() and native_arch == .aarch64) return init(first_address, context.mcontext.ss.fp); - } else { + + if (SelfInfo.supports_unwinding) { var iterator = init(first_address, null); iterator.unwind_state = .{ .debug_info = debug_info, .dwarf_context = try SelfInfo.UnwindContext.init(debug_info.allocator, context), }; - return iterator; } + + return init(first_address, null); } pub fn deinit(it: *StackIterator) void { @@ -725,11 +727,13 @@ pub fn writeCurrentStackTrace( tty_config: io.tty.Config, start_addr: ?usize, ) !void { - var context: ThreadContext = undefined; - const has_context = getContext(&context); if (native_os == .windows) { + var context: ThreadContext = undefined; + assert(getContext(&context)); return writeStackTraceWindows(out_stream, debug_info, tty_config, &context, start_addr); } + var context: ThreadContext = undefined; + const has_context = getContext(&context); var it = (if (has_context) blk: { break :blk StackIterator.initWithContext(start_addr, debug_info, &context) catch null; @@ -1340,7 +1344,7 @@ test "manage resources correctly" { } const writer = std.io.null_writer; - var di = try SelfInfo.openSelf(testing.allocator); + var di = try SelfInfo.open(testing.allocator); defer di.deinit(); try printSourceAtAddress(&di, writer, showMyTrace(), io.tty.detectConfig(std.io.getStdErr())); } @@ -1581,5 +1585,9 @@ pub inline fn inValgrind() bool { } test { + _ = &Dwarf; + _ = &MemoryAccessor; + _ = &Pdb; + _ = &SelfInfo; _ = &dumpHex; } diff --git a/lib/std/debug/Dwarf.zig b/lib/std/debug/Dwarf.zig index 991c731549..e30e3d06ac 100644 --- a/lib/std/debug/Dwarf.zig +++ b/lib/std/debug/Dwarf.zig @@ -2023,3 +2023,27 @@ fn pcRelBase(field_ptr: usize, pc_rel_offset: i64) !usize { return std.math.add(usize, field_ptr, @as(usize, @intCast(pc_rel_offset))); } } + +pub fn supportsUnwinding(target: std.Target) bool { + return switch (target.cpu.arch) { + .x86 => switch (target.os.tag) { + .linux, .netbsd, .solaris, .illumos => true, + else => false, + }, + .x86_64 => switch (target.os.tag) { + .linux, .netbsd, .freebsd, .openbsd, .macos, .ios, .solaris, .illumos => true, + else => false, + }, + .arm => switch (target.os.tag) { + .linux => true, + else => false, + }, + .aarch64 => switch (target.os.tag) { + .linux, .netbsd, .freebsd, .macos, .ios => true, + else => false, + }, + // Unwinding is possible on other targets but this implementation does + // not support them...yet! + else => false, + }; +} diff --git a/lib/std/debug/Dwarf/abi.zig b/lib/std/debug/Dwarf/abi.zig index e87f023d72..f153a10ba4 100644 --- a/lib/std/debug/Dwarf/abi.zig +++ b/lib/std/debug/Dwarf/abi.zig @@ -5,35 +5,14 @@ const mem = std.mem; const posix = std.posix; const Arch = std.Target.Cpu.Arch; -pub fn supportsUnwinding(target: std.Target) bool { - return switch (target.cpu.arch) { - .x86 => switch (target.os.tag) { - .linux, .netbsd, .solaris, .illumos => true, - else => false, - }, - .x86_64 => switch (target.os.tag) { - .linux, .netbsd, .freebsd, .openbsd, .macos, .ios, .solaris, .illumos => true, - else => false, - }, - .arm => switch (target.os.tag) { - .linux => true, - else => false, - }, - .aarch64 => switch (target.os.tag) { - .linux, .netbsd, .freebsd, .macos, .ios => true, - else => false, - }, - else => false, - }; -} - -pub fn ipRegNum(arch: Arch) u8 { +/// Returns `null` for CPU architectures without an instruction pointer register. +pub fn ipRegNum(arch: Arch) ?u8 { return switch (arch) { .x86 => 8, .x86_64 => 16, .arm => 15, .aarch64 => 32, - else => unreachable, + else => null, }; } diff --git a/lib/std/debug/Dwarf/expression.zig b/lib/std/debug/Dwarf/expression.zig index 7a3a4ed740..5fab56de6e 100644 --- a/lib/std/debug/Dwarf/expression.zig +++ b/lib/std/debug/Dwarf/expression.zig @@ -1190,11 +1190,11 @@ test "DWARF expressions" { mem.writeInt(usize, reg_bytes[0..@sizeOf(usize)], 0xee, native_endian); (try abi.regValueNative(&thread_context, abi.fpRegNum(native_arch, reg_context), reg_context)).* = 1; (try abi.regValueNative(&thread_context, abi.spRegNum(native_arch, reg_context), reg_context)).* = 2; - (try abi.regValueNative(&thread_context, abi.ipRegNum(native_arch), reg_context)).* = 3; + (try abi.regValueNative(&thread_context, abi.ipRegNum(native_arch).?, reg_context)).* = 3; try b.writeBreg(writer, abi.fpRegNum(native_arch, reg_context), @as(usize, 100)); try b.writeBreg(writer, abi.spRegNum(native_arch, reg_context), @as(usize, 200)); - try b.writeBregx(writer, abi.ipRegNum(native_arch), @as(usize, 300)); + try b.writeBregx(writer, abi.ipRegNum(native_arch).?, @as(usize, 300)); try b.writeRegvalType(writer, @as(u8, 0), @as(usize, 400)); _ = try stack_machine.run(program.items, allocator, context, 0); diff --git a/lib/std/debug/SelfInfo.zig b/lib/std/debug/SelfInfo.zig index 80a8cb4cd9..b1679a224b 100644 --- a/lib/std/debug/SelfInfo.zig +++ b/lib/std/debug/SelfInfo.zig @@ -34,18 +34,15 @@ allocator: Allocator, address_map: std.AutoHashMap(usize, *Module), modules: if (native_os == .windows) std.ArrayListUnmanaged(WindowsModule) else void, -pub const OpenSelfError = error{ +pub const OpenError = error{ MissingDebugInfo, UnsupportedOperatingSystem, } || @typeInfo(@typeInfo(@TypeOf(SelfInfo.init)).Fn.return_type.?).ErrorUnion.error_set; -pub fn openSelf(allocator: Allocator) OpenSelfError!SelfInfo { +pub fn open(allocator: Allocator) OpenError!SelfInfo { nosuspend { if (builtin.strip_debug_info) return error.MissingDebugInfo; - if (@hasDecl(root, "os") and @hasDecl(root.os, "debug") and @hasDecl(root.os.debug, "openSelfDebugInfo")) { - return root.os.debug.openSelfDebugInfo(allocator); - } switch (native_os) { .linux, .freebsd, @@ -1721,6 +1718,8 @@ pub const UnwindContext = struct { allocator: Allocator, thread_context: *std.debug.ThreadContext, ) !UnwindContext { + comptime assert(supports_unwinding); + const pc = stripInstructionPtrAuthCode( (try regValueNative(thread_context, ip_reg_num, null)).*, ); @@ -1982,8 +1981,8 @@ fn spRegNum(reg_context: Dwarf.abi.RegisterContext) u8 { return Dwarf.abi.spRegNum(native_arch, reg_context); } -const ip_reg_num = Dwarf.abi.ipRegNum(native_arch); -const supports_unwinding = Dwarf.abi.supportsUnwinding(builtin.target); +const ip_reg_num = Dwarf.abi.ipRegNum(native_arch).?; +pub const supports_unwinding = Dwarf.supportsUnwinding(builtin.target); fn unwindFrameMachODwarf( context: *UnwindContext, diff --git a/test/standalone/coff_dwarf/main.zig b/test/standalone/coff_dwarf/main.zig index 236aa1c5fa..1cf2587e58 100644 --- a/test/standalone/coff_dwarf/main.zig +++ b/test/standalone/coff_dwarf/main.zig @@ -9,7 +9,7 @@ pub fn main() !void { defer assert(gpa.deinit() == .ok); const allocator = gpa.allocator(); - var debug_info = try std.debug.openSelfDebugInfo(allocator); + var debug_info = try std.debug.SelfInfo.open(allocator); defer debug_info.deinit(); var add_addr: usize = undefined; From 6d606cc38b4df2b20af9d77367f8ab22bbbea092 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 2 Aug 2024 12:35:49 -0700 Subject: [PATCH 126/266] reintroduce std.Dwarf.abi.supportsUnwinding There are two concepts here: one for whether dwarf supports unwinding on that target, and another for whether the Zig standard library implements it yet. --- lib/std/debug/Dwarf.zig | 24 ------------------------ lib/std/debug/Dwarf/abi.zig | 25 +++++++++++++++++++++++++ lib/std/debug/SelfInfo.zig | 37 ++++++++++++++++++++++++++++++++++++- 3 files changed, 61 insertions(+), 25 deletions(-) diff --git a/lib/std/debug/Dwarf.zig b/lib/std/debug/Dwarf.zig index e30e3d06ac..991c731549 100644 --- a/lib/std/debug/Dwarf.zig +++ b/lib/std/debug/Dwarf.zig @@ -2023,27 +2023,3 @@ fn pcRelBase(field_ptr: usize, pc_rel_offset: i64) !usize { return std.math.add(usize, field_ptr, @as(usize, @intCast(pc_rel_offset))); } } - -pub fn supportsUnwinding(target: std.Target) bool { - return switch (target.cpu.arch) { - .x86 => switch (target.os.tag) { - .linux, .netbsd, .solaris, .illumos => true, - else => false, - }, - .x86_64 => switch (target.os.tag) { - .linux, .netbsd, .freebsd, .openbsd, .macos, .ios, .solaris, .illumos => true, - else => false, - }, - .arm => switch (target.os.tag) { - .linux => true, - else => false, - }, - .aarch64 => switch (target.os.tag) { - .linux, .netbsd, .freebsd, .macos, .ios => true, - else => false, - }, - // Unwinding is possible on other targets but this implementation does - // not support them...yet! - else => false, - }; -} diff --git a/lib/std/debug/Dwarf/abi.zig b/lib/std/debug/Dwarf/abi.zig index f153a10ba4..b2c4cb9536 100644 --- a/lib/std/debug/Dwarf/abi.zig +++ b/lib/std/debug/Dwarf/abi.zig @@ -5,6 +5,31 @@ const mem = std.mem; const posix = std.posix; const Arch = std.Target.Cpu.Arch; +/// Tells whether unwinding for this target is supported by the Dwarf standard. +/// +/// See also `std.debug.SelfInfo.supportsUnwinding` which tells whether the Zig +/// standard library has a working implementation of unwinding for this target. +pub fn supportsUnwinding(target: std.Target) bool { + return switch (target.cpu.arch) { + .amdgcn, + .nvptx, + .nvptx64, + .spirv, + .spirv32, + .spirv64, + .spu_2, + => false, + + // Enabling this causes relocation errors such as: + // error: invalid relocation type R_RISCV_SUB32 at offset 0x20 + .riscv64, .riscv32 => false, + + // Conservative guess. Feel free to update this logic with any targets + // that are known to not support Dwarf unwinding. + else => true, + }; +} + /// Returns `null` for CPU architectures without an instruction pointer register. pub fn ipRegNum(arch: Arch) ?u8 { return switch (arch) { diff --git a/lib/std/debug/SelfInfo.zig b/lib/std/debug/SelfInfo.zig index b1679a224b..c27f466fb3 100644 --- a/lib/std/debug/SelfInfo.zig +++ b/lib/std/debug/SelfInfo.zig @@ -1982,7 +1982,42 @@ fn spRegNum(reg_context: Dwarf.abi.RegisterContext) u8 { } const ip_reg_num = Dwarf.abi.ipRegNum(native_arch).?; -pub const supports_unwinding = Dwarf.supportsUnwinding(builtin.target); + +/// Tells whether unwinding for the host is implemented. +pub const supports_unwinding = supportsUnwinding(builtin.target); + +comptime { + if (supports_unwinding) assert(Dwarf.abi.supportsUnwinding(builtin.target)); +} + +/// Tells whether unwinding for this target is *implemented* here in the Zig +/// standard library. +/// +/// See also `Dwarf.abi.supportsUnwinding` which tells whether Dwarf supports +/// unwinding on that target *in theory*. +pub fn supportsUnwinding(target: std.Target) bool { + return switch (target.cpu.arch) { + .x86 => switch (target.os.tag) { + .linux, .netbsd, .solaris, .illumos => true, + else => false, + }, + .x86_64 => switch (target.os.tag) { + .linux, .netbsd, .freebsd, .openbsd, .macos, .ios, .solaris, .illumos => true, + else => false, + }, + .arm => switch (target.os.tag) { + .linux => true, + else => false, + }, + .aarch64 => switch (target.os.tag) { + .linux, .netbsd, .freebsd, .macos, .ios => true, + else => false, + }, + // Unwinding is possible on other targets but this implementation does + // not support them...yet! + else => false, + }; +} fn unwindFrameMachODwarf( context: *UnwindContext, From d71076c9826ddd66bfcc04b9a6ded9dbf540d239 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Sat, 3 Aug 2024 18:46:04 +0200 Subject: [PATCH 127/266] std.os.linux: Replace `@hasDecl()` with `!= void` check for VDSO. If there is a VDSO, it will have clock_gettime(). The main thing we're concerned about is architectures that don't have a VDSO at all, of which there are a few. --- lib/std/os/linux.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index 198481c373..7eb0fdba69 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -1399,7 +1399,7 @@ const VdsoClockGettime = *align(1) const fn (clockid_t, *timespec) callconv(.C) var vdso_clock_gettime: ?VdsoClockGettime = &init_vdso_clock_gettime; pub fn clock_gettime(clk_id: clockid_t, tp: *timespec) usize { - if (@hasDecl(VDSO, "CGT_SYM")) { + if (VDSO != void) { const ptr = @atomicLoad(?VdsoClockGettime, &vdso_clock_gettime, .unordered); if (ptr) |f| { const rc = f(clk_id, tp); From 64e119124f03e94467032949cbd9b51c8311db05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Sat, 3 Aug 2024 18:48:53 +0200 Subject: [PATCH 128/266] std.os.linux: Fix CGT_SYM for mips/mips64. --- lib/std/os/linux/mips.zig | 4 ++-- lib/std/os/linux/mips64.zig | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/std/os/linux/mips.zig b/lib/std/os/linux/mips.zig index abf047e838..853df45fce 100644 --- a/lib/std/os/linux/mips.zig +++ b/lib/std/os/linux/mips.zig @@ -242,8 +242,8 @@ pub const F = struct { pub const MMAP2_UNIT = 4096; pub const VDSO = struct { - pub const CGT_SYM = "__kernel_clock_gettime"; - pub const CGT_VER = "LINUX_2.6.39"; + pub const CGT_SYM = "__vdso_clock_gettime"; + pub const CGT_VER = "LINUX_2.6"; }; pub const Flock = extern struct { diff --git a/lib/std/os/linux/mips64.zig b/lib/std/os/linux/mips64.zig index 9be0f41c4f..5089ba6fd3 100644 --- a/lib/std/os/linux/mips64.zig +++ b/lib/std/os/linux/mips64.zig @@ -227,8 +227,8 @@ pub const F = struct { pub const MMAP2_UNIT = 4096; pub const VDSO = struct { - pub const CGT_SYM = "__kernel_clock_gettime"; - pub const CGT_VER = "LINUX_2.6.39"; + pub const CGT_SYM = "__vdso_clock_gettime"; + pub const CGT_VER = "LINUX_2.6"; }; pub const Flock = extern struct { From 0ad97b41229805fac0e20d88cb24118535fbf056 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Sat, 3 Aug 2024 18:49:12 +0200 Subject: [PATCH 129/266] std.os.linux: Add VDSO definition for riscv32/riscv64. --- lib/std/os/linux/riscv32.zig | 5 ++++- lib/std/os/linux/riscv64.zig | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/std/os/linux/riscv32.zig b/lib/std/os/linux/riscv32.zig index 219eb5cc19..b8023c5d5c 100644 --- a/lib/std/os/linux/riscv32.zig +++ b/lib/std/os/linux/riscv32.zig @@ -188,7 +188,10 @@ pub const Elf_Symndx = u32; pub const MMAP2_UNIT = 4096; -pub const VDSO = struct {}; +pub const VDSO = struct { + pub const CGT_SYM = "__vdso_clock_gettime"; + pub const CGT_VER = "LINUX_4.15"; +}; /// TODO pub const ucontext_t = void; diff --git a/lib/std/os/linux/riscv64.zig b/lib/std/os/linux/riscv64.zig index fc0893d5c1..8c3c8fe289 100644 --- a/lib/std/os/linux/riscv64.zig +++ b/lib/std/os/linux/riscv64.zig @@ -215,7 +215,10 @@ pub const Stat = extern struct { pub const Elf_Symndx = u32; -pub const VDSO = struct {}; +pub const VDSO = struct { + pub const CGT_SYM = "__vdso_clock_gettime"; + pub const CGT_VER = "LINUX_4.15"; +}; /// TODO pub const ucontext_t = void; From 8056a851517cd92590ddb9a4b76ce7b1f4aeb242 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Sat, 3 Aug 2024 19:40:12 +0200 Subject: [PATCH 130/266] std: Move start_windows_tls.zig to os/windows/tls.zig. Just to be consistent with Linux. --- lib/std/{start_windows_tls.zig => os/windows/tls.zig} | 0 lib/std/start.zig | 6 +++--- 2 files changed, 3 insertions(+), 3 deletions(-) rename lib/std/{start_windows_tls.zig => os/windows/tls.zig} (100%) diff --git a/lib/std/start_windows_tls.zig b/lib/std/os/windows/tls.zig similarity index 100% rename from lib/std/start_windows_tls.zig rename to lib/std/os/windows/tls.zig diff --git a/lib/std/start.zig b/lib/std/start.zig index fd8661660b..edbe4e9bca 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -176,7 +176,7 @@ fn _DllMainCRTStartup( lpReserved: std.os.windows.LPVOID, ) callconv(std.os.windows.WINAPI) std.os.windows.BOOL { if (!builtin.single_threaded and !builtin.link_libc) { - _ = @import("start_windows_tls.zig"); + _ = @import("os/windows/tls.zig"); } if (@hasDecl(root, "DllMain")) { @@ -427,7 +427,7 @@ fn _start() callconv(.Naked) noreturn { fn WinStartup() callconv(std.os.windows.WINAPI) noreturn { @setAlignStack(16); if (!builtin.single_threaded and !builtin.link_libc) { - _ = @import("start_windows_tls.zig"); + _ = @import("os/windows/tls.zig"); } std.debug.maybeEnableSegfaultHandler(); @@ -438,7 +438,7 @@ fn WinStartup() callconv(std.os.windows.WINAPI) noreturn { fn wWinMainCRTStartup() callconv(std.os.windows.WINAPI) noreturn { @setAlignStack(16); if (!builtin.single_threaded and !builtin.link_libc) { - _ = @import("start_windows_tls.zig"); + _ = @import("os/windows/tls.zig"); } std.debug.maybeEnableSegfaultHandler(); From c2fcdc21c27803e5cbb6dfcb6f61aa0905f6df38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Sat, 3 Aug 2024 19:55:44 +0200 Subject: [PATCH 131/266] std.os.windows.tls: Change type of `_tls_start`/`_tls_end` to `*anyopaque`. If they're typed as `u8`, they can be aligned to anything. We want at least pointer size alignment. --- lib/std/os/windows/tls.zig | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/std/os/windows/tls.zig b/lib/std/os/windows/tls.zig index 48880b4811..e8735a2707 100644 --- a/lib/std/os/windows/tls.zig +++ b/lib/std/os/windows/tls.zig @@ -2,8 +2,8 @@ const std = @import("std"); const builtin = @import("builtin"); export var _tls_index: u32 = std.os.windows.TLS_OUT_OF_INDEXES; -export var _tls_start: u8 linksection(".tls") = 0; -export var _tls_end: u8 linksection(".tls$ZZZ") = 0; +export var _tls_start: ?*anyopaque linksection(".tls") = null; +export var _tls_end: ?*anyopaque linksection(".tls$ZZZ") = null; export var __xl_a: std.os.windows.PIMAGE_TLS_CALLBACK linksection(".CRT$XLA") = null; export var __xl_z: std.os.windows.PIMAGE_TLS_CALLBACK linksection(".CRT$XLZ") = null; @@ -31,8 +31,8 @@ comptime { //}; // This is the workaround because we can't do @intFromPtr at comptime like that. pub const IMAGE_TLS_DIRECTORY = extern struct { - StartAddressOfRawData: *anyopaque, - EndAddressOfRawData: *anyopaque, + StartAddressOfRawData: *?*anyopaque, + EndAddressOfRawData: *?*anyopaque, AddressOfIndex: *anyopaque, AddressOfCallBacks: *anyopaque, SizeOfZeroFill: u32, From 0f1db901980f9329b3c45c016eb467f083493060 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Sat, 3 Aug 2024 19:56:42 +0200 Subject: [PATCH 132/266] std.os.windows.tls: Slightly improve type safety. --- lib/std/os/windows/tls.zig | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/std/os/windows/tls.zig b/lib/std/os/windows/tls.zig index e8735a2707..658eff8681 100644 --- a/lib/std/os/windows/tls.zig +++ b/lib/std/os/windows/tls.zig @@ -1,11 +1,12 @@ const std = @import("std"); const builtin = @import("builtin"); +const windows = std.os.windows; export var _tls_index: u32 = std.os.windows.TLS_OUT_OF_INDEXES; export var _tls_start: ?*anyopaque linksection(".tls") = null; export var _tls_end: ?*anyopaque linksection(".tls$ZZZ") = null; -export var __xl_a: std.os.windows.PIMAGE_TLS_CALLBACK linksection(".CRT$XLA") = null; -export var __xl_z: std.os.windows.PIMAGE_TLS_CALLBACK linksection(".CRT$XLZ") = null; +export var __xl_a: windows.PIMAGE_TLS_CALLBACK linksection(".CRT$XLA") = null; +export var __xl_z: windows.PIMAGE_TLS_CALLBACK linksection(".CRT$XLZ") = null; comptime { if (builtin.target.cpu.arch == .x86 and builtin.zig_backend != .stage2_c) { @@ -33,8 +34,8 @@ comptime { pub const IMAGE_TLS_DIRECTORY = extern struct { StartAddressOfRawData: *?*anyopaque, EndAddressOfRawData: *?*anyopaque, - AddressOfIndex: *anyopaque, - AddressOfCallBacks: *anyopaque, + AddressOfIndex: *u32, + AddressOfCallBacks: *windows.PIMAGE_TLS_CALLBACK, SizeOfZeroFill: u32, Characteristics: u32, }; @@ -42,7 +43,7 @@ export const _tls_used linksection(".rdata$T") = IMAGE_TLS_DIRECTORY{ .StartAddressOfRawData = &_tls_start, .EndAddressOfRawData = &_tls_end, .AddressOfIndex = &_tls_index, - .AddressOfCallBacks = @as(*anyopaque, @ptrCast(&__xl_a)), + .AddressOfCallBacks = &__xl_a, .SizeOfZeroFill = 0, .Characteristics = 0, }; From 1d8fca0060ecfe700df7722e6763de94e2bd0b51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Sat, 3 Aug 2024 19:57:06 +0200 Subject: [PATCH 133/266] std.os.windows.tls: Only define _tls_array when targeting MSVC. LLVM does not use it when emitting code for the MinGW ABI. --- lib/std/os/windows/tls.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/os/windows/tls.zig b/lib/std/os/windows/tls.zig index 658eff8681..e4fceebfc4 100644 --- a/lib/std/os/windows/tls.zig +++ b/lib/std/os/windows/tls.zig @@ -9,7 +9,7 @@ export var __xl_a: windows.PIMAGE_TLS_CALLBACK linksection(".CRT$XLA") = null; export var __xl_z: windows.PIMAGE_TLS_CALLBACK linksection(".CRT$XLZ") = null; comptime { - if (builtin.target.cpu.arch == .x86 and builtin.zig_backend != .stage2_c) { + if (builtin.cpu.arch == .x86 and builtin.abi == .msvc and builtin.zig_backend != .stage2_c) { // The __tls_array is the offset of the ThreadLocalStoragePointer field // in the TEB block whose base address held in the %fs segment. asm ( From cb1fffb29eae6eafd46c87d3950911f496288ab2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Sat, 3 Aug 2024 20:18:14 +0200 Subject: [PATCH 134/266] std.os.windows.tls: Set `AddressOfCallBacks` to `&__xl_a + 1`. `__xl_a` is just a global variable containing a null function pointer. There's nothing magical about it or its name at all. The section names used on `__xl_a` and `__xl_b` (`.CRT$XLA` and `.CRT$XLZ`) are the real magic here. The compiler emits TLS variables into `.CRT$XL` sections, where `x` is an uppercase letter between A and Z (exclusive). The linker then sorts those sections alphabetically (due to the `$`), and the result is a neat array of TLS initialization callbacks between `__xl_a` and `__xl_z`. That array is null-terminated, though! Normally, `__xl_z` serves as the null terminator; however, by pointing `AddressesOfCallBacks` to `__xl_a`, which just contains a null function pointer, we've effectively made it so that the PE loader will just immediately stop invoking TLS callbacks. Fix that by pointing to the first actual TLS callback instead (or `__xl_z` if there are none). --- lib/std/os/windows/tls.zig | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/std/os/windows/tls.zig b/lib/std/os/windows/tls.zig index e4fceebfc4..f98e22b620 100644 --- a/lib/std/os/windows/tls.zig +++ b/lib/std/os/windows/tls.zig @@ -20,8 +20,6 @@ comptime { } // TODO this is how I would like it to be expressed -// TODO also note, ReactOS has a +1 on StartAddressOfRawData and AddressOfCallBacks. Investigate -// why they do that. //export const _tls_used linksection(".rdata$T") = std.os.windows.IMAGE_TLS_DIRECTORY { // .StartAddressOfRawData = @intFromPtr(&_tls_start), // .EndAddressOfRawData = @intFromPtr(&_tls_end), @@ -35,7 +33,7 @@ pub const IMAGE_TLS_DIRECTORY = extern struct { StartAddressOfRawData: *?*anyopaque, EndAddressOfRawData: *?*anyopaque, AddressOfIndex: *u32, - AddressOfCallBacks: *windows.PIMAGE_TLS_CALLBACK, + AddressOfCallBacks: [*:null]windows.PIMAGE_TLS_CALLBACK, SizeOfZeroFill: u32, Characteristics: u32, }; @@ -43,7 +41,10 @@ export const _tls_used linksection(".rdata$T") = IMAGE_TLS_DIRECTORY{ .StartAddressOfRawData = &_tls_start, .EndAddressOfRawData = &_tls_end, .AddressOfIndex = &_tls_index, - .AddressOfCallBacks = &__xl_a, + // __xl_a is just a global variable containing a null pointer; the actual callbacks sit in + // between __xl_a and __xl_z. So we need to skip over __xl_a here. If there are no callbacks, + // this just means we point to __xl_z (the null terminator). + .AddressOfCallBacks = @as([*:null]windows.PIMAGE_TLS_CALLBACK, @ptrCast(&__xl_a)) + 1, .SizeOfZeroFill = 0, .Characteristics = 0, }; From a18293214dbe65f74a1420cd5728e40427236f6e Mon Sep 17 00:00:00 2001 From: Linus Groh Date: Sat, 3 Aug 2024 02:37:31 +0100 Subject: [PATCH 135/266] std.Target: Specify dynamic linker and C type sizes for serenity --- lib/std/Target.zig | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/std/Target.zig b/lib/std/Target.zig index 8d9f596072..8a21a7256f 100644 --- a/lib/std/Target.zig +++ b/lib/std/Target.zig @@ -1771,6 +1771,8 @@ pub const DynamicLinker = struct { .visionos, => init("/usr/lib/dyld"), + .serenity => init("/usr/lib/Loader.so"), + // Operating systems in this list have been verified as not having a standard // dynamic linker path. .freestanding, @@ -1783,7 +1785,6 @@ pub const DynamicLinker = struct { .vulkan, .other, .plan9, - .serenity, => none, // TODO revisit when multi-arch for Haiku is available @@ -2086,6 +2087,7 @@ pub fn c_type_bit_size(target: Target, c_type: CType) u16 { .haiku, .fuchsia, .minix, + .serenity, => switch (target.cpu.arch) { .msp430 => switch (c_type) { .char => return 8, @@ -2291,7 +2293,6 @@ pub fn c_type_bit_size(target: Target, c_type: CType) u16 { .driverkit, .shadermodel, .liteos, - .serenity, => @panic("TODO specify the C integer and float type sizes for this OS"), } } From ea847535fc393f7f73801285af8960379e9376c8 Mon Sep 17 00:00:00 2001 From: YANG Xudong Date: Sat, 3 Aug 2024 15:54:01 +0800 Subject: [PATCH 136/266] Add loongarch 64 to gen_stubs.zig. --- tools/gen_stubs.zig | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tools/gen_stubs.zig b/tools/gen_stubs.zig index 47727587ed..23cfd57d55 100644 --- a/tools/gen_stubs.zig +++ b/tools/gen_stubs.zig @@ -2,7 +2,7 @@ //! ./gen_stubs /path/to/musl/build-all >libc.S //! //! The directory 'build-all' is expected to contain these subdirectories: -//! arm x86 mips mips64 powerpc powerpc64 riscv32 riscv64 x86_64 +//! arm x86 mips mips64 powerpc powerpc64 riscv32 riscv64 x86_64 loongarch64 //! //! ...each with 'lib/libc.so' inside of them. //! @@ -18,6 +18,7 @@ //! - `-DARCH_powerpc` //! - `-DARCH_powerpc64` //! - `-DARCH_aarch64` +//! - `-DARCH_loongarch64` // TODO: pick the best index to put them into instead of at the end // - e.g. find a common previous symbol and put it after that one @@ -77,7 +78,8 @@ const MultiSym = struct { ms.present[archIndex(.x86_64)] == false and ms.present[archIndex(.powerpc)] == true and ms.present[archIndex(.powerpc64)] == false and - ms.present[archIndex(.aarch64)] == false; + ms.present[archIndex(.aarch64)] == false and + ms.present[archIndex(.loongarch64)] == false; } fn commonSize(ms: MultiSym) ?u64 { @@ -121,6 +123,7 @@ const MultiSym = struct { .{ .powerpc, 4 }, .{ .powerpc64, 8 }, .{ .aarch64, 8 }, + .{ .loongarch64, 8 }, }; inline for (map) |item| { const arch = item[0]; @@ -144,6 +147,7 @@ const MultiSym = struct { .{ .powerpc, 8 }, .{ .powerpc64, 16 }, .{ .aarch64, 16 }, + .{ .loongarch64, 16 }, }; inline for (map) |item| { const arch = item[0]; @@ -167,6 +171,7 @@ const MultiSym = struct { .{ .powerpc, 1 }, .{ .powerpc64, 2 }, .{ .aarch64, 2 }, + .{ .loongarch64, 2 }, }; inline for (map) |item| { const arch = item[0]; From a655c15c4004d553ea462652f69acd37e4514f79 Mon Sep 17 00:00:00 2001 From: Fri3dNstuff <102751849+Fri3dNstuff@users.noreply.github.com> Date: Mon, 5 Aug 2024 01:02:15 +0300 Subject: [PATCH 137/266] std.sort: Remove key argument from binary-search-like functions (#20927) closes #20110 --- lib/compiler/aro/aro/Preprocessor.zig | 7 +- lib/std/debug/SelfInfo.zig | 10 +- lib/std/sort.zig | 571 +++++++++++++------------- 3 files changed, 300 insertions(+), 288 deletions(-) diff --git a/lib/compiler/aro/aro/Preprocessor.zig b/lib/compiler/aro/aro/Preprocessor.zig index 06193bf3e4..aa0a64c3e7 100644 --- a/lib/compiler/aro/aro/Preprocessor.zig +++ b/lib/compiler/aro/aro/Preprocessor.zig @@ -265,14 +265,13 @@ fn clearBuffers(pp: *Preprocessor) void { pub fn expansionSlice(pp: *Preprocessor, tok: Tree.TokenIndex) []Source.Location { const S = struct { - fn order_token_index(context: void, lhs: Tree.TokenIndex, rhs: Tree.TokenIndex) std.math.Order { - _ = context; - return std.math.order(lhs, rhs); + fn orderTokenIndex(context: Tree.TokenIndex, item: Tree.TokenIndex) std.math.Order { + return std.math.order(item, context); } }; const indices = pp.expansion_entries.items(.idx); - const idx = std.sort.binarySearch(Tree.TokenIndex, tok, indices, {}, S.order_token_index) orelse return &.{}; + const idx = std.sort.binarySearch(Tree.TokenIndex, indices, tok, S.orderTokenIndex) orelse return &.{}; const locs = pp.expansion_entries.items(.locs)[idx]; var i: usize = 0; while (locs[i].id != .unused) : (i += 1) {} diff --git a/lib/std/debug/SelfInfo.zig b/lib/std/debug/SelfInfo.zig index c27f466fb3..f9747a088e 100644 --- a/lib/std/debug/SelfInfo.zig +++ b/lib/std/debug/SelfInfo.zig @@ -1842,14 +1842,14 @@ pub fn unwindFrameDwarf( &fde, ); } else { - const index = std.sort.binarySearch(Dwarf.FrameDescriptionEntry, context.pc, di.fde_list.items, {}, struct { - pub fn compareFn(_: void, pc: usize, mid_item: Dwarf.FrameDescriptionEntry) std.math.Order { - if (pc < mid_item.pc_begin) return .lt; + const index = std.sort.binarySearch(Dwarf.FrameDescriptionEntry, di.fde_list.items, context.pc, struct { + pub fn compareFn(pc: usize, item: Dwarf.FrameDescriptionEntry) std.math.Order { + if (pc < item.pc_begin) return .gt; - const range_end = mid_item.pc_begin + mid_item.pc_range; + const range_end = item.pc_begin + item.pc_range; if (pc < range_end) return .eq; - return .gt; + return .lt; } }.compareFn); diff --git a/lib/std/sort.zig b/lib/std/sort.zig index d3e6f9c16d..9dad2949bf 100644 --- a/lib/std/sort.zig +++ b/lib/std/sort.zig @@ -427,375 +427,388 @@ test "sort fuzz testing" { } } -/// Returns the index of an element in `items` equal to `key`. -/// If there are multiple such elements, returns the index of any one of them. -/// If there are no such elements, returns `null`. +/// Returns the index of an element in `items` returning `.eq` when given to `compareFn`. +/// - If there are multiple such elements, returns the index of any one of them. +/// - If there are no such elements, returns `null`. /// -/// `items` must be sorted in ascending order with respect to `compareFn`. +/// `items` must be sorted in ascending order with respect to `compareFn`: +/// ``` +/// [0] [len] +/// ┌───┬───┬─/ /─┬───┬───┬───┬─/ /─┬───┬───┬───┬─/ /─┬───┐ +/// │.lt│.lt│ \ \ │.lt│.eq│.eq│ \ \ │.eq│.gt│.gt│ \ \ │.gt│ +/// └───┴───┴─/ /─┴───┴───┴───┴─/ /─┴───┴───┴───┴─/ /─┴───┘ +/// ├─────────────────┼─────────────────┼─────────────────┤ +/// ↳ zero or more ↳ zero or more ↳ zero or more +/// ├─────────────────┤ +/// ↳ if not null, returned +/// index is in this range +/// ``` /// -/// O(log n) complexity. +/// `O(log n)` time complexity. +/// +/// See also: `lowerBound, `upperBound`, `partitionPoint`, `equalRange`. pub fn binarySearch( comptime T: type, - key: anytype, items: []const T, context: anytype, - comptime compareFn: fn (context: @TypeOf(context), key: @TypeOf(key), mid_item: T) math.Order, + comptime compareFn: fn (@TypeOf(context), T) std.math.Order, ) ?usize { - var left: usize = 0; - var right: usize = items.len; + var low: usize = 0; + var high: usize = items.len; - while (left < right) { + while (low < high) { // Avoid overflowing in the midpoint calculation - const mid = left + (right - left) / 2; - // Compare the key with the midpoint element - switch (compareFn(context, key, items[mid])) { + const mid = low + (high - low) / 2; + switch (compareFn(context, items[mid])) { .eq => return mid, - .gt => left = mid + 1, - .lt => right = mid, + .lt => low = mid + 1, // item too small + .gt => high = mid, // item too big } } - return null; } test binarySearch { const S = struct { - fn order_u32(context: void, lhs: u32, rhs: u32) math.Order { - _ = context; - return math.order(lhs, rhs); + fn orderU32(context: u32, item: u32) std.math.Order { + return std.math.order(item, context); } - fn order_i32(context: void, lhs: i32, rhs: i32) math.Order { - _ = context; - return math.order(lhs, rhs); + fn orderI32(context: i32, item: i32) std.math.Order { + return std.math.order(item, context); + } + fn orderLength(context: usize, item: []const u8) std.math.Order { + return std.math.order(item.len, context); } }; - try testing.expectEqual( - @as(?usize, null), - binarySearch(u32, @as(u32, 1), &[_]u32{}, {}, S.order_u32), - ); - try testing.expectEqual( - @as(?usize, 0), - binarySearch(u32, @as(u32, 1), &[_]u32{1}, {}, S.order_u32), - ); - try testing.expectEqual( - @as(?usize, null), - binarySearch(u32, @as(u32, 1), &[_]u32{0}, {}, S.order_u32), - ); - try testing.expectEqual( - @as(?usize, null), - binarySearch(u32, @as(u32, 0), &[_]u32{1}, {}, S.order_u32), - ); - try testing.expectEqual( - @as(?usize, 4), - binarySearch(u32, @as(u32, 5), &[_]u32{ 1, 2, 3, 4, 5 }, {}, S.order_u32), - ); - try testing.expectEqual( - @as(?usize, 0), - binarySearch(u32, @as(u32, 2), &[_]u32{ 2, 4, 8, 16, 32, 64 }, {}, S.order_u32), - ); - try testing.expectEqual( - @as(?usize, 1), - binarySearch(i32, @as(i32, -4), &[_]i32{ -7, -4, 0, 9, 10 }, {}, S.order_i32), - ); - try testing.expectEqual( - @as(?usize, 3), - binarySearch(i32, @as(i32, 98), &[_]i32{ -100, -25, 2, 98, 99, 100 }, {}, S.order_i32), - ); const R = struct { b: i32, e: i32, fn r(b: i32, e: i32) @This() { - return @This(){ .b = b, .e = e }; + return .{ .b = b, .e = e }; } - fn order(context: void, key: i32, mid_item: @This()) math.Order { - _ = context; - - if (key < mid_item.b) { + fn order(context: i32, item: @This()) std.math.Order { + if (item.e < context) { return .lt; - } - - if (key > mid_item.e) { + } else if (item.b > context) { return .gt; + } else { + return .eq; } - - return .eq; } }; - try testing.expectEqual( - @as(?usize, null), - binarySearch(R, @as(i32, -45), &[_]R{ R.r(-100, -50), R.r(-40, -20), R.r(-10, 20), R.r(30, 40) }, {}, R.order), - ); - try testing.expectEqual( - @as(?usize, 2), - binarySearch(R, @as(i32, 10), &[_]R{ R.r(-100, -50), R.r(-40, -20), R.r(-10, 20), R.r(30, 40) }, {}, R.order), - ); - try testing.expectEqual( - @as(?usize, 1), - binarySearch(R, @as(i32, -20), &[_]R{ R.r(-100, -50), R.r(-40, -20), R.r(-10, 20), R.r(30, 40) }, {}, R.order), - ); + + try std.testing.expectEqual(null, binarySearch(u32, &[_]u32{}, @as(u32, 1), S.orderU32)); + try std.testing.expectEqual(0, binarySearch(u32, &[_]u32{1}, @as(u32, 1), S.orderU32)); + try std.testing.expectEqual(null, binarySearch(u32, &[_]u32{0}, @as(u32, 1), S.orderU32)); + try std.testing.expectEqual(null, binarySearch(u32, &[_]u32{1}, @as(u32, 0), S.orderU32)); + try std.testing.expectEqual(4, binarySearch(u32, &[_]u32{ 1, 2, 3, 4, 5 }, @as(u32, 5), S.orderU32)); + try std.testing.expectEqual(0, binarySearch(u32, &[_]u32{ 2, 4, 8, 16, 32, 64 }, @as(u32, 2), S.orderU32)); + try std.testing.expectEqual(1, binarySearch(i32, &[_]i32{ -7, -4, 0, 9, 10 }, @as(i32, -4), S.orderI32)); + try std.testing.expectEqual(3, binarySearch(i32, &[_]i32{ -100, -25, 2, 98, 99, 100 }, @as(i32, 98), S.orderI32)); + try std.testing.expectEqual(null, binarySearch(R, &[_]R{ R.r(-100, -50), R.r(-40, -20), R.r(-10, 20), R.r(30, 40) }, @as(i32, -45), R.order)); + try std.testing.expectEqual(2, binarySearch(R, &[_]R{ R.r(-100, -50), R.r(-40, -20), R.r(-10, 20), R.r(30, 40) }, @as(i32, 10), R.order)); + try std.testing.expectEqual(1, binarySearch(R, &[_]R{ R.r(-100, -50), R.r(-40, -20), R.r(-10, 20), R.r(30, 40) }, @as(i32, -20), R.order)); + try std.testing.expectEqual(2, binarySearch([]const u8, &[_][]const u8{ "", "abc", "1234", "vwxyz" }, @as(usize, 4), S.orderLength)); } -/// Returns the index of the first element in `items` greater than or equal to `key`, -/// or `items.len` if all elements are less than `key`. +/// Returns the index of the first element in `items` returning `.eq` or `.gt` +/// when given to `compareFn`. +/// - Returns `items.len` if all elements return `.lt`. /// -/// `items` must be sorted in ascending order with respect to `compareFn`. +/// `items` must be sorted in ascending order with respect to `compareFn`: +/// ``` +/// [0] [len] +/// ┌───┬───┬─/ /─┬───┬───┬───┬─/ /─┬───┬───┬───┬─/ /─┬───┐ +/// │.lt│.lt│ \ \ │.lt│.eq│.eq│ \ \ │.eq│.gt│.gt│ \ \ │.gt│ +/// └───┴───┴─/ /─┴───┴───┴───┴─/ /─┴───┴───┴───┴─/ /─┴───┘ +/// ├─────────────────┼─────────────────┼─────────────────┤ +/// ↳ zero or more ↳ zero or more ↳ zero or more +/// ├───┤ +/// ↳ returned index +/// ``` /// -/// O(log n) complexity. +/// `O(log n)` time complexity. +/// +/// See also: `binarySearch`, `upperBound`, `partitionPoint`, `equalRange`. pub fn lowerBound( comptime T: type, - key: anytype, items: []const T, context: anytype, - comptime lessThan: fn (context: @TypeOf(context), lhs: @TypeOf(key), rhs: T) bool, + comptime compareFn: fn (@TypeOf(context), T) std.math.Order, ) usize { - var left: usize = 0; - var right: usize = items.len; - - while (left < right) { - const mid = left + (right - left) / 2; - if (lessThan(context, items[mid], key)) { - left = mid + 1; - } else { - right = mid; + const S = struct { + fn predicate(ctx: @TypeOf(context), item: T) bool { + return compareFn(ctx, item) == .lt; } - } - - return left; + }; + return partitionPoint(T, items, context, S.predicate); } test lowerBound { const S = struct { - fn lower_u32(context: void, lhs: u32, rhs: u32) bool { - _ = context; - return lhs < rhs; + fn compareU32(context: u32, item: u32) std.math.Order { + return std.math.order(item, context); } - fn lower_i32(context: void, lhs: i32, rhs: i32) bool { - _ = context; - return lhs < rhs; + fn compareI32(context: i32, item: i32) std.math.Order { + return std.math.order(item, context); } - fn lower_f32(context: void, lhs: f32, rhs: f32) bool { - _ = context; - return lhs < rhs; + fn compareF32(context: f32, item: f32) std.math.Order { + return std.math.order(item, context); + } + }; + const R = struct { + val: i32, + + fn r(val: i32) @This() { + return .{ .val = val }; + } + + fn compareFn(context: i32, item: @This()) std.math.Order { + return std.math.order(item.val, context); } }; - try testing.expectEqual( - @as(usize, 0), - lowerBound(u32, @as(u32, 0), &[_]u32{}, {}, S.lower_u32), - ); - try testing.expectEqual( - @as(usize, 0), - lowerBound(u32, @as(u32, 0), &[_]u32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_u32), - ); - try testing.expectEqual( - @as(usize, 0), - lowerBound(u32, @as(u32, 2), &[_]u32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_u32), - ); - try testing.expectEqual( - @as(usize, 2), - lowerBound(u32, @as(u32, 5), &[_]u32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_u32), - ); - try testing.expectEqual( - @as(usize, 2), - lowerBound(u32, @as(u32, 8), &[_]u32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_u32), - ); - try testing.expectEqual( - @as(usize, 6), - lowerBound(u32, @as(u32, 8), &[_]u32{ 2, 4, 7, 7, 7, 7, 16, 32, 64 }, {}, S.lower_u32), - ); - try testing.expectEqual( - @as(usize, 2), - lowerBound(u32, @as(u32, 8), &[_]u32{ 2, 4, 8, 8, 8, 8, 16, 32, 64 }, {}, S.lower_u32), - ); - try testing.expectEqual( - @as(usize, 5), - lowerBound(u32, @as(u32, 64), &[_]u32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_u32), - ); - try testing.expectEqual( - @as(usize, 6), - lowerBound(u32, @as(u32, 100), &[_]u32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_u32), - ); - try testing.expectEqual( - @as(usize, 2), - lowerBound(i32, @as(i32, 5), &[_]i32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_i32), - ); - try testing.expectEqual( - @as(usize, 1), - lowerBound(f32, @as(f32, -33.4), &[_]f32{ -54.2, -26.7, 0.0, 56.55, 100.1, 322.0 }, {}, S.lower_f32), - ); + try std.testing.expectEqual(0, lowerBound(u32, &[_]u32{}, @as(u32, 0), S.compareU32)); + try std.testing.expectEqual(0, lowerBound(u32, &[_]u32{ 2, 4, 8, 16, 32, 64 }, @as(u32, 0), S.compareU32)); + try std.testing.expectEqual(0, lowerBound(u32, &[_]u32{ 2, 4, 8, 16, 32, 64 }, @as(u32, 2), S.compareU32)); + try std.testing.expectEqual(2, lowerBound(u32, &[_]u32{ 2, 4, 8, 16, 32, 64 }, @as(u32, 5), S.compareU32)); + try std.testing.expectEqual(2, lowerBound(u32, &[_]u32{ 2, 4, 8, 16, 32, 64 }, @as(u32, 8), S.compareU32)); + try std.testing.expectEqual(6, lowerBound(u32, &[_]u32{ 2, 4, 7, 7, 7, 7, 16, 32, 64 }, @as(u32, 8), S.compareU32)); + try std.testing.expectEqual(2, lowerBound(u32, &[_]u32{ 2, 4, 8, 8, 8, 8, 16, 32, 64 }, @as(u32, 8), S.compareU32)); + try std.testing.expectEqual(5, lowerBound(u32, &[_]u32{ 2, 4, 8, 16, 32, 64 }, @as(u32, 64), S.compareU32)); + try std.testing.expectEqual(6, lowerBound(u32, &[_]u32{ 2, 4, 8, 16, 32, 64 }, @as(u32, 100), S.compareU32)); + try std.testing.expectEqual(2, lowerBound(i32, &[_]i32{ 2, 4, 8, 16, 32, 64 }, @as(i32, 5), S.compareI32)); + try std.testing.expectEqual(1, lowerBound(f32, &[_]f32{ -54.2, -26.7, 0.0, 56.55, 100.1, 322.0 }, @as(f32, -33.4), S.compareF32)); + try std.testing.expectEqual(2, lowerBound(R, &[_]R{ R.r(-100), R.r(-40), R.r(-10), R.r(30) }, @as(i32, -20), R.compareFn)); } -/// Returns the index of the first element in `items` greater than `key`, -/// or `items.len` if all elements are less than or equal to `key`. +/// Returns the index of the first element in `items` returning `.gt` +/// when given to `compareFn`. +/// - Returns `items.len` if none of the elements return `.gt`. /// -/// `items` must be sorted in ascending order with respect to `compareFn`. +/// `items` must be sorted in ascending order with respect to `compareFn`: +/// ``` +/// [0] [len] +/// ┌───┬───┬─/ /─┬───┬───┬───┬─/ /─┬───┬───┬───┬─/ /─┬───┐ +/// │.lt│.lt│ \ \ │.lt│.eq│.eq│ \ \ │.eq│.gt│.gt│ \ \ │.gt│ +/// └───┴───┴─/ /─┴───┴───┴───┴─/ /─┴───┴───┴───┴─/ /─┴───┘ +/// ├─────────────────┼─────────────────┼─────────────────┤ +/// ↳ zero or more ↳ zero or more ↳ zero or more +/// ├───┤ +/// ↳ returned index +/// ``` /// -/// O(log n) complexity. +/// `O(log n)` time complexity. +/// +/// See also: `binarySearch`, `lowerBound`, `partitionPoint`, `equalRange`. pub fn upperBound( comptime T: type, - key: anytype, items: []const T, context: anytype, - comptime lessThan: fn (context: @TypeOf(context), lhs: @TypeOf(key), rhs: T) bool, + comptime compareFn: fn (@TypeOf(context), T) std.math.Order, ) usize { - var left: usize = 0; - var right: usize = items.len; - - while (left < right) { - const mid = left + (right - left) / 2; - if (!lessThan(context, key, items[mid])) { - left = mid + 1; - } else { - right = mid; + const S = struct { + fn predicate(ctx: @TypeOf(context), item: T) bool { + return compareFn(ctx, item) != .gt; } - } - - return left; + }; + return partitionPoint(T, items, context, S.predicate); } test upperBound { const S = struct { - fn lower_u32(context: void, lhs: u32, rhs: u32) bool { - _ = context; - return lhs < rhs; + fn compareU32(context: u32, item: u32) std.math.Order { + return std.math.order(item, context); } - fn lower_i32(context: void, lhs: i32, rhs: i32) bool { - _ = context; - return lhs < rhs; + fn compareI32(context: i32, item: i32) std.math.Order { + return std.math.order(item, context); } - fn lower_f32(context: void, lhs: f32, rhs: f32) bool { - _ = context; - return lhs < rhs; + fn compareF32(context: f32, item: f32) std.math.Order { + return std.math.order(item, context); + } + }; + const R = struct { + val: i32, + + fn r(val: i32) @This() { + return .{ .val = val }; + } + + fn compareFn(context: i32, item: @This()) std.math.Order { + return std.math.order(item.val, context); } }; - try testing.expectEqual( - @as(usize, 0), - upperBound(u32, @as(u32, 0), &[_]u32{}, {}, S.lower_u32), - ); - try testing.expectEqual( - @as(usize, 0), - upperBound(u32, @as(u32, 0), &[_]u32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_u32), - ); - try testing.expectEqual( - @as(usize, 1), - upperBound(u32, @as(u32, 2), &[_]u32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_u32), - ); - try testing.expectEqual( - @as(usize, 2), - upperBound(u32, @as(u32, 5), &[_]u32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_u32), - ); - try testing.expectEqual( - @as(usize, 6), - upperBound(u32, @as(u32, 8), &[_]u32{ 2, 4, 7, 7, 7, 7, 16, 32, 64 }, {}, S.lower_u32), - ); - try testing.expectEqual( - @as(usize, 6), - upperBound(u32, @as(u32, 8), &[_]u32{ 2, 4, 8, 8, 8, 8, 16, 32, 64 }, {}, S.lower_u32), - ); - try testing.expectEqual( - @as(usize, 3), - upperBound(u32, @as(u32, 8), &[_]u32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_u32), - ); - try testing.expectEqual( - @as(usize, 6), - upperBound(u32, @as(u32, 64), &[_]u32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_u32), - ); - try testing.expectEqual( - @as(usize, 6), - upperBound(u32, @as(u32, 100), &[_]u32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_u32), - ); - try testing.expectEqual( - @as(usize, 2), - upperBound(i32, @as(i32, 5), &[_]i32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_i32), - ); - try testing.expectEqual( - @as(usize, 1), - upperBound(f32, @as(f32, -33.4), &[_]f32{ -54.2, -26.7, 0.0, 56.55, 100.1, 322.0 }, {}, S.lower_f32), - ); + try std.testing.expectEqual(0, upperBound(u32, &[_]u32{}, @as(u32, 0), S.compareU32)); + try std.testing.expectEqual(0, upperBound(u32, &[_]u32{ 2, 4, 8, 16, 32, 64 }, @as(u32, 0), S.compareU32)); + try std.testing.expectEqual(1, upperBound(u32, &[_]u32{ 2, 4, 8, 16, 32, 64 }, @as(u32, 2), S.compareU32)); + try std.testing.expectEqual(2, upperBound(u32, &[_]u32{ 2, 4, 8, 16, 32, 64 }, @as(u32, 5), S.compareU32)); + try std.testing.expectEqual(6, upperBound(u32, &[_]u32{ 2, 4, 7, 7, 7, 7, 16, 32, 64 }, @as(u32, 8), S.compareU32)); + try std.testing.expectEqual(6, upperBound(u32, &[_]u32{ 2, 4, 8, 8, 8, 8, 16, 32, 64 }, @as(u32, 8), S.compareU32)); + try std.testing.expectEqual(3, upperBound(u32, &[_]u32{ 2, 4, 8, 16, 32, 64 }, @as(u32, 8), S.compareU32)); + try std.testing.expectEqual(6, upperBound(u32, &[_]u32{ 2, 4, 8, 16, 32, 64 }, @as(u32, 64), S.compareU32)); + try std.testing.expectEqual(6, upperBound(u32, &[_]u32{ 2, 4, 8, 16, 32, 64 }, @as(u32, 100), S.compareU32)); + try std.testing.expectEqual(2, upperBound(i32, &[_]i32{ 2, 4, 8, 16, 32, 64 }, @as(i32, 5), S.compareI32)); + try std.testing.expectEqual(1, upperBound(f32, &[_]f32{ -54.2, -26.7, 0.0, 56.55, 100.1, 322.0 }, @as(f32, -33.4), S.compareF32)); + try std.testing.expectEqual(2, upperBound(R, &[_]R{ R.r(-100), R.r(-40), R.r(-10), R.r(30) }, @as(i32, -20), R.compareFn)); } -/// Returns a tuple of the lower and upper indices in `items` between which all elements are equal to `key`. -/// If no element in `items` is equal to `key`, both indices are the -/// index of the first element in `items` greater than `key`. -/// If no element in `items` is greater than `key`, both indices equal `items.len`. +/// Returns the index of the partition point of `items` in relation to the given predicate. +/// - If all elements of `items` satisfy the predicate the returned value is `items.len`. /// -/// `items` must be sorted in ascending order with respect to `compareFn`. +/// `items` must contain a prefix for which all elements satisfy the predicate, +/// and beyond which none of the elements satisfy the predicate: +/// ``` +/// [0] [len] +/// ┌────┬────┬─/ /─┬────┬─────┬─────┬─/ /─┬─────┐ +/// │true│true│ \ \ │true│false│false│ \ \ │false│ +/// └────┴────┴─/ /─┴────┴─────┴─────┴─/ /─┴─────┘ +/// ├────────────────────┼───────────────────────┤ +/// ↳ zero or more ↳ zero or more +/// ├─────┤ +/// ↳ returned index +/// ``` /// -/// O(log n) complexity. +/// `O(log n)` time complexity. /// -/// See also: `lowerBound` and `upperBound`. -pub fn equalRange( +/// See also: `binarySearch`, `lowerBound, `upperBound`, `equalRange`. +pub fn partitionPoint( comptime T: type, - key: anytype, items: []const T, context: anytype, - comptime lessThan: fn (context: @TypeOf(context), lhs: @TypeOf(key), rhs: T) bool, + comptime predicate: fn (@TypeOf(context), T) bool, +) usize { + var low: usize = 0; + var high: usize = items.len; + + while (low < high) { + const mid = low + (high - low) / 2; + if (predicate(context, items[mid])) { + low = mid + 1; + } else { + high = mid; + } + } + return low; +} + +test partitionPoint { + const S = struct { + fn lowerU32(context: u32, item: u32) bool { + return item < context; + } + fn lowerI32(context: i32, item: i32) bool { + return item < context; + } + fn lowerF32(context: f32, item: f32) bool { + return item < context; + } + fn lowerEqU32(context: u32, item: u32) bool { + return item <= context; + } + fn lowerEqI32(context: i32, item: i32) bool { + return item <= context; + } + fn lowerEqF32(context: f32, item: f32) bool { + return item <= context; + } + fn isEven(_: void, item: u8) bool { + return item % 2 == 0; + } + }; + + try std.testing.expectEqual(0, partitionPoint(u32, &[_]u32{}, @as(u32, 0), S.lowerU32)); + try std.testing.expectEqual(0, partitionPoint(u32, &[_]u32{ 2, 4, 8, 16, 32, 64 }, @as(u32, 0), S.lowerU32)); + try std.testing.expectEqual(0, partitionPoint(u32, &[_]u32{ 2, 4, 8, 16, 32, 64 }, @as(u32, 2), S.lowerU32)); + try std.testing.expectEqual(2, partitionPoint(u32, &[_]u32{ 2, 4, 8, 16, 32, 64 }, @as(u32, 5), S.lowerU32)); + try std.testing.expectEqual(2, partitionPoint(u32, &[_]u32{ 2, 4, 8, 16, 32, 64 }, @as(u32, 8), S.lowerU32)); + try std.testing.expectEqual(6, partitionPoint(u32, &[_]u32{ 2, 4, 7, 7, 7, 7, 16, 32, 64 }, @as(u32, 8), S.lowerU32)); + try std.testing.expectEqual(2, partitionPoint(u32, &[_]u32{ 2, 4, 8, 8, 8, 8, 16, 32, 64 }, @as(u32, 8), S.lowerU32)); + try std.testing.expectEqual(5, partitionPoint(u32, &[_]u32{ 2, 4, 8, 16, 32, 64 }, @as(u32, 64), S.lowerU32)); + try std.testing.expectEqual(6, partitionPoint(u32, &[_]u32{ 2, 4, 8, 16, 32, 64 }, @as(u32, 100), S.lowerU32)); + try std.testing.expectEqual(2, partitionPoint(i32, &[_]i32{ 2, 4, 8, 16, 32, 64 }, @as(i32, 5), S.lowerI32)); + try std.testing.expectEqual(1, partitionPoint(f32, &[_]f32{ -54.2, -26.7, 0.0, 56.55, 100.1, 322.0 }, @as(f32, -33.4), S.lowerF32)); + try std.testing.expectEqual(0, partitionPoint(u32, &[_]u32{}, @as(u32, 0), S.lowerEqU32)); + try std.testing.expectEqual(0, partitionPoint(u32, &[_]u32{ 2, 4, 8, 16, 32, 64 }, @as(u32, 0), S.lowerEqU32)); + try std.testing.expectEqual(1, partitionPoint(u32, &[_]u32{ 2, 4, 8, 16, 32, 64 }, @as(u32, 2), S.lowerEqU32)); + try std.testing.expectEqual(2, partitionPoint(u32, &[_]u32{ 2, 4, 8, 16, 32, 64 }, @as(u32, 5), S.lowerEqU32)); + try std.testing.expectEqual(6, partitionPoint(u32, &[_]u32{ 2, 4, 7, 7, 7, 7, 16, 32, 64 }, @as(u32, 8), S.lowerEqU32)); + try std.testing.expectEqual(6, partitionPoint(u32, &[_]u32{ 2, 4, 8, 8, 8, 8, 16, 32, 64 }, @as(u32, 8), S.lowerEqU32)); + try std.testing.expectEqual(3, partitionPoint(u32, &[_]u32{ 2, 4, 8, 16, 32, 64 }, @as(u32, 8), S.lowerEqU32)); + try std.testing.expectEqual(6, partitionPoint(u32, &[_]u32{ 2, 4, 8, 16, 32, 64 }, @as(u32, 64), S.lowerEqU32)); + try std.testing.expectEqual(6, partitionPoint(u32, &[_]u32{ 2, 4, 8, 16, 32, 64 }, @as(u32, 100), S.lowerEqU32)); + try std.testing.expectEqual(2, partitionPoint(i32, &[_]i32{ 2, 4, 8, 16, 32, 64 }, @as(i32, 5), S.lowerEqI32)); + try std.testing.expectEqual(1, partitionPoint(f32, &[_]f32{ -54.2, -26.7, 0.0, 56.55, 100.1, 322.0 }, @as(f32, -33.4), S.lowerEqF32)); + try std.testing.expectEqual(4, partitionPoint(u8, &[_]u8{ 0, 50, 14, 2, 5, 71 }, {}, S.isEven)); +} + +/// Returns a tuple of the lower and upper indices in `items` between which all +/// elements return `.eq` when given to `compareFn`. +/// - If no element in `items` returns `.eq`, both indices are the +/// index of the first element in `items` returning `.gt`. +/// - If no element in `items` returns `.gt`, both indices equal `items.len`. +/// +/// `items` must be sorted in ascending order with respect to `compareFn`: +/// ``` +/// [0] [len] +/// ┌───┬───┬─/ /─┬───┬───┬───┬─/ /─┬───┬───┬───┬─/ /─┬───┐ +/// │.lt│.lt│ \ \ │.lt│.eq│.eq│ \ \ │.eq│.gt│.gt│ \ \ │.gt│ +/// └───┴───┴─/ /─┴───┴───┴───┴─/ /─┴───┴───┴───┴─/ /─┴───┘ +/// ├─────────────────┼─────────────────┼─────────────────┤ +/// ↳ zero or more ↳ zero or more ↳ zero or more +/// ├─────────────────┤ +/// ↳ returned range +/// ``` +/// +/// `O(log n)` time complexity. +/// +/// See also: `binarySearch`, `lowerBound, `upperBound`, `partitionPoint`. +pub fn equalRange( + comptime T: type, + items: []const T, + context: anytype, + comptime compareFn: fn (@TypeOf(context), T) std.math.Order, ) struct { usize, usize } { return .{ - lowerBound(T, key, items, context, lessThan), - upperBound(T, key, items, context, lessThan), + lowerBound(T, items, context, compareFn), + upperBound(T, items, context, compareFn), }; } test equalRange { const S = struct { - fn lower_u32(context: void, lhs: u32, rhs: u32) bool { - _ = context; - return lhs < rhs; + fn orderU32(context: u32, item: u32) std.math.Order { + return std.math.order(item, context); } - fn lower_i32(context: void, lhs: i32, rhs: i32) bool { - _ = context; - return lhs < rhs; + fn orderI32(context: i32, item: i32) std.math.Order { + return std.math.order(item, context); } - fn lower_f32(context: void, lhs: f32, rhs: f32) bool { - _ = context; - return lhs < rhs; + fn orderF32(context: f32, item: f32) std.math.Order { + return std.math.order(item, context); + } + fn orderLength(context: usize, item: []const u8) std.math.Order { + return std.math.order(item.len, context); } }; - try testing.expectEqual( - @as(struct { usize, usize }, .{ 0, 0 }), - equalRange(i32, @as(i32, 0), &[_]i32{}, {}, S.lower_i32), - ); - try testing.expectEqual( - @as(struct { usize, usize }, .{ 0, 0 }), - equalRange(i32, @as(i32, 0), &[_]i32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_i32), - ); - try testing.expectEqual( - @as(struct { usize, usize }, .{ 0, 1 }), - equalRange(i32, @as(i32, 2), &[_]i32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_i32), - ); - try testing.expectEqual( - @as(struct { usize, usize }, .{ 2, 2 }), - equalRange(i32, @as(i32, 5), &[_]i32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_i32), - ); - try testing.expectEqual( - @as(struct { usize, usize }, .{ 2, 3 }), - equalRange(i32, @as(i32, 8), &[_]i32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_i32), - ); - try testing.expectEqual( - @as(struct { usize, usize }, .{ 5, 6 }), - equalRange(i32, @as(i32, 64), &[_]i32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_i32), - ); - try testing.expectEqual( - @as(struct { usize, usize }, .{ 6, 6 }), - equalRange(i32, @as(i32, 100), &[_]i32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_i32), - ); - try testing.expectEqual( - @as(struct { usize, usize }, .{ 2, 6 }), - equalRange(i32, @as(i32, 8), &[_]i32{ 2, 4, 8, 8, 8, 8, 15, 22 }, {}, S.lower_i32), - ); - try testing.expectEqual( - @as(struct { usize, usize }, .{ 2, 2 }), - equalRange(u32, @as(u32, 5), &[_]u32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_u32), - ); - try testing.expectEqual( - @as(struct { usize, usize }, .{ 1, 1 }), - equalRange(f32, @as(f32, -33.4), &[_]f32{ -54.2, -26.7, 0.0, 56.55, 100.1, 322.0 }, {}, S.lower_f32), - ); + try std.testing.expectEqual(.{ 0, 0 }, equalRange(i32, &[_]i32{}, @as(i32, 0), S.orderI32)); + try std.testing.expectEqual(.{ 0, 0 }, equalRange(i32, &[_]i32{ 2, 4, 8, 16, 32, 64 }, @as(i32, 0), S.orderI32)); + try std.testing.expectEqual(.{ 0, 1 }, equalRange(i32, &[_]i32{ 2, 4, 8, 16, 32, 64 }, @as(i32, 2), S.orderI32)); + try std.testing.expectEqual(.{ 2, 2 }, equalRange(i32, &[_]i32{ 2, 4, 8, 16, 32, 64 }, @as(i32, 5), S.orderI32)); + try std.testing.expectEqual(.{ 2, 3 }, equalRange(i32, &[_]i32{ 2, 4, 8, 16, 32, 64 }, @as(i32, 8), S.orderI32)); + try std.testing.expectEqual(.{ 5, 6 }, equalRange(i32, &[_]i32{ 2, 4, 8, 16, 32, 64 }, @as(i32, 64), S.orderI32)); + try std.testing.expectEqual(.{ 6, 6 }, equalRange(i32, &[_]i32{ 2, 4, 8, 16, 32, 64 }, @as(i32, 100), S.orderI32)); + try std.testing.expectEqual(.{ 2, 6 }, equalRange(i32, &[_]i32{ 2, 4, 8, 8, 8, 8, 15, 22 }, @as(i32, 8), S.orderI32)); + try std.testing.expectEqual(.{ 2, 2 }, equalRange(u32, &[_]u32{ 2, 4, 8, 16, 32, 64 }, @as(u32, 5), S.orderU32)); + try std.testing.expectEqual(.{ 1, 1 }, equalRange(f32, &[_]f32{ -54.2, -26.7, 0.0, 56.55, 100.1, 322.0 }, @as(f32, -33.4), S.orderF32)); + try std.testing.expectEqual(.{ 3, 5 }, equalRange( + []const u8, + &[_][]const u8{ "Mars", "Venus", "Earth", "Saturn", "Uranus", "Mercury", "Jupiter", "Neptune" }, + @as(usize, 6), + S.orderLength, + )); } pub fn argMin( From 4ba9a6f44c07ac4501766704f39c2d749b01d8da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Sun, 4 Aug 2024 08:52:26 +0200 Subject: [PATCH 138/266] process_headers: Add sparcv9-linux-gnu for glibc. This is 32-bit SPARC targeting the v8 ABI but v9 CPU. --- tools/process_headers.zig | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/tools/process_headers.zig b/tools/process_headers.zig index a15481afa2..4be476a456 100644 --- a/tools/process_headers.zig +++ b/tools/process_headers.zig @@ -174,12 +174,11 @@ const glibc_targets = [_]LibCTarget{ .arch = MultiArch{ .specific = Arch.s390x }, .abi = MultiAbi{ .specific = Abi.gnu }, }, - // It's unclear which zig target this glibc sparcv9 target maps to. - //LibCTarget{ - // .name = "sparcv9-linux-gnu", - // .arch = MultiArch{ .specific = Arch.sparc }, - // .abi = MultiAbi{ .specific = Abi.gnu }, - //}, + LibCTarget{ + .name = "sparcv9-linux-gnu", + .arch = MultiArch{ .specific = Arch.sparc }, + .abi = MultiAbi{ .specific = Abi.gnu }, + }, LibCTarget{ .name = "sparc64-linux-gnu", .arch = MultiArch{ .specific = Arch.sparc64 }, From 27775f1a9eecc6142834affb524eadd33b9e56b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Sun, 4 Aug 2024 11:04:23 +0200 Subject: [PATCH 139/266] std.Target: Use v9 as the baseline CPU for sparc32. It is impossible to even build projects like glibc when targeting a generic SPARC v8 CPU; LEON3 is effectively considered the baseline for `sparc-linux-gnu` now, particularly due to it supporting a CASA instruction similar to the one in SPARC v9. However, it's slightly incompatible with SPARC v9 due to having a different ASI tag, so resulting binaries would not be portable to regular SPARC CPUs. So, as the least bad option, make v9 the baseline for sparc32. --- lib/std/Target.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/Target.zig b/lib/std/Target.zig index 8d9f596072..f9a6da8ce0 100644 --- a/lib/std/Target.zig +++ b/lib/std/Target.zig @@ -1502,7 +1502,7 @@ pub const Cpu = struct { .x86 => &x86.cpu.pentium4, .nvptx, .nvptx64 => &nvptx.cpu.sm_20, .s390x => &s390x.cpu.arch8, - .sparc => &sparc.cpu.v8, + .sparc => &sparc.cpu.v9, // glibc does not work with 'plain' v8. .loongarch64 => &loongarch.cpu.loongarch64, else => generic(arch), From 388248ad337e0bbca05f2f40bf42c46bcdc61e47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Mon, 5 Aug 2024 12:40:18 +0200 Subject: [PATCH 140/266] std.zig.target: Set minimum glibc version for csky to 2.29. https://lists.gnu.org/archive/html/info-gnu/2019-01/msg00018.html --- lib/std/zig/target.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/std/zig/target.zig b/lib/std/zig/target.zig index d86d47ba59..be2c944e76 100644 --- a/lib/std/zig/target.zig +++ b/lib/std/zig/target.zig @@ -28,8 +28,8 @@ pub const available_libcs = [_]ArchOsAbi{ .{ .arch = .thumb, .os = .linux, .abi = .musleabi }, .{ .arch = .thumb, .os = .linux, .abi = .musleabihf }, .{ .arch = .arm, .os = .windows, .abi = .gnu }, - .{ .arch = .csky, .os = .linux, .abi = .gnueabi }, - .{ .arch = .csky, .os = .linux, .abi = .gnueabihf }, + .{ .arch = .csky, .os = .linux, .abi = .gnueabi, .glibc_min = .{ .major = 2, .minor = 29, .patch = 0 } }, + .{ .arch = .csky, .os = .linux, .abi = .gnueabihf, .glibc_min = .{ .major = 2, .minor = 29, .patch = 0 } }, .{ .arch = .x86, .os = .linux, .abi = .gnu }, .{ .arch = .x86, .os = .linux, .abi = .musl }, .{ .arch = .x86, .os = .windows, .abi = .gnu }, From 1b88c6a8a2cddef53d80ef5f73d6435828874d51 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 5 Aug 2024 09:47:26 -0700 Subject: [PATCH 141/266] glibc: update abilists file updated for glibc 2.40, and adds arc, csky, and m68k --- lib/libc/glibc/abilists | Bin 217016 -> 274738 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/lib/libc/glibc/abilists b/lib/libc/glibc/abilists index e7e2e6f3afb02036e6995f3eba3244d76f1d65f3..4e617973270d8c6fd227a4d35865cd7aa9a4bc07 100644 GIT binary patch literal 274738 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 From d6945eeba97fa20256cbff86318950fc376fce91 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 5 Aug 2024 09:53:16 -0700 Subject: [PATCH 142/266] Revert "glibc: Add a temporary hack in abilists loading due to sparcel removal." This reverts commit 62a01851d9c433ea3f2e98cc986e75d32aece443. No longer needed with the abilists update. --- src/glibc.zig | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/glibc.zig b/src/glibc.zig index 85f1828ad4..5214d0a977 100644 --- a/src/glibc.zig +++ b/src/glibc.zig @@ -122,17 +122,6 @@ pub fn loadMetaData(gpa: Allocator, contents: []const u8) LoadMetaDataError!*ABI return error.ZigInstallationCorrupt; }; const arch_tag = std.meta.stringToEnum(std.Target.Cpu.Arch, arch_name) orelse { - // TODO: Remove this on the next glibc abilists update. - if (mem.eql(u8, arch_name, "sparcel")) { - targets[i] = .{ - .arch = .sparc, - .os = .linux, - .abi = .gnu, - }; - - continue; - } - log.err("abilists: unrecognized arch: '{s}'", .{arch_name}); return error.ZigInstallationCorrupt; }; From 724804a4e026eea8cd42804b44bb0449d4ab3f3c Mon Sep 17 00:00:00 2001 From: ippsav <69125922+ippsav@users.noreply.github.com> Date: Mon, 5 Aug 2024 18:43:47 +0100 Subject: [PATCH 143/266] Rewrite `generate_linux_syscalls.zig` (#20895) refactors the syscall generation tool aiming to reduce code duplication for both the table based arches and the ones generated using a preprocessor. --- tools/generate_linux_syscalls.zig | 1297 ++++++++++++++--------------- 1 file changed, 617 insertions(+), 680 deletions(-) diff --git a/tools/generate_linux_syscalls.zig b/tools/generate_linux_syscalls.zig index b95fe9c524..07f9869eaf 100644 --- a/tools/generate_linux_syscalls.zig +++ b/tools/generate_linux_syscalls.zig @@ -48,6 +48,600 @@ fn isReservedNameOld(name: []const u8) bool { std.mem.startsWith(u8, name, "unused"); } +const default_args: []const []const u8 = &.{ + "-E", + // -dM is cleaner, but -dD preserves iteration order. + "-dD", + // No need for line-markers. + "-P", + "-nostdinc", + // Using -I=[dir] includes the zig linux headers, which we don't want. + "-Itools/include", + "-Itools/include/uapi", + // Output the syscall in a format we can easily recognize. + "-D __SYSCALL(nr, nm)=zigsyscall nm nr", +}; + +const ProcessPreprocessedFileFn = *const fn (bytes: []const u8, writer: anytype) anyerror!void; +const ProcessTableBasedArchFileFn = *const fn ( + bytes: []const u8, + filters: Filters, + writer: anytype, + optional_writer: anytype, +) anyerror!void; + +const FlowControl = enum { + @"break", + @"continue", + none, +}; + +const AbiCheckParams = struct { abi: []const u8, flow: FlowControl }; + +const Filters = struct { + abiCheckParams: ?AbiCheckParams, + fixedName: ?*const fn (name: []const u8) []const u8, + isReservedNameOld: ?*const fn (name: []const u8) bool, +}; + +fn abiCheck(abi: []const u8, params: *const AbiCheckParams) FlowControl { + if (mem.eql(u8, abi, params.abi)) return params.flow; + return .none; +} + +fn fixedName(name: []const u8) []const u8 { + return if (stdlib_renames.get(name)) |fixed| fixed else name; +} + +const ArchInfo = union(enum) { + table: struct { + name: []const u8, + enum_name: []const u8, + file_path: []const u8, + header: ?[]const u8, + extra_values: ?[]const u8, + process_file: ProcessTableBasedArchFileFn, + filters: Filters, + additional_enum: ?[]const u8, + }, + preprocessor: struct { + name: []const u8, + enum_name: []const u8, + file_path: []const u8, + child_options: struct { + comptime additional_args: ?[]const []const u8 = null, + target: []const u8, + + pub inline fn getArgs(self: *const @This(), zig_exe: []const u8, file_path: []const u8) []const []const u8 { + const additional_args: []const []const u8 = self.additional_args orelse &.{}; + return .{ zig_exe, "cc" } ++ additional_args ++ .{ "-target", self.target } ++ default_args ++ .{file_path}; + } + }, + header: ?[]const u8, + extra_values: ?[]const u8, + process_file: ProcessPreprocessedFileFn, + additional_enum: ?[]const u8, + }, +}; + +const arch_infos = [_]ArchInfo{ + .{ + // These architectures have their syscall definitions generated from a TSV + // file, processed via scripts/syscallhdr.sh. + .table = .{ + .name = "x86", + .enum_name = "X86", + .file_path = "arch/x86/entry/syscalls/syscall_32.tbl", + .process_file = &processTableBasedArch, + .filters = .{ + .abiCheckParams = null, + .fixedName = &fixedName, + .isReservedNameOld = null, + }, + .header = null, + .extra_values = null, + .additional_enum = null, + }, + }, + .{ + .table = .{ + .name = "x64", + .enum_name = "X64", + .file_path = "arch/x86/entry/syscalls/syscall_64.tbl", + .process_file = &processTableBasedArch, + .filters = .{ + // The x32 abi syscalls are always at the end. + .abiCheckParams = .{ .abi = "x32", .flow = .@"break" }, + .fixedName = &fixedName, + .isReservedNameOld = null, + }, + .header = null, + .extra_values = null, + .additional_enum = null, + }, + }, + .{ + .table = .{ + .name = "arm", + .enum_name = "Arm", + .file_path = "arch/arm/tools/syscall.tbl", + .process_file = &processTableBasedArch, + .filters = .{ + .abiCheckParams = .{ .abi = "oabi", .flow = .@"continue" }, + .fixedName = &fixedName, + .isReservedNameOld = null, + }, + .header = " const arm_base = 0x0f0000;\n\n", + // TODO: maybe extract these from arch/arm/include/uapi/asm/unistd.h + .extra_values = + \\ + \\ breakpoint = arm_base + 1, + \\ cacheflush = arm_base + 2, + \\ usr26 = arm_base + 3, + \\ usr32 = arm_base + 4, + \\ set_tls = arm_base + 5, + \\ get_tls = arm_base + 6, + \\ + , + .additional_enum = null, + }, + }, + .{ + .table = .{ + .name = "sparc", + .enum_name = "Sparc", + .file_path = "arch/sparc/kernel/syscalls/syscall.tbl", + .process_file = &processTableBasedArch, + .filters = .{ + .abiCheckParams = .{ .abi = "64", .flow = .@"continue" }, + .fixedName = &fixedName, + .isReservedNameOld = null, + }, + .header = null, + .extra_values = null, + .additional_enum = null, + }, + }, + .{ + .table = .{ + .name = "sparc64", + .enum_name = "Sparc64", + .file_path = "arch/sparc/kernel/syscalls/syscall.tbl", + .process_file = &processTableBasedArch, + .filters = .{ + .abiCheckParams = .{ .abi = "32", .flow = .@"continue" }, + .fixedName = &fixedName, + .isReservedNameOld = null, + }, + .header = null, + .extra_values = null, + .additional_enum = null, + }, + }, + .{ + .table = .{ + .name = "m68k", + .enum_name = "M68k", + .file_path = "arch/m68k/kernel/syscalls/syscall.tbl", + .process_file = &processTableBasedArch, + .filters = .{ + // abi is always common + .abiCheckParams = null, + .fixedName = &fixedName, + .isReservedNameOld = null, + }, + .header = null, + .extra_values = null, + .additional_enum = null, + }, + }, + .{ + .table = .{ + .name = "mips_o32", + .enum_name = "MipsO32", + .file_path = "arch/mips/kernel/syscalls/syscall_o32.tbl", + .process_file = &processMipsBasedArch, + .filters = .{ + // abi is always o32 + .abiCheckParams = null, + .fixedName = &fixedName, + .isReservedNameOld = &isReservedNameOld, + }, + .header = " const linux_base = 4000;\n\n", + .extra_values = null, + .additional_enum = null, + }, + }, + .{ + .table = .{ + .name = "mips_n64", + .enum_name = "MipsN64", + .file_path = "arch/mips/kernel/syscalls/syscall_n64.tbl", + .process_file = &processMipsBasedArch, + .filters = .{ + // abi is always n64 + .abiCheckParams = null, + .fixedName = &fixedName, + .isReservedNameOld = &isReservedNameOld, + }, + .header = " const linux_base = 5000;\n\n", + .extra_values = null, + .additional_enum = null, + }, + }, + .{ + .table = .{ + .name = "mips_n32", + .enum_name = "MipsN32", + .file_path = "arch/mips/kernel/syscalls/syscall_n32.tbl", + .process_file = &processMipsBasedArch, + .filters = .{ + // abi is always n32 + .abiCheckParams = null, + .fixedName = &fixedName, + .isReservedNameOld = &isReservedNameOld, + }, + .header = " const linux_base = 6000;\n\n", + .extra_values = null, + .additional_enum = null, + }, + }, + .{ + .table = .{ + .name = "powerpc", + .enum_name = "PowerPC", + .file_path = "arch/powerpc/kernel/syscalls/syscall.tbl", + .process_file = &processPowerPcBasedArch, + .filters = .{ + .abiCheckParams = null, + .fixedName = null, + .isReservedNameOld = null, + }, + .header = null, + .extra_values = null, + .additional_enum = "PowerPC64", + }, + }, + .{ + .table = .{ + .name = "s390x", + .enum_name = "S390x", + .file_path = "arch/s390/kernel/syscalls/syscall.tbl", + .process_file = &processTableBasedArch, + .filters = .{ + // 32-bit s390 support in linux is deprecated + .abiCheckParams = .{ .abi = "32", .flow = .@"continue" }, + .fixedName = &fixedName, + .isReservedNameOld = null, + }, + .header = null, + .extra_values = null, + .additional_enum = null, + }, + }, + .{ + .table = .{ + .name = "xtensa", + .enum_name = "Xtensa", + .file_path = "arch/xtensa/kernel/syscalls/syscall.tbl", + .process_file = &processTableBasedArch, + .filters = .{ + // abi is always common + .abiCheckParams = null, + .fixedName = fixedName, + .isReservedNameOld = &isReservedNameOld, + }, + .header = null, + .extra_values = null, + .additional_enum = null, + }, + }, + .{ + .preprocessor = .{ + .name = "arm64", + .enum_name = "Arm64", + .file_path = "arch/arm64/include/uapi/asm/unistd.h", + .child_options = .{ + .additional_args = null, + .target = "aarch64-freestanding-none", + }, + .process_file = &processPreprocessedFile, + .header = null, + .extra_values = null, + .additional_enum = null, + }, + }, + .{ + .preprocessor = .{ + .name = "riscv32", + .enum_name = "RiscV32", + .file_path = "arch/riscv/include/uapi/asm/unistd.h", + .child_options = .{ + .additional_args = null, + .target = "riscv32-freestanding-none", + }, + .process_file = &processPreprocessedFile, + .header = null, + .extra_values = null, + .additional_enum = null, + }, + }, + .{ + .preprocessor = .{ + .name = "riscv64", + .enum_name = "RiscV64", + .file_path = "arch/riscv/include/uapi/asm/unistd.h", + .child_options = .{ + .additional_args = null, + .target = "riscv64-freestanding-none", + }, + .process_file = &processPreprocessedFile, + .header = null, + .extra_values = null, + .additional_enum = null, + }, + }, + .{ + .preprocessor = .{ + .name = "loongarch", + .enum_name = "LoongArch64", + .file_path = "arch/loongarch/include/uapi/asm/unistd.h", + .child_options = .{ + .additional_args = null, + .target = "loongarch64-freestanding-none", + }, + .process_file = &processPreprocessedFile, + .header = null, + .extra_values = null, + .additional_enum = null, + }, + }, + .{ + .preprocessor = .{ + .name = "arc", + .enum_name = "Arc", + .file_path = "arch/arc/include/uapi/asm/unistd.h", + .child_options = .{ + .additional_args = null, + .target = "arc-freestanding-none", + }, + .process_file = &processPreprocessedFile, + .header = null, + .extra_values = null, + .additional_enum = null, + }, + }, + .{ + .preprocessor = .{ + .name = "csky", + .enum_name = "CSky", + .file_path = "arch/csky/include/uapi/asm/unistd.h", + .child_options = .{ + .additional_args = null, + .target = "csky-freestanding-none", + }, + .process_file = &processPreprocessedFile, + .header = null, + .extra_values = null, + .additional_enum = null, + }, + }, + .{ + .preprocessor = .{ + .name = "hexagon", + .enum_name = "Hexagon", + .file_path = "arch/hexagon/include/uapi/asm/unistd.h", + .child_options = .{ + .additional_args = null, + .target = "hexagon-freestanding-none", + }, + .process_file = &processPreprocessedFile, + .header = null, + .extra_values = null, + .additional_enum = null, + }, + }, +}; + +fn processPreprocessedFile( + bytes: []const u8, + writer: anytype, +) !void { + var lines = mem.tokenizeScalar(u8, bytes, '\n'); + while (lines.next()) |line| { + var fields = mem.tokenizeAny(u8, line, " "); + const prefix = fields.next() orelse return error.Incomplete; + + if (!mem.eql(u8, prefix, "zigsyscall")) continue; + + const sys_name = fields.next() orelse return error.Incomplete; + const value = fields.rest(); + const name = (getOverridenNameNew(value) orelse sys_name)["sys_".len..]; + const fixed_name = if (stdlib_renames_new.get(name)) |f| f else if (stdlib_renames.get(name)) |f| f else name; + + try writer.print(" {p} = {s},\n", .{ zig.fmtId(fixed_name), value }); + } +} + +fn processTableBasedArch( + bytes: []const u8, + filters: Filters, + writer: anytype, + optional_writer: anytype, +) !void { + _ = optional_writer; + + var lines = mem.tokenizeScalar(u8, bytes, '\n'); + while (lines.next()) |line| { + if (line[0] == '#') continue; + + var fields = mem.tokenizeAny(u8, line, " \t"); + const number = fields.next() orelse return error.Incomplete; + + const abi = fields.next() orelse return error.Incomplete; + if (filters.abiCheckParams) |*params| { + switch (abiCheck(abi, params)) { + .none => {}, + .@"break" => break, + .@"continue" => continue, + } + } + const name = fields.next() orelse return error.Incomplete; + if (filters.isReservedNameOld) |isReservedNameOldFn| { + if (isReservedNameOldFn(name)) continue; + } + const fixed_name = if (filters.fixedName) |fixedNameFn| fixedNameFn(name) else name; + + try writer.print(" {p} = {s},\n", .{ zig.fmtId(fixed_name), number }); + } +} + +fn processMipsBasedArch( + bytes: []const u8, + filters: Filters, + writer: anytype, + optional_writer: anytype, +) !void { + _ = optional_writer; + + var lines = mem.tokenizeScalar(u8, bytes, '\n'); + while (lines.next()) |line| { + if (line[0] == '#') continue; + + var fields = mem.tokenizeAny(u8, line, " \t"); + const number = fields.next() orelse return error.Incomplete; + + const abi = fields.next() orelse return error.Incomplete; + if (filters.abiCheckParams) |*params| { + switch (abiCheck(abi, params)) { + .none => {}, + .@"break" => break, + .@"continue" => continue, + } + } + const name = fields.next() orelse return error.Incomplete; + if (filters.isReservedNameOld) |isReservedNameOldFn| { + if (isReservedNameOldFn(name)) continue; + } + const fixed_name = if (filters.fixedName) |fixedNameFn| fixedNameFn(name) else name; + + try writer.print(" {p} = linux_base + {s},\n", .{ zig.fmtId(fixed_name), number }); + } +} + +fn processPowerPcBasedArch( + bytes: []const u8, + filters: Filters, + writer: anytype, + optional_writer: anytype, +) !void { + _ = filters; + var lines = mem.tokenizeScalar(u8, bytes, '\n'); + + while (lines.next()) |line| { + if (line[0] == '#') continue; + + var fields = mem.tokenizeAny(u8, line, " \t"); + const number = fields.next() orelse return error.Incomplete; + const abi = fields.next() orelse return error.Incomplete; + const name = fields.next() orelse return error.Incomplete; + const fixed_name = if (stdlib_renames.get(name)) |fixed| fixed else name; + + if (mem.eql(u8, abi, "spu")) { + continue; + } else if (mem.eql(u8, abi, "32")) { + try writer.print(" {p} = {s},\n", .{ zig.fmtId(fixed_name), number }); + } else if (mem.eql(u8, abi, "64")) { + try optional_writer.?.print(" {p} = {s},\n", .{ zig.fmtId(fixed_name), number }); + } else { // common/nospu + try writer.print(" {p} = {s},\n", .{ zig.fmtId(fixed_name), number }); + try optional_writer.?.print(" {p} = {s},\n", .{ zig.fmtId(fixed_name), number }); + } + } +} + +fn generateSyscallsFromTable( + allocator: std.mem.Allocator, + buf: []u8, + linux_dir: std.fs.Dir, + writer: anytype, + _arch_info: *const ArchInfo, +) !void { + std.debug.assert(_arch_info.* == .table); + + const arch_info = _arch_info.table; + + const table = try linux_dir.readFile(arch_info.file_path, buf); + + var optional_array_list: ?std.ArrayList(u8) = if (arch_info.additional_enum) |_| std.ArrayList(u8).init(allocator) else null; + const optional_writer = if (optional_array_list) |_| optional_array_list.?.writer() else null; + + try writer.print("pub const {s} = enum(usize) {{\n", .{arch_info.enum_name}); + + if (arch_info.header) |header| { + try writer.writeAll(header); + } + + try arch_info.process_file(table, arch_info.filters, writer, optional_writer); + + if (arch_info.extra_values) |extra_values| { + try writer.writeAll(extra_values); + } + + try writer.writeAll("};\n\n"); + + if (arch_info.additional_enum) |additional_enum| { + try writer.print("pub const {s} = enum(usize) {{\n", .{additional_enum}); + try writer.writeAll(optional_array_list.?.items); + try writer.writeAll("};\n\n"); + } +} + +fn generateSyscallsFromPreprocessor( + allocator: std.mem.Allocator, + linux_dir: std.fs.Dir, + linux_path: []const u8, + zig_exe: []const u8, + writer: anytype, + _arch_info: *const ArchInfo, +) !void { + std.debug.assert(_arch_info.* == .preprocessor); + + const arch_info = _arch_info.preprocessor; + + const child_result = try std.process.Child.run(.{ + .allocator = allocator, + .argv = arch_info.child_options.getArgs(zig_exe, arch_info.file_path), + .cwd = linux_path, + .cwd_dir = linux_dir, + }); + if (child_result.stderr.len > 0) std.debug.print("{s}\n", .{child_result.stderr}); + + const defines = switch (child_result.term) { + .Exited => |code| if (code == 0) child_result.stdout else { + std.debug.print("zig cc exited with code {d}\n", .{code}); + std.process.exit(1); + }, + else => { + std.debug.print("zig cc crashed\n", .{}); + std.process.exit(1); + }, + }; + + try writer.print("pub const {s} = enum(usize) {{\n", .{arch_info.enum_name}); + if (arch_info.header) |header| { + try writer.writeAll(header); + } + + try arch_info.process_file(defines, writer); + + if (arch_info.extra_values) |extra_values| { + try writer.writeAll(extra_values); + } + + try writer.writeAll("};\n\n"); +} + pub fn main() !void { var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer arena.deinit(); @@ -62,10 +656,8 @@ pub fn main() !void { var buf_out = std.io.bufferedWriter(std.io.getStdOut().writer()); const writer = buf_out.writer(); - // As of 5.17.1, the largest table is 23467 bytes. - // 32k should be enough for now. - const buf = try allocator.alloc(u8, 1 << 15); - const linux_dir = try std.fs.openDirAbsolute(linux_path, .{}); + var linux_dir = try std.fs.cwd().openDir(linux_path, .{}); + defer linux_dir.close(); try writer.writeAll( \\// This file is automatically generated. @@ -74,684 +666,29 @@ pub fn main() !void { \\ ); - // These architectures have their syscall definitions generated from a TSV - // file, processed via scripts/syscallhdr.sh. - { - try writer.writeAll("pub const X86 = enum(usize) {\n"); + // As of 5.17.1, the largest table is 23467 bytes. + // 32k should be enough for now. + const buf = try allocator.alloc(u8, 1 << 15); + defer allocator.free(buf); - const table = try linux_dir.readFile("arch/x86/entry/syscalls/syscall_32.tbl", buf); - var lines = mem.tokenizeScalar(u8, table, '\n'); - while (lines.next()) |line| { - if (line[0] == '#') continue; - - var fields = mem.tokenizeAny(u8, line, " \t"); - const number = fields.next() orelse return error.Incomplete; - // abi is always i386 - _ = fields.next() orelse return error.Incomplete; - const name = fields.next() orelse return error.Incomplete; - const fixed_name = if (stdlib_renames.get(name)) |fixed| fixed else name; - - try writer.print(" {p} = {s},\n", .{ zig.fmtId(fixed_name), number }); + inline for (arch_infos) |arch_info| { + switch (arch_info) { + .table => try generateSyscallsFromTable( + allocator, + buf, + linux_dir, + writer, + &arch_info, + ), + .preprocessor => try generateSyscallsFromPreprocessor( + allocator, + linux_dir, + linux_path, + zig_exe, + writer, + &arch_info, + ), } - - try writer.writeAll("};\n\n"); - } - { - try writer.writeAll("pub const X64 = enum(usize) {\n"); - - const table = try linux_dir.readFile("arch/x86/entry/syscalls/syscall_64.tbl", buf); - var lines = mem.tokenizeScalar(u8, table, '\n'); - while (lines.next()) |line| { - if (line[0] == '#') continue; - - var fields = mem.tokenizeAny(u8, line, " \t"); - const number = fields.next() orelse return error.Incomplete; - const abi = fields.next() orelse return error.Incomplete; - // The x32 abi syscalls are always at the end. - if (mem.eql(u8, abi, "x32")) break; - const name = fields.next() orelse return error.Incomplete; - const fixed_name = if (stdlib_renames.get(name)) |fixed| fixed else name; - - try writer.print(" {p} = {s},\n", .{ zig.fmtId(fixed_name), number }); - } - - try writer.writeAll("};\n\n"); - } - { - try writer.writeAll( - \\pub const Arm = enum(usize) { - \\ const arm_base = 0x0f0000; - \\ - \\ - ); - - const table = try linux_dir.readFile("arch/arm/tools/syscall.tbl", buf); - var lines = mem.tokenizeScalar(u8, table, '\n'); - while (lines.next()) |line| { - if (line[0] == '#') continue; - - var fields = mem.tokenizeAny(u8, line, " \t"); - const number = fields.next() orelse return error.Incomplete; - const abi = fields.next() orelse return error.Incomplete; - if (mem.eql(u8, abi, "oabi")) continue; - const name = fields.next() orelse return error.Incomplete; - const fixed_name = if (stdlib_renames.get(name)) |fixed| fixed else name; - - try writer.print(" {p} = {s},\n", .{ zig.fmtId(fixed_name), number }); - } - - // TODO: maybe extract these from arch/arm/include/uapi/asm/unistd.h - try writer.writeAll( - \\ - \\ breakpoint = arm_base + 1, - \\ cacheflush = arm_base + 2, - \\ usr26 = arm_base + 3, - \\ usr32 = arm_base + 4, - \\ set_tls = arm_base + 5, - \\ get_tls = arm_base + 6, - \\}; - \\ - \\ - ); - } - { - try writer.writeAll("pub const Sparc = enum(usize) {\n"); - const table = try linux_dir.readFile("arch/sparc/kernel/syscalls/syscall.tbl", buf); - var lines = mem.tokenizeScalar(u8, table, '\n'); - while (lines.next()) |line| { - if (line[0] == '#') continue; - - var fields = mem.tokenizeAny(u8, line, " \t"); - const number = fields.next() orelse return error.Incomplete; - const abi = fields.next() orelse return error.Incomplete; - if (mem.eql(u8, abi, "64")) continue; - const name = fields.next() orelse return error.Incomplete; - const fixed_name = if (stdlib_renames.get(name)) |fixed| fixed else name; - - try writer.print(" {p} = {s},\n", .{ zig.fmtId(fixed_name), number }); - } - - try writer.writeAll("};\n\n"); - } - { - try writer.writeAll("pub const Sparc64 = enum(usize) {\n"); - const table = try linux_dir.readFile("arch/sparc/kernel/syscalls/syscall.tbl", buf); - var lines = mem.tokenizeScalar(u8, table, '\n'); - while (lines.next()) |line| { - if (line[0] == '#') continue; - - var fields = mem.tokenizeAny(u8, line, " \t"); - const number = fields.next() orelse return error.Incomplete; - const abi = fields.next() orelse return error.Incomplete; - if (mem.eql(u8, abi, "32")) continue; - const name = fields.next() orelse return error.Incomplete; - const fixed_name = if (stdlib_renames.get(name)) |fixed| fixed else name; - - try writer.print(" {p} = {s},\n", .{ zig.fmtId(fixed_name), number }); - } - - try writer.writeAll("};\n\n"); - } - { - try writer.writeAll("pub const M68k = enum(usize) {\n"); - - const table = try linux_dir.readFile("arch/m68k/kernel/syscalls/syscall.tbl", buf); - var lines = mem.tokenizeScalar(u8, table, '\n'); - while (lines.next()) |line| { - if (line[0] == '#') continue; - - var fields = mem.tokenizeAny(u8, line, " \t"); - const number = fields.next() orelse return error.Incomplete; - // abi is always common - _ = fields.next() orelse return error.Incomplete; - const name = fields.next() orelse return error.Incomplete; - const fixed_name = if (stdlib_renames.get(name)) |fixed| fixed else name; - - try writer.print(" {p} = {s},\n", .{ zig.fmtId(fixed_name), number }); - } - - try writer.writeAll("};\n\n"); - } - { - try writer.writeAll( - \\pub const MipsO32 = enum(usize) { - \\ const linux_base = 4000; - \\ - \\ - ); - - const table = try linux_dir.readFile("arch/mips/kernel/syscalls/syscall_o32.tbl", buf); - var lines = mem.tokenizeScalar(u8, table, '\n'); - while (lines.next()) |line| { - if (line[0] == '#') continue; - - var fields = mem.tokenizeAny(u8, line, " \t"); - const number = fields.next() orelse return error.Incomplete; - // abi is always o32 - _ = fields.next() orelse return error.Incomplete; - const name = fields.next() orelse return error.Incomplete; - if (isReservedNameOld(name)) continue; - const fixed_name = if (stdlib_renames.get(name)) |fixed| fixed else name; - - try writer.print(" {p} = linux_base + {s},\n", .{ zig.fmtId(fixed_name), number }); - } - - try writer.writeAll("};\n\n"); - } - { - try writer.writeAll( - \\pub const MipsN64 = enum(usize) { - \\ const linux_base = 5000; - \\ - \\ - ); - - const table = try linux_dir.readFile("arch/mips/kernel/syscalls/syscall_n64.tbl", buf); - var lines = mem.tokenizeScalar(u8, table, '\n'); - while (lines.next()) |line| { - if (line[0] == '#') continue; - - var fields = mem.tokenizeAny(u8, line, " \t"); - const number = fields.next() orelse return error.Incomplete; - // abi is always n64 - _ = fields.next() orelse return error.Incomplete; - const name = fields.next() orelse return error.Incomplete; - if (isReservedNameOld(name)) continue; - const fixed_name = if (stdlib_renames.get(name)) |fixed| fixed else name; - - try writer.print(" {p} = linux_base + {s},\n", .{ zig.fmtId(fixed_name), number }); - } - - try writer.writeAll("};\n\n"); - } - { - try writer.writeAll( - \\pub const MipsN32 = enum(usize) { - \\ const linux_base = 6000; - \\ - \\ - ); - - const table = try linux_dir.readFile("arch/mips/kernel/syscalls/syscall_n32.tbl", buf); - var lines = mem.tokenizeScalar(u8, table, '\n'); - while (lines.next()) |line| { - if (line[0] == '#') continue; - - var fields = mem.tokenizeAny(u8, line, " \t"); - const number = fields.next() orelse return error.Incomplete; - // abi is always n32 - _ = fields.next() orelse return error.Incomplete; - const name = fields.next() orelse return error.Incomplete; - if (isReservedNameOld(name)) continue; - const fixed_name = if (stdlib_renames.get(name)) |fixed| fixed else name; - - try writer.print(" {p} = linux_base + {s},\n", .{ zig.fmtId(fixed_name), number }); - } - - try writer.writeAll("};\n\n"); - } - { - try writer.writeAll("pub const PowerPC = enum(usize) {\n"); - - const table = try linux_dir.readFile("arch/powerpc/kernel/syscalls/syscall.tbl", buf); - var list_64 = std.ArrayList(u8).init(allocator); - var lines = mem.tokenizeScalar(u8, table, '\n'); - while (lines.next()) |line| { - if (line[0] == '#') continue; - - var fields = mem.tokenizeAny(u8, line, " \t"); - const number = fields.next() orelse return error.Incomplete; - const abi = fields.next() orelse return error.Incomplete; - const name = fields.next() orelse return error.Incomplete; - const fixed_name = if (stdlib_renames.get(name)) |fixed| fixed else name; - - if (mem.eql(u8, abi, "spu")) { - continue; - } else if (mem.eql(u8, abi, "32")) { - try writer.print(" {p} = {s},\n", .{ zig.fmtId(fixed_name), number }); - } else if (mem.eql(u8, abi, "64")) { - try list_64.writer().print(" {p} = {s},\n", .{ zig.fmtId(fixed_name), number }); - } else { // common/nospu - try writer.print(" {p} = {s},\n", .{ zig.fmtId(fixed_name), number }); - try list_64.writer().print(" {p} = {s},\n", .{ zig.fmtId(fixed_name), number }); - } - } - - try writer.writeAll( - \\}; - \\ - \\pub const PowerPC64 = enum(usize) { - \\ - ); - try writer.writeAll(list_64.items); - try writer.writeAll("};\n\n"); - } - { - try writer.writeAll("pub const S390x = enum(usize) {\n"); - - const table = try linux_dir.readFile("arch/s390/kernel/syscalls/syscall.tbl", buf); - var lines = mem.tokenizeScalar(u8, table, '\n'); - while (lines.next()) |line| { - if (line[0] == '#') continue; - - var fields = mem.tokenizeAny(u8, line, " \t"); - const number = fields.next() orelse return error.Incomplete; - const abi = fields.next() orelse return error.Incomplete; - if (mem.eql(u8, abi, "32")) continue; // 32-bit s390 support in linux is deprecated - const name = fields.next() orelse return error.Incomplete; - const fixed_name = if (stdlib_renames.get(name)) |fixed| fixed else name; - - try writer.print(" {p} = {s},\n", .{ zig.fmtId(fixed_name), number }); - } - - try writer.writeAll("};\n\n"); - } - { - try writer.writeAll("pub const Xtensa = enum(usize) {\n"); - - const table = try linux_dir.readFile("arch/xtensa/kernel/syscalls/syscall.tbl", buf); - var lines = mem.tokenizeScalar(u8, table, '\n'); - while (lines.next()) |line| { - if (line[0] == '#') continue; - - var fields = mem.tokenizeAny(u8, line, " \t"); - const number = fields.next() orelse return error.Incomplete; - // abi is always common - _ = fields.next() orelse return error.Incomplete; - const name = fields.next() orelse return error.Incomplete; - if (isReservedNameOld(name)) continue; - const fixed_name = if (stdlib_renames.get(name)) |fixed| fixed else name; - - try writer.print(" {p} = {s},\n", .{ zig.fmtId(fixed_name), number }); - } - - try writer.writeAll("};\n\n"); - } - - // Newer architectures (starting with aarch64 c. 2012) now use the same C - // header file for their syscall numbers. Arch-specific headers are used to - // define pre-proc. vars that add additional (usually obsolete) syscalls. - { - try writer.writeAll("pub const Arm64 = enum(usize) {\n"); - - const child_args = [_][]const u8{ - zig_exe, - "cc", - "-target", - "aarch64-linux-gnu", - "-E", - // -dM is cleaner, but -dD preserves iteration order. - "-dD", - // No need for line-markers. - "-P", - "-nostdinc", - // Using -I=[dir] includes the zig linux headers, which we don't want. - "-Itools/include", - "-Itools/include/uapi", - // Output the syscall in a format we can easily recognize. - "-D __SYSCALL(nr, nm)=zigsyscall nm nr", - "arch/arm64/include/uapi/asm/unistd.h", - }; - - const child_result = try std.process.Child.run(.{ - .allocator = allocator, - .argv = &child_args, - .cwd = linux_path, - .cwd_dir = linux_dir, - }); - if (child_result.stderr.len > 0) std.debug.print("{s}\n", .{child_result.stderr}); - - const defines = switch (child_result.term) { - .Exited => |code| if (code == 0) child_result.stdout else { - std.debug.print("zig cc exited with code {d}\n", .{code}); - std.process.exit(1); - }, - else => { - std.debug.print("zig cc crashed\n", .{}); - std.process.exit(1); - }, - }; - - var lines = mem.tokenizeScalar(u8, defines, '\n'); - while (lines.next()) |line| { - var fields = mem.tokenizeAny(u8, line, " "); - const prefix = fields.next() orelse return error.Incomplete; - - if (!mem.eql(u8, prefix, "zigsyscall")) continue; - - const sys_name = fields.next() orelse return error.Incomplete; - const value = fields.rest(); - const name = (getOverridenNameNew(value) orelse sys_name)["sys_".len..]; - const fixed_name = if (stdlib_renames_new.get(name)) |f| f else if (stdlib_renames.get(name)) |f| f else name; - - try writer.print(" {p} = {s},\n", .{ zig.fmtId(fixed_name), value }); - } - - try writer.writeAll("};\n\n"); - } - { - try writer.writeAll("pub const RiscV32 = enum(usize) {\n"); - - const child_args = [_][]const u8{ - zig_exe, - "cc", - "-target", - "riscv32-linux-gnuilp32", - "-E", - "-dD", - "-P", - "-nostdinc", - "-Itools/include", - "-Itools/include/uapi", - "-D __SYSCALL(nr, nm)=zigsyscall nm nr", - "arch/riscv/include/uapi/asm/unistd.h", - }; - - const child_result = try std.process.Child.run(.{ - .allocator = allocator, - .argv = &child_args, - .cwd = linux_path, - .cwd_dir = linux_dir, - }); - if (child_result.stderr.len > 0) std.debug.print("{s}\n", .{child_result.stderr}); - - const defines = switch (child_result.term) { - .Exited => |code| if (code == 0) child_result.stdout else { - std.debug.print("zig cc exited with code {d}\n", .{code}); - std.process.exit(1); - }, - else => { - std.debug.print("zig cc crashed\n", .{}); - std.process.exit(1); - }, - }; - - var lines = mem.tokenizeScalar(u8, defines, '\n'); - while (lines.next()) |line| { - var fields = mem.tokenizeAny(u8, line, " "); - const prefix = fields.next() orelse return error.Incomplete; - - if (!mem.eql(u8, prefix, "zigsyscall")) continue; - - const sys_name = fields.next() orelse return error.Incomplete; - const value = fields.rest(); - const name = (getOverridenNameNew(value) orelse sys_name)["sys_".len..]; - const fixed_name = if (stdlib_renames_new.get(name)) |f| f else if (stdlib_renames.get(name)) |f| f else name; - - try writer.print(" {p} = {s},\n", .{ zig.fmtId(fixed_name), value }); - } - - try writer.writeAll("};\n\n"); - } - { - try writer.writeAll("pub const RiscV64 = enum(usize) {\n"); - - const child_args = [_][]const u8{ - zig_exe, - "cc", - "-target", - "riscv64-linux-gnu", - "-E", - "-dD", - "-P", - "-nostdinc", - "-Itools/include", - "-Itools/include/uapi", - "-D __SYSCALL(nr, nm)=zigsyscall nm nr", - "arch/riscv/include/uapi/asm/unistd.h", - }; - - const child_result = try std.process.Child.run(.{ - .allocator = allocator, - .argv = &child_args, - .cwd = linux_path, - .cwd_dir = linux_dir, - }); - if (child_result.stderr.len > 0) std.debug.print("{s}\n", .{child_result.stderr}); - - const defines = switch (child_result.term) { - .Exited => |code| if (code == 0) child_result.stdout else { - std.debug.print("zig cc exited with code {d}\n", .{code}); - std.process.exit(1); - }, - else => { - std.debug.print("zig cc crashed\n", .{}); - std.process.exit(1); - }, - }; - - var lines = mem.tokenizeScalar(u8, defines, '\n'); - while (lines.next()) |line| { - var fields = mem.tokenizeAny(u8, line, " "); - const prefix = fields.next() orelse return error.Incomplete; - - if (!mem.eql(u8, prefix, "zigsyscall")) continue; - - const sys_name = fields.next() orelse return error.Incomplete; - const value = fields.rest(); - const name = (getOverridenNameNew(value) orelse sys_name)["sys_".len..]; - const fixed_name = if (stdlib_renames_new.get(name)) |f| f else if (stdlib_renames.get(name)) |f| f else name; - - try writer.print(" {p} = {s},\n", .{ zig.fmtId(fixed_name), value }); - } - - try writer.writeAll("};\n\n"); - } - { - try writer.writeAll("pub const LoongArch64 = enum(usize) {\n"); - - const child_args = [_][]const u8{ - zig_exe, - "cc", - "-target", - "loongarch64-linux-gnu", - "-E", - "-dD", - "-P", - "-nostdinc", - "-Itools/include", - "-Itools/include/uapi", - "-D __SYSCALL(nr, nm)=zigsyscall nm nr", - "arch/loongarch/include/uapi/asm/unistd.h", - }; - - const child_result = try std.process.Child.run(.{ - .allocator = allocator, - .argv = &child_args, - .cwd = linux_path, - .cwd_dir = linux_dir, - }); - if (child_result.stderr.len > 0) std.debug.print("{s}\n", .{child_result.stderr}); - - const defines = switch (child_result.term) { - .Exited => |code| if (code == 0) child_result.stdout else { - std.debug.print("zig cc exited with code {d}\n", .{code}); - std.process.exit(1); - }, - else => { - std.debug.print("zig cc crashed\n", .{}); - std.process.exit(1); - }, - }; - - var lines = mem.tokenizeScalar(u8, defines, '\n'); - while (lines.next()) |line| { - var fields = mem.tokenizeAny(u8, line, " "); - const prefix = fields.next() orelse return error.Incomplete; - - if (!mem.eql(u8, prefix, "zigsyscall")) continue; - - const sys_name = fields.next() orelse return error.Incomplete; - const value = fields.rest(); - const name = (getOverridenNameNew(value) orelse sys_name)["sys_".len..]; - const fixed_name = if (stdlib_renames_new.get(name)) |f| f else if (stdlib_renames.get(name)) |f| f else name; - - try writer.print(" {p} = {s},\n", .{ zig.fmtId(fixed_name), value }); - } - - try writer.writeAll("};\n\n"); - } - { - try writer.writeAll("pub const Arc = enum(usize) {\n"); - - const child_args = [_][]const u8{ - zig_exe, - "cc", - "-target", - "arc-freestanding-none", - "-E", - "-dD", - "-P", - "-nostdinc", - "-Itools/include", - "-Itools/include/uapi", - "-D __SYSCALL(nr, nm)=zigsyscall nm nr", - "arch/arc/include/uapi/asm/unistd.h", - }; - - const child_result = try std.process.Child.run(.{ - .allocator = allocator, - .argv = &child_args, - .cwd = linux_path, - .cwd_dir = linux_dir, - }); - if (child_result.stderr.len > 0) std.debug.print("{s}\n", .{child_result.stderr}); - - const defines = switch (child_result.term) { - .Exited => |code| if (code == 0) child_result.stdout else { - std.debug.print("zig cc exited with code {d}\n", .{code}); - std.process.exit(1); - }, - else => { - std.debug.print("zig cc crashed\n", .{}); - std.process.exit(1); - }, - }; - - var lines = mem.tokenizeScalar(u8, defines, '\n'); - while (lines.next()) |line| { - var fields = mem.tokenizeAny(u8, line, " "); - const prefix = fields.next() orelse return error.Incomplete; - - if (!mem.eql(u8, prefix, "zigsyscall")) continue; - - const sys_name = fields.next() orelse return error.Incomplete; - const value = fields.rest(); - const name = (getOverridenNameNew(value) orelse sys_name)["sys_".len..]; - const fixed_name = if (stdlib_renames_new.get(name)) |f| f else if (stdlib_renames.get(name)) |f| f else name; - - try writer.print(" {p} = {s},\n", .{ zig.fmtId(fixed_name), value }); - } - - try writer.writeAll("};\n\n"); - } - { - try writer.writeAll("pub const CSky = enum(usize) {\n"); - - const child_args = [_][]const u8{ - zig_exe, - "cc", - "-target", - "csky-freestanding-none", - "-E", - "-dD", - "-P", - "-nostdinc", - "-Itools/include", - "-Itools/include/uapi", - "-D __SYSCALL(nr, nm)=zigsyscall nm nr", - "arch/csky/include/uapi/asm/unistd.h", - }; - - const child_result = try std.process.Child.run(.{ - .allocator = allocator, - .argv = &child_args, - .cwd = linux_path, - .cwd_dir = linux_dir, - }); - if (child_result.stderr.len > 0) std.debug.print("{s}\n", .{child_result.stderr}); - - const defines = switch (child_result.term) { - .Exited => |code| if (code == 0) child_result.stdout else { - std.debug.print("zig cc exited with code {d}\n", .{code}); - std.process.exit(1); - }, - else => { - std.debug.print("zig cc crashed\n", .{}); - std.process.exit(1); - }, - }; - - var lines = mem.tokenizeScalar(u8, defines, '\n'); - while (lines.next()) |line| { - var fields = mem.tokenizeAny(u8, line, " "); - const prefix = fields.next() orelse return error.Incomplete; - - if (!mem.eql(u8, prefix, "zigsyscall")) continue; - - const sys_name = fields.next() orelse return error.Incomplete; - const value = fields.rest(); - const name = (getOverridenNameNew(value) orelse sys_name)["sys_".len..]; - const fixed_name = if (stdlib_renames_new.get(name)) |f| f else if (stdlib_renames.get(name)) |f| f else name; - - try writer.print(" {p} = {s},\n", .{ zig.fmtId(fixed_name), value }); - } - - try writer.writeAll("};\n\n"); - } - { - try writer.writeAll("pub const Hexagon = enum(usize) {\n"); - - const child_args = [_][]const u8{ - zig_exe, - "cc", - "-target", - "hexagon-freestanding-none", - "-E", - "-dD", - "-P", - "-nostdinc", - "-Itools/include", - "-Itools/include/uapi", - "-D __SYSCALL(nr, nm)=zigsyscall nm nr", - "arch/hexagon/include/uapi/asm/unistd.h", - }; - - const child_result = try std.process.Child.run(.{ - .allocator = allocator, - .argv = &child_args, - .cwd = linux_path, - .cwd_dir = linux_dir, - }); - if (child_result.stderr.len > 0) std.debug.print("{s}\n", .{child_result.stderr}); - - const defines = switch (child_result.term) { - .Exited => |code| if (code == 0) child_result.stdout else { - std.debug.print("zig cc exited with code {d}\n", .{code}); - std.process.exit(1); - }, - else => { - std.debug.print("zig cc crashed\n", .{}); - std.process.exit(1); - }, - }; - - var lines = mem.tokenizeScalar(u8, defines, '\n'); - while (lines.next()) |line| { - var fields = mem.tokenizeAny(u8, line, " "); - const prefix = fields.next() orelse return error.Incomplete; - - if (!mem.eql(u8, prefix, "zigsyscall")) continue; - - const sys_name = fields.next() orelse return error.Incomplete; - const value = fields.rest(); - const name = (getOverridenNameNew(value) orelse sys_name)["sys_".len..]; - const fixed_name = if (stdlib_renames_new.get(name)) |f| f else if (stdlib_renames.get(name)) |f| f else name; - - try writer.print(" {p} = {s},\n", .{ zig.fmtId(fixed_name), value }); - } - - try writer.writeAll("};\n\n"); } try buf_out.flush(); From 4bdf04654e3bd8ac09882cb49595f6797f2f5d09 Mon Sep 17 00:00:00 2001 From: Evan Haas Date: Mon, 5 Aug 2024 12:54:56 -0700 Subject: [PATCH 144/266] tools: Add tool for checking size and alignment of C types Prints _Static_asserts for the size and alignment of all the basic built-in C types. The output can be run through a compiler for the specified target to verify that Zig's values are the same as those used by a C compiler for the target. --- tools/generate_c_size_and_align_checks.zig | 57 ++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 tools/generate_c_size_and_align_checks.zig diff --git a/tools/generate_c_size_and_align_checks.zig b/tools/generate_c_size_and_align_checks.zig new file mode 100644 index 0000000000..ed52da6286 --- /dev/null +++ b/tools/generate_c_size_and_align_checks.zig @@ -0,0 +1,57 @@ +//! Usage: zig run tools/generate_c_size_and_align_checks.zig -- [target_triple] +//! e.g. zig run tools/generate_c_size_and_align_checks.zig -- x86_64-linux-gnu +//! +//! Prints _Static_asserts for the size and alignment of all the basic built-in C +//! types. The output can be run through a compiler for the specified target to +//! verify that Zig's values are the same as those used by a C compiler for the +//! target. + +const std = @import("std"); + +fn c_name(ty: std.Target.CType) []const u8 { + return switch (ty) { + .char => "char", + .short => "short", + .ushort => "unsigned short", + .int => "int", + .uint => "unsigned int", + .long => "long", + .ulong => "unsigned long", + .longlong => "long long", + .ulonglong => "unsigned long long", + .float => "float", + .double => "double", + .longdouble => "long double", + }; +} + +var general_purpose_allocator = std.heap.GeneralPurposeAllocator(.{}){}; + +pub fn main() !void { + const gpa = general_purpose_allocator.allocator(); + defer std.debug.assert(general_purpose_allocator.deinit() == .ok); + + const args = try std.process.argsAlloc(gpa); + defer std.process.argsFree(gpa, args); + + if (args.len != 2) { + std.debug.print("Usage: {s} [target_triple]\n", .{args[0]}); + std.process.exit(1); + } + + const query = try std.Target.Query.parse(.{ .arch_os_abi = args[1] }); + const target = try std.zig.system.resolveTargetQuery(query); + + const stdout = std.io.getStdOut().writer(); + inline for (@typeInfo(std.Target.CType).Enum.fields) |field| { + const c_type: std.Target.CType = @enumFromInt(field.value); + try stdout.print("_Static_assert(sizeof({s}) == {d}, \"\");\n", .{ + c_name(c_type), + target.c_type_byte_size(c_type), + }); + try stdout.print("_Static_assert(_Alignof({s}) == {d}, \"\");\n\n", .{ + c_name(c_type), + target.c_type_alignment(c_type), + }); + } +} From 8daf7673a5d92514407f333de60865ea06fcb274 Mon Sep 17 00:00:00 2001 From: Evan Haas Date: Mon, 5 Aug 2024 13:15:05 -0700 Subject: [PATCH 145/266] test: Add generate_c_size_and_align_checks.zig to standalone build test --- test/standalone/build.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/test/standalone/build.zig b/test/standalone/build.zig index b2575bc83e..98b48576ad 100644 --- a/test/standalone/build.zig +++ b/test/standalone/build.zig @@ -34,6 +34,7 @@ pub fn build(b: *std.Build) void { "../../tools/gen_outline_atomics.zig", "../../tools/gen_spirv_spec.zig", "../../tools/gen_stubs.zig", + "../../tools/generate_c_size_and_align_checks.zig", "../../tools/generate_linux_syscalls.zig", "../../tools/process_headers.zig", "../../tools/update-linux-headers.zig", From b2dd0a669a96b874271069bb2ea3dd41dcc2fd48 Mon Sep 17 00:00:00 2001 From: Evan Haas Date: Tue, 6 Aug 2024 07:02:22 -0700 Subject: [PATCH 146/266] generate_c_size_and_align_checks: print failed condition in _Static_assert --- tools/generate_c_size_and_align_checks.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/generate_c_size_and_align_checks.zig b/tools/generate_c_size_and_align_checks.zig index ed52da6286..d0d6bb66d5 100644 --- a/tools/generate_c_size_and_align_checks.zig +++ b/tools/generate_c_size_and_align_checks.zig @@ -45,11 +45,11 @@ pub fn main() !void { const stdout = std.io.getStdOut().writer(); inline for (@typeInfo(std.Target.CType).Enum.fields) |field| { const c_type: std.Target.CType = @enumFromInt(field.value); - try stdout.print("_Static_assert(sizeof({s}) == {d}, \"\");\n", .{ + try stdout.print("_Static_assert(sizeof({0s}) == {1d}, \"sizeof({0s}) == {1d}\");\n", .{ c_name(c_type), target.c_type_byte_size(c_type), }); - try stdout.print("_Static_assert(_Alignof({s}) == {d}, \"\");\n\n", .{ + try stdout.print("_Static_assert(_Alignof({0s}) == {1d}, \"_Alignof({0s}) == {1d}\");\n\n", .{ c_name(c_type), target.c_type_alignment(c_type), }); From 679ad36fa069a723aa9094afbd09d1b460232dda Mon Sep 17 00:00:00 2001 From: Evan Haas Date: Tue, 6 Aug 2024 07:06:57 -0700 Subject: [PATCH 147/266] generate_c_size_and_align_checks: add __alignof check --- tools/generate_c_size_and_align_checks.zig | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tools/generate_c_size_and_align_checks.zig b/tools/generate_c_size_and_align_checks.zig index d0d6bb66d5..840ef2b6eb 100644 --- a/tools/generate_c_size_and_align_checks.zig +++ b/tools/generate_c_size_and_align_checks.zig @@ -53,5 +53,9 @@ pub fn main() !void { c_name(c_type), target.c_type_alignment(c_type), }); + try stdout.print("_Static_assert(__alignof({0s}) == {1d}, \"__alignof({0s}) == {1d}\");\n\n", .{ + c_name(c_type), + target.c_type_preferred_alignment(c_type), + }); } } From ab154be7f0a89b271686d740f7e50545414a6024 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Tue, 6 Aug 2024 17:10:10 +0200 Subject: [PATCH 148/266] std.Target: Fix ptrBitWidth_cpu_abi() for dxil (64-bit, not 32-bit). The DXIL documentation claims 32-bit pointers: https://github.com/microsoft/DirectXShaderCompiler/blob/main/docs/DXIL.rst#memory-accesses Despite this, Clang considers pointers 64-bit when targeting it. --- lib/std/Target.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/Target.zig b/lib/std/Target.zig index 4e43055810..e8df26f52e 100644 --- a/lib/std/Target.zig +++ b/lib/std/Target.zig @@ -1854,12 +1854,12 @@ pub fn ptrBitWidth_cpu_abi(cpu: Cpu, abi: Abi) u16 { .wasm32, .spirv32, .loongarch32, - .dxil, .xtensa, => 32, .aarch64, .aarch64_be, + .dxil, .mips64, .mips64el, .powerpc64, From db8f00e277016e12495bcc5217090052eea69eb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Tue, 6 Aug 2024 15:51:40 +0200 Subject: [PATCH 149/266] std.Target: Fix ptrBitWidth_cpu_abi() for sparc32. CPU feature set has nothing to do with ABI choice; the pointer bit width is determined only by looking at the choice of sparc vs sparc64. --- lib/std/Target.zig | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/std/Target.zig b/lib/std/Target.zig index e8df26f52e..facdc1c01c 100644 --- a/lib/std/Target.zig +++ b/lib/std/Target.zig @@ -1852,6 +1852,7 @@ pub fn ptrBitWidth_cpu_abi(cpu: Cpu, abi: Abi) u16 { .kalimba, .lanai, .wasm32, + .sparc, .spirv32, .loongarch32, .xtensa, @@ -1878,8 +1879,6 @@ pub fn ptrBitWidth_cpu_abi(cpu: Cpu, abi: Abi) u16 { .loongarch64, => 64, - .sparc => if (std.Target.sparc.featureSetHas(cpu.features, .v9)) 64 else 32, - .spirv => @panic("TODO what should this value be?"), }; } From e67388c2e538789aa3e46f625f6e08696093dcdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Tue, 6 Aug 2024 17:12:33 +0200 Subject: [PATCH 150/266] std.Target: Fix C type alignment calculation for dxil. --- lib/std/Target.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/std/Target.zig b/lib/std/Target.zig index facdc1c01c..c05c47cac0 100644 --- a/lib/std/Target.zig +++ b/lib/std/Target.zig @@ -2343,7 +2343,6 @@ pub fn c_type_alignment(target: Target, c_type: CType) u16 { .csky, .x86, .xcore, - .dxil, .loongarch32, .spirv32, .kalimba, @@ -2355,6 +2354,7 @@ pub fn c_type_alignment(target: Target, c_type: CType) u16 { .amdgcn, .bpfel, .bpfeb, + .dxil, .hexagon, .loongarch64, .m68k, @@ -2448,7 +2448,6 @@ pub fn c_type_preferred_alignment(target: Target, c_type: CType) u16 { .csky, .xcore, - .dxil, .loongarch32, .spirv32, .kalimba, @@ -2466,6 +2465,7 @@ pub fn c_type_preferred_alignment(target: Target, c_type: CType) u16 { .amdgcn, .bpfel, .bpfeb, + .dxil, .hexagon, .x86, .loongarch64, From 29321ca4a2abc5c4c936840f2bc6002babbdbf3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Tue, 6 Aug 2024 18:43:29 +0200 Subject: [PATCH 151/266] std.Target: Fix C type alignment calculation for spirv32. --- lib/std/Target.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/std/Target.zig b/lib/std/Target.zig index c05c47cac0..e1a19d28cf 100644 --- a/lib/std/Target.zig +++ b/lib/std/Target.zig @@ -2344,7 +2344,6 @@ pub fn c_type_alignment(target: Target, c_type: CType) u16 { .x86, .xcore, .loongarch32, - .spirv32, .kalimba, .ve, .spu_2, @@ -2366,6 +2365,7 @@ pub fn c_type_alignment(target: Target, c_type: CType) u16 { .nvptx, .nvptx64, .s390x, + .spirv32, .spirv64, => 8, @@ -2449,7 +2449,6 @@ pub fn c_type_preferred_alignment(target: Target, c_type: CType) u16 { .csky, .xcore, .loongarch32, - .spirv32, .kalimba, .ve, .spu_2, @@ -2478,6 +2477,7 @@ pub fn c_type_preferred_alignment(target: Target, c_type: CType) u16 { .nvptx, .nvptx64, .s390x, + .spirv32, .spirv64, => 8, From 98153c8d816ec3cc1c68756d10689ee7be3e32f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Tue, 6 Aug 2024 16:01:57 +0200 Subject: [PATCH 152/266] std.Target: Fix C type alignment calculation for loongarch64. --- lib/std/Target.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/std/Target.zig b/lib/std/Target.zig index e1a19d28cf..eb38c3980c 100644 --- a/lib/std/Target.zig +++ b/lib/std/Target.zig @@ -2355,7 +2355,6 @@ pub fn c_type_alignment(target: Target, c_type: CType) u16 { .bpfeb, .dxil, .hexagon, - .loongarch64, .m68k, .mips, .mipsel, @@ -2371,6 +2370,7 @@ pub fn c_type_alignment(target: Target, c_type: CType) u16 { .aarch64, .aarch64_be, + .loongarch64, .mips64, .mips64el, .powerpc, @@ -2467,7 +2467,6 @@ pub fn c_type_preferred_alignment(target: Target, c_type: CType) u16 { .dxil, .hexagon, .x86, - .loongarch64, .m68k, .mips, .mipsel, @@ -2483,6 +2482,7 @@ pub fn c_type_preferred_alignment(target: Target, c_type: CType) u16 { .aarch64, .aarch64_be, + .loongarch64, .mips64, .mips64el, .powerpc, From e6788625209adeeb06d0dde516f0cb8e25e338bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Tue, 6 Aug 2024 15:48:47 +0200 Subject: [PATCH 153/266] std.Target: Fix C type alignment calculation for sparc64. --- lib/std/Target.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/std/Target.zig b/lib/std/Target.zig index eb38c3980c..d1351e7d41 100644 --- a/lib/std/Target.zig +++ b/lib/std/Target.zig @@ -2359,7 +2359,6 @@ pub fn c_type_alignment(target: Target, c_type: CType) u16 { .mips, .mipsel, .sparc, - .sparc64, .lanai, .nvptx, .nvptx64, @@ -2379,6 +2378,7 @@ pub fn c_type_alignment(target: Target, c_type: CType) u16 { .powerpc64le, .riscv32, .riscv64, + .sparc64, .x86_64, .wasm32, .wasm64, @@ -2471,7 +2471,6 @@ pub fn c_type_preferred_alignment(target: Target, c_type: CType) u16 { .mips, .mipsel, .sparc, - .sparc64, .lanai, .nvptx, .nvptx64, @@ -2491,6 +2490,7 @@ pub fn c_type_preferred_alignment(target: Target, c_type: CType) u16 { .powerpc64le, .riscv32, .riscv64, + .sparc64, .x86_64, .wasm32, .wasm64, From 5dd66cd964dba48709a817e86dc004ed703e5ab1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Tue, 6 Aug 2024 17:02:20 +0200 Subject: [PATCH 154/266] std.Target: Fix C type alignment calculation for ve. --- lib/std/Target.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/std/Target.zig b/lib/std/Target.zig index d1351e7d41..7248cce807 100644 --- a/lib/std/Target.zig +++ b/lib/std/Target.zig @@ -2345,7 +2345,6 @@ pub fn c_type_alignment(target: Target, c_type: CType) u16 { .xcore, .loongarch32, .kalimba, - .ve, .spu_2, .xtensa, => 4, @@ -2380,6 +2379,7 @@ pub fn c_type_alignment(target: Target, c_type: CType) u16 { .riscv64, .sparc64, .x86_64, + .ve, .wasm32, .wasm64, => 16, @@ -2450,7 +2450,6 @@ pub fn c_type_preferred_alignment(target: Target, c_type: CType) u16 { .xcore, .loongarch32, .kalimba, - .ve, .spu_2, .xtensa, => 4, @@ -2492,6 +2491,7 @@ pub fn c_type_preferred_alignment(target: Target, c_type: CType) u16 { .riscv64, .sparc64, .x86_64, + .ve, .wasm32, .wasm64, => 16, From 606d011acf8c2a75ea1485174c2c1d24a612c86b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Tue, 6 Aug 2024 18:29:47 +0200 Subject: [PATCH 155/266] std.Target: Fix C long long size for opencl (8, not 16). This value was correct for the old SPIR, but not for SPIR-V. --- lib/std/Target.zig | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/std/Target.zig b/lib/std/Target.zig index 7248cce807..067c3c6667 100644 --- a/lib/std/Target.zig +++ b/lib/std/Target.zig @@ -2263,8 +2263,7 @@ pub fn c_type_bit_size(target: Target, c_type: CType) u16 { .char => return 8, .short, .ushort => return 16, .int, .uint, .float => return 32, - .long, .ulong, .double => return 64, - .longlong, .ulonglong => return 128, + .long, .ulong, .longlong, .ulonglong, .double => return 64, // Note: The OpenCL specification does not guarantee a particular size for long double, // but clang uses 128 bits. .longdouble => return 128, From a1d3e567635e79e0fbd8d56225d5098bc6cb8bbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Tue, 6 Aug 2024 19:37:49 +0200 Subject: [PATCH 156/266] std.Target: Fix C long double size for opencl (8, not 16). This value was correct for the old SPIR, but not for SPIR-V. --- lib/std/Target.zig | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/std/Target.zig b/lib/std/Target.zig index 067c3c6667..b616d34b43 100644 --- a/lib/std/Target.zig +++ b/lib/std/Target.zig @@ -2264,9 +2264,7 @@ pub fn c_type_bit_size(target: Target, c_type: CType) u16 { .short, .ushort => return 16, .int, .uint, .float => return 32, .long, .ulong, .longlong, .ulonglong, .double => return 64, - // Note: The OpenCL specification does not guarantee a particular size for long double, - // but clang uses 128 bits. - .longdouble => return 128, + .longdouble => return 64, }, .ps4, .ps5 => switch (c_type) { From eef499812c37112f4b03bde73a632c23ca753d20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Tue, 6 Aug 2024 17:27:32 +0200 Subject: [PATCH 157/266] std.Target: Fix C long double size for amdhsa, amdpal, and mesa3d (8, not 16). --- lib/std/Target.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/Target.zig b/lib/std/Target.zig index b616d34b43..20966a237b 100644 --- a/lib/std/Target.zig +++ b/lib/std/Target.zig @@ -2256,7 +2256,7 @@ pub fn c_type_bit_size(target: Target, c_type: CType) u16 { .short, .ushort => return 16, .int, .uint, .float => return 32, .long, .ulong, .longlong, .ulonglong, .double => return 64, - .longdouble => return 128, + .longdouble => return 64, }, .opencl, .vulkan => switch (c_type) { From 7b47ebe5766e3d4824bb05a9a06de543b3335375 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Tue, 6 Aug 2024 16:34:55 +0200 Subject: [PATCH 158/266] std.Target: Fix C long double size for sparc32 (8, not 16). --- lib/std/Target.zig | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/std/Target.zig b/lib/std/Target.zig index 20966a237b..8c56fb4ce1 100644 --- a/lib/std/Target.zig +++ b/lib/std/Target.zig @@ -2060,7 +2060,6 @@ pub fn c_type_bit_size(target: Target, c_type: CType) u16 { .aarch64, .aarch64_be, .s390x, - .sparc, .sparc64, .wasm32, .wasm64, @@ -2166,7 +2165,6 @@ pub fn c_type_bit_size(target: Target, c_type: CType) u16 { .s390x, .mips64, .mips64el, - .sparc, .sparc64, .wasm32, .wasm64, From 231f322a65acd353e4a88907cea7621b6adc2c4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Tue, 6 Aug 2024 17:01:45 +0200 Subject: [PATCH 159/266] std.Target: Fix C long double size for ve (16, not 8). --- lib/std/Target.zig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/std/Target.zig b/lib/std/Target.zig index 8c56fb4ce1..dc20d63988 100644 --- a/lib/std/Target.zig +++ b/lib/std/Target.zig @@ -2065,6 +2065,7 @@ pub fn c_type_bit_size(target: Target, c_type: CType) u16 { .wasm64, .loongarch32, .loongarch64, + .ve, => return 128, else => return 64, @@ -2170,6 +2171,7 @@ pub fn c_type_bit_size(target: Target, c_type: CType) u16 { .wasm64, .loongarch32, .loongarch64, + .ve, => return 128, else => return 64, From 23b5a6c71eb3229e0456a6900f3672c77b91d1f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Tue, 6 Aug 2024 19:48:01 +0200 Subject: [PATCH 160/266] std.Target: Treat spirv as identical to spirv64 for ABI size/alignment purposes. This is arbitrary since spirv (as opposed to spirv32/spirv64) refers to the version with logical memory layout, i.e. no 'real' pointers. This change at least matches what clang does. --- lib/std/Target.zig | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/lib/std/Target.zig b/lib/std/Target.zig index dc20d63988..a88e161325 100644 --- a/lib/std/Target.zig +++ b/lib/std/Target.zig @@ -1875,11 +1875,10 @@ pub fn ptrBitWidth_cpu_abi(cpu: Cpu, abi: Abi) u16 { .sparc64, .s390x, .ve, + .spirv, .spirv64, .loongarch64, => 64, - - .spirv => @panic("TODO what should this value be?"), }; } @@ -2359,6 +2358,7 @@ pub fn c_type_alignment(target: Target, c_type: CType) u16 { .nvptx, .nvptx64, .s390x, + .spirv, .spirv32, .spirv64, => 8, @@ -2380,8 +2380,6 @@ pub fn c_type_alignment(target: Target, c_type: CType) u16 { .wasm32, .wasm64, => 16, - - .spirv => @panic("TODO what should this value be?"), }), ); } @@ -2471,6 +2469,7 @@ pub fn c_type_preferred_alignment(target: Target, c_type: CType) u16 { .nvptx, .nvptx64, .s390x, + .spirv, .spirv32, .spirv64, => 8, @@ -2492,8 +2491,6 @@ pub fn c_type_preferred_alignment(target: Target, c_type: CType) u16 { .wasm32, .wasm64, => 16, - - .spirv => @panic("TODO what should this value be?"), }), ); } From ef502daafe2b4e216c68fe07ff05aa91d539b831 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Tue, 6 Aug 2024 17:27:07 +0200 Subject: [PATCH 161/266] std.Target: Handle mesa3d in c_type_bit_size(). --- lib/std/Target.zig | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/std/Target.zig b/lib/std/Target.zig index a88e161325..d5bc61e663 100644 --- a/lib/std/Target.zig +++ b/lib/std/Target.zig @@ -2250,7 +2250,7 @@ pub fn c_type_bit_size(target: Target, c_type: CType) u16 { .longdouble => return 64, }, - .amdhsa, .amdpal => switch (c_type) { + .amdhsa, .amdpal, .mesa3d => switch (c_type) { .char => return 8, .short, .ushort => return 16, .int, .uint, .float => return 32, @@ -2280,7 +2280,6 @@ pub fn c_type_bit_size(target: Target, c_type: CType) u16 { .rtems, .aix, .elfiamcu, - .mesa3d, .contiki, .hermit, .hurd, From e9b5377b8e411b73d1004f4e15d072ab56aca62e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Sat, 3 Aug 2024 18:24:35 +0200 Subject: [PATCH 162/266] musl: Disable warnings in all compilations. This is what upstream's configure does. Previously, we only disabled warnings in some musl compilations, with `rcrt1.o` notably being one for which we didn't. This resulted in a warning in `dlstart.c` which is included in `rcrt1.c`. So let's just be consistent and disable warnings for all musl code. Closes #13385. --- src/musl.zig | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/musl.zig b/src/musl.zig index b75009c447..a267993fbe 100644 --- a/src/musl.zig +++ b/src/musl.zig @@ -32,9 +32,6 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile, prog_node: std.Progre .crti_o => { var args = std.ArrayList([]const u8).init(arena); try addCcArgs(comp, arena, &args, false); - try args.appendSlice(&[_][]const u8{ - "-Qunused-arguments", - }); var files = [_]Compilation.CSourceFile{ .{ .src_path = try start_asm_path(comp, arena, "crti.s"), @@ -47,9 +44,6 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile, prog_node: std.Progre .crtn_o => { var args = std.ArrayList([]const u8).init(arena); try addCcArgs(comp, arena, &args, false); - try args.appendSlice(&[_][]const u8{ - "-Qunused-arguments", - }); var files = [_]Compilation.CSourceFile{ .{ .src_path = try start_asm_path(comp, arena, "crtn.s"), @@ -189,10 +183,6 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile, prog_node: std.Progre var args = std.ArrayList([]const u8).init(arena); try addCcArgs(comp, arena, &args, ext == .o3); - try args.appendSlice(&[_][]const u8{ - "-Qunused-arguments", - "-w", // disable all warnings - }); const c_source_file = try c_source_files.addOne(); c_source_file.* = .{ .src_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{ "libc", src_file }), @@ -427,6 +417,9 @@ fn addCcArgs( "-fno-asynchronous-unwind-tables", "-ffunction-sections", "-fdata-sections", + + "-Qunused-arguments", + "-w", // disable all warnings }); } From a60810b5a31f7c3a9d1b1f5152c889e45d9ee2f7 Mon Sep 17 00:00:00 2001 From: Kyle Schwarz Date: Sat, 3 Aug 2024 11:28:09 -0400 Subject: [PATCH 163/266] cmake: add ZIG_EXTRA_BUILD_ARGS option --- CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index f99e59e41f..6bd5f24a97 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -945,6 +945,11 @@ set(ZIG_BUILD_ARGS -Dno-langref ) +option(ZIG_EXTRA_BUILD_ARGS "Extra zig build args") +if(ZIG_EXTRA_BUILD_ARGS) + list(APPEND ZIG_BUILD_ARGS ${ZIG_EXTRA_BUILD_ARGS}) +endif() + if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") list(APPEND ZIG_BUILD_ARGS -Doptimize=Debug) elseif("${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo") From 650785c718b29496548b06bd043eb03887a182d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Wed, 7 Aug 2024 02:04:59 +0200 Subject: [PATCH 164/266] target: Fix hasLlvmSupport() for dxil, spirv[32,64], and kalimba. --- src/target.zig | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/target.zig b/src/target.zig index 768e4d957e..b9ff8ba5be 100644 --- a/src/target.zig +++ b/src/target.zig @@ -119,7 +119,6 @@ pub fn hasLlvmSupport(target: std.Target, ofmt: std.Target.ObjectFormat) bool { .bpfel, .bpfeb, .csky, - .dxil, .hexagon, .loongarch32, .loongarch64, @@ -147,17 +146,23 @@ pub fn hasLlvmSupport(target: std.Target, ofmt: std.Target.ObjectFormat) bool { .xtensa, .nvptx, .nvptx64, - .spirv, - .spirv32, - .spirv64, - .kalimba, .lanai, .wasm32, .wasm64, .ve, => true, - .spu_2 => false, + // An LLVM backend exists but we don't currently support using it. + .dxil, + .spirv, + .spirv32, + .spirv64, + => false, + + // No LLVM backend exists. + .kalimba, + .spu_2, + => false, }; } From a9f68410d0a6dd4c2509cf897b283c7ab8f97bdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Wed, 7 Aug 2024 02:05:43 +0200 Subject: [PATCH 165/266] llvm: Clarify in initializeLLVMTarget() that there's no kalimba backend. --- src/codegen/llvm.zig | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 23e39caa98..6c78fcfe37 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -12083,14 +12083,16 @@ pub fn initializeLLVMTarget(arch: std.Target.Cpu.Arch) void { llvm.LLVMInitializeLoongArchAsmParser(); }, - // LLVM backends that have no initialization functions. + // We don't currently support using these backends. .spirv, .spirv32, .spirv64, - .kalimba, .dxil, => {}, - .spu_2 => unreachable, // LLVM does not support this backend + // LLVM does does not have a backend for these. + .kalimba, + .spu_2, + => unreachable, } } From 746f20d21fcd6a0a9ccbf662cd381f16c5064cd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Wed, 7 Aug 2024 02:08:34 +0200 Subject: [PATCH 166/266] llvm: Use unreachable in targetTriple() for targets without LLVM support. --- src/codegen/llvm.zig | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 6c78fcfe37..621490f7c7 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -81,12 +81,14 @@ pub fn targetTriple(allocator: Allocator, target: std.Target) ![]const u8 { .spirv => "spirv", .spirv32 => "spirv32", .spirv64 => "spirv64", - .kalimba => "kalimba", .lanai => "lanai", .wasm32 => "wasm32", .wasm64 => "wasm64", .ve => "ve", - .spu_2 => return error.@"LLVM backend does not support SPU Mark II", + + .kalimba, + .spu_2, + => unreachable, // Gated by hasLlvmSupport(). }; try llvm_triple.appendSlice(llvm_arch); try llvm_triple.appendSlice("-unknown-"); From 97643c1ecc9621b088807f446f06fe51e2db8fb6 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 29 Jul 2024 21:44:38 -0700 Subject: [PATCH 167/266] fuzzer: track code coverage from all runs When a unique run is encountered, track it in a bit set memory-mapped into the fuzz directory so it can be observed by other processes, even while the fuzzer is running. --- lib/compiler/test_runner.zig | 2 + lib/fuzzer.zig | 195 +++++++++++++++++++++++++++++++---- 2 files changed, 177 insertions(+), 20 deletions(-) diff --git a/lib/compiler/test_runner.zig b/lib/compiler/test_runner.zig index be793376a5..64f6f230fe 100644 --- a/lib/compiler/test_runner.zig +++ b/lib/compiler/test_runner.zig @@ -41,6 +41,7 @@ pub fn main() void { } fba.reset(); + if (builtin.fuzz) fuzzer_init(); if (listen) { return mainServer() catch @panic("internal test runner failure"); @@ -323,6 +324,7 @@ const FuzzerSlice = extern struct { var is_fuzz_test: bool = undefined; extern fn fuzzer_next() FuzzerSlice; +extern fn fuzzer_init() void; pub fn fuzzInput(options: testing.FuzzInputOptions) []const u8 { @disableInstrumentation(); diff --git a/lib/fuzzer.zig b/lib/fuzzer.zig index 60876e0bfb..4673bf6e31 100644 --- a/lib/fuzzer.zig +++ b/lib/fuzzer.zig @@ -2,6 +2,7 @@ const builtin = @import("builtin"); const std = @import("std"); const Allocator = std.mem.Allocator; const assert = std.debug.assert; +const fatal = std.process.fatal; pub const std_options = .{ .logFn = logOverride, @@ -17,7 +18,7 @@ fn logOverride( ) void { if (builtin.mode != .Debug) return; const f = if (log_file) |f| f else f: { - const f = std.fs.cwd().createFile("libfuzzer.log", .{}) catch @panic("failed to open fuzzer log file"); + const f = fuzzer.dir.createFile("libfuzzer.log", .{}) catch @panic("failed to open fuzzer log file"); log_file = f; break :f f; }; @@ -28,16 +29,17 @@ fn logOverride( export threadlocal var __sancov_lowest_stack: usize = 0; -export fn __sanitizer_cov_8bit_counters_init(start: [*]u8, stop: [*]u8) void { - std.log.debug("__sanitizer_cov_8bit_counters_init start={*}, stop={*}", .{ start, stop }); +var module_count_8bc: usize = 0; +var module_count_pcs: usize = 0; + +export fn __sanitizer_cov_8bit_counters_init(start: [*]u8, end: [*]u8) void { + assert(@atomicRmw(usize, &module_count_8bc, .Add, 1, .monotonic) == 0); + fuzzer.pc_counters = start[0 .. end - start]; } -export fn __sanitizer_cov_pcs_init(pc_start: [*]const usize, pc_end: [*]const usize) void { - std.log.debug("__sanitizer_cov_pcs_init pc_start={*}, pc_end={*}", .{ pc_start, pc_end }); - fuzzer.pc_range = .{ - .start = @intFromPtr(pc_start), - .end = @intFromPtr(pc_start), - }; +export fn __sanitizer_cov_pcs_init(start: [*]const Fuzzer.FlaggedPc, end: [*]const Fuzzer.FlaggedPc) void { + assert(@atomicRmw(usize, &module_count_pcs, .Add, 1, .monotonic) == 0); + fuzzer.flagged_pcs = start[0 .. end - start]; } export fn __sanitizer_cov_trace_const_cmp1(arg1: u8, arg2: u8) void { @@ -102,11 +104,25 @@ const Fuzzer = struct { gpa: Allocator, rng: std.Random.DefaultPrng, input: std.ArrayListUnmanaged(u8), - pc_range: PcRange, - count: usize, + flagged_pcs: []const FlaggedPc, + pc_counters: []u8, + n_runs: usize, recent_cases: RunMap, deduplicated_runs: usize, + /// Data collected from code coverage instrumentation from one execution of + /// the test function. coverage: Coverage, + /// Tracks which PCs have been seen across all runs that do not crash the fuzzer process. + /// Stored in a memory-mapped file so that it can be shared with other + /// processes and viewed while the fuzzer is running. + seen_pcs: MemoryMappedList, + dir: std.fs.Dir, + + const SeenPcsHeader = extern struct { + n_runs: usize, + pcs_len: usize, + lowest_stack: usize, + }; const RunMap = std.ArrayHashMapUnmanaged(Run, void, Run.HashContext, false); @@ -161,9 +177,12 @@ const Fuzzer = struct { } }; - const PcRange = struct { - start: usize, - end: usize, + const FlaggedPc = extern struct { + addr: usize, + flags: packed struct(usize) { + entry: bool, + _: @Type(.{ .Int = .{ .signedness = .unsigned, .bits = @bitSizeOf(usize) - 1 } }), + }, }; const Analysis = struct { @@ -171,6 +190,56 @@ const Fuzzer = struct { id: Run.Id, }; + fn init(f: *Fuzzer, dir: std.fs.Dir) !void { + f.dir = dir; + + // Layout of this file: + // - Header + // - list of PC addresses (usize elements) + // - list of hit flag, 1 bit per address (stored in u8 elements) + const coverage_file = dir.createFile("coverage", .{ + .read = true, + .truncate = false, + }) catch |err| fatal("unable to create coverage file: {s}", .{@errorName(err)}); + const flagged_pcs = f.flagged_pcs; + const n_bitset_elems = (flagged_pcs.len + 7) / 8; + const bytes_len = @sizeOf(SeenPcsHeader) + flagged_pcs.len * @sizeOf(usize) + n_bitset_elems; + const existing_len = coverage_file.getEndPos() catch |err| { + fatal("unable to check len of coverage file: {s}", .{@errorName(err)}); + }; + if (existing_len == 0) { + coverage_file.setEndPos(bytes_len) catch |err| { + fatal("unable to set len of coverage file: {s}", .{@errorName(err)}); + }; + } else if (existing_len != bytes_len) { + fatal("incompatible existing coverage file (differing lengths)", .{}); + } + f.seen_pcs = MemoryMappedList.init(coverage_file, existing_len, bytes_len) catch |err| { + fatal("unable to init coverage memory map: {s}", .{@errorName(err)}); + }; + if (existing_len != 0) { + const existing_pcs = std.mem.bytesAsSlice(usize, f.seen_pcs.items[@sizeOf(SeenPcsHeader)..][0 .. flagged_pcs.len * @sizeOf(usize)]); + for (existing_pcs, flagged_pcs, 0..) |old, new, i| { + if (old != new.addr) { + fatal("incompatible existing coverage file (differing PC at index {d}: {x} != {x})", .{ + i, old, new.addr, + }); + } + } + } else { + const header: SeenPcsHeader = .{ + .n_runs = 0, + .pcs_len = flagged_pcs.len, + .lowest_stack = std.math.maxInt(usize), + }; + f.seen_pcs.appendSliceAssumeCapacity(std.mem.asBytes(&header)); + for (flagged_pcs) |flagged_pc| { + f.seen_pcs.appendSliceAssumeCapacity(std.mem.asBytes(&flagged_pc.addr)); + } + f.seen_pcs.appendNTimesAssumeCapacity(0, n_bitset_elems); + } + } + fn analyzeLastRun(f: *Fuzzer) Analysis { return .{ .id = f.coverage.run_id_hasher.final(), @@ -194,7 +263,7 @@ const Fuzzer = struct { .score = 0, }, {}); } else { - if (f.count % 1000 == 0) f.dumpStats(); + if (f.n_runs % 1000 == 0) f.dumpStats(); const analysis = f.analyzeLastRun(); const gop = f.recent_cases.getOrPutAssumeCapacity(.{ @@ -217,6 +286,25 @@ const Fuzzer = struct { .input = try gpa.dupe(u8, f.input.items), .score = analysis.score, }; + + // Track code coverage from all runs. + { + const seen_pcs = f.seen_pcs.items[@sizeOf(SeenPcsHeader) + f.flagged_pcs.len * @sizeOf(usize) ..]; + for (seen_pcs, 0..) |*elem, i| { + const byte_i = i / 8; + const mask: u8 = + (@as(u8, @intFromBool(f.pc_counters[byte_i + 0] != 0)) << 0) | + (@as(u8, @intFromBool(f.pc_counters[byte_i + 1] != 0)) << 1) | + (@as(u8, @intFromBool(f.pc_counters[byte_i + 2] != 0)) << 2) | + (@as(u8, @intFromBool(f.pc_counters[byte_i + 3] != 0)) << 3) | + (@as(u8, @intFromBool(f.pc_counters[byte_i + 4] != 0)) << 4) | + (@as(u8, @intFromBool(f.pc_counters[byte_i + 5] != 0)) << 5) | + (@as(u8, @intFromBool(f.pc_counters[byte_i + 6] != 0)) << 6) | + (@as(u8, @intFromBool(f.pc_counters[byte_i + 7] != 0)) << 7); + + _ = @atomicRmw(u8, elem, .Or, mask, .monotonic); + } + } } if (f.recent_cases.entries.len >= 100) { @@ -244,8 +332,12 @@ const Fuzzer = struct { f.input.appendSliceAssumeCapacity(run.input); try f.mutate(); + f.n_runs += 1; + const header: *volatile SeenPcsHeader = @ptrCast(f.seen_pcs.items[0..@sizeOf(SeenPcsHeader)]); + _ = @atomicRmw(usize, &header.n_runs, .Add, 1, .monotonic); + _ = @atomicRmw(usize, &header.lowest_stack, .Min, __sancov_lowest_stack, .monotonic); + @memset(f.pc_counters, 0); f.coverage.reset(); - f.count += 1; return f.input.items; } @@ -257,8 +349,7 @@ const Fuzzer = struct { fn dumpStats(f: *Fuzzer) void { std.log.info("stats: runs={d} deduplicated={d}", .{ - f.count, - f.deduplicated_runs, + f.n_runs, f.deduplicated_runs, }); for (f.recent_cases.keys()[0..@min(f.recent_cases.entries.len, 5)], 0..) |run, i| { std.log.info("best[{d}] id={x} score={d} input: '{}'", .{ @@ -303,11 +394,14 @@ var fuzzer: Fuzzer = .{ .gpa = general_purpose_allocator.allocator(), .rng = std.Random.DefaultPrng.init(0), .input = .{}, - .pc_range = .{ .start = 0, .end = 0 }, - .count = 0, + .flagged_pcs = undefined, + .pc_counters = undefined, + .n_runs = 0, .deduplicated_runs = 0, .recent_cases = .{}, .coverage = undefined, + .dir = undefined, + .seen_pcs = undefined, }; export fn fuzzer_next() Fuzzer.Slice { @@ -315,3 +409,64 @@ export fn fuzzer_next() Fuzzer.Slice { error.OutOfMemory => @panic("out of memory"), }); } + +export fn fuzzer_init() void { + if (module_count_8bc == 0) fatal("__sanitizer_cov_8bit_counters_init was never called", .{}); + if (module_count_pcs == 0) fatal("__sanitizer_cov_pcs_init was never called", .{}); + + // TODO: move this to .zig-cache/f + const fuzz_dir = std.fs.cwd().makeOpenPath("f", .{ .iterate = true }) catch |err| { + fatal("unable to open fuzz directory 'f': {s}", .{@errorName(err)}); + }; + fuzzer.init(fuzz_dir) catch |err| fatal("unable to init fuzzer: {s}", .{@errorName(err)}); +} + +/// Like `std.ArrayListUnmanaged(u8)` but backed by memory mapping. +pub const MemoryMappedList = struct { + /// Contents of the list. + /// + /// Pointers to elements in this slice are invalidated by various functions + /// of this ArrayList in accordance with the respective documentation. In + /// all cases, "invalidated" means that the memory has been passed to this + /// allocator's resize or free function. + items: []align(std.mem.page_size) volatile u8, + /// How many bytes this list can hold without allocating additional memory. + capacity: usize, + + pub fn init(file: std.fs.File, length: usize, capacity: usize) !MemoryMappedList { + const ptr = try std.posix.mmap( + null, + capacity, + std.posix.PROT.READ | std.posix.PROT.WRITE, + .{ .TYPE = .SHARED }, + file.handle, + 0, + ); + return .{ + .items = ptr[0..length], + .capacity = capacity, + }; + } + + /// Append the slice of items to the list. + /// Asserts that the list can hold the additional items. + pub fn appendSliceAssumeCapacity(l: *MemoryMappedList, items: []const u8) void { + const old_len = l.items.len; + const new_len = old_len + items.len; + assert(new_len <= l.capacity); + l.items.len = new_len; + @memcpy(l.items[old_len..][0..items.len], items); + } + + /// Append a value to the list `n` times. + /// Never invalidates element pointers. + /// The function is inline so that a comptime-known `value` parameter will + /// have better memset codegen in case it has a repeated byte pattern. + /// Asserts that the list can hold the additional items. + pub inline fn appendNTimesAssumeCapacity(l: *MemoryMappedList, value: u8, n: usize) void { + const new_len = l.items.len + n; + assert(new_len <= l.capacity); + @memset(l.items.ptr[l.items.len..new_len], value); + l.items.len = new_len; + } +}; From ffc050e0557c5d951cd293ca365ed0cd3cdf83db Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 29 Jul 2024 21:59:23 -0700 Subject: [PATCH 168/266] fuzzer: log errors and move deduplicated runs to shared mem --- lib/fuzzer.zig | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/lib/fuzzer.zig b/lib/fuzzer.zig index 4673bf6e31..7edab785a9 100644 --- a/lib/fuzzer.zig +++ b/lib/fuzzer.zig @@ -16,7 +16,6 @@ fn logOverride( comptime format: []const u8, args: anytype, ) void { - if (builtin.mode != .Debug) return; const f = if (log_file) |f| f else f: { const f = fuzzer.dir.createFile("libfuzzer.log", .{}) catch @panic("failed to open fuzzer log file"); log_file = f; @@ -27,7 +26,7 @@ fn logOverride( f.writer().print(prefix1 ++ prefix2 ++ format ++ "\n", args) catch @panic("failed to write to fuzzer log"); } -export threadlocal var __sancov_lowest_stack: usize = 0; +export threadlocal var __sancov_lowest_stack: usize = std.math.maxInt(usize); var module_count_8bc: usize = 0; var module_count_pcs: usize = 0; @@ -108,7 +107,6 @@ const Fuzzer = struct { pc_counters: []u8, n_runs: usize, recent_cases: RunMap, - deduplicated_runs: usize, /// Data collected from code coverage instrumentation from one execution of /// the test function. coverage: Coverage, @@ -120,6 +118,7 @@ const Fuzzer = struct { const SeenPcsHeader = extern struct { n_runs: usize, + deduplicated_runs: usize, pcs_len: usize, lowest_stack: usize, }; @@ -229,6 +228,7 @@ const Fuzzer = struct { } else { const header: SeenPcsHeader = .{ .n_runs = 0, + .deduplicated_runs = 0, .pcs_len = flagged_pcs.len, .lowest_stack = std.math.maxInt(usize), }; @@ -273,7 +273,8 @@ const Fuzzer = struct { }); if (gop.found_existing) { //std.log.info("duplicate analysis: score={d} id={d}", .{ analysis.score, analysis.id }); - f.deduplicated_runs += 1; + const header: *volatile SeenPcsHeader = @ptrCast(f.seen_pcs.items[0..@sizeOf(SeenPcsHeader)]); + _ = @atomicRmw(usize, &header.deduplicated_runs, .Add, 1, .monotonic); if (f.input.items.len < gop.key_ptr.input.len or gop.key_ptr.score == 0) { gpa.free(gop.key_ptr.input); gop.key_ptr.input = try gpa.dupe(u8, f.input.items); @@ -348,9 +349,6 @@ const Fuzzer = struct { } fn dumpStats(f: *Fuzzer) void { - std.log.info("stats: runs={d} deduplicated={d}", .{ - f.n_runs, f.deduplicated_runs, - }); for (f.recent_cases.keys()[0..@min(f.recent_cases.entries.len, 5)], 0..) |run, i| { std.log.info("best[{d}] id={x} score={d} input: '{}'", .{ i, run.id, run.score, std.zig.fmtEscapes(run.input), @@ -397,7 +395,6 @@ var fuzzer: Fuzzer = .{ .flagged_pcs = undefined, .pc_counters = undefined, .n_runs = 0, - .deduplicated_runs = 0, .recent_cases = .{}, .coverage = undefined, .dir = undefined, From e0ffac4e3c2271a617616760f3084f5f01fb0785 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 31 Jul 2024 22:54:05 -0700 Subject: [PATCH 169/266] introduce a web interface for fuzzing * new .zig-cache subdirectory: 'v' - stores coverage information with filename of hash of PCs that want coverage. This hash is a hex encoding of the 64-bit coverage ID. * build runner * fixed bug in file system inputs when a compile step has an overridden zig_lib_dir field set. * set some std lib options optimized for the build runner - no side channel mitigations - no Transport Layer Security - no crypto fork safety * add a --port CLI arg for choosing the port the fuzzing web interface listens on. it defaults to choosing a random open port. * introduce a web server, and serve a basic single page application - shares wasm code with autodocs - assets are created live on request, for convenient development experience. main.wasm is properly cached if nothing changes. - sources.tar comes from file system inputs (introduced with the `--watch` feature) * receives coverage ID from test runner and sends it on a thread-safe queue to the WebServer. * test runner - takes a zig cache directory argument now, for where to put coverage information. - sends coverage ID to parent process * fuzzer - puts its logs (in debug mode) in .zig-cache/tmp/libfuzzer.log - computes coverage_id and makes it available with `fuzzer_coverage_id` exported function. - the memory-mapped coverage file is now namespaced by the coverage id in hex encoding, in `.zig-cache/v` * tokenizer - add a fuzz test to check that several properties are upheld --- lib/compiler/build_runner.zig | 29 ++- lib/compiler/test_runner.zig | 22 +- lib/docs/wasm/main.zig | 4 +- lib/fuzzer.zig | 73 ++++-- lib/fuzzer/index.html | 76 ++++++ lib/fuzzer/main.js | 40 +++ lib/fuzzer/wasm/main.zig | 99 ++++++++ lib/std/Build.zig | 12 +- lib/std/Build/Fuzz.zig | 455 ++++++++++++++++++++++++++++++++-- lib/std/Build/Step.zig | 3 +- lib/std/Build/Step/Run.zig | 52 ++-- lib/std/zig/Server.zig | 24 +- lib/std/zig/tokenizer.zig | 45 ++++ 13 files changed, 872 insertions(+), 62 deletions(-) create mode 100644 lib/fuzzer/index.html create mode 100644 lib/fuzzer/main.js create mode 100644 lib/fuzzer/wasm/main.zig diff --git a/lib/compiler/build_runner.zig b/lib/compiler/build_runner.zig index 8bb03939dc..76be8610ef 100644 --- a/lib/compiler/build_runner.zig +++ b/lib/compiler/build_runner.zig @@ -17,6 +17,12 @@ const runner = @This(); pub const root = @import("@build"); pub const dependencies = @import("@dependencies"); +pub const std_options: std.Options = .{ + .side_channels_mitigations = .none, + .http_disable_tls = true, + .crypto_fork_safety = false, +}; + pub fn main() !void { // Here we use an ArenaAllocator backed by a page allocator because a build is a short-lived, // one shot program. We don't need to waste time freeing memory and finding places to squish @@ -106,6 +112,7 @@ pub fn main() !void { var watch = false; var fuzz = false; var debounce_interval_ms: u16 = 50; + var listen_port: u16 = 0; while (nextArg(args, &arg_idx)) |arg| { if (mem.startsWith(u8, arg, "-Z")) { @@ -203,6 +210,14 @@ pub fn main() !void { next_arg, @errorName(err), }); }; + } else if (mem.eql(u8, arg, "--port")) { + const next_arg = nextArg(args, &arg_idx) orelse + fatalWithHint("expected u16 after '{s}'", .{arg}); + listen_port = std.fmt.parseUnsigned(u16, next_arg, 10) catch |err| { + fatal("unable to parse port '{s}' as unsigned 16-bit integer: {s}\n", .{ + next_arg, @errorName(err), + }); + }; } else if (mem.eql(u8, arg, "--debug-log")) { const next_arg = nextArgOrFatal(args, &arg_idx); try debug_log_scopes.append(next_arg); @@ -403,7 +418,19 @@ pub fn main() !void { else => return err, }; if (fuzz) { - Fuzz.start(&run.thread_pool, run.step_stack.keys(), run.ttyconf, main_progress_node); + const listen_address = std.net.Address.parseIp("127.0.0.1", listen_port) catch unreachable; + try Fuzz.start( + gpa, + arena, + global_cache_directory, + zig_lib_directory, + zig_exe, + &run.thread_pool, + run.step_stack.keys(), + run.ttyconf, + listen_address, + main_progress_node, + ); } if (!watch) return cleanExit(); diff --git a/lib/compiler/test_runner.zig b/lib/compiler/test_runner.zig index 64f6f230fe..65580fcd3f 100644 --- a/lib/compiler/test_runner.zig +++ b/lib/compiler/test_runner.zig @@ -28,6 +28,7 @@ pub fn main() void { @panic("unable to parse command line args"); var listen = false; + var opt_cache_dir: ?[]const u8 = null; for (args[1..]) |arg| { if (std.mem.eql(u8, arg, "--listen=-")) { @@ -35,13 +36,18 @@ pub fn main() void { } else if (std.mem.startsWith(u8, arg, "--seed=")) { testing.random_seed = std.fmt.parseUnsigned(u32, arg["--seed=".len..], 0) catch @panic("unable to parse --seed command line argument"); + } else if (std.mem.startsWith(u8, arg, "--cache-dir")) { + opt_cache_dir = arg["--cache-dir=".len..]; } else { @panic("unrecognized command line argument"); } } fba.reset(); - if (builtin.fuzz) fuzzer_init(); + if (builtin.fuzz) { + const cache_dir = opt_cache_dir orelse @panic("missing --cache-dir=[path] argument"); + fuzzer_init(FuzzerSlice.fromSlice(cache_dir)); + } if (listen) { return mainServer() catch @panic("internal test runner failure"); @@ -60,6 +66,11 @@ fn mainServer() !void { }); defer server.deinit(); + if (builtin.fuzz) { + const coverage_id = fuzzer_coverage_id(); + try server.serveU64Message(.coverage_id, coverage_id); + } + while (true) { const hdr = try server.receiveMessage(); switch (hdr.tag) { @@ -316,15 +327,22 @@ const FuzzerSlice = extern struct { ptr: [*]const u8, len: usize, + /// Inline to avoid fuzzer instrumentation. inline fn toSlice(s: FuzzerSlice) []const u8 { return s.ptr[0..s.len]; } + + /// Inline to avoid fuzzer instrumentation. + inline fn fromSlice(s: []const u8) FuzzerSlice { + return .{ .ptr = s.ptr, .len = s.len }; + } }; var is_fuzz_test: bool = undefined; extern fn fuzzer_next() FuzzerSlice; -extern fn fuzzer_init() void; +extern fn fuzzer_init(cache_dir: FuzzerSlice) void; +extern fn fuzzer_coverage_id() u64; pub fn fuzzInput(options: testing.FuzzInputOptions) []const u8 { @disableInstrumentation(); diff --git a/lib/docs/wasm/main.zig b/lib/docs/wasm/main.zig index f5ce02d7d6..214f28c24b 100644 --- a/lib/docs/wasm/main.zig +++ b/lib/docs/wasm/main.zig @@ -53,7 +53,7 @@ export fn unpack(tar_ptr: [*]u8, tar_len: usize) void { const tar_bytes = tar_ptr[0..tar_len]; //log.debug("received {d} bytes of tar file", .{tar_bytes.len}); - unpack_inner(tar_bytes) catch |err| { + unpackInner(tar_bytes) catch |err| { fatal("unable to unpack tar: {s}", .{@errorName(err)}); }; } @@ -750,7 +750,7 @@ export fn decl_type_html(decl_index: Decl.Index) String { const Oom = error{OutOfMemory}; -fn unpack_inner(tar_bytes: []u8) !void { +fn unpackInner(tar_bytes: []u8) !void { var fbs = std.io.fixedBufferStream(tar_bytes); var file_name_buffer: [1024]u8 = undefined; var link_name_buffer: [1024]u8 = undefined; diff --git a/lib/fuzzer.zig b/lib/fuzzer.zig index 7edab785a9..ede3663cdc 100644 --- a/lib/fuzzer.zig +++ b/lib/fuzzer.zig @@ -17,7 +17,8 @@ fn logOverride( args: anytype, ) void { const f = if (log_file) |f| f else f: { - const f = fuzzer.dir.createFile("libfuzzer.log", .{}) catch @panic("failed to open fuzzer log file"); + const f = fuzzer.cache_dir.createFile("tmp/libfuzzer.log", .{}) catch + @panic("failed to open fuzzer log file"); log_file = f; break :f f; }; @@ -114,7 +115,10 @@ const Fuzzer = struct { /// Stored in a memory-mapped file so that it can be shared with other /// processes and viewed while the fuzzer is running. seen_pcs: MemoryMappedList, - dir: std.fs.Dir, + cache_dir: std.fs.Dir, + /// Identifies the file name that will be used to store coverage + /// information, available to other processes. + coverage_id: u64, const SeenPcsHeader = extern struct { n_runs: usize, @@ -189,18 +193,31 @@ const Fuzzer = struct { id: Run.Id, }; - fn init(f: *Fuzzer, dir: std.fs.Dir) !void { - f.dir = dir; + fn init(f: *Fuzzer, cache_dir: std.fs.Dir) !void { + const flagged_pcs = f.flagged_pcs; + + f.cache_dir = cache_dir; + + // Choose a file name for the coverage based on a hash of the PCs that will be stored within. + const pc_digest = d: { + var hasher = std.hash.Wyhash.init(0); + for (flagged_pcs) |flagged_pc| { + hasher.update(std.mem.asBytes(&flagged_pc.addr)); + } + break :d f.coverage.run_id_hasher.final(); + }; + f.coverage_id = pc_digest; + const hex_digest = std.fmt.hex(pc_digest); + const coverage_file_path = "v/" ++ hex_digest; // Layout of this file: // - Header // - list of PC addresses (usize elements) // - list of hit flag, 1 bit per address (stored in u8 elements) - const coverage_file = dir.createFile("coverage", .{ + const coverage_file = createFileBail(cache_dir, coverage_file_path, .{ .read = true, .truncate = false, - }) catch |err| fatal("unable to create coverage file: {s}", .{@errorName(err)}); - const flagged_pcs = f.flagged_pcs; + }); const n_bitset_elems = (flagged_pcs.len + 7) / 8; const bytes_len = @sizeOf(SeenPcsHeader) + flagged_pcs.len * @sizeOf(usize) + n_bitset_elems; const existing_len = coverage_file.getEndPos() catch |err| { @@ -217,7 +234,8 @@ const Fuzzer = struct { fatal("unable to init coverage memory map: {s}", .{@errorName(err)}); }; if (existing_len != 0) { - const existing_pcs = std.mem.bytesAsSlice(usize, f.seen_pcs.items[@sizeOf(SeenPcsHeader)..][0 .. flagged_pcs.len * @sizeOf(usize)]); + const existing_pcs_bytes = f.seen_pcs.items[@sizeOf(SeenPcsHeader)..][0 .. flagged_pcs.len * @sizeOf(usize)]; + const existing_pcs = std.mem.bytesAsSlice(usize, existing_pcs_bytes); for (existing_pcs, flagged_pcs, 0..) |old, new, i| { if (old != new.addr) { fatal("incompatible existing coverage file (differing PC at index {d}: {x} != {x})", .{ @@ -380,6 +398,21 @@ const Fuzzer = struct { } }; +fn createFileBail(dir: std.fs.Dir, sub_path: []const u8, flags: std.fs.File.CreateFlags) std.fs.File { + return dir.createFile(sub_path, flags) catch |err| switch (err) { + error.FileNotFound => { + const dir_name = std.fs.path.dirname(sub_path).?; + dir.makePath(dir_name) catch |e| { + fatal("unable to make path '{s}': {s}", .{ dir_name, @errorName(e) }); + }; + return dir.createFile(sub_path, flags) catch |e| { + fatal("unable to create file '{s}': {s}", .{ sub_path, @errorName(e) }); + }; + }, + else => fatal("unable to create file '{s}': {s}", .{ sub_path, @errorName(err) }), + }; +} + fn oom(err: anytype) noreturn { switch (err) { error.OutOfMemory => @panic("out of memory"), @@ -397,25 +430,35 @@ var fuzzer: Fuzzer = .{ .n_runs = 0, .recent_cases = .{}, .coverage = undefined, - .dir = undefined, + .cache_dir = undefined, .seen_pcs = undefined, + .coverage_id = undefined, }; +/// Invalid until `fuzzer_init` is called. +export fn fuzzer_coverage_id() u64 { + return fuzzer.coverage_id; +} + export fn fuzzer_next() Fuzzer.Slice { return Fuzzer.Slice.fromZig(fuzzer.next() catch |err| switch (err) { error.OutOfMemory => @panic("out of memory"), }); } -export fn fuzzer_init() void { +export fn fuzzer_init(cache_dir_struct: Fuzzer.Slice) void { if (module_count_8bc == 0) fatal("__sanitizer_cov_8bit_counters_init was never called", .{}); if (module_count_pcs == 0) fatal("__sanitizer_cov_pcs_init was never called", .{}); - // TODO: move this to .zig-cache/f - const fuzz_dir = std.fs.cwd().makeOpenPath("f", .{ .iterate = true }) catch |err| { - fatal("unable to open fuzz directory 'f': {s}", .{@errorName(err)}); - }; - fuzzer.init(fuzz_dir) catch |err| fatal("unable to init fuzzer: {s}", .{@errorName(err)}); + const cache_dir_path = cache_dir_struct.toZig(); + const cache_dir = if (cache_dir_path.len == 0) + std.fs.cwd() + else + std.fs.cwd().makeOpenPath(cache_dir_path, .{ .iterate = true }) catch |err| { + fatal("unable to open fuzz directory '{s}': {s}", .{ cache_dir_path, @errorName(err) }); + }; + + fuzzer.init(cache_dir) catch |err| fatal("unable to init fuzzer: {s}", .{@errorName(err)}); } /// Like `std.ArrayListUnmanaged(u8)` but backed by memory mapping. diff --git a/lib/fuzzer/index.html b/lib/fuzzer/index.html new file mode 100644 index 0000000000..c1ef059ad6 --- /dev/null +++ b/lib/fuzzer/index.html @@ -0,0 +1,76 @@ + + + + + Zig Documentation + + + + + + + diff --git a/lib/fuzzer/main.js b/lib/fuzzer/main.js new file mode 100644 index 0000000000..9b0d4cd8c3 --- /dev/null +++ b/lib/fuzzer/main.js @@ -0,0 +1,40 @@ +(function() { + let wasm_promise = fetch("main.wasm"); + let sources_promise = fetch("sources.tar").then(function(response) { + if (!response.ok) throw new Error("unable to download sources"); + return response.arrayBuffer(); + }); + var wasm_exports = null; + + const text_decoder = new TextDecoder(); + const text_encoder = new TextEncoder(); + + WebAssembly.instantiateStreaming(wasm_promise, { + js: { + log: function(ptr, len) { + const msg = decodeString(ptr, len); + console.log(msg); + }, + panic: function (ptr, len) { + const msg = decodeString(ptr, len); + throw new Error("panic: " + msg); + }, + }, + }).then(function(obj) { + wasm_exports = obj.instance.exports; + window.wasm = obj; // for debugging + + sources_promise.then(function(buffer) { + const js_array = new Uint8Array(buffer); + const ptr = wasm_exports.alloc(js_array.length); + const wasm_array = new Uint8Array(wasm_exports.memory.buffer, ptr, js_array.length); + wasm_array.set(js_array); + wasm_exports.unpack(ptr, js_array.length); + }); + }); + + function decodeString(ptr, len) { + if (len === 0) return ""; + return text_decoder.decode(new Uint8Array(wasm_exports.memory.buffer, ptr, len)); + } +})(); diff --git a/lib/fuzzer/wasm/main.zig b/lib/fuzzer/wasm/main.zig new file mode 100644 index 0000000000..09b9d81068 --- /dev/null +++ b/lib/fuzzer/wasm/main.zig @@ -0,0 +1,99 @@ +const std = @import("std"); +const assert = std.debug.assert; + +const Walk = @import("Walk"); + +const gpa = std.heap.wasm_allocator; +const log = std.log; + +const js = struct { + extern "js" fn log(ptr: [*]const u8, len: usize) void; + extern "js" fn panic(ptr: [*]const u8, len: usize) noreturn; +}; + +pub const std_options: std.Options = .{ + .logFn = logFn, +}; + +pub fn panic(msg: []const u8, st: ?*std.builtin.StackTrace, addr: ?usize) noreturn { + _ = st; + _ = addr; + log.err("panic: {s}", .{msg}); + @trap(); +} + +fn logFn( + comptime message_level: log.Level, + comptime scope: @TypeOf(.enum_literal), + comptime format: []const u8, + args: anytype, +) void { + const level_txt = comptime message_level.asText(); + const prefix2 = if (scope == .default) ": " else "(" ++ @tagName(scope) ++ "): "; + var buf: [500]u8 = undefined; + const line = std.fmt.bufPrint(&buf, level_txt ++ prefix2 ++ format, args) catch l: { + buf[buf.len - 3 ..][0..3].* = "...".*; + break :l &buf; + }; + js.log(line.ptr, line.len); +} + +export fn alloc(n: usize) [*]u8 { + const slice = gpa.alloc(u8, n) catch @panic("OOM"); + return slice.ptr; +} + +export fn unpack(tar_ptr: [*]u8, tar_len: usize) void { + const tar_bytes = tar_ptr[0..tar_len]; + log.debug("received {d} bytes of tar file", .{tar_bytes.len}); + + unpackInner(tar_bytes) catch |err| { + fatal("unable to unpack tar: {s}", .{@errorName(err)}); + }; +} + +fn unpackInner(tar_bytes: []u8) !void { + var fbs = std.io.fixedBufferStream(tar_bytes); + var file_name_buffer: [1024]u8 = undefined; + var link_name_buffer: [1024]u8 = undefined; + var it = std.tar.iterator(fbs.reader(), .{ + .file_name_buffer = &file_name_buffer, + .link_name_buffer = &link_name_buffer, + }); + while (try it.next()) |tar_file| { + switch (tar_file.kind) { + .file => { + if (tar_file.size == 0 and tar_file.name.len == 0) break; + if (std.mem.endsWith(u8, tar_file.name, ".zig")) { + log.debug("found file: '{s}'", .{tar_file.name}); + const file_name = try gpa.dupe(u8, tar_file.name); + if (std.mem.indexOfScalar(u8, file_name, '/')) |pkg_name_end| { + const pkg_name = file_name[0..pkg_name_end]; + const gop = try Walk.modules.getOrPut(gpa, pkg_name); + const file: Walk.File.Index = @enumFromInt(Walk.files.entries.len); + if (!gop.found_existing or + std.mem.eql(u8, file_name[pkg_name_end..], "/root.zig") or + std.mem.eql(u8, file_name[pkg_name_end + 1 .. file_name.len - ".zig".len], pkg_name)) + { + gop.value_ptr.* = file; + } + const file_bytes = tar_bytes[fbs.pos..][0..@intCast(tar_file.size)]; + assert(file == try Walk.add_file(file_name, file_bytes)); + } + } else { + log.warn("skipping: '{s}' - the tar creation should have done that", .{tar_file.name}); + } + }, + else => continue, + } + } +} + +fn fatal(comptime format: []const u8, args: anytype) noreturn { + var buf: [500]u8 = undefined; + const line = std.fmt.bufPrint(&buf, format, args) catch l: { + buf[buf.len - 3 ..][0..3].* = "...".*; + break :l &buf; + }; + js.panic(line.ptr, line.len); +} diff --git a/lib/std/Build.zig b/lib/std/Build.zig index 7612ad0d6d..03743cf52e 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -2300,22 +2300,26 @@ pub const LazyPath = union(enum) { } pub fn path(lazy_path: LazyPath, b: *Build, sub_path: []const u8) LazyPath { + return lazy_path.join(b.allocator, sub_path) catch @panic("OOM"); + } + + pub fn join(lazy_path: LazyPath, arena: Allocator, sub_path: []const u8) Allocator.Error!LazyPath { return switch (lazy_path) { .src_path => |src| .{ .src_path = .{ .owner = src.owner, - .sub_path = b.pathResolve(&.{ src.sub_path, sub_path }), + .sub_path = try fs.path.resolve(arena, &.{ src.sub_path, sub_path }), } }, .generated => |gen| .{ .generated = .{ .file = gen.file, .up = gen.up, - .sub_path = b.pathResolve(&.{ gen.sub_path, sub_path }), + .sub_path = try fs.path.resolve(arena, &.{ gen.sub_path, sub_path }), } }, .cwd_relative => |cwd_relative| .{ - .cwd_relative = b.pathResolve(&.{ cwd_relative, sub_path }), + .cwd_relative = try fs.path.resolve(arena, &.{ cwd_relative, sub_path }), }, .dependency => |dep| .{ .dependency = .{ .dependency = dep.dependency, - .sub_path = b.pathResolve(&.{ dep.sub_path, sub_path }), + .sub_path = try fs.path.resolve(arena, &.{ dep.sub_path, sub_path }), } }, }; } diff --git a/lib/std/Build/Fuzz.zig b/lib/std/Build/Fuzz.zig index 2628b92516..e26f587eac 100644 --- a/lib/std/Build/Fuzz.zig +++ b/lib/std/Build/Fuzz.zig @@ -1,59 +1,479 @@ +const builtin = @import("builtin"); const std = @import("../std.zig"); -const Fuzz = @This(); +const Build = std.Build; const Step = std.Build.Step; const assert = std.debug.assert; const fatal = std.process.fatal; +const Allocator = std.mem.Allocator; +const log = std.log; + +const Fuzz = @This(); const build_runner = @import("root"); pub fn start( + gpa: Allocator, + arena: Allocator, + global_cache_directory: Build.Cache.Directory, + zig_lib_directory: Build.Cache.Directory, + zig_exe_path: []const u8, thread_pool: *std.Thread.Pool, all_steps: []const *Step, ttyconf: std.io.tty.Config, + listen_address: std.net.Address, prog_node: std.Progress.Node, -) void { - const count = block: { +) Allocator.Error!void { + const fuzz_run_steps = block: { const rebuild_node = prog_node.start("Rebuilding Unit Tests", 0); defer rebuild_node.end(); - var count: usize = 0; var wait_group: std.Thread.WaitGroup = .{}; defer wait_group.wait(); + var fuzz_run_steps: std.ArrayListUnmanaged(*Step.Run) = .{}; + defer fuzz_run_steps.deinit(gpa); for (all_steps) |step| { const run = step.cast(Step.Run) orelse continue; if (run.fuzz_tests.items.len > 0 and run.producer != null) { thread_pool.spawnWg(&wait_group, rebuildTestsWorkerRun, .{ run, ttyconf, rebuild_node }); - count += 1; + try fuzz_run_steps.append(gpa, run); } } - if (count == 0) fatal("no fuzz tests found", .{}); - rebuild_node.setEstimatedTotalItems(count); - break :block count; + if (fuzz_run_steps.items.len == 0) fatal("no fuzz tests found", .{}); + rebuild_node.setEstimatedTotalItems(fuzz_run_steps.items.len); + break :block try arena.dupe(*Step.Run, fuzz_run_steps.items); }; // Detect failure. - for (all_steps) |step| { - const run = step.cast(Step.Run) orelse continue; - if (run.fuzz_tests.items.len > 0 and run.rebuilt_executable == null) + for (fuzz_run_steps) |run| { + assert(run.fuzz_tests.items.len > 0); + if (run.rebuilt_executable == null) fatal("one or more unit tests failed to be rebuilt in fuzz mode", .{}); } + var web_server: WebServer = .{ + .gpa = gpa, + .global_cache_directory = global_cache_directory, + .zig_lib_directory = zig_lib_directory, + .zig_exe_path = zig_exe_path, + .msg_queue = .{}, + .mutex = .{}, + .listen_address = listen_address, + .fuzz_run_steps = fuzz_run_steps, + }; + + const web_server_thread = std.Thread.spawn(.{}, WebServer.run, .{&web_server}) catch |err| { + fatal("unable to spawn web server thread: {s}", .{@errorName(err)}); + }; + defer web_server_thread.join(); + { - const fuzz_node = prog_node.start("Fuzzing", count); + const fuzz_node = prog_node.start("Fuzzing", fuzz_run_steps.len); defer fuzz_node.end(); var wait_group: std.Thread.WaitGroup = .{}; defer wait_group.wait(); - for (all_steps) |step| { - const run = step.cast(Step.Run) orelse continue; + for (fuzz_run_steps) |run| { for (run.fuzz_tests.items) |unit_test_index| { assert(run.rebuilt_executable != null); - thread_pool.spawnWg(&wait_group, fuzzWorkerRun, .{ run, unit_test_index, ttyconf, fuzz_node }); + thread_pool.spawnWg(&wait_group, fuzzWorkerRun, .{ + run, &web_server, unit_test_index, ttyconf, fuzz_node, + }); } } } - fatal("all fuzz workers crashed", .{}); + log.err("all fuzz workers crashed", .{}); } +pub const WebServer = struct { + gpa: Allocator, + global_cache_directory: Build.Cache.Directory, + zig_lib_directory: Build.Cache.Directory, + zig_exe_path: []const u8, + /// Messages from fuzz workers. Protected by mutex. + msg_queue: std.ArrayListUnmanaged(Msg), + mutex: std.Thread.Mutex, + listen_address: std.net.Address, + fuzz_run_steps: []const *Step.Run, + + const Msg = union(enum) { + coverage_id: u64, + }; + + fn run(ws: *WebServer) void { + var http_server = ws.listen_address.listen(.{ + .reuse_address = true, + }) catch |err| { + log.err("failed to listen to port {d}: {s}", .{ ws.listen_address.in.getPort(), @errorName(err) }); + return; + }; + const port = http_server.listen_address.in.getPort(); + log.info("web interface listening at http://127.0.0.1:{d}/", .{port}); + + while (true) { + const connection = http_server.accept() catch |err| { + log.err("failed to accept connection: {s}", .{@errorName(err)}); + return; + }; + _ = std.Thread.spawn(.{}, accept, .{ ws, connection }) catch |err| { + log.err("unable to spawn connection thread: {s}", .{@errorName(err)}); + connection.stream.close(); + continue; + }; + } + } + + fn accept(ws: *WebServer, connection: std.net.Server.Connection) void { + defer connection.stream.close(); + + var read_buffer: [8000]u8 = undefined; + var server = std.http.Server.init(connection, &read_buffer); + while (server.state == .ready) { + var request = server.receiveHead() catch |err| switch (err) { + error.HttpConnectionClosing => return, + else => { + log.err("closing http connection: {s}", .{@errorName(err)}); + return; + }, + }; + serveRequest(ws, &request) catch |err| switch (err) { + error.AlreadyReported => return, + else => |e| { + log.err("unable to serve {s}: {s}", .{ request.head.target, @errorName(e) }); + return; + }, + }; + } + } + + fn serveRequest(ws: *WebServer, request: *std.http.Server.Request) !void { + if (std.mem.eql(u8, request.head.target, "/") or + std.mem.eql(u8, request.head.target, "/debug") or + std.mem.eql(u8, request.head.target, "/debug/")) + { + try serveFile(ws, request, "fuzzer/index.html", "text/html"); + } else if (std.mem.eql(u8, request.head.target, "/main.js") or + std.mem.eql(u8, request.head.target, "/debug/main.js")) + { + try serveFile(ws, request, "fuzzer/main.js", "application/javascript"); + } else if (std.mem.eql(u8, request.head.target, "/main.wasm")) { + try serveWasm(ws, request, .ReleaseFast); + } else if (std.mem.eql(u8, request.head.target, "/debug/main.wasm")) { + try serveWasm(ws, request, .Debug); + } else if (std.mem.eql(u8, request.head.target, "/sources.tar") or + std.mem.eql(u8, request.head.target, "/debug/sources.tar")) + { + try serveSourcesTar(ws, request); + } else { + try request.respond("not found", .{ + .status = .not_found, + .extra_headers = &.{ + .{ .name = "content-type", .value = "text/plain" }, + }, + }); + } + } + + fn serveFile( + ws: *WebServer, + request: *std.http.Server.Request, + name: []const u8, + content_type: []const u8, + ) !void { + const gpa = ws.gpa; + // The desired API is actually sendfile, which will require enhancing std.http.Server. + // We load the file with every request so that the user can make changes to the file + // and refresh the HTML page without restarting this server. + const file_contents = ws.zig_lib_directory.handle.readFileAlloc(gpa, name, 10 * 1024 * 1024) catch |err| { + log.err("failed to read '{}{s}': {s}", .{ ws.zig_lib_directory, name, @errorName(err) }); + return error.AlreadyReported; + }; + defer gpa.free(file_contents); + try request.respond(file_contents, .{ + .extra_headers = &.{ + .{ .name = "content-type", .value = content_type }, + cache_control_header, + }, + }); + } + + fn serveWasm( + ws: *WebServer, + request: *std.http.Server.Request, + optimize_mode: std.builtin.OptimizeMode, + ) !void { + const gpa = ws.gpa; + + var arena_instance = std.heap.ArenaAllocator.init(gpa); + defer arena_instance.deinit(); + const arena = arena_instance.allocator(); + + // Do the compilation every request, so that the user can edit the files + // and see the changes without restarting the server. + const wasm_binary_path = try buildWasmBinary(ws, arena, optimize_mode); + // std.http.Server does not have a sendfile API yet. + const file_contents = try std.fs.cwd().readFileAlloc(gpa, wasm_binary_path, 10 * 1024 * 1024); + defer gpa.free(file_contents); + try request.respond(file_contents, .{ + .extra_headers = &.{ + .{ .name = "content-type", .value = "application/wasm" }, + cache_control_header, + }, + }); + } + + fn buildWasmBinary( + ws: *WebServer, + arena: Allocator, + optimize_mode: std.builtin.OptimizeMode, + ) ![]const u8 { + const gpa = ws.gpa; + + const main_src_path: Build.Cache.Path = .{ + .root_dir = ws.zig_lib_directory, + .sub_path = "fuzzer/wasm/main.zig", + }; + const walk_src_path: Build.Cache.Path = .{ + .root_dir = ws.zig_lib_directory, + .sub_path = "docs/wasm/Walk.zig", + }; + + var argv: std.ArrayListUnmanaged([]const u8) = .{}; + + try argv.appendSlice(arena, &.{ + ws.zig_exe_path, + "build-exe", + "-fno-entry", + "-O", + @tagName(optimize_mode), + "-target", + "wasm32-freestanding", + "-mcpu", + "baseline+atomics+bulk_memory+multivalue+mutable_globals+nontrapping_fptoint+reference_types+sign_ext", + "--cache-dir", + ws.global_cache_directory.path orelse ".", + "--global-cache-dir", + ws.global_cache_directory.path orelse ".", + "--name", + "fuzzer", + "-rdynamic", + "--dep", + "Walk", + try std.fmt.allocPrint(arena, "-Mroot={}", .{main_src_path}), + try std.fmt.allocPrint(arena, "-MWalk={}", .{walk_src_path}), + "--listen=-", + }); + + var child = std.process.Child.init(argv.items, gpa); + child.stdin_behavior = .Pipe; + child.stdout_behavior = .Pipe; + child.stderr_behavior = .Pipe; + try child.spawn(); + + var poller = std.io.poll(gpa, enum { stdout, stderr }, .{ + .stdout = child.stdout.?, + .stderr = child.stderr.?, + }); + defer poller.deinit(); + + try sendMessage(child.stdin.?, .update); + try sendMessage(child.stdin.?, .exit); + + const Header = std.zig.Server.Message.Header; + var result: ?[]const u8 = null; + var result_error_bundle = std.zig.ErrorBundle.empty; + + const stdout = poller.fifo(.stdout); + + poll: while (true) { + while (stdout.readableLength() < @sizeOf(Header)) { + if (!(try poller.poll())) break :poll; + } + const header = stdout.reader().readStruct(Header) catch unreachable; + while (stdout.readableLength() < header.bytes_len) { + if (!(try poller.poll())) break :poll; + } + const body = stdout.readableSliceOfLen(header.bytes_len); + + switch (header.tag) { + .zig_version => { + if (!std.mem.eql(u8, builtin.zig_version_string, body)) { + return error.ZigProtocolVersionMismatch; + } + }, + .error_bundle => { + const EbHdr = std.zig.Server.Message.ErrorBundle; + const eb_hdr = @as(*align(1) const EbHdr, @ptrCast(body)); + const extra_bytes = + body[@sizeOf(EbHdr)..][0 .. @sizeOf(u32) * eb_hdr.extra_len]; + const string_bytes = + body[@sizeOf(EbHdr) + extra_bytes.len ..][0..eb_hdr.string_bytes_len]; + // TODO: use @ptrCast when the compiler supports it + const unaligned_extra = std.mem.bytesAsSlice(u32, extra_bytes); + const extra_array = try arena.alloc(u32, unaligned_extra.len); + @memcpy(extra_array, unaligned_extra); + result_error_bundle = .{ + .string_bytes = try arena.dupe(u8, string_bytes), + .extra = extra_array, + }; + }, + .emit_bin_path => { + const EbpHdr = std.zig.Server.Message.EmitBinPath; + const ebp_hdr = @as(*align(1) const EbpHdr, @ptrCast(body)); + if (!ebp_hdr.flags.cache_hit) { + log.info("source changes detected; rebuilt wasm component", .{}); + } + result = try arena.dupe(u8, body[@sizeOf(EbpHdr)..]); + }, + else => {}, // ignore other messages + } + + stdout.discard(body.len); + } + + const stderr = poller.fifo(.stderr); + if (stderr.readableLength() > 0) { + const owned_stderr = try stderr.toOwnedSlice(); + defer gpa.free(owned_stderr); + std.debug.print("{s}", .{owned_stderr}); + } + + // Send EOF to stdin. + child.stdin.?.close(); + child.stdin = null; + + switch (try child.wait()) { + .Exited => |code| { + if (code != 0) { + log.err( + "the following command exited with error code {d}:\n{s}", + .{ code, try Build.Step.allocPrintCmd(arena, null, argv.items) }, + ); + return error.WasmCompilationFailed; + } + }, + .Signal, .Stopped, .Unknown => { + log.err( + "the following command terminated unexpectedly:\n{s}", + .{try Build.Step.allocPrintCmd(arena, null, argv.items)}, + ); + return error.WasmCompilationFailed; + }, + } + + if (result_error_bundle.errorMessageCount() > 0) { + const color = std.zig.Color.auto; + result_error_bundle.renderToStdErr(color.renderOptions()); + log.err("the following command failed with {d} compilation errors:\n{s}", .{ + result_error_bundle.errorMessageCount(), + try Build.Step.allocPrintCmd(arena, null, argv.items), + }); + return error.WasmCompilationFailed; + } + + return result orelse { + log.err("child process failed to report result\n{s}", .{ + try Build.Step.allocPrintCmd(arena, null, argv.items), + }); + return error.WasmCompilationFailed; + }; + } + + fn sendMessage(file: std.fs.File, tag: std.zig.Client.Message.Tag) !void { + const header: std.zig.Client.Message.Header = .{ + .tag = tag, + .bytes_len = 0, + }; + try file.writeAll(std.mem.asBytes(&header)); + } + + fn serveSourcesTar(ws: *WebServer, request: *std.http.Server.Request) !void { + const gpa = ws.gpa; + + var arena_instance = std.heap.ArenaAllocator.init(gpa); + defer arena_instance.deinit(); + const arena = arena_instance.allocator(); + + var send_buffer: [0x4000]u8 = undefined; + var response = request.respondStreaming(.{ + .send_buffer = &send_buffer, + .respond_options = .{ + .extra_headers = &.{ + .{ .name = "content-type", .value = "application/x-tar" }, + cache_control_header, + }, + }, + }); + const w = response.writer(); + + const DedupeTable = std.ArrayHashMapUnmanaged(Build.Cache.Path, void, Build.Cache.Path.TableAdapter, false); + var dedupe_table: DedupeTable = .{}; + defer dedupe_table.deinit(gpa); + + for (ws.fuzz_run_steps) |run_step| { + const compile_step_inputs = run_step.producer.?.step.inputs.table; + for (compile_step_inputs.keys(), compile_step_inputs.values()) |dir_path, *file_list| { + try dedupe_table.ensureUnusedCapacity(gpa, file_list.items.len); + for (file_list.items) |sub_path| { + // Special file "." means the entire directory. + if (std.mem.eql(u8, sub_path, ".")) continue; + const joined_path = try dir_path.join(arena, sub_path); + _ = dedupe_table.getOrPutAssumeCapacity(joined_path); + } + } + } + + const deduped_paths = dedupe_table.keys(); + const SortContext = struct { + pub fn lessThan(this: @This(), lhs: Build.Cache.Path, rhs: Build.Cache.Path) bool { + _ = this; + return switch (std.mem.order(u8, lhs.root_dir.path orelse ".", rhs.root_dir.path orelse ".")) { + .lt => true, + .gt => false, + .eq => std.mem.lessThan(u8, lhs.sub_path, rhs.sub_path), + }; + } + }; + std.mem.sortUnstable(Build.Cache.Path, deduped_paths, SortContext{}, SortContext.lessThan); + + for (deduped_paths) |joined_path| { + var file = joined_path.root_dir.handle.openFile(joined_path.sub_path, .{}) catch |err| { + log.err("failed to open {}: {s}", .{ joined_path, @errorName(err) }); + continue; + }; + defer file.close(); + + const stat = file.stat() catch |err| { + log.err("failed to stat {}: {s}", .{ joined_path, @errorName(err) }); + continue; + }; + if (stat.kind != .file) + continue; + + const padding = p: { + const remainder = stat.size % 512; + break :p if (remainder > 0) 512 - remainder else 0; + }; + + var file_header = std.tar.output.Header.init(); + file_header.typeflag = .regular; + try file_header.setPath(joined_path.root_dir.path orelse ".", joined_path.sub_path); + try file_header.setSize(stat.size); + try file_header.updateChecksum(); + try w.writeAll(std.mem.asBytes(&file_header)); + try w.writeFile(file); + try w.writeByteNTimes(0, padding); + } + + // intentionally omitting the pointless trailer + //try w.writeByteNTimes(0, 512 * 2); + try response.end(); + } + + const cache_control_header: std.http.Header = .{ + .name = "cache-control", + .value = "max-age=0, must-revalidate", + }; +}; + fn rebuildTestsWorkerRun(run: *Step.Run, ttyconf: std.io.tty.Config, parent_prog_node: std.Progress.Node) void { const gpa = run.step.owner.allocator; const stderr = std.io.getStdErr(); @@ -88,6 +508,7 @@ fn rebuildTestsWorkerRun(run: *Step.Run, ttyconf: std.io.tty.Config, parent_prog fn fuzzWorkerRun( run: *Step.Run, + web_server: *WebServer, unit_test_index: u32, ttyconf: std.io.tty.Config, parent_prog_node: std.Progress.Node, @@ -98,7 +519,7 @@ fn fuzzWorkerRun( const prog_node = parent_prog_node.start(test_name, 0); defer prog_node.end(); - run.rerunInFuzzMode(unit_test_index, prog_node) catch |err| switch (err) { + run.rerunInFuzzMode(web_server, unit_test_index, prog_node) catch |err| switch (err) { error.MakeFailed => { const stderr = std.io.getStdErr(); std.debug.lockStdErr(); diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index 8f3236d867..47a6e49a82 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -559,7 +559,8 @@ fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool) !?[]const u8 { }, .zig_lib => zl: { if (s.cast(Step.Compile)) |compile| { - if (compile.zig_lib_dir) |lp| { + if (compile.zig_lib_dir) |zig_lib_dir| { + const lp = try zig_lib_dir.join(arena, sub_path); try addWatchInput(s, lp); break :zl; } diff --git a/lib/std/Build/Step/Run.zig b/lib/std/Build/Step/Run.zig index c2d25cd82c..e494e969f0 100644 --- a/lib/std/Build/Step/Run.zig +++ b/lib/std/Build/Step/Run.zig @@ -205,6 +205,7 @@ pub fn enableTestRunnerMode(run: *Run) void { run.stdio = .zig_test; run.addArgs(&.{ std.fmt.allocPrint(arena, "--seed=0x{x}", .{b.graph.random_seed}) catch @panic("OOM"), + std.fmt.allocPrint(arena, "--cache-dir={s}", .{b.cache_root.path orelse ""}) catch @panic("OOM"), "--listen=-", }); } @@ -845,7 +846,12 @@ fn make(step: *Step, options: Step.MakeOptions) !void { ); } -pub fn rerunInFuzzMode(run: *Run, unit_test_index: u32, prog_node: std.Progress.Node) !void { +pub fn rerunInFuzzMode( + run: *Run, + web_server: *std.Build.Fuzz.WebServer, + unit_test_index: u32, + prog_node: std.Progress.Node, +) !void { const step = &run.step; const b = step.owner; const arena = b.allocator; @@ -877,7 +883,10 @@ pub fn rerunInFuzzMode(run: *Run, unit_test_index: u32, prog_node: std.Progress. const has_side_effects = false; const rand_int = std.crypto.random.int(u64); const tmp_dir_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(rand_int); - try runCommand(run, argv_list.items, has_side_effects, tmp_dir_path, prog_node, unit_test_index); + try runCommand(run, argv_list.items, has_side_effects, tmp_dir_path, prog_node, .{ + .unit_test_index = unit_test_index, + .web_server = web_server, + }); } fn populateGeneratedPaths( @@ -952,13 +961,18 @@ fn termMatches(expected: ?std.process.Child.Term, actual: std.process.Child.Term }; } +const FuzzContext = struct { + web_server: *std.Build.Fuzz.WebServer, + unit_test_index: u32, +}; + fn runCommand( run: *Run, argv: []const []const u8, has_side_effects: bool, output_dir_path: []const u8, prog_node: std.Progress.Node, - fuzz_unit_test_index: ?u32, + fuzz_context: ?FuzzContext, ) !void { const step = &run.step; const b = step.owner; @@ -977,7 +991,7 @@ fn runCommand( var interp_argv = std.ArrayList([]const u8).init(b.allocator); defer interp_argv.deinit(); - const result = spawnChildAndCollect(run, argv, has_side_effects, prog_node, fuzz_unit_test_index) catch |err| term: { + const result = spawnChildAndCollect(run, argv, has_side_effects, prog_node, fuzz_context) catch |err| term: { // InvalidExe: cpu arch mismatch // FileNotFound: can happen with a wrong dynamic linker path if (err == error.InvalidExe or err == error.FileNotFound) interpret: { @@ -1113,7 +1127,7 @@ fn runCommand( try Step.handleVerbose2(step.owner, cwd, run.env_map, interp_argv.items); - break :term spawnChildAndCollect(run, interp_argv.items, has_side_effects, prog_node, fuzz_unit_test_index) catch |e| { + break :term spawnChildAndCollect(run, interp_argv.items, has_side_effects, prog_node, fuzz_context) catch |e| { if (!run.failing_to_execute_foreign_is_an_error) return error.MakeSkipped; return step.fail("unable to spawn interpreter {s}: {s}", .{ @@ -1133,7 +1147,7 @@ fn runCommand( const final_argv = if (interp_argv.items.len == 0) argv else interp_argv.items; - if (fuzz_unit_test_index != null) { + if (fuzz_context != null) { try step.handleChildProcessTerm(result.term, cwd, final_argv); return; } @@ -1298,12 +1312,12 @@ fn spawnChildAndCollect( argv: []const []const u8, has_side_effects: bool, prog_node: std.Progress.Node, - fuzz_unit_test_index: ?u32, + fuzz_context: ?FuzzContext, ) !ChildProcResult { const b = run.step.owner; const arena = b.allocator; - if (fuzz_unit_test_index != null) { + if (fuzz_context != null) { assert(!has_side_effects); assert(run.stdio == .zig_test); } @@ -1357,7 +1371,7 @@ fn spawnChildAndCollect( var timer = try std.time.Timer.start(); const result = if (run.stdio == .zig_test) - evalZigTest(run, &child, prog_node, fuzz_unit_test_index) + evalZigTest(run, &child, prog_node, fuzz_context) else evalGeneric(run, &child); @@ -1383,7 +1397,7 @@ fn evalZigTest( run: *Run, child: *std.process.Child, prog_node: std.Progress.Node, - fuzz_unit_test_index: ?u32, + fuzz_context: ?FuzzContext, ) !StdIoResult { const gpa = run.step.owner.allocator; const arena = run.step.owner.allocator; @@ -1394,8 +1408,8 @@ fn evalZigTest( }); defer poller.deinit(); - if (fuzz_unit_test_index) |index| { - try sendRunTestMessage(child.stdin.?, .start_fuzzing, index); + if (fuzz_context) |fuzz| { + try sendRunTestMessage(child.stdin.?, .start_fuzzing, fuzz.unit_test_index); } else { run.fuzz_tests.clearRetainingCapacity(); try sendMessage(child.stdin.?, .query_test_metadata); @@ -1437,7 +1451,7 @@ fn evalZigTest( } }, .test_metadata => { - assert(fuzz_unit_test_index == null); + assert(fuzz_context == null); const TmHdr = std.zig.Server.Message.TestMetadata; const tm_hdr = @as(*align(1) const TmHdr, @ptrCast(body)); test_count = tm_hdr.tests_len; @@ -1466,7 +1480,7 @@ fn evalZigTest( try requestNextTest(child.stdin.?, &metadata.?, &sub_prog_node); }, .test_results => { - assert(fuzz_unit_test_index == null); + assert(fuzz_context == null); const md = metadata.?; const TrHdr = std.zig.Server.Message.TestResults; @@ -1500,6 +1514,16 @@ fn evalZigTest( try requestNextTest(child.stdin.?, &metadata.?, &sub_prog_node); }, + .coverage_id => { + const web_server = fuzz_context.?.web_server; + const msg_ptr: *align(1) const u64 = @ptrCast(body); + const coverage_id = msg_ptr.*; + { + web_server.mutex.lock(); + defer web_server.mutex.unlock(); + try web_server.msg_queue.append(web_server.gpa, .{ .coverage_id = coverage_id }); + } + }, else => {}, // ignore other messages } diff --git a/lib/std/zig/Server.zig b/lib/std/zig/Server.zig index f1e564d43e..93ce6cc01f 100644 --- a/lib/std/zig/Server.zig +++ b/lib/std/zig/Server.zig @@ -28,6 +28,10 @@ pub const Message = struct { /// The remaining bytes is the file path relative to that prefix. /// The prefixes are hard-coded in Compilation.create (cwd, zig lib dir, local cache dir) file_system_inputs, + /// Body is a u64le that indicates the file path within the cache used + /// to store coverage information. The integer is a hash of the PCs + /// stored within that file. + coverage_id, _, }; @@ -180,6 +184,14 @@ pub fn serveMessage( try s.out.writevAll(iovecs[0 .. bufs.len + 1]); } +pub fn serveU64Message(s: *Server, tag: OutMessage.Tag, int: u64) !void { + const msg_le = bswap(int); + return s.serveMessage(.{ + .tag = tag, + .bytes_len = @sizeOf(u64), + }, &.{std.mem.asBytes(&msg_le)}); +} + pub fn serveEmitBinPath( s: *Server, fs_path: []const u8, @@ -187,7 +199,7 @@ pub fn serveEmitBinPath( ) !void { try s.serveMessage(.{ .tag = .emit_bin_path, - .bytes_len = @as(u32, @intCast(fs_path.len + @sizeOf(OutMessage.EmitBinPath))), + .bytes_len = @intCast(fs_path.len + @sizeOf(OutMessage.EmitBinPath)), }, &.{ std.mem.asBytes(&header), fs_path, @@ -201,7 +213,7 @@ pub fn serveTestResults( const msg_le = bswap(msg); try s.serveMessage(.{ .tag = .test_results, - .bytes_len = @as(u32, @intCast(@sizeOf(OutMessage.TestResults))), + .bytes_len = @intCast(@sizeOf(OutMessage.TestResults)), }, &.{ std.mem.asBytes(&msg_le), }); @@ -209,14 +221,14 @@ pub fn serveTestResults( pub fn serveErrorBundle(s: *Server, error_bundle: std.zig.ErrorBundle) !void { const eb_hdr: OutMessage.ErrorBundle = .{ - .extra_len = @as(u32, @intCast(error_bundle.extra.len)), - .string_bytes_len = @as(u32, @intCast(error_bundle.string_bytes.len)), + .extra_len = @intCast(error_bundle.extra.len), + .string_bytes_len = @intCast(error_bundle.string_bytes.len), }; const bytes_len = @sizeOf(OutMessage.ErrorBundle) + 4 * error_bundle.extra.len + error_bundle.string_bytes.len; try s.serveMessage(.{ .tag = .error_bundle, - .bytes_len = @as(u32, @intCast(bytes_len)), + .bytes_len = @intCast(bytes_len), }, &.{ std.mem.asBytes(&eb_hdr), // TODO: implement @ptrCast between slices changing the length @@ -251,7 +263,7 @@ pub fn serveTestMetadata(s: *Server, test_metadata: TestMetadata) !void { return s.serveMessage(.{ .tag = .test_metadata, - .bytes_len = @as(u32, @intCast(bytes_len)), + .bytes_len = @intCast(bytes_len), }, &.{ std.mem.asBytes(&header), // TODO: implement @ptrCast between slices changing the length diff --git a/lib/std/zig/tokenizer.zig b/lib/std/zig/tokenizer.zig index c375818770..b63bde5633 100644 --- a/lib/std/zig/tokenizer.zig +++ b/lib/std/zig/tokenizer.zig @@ -1840,3 +1840,48 @@ fn testTokenize(source: [:0]const u8, expected_token_tags: []const Token.Tag) !v try std.testing.expectEqual(source.len, last_token.loc.start); try std.testing.expectEqual(source.len, last_token.loc.end); } + +test "fuzzable properties upheld" { + const source = std.testing.fuzzInput(.{}); + const source0 = try std.testing.allocator.dupeZ(u8, source); + defer std.testing.allocator.free(source0); + var tokenizer = Tokenizer.init(source0); + var tokenization_failed = false; + while (true) { + const token = tokenizer.next(); + + // Property: token end location after start location (or equal) + try std.testing.expect(token.loc.end >= token.loc.start); + + switch (token.tag) { + .invalid => { + tokenization_failed = true; + + // Property: invalid token always ends at newline or eof + try std.testing.expect(source0[token.loc.end] == '\n' or source0[token.loc.end] == 0); + }, + .eof => { + // Property: EOF token is always 0-length at end of source. + try std.testing.expectEqual(source0.len, token.loc.start); + try std.testing.expectEqual(source0.len, token.loc.end); + break; + }, + else => continue, + } + } + + if (source0.len > 0) for (source0, source0[1..][0..source0.len]) |cur, next| { + // Property: No null byte allowed except at end. + if (cur == 0) { + try std.testing.expect(tokenization_failed); + } + // Property: No ASCII control characters other than \n and \t are allowed. + if (std.ascii.isControl(cur) and cur != '\n' and cur != '\t') { + try std.testing.expect(tokenization_failed); + } + // Property: All '\r' must be followed by '\n'. + if (cur == '\r' and next != '\n') { + try std.testing.expect(tokenization_failed); + } + }; +} From 107b27276602d1935a90ae97c57f640b090088b5 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 31 Jul 2024 23:45:43 -0700 Subject: [PATCH 170/266] fuzzer: share zig to html rendering with autodocs --- lib/compiler/std-docs.zig | 9 +- lib/docs/wasm/Decl.zig | 18 +- lib/docs/wasm/Walk.zig | 20 +- lib/docs/wasm/html_render.zig | 388 ++++++++++++++++++++++++++++++++ lib/docs/wasm/main.zig | 406 ++-------------------------------- lib/fuzzer/index.html | 60 ++++- lib/fuzzer/main.js | 48 ++++ lib/fuzzer/wasm/main.zig | 44 ++++ lib/std/Build/Fuzz.zig | 39 ++-- 9 files changed, 598 insertions(+), 434 deletions(-) create mode 100644 lib/docs/wasm/html_render.zig diff --git a/lib/compiler/std-docs.zig b/lib/compiler/std-docs.zig index eaabcfa936..c11665101c 100644 --- a/lib/compiler/std-docs.zig +++ b/lib/compiler/std-docs.zig @@ -275,10 +275,6 @@ fn buildWasmBinary( ) ![]const u8 { const gpa = context.gpa; - const main_src_path = try std.fs.path.join(arena, &.{ - context.zig_lib_directory, "docs", "wasm", "main.zig", - }); - var argv: std.ArrayListUnmanaged([]const u8) = .{}; try argv.appendSlice(arena, &.{ @@ -298,7 +294,10 @@ fn buildWasmBinary( "--name", "autodoc", "-rdynamic", - main_src_path, + "--dep", + "Walk", + try std.fmt.allocPrint(arena, "-Mroot={s}/docs/wasm/main.zig", .{context.zig_lib_directory}), + try std.fmt.allocPrint(arena, "-MWalk={s}/docs/wasm/Walk.zig", .{context.zig_lib_directory}), "--listen=-", }); diff --git a/lib/docs/wasm/Decl.zig b/lib/docs/wasm/Decl.zig index 0260ce0285..2546355987 100644 --- a/lib/docs/wasm/Decl.zig +++ b/lib/docs/wasm/Decl.zig @@ -1,3 +1,12 @@ +const Decl = @This(); +const std = @import("std"); +const Ast = std.zig.Ast; +const Walk = @import("Walk.zig"); +const gpa = std.heap.wasm_allocator; +const assert = std.debug.assert; +const log = std.log; +const Oom = error{OutOfMemory}; + ast_node: Ast.Node.Index, file: Walk.File.Index, /// The decl whose namespace this is in. @@ -215,12 +224,3 @@ pub fn find(search_string: []const u8) Decl.Index { } return current_decl_index; } - -const Decl = @This(); -const std = @import("std"); -const Ast = std.zig.Ast; -const Walk = @import("Walk.zig"); -const gpa = std.heap.wasm_allocator; -const assert = std.debug.assert; -const log = std.log; -const Oom = error{OutOfMemory}; diff --git a/lib/docs/wasm/Walk.zig b/lib/docs/wasm/Walk.zig index a22da861a8..ae924b8c38 100644 --- a/lib/docs/wasm/Walk.zig +++ b/lib/docs/wasm/Walk.zig @@ -1,4 +1,15 @@ //! Find and annotate identifiers with links to their declarations. + +const Walk = @This(); +const std = @import("std"); +const Ast = std.zig.Ast; +const assert = std.debug.assert; +const log = std.log; +const gpa = std.heap.wasm_allocator; +const Oom = error{OutOfMemory}; + +pub const Decl = @import("Decl.zig"); + pub var files: std.StringArrayHashMapUnmanaged(File) = .{}; pub var decls: std.ArrayListUnmanaged(Decl) = .{}; pub var modules: std.StringArrayHashMapUnmanaged(File.Index) = .{}; @@ -1120,15 +1131,6 @@ pub fn isPrimitiveNonType(name: []const u8) bool { // try w.root(); //} -const Walk = @This(); -const std = @import("std"); -const Ast = std.zig.Ast; -const assert = std.debug.assert; -const Decl = @import("Decl.zig"); -const log = std.log; -const gpa = std.heap.wasm_allocator; -const Oom = error{OutOfMemory}; - fn shrinkToFit(m: anytype) void { m.shrinkAndFree(gpa, m.entries.len); } diff --git a/lib/docs/wasm/html_render.zig b/lib/docs/wasm/html_render.zig new file mode 100644 index 0000000000..cce201049d --- /dev/null +++ b/lib/docs/wasm/html_render.zig @@ -0,0 +1,388 @@ +const std = @import("std"); +const Ast = std.zig.Ast; +const assert = std.debug.assert; + +const Walk = @import("Walk"); +const Decl = Walk.Decl; + +const gpa = std.heap.wasm_allocator; +const Oom = error{OutOfMemory}; + +/// Delete this to find out where URL escaping needs to be added. +pub const missing_feature_url_escape = true; + +pub const RenderSourceOptions = struct { + skip_doc_comments: bool = false, + skip_comments: bool = false, + collapse_whitespace: bool = false, + fn_link: Decl.Index = .none, +}; + +pub fn fileSourceHtml( + file_index: Walk.File.Index, + out: *std.ArrayListUnmanaged(u8), + root_node: Ast.Node.Index, + options: RenderSourceOptions, +) !void { + const ast = file_index.get_ast(); + const file = file_index.get(); + + const g = struct { + var field_access_buffer: std.ArrayListUnmanaged(u8) = .{}; + }; + + const token_tags = ast.tokens.items(.tag); + const token_starts = ast.tokens.items(.start); + const main_tokens = ast.nodes.items(.main_token); + + const start_token = ast.firstToken(root_node); + const end_token = ast.lastToken(root_node) + 1; + + var cursor: usize = token_starts[start_token]; + + var indent: usize = 0; + if (std.mem.lastIndexOf(u8, ast.source[0..cursor], "\n")) |newline_index| { + for (ast.source[newline_index + 1 .. cursor]) |c| { + if (c == ' ') { + indent += 1; + } else { + break; + } + } + } + + for ( + token_tags[start_token..end_token], + token_starts[start_token..end_token], + start_token.., + ) |tag, start, token_index| { + const between = ast.source[cursor..start]; + if (std.mem.trim(u8, between, " \t\r\n").len > 0) { + if (!options.skip_comments) { + try out.appendSlice(gpa, ""); + try appendUnindented(out, between, indent); + try out.appendSlice(gpa, ""); + } + } else if (between.len > 0) { + if (options.collapse_whitespace) { + if (out.items.len > 0 and out.items[out.items.len - 1] != ' ') + try out.append(gpa, ' '); + } else { + try appendUnindented(out, between, indent); + } + } + if (tag == .eof) break; + const slice = ast.tokenSlice(token_index); + cursor = start + slice.len; + switch (tag) { + .eof => unreachable, + + .keyword_addrspace, + .keyword_align, + .keyword_and, + .keyword_asm, + .keyword_async, + .keyword_await, + .keyword_break, + .keyword_catch, + .keyword_comptime, + .keyword_const, + .keyword_continue, + .keyword_defer, + .keyword_else, + .keyword_enum, + .keyword_errdefer, + .keyword_error, + .keyword_export, + .keyword_extern, + .keyword_for, + .keyword_if, + .keyword_inline, + .keyword_noalias, + .keyword_noinline, + .keyword_nosuspend, + .keyword_opaque, + .keyword_or, + .keyword_orelse, + .keyword_packed, + .keyword_anyframe, + .keyword_pub, + .keyword_resume, + .keyword_return, + .keyword_linksection, + .keyword_callconv, + .keyword_struct, + .keyword_suspend, + .keyword_switch, + .keyword_test, + .keyword_threadlocal, + .keyword_try, + .keyword_union, + .keyword_unreachable, + .keyword_usingnamespace, + .keyword_var, + .keyword_volatile, + .keyword_allowzero, + .keyword_while, + .keyword_anytype, + .keyword_fn, + => { + try out.appendSlice(gpa, ""); + try appendEscaped(out, slice); + try out.appendSlice(gpa, ""); + }, + + .string_literal, + .char_literal, + .multiline_string_literal_line, + => { + try out.appendSlice(gpa, ""); + try appendEscaped(out, slice); + try out.appendSlice(gpa, ""); + }, + + .builtin => { + try out.appendSlice(gpa, ""); + try appendEscaped(out, slice); + try out.appendSlice(gpa, ""); + }, + + .doc_comment, + .container_doc_comment, + => { + if (!options.skip_doc_comments) { + try out.appendSlice(gpa, ""); + try appendEscaped(out, slice); + try out.appendSlice(gpa, ""); + } + }, + + .identifier => i: { + if (options.fn_link != .none) { + const fn_link = options.fn_link.get(); + const fn_token = main_tokens[fn_link.ast_node]; + if (token_index == fn_token + 1) { + try out.appendSlice(gpa, ""); + try appendEscaped(out, slice); + try out.appendSlice(gpa, ""); + break :i; + } + } + + if (token_index > 0 and token_tags[token_index - 1] == .keyword_fn) { + try out.appendSlice(gpa, ""); + try appendEscaped(out, slice); + try out.appendSlice(gpa, ""); + break :i; + } + + if (Walk.isPrimitiveNonType(slice)) { + try out.appendSlice(gpa, ""); + try appendEscaped(out, slice); + try out.appendSlice(gpa, ""); + break :i; + } + + if (std.zig.primitives.isPrimitive(slice)) { + try out.appendSlice(gpa, ""); + try appendEscaped(out, slice); + try out.appendSlice(gpa, ""); + break :i; + } + + if (file.token_parents.get(token_index)) |field_access_node| { + g.field_access_buffer.clearRetainingCapacity(); + try walkFieldAccesses(file_index, &g.field_access_buffer, field_access_node); + if (g.field_access_buffer.items.len > 0) { + try out.appendSlice(gpa, ""); + try appendEscaped(out, slice); + try out.appendSlice(gpa, ""); + } else { + try appendEscaped(out, slice); + } + break :i; + } + + { + g.field_access_buffer.clearRetainingCapacity(); + try resolveIdentLink(file_index, &g.field_access_buffer, token_index); + if (g.field_access_buffer.items.len > 0) { + try out.appendSlice(gpa, ""); + try appendEscaped(out, slice); + try out.appendSlice(gpa, ""); + break :i; + } + } + + try appendEscaped(out, slice); + }, + + .number_literal => { + try out.appendSlice(gpa, ""); + try appendEscaped(out, slice); + try out.appendSlice(gpa, ""); + }, + + .bang, + .pipe, + .pipe_pipe, + .pipe_equal, + .equal, + .equal_equal, + .equal_angle_bracket_right, + .bang_equal, + .l_paren, + .r_paren, + .semicolon, + .percent, + .percent_equal, + .l_brace, + .r_brace, + .l_bracket, + .r_bracket, + .period, + .period_asterisk, + .ellipsis2, + .ellipsis3, + .caret, + .caret_equal, + .plus, + .plus_plus, + .plus_equal, + .plus_percent, + .plus_percent_equal, + .plus_pipe, + .plus_pipe_equal, + .minus, + .minus_equal, + .minus_percent, + .minus_percent_equal, + .minus_pipe, + .minus_pipe_equal, + .asterisk, + .asterisk_equal, + .asterisk_asterisk, + .asterisk_percent, + .asterisk_percent_equal, + .asterisk_pipe, + .asterisk_pipe_equal, + .arrow, + .colon, + .slash, + .slash_equal, + .comma, + .ampersand, + .ampersand_equal, + .question_mark, + .angle_bracket_left, + .angle_bracket_left_equal, + .angle_bracket_angle_bracket_left, + .angle_bracket_angle_bracket_left_equal, + .angle_bracket_angle_bracket_left_pipe, + .angle_bracket_angle_bracket_left_pipe_equal, + .angle_bracket_right, + .angle_bracket_right_equal, + .angle_bracket_angle_bracket_right, + .angle_bracket_angle_bracket_right_equal, + .tilde, + => try appendEscaped(out, slice), + + .invalid, .invalid_periodasterisks => return error.InvalidToken, + } + } +} + +fn appendUnindented(out: *std.ArrayListUnmanaged(u8), s: []const u8, indent: usize) !void { + var it = std.mem.splitScalar(u8, s, '\n'); + var is_first_line = true; + while (it.next()) |line| { + if (is_first_line) { + try appendEscaped(out, line); + is_first_line = false; + } else { + try out.appendSlice(gpa, "\n"); + try appendEscaped(out, unindent(line, indent)); + } + } +} + +pub fn appendEscaped(out: *std.ArrayListUnmanaged(u8), s: []const u8) !void { + for (s) |c| { + try out.ensureUnusedCapacity(gpa, 6); + switch (c) { + '&' => out.appendSliceAssumeCapacity("&"), + '<' => out.appendSliceAssumeCapacity("<"), + '>' => out.appendSliceAssumeCapacity(">"), + '"' => out.appendSliceAssumeCapacity("""), + else => out.appendAssumeCapacity(c), + } + } +} + +fn walkFieldAccesses( + file_index: Walk.File.Index, + out: *std.ArrayListUnmanaged(u8), + node: Ast.Node.Index, +) Oom!void { + const ast = file_index.get_ast(); + const node_tags = ast.nodes.items(.tag); + assert(node_tags[node] == .field_access); + const node_datas = ast.nodes.items(.data); + const main_tokens = ast.nodes.items(.main_token); + const object_node = node_datas[node].lhs; + const dot_token = main_tokens[node]; + const field_ident = dot_token + 1; + switch (node_tags[object_node]) { + .identifier => { + const lhs_ident = main_tokens[object_node]; + try resolveIdentLink(file_index, out, lhs_ident); + }, + .field_access => { + try walkFieldAccesses(file_index, out, object_node); + }, + else => {}, + } + if (out.items.len > 0) { + try out.append(gpa, '.'); + try out.appendSlice(gpa, ast.tokenSlice(field_ident)); + } +} + +fn resolveIdentLink( + file_index: Walk.File.Index, + out: *std.ArrayListUnmanaged(u8), + ident_token: Ast.TokenIndex, +) Oom!void { + const decl_index = file_index.get().lookup_token(ident_token); + if (decl_index == .none) return; + try resolveDeclLink(decl_index, out); +} + +fn unindent(s: []const u8, indent: usize) []const u8 { + var indent_idx: usize = 0; + for (s) |c| { + if (c == ' ' and indent_idx < indent) { + indent_idx += 1; + } else { + break; + } + } + return s[indent_idx..]; +} + +pub fn resolveDeclLink(decl_index: Decl.Index, out: *std.ArrayListUnmanaged(u8)) Oom!void { + const decl = decl_index.get(); + switch (decl.categorize()) { + .alias => |alias_decl| try alias_decl.get().fqn(out), + else => try decl.fqn(out), + } +} diff --git a/lib/docs/wasm/main.zig b/lib/docs/wasm/main.zig index 214f28c24b..55882aaf7d 100644 --- a/lib/docs/wasm/main.zig +++ b/lib/docs/wasm/main.zig @@ -1,15 +1,17 @@ -/// Delete this to find out where URL escaping needs to be added. -const missing_feature_url_escape = true; - -const gpa = std.heap.wasm_allocator; - const std = @import("std"); const log = std.log; const assert = std.debug.assert; const Ast = std.zig.Ast; -const Walk = @import("Walk.zig"); +const Walk = @import("Walk"); const markdown = @import("markdown.zig"); -const Decl = @import("Decl.zig"); +const Decl = Walk.Decl; + +const fileSourceHtml = @import("html_render.zig").fileSourceHtml; +const appendEscaped = @import("html_render.zig").appendEscaped; +const resolveDeclLink = @import("html_render.zig").resolveDeclLink; +const missing_feature_url_escape = @import("html_render.zig").missing_feature_url_escape; + +const gpa = std.heap.wasm_allocator; const js = struct { extern "js" fn log(ptr: [*]const u8, len: usize) void; @@ -439,7 +441,7 @@ fn decl_field_html_fallible( const decl = decl_index.get(); const ast = decl.file.get_ast(); try out.appendSlice(gpa, "
");
-    try file_source_html(decl.file, out, field_node, .{});
+    try fileSourceHtml(decl.file, out, field_node, .{});
     try out.appendSlice(gpa, "
"); const field = ast.fullContainerField(field_node).?; @@ -478,7 +480,7 @@ fn decl_param_html_fallible( try out.appendSlice(gpa, "
");
     try appendEscaped(out, name);
     try out.appendSlice(gpa, ": ");
-    try file_source_html(decl.file, out, param_node, .{});
+    try fileSourceHtml(decl.file, out, param_node, .{});
     try out.appendSlice(gpa, "
"); if (ast.tokens.items(.tag)[first_doc_comment] == .doc_comment) { @@ -506,7 +508,7 @@ export fn decl_fn_proto_html(decl_index: Decl.Index, linkify_fn_name: bool) Stri }; string_result.clearRetainingCapacity(); - file_source_html(decl.file, &string_result, proto_node, .{ + fileSourceHtml(decl.file, &string_result, proto_node, .{ .skip_doc_comments = true, .skip_comments = true, .collapse_whitespace = true, @@ -521,7 +523,7 @@ export fn decl_source_html(decl_index: Decl.Index) String { const decl = decl_index.get(); string_result.clearRetainingCapacity(); - file_source_html(decl.file, &string_result, decl.ast_node, .{}) catch |err| { + fileSourceHtml(decl.file, &string_result, decl.ast_node, .{}) catch |err| { fatal("unable to render source: {s}", .{@errorName(err)}); }; return String.init(string_result.items); @@ -533,7 +535,7 @@ export fn decl_doctest_html(decl_index: Decl.Index) String { return String.init(""); string_result.clearRetainingCapacity(); - file_source_html(decl.file, &string_result, doctest_ast_node, .{}) catch |err| { + fileSourceHtml(decl.file, &string_result, doctest_ast_node, .{}) catch |err| { fatal("unable to render source: {s}", .{@errorName(err)}); }; return String.init(string_result.items); @@ -691,7 +693,7 @@ fn render_docs( const content = doc.string(data.text.content); if (resolve_decl_path(r.context, content)) |resolved_decl_index| { g.link_buffer.clearRetainingCapacity(); - try resolve_decl_link(resolved_decl_index, &g.link_buffer); + try resolveDeclLink(resolved_decl_index, &g.link_buffer); try writer.writeAll("") catch @panic("OOM"); - file_source_html(decl.file, &string_result, var_decl.ast.type_node, .{ + fileSourceHtml(decl.file, &string_result, var_decl.ast.type_node, .{ .skip_comments = true, .collapse_whitespace = true, }) catch |e| { @@ -902,382 +904,6 @@ export fn namespace_members(parent: Decl.Index, include_private: bool) Slice(Dec return Slice(Decl.Index).init(g.members.items); } -const RenderSourceOptions = struct { - skip_doc_comments: bool = false, - skip_comments: bool = false, - collapse_whitespace: bool = false, - fn_link: Decl.Index = .none, -}; - -fn file_source_html( - file_index: Walk.File.Index, - out: *std.ArrayListUnmanaged(u8), - root_node: Ast.Node.Index, - options: RenderSourceOptions, -) !void { - const ast = file_index.get_ast(); - const file = file_index.get(); - - const g = struct { - var field_access_buffer: std.ArrayListUnmanaged(u8) = .{}; - }; - - const token_tags = ast.tokens.items(.tag); - const token_starts = ast.tokens.items(.start); - const main_tokens = ast.nodes.items(.main_token); - - const start_token = ast.firstToken(root_node); - const end_token = ast.lastToken(root_node) + 1; - - var cursor: usize = token_starts[start_token]; - - var indent: usize = 0; - if (std.mem.lastIndexOf(u8, ast.source[0..cursor], "\n")) |newline_index| { - for (ast.source[newline_index + 1 .. cursor]) |c| { - if (c == ' ') { - indent += 1; - } else { - break; - } - } - } - - for ( - token_tags[start_token..end_token], - token_starts[start_token..end_token], - start_token.., - ) |tag, start, token_index| { - const between = ast.source[cursor..start]; - if (std.mem.trim(u8, between, " \t\r\n").len > 0) { - if (!options.skip_comments) { - try out.appendSlice(gpa, ""); - try appendUnindented(out, between, indent); - try out.appendSlice(gpa, ""); - } - } else if (between.len > 0) { - if (options.collapse_whitespace) { - if (out.items.len > 0 and out.items[out.items.len - 1] != ' ') - try out.append(gpa, ' '); - } else { - try appendUnindented(out, between, indent); - } - } - if (tag == .eof) break; - const slice = ast.tokenSlice(token_index); - cursor = start + slice.len; - switch (tag) { - .eof => unreachable, - - .keyword_addrspace, - .keyword_align, - .keyword_and, - .keyword_asm, - .keyword_async, - .keyword_await, - .keyword_break, - .keyword_catch, - .keyword_comptime, - .keyword_const, - .keyword_continue, - .keyword_defer, - .keyword_else, - .keyword_enum, - .keyword_errdefer, - .keyword_error, - .keyword_export, - .keyword_extern, - .keyword_for, - .keyword_if, - .keyword_inline, - .keyword_noalias, - .keyword_noinline, - .keyword_nosuspend, - .keyword_opaque, - .keyword_or, - .keyword_orelse, - .keyword_packed, - .keyword_anyframe, - .keyword_pub, - .keyword_resume, - .keyword_return, - .keyword_linksection, - .keyword_callconv, - .keyword_struct, - .keyword_suspend, - .keyword_switch, - .keyword_test, - .keyword_threadlocal, - .keyword_try, - .keyword_union, - .keyword_unreachable, - .keyword_usingnamespace, - .keyword_var, - .keyword_volatile, - .keyword_allowzero, - .keyword_while, - .keyword_anytype, - .keyword_fn, - => { - try out.appendSlice(gpa, ""); - try appendEscaped(out, slice); - try out.appendSlice(gpa, ""); - }, - - .string_literal, - .char_literal, - .multiline_string_literal_line, - => { - try out.appendSlice(gpa, ""); - try appendEscaped(out, slice); - try out.appendSlice(gpa, ""); - }, - - .builtin => { - try out.appendSlice(gpa, ""); - try appendEscaped(out, slice); - try out.appendSlice(gpa, ""); - }, - - .doc_comment, - .container_doc_comment, - => { - if (!options.skip_doc_comments) { - try out.appendSlice(gpa, ""); - try appendEscaped(out, slice); - try out.appendSlice(gpa, ""); - } - }, - - .identifier => i: { - if (options.fn_link != .none) { - const fn_link = options.fn_link.get(); - const fn_token = main_tokens[fn_link.ast_node]; - if (token_index == fn_token + 1) { - try out.appendSlice(gpa, ""); - try appendEscaped(out, slice); - try out.appendSlice(gpa, ""); - break :i; - } - } - - if (token_index > 0 and token_tags[token_index - 1] == .keyword_fn) { - try out.appendSlice(gpa, ""); - try appendEscaped(out, slice); - try out.appendSlice(gpa, ""); - break :i; - } - - if (Walk.isPrimitiveNonType(slice)) { - try out.appendSlice(gpa, ""); - try appendEscaped(out, slice); - try out.appendSlice(gpa, ""); - break :i; - } - - if (std.zig.primitives.isPrimitive(slice)) { - try out.appendSlice(gpa, ""); - try appendEscaped(out, slice); - try out.appendSlice(gpa, ""); - break :i; - } - - if (file.token_parents.get(token_index)) |field_access_node| { - g.field_access_buffer.clearRetainingCapacity(); - try walk_field_accesses(file_index, &g.field_access_buffer, field_access_node); - if (g.field_access_buffer.items.len > 0) { - try out.appendSlice(gpa, ""); - try appendEscaped(out, slice); - try out.appendSlice(gpa, ""); - } else { - try appendEscaped(out, slice); - } - break :i; - } - - { - g.field_access_buffer.clearRetainingCapacity(); - try resolve_ident_link(file_index, &g.field_access_buffer, token_index); - if (g.field_access_buffer.items.len > 0) { - try out.appendSlice(gpa, ""); - try appendEscaped(out, slice); - try out.appendSlice(gpa, ""); - break :i; - } - } - - try appendEscaped(out, slice); - }, - - .number_literal => { - try out.appendSlice(gpa, ""); - try appendEscaped(out, slice); - try out.appendSlice(gpa, ""); - }, - - .bang, - .pipe, - .pipe_pipe, - .pipe_equal, - .equal, - .equal_equal, - .equal_angle_bracket_right, - .bang_equal, - .l_paren, - .r_paren, - .semicolon, - .percent, - .percent_equal, - .l_brace, - .r_brace, - .l_bracket, - .r_bracket, - .period, - .period_asterisk, - .ellipsis2, - .ellipsis3, - .caret, - .caret_equal, - .plus, - .plus_plus, - .plus_equal, - .plus_percent, - .plus_percent_equal, - .plus_pipe, - .plus_pipe_equal, - .minus, - .minus_equal, - .minus_percent, - .minus_percent_equal, - .minus_pipe, - .minus_pipe_equal, - .asterisk, - .asterisk_equal, - .asterisk_asterisk, - .asterisk_percent, - .asterisk_percent_equal, - .asterisk_pipe, - .asterisk_pipe_equal, - .arrow, - .colon, - .slash, - .slash_equal, - .comma, - .ampersand, - .ampersand_equal, - .question_mark, - .angle_bracket_left, - .angle_bracket_left_equal, - .angle_bracket_angle_bracket_left, - .angle_bracket_angle_bracket_left_equal, - .angle_bracket_angle_bracket_left_pipe, - .angle_bracket_angle_bracket_left_pipe_equal, - .angle_bracket_right, - .angle_bracket_right_equal, - .angle_bracket_angle_bracket_right, - .angle_bracket_angle_bracket_right_equal, - .tilde, - => try appendEscaped(out, slice), - - .invalid, .invalid_periodasterisks => return error.InvalidToken, - } - } -} - -fn unindent(s: []const u8, indent: usize) []const u8 { - var indent_idx: usize = 0; - for (s) |c| { - if (c == ' ' and indent_idx < indent) { - indent_idx += 1; - } else { - break; - } - } - return s[indent_idx..]; -} - -fn appendUnindented(out: *std.ArrayListUnmanaged(u8), s: []const u8, indent: usize) !void { - var it = std.mem.splitScalar(u8, s, '\n'); - var is_first_line = true; - while (it.next()) |line| { - if (is_first_line) { - try appendEscaped(out, line); - is_first_line = false; - } else { - try out.appendSlice(gpa, "\n"); - try appendEscaped(out, unindent(line, indent)); - } - } -} - -fn resolve_ident_link( - file_index: Walk.File.Index, - out: *std.ArrayListUnmanaged(u8), - ident_token: Ast.TokenIndex, -) Oom!void { - const decl_index = file_index.get().lookup_token(ident_token); - if (decl_index == .none) return; - try resolve_decl_link(decl_index, out); -} - -fn resolve_decl_link(decl_index: Decl.Index, out: *std.ArrayListUnmanaged(u8)) Oom!void { - const decl = decl_index.get(); - switch (decl.categorize()) { - .alias => |alias_decl| try alias_decl.get().fqn(out), - else => try decl.fqn(out), - } -} - -fn walk_field_accesses( - file_index: Walk.File.Index, - out: *std.ArrayListUnmanaged(u8), - node: Ast.Node.Index, -) Oom!void { - const ast = file_index.get_ast(); - const node_tags = ast.nodes.items(.tag); - assert(node_tags[node] == .field_access); - const node_datas = ast.nodes.items(.data); - const main_tokens = ast.nodes.items(.main_token); - const object_node = node_datas[node].lhs; - const dot_token = main_tokens[node]; - const field_ident = dot_token + 1; - switch (node_tags[object_node]) { - .identifier => { - const lhs_ident = main_tokens[object_node]; - try resolve_ident_link(file_index, out, lhs_ident); - }, - .field_access => { - try walk_field_accesses(file_index, out, object_node); - }, - else => {}, - } - if (out.items.len > 0) { - try out.append(gpa, '.'); - try out.appendSlice(gpa, ast.tokenSlice(field_ident)); - } -} - -fn appendEscaped(out: *std.ArrayListUnmanaged(u8), s: []const u8) !void { - for (s) |c| { - try out.ensureUnusedCapacity(gpa, 6); - switch (c) { - '&' => out.appendSliceAssumeCapacity("&"), - '<' => out.appendSliceAssumeCapacity("<"), - '>' => out.appendSliceAssumeCapacity(">"), - '"' => out.appendSliceAssumeCapacity("""), - else => out.appendAssumeCapacity(c), - } - } -} - fn count_scalar(haystack: []const u8, needle: u8) usize { var total: usize = 0; for (haystack) |elem| { diff --git a/lib/fuzzer/index.html b/lib/fuzzer/index.html index c1ef059ad6..dadc2f91d3 100644 --- a/lib/fuzzer/index.html +++ b/lib/fuzzer/index.html @@ -2,12 +2,56 @@ - Zig Documentation + Zig Build System Interface + diff --git a/lib/fuzzer/main.js b/lib/fuzzer/main.js index 9b0d4cd8c3..71e6b5fa54 100644 --- a/lib/fuzzer/main.js +++ b/lib/fuzzer/main.js @@ -1,4 +1,7 @@ (function() { + const domSectSource = document.getElementById("sectSource"); + const domSourceText = document.getElementById("sourceText"); + let wasm_promise = fetch("main.wasm"); let sources_promise = fetch("sources.tar").then(function(response) { if (!response.ok) throw new Error("unable to download sources"); @@ -30,11 +33,56 @@ const wasm_array = new Uint8Array(wasm_exports.memory.buffer, ptr, js_array.length); wasm_array.set(js_array); wasm_exports.unpack(ptr, js_array.length); + + render(); }); }); + function render() { + domSectSource.classList.add("hidden"); + + // TODO this is temporary debugging data + renderSource("/home/andy/dev/zig/lib/std/zig/tokenizer.zig"); + } + + function renderSource(path) { + const decl_index = findFileRoot(path); + if (decl_index == null) throw new Error("file not found: " + path); + + const h2 = domSectSource.children[0]; + h2.innerText = path; + domSourceText.innerHTML = declSourceHtml(decl_index); + + domSectSource.classList.remove("hidden"); + } + + function findFileRoot(path) { + setInputString(path); + const result = wasm_exports.find_file_root(); + if (result === -1) return null; + return result; + } + function decodeString(ptr, len) { if (len === 0) return ""; return text_decoder.decode(new Uint8Array(wasm_exports.memory.buffer, ptr, len)); } + + function setInputString(s) { + const jsArray = text_encoder.encode(s); + const len = jsArray.length; + const ptr = wasm_exports.set_input_string(len); + const wasmArray = new Uint8Array(wasm_exports.memory.buffer, ptr, len); + wasmArray.set(jsArray); + } + + function declSourceHtml(decl_index) { + return unwrapString(wasm_exports.decl_source_html(decl_index)); + } + + function unwrapString(bigint) { + const ptr = Number(bigint & 0xffffffffn); + const len = Number(bigint >> 32n); + return decodeString(ptr, len); + } })(); diff --git a/lib/fuzzer/wasm/main.zig b/lib/fuzzer/wasm/main.zig index 09b9d81068..5045f784cc 100644 --- a/lib/fuzzer/wasm/main.zig +++ b/lib/fuzzer/wasm/main.zig @@ -2,6 +2,8 @@ const std = @import("std"); const assert = std.debug.assert; const Walk = @import("Walk"); +const Decl = Walk.Decl; +const html_render = @import("html_render"); const gpa = std.heap.wasm_allocator; const log = std.log; @@ -52,6 +54,48 @@ export fn unpack(tar_ptr: [*]u8, tar_len: usize) void { }; } +/// Set by `set_input_string`. +var input_string: std.ArrayListUnmanaged(u8) = .{}; +var string_result: std.ArrayListUnmanaged(u8) = .{}; + +export fn set_input_string(len: usize) [*]u8 { + input_string.resize(gpa, len) catch @panic("OOM"); + return input_string.items.ptr; +} + +/// Looks up the root struct decl corresponding to a file by path. +/// Uses `input_string`. +export fn find_file_root() Decl.Index { + const file: Walk.File.Index = @enumFromInt(Walk.files.getIndex(input_string.items) orelse return .none); + return file.findRootDecl(); +} + +export fn decl_source_html(decl_index: Decl.Index) String { + const decl = decl_index.get(); + + string_result.clearRetainingCapacity(); + html_render.fileSourceHtml(decl.file, &string_result, decl.ast_node, .{}) catch |err| { + fatal("unable to render source: {s}", .{@errorName(err)}); + }; + return String.init(string_result.items); +} + +const String = Slice(u8); + +fn Slice(T: type) type { + return packed struct(u64) { + ptr: u32, + len: u32, + + fn init(s: []const T) @This() { + return .{ + .ptr = @intFromPtr(s.ptr), + .len = s.len, + }; + } + }; +} + fn unpackInner(tar_bytes: []u8) !void { var fbs = std.io.fixedBufferStream(tar_bytes); var file_name_buffer: [1024]u8 = undefined; diff --git a/lib/std/Build/Fuzz.zig b/lib/std/Build/Fuzz.zig index e26f587eac..46d9bfc8fd 100644 --- a/lib/std/Build/Fuzz.zig +++ b/lib/std/Build/Fuzz.zig @@ -235,30 +235,29 @@ pub const WebServer = struct { .root_dir = ws.zig_lib_directory, .sub_path = "docs/wasm/Walk.zig", }; + const html_render_src_path: Build.Cache.Path = .{ + .root_dir = ws.zig_lib_directory, + .sub_path = "docs/wasm/html_render.zig", + }; var argv: std.ArrayListUnmanaged([]const u8) = .{}; try argv.appendSlice(arena, &.{ - ws.zig_exe_path, - "build-exe", - "-fno-entry", - "-O", - @tagName(optimize_mode), - "-target", - "wasm32-freestanding", - "-mcpu", - "baseline+atomics+bulk_memory+multivalue+mutable_globals+nontrapping_fptoint+reference_types+sign_ext", - "--cache-dir", - ws.global_cache_directory.path orelse ".", - "--global-cache-dir", - ws.global_cache_directory.path orelse ".", - "--name", - "fuzzer", - "-rdynamic", - "--dep", - "Walk", - try std.fmt.allocPrint(arena, "-Mroot={}", .{main_src_path}), - try std.fmt.allocPrint(arena, "-MWalk={}", .{walk_src_path}), + ws.zig_exe_path, "build-exe", // + "-fno-entry", // + "-O", @tagName(optimize_mode), // + "-target", "wasm32-freestanding", // + "-mcpu", "baseline+atomics+bulk_memory+multivalue+mutable_globals+nontrapping_fptoint+reference_types+sign_ext", // + "--cache-dir", ws.global_cache_directory.path orelse ".", // + "--global-cache-dir", ws.global_cache_directory.path orelse ".", // + "--name", "fuzzer", // + "-rdynamic", // + "--dep", "Walk", // + "--dep", "html_render", // + try std.fmt.allocPrint(arena, "-Mroot={}", .{main_src_path}), // + try std.fmt.allocPrint(arena, "-MWalk={}", .{walk_src_path}), // + "--dep", "Walk", // + try std.fmt.allocPrint(arena, "-Mhtml_render={}", .{html_render_src_path}), // "--listen=-", }); From 2e12b45d8b43d69e144887df4b04a2d383ff25d4 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 2 Aug 2024 16:31:49 -0700 Subject: [PATCH 171/266] introduce tool for dumping coverage file with debug info resolved. begin efforts of providing `std.debug.Info`, a cross-platform abstraction for loading debug information into an in-memory format that supports queries such as "what is the source location of this virtual memory address?" Unlike `std.debug.SelfInfo`, this API does not assume the debug information in question happens to match the host CPU architecture, OS, or other target properties. --- lib/std/Build/Cache/Path.zig | 8 +- lib/std/debug.zig | 33 +-- lib/std/debug/Dwarf.zig | 394 ++++++++++++++++++++++++++++++++--- lib/std/debug/Info.zig | 57 +++++ lib/std/debug/SelfInfo.zig | 264 ++--------------------- tools/dump-cov.zig | 70 +++++++ 6 files changed, 541 insertions(+), 285 deletions(-) create mode 100644 lib/std/debug/Info.zig create mode 100644 tools/dump-cov.zig diff --git a/lib/std/Build/Cache/Path.zig b/lib/std/Build/Cache/Path.zig index b81786d0a8..65c6f6a9bc 100644 --- a/lib/std/Build/Cache/Path.zig +++ b/lib/std/Build/Cache/Path.zig @@ -32,16 +32,16 @@ pub fn resolvePosix(p: Path, arena: Allocator, sub_path: []const u8) Allocator.E }; } -pub fn joinString(p: Path, allocator: Allocator, sub_path: []const u8) Allocator.Error![]u8 { +pub fn joinString(p: Path, gpa: Allocator, sub_path: []const u8) Allocator.Error![]u8 { const parts: []const []const u8 = if (p.sub_path.len == 0) &.{sub_path} else &.{ p.sub_path, sub_path }; - return p.root_dir.join(allocator, parts); + return p.root_dir.join(gpa, parts); } -pub fn joinStringZ(p: Path, allocator: Allocator, sub_path: []const u8) Allocator.Error![:0]u8 { +pub fn joinStringZ(p: Path, gpa: Allocator, sub_path: []const u8) Allocator.Error![:0]u8 { const parts: []const []const u8 = if (p.sub_path.len == 0) &.{sub_path} else &.{ p.sub_path, sub_path }; - return p.root_dir.joinZ(allocator, parts); + return p.root_dir.joinZ(gpa, parts); } pub fn openFile( diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 4d3437f665..907f7711a7 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -17,6 +17,7 @@ pub const MemoryAccessor = @import("debug/MemoryAccessor.zig"); pub const Dwarf = @import("debug/Dwarf.zig"); pub const Pdb = @import("debug/Pdb.zig"); pub const SelfInfo = @import("debug/SelfInfo.zig"); +pub const Info = @import("debug/Info.zig"); /// Unresolved source locations can be represented with a single `usize` that /// corresponds to a virtual memory address of the program counter. Combined @@ -28,6 +29,12 @@ pub const SourceLocation = struct { file_name: []const u8, }; +pub const Symbol = struct { + name: []const u8 = "???", + compile_unit_name: []const u8 = "???", + source_location: ?SourceLocation = null, +}; + /// Deprecated because it returns the optimization mode of the standard /// library, when the caller probably wants to use the optimization mode of /// their own module. @@ -871,13 +878,13 @@ pub fn printSourceAtAddress(debug_info: *SelfInfo, out_stream: anytype, address: error.MissingDebugInfo, error.InvalidDebugInfo => return printUnknownSource(debug_info, out_stream, address, tty_config), else => return err, }; - defer symbol_info.deinit(debug_info.allocator); + defer if (symbol_info.source_location) |sl| debug_info.allocator.free(sl.file_name); return printLineInfo( out_stream, - symbol_info.line_info, + symbol_info.source_location, address, - symbol_info.symbol_name, + symbol_info.name, symbol_info.compile_unit_name, tty_config, printLineFromFileAnyOs, @@ -886,7 +893,7 @@ pub fn printSourceAtAddress(debug_info: *SelfInfo, out_stream: anytype, address: fn printLineInfo( out_stream: anytype, - line_info: ?SourceLocation, + source_location: ?SourceLocation, address: usize, symbol_name: []const u8, compile_unit_name: []const u8, @@ -896,8 +903,8 @@ fn printLineInfo( nosuspend { try tty_config.setColor(out_stream, .bold); - if (line_info) |*li| { - try out_stream.print("{s}:{d}:{d}", .{ li.file_name, li.line, li.column }); + if (source_location) |*sl| { + try out_stream.print("{s}:{d}:{d}", .{ sl.file_name, sl.line, sl.column }); } else { try out_stream.writeAll("???:?:?"); } @@ -910,11 +917,11 @@ fn printLineInfo( try out_stream.writeAll("\n"); // Show the matching source code line if possible - if (line_info) |li| { - if (printLineFromFile(out_stream, li)) { - if (li.column > 0) { + if (source_location) |sl| { + if (printLineFromFile(out_stream, sl)) { + if (sl.column > 0) { // The caret already takes one char - const space_needed = @as(usize, @intCast(li.column - 1)); + const space_needed = @as(usize, @intCast(sl.column - 1)); try out_stream.writeByteNTimes(' ', space_needed); try tty_config.setColor(out_stream, .green); @@ -932,10 +939,10 @@ fn printLineInfo( } } -fn printLineFromFileAnyOs(out_stream: anytype, line_info: SourceLocation) !void { +fn printLineFromFileAnyOs(out_stream: anytype, source_location: SourceLocation) !void { // Need this to always block even in async I/O mode, because this could potentially // be called from e.g. the event loop code crashing. - var f = try fs.cwd().openFile(line_info.file_name, .{}); + var f = try fs.cwd().openFile(source_location.file_name, .{}); defer f.close(); // TODO fstat and make sure that the file has the correct size @@ -944,7 +951,7 @@ fn printLineFromFileAnyOs(out_stream: anytype, line_info: SourceLocation) !void const line_start = seek: { var current_line_start: usize = 0; var next_line: usize = 1; - while (next_line != line_info.line) { + while (next_line != source_location.line) { const slice = buf[current_line_start..amt_read]; if (mem.indexOfScalar(u8, slice, '\n')) |pos| { next_line += 1; diff --git a/lib/std/debug/Dwarf.zig b/lib/std/debug/Dwarf.zig index 991c731549..3c150b3b18 100644 --- a/lib/std/debug/Dwarf.zig +++ b/lib/std/debug/Dwarf.zig @@ -12,6 +12,8 @@ const native_endian = builtin.cpu.arch.endian(); const std = @import("../std.zig"); const Allocator = std.mem.Allocator; +const elf = std.elf; +const mem = std.mem; const DW = std.dwarf; const AT = DW.AT; const EH = DW.EH; @@ -22,8 +24,8 @@ const UT = DW.UT; const assert = std.debug.assert; const cast = std.math.cast; const maxInt = std.math.maxInt; -const readInt = std.mem.readInt; const MemoryAccessor = std.debug.MemoryAccessor; +const Path = std.Build.Cache.Path; /// Did I mention this is deprecated? const DeprecatedFixedBufferReader = std.debug.DeprecatedFixedBufferReader; @@ -252,13 +254,13 @@ pub const Die = struct { .@"32" => { const byte_offset = compile_unit.str_offsets_base + 4 * index; if (byte_offset + 4 > debug_str_offsets.len) return bad(); - const offset = readInt(u32, debug_str_offsets[byte_offset..][0..4], di.endian); + const offset = mem.readInt(u32, debug_str_offsets[byte_offset..][0..4], di.endian); return getStringGeneric(opt_str, offset); }, .@"64" => { const byte_offset = compile_unit.str_offsets_base + 8 * index; if (byte_offset + 8 > debug_str_offsets.len) return bad(); - const offset = readInt(u64, debug_str_offsets[byte_offset..][0..8], di.endian); + const offset = mem.readInt(u64, debug_str_offsets[byte_offset..][0..8], di.endian); return getStringGeneric(opt_str, offset); }, } @@ -721,12 +723,14 @@ const num_sections = std.enums.directEnumArrayLen(Section.Id, 0); pub const SectionArray = [num_sections]?Section; pub const null_section_array = [_]?Section{null} ** num_sections; +pub const OpenError = ScanError; + /// Initialize DWARF info. The caller has the responsibility to initialize most /// the `Dwarf` fields before calling. `binary_mem` is the raw bytes of the /// main binary file (not the secondary debug info file). -pub fn open(di: *Dwarf, allocator: Allocator) !void { - try di.scanAllFunctions(allocator); - try di.scanAllCompileUnits(allocator); +pub fn open(di: *Dwarf, gpa: Allocator) OpenError!void { + try di.scanAllFunctions(gpa); + try di.scanAllCompileUnits(gpa); } const PcRange = struct { @@ -747,21 +751,21 @@ pub fn sectionVirtualOffset(di: Dwarf, dwarf_section: Section.Id, base_address: return if (di.sections[@intFromEnum(dwarf_section)]) |s| s.virtualOffset(base_address) else null; } -pub fn deinit(di: *Dwarf, allocator: Allocator) void { +pub fn deinit(di: *Dwarf, gpa: Allocator) void { for (di.sections) |opt_section| { - if (opt_section) |s| if (s.owned) allocator.free(s.data); + if (opt_section) |s| if (s.owned) gpa.free(s.data); } for (di.abbrev_table_list.items) |*abbrev| { - abbrev.deinit(allocator); + abbrev.deinit(gpa); } - di.abbrev_table_list.deinit(allocator); + di.abbrev_table_list.deinit(gpa); for (di.compile_unit_list.items) |*cu| { - cu.die.deinit(allocator); + cu.die.deinit(gpa); } - di.compile_unit_list.deinit(allocator); - di.func_list.deinit(allocator); - di.cie_map.deinit(allocator); - di.fde_list.deinit(allocator); + di.compile_unit_list.deinit(gpa); + di.func_list.deinit(gpa); + di.cie_map.deinit(gpa); + di.fde_list.deinit(gpa); di.* = undefined; } @@ -777,7 +781,12 @@ pub fn getSymbolName(di: *Dwarf, address: u64) ?[]const u8 { return null; } -fn scanAllFunctions(di: *Dwarf, allocator: Allocator) !void { +const ScanError = error{ + InvalidDebugInfo, + MissingDebugInfo, +} || Allocator.Error || std.debug.DeprecatedFixedBufferReader.Error; + +fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void { var fbr: DeprecatedFixedBufferReader = .{ .buf = di.section(.debug_info).?, .endian = di.endian }; var this_unit_offset: u64 = 0; @@ -964,7 +973,7 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) !void { } } -fn scanAllCompileUnits(di: *Dwarf, allocator: Allocator) !void { +fn scanAllCompileUnits(di: *Dwarf, allocator: Allocator) ScanError!void { var fbr: DeprecatedFixedBufferReader = .{ .buf = di.section(.debug_info).?, .endian = di.endian }; var this_unit_offset: u64 = 0; @@ -1070,13 +1079,13 @@ const DebugRangeIterator = struct { .@"32" => { const offset_loc = @as(usize, @intCast(compile_unit.rnglists_base + 4 * idx)); if (offset_loc + 4 > debug_ranges.len) return bad(); - const offset = readInt(u32, debug_ranges[offset_loc..][0..4], di.endian); + const offset = mem.readInt(u32, debug_ranges[offset_loc..][0..4], di.endian); break :off compile_unit.rnglists_base + offset; }, .@"64" => { const offset_loc = @as(usize, @intCast(compile_unit.rnglists_base + 8 * idx)); if (offset_loc + 8 > debug_ranges.len) return bad(); - const offset = readInt(u64, debug_ranges[offset_loc..][0..8], di.endian); + const offset = mem.readInt(u64, debug_ranges[offset_loc..][0..8], di.endian); break :off compile_unit.rnglists_base + offset; }, } @@ -1287,7 +1296,7 @@ fn parseDie( attrs_buf: []Die.Attr, abbrev_table: *const Abbrev.Table, format: Format, -) !?Die { +) ScanError!?Die { const abbrev_code = try fbr.readUleb128(u64); if (abbrev_code == 0) return null; const table_entry = abbrev_table.get(abbrev_code) orelse return bad(); @@ -1588,7 +1597,7 @@ fn readDebugAddr(di: Dwarf, compile_unit: CompileUnit, index: u64) !u64 { // The header is 8 or 12 bytes depending on is_64. if (compile_unit.addr_base < 8) return bad(); - const version = readInt(u16, debug_addr[compile_unit.addr_base - 4 ..][0..2], di.endian); + const version = mem.readInt(u16, debug_addr[compile_unit.addr_base - 4 ..][0..2], di.endian); if (version != 5) return bad(); const addr_size = debug_addr[compile_unit.addr_base - 2]; @@ -1598,9 +1607,9 @@ fn readDebugAddr(di: Dwarf, compile_unit: CompileUnit, index: u64) !u64 { if (byte_offset + addr_size > debug_addr.len) return bad(); return switch (addr_size) { 1 => debug_addr[byte_offset], - 2 => readInt(u16, debug_addr[byte_offset..][0..2], di.endian), - 4 => readInt(u32, debug_addr[byte_offset..][0..4], di.endian), - 8 => readInt(u64, debug_addr[byte_offset..][0..8], di.endian), + 2 => mem.readInt(u16, debug_addr[byte_offset..][0..2], di.endian), + 4 => mem.readInt(u32, debug_addr[byte_offset..][0..4], di.endian), + 8 => mem.readInt(u64, debug_addr[byte_offset..][0..8], di.endian), else => bad(), }; } @@ -1699,7 +1708,7 @@ fn parseFormValue( form_id: u64, format: Format, implicit_const: ?i64, -) anyerror!FormValue { +) ScanError!FormValue { return switch (form_id) { FORM.addr => .{ .addr = try fbr.readAddress(switch (@bitSizeOf(usize)) { 32 => .@"32", @@ -1892,7 +1901,8 @@ const UnitHeader = struct { header_length: u4, unit_length: u64, }; -fn readUnitHeader(fbr: *DeprecatedFixedBufferReader, opt_ma: ?*MemoryAccessor) !UnitHeader { + +fn readUnitHeader(fbr: *DeprecatedFixedBufferReader, opt_ma: ?*MemoryAccessor) ScanError!UnitHeader { return switch (try if (opt_ma) |ma| fbr.readIntChecked(u32, ma) else fbr.readInt(u32)) { 0...0xfffffff0 - 1 => |unit_length| .{ .format = .@"32", @@ -2023,3 +2033,335 @@ fn pcRelBase(field_ptr: usize, pc_rel_offset: i64) !usize { return std.math.add(usize, field_ptr, @as(usize, @intCast(pc_rel_offset))); } } + +pub const ElfModule = struct { + base_address: usize, + dwarf: Dwarf, + mapped_memory: []align(std.mem.page_size) const u8, + external_mapped_memory: ?[]align(std.mem.page_size) const u8, + + pub fn deinit(self: *@This(), allocator: Allocator) void { + self.dwarf.deinit(allocator); + std.posix.munmap(self.mapped_memory); + if (self.external_mapped_memory) |m| std.posix.munmap(m); + } + + pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !std.debug.Symbol { + // Translate the VA into an address into this object + const relocated_address = address - self.base_address; + return self.dwarf.getSymbol(allocator, relocated_address); + } + + pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*const Dwarf { + _ = allocator; + _ = address; + return &self.dwarf; + } + + pub const LoadError = error{ + InvalidDebugInfo, + MissingDebugInfo, + InvalidElfMagic, + InvalidElfVersion, + InvalidElfEndian, + /// TODO: implement this and then remove this error code + UnimplementedDwarfForeignEndian, + /// The debug info may be valid but this implementation uses memory + /// mapping which limits things to usize. If the target debug info is + /// 64-bit and host is 32-bit, there may be debug info that is not + /// supportable using this method. + Overflow, + + PermissionDenied, + LockedMemoryLimitExceeded, + MemoryMappingNotSupported, + } || Allocator.Error || std.fs.File.OpenError || OpenError; + + /// Reads debug info from an already mapped ELF file. + /// + /// If the required sections aren't present but a reference to external debug + /// info is, then this this function will recurse to attempt to load the debug + /// sections from an external file. + pub fn load( + gpa: Allocator, + mapped_mem: []align(std.mem.page_size) const u8, + build_id: ?[]const u8, + expected_crc: ?u32, + parent_sections: *Dwarf.SectionArray, + parent_mapped_mem: ?[]align(std.mem.page_size) const u8, + elf_filename: ?[]const u8, + ) LoadError!Dwarf.ElfModule { + if (expected_crc) |crc| if (crc != std.hash.crc.Crc32.hash(mapped_mem)) return error.InvalidDebugInfo; + + const hdr: *const elf.Ehdr = @ptrCast(&mapped_mem[0]); + if (!mem.eql(u8, hdr.e_ident[0..4], elf.MAGIC)) return error.InvalidElfMagic; + if (hdr.e_ident[elf.EI_VERSION] != 1) return error.InvalidElfVersion; + + const endian: std.builtin.Endian = switch (hdr.e_ident[elf.EI_DATA]) { + elf.ELFDATA2LSB => .little, + elf.ELFDATA2MSB => .big, + else => return error.InvalidElfEndian, + }; + if (endian != native_endian) return error.UnimplementedDwarfForeignEndian; + + const shoff = hdr.e_shoff; + const str_section_off = shoff + @as(u64, hdr.e_shentsize) * @as(u64, hdr.e_shstrndx); + const str_shdr: *const elf.Shdr = @ptrCast(@alignCast(&mapped_mem[cast(usize, str_section_off) orelse return error.Overflow])); + const header_strings = mapped_mem[str_shdr.sh_offset..][0..str_shdr.sh_size]; + const shdrs = @as( + [*]const elf.Shdr, + @ptrCast(@alignCast(&mapped_mem[shoff])), + )[0..hdr.e_shnum]; + + var sections: Dwarf.SectionArray = Dwarf.null_section_array; + + // Combine section list. This takes ownership over any owned sections from the parent scope. + for (parent_sections, §ions) |*parent, *section_elem| { + if (parent.*) |*p| { + section_elem.* = p.*; + p.owned = false; + } + } + errdefer for (sections) |opt_section| if (opt_section) |s| if (s.owned) gpa.free(s.data); + + var separate_debug_filename: ?[]const u8 = null; + var separate_debug_crc: ?u32 = null; + + for (shdrs) |*shdr| { + if (shdr.sh_type == elf.SHT_NULL or shdr.sh_type == elf.SHT_NOBITS) continue; + const name = mem.sliceTo(header_strings[shdr.sh_name..], 0); + + if (mem.eql(u8, name, ".gnu_debuglink")) { + const gnu_debuglink = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size); + const debug_filename = mem.sliceTo(@as([*:0]const u8, @ptrCast(gnu_debuglink.ptr)), 0); + const crc_offset = mem.alignForward(usize, @intFromPtr(&debug_filename[debug_filename.len]) + 1, 4) - @intFromPtr(gnu_debuglink.ptr); + const crc_bytes = gnu_debuglink[crc_offset..][0..4]; + separate_debug_crc = mem.readInt(u32, crc_bytes, native_endian); + separate_debug_filename = debug_filename; + continue; + } + + var section_index: ?usize = null; + inline for (@typeInfo(Dwarf.Section.Id).Enum.fields, 0..) |sect, i| { + if (mem.eql(u8, "." ++ sect.name, name)) section_index = i; + } + if (section_index == null) continue; + if (sections[section_index.?] != null) continue; + + const section_bytes = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size); + sections[section_index.?] = if ((shdr.sh_flags & elf.SHF_COMPRESSED) > 0) blk: { + var section_stream = std.io.fixedBufferStream(section_bytes); + const section_reader = section_stream.reader(); + const chdr = section_reader.readStruct(elf.Chdr) catch continue; + if (chdr.ch_type != .ZLIB) continue; + + var zlib_stream = std.compress.zlib.decompressor(section_reader); + + const decompressed_section = try gpa.alloc(u8, chdr.ch_size); + errdefer gpa.free(decompressed_section); + + const read = zlib_stream.reader().readAll(decompressed_section) catch continue; + assert(read == decompressed_section.len); + + break :blk .{ + .data = decompressed_section, + .virtual_address = shdr.sh_addr, + .owned = true, + }; + } else .{ + .data = section_bytes, + .virtual_address = shdr.sh_addr, + .owned = false, + }; + } + + const missing_debug_info = + sections[@intFromEnum(Dwarf.Section.Id.debug_info)] == null or + sections[@intFromEnum(Dwarf.Section.Id.debug_abbrev)] == null or + sections[@intFromEnum(Dwarf.Section.Id.debug_str)] == null or + sections[@intFromEnum(Dwarf.Section.Id.debug_line)] == null; + + // Attempt to load debug info from an external file + // See: https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html + if (missing_debug_info) { + + // Only allow one level of debug info nesting + if (parent_mapped_mem) |_| { + return error.MissingDebugInfo; + } + + const global_debug_directories = [_][]const u8{ + "/usr/lib/debug", + }; + + // /.build-id/<2-character id prefix>/.debug + if (build_id) |id| blk: { + if (id.len < 3) break :blk; + + // Either md5 (16 bytes) or sha1 (20 bytes) are used here in practice + const extension = ".debug"; + var id_prefix_buf: [2]u8 = undefined; + var filename_buf: [38 + extension.len]u8 = undefined; + + _ = std.fmt.bufPrint(&id_prefix_buf, "{s}", .{std.fmt.fmtSliceHexLower(id[0..1])}) catch unreachable; + const filename = std.fmt.bufPrint( + &filename_buf, + "{s}" ++ extension, + .{std.fmt.fmtSliceHexLower(id[1..])}, + ) catch break :blk; + + for (global_debug_directories) |global_directory| { + const path: Path = .{ + .root_dir = std.Build.Cache.Directory.cwd(), + .sub_path = try std.fs.path.join(gpa, &.{ + global_directory, ".build-id", &id_prefix_buf, filename, + }), + }; + defer gpa.free(path.sub_path); + + return loadPath(gpa, path, null, separate_debug_crc, §ions, mapped_mem) catch continue; + } + } + + // use the path from .gnu_debuglink, in the same search order as gdb + if (separate_debug_filename) |separate_filename| blk: { + if (elf_filename != null and mem.eql(u8, elf_filename.?, separate_filename)) + return error.MissingDebugInfo; + + // / + if (loadPath( + gpa, + .{ + .root_dir = std.Build.Cache.Directory.cwd(), + .sub_path = separate_filename, + }, + null, + separate_debug_crc, + §ions, + mapped_mem, + )) |debug_info| { + return debug_info; + } else |_| {} + + // /.debug/ + { + const path: Path = .{ + .root_dir = std.Build.Cache.Directory.cwd(), + .sub_path = try std.fs.path.join(gpa, &.{ ".debug", separate_filename }), + }; + defer gpa.free(path.sub_path); + + if (loadPath(gpa, path, null, separate_debug_crc, §ions, mapped_mem)) |debug_info| return debug_info else |_| {} + } + + var cwd_buf: [std.fs.max_path_bytes]u8 = undefined; + const cwd_path = std.posix.realpath(".", &cwd_buf) catch break :blk; + + // // + for (global_debug_directories) |global_directory| { + const path: Path = .{ + .root_dir = std.Build.Cache.Directory.cwd(), + .sub_path = try std.fs.path.join(gpa, &.{ global_directory, cwd_path, separate_filename }), + }; + defer gpa.free(path.sub_path); + if (loadPath(gpa, path, null, separate_debug_crc, §ions, mapped_mem)) |debug_info| return debug_info else |_| {} + } + } + + return error.MissingDebugInfo; + } + + var di: Dwarf = .{ + .endian = endian, + .sections = sections, + .is_macho = false, + }; + + try Dwarf.open(&di, gpa); + + return .{ + .base_address = 0, + .dwarf = di, + .mapped_memory = parent_mapped_mem orelse mapped_mem, + .external_mapped_memory = if (parent_mapped_mem != null) mapped_mem else null, + }; + } + + pub fn loadPath( + gpa: Allocator, + elf_file_path: Path, + build_id: ?[]const u8, + expected_crc: ?u32, + parent_sections: *Dwarf.SectionArray, + parent_mapped_mem: ?[]align(std.mem.page_size) const u8, + ) LoadError!Dwarf.ElfModule { + const elf_file = elf_file_path.root_dir.handle.openFile(elf_file_path.sub_path, .{}) catch |err| switch (err) { + error.FileNotFound => return missing(), + else => return err, + }; + defer elf_file.close(); + + const end_pos = elf_file.getEndPos() catch return bad(); + const file_len = cast(usize, end_pos) orelse return error.Overflow; + + const mapped_mem = try std.posix.mmap( + null, + file_len, + std.posix.PROT.READ, + .{ .TYPE = .SHARED }, + elf_file.handle, + 0, + ); + errdefer std.posix.munmap(mapped_mem); + + return load( + gpa, + mapped_mem, + build_id, + expected_crc, + parent_sections, + parent_mapped_mem, + elf_file_path.sub_path, + ); + } +}; + +/// Given an array of virtual memory addresses, sorted ascending, outputs a +/// corresponding array of source locations, by appending to the provided +/// array list. +pub fn resolveSourceLocations( + d: *Dwarf, + gpa: Allocator, + sorted_pc_addrs: []const u64, + /// Asserts its length equals length of `sorted_pc_addrs`. + output: []std.debug.SourceLocation, +) error{ MissingDebugInfo, InvalidDebugInfo }!void { + assert(sorted_pc_addrs.len == output.len); + _ = d; + _ = gpa; + @panic("TODO"); +} + +fn getSymbol(di: *Dwarf, allocator: Allocator, address: u64) !std.debug.Symbol { + if (di.findCompileUnit(address)) |compile_unit| { + return .{ + .name = di.getSymbolName(address) orelse "???", + .compile_unit_name = compile_unit.die.getAttrString(di, std.dwarf.AT.name, di.section(.debug_str), compile_unit.*) catch |err| switch (err) { + error.MissingDebugInfo, error.InvalidDebugInfo => "???", + }, + .source_location = di.getLineNumberInfo(allocator, compile_unit.*, address) catch |err| switch (err) { + error.MissingDebugInfo, error.InvalidDebugInfo => null, + else => return err, + }, + }; + } else |err| switch (err) { + error.MissingDebugInfo, error.InvalidDebugInfo => return .{}, + else => return err, + } +} + +pub fn chopSlice(ptr: []const u8, offset: u64, size: u64) error{Overflow}![]const u8 { + const start = cast(usize, offset) orelse return error.Overflow; + const end = start + (cast(usize, size) orelse return error.Overflow); + return ptr[start..end]; +} diff --git a/lib/std/debug/Info.zig b/lib/std/debug/Info.zig new file mode 100644 index 0000000000..5276ba68ec --- /dev/null +++ b/lib/std/debug/Info.zig @@ -0,0 +1,57 @@ +//! Cross-platform abstraction for loading debug information into an in-memory +//! format that supports queries such as "what is the source location of this +//! virtual memory address?" +//! +//! Unlike `std.debug.SelfInfo`, this API does not assume the debug information +//! in question happens to match the host CPU architecture, OS, or other target +//! properties. + +const std = @import("../std.zig"); +const Allocator = std.mem.Allocator; +const Path = std.Build.Cache.Path; +const Dwarf = std.debug.Dwarf; +const page_size = std.mem.page_size; +const assert = std.debug.assert; + +const Info = @This(); + +/// Sorted by key, ascending. +address_map: std.AutoArrayHashMapUnmanaged(u64, Dwarf.ElfModule), + +pub const LoadError = Dwarf.ElfModule.LoadError; + +pub fn load(gpa: Allocator, path: Path) LoadError!Info { + var sections: Dwarf.SectionArray = Dwarf.null_section_array; + const elf_module = try Dwarf.ElfModule.loadPath(gpa, path, null, null, §ions, null); + var info: Info = .{ + .address_map = .{}, + }; + try info.address_map.put(gpa, elf_module.base_address, elf_module); + return info; +} + +pub fn deinit(info: *Info, gpa: Allocator) void { + for (info.address_map.values()) |*elf_module| { + elf_module.dwarf.deinit(gpa); + } + info.address_map.deinit(gpa); + info.* = undefined; +} + +pub const ResolveSourceLocationsError = error{ + MissingDebugInfo, + InvalidDebugInfo, +} || Allocator.Error; + +pub fn resolveSourceLocations( + info: *Info, + gpa: Allocator, + sorted_pc_addrs: []const u64, + /// Asserts its length equals length of `sorted_pc_addrs`. + output: []std.debug.SourceLocation, +) ResolveSourceLocationsError!void { + assert(sorted_pc_addrs.len == output.len); + if (info.address_map.entries.len != 1) @panic("TODO"); + const elf_module = &info.address_map.values()[0]; + return elf_module.dwarf.resolveSourceLocations(gpa, sorted_pc_addrs, output); +} diff --git a/lib/std/debug/SelfInfo.zig b/lib/std/debug/SelfInfo.zig index f9747a088e..79cbd19a41 100644 --- a/lib/std/debug/SelfInfo.zig +++ b/lib/std/debug/SelfInfo.zig @@ -587,7 +587,7 @@ pub const Module = switch (native_os) { } if (section_index == null) continue; - const section_bytes = try chopSlice(mapped_mem, sect.offset, sect.size); + const section_bytes = try Dwarf.chopSlice(mapped_mem, sect.offset, sect.size); sections[section_index.?] = .{ .data = section_bytes, .virtual_address = sect.addr, @@ -622,7 +622,7 @@ pub const Module = switch (native_os) { return result.value_ptr; } - pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !SymbolInfo { + pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !Dwarf.SymbolInfo { nosuspend { const result = try self.getOFileInfoForAddress(allocator, address); if (result.symbol == null) return .{}; @@ -641,7 +641,7 @@ pub const Module = switch (native_os) { const addr_off = result.relocated_address - result.symbol.?.addr; const o_file_di = &result.o_file_info.?.di; if (o_file_di.findCompileUnit(relocated_address_o)) |compile_unit| { - return SymbolInfo{ + return .{ .symbol_name = o_file_di.getSymbolName(relocated_address_o) orelse "???", .compile_unit_name = compile_unit.die.getAttrString( o_file_di, @@ -662,7 +662,7 @@ pub const Module = switch (native_os) { }; } else |err| switch (err) { error.MissingDebugInfo, error.InvalidDebugInfo => { - return SymbolInfo{ .symbol_name = stab_symbol }; + return .{ .symbol_name = stab_symbol }; }, else => return err, } @@ -729,7 +729,7 @@ pub const Module = switch (native_os) { } } - fn getSymbolFromPdb(self: *@This(), relocated_address: usize) !?SymbolInfo { + fn getSymbolFromPdb(self: *@This(), relocated_address: usize) !?std.debug.Symbol { var coff_section: *align(1) const coff.SectionHeader = undefined; const mod_index = for (self.pdb.?.sect_contribs) |sect_contrib| { if (sect_contrib.Section > self.coff_section_headers.len) continue; @@ -759,14 +759,14 @@ pub const Module = switch (native_os) { relocated_address - coff_section.virtual_address, ); - return SymbolInfo{ + return .{ .symbol_name = symbol_name, .compile_unit_name = obj_basename, .line_info = opt_line_info, }; } - pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !SymbolInfo { + pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !std.debug.Symbol { // Translate the VA into an address into this object const relocated_address = address - self.base_address; @@ -776,10 +776,10 @@ pub const Module = switch (native_os) { if (self.dwarf) |*dwarf| { const dwarf_address = relocated_address + self.coff_image_base; - return getSymbolFromDwarf(allocator, dwarf_address, dwarf); + return dwarf.getSymbol(allocator, dwarf_address); } - return SymbolInfo{}; + return .{}; } pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*const Dwarf { @@ -792,41 +792,18 @@ pub const Module = switch (native_os) { }; } }, - .linux, .netbsd, .freebsd, .dragonfly, .openbsd, .haiku, .solaris, .illumos => struct { - base_address: usize, - dwarf: Dwarf, - mapped_memory: []align(mem.page_size) const u8, - external_mapped_memory: ?[]align(mem.page_size) const u8, - - pub fn deinit(self: *@This(), allocator: Allocator) void { - self.dwarf.deinit(allocator); - posix.munmap(self.mapped_memory); - if (self.external_mapped_memory) |m| posix.munmap(m); - } - - pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !SymbolInfo { - // Translate the VA into an address into this object - const relocated_address = address - self.base_address; - return getSymbolFromDwarf(allocator, relocated_address, &self.dwarf); - } - - pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*const Dwarf { - _ = allocator; - _ = address; - return &self.dwarf; - } - }, + .linux, .netbsd, .freebsd, .dragonfly, .openbsd, .haiku, .solaris, .illumos => Dwarf.ElfModule, .wasi, .emscripten => struct { pub fn deinit(self: *@This(), allocator: Allocator) void { _ = self; _ = allocator; } - pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !SymbolInfo { + pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !std.debug.Symbol { _ = self; _ = allocator; _ = address; - return SymbolInfo{}; + return .{}; } pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*const Dwarf { @@ -1068,7 +1045,7 @@ pub fn readElfDebugInfo( expected_crc: ?u32, parent_sections: *Dwarf.SectionArray, parent_mapped_mem: ?[]align(mem.page_size) const u8, -) !Module { +) !Dwarf.ElfModule { nosuspend { const elf_file = (if (elf_filename) |filename| blk: { break :blk fs.cwd().openFile(filename, .{}); @@ -1078,176 +1055,15 @@ pub fn readElfDebugInfo( }; const mapped_mem = try mapWholeFile(elf_file); - if (expected_crc) |crc| if (crc != std.hash.crc.Crc32.hash(mapped_mem)) return error.InvalidDebugInfo; - - const hdr: *const elf.Ehdr = @ptrCast(&mapped_mem[0]); - if (!mem.eql(u8, hdr.e_ident[0..4], elf.MAGIC)) return error.InvalidElfMagic; - if (hdr.e_ident[elf.EI_VERSION] != 1) return error.InvalidElfVersion; - - const endian: std.builtin.Endian = switch (hdr.e_ident[elf.EI_DATA]) { - elf.ELFDATA2LSB => .little, - elf.ELFDATA2MSB => .big, - else => return error.InvalidElfEndian, - }; - assert(endian == native_endian); // this is our own debug info - - const shoff = hdr.e_shoff; - const str_section_off = shoff + @as(u64, hdr.e_shentsize) * @as(u64, hdr.e_shstrndx); - const str_shdr: *const elf.Shdr = @ptrCast(@alignCast(&mapped_mem[math.cast(usize, str_section_off) orelse return error.Overflow])); - const header_strings = mapped_mem[str_shdr.sh_offset..][0..str_shdr.sh_size]; - const shdrs = @as( - [*]const elf.Shdr, - @ptrCast(@alignCast(&mapped_mem[shoff])), - )[0..hdr.e_shnum]; - - var sections: Dwarf.SectionArray = Dwarf.null_section_array; - - // Combine section list. This takes ownership over any owned sections from the parent scope. - for (parent_sections, §ions) |*parent, *section| { - if (parent.*) |*p| { - section.* = p.*; - p.owned = false; - } - } - errdefer for (sections) |section| if (section) |s| if (s.owned) allocator.free(s.data); - - var separate_debug_filename: ?[]const u8 = null; - var separate_debug_crc: ?u32 = null; - - for (shdrs) |*shdr| { - if (shdr.sh_type == elf.SHT_NULL or shdr.sh_type == elf.SHT_NOBITS) continue; - const name = mem.sliceTo(header_strings[shdr.sh_name..], 0); - - if (mem.eql(u8, name, ".gnu_debuglink")) { - const gnu_debuglink = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size); - const debug_filename = mem.sliceTo(@as([*:0]const u8, @ptrCast(gnu_debuglink.ptr)), 0); - const crc_offset = mem.alignForward(usize, @intFromPtr(&debug_filename[debug_filename.len]) + 1, 4) - @intFromPtr(gnu_debuglink.ptr); - const crc_bytes = gnu_debuglink[crc_offset..][0..4]; - separate_debug_crc = mem.readInt(u32, crc_bytes, native_endian); - separate_debug_filename = debug_filename; - continue; - } - - var section_index: ?usize = null; - inline for (@typeInfo(Dwarf.Section.Id).Enum.fields, 0..) |section, i| { - if (mem.eql(u8, "." ++ section.name, name)) section_index = i; - } - if (section_index == null) continue; - if (sections[section_index.?] != null) continue; - - const section_bytes = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size); - sections[section_index.?] = if ((shdr.sh_flags & elf.SHF_COMPRESSED) > 0) blk: { - var section_stream = std.io.fixedBufferStream(section_bytes); - var section_reader = section_stream.reader(); - const chdr = section_reader.readStruct(elf.Chdr) catch continue; - if (chdr.ch_type != .ZLIB) continue; - - var zlib_stream = std.compress.zlib.decompressor(section_stream.reader()); - - const decompressed_section = try allocator.alloc(u8, chdr.ch_size); - errdefer allocator.free(decompressed_section); - - const read = zlib_stream.reader().readAll(decompressed_section) catch continue; - assert(read == decompressed_section.len); - - break :blk .{ - .data = decompressed_section, - .virtual_address = shdr.sh_addr, - .owned = true, - }; - } else .{ - .data = section_bytes, - .virtual_address = shdr.sh_addr, - .owned = false, - }; - } - - const missing_debug_info = - sections[@intFromEnum(Dwarf.Section.Id.debug_info)] == null or - sections[@intFromEnum(Dwarf.Section.Id.debug_abbrev)] == null or - sections[@intFromEnum(Dwarf.Section.Id.debug_str)] == null or - sections[@intFromEnum(Dwarf.Section.Id.debug_line)] == null; - - // Attempt to load debug info from an external file - // See: https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html - if (missing_debug_info) { - - // Only allow one level of debug info nesting - if (parent_mapped_mem) |_| { - return error.MissingDebugInfo; - } - - const global_debug_directories = [_][]const u8{ - "/usr/lib/debug", - }; - - // /.build-id/<2-character id prefix>/.debug - if (build_id) |id| blk: { - if (id.len < 3) break :blk; - - // Either md5 (16 bytes) or sha1 (20 bytes) are used here in practice - const extension = ".debug"; - var id_prefix_buf: [2]u8 = undefined; - var filename_buf: [38 + extension.len]u8 = undefined; - - _ = std.fmt.bufPrint(&id_prefix_buf, "{s}", .{std.fmt.fmtSliceHexLower(id[0..1])}) catch unreachable; - const filename = std.fmt.bufPrint( - &filename_buf, - "{s}" ++ extension, - .{std.fmt.fmtSliceHexLower(id[1..])}, - ) catch break :blk; - - for (global_debug_directories) |global_directory| { - const path = try fs.path.join(allocator, &.{ global_directory, ".build-id", &id_prefix_buf, filename }); - defer allocator.free(path); - - return readElfDebugInfo(allocator, path, null, separate_debug_crc, §ions, mapped_mem) catch continue; - } - } - - // use the path from .gnu_debuglink, in the same search order as gdb - if (separate_debug_filename) |separate_filename| blk: { - if (elf_filename != null and mem.eql(u8, elf_filename.?, separate_filename)) return error.MissingDebugInfo; - - // / - if (readElfDebugInfo(allocator, separate_filename, null, separate_debug_crc, §ions, mapped_mem)) |debug_info| return debug_info else |_| {} - - // /.debug/ - { - const path = try fs.path.join(allocator, &.{ ".debug", separate_filename }); - defer allocator.free(path); - - if (readElfDebugInfo(allocator, path, null, separate_debug_crc, §ions, mapped_mem)) |debug_info| return debug_info else |_| {} - } - - var cwd_buf: [fs.max_path_bytes]u8 = undefined; - const cwd_path = posix.realpath(".", &cwd_buf) catch break :blk; - - // // - for (global_debug_directories) |global_directory| { - const path = try fs.path.join(allocator, &.{ global_directory, cwd_path, separate_filename }); - defer allocator.free(path); - if (readElfDebugInfo(allocator, path, null, separate_debug_crc, §ions, mapped_mem)) |debug_info| return debug_info else |_| {} - } - } - - return error.MissingDebugInfo; - } - - var di = Dwarf{ - .endian = endian, - .sections = sections, - .is_macho = false, - }; - - try Dwarf.open(&di, allocator); - - return .{ - .base_address = undefined, - .dwarf = di, - .mapped_memory = parent_mapped_mem orelse mapped_mem, - .external_mapped_memory = if (parent_mapped_mem != null) mapped_mem else null, - }; + return Dwarf.ElfModule.load( + allocator, + mapped_mem, + build_id, + expected_crc, + parent_sections, + parent_mapped_mem, + elf_filename, + ); } } @@ -1289,22 +1105,6 @@ fn mapWholeFile(file: File) ![]align(mem.page_size) const u8 { } } -fn chopSlice(ptr: []const u8, offset: u64, size: u64) error{Overflow}![]const u8 { - const start = math.cast(usize, offset) orelse return error.Overflow; - const end = start + (math.cast(usize, size) orelse return error.Overflow); - return ptr[start..end]; -} - -pub const SymbolInfo = struct { - symbol_name: []const u8 = "???", - compile_unit_name: []const u8 = "???", - line_info: ?std.debug.SourceLocation = null, - - pub fn deinit(self: SymbolInfo, allocator: Allocator) void { - if (self.line_info) |li| allocator.free(li.file_name); - } -}; - fn machoSearchSymbols(symbols: []const MachoSymbol, address: usize) ?*const MachoSymbol { var min: usize = 0; var max: usize = symbols.len - 1; @@ -1350,26 +1150,6 @@ test machoSearchSymbols { try testing.expectEqual(&symbols[2], machoSearchSymbols(&symbols, 5000).?); } -fn getSymbolFromDwarf(allocator: Allocator, address: u64, di: *Dwarf) !SymbolInfo { - if (nosuspend di.findCompileUnit(address)) |compile_unit| { - return SymbolInfo{ - .symbol_name = nosuspend di.getSymbolName(address) orelse "???", - .compile_unit_name = compile_unit.die.getAttrString(di, std.dwarf.AT.name, di.section(.debug_str), compile_unit.*) catch |err| switch (err) { - error.MissingDebugInfo, error.InvalidDebugInfo => "???", - }, - .line_info = nosuspend di.getLineNumberInfo(allocator, compile_unit.*, address) catch |err| switch (err) { - error.MissingDebugInfo, error.InvalidDebugInfo => null, - else => return err, - }, - }; - } else |err| switch (err) { - error.MissingDebugInfo, error.InvalidDebugInfo => { - return SymbolInfo{}; - }, - else => return err, - } -} - /// Unwind a frame using MachO compact unwind info (from __unwind_info). /// If the compact encoding can't encode a way to unwind a frame, it will /// defer unwinding to DWARF, in which case `.eh_frame` will be used if available. diff --git a/tools/dump-cov.zig b/tools/dump-cov.zig new file mode 100644 index 0000000000..aba2911a91 --- /dev/null +++ b/tools/dump-cov.zig @@ -0,0 +1,70 @@ +//! Reads a Zig coverage file and prints human-readable information to stdout, +//! including file:line:column information for each PC. + +const std = @import("std"); +const fatal = std.process.fatal; +const Path = std.Build.Cache.Path; +const assert = std.debug.assert; + +pub fn main() !void { + var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .{}; + defer _ = general_purpose_allocator.deinit(); + const gpa = general_purpose_allocator.allocator(); + + var arena_instance = std.heap.ArenaAllocator.init(gpa); + defer arena_instance.deinit(); + const arena = arena_instance.allocator(); + + const args = try std.process.argsAlloc(arena); + const exe_file_name = args[1]; + const cov_file_name = args[2]; + + const exe_path: Path = .{ + .root_dir = std.Build.Cache.Directory.cwd(), + .sub_path = exe_file_name, + }; + const cov_path: Path = .{ + .root_dir = std.Build.Cache.Directory.cwd(), + .sub_path = cov_file_name, + }; + + var debug_info = std.debug.Info.load(gpa, exe_path) catch |err| { + fatal("failed to load debug info for {}: {s}", .{ exe_path, @errorName(err) }); + }; + defer debug_info.deinit(gpa); + + const cov_bytes = cov_path.root_dir.handle.readFileAlloc(arena, cov_path.sub_path, 1 << 30) catch |err| { + fatal("failed to load coverage file {}: {s}", .{ cov_path, @errorName(err) }); + }; + + var bw = std.io.bufferedWriter(std.io.getStdOut().writer()); + const stdout = bw.writer(); + + const header: *align(1) SeenPcsHeader = @ptrCast(cov_bytes); + try stdout.print("{any}\n", .{header.*}); + //const n_bitset_elems = (header.pcs_len + 7) / 8; + const pcs_bytes = cov_bytes[@sizeOf(SeenPcsHeader)..][0 .. header.pcs_len * @sizeOf(usize)]; + const pcs = try arena.alloc(usize, header.pcs_len); + for (0..pcs_bytes.len / @sizeOf(usize), pcs) |i, *pc| { + pc.* = std.mem.readInt(usize, pcs_bytes[i * @sizeOf(usize) ..][0..@sizeOf(usize)], .little); + } + assert(std.sort.isSorted(usize, pcs, {}, std.sort.asc(usize))); + + const source_locations = try arena.alloc(std.debug.SourceLocation, pcs.len); + try debug_info.resolveSourceLocations(gpa, pcs, source_locations); + + for (pcs, source_locations) |pc, sl| { + try stdout.print("{x}: {s}:{d}:{d}\n", .{ + pc, sl.file_name, sl.line, sl.column, + }); + } + + try bw.flush(); +} + +const SeenPcsHeader = extern struct { + n_runs: usize, + deduplicated_runs: usize, + pcs_len: usize, + lowest_stack: usize, +}; From de47acd732dca8b4d2f2b3559307f488ccac940d Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 2 Aug 2024 17:45:31 -0700 Subject: [PATCH 172/266] code coverage dumping tool basic implementation * std.debug.Dwarf: add `sortCompileUnits` along with a field to track the state for the purpose of assertions and correct API usage. This makes batch lookups faster. - in the future, findCompileUnit should be enhanced to rely on sorted compile units as well. * implement `std.debug.Dwarf.resolveSourceLocations` as well as `std.debug.Info.resolveSourceLocations`. It's still pretty slow, since it calls getLineNumberInfo for each array element, repeating a lot of work unnecessarily. * integrate these APIs with `std.Progress` to understand what is taking so long. The output I'm seeing from this tool shows a lot of missing source locations. In particular, the main area of interest is missing for my tokenizer fuzzing example. --- lib/std/debug.zig | 6 +++ lib/std/debug/Dwarf.zig | 85 +++++++++++++++++++++++++++++++++++++---- lib/std/debug/Info.zig | 17 +++++---- tools/dump-cov.zig | 10 ++++- 4 files changed, 102 insertions(+), 16 deletions(-) diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 907f7711a7..6d034146c3 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -27,6 +27,12 @@ pub const SourceLocation = struct { line: u64, column: u64, file_name: []const u8, + + pub const invalid: SourceLocation = .{ + .line = 0, + .column = 0, + .file_name = &.{}, + }; }; pub const Symbol = struct { diff --git a/lib/std/debug/Dwarf.zig b/lib/std/debug/Dwarf.zig index 3c150b3b18..170fa774c0 100644 --- a/lib/std/debug/Dwarf.zig +++ b/lib/std/debug/Dwarf.zig @@ -39,6 +39,7 @@ pub const call_frame = @import("Dwarf/call_frame.zig"); endian: std.builtin.Endian, sections: SectionArray = null_section_array, is_macho: bool, +compile_units_sorted: bool, // Filled later by the initializer abbrev_table_list: std.ArrayListUnmanaged(Abbrev.Table) = .{}, @@ -728,9 +729,9 @@ pub const OpenError = ScanError; /// Initialize DWARF info. The caller has the responsibility to initialize most /// the `Dwarf` fields before calling. `binary_mem` is the raw bytes of the /// main binary file (not the secondary debug info file). -pub fn open(di: *Dwarf, gpa: Allocator) OpenError!void { - try di.scanAllFunctions(gpa); - try di.scanAllCompileUnits(gpa); +pub fn open(d: *Dwarf, gpa: Allocator) OpenError!void { + try d.scanAllFunctions(gpa); + try d.scanAllCompileUnits(gpa); } const PcRange = struct { @@ -1061,6 +1062,39 @@ fn scanAllCompileUnits(di: *Dwarf, allocator: Allocator) ScanError!void { } } +/// Populate missing PC ranges in compilation units, and then sort them by start address. +/// Does not guarantee pc_range to be non-null because there could be missing debug info. +pub fn sortCompileUnits(d: *Dwarf) ScanError!void { + assert(!d.compile_units_sorted); + + for (d.compile_unit_list.items) |*cu| { + if (cu.pc_range != null) continue; + const ranges_value = cu.die.getAttr(AT.ranges) orelse continue; + var iter = DebugRangeIterator.init(ranges_value, d, cu) catch continue; + var start: u64 = maxInt(u64); + var end: u64 = 0; + while (try iter.next()) |range| { + start = @min(start, range.start_addr); + end = @max(end, range.end_addr); + } + if (end != 0) cu.pc_range = .{ + .start = start, + .end = end, + }; + } + + std.mem.sortUnstable(CompileUnit, d.compile_unit_list.items, {}, struct { + fn lessThan(ctx: void, a: CompileUnit, b: CompileUnit) bool { + _ = ctx; + const a_range = a.pc_range orelse return false; + const b_range = b.pc_range orelse return true; + return a_range.start < b_range.start; + } + }.lessThan); + + d.compile_units_sorted = true; +} + const DebugRangeIterator = struct { base_address: u64, section_type: Section.Id, @@ -1208,6 +1242,7 @@ const DebugRangeIterator = struct { } }; +/// TODO: change this to binary searching the sorted compile unit list pub fn findCompileUnit(di: *const Dwarf, target_address: u64) !*const CompileUnit { for (di.compile_unit_list.items) |*compile_unit| { if (compile_unit.pc_range) |range| { @@ -2275,6 +2310,7 @@ pub const ElfModule = struct { .endian = endian, .sections = sections, .is_macho = false, + .compile_units_sorted = false, }; try Dwarf.open(&di, gpa); @@ -2326,6 +2362,8 @@ pub const ElfModule = struct { } }; +pub const ResolveSourceLocationsError = Allocator.Error || DeprecatedFixedBufferReader.Error; + /// Given an array of virtual memory addresses, sorted ascending, outputs a /// corresponding array of source locations, by appending to the provided /// array list. @@ -2335,11 +2373,44 @@ pub fn resolveSourceLocations( sorted_pc_addrs: []const u64, /// Asserts its length equals length of `sorted_pc_addrs`. output: []std.debug.SourceLocation, -) error{ MissingDebugInfo, InvalidDebugInfo }!void { + parent_prog_node: std.Progress.Node, +) ResolveSourceLocationsError!void { assert(sorted_pc_addrs.len == output.len); - _ = d; - _ = gpa; - @panic("TODO"); + assert(d.compile_units_sorted); + + const prog_node = parent_prog_node.start("Resolve Source Locations", sorted_pc_addrs.len); + defer prog_node.end(); + + var cu_i: usize = 0; + var cu: *const CompileUnit = &d.compile_unit_list.items[0]; + var range = cu.pc_range.?; + next_pc: for (sorted_pc_addrs, output) |pc, *out| { + defer prog_node.completeOne(); + while (pc >= range.end) { + cu_i += 1; + if (cu_i >= d.compile_unit_list.items.len) { + out.* = std.debug.SourceLocation.invalid; + continue :next_pc; + } + cu = &d.compile_unit_list.items[cu_i]; + range = cu.pc_range orelse { + out.* = std.debug.SourceLocation.invalid; + continue :next_pc; + }; + } + if (pc < range.start) { + out.* = std.debug.SourceLocation.invalid; + continue :next_pc; + } + // TODO: instead of calling this function, break the function up into one that parses the + // information once and prepares a context that can be reused for the entire batch. + if (getLineNumberInfo(d, gpa, cu.*, pc)) |src_loc| { + out.* = src_loc; + } else |err| switch (err) { + error.MissingDebugInfo, error.InvalidDebugInfo => out.* = std.debug.SourceLocation.invalid, + else => |e| return e, + } + } } fn getSymbol(di: *Dwarf, allocator: Allocator, address: u64) !std.debug.Symbol { diff --git a/lib/std/debug/Info.zig b/lib/std/debug/Info.zig index 5276ba68ec..3c61c4072f 100644 --- a/lib/std/debug/Info.zig +++ b/lib/std/debug/Info.zig @@ -20,9 +20,14 @@ address_map: std.AutoArrayHashMapUnmanaged(u64, Dwarf.ElfModule), pub const LoadError = Dwarf.ElfModule.LoadError; -pub fn load(gpa: Allocator, path: Path) LoadError!Info { +pub fn load(gpa: Allocator, path: Path, parent_prog_node: std.Progress.Node) LoadError!Info { var sections: Dwarf.SectionArray = Dwarf.null_section_array; - const elf_module = try Dwarf.ElfModule.loadPath(gpa, path, null, null, §ions, null); + var prog_node = parent_prog_node.start("Loading Debug Info", 0); + defer prog_node.end(); + var elf_module = try Dwarf.ElfModule.loadPath(gpa, path, null, null, §ions, null); + prog_node.end(); + prog_node = parent_prog_node.start("Sort Compile Units", 0); + try elf_module.dwarf.sortCompileUnits(); var info: Info = .{ .address_map = .{}, }; @@ -38,10 +43,7 @@ pub fn deinit(info: *Info, gpa: Allocator) void { info.* = undefined; } -pub const ResolveSourceLocationsError = error{ - MissingDebugInfo, - InvalidDebugInfo, -} || Allocator.Error; +pub const ResolveSourceLocationsError = Dwarf.ResolveSourceLocationsError; pub fn resolveSourceLocations( info: *Info, @@ -49,9 +51,10 @@ pub fn resolveSourceLocations( sorted_pc_addrs: []const u64, /// Asserts its length equals length of `sorted_pc_addrs`. output: []std.debug.SourceLocation, + parent_prog_node: std.Progress.Node, ) ResolveSourceLocationsError!void { assert(sorted_pc_addrs.len == output.len); if (info.address_map.entries.len != 1) @panic("TODO"); const elf_module = &info.address_map.values()[0]; - return elf_module.dwarf.resolveSourceLocations(gpa, sorted_pc_addrs, output); + return elf_module.dwarf.resolveSourceLocations(gpa, sorted_pc_addrs, output, parent_prog_node); } diff --git a/tools/dump-cov.zig b/tools/dump-cov.zig index aba2911a91..8449dec33e 100644 --- a/tools/dump-cov.zig +++ b/tools/dump-cov.zig @@ -28,7 +28,10 @@ pub fn main() !void { .sub_path = cov_file_name, }; - var debug_info = std.debug.Info.load(gpa, exe_path) catch |err| { + const prog_node = std.Progress.start(.{}); + defer prog_node.end(); + + var debug_info = std.debug.Info.load(gpa, exe_path, prog_node) catch |err| { fatal("failed to load debug info for {}: {s}", .{ exe_path, @errorName(err) }); }; defer debug_info.deinit(gpa); @@ -51,7 +54,10 @@ pub fn main() !void { assert(std.sort.isSorted(usize, pcs, {}, std.sort.asc(usize))); const source_locations = try arena.alloc(std.debug.SourceLocation, pcs.len); - try debug_info.resolveSourceLocations(gpa, pcs, source_locations); + try debug_info.resolveSourceLocations(gpa, pcs, source_locations, prog_node); + defer for (source_locations) |sl| { + gpa.free(sl.file_name); + }; for (pcs, source_locations) |pc, sl| { try stdout.print("{x}: {s}:{d}:{d}\n", .{ From 66954e833051872308641b3a1af12aa865d5d59a Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 2 Aug 2024 21:22:33 -0700 Subject: [PATCH 173/266] std.debug.FixedBufferReader is fine it does not need to be deprecated --- lib/std/debug.zig | 95 +---------------------------- lib/std/debug/Dwarf.zig | 41 ++++++------- lib/std/debug/FixedBufferReader.zig | 91 +++++++++++++++++++++++++++ lib/std/debug/SelfInfo.zig | 2 +- 4 files changed, 114 insertions(+), 115 deletions(-) create mode 100644 lib/std/debug/FixedBufferReader.zig diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 6d034146c3..80c196e9d8 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -14,6 +14,7 @@ const native_os = builtin.os.tag; const native_endian = native_arch.endian(); pub const MemoryAccessor = @import("debug/MemoryAccessor.zig"); +pub const FixedBufferReader = @import("debug/FixedBufferReader.zig"); pub const Dwarf = @import("debug/Dwarf.zig"); pub const Pdb = @import("debug/Pdb.zig"); pub const SelfInfo = @import("debug/SelfInfo.zig"); @@ -1494,99 +1495,6 @@ pub const SafetyLock = struct { } }; -/// Deprecated. Don't use this, just read from your memory directly. -/// -/// This only exists because someone was too lazy to rework logic that used to -/// operate on an open file to operate on a memory buffer instead. -pub const DeprecatedFixedBufferReader = struct { - buf: []const u8, - pos: usize = 0, - endian: std.builtin.Endian, - - pub const Error = error{ EndOfBuffer, Overflow, InvalidBuffer }; - - pub fn seekTo(fbr: *DeprecatedFixedBufferReader, pos: u64) Error!void { - if (pos > fbr.buf.len) return error.EndOfBuffer; - fbr.pos = @intCast(pos); - } - - pub fn seekForward(fbr: *DeprecatedFixedBufferReader, amount: u64) Error!void { - if (fbr.buf.len - fbr.pos < amount) return error.EndOfBuffer; - fbr.pos += @intCast(amount); - } - - pub inline fn readByte(fbr: *DeprecatedFixedBufferReader) Error!u8 { - if (fbr.pos >= fbr.buf.len) return error.EndOfBuffer; - defer fbr.pos += 1; - return fbr.buf[fbr.pos]; - } - - pub fn readByteSigned(fbr: *DeprecatedFixedBufferReader) Error!i8 { - return @bitCast(try fbr.readByte()); - } - - pub fn readInt(fbr: *DeprecatedFixedBufferReader, comptime T: type) Error!T { - const size = @divExact(@typeInfo(T).Int.bits, 8); - if (fbr.buf.len - fbr.pos < size) return error.EndOfBuffer; - defer fbr.pos += size; - return std.mem.readInt(T, fbr.buf[fbr.pos..][0..size], fbr.endian); - } - - pub fn readIntChecked( - fbr: *DeprecatedFixedBufferReader, - comptime T: type, - ma: *MemoryAccessor, - ) Error!T { - if (ma.load(T, @intFromPtr(fbr.buf[fbr.pos..].ptr)) == null) - return error.InvalidBuffer; - - return fbr.readInt(T); - } - - pub fn readUleb128(fbr: *DeprecatedFixedBufferReader, comptime T: type) Error!T { - return std.leb.readUleb128(T, fbr); - } - - pub fn readIleb128(fbr: *DeprecatedFixedBufferReader, comptime T: type) Error!T { - return std.leb.readIleb128(T, fbr); - } - - pub fn readAddress(fbr: *DeprecatedFixedBufferReader, format: std.dwarf.Format) Error!u64 { - return switch (format) { - .@"32" => try fbr.readInt(u32), - .@"64" => try fbr.readInt(u64), - }; - } - - pub fn readAddressChecked( - fbr: *DeprecatedFixedBufferReader, - format: std.dwarf.Format, - ma: *MemoryAccessor, - ) Error!u64 { - return switch (format) { - .@"32" => try fbr.readIntChecked(u32, ma), - .@"64" => try fbr.readIntChecked(u64, ma), - }; - } - - pub fn readBytes(fbr: *DeprecatedFixedBufferReader, len: usize) Error![]const u8 { - if (fbr.buf.len - fbr.pos < len) return error.EndOfBuffer; - defer fbr.pos += len; - return fbr.buf[fbr.pos..][0..len]; - } - - pub fn readBytesTo(fbr: *DeprecatedFixedBufferReader, comptime sentinel: u8) Error![:sentinel]const u8 { - const end = @call(.always_inline, std.mem.indexOfScalarPos, .{ - u8, - fbr.buf, - fbr.pos, - sentinel, - }) orelse return error.EndOfBuffer; - defer fbr.pos = end + 1; - return fbr.buf[fbr.pos..end :sentinel]; - } -}; - /// Detect whether the program is being executed in the Valgrind virtual machine. /// /// When Valgrind integrations are disabled, this returns comptime-known false. @@ -1600,6 +1508,7 @@ pub inline fn inValgrind() bool { test { _ = &Dwarf; _ = &MemoryAccessor; + _ = &FixedBufferReader; _ = &Pdb; _ = &SelfInfo; _ = &dumpHex; diff --git a/lib/std/debug/Dwarf.zig b/lib/std/debug/Dwarf.zig index 170fa774c0..446dc58990 100644 --- a/lib/std/debug/Dwarf.zig +++ b/lib/std/debug/Dwarf.zig @@ -27,8 +27,7 @@ const maxInt = std.math.maxInt; const MemoryAccessor = std.debug.MemoryAccessor; const Path = std.Build.Cache.Path; -/// Did I mention this is deprecated? -const DeprecatedFixedBufferReader = std.debug.DeprecatedFixedBufferReader; +const FixedBufferReader = std.debug.FixedBufferReader; const Dwarf = @This(); @@ -328,7 +327,7 @@ pub const ExceptionFrameHeader = struct { var left: usize = 0; var len: usize = self.fde_count; - var fbr: DeprecatedFixedBufferReader = .{ .buf = self.entries, .endian = native_endian }; + var fbr: FixedBufferReader = .{ .buf = self.entries, .endian = native_endian }; while (len > 1) { const mid = left + len / 2; @@ -371,7 +370,7 @@ pub const ExceptionFrameHeader = struct { const eh_frame = @as([*]const u8, @ptrFromInt(self.eh_frame_ptr))[0 .. eh_frame_len orelse maxInt(u32)]; const fde_offset = fde_ptr - self.eh_frame_ptr; - var eh_frame_fbr: DeprecatedFixedBufferReader = .{ + var eh_frame_fbr: FixedBufferReader = .{ .buf = eh_frame, .pos = fde_offset, .endian = native_endian, @@ -429,9 +428,9 @@ pub const EntryHeader = struct { } /// Reads a header for either an FDE or a CIE, then advances the fbr to the position after the trailing structure. - /// `fbr` must be a DeprecatedFixedBufferReader backed by either the .eh_frame or .debug_frame sections. + /// `fbr` must be a FixedBufferReader backed by either the .eh_frame or .debug_frame sections. pub fn read( - fbr: *DeprecatedFixedBufferReader, + fbr: *FixedBufferReader, opt_ma: ?*MemoryAccessor, dwarf_section: Section.Id, ) !EntryHeader { @@ -544,7 +543,7 @@ pub const CommonInformationEntry = struct { ) !CommonInformationEntry { if (addr_size_bytes > 8) return error.UnsupportedAddrSize; - var fbr: DeprecatedFixedBufferReader = .{ .buf = cie_bytes, .endian = endian }; + var fbr: FixedBufferReader = .{ .buf = cie_bytes, .endian = endian }; const version = try fbr.readByte(); switch (dwarf_section) { @@ -678,7 +677,7 @@ pub const FrameDescriptionEntry = struct { ) !FrameDescriptionEntry { if (addr_size_bytes > 8) return error.InvalidAddrSize; - var fbr: DeprecatedFixedBufferReader = .{ .buf = fde_bytes, .endian = endian }; + var fbr: FixedBufferReader = .{ .buf = fde_bytes, .endian = endian }; const pc_begin = try readEhPointer(&fbr, cie.fde_pointer_enc, addr_size_bytes, .{ .pc_rel_base = try pcRelBase(@intFromPtr(&fde_bytes[fbr.pos]), pc_rel_offset), @@ -785,10 +784,10 @@ pub fn getSymbolName(di: *Dwarf, address: u64) ?[]const u8 { const ScanError = error{ InvalidDebugInfo, MissingDebugInfo, -} || Allocator.Error || std.debug.DeprecatedFixedBufferReader.Error; +} || Allocator.Error || std.debug.FixedBufferReader.Error; fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void { - var fbr: DeprecatedFixedBufferReader = .{ .buf = di.section(.debug_info).?, .endian = di.endian }; + var fbr: FixedBufferReader = .{ .buf = di.section(.debug_info).?, .endian = di.endian }; var this_unit_offset: u64 = 0; while (this_unit_offset < fbr.buf.len) { @@ -975,7 +974,7 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void { } fn scanAllCompileUnits(di: *Dwarf, allocator: Allocator) ScanError!void { - var fbr: DeprecatedFixedBufferReader = .{ .buf = di.section(.debug_info).?, .endian = di.endian }; + var fbr: FixedBufferReader = .{ .buf = di.section(.debug_info).?, .endian = di.endian }; var this_unit_offset: u64 = 0; var attrs_buf = std.ArrayList(Die.Attr).init(allocator); @@ -1100,7 +1099,7 @@ const DebugRangeIterator = struct { section_type: Section.Id, di: *const Dwarf, compile_unit: *const CompileUnit, - fbr: DeprecatedFixedBufferReader, + fbr: FixedBufferReader, pub fn init(ranges_value: *const FormValue, di: *const Dwarf, compile_unit: *const CompileUnit) !@This() { const section_type = if (compile_unit.version >= 5) Section.Id.debug_rnglists else Section.Id.debug_ranges; @@ -1275,7 +1274,7 @@ fn getAbbrevTable(di: *Dwarf, allocator: Allocator, abbrev_offset: u64) !*const } fn parseAbbrevTable(di: *Dwarf, allocator: Allocator, offset: u64) !Abbrev.Table { - var fbr: DeprecatedFixedBufferReader = .{ + var fbr: FixedBufferReader = .{ .buf = di.section(.debug_abbrev).?, .pos = cast(usize, offset) orelse return bad(), .endian = di.endian, @@ -1327,7 +1326,7 @@ fn parseAbbrevTable(di: *Dwarf, allocator: Allocator, offset: u64) !Abbrev.Table } fn parseDie( - fbr: *DeprecatedFixedBufferReader, + fbr: *FixedBufferReader, attrs_buf: []Die.Attr, abbrev_table: *const Abbrev.Table, format: Format, @@ -1362,7 +1361,7 @@ pub fn getLineNumberInfo( const compile_unit_cwd = try compile_unit.die.getAttrString(di, AT.comp_dir, di.section(.debug_line_str), compile_unit); const line_info_offset = try compile_unit.die.getAttrSecOffset(AT.stmt_list); - var fbr: DeprecatedFixedBufferReader = .{ .buf = di.section(.debug_line).?, .endian = di.endian }; + var fbr: FixedBufferReader = .{ .buf = di.section(.debug_line).?, .endian = di.endian }; try fbr.seekTo(line_info_offset); const unit_header = try readUnitHeader(&fbr, null); @@ -1655,7 +1654,7 @@ fn readDebugAddr(di: Dwarf, compile_unit: CompileUnit, index: u64) !u64 { /// of FDEs is built for binary searching during unwinding. pub fn scanAllUnwindInfo(di: *Dwarf, allocator: Allocator, base_address: usize) !void { if (di.section(.eh_frame_hdr)) |eh_frame_hdr| blk: { - var fbr: DeprecatedFixedBufferReader = .{ .buf = eh_frame_hdr, .endian = native_endian }; + var fbr: FixedBufferReader = .{ .buf = eh_frame_hdr, .endian = native_endian }; const version = try fbr.readByte(); if (version != 1) break :blk; @@ -1695,7 +1694,7 @@ pub fn scanAllUnwindInfo(di: *Dwarf, allocator: Allocator, base_address: usize) const frame_sections = [2]Section.Id{ .eh_frame, .debug_frame }; for (frame_sections) |frame_section| { if (di.section(frame_section)) |section_data| { - var fbr: DeprecatedFixedBufferReader = .{ .buf = section_data, .endian = di.endian }; + var fbr: FixedBufferReader = .{ .buf = section_data, .endian = di.endian }; while (fbr.pos < fbr.buf.len) { const entry_header = try EntryHeader.read(&fbr, null, frame_section); switch (entry_header.type) { @@ -1739,7 +1738,7 @@ pub fn scanAllUnwindInfo(di: *Dwarf, allocator: Allocator, base_address: usize) } fn parseFormValue( - fbr: *DeprecatedFixedBufferReader, + fbr: *FixedBufferReader, form_id: u64, format: Format, implicit_const: ?i64, @@ -1937,7 +1936,7 @@ const UnitHeader = struct { unit_length: u64, }; -fn readUnitHeader(fbr: *DeprecatedFixedBufferReader, opt_ma: ?*MemoryAccessor) ScanError!UnitHeader { +fn readUnitHeader(fbr: *FixedBufferReader, opt_ma: ?*MemoryAccessor) ScanError!UnitHeader { return switch (try if (opt_ma) |ma| fbr.readIntChecked(u32, ma) else fbr.readInt(u32)) { 0...0xfffffff0 - 1 => |unit_length| .{ .format = .@"32", @@ -2002,7 +2001,7 @@ const EhPointerContext = struct { text_rel_base: ?u64 = null, function_rel_base: ?u64 = null, }; -fn readEhPointer(fbr: *DeprecatedFixedBufferReader, enc: u8, addr_size_bytes: u8, ctx: EhPointerContext) !?u64 { +fn readEhPointer(fbr: *FixedBufferReader, enc: u8, addr_size_bytes: u8, ctx: EhPointerContext) !?u64 { if (enc == EH.PE.omit) return null; const value: union(enum) { @@ -2362,7 +2361,7 @@ pub const ElfModule = struct { } }; -pub const ResolveSourceLocationsError = Allocator.Error || DeprecatedFixedBufferReader.Error; +pub const ResolveSourceLocationsError = Allocator.Error || FixedBufferReader.Error; /// Given an array of virtual memory addresses, sorted ascending, outputs a /// corresponding array of source locations, by appending to the provided diff --git a/lib/std/debug/FixedBufferReader.zig b/lib/std/debug/FixedBufferReader.zig new file mode 100644 index 0000000000..2a90ba569e --- /dev/null +++ b/lib/std/debug/FixedBufferReader.zig @@ -0,0 +1,91 @@ +const std = @import("std.zig"); +const MemoryAccessor = std.debug.MemoryAccessor; + +const FixedBufferReader = @This(); + +buf: []const u8, +pos: usize = 0, +endian: std.builtin.Endian, + +pub const Error = error{ EndOfBuffer, Overflow, InvalidBuffer }; + +pub fn seekTo(fbr: *FixedBufferReader, pos: u64) Error!void { + if (pos > fbr.buf.len) return error.EndOfBuffer; + fbr.pos = @intCast(pos); +} + +pub fn seekForward(fbr: *FixedBufferReader, amount: u64) Error!void { + if (fbr.buf.len - fbr.pos < amount) return error.EndOfBuffer; + fbr.pos += @intCast(amount); +} + +pub inline fn readByte(fbr: *FixedBufferReader) Error!u8 { + if (fbr.pos >= fbr.buf.len) return error.EndOfBuffer; + defer fbr.pos += 1; + return fbr.buf[fbr.pos]; +} + +pub fn readByteSigned(fbr: *FixedBufferReader) Error!i8 { + return @bitCast(try fbr.readByte()); +} + +pub fn readInt(fbr: *FixedBufferReader, comptime T: type) Error!T { + const size = @divExact(@typeInfo(T).Int.bits, 8); + if (fbr.buf.len - fbr.pos < size) return error.EndOfBuffer; + defer fbr.pos += size; + return std.mem.readInt(T, fbr.buf[fbr.pos..][0..size], fbr.endian); +} + +pub fn readIntChecked( + fbr: *FixedBufferReader, + comptime T: type, + ma: *MemoryAccessor, +) Error!T { + if (ma.load(T, @intFromPtr(fbr.buf[fbr.pos..].ptr)) == null) + return error.InvalidBuffer; + + return fbr.readInt(T); +} + +pub fn readUleb128(fbr: *FixedBufferReader, comptime T: type) Error!T { + return std.leb.readUleb128(T, fbr); +} + +pub fn readIleb128(fbr: *FixedBufferReader, comptime T: type) Error!T { + return std.leb.readIleb128(T, fbr); +} + +pub fn readAddress(fbr: *FixedBufferReader, format: std.dwarf.Format) Error!u64 { + return switch (format) { + .@"32" => try fbr.readInt(u32), + .@"64" => try fbr.readInt(u64), + }; +} + +pub fn readAddressChecked( + fbr: *FixedBufferReader, + format: std.dwarf.Format, + ma: *MemoryAccessor, +) Error!u64 { + return switch (format) { + .@"32" => try fbr.readIntChecked(u32, ma), + .@"64" => try fbr.readIntChecked(u64, ma), + }; +} + +pub fn readBytes(fbr: *FixedBufferReader, len: usize) Error![]const u8 { + if (fbr.buf.len - fbr.pos < len) return error.EndOfBuffer; + defer fbr.pos += len; + return fbr.buf[fbr.pos..][0..len]; +} + +pub fn readBytesTo(fbr: *FixedBufferReader, comptime sentinel: u8) Error![:sentinel]const u8 { + const end = @call(.always_inline, std.mem.indexOfScalarPos, .{ + u8, + fbr.buf, + fbr.pos, + sentinel, + }) orelse return error.EndOfBuffer; + defer fbr.pos = end + 1; + return fbr.buf[fbr.pos..end :sentinel]; +} diff --git a/lib/std/debug/SelfInfo.zig b/lib/std/debug/SelfInfo.zig index 79cbd19a41..ba0d7bc039 100644 --- a/lib/std/debug/SelfInfo.zig +++ b/lib/std/debug/SelfInfo.zig @@ -1576,7 +1576,7 @@ pub fn unwindFrameDwarf( const frame_section = di.section(dwarf_section) orelse return error.MissingFDE; if (fde_offset >= frame_section.len) return error.MissingFDE; - var fbr: std.debug.DeprecatedFixedBufferReader = .{ + var fbr: std.debug.FixedBufferReader = .{ .buf = frame_section, .pos = fde_offset, .endian = di.endian, From 1792258dc813cde7083fd7860442e6ec92afd4ba Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 2 Aug 2024 23:31:28 -0700 Subject: [PATCH 174/266] std.debug.Dwarf: precompute .debug_line table yields a 60x speedup for resolveSourceLocations in debug builds --- lib/std/debug.zig | 2 +- lib/std/debug/Dwarf.zig | 312 +++++++++++++--------------- lib/std/debug/FixedBufferReader.zig | 4 +- 3 files changed, 151 insertions(+), 167 deletions(-) diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 80c196e9d8..7f4f6b7df2 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -762,7 +762,7 @@ pub fn writeCurrentStackTrace( // an overflow. We do not need to signal `StackIterator` as it will correctly detect this // condition on the subsequent iteration and return `null` thus terminating the loop. // same behaviour for x86-windows-msvc - const address = if (return_address == 0) return_address else return_address - 1; + const address = return_address -| 1; try printSourceAtAddress(debug_info, out_stream, address, tty_config); } else printLastUnwindError(&it, debug_info, out_stream, tty_config); } diff --git a/lib/std/debug/Dwarf.zig b/lib/std/debug/Dwarf.zig index 446dc58990..06ffad9441 100644 --- a/lib/std/debug/Dwarf.zig +++ b/lib/std/debug/Dwarf.zig @@ -138,6 +138,29 @@ pub const CompileUnit = struct { rnglists_base: usize, loclists_base: usize, frame_base: ?*const FormValue, + + src_loc_cache: ?SrcLocCache, + + pub const SrcLocCache = struct { + line_table: LineTable, + directories: []const FileEntry, + files: []FileEntry, + version: u16, + + pub const LineTable = std.AutoArrayHashMapUnmanaged(u64, LineEntry); + + pub const LineEntry = struct { + line: u32, + column: u32, + file: u32, + }; + + pub fn findSource(slc: *const SrcLocCache, address: u64) !LineEntry { + const index = std.sort.upperBound(u64, address, slc.line_table.keys(), {}, std.sort.asc(u64)); + if (index == 0) return missing(); + return slc.line_table.values()[index - 1]; + } + }; }; pub const FormValue = union(enum) { @@ -760,6 +783,11 @@ pub fn deinit(di: *Dwarf, gpa: Allocator) void { } di.abbrev_table_list.deinit(gpa); for (di.compile_unit_list.items) |*cu| { + if (cu.src_loc_cache) |*slc| { + slc.line_table.deinit(gpa); + gpa.free(slc.directories); + gpa.free(slc.files); + } cu.die.deinit(gpa); } di.compile_unit_list.deinit(gpa); @@ -846,6 +874,7 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void { .rnglists_base = 0, .loclists_base = 0, .frame_base = null, + .src_loc_cache = null, }; while (true) { @@ -1032,6 +1061,7 @@ fn scanAllCompileUnits(di: *Dwarf, allocator: Allocator) ScanError!void { .rnglists_base = if (compile_unit_die.getAttr(AT.rnglists_base)) |fv| try fv.getUInt(usize) else 0, .loclists_base = if (compile_unit_die.getAttr(AT.loclists_base)) |fv| try fv.getUInt(usize) else 0, .frame_base = compile_unit_die.getAttr(AT.frame_base), + .src_loc_cache = null, }; compile_unit.pc_range = x: { @@ -1242,7 +1272,7 @@ const DebugRangeIterator = struct { }; /// TODO: change this to binary searching the sorted compile unit list -pub fn findCompileUnit(di: *const Dwarf, target_address: u64) !*const CompileUnit { +pub fn findCompileUnit(di: *const Dwarf, target_address: u64) !*CompileUnit { for (di.compile_unit_list.items) |*compile_unit| { if (compile_unit.pc_range) |range| { if (target_address >= range.start and target_address < range.end) return compile_unit; @@ -1352,34 +1382,36 @@ fn parseDie( }; } -pub fn getLineNumberInfo( - di: *Dwarf, - allocator: Allocator, - compile_unit: CompileUnit, - target_address: u64, -) !std.debug.SourceLocation { - const compile_unit_cwd = try compile_unit.die.getAttrString(di, AT.comp_dir, di.section(.debug_line_str), compile_unit); +fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) !CompileUnit.SrcLocCache { + const compile_unit_cwd = try compile_unit.die.getAttrString(d, AT.comp_dir, d.section(.debug_line_str), compile_unit.*); const line_info_offset = try compile_unit.die.getAttrSecOffset(AT.stmt_list); - var fbr: FixedBufferReader = .{ .buf = di.section(.debug_line).?, .endian = di.endian }; + var fbr: FixedBufferReader = .{ + .buf = d.section(.debug_line).?, + .endian = d.endian, + }; try fbr.seekTo(line_info_offset); const unit_header = try readUnitHeader(&fbr, null); if (unit_header.unit_length == 0) return missing(); + const next_offset = unit_header.header_length + unit_header.unit_length; const version = try fbr.readInt(u16); if (version < 2) return bad(); - var addr_size: u8 = switch (unit_header.format) { - .@"32" => 4, - .@"64" => 8, + const addr_size: u8, const seg_size: u8 = if (version >= 5) .{ + try fbr.readByte(), + try fbr.readByte(), + } else .{ + switch (unit_header.format) { + .@"32" => 4, + .@"64" => 8, + }, + 0, }; - var seg_size: u8 = 0; - if (version >= 5) { - addr_size = try fbr.readByte(); - seg_size = try fbr.readByte(); - } + _ = addr_size; + _ = seg_size; const prologue_length = try fbr.readAddress(unit_header.format); const prog_start_offset = fbr.pos + prologue_length; @@ -1388,8 +1420,8 @@ pub fn getLineNumberInfo( if (minimum_instruction_length == 0) return bad(); if (version >= 4) { - // maximum_operations_per_instruction - _ = try fbr.readByte(); + const maximum_operations_per_instruction = try fbr.readByte(); + _ = maximum_operations_per_instruction; } const default_is_stmt = (try fbr.readByte()) != 0; @@ -1402,18 +1434,18 @@ pub fn getLineNumberInfo( const standard_opcode_lengths = try fbr.readBytes(opcode_base - 1); - var include_directories = std.ArrayList(FileEntry).init(allocator); - defer include_directories.deinit(); - var file_entries = std.ArrayList(FileEntry).init(allocator); - defer file_entries.deinit(); + var directories: std.ArrayListUnmanaged(FileEntry) = .{}; + defer directories.deinit(gpa); + var file_entries: std.ArrayListUnmanaged(FileEntry) = .{}; + defer file_entries.deinit(gpa); if (version < 5) { - try include_directories.append(.{ .path = compile_unit_cwd }); + try directories.append(gpa, .{ .path = compile_unit_cwd }); while (true) { const dir = try fbr.readBytesTo(0); if (dir.len == 0) break; - try include_directories.append(.{ .path = dir }); + try directories.append(gpa, .{ .path = dir }); } while (true) { @@ -1422,7 +1454,7 @@ pub fn getLineNumberInfo( const dir_index = try fbr.readUleb128(u32); const mtime = try fbr.readUleb128(u64); const size = try fbr.readUleb128(u64); - try file_entries.append(.{ + try file_entries.append(gpa, .{ .path = file_name, .dir_index = dir_index, .mtime = mtime, @@ -1446,31 +1478,27 @@ pub fn getLineNumberInfo( } const directories_count = try fbr.readUleb128(usize); - try include_directories.ensureUnusedCapacity(directories_count); - { - var i: usize = 0; - while (i < directories_count) : (i += 1) { - var e: FileEntry = .{ .path = &.{} }; - for (dir_ent_fmt_buf[0..directory_entry_format_count]) |ent_fmt| { - const form_value = try parseFormValue( - &fbr, - ent_fmt.form_code, - unit_header.format, - null, - ); - switch (ent_fmt.content_type_code) { - DW.LNCT.path => e.path = try form_value.getString(di.*), - DW.LNCT.directory_index => e.dir_index = try form_value.getUInt(u32), - DW.LNCT.timestamp => e.mtime = try form_value.getUInt(u64), - DW.LNCT.size => e.size = try form_value.getUInt(u64), - DW.LNCT.MD5 => e.md5 = switch (form_value) { - .data16 => |data16| data16.*, - else => return bad(), - }, - else => continue, - } + + for (try directories.addManyAsSlice(gpa, directories_count)) |*e| { + e.* = .{ .path = &.{} }; + for (dir_ent_fmt_buf[0..directory_entry_format_count]) |ent_fmt| { + const form_value = try parseFormValue( + &fbr, + ent_fmt.form_code, + unit_header.format, + null, + ); + switch (ent_fmt.content_type_code) { + DW.LNCT.path => e.path = try form_value.getString(d.*), + DW.LNCT.directory_index => e.dir_index = try form_value.getUInt(u32), + DW.LNCT.timestamp => e.mtime = try form_value.getUInt(u64), + DW.LNCT.size => e.size = try form_value.getUInt(u64), + DW.LNCT.MD5 => e.md5 = switch (form_value) { + .data16 => |data16| data16.*, + else => return bad(), + }, + else => continue, } - include_directories.appendAssumeCapacity(e); } } } @@ -1486,41 +1514,35 @@ pub fn getLineNumberInfo( } const file_names_count = try fbr.readUleb128(usize); - try file_entries.ensureUnusedCapacity(file_names_count); - { - var i: usize = 0; - while (i < file_names_count) : (i += 1) { - var e: FileEntry = .{ .path = &.{} }; - for (file_ent_fmt_buf[0..file_name_entry_format_count]) |ent_fmt| { - const form_value = try parseFormValue( - &fbr, - ent_fmt.form_code, - unit_header.format, - null, - ); - switch (ent_fmt.content_type_code) { - DW.LNCT.path => e.path = try form_value.getString(di.*), - DW.LNCT.directory_index => e.dir_index = try form_value.getUInt(u32), - DW.LNCT.timestamp => e.mtime = try form_value.getUInt(u64), - DW.LNCT.size => e.size = try form_value.getUInt(u64), - DW.LNCT.MD5 => e.md5 = switch (form_value) { - .data16 => |data16| data16.*, - else => return bad(), - }, - else => continue, - } + try file_entries.ensureUnusedCapacity(gpa, file_names_count); + + for (try file_entries.addManyAsSlice(gpa, file_names_count)) |*e| { + e.* = .{ .path = &.{} }; + for (file_ent_fmt_buf[0..file_name_entry_format_count]) |ent_fmt| { + const form_value = try parseFormValue( + &fbr, + ent_fmt.form_code, + unit_header.format, + null, + ); + switch (ent_fmt.content_type_code) { + DW.LNCT.path => e.path = try form_value.getString(d.*), + DW.LNCT.directory_index => e.dir_index = try form_value.getUInt(u32), + DW.LNCT.timestamp => e.mtime = try form_value.getUInt(u64), + DW.LNCT.size => e.size = try form_value.getUInt(u64), + DW.LNCT.MD5 => e.md5 = switch (form_value) { + .data16 => |data16| data16.*, + else => return bad(), + }, + else => continue, } - file_entries.appendAssumeCapacity(e); } } } - var prog = LineNumberProgram.init( - default_is_stmt, - include_directories.items, - target_address, - version, - ); + var prog = LineNumberProgram.init(default_is_stmt, version); + var line_table: CompileUnit.SrcLocCache.LineTable = .{}; + errdefer line_table.deinit(gpa); try fbr.seekTo(prog_start_offset); @@ -1536,7 +1558,7 @@ pub fn getLineNumberInfo( switch (sub_op) { DW.LNE.end_sequence => { prog.end_sequence = true; - if (try prog.checkLineMatch(allocator, file_entries.items)) |info| return info; + try prog.addRow(gpa, &line_table); prog.reset(); }, DW.LNE.set_address => { @@ -1548,7 +1570,7 @@ pub fn getLineNumberInfo( const dir_index = try fbr.readUleb128(u32); const mtime = try fbr.readUleb128(u64); const size = try fbr.readUleb128(u64); - try file_entries.append(.{ + try file_entries.append(gpa, .{ .path = path, .dir_index = dir_index, .mtime = mtime, @@ -1564,12 +1586,12 @@ pub fn getLineNumberInfo( const inc_line = @as(i32, line_base) + @as(i32, adjusted_opcode % line_range); prog.line += inc_line; prog.address += inc_addr; - if (try prog.checkLineMatch(allocator, file_entries.items)) |info| return info; + try prog.addRow(gpa, &line_table); prog.basic_block = false; } else { switch (opcode) { DW.LNS.copy => { - if (try prog.checkLineMatch(allocator, file_entries.items)) |info| return info; + try prog.addRow(gpa, &line_table); prog.basic_block = false; }, DW.LNS.advance_pc => { @@ -1611,7 +1633,35 @@ pub fn getLineNumberInfo( } } - return missing(); + return .{ + .line_table = line_table, + .directories = try directories.toOwnedSlice(gpa), + .files = try file_entries.toOwnedSlice(gpa), + .version = version, + }; +} + +pub fn getLineNumberInfo( + d: *Dwarf, + gpa: Allocator, + compile_unit: *CompileUnit, + target_address: u64, +) !std.debug.SourceLocation { + if (compile_unit.src_loc_cache == null) + compile_unit.src_loc_cache = try runLineNumberProgram(d, gpa, compile_unit); + const slc = &compile_unit.src_loc_cache.?; + const entry = try slc.findSource(target_address); + const file_index = entry.file - @intFromBool(slc.version < 5); + if (file_index >= slc.files.len) return bad(); + const file_entry = &slc.files[file_index]; + if (file_entry.dir_index >= slc.directories.len) return bad(); + const dir_name = slc.directories[file_entry.dir_index].path; + const file_name = try std.fs.path.join(gpa, &.{ dir_name, file_entry.path }); + return .{ + .line = entry.line, + .column = entry.column, + .file_name = file_name, + }; } fn getString(di: Dwarf, offset: u64) ![:0]const u8 { @@ -1826,17 +1876,6 @@ const LineNumberProgram = struct { end_sequence: bool, default_is_stmt: bool, - target_address: u64, - include_dirs: []const FileEntry, - - prev_valid: bool, - prev_address: u64, - prev_file: usize, - prev_line: i64, - prev_column: u64, - prev_is_stmt: bool, - prev_basic_block: bool, - prev_end_sequence: bool, // Reset the state machine following the DWARF specification pub fn reset(self: *LineNumberProgram) void { @@ -1847,24 +1886,10 @@ const LineNumberProgram = struct { self.is_stmt = self.default_is_stmt; self.basic_block = false; self.end_sequence = false; - // Invalidate all the remaining fields - self.prev_valid = false; - self.prev_address = 0; - self.prev_file = undefined; - self.prev_line = undefined; - self.prev_column = undefined; - self.prev_is_stmt = undefined; - self.prev_basic_block = undefined; - self.prev_end_sequence = undefined; } - pub fn init( - is_stmt: bool, - include_dirs: []const FileEntry, - target_address: u64, - version: u16, - ) LineNumberProgram { - return LineNumberProgram{ + pub fn init(is_stmt: bool, version: u16) LineNumberProgram { + return .{ .address = 0, .file = 1, .line = 1, @@ -1873,60 +1898,17 @@ const LineNumberProgram = struct { .is_stmt = is_stmt, .basic_block = false, .end_sequence = false, - .include_dirs = include_dirs, .default_is_stmt = is_stmt, - .target_address = target_address, - .prev_valid = false, - .prev_address = 0, - .prev_file = undefined, - .prev_line = undefined, - .prev_column = undefined, - .prev_is_stmt = undefined, - .prev_basic_block = undefined, - .prev_end_sequence = undefined, }; } - pub fn checkLineMatch( - self: *LineNumberProgram, - allocator: Allocator, - file_entries: []const FileEntry, - ) !?std.debug.SourceLocation { - if (self.prev_valid and - self.target_address >= self.prev_address and - self.target_address < self.address) - { - const file_index = if (self.version >= 5) self.prev_file else i: { - if (self.prev_file == 0) return missing(); - break :i self.prev_file - 1; - }; - - if (file_index >= file_entries.len) return bad(); - const file_entry = &file_entries[file_index]; - - if (file_entry.dir_index >= self.include_dirs.len) return bad(); - const dir_name = self.include_dirs[file_entry.dir_index].path; - - const file_name = try std.fs.path.join(allocator, &[_][]const u8{ - dir_name, file_entry.path, - }); - - return std.debug.SourceLocation{ - .line = if (self.prev_line >= 0) @as(u64, @intCast(self.prev_line)) else 0, - .column = self.prev_column, - .file_name = file_name, - }; - } - - self.prev_valid = true; - self.prev_address = self.address; - self.prev_file = self.file; - self.prev_line = self.line; - self.prev_column = self.column; - self.prev_is_stmt = self.is_stmt; - self.prev_basic_block = self.basic_block; - self.prev_end_sequence = self.end_sequence; - return null; + pub fn addRow(prog: *LineNumberProgram, gpa: Allocator, table: *CompileUnit.SrcLocCache.LineTable) !void { + if (prog.line == 0) return; // garbage data + try table.put(gpa, prog.address, .{ + .line = cast(u32, prog.line) orelse maxInt(u32), + .column = cast(u32, prog.column) orelse maxInt(u32), + .file = cast(u32, prog.file) orelse return bad(), + }); } }; @@ -2381,7 +2363,7 @@ pub fn resolveSourceLocations( defer prog_node.end(); var cu_i: usize = 0; - var cu: *const CompileUnit = &d.compile_unit_list.items[0]; + var cu: *CompileUnit = &d.compile_unit_list.items[0]; var range = cu.pc_range.?; next_pc: for (sorted_pc_addrs, output) |pc, *out| { defer prog_node.completeOne(); @@ -2403,7 +2385,7 @@ pub fn resolveSourceLocations( } // TODO: instead of calling this function, break the function up into one that parses the // information once and prepares a context that can be reused for the entire batch. - if (getLineNumberInfo(d, gpa, cu.*, pc)) |src_loc| { + if (getLineNumberInfo(d, gpa, cu, pc)) |src_loc| { out.* = src_loc; } else |err| switch (err) { error.MissingDebugInfo, error.InvalidDebugInfo => out.* = std.debug.SourceLocation.invalid, @@ -2419,7 +2401,7 @@ fn getSymbol(di: *Dwarf, allocator: Allocator, address: u64) !std.debug.Symbol { .compile_unit_name = compile_unit.die.getAttrString(di, std.dwarf.AT.name, di.section(.debug_str), compile_unit.*) catch |err| switch (err) { error.MissingDebugInfo, error.InvalidDebugInfo => "???", }, - .source_location = di.getLineNumberInfo(allocator, compile_unit.*, address) catch |err| switch (err) { + .source_location = di.getLineNumberInfo(allocator, compile_unit, address) catch |err| switch (err) { error.MissingDebugInfo, error.InvalidDebugInfo => null, else => return err, }, diff --git a/lib/std/debug/FixedBufferReader.zig b/lib/std/debug/FixedBufferReader.zig index 2a90ba569e..494245a9e9 100644 --- a/lib/std/debug/FixedBufferReader.zig +++ b/lib/std/debug/FixedBufferReader.zig @@ -1,4 +1,6 @@ -const std = @import("std.zig"); +//! Optimized for performance in debug builds. + +const std = @import("../std.zig"); const MemoryAccessor = std.debug.MemoryAccessor; const FixedBufferReader = @This(); From c2ab4614b69a2303d640837df357c2336b0cedf2 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 2 Aug 2024 23:38:34 -0700 Subject: [PATCH 175/266] std.Debug.Info: remove std.Progress integration it's too fast to need it now --- lib/std/debug/Dwarf.zig | 5 ----- lib/std/debug/Info.zig | 9 ++------- tools/dump-cov.zig | 7 ++----- 3 files changed, 4 insertions(+), 17 deletions(-) diff --git a/lib/std/debug/Dwarf.zig b/lib/std/debug/Dwarf.zig index 06ffad9441..9689ac98b3 100644 --- a/lib/std/debug/Dwarf.zig +++ b/lib/std/debug/Dwarf.zig @@ -2354,19 +2354,14 @@ pub fn resolveSourceLocations( sorted_pc_addrs: []const u64, /// Asserts its length equals length of `sorted_pc_addrs`. output: []std.debug.SourceLocation, - parent_prog_node: std.Progress.Node, ) ResolveSourceLocationsError!void { assert(sorted_pc_addrs.len == output.len); assert(d.compile_units_sorted); - const prog_node = parent_prog_node.start("Resolve Source Locations", sorted_pc_addrs.len); - defer prog_node.end(); - var cu_i: usize = 0; var cu: *CompileUnit = &d.compile_unit_list.items[0]; var range = cu.pc_range.?; next_pc: for (sorted_pc_addrs, output) |pc, *out| { - defer prog_node.completeOne(); while (pc >= range.end) { cu_i += 1; if (cu_i >= d.compile_unit_list.items.len) { diff --git a/lib/std/debug/Info.zig b/lib/std/debug/Info.zig index 3c61c4072f..f31b2f22c4 100644 --- a/lib/std/debug/Info.zig +++ b/lib/std/debug/Info.zig @@ -20,13 +20,9 @@ address_map: std.AutoArrayHashMapUnmanaged(u64, Dwarf.ElfModule), pub const LoadError = Dwarf.ElfModule.LoadError; -pub fn load(gpa: Allocator, path: Path, parent_prog_node: std.Progress.Node) LoadError!Info { +pub fn load(gpa: Allocator, path: Path) LoadError!Info { var sections: Dwarf.SectionArray = Dwarf.null_section_array; - var prog_node = parent_prog_node.start("Loading Debug Info", 0); - defer prog_node.end(); var elf_module = try Dwarf.ElfModule.loadPath(gpa, path, null, null, §ions, null); - prog_node.end(); - prog_node = parent_prog_node.start("Sort Compile Units", 0); try elf_module.dwarf.sortCompileUnits(); var info: Info = .{ .address_map = .{}, @@ -51,10 +47,9 @@ pub fn resolveSourceLocations( sorted_pc_addrs: []const u64, /// Asserts its length equals length of `sorted_pc_addrs`. output: []std.debug.SourceLocation, - parent_prog_node: std.Progress.Node, ) ResolveSourceLocationsError!void { assert(sorted_pc_addrs.len == output.len); if (info.address_map.entries.len != 1) @panic("TODO"); const elf_module = &info.address_map.values()[0]; - return elf_module.dwarf.resolveSourceLocations(gpa, sorted_pc_addrs, output, parent_prog_node); + return elf_module.dwarf.resolveSourceLocations(gpa, sorted_pc_addrs, output); } diff --git a/tools/dump-cov.zig b/tools/dump-cov.zig index 8449dec33e..f821dde611 100644 --- a/tools/dump-cov.zig +++ b/tools/dump-cov.zig @@ -28,10 +28,7 @@ pub fn main() !void { .sub_path = cov_file_name, }; - const prog_node = std.Progress.start(.{}); - defer prog_node.end(); - - var debug_info = std.debug.Info.load(gpa, exe_path, prog_node) catch |err| { + var debug_info = std.debug.Info.load(gpa, exe_path) catch |err| { fatal("failed to load debug info for {}: {s}", .{ exe_path, @errorName(err) }); }; defer debug_info.deinit(gpa); @@ -54,7 +51,7 @@ pub fn main() !void { assert(std.sort.isSorted(usize, pcs, {}, std.sort.asc(usize))); const source_locations = try arena.alloc(std.debug.SourceLocation, pcs.len); - try debug_info.resolveSourceLocations(gpa, pcs, source_locations, prog_node); + try debug_info.resolveSourceLocations(gpa, pcs, source_locations); defer for (source_locations) |sl| { gpa.free(sl.file_name); }; From 53aa9d75a9b10c9cd277031e604a631452d34e8c Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 3 Aug 2024 17:42:08 -0700 Subject: [PATCH 176/266] std.debug.Info.resolveSourceLocations: O(N) implementation --- lib/std/debug/Dwarf.zig | 59 +++------------- lib/std/debug/Info.zig | 146 +++++++++++++++++++++++++++++++++++++++- tools/dump-cov.zig | 11 ++- 3 files changed, 157 insertions(+), 59 deletions(-) diff --git a/lib/std/debug/Dwarf.zig b/lib/std/debug/Dwarf.zig index 9689ac98b3..cd37795351 100644 --- a/lib/std/debug/Dwarf.zig +++ b/lib/std/debug/Dwarf.zig @@ -152,6 +152,7 @@ pub const CompileUnit = struct { pub const LineEntry = struct { line: u32, column: u32, + /// Offset by 1 depending on whether Dwarf version is >= 5. file: u32, }; @@ -809,7 +810,7 @@ pub fn getSymbolName(di: *Dwarf, address: u64) ?[]const u8 { return null; } -const ScanError = error{ +pub const ScanError = error{ InvalidDebugInfo, MissingDebugInfo, } || Allocator.Error || std.debug.FixedBufferReader.Error; @@ -1113,7 +1114,7 @@ pub fn sortCompileUnits(d: *Dwarf) ScanError!void { } std.mem.sortUnstable(CompileUnit, d.compile_unit_list.items, {}, struct { - fn lessThan(ctx: void, a: CompileUnit, b: CompileUnit) bool { + pub fn lessThan(ctx: void, a: CompileUnit, b: CompileUnit) bool { _ = ctx; const a_range = a.pc_range orelse return false; const b_range = b.pc_range orelse return true; @@ -1641,14 +1642,18 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) ! }; } +pub fn populateSrcLocCache(d: *Dwarf, gpa: Allocator, cu: *CompileUnit) ScanError!void { + if (cu.src_loc_cache != null) return; + cu.src_loc_cache = try runLineNumberProgram(d, gpa, cu); +} + pub fn getLineNumberInfo( d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit, target_address: u64, ) !std.debug.SourceLocation { - if (compile_unit.src_loc_cache == null) - compile_unit.src_loc_cache = try runLineNumberProgram(d, gpa, compile_unit); + try populateSrcLocCache(d, gpa, compile_unit); const slc = &compile_unit.src_loc_cache.?; const entry = try slc.findSource(target_address); const file_index = entry.file - @intFromBool(slc.version < 5); @@ -2343,52 +2348,6 @@ pub const ElfModule = struct { } }; -pub const ResolveSourceLocationsError = Allocator.Error || FixedBufferReader.Error; - -/// Given an array of virtual memory addresses, sorted ascending, outputs a -/// corresponding array of source locations, by appending to the provided -/// array list. -pub fn resolveSourceLocations( - d: *Dwarf, - gpa: Allocator, - sorted_pc_addrs: []const u64, - /// Asserts its length equals length of `sorted_pc_addrs`. - output: []std.debug.SourceLocation, -) ResolveSourceLocationsError!void { - assert(sorted_pc_addrs.len == output.len); - assert(d.compile_units_sorted); - - var cu_i: usize = 0; - var cu: *CompileUnit = &d.compile_unit_list.items[0]; - var range = cu.pc_range.?; - next_pc: for (sorted_pc_addrs, output) |pc, *out| { - while (pc >= range.end) { - cu_i += 1; - if (cu_i >= d.compile_unit_list.items.len) { - out.* = std.debug.SourceLocation.invalid; - continue :next_pc; - } - cu = &d.compile_unit_list.items[cu_i]; - range = cu.pc_range orelse { - out.* = std.debug.SourceLocation.invalid; - continue :next_pc; - }; - } - if (pc < range.start) { - out.* = std.debug.SourceLocation.invalid; - continue :next_pc; - } - // TODO: instead of calling this function, break the function up into one that parses the - // information once and prepares a context that can be reused for the entire batch. - if (getLineNumberInfo(d, gpa, cu, pc)) |src_loc| { - out.* = src_loc; - } else |err| switch (err) { - error.MissingDebugInfo, error.InvalidDebugInfo => out.* = std.debug.SourceLocation.invalid, - else => |e| return e, - } - } -} - fn getSymbol(di: *Dwarf, allocator: Allocator, address: u64) !std.debug.Symbol { if (di.findCompileUnit(address)) |compile_unit| { return .{ diff --git a/lib/std/debug/Info.zig b/lib/std/debug/Info.zig index f31b2f22c4..a52de6549b 100644 --- a/lib/std/debug/Info.zig +++ b/lib/std/debug/Info.zig @@ -12,12 +12,66 @@ const Path = std.Build.Cache.Path; const Dwarf = std.debug.Dwarf; const page_size = std.mem.page_size; const assert = std.debug.assert; +const Hash = std.hash.Wyhash; const Info = @This(); /// Sorted by key, ascending. address_map: std.AutoArrayHashMapUnmanaged(u64, Dwarf.ElfModule), +/// Provides a globally-scoped integer index for directories. +/// +/// As opposed to, for example, a directory index that is compilation-unit +/// scoped inside a single ELF module. +/// +/// String memory references the memory-mapped debug information. +/// +/// Protected by `mutex`. +directories: std.StringArrayHashMapUnmanaged(void), +/// Provides a globally-scoped integer index for files. +/// +/// String memory references the memory-mapped debug information. +/// +/// Protected by `mutex`. +files: std.ArrayHashMapUnmanaged(File, void, File.MapContext, false), +/// Protects `directories` and `files`. +mutex: std.Thread.Mutex, + +pub const SourceLocation = struct { + file: File.Index, + line: u32, + column: u32, + + pub const invalid: SourceLocation = .{ + .file = .invalid, + .line = 0, + .column = 0, + }; +}; + +pub const File = struct { + directory_index: u32, + basename: []const u8, + + pub const Index = enum(u32) { + invalid = std.math.maxInt(u32), + _, + }; + + pub const MapContext = struct { + pub fn hash(ctx: MapContext, a: File) u32 { + _ = ctx; + return @truncate(Hash.hash(a.directory_index, a.basename)); + } + + pub fn eql(ctx: MapContext, a: File, b: File, b_index: usize) bool { + _ = ctx; + _ = b_index; + return a.directory_index == b.directory_index and std.mem.eql(u8, a.basename, b.basename); + } + }; +}; + pub const LoadError = Dwarf.ElfModule.LoadError; pub fn load(gpa: Allocator, path: Path) LoadError!Info { @@ -26,12 +80,17 @@ pub fn load(gpa: Allocator, path: Path) LoadError!Info { try elf_module.dwarf.sortCompileUnits(); var info: Info = .{ .address_map = .{}, + .directories = .{}, + .files = .{}, + .mutex = .{}, }; try info.address_map.put(gpa, elf_module.base_address, elf_module); return info; } pub fn deinit(info: *Info, gpa: Allocator) void { + info.directories.deinit(gpa); + info.files.deinit(gpa); for (info.address_map.values()) |*elf_module| { elf_module.dwarf.deinit(gpa); } @@ -39,17 +98,98 @@ pub fn deinit(info: *Info, gpa: Allocator) void { info.* = undefined; } -pub const ResolveSourceLocationsError = Dwarf.ResolveSourceLocationsError; +pub fn fileAt(info: *Info, index: File.Index) *File { + return &info.files.keys()[@intFromEnum(index)]; +} +pub const ResolveSourceLocationsError = Dwarf.ScanError; + +/// Given an array of virtual memory addresses, sorted ascending, outputs a +/// corresponding array of source locations. pub fn resolveSourceLocations( info: *Info, gpa: Allocator, sorted_pc_addrs: []const u64, /// Asserts its length equals length of `sorted_pc_addrs`. - output: []std.debug.SourceLocation, + output: []SourceLocation, ) ResolveSourceLocationsError!void { assert(sorted_pc_addrs.len == output.len); if (info.address_map.entries.len != 1) @panic("TODO"); const elf_module = &info.address_map.values()[0]; - return elf_module.dwarf.resolveSourceLocations(gpa, sorted_pc_addrs, output); + return resolveSourceLocationsDwarf(info, gpa, sorted_pc_addrs, output, &elf_module.dwarf); +} + +pub fn resolveSourceLocationsDwarf( + info: *Info, + gpa: Allocator, + sorted_pc_addrs: []const u64, + /// Asserts its length equals length of `sorted_pc_addrs`. + output: []SourceLocation, + d: *Dwarf, +) ResolveSourceLocationsError!void { + assert(sorted_pc_addrs.len == output.len); + assert(d.compile_units_sorted); + + var cu_i: usize = 0; + var line_table_i: usize = 0; + var cu: *Dwarf.CompileUnit = &d.compile_unit_list.items[0]; + var range = cu.pc_range.?; + // Protects directories and files tables from other threads. + info.mutex.lock(); + defer info.mutex.unlock(); + next_pc: for (sorted_pc_addrs, output) |pc, *out| { + while (pc >= range.end) { + cu_i += 1; + if (cu_i >= d.compile_unit_list.items.len) { + out.* = SourceLocation.invalid; + continue :next_pc; + } + cu = &d.compile_unit_list.items[cu_i]; + line_table_i = 0; + range = cu.pc_range orelse { + out.* = SourceLocation.invalid; + continue :next_pc; + }; + } + if (pc < range.start) { + out.* = SourceLocation.invalid; + continue :next_pc; + } + if (line_table_i == 0) { + line_table_i = 1; + info.mutex.unlock(); + defer info.mutex.lock(); + d.populateSrcLocCache(gpa, cu) catch |err| switch (err) { + error.MissingDebugInfo, error.InvalidDebugInfo => { + out.* = SourceLocation.invalid; + cu_i += 1; + if (cu_i < d.compile_unit_list.items.len) { + cu = &d.compile_unit_list.items[cu_i]; + line_table_i = 0; + if (cu.pc_range) |r| range = r; + } + continue :next_pc; + }, + else => |e| return e, + }; + } + const slc = &cu.src_loc_cache.?; + const table_addrs = slc.line_table.keys(); + while (line_table_i < table_addrs.len and table_addrs[line_table_i] < pc) line_table_i += 1; + + const entry = slc.line_table.values()[line_table_i - 1]; + const corrected_file_index = entry.file - @intFromBool(slc.version < 5); + const file_entry = slc.files[corrected_file_index]; + const dir_path = slc.directories[file_entry.dir_index].path; + const dir_gop = try info.directories.getOrPut(gpa, dir_path); + const file_gop = try info.files.getOrPut(gpa, .{ + .directory_index = @intCast(dir_gop.index), + .basename = file_entry.path, + }); + out.* = .{ + .file = @enumFromInt(file_gop.index), + .line = entry.line, + .column = entry.column, + }; + } } diff --git a/tools/dump-cov.zig b/tools/dump-cov.zig index f821dde611..bd096b9fc0 100644 --- a/tools/dump-cov.zig +++ b/tools/dump-cov.zig @@ -50,15 +50,14 @@ pub fn main() !void { } assert(std.sort.isSorted(usize, pcs, {}, std.sort.asc(usize))); - const source_locations = try arena.alloc(std.debug.SourceLocation, pcs.len); + const source_locations = try arena.alloc(std.debug.Info.SourceLocation, pcs.len); try debug_info.resolveSourceLocations(gpa, pcs, source_locations); - defer for (source_locations) |sl| { - gpa.free(sl.file_name); - }; for (pcs, source_locations) |pc, sl| { - try stdout.print("{x}: {s}:{d}:{d}\n", .{ - pc, sl.file_name, sl.line, sl.column, + const file = debug_info.fileAt(sl.file); + const dir_name = debug_info.directories.keys()[file.directory_index]; + try stdout.print("{x}: {s}/{s}:{d}:{d}\n", .{ + pc, dir_name, file.basename, sl.line, sl.column, }); } From 5f92a036f9a9a137e4276d0f605e4cb940eca3a7 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 3 Aug 2024 18:28:58 -0700 Subject: [PATCH 177/266] README: update how std lib docs are found in a release build --- README.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 51c39f0796..7c4adbf82a 100644 --- a/README.md +++ b/README.md @@ -13,11 +13,9 @@ Documentation** corresponding to the version of Zig that you are using by following the appropriate link on the [download page](https://ziglang.org/download). -Otherwise, you're looking at a release of Zig, and you can find documentation -here: - - * doc/langref.html - * doc/std/index.html +Otherwise, you're looking at a release of Zig, so you can find the language +reference at `doc/langref.html`, and the standard library documentation by +running `zig std`, which will open a browser tab. ## Installation From 517cfb0dd1e2b5b8efc8e90ce4e5593a38fa158c Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 4 Aug 2024 00:16:28 -0700 Subject: [PATCH 178/266] fuzzing: progress towards web UI * libfuzzer: close file after mmap * fuzzer/main.js: connect with EventSource and debug dump the messages. currently this prints how many fuzzer runs have been attempted to console.log. * extract some `std.debug.Info` logic into `std.debug.Coverage`. Prepares for consolidation across multiple different executables which share source files, and makes it possible to send all the PC/SourceLocation mapping data with 4 memcpy'd arrays. * std.Build.Fuzz: - spawn a thread to watch the message queue and signal event subscribers. - track coverage map data - respond to /events URL with EventSource messages on a timer --- lib/fuzzer.zig | 1 + lib/fuzzer/main.js | 15 ++- lib/std/Build/Fuzz.zig | 211 ++++++++++++++++++++++++++++++-- lib/std/Build/Step/Run.zig | 6 +- lib/std/debug.zig | 1 + lib/std/debug/Coverage.zig | 244 +++++++++++++++++++++++++++++++++++++ lib/std/debug/Info.zig | 153 ++--------------------- tools/dump-cov.zig | 16 ++- 8 files changed, 480 insertions(+), 167 deletions(-) create mode 100644 lib/std/debug/Coverage.zig diff --git a/lib/fuzzer.zig b/lib/fuzzer.zig index ede3663cdc..0d968cd60d 100644 --- a/lib/fuzzer.zig +++ b/lib/fuzzer.zig @@ -218,6 +218,7 @@ const Fuzzer = struct { .read = true, .truncate = false, }); + defer coverage_file.close(); const n_bitset_elems = (flagged_pcs.len + 7) / 8; const bytes_len = @sizeOf(SeenPcsHeader) + flagged_pcs.len * @sizeOf(usize) + n_bitset_elems; const existing_len = coverage_file.getEndPos() catch |err| { diff --git a/lib/fuzzer/main.js b/lib/fuzzer/main.js index 71e6b5fa54..872ac3d4b5 100644 --- a/lib/fuzzer/main.js +++ b/lib/fuzzer/main.js @@ -12,6 +12,9 @@ const text_decoder = new TextDecoder(); const text_encoder = new TextEncoder(); + const eventSource = new EventSource("events"); + eventSource.addEventListener('message', onMessage, false); + WebAssembly.instantiateStreaming(wasm_promise, { js: { log: function(ptr, len) { @@ -38,11 +41,15 @@ }); }); - function render() { - domSectSource.classList.add("hidden"); + function onMessage(e) { + console.log("Message", e.data); + } - // TODO this is temporary debugging data - renderSource("/home/andy/dev/zig/lib/std/zig/tokenizer.zig"); + function render() { + domSectSource.classList.add("hidden"); + + // TODO this is temporary debugging data + renderSource("/home/andy/dev/zig/lib/std/zig/tokenizer.zig"); } function renderSource(path) { diff --git a/lib/std/Build/Fuzz.zig b/lib/std/Build/Fuzz.zig index 46d9bfc8fd..0ff82f3677 100644 --- a/lib/std/Build/Fuzz.zig +++ b/lib/std/Build/Fuzz.zig @@ -6,6 +6,7 @@ const assert = std.debug.assert; const fatal = std.process.fatal; const Allocator = std.mem.Allocator; const log = std.log; +const Coverage = std.debug.Coverage; const Fuzz = @This(); const build_runner = @import("root"); @@ -53,17 +54,30 @@ pub fn start( .global_cache_directory = global_cache_directory, .zig_lib_directory = zig_lib_directory, .zig_exe_path = zig_exe_path, - .msg_queue = .{}, - .mutex = .{}, .listen_address = listen_address, .fuzz_run_steps = fuzz_run_steps, + + .msg_queue = .{}, + .mutex = .{}, + .condition = .{}, + + .coverage_files = .{}, + .coverage_mutex = .{}, + .coverage_condition = .{}, }; + // For accepting HTTP connections. const web_server_thread = std.Thread.spawn(.{}, WebServer.run, .{&web_server}) catch |err| { fatal("unable to spawn web server thread: {s}", .{@errorName(err)}); }; defer web_server_thread.join(); + // For polling messages and sending updates to subscribers. + const coverage_thread = std.Thread.spawn(.{}, WebServer.coverageRun, .{&web_server}) catch |err| { + fatal("unable to spawn coverage thread: {s}", .{@errorName(err)}); + }; + defer coverage_thread.join(); + { const fuzz_node = prog_node.start("Fuzzing", fuzz_run_steps.len); defer fuzz_node.end(); @@ -88,14 +102,38 @@ pub const WebServer = struct { global_cache_directory: Build.Cache.Directory, zig_lib_directory: Build.Cache.Directory, zig_exe_path: []const u8, - /// Messages from fuzz workers. Protected by mutex. - msg_queue: std.ArrayListUnmanaged(Msg), - mutex: std.Thread.Mutex, listen_address: std.net.Address, fuzz_run_steps: []const *Step.Run, + /// Messages from fuzz workers. Protected by mutex. + msg_queue: std.ArrayListUnmanaged(Msg), + /// Protects `msg_queue` only. + mutex: std.Thread.Mutex, + /// Signaled when there is a message in `msg_queue`. + condition: std.Thread.Condition, + + coverage_files: std.AutoArrayHashMapUnmanaged(u64, CoverageMap), + /// Protects `coverage_files` only. + coverage_mutex: std.Thread.Mutex, + /// Signaled when `coverage_files` changes. + coverage_condition: std.Thread.Condition, + + const CoverageMap = struct { + mapped_memory: []align(std.mem.page_size) const u8, + coverage: Coverage, + + fn deinit(cm: *CoverageMap, gpa: Allocator) void { + std.posix.munmap(cm.mapped_memory); + cm.coverage.deinit(gpa); + cm.* = undefined; + } + }; + const Msg = union(enum) { - coverage_id: u64, + coverage: struct { + id: u64, + run: *Step.Run, + }, }; fn run(ws: *WebServer) void { @@ -162,6 +200,10 @@ pub const WebServer = struct { std.mem.eql(u8, request.head.target, "/debug/sources.tar")) { try serveSourcesTar(ws, request); + } else if (std.mem.eql(u8, request.head.target, "/events") or + std.mem.eql(u8, request.head.target, "/debug/events")) + { + try serveEvents(ws, request); } else { try request.respond("not found", .{ .status = .not_found, @@ -384,6 +426,58 @@ pub const WebServer = struct { try file.writeAll(std.mem.asBytes(&header)); } + fn serveEvents(ws: *WebServer, request: *std.http.Server.Request) !void { + var send_buffer: [0x4000]u8 = undefined; + var response = request.respondStreaming(.{ + .send_buffer = &send_buffer, + .respond_options = .{ + .extra_headers = &.{ + .{ .name = "content-type", .value = "text/event-stream" }, + }, + .transfer_encoding = .none, + }, + }); + + ws.coverage_mutex.lock(); + defer ws.coverage_mutex.unlock(); + + if (getStats(ws)) |stats| { + try response.writer().print("data: {d}\n\n", .{stats.n_runs}); + } else { + try response.writeAll("data: loading debug information\n\n"); + } + try response.flush(); + + while (true) { + ws.coverage_condition.timedWait(&ws.coverage_mutex, std.time.ns_per_ms * 500) catch {}; + if (getStats(ws)) |stats| { + try response.writer().print("data: {d}\n\n", .{stats.n_runs}); + try response.flush(); + } + } + } + + const Stats = struct { + n_runs: u64, + }; + + fn getStats(ws: *WebServer) ?Stats { + const coverage_maps = ws.coverage_files.values(); + if (coverage_maps.len == 0) return null; + // TODO: make each events URL correspond to one coverage map + const ptr = coverage_maps[0].mapped_memory; + const SeenPcsHeader = extern struct { + n_runs: usize, + deduplicated_runs: usize, + pcs_len: usize, + lowest_stack: usize, + }; + const header: *const SeenPcsHeader = @ptrCast(ptr[0..@sizeOf(SeenPcsHeader)]); + return .{ + .n_runs = @atomicLoad(usize, &header.n_runs, .monotonic), + }; + } + fn serveSourcesTar(ws: *WebServer, request: *std.http.Server.Request) !void { const gpa = ws.gpa; @@ -471,6 +565,95 @@ pub const WebServer = struct { .name = "cache-control", .value = "max-age=0, must-revalidate", }; + + fn coverageRun(ws: *WebServer) void { + ws.mutex.lock(); + defer ws.mutex.unlock(); + + while (true) { + ws.condition.wait(&ws.mutex); + for (ws.msg_queue.items) |msg| switch (msg) { + .coverage => |coverage| prepareTables(ws, coverage.run, coverage.id) catch |err| switch (err) { + error.AlreadyReported => continue, + else => |e| log.err("failed to prepare code coverage tables: {s}", .{@errorName(e)}), + }, + }; + ws.msg_queue.clearRetainingCapacity(); + } + } + + fn prepareTables( + ws: *WebServer, + run_step: *Step.Run, + coverage_id: u64, + ) error{ OutOfMemory, AlreadyReported }!void { + const gpa = ws.gpa; + + ws.coverage_mutex.lock(); + defer ws.coverage_mutex.unlock(); + + const gop = try ws.coverage_files.getOrPut(gpa, coverage_id); + if (gop.found_existing) { + // We are fuzzing the same executable with multiple threads. + // Perhaps the same unit test; perhaps a different one. In any + // case, since the coverage file is the same, we only have to + // notice changes to that one file in order to learn coverage for + // this particular executable. + return; + } + errdefer _ = ws.coverage_files.pop(); + + gop.value_ptr.* = .{ + .coverage = std.debug.Coverage.init, + .mapped_memory = undefined, // populated below + }; + errdefer gop.value_ptr.coverage.deinit(gpa); + + const rebuilt_exe_path: Build.Cache.Path = .{ + .root_dir = Build.Cache.Directory.cwd(), + .sub_path = run_step.rebuilt_executable.?, + }; + var debug_info = std.debug.Info.load(gpa, rebuilt_exe_path, &gop.value_ptr.coverage) catch |err| { + log.err("step '{s}': failed to load debug information for '{}': {s}", .{ + run_step.step.name, rebuilt_exe_path, @errorName(err), + }); + return error.AlreadyReported; + }; + defer debug_info.deinit(gpa); + + const coverage_file_path: Build.Cache.Path = .{ + .root_dir = run_step.step.owner.cache_root, + .sub_path = "v/" ++ std.fmt.hex(coverage_id), + }; + var coverage_file = coverage_file_path.root_dir.handle.openFile(coverage_file_path.sub_path, .{}) catch |err| { + log.err("step '{s}': failed to load coverage file '{}': {s}", .{ + run_step.step.name, coverage_file_path, @errorName(err), + }); + return error.AlreadyReported; + }; + defer coverage_file.close(); + + const file_size = coverage_file.getEndPos() catch |err| { + log.err("unable to check len of coverage file '{}': {s}", .{ coverage_file_path, @errorName(err) }); + return error.AlreadyReported; + }; + + const mapped_memory = std.posix.mmap( + null, + file_size, + std.posix.PROT.READ, + .{ .TYPE = .SHARED }, + coverage_file.handle, + 0, + ) catch |err| { + log.err("failed to map coverage file '{}': {s}", .{ coverage_file_path, @errorName(err) }); + return error.AlreadyReported; + }; + + gop.value_ptr.mapped_memory = mapped_memory; + + ws.coverage_condition.broadcast(); + } }; fn rebuildTestsWorkerRun(run: *Step.Run, ttyconf: std.io.tty.Config, parent_prog_node: std.Progress.Node) void { @@ -493,16 +676,16 @@ fn rebuildTestsWorkerRun(run: *Step.Run, ttyconf: std.io.tty.Config, parent_prog build_runner.printErrorMessages(gpa, &compile.step, ttyconf, stderr, false) catch {}; } - if (result) |rebuilt_bin_path| { - run.rebuilt_executable = rebuilt_bin_path; - } else |err| switch (err) { - error.MakeFailed => {}, + const rebuilt_bin_path = result catch |err| switch (err) { + error.MakeFailed => return, else => { - std.debug.print("step '{s}': failed to rebuild in fuzz mode: {s}\n", .{ + log.err("step '{s}': failed to rebuild in fuzz mode: {s}", .{ compile.step.name, @errorName(err), }); + return; }, - } + }; + run.rebuilt_executable = rebuilt_bin_path; } fn fuzzWorkerRun( @@ -524,11 +707,13 @@ fn fuzzWorkerRun( std.debug.lockStdErr(); defer std.debug.unlockStdErr(); build_runner.printErrorMessages(gpa, &run.step, ttyconf, stderr, false) catch {}; + return; }, else => { - std.debug.print("step '{s}': failed to rebuild '{s}' in fuzz mode: {s}\n", .{ + log.err("step '{s}': failed to rerun '{s}' in fuzz mode: {s}", .{ run.step.name, test_name, @errorName(err), }); + return; }, }; } diff --git a/lib/std/Build/Step/Run.zig b/lib/std/Build/Step/Run.zig index e494e969f0..b08ecfee78 100644 --- a/lib/std/Build/Step/Run.zig +++ b/lib/std/Build/Step/Run.zig @@ -1521,7 +1521,11 @@ fn evalZigTest( { web_server.mutex.lock(); defer web_server.mutex.unlock(); - try web_server.msg_queue.append(web_server.gpa, .{ .coverage_id = coverage_id }); + try web_server.msg_queue.append(web_server.gpa, .{ .coverage = .{ + .id = coverage_id, + .run = run, + } }); + web_server.condition.signal(); } }, else => {}, // ignore other messages diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 7f4f6b7df2..a3a8a533ee 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -19,6 +19,7 @@ pub const Dwarf = @import("debug/Dwarf.zig"); pub const Pdb = @import("debug/Pdb.zig"); pub const SelfInfo = @import("debug/SelfInfo.zig"); pub const Info = @import("debug/Info.zig"); +pub const Coverage = @import("debug/Coverage.zig"); /// Unresolved source locations can be represented with a single `usize` that /// corresponds to a virtual memory address of the program counter. Combined diff --git a/lib/std/debug/Coverage.zig b/lib/std/debug/Coverage.zig new file mode 100644 index 0000000000..d9cc7fdebd --- /dev/null +++ b/lib/std/debug/Coverage.zig @@ -0,0 +1,244 @@ +const std = @import("../std.zig"); +const Allocator = std.mem.Allocator; +const Hash = std.hash.Wyhash; +const Dwarf = std.debug.Dwarf; +const assert = std.debug.assert; + +const Coverage = @This(); + +/// Provides a globally-scoped integer index for directories. +/// +/// As opposed to, for example, a directory index that is compilation-unit +/// scoped inside a single ELF module. +/// +/// String memory references the memory-mapped debug information. +/// +/// Protected by `mutex`. +directories: std.ArrayHashMapUnmanaged(String, void, String.MapContext, false), +/// Provides a globally-scoped integer index for files. +/// +/// String memory references the memory-mapped debug information. +/// +/// Protected by `mutex`. +files: std.ArrayHashMapUnmanaged(File, void, File.MapContext, false), +string_bytes: std.ArrayListUnmanaged(u8), +/// Protects the other fields. +mutex: std.Thread.Mutex, + +pub const init: Coverage = .{ + .directories = .{}, + .files = .{}, + .mutex = .{}, + .string_bytes = .{}, +}; + +pub const String = enum(u32) { + _, + + pub const MapContext = struct { + string_bytes: []const u8, + + pub fn eql(self: @This(), a: String, b: String, b_index: usize) bool { + _ = b_index; + const a_slice = span(self.string_bytes[@intFromEnum(a)..]); + const b_slice = span(self.string_bytes[@intFromEnum(b)..]); + return std.mem.eql(u8, a_slice, b_slice); + } + + pub fn hash(self: @This(), a: String) u32 { + return @truncate(Hash.hash(0, span(self.string_bytes[@intFromEnum(a)..]))); + } + }; + + pub const SliceAdapter = struct { + string_bytes: []const u8, + + pub fn eql(self: @This(), a_slice: []const u8, b: String, b_index: usize) bool { + _ = b_index; + const b_slice = span(self.string_bytes[@intFromEnum(b)..]); + return std.mem.eql(u8, a_slice, b_slice); + } + pub fn hash(self: @This(), a: []const u8) u32 { + _ = self; + return @truncate(Hash.hash(0, a)); + } + }; +}; + +pub const SourceLocation = struct { + file: File.Index, + line: u32, + column: u32, + + pub const invalid: SourceLocation = .{ + .file = .invalid, + .line = 0, + .column = 0, + }; +}; + +pub const File = struct { + directory_index: u32, + basename: String, + + pub const Index = enum(u32) { + invalid = std.math.maxInt(u32), + _, + }; + + pub const MapContext = struct { + string_bytes: []const u8, + + pub fn hash(self: MapContext, a: File) u32 { + const a_basename = span(self.string_bytes[@intFromEnum(a.basename)..]); + return @truncate(Hash.hash(a.directory_index, a_basename)); + } + + pub fn eql(self: MapContext, a: File, b: File, b_index: usize) bool { + _ = b_index; + if (a.directory_index != b.directory_index) return false; + const a_basename = span(self.string_bytes[@intFromEnum(a.basename)..]); + const b_basename = span(self.string_bytes[@intFromEnum(b.basename)..]); + return std.mem.eql(u8, a_basename, b_basename); + } + }; + + pub const SliceAdapter = struct { + string_bytes: []const u8, + + pub const Entry = struct { + directory_index: u32, + basename: []const u8, + }; + + pub fn hash(self: @This(), a: Entry) u32 { + _ = self; + return @truncate(Hash.hash(a.directory_index, a.basename)); + } + + pub fn eql(self: @This(), a: Entry, b: File, b_index: usize) bool { + _ = b_index; + if (a.directory_index != b.directory_index) return false; + const b_basename = span(self.string_bytes[@intFromEnum(b.basename)..]); + return std.mem.eql(u8, a.basename, b_basename); + } + }; +}; + +pub fn deinit(cov: *Coverage, gpa: Allocator) void { + cov.directories.deinit(gpa); + cov.files.deinit(gpa); + cov.string_bytes.deinit(gpa); + cov.* = undefined; +} + +pub fn fileAt(cov: *Coverage, index: File.Index) *File { + return &cov.files.keys()[@intFromEnum(index)]; +} + +pub fn stringAt(cov: *Coverage, index: String) [:0]const u8 { + return span(cov.string_bytes.items[@intFromEnum(index)..]); +} + +pub const ResolveAddressesDwarfError = Dwarf.ScanError; + +pub fn resolveAddressesDwarf( + cov: *Coverage, + gpa: Allocator, + sorted_pc_addrs: []const u64, + /// Asserts its length equals length of `sorted_pc_addrs`. + output: []SourceLocation, + d: *Dwarf, +) ResolveAddressesDwarfError!void { + assert(sorted_pc_addrs.len == output.len); + assert(d.compile_units_sorted); + + var cu_i: usize = 0; + var line_table_i: usize = 0; + var cu: *Dwarf.CompileUnit = &d.compile_unit_list.items[0]; + var range = cu.pc_range.?; + // Protects directories and files tables from other threads. + cov.mutex.lock(); + defer cov.mutex.unlock(); + next_pc: for (sorted_pc_addrs, output) |pc, *out| { + while (pc >= range.end) { + cu_i += 1; + if (cu_i >= d.compile_unit_list.items.len) { + out.* = SourceLocation.invalid; + continue :next_pc; + } + cu = &d.compile_unit_list.items[cu_i]; + line_table_i = 0; + range = cu.pc_range orelse { + out.* = SourceLocation.invalid; + continue :next_pc; + }; + } + if (pc < range.start) { + out.* = SourceLocation.invalid; + continue :next_pc; + } + if (line_table_i == 0) { + line_table_i = 1; + cov.mutex.unlock(); + defer cov.mutex.lock(); + d.populateSrcLocCache(gpa, cu) catch |err| switch (err) { + error.MissingDebugInfo, error.InvalidDebugInfo => { + out.* = SourceLocation.invalid; + cu_i += 1; + if (cu_i < d.compile_unit_list.items.len) { + cu = &d.compile_unit_list.items[cu_i]; + line_table_i = 0; + if (cu.pc_range) |r| range = r; + } + continue :next_pc; + }, + else => |e| return e, + }; + } + const slc = &cu.src_loc_cache.?; + const table_addrs = slc.line_table.keys(); + while (line_table_i < table_addrs.len and table_addrs[line_table_i] < pc) line_table_i += 1; + + const entry = slc.line_table.values()[line_table_i - 1]; + const corrected_file_index = entry.file - @intFromBool(slc.version < 5); + const file_entry = slc.files[corrected_file_index]; + const dir_path = slc.directories[file_entry.dir_index].path; + try cov.string_bytes.ensureUnusedCapacity(gpa, dir_path.len + file_entry.path.len + 2); + const dir_gop = try cov.directories.getOrPutContextAdapted(gpa, dir_path, String.SliceAdapter{ + .string_bytes = cov.string_bytes.items, + }, String.MapContext{ + .string_bytes = cov.string_bytes.items, + }); + if (!dir_gop.found_existing) + dir_gop.key_ptr.* = addStringAssumeCapacity(cov, dir_path); + const file_gop = try cov.files.getOrPutContextAdapted(gpa, File.SliceAdapter.Entry{ + .directory_index = @intCast(dir_gop.index), + .basename = file_entry.path, + }, File.SliceAdapter{ + .string_bytes = cov.string_bytes.items, + }, File.MapContext{ + .string_bytes = cov.string_bytes.items, + }); + if (!file_gop.found_existing) file_gop.key_ptr.* = .{ + .directory_index = @intCast(dir_gop.index), + .basename = addStringAssumeCapacity(cov, file_entry.path), + }; + out.* = .{ + .file = @enumFromInt(file_gop.index), + .line = entry.line, + .column = entry.column, + }; + } +} + +pub fn addStringAssumeCapacity(cov: *Coverage, s: []const u8) String { + const result: String = @enumFromInt(cov.string_bytes.items.len); + cov.string_bytes.appendSliceAssumeCapacity(s); + cov.string_bytes.appendAssumeCapacity(0); + return result; +} + +fn span(s: []const u8) [:0]const u8 { + return std.mem.sliceTo(@as([:0]const u8, @ptrCast(s)), 0); +} diff --git a/lib/std/debug/Info.zig b/lib/std/debug/Info.zig index a52de6549b..ee191d2c12 100644 --- a/lib/std/debug/Info.zig +++ b/lib/std/debug/Info.zig @@ -12,85 +12,31 @@ const Path = std.Build.Cache.Path; const Dwarf = std.debug.Dwarf; const page_size = std.mem.page_size; const assert = std.debug.assert; -const Hash = std.hash.Wyhash; +const Coverage = std.debug.Coverage; +const SourceLocation = std.debug.Coverage.SourceLocation; const Info = @This(); /// Sorted by key, ascending. address_map: std.AutoArrayHashMapUnmanaged(u64, Dwarf.ElfModule), - -/// Provides a globally-scoped integer index for directories. -/// -/// As opposed to, for example, a directory index that is compilation-unit -/// scoped inside a single ELF module. -/// -/// String memory references the memory-mapped debug information. -/// -/// Protected by `mutex`. -directories: std.StringArrayHashMapUnmanaged(void), -/// Provides a globally-scoped integer index for files. -/// -/// String memory references the memory-mapped debug information. -/// -/// Protected by `mutex`. -files: std.ArrayHashMapUnmanaged(File, void, File.MapContext, false), -/// Protects `directories` and `files`. -mutex: std.Thread.Mutex, - -pub const SourceLocation = struct { - file: File.Index, - line: u32, - column: u32, - - pub const invalid: SourceLocation = .{ - .file = .invalid, - .line = 0, - .column = 0, - }; -}; - -pub const File = struct { - directory_index: u32, - basename: []const u8, - - pub const Index = enum(u32) { - invalid = std.math.maxInt(u32), - _, - }; - - pub const MapContext = struct { - pub fn hash(ctx: MapContext, a: File) u32 { - _ = ctx; - return @truncate(Hash.hash(a.directory_index, a.basename)); - } - - pub fn eql(ctx: MapContext, a: File, b: File, b_index: usize) bool { - _ = ctx; - _ = b_index; - return a.directory_index == b.directory_index and std.mem.eql(u8, a.basename, b.basename); - } - }; -}; +/// Externally managed, outlives this `Info` instance. +coverage: *Coverage, pub const LoadError = Dwarf.ElfModule.LoadError; -pub fn load(gpa: Allocator, path: Path) LoadError!Info { +pub fn load(gpa: Allocator, path: Path, coverage: *Coverage) LoadError!Info { var sections: Dwarf.SectionArray = Dwarf.null_section_array; var elf_module = try Dwarf.ElfModule.loadPath(gpa, path, null, null, §ions, null); try elf_module.dwarf.sortCompileUnits(); var info: Info = .{ .address_map = .{}, - .directories = .{}, - .files = .{}, - .mutex = .{}, + .coverage = coverage, }; try info.address_map.put(gpa, elf_module.base_address, elf_module); return info; } pub fn deinit(info: *Info, gpa: Allocator) void { - info.directories.deinit(gpa); - info.files.deinit(gpa); for (info.address_map.values()) |*elf_module| { elf_module.dwarf.deinit(gpa); } @@ -98,98 +44,19 @@ pub fn deinit(info: *Info, gpa: Allocator) void { info.* = undefined; } -pub fn fileAt(info: *Info, index: File.Index) *File { - return &info.files.keys()[@intFromEnum(index)]; -} - -pub const ResolveSourceLocationsError = Dwarf.ScanError; +pub const ResolveAddressesError = Coverage.ResolveAddressesDwarfError; /// Given an array of virtual memory addresses, sorted ascending, outputs a /// corresponding array of source locations. -pub fn resolveSourceLocations( +pub fn resolveAddresses( info: *Info, gpa: Allocator, sorted_pc_addrs: []const u64, /// Asserts its length equals length of `sorted_pc_addrs`. output: []SourceLocation, -) ResolveSourceLocationsError!void { +) ResolveAddressesError!void { assert(sorted_pc_addrs.len == output.len); if (info.address_map.entries.len != 1) @panic("TODO"); const elf_module = &info.address_map.values()[0]; - return resolveSourceLocationsDwarf(info, gpa, sorted_pc_addrs, output, &elf_module.dwarf); -} - -pub fn resolveSourceLocationsDwarf( - info: *Info, - gpa: Allocator, - sorted_pc_addrs: []const u64, - /// Asserts its length equals length of `sorted_pc_addrs`. - output: []SourceLocation, - d: *Dwarf, -) ResolveSourceLocationsError!void { - assert(sorted_pc_addrs.len == output.len); - assert(d.compile_units_sorted); - - var cu_i: usize = 0; - var line_table_i: usize = 0; - var cu: *Dwarf.CompileUnit = &d.compile_unit_list.items[0]; - var range = cu.pc_range.?; - // Protects directories and files tables from other threads. - info.mutex.lock(); - defer info.mutex.unlock(); - next_pc: for (sorted_pc_addrs, output) |pc, *out| { - while (pc >= range.end) { - cu_i += 1; - if (cu_i >= d.compile_unit_list.items.len) { - out.* = SourceLocation.invalid; - continue :next_pc; - } - cu = &d.compile_unit_list.items[cu_i]; - line_table_i = 0; - range = cu.pc_range orelse { - out.* = SourceLocation.invalid; - continue :next_pc; - }; - } - if (pc < range.start) { - out.* = SourceLocation.invalid; - continue :next_pc; - } - if (line_table_i == 0) { - line_table_i = 1; - info.mutex.unlock(); - defer info.mutex.lock(); - d.populateSrcLocCache(gpa, cu) catch |err| switch (err) { - error.MissingDebugInfo, error.InvalidDebugInfo => { - out.* = SourceLocation.invalid; - cu_i += 1; - if (cu_i < d.compile_unit_list.items.len) { - cu = &d.compile_unit_list.items[cu_i]; - line_table_i = 0; - if (cu.pc_range) |r| range = r; - } - continue :next_pc; - }, - else => |e| return e, - }; - } - const slc = &cu.src_loc_cache.?; - const table_addrs = slc.line_table.keys(); - while (line_table_i < table_addrs.len and table_addrs[line_table_i] < pc) line_table_i += 1; - - const entry = slc.line_table.values()[line_table_i - 1]; - const corrected_file_index = entry.file - @intFromBool(slc.version < 5); - const file_entry = slc.files[corrected_file_index]; - const dir_path = slc.directories[file_entry.dir_index].path; - const dir_gop = try info.directories.getOrPut(gpa, dir_path); - const file_gop = try info.files.getOrPut(gpa, .{ - .directory_index = @intCast(dir_gop.index), - .basename = file_entry.path, - }); - out.* = .{ - .file = @enumFromInt(file_gop.index), - .line = entry.line, - .column = entry.column, - }; - } + return info.coverage.resolveAddressesDwarf(gpa, sorted_pc_addrs, output, &elf_module.dwarf); } diff --git a/tools/dump-cov.zig b/tools/dump-cov.zig index bd096b9fc0..fb08907cad 100644 --- a/tools/dump-cov.zig +++ b/tools/dump-cov.zig @@ -28,7 +28,10 @@ pub fn main() !void { .sub_path = cov_file_name, }; - var debug_info = std.debug.Info.load(gpa, exe_path) catch |err| { + var coverage = std.debug.Coverage.init; + defer coverage.deinit(gpa); + + var debug_info = std.debug.Info.load(gpa, exe_path, &coverage) catch |err| { fatal("failed to load debug info for {}: {s}", .{ exe_path, @errorName(err) }); }; defer debug_info.deinit(gpa); @@ -50,14 +53,15 @@ pub fn main() !void { } assert(std.sort.isSorted(usize, pcs, {}, std.sort.asc(usize))); - const source_locations = try arena.alloc(std.debug.Info.SourceLocation, pcs.len); - try debug_info.resolveSourceLocations(gpa, pcs, source_locations); + const source_locations = try arena.alloc(std.debug.Coverage.SourceLocation, pcs.len); + try debug_info.resolveAddresses(gpa, pcs, source_locations); for (pcs, source_locations) |pc, sl| { - const file = debug_info.fileAt(sl.file); - const dir_name = debug_info.directories.keys()[file.directory_index]; + const file = debug_info.coverage.fileAt(sl.file); + const dir_name = debug_info.coverage.directories.keys()[file.directory_index]; + const dir_name_slice = debug_info.coverage.stringAt(dir_name); try stdout.print("{x}: {s}/{s}:{d}:{d}\n", .{ - pc, dir_name, file.basename, sl.line, sl.column, + pc, dir_name_slice, debug_info.coverage.stringAt(file.basename), sl.line, sl.column, }); } From d36c18274813268fd9edb633a31dd9e94ae11040 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 4 Aug 2024 15:24:54 -0700 Subject: [PATCH 179/266] std.posix: add some more void bits prevents unnecessary compilation errors on wasm32-freestanding --- lib/std/posix.zig | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/std/posix.zig b/lib/std/posix.zig index e04efbbcc0..02f2d975dd 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -47,6 +47,11 @@ else switch (native_os) { .plan9 => std.os.plan9, else => struct { pub const ucontext_t = void; + pub const pid_t = void; + pub const pollfd = void; + pub const fd_t = void; + pub const uid_t = void; + pub const gid_t = void; }, }; From b9fd0eeca6ba45058ed67fc211bb50a93c971a28 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 4 Aug 2024 15:25:42 -0700 Subject: [PATCH 180/266] add std.http.WebSocket --- lib/std/http.zig | 2 + lib/std/http/WebSocket.zig | 243 +++++++++++++++++++++++++++++++++++++ 2 files changed, 245 insertions(+) create mode 100644 lib/std/http/WebSocket.zig diff --git a/lib/std/http.zig b/lib/std/http.zig index 621c7a5f0d..d5d5583299 100644 --- a/lib/std/http.zig +++ b/lib/std/http.zig @@ -4,6 +4,7 @@ pub const protocol = @import("http/protocol.zig"); pub const HeadParser = @import("http/HeadParser.zig"); pub const ChunkParser = @import("http/ChunkParser.zig"); pub const HeaderIterator = @import("http/HeaderIterator.zig"); +pub const WebSocket = @import("http/WebSocket.zig"); pub const Version = enum { @"HTTP/1.0", @@ -318,6 +319,7 @@ test { _ = Status; _ = HeadParser; _ = ChunkParser; + _ = WebSocket; _ = @import("http/test.zig"); } } diff --git a/lib/std/http/WebSocket.zig b/lib/std/http/WebSocket.zig new file mode 100644 index 0000000000..ad513fddf8 --- /dev/null +++ b/lib/std/http/WebSocket.zig @@ -0,0 +1,243 @@ +//! See https://tools.ietf.org/html/rfc6455 + +const builtin = @import("builtin"); +const std = @import("std"); +const WebSocket = @This(); +const assert = std.debug.assert; +const native_endian = builtin.cpu.arch.endian(); + +key: []const u8, +request: *std.http.Server.Request, +recv_fifo: std.fifo.LinearFifo(u8, .Slice), +reader: std.io.AnyReader, +response: std.http.Server.Response, +/// Number of bytes that have been peeked but not discarded yet. +outstanding_len: usize, + +pub const InitError = error{WebSocketUpgradeMissingKey} || + std.http.Server.Request.ReaderError; + +pub fn init( + ws: *WebSocket, + request: *std.http.Server.Request, + send_buffer: []u8, + recv_buffer: []align(4) u8, +) InitError!bool { + var sec_websocket_key: ?[]const u8 = null; + var upgrade_websocket: bool = false; + var it = request.iterateHeaders(); + while (it.next()) |header| { + if (std.ascii.eqlIgnoreCase(header.name, "sec-websocket-key")) { + sec_websocket_key = header.value; + } else if (std.ascii.eqlIgnoreCase(header.name, "upgrade")) { + if (!std.mem.eql(u8, header.value, "websocket")) + return false; + upgrade_websocket = true; + } + } + if (!upgrade_websocket) + return false; + + const key = sec_websocket_key orelse return error.WebSocketUpgradeMissingKey; + + var sha1 = std.crypto.hash.Sha1.init(.{}); + sha1.update(key); + sha1.update("258EAFA5-E914-47DA-95CA-C5AB0DC85B11"); + var digest: [std.crypto.hash.Sha1.digest_length]u8 = undefined; + sha1.final(&digest); + var base64_digest: [28]u8 = undefined; + assert(std.base64.standard.Encoder.encode(&base64_digest, &digest).len == base64_digest.len); + + request.head.content_length = std.math.maxInt(u64); + + ws.* = .{ + .key = key, + .recv_fifo = std.fifo.LinearFifo(u8, .Slice).init(recv_buffer), + .reader = try request.reader(), + .response = request.respondStreaming(.{ + .send_buffer = send_buffer, + .respond_options = .{ + .status = .switching_protocols, + .extra_headers = &.{ + .{ .name = "upgrade", .value = "websocket" }, + .{ .name = "connection", .value = "upgrade" }, + .{ .name = "sec-websocket-accept", .value = &base64_digest }, + }, + .transfer_encoding = .none, + }, + }), + .request = request, + .outstanding_len = 0, + }; + return true; +} + +pub const Header0 = packed struct(u8) { + opcode: Opcode, + rsv3: u1 = 0, + rsv2: u1 = 0, + rsv1: u1 = 0, + fin: bool, +}; + +pub const Header1 = packed struct(u8) { + payload_len: enum(u7) { + len16 = 126, + len64 = 127, + _, + }, + mask: bool, +}; + +pub const Opcode = enum(u4) { + continuation = 0, + text = 1, + binary = 2, + connection_close = 8, + ping = 9, + /// "A Pong frame MAY be sent unsolicited. This serves as a unidirectional + /// heartbeat. A response to an unsolicited Pong frame is not expected." + pong = 10, + _, +}; + +pub const ReadSmallTextMessageError = error{ + ConnectionClose, + UnexpectedOpCode, + MessageTooBig, + MissingMaskBit, +} || RecvError; + +pub const SmallMessage = struct { + /// Can be text, binary, or ping. + opcode: Opcode, + data: []u8, +}; + +/// Reads the next message from the WebSocket stream, failing if the message does not fit +/// into `recv_buffer`. +pub fn readSmallMessage(ws: *WebSocket) ReadSmallTextMessageError!SmallMessage { + while (true) { + const header_bytes = (try recv(ws, 2))[0..2]; + const h0: Header0 = @bitCast(header_bytes[0]); + const h1: Header1 = @bitCast(header_bytes[1]); + + switch (h0.opcode) { + .text, .binary, .pong, .ping => {}, + .connection_close => return error.ConnectionClose, + .continuation => return error.UnexpectedOpCode, + _ => return error.UnexpectedOpCode, + } + + if (!h0.fin) return error.MessageTooBig; + if (!h1.mask) return error.MissingMaskBit; + + const len: usize = switch (h1.payload_len) { + .len16 => try recvReadInt(ws, u16), + .len64 => std.math.cast(usize, try recvReadInt(ws, u64)) orelse return error.MessageTooBig, + else => @intFromEnum(h1.payload_len), + }; + if (len > ws.recv_fifo.buf.len) return error.MessageTooBig; + + const mask: u32 = @bitCast((try recv(ws, 4))[0..4].*); + const payload = try recv(ws, len); + + // Skip pongs. + if (h0.opcode == .pong) continue; + + // The last item may contain a partial word of unused data. + const floored_len = (payload.len / 4) * 4; + const u32_payload: []align(1) u32 = @alignCast(std.mem.bytesAsSlice(u32, payload[0..floored_len])); + for (u32_payload) |*elem| elem.* ^= mask; + const mask_bytes = std.mem.asBytes(&mask)[0 .. payload.len - floored_len]; + for (payload[floored_len..], mask_bytes) |*leftover, m| leftover.* ^= m; + + return .{ + .opcode = h0.opcode, + .data = payload, + }; + } +} + +const RecvError = std.http.Server.Request.ReadError || error{EndOfStream}; + +fn recv(ws: *WebSocket, len: usize) RecvError![]u8 { + ws.recv_fifo.discard(ws.outstanding_len); + assert(len <= ws.recv_fifo.buf.len); + if (len > ws.recv_fifo.count) { + const small_buf = ws.recv_fifo.writableSlice(0); + const needed = len - ws.recv_fifo.count; + const buf = if (small_buf.len >= needed) small_buf else b: { + ws.recv_fifo.realign(); + break :b ws.recv_fifo.writableSlice(0); + }; + const n = try @as(RecvError!usize, @errorCast(ws.reader.readAtLeast(buf, needed))); + if (n < needed) return error.EndOfStream; + ws.recv_fifo.update(n); + } + ws.outstanding_len = len; + // TODO: improve the std lib API so this cast isn't necessary. + return @constCast(ws.recv_fifo.readableSliceOfLen(len)); +} + +fn recvReadInt(ws: *WebSocket, comptime I: type) !I { + const unswapped: I = @bitCast((try recv(ws, @sizeOf(I)))[0..@sizeOf(I)].*); + return switch (native_endian) { + .little => @byteSwap(unswapped), + .big => unswapped, + }; +} + +pub const WriteError = std.http.Server.Response.WriteError; + +pub fn writeMessage(ws: *WebSocket, message: []const u8, opcode: Opcode) WriteError!void { + const iovecs: [1]std.posix.iovec_const = .{ + .{ .base = message.ptr, .len = message.len }, + }; + return writeMessagev(ws, &iovecs, opcode); +} + +pub fn writeMessagev(ws: *WebSocket, message: []const std.posix.iovec_const, opcode: Opcode) WriteError!void { + const total_len = l: { + var total_len: u64 = 0; + for (message) |iovec| total_len += iovec.len; + break :l total_len; + }; + + var header_buf: [2 + 8]u8 = undefined; + header_buf[0] = @bitCast(@as(Header0, .{ + .opcode = opcode, + .fin = true, + })); + const header = switch (total_len) { + 0...125 => blk: { + header_buf[1] = @bitCast(@as(Header1, .{ + .payload_len = @enumFromInt(total_len), + .mask = false, + })); + break :blk header_buf[0..2]; + }, + 126...0xffff => blk: { + header_buf[1] = @bitCast(@as(Header1, .{ + .payload_len = .len16, + .mask = false, + })); + std.mem.writeInt(u16, header_buf[2..4], @intCast(total_len), .big); + break :blk header_buf[0..4]; + }, + else => blk: { + header_buf[1] = @bitCast(@as(Header1, .{ + .payload_len = .len64, + .mask = false, + })); + std.mem.writeInt(u64, header_buf[2..10], total_len, .big); + break :blk header_buf[0..10]; + }, + }; + + const response = &ws.response; + try response.writeAll(header); + for (message) |iovec| + try response.writeAll(iovec.base[0..iovec.len]); + try response.flush(); +} From 22925636f7afc0f334f1d44257c007a1d2ccd63f Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 4 Aug 2024 15:26:18 -0700 Subject: [PATCH 181/266] std.debug.Coverage: use extern structs helps the serialization use case --- lib/std/debug/Coverage.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/std/debug/Coverage.zig b/lib/std/debug/Coverage.zig index d9cc7fdebd..f341efaffb 100644 --- a/lib/std/debug/Coverage.zig +++ b/lib/std/debug/Coverage.zig @@ -65,7 +65,7 @@ pub const String = enum(u32) { }; }; -pub const SourceLocation = struct { +pub const SourceLocation = extern struct { file: File.Index, line: u32, column: u32, @@ -77,7 +77,7 @@ pub const SourceLocation = struct { }; }; -pub const File = struct { +pub const File = extern struct { directory_index: u32, basename: String, From dec7e45f7c7e61a3778767bbc7f8e1e9a33b01fa Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 4 Aug 2024 15:27:13 -0700 Subject: [PATCH 182/266] fuzzer web UI: receive coverage information * libfuzzer: track unique runs instead of deduplicated runs - easier for consumers to notice when to recheck the covered bits. * move common definitions to `std.Build.Fuzz.abi`. build runner sends all the information needed to fuzzer web interface client needed in order to display inline coverage information along with source code. --- lib/fuzzer.zig | 15 +- lib/fuzzer/index.html | 1 + lib/fuzzer/main.js | 203 +++++++---- lib/fuzzer/wasm/main.zig | 83 ++++- lib/std/Build/Fuzz.zig | 563 +--------------------------- lib/std/Build/Fuzz/WebServer.zig | 605 +++++++++++++++++++++++++++++++ lib/std/Build/Fuzz/abi.zig | 55 +++ 7 files changed, 877 insertions(+), 648 deletions(-) create mode 100644 lib/std/Build/Fuzz/WebServer.zig create mode 100644 lib/std/Build/Fuzz/abi.zig diff --git a/lib/fuzzer.zig b/lib/fuzzer.zig index 0d968cd60d..a3446f9823 100644 --- a/lib/fuzzer.zig +++ b/lib/fuzzer.zig @@ -3,6 +3,7 @@ const std = @import("std"); const Allocator = std.mem.Allocator; const assert = std.debug.assert; const fatal = std.process.fatal; +const SeenPcsHeader = std.Build.Fuzz.abi.SeenPcsHeader; pub const std_options = .{ .logFn = logOverride, @@ -120,13 +121,6 @@ const Fuzzer = struct { /// information, available to other processes. coverage_id: u64, - const SeenPcsHeader = extern struct { - n_runs: usize, - deduplicated_runs: usize, - pcs_len: usize, - lowest_stack: usize, - }; - const RunMap = std.ArrayHashMapUnmanaged(Run, void, Run.HashContext, false); const Coverage = struct { @@ -247,7 +241,7 @@ const Fuzzer = struct { } else { const header: SeenPcsHeader = .{ .n_runs = 0, - .deduplicated_runs = 0, + .unique_runs = 0, .pcs_len = flagged_pcs.len, .lowest_stack = std.math.maxInt(usize), }; @@ -292,8 +286,6 @@ const Fuzzer = struct { }); if (gop.found_existing) { //std.log.info("duplicate analysis: score={d} id={d}", .{ analysis.score, analysis.id }); - const header: *volatile SeenPcsHeader = @ptrCast(f.seen_pcs.items[0..@sizeOf(SeenPcsHeader)]); - _ = @atomicRmw(usize, &header.deduplicated_runs, .Add, 1, .monotonic); if (f.input.items.len < gop.key_ptr.input.len or gop.key_ptr.score == 0) { gpa.free(gop.key_ptr.input); gop.key_ptr.input = try gpa.dupe(u8, f.input.items); @@ -325,6 +317,9 @@ const Fuzzer = struct { _ = @atomicRmw(u8, elem, .Or, mask, .monotonic); } } + + const header: *volatile SeenPcsHeader = @ptrCast(f.seen_pcs.items[0..@sizeOf(SeenPcsHeader)]); + _ = @atomicRmw(usize, &header.unique_runs, .Add, 1, .monotonic); } if (f.recent_cases.entries.len >= 100) { diff --git a/lib/fuzzer/index.html b/lib/fuzzer/index.html index dadc2f91d3..0753bcae67 100644 --- a/lib/fuzzer/index.html +++ b/lib/fuzzer/index.html @@ -124,6 +124,7 @@ +

Loading JavaScript...