authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-09-02 23:17:09+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-09-30 13:44:50+01:00
log4b47a377175c30ae148bae1191ae49e3ace819db
tree25f0a3c5cb0dc6cdb790bd80733519fefb8b462e
parent665f13b0cde4b9c2e69b139a87d272a67a9489e1
signaturelock-open Commit is signed but in an unrecognized format.

stash? more like no


4 files changed, 116 insertions(+), 100 deletions(-)

lib/std/debug/Dwarf/Unwind.zig+2-2
......@@ -553,8 +553,8 @@ fn readEhPointerAbs(r: *Reader, enc_ty: EH.PE.Type, addr_size_bytes: u8, endian:
553553 };
554554}
555555/// Returns `error.InvalidDebugInfo` if the encoding is `EH.PE.omit`.
556fn readEhPointer(fbr: *Reader, enc: EH.PE, addr_size_bytes: u8, ctx: EhPointerContext, endian: Endian) !u64 {
557 const offset = try readEhPointerAbs(fbr, enc.type, addr_size_bytes, endian);
556fn readEhPointer(r: *Reader, enc: EH.PE, addr_size_bytes: u8, ctx: EhPointerContext, endian: Endian) !u64 {
557 const offset = try readEhPointerAbs(r, enc.type, addr_size_bytes, endian);
558558 const base = switch (enc.rel) {
559559 .abs, .aligned => 0,
560560 .pcrel => ctx.pc_rel_base,
lib/std/debug/SelfInfo.zig+3-5
......@@ -76,7 +76,7 @@ pub fn deinit(self: *SelfInfo, gpa: Allocator) void {
7676 self.modules.deinit(gpa);
7777 if (Module.LookupCache != void) self.lookup_cache.deinit(gpa);
7878}
79comptime {
79test {
8080 // `std.debug` does not currently utilize `deinit`, as it keeps hold of debug info for the
8181 // whole lifetime of the program. Let's try to avoid it bitrotting.
8282 _ = &deinit;
......@@ -85,7 +85,7 @@ comptime {
8585pub fn unwindFrame(self: *SelfInfo, gpa: Allocator, context: *UnwindContext) !usize {
8686 comptime assert(supports_unwinding);
8787 const module: Module = try .lookup(&self.lookup_cache, gpa, context.pc);
88 const gop = try self.modules.getOrPut(gpa, module.load_offset);
88 const gop = try self.modules.getOrPut(gpa, module.key());
8989 self.modules.lockPointers();
9090 defer self.modules.unlockPointers();
9191 if (!gop.found_existing) gop.value_ptr.* = .init;
......@@ -128,9 +128,7 @@ const Module = switch (native_os) {
128128 .macos, .ios, .watchos, .tvos, .visionos => @import("SelfInfo/DarwinModule.zig"),
129129 .uefi, .windows => @import("SelfInfo/WindowsModule.zig"),
130130 .wasi, .emscripten => struct {
131 const LookupCache = struct {
132 const init: LookupCache = .{};
133 };
131 const LookupCache = void;
134132 fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) !Module {
135133 _ = cache;
136134 _ = gpa;
lib/std/debug/SelfInfo/DarwinModule.zig+109-91
......@@ -43,7 +43,37 @@ pub fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) !DarwinModule
4343 }
4444 return error.MissingDebugInfo;
4545}
46fn loadLocationInfo(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo) !void {
46fn 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}
76fn loadFullInfo(module: *const DarwinModule, gpa: Allocator) !DebugInfo.Full {
4777 const mapped_mem = try mapDebugInfoFile(module.name);
4878 errdefer posix.munmap(mapped_mem);
4979
......@@ -149,49 +179,19 @@ fn loadLocationInfo(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo)
149179 // This sort is so that we can binary search later.
150180 mem.sort(MachoSymbol, symbols_slice, {}, MachoSymbol.addressLessThan);
151181
152 di.full = .{
182 return .{
153183 .mapped_memory = mapped_mem,
154184 .symbols = symbols_slice,
155185 .strings = strings,
156186 .ofiles = .empty,
157187 };
158188}
159fn 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}
191189pub 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
193193 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 .{
195195 .name = null,
196196 .compile_unit_name = null,
197197 .source_location = null,
......@@ -202,8 +202,7 @@ pub fn getSymbolAtAddress(module: *const DarwinModule, gpa: Allocator, di: *Debu
202202
203203 // Take the symbol name from the N_FUN STAB entry, we're going to
204204 // 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);
207206
208207 // If any information is missing, we can at least return this from now on.
209208 const sym_only_result: std.debug.Symbol = .{
......@@ -213,10 +212,11 @@ pub fn getSymbolAtAddress(module: *const DarwinModule, gpa: Allocator, di: *Debu
213212 };
214213
215214 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);
217216 if (!gop.found_existing) {
217 const o_file_path = mem.sliceTo(full.strings[symbol.ofile..], 0);
218218 gop.value_ptr.* = DebugInfo.loadOFile(gpa, o_file_path) catch |err| {
219 defer _ = di.full.?.ofiles.pop().?;
219 defer _ = full.ofiles.pop().?;
220220 switch (err) {
221221 error.MissingDebugInfo,
222222 error.InvalidDebugInfo,
......@@ -228,7 +228,11 @@ pub fn getSymbolAtAddress(module: *const DarwinModule, gpa: Allocator, di: *Debu
228228 break :of gop.value_ptr;
229229 };
230230
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;
232236
233237 const compile_unit = o_file.dwarf.findCompileUnit(native_endian, symbol_ofile_vaddr) catch |err| switch (err) {
234238 error.MissingDebugInfo, error.InvalidDebugInfo => return sym_only_result,
......@@ -257,28 +261,15 @@ pub fn getSymbolAtAddress(module: *const DarwinModule, gpa: Allocator, di: *Debu
257261 },
258262 };
259263}
260pub 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}
272264/// Unwind a frame using MachO compact unwind info (from __unwind_info).
273265/// If the compact encoding can't encode a way to unwind a frame, it will
274266/// defer unwinding to DWARF, in which case `.eh_frame` will be used if available.
275fn 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 {
267pub 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;
282273 if (unwind_info.len < @sizeOf(macho.unwind_info_section_header)) return error.InvalidUnwindInfo;
283274 const header: *align(1) const macho.unwind_info_section_header = @ptrCast(unwind_info);
284275
......@@ -288,7 +279,7 @@ fn unwindFrameMachO(
288279 if (indices.len == 0) return error.MissingUnwindInfo;
289280
290281 // 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;
292283
293284 const start_offset: u32, const first_level_offset: u32 = index: {
294285 var left: usize = 0;
......@@ -443,7 +434,7 @@ fn unwindFrameMachO(
443434 }
444435 // In .STACK_IND, the stack size is inferred from the subq instruction at the beginning of the function.
445436 const sub_offset_addr =
446 text_base +
437 module.text_base +
447438 entry.function_offset +
448439 frameless.stack.indirect.sub_offset;
449440 // `sub_offset_addr` points to the offset of the literal within the instruction
......@@ -502,11 +493,11 @@ fn unwindFrameMachO(
502493 break :ip new_ip;
503494 },
504495 .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;
507498 return context.unwindFrameDwarf(
508499 &.initSection(.eh_frame, eh_frame_vaddr, eh_frame),
509 load_offset,
500 module.load_offset,
510501 @intCast(encoding.value.x86_64.dwarf),
511502 );
512503 },
......@@ -521,11 +512,11 @@ fn unwindFrameMachO(
521512 break :ip new_ip;
522513 },
523514 .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;
526517 return context.unwindFrameDwarf(
527518 &.initSection(.eh_frame, eh_frame_vaddr, eh_frame),
528 load_offset,
519 module.load_offset,
529520 @intCast(encoding.value.x86_64.dwarf),
530521 );
531522 },
......@@ -580,19 +571,9 @@ fn unwindFrameMachO(
580571/// No cache needed, because `_dyld_get_image_header` etc are already fast.
581572pub const LookupCache = void;
582573pub 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,
588575 // 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,
596577
597578 pub const init: DebugInfo = .{
598579 .unwind = null,
......@@ -603,7 +584,7 @@ pub const DebugInfo = struct {
603584 if (di.full) |*full| {
604585 for (full.ofiles.values()) |*ofile| {
605586 ofile.dwarf.deinit(gpa);
606 ofile.addr_table.deinit(gpa);
587 ofile.symbols_by_name.deinit(gpa);
607588 }
608589 full.ofiles.deinit(gpa);
609590 gpa.free(full.symbols);
......@@ -611,10 +592,42 @@ pub const DebugInfo = struct {
611592 }
612593 }
613594
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
614609 const OFile = struct {
615610 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 };
618631 };
619632
620633 fn loadOFile(gpa: Allocator, o_file_path: []const u8) !OFile {
......@@ -645,17 +658,17 @@ pub const DebugInfo = struct {
645658
646659 if (mapped_mem.len < symtab_cmd.stroff + symtab_cmd.strsize) return error.InvalidDebugInfo;
647660 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];
649662
650663 const n_sym_bytes = symtab_cmd.nsyms * @sizeOf(macho.nlist_64);
651664 if (mapped_mem.len < symtab_cmd.symoff + n_sym_bytes) return error.InvalidDebugInfo;
652665 const symtab: []align(1) const macho.nlist_64 = @ptrCast(mapped_mem[symtab_cmd.symoff..][0..n_sym_bytes]);
653666
654667 // 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| {
659672 if (sym.n_strx == 0) continue;
660673 switch (sym.n_type.bits.type) {
661674 .undf => continue, // includes tentative symbols
......@@ -663,9 +676,12 @@ pub const DebugInfo = struct {
663676 else => {},
664677 }
665678 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 );
667683 if (gop.found_existing) return error.InvalidDebugInfo;
668 gop.value_ptr.* = sym.n_value;
684 gop.key_ptr.* = @intCast(sym_index);
669685 }
670686
671687 var sections: Dwarf.SectionArray = @splat(null);
......@@ -697,7 +713,9 @@ pub const DebugInfo = struct {
697713
698714 return .{
699715 .dwarf = dwarf,
700 .addr_table = addr_table.move(),
716 .strtab = strtab,
717 .symtab = symtab,
718 .symbols_by_name = symbols_by_name.move(),
701719 };
702720 }
703721};
lib/std/debug/SelfInfo/ElfModule.zig+2-2
......@@ -92,7 +92,7 @@ pub fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) !ElfModule {
9292 };
9393 return error.MissingDebugInfo;
9494}
95fn loadLocationInfo(module: *const ElfModule, gpa: Allocator, di: *DebugInfo) !void {
95fn loadDwarf(module: *const ElfModule, gpa: Allocator, di: *DebugInfo) !void {
9696 if (module.name.len > 0) {
9797 di.loaded_elf = Dwarf.ElfModule.load(gpa, .{
9898 .root_dir = .cwd(),
......@@ -116,7 +116,7 @@ fn loadLocationInfo(module: *const ElfModule, gpa: Allocator, di: *DebugInfo) !v
116116 }
117117}
118118pub fn getSymbolAtAddress(module: *const ElfModule, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol {
119 if (di.loaded_elf == null) try module.loadLocationInfo(gpa, di);
119 if (di.loaded_elf == null) try module.loadDwarf(gpa, di);
120120 const vaddr = address - module.load_offset;
121121 return di.loaded_elf.?.dwarf.getSymbol(gpa, native_endian, vaddr);
122122}