debug: fixup base address calculations for macho

dwarf: fixup x86 register mapping logic
dwarf: change the register context update to update in-place instead of copying
debug: always print the unwind error type
This commit is contained in:
kcbanner 2023-07-03 14:31:29 -04:00
parent b85f84061a
commit 412cd789bf
5 changed files with 108 additions and 51 deletions

View File

@ -148,11 +148,13 @@ pub const ucontext_t = extern struct {
link: ?*ucontext_t,
mcsize: u64,
mcontext: *mcontext_t,
__mcontext_data: mcontext_t,
};
pub const mcontext_t = extern struct {
es: arch_bits.exception_state,
ss: arch_bits.thread_state,
fs: arch_bits.float_state,
};
extern "c" fn __error() *c_int;

View File

@ -31,6 +31,29 @@ pub const thread_state = extern struct {
gs: u64,
};
const stmm_reg = [16]u8;
const xmm_reg = [16]u8;
pub const float_state = extern struct {
reserved: [2]c_int,
fcw: u16,
fsw: u16,
ftw: u8,
rsrv1: u8,
fop: u16,
ip: u32,
cs: u16,
rsrv2: u16,
dp: u32,
ds: u16,
rsrv3: u16,
mxcsr: u32,
mxcsrmask: u32,
stmm: [8]stmm_reg,
xmm: [16]xmm_reg,
rsrv4: [96]u8,
reserved1: c_int,
};
pub const THREAD_STATE = 4;
pub const THREAD_STATE_COUNT: c.mach_msg_type_number_t = @sizeOf(thread_state) / @sizeOf(c_int);

View File

@ -444,7 +444,10 @@ pub inline fn getContext(context: *StackTraceContext) bool {
return true;
}
return have_getcontext and os.system.getcontext(context) == 0;
const result = have_getcontext and os.system.getcontext(context) == 0;
if (native_os == .macos) assert(context.mcsize == @sizeOf(std.c.mcontext_t));
return result;
}
pub const UnwindError = if (have_ucontext)
@ -553,6 +556,7 @@ pub const StackIterator = struct {
if (native_os == .freestanding) 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) {
@ -815,11 +819,7 @@ fn printUnknownSource(debug_info: *DebugInfo, out_stream: anytype, address: usiz
pub fn printUnwindError(debug_info: *DebugInfo, 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) {
try out_stream.print("Unwind information for {s} was not available ({}), trace may be incomplete\n\n", .{ module_name, err });
} else {
try out_stream.print("Unwind information for {s} was not available, trace may be incomplete\n\n", .{module_name});
}
try out_stream.print("Unwind information for {s} was not available ({}), trace may be incomplete\n\n", .{ module_name, err });
try tty_config.setColor(out_stream, .reset);
}
@ -1309,6 +1309,7 @@ fn readMachODebugInfo(allocator: mem.Allocator, macho_file: File) !ModuleDebugIn
return ModuleDebugInfo{
.base_address = undefined,
.vmaddr_slide = undefined,
.mapped_memory = mapped_mem,
.ofiles = ModuleDebugInfo.OFileTable.init(allocator),
.symbols = symbols,
@ -1514,11 +1515,10 @@ pub const DebugInfo = struct {
var i: u32 = 0;
while (i < image_count) : (i += 1) {
const base_address = std.c._dyld_get_image_vmaddr_slide(i);
if (address < base_address) continue;
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,
@ -1527,14 +1527,16 @@ pub const DebugInfo = struct {
@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).?;
const rebased_address = address - base_address;
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 (rebased_address >= seg_start and rebased_address < seg_end) {
if (original_address >= seg_start and original_address < seg_end) {
if (self.address_map.get(base_address)) |obj_di| {
return obj_di;
}
@ -1551,6 +1553,7 @@ pub const DebugInfo = struct {
};
obj_di.* = try readMachODebugInfo(self.allocator, macho_file);
obj_di.base_address = base_address;
obj_di.vmaddr_slide = vmaddr_slide;
try self.address_map.putNoClobber(base_address, obj_di);
@ -1808,6 +1811,7 @@ pub const DebugInfo = struct {
pub const ModuleDebugInfo = switch (native_os) {
.macos, .ios, .watchos, .tvos => struct {
base_address: usize,
vmaddr_slide: usize,
mapped_memory: []align(mem.page_size) const u8,
symbols: []const MachoSymbol,
strings: [:0]const u8,
@ -1972,7 +1976,7 @@ pub const ModuleDebugInfo = switch (native_os) {
} {
nosuspend {
// Translate the VA into an address into this object
const relocated_address = address - self.base_address;
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 .{

View File

@ -1705,28 +1705,53 @@ pub const DwarfInfo = struct {
if (!context.isValidMemory(context.cfa.?)) return error.InvalidCFA;
// Update the context with the previous frame's values
var next_ucontext = context.ucontext;
// Buffering the modifications is done because copying the ucontext 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 ucontext
old_value: []u8,
// Backed by arena
new_value: []const u8,
prev: ?*@This(),
};
var update_tail: ?*RegisterUpdate = null;
var has_next_ip = false;
for (context.vm.rowColumns(row.*)) |column| {
if (column.register) |register| {
const dest = try abi.regBytes(&next_ucontext, register, context.reg_ctx);
if (register == cie.return_address_register) {
has_next_ip = column.rule != .undefined;
}
const old_value = try abi.regBytes(&context.ucontext, register, context.reg_ctx);
const new_value = try update_allocator.alloc(u8, old_value.len);
const prev = update_tail;
update_tail = try update_allocator.create(RegisterUpdate);
update_tail.?.* = .{
.old_value = old_value,
.new_value = new_value,
.prev = prev,
};
try column.resolveValue(
context,
compile_unit,
&context.ucontext,
context.reg_ctx,
dest,
new_value,
);
}
}
context.ucontext = next_ucontext;
while (update_tail) |tail| {
@memcpy(tail.old_value, tail.new_value);
update_tail = tail.prev;
}
if (has_next_ip) {
context.pc = mem.readIntSliceNative(usize, try abi.regBytes(&context.ucontext, comptime abi.ipRegNum(), context.reg_ctx));

View File

@ -68,38 +68,41 @@ pub fn regBytes(ucontext_ptr: anytype, reg_number: u8, reg_ctx: ?RegisterContext
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 => switch (builtin.os.tag) {
.linux, .netbsd, .solaris => 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,
},
else => error.UnimplementedOs,
},
.x86_64 => switch (builtin.os.tag) {
.linux, .netbsd, .solaris => switch (reg_number) {