1lock: Io.RwLock,
2ntdll_handle: ?if (load_dll_notification_procs) *anyopaque else noreturn,
3notification_cookie: ?LDR.DLL_NOTIFICATION.COOKIE,
4modules: std.ArrayList(Module),
5
6pub const init: SelfInfo = .{
7 .lock = .init,
8 .ntdll_handle = null,
9 .notification_cookie = null,
10 .modules = .empty,
11};
12pub fn deinit(si: *SelfInfo, io: Io) void {
13 const gpa = std.debug.getDebugInfoAllocator();
14 if (si.notification_cookie) |cookie| unregister: {
15 switch ((si.getNtdllProc(.LdrUnregisterDllNotification) catch break :unregister)(cookie)) {
16 .SUCCESS => {},
17 else => |status| windows.unexpectedStatus(status) catch break :unregister,
18 }
19 }
20 if (si.ntdll_handle) |handle| switch (windows.ntdll.LdrUnloadDll(handle)) {
21 .SUCCESS => {},
22 else => |status| windows.unexpectedStatus(status) catch {},
23 };
24 for (si.modules.items) |*module| module.deinit(gpa, io);
25 si.modules.deinit(gpa);
26}
27
28pub fn getSymbols(
29 si: *SelfInfo,
30 io: Io,
31 symbol_allocator: Allocator,
32 text_arena: Allocator,
33 address: usize,
34 resolve_inline_callers: bool,
35 symbols: *std.ArrayList(std.debug.Symbol),
36) Error!void {
37 const gpa = std.debug.getDebugInfoAllocator();
38 try si.lock.lockShared(io);
39 defer si.lock.unlockShared(io);
40 const module = try si.findModule(gpa, address);
41 const di = try module.getDebugInfo(gpa, io);
42 return di.getSymbols(
43 symbol_allocator,
44 text_arena,
45 address - @intFromPtr(module.entry.DllBase),
46 resolve_inline_callers,
47 symbols,
48 );
49}
50
51pub fn getModuleName(si: *SelfInfo, io: Io, address: usize) Error![]const u8 {
52 const gpa = std.debug.getDebugInfoAllocator();
53 try si.lock.lockShared(io);
54 defer si.lock.unlockShared(io);
55 const module = try si.findModule(gpa, address);
56 return module.name orelse {
57 const name = try std.unicode.wtf16LeToWtf8Alloc(gpa, module.entry.BaseDllName.slice());
58 module.name = name;
59 return name;
60 };
61}
62pub fn getModuleSlide(si: *SelfInfo, io: Io, address: usize) Error!usize {
63 const gpa = std.debug.getDebugInfoAllocator();
64 try si.lock.lockShared(io);
65 defer si.lock.unlockShared(io);
66 const module = try si.findModule(gpa, address);
67 return module.base_address;
68}
69
70pub const can_unwind: bool = switch (builtin.cpu.arch) {
71 else => true,
72 // On x86, `RtlVirtualUnwind` does not exist. We could in theory use `RtlCaptureStackBackTrace`
73 // instead, but on x86, it turns out that function is just... doing FP unwinding with esp! It's
74 // hard to find implementation details to confirm that, but the most authoritative source I have
75 // is an entry in the LLVM mailing list from 2020/08/16 which contains this quote:
76 //
77 // > x86 doesn't have what most architectures would consider an "unwinder" in the sense of
78 // > restoring registers; there is simply a linked list of frames that participate in SEH and
79 // > that desire to be called for a dynamic unwind operation, so RtlCaptureStackBackTrace
80 // > assumes that EBP-based frames are in use and walks an EBP-based frame chain on x86 - not
81 // > all x86 code is written with EBP-based frames so while even though we generally build the
82 // > OS that way, you might always run the risk of encountering external code that uses EBP as a
83 // > general purpose register for which such an unwind attempt for a stack trace would fail.
84 //
85 // Regardless, it's easy to effectively confirm this hypothesis just by compiling some code with
86 // `-fomit-frame-pointer -OReleaseFast` and observing that `RtlCaptureStackBackTrace` returns an
87 // empty trace when it's called in such an application. Note that without `-OReleaseFast` or
88 // similar, LLVM seems reluctant to ever clobber ebp, so you'll get a trace returned which just
89 // contains all of the kernel32/ntdll frames but none of your own. Don't be deceived---this is
90 // just coincidental!
91 //
92 // Anyway, the point is, the only stack walking primitive on x86-windows is FP unwinding. We
93 // *could* ask Microsoft to do that for us with `RtlCaptureStackBackTrace`... but better to just
94 // use our existing FP unwinder in `std.debug`!
95 .x86 => false,
96};
97pub const UnwindContext = struct {
98 pc: usize,
99 cur: windows.CONTEXT,
100 history_table: windows.UNWIND_HISTORY_TABLE,
101 pub fn init(ctx: *const std.debug.cpu_context.Native) UnwindContext {
102 return .{
103 .pc = @returnAddress(),
104 .cur = switch (builtin.cpu.arch) {
105 .x86_64 => std.mem.zeroInit(windows.CONTEXT, .{
106 .Rax = ctx.gprs.get(.rax),
107 .Rcx = ctx.gprs.get(.rcx),
108 .Rdx = ctx.gprs.get(.rdx),
109 .Rbx = ctx.gprs.get(.rbx),
110 .Rsp = ctx.gprs.get(.rsp),
111 .Rbp = ctx.gprs.get(.rbp),
112 .Rsi = ctx.gprs.get(.rsi),
113 .Rdi = ctx.gprs.get(.rdi),
114 .R8 = ctx.gprs.get(.r8),
115 .R9 = ctx.gprs.get(.r9),
116 .R10 = ctx.gprs.get(.r10),
117 .R11 = ctx.gprs.get(.r11),
118 .R12 = ctx.gprs.get(.r12),
119 .R13 = ctx.gprs.get(.r13),
120 .R14 = ctx.gprs.get(.r14),
121 .R15 = ctx.gprs.get(.r15),
122 .Rip = ctx.gprs.get(.rip),
123 }),
124 .aarch64 => .{
125 .ContextFlags = 0,
126 .Cpsr = 0,
127 .DUMMYUNIONNAME = .{ .X = ctx.x },
128 .Sp = ctx.sp,
129 .Pc = ctx.pc,
130 .V = @splat(.{ .B = @splat(0) }),
131 .Fpcr = 0,
132 .Fpsr = 0,
133 .Bcr = @splat(0),
134 .Bvr = @splat(0),
135 .Wcr = @splat(0),
136 .Wvr = @splat(0),
137 },
138 .thumb => .{
139 .ContextFlags = 0,
140 .R0 = ctx.r[0],
141 .R1 = ctx.r[1],
142 .R2 = ctx.r[2],
143 .R3 = ctx.r[3],
144 .R4 = ctx.r[4],
145 .R5 = ctx.r[5],
146 .R6 = ctx.r[6],
147 .R7 = ctx.r[7],
148 .R8 = ctx.r[8],
149 .R9 = ctx.r[9],
150 .R10 = ctx.r[10],
151 .R11 = ctx.r[11],
152 .R12 = ctx.r[12],
153 .Sp = ctx.r[13],
154 .Lr = ctx.r[14],
155 .Pc = ctx.r[15],
156 .Cpsr = 0,
157 .Fpcsr = 0,
158 .Padding = 0,
159 .DUMMYUNIONNAME = .{ .S = @splat(0) },
160 .Bvr = @splat(0),
161 .Bcr = @splat(0),
162 .Wvr = @splat(0),
163 .Wcr = @splat(0),
164 .Padding2 = @splat(0),
165 },
166 else => comptime unreachable,
167 },
168 .history_table = std.mem.zeroes(windows.UNWIND_HISTORY_TABLE),
169 };
170 }
171 pub fn deinit(ctx: *UnwindContext) void {
172 _ = ctx;
173 }
174 pub fn getFp(ctx: *UnwindContext) usize {
175 return ctx.cur.getRegs().bp;
176 }
177};
178pub fn unwindFrame(si: *SelfInfo, io: Io, context: *UnwindContext) Error!usize {
179 _ = si;
180 _ = io;
181
182 const current_regs = context.cur.getRegs();
183 var image_base: usize = undefined;
184 if (windows.ntdll.RtlLookupFunctionEntry(current_regs.ip, &image_base, &context.history_table)) |runtime_function| {
185 var handler_data: ?*anyopaque = null;
186 var establisher_frame: usize = undefined;
187 _ = windows.ntdll.RtlVirtualUnwind(
188 windows.UNW_FLAG_NHANDLER,
189 image_base,
190 current_regs.ip,
191 runtime_function,
192 &context.cur,
193 &handler_data,
194 &establisher_frame,
195 null,
196 );
197 } else {
198 // leaf function
199 context.cur.setIp(@as(*const usize, @ptrFromInt(current_regs.sp)).*);
200 context.cur.setSp(current_regs.sp + @sizeOf(usize));
201 }
202
203 const next_regs = context.cur.getRegs();
204 const tib = &windows.teb().NtTib;
205 if (next_regs.sp < @intFromPtr(tib.StackLimit) or next_regs.sp > @intFromPtr(tib.StackBase)) {
206 context.pc = 0;
207 return 0;
208 }
209 // Like `DwarfUnwindContext.unwindFrame`, adjust our next lookup pc in case the `call` was this
210 // function's last instruction making `next_regs.ip` one byte past its end.
211 context.pc = next_regs.ip -| 1;
212 return next_regs.ip;
213}
214
215const Module = struct {
216 entry: *const LDR.DATA_TABLE_ENTRY,
217 name: ?[]const u8,
218 di: ?(Error!DebugInfo),
219
220 const DebugInfo = struct {
221 arena: std.heap.ArenaAllocator.State,
222 coff_image_base: u64,
223 mapped_file: ?MappedFile,
224 dwarf: ?Dwarf,
225 pdb: ?Pdb,
226 coff_section_headers: []coff.SectionHeader,
227
228 const MappedFile = struct {
229 file: Io.File,
230 section_handle: windows.HANDLE,
231 section_view: []const u8,
232 fn deinit(mf: *const MappedFile, io: Io) void {
233 const process_handle = windows.GetCurrentProcess();
234 switch (windows.ntdll.NtUnmapViewOfSection(
235 process_handle,
236 @constCast(mf.section_view.ptr),
237 )) {
238 .SUCCESS => {},
239 else => |status| windows.unexpectedStatus(status) catch {},
240 }
241 windows.CloseHandle(mf.section_handle);
242 mf.file.close(io);
243 }
244 };
245
246 fn deinit(di: *DebugInfo, gpa: Allocator, io: Io) void {
247 if (di.dwarf) |*dwarf| dwarf.deinit(gpa);
248 if (di.pdb) |*pdb| {
249 pdb.file_reader.file.close(io);
250 pdb.deinit();
251 }
252 if (di.mapped_file) |*mf| mf.deinit(io);
253
254 var arena = di.arena.promote(gpa);
255 arena.deinit();
256 }
257
258 fn getSymbols(
259 di: *DebugInfo,
260 symbol_allocator: Allocator,
261 text_arena: Allocator,
262 vaddr: usize,
263 resolve_inline_callers: bool,
264 symbols: *std.ArrayList(std.debug.Symbol),
265 ) Error!void {
266 pdb: {
267 const pdb = &(di.pdb orelse break :pdb);
268 var coff_section: *align(1) const coff.SectionHeader = undefined;
269 const mod_index = for (pdb.sect_contribs) |sect_contrib| {
270 if (sect_contrib.section > di.coff_section_headers.len) continue;
271 // Remember that SectionContribEntry.Section is 1-based.
272 coff_section = &di.coff_section_headers[sect_contrib.section - 1];
273
274 const vaddr_start = coff_section.virtual_address + sect_contrib.offset;
275 const vaddr_end = vaddr_start + sect_contrib.size;
276 if (vaddr >= vaddr_start and vaddr < vaddr_end) {
277 break sect_contrib.module_index;
278 }
279 } else {
280 // we have no information to add to the address
281 break :pdb;
282 };
283 const module = pdb.getModule(mod_index) catch |err| switch (err) {
284 error.InvalidDebugInfo,
285 error.MissingDebugInfo,
286 error.OutOfMemory,
287 => |e| return e,
288
289 error.ReadFailed,
290 error.EndOfStream,
291 => return error.InvalidDebugInfo,
292 } orelse {
293 return error.InvalidDebugInfo; // bad module index
294 };
295
296 const addr = vaddr - coff_section.virtual_address;
297 const maybe_proc = pdb.getProcSym(module, addr);
298 const compile_unit_name = fs.path.basename(module.obj_file_name);
299 const symbols_top = symbols.items.len;
300 if (maybe_proc) |proc| {
301 const offset_in_func = addr - proc.code_offset;
302 var last_inlinee: ?u32 = null;
303 var iter = pdb.getInlinees(module, proc);
304 while (iter.next(module)) |inline_site| {
305 // Filter out duplicate inline sites. Tools like llvm-addr2line output
306 // duplicate sites in the same cases as us if we elide this check,
307 // implying that they exist in the underlying data and are not indicative
308 // of a parser bug. No useful information is lost here since an inline site
309 // can't actually reference itself.
310 if (inline_site.inlinee == last_inlinee) continue;
311
312 // If our address points into this site, get the source location(s) it
313 // points at
314 var line_iter = pdb.getInlineeSourceLines(module, inline_site.inlinee);
315 while (line_iter.next()) |inlinee_src_line| {
316 const maybe_loc = pdb.getInlineSiteSourceLocation(
317 text_arena,
318 module,
319 inline_site,
320 inlinee_src_line,
321 offset_in_func,
322 ) catch continue;
323 const loc = maybe_loc orelse continue;
324
325 // If we aren't trying to resolve inline callers, and we've matched a
326 // new inline site, we want to overwrite the previously appended
327 // results.
328 if (!resolve_inline_callers and inline_site.inlinee != last_inlinee) {
329 symbols.items.len = symbols_top;
330 }
331
332 // Only resolve the name if we're resolving inline callers, otherwise
333 // wait until we're done to avoid duplicated work.
334 const name = if (resolve_inline_callers)
335 pdb.findInlineeName(inline_site.inlinee)
336 else
337 null;
338
339 try symbols.append(symbol_allocator, .{
340 .name = name,
341 .compile_unit_name = compile_unit_name,
342 .source_location = loc,
343 });
344
345 last_inlinee = inline_site.inlinee;
346 }
347 }
348
349 if (resolve_inline_callers) {
350 // Inline sites are stored in the pdb in reverse order, so we reverse the
351 // matching sites here. We could alternatively use the parent fields to
352 // determine the order, but this would introduce seemingly unecessary
353 // complexity.
354 std.mem.reverse(std.debug.Symbol, symbols.items);
355 } else if (last_inlinee) |inlinee| {
356 // If we aren't resolving inline callers, then all results will have the
357 // same inline site, and we resolve its name once at the end.
358 const name = pdb.findInlineeName(inlinee);
359 for (symbols.items) |*symbol| symbol.name = name;
360 }
361 }
362
363 // If there's room for another symbol, add the actual proc
364 if (resolve_inline_callers or symbols.items.len == 0) {
365 try symbols.append(symbol_allocator, .{
366 .name = if (maybe_proc) |proc| pdb.getSymbolName(proc) else null,
367 .compile_unit_name = compile_unit_name,
368 .source_location = pdb.getLineNumberInfo(text_arena, module, addr) catch null,
369 });
370 }
371
372 return;
373 }
374
375 dwarf: {
376 const dwarf = &(di.dwarf orelse break :dwarf);
377 const addr = vaddr + di.coff_image_base;
378 return dwarf.getSymbols(
379 symbol_allocator,
380 text_arena,
381 native_endian,
382 addr,
383 resolve_inline_callers,
384 symbols,
385 );
386 }
387
388 return error.MissingDebugInfo;
389 }
390 };
391
392 fn deinit(module: *Module, gpa: Allocator, io: Io) void {
393 if (module.name) |name| gpa.free(name);
394 if (module.di) |*di_or_err| if (di_or_err.*) |*di| di.deinit(gpa, io) else |_| {};
395 module.* = undefined;
396 }
397
398 fn getDebugInfo(module: *Module, gpa: Allocator, io: Io) Error!*DebugInfo {
399 if (module.di == null) module.di = loadDebugInfo(module, gpa, io);
400 return if (module.di.?) |*di| di else |err| err;
401 }
402 fn loadDebugInfo(module: *const Module, gpa: Allocator, io: Io) Error!DebugInfo {
403 const mapped_ptr: [*]const u8 = @ptrCast(module.entry.DllBase);
404 const mapped = mapped_ptr[0..module.entry.SizeOfImage];
405 var coff_obj = coff.Coff.init(mapped, true) catch return error.InvalidDebugInfo;
406
407 var arena_instance: std.heap.ArenaAllocator = .init(gpa);
408 errdefer arena_instance.deinit();
409 const arena = arena_instance.allocator();
410
411 // The string table is not mapped into memory by the loader, so if a section name is in the
412 // string table then we have to map the full image file from disk. This can happen when
413 // a binary is produced with -gdwarf, since the section names are longer than 8 bytes.
414 const mapped_file: ?DebugInfo.MappedFile = mapped: {
415 if (!coff_obj.strtabRequired()) break :mapped null;
416 var path_buffer: [4 + windows.PATH_MAX_WIDE]u16 = undefined;
417 path_buffer[0..4].* = .{ '\\', '?', '?', '\\' }; // openFileAbsoluteW requires the prefix to be present
418 const path_slice = module.entry.FullDllName.slice();
419 @memcpy(path_buffer[4..][0..path_slice.len], path_slice);
420 const coff_file = Io.Threaded.dirOpenFileWtf16(
421 null,
422 path_buffer[0 .. 4 + path_slice.len],
423 .{},
424 ) catch |err| switch (err) {
425 error.Canceled => |e| return e,
426 error.Unexpected => |e| return e,
427 error.FileNotFound => return error.MissingDebugInfo,
428
429 error.FileTooBig,
430 error.IsDir,
431 error.NotDir,
432 error.SymLinkLoop,
433 error.NameTooLong,
434 error.BadPathName,
435 => return error.InvalidDebugInfo,
436
437 error.SystemResources,
438 error.WouldBlock,
439 error.AccessDenied,
440 error.PermissionDenied,
441 error.NoSpaceLeft,
442 error.DeviceBusy,
443 error.NoDevice,
444 error.PathAlreadyExists,
445 error.PipeBusy,
446 error.NetworkNotFound,
447 error.AntivirusInterference,
448 error.ProcessFdQuotaExceeded,
449 error.SystemFdQuotaExceeded,
450 error.FileLocksUnsupported,
451 error.FileBusy,
452 error.ReadOnlyFileSystem,
453 => return error.ReadFailed,
454 };
455 errdefer coff_file.close(io);
456 var section_handle: windows.HANDLE = undefined;
457 const create_section_rc = windows.ntdll.NtCreateSection(
458 &section_handle,
459 .{
460 .SPECIFIC = .{ .SECTION = .{
461 .QUERY = true,
462 .MAP_READ = true,
463 } },
464 .STANDARD = .{ .RIGHTS = .REQUIRED },
465 },
466 null,
467 null,
468 .{ .READONLY = true },
469 // The documentation states that if no AllocationAttribute is specified,
470 // then SEC_COMMIT is the default.
471 // In practice, this isn't the case and specifying 0 will result in INVALID_PARAMETER_6.
472 .{ .COMMIT = true },
473 coff_file.handle,
474 );
475 if (create_section_rc != .SUCCESS) return error.MissingDebugInfo;
476 errdefer windows.CloseHandle(section_handle);
477 var coff_len: usize = 0;
478 var section_view_ptr: ?[*]const u8 = null;
479 const process_handle = windows.GetCurrentProcess();
480 const map_section_rc = windows.ntdll.NtMapViewOfSection(
481 section_handle,
482 process_handle,
483 @ptrCast(&section_view_ptr),
484 null,
485 0,
486 null,
487 &coff_len,
488 .Unmap,
489 .{},
490 .{ .READONLY = true },
491 );
492 if (map_section_rc != .SUCCESS) return error.MissingDebugInfo;
493 errdefer switch (windows.ntdll.NtUnmapViewOfSection(
494 process_handle,
495 @constCast(section_view_ptr.?),
496 )) {
497 .SUCCESS => {},
498 else => |status| windows.unexpectedStatus(status) catch {},
499 };
500 const section_view = section_view_ptr.?[0..coff_len];
501 coff_obj = coff.Coff.init(section_view, false) catch return error.InvalidDebugInfo;
502 break :mapped .{
503 .file = coff_file,
504 .section_handle = section_handle,
505 .section_view = section_view,
506 };
507 };
508 errdefer if (mapped_file) |*mf| mf.deinit(io);
509
510 const coff_image_base = coff_obj.getImageBase();
511
512 var opt_dwarf: ?Dwarf = dwarf: {
513 if (coff_obj.getSectionByName(".debug_info") == null) break :dwarf null;
514
515 var sections: Dwarf.SectionArray = undefined;
516 inline for (@typeInfo(Dwarf.Section.Id).@"enum".field_names, 0..) |section_name, i| {
517 sections[i] = if (coff_obj.getSectionByName("." ++ section_name)) |section_header| .{
518 .data = try coff_obj.getSectionDataAlloc(section_header, arena),
519 .owned = false,
520 } else null;
521 }
522 break :dwarf .{ .sections = sections };
523 };
524 errdefer if (opt_dwarf) |*dwarf| dwarf.deinit(gpa);
525
526 if (opt_dwarf) |*dwarf| {
527 dwarf.open(gpa, native_endian) catch |err| switch (err) {
528 error.Overflow,
529 error.EndOfStream,
530 error.StreamTooLong,
531 error.ReadFailed,
532 => return error.InvalidDebugInfo,
533
534 error.InvalidDebugInfo,
535 error.MissingDebugInfo,
536 error.OutOfMemory,
537 => |e| return e,
538 };
539 }
540
541 var opt_pdb: ?Pdb = pdb: {
542 const path = coff_obj.getPdbPath() catch {
543 return error.InvalidDebugInfo;
544 } orelse {
545 break :pdb null;
546 };
547 const pdb_file_open_result = if (fs.path.isAbsolute(path)) res: {
548 break :res Io.Dir.cwd().openFile(io, path, .{});
549 } else res: {
550 const self_dir = std.process.executableDirPathAlloc(io, gpa) catch |err| switch (err) {
551 error.OutOfMemory, error.Unexpected => |e| return e,
552 else => return error.ReadFailed,
553 };
554 defer gpa.free(self_dir);
555 const abs_path = try fs.path.join(gpa, &.{ self_dir, path });
556 defer gpa.free(abs_path);
557 break :res Io.Dir.cwd().openFile(io, abs_path, .{});
558 };
559 const pdb_file = pdb_file_open_result catch |err| switch (err) {
560 error.FileNotFound, error.IsDir => break :pdb null,
561 else => return error.ReadFailed,
562 };
563 errdefer pdb_file.close(io);
564
565 const pdb_reader = try arena.create(Io.File.Reader);
566 pdb_reader.* = pdb_file.reader(io, try arena.alloc(u8, 4096));
567
568 var pdb = Pdb.init(gpa, pdb_reader) catch |err| switch (err) {
569 error.OutOfMemory, error.ReadFailed, error.Unexpected => |e| return e,
570 else => return error.InvalidDebugInfo,
571 };
572 errdefer pdb.deinit();
573 pdb.parseInfoStream() catch |err| switch (err) {
574 error.UnknownPDBVersion => return error.UnsupportedDebugInfo,
575 error.EndOfStream => return error.InvalidDebugInfo,
576
577 error.InvalidDebugInfo,
578 error.MissingDebugInfo,
579 error.OutOfMemory,
580 error.ReadFailed,
581 => |e| return e,
582 };
583 pdb.parseDbiStream() catch |err| switch (err) {
584 error.UnknownPDBVersion => return error.UnsupportedDebugInfo,
585
586 error.EndOfStream,
587 error.EOF,
588 error.StreamTooLong,
589 error.WriteFailed,
590 => return error.InvalidDebugInfo,
591
592 error.InvalidDebugInfo,
593 error.OutOfMemory,
594 error.ReadFailed,
595 => |e| return e,
596 };
597 pdb.parseIpiStream() catch |err| switch (err) {
598 error.UnknownPDBVersion => return error.UnsupportedDebugInfo,
599
600 error.EndOfStream,
601 => return error.InvalidDebugInfo,
602
603 error.OutOfMemory,
604 error.ReadFailed,
605 => |e| return e,
606 };
607
608 if (!std.mem.eql(u8, &coff_obj.guid, &pdb.guid) or coff_obj.age != pdb.age)
609 return error.InvalidDebugInfo;
610
611 break :pdb pdb;
612 };
613 errdefer if (opt_pdb) |*pdb| {
614 pdb.file_reader.file.close(io);
615 pdb.deinit();
616 };
617
618 const coff_section_headers: []coff.SectionHeader = if (opt_pdb != null) csh: {
619 break :csh try coff_obj.getSectionHeadersAlloc(arena);
620 } else &.{};
621
622 return .{
623 .arena = arena_instance.state,
624 .coff_image_base = coff_image_base,
625 .mapped_file = mapped_file,
626 .dwarf = opt_dwarf,
627 .pdb = opt_pdb,
628 .coff_section_headers = coff_section_headers,
629 };
630 }
631};
632
633/// Assumes we already hold `si.lock`.
634fn findModule(si: *SelfInfo, gpa: Allocator, address: usize) error{ MissingDebugInfo, OutOfMemory, Unexpected }!*Module {
635 for (si.modules.items) |*mod| {
636 const base = @intFromPtr(mod.entry.DllBase);
637 if (address >= base and address < base + mod.entry.SizeOfImage) return mod;
638 }
639 try si.modules.ensureUnusedCapacity(gpa, 1);
640 var entry: *LDR.DATA_TABLE_ENTRY = undefined;
641 switch (windows.ntdll.LdrFindEntryForAddress(@ptrFromInt(address), &entry)) {
642 .SUCCESS => {},
643 .DLL_NOT_FOUND => return error.MissingDebugInfo,
644 else => |status| return windows.unexpectedStatus(status),
645 }
646 if (si.notification_cookie == null) {
647 var notification_cookie: LDR.DLL_NOTIFICATION.COOKIE = undefined;
648 switch ((try si.getNtdllProc(.LdrRegisterDllNotification))(
649 .{},
650 &dllNotification,
651 si,
652 &notification_cookie,
653 )) {
654 .SUCCESS => si.notification_cookie = notification_cookie,
655 else => |status| return windows.unexpectedStatus(status),
656 }
657 }
658 const mod = si.modules.addOneAssumeCapacity();
659 mod.* = .{ .entry = entry, .name = null, .di = null };
660 return mod;
661}
662
663inline fn getNtdllProc(
664 si: *SelfInfo,
665 comptime proc: std.meta.DeclEnum(windows.ntdll),
666) !@TypeOf(&@field(windows.ntdll, @tagName(proc))) {
667 return if (load_dll_notification_procs)
668 @ptrCast(try si.loadNtdllProc(@tagName(proc)))
669 else
670 &@field(windows.ntdll, @tagName(proc));
671}
672fn loadNtdllProc(si: *SelfInfo, name: []const u8) Io.UnexpectedError!*anyopaque {
673 const ntdll_handle = si.ntdll_handle orelse ntdll_handle: {
674 var ntdll_handle: *anyopaque = undefined;
675 switch (windows.ntdll.LdrLoadDll(null, null, &.init(
676 &.{ 'n', 't', 'd', 'l', 'l', '.', 'd', 'l', 'l' },
677 ), &ntdll_handle)) {
678 .SUCCESS => {},
679 .DLL_NOT_FOUND => return error.Unexpected,
680 else => |status| return windows.unexpectedStatus(status),
681 }
682 si.ntdll_handle = ntdll_handle;
683 break :ntdll_handle ntdll_handle;
684 };
685 var proc_addr: *anyopaque = undefined;
686 switch (windows.ntdll.LdrGetProcedureAddress(ntdll_handle, &.init(name), 0, &proc_addr)) {
687 .SUCCESS => {},
688 else => |status| return windows.unexpectedStatus(status),
689 }
690 return proc_addr;
691}
692
693fn dllNotification(
694 reason: LDR.DLL_NOTIFICATION.REASON,
695 data: *const LDR.DLL_NOTIFICATION.DATA,
696 context: ?*anyopaque,
697) callconv(.winapi) void {
698 const si: *SelfInfo = @ptrCast(@alignCast(context));
699 switch (reason) {
700 .LOADED => {},
701 .UNLOADED => {
702 const io = std.Options.debug_io;
703 si.lock.lockUncancelable(io);
704 defer si.lock.unlock(io);
705 for (si.modules.items, 0..) |*mod, mod_index| {
706 if (mod.entry.DllBase != data.Unloaded.DllBase) continue;
707 mod.deinit(std.debug.getDebugInfoAllocator(), io);
708 _ = si.modules.swapRemove(mod_index);
709 break;
710 }
711 },
712 }
713}
714
715const std = @import("std");
716const Io = std.Io;
717const Allocator = std.mem.Allocator;
718const Dwarf = std.debug.Dwarf;
719const Pdb = std.debug.Pdb;
720const Error = std.debug.SelfInfoError;
721const coff = std.coff;
722const fs = std.fs;
723const windows = std.os.windows;
724const LDR = windows.LDR;
725
726const builtin = @import("builtin");
727const native_endian = builtin.target.cpu.arch.endian();
728const load_dll_notification_procs = builtin.abi == .msvc and switch (builtin.zig_backend) {
729 .stage2_c => true,
730 else => switch (builtin.output_mode) {
731 .Exe => false,
732 .Lib => switch (builtin.link_mode) {
733 .static => true,
734 .dynamic => false,
735 },
736 .Obj => true,
737 },
738};
739
740const SelfInfo = @This();