diff options
| author | kcbanner <kcbanner@gmail.com> | 2023-05-22 14:45:24 -0400 |
|---|---|---|
| committer | kcbanner <kcbanner@gmail.com> | 2023-07-20 22:58:13 -0400 |
| commit | 9145ff7da073966ace27151f7a0921b20c7860f4 (patch) | |
| tree | e033aa47d0a73180f7f7bcdb220f2e91d2897db0 /lib/std | |
| parent | b449d98a935a20429874d8eb379d9cc0e49c5fcd (diff) | |
| download | zig-9145ff7da073966ace27151f7a0921b20c7860f4.tar.gz zig-9145ff7da073966ace27151f7a0921b20c7860f4.zip | |
dwarf: implement more register number mappings
- add dwarf.abi.RegisterContext to handle register numbers changing based on DWARF format
Diffstat (limited to 'lib/std')
| -rw-r--r-- | lib/std/debug.zig | 12 | ||||
| -rw-r--r-- | lib/std/dwarf.zig | 24 | ||||
| -rw-r--r-- | lib/std/dwarf/abi.zig | 267 | ||||
| -rw-r--r-- | lib/std/dwarf/call_frame.zig | 4 |
4 files changed, 226 insertions, 81 deletions
diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 5e63bd9704..66dfdc1838 100644 --- a/lib/std/debug.zig +++ b/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), diff --git a/lib/std/dwarf.zig b/lib/std/dwarf.zig index d36aceee93..99c26051c8 100644 --- a/lib/std/dwarf.zig +++ b/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)); } }; diff --git a/lib/std/dwarf/abi.zig b/lib/std/dwarf/abi.zig index 913743f0f8..35f805bbf4 100644 --- a/lib/std/dwarf/abi.zig +++ b/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, diff --git a/lib/std/dwarf/call_frame.zig b/lib/std/dwarf/call_frame.zig index e9761206ed..8a8d083031 100644 --- a/lib/std/dwarf/call_frame.zig +++ b/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 |
