| 1 | lock: Io.RwLock, |
| 2 | ntdll_handle: ?if (load_dll_notification_procs) *anyopaque else noreturn, |
| 3 | notification_cookie: ?LDR.DLL_NOTIFICATION.COOKIE, |
| 4 | modules: std.ArrayList(Module), |
| 5 | |
| 6 | pub const init: SelfInfo = .{ |
| 7 | .lock = .init, |
| 8 | .ntdll_handle = null, |
| 9 | .notification_cookie = null, |
| 10 | .modules = .empty, |
| 11 | }; |
| 12 | pub 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 | |
| 28 | pub 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 | |
| 51 | pub 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 | } |
| 62 | pub 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 | |
| 70 | pub 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 | }; |
| 97 | pub 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 | }; |
| 178 | pub 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 | |
| 215 | const 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`. |
| 634 | fn 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 | |
| 663 | inline 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 | } |
| 672 | fn 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 | |
| 693 | fn 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 | |
| 715 | const std = @import("std"); |
| 716 | const Io = std.Io; |
| 717 | const Allocator = std.mem.Allocator; |
| 718 | const Dwarf = std.debug.Dwarf; |
| 719 | const Pdb = std.debug.Pdb; |
| 720 | const Error = std.debug.SelfInfoError; |
| 721 | const coff = std.coff; |
| 722 | const fs = std.fs; |
| 723 | const windows = std.os.windows; |
| 724 | const LDR = windows.LDR; |
| 725 | |
| 726 | const builtin = @import("builtin"); |
| 727 | const native_endian = builtin.target.cpu.arch.endian(); |
| 728 | const 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 | |
| 740 | const SelfInfo = @This(); |