| ... | ... | @@ -43,7 +43,37 @@ pub fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) !DarwinModule |
| 43 | 43 | } |
| 44 | 44 | return error.MissingDebugInfo; |
| 45 | 45 | } |
| 46 | | fn loadLocationInfo(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo) !void { |
| 46 | fn loadUnwindInfo(module: *const DarwinModule) DebugInfo.Unwind { |
| 47 | const header: *std.macho.mach_header = @ptrFromInt(module.text_base); |
| 48 | |
| 49 | var it: macho.LoadCommandIterator = .{ |
| 50 | .ncmds = header.ncmds, |
| 51 | .buffer = @as([*]u8, @ptrCast(header))[@sizeOf(macho.mach_header_64)..][0..header.sizeofcmds], |
| 52 | }; |
| 53 | const sections = while (it.next()) |load_cmd| { |
| 54 | if (load_cmd.cmd() != .SEGMENT_64) continue; |
| 55 | const segment_cmd = load_cmd.cast(macho.segment_command_64).?; |
| 56 | if (!mem.eql(u8, segment_cmd.segName(), "__TEXT")) continue; |
| 57 | break load_cmd.getSections(); |
| 58 | } else unreachable; |
| 59 | |
| 60 | var unwind_info: ?[]const u8 = null; |
| 61 | var eh_frame: ?[]const u8 = null; |
| 62 | for (sections) |sect| { |
| 63 | if (mem.eql(u8, sect.sectName(), "__unwind_info")) { |
| 64 | const sect_ptr: [*]u8 = @ptrFromInt(@as(usize, @intCast(module.load_offset + sect.addr))); |
| 65 | unwind_info = sect_ptr[0..@intCast(sect.size)]; |
| 66 | } else if (mem.eql(u8, sect.sectName(), "__eh_frame")) { |
| 67 | const sect_ptr: [*]u8 = @ptrFromInt(@as(usize, @intCast(module.load_offset + sect.addr))); |
| 68 | eh_frame = sect_ptr[0..@intCast(sect.size)]; |
| 69 | } |
| 70 | } |
| 71 | return .{ |
| 72 | .unwind_info = unwind_info, |
| 73 | .eh_frame = eh_frame, |
| 74 | }; |
| 75 | } |
| 76 | fn loadFullInfo(module: *const DarwinModule, gpa: Allocator) !DebugInfo.Full { |
| 47 | 77 | const mapped_mem = try mapDebugInfoFile(module.name); |
| 48 | 78 | errdefer posix.munmap(mapped_mem); |
| 49 | 79 | |
| ... | ... | @@ -149,49 +179,19 @@ fn loadLocationInfo(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo) |
| 149 | 179 | // This sort is so that we can binary search later. |
| 150 | 180 | mem.sort(MachoSymbol, symbols_slice, {}, MachoSymbol.addressLessThan); |
| 151 | 181 | |
| 152 | | di.full = .{ |
| 182 | return .{ |
| 153 | 183 | .mapped_memory = mapped_mem, |
| 154 | 184 | .symbols = symbols_slice, |
| 155 | 185 | .strings = strings, |
| 156 | 186 | .ofiles = .empty, |
| 157 | 187 | }; |
| 158 | 188 | } |
| 159 | | fn loadUnwindInfo(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo) !void { |
| 160 | | _ = gpa; |
| 161 | | |
| 162 | | const header: *std.macho.mach_header = @ptrFromInt(module.text_base); |
| 163 | | |
| 164 | | var it: macho.LoadCommandIterator = .{ |
| 165 | | .ncmds = header.ncmds, |
| 166 | | .buffer = @as([*]u8, @ptrCast(header))[@sizeOf(macho.mach_header_64)..][0..header.sizeofcmds], |
| 167 | | }; |
| 168 | | const sections = while (it.next()) |load_cmd| { |
| 169 | | if (load_cmd.cmd() != .SEGMENT_64) continue; |
| 170 | | const segment_cmd = load_cmd.cast(macho.segment_command_64).?; |
| 171 | | if (!mem.eql(u8, segment_cmd.segName(), "__TEXT")) continue; |
| 172 | | break load_cmd.getSections(); |
| 173 | | } else unreachable; |
| 174 | | |
| 175 | | var unwind_info: ?[]const u8 = null; |
| 176 | | var eh_frame: ?[]const u8 = null; |
| 177 | | for (sections) |sect| { |
| 178 | | if (mem.eql(u8, sect.sectName(), "__unwind_info")) { |
| 179 | | const sect_ptr: [*]u8 = @ptrFromInt(@as(usize, @intCast(module.load_offset + sect.addr))); |
| 180 | | unwind_info = sect_ptr[0..@intCast(sect.size)]; |
| 181 | | } else if (mem.eql(u8, sect.sectName(), "__eh_frame")) { |
| 182 | | const sect_ptr: [*]u8 = @ptrFromInt(@as(usize, @intCast(module.load_offset + sect.addr))); |
| 183 | | eh_frame = sect_ptr[0..@intCast(sect.size)]; |
| 184 | | } |
| 185 | | } |
| 186 | | di.unwind = .{ |
| 187 | | .unwind_info = unwind_info, |
| 188 | | .eh_frame = eh_frame, |
| 189 | | }; |
| 190 | | } |
| 191 | 189 | pub fn getSymbolAtAddress(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol { |
| 192 | | if (di.full == null) try module.loadLocationInfo(gpa, di); |
| 190 | if (di.full == null) di.full = try module.loadFullInfo(gpa); |
| 191 | const full = &di.full.?; |
| 192 | |
| 193 | 193 | const vaddr = address - module.load_offset; |
| 194 | | const symbol = MachoSymbol.find(di.full.?.symbols, vaddr) orelse return .{ |
| 194 | const symbol = MachoSymbol.find(full.symbols, vaddr) orelse return .{ |
| 195 | 195 | .name = null, |
| 196 | 196 | .compile_unit_name = null, |
| 197 | 197 | .source_location = null, |
| ... | ... | @@ -202,8 +202,7 @@ pub fn getSymbolAtAddress(module: *const DarwinModule, gpa: Allocator, di: *Debu |
| 202 | 202 | |
| 203 | 203 | // Take the symbol name from the N_FUN STAB entry, we're going to |
| 204 | 204 | // use it if we fail to find the DWARF infos |
| 205 | | const stab_symbol = mem.sliceTo(di.full.?.strings[symbol.strx..], 0); |
| 206 | | const o_file_path = mem.sliceTo(di.full.?.strings[symbol.ofile..], 0); |
| 205 | const stab_symbol = mem.sliceTo(full.strings[symbol.strx..], 0); |
| 207 | 206 | |
| 208 | 207 | // If any information is missing, we can at least return this from now on. |
| 209 | 208 | const sym_only_result: std.debug.Symbol = .{ |
| ... | ... | @@ -213,10 +212,11 @@ pub fn getSymbolAtAddress(module: *const DarwinModule, gpa: Allocator, di: *Debu |
| 213 | 212 | }; |
| 214 | 213 | |
| 215 | 214 | const o_file: *DebugInfo.OFile = of: { |
| 216 | | const gop = try di.full.?.ofiles.getOrPut(gpa, o_file_path); |
| 215 | const gop = try full.ofiles.getOrPut(gpa, symbol.ofile); |
| 217 | 216 | if (!gop.found_existing) { |
| 217 | const o_file_path = mem.sliceTo(full.strings[symbol.ofile..], 0); |
| 218 | 218 | gop.value_ptr.* = DebugInfo.loadOFile(gpa, o_file_path) catch |err| { |
| 219 | | defer _ = di.full.?.ofiles.pop().?; |
| 219 | defer _ = full.ofiles.pop().?; |
| 220 | 220 | switch (err) { |
| 221 | 221 | error.MissingDebugInfo, |
| 222 | 222 | error.InvalidDebugInfo, |
| ... | ... | @@ -228,7 +228,11 @@ pub fn getSymbolAtAddress(module: *const DarwinModule, gpa: Allocator, di: *Debu |
| 228 | 228 | break :of gop.value_ptr; |
| 229 | 229 | }; |
| 230 | 230 | |
| 231 | | const symbol_ofile_vaddr = o_file.addr_table.get(stab_symbol) orelse return sym_only_result; |
| 231 | const symbol_index = o_file.symbols_by_name.getKeyAdapted( |
| 232 | @as([]const u8, stab_symbol), |
| 233 | @as(DebugInfo.OFile.SymbolAdapter, .{ .strtab = o_file.strtab, .symtab = o_file.symtab }), |
| 234 | ) orelse return sym_only_result; |
| 235 | const symbol_ofile_vaddr = o_file.symtab[symbol_index].n_value; |
| 232 | 236 | |
| 233 | 237 | const compile_unit = o_file.dwarf.findCompileUnit(native_endian, symbol_ofile_vaddr) catch |err| switch (err) { |
| 234 | 238 | error.MissingDebugInfo, error.InvalidDebugInfo => return sym_only_result, |
| ... | ... | @@ -257,28 +261,15 @@ pub fn getSymbolAtAddress(module: *const DarwinModule, gpa: Allocator, di: *Debu |
| 257 | 261 | }, |
| 258 | 262 | }; |
| 259 | 263 | } |
| 260 | | pub fn unwindFrame(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) !usize { |
| 261 | | if (di.unwind == null) try module.loadUnwindInfo(gpa, di); |
| 262 | | const unwind_info = di.unwind.?.unwind_info orelse return error.MissingUnwindInfo; |
| 263 | | // MLUGG TODO: inline? |
| 264 | | return unwindFrameMachO( |
| 265 | | module.text_base, |
| 266 | | module.load_offset, |
| 267 | | context, |
| 268 | | unwind_info, |
| 269 | | di.unwind.?.eh_frame, |
| 270 | | ); |
| 271 | | } |
| 272 | 264 | /// Unwind a frame using MachO compact unwind info (from __unwind_info). |
| 273 | 265 | /// If the compact encoding can't encode a way to unwind a frame, it will |
| 274 | 266 | /// defer unwinding to DWARF, in which case `.eh_frame` will be used if available. |
| 275 | | fn unwindFrameMachO( |
| 276 | | text_base: usize, |
| 277 | | load_offset: usize, |
| 278 | | context: *UnwindContext, |
| 279 | | unwind_info: []const u8, |
| 280 | | opt_eh_frame: ?[]const u8, |
| 281 | | ) !usize { |
| 267 | pub fn unwindFrame(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) !usize { |
| 268 | _ = gpa; |
| 269 | if (di.unwind == null) di.unwind = module.loadUnwindInfo(); |
| 270 | const unwind = &di.unwind.?; |
| 271 | |
| 272 | const unwind_info = unwind.unwind_info orelse return error.MissingUnwindInfo; |
| 282 | 273 | if (unwind_info.len < @sizeOf(macho.unwind_info_section_header)) return error.InvalidUnwindInfo; |
| 283 | 274 | const header: *align(1) const macho.unwind_info_section_header = @ptrCast(unwind_info); |
| 284 | 275 | |
| ... | ... | @@ -288,7 +279,7 @@ fn unwindFrameMachO( |
| 288 | 279 | if (indices.len == 0) return error.MissingUnwindInfo; |
| 289 | 280 | |
| 290 | 281 | // offset of the PC into the `__TEXT` segment |
| 291 | | const pc_text_offset = context.pc - text_base; |
| 282 | const pc_text_offset = context.pc - module.text_base; |
| 292 | 283 | |
| 293 | 284 | const start_offset: u32, const first_level_offset: u32 = index: { |
| 294 | 285 | var left: usize = 0; |
| ... | ... | @@ -443,7 +434,7 @@ fn unwindFrameMachO( |
| 443 | 434 | } |
| 444 | 435 | // In .STACK_IND, the stack size is inferred from the subq instruction at the beginning of the function. |
| 445 | 436 | const sub_offset_addr = |
| 446 | | text_base + |
| 437 | module.text_base + |
| 447 | 438 | entry.function_offset + |
| 448 | 439 | frameless.stack.indirect.sub_offset; |
| 449 | 440 | // `sub_offset_addr` points to the offset of the literal within the instruction |
| ... | ... | @@ -502,11 +493,11 @@ fn unwindFrameMachO( |
| 502 | 493 | break :ip new_ip; |
| 503 | 494 | }, |
| 504 | 495 | .DWARF => { |
| 505 | | const eh_frame = opt_eh_frame orelse return error.MissingEhFrame; |
| 506 | | const eh_frame_vaddr = @intFromPtr(eh_frame.ptr) - load_offset; |
| 496 | const eh_frame = unwind.eh_frame orelse return error.MissingEhFrame; |
| 497 | const eh_frame_vaddr = @intFromPtr(eh_frame.ptr) - module.load_offset; |
| 507 | 498 | return context.unwindFrameDwarf( |
| 508 | 499 | &.initSection(.eh_frame, eh_frame_vaddr, eh_frame), |
| 509 | | load_offset, |
| 500 | module.load_offset, |
| 510 | 501 | @intCast(encoding.value.x86_64.dwarf), |
| 511 | 502 | ); |
| 512 | 503 | }, |
| ... | ... | @@ -521,11 +512,11 @@ fn unwindFrameMachO( |
| 521 | 512 | break :ip new_ip; |
| 522 | 513 | }, |
| 523 | 514 | .DWARF => { |
| 524 | | const eh_frame = opt_eh_frame orelse return error.MissingEhFrame; |
| 525 | | const eh_frame_vaddr = @intFromPtr(eh_frame.ptr) - load_offset; |
| 515 | const eh_frame = unwind.eh_frame orelse return error.MissingEhFrame; |
| 516 | const eh_frame_vaddr = @intFromPtr(eh_frame.ptr) - module.load_offset; |
| 526 | 517 | return context.unwindFrameDwarf( |
| 527 | 518 | &.initSection(.eh_frame, eh_frame_vaddr, eh_frame), |
| 528 | | load_offset, |
| 519 | module.load_offset, |
| 529 | 520 | @intCast(encoding.value.x86_64.dwarf), |
| 530 | 521 | ); |
| 531 | 522 | }, |
| ... | ... | @@ -580,19 +571,9 @@ fn unwindFrameMachO( |
| 580 | 571 | /// No cache needed, because `_dyld_get_image_header` etc are already fast. |
| 581 | 572 | pub const LookupCache = void; |
| 582 | 573 | pub const DebugInfo = struct { |
| 583 | | unwind: ?struct { |
| 584 | | // Backed by the in-memory sections mapped by the loader |
| 585 | | unwind_info: ?[]const u8, |
| 586 | | eh_frame: ?[]const u8, |
| 587 | | }, |
| 574 | unwind: ?Unwind, |
| 588 | 575 | // MLUGG TODO: awful field name |
| 589 | | full: ?struct { |
| 590 | | mapped_memory: []align(std.heap.page_size_min) const u8, |
| 591 | | symbols: []const MachoSymbol, |
| 592 | | strings: [:0]const u8, |
| 593 | | // MLUGG TODO: this could use an adapter to just index straight into `strings`! |
| 594 | | ofiles: std.StringArrayHashMapUnmanaged(OFile), |
| 595 | | }, |
| 576 | full: ?Full, |
| 596 | 577 | |
| 597 | 578 | pub const init: DebugInfo = .{ |
| 598 | 579 | .unwind = null, |
| ... | ... | @@ -603,7 +584,7 @@ pub const DebugInfo = struct { |
| 603 | 584 | if (di.full) |*full| { |
| 604 | 585 | for (full.ofiles.values()) |*ofile| { |
| 605 | 586 | ofile.dwarf.deinit(gpa); |
| 606 | | ofile.addr_table.deinit(gpa); |
| 587 | ofile.symbols_by_name.deinit(gpa); |
| 607 | 588 | } |
| 608 | 589 | full.ofiles.deinit(gpa); |
| 609 | 590 | gpa.free(full.symbols); |
| ... | ... | @@ -611,10 +592,42 @@ pub const DebugInfo = struct { |
| 611 | 592 | } |
| 612 | 593 | } |
| 613 | 594 | |
| 595 | const Unwind = struct { |
| 596 | // Backed by the in-memory sections mapped by the loader |
| 597 | unwind_info: ?[]const u8, |
| 598 | eh_frame: ?[]const u8, |
| 599 | }; |
| 600 | |
| 601 | const Full = struct { |
| 602 | mapped_memory: []align(std.heap.page_size_min) const u8, |
| 603 | symbols: []const MachoSymbol, |
| 604 | strings: [:0]const u8, |
| 605 | /// Key is index into `strings` of the file path. |
| 606 | ofiles: std.AutoArrayHashMapUnmanaged(u32, OFile), |
| 607 | }; |
| 608 | |
| 614 | 609 | const OFile = struct { |
| 615 | 610 | dwarf: Dwarf, |
| 616 | | // MLUGG TODO: this could use an adapter to just index straight into the strtab! |
| 617 | | addr_table: std.StringArrayHashMapUnmanaged(u64), |
| 611 | strtab: [:0]const u8, |
| 612 | symtab: []align(1) const macho.nlist_64, |
| 613 | /// All named symbols in `symtab`. Stored `u32` key is the index into `symtab`. Accessed |
| 614 | /// through `SymbolAdapter`, so that the symbol name is used as the logical key. |
| 615 | symbols_by_name: std.ArrayHashMapUnmanaged(u32, void, void, true), |
| 616 | |
| 617 | const SymbolAdapter = struct { |
| 618 | strtab: [:0]const u8, |
| 619 | symtab: []align(1) const macho.nlist_64, |
| 620 | pub fn hash(ctx: SymbolAdapter, sym_name: []const u8) u32 { |
| 621 | _ = ctx; |
| 622 | return @truncate(std.hash.Wyhash.hash(0, sym_name)); |
| 623 | } |
| 624 | pub fn eql(ctx: SymbolAdapter, a_sym_name: []const u8, b_sym_index: u32, b_index: usize) bool { |
| 625 | _ = b_index; |
| 626 | const b_sym = ctx.symtab[b_sym_index]; |
| 627 | const b_sym_name = std.mem.sliceTo(ctx.strtab[b_sym.n_strx..], 0); |
| 628 | return mem.eql(u8, a_sym_name, b_sym_name); |
| 629 | } |
| 630 | }; |
| 618 | 631 | }; |
| 619 | 632 | |
| 620 | 633 | fn loadOFile(gpa: Allocator, o_file_path: []const u8) !OFile { |
| ... | ... | @@ -645,17 +658,17 @@ pub const DebugInfo = struct { |
| 645 | 658 | |
| 646 | 659 | if (mapped_mem.len < symtab_cmd.stroff + symtab_cmd.strsize) return error.InvalidDebugInfo; |
| 647 | 660 | if (mapped_mem[symtab_cmd.stroff + symtab_cmd.strsize - 1] != 0) return error.InvalidDebugInfo; |
| 648 | | const strtab = mapped_mem[symtab_cmd.stroff..][0 .. symtab_cmd.strsize - 1]; |
| 661 | const strtab = mapped_mem[symtab_cmd.stroff..][0 .. symtab_cmd.strsize - 1 :0]; |
| 649 | 662 | |
| 650 | 663 | const n_sym_bytes = symtab_cmd.nsyms * @sizeOf(macho.nlist_64); |
| 651 | 664 | if (mapped_mem.len < symtab_cmd.symoff + n_sym_bytes) return error.InvalidDebugInfo; |
| 652 | 665 | const symtab: []align(1) const macho.nlist_64 = @ptrCast(mapped_mem[symtab_cmd.symoff..][0..n_sym_bytes]); |
| 653 | 666 | |
| 654 | 667 | // TODO handle tentative (common) symbols |
| 655 | | var addr_table: std.StringArrayHashMapUnmanaged(u64) = .empty; |
| 656 | | defer addr_table.deinit(gpa); |
| 657 | | try addr_table.ensureUnusedCapacity(gpa, @intCast(symtab.len)); |
| 658 | | for (symtab) |sym| { |
| 668 | var symbols_by_name: std.ArrayHashMapUnmanaged(u32, void, void, true) = .empty; |
| 669 | defer symbols_by_name.deinit(gpa); |
| 670 | try symbols_by_name.ensureUnusedCapacity(gpa, @intCast(symtab.len)); |
| 671 | for (symtab, 0..) |sym, sym_index| { |
| 659 | 672 | if (sym.n_strx == 0) continue; |
| 660 | 673 | switch (sym.n_type.bits.type) { |
| 661 | 674 | .undf => continue, // includes tentative symbols |
| ... | ... | @@ -663,9 +676,12 @@ pub const DebugInfo = struct { |
| 663 | 676 | else => {}, |
| 664 | 677 | } |
| 665 | 678 | const sym_name = mem.sliceTo(strtab[sym.n_strx..], 0); |
| 666 | | const gop = addr_table.getOrPutAssumeCapacity(sym_name); |
| 679 | const gop = symbols_by_name.getOrPutAssumeCapacityAdapted( |
| 680 | @as([]const u8, sym_name), |
| 681 | @as(DebugInfo.OFile.SymbolAdapter, .{ .strtab = strtab, .symtab = symtab }), |
| 682 | ); |
| 667 | 683 | if (gop.found_existing) return error.InvalidDebugInfo; |
| 668 | | gop.value_ptr.* = sym.n_value; |
| 684 | gop.key_ptr.* = @intCast(sym_index); |
| 669 | 685 | } |
| 670 | 686 | |
| 671 | 687 | var sections: Dwarf.SectionArray = @splat(null); |
| ... | ... | @@ -697,7 +713,9 @@ pub const DebugInfo = struct { |
| 697 | 713 | |
| 698 | 714 | return .{ |
| 699 | 715 | .dwarf = dwarf, |
| 700 | | .addr_table = addr_table.move(), |
| 716 | .strtab = strtab, |
| 717 | .symtab = symtab, |
| 718 | .symbols_by_name = symbols_by_name.move(), |
| 701 | 719 | }; |
| 702 | 720 | } |
| 703 | 721 | }; |