blob 117d2dfd (63252B) - Raw
1 // SPDX-License-Identifier: MIT 2 // Copyright (c) 2015-2021 Zig Contributors 3 // This file is part of [zig](https://ziglang.org/), which is MIT licensed. 4 // The MIT license requires this copyright notice to be included in all copies 5 // and substantial portions of the software. 6 const std = @import("std.zig"); 7 const builtin = std.builtin; 8 const math = std.math; 9 const mem = std.mem; 10 const io = std.io; 11 const os = std.os; 12 const fs = std.fs; 13 const process = std.process; 14 const elf = std.elf; 15 const DW = std.dwarf; 16 const macho = std.macho; 17 const coff = std.coff; 18 const pdb = std.pdb; 19 const ArrayList = std.ArrayList; 20 const root = @import("root"); 21 const maxInt = std.math.maxInt; 22 const File = std.fs.File; 23 const windows = std.os.windows; 24 const native_arch = std.Target.current.cpu.arch; 25 const native_os = std.Target.current.os.tag; 26 const native_endian = native_arch.endian(); 27 28 pub const runtime_safety = switch (builtin.mode) { 29 .Debug, .ReleaseSafe => true, 30 .ReleaseFast, .ReleaseSmall => false, 31 }; 32 33 pub const LineInfo = struct { 34 line: u64, 35 column: u64, 36 file_name: []const u8, 37 allocator: ?*mem.Allocator, 38 39 fn deinit(self: LineInfo) void { 40 const allocator = self.allocator orelse return; 41 allocator.free(self.file_name); 42 } 43 }; 44 45 pub const SymbolInfo = struct { 46 symbol_name: []const u8 = "???", 47 compile_unit_name: []const u8 = "???", 48 line_info: ?LineInfo = null, 49 50 fn deinit(self: @This()) void { 51 if (self.line_info) |li| { 52 li.deinit(); 53 } 54 } 55 }; 56 const PdbOrDwarf = union(enum) { 57 pdb: pdb.Pdb, 58 dwarf: DW.DwarfInfo, 59 }; 60 61 var stderr_mutex = std.Thread.Mutex{}; 62 63 /// Deprecated. Use `std.log` functions for logging or `std.debug.print` for 64 /// "printf debugging". 65 pub const warn = print; 66 67 /// Print to stderr, unbuffered, and silently returning on failure. Intended 68 /// for use in "printf debugging." Use `std.log` functions for proper logging. 69 pub fn print(comptime fmt: []const u8, args: anytype) void { 70 const held = stderr_mutex.acquire(); 71 defer held.release(); 72 const stderr = io.getStdErr().writer(); 73 nosuspend stderr.print(fmt, args) catch return; 74 } 75 76 pub fn getStderrMutex() *std.Thread.Mutex { 77 return &stderr_mutex; 78 } 79 80 /// TODO multithreaded awareness 81 var self_debug_info: ?DebugInfo = null; 82 83 pub fn getSelfDebugInfo() !*DebugInfo { 84 if (self_debug_info) |*info| { 85 return info; 86 } else { 87 self_debug_info = try openSelfDebugInfo(getDebugInfoAllocator()); 88 return &self_debug_info.?; 89 } 90 } 91 92 pub fn detectTTYConfig() TTY.Config { 93 var bytes: [128]u8 = undefined; 94 const allocator = &std.heap.FixedBufferAllocator.init(bytes[0..]).allocator; 95 if (process.getEnvVarOwned(allocator, "ZIG_DEBUG_COLOR")) |_| { 96 return .escape_codes; 97 } else |_| { 98 const stderr_file = io.getStdErr(); 99 if (stderr_file.supportsAnsiEscapeCodes()) { 100 return .escape_codes; 101 } else if (native_os == .windows and stderr_file.isTty()) { 102 return .windows_api; 103 } else { 104 return .no_color; 105 } 106 } 107 } 108 109 /// Tries to print the current stack trace to stderr, unbuffered, and ignores any error returned. 110 /// TODO multithreaded awareness 111 pub fn dumpCurrentStackTrace(start_addr: ?usize) void { 112 nosuspend { 113 const stderr = io.getStdErr().writer(); 114 if (builtin.strip_debug_info) { 115 stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return; 116 return; 117 } 118 const debug_info = getSelfDebugInfo() catch |err| { 119 stderr.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{@errorName(err)}) catch return; 120 return; 121 }; 122 writeCurrentStackTrace(stderr, debug_info, detectTTYConfig(), start_addr) catch |err| { 123 stderr.print("Unable to dump stack trace: {s}\n", .{@errorName(err)}) catch return; 124 return; 125 }; 126 } 127 } 128 129 /// Tries to print the stack trace starting from the supplied base pointer to stderr, 130 /// unbuffered, and ignores any error returned. 131 /// TODO multithreaded awareness 132 pub fn dumpStackTraceFromBase(bp: usize, ip: usize) void { 133 nosuspend { 134 const stderr = io.getStdErr().writer(); 135 if (builtin.strip_debug_info) { 136 stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return; 137 return; 138 } 139 const debug_info = getSelfDebugInfo() catch |err| { 140 stderr.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{@errorName(err)}) catch return; 141 return; 142 }; 143 const tty_config = detectTTYConfig(); 144 printSourceAtAddress(debug_info, stderr, ip, tty_config) catch return; 145 var it = StackIterator.init(null, bp); 146 while (it.next()) |return_address| { 147 printSourceAtAddress(debug_info, stderr, return_address - 1, tty_config) catch return; 148 } 149 } 150 } 151 152 /// Returns a slice with the same pointer as addresses, with a potentially smaller len. 153 /// On Windows, when first_address is not null, we ask for at least 32 stack frames, 154 /// and then try to find the first address. If addresses.len is more than 32, we 155 /// capture that many stack frames exactly, and then look for the first address, 156 /// chopping off the irrelevant frames and shifting so that the returned addresses pointer 157 /// equals the passed in addresses pointer. 158 pub fn captureStackTrace(first_address: ?usize, stack_trace: *builtin.StackTrace) void { 159 if (native_os == .windows) { 160 const addrs = stack_trace.instruction_addresses; 161 const u32_addrs_len = @intCast(u32, addrs.len); 162 const first_addr = first_address orelse { 163 stack_trace.index = windows.ntdll.RtlCaptureStackBackTrace( 164 0, 165 u32_addrs_len, 166 @ptrCast(**c_void, addrs.ptr), 167 null, 168 ); 169 return; 170 }; 171 var addr_buf_stack: [32]usize = undefined; 172 const addr_buf = if (addr_buf_stack.len > addrs.len) addr_buf_stack[0..] else addrs; 173 const n = windows.ntdll.RtlCaptureStackBackTrace(0, u32_addrs_len, @ptrCast(**c_void, addr_buf.ptr), null); 174 const first_index = for (addr_buf[0..n]) |addr, i| { 175 if (addr == first_addr) { 176 break i; 177 } 178 } else { 179 stack_trace.index = 0; 180 return; 181 }; 182 const slice = addr_buf[first_index..n]; 183 // We use a for loop here because slice and addrs may alias. 184 for (slice) |addr, i| { 185 addrs[i] = addr; 186 } 187 stack_trace.index = slice.len; 188 } else { 189 var it = StackIterator.init(first_address, null); 190 for (stack_trace.instruction_addresses) |*addr, i| { 191 addr.* = it.next() orelse { 192 stack_trace.index = i; 193 return; 194 }; 195 } 196 stack_trace.index = stack_trace.instruction_addresses.len; 197 } 198 } 199 200 /// Tries to print a stack trace to stderr, unbuffered, and ignores any error returned. 201 /// TODO multithreaded awareness 202 pub fn dumpStackTrace(stack_trace: builtin.StackTrace) void { 203 nosuspend { 204 const stderr = io.getStdErr().writer(); 205 if (builtin.strip_debug_info) { 206 stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return; 207 return; 208 } 209 const debug_info = getSelfDebugInfo() catch |err| { 210 stderr.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{@errorName(err)}) catch return; 211 return; 212 }; 213 writeStackTrace(stack_trace, stderr, getDebugInfoAllocator(), debug_info, detectTTYConfig()) catch |err| { 214 stderr.print("Unable to dump stack trace: {s}\n", .{@errorName(err)}) catch return; 215 return; 216 }; 217 } 218 } 219 220 /// This function invokes undefined behavior when `ok` is `false`. 221 /// In Debug and ReleaseSafe modes, calls to this function are always 222 /// generated, and the `unreachable` statement triggers a panic. 223 /// In ReleaseFast and ReleaseSmall modes, calls to this function are 224 /// optimized away, and in fact the optimizer is able to use the assertion 225 /// in its heuristics. 226 /// Inside a test block, it is best to use the `std.testing` module rather 227 /// than this function, because this function may not detect a test failure 228 /// in ReleaseFast and ReleaseSmall mode. Outside of a test block, this assert 229 /// function is the correct function to use. 230 pub fn assert(ok: bool) void { 231 if (!ok) unreachable; // assertion failure 232 } 233 234 pub fn panic(comptime format: []const u8, args: anytype) noreturn { 235 @setCold(true); 236 // TODO: remove conditional once wasi / LLVM defines __builtin_return_address 237 const first_trace_addr = if (native_os == .wasi) null else @returnAddress(); 238 panicExtra(null, first_trace_addr, format, args); 239 } 240 241 /// Non-zero whenever the program triggered a panic. 242 /// The counter is incremented/decremented atomically. 243 var panicking: u8 = 0; 244 245 // Locked to avoid interleaving panic messages from multiple threads. 246 var panic_mutex = std.Thread.Mutex{}; 247 248 /// Counts how many times the panic handler is invoked by this thread. 249 /// This is used to catch and handle panics triggered by the panic handler. 250 threadlocal var panic_stage: usize = 0; 251 252 pub fn panicExtra(trace: ?*const builtin.StackTrace, first_trace_addr: ?usize, comptime format: []const u8, args: anytype) noreturn { 253 @setCold(true); 254 255 if (enable_segfault_handler) { 256 // If a segfault happens while panicking, we want it to actually segfault, not trigger 257 // the handler. 258 resetSegfaultHandler(); 259 } 260 261 nosuspend switch (panic_stage) { 262 0 => { 263 panic_stage = 1; 264 265 _ = @atomicRmw(u8, &panicking, .Add, 1, .SeqCst); 266 267 // Make sure to release the mutex when done 268 { 269 const held = panic_mutex.acquire(); 270 defer held.release(); 271 272 const stderr = io.getStdErr().writer(); 273 if (builtin.single_threaded) { 274 stderr.print("panic: ", .{}) catch os.abort(); 275 } else { 276 const current_thread_id = std.Thread.getCurrentThreadId(); 277 stderr.print("thread {d} panic: ", .{current_thread_id}) catch os.abort(); 278 } 279 stderr.print(format ++ "\n", args) catch os.abort(); 280 if (trace) |t| { 281 dumpStackTrace(t.*); 282 } 283 dumpCurrentStackTrace(first_trace_addr); 284 } 285 286 if (@atomicRmw(u8, &panicking, .Sub, 1, .SeqCst) != 1) { 287 // Another thread is panicking, wait for the last one to finish 288 // and call abort() 289 290 // Sleep forever without hammering the CPU 291 var event: std.Thread.StaticResetEvent = .{}; 292 event.wait(); 293 unreachable; 294 } 295 }, 296 1 => { 297 panic_stage = 2; 298 299 // A panic happened while trying to print a previous panic message, 300 // we're still holding the mutex but that's fine as we're going to 301 // call abort() 302 const stderr = io.getStdErr().writer(); 303 stderr.print("Panicked during a panic. Aborting.\n", .{}) catch os.abort(); 304 }, 305 else => { 306 // Panicked while printing "Panicked during a panic." 307 }, 308 }; 309 310 os.abort(); 311 } 312 313 const RED = "\x1b[31;1m"; 314 const GREEN = "\x1b[32;1m"; 315 const CYAN = "\x1b[36;1m"; 316 const WHITE = "\x1b[37;1m"; 317 const BOLD = "\x1b[1m"; 318 const DIM = "\x1b[2m"; 319 const RESET = "\x1b[0m"; 320 321 pub fn writeStackTrace( 322 stack_trace: builtin.StackTrace, 323 out_stream: anytype, 324 allocator: *mem.Allocator, 325 debug_info: *DebugInfo, 326 tty_config: TTY.Config, 327 ) !void { 328 if (builtin.strip_debug_info) return error.MissingDebugInfo; 329 var frame_index: usize = 0; 330 var frames_left: usize = std.math.min(stack_trace.index, stack_trace.instruction_addresses.len); 331 332 while (frames_left != 0) : ({ 333 frames_left -= 1; 334 frame_index = (frame_index + 1) % stack_trace.instruction_addresses.len; 335 }) { 336 const return_address = stack_trace.instruction_addresses[frame_index]; 337 try printSourceAtAddress(debug_info, out_stream, return_address - 1, tty_config); 338 } 339 } 340 341 pub const StackIterator = struct { 342 // Skip every frame before this address is found. 343 first_address: ?usize, 344 // Last known value of the frame pointer register. 345 fp: usize, 346 347 pub fn init(first_address: ?usize, fp: ?usize) StackIterator { 348 if (native_arch == .sparcv9) { 349 // Flush all the register windows on stack. 350 asm volatile ( 351 \\ flushw 352 ::: "memory"); 353 } 354 355 return StackIterator{ 356 .first_address = first_address, 357 .fp = fp orelse @frameAddress(), 358 }; 359 } 360 361 // Offset of the saved BP wrt the frame pointer. 362 const fp_offset = if (native_arch.isRISCV()) 363 // On RISC-V the frame pointer points to the top of the saved register 364 // area, on pretty much every other architecture it points to the stack 365 // slot where the previous frame pointer is saved. 366 2 * @sizeOf(usize) 367 else if (native_arch.isSPARC()) 368 // On SPARC the previous frame pointer is stored at 14 slots past %fp+BIAS. 369 14 * @sizeOf(usize) 370 else 371 0; 372 373 const fp_bias = if (native_arch.isSPARC()) 374 // On SPARC frame pointers are biased by a constant. 375 2047 376 else 377 0; 378 379 // Positive offset of the saved PC wrt the frame pointer. 380 const pc_offset = if (native_arch == .powerpc64le) 381 2 * @sizeOf(usize) 382 else 383 @sizeOf(usize); 384 385 pub fn next(self: *StackIterator) ?usize { 386 var address = self.next_internal() orelse return null; 387 388 if (self.first_address) |first_address| { 389 while (address != first_address) { 390 address = self.next_internal() orelse return null; 391 } 392 self.first_address = null; 393 } 394 395 return address; 396 } 397 398 fn next_internal(self: *StackIterator) ?usize { 399 const fp = if (comptime native_arch.isSPARC()) 400 // On SPARC the offset is positive. (!) 401 math.add(usize, self.fp, fp_offset) catch return null 402 else 403 math.sub(usize, self.fp, fp_offset) catch return null; 404 405 // Sanity check. 406 if (fp == 0 or !mem.isAligned(fp, @alignOf(usize))) 407 return null; 408 409 const new_fp = math.add(usize, @intToPtr(*const usize, fp).*, fp_bias) catch return null; 410 411 // Sanity check: the stack grows down thus all the parent frames must be 412 // be at addresses that are greater (or equal) than the previous one. 413 // A zero frame pointer often signals this is the last frame, that case 414 // is gracefully handled by the next call to next_internal. 415 if (new_fp != 0 and new_fp < self.fp) 416 return null; 417 418 const new_pc = @intToPtr( 419 *const usize, 420 math.add(usize, fp, pc_offset) catch return null, 421 ).*; 422 423 self.fp = new_fp; 424 425 return new_pc; 426 } 427 }; 428 429 pub fn writeCurrentStackTrace( 430 out_stream: anytype, 431 debug_info: *DebugInfo, 432 tty_config: TTY.Config, 433 start_addr: ?usize, 434 ) !void { 435 if (native_os == .windows) { 436 return writeCurrentStackTraceWindows(out_stream, debug_info, tty_config, start_addr); 437 } 438 var it = StackIterator.init(start_addr, null); 439 while (it.next()) |return_address| { 440 try printSourceAtAddress(debug_info, out_stream, return_address - 1, tty_config); 441 } 442 } 443 444 pub fn writeCurrentStackTraceWindows( 445 out_stream: anytype, 446 debug_info: *DebugInfo, 447 tty_config: TTY.Config, 448 start_addr: ?usize, 449 ) !void { 450 var addr_buf: [1024]usize = undefined; 451 const n = windows.ntdll.RtlCaptureStackBackTrace(0, addr_buf.len, @ptrCast(**c_void, &addr_buf), null); 452 const addrs = addr_buf[0..n]; 453 var start_i: usize = if (start_addr) |saddr| blk: { 454 for (addrs) |addr, i| { 455 if (addr == saddr) break :blk i; 456 } 457 return; 458 } else 0; 459 for (addrs[start_i..]) |addr| { 460 try printSourceAtAddress(debug_info, out_stream, addr - 1, tty_config); 461 } 462 } 463 464 pub const TTY = struct { 465 pub const Color = enum { 466 Red, 467 Green, 468 Cyan, 469 White, 470 Dim, 471 Bold, 472 Reset, 473 }; 474 475 pub const Config = enum { 476 no_color, 477 escape_codes, 478 // TODO give this a payload of file handle 479 windows_api, 480 481 pub fn setColor(conf: Config, out_stream: anytype, color: Color) void { 482 nosuspend switch (conf) { 483 .no_color => return, 484 .escape_codes => switch (color) { 485 .Red => out_stream.writeAll(RED) catch return, 486 .Green => out_stream.writeAll(GREEN) catch return, 487 .Cyan => out_stream.writeAll(CYAN) catch return, 488 .White => out_stream.writeAll(WHITE) catch return, 489 .Dim => out_stream.writeAll(DIM) catch return, 490 .Bold => out_stream.writeAll(BOLD) catch return, 491 .Reset => out_stream.writeAll(RESET) catch return, 492 }, 493 .windows_api => if (native_os == .windows) { 494 const stderr_file = io.getStdErr(); 495 const S = struct { 496 var attrs: windows.WORD = undefined; 497 var init_attrs = false; 498 }; 499 if (!S.init_attrs) { 500 S.init_attrs = true; 501 var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined; 502 // TODO handle error 503 _ = windows.kernel32.GetConsoleScreenBufferInfo(stderr_file.handle, &info); 504 S.attrs = info.wAttributes; 505 } 506 507 // TODO handle errors 508 switch (color) { 509 .Red => { 510 _ = windows.SetConsoleTextAttribute(stderr_file.handle, windows.FOREGROUND_RED | windows.FOREGROUND_INTENSITY) catch {}; 511 }, 512 .Green => { 513 _ = windows.SetConsoleTextAttribute(stderr_file.handle, windows.FOREGROUND_GREEN | windows.FOREGROUND_INTENSITY) catch {}; 514 }, 515 .Cyan => { 516 _ = windows.SetConsoleTextAttribute(stderr_file.handle, windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY) catch {}; 517 }, 518 .White, .Bold => { 519 _ = windows.SetConsoleTextAttribute(stderr_file.handle, windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY) catch {}; 520 }, 521 .Dim => { 522 _ = windows.SetConsoleTextAttribute(stderr_file.handle, windows.FOREGROUND_INTENSITY) catch {}; 523 }, 524 .Reset => { 525 _ = windows.SetConsoleTextAttribute(stderr_file.handle, S.attrs) catch {}; 526 }, 527 } 528 } else { 529 unreachable; 530 }, 531 }; 532 } 533 }; 534 }; 535 536 fn machoSearchSymbols(symbols: []const MachoSymbol, address: usize) ?*const MachoSymbol { 537 var min: usize = 0; 538 var max: usize = symbols.len - 1; // Exclude sentinel. 539 while (min < max) { 540 const mid = min + (max - min) / 2; 541 const curr = &symbols[mid]; 542 const next = &symbols[mid + 1]; 543 if (address >= next.address()) { 544 min = mid + 1; 545 } else if (address < curr.address()) { 546 max = mid; 547 } else { 548 return curr; 549 } 550 } 551 return null; 552 } 553 554 /// TODO resources https://github.com/ziglang/zig/issues/4353 555 pub fn printSourceAtAddress(debug_info: *DebugInfo, out_stream: anytype, address: usize, tty_config: TTY.Config) !void { 556 const module = debug_info.getModuleForAddress(address) catch |err| switch (err) { 557 error.MissingDebugInfo, error.InvalidDebugInfo => { 558 return printLineInfo( 559 out_stream, 560 null, 561 address, 562 "???", 563 "???", 564 tty_config, 565 printLineFromFileAnyOs, 566 ); 567 }, 568 else => return err, 569 }; 570 571 const symbol_info = try module.getSymbolAtAddress(address); 572 defer symbol_info.deinit(); 573 574 return printLineInfo( 575 out_stream, 576 symbol_info.line_info, 577 address, 578 symbol_info.symbol_name, 579 symbol_info.compile_unit_name, 580 tty_config, 581 printLineFromFileAnyOs, 582 ); 583 } 584 585 fn printLineInfo( 586 out_stream: anytype, 587 line_info: ?LineInfo, 588 address: usize, 589 symbol_name: []const u8, 590 compile_unit_name: []const u8, 591 tty_config: TTY.Config, 592 comptime printLineFromFile: anytype, 593 ) !void { 594 nosuspend { 595 tty_config.setColor(out_stream, .Bold); 596 597 if (line_info) |*li| { 598 try out_stream.print("{s}:{d}:{d}", .{ li.file_name, li.line, li.column }); 599 } else { 600 try out_stream.writeAll("???:?:?"); 601 } 602 603 tty_config.setColor(out_stream, .Reset); 604 try out_stream.writeAll(": "); 605 tty_config.setColor(out_stream, .Dim); 606 try out_stream.print("0x{x} in {s} ({s})", .{ address, symbol_name, compile_unit_name }); 607 tty_config.setColor(out_stream, .Reset); 608 try out_stream.writeAll("\n"); 609 610 // Show the matching source code line if possible 611 if (line_info) |li| { 612 if (printLineFromFile(out_stream, li)) { 613 if (li.column > 0) { 614 // The caret already takes one char 615 const space_needed = @intCast(usize, li.column - 1); 616 617 try out_stream.writeByteNTimes(' ', space_needed); 618 tty_config.setColor(out_stream, .Green); 619 try out_stream.writeAll("^"); 620 tty_config.setColor(out_stream, .Reset); 621 } 622 try out_stream.writeAll("\n"); 623 } else |err| switch (err) { 624 error.EndOfFile, error.FileNotFound => {}, 625 error.BadPathName => {}, 626 error.AccessDenied => {}, 627 else => return err, 628 } 629 } 630 } 631 } 632 633 // TODO use this 634 pub const OpenSelfDebugInfoError = error{ 635 MissingDebugInfo, 636 OutOfMemory, 637 UnsupportedOperatingSystem, 638 }; 639 640 /// TODO resources https://github.com/ziglang/zig/issues/4353 641 pub fn openSelfDebugInfo(allocator: *mem.Allocator) anyerror!DebugInfo { 642 nosuspend { 643 if (builtin.strip_debug_info) 644 return error.MissingDebugInfo; 645 if (@hasDecl(root, "os") and @hasDecl(root.os, "debug") and @hasDecl(root.os.debug, "openSelfDebugInfo")) { 646 return root.os.debug.openSelfDebugInfo(allocator); 647 } 648 switch (native_os) { 649 .linux, 650 .freebsd, 651 .netbsd, 652 .dragonfly, 653 .openbsd, 654 .macos, 655 .windows, 656 => return DebugInfo.init(allocator), 657 else => return error.UnsupportedDebugInfo, 658 } 659 } 660 } 661 662 /// This takes ownership of coff_file: users of this function should not close 663 /// it themselves, even on error. 664 /// TODO resources https://github.com/ziglang/zig/issues/4353 665 /// TODO it's weird to take ownership even on error, rework this code. 666 fn readCoffDebugInfo(allocator: *mem.Allocator, coff_file: File) !ModuleDebugInfo { 667 nosuspend { 668 errdefer coff_file.close(); 669 670 const coff_obj = try allocator.create(coff.Coff); 671 coff_obj.* = coff.Coff.init(allocator, coff_file); 672 673 var di = ModuleDebugInfo{ 674 .base_address = undefined, 675 .coff = coff_obj, 676 .debug_data = undefined, 677 }; 678 679 try di.coff.loadHeader(); 680 try di.coff.loadSections(); 681 if (di.coff.getSection(".debug_info")) |sec| { 682 // This coff file has embedded DWARF debug info 683 // TODO: free the section data slices 684 const debug_info_data = di.coff.getSectionData(".debug_info", allocator) catch null; 685 const debug_abbrev_data = di.coff.getSectionData(".debug_abbrev", allocator) catch null; 686 const debug_str_data = di.coff.getSectionData(".debug_str", allocator) catch null; 687 const debug_line_data = di.coff.getSectionData(".debug_line", allocator) catch null; 688 const debug_ranges_data = di.coff.getSectionData(".debug_ranges", allocator) catch null; 689 690 var dwarf = DW.DwarfInfo{ 691 .endian = native_endian, 692 .debug_info = debug_info_data orelse return error.MissingDebugInfo, 693 .debug_abbrev = debug_abbrev_data orelse return error.MissingDebugInfo, 694 .debug_str = debug_str_data orelse return error.MissingDebugInfo, 695 .debug_line = debug_line_data orelse return error.MissingDebugInfo, 696 .debug_ranges = debug_ranges_data, 697 }; 698 try DW.openDwarfDebugInfo(&dwarf, allocator); 699 di.debug_data = PdbOrDwarf{ .dwarf = dwarf }; 700 return di; 701 } 702 703 var path_buf: [windows.MAX_PATH]u8 = undefined; 704 const len = try di.coff.getPdbPath(path_buf[0..]); 705 const raw_path = path_buf[0..len]; 706 707 const path = try fs.path.resolve(allocator, &[_][]const u8{raw_path}); 708 defer allocator.free(path); 709 710 di.debug_data = PdbOrDwarf{ .pdb = undefined }; 711 di.debug_data.pdb = try pdb.Pdb.init(allocator, path); 712 try di.debug_data.pdb.parseInfoStream(); 713 try di.debug_data.pdb.parseDbiStream(); 714 715 if (!mem.eql(u8, &di.coff.guid, &di.debug_data.pdb.guid) or di.coff.age != di.debug_data.pdb.age) 716 return error.InvalidDebugInfo; 717 718 return di; 719 } 720 } 721 722 fn chopSlice(ptr: []const u8, offset: u64, size: u64) ![]const u8 { 723 const start = try math.cast(usize, offset); 724 const end = start + try math.cast(usize, size); 725 return ptr[start..end]; 726 } 727 728 /// This takes ownership of elf_file: users of this function should not close 729 /// it themselves, even on error. 730 /// TODO resources https://github.com/ziglang/zig/issues/4353 731 /// TODO it's weird to take ownership even on error, rework this code. 732 pub fn readElfDebugInfo(allocator: *mem.Allocator, elf_file: File) !ModuleDebugInfo { 733 nosuspend { 734 const mapped_mem = try mapWholeFile(elf_file); 735 const hdr = @ptrCast(*const elf.Ehdr, &mapped_mem[0]); 736 if (!mem.eql(u8, hdr.e_ident[0..4], "\x7fELF")) return error.InvalidElfMagic; 737 if (hdr.e_ident[elf.EI_VERSION] != 1) return error.InvalidElfVersion; 738 739 const endian: builtin.Endian = switch (hdr.e_ident[elf.EI_DATA]) { 740 elf.ELFDATA2LSB => .Little, 741 elf.ELFDATA2MSB => .Big, 742 else => return error.InvalidElfEndian, 743 }; 744 assert(endian == native_endian); // this is our own debug info 745 746 const shoff = hdr.e_shoff; 747 const str_section_off = shoff + @as(u64, hdr.e_shentsize) * @as(u64, hdr.e_shstrndx); 748 const str_shdr = @ptrCast( 749 *const elf.Shdr, 750 @alignCast(@alignOf(elf.Shdr), &mapped_mem[try math.cast(usize, str_section_off)]), 751 ); 752 const header_strings = mapped_mem[str_shdr.sh_offset .. str_shdr.sh_offset + str_shdr.sh_size]; 753 const shdrs = @ptrCast( 754 [*]const elf.Shdr, 755 @alignCast(@alignOf(elf.Shdr), &mapped_mem[shoff]), 756 )[0..hdr.e_shnum]; 757 758 var opt_debug_info: ?[]const u8 = null; 759 var opt_debug_abbrev: ?[]const u8 = null; 760 var opt_debug_str: ?[]const u8 = null; 761 var opt_debug_line: ?[]const u8 = null; 762 var opt_debug_ranges: ?[]const u8 = null; 763 764 for (shdrs) |*shdr| { 765 if (shdr.sh_type == elf.SHT_NULL) continue; 766 767 const name = std.mem.span(std.meta.assumeSentinel(header_strings[shdr.sh_name..].ptr, 0)); 768 if (mem.eql(u8, name, ".debug_info")) { 769 opt_debug_info = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size); 770 } else if (mem.eql(u8, name, ".debug_abbrev")) { 771 opt_debug_abbrev = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size); 772 } else if (mem.eql(u8, name, ".debug_str")) { 773 opt_debug_str = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size); 774 } else if (mem.eql(u8, name, ".debug_line")) { 775 opt_debug_line = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size); 776 } else if (mem.eql(u8, name, ".debug_ranges")) { 777 opt_debug_ranges = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size); 778 } 779 } 780 781 var di = DW.DwarfInfo{ 782 .endian = endian, 783 .debug_info = opt_debug_info orelse return error.MissingDebugInfo, 784 .debug_abbrev = opt_debug_abbrev orelse return error.MissingDebugInfo, 785 .debug_str = opt_debug_str orelse return error.MissingDebugInfo, 786 .debug_line = opt_debug_line orelse return error.MissingDebugInfo, 787 .debug_ranges = opt_debug_ranges, 788 }; 789 790 try DW.openDwarfDebugInfo(&di, allocator); 791 792 return ModuleDebugInfo{ 793 .base_address = undefined, 794 .dwarf = di, 795 .mapped_memory = mapped_mem, 796 }; 797 } 798 } 799 800 /// TODO resources https://github.com/ziglang/zig/issues/4353 801 /// This takes ownership of macho_file: users of this function should not close 802 /// it themselves, even on error. 803 /// TODO it's weird to take ownership even on error, rework this code. 804 fn readMachODebugInfo(allocator: *mem.Allocator, macho_file: File) !ModuleDebugInfo { 805 const mapped_mem = try mapWholeFile(macho_file); 806 807 const hdr = @ptrCast( 808 *const macho.mach_header_64, 809 @alignCast(@alignOf(macho.mach_header_64), mapped_mem.ptr), 810 ); 811 if (hdr.magic != macho.MH_MAGIC_64) 812 return error.InvalidDebugInfo; 813 814 const hdr_base = @ptrCast([*]const u8, hdr); 815 var ptr = hdr_base + @sizeOf(macho.mach_header_64); 816 var ncmd: u32 = hdr.ncmds; 817 const symtab = while (ncmd != 0) : (ncmd -= 1) { 818 const lc = @ptrCast(*const std.macho.load_command, ptr); 819 switch (lc.cmd) { 820 std.macho.LC_SYMTAB => break @ptrCast(*const std.macho.symtab_command, ptr), 821 else => {}, 822 } 823 ptr = @alignCast(@alignOf(std.macho.load_command), ptr + lc.cmdsize); 824 } else { 825 return error.MissingDebugInfo; 826 }; 827 const syms = @ptrCast([*]const macho.nlist_64, @alignCast(@alignOf(macho.nlist_64), hdr_base + symtab.symoff))[0..symtab.nsyms]; 828 const strings = @ptrCast([*]const u8, hdr_base + symtab.stroff)[0 .. symtab.strsize - 1 :0]; 829 830 const symbols_buf = try allocator.alloc(MachoSymbol, syms.len); 831 832 var ofile: ?*const macho.nlist_64 = null; 833 var reloc: u64 = 0; 834 var symbol_index: usize = 0; 835 var last_len: u64 = 0; 836 for (syms) |*sym| { 837 if (sym.n_type & std.macho.N_STAB != 0) { 838 switch (sym.n_type) { 839 std.macho.N_OSO => { 840 ofile = sym; 841 reloc = 0; 842 }, 843 std.macho.N_FUN => { 844 if (sym.n_sect == 0) { 845 last_len = sym.n_value; 846 } else { 847 symbols_buf[symbol_index] = MachoSymbol{ 848 .nlist = sym, 849 .ofile = ofile, 850 .reloc = reloc, 851 }; 852 symbol_index += 1; 853 } 854 }, 855 std.macho.N_BNSYM => { 856 if (reloc == 0) { 857 reloc = sym.n_value; 858 } 859 }, 860 else => continue, 861 } 862 } 863 } 864 const sentinel = try allocator.create(macho.nlist_64); 865 sentinel.* = macho.nlist_64{ 866 .n_strx = 0, 867 .n_type = 36, 868 .n_sect = 0, 869 .n_desc = 0, 870 .n_value = symbols_buf[symbol_index - 1].nlist.n_value + last_len, 871 }; 872 873 const symbols = allocator.shrink(symbols_buf, symbol_index); 874 875 // Even though lld emits symbols in ascending order, this debug code 876 // should work for programs linked in any valid way. 877 // This sort is so that we can binary search later. 878 std.sort.sort(MachoSymbol, symbols, {}, MachoSymbol.addressLessThan); 879 880 return ModuleDebugInfo{ 881 .base_address = undefined, 882 .mapped_memory = mapped_mem, 883 .ofiles = ModuleDebugInfo.OFileTable.init(allocator), 884 .symbols = symbols, 885 .strings = strings, 886 }; 887 } 888 889 fn printLineFromFileAnyOs(out_stream: anytype, line_info: LineInfo) !void { 890 // Need this to always block even in async I/O mode, because this could potentially 891 // be called from e.g. the event loop code crashing. 892 var f = try fs.cwd().openFile(line_info.file_name, .{ .intended_io_mode = .blocking }); 893 defer f.close(); 894 // TODO fstat and make sure that the file has the correct size 895 896 var buf: [mem.page_size]u8 = undefined; 897 var line: usize = 1; 898 var column: usize = 1; 899 while (true) { 900 const amt_read = try f.read(buf[0..]); 901 const slice = buf[0..amt_read]; 902 903 for (slice) |byte| { 904 if (line == line_info.line) { 905 try out_stream.writeByte(byte); 906 if (byte == '\n') { 907 return; 908 } 909 } 910 if (byte == '\n') { 911 line += 1; 912 column = 1; 913 } else { 914 column += 1; 915 } 916 } 917 918 if (amt_read < buf.len) return error.EndOfFile; 919 } 920 } 921 922 const MachoSymbol = struct { 923 nlist: *const macho.nlist_64, 924 ofile: ?*const macho.nlist_64, 925 reloc: u64, 926 927 /// Returns the address from the macho file 928 fn address(self: MachoSymbol) u64 { 929 return self.nlist.n_value; 930 } 931 932 fn addressLessThan(context: void, lhs: MachoSymbol, rhs: MachoSymbol) bool { 933 return lhs.address() < rhs.address(); 934 } 935 }; 936 937 /// `file` is expected to have been opened with .intended_io_mode == .blocking. 938 /// Takes ownership of file, even on error. 939 /// TODO it's weird to take ownership even on error, rework this code. 940 fn mapWholeFile(file: File) ![]align(mem.page_size) const u8 { 941 nosuspend { 942 defer file.close(); 943 944 const file_len = try math.cast(usize, try file.getEndPos()); 945 const mapped_mem = try os.mmap( 946 null, 947 file_len, 948 os.PROT_READ, 949 os.MAP_SHARED, 950 file.handle, 951 0, 952 ); 953 errdefer os.munmap(mapped_mem); 954 955 return mapped_mem; 956 } 957 } 958 959 pub const DebugInfo = struct { 960 allocator: *mem.Allocator, 961 address_map: std.AutoHashMap(usize, *ModuleDebugInfo), 962 963 pub fn init(allocator: *mem.Allocator) DebugInfo { 964 return DebugInfo{ 965 .allocator = allocator, 966 .address_map = std.AutoHashMap(usize, *ModuleDebugInfo).init(allocator), 967 }; 968 } 969 970 pub fn deinit(self: *DebugInfo) void { 971 // TODO: resources https://github.com/ziglang/zig/issues/4353 972 self.address_map.deinit(); 973 } 974 975 pub fn getModuleForAddress(self: *DebugInfo, address: usize) !*ModuleDebugInfo { 976 if (comptime std.Target.current.isDarwin()) { 977 return self.lookupModuleDyld(address); 978 } else if (native_os == .windows) { 979 return self.lookupModuleWin32(address); 980 } else if (native_os == .haiku) { 981 return self.lookupModuleHaiku(address); 982 } else { 983 return self.lookupModuleDl(address); 984 } 985 } 986 987 fn lookupModuleDyld(self: *DebugInfo, address: usize) !*ModuleDebugInfo { 988 const image_count = std.c._dyld_image_count(); 989 990 var i: u32 = 0; 991 while (i < image_count) : (i += 1) { 992 const base_address = std.c._dyld_get_image_vmaddr_slide(i); 993 994 if (address < base_address) continue; 995 996 const header = std.c._dyld_get_image_header(i) orelse continue; 997 // The array of load commands is right after the header 998 var cmd_ptr = @intToPtr([*]u8, @ptrToInt(header) + @sizeOf(macho.mach_header_64)); 999 1000 var cmds = header.ncmds; 1001 while (cmds != 0) : (cmds -= 1) { 1002 const lc = @ptrCast( 1003 *macho.load_command, 1004 @alignCast(@alignOf(macho.load_command), cmd_ptr), 1005 ); 1006 cmd_ptr += lc.cmdsize; 1007 if (lc.cmd != macho.LC_SEGMENT_64) continue; 1008 1009 const segment_cmd = @ptrCast( 1010 *const std.macho.segment_command_64, 1011 @alignCast(@alignOf(std.macho.segment_command_64), lc), 1012 ); 1013 1014 const rebased_address = address - base_address; 1015 const seg_start = segment_cmd.vmaddr; 1016 const seg_end = seg_start + segment_cmd.vmsize; 1017 1018 if (rebased_address >= seg_start and rebased_address < seg_end) { 1019 if (self.address_map.get(base_address)) |obj_di| { 1020 return obj_di; 1021 } 1022 1023 const obj_di = try self.allocator.create(ModuleDebugInfo); 1024 errdefer self.allocator.destroy(obj_di); 1025 1026 const macho_path = mem.spanZ(std.c._dyld_get_image_name(i)); 1027 const macho_file = fs.cwd().openFile(macho_path, .{ .intended_io_mode = .blocking }) catch |err| switch (err) { 1028 error.FileNotFound => return error.MissingDebugInfo, 1029 else => return err, 1030 }; 1031 obj_di.* = try readMachODebugInfo(self.allocator, macho_file); 1032 obj_di.base_address = base_address; 1033 1034 try self.address_map.putNoClobber(base_address, obj_di); 1035 1036 return obj_di; 1037 } 1038 } 1039 } 1040 1041 return error.MissingDebugInfo; 1042 } 1043 1044 fn lookupModuleWin32(self: *DebugInfo, address: usize) !*ModuleDebugInfo { 1045 const process_handle = windows.kernel32.GetCurrentProcess(); 1046 1047 // Find how many modules are actually loaded 1048 var dummy: windows.HMODULE = undefined; 1049 var bytes_needed: windows.DWORD = undefined; 1050 if (windows.kernel32.K32EnumProcessModules( 1051 process_handle, 1052 @ptrCast([*]windows.HMODULE, &dummy), 1053 0, 1054 &bytes_needed, 1055 ) == 0) 1056 return error.MissingDebugInfo; 1057 1058 const needed_modules = bytes_needed / @sizeOf(windows.HMODULE); 1059 1060 // Fetch the complete module list 1061 var modules = try self.allocator.alloc(windows.HMODULE, needed_modules); 1062 defer self.allocator.free(modules); 1063 if (windows.kernel32.K32EnumProcessModules( 1064 process_handle, 1065 modules.ptr, 1066 try math.cast(windows.DWORD, modules.len * @sizeOf(windows.HMODULE)), 1067 &bytes_needed, 1068 ) == 0) 1069 return error.MissingDebugInfo; 1070 1071 // There's an unavoidable TOCTOU problem here, the module list may have 1072 // changed between the two EnumProcessModules call. 1073 // Pick the smallest amount of elements to avoid processing garbage. 1074 const needed_modules_after = bytes_needed / @sizeOf(windows.HMODULE); 1075 const loaded_modules = math.min(needed_modules, needed_modules_after); 1076 1077 for (modules[0..loaded_modules]) |module| { 1078 var info: windows.MODULEINFO = undefined; 1079 if (windows.kernel32.K32GetModuleInformation( 1080 process_handle, 1081 module, 1082 &info, 1083 @sizeOf(@TypeOf(info)), 1084 ) == 0) 1085 return error.MissingDebugInfo; 1086 1087 const seg_start = @ptrToInt(info.lpBaseOfDll); 1088 const seg_end = seg_start + info.SizeOfImage; 1089 1090 if (address >= seg_start and address < seg_end) { 1091 if (self.address_map.get(seg_start)) |obj_di| { 1092 return obj_di; 1093 } 1094 1095 var name_buffer: [windows.PATH_MAX_WIDE + 4:0]u16 = undefined; 1096 // openFileAbsoluteW requires the prefix to be present 1097 mem.copy(u16, name_buffer[0..4], &[_]u16{ '\\', '?', '?', '\\' }); 1098 const len = windows.kernel32.K32GetModuleFileNameExW( 1099 process_handle, 1100 module, 1101 @ptrCast(windows.LPWSTR, &name_buffer[4]), 1102 windows.PATH_MAX_WIDE, 1103 ); 1104 assert(len > 0); 1105 1106 const obj_di = try self.allocator.create(ModuleDebugInfo); 1107 errdefer self.allocator.destroy(obj_di); 1108 1109 const coff_file = fs.openFileAbsoluteW(name_buffer[0 .. len + 4 :0], .{}) catch |err| switch (err) { 1110 error.FileNotFound => return error.MissingDebugInfo, 1111 else => return err, 1112 }; 1113 obj_di.* = try readCoffDebugInfo(self.allocator, coff_file); 1114 obj_di.base_address = seg_start; 1115 1116 try self.address_map.putNoClobber(seg_start, obj_di); 1117 1118 return obj_di; 1119 } 1120 } 1121 1122 return error.MissingDebugInfo; 1123 } 1124 1125 fn lookupModuleDl(self: *DebugInfo, address: usize) !*ModuleDebugInfo { 1126 var ctx: struct { 1127 // Input 1128 address: usize, 1129 // Output 1130 base_address: usize = undefined, 1131 name: []const u8 = undefined, 1132 } = .{ .address = address }; 1133 const CtxTy = @TypeOf(ctx); 1134 1135 if (os.dl_iterate_phdr(&ctx, anyerror, struct { 1136 fn callback(info: *os.dl_phdr_info, size: usize, context: *CtxTy) !void { 1137 // The base address is too high 1138 if (context.address < info.dlpi_addr) 1139 return; 1140 1141 const phdrs = info.dlpi_phdr[0..info.dlpi_phnum]; 1142 for (phdrs) |*phdr| { 1143 if (phdr.p_type != elf.PT_LOAD) continue; 1144 1145 const seg_start = info.dlpi_addr + phdr.p_vaddr; 1146 const seg_end = seg_start + phdr.p_memsz; 1147 1148 if (context.address >= seg_start and context.address < seg_end) { 1149 // Android libc uses NULL instead of an empty string to mark the 1150 // main program 1151 context.name = mem.spanZ(info.dlpi_name) orelse ""; 1152 context.base_address = info.dlpi_addr; 1153 // Stop the iteration 1154 return error.Found; 1155 } 1156 } 1157 } 1158 }.callback)) { 1159 return error.MissingDebugInfo; 1160 } else |err| switch (err) { 1161 error.Found => {}, 1162 else => return error.MissingDebugInfo, 1163 } 1164 1165 if (self.address_map.get(ctx.base_address)) |obj_di| { 1166 return obj_di; 1167 } 1168 1169 const obj_di = try self.allocator.create(ModuleDebugInfo); 1170 errdefer self.allocator.destroy(obj_di); 1171 1172 // TODO https://github.com/ziglang/zig/issues/5525 1173 const copy = if (ctx.name.len > 0) 1174 fs.cwd().openFile(ctx.name, .{ .intended_io_mode = .blocking }) 1175 else 1176 fs.openSelfExe(.{ .intended_io_mode = .blocking }); 1177 1178 const elf_file = copy catch |err| switch (err) { 1179 error.FileNotFound => return error.MissingDebugInfo, 1180 else => return err, 1181 }; 1182 1183 obj_di.* = try readElfDebugInfo(self.allocator, elf_file); 1184 obj_di.base_address = ctx.base_address; 1185 1186 try self.address_map.putNoClobber(ctx.base_address, obj_di); 1187 1188 return obj_di; 1189 } 1190 1191 fn lookupModuleHaiku(self: *DebugInfo, address: usize) !*ModuleDebugInfo { 1192 @panic("TODO implement lookup module for Haiku"); 1193 } 1194 }; 1195 1196 pub const ModuleDebugInfo = switch (native_os) { 1197 .macos, .ios, .watchos, .tvos => struct { 1198 base_address: usize, 1199 mapped_memory: []const u8, 1200 symbols: []const MachoSymbol, 1201 strings: [:0]const u8, 1202 ofiles: OFileTable, 1203 1204 const OFileTable = std.StringHashMap(DW.DwarfInfo); 1205 1206 pub fn allocator(self: @This()) *mem.Allocator { 1207 return self.ofiles.allocator; 1208 } 1209 1210 fn loadOFile(self: *@This(), o_file_path: []const u8) !DW.DwarfInfo { 1211 const o_file = try fs.cwd().openFile(o_file_path, .{ .intended_io_mode = .blocking }); 1212 const mapped_mem = try mapWholeFile(o_file); 1213 1214 const hdr = @ptrCast( 1215 *const macho.mach_header_64, 1216 @alignCast(@alignOf(macho.mach_header_64), mapped_mem.ptr), 1217 ); 1218 if (hdr.magic != std.macho.MH_MAGIC_64) 1219 return error.InvalidDebugInfo; 1220 1221 const hdr_base = @ptrCast([*]const u8, hdr); 1222 var ptr = hdr_base + @sizeOf(macho.mach_header_64); 1223 var ncmd: u32 = hdr.ncmds; 1224 const segcmd = while (ncmd != 0) : (ncmd -= 1) { 1225 const lc = @ptrCast(*const std.macho.load_command, ptr); 1226 switch (lc.cmd) { 1227 std.macho.LC_SEGMENT_64 => { 1228 break @ptrCast( 1229 *const std.macho.segment_command_64, 1230 @alignCast(@alignOf(std.macho.segment_command_64), ptr), 1231 ); 1232 }, 1233 else => {}, 1234 } 1235 ptr = @alignCast(@alignOf(std.macho.load_command), ptr + lc.cmdsize); 1236 } else { 1237 return error.MissingDebugInfo; 1238 }; 1239 1240 var opt_debug_line: ?*const macho.section_64 = null; 1241 var opt_debug_info: ?*const macho.section_64 = null; 1242 var opt_debug_abbrev: ?*const macho.section_64 = null; 1243 var opt_debug_str: ?*const macho.section_64 = null; 1244 var opt_debug_ranges: ?*const macho.section_64 = null; 1245 1246 const sections = @ptrCast( 1247 [*]const macho.section_64, 1248 @alignCast(@alignOf(macho.section_64), ptr + @sizeOf(std.macho.segment_command_64)), 1249 )[0..segcmd.nsects]; 1250 for (sections) |*sect| { 1251 // The section name may not exceed 16 chars and a trailing null may 1252 // not be present 1253 const name = if (mem.indexOfScalar(u8, sect.sectname[0..], 0)) |last| 1254 sect.sectname[0..last] 1255 else 1256 sect.sectname[0..]; 1257 1258 if (mem.eql(u8, name, "__debug_line")) { 1259 opt_debug_line = sect; 1260 } else if (mem.eql(u8, name, "__debug_info")) { 1261 opt_debug_info = sect; 1262 } else if (mem.eql(u8, name, "__debug_abbrev")) { 1263 opt_debug_abbrev = sect; 1264 } else if (mem.eql(u8, name, "__debug_str")) { 1265 opt_debug_str = sect; 1266 } else if (mem.eql(u8, name, "__debug_ranges")) { 1267 opt_debug_ranges = sect; 1268 } 1269 } 1270 1271 const debug_line = opt_debug_line orelse 1272 return error.MissingDebugInfo; 1273 const debug_info = opt_debug_info orelse 1274 return error.MissingDebugInfo; 1275 const debug_str = opt_debug_str orelse 1276 return error.MissingDebugInfo; 1277 const debug_abbrev = opt_debug_abbrev orelse 1278 return error.MissingDebugInfo; 1279 1280 var di = DW.DwarfInfo{ 1281 .endian = .Little, 1282 .debug_info = try chopSlice(mapped_mem, debug_info.offset, debug_info.size), 1283 .debug_abbrev = try chopSlice(mapped_mem, debug_abbrev.offset, debug_abbrev.size), 1284 .debug_str = try chopSlice(mapped_mem, debug_str.offset, debug_str.size), 1285 .debug_line = try chopSlice(mapped_mem, debug_line.offset, debug_line.size), 1286 .debug_ranges = if (opt_debug_ranges) |debug_ranges| 1287 try chopSlice(mapped_mem, debug_ranges.offset, debug_ranges.size) 1288 else 1289 null, 1290 }; 1291 1292 try DW.openDwarfDebugInfo(&di, self.allocator()); 1293 1294 // Add the debug info to the cache 1295 try self.ofiles.putNoClobber(o_file_path, di); 1296 1297 return di; 1298 } 1299 1300 pub fn getSymbolAtAddress(self: *@This(), address: usize) !SymbolInfo { 1301 nosuspend { 1302 // Translate the VA into an address into this object 1303 const relocated_address = address - self.base_address; 1304 assert(relocated_address >= 0x100000000); 1305 1306 // Find the .o file where this symbol is defined 1307 const symbol = machoSearchSymbols(self.symbols, relocated_address) orelse 1308 return SymbolInfo{}; 1309 1310 // Take the symbol name from the N_FUN STAB entry, we're going to 1311 // use it if we fail to find the DWARF infos 1312 const stab_symbol = mem.spanZ(self.strings[symbol.nlist.n_strx..]); 1313 1314 if (symbol.ofile == null) 1315 return SymbolInfo{ .symbol_name = stab_symbol }; 1316 1317 const o_file_path = mem.spanZ(self.strings[symbol.ofile.?.n_strx..]); 1318 1319 // Check if its debug infos are already in the cache 1320 var o_file_di = self.ofiles.get(o_file_path) orelse 1321 (self.loadOFile(o_file_path) catch |err| switch (err) { 1322 error.FileNotFound, 1323 error.MissingDebugInfo, 1324 error.InvalidDebugInfo, 1325 => { 1326 return SymbolInfo{ .symbol_name = stab_symbol }; 1327 }, 1328 else => return err, 1329 }); 1330 1331 // Translate again the address, this time into an address inside the 1332 // .o file 1333 const relocated_address_o = relocated_address - symbol.reloc; 1334 1335 if (o_file_di.findCompileUnit(relocated_address_o)) |compile_unit| { 1336 return SymbolInfo{ 1337 .symbol_name = o_file_di.getSymbolName(relocated_address_o) orelse "???", 1338 .compile_unit_name = compile_unit.die.getAttrString(&o_file_di, DW.AT_name) catch |err| switch (err) { 1339 error.MissingDebugInfo, error.InvalidDebugInfo => "???", 1340 else => return err, 1341 }, 1342 .line_info = o_file_di.getLineNumberInfo(compile_unit.*, relocated_address_o) catch |err| switch (err) { 1343 error.MissingDebugInfo, error.InvalidDebugInfo => null, 1344 else => return err, 1345 }, 1346 }; 1347 } else |err| switch (err) { 1348 error.MissingDebugInfo, error.InvalidDebugInfo => { 1349 return SymbolInfo{ .symbol_name = stab_symbol }; 1350 }, 1351 else => return err, 1352 } 1353 1354 unreachable; 1355 } 1356 } 1357 }, 1358 .uefi, .windows => struct { 1359 base_address: usize, 1360 debug_data: PdbOrDwarf, 1361 coff: *coff.Coff, 1362 1363 pub fn allocator(self: @This()) *mem.Allocator { 1364 return self.coff.allocator; 1365 } 1366 1367 pub fn getSymbolAtAddress(self: *@This(), address: usize) !SymbolInfo { 1368 // Translate the VA into an address into this object 1369 const relocated_address = address - self.base_address; 1370 1371 switch (self.debug_data) { 1372 .dwarf => |*dwarf| { 1373 const dwarf_address = relocated_address + self.coff.pe_header.image_base; 1374 return getSymbolFromDwarf(dwarf_address, dwarf); 1375 }, 1376 .pdb => { 1377 // fallthrough to pdb handling 1378 }, 1379 } 1380 1381 var coff_section: *coff.Section = undefined; 1382 const mod_index = for (self.debug_data.pdb.sect_contribs) |sect_contrib| { 1383 if (sect_contrib.Section > self.coff.sections.items.len) continue; 1384 // Remember that SectionContribEntry.Section is 1-based. 1385 coff_section = &self.coff.sections.items[sect_contrib.Section - 1]; 1386 1387 const vaddr_start = coff_section.header.virtual_address + sect_contrib.Offset; 1388 const vaddr_end = vaddr_start + sect_contrib.Size; 1389 if (relocated_address >= vaddr_start and relocated_address < vaddr_end) { 1390 break sect_contrib.ModuleIndex; 1391 } 1392 } else { 1393 // we have no information to add to the address 1394 return SymbolInfo{}; 1395 }; 1396 1397 const module = (try self.debug_data.pdb.getModule(mod_index)) orelse 1398 return error.InvalidDebugInfo; 1399 const obj_basename = fs.path.basename(module.obj_file_name); 1400 1401 const symbol_name = self.debug_data.pdb.getSymbolName( 1402 module, 1403 relocated_address - coff_section.header.virtual_address, 1404 ) orelse "???"; 1405 const opt_line_info = try self.debug_data.pdb.getLineNumberInfo( 1406 module, 1407 relocated_address - coff_section.header.virtual_address, 1408 ); 1409 1410 return SymbolInfo{ 1411 .symbol_name = symbol_name, 1412 .compile_unit_name = obj_basename, 1413 .line_info = opt_line_info, 1414 }; 1415 } 1416 }, 1417 .linux, .netbsd, .freebsd, .dragonfly, .openbsd, .haiku => struct { 1418 base_address: usize, 1419 dwarf: DW.DwarfInfo, 1420 mapped_memory: []const u8, 1421 1422 pub fn getSymbolAtAddress(self: *@This(), address: usize) !SymbolInfo { 1423 // Translate the VA into an address into this object 1424 const relocated_address = address - self.base_address; 1425 return getSymbolFromDwarf(relocated_address, &self.dwarf); 1426 } 1427 }, 1428 else => DW.DwarfInfo, 1429 }; 1430 1431 fn getSymbolFromDwarf(address: u64, di: *DW.DwarfInfo) !SymbolInfo { 1432 if (nosuspend di.findCompileUnit(address)) |compile_unit| { 1433 return SymbolInfo{ 1434 .symbol_name = nosuspend di.getSymbolName(address) orelse "???", 1435 .compile_unit_name = compile_unit.die.getAttrString(di, DW.AT_name) catch |err| switch (err) { 1436 error.MissingDebugInfo, error.InvalidDebugInfo => "???", 1437 else => return err, 1438 }, 1439 .line_info = nosuspend di.getLineNumberInfo(compile_unit.*, address) catch |err| switch (err) { 1440 error.MissingDebugInfo, error.InvalidDebugInfo => null, 1441 else => return err, 1442 }, 1443 }; 1444 } else |err| switch (err) { 1445 error.MissingDebugInfo, error.InvalidDebugInfo => { 1446 return SymbolInfo{}; 1447 }, 1448 else => return err, 1449 } 1450 } 1451 1452 /// TODO multithreaded awareness 1453 var debug_info_allocator: ?*mem.Allocator = null; 1454 var debug_info_arena_allocator: std.heap.ArenaAllocator = undefined; 1455 fn getDebugInfoAllocator() *mem.Allocator { 1456 if (debug_info_allocator) |a| return a; 1457 1458 debug_info_arena_allocator = std.heap.ArenaAllocator.init(std.heap.page_allocator); 1459 debug_info_allocator = &debug_info_arena_allocator.allocator; 1460 return &debug_info_arena_allocator.allocator; 1461 } 1462 1463 /// Whether or not the current target can print useful debug information when a segfault occurs. 1464 pub const have_segfault_handling_support = switch (native_os) { 1465 .linux, .netbsd => true, 1466 .windows => true, 1467 .freebsd, .openbsd => @hasDecl(os, "ucontext_t"), 1468 else => false, 1469 }; 1470 pub const enable_segfault_handler: bool = if (@hasDecl(root, "enable_segfault_handler")) 1471 root.enable_segfault_handler 1472 else 1473 runtime_safety and have_segfault_handling_support; 1474 1475 pub fn maybeEnableSegfaultHandler() void { 1476 if (enable_segfault_handler) { 1477 std.debug.attachSegfaultHandler(); 1478 } 1479 } 1480 1481 var windows_segfault_handle: ?windows.HANDLE = null; 1482 1483 /// Attaches a global SIGSEGV handler which calls @panic("segmentation fault"); 1484 pub fn attachSegfaultHandler() void { 1485 if (!have_segfault_handling_support) { 1486 @compileError("segfault handler not supported for this target"); 1487 } 1488 if (native_os == .windows) { 1489 windows_segfault_handle = windows.kernel32.AddVectoredExceptionHandler(0, handleSegfaultWindows); 1490 return; 1491 } 1492 var act = os.Sigaction{ 1493 .handler = .{ .sigaction = handleSegfaultLinux }, 1494 .mask = os.empty_sigset, 1495 .flags = (os.SA_SIGINFO | os.SA_RESTART | os.SA_RESETHAND), 1496 }; 1497 1498 os.sigaction(os.SIGSEGV, &act, null); 1499 os.sigaction(os.SIGILL, &act, null); 1500 os.sigaction(os.SIGBUS, &act, null); 1501 } 1502 1503 fn resetSegfaultHandler() void { 1504 if (native_os == .windows) { 1505 if (windows_segfault_handle) |handle| { 1506 assert(windows.kernel32.RemoveVectoredExceptionHandler(handle) != 0); 1507 windows_segfault_handle = null; 1508 } 1509 return; 1510 } 1511 var act = os.Sigaction{ 1512 .handler = .{ .sigaction = os.SIG_DFL }, 1513 .mask = os.empty_sigset, 1514 .flags = 0, 1515 }; 1516 os.sigaction(os.SIGSEGV, &act, null); 1517 os.sigaction(os.SIGILL, &act, null); 1518 os.sigaction(os.SIGBUS, &act, null); 1519 } 1520 1521 fn handleSegfaultLinux(sig: i32, info: *const os.siginfo_t, ctx_ptr: ?*const c_void) callconv(.C) noreturn { 1522 // Reset to the default handler so that if a segfault happens in this handler it will crash 1523 // the process. Also when this handler returns, the original instruction will be repeated 1524 // and the resulting segfault will crash the process rather than continually dump stack traces. 1525 resetSegfaultHandler(); 1526 1527 const addr = switch (native_os) { 1528 .linux => @ptrToInt(info.fields.sigfault.addr), 1529 .freebsd => @ptrToInt(info.addr), 1530 .netbsd => @ptrToInt(info.info.reason.fault.addr), 1531 .openbsd => @ptrToInt(info.data.fault.addr), 1532 else => unreachable, 1533 }; 1534 1535 // Don't use std.debug.print() as stderr_mutex may still be locked. 1536 nosuspend { 1537 const stderr = io.getStdErr().writer(); 1538 _ = switch (sig) { 1539 os.SIGSEGV => stderr.print("Segmentation fault at address 0x{x}\n", .{addr}), 1540 os.SIGILL => stderr.print("Illegal instruction at address 0x{x}\n", .{addr}), 1541 os.SIGBUS => stderr.print("Bus error at address 0x{x}\n", .{addr}), 1542 else => unreachable, 1543 } catch os.abort(); 1544 } 1545 1546 switch (native_arch) { 1547 .i386 => { 1548 const ctx = @ptrCast(*const os.ucontext_t, @alignCast(@alignOf(os.ucontext_t), ctx_ptr)); 1549 const ip = @intCast(usize, ctx.mcontext.gregs[os.REG_EIP]); 1550 const bp = @intCast(usize, ctx.mcontext.gregs[os.REG_EBP]); 1551 dumpStackTraceFromBase(bp, ip); 1552 }, 1553 .x86_64 => { 1554 const ctx = @ptrCast(*const os.ucontext_t, @alignCast(@alignOf(os.ucontext_t), ctx_ptr)); 1555 const ip = switch (native_os) { 1556 .linux, .netbsd => @intCast(usize, ctx.mcontext.gregs[os.REG_RIP]), 1557 .freebsd => @intCast(usize, ctx.mcontext.rip), 1558 .openbsd => @intCast(usize, ctx.sc_rip), 1559 else => unreachable, 1560 }; 1561 const bp = switch (native_os) { 1562 .linux, .netbsd => @intCast(usize, ctx.mcontext.gregs[os.REG_RBP]), 1563 .openbsd => @intCast(usize, ctx.sc_rbp), 1564 .freebsd => @intCast(usize, ctx.mcontext.rbp), 1565 else => unreachable, 1566 }; 1567 dumpStackTraceFromBase(bp, ip); 1568 }, 1569 .arm => { 1570 const ctx = @ptrCast(*const os.ucontext_t, @alignCast(@alignOf(os.ucontext_t), ctx_ptr)); 1571 const ip = @intCast(usize, ctx.mcontext.arm_pc); 1572 const bp = @intCast(usize, ctx.mcontext.arm_fp); 1573 dumpStackTraceFromBase(bp, ip); 1574 }, 1575 .aarch64 => { 1576 const ctx = @ptrCast(*const os.ucontext_t, @alignCast(@alignOf(os.ucontext_t), ctx_ptr)); 1577 const ip = @intCast(usize, ctx.mcontext.pc); 1578 // x29 is the ABI-designated frame pointer 1579 const bp = @intCast(usize, ctx.mcontext.regs[29]); 1580 dumpStackTraceFromBase(bp, ip); 1581 }, 1582 else => {}, 1583 } 1584 1585 // We cannot allow the signal handler to return because when it runs the original instruction 1586 // again, the memory may be mapped and undefined behavior would occur rather than repeating 1587 // the segfault. So we simply abort here. 1588 os.abort(); 1589 } 1590 1591 fn handleSegfaultWindows(info: *windows.EXCEPTION_POINTERS) callconv(windows.WINAPI) c_long { 1592 switch (info.ExceptionRecord.ExceptionCode) { 1593 windows.EXCEPTION_DATATYPE_MISALIGNMENT => handleSegfaultWindowsExtra(info, 0, "Unaligned Memory Access"), 1594 windows.EXCEPTION_ACCESS_VIOLATION => handleSegfaultWindowsExtra(info, 1, null), 1595 windows.EXCEPTION_ILLEGAL_INSTRUCTION => handleSegfaultWindowsExtra(info, 2, null), 1596 windows.EXCEPTION_STACK_OVERFLOW => handleSegfaultWindowsExtra(info, 0, "Stack Overflow"), 1597 else => return windows.EXCEPTION_CONTINUE_SEARCH, 1598 } 1599 } 1600 1601 // zig won't let me use an anon enum here https://github.com/ziglang/zig/issues/3707 1602 fn handleSegfaultWindowsExtra(info: *windows.EXCEPTION_POINTERS, comptime msg: u8, comptime format: ?[]const u8) noreturn { 1603 const exception_address = @ptrToInt(info.ExceptionRecord.ExceptionAddress); 1604 if (@hasDecl(windows, "CONTEXT")) { 1605 const regs = info.ContextRecord.getRegs(); 1606 // Don't use std.debug.print() as stderr_mutex may still be locked. 1607 nosuspend { 1608 const stderr = io.getStdErr().writer(); 1609 _ = switch (msg) { 1610 0 => stderr.print("{s}\n", .{format.?}), 1611 1 => stderr.print("Segmentation fault at address 0x{x}\n", .{info.ExceptionRecord.ExceptionInformation[1]}), 1612 2 => stderr.print("Illegal instruction at address 0x{x}\n", .{regs.ip}), 1613 else => unreachable, 1614 } catch os.abort(); 1615 } 1616 1617 dumpStackTraceFromBase(regs.bp, regs.ip); 1618 os.abort(); 1619 } else { 1620 switch (msg) { 1621 0 => panicExtra(null, exception_address, format.?, .{}), 1622 1 => panicExtra(null, exception_address, "Segmentation fault at address 0x{x}", .{info.ExceptionRecord.ExceptionInformation[1]}), 1623 2 => panicExtra(null, exception_address, "Illegal Instruction", .{}), 1624 else => unreachable, 1625 } 1626 } 1627 } 1628 1629 pub fn dumpStackPointerAddr(prefix: []const u8) void { 1630 const sp = asm ("" 1631 : [argc] "={rsp}" (-> usize) 1632 ); 1633 std.debug.warn("{} sp = 0x{x}\n", .{ prefix, sp }); 1634 }