Commit 9145ff7da0

kcbanner <kcbanner@gmail.com>
2023-05-22 20:45:24
dwarf: implement more register number mappings
- add dwarf.abi.RegisterContext to handle register numbers changing based on DWARF format
1 parent b449d98
Changed files (4)
lib/std/dwarf/abi.zig
@@ -3,56 +3,87 @@ const std = @import("../std.zig");
 const os = std.os;
 const mem = std.mem;
 
-/// Maps register names to their DWARF register number.
-/// `bp`, `ip`, and `sp` are provided as aliases.
-pub const Register = switch (builtin.cpu.arch) {
-    .x86 => {
-
-        //pub const ip = Register.eip;
-        //pub const sp = Register.
-    },
-   .x86_64 => enum(u8) {
-        rax,
-        rdx,
-        rcx,
-        rbx,
-        rsi,
-        rdi,
-        rbp,
-        rsp,
-        r8,
-        r9,
-        r10,
-        r11,
-        r12,
-        r13,
-        r14,
-        r15,
-        rip,
-        xmm0,
-        xmm1,
-        xmm2,
-        xmm3,
-        xmm4,
-        xmm5,
-        xmm6,
-        xmm7,
-        xmm8,
-        xmm9,
-        xmm10,
-        xmm11,
-        xmm12,
-        xmm13,
-        xmm14,
-        xmm15,
-
-       pub const fp = Register.rbp;
-       pub const ip = Register.rip;
-       pub const sp = Register.rsp;
-    },
-    else => enum {},
+pub const RegisterContext = struct {
+    eh_frame: bool,
+    is_macho: bool,
 };
 
+pub fn ipRegNum() u8 {
+    return switch (builtin.cpu.arch) {
+        .x86 => 8,
+        .x86_64 => 16,
+        .arm => error.InvalidRegister, // TODO
+        .aarch64 => error.InvalidRegister, // TODO
+
+        // const ctx = @ptrCast(*const os.ucontext_t, @alignCast(@alignOf(os.ucontext_t), ctx_ptr));
+        // const ip = switch (native_os) {
+        //     .macos => @intCast(usize, ctx.mcontext.ss.pc),
+        //     .netbsd => @intCast(usize, ctx.mcontext.gregs[os.REG.PC]),
+        //     .freebsd => @intCast(usize, ctx.mcontext.gpregs.elr),
+        //     else => @intCast(usize, ctx.mcontext.pc),
+        // };
+        // // x29 is the ABI-designated frame pointer
+        // const bp = switch (native_os) {
+        //     .macos => @intCast(usize, ctx.mcontext.ss.fp),
+        //     .netbsd => @intCast(usize, ctx.mcontext.gregs[os.REG.FP]),
+        //     .freebsd => @intCast(usize, ctx.mcontext.gpregs.x[os.REG.FP]),
+        //     else => @intCast(usize, ctx.mcontext.regs[29]),
+        // };
+        else => unreachable,
+    };
+}
+
+pub fn fpRegNum(reg_ctx: RegisterContext) u8 {
+    return switch (builtin.cpu.arch) {
+        // GCC on OS X did the opposite of ELF for these registers (only in .eh_frame), and that is now the convention for MachO
+        .x86 => if (reg_ctx.eh_frame and reg_ctx.is_macho) 4 else 5,
+        .x86_64 => 6,
+        .arm => error.InvalidRegister, // TODO
+        .aarch64 => error.InvalidRegister, // TODO
+
+        // const ctx = @ptrCast(*const os.ucontext_t, @alignCast(@alignOf(os.ucontext_t), ctx_ptr));
+        // const ip = switch (native_os) {
+        //     .macos => @intCast(usize, ctx.mcontext.ss.pc),
+        //     .netbsd => @intCast(usize, ctx.mcontext.gregs[os.REG.PC]),
+        //     .freebsd => @intCast(usize, ctx.mcontext.gpregs.elr),
+        //     else => @intCast(usize, ctx.mcontext.pc),
+        // };
+        // // x29 is the ABI-designated frame pointer
+        // const bp = switch (native_os) {
+        //     .macos => @intCast(usize, ctx.mcontext.ss.fp),
+        //     .netbsd => @intCast(usize, ctx.mcontext.gregs[os.REG.FP]),
+        //     .freebsd => @intCast(usize, ctx.mcontext.gpregs.x[os.REG.FP]),
+        //     else => @intCast(usize, ctx.mcontext.regs[29]),
+        // };
+        else => unreachable,
+    };
+}
+
+pub fn spRegNum(reg_ctx: RegisterContext) u8 {
+    return switch (builtin.cpu.arch) {
+        .x86 => if (reg_ctx.eh_frame and reg_ctx.is_macho) 5 else 4,
+        .x86_64 => 7,
+        .arm => error.InvalidRegister, // TODO
+        .aarch64 => error.InvalidRegister, // TODO
+
+        // const ctx = @ptrCast(*const os.ucontext_t, @alignCast(@alignOf(os.ucontext_t), ctx_ptr));
+        // const ip = switch (native_os) {
+        //     .macos => @intCast(usize, ctx.mcontext.ss.pc),
+        //     .netbsd => @intCast(usize, ctx.mcontext.gregs[os.REG.PC]),
+        //     .freebsd => @intCast(usize, ctx.mcontext.gpregs.elr),
+        //     else => @intCast(usize, ctx.mcontext.pc),
+        // };
+        // // x29 is the ABI-designated frame pointer
+        // const bp = switch (native_os) {
+        //     .macos => @intCast(usize, ctx.mcontext.ss.fp),
+        //     .netbsd => @intCast(usize, ctx.mcontext.gregs[os.REG.FP]),
+        //     .freebsd => @intCast(usize, ctx.mcontext.gpregs.x[os.REG.FP]),
+        //     else => @intCast(usize, ctx.mcontext.regs[29]),
+        // };
+        else => unreachable,
+    };
+}
+
 fn RegBytesReturnType(comptime ContextPtrType: type) type {
     const info = @typeInfo(ContextPtrType);
     if (info != .Pointer or info.Pointer.child != os.ucontext_t) {
@@ -62,36 +93,132 @@ fn RegBytesReturnType(comptime ContextPtrType: type) type {
     return if (info.Pointer.is_const) return []const u8 else []u8;
 }
 
-/// Returns a slice containing the backing storage for `reg_number`
-pub fn regBytes(ucontext_ptr: anytype, reg_number: u8) !RegBytesReturnType(@TypeOf(ucontext_ptr)) {
+/// Returns a slice containing the backing storage for `reg_number`.
+///
+/// `reg_ctx` 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(ucontext_ptr: anytype, reg_number: u8, reg_ctx: ?RegisterContext) !RegBytesReturnType(@TypeOf(ucontext_ptr)) {
     var m = &ucontext_ptr.mcontext;
 
     return switch (builtin.cpu.arch) {
+        .x86 => switch (reg_number) {
+            0 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.EAX]),
+            1 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.ECX]),
+            2 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.EDX]),
+            3 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.EBX]),
+            4...5 => if (reg_ctx) |r| bytes: {
+                if (reg_number == 4) {
+                    break :bytes if (r.eh_frame and r.is_macho)
+                        mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.EBP])
+                    else
+                        mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.ESP]);
+                } else {
+                    break :bytes if (r.eh_frame and r.is_macho)
+                        mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.ESP])
+                    else
+                        mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.EBP]);
+                }
+            } else error.RegisterContextRequired,
+            6 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.ESI]),
+            7 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.EDI]),
+            8 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.EIP]),
+            9 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.EFL]),
+            10 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.CS]),
+            11 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.SS]),
+            12 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.DS]),
+            13 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.ES]),
+            14 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.FS]),
+            15 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.GS]),
+            16...23 => error.InvalidRegister, // TODO: Support loading ST0-ST7 from mcontext.fpregs
+            // TODO: Map TRAPNO, ERR, UESP
+            32...39 => error.InvalidRegister, // TODO: Support loading XMM0-XMM7 from mcontext.fpregs
+            else => error.InvalidRegister,
+        },
         .x86_64 => switch (builtin.os.tag) {
             .linux, .netbsd, .solaris => switch (reg_number) {
-                0 => mem.asBytes(&m.gregs[os.REG.RAX]),
-                1 => mem.asBytes(&m.gregs[os.REG.RDX]),
-                2 => mem.asBytes(&m.gregs[os.REG.RCX]),
-                3 => mem.asBytes(&m.gregs[os.REG.RBX]),
-                4 => mem.asBytes(&m.gregs[os.REG.RSI]),
-                5 => mem.asBytes(&m.gregs[os.REG.RDI]),
-                6 => mem.asBytes(&m.gregs[os.REG.RBP]),
-                7 => mem.asBytes(&m.gregs[os.REG.RSP]),
-                8 => mem.asBytes(&m.gregs[os.REG.R8]),
-                9 => mem.asBytes(&m.gregs[os.REG.R9]),
-                10 => mem.asBytes(&m.gregs[os.REG.R10]),
-                11 => mem.asBytes(&m.gregs[os.REG.R11]),
-                12 => mem.asBytes(&m.gregs[os.REG.R12]),
-                13 => mem.asBytes(&m.gregs[os.REG.R13]),
-                14 => mem.asBytes(&m.gregs[os.REG.R14]),
-                15 => mem.asBytes(&m.gregs[os.REG.R15]),
-                16 => mem.asBytes(&m.gregs[os.REG.RIP]),
+                0 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.RAX]),
+                1 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.RDX]),
+                2 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.RCX]),
+                3 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.RBX]),
+                4 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.RSI]),
+                5 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.RDI]),
+                6 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.RBP]),
+                7 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.RSP]),
+                8 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.R8]),
+                9 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.R9]),
+                10 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.R10]),
+                11 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.R11]),
+                12 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.R12]),
+                13 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.R13]),
+                14 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.R14]),
+                15 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.R15]),
+                16 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.RIP]),
                 17...32 => |i| mem.asBytes(&m.fpregs.xmm[i - 17]),
                 else => error.InvalidRegister,
             },
