zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

commit b64ce08c993f0871b2f9be7df561da69d9a9037e (tree)
parent b7a544c527f0d54cf9d044f26daf272e9297a786
Author: Alex Rønne Petersen <alex@alexrp.com>
Date:   Tue, 16 Jun 2026 21:24:05 +0200

Merge pull request '`std.debug.Dwarf`: fix unwinding when address size is smaller than register size' (#35807) from alexrp/zig:std-debug-ilp32 into master

Reviewed-on: https://codeberg.org/ziglang/zig/pulls/35807
Reviewed-by: mlugg <mlugg@noreply.codeberg.org>

Diffstat:
Mlib/std/debug/Dwarf/SelfUnwinder.zig | 21++++++++-------------
Mlib/std/debug/Dwarf/expression.zig | 4++--
Mlib/std/debug/SelfInfo/Elf.zig | 10----------
Mlib/std/debug/cpu_context.zig | 177++++++++++++++++++++++++++++++++++++++++++++++---------------------------------
4 files changed, 114 insertions(+), 98 deletions(-)

diff --git a/lib/std/debug/Dwarf/SelfUnwinder.zig b/lib/std/debug/Dwarf/SelfUnwinder.zig @@ -168,7 +168,7 @@ fn nextInner(unwinder: *SelfUnwinder, gpa: Allocator, cache_entry: *const CacheE .none => return error.InvalidDebugInfo, .reg_off => |ro| cfa: { const ptr = try regNative(&unwinder.cpu_state, ro.register); - break :cfa try applyOffset(ptr.*, ro.offset); + break :cfa try applyOffset(@intCast(ptr.*), ro.offset); }, .expression => |expr| cfa: { // On most implemented architectures, the CFA is defined to be the previous frame's SP. @@ -181,7 +181,7 @@ fn nextInner(unwinder: *SelfUnwinder, gpa: Allocator, cache_entry: *const CacheE const value = try unwinder.expr_vm.run(expr, gpa, .{ .format = format, .cpu_context = &unwinder.cpu_state, - }, prev_cfa_val) orelse return error.InvalidDebugInfo; + }, @intCast(prev_cfa_val)) orelse return error.InvalidDebugInfo; switch (value) { .generic => |g| break :cfa g, else => return error.InvalidDebugInfo, @@ -203,7 +203,7 @@ fn nextInner(unwinder: *SelfUnwinder, gpa: Allocator, cache_entry: *const CacheE const new_val: union(enum) { same, undefined, - val: usize, + val: std.debug.cpu_context.Native.Gpr, bytes: []const u8, } = switch (rule) { .default => val: { @@ -219,7 +219,7 @@ fn nextInner(unwinder: *SelfUnwinder, gpa: Allocator, cache_entry: *const CacheE .undefined => .undefined, .same_value => .same, .offset => |offset| val: { - const ptr: *const usize = @ptrFromInt(try applyOffset(cfa, offset)); + const ptr: *const std.debug.cpu_context.Native.Gpr = @ptrFromInt(try applyOffset(cfa, offset)); break :val .{ .val = ptr.* }; }, .val_offset => |offset| .{ .val = try applyOffset(cfa, offset) }, @@ -260,12 +260,7 @@ fn nextInner(unwinder: *SelfUnwinder, gpa: Allocator, cache_entry: *const CacheE has_return_address = false; } }, - .val => |val| { - const dest = try new_cpu_state.dwarfRegisterBytes(@intCast(register)); - if (dest.len != @sizeOf(usize)) return error.InvalidDebugInfo; - const dest_ptr: *align(1) usize = @ptrCast(dest); - dest_ptr.* = val; - }, + .val => |val| (try regNative(&new_cpu_state, register)).* = val, .bytes => |src| { const dest = try new_cpu_state.dwarfRegisterBytes(@intCast(register)); if (dest.len != src.len) return error.InvalidDebugInfo; @@ -275,7 +270,7 @@ fn nextInner(unwinder: *SelfUnwinder, gpa: Allocator, cache_entry: *const CacheE } const return_address = if (has_return_address) - stripInstructionPtrAuthCode((try regNative(&new_cpu_state, return_address_register)).*) + stripInstructionPtrAuthCode(@intCast((try regNative(&new_cpu_state, return_address_register)).*)) else 0; @@ -303,9 +298,9 @@ pub fn regNative(ctx: *std.debug.cpu_context.Native, num: u16) error{ InvalidRegister, UnsupportedRegister, IncompatibleRegisterSize, -}!*align(1) usize { +}!*align(1) std.debug.cpu_context.Native.Gpr { const bytes = try ctx.dwarfRegisterBytes(num); - if (bytes.len != @sizeOf(usize)) return error.IncompatibleRegisterSize; + if (bytes.len != @sizeOf(std.debug.cpu_context.Native.Gpr)) return error.IncompatibleRegisterSize; return @ptrCast(bytes); } diff --git a/lib/std/debug/Dwarf/expression.zig b/lib/std/debug/Dwarf/expression.zig @@ -387,7 +387,7 @@ pub fn StackMachine(comptime options: Options) type { .regval_type = .{ .type_offset = rt.type_offset, .type_size = @sizeOf(addr_type), - .value = (try regNative(cpu_context, rt.register)).*, + .value = @intCast((try regNative(cpu_context, rt.register)).*), }, }); }, @@ -738,7 +738,7 @@ pub fn StackMachine(comptime options: Options) type { var block_stream: std.Io.Reader = .fixed(block); const register = (try readOperand(&block_stream, block[0], context)).?.register; const value = (try regNative(cpu_context, register)).*; - try self.stack.append(allocator, .{ .generic = value }); + try self.stack.append(allocator, .{ .generic = @intCast(value) }); } else { var stack_machine: Self = .{}; defer stack_machine.deinit(allocator); diff --git a/lib/std/debug/SelfInfo/Elf.zig b/lib/std/debug/SelfInfo/Elf.zig @@ -92,16 +92,6 @@ pub fn getModuleSlide(si: *SelfInfo, io: Io, address: usize) Error!usize { } pub const can_unwind: bool = s: { - // The DWARF code can't deal with ILP32 ABIs yet: https://github.com/ziglang/zig/issues/25447 - switch (builtin.target.abi) { - .gnuabin32, - .muslabin32, - .gnux32, - .muslx32, - => break :s false, - else => {}, - } - // Notably, we are yet to support unwinding on ARM. There, unwinding is not done through // `.eh_frame`, but instead with the `.ARM.exidx` section, which has a different format. const archs: []const std.Target.Cpu.Arch = switch (builtin.target.os.tag) { diff --git a/lib/std/debug/cpu_context.zig b/lib/std/debug/cpu_context.zig @@ -240,9 +240,11 @@ pub fn fromWindowsContext(ctx: *const std.os.windows.CONTEXT) Native { /// This is an `extern struct` so that inline assembly in `current` can use field offsets. const Aarch64 = extern struct { /// The numbered general-purpose registers X0 - X30. - x: [31]u64, - sp: u64, - pc: u64, + x: [31]Gpr, + sp: Gpr, + pc: Gpr, + + pub const Gpr = u64; pub inline fn current() Aarch64 { var ctx: Aarch64 = undefined; @@ -273,10 +275,10 @@ const Aarch64 = extern struct { return ctx; } - pub fn getFp(ctx: *const Aarch64) u64 { + pub fn getFp(ctx: *const Aarch64) usize { return ctx.x[29]; } - pub fn getPc(ctx: *const Aarch64) u64 { + pub fn getPc(ctx: *const Aarch64) usize { return ctx.pc; } @@ -308,8 +310,10 @@ const Aarch64 = extern struct { const Alpha = extern struct { /// The numbered general-purpose registers R0 - R31. - r: [32]u64, - pc: u64, + r: [32]Gpr, + pc: Gpr, + + pub const Gpr = u64; pub inline fn current() Alpha { var ctx: Alpha = undefined; @@ -355,10 +359,10 @@ const Alpha = extern struct { return ctx; } - pub fn getFp(ctx: *const Alpha) u64 { + pub fn getFp(ctx: *const Alpha) usize { return ctx.r[15]; } - pub fn getPc(ctx: *const Alpha) u64 { + pub fn getPc(ctx: *const Alpha) usize { return ctx.pc; } @@ -378,8 +382,10 @@ const Alpha = extern struct { /// This is an `extern struct` so that inline assembly in `current` can use field offsets. const Arc = extern struct { /// The numbered general-purpose registers r0 - r31. - r: [32]u32, - pcl: u32, + r: [32]Gpr, + pcl: Gpr, + + pub const Gpr = u32; pub inline fn current() Arc { var ctx: Arc = undefined; @@ -423,10 +429,10 @@ const Arc = extern struct { return ctx; } - pub fn getFp(ctx: *const Arc) u32 { + pub fn getFp(ctx: *const Arc) usize { return ctx.r[27]; } - pub fn getPc(ctx: *const Arc) u32 { + pub fn getPc(ctx: *const Arc) usize { return ctx.pcl; } @@ -446,7 +452,9 @@ const Arc = extern struct { const Arm = struct { /// The numbered general-purpose registers R0 - R15. - r: [16]u32, + r: [16]Gpr, + + pub const Gpr = u32; pub inline fn current() Arm { var ctx: Arm = undefined; @@ -462,10 +470,10 @@ const Arm = struct { return ctx; } - pub fn getFp(ctx: *const Arm) u32 { + pub fn getFp(ctx: *const Arm) usize { return ctx.r[11]; } - pub fn getPc(ctx: *const Arm) u32 { + pub fn getPc(ctx: *const Arm) usize { return ctx.r[15]; } @@ -512,8 +520,10 @@ const Arm = struct { /// This is an `extern struct` so that inline assembly in `current` can use field offsets. const Csky = extern struct { /// The numbered general-purpose registers r0 - r31. - r: [32]u32, - pc: u32, + r: [32]Gpr, + pc: Gpr, + + pub const Gpr = u32; pub inline fn current() Csky { var ctx: Csky = undefined; @@ -528,10 +538,10 @@ const Csky = extern struct { return ctx; } - pub fn getFp(ctx: *const Csky) u32 { + pub fn getFp(ctx: *const Csky) usize { return ctx.r[14]; } - pub fn getPc(ctx: *const Csky) u32 { + pub fn getPc(ctx: *const Csky) usize { return ctx.pc; } @@ -550,8 +560,10 @@ const Csky = extern struct { /// This is an `extern struct` so that inline assembly in `current` can use field offsets. const Hexagon = extern struct { /// The numbered general-purpose registers r0 - r31. - r: [32]u32, - pc: u32, + r: [32]Gpr, + pc: Gpr, + + pub const Gpr = u32; pub inline fn current() Hexagon { var ctx: Hexagon = undefined; @@ -596,10 +608,10 @@ const Hexagon = extern struct { return ctx; } - pub fn getFp(ctx: *const Hexagon) u32 { + pub fn getFp(ctx: *const Hexagon) usize { return ctx.r[30]; } - pub fn getPc(ctx: *const Hexagon) u32 { + pub fn getPc(ctx: *const Hexagon) usize { return ctx.pc; } @@ -623,9 +635,11 @@ const Hexagon = extern struct { /// This is an `extern struct` so that inline assembly in `current` can use field offsets. const Kvx = extern struct { - r: [64]u64, - ra: u64, - pc: u64, + r: [64]Gpr, + ra: Gpr, + pc: Gpr, + + pub const Gpr = u64; pub inline fn current() Kvx { var ctx: Kvx = undefined; @@ -671,10 +685,10 @@ const Kvx = extern struct { return ctx; } - pub fn getFp(ctx: *const Kvx) u64 { + pub fn getFp(ctx: *const Kvx) usize { return ctx.r[14]; } - pub fn getPc(ctx: *const Kvx) u64 { + pub fn getPc(ctx: *const Kvx) usize { return ctx.pc; } @@ -695,7 +709,9 @@ const Kvx = extern struct { /// This is an `extern struct` so that inline assembly in `current` can use field offsets. const Lanai = extern struct { - r: [32]u32, + r: [32]Gpr, + + pub const Gpr = u32; pub inline fn current() Lanai { var ctx: Lanai = undefined; @@ -738,10 +754,10 @@ const Lanai = extern struct { return ctx; } - pub fn getFp(ctx: *const Lanai) u32 { + pub fn getFp(ctx: *const Lanai) usize { return ctx.r[5]; } - pub fn getPc(ctx: *const Lanai) u32 { + pub fn getPc(ctx: *const Lanai) usize { return ctx.r[2]; } @@ -842,10 +858,10 @@ const LoongArch = extern struct { return ctx; } - pub fn getFp(ctx: *const LoongArch) Gpr { + pub fn getFp(ctx: *const LoongArch) usize { return ctx.r[22]; } - pub fn getPc(ctx: *const LoongArch) Gpr { + pub fn getPc(ctx: *const LoongArch) usize { return ctx.pc; } @@ -864,10 +880,12 @@ const LoongArch = extern struct { /// This is an `extern struct` so that inline assembly in `current` can use field offsets. const M68k = extern struct { /// The numbered data registers d0 - d7. - d: [8]u32, + d: [8]Gpr, /// The numbered address registers a0 - a7. - a: [8]u32, - pc: u32, + a: [8]Gpr, + pc: Gpr, + + pub const Gpr = u32; pub inline fn current() M68k { var ctx: M68k = undefined; @@ -881,10 +899,10 @@ const M68k = extern struct { return ctx; } - pub fn getFp(ctx: *const M68k) u32 { + pub fn getFp(ctx: *const M68k) usize { return ctx.a[6]; } - pub fn getPc(ctx: *const M68k) u32 { + pub fn getPc(ctx: *const M68k) usize { return ctx.pc; } @@ -905,8 +923,10 @@ const M68k = extern struct { /// This is an `extern struct` so that inline assembly in `current` can use field offsets. const M88k = extern struct { /// The numbered general-purpose registers r0 - r31. - r: [32]u32, - xip: u32, + r: [32]Gpr, + xip: Gpr, + + pub const Gpr = u32; pub inline fn current() M88k { var ctx: M88k = undefined; @@ -952,10 +972,10 @@ const M88k = extern struct { return ctx; } - pub fn getFp(ctx: *const M88k) u32 { + pub fn getFp(ctx: *const M88k) usize { return ctx.r[30]; } - pub fn getPc(ctx: *const M88k) u32 { + pub fn getPc(ctx: *const M88k) usize { return ctx.xip; } @@ -1103,8 +1123,10 @@ const Mips = extern struct { /// This is an `extern struct` so that inline assembly in `current` can use field offsets. const Or1k = extern struct { /// The numbered general-purpose registers r0 - r31. - r: [32]u32, - pc: u32, + r: [32]Gpr, + pc: Gpr, + + pub const Gpr = u32; pub inline fn current() Or1k { var ctx: Or1k = undefined; @@ -1150,10 +1172,10 @@ const Or1k = extern struct { return ctx; } - pub fn getFp(ctx: *const Or1k) u32 { + pub fn getFp(ctx: *const Or1k) usize { return ctx.r[2]; } - pub fn getPc(ctx: *const Or1k) u32 { + pub fn getPc(ctx: *const Or1k) usize { return ctx.pc; } @@ -1262,10 +1284,10 @@ const Powerpc = extern struct { return ctx; } - pub fn getFp(ctx: *const Powerpc) Gpr { + pub fn getFp(ctx: *const Powerpc) usize { return ctx.r[1]; } - pub fn getPc(ctx: *const Powerpc) Gpr { + pub fn getPc(ctx: *const Powerpc) usize { return ctx.pc; } @@ -1415,10 +1437,10 @@ const Riscv = extern struct { return ctx; } - pub fn getFp(ctx: *const Riscv) Gpr { + pub fn getFp(ctx: *const Riscv) usize { return ctx.x[8]; } - pub fn getPc(ctx: *const Riscv) Gpr { + pub fn getPc(ctx: *const Riscv) usize { return ctx.pc; } @@ -1441,13 +1463,15 @@ const Riscv = extern struct { /// This is an `extern struct` so that inline assembly in `current` can use field offsets. const S390x = extern struct { /// The numbered general-purpose registers r0 - r15. - r: [16]u64, + r: [16]Gpr, /// The program counter. psw: extern struct { - mask: u64, - addr: u64, + mask: Gpr, + addr: Gpr, }, + pub const Gpr = u64; + pub inline fn current() S390x { var ctx: S390x = undefined; asm volatile ( @@ -1462,10 +1486,10 @@ const S390x = extern struct { return ctx; } - pub fn getFp(ctx: *const S390x) u64 { + pub fn getFp(ctx: *const S390x) usize { return ctx.r[11]; } - pub fn getPc(ctx: *const S390x) u64 { + pub fn getPc(ctx: *const S390x) usize { return ctx.psw.addr; } @@ -1571,10 +1595,10 @@ const Sparc = extern struct { asm volatile ("ta 3" ::: .{ .memory = true }); // ST_FLUSH_WINDOWS } - pub fn getFp(ctx: *const Sparc) Gpr { + pub fn getFp(ctx: *const Sparc) usize { return ctx.i[6]; } - pub fn getPc(ctx: *const Sparc) Gpr { + pub fn getPc(ctx: *const Sparc) usize { return ctx.pc; } @@ -1593,8 +1617,10 @@ const Sparc = extern struct { /// This is an `extern struct` so that inline assembly in `current` can use field offsets. const Ve = extern struct { - s: [64]u64, - ic: u64, + s: [64]Gpr, + ic: Gpr, + + pub const Gpr = u64; pub inline fn current() Ve { var ctx: Ve = undefined; @@ -1672,10 +1698,10 @@ const Ve = extern struct { return ctx; } - pub fn getFp(ctx: *const Ve) u64 { + pub fn getFp(ctx: *const Ve) usize { return ctx.s[9]; } - pub fn getPc(ctx: *const Ve) u64 { + pub fn getPc(ctx: *const Ve) usize { return ctx.ic; } @@ -1693,14 +1719,15 @@ const Ve = extern struct { }; const X86_16 = struct { - pub const Register = enum { + regs: std.enums.EnumArray(GprName, Gpr), + + pub const GprName = enum { // zig fmt: off sp, bp, ss, ip, cs, // zig fmt: on }; - - regs: std.enums.EnumArray(Register, u16), + pub const Gpr = u16; pub inline fn current() X86_16 { var ctx: X86_16 = undefined; @@ -1719,10 +1746,10 @@ const X86_16 = struct { return ctx; } - pub fn getFp(ctx: *const X86_16) u16 { + pub fn getFp(ctx: *const X86_16) usize { return ctx.regs.get(.bp); } - pub fn getPc(ctx: *const X86_16) u16 { + pub fn getPc(ctx: *const X86_16) usize { return ctx.regs.get(.ip); } @@ -1740,17 +1767,19 @@ const X86_16 = struct { }; const X86 = struct { + gprs: std.enums.EnumArray(GprName, Gpr), + /// The first 8 registers here intentionally match the order of registers in the x86 instruction /// encoding. This order is inherited by the PUSHA instruction and the DWARF register mappings, /// among other things. - pub const Gpr = enum { + pub const GprName = enum { // zig fmt: off eax, ecx, edx, ebx, esp, ebp, esi, edi, eip, // zig fmt: on }; - gprs: std.enums.EnumArray(Gpr, u32), + pub const Gpr = u32; pub inline fn current() X86 { var ctx: X86 = undefined; @@ -1772,10 +1801,10 @@ const X86 = struct { return ctx; } - pub fn getFp(ctx: *const X86) u32 { + pub fn getFp(ctx: *const X86) usize { return ctx.gprs.get(.ebp); } - pub fn getPc(ctx: *const X86) u32 { + pub fn getPc(ctx: *const X86) usize { return ctx.gprs.get(.eip); } @@ -1806,10 +1835,12 @@ const X86 = struct { }; const X86_64 = struct { + gprs: std.enums.EnumArray(GprName, Gpr), + /// The order here intentionally matches the order of the DWARF register mappings. It's unclear /// where those mappings actually originated from---the ordering of the first 4 registers seems /// quite unusual---but it is currently convenient for us to match DWARF. - pub const Gpr = enum { + pub const GprName = enum { // zig fmt: off rax, rdx, rcx, rbx, rsi, rdi, rbp, rsp, @@ -1818,7 +1849,7 @@ const X86_64 = struct { rip, // zig fmt: on }; - gprs: std.enums.EnumArray(Gpr, u64), + pub const Gpr = u64; pub inline fn current() X86_64 { var ctx: X86_64 = undefined;