From c23a5ccd19f08d485820a1d5deea04a6e10fe7bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Tue, 7 Oct 2025 09:28:43 +0200 Subject: [PATCH 1/2] std.debug.Dwarf.SelfUnwinder: skip caching rules for unsupported registers For unwinding purposes, we don't care about unsupported registers. Yet because we added these rules to the cache entry, we'd later try to evaluate them and thus fail the unwind attempt for no good reason. They'd also take up cache rule slots that would be better spent on actually relevant registers. Note that any attempt to read unsupported registers during unwinding will still fail the unwind attempt as expected. --- lib/std/debug/Dwarf/SelfUnwinder.zig | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/lib/std/debug/Dwarf/SelfUnwinder.zig b/lib/std/debug/Dwarf/SelfUnwinder.zig index 3a7b70b037..24afdaba1d 100644 --- a/lib/std/debug/Dwarf/SelfUnwinder.zig +++ b/lib/std/debug/Dwarf/SelfUnwinder.zig @@ -15,14 +15,14 @@ cfi_vm: Dwarf.Unwind.VirtualMachine, expr_vm: Dwarf.expression.StackMachine(.{ .call_frame_context = true }), pub const CacheEntry = struct { - const max_regs = 32; + const max_rules = 32; pc: usize, cie: *const Dwarf.Unwind.CommonInformationEntry, cfa_rule: Dwarf.Unwind.VirtualMachine.CfaRule, num_rules: u8, - rules_regs: [max_regs]u16, - rules: [max_regs]Dwarf.Unwind.VirtualMachine.RegisterRule, + rules_regs: [max_rules]u16, + rules: [max_rules]Dwarf.Unwind.VirtualMachine.RegisterRule, pub fn find(entries: []const CacheEntry, pc: usize) ?*const CacheEntry { assert(pc != 0); @@ -108,22 +108,30 @@ pub fn computeRules( unwinder.cfi_vm.reset(); const row = try unwinder.cfi_vm.runTo(gpa, pc_vaddr, cie, &fde, @sizeOf(usize), native_endian); - const cols = unwinder.cfi_vm.rowColumns(&row); - - if (cols.len > CacheEntry.max_regs) return error.UnsupportedDebugInfo; var entry: CacheEntry = .{ .pc = unwinder.pc, .cie = cie, .cfa_rule = row.cfa, - .num_rules = @intCast(cols.len), + .num_rules = undefined, .rules_regs = undefined, .rules = undefined, }; - for (cols, 0..) |col, i| { + var i: usize = 0; + for (unwinder.cfi_vm.rowColumns(&row)) |col| { + if (i == CacheEntry.max_rules) return error.UnsupportedDebugInfo; + + _ = unwinder.cpu_state.dwarfRegisterBytes(col.register) catch |err| switch (err) { + // Reading an unsupported register during unwinding will result in an error, so there is + // no point wasting a rule slot in the cache entry for it. + error.UnsupportedRegister => continue, + error.InvalidRegister => return error.InvalidDebugInfo, + }; entry.rules_regs[i] = col.register; entry.rules[i] = col.rule; + i += 1; } + entry.num_rules = @intCast(i); return entry; } From a06db282c75dba5fa0a2859088840a24d04c7c7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Tue, 7 Oct 2025 09:33:42 +0200 Subject: [PATCH 2/2] std.debug.SelfInfo.MachO: don't restore vector registers during unwinding We know that these are unsupported and irrelevant for unwinding, so don't fail the unwind attempt trying to read/write them for no ultimate purpose. --- lib/std/debug/SelfInfo/MachO.zig | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/lib/std/debug/SelfInfo/MachO.zig b/lib/std/debug/SelfInfo/MachO.zig index eb4d0854d8..a89a2f0fb5 100644 --- a/lib/std/debug/SelfInfo/MachO.zig +++ b/lib/std/debug/SelfInfo/MachO.zig @@ -401,21 +401,11 @@ fn unwindFrameInner(si: *SelfInfo, gpa: Allocator, context: *UnwindContext) !usi } } - inline for (@typeInfo(@TypeOf(frame.d_reg_pairs)).@"struct".fields, 0..) |field, i| { - if (@field(frame.d_reg_pairs, field.name) != 0) { - // Only the lower half of the 128-bit V registers are restored during unwinding - { - const dest: *align(1) usize = @ptrCast(try context.cpu_state.dwarfRegisterBytes(64 + 8 + i)); - dest.* = @as(*const usize, @ptrFromInt(reg_addr)).*; - } - reg_addr += @sizeOf(usize); - { - const dest: *align(1) usize = @ptrCast(try context.cpu_state.dwarfRegisterBytes(64 + 9 + i)); - dest.* = @as(*const usize, @ptrFromInt(reg_addr)).*; - } - reg_addr += @sizeOf(usize); - } - } + // We intentionally skip restoring `frame.d_reg_pairs`; we know we don't support + // vector registers in the AArch64 `cpu_context` anyway, so there's no reason to + // fail a legitimate unwind just because we're asked to restore the registers here. + // If some weird/broken unwind info tells us to read them later, we will fail then. + reg_addr += 16 * @as(usize, @popCount(@as(u4, @bitCast(frame.d_reg_pairs)))); const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*; const new_fp = @as(*const usize, @ptrFromInt(fp)).*;