authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-09-02 18:36:05+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-09-30 13:44:50+01:00
logba3f38959a31ace9af1816f16cda6c0717518b7f
tree3dc9419d74c55a563b69554c781a9faaa61f5ff3
parent1397b95143a799d836f549448485d87a70de391c
signaturelock-open Commit is signed but in an unrecognized format.

split SelfInfo into a file per impl


5 files changed, 1411 insertions(+), 1372 deletions(-)

lib/std/debug/Dwarf/abi.zig+1-1
...@@ -7,7 +7,7 @@ const Arch = std.Target.Cpu.Arch;...@@ -7,7 +7,7 @@ const Arch = std.Target.Cpu.Arch;
77
8/// Tells whether unwinding for this target is supported by the Dwarf standard.8/// Tells whether unwinding for this target is supported by the Dwarf standard.
9///9///
10/// See also `std.debug.SelfInfo.supportsUnwinding` which tells whether the Zig10/// See also `std.debug.SelfInfo.supports_unwinding` which tells whether the Zig
11/// standard library has a working implementation of unwinding for this target.11/// standard library has a working implementation of unwinding for this target.
12pub fn supportsUnwinding(target: *const std.Target) bool {12pub fn supportsUnwinding(target: *const std.Target) bool {
13 return switch (target.cpu.arch) {13 return switch (target.cpu.arch) {
lib/std/debug/SelfInfo.zig+210-1371
...@@ -11,18 +11,8 @@ const native_arch = builtin.cpu.arch;...@@ -11,18 +11,8 @@ const native_arch = builtin.cpu.arch;
11const std = @import("../std.zig");11const std = @import("../std.zig");
12const mem = std.mem;12const mem = std.mem;
13const Allocator = std.mem.Allocator;13const Allocator = std.mem.Allocator;
14const windows = std.os.windows;
15const macho = std.macho;
16const fs = std.fs;
17const coff = std.coff;
18const assert = std.debug.assert;14const assert = std.debug.assert;
19const posix = std.posix;
20const elf = std.elf;
21const Dwarf = std.debug.Dwarf;15const Dwarf = std.debug.Dwarf;
22const Pdb = std.debug.Pdb;
23const File = std.fs.File;
24const math = std.math;
25const testing = std.testing;
26const regBytes = Dwarf.abi.regBytes;16const regBytes = Dwarf.abi.regBytes;
27const regValueNative = Dwarf.abi.regValueNative;17const regValueNative = Dwarf.abi.regValueNative;
2818
...@@ -31,6 +21,7 @@ const SelfInfo = @This();...@@ -31,6 +21,7 @@ const SelfInfo = @This();
31modules: std.AutoHashMapUnmanaged(usize, Module.DebugInfo),21modules: std.AutoHashMapUnmanaged(usize, Module.DebugInfo),
32lookup_cache: Module.LookupCache,22lookup_cache: Module.LookupCache,
3323
24/// Indicates whether the `SelfInfo` implementation has support for this target.
34pub const target_supported: bool = switch (native_os) {25pub const target_supported: bool = switch (native_os) {
35 .linux,26 .linux,
36 .freebsd,27 .freebsd,
...@@ -45,9 +36,39 @@ pub const target_supported: bool = switch (native_os) {...@@ -45,9 +36,39 @@ pub const target_supported: bool = switch (native_os) {
45 else => false,36 else => false,
46};37};
4738
39/// Indicates whether unwinding for the host is *implemented* here in the Zig
40/// standard library.
41///
42/// See also `Dwarf.abi.supportsUnwinding` which tells whether Dwarf supports
43/// unwinding on a target *in theory*.
44pub const supports_unwinding: bool = switch (builtin.target.cpu.arch) {
45 .x86 => switch (builtin.target.os.tag) {
46 .linux, .netbsd, .solaris, .illumos => true,
47 else => false,
48 },
49 .x86_64 => switch (builtin.target.os.tag) {
50 .linux, .netbsd, .freebsd, .openbsd, .macos, .ios, .solaris, .illumos => true,
51 else => false,
52 },
53 .arm, .armeb, .thumb, .thumbeb => switch (builtin.target.os.tag) {
54 .linux => true,
55 else => false,
56 },
57 .aarch64, .aarch64_be => switch (builtin.target.os.tag) {
58 .linux, .netbsd, .freebsd, .macos, .ios => true,
59 else => false,
60 },
61 // Unwinding is possible on other targets but this implementation does
62 // not support them...yet!
63 else => false,
64};
65comptime {
66 if (supports_unwinding) assert(Dwarf.abi.supportsUnwinding(&builtin.target));
67}
68
48pub const init: SelfInfo = .{69pub const init: SelfInfo = .{
49 .modules = .empty,70 .modules = .empty,
50 .lookup_cache = if (Module.LookupCache != void) .init,71 .lookup_cache = .init,
51};72};
5273
53pub fn deinit(self: *SelfInfo) void {74pub fn deinit(self: *SelfInfo) void {
...@@ -59,19 +80,14 @@ pub fn deinit(self: *SelfInfo) void {...@@ -59,19 +80,14 @@ pub fn deinit(self: *SelfInfo) void {
59 self.allocator.destroy(mdi);80 self.allocator.destroy(mdi);
60 }81 }
61 self.modules.deinit(self.allocator);82 self.modules.deinit(self.allocator);
62 if (native_os == .windows) {
63 for (self.modules.items) |module| {
64 self.allocator.free(module.name);
65 if (module.mapped_file) |mapped_file| mapped_file.deinit();
66 }
67 self.modules.deinit(self.allocator);
68 }
69}83}
7084
71pub fn unwindFrame(self: *SelfInfo, gpa: Allocator, context: *UnwindContext) !usize {85pub fn unwindFrame(self: *SelfInfo, gpa: Allocator, context: *UnwindContext) !usize {
72 comptime assert(target_supported);86 comptime assert(supports_unwinding);
73 const module: Module = try .lookup(&self.lookup_cache, gpa, context.pc);87 const module: Module = try .lookup(&self.lookup_cache, gpa, context.pc);
74 const gop = try self.modules.getOrPut(gpa, module.load_offset);88 const gop = try self.modules.getOrPut(gpa, module.load_offset);
89 self.modules.lockPointers();
90 defer self.modules.unlockPointers();
75 if (!gop.found_existing) gop.value_ptr.* = .init;91 if (!gop.found_existing) gop.value_ptr.* = .init;
76 return module.unwindFrame(gpa, gop.value_ptr, context);92 return module.unwindFrame(gpa, gop.value_ptr, context);
77}93}
...@@ -80,417 +96,39 @@ pub fn getSymbolAtAddress(self: *SelfInfo, gpa: Allocator, address: usize) !std....@@ -80,417 +96,39 @@ pub fn getSymbolAtAddress(self: *SelfInfo, gpa: Allocator, address: usize) !std.
80 comptime assert(target_supported);96 comptime assert(target_supported);
81 const module: Module = try .lookup(&self.lookup_cache, gpa, address);97 const module: Module = try .lookup(&self.lookup_cache, gpa, address);
82 const gop = try self.modules.getOrPut(gpa, module.key());98 const gop = try self.modules.getOrPut(gpa, module.key());
99 self.modules.lockPointers();
100 defer self.modules.unlockPointers();
83 if (!gop.found_existing) gop.value_ptr.* = .init;101 if (!gop.found_existing) gop.value_ptr.* = .init;
84 return module.getSymbolAtAddress(gpa, gop.value_ptr, address);102 return module.getSymbolAtAddress(gpa, gop.value_ptr, address);
85}103}
86104
87/// Returns the module name for a given address.
88/// This can be called when getModuleForAddress fails, so implementations should provide
89/// a path that doesn't rely on any side-effects of a prior successful module lookup.
90pub fn getModuleNameForAddress(self: *SelfInfo, gpa: Allocator, address: usize) error{ Unexpected, OutOfMemory, MissingDebugInfo }![]const u8 {105pub fn getModuleNameForAddress(self: *SelfInfo, gpa: Allocator, address: usize) error{ Unexpected, OutOfMemory, MissingDebugInfo }![]const u8 {
91 comptime assert(target_supported);106 comptime assert(target_supported);
92 const module: Module = try .lookup(&self.lookup_cache, gpa, address);107 const module: Module = try .lookup(&self.lookup_cache, gpa, address);
93 return module.name;108 return module.name;
94}109}
95110
111/// This type contains the target-specific implementation. It must expose the following declarations:
112///
113/// * `LookupCache: type`
114/// * `LookupCache.init: LookupCache`
115/// * `lookup: fn (*LookupCache, Allocator, address: usize) !Module`
116/// * `key: fn (*const Module) usize`
117/// * `DebugInfo: type`
118/// * `DebugInfo.init: DebugInfo`
119/// * `getSymbolAtAddress: fn (*const Module, Allocator, *DebugInfo, address: usize) !std.debug.Symbol`
120///
121/// If unwinding is supported on this target, it must additionally expose the following declarations:
122///
123/// * `unwindFrame: fn (*const Module, Allocator, *DebugInfo, *UnwindContext) !usize`
96const Module = switch (native_os) {124const Module = switch (native_os) {
97 else => {}, // Dwarf, // TODO MLUGG: it's this on master but that's definitely broken atm...125 else => {}, // Dwarf, // TODO MLUGG: it's this on master but that's definitely broken atm...
98 .macos, .ios, .watchos, .tvos, .visionos => struct {126 .linux, .netbsd, .freebsd, .dragonfly, .openbsd, .haiku, .solaris, .illumos => @import("SelfInfo/ElfModule.zig"),
99 /// The runtime address where __TEXT is loaded.127 .macos, .ios, .watchos, .tvos, .visionos => @import("SelfInfo/DarwinModule.zig"),
100 text_base: usize,128 .uefi, .windows => @import("SelfInfo/WindowsModule.zig"),
101 load_offset: usize,
102 name: []const u8,
103 fn key(m: *const Module) usize {
104 return m.text_base;
105 }
106 fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) !Module {
107 _ = cache;
108 _ = gpa;
109 const image_count = std.c._dyld_image_count();
110 for (0..image_count) |image_idx| {
111 const header = std.c._dyld_get_image_header(@intCast(image_idx)) orelse continue;
112 const text_base = @intFromPtr(header);
113 if (address < text_base) continue;
114 const load_offset = std.c._dyld_get_image_vmaddr_slide(@intCast(image_idx));
115
116 // Find the __TEXT segment
117 var it: macho.LoadCommandIterator = .{
118 .ncmds = header.ncmds,
119 .buffer = @as([*]u8, @ptrCast(header))[@sizeOf(macho.mach_header_64)..][0..header.sizeofcmds],
120 };
121 const text_segment_cmd = while (it.next()) |load_cmd| {
122 if (load_cmd.cmd() != .SEGMENT_64) continue;
123 const segment_cmd = load_cmd.cast(macho.segment_command_64).?;
124 if (!mem.eql(u8, segment_cmd.segName(), "__TEXT")) continue;
125 break segment_cmd;
126 } else continue;
127
128 const seg_start = load_offset + text_segment_cmd.vmaddr;
129 assert(seg_start == text_base);
130 const seg_end = seg_start + text_segment_cmd.vmsize;
131 if (address < seg_start or address >= seg_end) continue;
132
133 // We've found the matching __TEXT segment. This is the image we need.
134 return .{
135 .text_base = text_base,
136 .load_offset = load_offset,
137 .name = mem.span(std.c._dyld_get_image_name(@intCast(image_idx))),
138 };
139 }
140 return error.MissingDebugInfo;
141 }
142 fn loadLocationInfo(module: *const Module, gpa: Allocator, di: *Module.DebugInfo) !void {
143 const mapped_mem = try mapDebugInfoFile(module.name);
144 errdefer posix.munmap(mapped_mem);
145
146 const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_mem.ptr));
147 if (hdr.magic != macho.MH_MAGIC_64)
148 return error.InvalidDebugInfo;
149
150 const symtab: macho.symtab_command = symtab: {
151 var it: macho.LoadCommandIterator = .{
152 .ncmds = hdr.ncmds,
153 .buffer = mapped_mem[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds],
154 };
155 while (it.next()) |cmd| switch (cmd.cmd()) {
156 .SYMTAB => break :symtab cmd.cast(macho.symtab_command) orelse return error.InvalidDebugInfo,
157 else => {},
158 };
159 return error.MissingDebugInfo;
160 };
161
162 const syms_ptr: [*]align(1) const macho.nlist_64 = @ptrCast(mapped_mem[symtab.symoff..]);
163 const syms = syms_ptr[0..symtab.nsyms];
164 const strings = mapped_mem[symtab.stroff..][0 .. symtab.strsize - 1 :0];
165
166 var symbols: std.ArrayList(MachoSymbol) = try .initCapacity(gpa, syms.len);
167 defer symbols.deinit(gpa);
168
169 var ofile: u32 = undefined;
170 var last_sym: MachoSymbol = undefined;
171 var state: enum {
172 init,
173 oso_open,
174 oso_close,
175 bnsym,
176 fun_strx,
177 fun_size,
178 ensym,
179 } = .init;
180
181 for (syms) |*sym| {
182 if (sym.n_type.bits.is_stab == 0) continue;
183
184 // TODO handle globals N_GSYM, and statics N_STSYM
185 switch (sym.n_type.stab) {
186 .oso => switch (state) {
187 .init, .oso_close => {
188 state = .oso_open;
189 ofile = sym.n_strx;
190 },
191 else => return error.InvalidDebugInfo,
192 },
193 .bnsym => switch (state) {
194 .oso_open, .ensym => {
195 state = .bnsym;
196 last_sym = .{
197 .strx = 0,
198 .addr = sym.n_value,
199 .size = 0,
200 .ofile = ofile,
201 };
202 },
203 else => return error.InvalidDebugInfo,
204 },
205 .fun => switch (state) {
206 .bnsym => {
207 state = .fun_strx;
208 last_sym.strx = sym.n_strx;
209 },
210 .fun_strx => {
211 state = .fun_size;
212 last_sym.size = @intCast(sym.n_value);
213 },
214 else => return error.InvalidDebugInfo,
215 },
216 .ensym => switch (state) {
217 .fun_size => {
218 state = .ensym;
219 symbols.appendAssumeCapacity(last_sym);
220 },
221 else => return error.InvalidDebugInfo,
222 },
223 .so => switch (state) {
224 .init, .oso_close => {},
225 .oso_open, .ensym => {
226 state = .oso_close;
227 },
228 else => return error.InvalidDebugInfo,
229 },
230 else => {},
231 }
232 }
233
234 switch (state) {
235 .init => return error.MissingDebugInfo,
236 .oso_close => {},
237 else => return error.InvalidDebugInfo,
238 }
239
240 const symbols_slice = try symbols.toOwnedSlice(gpa);
241 errdefer gpa.free(symbols_slice);
242
243 // Even though lld emits symbols in ascending order, this debug code
244 // should work for programs linked in any valid way.
245 // This sort is so that we can binary search later.
246 mem.sort(MachoSymbol, symbols_slice, {}, MachoSymbol.addressLessThan);
247
248 di.full = .{
249 .mapped_memory = mapped_mem,
250 .symbols = symbols_slice,
251 .strings = strings,
252 .ofiles = .empty,
253 };
254 }
255 fn loadUnwindInfo(module: *const Module, gpa: Allocator, di: *Module.DebugInfo) !void {
256 _ = gpa;
257
258 const header: *std.macho.mach_header = @ptrFromInt(module.text_base);
259
260 var it: macho.LoadCommandIterator = .{
261 .ncmds = header.ncmds,
262 .buffer = @as([*]u8, @ptrCast(header))[@sizeOf(macho.mach_header_64)..][0..header.sizeofcmds],
263 };
264 const sections = while (it.next()) |load_cmd| {
265 if (load_cmd.cmd() != .SEGMENT_64) continue;
266 const segment_cmd = load_cmd.cast(macho.segment_command_64).?;
267 if (!mem.eql(u8, segment_cmd.segName(), "__TEXT")) continue;
268 break load_cmd.getSections();
269 } else unreachable;
270
271 var unwind_info: ?[]const u8 = null;
272 var eh_frame: ?[]const u8 = null;
273 for (sections) |sect| {
274 if (mem.eql(u8, sect.sectName(), "__unwind_info")) {
275 const sect_ptr: [*]u8 = @ptrFromInt(@as(usize, @intCast(module.load_offset + sect.addr)));
276 unwind_info = sect_ptr[0..@intCast(sect.size)];
277 } else if (mem.eql(u8, sect.sectName(), "__eh_frame")) {
278 const sect_ptr: [*]u8 = @ptrFromInt(@as(usize, @intCast(module.load_offset + sect.addr)));
279 eh_frame = sect_ptr[0..@intCast(sect.size)];
280 }
281 }
282 di.unwind = .{
283 .unwind_info = unwind_info,
284 .eh_frame = eh_frame,
285 };
286 }
287 fn getSymbolAtAddress(module: *const Module, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol {
288 if (di.full == null) try module.loadLocationInfo(gpa, di);
289 const vaddr = address - module.load_offset;
290 const symbol = MachoSymbol.find(di.full.?.symbols, vaddr) orelse return .{
291 .name = null,
292 .compile_unit_name = null,
293 .source_location = null,
294 };
295
296 // offset of `address` from start of `symbol`
297 const address_symbol_offset = vaddr - symbol.addr;
298
299 // Take the symbol name from the N_FUN STAB entry, we're going to
300 // use it if we fail to find the DWARF infos
301 const stab_symbol = mem.sliceTo(di.full.?.strings[symbol.strx..], 0);
302 const o_file_path = mem.sliceTo(di.full.?.strings[symbol.ofile..], 0);
303
304 // If any information is missing, we can at least return this from now on.
305 const sym_only_result: std.debug.Symbol = .{
306 .name = stab_symbol,
307 .compile_unit_name = null,
308 .source_location = null,
309 };
310
311 const o_file: *DebugInfo.OFile = of: {
312 const gop = try di.full.?.ofiles.getOrPut(gpa, o_file_path);
313 if (!gop.found_existing) {
314 gop.value_ptr.* = DebugInfo.loadOFile(gpa, o_file_path) catch |err| {
315 defer _ = di.full.?.ofiles.pop().?;
316 switch (err) {
317 error.MissingDebugInfo,
318 error.InvalidDebugInfo,
319 => return sym_only_result,
320 else => |e| return e,
321 }
322 };
323 }
324 break :of gop.value_ptr;
325 };
326
327 const symbol_ofile_vaddr = o_file.addr_table.get(stab_symbol) orelse return sym_only_result;
328
329 const compile_unit = o_file.dwarf.findCompileUnit(native_endian, symbol_ofile_vaddr) catch |err| switch (err) {
330 error.MissingDebugInfo, error.InvalidDebugInfo => return sym_only_result,
331 else => |e| return e,
332 };
333
334 return .{
335 .name = o_file.dwarf.getSymbolName(symbol_ofile_vaddr) orelse stab_symbol,
336 .compile_unit_name = compile_unit.die.getAttrString(
337 &o_file.dwarf,
338 native_endian,
339 std.dwarf.AT.name,
340 o_file.dwarf.section(.debug_str),
341 compile_unit,
342 ) catch |err| switch (err) {
343 error.MissingDebugInfo, error.InvalidDebugInfo => null,
344 },
345 .source_location = o_file.dwarf.getLineNumberInfo(
346 gpa,
347 native_endian,
348 compile_unit,
349 symbol_ofile_vaddr + address_symbol_offset,
350 ) catch |err| switch (err) {
351 error.MissingDebugInfo, error.InvalidDebugInfo => null,
352 else => return err,
353 },
354 };
355 }
356 fn unwindFrame(module: *const Module, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) !usize {
357 if (di.unwind == null) try module.loadUnwindInfo(gpa, di);
358 const unwind_info = di.unwind.?.unwind_info orelse return error.MissingUnwindInfo;
359 // MLUGG TODO: inline?
360 return unwindFrameMachO(
361 module.text_base,
362 module.load_offset,
363 context,
364 unwind_info,
365 di.unwind.?.eh_frame,
366 );
367 }
368 const LookupCache = void;
369 const DebugInfo = struct {
370 unwind: ?struct {
371 // Backed by the in-memory sections mapped by the loader
372 unwind_info: ?[]const u8,
373 eh_frame: ?[]const u8,
374 },
375 // MLUGG TODO: awful field name
376 full: ?struct {
377 mapped_memory: []align(std.heap.page_size_min) const u8,
378 symbols: []const MachoSymbol,
379 strings: [:0]const u8,
380 // MLUGG TODO: this could use an adapter to just index straight into `strings`!
381 ofiles: std.StringArrayHashMapUnmanaged(OFile),
382 },
383
384 const init: DebugInfo = .{
385 .unwind = null,
386 .full = null,
387 };
388
389 const OFile = struct {
390 dwarf: Dwarf,
391 // MLUGG TODO: this could use an adapter to just index straight into the strtab!
392 addr_table: std.StringArrayHashMapUnmanaged(u64),
393 };
394
395 fn deinit(di: *DebugInfo, gpa: Allocator) void {
396 for (di.full.ofiles.values()) |*ofile| {
397 ofile.dwarf.deinit(gpa);
398 ofile.addr_table.deinit(gpa);
399 }
400 di.full.ofiles.deinit();
401 gpa.free(di.full.symbols);
402 posix.munmap(di.full.mapped_memory);
403 }
404
405 fn loadOFile(gpa: Allocator, o_file_path: []const u8) !OFile {
406 const mapped_mem = try mapDebugInfoFile(o_file_path);
407 errdefer posix.munmap(mapped_mem);
408
409 if (mapped_mem.len < @sizeOf(macho.mach_header_64)) return error.InvalidDebugInfo;
410 const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_mem.ptr));
411 if (hdr.magic != std.macho.MH_MAGIC_64) return error.InvalidDebugInfo;
412
413 const seg_cmd: macho.LoadCommandIterator.LoadCommand, const symtab_cmd: macho.symtab_command = cmds: {
414 var seg_cmd: ?macho.LoadCommandIterator.LoadCommand = null;
415 var symtab_cmd: ?macho.symtab_command = null;
416 var it: macho.LoadCommandIterator = .{
417 .ncmds = hdr.ncmds,
418 .buffer = mapped_mem[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds],
419 };
420 while (it.next()) |cmd| switch (cmd.cmd()) {
421 .SEGMENT_64 => seg_cmd = cmd,
422 .SYMTAB => symtab_cmd = cmd.cast(macho.symtab_command) orelse return error.InvalidDebugInfo,
423 else => {},
424 };
425 break :cmds .{
426 seg_cmd orelse return error.MissingDebugInfo,
427 symtab_cmd orelse return error.MissingDebugInfo,
428 };
429 };
430
431 if (mapped_mem.len < symtab_cmd.stroff + symtab_cmd.strsize) return error.InvalidDebugInfo;
432 if (mapped_mem[symtab_cmd.stroff + symtab_cmd.strsize - 1] != 0) return error.InvalidDebugInfo;
433 const strtab = mapped_mem[symtab_cmd.stroff..][0 .. symtab_cmd.strsize - 1];
434
435 const n_sym_bytes = symtab_cmd.nsyms * @sizeOf(macho.nlist_64);
436 if (mapped_mem.len < symtab_cmd.symoff + n_sym_bytes) return error.InvalidDebugInfo;
437 const symtab: []align(1) const macho.nlist_64 = @ptrCast(mapped_mem[symtab_cmd.symoff..][0..n_sym_bytes]);
438
439 // TODO handle tentative (common) symbols
440 var addr_table: std.StringArrayHashMapUnmanaged(u64) = .empty;
441 defer addr_table.deinit(gpa);
442 try addr_table.ensureUnusedCapacity(gpa, @intCast(symtab.len));
443 for (symtab) |sym| {
444 if (sym.n_strx == 0) continue;
445 switch (sym.n_type.bits.type) {
446 .undf => continue, // includes tentative symbols
447 .abs => continue,
448 else => {},
449 }
450 const sym_name = mem.sliceTo(strtab[sym.n_strx..], 0);
451 const gop = addr_table.getOrPutAssumeCapacity(sym_name);
452 if (gop.found_existing) return error.InvalidDebugInfo;
453 gop.value_ptr.* = sym.n_value;
454 }
455
456 var sections: Dwarf.SectionArray = @splat(null);
457 for (seg_cmd.getSections()) |sect| {
458 if (!std.mem.eql(u8, "__DWARF", sect.segName())) continue;
459
460 const section_index: usize = inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields, 0..) |section, i| {
461 if (mem.eql(u8, "__" ++ section.name, sect.sectName())) break i;
462 } else continue;
463
464 if (mapped_mem.len < sect.offset + sect.size) return error.InvalidDebugInfo;
465 const section_bytes = mapped_mem[sect.offset..][0..sect.size];
466 sections[section_index] = .{
467 .data = section_bytes,
468 .owned = false,
469 };
470 }
471
472 const missing_debug_info =
473 sections[@intFromEnum(Dwarf.Section.Id.debug_info)] == null or
474 sections[@intFromEnum(Dwarf.Section.Id.debug_abbrev)] == null or
475 sections[@intFromEnum(Dwarf.Section.Id.debug_str)] == null or
476 sections[@intFromEnum(Dwarf.Section.Id.debug_line)] == null;
477 if (missing_debug_info) return error.MissingDebugInfo;
478
479 var dwarf: Dwarf = .{ .sections = sections };
480 errdefer dwarf.deinit(gpa);
481 try dwarf.open(gpa, native_endian);
482
483 return .{
484 .dwarf = dwarf,
485 .addr_table = addr_table.move(),
486 };
487 }
488 };
489 },
490 .wasi, .emscripten => struct {129 .wasi, .emscripten => struct {
491 const LookupCache = void;130 const LookupCache = struct {
492 const DebugInfo = struct {131 const init: LookupCache = .{};
493 const init: DebugInfo = .{};
494 };132 };
495 fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) !Module {133 fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) !Module {
496 _ = cache;134 _ = cache;
...@@ -498,6 +136,9 @@ const Module = switch (native_os) {...@@ -498,6 +136,9 @@ const Module = switch (native_os) {
498 _ = address;136 _ = address;
499 @panic("TODO implement lookup module for Wasm");137 @panic("TODO implement lookup module for Wasm");
500 }138 }
139 const DebugInfo = struct {
140 const init: DebugInfo = .{};
141 };
501 fn getSymbolAtAddress(module: *const Module, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol {142 fn getSymbolAtAddress(module: *const Module, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol {
502 _ = module;143 _ = module;
503 _ = gpa;144 _ = gpa;
...@@ -506,430 +147,9 @@ const Module = switch (native_os) {...@@ -506,430 +147,9 @@ const Module = switch (native_os) {
506 unreachable;147 unreachable;
507 }148 }
508 },149 },
509 .linux, .netbsd, .freebsd, .dragonfly, .openbsd, .haiku, .solaris, .illumos => struct {
510 load_offset: usize,
511 name: []const u8,
512 build_id: ?[]const u8,
513 gnu_eh_frame: ?[]const u8,
514 const LookupCache = void;
515 const DebugInfo = struct {
516 loaded_elf: ?Dwarf.ElfModule,
517 unwind: ?Dwarf.Unwind,
518 const init: DebugInfo = .{
519 .loaded_elf = null,
520 .unwind = null,
521 };
522 };
523 fn key(m: Module) usize {
524 return m.load_offset;
525 }
526 fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) !Module {
527 _ = cache;
528 _ = gpa;
529 if (native_os == .haiku) @panic("TODO implement lookup module for Haiku");
530 const DlIterContext = struct {
531 /// input
532 address: usize,
533 /// output
534 module: Module,
535
536 fn callback(info: *posix.dl_phdr_info, size: usize, context: *@This()) !void {
537 _ = size;
538 // The base address is too high
539 if (context.address < info.addr)
540 return;
541
542 const phdrs = info.phdr[0..info.phnum];
543 for (phdrs) |*phdr| {
544 if (phdr.p_type != elf.PT_LOAD) continue;
545
546 // Overflowing addition is used to handle the case of VSDOs having a p_vaddr = 0xffffffffff700000
547 const seg_start = info.addr +% phdr.p_vaddr;
548 const seg_end = seg_start + phdr.p_memsz;
549 if (context.address >= seg_start and context.address < seg_end) {
550 context.module = .{
551 .load_offset = info.addr,
552 // Android libc uses NULL instead of "" to mark the main program
553 .name = mem.sliceTo(info.name, 0) orelse "",
554 .build_id = null,
555 .gnu_eh_frame = null,
556 };
557 break;
558 }
559 } else return;
560
561 for (info.phdr[0..info.phnum]) |phdr| {
562 switch (phdr.p_type) {
563 elf.PT_NOTE => {
564 // Look for .note.gnu.build-id
565 const segment_ptr: [*]const u8 = @ptrFromInt(info.addr + phdr.p_vaddr);
566 var r: std.Io.Reader = .fixed(segment_ptr[0..phdr.p_memsz]);
567 const name_size = r.takeInt(u32, native_endian) catch continue;
568 const desc_size = r.takeInt(u32, native_endian) catch continue;
569 const note_type = r.takeInt(u32, native_endian) catch continue;
570 const name = r.take(name_size) catch continue;
571 if (note_type != elf.NT_GNU_BUILD_ID) continue;
572 if (!mem.eql(u8, name, "GNU\x00")) continue;
573 const desc = r.take(desc_size) catch continue;
574 context.module.build_id = desc;
575 },
576 elf.PT_GNU_EH_FRAME => {
577 const segment_ptr: [*]const u8 = @ptrFromInt(info.addr + phdr.p_vaddr);
578 context.module.gnu_eh_frame = segment_ptr[0..phdr.p_memsz];
579 },
580 else => {},
581 }
582 }
583
584 // Stop the iteration
585 return error.Found;
586 }
587 };
588 var ctx: DlIterContext = .{
589 .address = address,
590 .module = undefined,
591 };
592 posix.dl_iterate_phdr(&ctx, error{Found}, DlIterContext.callback) catch |err| switch (err) {
593 error.Found => return ctx.module,
594 };
595 return error.MissingDebugInfo;
596 }
597 fn loadLocationInfo(module: *const Module, gpa: Allocator, di: *Module.DebugInfo) !void {
598 if (module.name.len > 0) {
599 di.loaded_elf = Dwarf.ElfModule.load(gpa, .{
600 .root_dir = .cwd(),
601 .sub_path = module.name,
602 }, module.build_id, null, null, null) catch |err| switch (err) {
603 error.FileNotFound => return error.MissingDebugInfo,
604 error.Overflow => return error.InvalidDebugInfo,
605 else => |e| return e,
606 };
607 } else {
608 const path = try std.fs.selfExePathAlloc(gpa);
609 defer gpa.free(path);
610 di.loaded_elf = Dwarf.ElfModule.load(gpa, .{
611 .root_dir = .cwd(),
612 .sub_path = path,
613 }, module.build_id, null, null, null) catch |err| switch (err) {
614 error.FileNotFound => return error.MissingDebugInfo,
615 error.Overflow => return error.InvalidDebugInfo,
616 else => |e| return e,
617 };
618 }
619 }
620 fn getSymbolAtAddress(module: *const Module, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol {
621 if (di.loaded_elf == null) try module.loadLocationInfo(gpa, di);
622 const vaddr = address - module.load_offset;
623 return di.loaded_elf.?.dwarf.getSymbol(gpa, native_endian, vaddr);
624 }
625 fn loadUnwindInfo(module: *const Module, gpa: Allocator, di: *Module.DebugInfo) !void {
626 const section_bytes = module.gnu_eh_frame orelse return error.MissingUnwindInfo; // MLUGG TODO: load from file
627 const section_vaddr: u64 = @intFromPtr(section_bytes.ptr) - module.load_offset;
628 const header: Dwarf.Unwind.EhFrameHeader = try .parse(section_vaddr, section_bytes, @sizeOf(usize), native_endian);
629 di.unwind = .initEhFrameHdr(header, section_vaddr, @ptrFromInt(module.load_offset + header.eh_frame_vaddr));
630 try di.unwind.?.prepareLookup(gpa, @sizeOf(usize), native_endian);
631 }
632 fn unwindFrame(module: *const Module, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) !usize {
633 if (di.unwind == null) try module.loadUnwindInfo(gpa, di);
634 return unwindFrameDwarf(&di.unwind.?, module.load_offset, context, null);
635 }
636 },
637 .uefi, .windows => struct {
638 base_address: usize,
639 size: usize,
640 name: []const u8,
641 handle: windows.HMODULE,
642 fn key(m: Module) usize {
643 return m.base_address;
644 }
645 fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) !Module {
646 if (lookupInCache(cache, address)) |m| return m;
647 {
648 // Check a new module hasn't been loaded
649 cache.modules.clearRetainingCapacity();
650
651 const handle = windows.kernel32.CreateToolhelp32Snapshot(windows.TH32CS_SNAPMODULE | windows.TH32CS_SNAPMODULE32, 0);
652 if (handle == windows.INVALID_HANDLE_VALUE) {
653 return windows.unexpectedError(windows.GetLastError());
654 }
655 defer windows.CloseHandle(handle);
656
657 var entry: windows.MODULEENTRY32 = undefined;
658 entry.dwSize = @sizeOf(windows.MODULEENTRY32);
659 if (windows.kernel32.Module32First(handle, &entry) != 0) {
660 try cache.modules.append(gpa, entry);
661 while (windows.kernel32.Module32Next(handle, &entry) != 0) {
662 try cache.modules.append(gpa, entry);
663 }
664 }
665 }
666 if (lookupInCache(cache, address)) |m| return m;
667 return error.MissingDebugInfo;
668 }
669 fn lookupInCache(cache: *const LookupCache, address: usize) ?Module {
670 for (cache.modules.items) |*entry| {
671 const base_address = @intFromPtr(entry.modBaseAddr);
672 if (address >= base_address and address < base_address + entry.modBaseSize) {
673 return .{
674 .base_address = base_address,
675 .size = entry.modBaseSize,
676 .name = std.mem.sliceTo(&entry.szModule, 0),
677 .handle = entry.hModule,
678 };
679 }
680 }
681 return null;
682 }
683 fn loadLocationInfo(module: *const Module, gpa: Allocator, di: *DebugInfo) !void {
684 const mapped_ptr: [*]const u8 = @ptrFromInt(module.base_address);
685 const mapped = mapped_ptr[0..module.size];
686 var coff_obj = coff.Coff.init(mapped, true) catch return error.InvalidDebugInfo;
687 // The string table is not mapped into memory by the loader, so if a section name is in the
688 // string table then we have to map the full image file from disk. This can happen when
689 // a binary is produced with -gdwarf, since the section names are longer than 8 bytes.
690 if (coff_obj.strtabRequired()) {
691 var name_buffer: [windows.PATH_MAX_WIDE + 4:0]u16 = undefined;
692 name_buffer[0..4].* = .{ '\\', '?', '?', '\\' }; // openFileAbsoluteW requires the prefix to be present
693 const process_handle = windows.GetCurrentProcess();
694 const len = windows.kernel32.GetModuleFileNameExW(
695 process_handle,
696 module.handle,
697 name_buffer[4..],
698 windows.PATH_MAX_WIDE,
699 );
700 if (len == 0) return error.MissingDebugInfo;
701 const coff_file = fs.openFileAbsoluteW(name_buffer[0 .. len + 4 :0], .{}) catch |err| switch (err) {
702 error.FileNotFound => return error.MissingDebugInfo,
703 else => |e| return e,
704 };
705 errdefer coff_file.close();
706 var section_handle: windows.HANDLE = undefined;
707 const create_section_rc = windows.ntdll.NtCreateSection(
708 &section_handle,
709 windows.STANDARD_RIGHTS_REQUIRED | windows.SECTION_QUERY | windows.SECTION_MAP_READ,
710 null,
711 null,
712 windows.PAGE_READONLY,
713 // The documentation states that if no AllocationAttribute is specified, then SEC_COMMIT is the default.
714 // In practice, this isn't the case and specifying 0 will result in INVALID_PARAMETER_6.
715 windows.SEC_COMMIT,
716 coff_file.handle,
717 );
718 if (create_section_rc != .SUCCESS) return error.MissingDebugInfo;
719 errdefer windows.CloseHandle(section_handle);
720 var coff_len: usize = 0;
721 var section_view_ptr: [*]const u8 = undefined;
722 const map_section_rc = windows.ntdll.NtMapViewOfSection(
723 section_handle,
724 process_handle,
725 @ptrCast(&section_view_ptr),
726 null,
727 0,
728 null,
729 &coff_len,
730 .ViewUnmap,
731 0,
732 windows.PAGE_READONLY,
733 );
734 if (map_section_rc != .SUCCESS) return error.MissingDebugInfo;
735 errdefer assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @constCast(section_view_ptr)) == .SUCCESS);
736 const section_view = section_view_ptr[0..coff_len];
737 coff_obj = coff.Coff.init(section_view, false) catch return error.InvalidDebugInfo;
738 di.mapped_file = .{
739 .file = coff_file,
740 .section_handle = section_handle,
741 .section_view = section_view,
742 };
743 }
744 di.coff_image_base = coff_obj.getImageBase();
745
746 if (coff_obj.getSectionByName(".debug_info")) |_| {
747 di.dwarf = .{};
748
749 inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields, 0..) |section, i| {
750 di.dwarf.?.sections[i] = if (coff_obj.getSectionByName("." ++ section.name)) |section_header| blk: {
751 break :blk .{
752 .data = try coff_obj.getSectionDataAlloc(section_header, gpa),
753 .owned = true,
754 };
755 } else null;
756 }
757
758 try di.dwarf.?.open(gpa, native_endian);
759 }
760
761 if (try coff_obj.getPdbPath()) |raw_path| pdb: {
762 const path = blk: {
763 if (fs.path.isAbsolute(raw_path)) {
764 break :blk raw_path;
765 } else {
766 const self_dir = try fs.selfExeDirPathAlloc(gpa);
767 defer gpa.free(self_dir);
768 break :blk try fs.path.join(gpa, &.{ self_dir, raw_path });
769 }
770 };
771 defer if (path.ptr != raw_path.ptr) gpa.free(path);
772
773 di.pdb = Pdb.init(gpa, path) catch |err| switch (err) {
774 error.FileNotFound, error.IsDir => break :pdb,
775 else => return err,
776 };
777 try di.pdb.?.parseInfoStream();
778 try di.pdb.?.parseDbiStream();
779
780 if (!mem.eql(u8, &coff_obj.guid, &di.pdb.?.guid) or coff_obj.age != di.pdb.?.age)
781 return error.InvalidDebugInfo;
782
783 di.coff_section_headers = try coff_obj.getSectionHeadersAlloc(gpa);
784 }
785
786 di.loaded = true;
787 }
788 const LookupCache = struct {
789 modules: std.ArrayListUnmanaged(windows.MODULEENTRY32),
790 const init: LookupCache = .{ .modules = .empty };
791 };
792 const DebugInfo = struct {
793 loaded: bool,
794
795 coff_image_base: u64,
796 mapped_file: ?struct {
797 file: File,
798 section_handle: windows.HANDLE,
799 section_view: []const u8,
800 fn deinit(mapped: @This()) void {
801 const process_handle = windows.GetCurrentProcess();
802 assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @constCast(mapped.section_view.ptr)) == .SUCCESS);
803 windows.CloseHandle(mapped.section_handle);
804 mapped.file.close();
805 }
806 },
807
808 dwarf: ?Dwarf,
809
810 pdb: ?Pdb,
811 /// Populated iff `pdb != null`; otherwise `&.{}`.
812 coff_section_headers: []coff.SectionHeader,
813
814 const init: DebugInfo = .{
815 .loaded = false,
816 .coff_image_base = undefined,
817 .mapped_file = null,
818 .dwarf = null,
819 .pdb = null,
820 .coff_section_headers = &.{},
821 };
822
823 fn deinit(di: *DebugInfo, gpa: Allocator) void {
824 if (di.dwarf) |*dwarf| dwarf.deinit(gpa);
825 if (di.pdb) |*pdb| pdb.deinit();
826 gpa.free(di.coff_section_headers);
827 if (di.mapped_file) |mapped| mapped.deinit();
828 }
829
830 fn getSymbolFromPdb(di: *DebugInfo, relocated_address: usize) !?std.debug.Symbol {
831 var coff_section: *align(1) const coff.SectionHeader = undefined;
832 const mod_index = for (di.pdb.?.sect_contribs) |sect_contrib| {
833 if (sect_contrib.section > di.coff_section_headers.len) continue;
834 // Remember that SectionContribEntry.Section is 1-based.
835 coff_section = &di.coff_section_headers[sect_contrib.section - 1];
836
837 const vaddr_start = coff_section.virtual_address + sect_contrib.offset;
838 const vaddr_end = vaddr_start + sect_contrib.size;
839 if (relocated_address >= vaddr_start and relocated_address < vaddr_end) {
840 break sect_contrib.module_index;
841 }
842 } else {
843 // we have no information to add to the address
844 return null;
845 };
846
847 const module = try di.pdb.?.getModule(mod_index) orelse return error.InvalidDebugInfo;
848
849 return .{
850 .name = di.pdb.?.getSymbolName(
851 module,
852 relocated_address - coff_section.virtual_address,
853 ),
854 .compile_unit_name = fs.path.basename(module.obj_file_name),
855 .source_location = try di.pdb.?.getLineNumberInfo(
856 module,
857 relocated_address - coff_section.virtual_address,
858 ),
859 };
860 }
861 };
862
863 fn getSymbolAtAddress(module: *const Module, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol {
864 if (!di.loaded) try module.loadLocationInfo(gpa, di);
865 // Translate the runtime address into a virtual address into the module
866 const vaddr = address - module.base_address;
867
868 if (di.pdb != null) {
869 if (try di.getSymbolFromPdb(vaddr)) |symbol| return symbol;
870 }
871
872 if (di.dwarf) |*dwarf| {
873 const dwarf_address = vaddr + di.coff_image_base;
874 return dwarf.getSymbol(gpa, native_endian, dwarf_address);
875 }
876
877 return error.MissingDebugInfo;
878 }
879 },
880};
881
882const MachoSymbol = struct {
883 strx: u32,
884 addr: u64,
885 size: u32,
886 ofile: u32,
887 fn addressLessThan(context: void, lhs: MachoSymbol, rhs: MachoSymbol) bool {
888 _ = context;
889 return lhs.addr < rhs.addr;
890 }
891 /// Assumes that `symbols` is sorted in order of ascending `addr`.
892 fn find(symbols: []const MachoSymbol, address: usize) ?*const MachoSymbol {
893 if (symbols.len == 0) return null; // no potential match
894 if (address < symbols[0].addr) return null; // address is before the lowest-address symbol
895 var left: usize = 0;
896 var len: usize = symbols.len;
897 while (len > 1) {
898 const mid = left + len / 2;
899 if (address < symbols[mid].addr) {
900 len /= 2;
901 } else {
902 left = mid;
903 len -= len / 2;
904 }
905 }
906 return &symbols[left];
907 }
908
909 test find {
910 const symbols: []const MachoSymbol = &.{
911 .{ .addr = 100, .strx = undefined, .size = undefined, .ofile = undefined },
912 .{ .addr = 200, .strx = undefined, .size = undefined, .ofile = undefined },
913 .{ .addr = 300, .strx = undefined, .size = undefined, .ofile = undefined },
914 };
915
916 try testing.expectEqual(null, find(symbols, 0));
917 try testing.expectEqual(null, find(symbols, 99));
918 try testing.expectEqual(&symbols[0], find(symbols, 100).?);
919 try testing.expectEqual(&symbols[0], find(symbols, 150).?);
920 try testing.expectEqual(&symbols[0], find(symbols, 199).?);
921
922 try testing.expectEqual(&symbols[1], find(symbols, 200).?);
923 try testing.expectEqual(&symbols[1], find(symbols, 250).?);
924 try testing.expectEqual(&symbols[1], find(symbols, 299).?);
925
926 try testing.expectEqual(&symbols[2], find(symbols, 300).?);
927 try testing.expectEqual(&symbols[2], find(symbols, 301).?);
928 try testing.expectEqual(&symbols[2], find(symbols, 5000).?);
929 }
930};150};
931test {151test {
932 _ = MachoSymbol;152 _ = Module;
933}153}
934154
935pub const UnwindContext = struct {155pub const UnwindContext = struct {
...@@ -944,6 +164,7 @@ pub const UnwindContext = struct {...@@ -944,6 +164,7 @@ pub const UnwindContext = struct {
944 pub fn init(gpa: Allocator, thread_context: *std.debug.ThreadContext) !UnwindContext {164 pub fn init(gpa: Allocator, thread_context: *std.debug.ThreadContext) !UnwindContext {
945 comptime assert(supports_unwinding);165 comptime assert(supports_unwinding);
946166
167 const ip_reg_num = Dwarf.abi.ipRegNum(native_arch).?;
947 const pc = stripInstructionPtrAuthCode(168 const pc = stripInstructionPtrAuthCode(
948 (try regValueNative(thread_context, ip_reg_num, null)).*,169 (try regValueNative(thread_context, ip_reg_num, null)).*,
949 );170 );
...@@ -970,7 +191,7 @@ pub const UnwindContext = struct {...@@ -970,7 +191,7 @@ pub const UnwindContext = struct {
970 }191 }
971192
972 pub fn getFp(self: *const UnwindContext) !usize {193 pub fn getFp(self: *const UnwindContext) !usize {
973 return (try regValueNative(self.thread_context, fpRegNum(self.reg_context), self.reg_context)).*;194 return (try regValueNative(self.thread_context, Dwarf.abi.fpRegNum(native_arch, self.reg_context), self.reg_context)).*;
974 }195 }
975196
976 /// Resolves the register rule and places the result into `out` (see regBytes)197 /// Resolves the register rule and places the result into `out` (see regBytes)
...@@ -1019,7 +240,7 @@ pub const UnwindContext = struct {...@@ -1019,7 +240,7 @@ pub const UnwindContext = struct {
1019 .register => |register| {240 .register => |register| {
1020 const src = try regBytes(context.thread_context, register, context.reg_context);241 const src = try regBytes(context.thread_context, register, context.reg_context);
1021 if (src.len != out.len) return error.RegisterSizeMismatch;242 if (src.len != out.len) return error.RegisterSizeMismatch;
1022 @memcpy(out, try regBytes(context.thread_context, register, context.reg_context));243 @memcpy(out, src);
1023 },244 },
1024 .expression => |expression| {245 .expression => |expression| {
1025 context.stack_machine.reset();246 context.stack_machine.reset();
...@@ -1043,553 +264,171 @@ pub const UnwindContext = struct {...@@ -1043,553 +264,171 @@ pub const UnwindContext = struct {
1043 .architectural => return error.UnimplementedRegisterRule,264 .architectural => return error.UnimplementedRegisterRule,
1044 }265 }
1045 }266 }
1046};
1047
1048/// Some platforms use pointer authentication - the upper bits of instruction pointers contain a signature.
1049/// This function clears these signature bits to make the pointer usable.
1050pub inline fn stripInstructionPtrAuthCode(ptr: usize) usize {
1051 if (native_arch.isAARCH64()) {
1052 // `hint 0x07` maps to `xpaclri` (or `nop` if the hardware doesn't support it)
1053 // The save / restore is because `xpaclri` operates on x30 (LR)
1054 return asm (
1055 \\mov x16, x30
1056 \\mov x30, x15
1057 \\hint 0x07
1058 \\mov x15, x30
1059 \\mov x30, x16
1060 : [ret] "={x15}" (-> usize),
1061 : [ptr] "{x15}" (ptr),
1062 : .{ .x16 = true });
1063 }
1064
1065 return ptr;
1066}
1067
1068/// Unwind a stack frame using DWARF unwinding info, updating the register context.
1069///
1070/// If `.eh_frame_hdr` is available and complete, it will be used to binary search for the FDE.
1071/// Otherwise, a linear scan of `.eh_frame` and `.debug_frame` is done to find the FDE. The latter
1072/// may require lazily loading the data in those sections.
1073///
1074/// `explicit_fde_offset` is for cases where the FDE offset is known, such as when __unwind_info
1075/// defers unwinding to DWARF. This is an offset into the `.eh_frame` section.
1076fn unwindFrameDwarf(
1077 unwind: *const Dwarf.Unwind,
1078 load_offset: usize,
1079 context: *UnwindContext,
1080 explicit_fde_offset: ?usize,
1081) !usize {
1082 if (!supports_unwinding) return error.UnsupportedCpuArchitecture;
1083 if (context.pc == 0) return 0;
1084
1085 const pc_vaddr = context.pc - load_offset;
1086267
1087 const fde_offset = explicit_fde_offset orelse try unwind.lookupPc(268 /// Unwind a stack frame using DWARF unwinding info, updating the register context.
1088 pc_vaddr,269 ///
1089 @sizeOf(usize),270 /// If `.eh_frame_hdr` is available and complete, it will be used to binary search for the FDE.
1090 native_endian,271 /// Otherwise, a linear scan of `.eh_frame` and `.debug_frame` is done to find the FDE. The latter
1091 ) orelse return error.MissingDebugInfo;272 /// may require lazily loading the data in those sections.
1092 const format, const cie, const fde = try unwind.getFde(fde_offset, @sizeOf(usize), native_endian);273 ///
274 /// `explicit_fde_offset` is for cases where the FDE offset is known, such as when __unwind_info
275 /// defers unwinding to DWARF. This is an offset into the `.eh_frame` section.
276 pub fn unwindFrameDwarf(
277 context: *UnwindContext,
278 unwind: *const Dwarf.Unwind,
279 load_offset: usize,
280 explicit_fde_offset: ?usize,
281 ) !usize {
282 if (!supports_unwinding) return error.UnsupportedCpuArchitecture;
283 if (context.pc == 0) return 0;
284
285 const pc_vaddr = context.pc - load_offset;
286
287 const fde_offset = explicit_fde_offset orelse try unwind.lookupPc(
288 pc_vaddr,
289 @sizeOf(usize),
290 native_endian,
291 ) orelse return error.MissingDebugInfo;
292 const format, const cie, const fde = try unwind.getFde(fde_offset, @sizeOf(usize), native_endian);
293
294 // Check if this FDE *actually* includes the address.
295 if (pc_vaddr < fde.pc_begin or pc_vaddr >= fde.pc_begin + fde.pc_range) return error.MissingDebugInfo;
296
297 // Do not set `compile_unit` because the spec states that CFIs
298 // may not reference other debug sections anyway.
299 var expression_context: Dwarf.expression.Context = .{
300 .format = format,
301 .thread_context = context.thread_context,
302 .reg_context = context.reg_context,
303 .cfa = context.cfa,
304 };
1093305
1094 // Check if this FDE *actually* includes the address.306 context.vm.reset();
1095 if (pc_vaddr < fde.pc_begin or pc_vaddr >= fde.pc_begin + fde.pc_range) return error.MissingDebugInfo;307 context.reg_context.eh_frame = cie.version != 4;
308 context.reg_context.is_macho = native_os.isDarwin();
1096309
1097 // Do not set `compile_unit` because the spec states that CFIs310 const row = try context.vm.runTo(context.gpa, context.pc - load_offset, cie, fde, @sizeOf(usize), native_endian);
1098 // may not reference other debug sections anyway.311 context.cfa = switch (row.cfa.rule) {
1099 var expression_context: Dwarf.expression.Context = .{312 .val_offset => |offset| blk: {
1100 .format = format,313 const register = row.cfa.register orelse return error.InvalidCFARule;
1101 .thread_context = context.thread_context,314 const value = (try regValueNative(context.thread_context, register, context.reg_context)).*;
1102 .reg_context = context.reg_context,315 break :blk try applyOffset(value, offset);
1103 .cfa = context.cfa,316 },
1104 };317 .expression => |expr| blk: {
318 context.stack_machine.reset();
319 const value = try context.stack_machine.run(
320 expr,
321 context.gpa,
322 expression_context,
323 context.cfa,
324 );
1105325
1106 context.vm.reset();326 if (value) |v| {
1107 context.reg_context.eh_frame = cie.version != 4;327 if (v != .generic) return error.InvalidExpressionValue;
1108 context.reg_context.is_macho = native_os.isDarwin();328 break :blk v.generic;
329 } else return error.NoExpressionValue;
330 },
331 else => return error.InvalidCFARule,
332 };
1109333
1110 const row = try context.vm.runTo(context.gpa, context.pc - load_offset, cie, fde, @sizeOf(usize), native_endian);334 expression_context.cfa = context.cfa;
1111 context.cfa = switch (row.cfa.rule) {
1112 .val_offset => |offset| blk: {
1113 const register = row.cfa.register orelse return error.InvalidCFARule;
1114 const value = mem.readInt(usize, (try regBytes(context.thread_context, register, context.reg_context))[0..@sizeOf(usize)], native_endian);
1115 break :blk try applyOffset(value, offset);
1116 },
1117 .expression => |expr| blk: {
1118 context.stack_machine.reset();
1119 const value = try context.stack_machine.run(
1120 expr,
1121 context.gpa,
1122 expression_context,
1123 context.cfa,
1124 );
1125335
1126 if (value) |v| {336 // Buffering the modifications is done because copying the thread context is not portable,
1127 if (v != .generic) return error.InvalidExpressionValue;337 // some implementations (ie. darwin) use internal pointers to the mcontext.
1128 break :blk v.generic;338 var arena: std.heap.ArenaAllocator = .init(context.gpa);
1129 } else return error.NoExpressionValue;339 defer arena.deinit();
1130 },340 const update_arena = arena.allocator();
1131 else => return error.InvalidCFARule,
1132 };
1133341
1134 expression_context.cfa = context.cfa;342 const RegisterUpdate = struct {
343 // Backed by thread_context
344 dest: []u8,
345 // Backed by arena
346 src: []const u8,
347 prev: ?*@This(),
348 };
1135349
1136 // Buffering the modifications is done because copying the thread context is not portable,350 var update_tail: ?*RegisterUpdate = null;
1137 // some implementations (ie. darwin) use internal pointers to the mcontext.351 var has_return_address = true;
1138 var arena: std.heap.ArenaAllocator = .init(context.gpa);352 for (context.vm.rowColumns(row)) |column| {
1139 defer arena.deinit();353 if (column.register) |register| {
1140 const update_arena = arena.allocator();354 if (register == cie.return_address_register) {
355 has_return_address = column.rule != .undefined;
356 }
1141357
1142 const RegisterUpdate = struct {358 const dest = try regBytes(context.thread_context, register, context.reg_context);
1143 // Backed by thread_context359 const src = try update_arena.alloc(u8, dest.len);
1144 dest: []u8,360 try context.resolveRegisterRule(column, expression_context, src);
1145 // Backed by arena
1146 src: []const u8,
1147 prev: ?*@This(),
1148 };
1149361
1150 var update_tail: ?*RegisterUpdate = null;362 const new_update = try update_arena.create(RegisterUpdate);
1151 var has_return_address = true;363 new_update.* = .{
1152 for (context.vm.rowColumns(row)) |column| {364 .dest = dest,
1153 if (column.register) |register| {365 .src = src,
1154 if (register == cie.return_address_register) {366 .prev = update_tail,
1155 has_return_address = column.rule != .undefined;367 };
368 update_tail = new_update;
1156 }369 }
370 }
1157371
1158 const dest = try regBytes(context.thread_context, register, context.reg_context);372 // On all implemented architectures, the CFA is defined as being the previous frame's SP
1159 const src = try update_arena.alloc(u8, dest.len);373 (try regValueNative(context.thread_context, Dwarf.abi.spRegNum(native_arch, context.reg_context), context.reg_context)).* = context.cfa.?;
1160 try context.resolveRegisterRule(column, expression_context, src);
1161374
1162 const new_update = try update_arena.create(RegisterUpdate);375 while (update_tail) |tail| {
1163 new_update.* = .{376 @memcpy(tail.dest, tail.src);
1164 .dest = dest,377 update_tail = tail.prev;
1165 .src = src,
1166 .prev = update_tail,
1167 };
1168 update_tail = new_update;
1169 }378 }
1170 }
1171379
1172 // On all implemented architectures, the CFA is defined as being the previous frame's SP380 if (has_return_address) {
1173 (try regValueNative(context.thread_context, spRegNum(context.reg_context), context.reg_context)).* = context.cfa.?;381 context.pc = stripInstructionPtrAuthCode((try regValueNative(
382 context.thread_context,
383 cie.return_address_register,
384 context.reg_context,
385 )).*);
386 } else {
387 context.pc = 0;
388 }
1174389
1175 while (update_tail) |tail| {390 const ip_reg_num = Dwarf.abi.ipRegNum(native_arch).?;
1176 @memcpy(tail.dest, tail.src);391 (try regValueNative(context.thread_context, ip_reg_num, context.reg_context)).* = context.pc;
1177 update_tail = tail.prev;392
393 // The call instruction will have pushed the address of the instruction that follows the call as the return address.
394 // This next instruction may be past the end of the function if the caller was `noreturn` (ie. the last instruction in
395 // the function was the call). If we were to look up an FDE entry using the return address directly, it could end up
396 // either not finding an FDE at all, or using the next FDE in the program, producing incorrect results. To prevent this,
397 // we subtract one so that the next lookup is guaranteed to land inside the
398 //
399 // The exception to this rule is signal frames, where we return execution would be returned to the instruction
400 // that triggered the handler.
401 const return_address = context.pc;
402 if (context.pc > 0 and !cie.is_signal_frame) context.pc -= 1;
403
404 return return_address;
1178 }405 }
1179406 /// Since register rules are applied (usually) during a panic,
1180 if (has_return_address) {407 /// checked addition / subtraction is used so that we can return
1181 context.pc = stripInstructionPtrAuthCode(mem.readInt(usize, (try regBytes(408 /// an error and fall back to FP-based unwinding.
1182 context.thread_context,409 fn applyOffset(base: usize, offset: i64) !usize {
1183 cie.return_address_register,410 return if (offset >= 0)
1184 context.reg_context,411 try std.math.add(usize, base, @as(usize, @intCast(offset)))
1185 ))[0..@sizeOf(usize)], native_endian));412 else
1186 } else {413 try std.math.sub(usize, base, @as(usize, @intCast(-offset)));
1187 context.pc = 0;
1188 }414 }
1189415 /// Some platforms use pointer authentication - the upper bits of instruction pointers contain a signature.
1190 (try regValueNative(context.thread_context, ip_reg_num, context.reg_context)).* = context.pc;416 /// This function clears these signature bits to make the pointer usable.
1191417 pub inline fn stripInstructionPtrAuthCode(ptr: usize) usize {
1192 // The call instruction will have pushed the address of the instruction that follows the call as the return address.418 if (native_arch.isAARCH64()) {
1193 // This next instruction may be past the end of the function if the caller was `noreturn` (ie. the last instruction in419 // `hint 0x07` maps to `xpaclri` (or `nop` if the hardware doesn't support it)
1194 // the function was the call). If we were to look up an FDE entry using the return address directly, it could end up420 // The save / restore is because `xpaclri` operates on x30 (LR)
1195 // either not finding an FDE at all, or using the next FDE in the program, producing incorrect results. To prevent this,421 return asm (
1196 // we subtract one so that the next lookup is guaranteed to land inside the422 \\mov x16, x30
1197 //423 \\mov x30, x15
1198 // The exception to this rule is signal frames, where we return execution would be returned to the instruction424 \\hint 0x07
1199 // that triggered the handler.425 \\mov x15, x30
1200 const return_address = context.pc;426 \\mov x30, x16
1201 if (context.pc > 0 and !cie.is_signal_frame) context.pc -= 1;427 : [ret] "={x15}" (-> usize),
1202428 : [ptr] "{x15}" (ptr),
1203 return return_address;429 : .{ .x16 = true });
1204}
1205
1206fn fpRegNum(reg_context: Dwarf.abi.RegisterContext) u8 {
1207 return Dwarf.abi.fpRegNum(native_arch, reg_context);
1208}
1209
1210fn spRegNum(reg_context: Dwarf.abi.RegisterContext) u8 {
1211 return Dwarf.abi.spRegNum(native_arch, reg_context);
1212}
1213
1214const ip_reg_num = Dwarf.abi.ipRegNum(native_arch).?;
1215
1216/// Tells whether unwinding for the host is implemented.
1217pub const supports_unwinding = supportsUnwinding(&builtin.target);
1218
1219comptime {
1220 if (supports_unwinding) assert(Dwarf.abi.supportsUnwinding(&builtin.target));
1221}
1222
1223/// Tells whether unwinding for this target is *implemented* here in the Zig
1224/// standard library.
1225///
1226/// See also `Dwarf.abi.supportsUnwinding` which tells whether Dwarf supports
1227/// unwinding on that target *in theory*.
1228pub fn supportsUnwinding(target: *const std.Target) bool {
1229 return switch (target.cpu.arch) {
1230 .x86 => switch (target.os.tag) {
1231 .linux, .netbsd, .solaris, .illumos => true,
1232 else => false,
1233 },
1234 .x86_64 => switch (target.os.tag) {
1235 .linux, .netbsd, .freebsd, .openbsd, .macos, .ios, .solaris, .illumos => true,
1236 else => false,
1237 },
1238 .arm, .armeb, .thumb, .thumbeb => switch (target.os.tag) {
1239 .linux => true,
1240 else => false,
1241 },
1242 .aarch64, .aarch64_be => switch (target.os.tag) {
1243 .linux, .netbsd, .freebsd, .macos, .ios => true,
1244 else => false,
1245 },
1246 // Unwinding is possible on other targets but this implementation does
1247 // not support them...yet!
1248 else => false,
1249 };
1250}
1251
1252/// Since register rules are applied (usually) during a panic,
1253/// checked addition / subtraction is used so that we can return
1254/// an error and fall back to FP-based unwinding.
1255fn applyOffset(base: usize, offset: i64) !usize {
1256 return if (offset >= 0)
1257 try std.math.add(usize, base, @as(usize, @intCast(offset)))
1258 else
1259 try std.math.sub(usize, base, @as(usize, @intCast(-offset)));
1260}
1261
1262/// Uses `mmap` to map the file at `opt_path` (or, if `null`, the self executable image) into memory.
1263fn mapDebugInfoFile(opt_path: ?[]const u8) ![]align(std.heap.page_size_min) const u8 {
1264 const open_result = if (opt_path) |path|
1265 fs.cwd().openFile(path, .{})
1266 else
1267 fs.openSelfExe(.{});
1268 const file = open_result catch |err| switch (err) {
1269 error.FileNotFound => return error.MissingDebugInfo,
1270 else => |e| return e,
1271 };
1272 defer file.close();
1273
1274 const file_len = math.cast(usize, try file.getEndPos()) orelse return error.InvalidDebugInfo;
1275
1276 return posix.mmap(
1277 null,
1278 file_len,
1279 posix.PROT.READ,
1280 .{ .TYPE = .SHARED },
1281 file.handle,
1282 0,
1283 );
1284}
1285
1286/// Unwind a frame using MachO compact unwind info (from __unwind_info).
1287/// If the compact encoding can't encode a way to unwind a frame, it will
1288/// defer unwinding to DWARF, in which case `.eh_frame` will be used if available.
1289fn unwindFrameMachO(
1290 text_base: usize,
1291 load_offset: usize,
1292 context: *UnwindContext,
1293 unwind_info: []const u8,
1294 opt_eh_frame: ?[]const u8,
1295) !usize {
1296 if (unwind_info.len < @sizeOf(macho.unwind_info_section_header)) return error.InvalidUnwindInfo;
1297 const header: *align(1) const macho.unwind_info_section_header = @ptrCast(unwind_info);
1298
1299 const index_byte_count = header.indexCount * @sizeOf(macho.unwind_info_section_header_index_entry);
1300 if (unwind_info.len < header.indexSectionOffset + index_byte_count) return error.InvalidUnwindInfo;
1301 const indices: []align(1) const macho.unwind_info_section_header_index_entry = @ptrCast(unwind_info[header.indexSectionOffset..][0..index_byte_count]);
1302 if (indices.len == 0) return error.MissingUnwindInfo;
1303
1304 // offset of the PC into the `__TEXT` segment
1305 const pc_text_offset = context.pc - text_base;
1306
1307 const start_offset: u32, const first_level_offset: u32 = index: {
1308 var left: usize = 0;
1309 var len: usize = indices.len;
1310 while (len > 1) {
1311 const mid = left + len / 2;
1312 if (pc_text_offset < indices[mid].functionOffset) {
1313 len /= 2;
1314 } else {
1315 left = mid;
1316 len -= len / 2;
1317 }
1318 }430 }
1319 break :index .{ indices[left].secondLevelPagesSectionOffset, indices[left].functionOffset };
1320 };
1321 // An offset of 0 is a sentinel indicating a range does not have unwind info.
1322 if (start_offset == 0) return error.MissingUnwindInfo;
1323
1324 const common_encodings_byte_count = header.commonEncodingsArrayCount * @sizeOf(macho.compact_unwind_encoding_t);
1325 if (unwind_info.len < header.commonEncodingsArraySectionOffset + common_encodings_byte_count) return error.InvalidUnwindInfo;
1326 const common_encodings: []align(1) const macho.compact_unwind_encoding_t = @ptrCast(
1327 unwind_info[header.commonEncodingsArraySectionOffset..][0..common_encodings_byte_count],
1328 );
1329
1330 if (unwind_info.len < start_offset + @sizeOf(macho.UNWIND_SECOND_LEVEL)) return error.InvalidUnwindInfo;
1331 const kind: *align(1) const macho.UNWIND_SECOND_LEVEL = @ptrCast(unwind_info[start_offset..]);
1332
1333 const entry: struct {
1334 function_offset: usize,
1335 raw_encoding: u32,
1336 } = switch (kind.*) {
1337 .REGULAR => entry: {
1338 if (unwind_info.len < start_offset + @sizeOf(macho.unwind_info_regular_second_level_page_header)) return error.InvalidUnwindInfo;
1339 const page_header: *align(1) const macho.unwind_info_regular_second_level_page_header = @ptrCast(unwind_info[start_offset..]);
1340
1341 const entries_byte_count = page_header.entryCount * @sizeOf(macho.unwind_info_regular_second_level_entry);
1342 if (unwind_info.len < start_offset + entries_byte_count) return error.InvalidUnwindInfo;
1343 const entries: []align(1) const macho.unwind_info_regular_second_level_entry = @ptrCast(
1344 unwind_info[start_offset + page_header.entryPageOffset ..][0..entries_byte_count],
1345 );
1346 if (entries.len == 0) return error.InvalidUnwindInfo;
1347
1348 var left: usize = 0;
1349 var len: usize = entries.len;
1350 while (len > 1) {
1351 const mid = left + len / 2;
1352 if (pc_text_offset < entries[mid].functionOffset) {
1353 len /= 2;
1354 } else {
1355 left = mid;
1356 len -= len / 2;
1357 }
1358 }
1359 break :entry .{
1360 .function_offset = entries[left].functionOffset,
1361 .raw_encoding = entries[left].encoding,
1362 };
1363 },
1364 .COMPRESSED => entry: {
1365 if (unwind_info.len < start_offset + @sizeOf(macho.unwind_info_compressed_second_level_page_header)) return error.InvalidUnwindInfo;
1366 const page_header: *align(1) const macho.unwind_info_compressed_second_level_page_header = @ptrCast(unwind_info[start_offset..]);
1367
1368 const entries_byte_count = page_header.entryCount * @sizeOf(macho.UnwindInfoCompressedEntry);
1369 if (unwind_info.len < start_offset + entries_byte_count) return error.InvalidUnwindInfo;
1370 const entries: []align(1) const macho.UnwindInfoCompressedEntry = @ptrCast(
1371 unwind_info[start_offset + page_header.entryPageOffset ..][0..entries_byte_count],
1372 );
1373 if (entries.len == 0) return error.InvalidUnwindInfo;
1374
1375 var left: usize = 0;
1376 var len: usize = entries.len;
1377 while (len > 1) {
1378 const mid = left + len / 2;
1379 if (pc_text_offset < first_level_offset + entries[mid].funcOffset) {
1380 len /= 2;
1381 } else {
1382 left = mid;
1383 len -= len / 2;
1384 }
1385 }
1386 const entry = entries[left];
1387
1388 const function_offset = first_level_offset + entry.funcOffset;
1389 if (entry.encodingIndex < common_encodings.len) {
1390 break :entry .{
1391 .function_offset = function_offset,
1392 .raw_encoding = common_encodings[entry.encodingIndex],
1393 };
1394 }
1395
1396 const local_index = entry.encodingIndex - common_encodings.len;
1397 const local_encodings_byte_count = page_header.encodingsCount * @sizeOf(macho.compact_unwind_encoding_t);
1398 if (unwind_info.len < start_offset + page_header.encodingsPageOffset + local_encodings_byte_count) return error.InvalidUnwindInfo;
1399 const local_encodings: []align(1) const macho.compact_unwind_encoding_t = @ptrCast(
1400 unwind_info[start_offset + page_header.encodingsPageOffset ..][0..local_encodings_byte_count],
1401 );
1402 if (local_index >= local_encodings.len) return error.InvalidUnwindInfo;
1403 break :entry .{
1404 .function_offset = function_offset,
1405 .raw_encoding = local_encodings[local_index],
1406 };
1407 },
1408 else => return error.InvalidUnwindInfo,
1409 };
1410
1411 if (entry.raw_encoding == 0) return error.NoUnwindInfo;
1412 const reg_context: Dwarf.abi.RegisterContext = .{ .eh_frame = false, .is_macho = true };
1413431
1414 const encoding: macho.CompactUnwindEncoding = @bitCast(entry.raw_encoding);432 return ptr;
1415 const new_ip = switch (builtin.cpu.arch) {433 }
1416 .x86_64 => switch (encoding.mode.x86_64) {434};
1417 .OLD => return error.UnimplementedUnwindEncoding,
1418 .RBP_FRAME => ip: {
1419 const frame = encoding.value.x86_64.frame;
1420
1421 const fp = (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).*;
1422 const new_sp = fp + 2 * @sizeOf(usize);
1423
1424 const ip_ptr = fp + @sizeOf(usize);
1425 const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*;
1426 const new_fp = @as(*const usize, @ptrFromInt(fp)).*;
1427
1428 (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).* = new_fp;
1429 (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).* = new_sp;
1430 (try regValueNative(context.thread_context, ip_reg_num, reg_context)).* = new_ip;
1431
1432 const regs: [5]u3 = .{
1433 frame.reg0,
1434 frame.reg1,
1435 frame.reg2,
1436 frame.reg3,
1437 frame.reg4,
1438 };
1439 for (regs, 0..) |reg, i| {
1440 if (reg == 0) continue;
1441 const addr = fp - frame.frame_offset * @sizeOf(usize) + i * @sizeOf(usize);
1442 const reg_number = try Dwarf.compactUnwindToDwarfRegNumber(reg);
1443 (try regValueNative(context.thread_context, reg_number, reg_context)).* = @as(*const usize, @ptrFromInt(addr)).*;
1444 }
1445
1446 break :ip new_ip;
1447 },
1448 .STACK_IMMD,
1449 .STACK_IND,
1450 => ip: {
1451 const frameless = encoding.value.x86_64.frameless;
1452
1453 const sp = (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).*;
1454 const stack_size: usize = stack_size: {
1455 if (encoding.mode.x86_64 == .STACK_IMMD) {
1456 break :stack_size @as(usize, frameless.stack.direct.stack_size) * @sizeOf(usize);
1457 }
1458 // In .STACK_IND, the stack size is inferred from the subq instruction at the beginning of the function.
1459 const sub_offset_addr =
1460 text_base +
1461 entry.function_offset +
1462 frameless.stack.indirect.sub_offset;
1463 // `sub_offset_addr` points to the offset of the literal within the instruction
1464 const sub_operand = @as(*align(1) const u32, @ptrFromInt(sub_offset_addr)).*;
1465 break :stack_size sub_operand + @sizeOf(usize) * @as(usize, frameless.stack.indirect.stack_adjust);
1466 };
1467
1468 // Decode the Lehmer-coded sequence of registers.
1469 // For a description of the encoding see lib/libc/include/any-macos.13-any/mach-o/compact_unwind_encoding.h
1470
1471 // Decode the variable-based permutation number into its digits. Each digit represents
1472 // an index into the list of register numbers that weren't yet used in the sequence at
1473 // the time the digit was added.
1474 const reg_count = frameless.stack_reg_count;
1475 const ip_ptr = ip_ptr: {
1476 var digits: [6]u3 = undefined;
1477 var accumulator: usize = frameless.stack_reg_permutation;
1478 var base: usize = 2;
1479 for (0..reg_count) |i| {
1480 const div = accumulator / base;
1481 digits[digits.len - 1 - i] = @intCast(accumulator - base * div);
1482 accumulator = div;
1483 base += 1;
1484 }
1485
1486 var registers: [6]u3 = undefined;
1487 var used_indices: [6]bool = @splat(false);
1488 for (digits[digits.len - reg_count ..], 0..) |target_unused_index, i| {
1489 var unused_count: u8 = 0;
1490 const unused_index = for (used_indices, 0..) |used, index| {
1491 if (!used) {
1492 if (target_unused_index == unused_count) break index;
1493 unused_count += 1;
1494 }
1495 } else unreachable;
1496 registers[i] = @intCast(unused_index + 1);
1497 used_indices[unused_index] = true;
1498 }
1499
1500 var reg_addr = sp + stack_size - @sizeOf(usize) * @as(usize, reg_count + 1);
1501 for (0..reg_count) |i| {
1502 const reg_number = try Dwarf.compactUnwindToDwarfRegNumber(registers[i]);
1503 (try regValueNative(context.thread_context, reg_number, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*;
1504 reg_addr += @sizeOf(usize);
1505 }
1506
1507 break :ip_ptr reg_addr;
1508 };
1509
1510 const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*;
1511 const new_sp = ip_ptr + @sizeOf(usize);
1512
1513 (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).* = new_sp;
1514 (try regValueNative(context.thread_context, ip_reg_num, reg_context)).* = new_ip;
1515
1516 break :ip new_ip;
1517 },
1518 .DWARF => {
1519 const eh_frame = opt_eh_frame orelse return error.MissingEhFrame;
1520 const eh_frame_vaddr = @intFromPtr(eh_frame.ptr) - load_offset;
1521 return unwindFrameDwarf(
1522 &.initSection(.eh_frame, eh_frame_vaddr, eh_frame),
1523 load_offset,
1524 context,
1525 @intCast(encoding.value.x86_64.dwarf),
1526 );
1527 },
1528 },
1529 .aarch64, .aarch64_be => switch (encoding.mode.arm64) {
1530 .OLD => return error.UnimplementedUnwindEncoding,
1531 .FRAMELESS => ip: {
1532 const sp = (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).*;
1533 const new_sp = sp + encoding.value.arm64.frameless.stack_size * 16;
1534 const new_ip = (try regValueNative(context.thread_context, 30, reg_context)).*;
1535 (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).* = new_sp;
1536 break :ip new_ip;
1537 },
1538 .DWARF => {
1539 const eh_frame = opt_eh_frame orelse return error.MissingEhFrame;
1540 const eh_frame_vaddr = @intFromPtr(eh_frame.ptr) - load_offset;
1541 return unwindFrameDwarf(
1542 &.initSection(.eh_frame, eh_frame_vaddr, eh_frame),
1543 load_offset,
1544 context,
1545 @intCast(encoding.value.x86_64.dwarf),
1546 );
1547 },
1548 .FRAME => ip: {
1549 const frame = encoding.value.arm64.frame;
1550
1551 const fp = (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).*;
1552 const ip_ptr = fp + @sizeOf(usize);
1553
1554 var reg_addr = fp - @sizeOf(usize);
1555 inline for (@typeInfo(@TypeOf(frame.x_reg_pairs)).@"struct".fields, 0..) |field, i| {
1556 if (@field(frame.x_reg_pairs, field.name) != 0) {
1557 (try regValueNative(context.thread_context, 19 + i, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*;
1558 reg_addr += @sizeOf(usize);
1559 (try regValueNative(context.thread_context, 20 + i, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*;
1560 reg_addr += @sizeOf(usize);
1561 }
1562 }
1563
1564 inline for (@typeInfo(@TypeOf(frame.d_reg_pairs)).@"struct".fields, 0..) |field, i| {
1565 if (@field(frame.d_reg_pairs, field.name) != 0) {
1566 // Only the lower half of the 128-bit V registers are restored during unwinding
1567 {
1568 const dest: *align(1) usize = @ptrCast(try regBytes(context.thread_context, 64 + 8 + i, context.reg_context));
1569 dest.* = @as(*const usize, @ptrFromInt(reg_addr)).*;
1570 }
1571 reg_addr += @sizeOf(usize);
1572 {
1573 const dest: *align(1) usize = @ptrCast(try regBytes(context.thread_context, 64 + 9 + i, context.reg_context));
1574 dest.* = @as(*const usize, @ptrFromInt(reg_addr)).*;
1575 }
1576 reg_addr += @sizeOf(usize);
1577 }
1578 }
1579
1580 const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*;
1581 const new_fp = @as(*const usize, @ptrFromInt(fp)).*;
1582
1583 (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).* = new_fp;
1584 (try regValueNative(context.thread_context, ip_reg_num, reg_context)).* = new_ip;
1585
1586 break :ip new_ip;
1587 },
1588 },
1589 else => comptime unreachable, // unimplemented
1590 };
1591
1592 context.pc = stripInstructionPtrAuthCode(new_ip);
1593 if (context.pc > 0) context.pc -= 1;
1594 return new_ip;
1595}
lib/std/debug/SelfInfo/DarwinModule.zig created+801
...@@ -0,0 +1,801 @@
1/// The runtime address where __TEXT is loaded.
2text_base: usize,
3load_offset: usize,
4name: []const u8,
5
6pub fn key(m: *const DarwinModule) usize {
7 return m.text_base;
8}
9
10pub fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) !DarwinModule {
11 _ = cache;
12 _ = gpa;
13 const image_count = std.c._dyld_image_count();
14 for (0..image_count) |image_idx| {
15 const header = std.c._dyld_get_image_header(@intCast(image_idx)) orelse continue;
16 const text_base = @intFromPtr(header);
17 if (address < text_base) continue;
18 const load_offset = std.c._dyld_get_image_vmaddr_slide(@intCast(image_idx));
19
20 // Find the __TEXT segment
21 var it: macho.LoadCommandIterator = .{
22 .ncmds = header.ncmds,
23 .buffer = @as([*]u8, @ptrCast(header))[@sizeOf(macho.mach_header_64)..][0..header.sizeofcmds],
24 };
25 const text_segment_cmd = while (it.next()) |load_cmd| {
26 if (load_cmd.cmd() != .SEGMENT_64) continue;
27 const segment_cmd = load_cmd.cast(macho.segment_command_64).?;
28 if (!mem.eql(u8, segment_cmd.segName(), "__TEXT")) continue;
29 break segment_cmd;
30 } else continue;
31
32 const seg_start = load_offset + text_segment_cmd.vmaddr;
33 assert(seg_start == text_base);
34 const seg_end = seg_start + text_segment_cmd.vmsize;
35 if (address < seg_start or address >= seg_end) continue;
36
37 // We've found the matching __TEXT segment. This is the image we need.
38 return .{
39 .text_base = text_base,
40 .load_offset = load_offset,
41 .name = mem.span(std.c._dyld_get_image_name(@intCast(image_idx))),
42 };
43 }
44 return error.MissingDebugInfo;
45}
46fn loadLocationInfo(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo) !void {
47 const mapped_mem = try mapDebugInfoFile(module.name);
48 errdefer posix.munmap(mapped_mem);
49
50 const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_mem.ptr));
51 if (hdr.magic != macho.MH_MAGIC_64)
52 return error.InvalidDebugInfo;
53
54 const symtab: macho.symtab_command = symtab: {
55 var it: macho.LoadCommandIterator = .{
56 .ncmds = hdr.ncmds,
57 .buffer = mapped_mem[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds],
58 };
59 while (it.next()) |cmd| switch (cmd.cmd()) {
60 .SYMTAB => break :symtab cmd.cast(macho.symtab_command) orelse return error.InvalidDebugInfo,
61 else => {},
62 };
63 return error.MissingDebugInfo;
64 };
65
66 const syms_ptr: [*]align(1) const macho.nlist_64 = @ptrCast(mapped_mem[symtab.symoff..]);
67 const syms = syms_ptr[0..symtab.nsyms];
68 const strings = mapped_mem[symtab.stroff..][0 .. symtab.strsize - 1 :0];
69
70 var symbols: std.ArrayList(MachoSymbol) = try .initCapacity(gpa, syms.len);
71 defer symbols.deinit(gpa);
72
73 var ofile: u32 = undefined;
74 var last_sym: MachoSymbol = undefined;
75 var state: enum {
76 init,
77 oso_open,
78 oso_close,
79 bnsym,
80 fun_strx,
81 fun_size,
82 ensym,
83 } = .init;
84
85 for (syms) |*sym| {
86 if (sym.n_type.bits.is_stab == 0) continue;
87
88 // TODO handle globals N_GSYM, and statics N_STSYM
89 switch (sym.n_type.stab) {
90 .oso => switch (state) {
91 .init, .oso_close => {
92 state = .oso_open;
93 ofile = sym.n_strx;
94 },
95 else => return error.InvalidDebugInfo,
96 },
97 .bnsym => switch (state) {
98 .oso_open, .ensym => {
99 state = .bnsym;
100 last_sym = .{
101 .strx = 0,
102 .addr = sym.n_value,
103 .size = 0,
104 .ofile = ofile,
105 };
106 },
107 else => return error.InvalidDebugInfo,
108 },
109 .fun => switch (state) {
110 .bnsym => {
111 state = .fun_strx;
112 last_sym.strx = sym.n_strx;
113 },
114 .fun_strx => {
115 state = .fun_size;
116 last_sym.size = @intCast(sym.n_value);
117 },
118 else => return error.InvalidDebugInfo,
119 },
120 .ensym => switch (state) {
121 .fun_size => {
122 state = .ensym;
123 symbols.appendAssumeCapacity(last_sym);
124 },
125 else => return error.InvalidDebugInfo,
126 },
127 .so => switch (state) {
128 .init, .oso_close => {},
129 .oso_open, .ensym => {
130 state = .oso_close;
131 },
132 else => return error.InvalidDebugInfo,
133 },
134 else => {},
135 }
136 }
137
138 switch (state) {
139 .init => return error.MissingDebugInfo,
140 .oso_close => {},
141 else => return error.InvalidDebugInfo,
142 }
143
144 const symbols_slice = try symbols.toOwnedSlice(gpa);
145 errdefer gpa.free(symbols_slice);
146
147 // Even though lld emits symbols in ascending order, this debug code
148 // should work for programs linked in any valid way.
149 // This sort is so that we can binary search later.
150 mem.sort(MachoSymbol, symbols_slice, {}, MachoSymbol.addressLessThan);
151
152 di.full = .{
153 .mapped_memory = mapped_mem,
154 .symbols = symbols_slice,
155 .strings = strings,
156 .ofiles = .empty,
157 };
158}
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}
191pub fn getSymbolAtAddress(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol {
192 if (di.full == null) try module.loadLocationInfo(gpa, di);
193 const vaddr = address - module.load_offset;
194 const symbol = MachoSymbol.find(di.full.?.symbols, vaddr) orelse return .{
195 .name = null,
196 .compile_unit_name = null,
197 .source_location = null,
198 };
199
200 // offset of `address` from start of `symbol`
201 const address_symbol_offset = vaddr - symbol.addr;
202
203 // Take the symbol name from the N_FUN STAB entry, we're going to
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);
207
208 // If any information is missing, we can at least return this from now on.
209 const sym_only_result: std.debug.Symbol = .{
210 .name = stab_symbol,
211 .compile_unit_name = null,
212 .source_location = null,
213 };
214
215 const o_file: *DebugInfo.OFile = of: {
216 const gop = try di.full.?.ofiles.getOrPut(gpa, o_file_path);
217 if (!gop.found_existing) {
218 gop.value_ptr.* = DebugInfo.loadOFile(gpa, o_file_path) catch |err| {
219 defer _ = di.full.?.ofiles.pop().?;
220 switch (err) {
221 error.MissingDebugInfo,
222 error.InvalidDebugInfo,
223 => return sym_only_result,
224 else => |e| return e,
225 }
226 };
227 }
228 break :of gop.value_ptr;
229 };
230
231 const symbol_ofile_vaddr = o_file.addr_table.get(stab_symbol) orelse return sym_only_result;
232
233 const compile_unit = o_file.dwarf.findCompileUnit(native_endian, symbol_ofile_vaddr) catch |err| switch (err) {
234 error.MissingDebugInfo, error.InvalidDebugInfo => return sym_only_result,
235 else => |e| return e,
236 };
237
238 return .{
239 .name = o_file.dwarf.getSymbolName(symbol_ofile_vaddr) orelse stab_symbol,
240 .compile_unit_name = compile_unit.die.getAttrString(
241 &o_file.dwarf,
242 native_endian,
243 std.dwarf.AT.name,
244 o_file.dwarf.section(.debug_str),
245 compile_unit,
246 ) catch |err| switch (err) {
247 error.MissingDebugInfo, error.InvalidDebugInfo => null,
248 },
249 .source_location = o_file.dwarf.getLineNumberInfo(
250 gpa,
251 native_endian,
252 compile_unit,
253 symbol_ofile_vaddr + address_symbol_offset,
254 ) catch |err| switch (err) {
255 error.MissingDebugInfo, error.InvalidDebugInfo => null,
256 else => return err,
257 },
258 };
259}
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}
272/// Unwind a frame using MachO compact unwind info (from __unwind_info).
273/// If the compact encoding can't encode a way to unwind a frame, it will
274/// 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 {
282 if (unwind_info.len < @sizeOf(macho.unwind_info_section_header)) return error.InvalidUnwindInfo;
283 const header: *align(1) const macho.unwind_info_section_header = @ptrCast(unwind_info);
284
285 const index_byte_count = header.indexCount * @sizeOf(macho.unwind_info_section_header_index_entry);
286 if (unwind_info.len < header.indexSectionOffset + index_byte_count) return error.InvalidUnwindInfo;
287 const indices: []align(1) const macho.unwind_info_section_header_index_entry = @ptrCast(unwind_info[header.indexSectionOffset..][0..index_byte_count]);
288 if (indices.len == 0) return error.MissingUnwindInfo;
289
290 // offset of the PC into the `__TEXT` segment
291 const pc_text_offset = context.pc - text_base;
292
293 const start_offset: u32, const first_level_offset: u32 = index: {
294 var left: usize = 0;
295 var len: usize = indices.len;
296 while (len > 1) {
297 const mid = left + len / 2;
298 if (pc_text_offset < indices[mid].functionOffset) {
299 len /= 2;
300 } else {
301 left = mid;
302 len -= len / 2;
303 }
304 }
305 break :index .{ indices[left].secondLevelPagesSectionOffset, indices[left].functionOffset };
306 };
307 // An offset of 0 is a sentinel indicating a range does not have unwind info.
308 if (start_offset == 0) return error.MissingUnwindInfo;
309
310 const common_encodings_byte_count = header.commonEncodingsArrayCount * @sizeOf(macho.compact_unwind_encoding_t);
311 if (unwind_info.len < header.commonEncodingsArraySectionOffset + common_encodings_byte_count) return error.InvalidUnwindInfo;
312 const common_encodings: []align(1) const macho.compact_unwind_encoding_t = @ptrCast(
313 unwind_info[header.commonEncodingsArraySectionOffset..][0..common_encodings_byte_count],
314 );
315
316 if (unwind_info.len < start_offset + @sizeOf(macho.UNWIND_SECOND_LEVEL)) return error.InvalidUnwindInfo;
317 const kind: *align(1) const macho.UNWIND_SECOND_LEVEL = @ptrCast(unwind_info[start_offset..]);
318
319 const entry: struct {
320 function_offset: usize,
321 raw_encoding: u32,
322 } = switch (kind.*) {
323 .REGULAR => entry: {
324 if (unwind_info.len < start_offset + @sizeOf(macho.unwind_info_regular_second_level_page_header)) return error.InvalidUnwindInfo;
325 const page_header: *align(1) const macho.unwind_info_regular_second_level_page_header = @ptrCast(unwind_info[start_offset..]);
326
327 const entries_byte_count = page_header.entryCount * @sizeOf(macho.unwind_info_regular_second_level_entry);
328 if (unwind_info.len < start_offset + entries_byte_count) return error.InvalidUnwindInfo;
329 const entries: []align(1) const macho.unwind_info_regular_second_level_entry = @ptrCast(
330 unwind_info[start_offset + page_header.entryPageOffset ..][0..entries_byte_count],
331 );
332 if (entries.len == 0) return error.InvalidUnwindInfo;
333
334 var left: usize = 0;
335 var len: usize = entries.len;
336 while (len > 1) {
337 const mid = left + len / 2;
338 if (pc_text_offset < entries[mid].functionOffset) {
339 len /= 2;
340 } else {
341 left = mid;
342 len -= len / 2;
343 }
344 }
345 break :entry .{
346 .function_offset = entries[left].functionOffset,
347 .raw_encoding = entries[left].encoding,
348 };
349 },
350 .COMPRESSED => entry: {
351 if (unwind_info.len < start_offset + @sizeOf(macho.unwind_info_compressed_second_level_page_header)) return error.InvalidUnwindInfo;
352 const page_header: *align(1) const macho.unwind_info_compressed_second_level_page_header = @ptrCast(unwind_info[start_offset..]);
353
354 const entries_byte_count = page_header.entryCount * @sizeOf(macho.UnwindInfoCompressedEntry);
355 if (unwind_info.len < start_offset + entries_byte_count) return error.InvalidUnwindInfo;
356 const entries: []align(1) const macho.UnwindInfoCompressedEntry = @ptrCast(
357 unwind_info[start_offset + page_header.entryPageOffset ..][0..entries_byte_count],
358 );
359 if (entries.len == 0) return error.InvalidUnwindInfo;
360
361 var left: usize = 0;
362 var len: usize = entries.len;
363 while (len > 1) {
364 const mid = left + len / 2;
365 if (pc_text_offset < first_level_offset + entries[mid].funcOffset) {
366 len /= 2;
367 } else {
368 left = mid;
369 len -= len / 2;
370 }
371 }
372 const entry = entries[left];
373
374 const function_offset = first_level_offset + entry.funcOffset;
375 if (entry.encodingIndex < common_encodings.len) {
376 break :entry .{
377 .function_offset = function_offset,
378 .raw_encoding = common_encodings[entry.encodingIndex],
379 };
380 }
381
382 const local_index = entry.encodingIndex - common_encodings.len;
383 const local_encodings_byte_count = page_header.encodingsCount * @sizeOf(macho.compact_unwind_encoding_t);
384 if (unwind_info.len < start_offset + page_header.encodingsPageOffset + local_encodings_byte_count) return error.InvalidUnwindInfo;
385 const local_encodings: []align(1) const macho.compact_unwind_encoding_t = @ptrCast(
386 unwind_info[start_offset + page_header.encodingsPageOffset ..][0..local_encodings_byte_count],
387 );
388 if (local_index >= local_encodings.len) return error.InvalidUnwindInfo;
389 break :entry .{
390 .function_offset = function_offset,
391 .raw_encoding = local_encodings[local_index],
392 };
393 },
394 else => return error.InvalidUnwindInfo,
395 };
396
397 if (entry.raw_encoding == 0) return error.NoUnwindInfo;
398 const reg_context: Dwarf.abi.RegisterContext = .{ .eh_frame = false, .is_macho = true };
399
400 const encoding: macho.CompactUnwindEncoding = @bitCast(entry.raw_encoding);
401 const new_ip = switch (builtin.cpu.arch) {
402 .x86_64 => switch (encoding.mode.x86_64) {
403 .OLD => return error.UnimplementedUnwindEncoding,
404 .RBP_FRAME => ip: {
405 const frame = encoding.value.x86_64.frame;
406
407 const fp = (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).*;
408 const new_sp = fp + 2 * @sizeOf(usize);
409
410 const ip_ptr = fp + @sizeOf(usize);
411 const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*;
412 const new_fp = @as(*const usize, @ptrFromInt(fp)).*;
413
414 (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).* = new_fp;
415 (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).* = new_sp;
416 (try regValueNative(context.thread_context, ip_reg_num, reg_context)).* = new_ip;
417
418 const regs: [5]u3 = .{
419 frame.reg0,
420 frame.reg1,
421 frame.reg2,
422 frame.reg3,
423 frame.reg4,
424 };
425 for (regs, 0..) |reg, i| {
426 if (reg == 0) continue;
427 const addr = fp - frame.frame_offset * @sizeOf(usize) + i * @sizeOf(usize);
428 const reg_number = try Dwarf.compactUnwindToDwarfRegNumber(reg);
429 (try regValueNative(context.thread_context, reg_number, reg_context)).* = @as(*const usize, @ptrFromInt(addr)).*;
430 }
431
432 break :ip new_ip;
433 },
434 .STACK_IMMD,
435 .STACK_IND,
436 => ip: {
437 const frameless = encoding.value.x86_64.frameless;
438
439 const sp = (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).*;
440 const stack_size: usize = stack_size: {
441 if (encoding.mode.x86_64 == .STACK_IMMD) {
442 break :stack_size @as(usize, frameless.stack.direct.stack_size) * @sizeOf(usize);
443 }
444 // In .STACK_IND, the stack size is inferred from the subq instruction at the beginning of the function.
445 const sub_offset_addr =
446 text_base +
447 entry.function_offset +
448 frameless.stack.indirect.sub_offset;
449 // `sub_offset_addr` points to the offset of the literal within the instruction
450 const sub_operand = @as(*align(1) const u32, @ptrFromInt(sub_offset_addr)).*;
451 break :stack_size sub_operand + @sizeOf(usize) * @as(usize, frameless.stack.indirect.stack_adjust);
452 };
453
454 // Decode the Lehmer-coded sequence of registers.
455 // For a description of the encoding see lib/libc/include/any-macos.13-any/mach-o/compact_unwind_encoding.h
456
457 // Decode the variable-based permutation number into its digits. Each digit represents
458 // an index into the list of register numbers that weren't yet used in the sequence at
459 // the time the digit was added.
460 const reg_count = frameless.stack_reg_count;
461 const ip_ptr = ip_ptr: {
462 var digits: [6]u3 = undefined;
463 var accumulator: usize = frameless.stack_reg_permutation;
464 var base: usize = 2;
465 for (0..reg_count) |i| {
466 const div = accumulator / base;
467 digits[digits.len - 1 - i] = @intCast(accumulator - base * div);
468 accumulator = div;
469 base += 1;
470 }
471
472 var registers: [6]u3 = undefined;
473 var used_indices: [6]bool = @splat(false);
474 for (digits[digits.len - reg_count ..], 0..) |target_unused_index, i| {
475 var unused_count: u8 = 0;
476 const unused_index = for (used_indices, 0..) |used, index| {
477 if (!used) {
478 if (target_unused_index == unused_count) break index;
479 unused_count += 1;
480 }
481 } else unreachable;
482 registers[i] = @intCast(unused_index + 1);
483 used_indices[unused_index] = true;
484 }
485
486 var reg_addr = sp + stack_size - @sizeOf(usize) * @as(usize, reg_count + 1);
487 for (0..reg_count) |i| {
488 const reg_number = try Dwarf.compactUnwindToDwarfRegNumber(registers[i]);
489 (try regValueNative(context.thread_context, reg_number, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*;
490 reg_addr += @sizeOf(usize);
491 }
492
493 break :ip_ptr reg_addr;
494 };
495
496 const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*;
497 const new_sp = ip_ptr + @sizeOf(usize);
498
499 (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).* = new_sp;
500 (try regValueNative(context.thread_context, ip_reg_num, reg_context)).* = new_ip;
501
502 break :ip new_ip;
503 },
504 .DWARF => {
505 const eh_frame = opt_eh_frame orelse return error.MissingEhFrame;
506 const eh_frame_vaddr = @intFromPtr(eh_frame.ptr) - load_offset;
507 return context.unwindFrameDwarf(
508 &.initSection(.eh_frame, eh_frame_vaddr, eh_frame),
509 load_offset,
510 @intCast(encoding.value.x86_64.dwarf),
511 );
512 },
513 },
514 .aarch64, .aarch64_be => switch (encoding.mode.arm64) {
515 .OLD => return error.UnimplementedUnwindEncoding,
516 .FRAMELESS => ip: {
517 const sp = (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).*;
518 const new_sp = sp + encoding.value.arm64.frameless.stack_size * 16;
519 const new_ip = (try regValueNative(context.thread_context, 30, reg_context)).*;
520 (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).* = new_sp;
521 break :ip new_ip;
522 },
523 .DWARF => {
524 const eh_frame = opt_eh_frame orelse return error.MissingEhFrame;
525 const eh_frame_vaddr = @intFromPtr(eh_frame.ptr) - load_offset;
526 return context.unwindFrameDwarf(
527 &.initSection(.eh_frame, eh_frame_vaddr, eh_frame),
528 load_offset,
529 @intCast(encoding.value.x86_64.dwarf),
530 );
531 },
532 .FRAME => ip: {
533 const frame = encoding.value.arm64.frame;
534
535 const fp = (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).*;
536 const ip_ptr = fp + @sizeOf(usize);
537
538 var reg_addr = fp - @sizeOf(usize);
539 inline for (@typeInfo(@TypeOf(frame.x_reg_pairs)).@"struct".fields, 0..) |field, i| {
540 if (@field(frame.x_reg_pairs, field.name) != 0) {
541 (try regValueNative(context.thread_context, 19 + i, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*;
542 reg_addr += @sizeOf(usize);
543 (try regValueNative(context.thread_context, 20 + i, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*;
544 reg_addr += @sizeOf(usize);
545 }
546 }
547
548 inline for (@typeInfo(@TypeOf(frame.d_reg_pairs)).@"struct".fields, 0..) |field, i| {
549 if (@field(frame.d_reg_pairs, field.name) != 0) {
550 // Only the lower half of the 128-bit V registers are restored during unwinding
551 {
552 const dest: *align(1) usize = @ptrCast(try regBytes(context.thread_context, 64 + 8 + i, context.reg_context));
553 dest.* = @as(*const usize, @ptrFromInt(reg_addr)).*;
554 }
555 reg_addr += @sizeOf(usize);
556 {
557 const dest: *align(1) usize = @ptrCast(try regBytes(context.thread_context, 64 + 9 + i, context.reg_context));
558 dest.* = @as(*const usize, @ptrFromInt(reg_addr)).*;
559 }
560 reg_addr += @sizeOf(usize);
561 }
562 }
563
564 const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*;
565 const new_fp = @as(*const usize, @ptrFromInt(fp)).*;
566
567 (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).* = new_fp;
568 (try regValueNative(context.thread_context, ip_reg_num, reg_context)).* = new_ip;
569
570 break :ip new_ip;
571 },
572 },
573 else => comptime unreachable, // unimplemented
574 };
575
576 context.pc = UnwindContext.stripInstructionPtrAuthCode(new_ip);
577 if (context.pc > 0) context.pc -= 1;
578 return new_ip;
579}
580/// No cache needed, because `_dyld_get_image_header` etc are already fast.
581pub const LookupCache = struct {
582 pub const init: LookupCache = .{};
583};
584pub const DebugInfo = struct {
585 unwind: ?struct {
586 // Backed by the in-memory sections mapped by the loader
587 unwind_info: ?[]const u8,
588 eh_frame: ?[]const u8,
589 },
590 // MLUGG TODO: awful field name
591 full: ?struct {
592 mapped_memory: []align(std.heap.page_size_min) const u8,
593 symbols: []const MachoSymbol,
594 strings: [:0]const u8,
595 // MLUGG TODO: this could use an adapter to just index straight into `strings`!
596 ofiles: std.StringArrayHashMapUnmanaged(OFile),
597 },
598
599 pub const init: DebugInfo = .{
600 .unwind = null,
601 .full = null,
602 };
603
604 const OFile = struct {
605 dwarf: Dwarf,
606 // MLUGG TODO: this could use an adapter to just index straight into the strtab!
607 addr_table: std.StringArrayHashMapUnmanaged(u64),
608 };
609
610 fn deinit(di: *DebugInfo, gpa: Allocator) void {
611 for (di.full.ofiles.values()) |*ofile| {
612 ofile.dwarf.deinit(gpa);
613 ofile.addr_table.deinit(gpa);
614 }
615 di.full.ofiles.deinit();
616 gpa.free(di.full.symbols);
617 posix.munmap(di.full.mapped_memory);
618 }
619
620 fn loadOFile(gpa: Allocator, o_file_path: []const u8) !OFile {
621 const mapped_mem = try mapDebugInfoFile(o_file_path);
622 errdefer posix.munmap(mapped_mem);
623
624 if (mapped_mem.len < @sizeOf(macho.mach_header_64)) return error.InvalidDebugInfo;
625 const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_mem.ptr));
626 if (hdr.magic != std.macho.MH_MAGIC_64) return error.InvalidDebugInfo;
627
628 const seg_cmd: macho.LoadCommandIterator.LoadCommand, const symtab_cmd: macho.symtab_command = cmds: {
629 var seg_cmd: ?macho.LoadCommandIterator.LoadCommand = null;
630 var symtab_cmd: ?macho.symtab_command = null;
631 var it: macho.LoadCommandIterator = .{
632 .ncmds = hdr.ncmds,
633 .buffer = mapped_mem[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds],
634 };
635 while (it.next()) |cmd| switch (cmd.cmd()) {
636 .SEGMENT_64 => seg_cmd = cmd,
637 .SYMTAB => symtab_cmd = cmd.cast(macho.symtab_command) orelse return error.InvalidDebugInfo,
638 else => {},
639 };
640 break :cmds .{
641 seg_cmd orelse return error.MissingDebugInfo,
642 symtab_cmd orelse return error.MissingDebugInfo,
643 };
644 };
645
646 if (mapped_mem.len < symtab_cmd.stroff + symtab_cmd.strsize) return error.InvalidDebugInfo;
647 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];
649
650 const n_sym_bytes = symtab_cmd.nsyms * @sizeOf(macho.nlist_64);
651 if (mapped_mem.len < symtab_cmd.symoff + n_sym_bytes) return error.InvalidDebugInfo;
652 const symtab: []align(1) const macho.nlist_64 = @ptrCast(mapped_mem[symtab_cmd.symoff..][0..n_sym_bytes]);
653
654 // 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| {
659 if (sym.n_strx == 0) continue;
660 switch (sym.n_type.bits.type) {
661 .undf => continue, // includes tentative symbols
662 .abs => continue,
663 else => {},
664 }
665 const sym_name = mem.sliceTo(strtab[sym.n_strx..], 0);
666 const gop = addr_table.getOrPutAssumeCapacity(sym_name);
667 if (gop.found_existing) return error.InvalidDebugInfo;
668 gop.value_ptr.* = sym.n_value;
669 }
670
671 var sections: Dwarf.SectionArray = @splat(null);
672 for (seg_cmd.getSections()) |sect| {
673 if (!std.mem.eql(u8, "__DWARF", sect.segName())) continue;
674
675 const section_index: usize = inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields, 0..) |section, i| {
676 if (mem.eql(u8, "__" ++ section.name, sect.sectName())) break i;
677 } else continue;
678
679 if (mapped_mem.len < sect.offset + sect.size) return error.InvalidDebugInfo;
680 const section_bytes = mapped_mem[sect.offset..][0..sect.size];
681 sections[section_index] = .{
682 .data = section_bytes,
683 .owned = false,
684 };
685 }
686
687 const missing_debug_info =
688 sections[@intFromEnum(Dwarf.Section.Id.debug_info)] == null or
689 sections[@intFromEnum(Dwarf.Section.Id.debug_abbrev)] == null or
690 sections[@intFromEnum(Dwarf.Section.Id.debug_str)] == null or
691 sections[@intFromEnum(Dwarf.Section.Id.debug_line)] == null;
692 if (missing_debug_info) return error.MissingDebugInfo;
693
694 var dwarf: Dwarf = .{ .sections = sections };
695 errdefer dwarf.deinit(gpa);
696 try dwarf.open(gpa, native_endian);
697
698 return .{
699 .dwarf = dwarf,
700 .addr_table = addr_table.move(),
701 };
702 }
703};
704
705const MachoSymbol = struct {
706 strx: u32,
707 addr: u64,
708 size: u32,
709 ofile: u32,
710 fn addressLessThan(context: void, lhs: MachoSymbol, rhs: MachoSymbol) bool {
711 _ = context;
712 return lhs.addr < rhs.addr;
713 }
714 /// Assumes that `symbols` is sorted in order of ascending `addr`.
715 fn find(symbols: []const MachoSymbol, address: usize) ?*const MachoSymbol {
716 if (symbols.len == 0) return null; // no potential match
717 if (address < symbols[0].addr) return null; // address is before the lowest-address symbol
718 var left: usize = 0;
719 var len: usize = symbols.len;
720 while (len > 1) {
721 const mid = left + len / 2;
722 if (address < symbols[mid].addr) {
723 len /= 2;
724 } else {
725 left = mid;
726 len -= len / 2;
727 }
728 }
729 return &symbols[left];
730 }
731
732 test find {
733 const symbols: []const MachoSymbol = &.{
734 .{ .addr = 100, .strx = undefined, .size = undefined, .ofile = undefined },
735 .{ .addr = 200, .strx = undefined, .size = undefined, .ofile = undefined },
736 .{ .addr = 300, .strx = undefined, .size = undefined, .ofile = undefined },
737 };
738
739 try testing.expectEqual(null, find(symbols, 0));
740 try testing.expectEqual(null, find(symbols, 99));
741 try testing.expectEqual(&symbols[0], find(symbols, 100).?);
742 try testing.expectEqual(&symbols[0], find(symbols, 150).?);
743 try testing.expectEqual(&symbols[0], find(symbols, 199).?);
744
745 try testing.expectEqual(&symbols[1], find(symbols, 200).?);
746 try testing.expectEqual(&symbols[1], find(symbols, 250).?);
747 try testing.expectEqual(&symbols[1], find(symbols, 299).?);
748
749 try testing.expectEqual(&symbols[2], find(symbols, 300).?);
750 try testing.expectEqual(&symbols[2], find(symbols, 301).?);
751 try testing.expectEqual(&symbols[2], find(symbols, 5000).?);
752 }
753};
754test {
755 _ = MachoSymbol;
756}
757
758fn fpRegNum(reg_context: Dwarf.abi.RegisterContext) u8 {
759 return Dwarf.abi.fpRegNum(builtin.target.cpu.arch, reg_context);
760}
761fn spRegNum(reg_context: Dwarf.abi.RegisterContext) u8 {
762 return Dwarf.abi.spRegNum(builtin.target.cpu.arch, reg_context);
763}
764const ip_reg_num = Dwarf.abi.ipRegNum(builtin.target.cpu.arch).?;
765
766/// Uses `mmap` to map the file at `path` into memory.
767fn mapDebugInfoFile(path: []const u8) ![]align(std.heap.page_size_min) const u8 {
768 const file = std.fs.cwd().openFile(path, .{}) catch |err| switch (err) {
769 error.FileNotFound => return error.MissingDebugInfo,
770 else => |e| return e,
771 };
772 defer file.close();
773
774 const file_len = std.math.cast(usize, try file.getEndPos()) orelse return error.InvalidDebugInfo;
775
776 return posix.mmap(
777 null,
778 file_len,
779 posix.PROT.READ,
780 .{ .TYPE = .SHARED },
781 file.handle,
782 0,
783 );
784}
785
786const DarwinModule = @This();
787
788const std = @import("../../std.zig");
789const Allocator = std.mem.Allocator;
790const Dwarf = std.debug.Dwarf;
791const assert = std.debug.assert;
792const macho = std.macho;
793const mem = std.mem;
794const posix = std.posix;
795const testing = std.testing;
796const UnwindContext = std.debug.SelfInfo.UnwindContext;
797const regBytes = Dwarf.abi.regBytes;
798const regValueNative = Dwarf.abi.regValueNative;
799
800const builtin = @import("builtin");
801const native_endian = builtin.target.cpu.arch.endian();
lib/std/debug/SelfInfo/ElfModule.zig created+144
...@@ -0,0 +1,144 @@
1load_offset: usize,
2name: []const u8,
3build_id: ?[]const u8,
4gnu_eh_frame: ?[]const u8,
5
6/// No cache needed, because `dl_iterate_phdr` is already fast.
7pub const LookupCache = struct {
8 pub const init: LookupCache = .{};
9};
10
11pub const DebugInfo = struct {
12 loaded_elf: ?Dwarf.ElfModule,
13 unwind: ?Dwarf.Unwind,
14 pub const init: DebugInfo = .{
15 .loaded_elf = null,
16 .unwind = null,
17 };
18};
19
20pub fn key(m: ElfModule) usize {
21 return m.load_offset;
22}
23pub fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) !ElfModule {
24 _ = cache;
25 _ = gpa;
26 if (builtin.target.os.tag == .haiku) @panic("TODO implement lookup module for Haiku");
27 const DlIterContext = struct {
28 /// input
29 address: usize,
30 /// output
31 module: ElfModule,
32
33 fn callback(info: *std.posix.dl_phdr_info, size: usize, context: *@This()) !void {
34 _ = size;
35 // The base address is too high
36 if (context.address < info.addr)
37 return;
38
39 const phdrs = info.phdr[0..info.phnum];
40 for (phdrs) |*phdr| {
41 if (phdr.p_type != elf.PT_LOAD) continue;
42
43 // Overflowing addition is used to handle the case of VSDOs having a p_vaddr = 0xffffffffff700000
44 const seg_start = info.addr +% phdr.p_vaddr;
45 const seg_end = seg_start + phdr.p_memsz;
46 if (context.address >= seg_start and context.address < seg_end) {
47 context.module = .{
48 .load_offset = info.addr,
49 // Android libc uses NULL instead of "" to mark the main program
50 .name = mem.sliceTo(info.name, 0) orelse "",
51 .build_id = null,
52 .gnu_eh_frame = null,
53 };
54 break;
55 }
56 } else return;
57
58 for (info.phdr[0..info.phnum]) |phdr| {
59 switch (phdr.p_type) {
60 elf.PT_NOTE => {
61 // Look for .note.gnu.build-id
62 const segment_ptr: [*]const u8 = @ptrFromInt(info.addr + phdr.p_vaddr);
63 var r: std.Io.Reader = .fixed(segment_ptr[0..phdr.p_memsz]);
64 const name_size = r.takeInt(u32, native_endian) catch continue;
65 const desc_size = r.takeInt(u32, native_endian) catch continue;
66 const note_type = r.takeInt(u32, native_endian) catch continue;
67 const name = r.take(name_size) catch continue;
68 if (note_type != elf.NT_GNU_BUILD_ID) continue;
69 if (!mem.eql(u8, name, "GNU\x00")) continue;
70 const desc = r.take(desc_size) catch continue;
71 context.module.build_id = desc;
72 },
73 elf.PT_GNU_EH_FRAME => {
74 const segment_ptr: [*]const u8 = @ptrFromInt(info.addr + phdr.p_vaddr);
75 context.module.gnu_eh_frame = segment_ptr[0..phdr.p_memsz];
76 },
77 else => {},
78 }
79 }
80
81 // Stop the iteration
82 return error.Found;
83 }
84 };
85 var ctx: DlIterContext = .{
86 .address = address,
87 .module = undefined,
88 };
89 std.posix.dl_iterate_phdr(&ctx, error{Found}, DlIterContext.callback) catch |err| switch (err) {
90 error.Found => return ctx.module,
91 };
92 return error.MissingDebugInfo;
93}
94fn loadLocationInfo(module: *const ElfModule, gpa: Allocator, di: *DebugInfo) !void {
95 if (module.name.len > 0) {
96 di.loaded_elf = Dwarf.ElfModule.load(gpa, .{
97 .root_dir = .cwd(),
98 .sub_path = module.name,
99 }, module.build_id, null, null, null) catch |err| switch (err) {
100 error.FileNotFound => return error.MissingDebugInfo,
101 error.Overflow => return error.InvalidDebugInfo,
102 else => |e| return e,
103 };
104 } else {
105 const path = try std.fs.selfExePathAlloc(gpa);
106 defer gpa.free(path);
107 di.loaded_elf = Dwarf.ElfModule.load(gpa, .{
108 .root_dir = .cwd(),
109 .sub_path = path,
110 }, module.build_id, null, null, null) catch |err| switch (err) {
111 error.FileNotFound => return error.MissingDebugInfo,
112 error.Overflow => return error.InvalidDebugInfo,
113 else => |e| return e,
114 };
115 }
116}
117pub fn getSymbolAtAddress(module: *const ElfModule, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol {
118 if (di.loaded_elf == null) try module.loadLocationInfo(gpa, di);
119 const vaddr = address - module.load_offset;
120 return di.loaded_elf.?.dwarf.getSymbol(gpa, native_endian, vaddr);
121}
122fn loadUnwindInfo(module: *const ElfModule, gpa: Allocator, di: *DebugInfo) !void {
123 const section_bytes = module.gnu_eh_frame orelse return error.MissingUnwindInfo; // MLUGG TODO: load from file
124 const section_vaddr: u64 = @intFromPtr(section_bytes.ptr) - module.load_offset;
125 const header: Dwarf.Unwind.EhFrameHeader = try .parse(section_vaddr, section_bytes, @sizeOf(usize), native_endian);
126 di.unwind = .initEhFrameHdr(header, section_vaddr, @ptrFromInt(module.load_offset + header.eh_frame_vaddr));
127 try di.unwind.?.prepareLookup(gpa, @sizeOf(usize), native_endian);
128}
129pub fn unwindFrame(module: *const ElfModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) !usize {
130 if (di.unwind == null) try module.loadUnwindInfo(gpa, di);
131 return context.unwindFrameDwarf(&di.unwind.?, module.load_offset, null);
132}
133
134const ElfModule = @This();
135
136const std = @import("../../std.zig");
137const Allocator = std.mem.Allocator;
138const Dwarf = std.debug.Dwarf;
139const elf = std.elf;
140const mem = std.mem;
141const UnwindContext = std.debug.SelfInfo.UnwindContext;
142
143const builtin = @import("builtin");
144const native_endian = builtin.target.cpu.arch.endian();
lib/std/debug/SelfInfo/WindowsModule.zig created+255
...@@ -0,0 +1,255 @@
1base_address: usize,
2size: usize,
3name: []const u8,
4handle: windows.HMODULE,
5pub fn key(m: WindowsModule) usize {
6 return m.base_address;
7}
8pub fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) !WindowsModule {
9 if (lookupInCache(cache, address)) |m| return m;
10 {
11 // Check a new module hasn't been loaded
12 cache.modules.clearRetainingCapacity();
13
14 const handle = windows.kernel32.CreateToolhelp32Snapshot(windows.TH32CS_SNAPMODULE | windows.TH32CS_SNAPMODULE32, 0);
15 if (handle == windows.INVALID_HANDLE_VALUE) {
16 return windows.unexpectedError(windows.GetLastError());
17 }
18 defer windows.CloseHandle(handle);
19
20 var entry: windows.MODULEENTRY32 = undefined;
21 entry.dwSize = @sizeOf(windows.MODULEENTRY32);
22 if (windows.kernel32.Module32First(handle, &entry) != 0) {
23 try cache.modules.append(gpa, entry);
24 while (windows.kernel32.Module32Next(handle, &entry) != 0) {
25 try cache.modules.append(gpa, entry);
26 }
27 }
28 }
29 if (lookupInCache(cache, address)) |m| return m;
30 return error.MissingDebugInfo;
31}
32pub fn getSymbolAtAddress(module: *const WindowsModule, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol {
33 if (!di.loaded) try module.loadLocationInfo(gpa, di);
34 // Translate the runtime address into a virtual address into the module
35 const vaddr = address - module.base_address;
36
37 if (di.pdb != null) {
38 if (try di.getSymbolFromPdb(vaddr)) |symbol| return symbol;
39 }
40
41 if (di.dwarf) |*dwarf| {
42 const dwarf_address = vaddr + di.coff_image_base;
43 return dwarf.getSymbol(gpa, native_endian, dwarf_address);
44 }
45
46 return error.MissingDebugInfo;
47}
48fn lookupInCache(cache: *const LookupCache, address: usize) ?WindowsModule {
49 for (cache.modules.items) |*entry| {
50 const base_address = @intFromPtr(entry.modBaseAddr);
51 if (address >= base_address and address < base_address + entry.modBaseSize) {
52 return .{
53 .base_address = base_address,
54 .size = entry.modBaseSize,
55 .name = std.mem.sliceTo(&entry.szModule, 0),
56 .handle = entry.hModule,
57 };
58 }
59 }
60 return null;
61}
62fn loadLocationInfo(module: *const WindowsModule, gpa: Allocator, di: *DebugInfo) !void {
63 const mapped_ptr: [*]const u8 = @ptrFromInt(module.base_address);
64 const mapped = mapped_ptr[0..module.size];
65 var coff_obj = coff.Coff.init(mapped, true) catch return error.InvalidDebugInfo;
66 // The string table is not mapped into memory by the loader, so if a section name is in the
67 // string table then we have to map the full image file from disk. This can happen when
68 // a binary is produced with -gdwarf, since the section names are longer than 8 bytes.
69 if (coff_obj.strtabRequired()) {
70 var name_buffer: [windows.PATH_MAX_WIDE + 4:0]u16 = undefined;
71 name_buffer[0..4].* = .{ '\\', '?', '?', '\\' }; // openFileAbsoluteW requires the prefix to be present
72 const process_handle = windows.GetCurrentProcess();
73 const len = windows.kernel32.GetModuleFileNameExW(
74 process_handle,
75 module.handle,
76 name_buffer[4..],
77 windows.PATH_MAX_WIDE,
78 );
79 if (len == 0) return error.MissingDebugInfo;
80 const coff_file = fs.openFileAbsoluteW(name_buffer[0 .. len + 4 :0], .{}) catch |err| switch (err) {
81 error.FileNotFound => return error.MissingDebugInfo,
82 else => |e| return e,
83 };
84 errdefer coff_file.close();
85 var section_handle: windows.HANDLE = undefined;
86 const create_section_rc = windows.ntdll.NtCreateSection(
87 &section_handle,
88 windows.STANDARD_RIGHTS_REQUIRED | windows.SECTION_QUERY | windows.SECTION_MAP_READ,
89 null,
90 null,
91 windows.PAGE_READONLY,
92 // The documentation states that if no AllocationAttribute is specified, then SEC_COMMIT is the default.
93 // In practice, this isn't the case and specifying 0 will result in INVALID_PARAMETER_6.
94 windows.SEC_COMMIT,
95 coff_file.handle,
96 );
97 if (create_section_rc != .SUCCESS) return error.MissingDebugInfo;
98 errdefer windows.CloseHandle(section_handle);
99 var coff_len: usize = 0;
100 var section_view_ptr: [*]const u8 = undefined;
101 const map_section_rc = windows.ntdll.NtMapViewOfSection(
102 section_handle,
103 process_handle,
104 @ptrCast(&section_view_ptr),
105 null,
106 0,
107 null,
108 &coff_len,
109 .ViewUnmap,
110 0,
111 windows.PAGE_READONLY,
112 );
113 if (map_section_rc != .SUCCESS) return error.MissingDebugInfo;
114 errdefer assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @constCast(section_view_ptr)) == .SUCCESS);
115 const section_view = section_view_ptr[0..coff_len];
116 coff_obj = coff.Coff.init(section_view, false) catch return error.InvalidDebugInfo;
117 di.mapped_file = .{
118 .file = coff_file,
119 .section_handle = section_handle,
120 .section_view = section_view,
121 };
122 }
123 di.coff_image_base = coff_obj.getImageBase();
124
125 if (coff_obj.getSectionByName(".debug_info")) |_| {
126 di.dwarf = .{};
127
128 inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields, 0..) |section, i| {
129 di.dwarf.?.sections[i] = if (coff_obj.getSectionByName("." ++ section.name)) |section_header| blk: {
130 break :blk .{
131 .data = try coff_obj.getSectionDataAlloc(section_header, gpa),
132 .owned = true,
133 };
134 } else null;
135 }
136
137 try di.dwarf.?.open(gpa, native_endian);
138 }
139
140 if (try coff_obj.getPdbPath()) |raw_path| pdb: {
141 const path = blk: {
142 if (fs.path.isAbsolute(raw_path)) {
143 break :blk raw_path;
144 } else {
145 const self_dir = try fs.selfExeDirPathAlloc(gpa);
146 defer gpa.free(self_dir);
147 break :blk try fs.path.join(gpa, &.{ self_dir, raw_path });
148 }
149 };
150 defer if (path.ptr != raw_path.ptr) gpa.free(path);
151
152 di.pdb = Pdb.init(gpa, path) catch |err| switch (err) {
153 error.FileNotFound, error.IsDir => break :pdb,
154 else => return err,
155 };
156 try di.pdb.?.parseInfoStream();
157 try di.pdb.?.parseDbiStream();
158
159 if (!mem.eql(u8, &coff_obj.guid, &di.pdb.?.guid) or coff_obj.age != di.pdb.?.age)
160 return error.InvalidDebugInfo;
161
162 di.coff_section_headers = try coff_obj.getSectionHeadersAlloc(gpa);
163 }
164
165 di.loaded = true;
166}
167pub const LookupCache = struct {
168 modules: std.ArrayListUnmanaged(windows.MODULEENTRY32),
169 pub const init: LookupCache = .{ .modules = .empty };
170};
171pub const DebugInfo = struct {
172 loaded: bool,
173
174 coff_image_base: u64,
175 mapped_file: ?struct {
176 file: fs.File,
177 section_handle: windows.HANDLE,
178 section_view: []const u8,
179 fn deinit(mapped: @This()) void {
180 const process_handle = windows.GetCurrentProcess();
181 assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @constCast(mapped.section_view.ptr)) == .SUCCESS);
182 windows.CloseHandle(mapped.section_handle);
183 mapped.file.close();
184 }
185 },
186
187 dwarf: ?Dwarf,
188
189 pdb: ?Pdb,
190 /// Populated iff `pdb != null`; otherwise `&.{}`.
191 coff_section_headers: []coff.SectionHeader,
192
193 pub const init: DebugInfo = .{
194 .loaded = false,
195 .coff_image_base = undefined,
196 .mapped_file = null,
197 .dwarf = null,
198 .pdb = null,
199 .coff_section_headers = &.{},
200 };
201
202 fn deinit(di: *DebugInfo, gpa: Allocator) void {
203 if (di.dwarf) |*dwarf| dwarf.deinit(gpa);
204 if (di.pdb) |*pdb| pdb.deinit();
205 gpa.free(di.coff_section_headers);
206 if (di.mapped_file) |mapped| mapped.deinit();
207 }
208
209 fn getSymbolFromPdb(di: *DebugInfo, relocated_address: usize) !?std.debug.Symbol {
210 var coff_section: *align(1) const coff.SectionHeader = undefined;
211 const mod_index = for (di.pdb.?.sect_contribs) |sect_contrib| {
212 if (sect_contrib.section > di.coff_section_headers.len) continue;
213 // Remember that SectionContribEntry.Section is 1-based.
214 coff_section = &di.coff_section_headers[sect_contrib.section - 1];
215
216 const vaddr_start = coff_section.virtual_address + sect_contrib.offset;
217 const vaddr_end = vaddr_start + sect_contrib.size;
218 if (relocated_address >= vaddr_start and relocated_address < vaddr_end) {
219 break sect_contrib.module_index;
220 }
221 } else {
222 // we have no information to add to the address
223 return null;
224 };
225
226 const module = try di.pdb.?.getModule(mod_index) orelse return error.InvalidDebugInfo;
227
228 return .{
229 .name = di.pdb.?.getSymbolName(
230 module,
231 relocated_address - coff_section.virtual_address,
232 ),
233 .compile_unit_name = fs.path.basename(module.obj_file_name),
234 .source_location = try di.pdb.?.getLineNumberInfo(
235 module,
236 relocated_address - coff_section.virtual_address,
237 ),
238 };
239 }
240};
241
242const WindowsModule = @This();
243
244const std = @import("../../std.zig");
245const Allocator = std.mem.Allocator;
246const Dwarf = std.debug.Dwarf;
247const Pdb = std.debug.Pdb;
248const assert = std.debug.assert;
249const coff = std.coff;
250const fs = std.fs;
251const mem = std.mem;
252const windows = std.os.windows;
253
254const builtin = @import("builtin");
255const native_endian = builtin.target.cpu.arch.endian();