-            //.freebsd => @intCast(usize, ctx.mcontext.rip),
-            //.openbsd => @intCast(usize, ctx.sc_rip),
-            //.macos => @intCast(usize, ctx.mcontext.ss.rip),
+            .freebsd => switch (reg_number) {
+                0 => mem.asBytes(&ucontext_ptr.mcontext.rax),
+                1 => mem.asBytes(&ucontext_ptr.mcontext.rdx),
+                2 => mem.asBytes(&ucontext_ptr.mcontext.rcx),
+                3 => mem.asBytes(&ucontext_ptr.mcontext.rbx),
+                4 => mem.asBytes(&ucontext_ptr.mcontext.rsi),
+                5 => mem.asBytes(&ucontext_ptr.mcontext.rdi),
+                6 => mem.asBytes(&ucontext_ptr.mcontext.rbp),
+                7 => mem.asBytes(&ucontext_ptr.mcontext.rsp),
+                8 => mem.asBytes(&ucontext_ptr.mcontext.r8),
+                9 => mem.asBytes(&ucontext_ptr.mcontext.r9),
+                10 => mem.asBytes(&ucontext_ptr.mcontext.r10),
+                11 => mem.asBytes(&ucontext_ptr.mcontext.r11),
+                12 => mem.asBytes(&ucontext_ptr.mcontext.r12),
+                13 => mem.asBytes(&ucontext_ptr.mcontext.r13),
+                14 => mem.asBytes(&ucontext_ptr.mcontext.r14),
+                15 => mem.asBytes(&ucontext_ptr.mcontext.r15),
+                16 => mem.asBytes(&ucontext_ptr.mcontext.rip),
+                // TODO: Extract xmm state from mcontext.fpstate?
+                else => error.InvalidRegister,
+            },
+            .openbsd => switch (reg_number) {
+                0 => mem.asBytes(&ucontext_ptr.sc_rax),
+                1 => mem.asBytes(&ucontext_ptr.sc_rdx),
+                2 => mem.asBytes(&ucontext_ptr.sc_rcx),
+                3 => mem.asBytes(&ucontext_ptr.sc_rbx),
+                4 => mem.asBytes(&ucontext_ptr.sc_rsi),
+                5 => mem.asBytes(&ucontext_ptr.sc_rdi),
+                6 => mem.asBytes(&ucontext_ptr.sc_rbp),
+                7 => mem.asBytes(&ucontext_ptr.sc_rsp),
+                8 => mem.asBytes(&ucontext_ptr.sc_r8),
+                9 => mem.asBytes(&ucontext_ptr.sc_r9),
+                10 => mem.asBytes(&ucontext_ptr.sc_r10),
+                11 => mem.asBytes(&ucontext_ptr.sc_r11),
+                12 => mem.asBytes(&ucontext_ptr.sc_r12),
+                13 => mem.asBytes(&ucontext_ptr.sc_r13),
+                14 => mem.asBytes(&ucontext_ptr.sc_r14),
+                15 => mem.asBytes(&ucontext_ptr.sc_r15),
+                16 => mem.asBytes(&ucontext_ptr.sc_rip),
+                // TODO: Extract xmm state from sc_fpstate?
+                else => error.InvalidRegister,
+            },
+            .macos => switch (reg_number) {
+                0 => mem.asBytes(&ucontext_ptr.mcontext.ss.rax),
+                1 => mem.asBytes(&ucontext_ptr.mcontext.ss.rdx),
+                2 => mem.asBytes(&ucontext_ptr.mcontext.ss.rcx),
+                3 => mem.asBytes(&ucontext_ptr.mcontext.ss.rbx),
+                4 => mem.asBytes(&ucontext_ptr.mcontext.ss.rsi),
+                5 => mem.asBytes(&ucontext_ptr.mcontext.ss.rdi),
+                6 => mem.asBytes(&ucontext_ptr.mcontext.ss.rbp),
+                7 => mem.asBytes(&ucontext_ptr.mcontext.ss.rsp),
+                8 => mem.asBytes(&ucontext_ptr.mcontext.ss.r8),
+                9 => mem.asBytes(&ucontext_ptr.mcontext.ss.r9),
+                10 => mem.asBytes(&ucontext_ptr.mcontext.ss.r10),
+                11 => mem.asBytes(&ucontext_ptr.mcontext.ss.r11),
+                12 => mem.asBytes(&ucontext_ptr.mcontext.ss.r12),
+                13 => mem.asBytes(&ucontext_ptr.mcontext.ss.r13),
+                14 => mem.asBytes(&ucontext_ptr.mcontext.ss.r14),
+                15 => mem.asBytes(&ucontext_ptr.mcontext.ss.r15),
+                16 => mem.asBytes(&ucontext_ptr.mcontext.ss.rip),
+                else => error.InvalidRegister,
+            },
             else => error.UnimplementedOs,
         },
         else => error.UnimplementedArch,
lib/std/dwarf/call_frame.zig
@@ -304,9 +304,9 @@ pub const VirtualMachine = struct {
                     } else return error.InvalidCFA;
                 },
                 .register => |register| {
-                    const src = try abi.regBytes(&context.ucontext, register);
+                    const src = try abi.regBytes(&context.ucontext, register, context.reg_ctx);
                     if (src.len != out.len) return error.RegisterTypeMismatch;
-                    @memcpy(out, try abi.regBytes(&context.ucontext, register));
+                    @memcpy(out, try abi.regBytes(&context.ucontext, register, context.reg_ctx));
                 },
                 .expression => |expression| {
                     // TODO
lib/std/debug.zig
@@ -521,6 +521,8 @@ pub const StackIterator = struct {
     fn next_dwarf(self: *StackIterator) !void {
         const module = try self.debug_info.?.getModuleForAddress(self.dwarf_context.pc);
         if (module.getDwarfInfo()) |di| {
+            self.dwarf_context.reg_ctx.eh_frame = true;
+            self.dwarf_context.reg_ctx.is_macho = di.is_macho;
             try di.unwindFrame(self.debug_info.?.allocator, &self.dwarf_context, module.base_address);
         } else return error.MissingDebugInfo;
     }
@@ -532,6 +534,10 @@ pub const StackIterator = struct {
             } else |err| {
                 // Fall back to fp unwinding on the first failure,
                 // as the register context won't be updated
+
+                // TODO: Could still attempt dwarf unwinding after this, maybe marking non-updated registers as
+                //       invalid, so the unwind only fails if it requires out of date registers?
+
                 self.fp = self.dwarf_context.getFp() catch 0;
                 self.debug_info = null;
 
@@ -854,6 +860,7 @@ fn readCoffDebugInfo(allocator: mem.Allocator, coff_bytes: []const u8) !ModuleDe
             var dwarf = DW.DwarfInfo{
                 .endian = native_endian,
                 .sections = sections,
+                .is_macho = false,
             };
 
             try DW.openDwarfDebugInfo(&dwarf, allocator, coff_bytes);
@@ -1079,6 +1086,7 @@ pub fn readElfDebugInfo(
         var di = DW.DwarfInfo{
             .endian = endian,
             .sections = sections,
+            .is_macho = false,
         };
 
         try DW.openDwarfDebugInfo(&di, allocator, parent_mapped_mem orelse mapped_mem);
@@ -1682,6 +1690,10 @@ pub const ModuleDebugInfo = switch (native_os) {
 
             var di = DW.DwarfInfo{
                 .endian = .Little,
+                .is_macho = true,
+
+                // TODO: Get this compiling
+
                 .debug_info = try chopSlice(mapped_mem, debug_info.offset, debug_info.size),
                 .debug_abbrev = try chopSlice(mapped_mem, debug_abbrev.offset, debug_abbrev.size),
                 .debug_str = try chopSlice(mapped_mem, debug_str.offset, debug_str.size),
lib/std/dwarf.zig
@@ -686,6 +686,8 @@ pub const DwarfInfo = struct {
     // Sorted by start_pc
     fde_list: std.ArrayListUnmanaged(FrameDescriptionEntry) = .{},
 
+    is_macho: bool,
+
     pub fn section(di: DwarfInfo, dwarf_section: DwarfSection) ?[]const u8 {
         return if (di.sections[@enumToInt(dwarf_section)]) |s| s.data else null;
     }
@@ -712,6 +714,7 @@ pub const DwarfInfo = struct {
     }
 
     pub fn getSymbolName(di: *DwarfInfo, address: u64) ?[]const u8 {
+        // TODO: Can this be binary searched?
         for (di.func_list.items) |*func| {
             if (func.pc_range) |range| {
                 if (address >= range.start and address < range.end) {
@@ -853,6 +856,9 @@ pub const DwarfInfo = struct {
                             }
                         };
 
+                        // TODO: Debug issue where `puts` in Ubuntu's libc was not found
+                        //if (fn_name != null and pc_range != null) debug.print("func_list: {s} -> 0x{x}-0x{x}\n", .{fn_name.?, pc_range.?.start, pc_range.?.end});
+
                         try di.func_list.append(allocator, Func{
                             .name = fn_name,
                             .pc_range = pc_range,
@@ -1587,7 +1593,7 @@ pub const DwarfInfo = struct {
         context.cfa = switch (row.cfa.rule) {
             .val_offset => |offset| blk: {
                 const register = row.cfa.register orelse return error.InvalidCFARule;
-                const value = mem.readIntSliceNative(usize, try abi.regBytes(&context.ucontext, register));
+                const value = mem.readIntSliceNative(usize, try abi.regBytes(&context.ucontext, register, context.reg_ctx));
 
                 // TODO: Check isValidMemory?
                 break :blk try call_frame.applyOffset(value, offset);
@@ -1602,15 +1608,13 @@ pub const DwarfInfo = struct {
             else => return error.InvalidCFARule,
         };
 
-        // Update the context with the unwound values
-        // TODO: Need old cfa and pc?
-
+        // Update the context with the previous frame's values
         var next_ucontext = context.ucontext;
 
         var has_next_ip = false;
         for (vm.rowColumns(row)) |column| {
             if (column.register) |register| {
-                const dest = try abi.regBytes(&next_ucontext, register);
+                const dest = try abi.regBytes(&next_ucontext, register, context.reg_ctx);
                 if (register == cie.return_address_register) {
                     has_next_ip = column.rule != .undefined;
                 }
@@ -1622,12 +1626,12 @@ pub const DwarfInfo = struct {
         context.ucontext = next_ucontext;
 
         if (has_next_ip) {
-            context.pc = mem.readIntSliceNative(usize, try abi.regBytes(&context.ucontext, @enumToInt(abi.Register.ip)));
+            context.pc = mem.readIntSliceNative(usize, try abi.regBytes(&context.ucontext, abi.ipRegNum(), context.reg_ctx));
         } else {
             context.pc = 0;
         }
 
-        mem.writeIntSliceNative(usize, try abi.regBytes(&context.ucontext, @enumToInt(abi.Register.sp)), context.cfa.?);
+        mem.writeIntSliceNative(usize, try abi.regBytes(&context.ucontext, abi.spRegNum(context.reg_ctx), context.reg_ctx), context.cfa.?);
     }
 };
 
@@ -1635,18 +1639,20 @@ pub const UnwindContext = struct {
     cfa: ?usize,
     pc: usize,
     ucontext: os.ucontext_t,
+    reg_ctx: abi.RegisterContext,
 
     pub fn init(ucontext: *const os.ucontext_t) !UnwindContext {
-        const pc = mem.readIntSliceNative(usize, try abi.regBytes(ucontext, @enumToInt(abi.Register.ip)));
+        const pc = mem.readIntSliceNative(usize, try abi.regBytes(ucontext, abi.ipRegNum(), null));
         return .{
             .cfa = null,
             .pc = pc,
             .ucontext = ucontext.*,
+            .reg_ctx = undefined,
         };
     }
 
     pub fn getFp(self: *const UnwindContext) !usize {
-        return mem.readIntSliceNative(usize, try abi.regBytes(&self.ucontext, @enumToInt(abi.Register.fp)));
+        return mem.readIntSliceNative(usize, try abi.regBytes(&self.ucontext, abi.fpRegNum(self.reg_ctx), self.reg_ctx));
     }
 };