| ... | ... | @@ -1,19 +1,19 @@ |
| 1 | | const builtin = @import("builtin"); |
| 2 | 1 | const std = @import("std.zig"); |
| 3 | 2 | const math = std.math; |
| 4 | 3 | const mem = std.mem; |
| 5 | 4 | const posix = std.posix; |
| 6 | 5 | const fs = std.fs; |
| 7 | 6 | const testing = std.testing; |
| 8 | | const root = @import("root"); |
| 7 | const Allocator = mem.Allocator; |
| 9 | 8 | const File = std.fs.File; |
| 10 | 9 | const windows = std.os.windows; |
| 11 | | const native_arch = builtin.cpu.arch; |
| 12 | | const native_os = builtin.os.tag; |
| 13 | | const native_endian = native_arch.endian(); |
| 14 | 10 | const Writer = std.Io.Writer; |
| 15 | 11 | const tty = std.Io.tty; |
| 16 | 12 | |
| 13 | const builtin = @import("builtin"); |
| 14 | const native_arch = builtin.cpu.arch; |
| 15 | const native_os = builtin.os.tag; |
| 16 | |
| 17 | 17 | pub const Dwarf = @import("debug/Dwarf.zig"); |
| 18 | 18 | pub const Pdb = @import("debug/Pdb.zig"); |
| 19 | 19 | pub const SelfInfo = @import("debug/SelfInfo.zig"); |
| ... | ... | @@ -156,6 +156,11 @@ pub const Symbol = struct { |
| 156 | 156 | name: ?[]const u8, |
| 157 | 157 | compile_unit_name: ?[]const u8, |
| 158 | 158 | source_location: ?SourceLocation, |
| 159 | pub const unknown: Symbol = .{ |
| 160 | .name = null, |
| 161 | .compile_unit_name = null, |
| 162 | .source_location = null, |
| 163 | }; |
| 159 | 164 | }; |
| 160 | 165 | |
| 161 | 166 | /// Deprecated because it returns the optimization mode of the standard |
| ... | ... | @@ -186,7 +191,7 @@ pub const sys_can_stack_trace = switch (builtin.cpu.arch) { |
| 186 | 191 | .wasm64, |
| 187 | 192 | => native_os == .emscripten and builtin.mode == .Debug, |
| 188 | 193 | |
| 189 | | // `@returnAddress()` is unsupported in LLVM 13. |
| 194 | // `@returnAddress()` is unsupported in LLVM 21. |
| 190 | 195 | .bpfel, |
| 191 | 196 | .bpfeb, |
| 192 | 197 | => false, |
| ... | ... | @@ -234,8 +239,7 @@ pub fn print(comptime fmt: []const u8, args: anytype) void { |
| 234 | 239 | /// TODO multithreaded awareness |
| 235 | 240 | /// Marked `inline` to propagate a comptime-known error to callers. |
| 236 | 241 | pub inline fn getSelfDebugInfo() !*SelfInfo { |
| 237 | | if (builtin.strip_debug_info) return error.MissingDebugInfo; |
| 238 | | if (!SelfInfo.target_supported) return error.UnsupportedOperatingSystem; |
| 242 | if (!SelfInfo.target_supported) return error.UnsupportedTarget; |
| 239 | 243 | const S = struct { |
| 240 | 244 | var self_info: SelfInfo = .init; |
| 241 | 245 | }; |
| ... | ... | @@ -320,40 +324,11 @@ test dumpHexFallible { |
| 320 | 324 | try std.testing.expectEqualStrings(expected, aw.written()); |
| 321 | 325 | } |
| 322 | 326 | |
| 323 | | /// Tries to print the current stack trace to stderr, unbuffered, and ignores any error returned. |
| 324 | | pub fn dumpCurrentStackTrace(start_addr: ?usize) void { |
| 325 | | const stderr = lockStderrWriter(&.{}); |
| 326 | | defer unlockStderrWriter(); |
| 327 | | nosuspend dumpCurrentStackTraceToWriter(start_addr, stderr) catch return; |
| 328 | | } |
| 329 | | |
| 330 | | /// Prints the current stack trace to the provided writer. |
| 331 | | pub fn dumpCurrentStackTraceToWriter(start_addr: ?usize, writer: *Writer) !void { |
| 332 | | if (builtin.target.cpu.arch.isWasm()) { |
| 333 | | if (native_os == .wasi) { |
| 334 | | try writer.writeAll("Unable to dump stack trace: not implemented for Wasm\n"); |
| 335 | | } |
| 336 | | return; |
| 337 | | } |
| 338 | | if (builtin.strip_debug_info) { |
| 339 | | try writer.writeAll("Unable to dump stack trace: debug info stripped\n"); |
| 340 | | return; |
| 341 | | } |
| 342 | | const debug_info = getSelfDebugInfo() catch |err| { |
| 343 | | try writer.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{@errorName(err)}); |
| 344 | | return; |
| 345 | | }; |
| 346 | | writeCurrentStackTrace(writer, debug_info, tty.detectConfig(.stderr()), start_addr) catch |err| { |
| 347 | | try writer.print("Unable to dump stack trace: {s}\n", .{@errorName(err)}); |
| 348 | | return; |
| 349 | | }; |
| 350 | | } |
| 351 | | |
| 352 | 327 | pub const have_ucontext = posix.ucontext_t != void; |
| 353 | 328 | |
| 354 | 329 | /// Platform-specific thread state. This contains register state, and on some platforms |
| 355 | 330 | /// information about the stack. This is not safe to trivially copy, because some platforms |
| 356 | | /// use internal pointers within this structure. To make a copy, use `copyContext`. |
| 331 | /// use internal pointers within this structure. After copying, call `relocateContext`. |
| 357 | 332 | pub const ThreadContext = blk: { |
| 358 | 333 | if (native_os == .windows) { |
| 359 | 334 | break :blk windows.CONTEXT; |
| ... | ... | @@ -363,22 +338,12 @@ pub const ThreadContext = blk: { |
| 363 | 338 | break :blk void; |
| 364 | 339 | } |
| 365 | 340 | }; |
| 366 | | |
| 367 | | /// Copies one context to another, updating any internal pointers |
| 368 | | pub fn copyContext(source: *const ThreadContext, dest: *ThreadContext) void { |
| 369 | | if (!have_ucontext) return {}; |
| 370 | | dest.* = source.*; |
| 371 | | relocateContext(dest); |
| 372 | | } |
| 373 | | |
| 374 | | /// Updates any internal pointers in the context to reflect its current location |
| 375 | | pub fn relocateContext(context: *ThreadContext) void { |
| 376 | | return switch (native_os) { |
| 377 | | .macos => { |
| 378 | | context.mcontext = &context.__mcontext_data; |
| 379 | | }, |
| 341 | /// Updates any internal pointers of a `ThreadContext` after the caller copies it. |
| 342 | pub fn relocateContext(dest: *ThreadContext) void { |
| 343 | switch (native_os) { |
| 344 | .macos => dest.mcontext = &dest.__mcontext_data, |
| 380 | 345 | else => {}, |
| 381 | | }; |
| 346 | } |
| 382 | 347 | } |
| 383 | 348 | |
| 384 | 349 | pub const have_getcontext = @TypeOf(posix.system.getcontext) != void; |
| ... | ... | @@ -409,142 +374,6 @@ pub inline fn getContext(context: *ThreadContext) bool { |
| 409 | 374 | return result; |
| 410 | 375 | } |
| 411 | 376 | |
| 412 | | /// Tries to print the stack trace starting from the supplied base pointer to stderr, |
| 413 | | /// unbuffered, and ignores any error returned. |
| 414 | | /// TODO multithreaded awareness |
| 415 | | pub fn dumpStackTraceFromBase(context: *ThreadContext, stderr: *Writer) void { |
| 416 | | nosuspend { |
| 417 | | if (builtin.target.cpu.arch.isWasm()) { |
| 418 | | if (native_os == .wasi) { |
| 419 | | stderr.print("Unable to dump stack trace: not implemented for Wasm\n", .{}) catch return; |
| 420 | | } |
| 421 | | return; |
| 422 | | } |
| 423 | | if (builtin.strip_debug_info) { |
| 424 | | stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return; |
| 425 | | return; |
| 426 | | } |
| 427 | | const debug_info = getSelfDebugInfo() catch |err| { |
| 428 | | stderr.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{@errorName(err)}) catch return; |
| 429 | | return; |
| 430 | | }; |
| 431 | | const tty_config = tty.detectConfig(.stderr()); |
| 432 | | if (native_os == .windows) { |
| 433 | | // On x86_64 and aarch64, the stack will be unwound using RtlVirtualUnwind using the context |
| 434 | | // provided by the exception handler. On x86, RtlVirtualUnwind doesn't exist. Instead, a new backtrace |
| 435 | | // will be captured and frames prior to the exception will be filtered. |
| 436 | | // The caveat is that RtlCaptureStackBackTrace does not include the KiUserExceptionDispatcher frame, |
| 437 | | // which is where the IP in `context` points to, so it can't be used as start_addr. |
| 438 | | // Instead, start_addr is recovered from the stack. |
| 439 | | const start_addr = if (builtin.cpu.arch == .x86) @as(*const usize, @ptrFromInt(context.getRegs().bp + 4)).* else null; |
| 440 | | writeStackTraceWindows(stderr, debug_info, tty_config, context, start_addr) catch return; |
| 441 | | return; |
| 442 | | } |
| 443 | | |
| 444 | | var it = StackIterator.initWithContext(null, debug_info, context, @frameAddress()) catch return; |
| 445 | | defer it.deinit(); |
| 446 | | |
| 447 | | // DWARF unwinding on aarch64-macos is not complete so we need to get pc address from mcontext |
| 448 | | const pc_addr = it.unwind_state.?.dwarf_context.pc; |
| 449 | | printSourceAtAddress(debug_info, stderr, pc_addr, tty_config) catch return; |
| 450 | | |
| 451 | | while (it.next()) |return_address| { |
| 452 | | printLastUnwindError(&it, debug_info, stderr, tty_config); |
| 453 | | |
| 454 | | // On arm64 macOS, the address of the last frame is 0x0 rather than 0x1 as on x86_64 macOS, |
| 455 | | // therefore, we do a check for `return_address == 0` before subtracting 1 from it to avoid |
| 456 | | // an overflow. We do not need to signal `StackIterator` as it will correctly detect this |
| 457 | | // condition on the subsequent iteration and return `null` thus terminating the loop. |
| 458 | | // same behaviour for x86-windows-msvc |
| 459 | | const address = return_address -| 1; |
| 460 | | printSourceAtAddress(debug_info, stderr, address, tty_config) catch return; |
| 461 | | } else printLastUnwindError(&it, debug_info, stderr, tty_config); |
| 462 | | } |
| 463 | | } |
| 464 | | |
| 465 | | /// Returns a slice with the same pointer as addresses, with a potentially smaller len. |
| 466 | | /// On Windows, when first_address is not null, we ask for at least 32 stack frames, |
| 467 | | /// and then try to find the first address. If addresses.len is more than 32, we |
| 468 | | /// capture that many stack frames exactly, and then look for the first address, |
| 469 | | /// chopping off the irrelevant frames and shifting so that the returned addresses pointer |
| 470 | | /// equals the passed in addresses pointer. |
| 471 | | pub fn captureStackTrace(first_address: ?usize, stack_trace: *std.builtin.StackTrace) void { |
| 472 | | if (native_os == .windows) { |
| 473 | | const addrs = stack_trace.instruction_addresses; |
| 474 | | const first_addr = first_address orelse { |
| 475 | | stack_trace.index = walkStackWindows(addrs[0..], null); |
| 476 | | return; |
| 477 | | }; |
| 478 | | var addr_buf_stack: [32]usize = undefined; |
| 479 | | const addr_buf = if (addr_buf_stack.len > addrs.len) addr_buf_stack[0..] else addrs; |
| 480 | | const n = walkStackWindows(addr_buf[0..], null); |
| 481 | | const first_index = for (addr_buf[0..n], 0..) |addr, i| { |
| 482 | | if (addr == first_addr) { |
| 483 | | break i; |
| 484 | | } |
| 485 | | } else { |
| 486 | | stack_trace.index = 0; |
| 487 | | return; |
| 488 | | }; |
| 489 | | const end_index = @min(first_index + addrs.len, n); |
| 490 | | const slice = addr_buf[first_index..end_index]; |
| 491 | | // We use a for loop here because slice and addrs may alias. |
| 492 | | for (slice, 0..) |addr, i| { |
| 493 | | addrs[i] = addr; |
| 494 | | } |
| 495 | | stack_trace.index = slice.len; |
| 496 | | } else { |
| 497 | | if (builtin.cpu.arch == .powerpc64) { |
| 498 | | // https://github.com/ziglang/zig/issues/24970 |
| 499 | | stack_trace.index = 0; |
| 500 | | return; |
| 501 | | } |
| 502 | | var context: ThreadContext = undefined; |
| 503 | | const has_context = getContext(&context); |
| 504 | | |
| 505 | | var it = (if (has_context) blk: { |
| 506 | | break :blk StackIterator.initWithContext(first_address, getSelfDebugInfo() catch break :blk null, &context) catch null; |
| 507 | | } else null) orelse StackIterator.init(first_address, null); |
| 508 | | defer it.deinit(); |
| 509 | | for (stack_trace.instruction_addresses, 0..) |*addr, i| { |
| 510 | | addr.* = it.next() orelse { |
| 511 | | stack_trace.index = i; |
| 512 | | return; |
| 513 | | }; |
| 514 | | } |
| 515 | | stack_trace.index = stack_trace.instruction_addresses.len; |
| 516 | | } |
| 517 | | } |
| 518 | | |
| 519 | | /// Tries to print a stack trace to stderr, unbuffered, and ignores any error returned. |
| 520 | | /// TODO multithreaded awareness |
| 521 | | pub fn dumpStackTrace(stack_trace: std.builtin.StackTrace) void { |
| 522 | | nosuspend { |
| 523 | | if (builtin.target.cpu.arch.isWasm()) { |
| 524 | | if (native_os == .wasi) { |
| 525 | | const stderr = lockStderrWriter(&.{}); |
| 526 | | defer unlockStderrWriter(); |
| 527 | | stderr.writeAll("Unable to dump stack trace: not implemented for Wasm\n") catch return; |
| 528 | | } |
| 529 | | return; |
| 530 | | } |
| 531 | | const stderr = lockStderrWriter(&.{}); |
| 532 | | defer unlockStderrWriter(); |
| 533 | | if (builtin.strip_debug_info) { |
| 534 | | stderr.writeAll("Unable to dump stack trace: debug info stripped\n") catch return; |
| 535 | | return; |
| 536 | | } |
| 537 | | const debug_info = getSelfDebugInfo() catch |err| { |
| 538 | | stderr.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{@errorName(err)}) catch return; |
| 539 | | return; |
| 540 | | }; |
| 541 | | writeStackTrace(stack_trace, stderr, debug_info, tty.detectConfig(.stderr())) catch |err| { |
| 542 | | stderr.print("Unable to dump stack trace: {s}\n", .{@errorName(err)}) catch return; |
| 543 | | return; |
| 544 | | }; |
| 545 | | } |
| 546 | | } |
| 547 | | |
| 548 | 377 | /// Invokes detectable illegal behavior when `ok` is `false`. |
| 549 | 378 | /// |
| 550 | 379 | /// In Debug and ReleaseSafe modes, calls to this function are always |
| ... | ... | @@ -613,6 +442,24 @@ var panicking = std.atomic.Value(u8).init(0); |
| 613 | 442 | /// This is used to catch and handle panics triggered by the panic handler. |
| 614 | 443 | threadlocal var panic_stage: usize = 0; |
| 615 | 444 | |
| 445 | /// For backends that cannot handle the language features depended on by the |
| 446 | /// default panic handler, we will use a simpler implementation. |
| 447 | const use_trap_panic = switch (builtin.zig_backend) { |
| 448 | .stage2_aarch64, |
| 449 | .stage2_arm, |
| 450 | .stage2_powerpc, |
| 451 | .stage2_riscv64, |
| 452 | .stage2_spirv, |
| 453 | .stage2_wasm, |
| 454 | .stage2_x86, |
| 455 | => true, |
| 456 | .stage2_x86_64 => switch (builtin.target.ofmt) { |
| 457 | .elf, .macho => false, |
| 458 | else => true, |
| 459 | }, |
| 460 | else => false, |
| 461 | }; |
| 462 | |
| 616 | 463 | /// Dumps a stack trace to standard error, then aborts. |
| 617 | 464 | pub fn defaultPanic( |
| 618 | 465 | msg: []const u8, |
| ... | ... | @@ -620,8 +467,8 @@ pub fn defaultPanic( |
| 620 | 467 | ) noreturn { |
| 621 | 468 | @branchHint(.cold); |
| 622 | 469 | |
| 623 | | // For backends that cannot handle the language features depended on by the |
| 624 | | // default panic handler, we have a simpler panic handler: |
| 470 | if (use_trap_panic) @trap(); |
| 471 | |
| 625 | 472 | switch (builtin.zig_backend) { |
| 626 | 473 | .stage2_aarch64, |
| 627 | 474 | .stage2_arm, |
| ... | ... | @@ -686,41 +533,48 @@ pub fn defaultPanic( |
| 686 | 533 | resetSegfaultHandler(); |
| 687 | 534 | } |
| 688 | 535 | |
| 689 | | // Note there is similar logic in handleSegfaultPosix and handleSegfaultWindowsExtra. |
| 690 | | nosuspend switch (panic_stage) { |
| 536 | // There is very similar logic to the following in `handleSegfault`. |
| 537 | switch (panic_stage) { |
| 691 | 538 | 0 => { |
| 692 | 539 | panic_stage = 1; |
| 693 | | |
| 694 | 540 | _ = panicking.fetchAdd(1, .seq_cst); |
| 695 | 541 | |
| 696 | | { |
| 542 | trace: { |
| 543 | const tty_config = tty.detectConfig(.stderr()); |
| 544 | |
| 697 | 545 | const stderr = lockStderrWriter(&.{}); |
| 698 | 546 | defer unlockStderrWriter(); |
| 699 | 547 | |
| 700 | 548 | if (builtin.single_threaded) { |
| 701 | | stderr.print("panic: ", .{}) catch posix.abort(); |
| 549 | stderr.print("panic: ", .{}) catch break :trace; |
| 702 | 550 | } else { |
| 703 | 551 | const current_thread_id = std.Thread.getCurrentId(); |
| 704 | | stderr.print("thread {} panic: ", .{current_thread_id}) catch posix.abort(); |
| 552 | stderr.print("thread {} panic: ", .{current_thread_id}) catch break :trace; |
| 705 | 553 | } |
| 706 | | stderr.print("{s}\n", .{msg}) catch posix.abort(); |
| 554 | stderr.print("{s}\n", .{msg}) catch break :trace; |
| 707 | 555 | |
| 708 | | if (@errorReturnTrace()) |t| dumpStackTrace(t.*); |
| 709 | | dumpCurrentStackTraceToWriter(first_trace_addr orelse @returnAddress(), stderr) catch {}; |
| 556 | if (@errorReturnTrace()) |t| if (t.index > 0) { |
| 557 | stderr.writeAll("error return context:\n") catch break :trace; |
| 558 | writeStackTrace(t, stderr, tty_config) catch break :trace; |
| 559 | stderr.writeAll("\nstack trace:\n") catch break :trace; |
| 560 | }; |
| 561 | writeCurrentStackTrace(.{ |
| 562 | .first_address = first_trace_addr orelse @returnAddress(), |
| 563 | .allow_unsafe_unwind = true, // we're crashing anyway, give it our all! |
| 564 | }, stderr, tty_config) catch break :trace; |
| 710 | 565 | } |
| 711 | 566 | |
| 712 | 567 | waitForOtherThreadToFinishPanicking(); |
| 713 | 568 | }, |
| 714 | 569 | 1 => { |
| 715 | 570 | panic_stage = 2; |
| 716 | | |
| 717 | 571 | // A panic happened while trying to print a previous panic message. |
| 718 | 572 | // We're still holding the mutex but that's fine as we're going to |
| 719 | 573 | // call abort(). |
| 720 | 574 | fs.File.stderr().writeAll("aborting due to recursive panic\n") catch {}; |
| 721 | 575 | }, |
| 722 | 576 | else => {}, // Panicked while printing the recursive panic message. |
| 723 | | }; |
| 577 | } |
| 724 | 578 | |
| 725 | 579 | posix.abort(); |
| 726 | 580 | } |
| ... | ... | @@ -739,340 +593,307 @@ fn waitForOtherThreadToFinishPanicking() void { |
| 739 | 593 | } |
| 740 | 594 | } |
| 741 | 595 | |
| 742 | | pub fn writeStackTrace( |
| 743 | | stack_trace: std.builtin.StackTrace, |
| 744 | | writer: *Writer, |
| 745 | | debug_info: *SelfInfo, |
| 746 | | tty_config: tty.Config, |
| 747 | | ) !void { |
| 748 | | if (builtin.strip_debug_info) return error.MissingDebugInfo; |
| 749 | | var frame_index: usize = 0; |
| 750 | | var frames_left: usize = @min(stack_trace.index, stack_trace.instruction_addresses.len); |
| 751 | | |
| 752 | | while (frames_left != 0) : ({ |
| 753 | | frames_left -= 1; |
| 754 | | frame_index = (frame_index + 1) % stack_trace.instruction_addresses.len; |
| 755 | | }) { |
| 756 | | const return_address = stack_trace.instruction_addresses[frame_index]; |
| 757 | | try printSourceAtAddress(debug_info, writer, return_address -| 1, tty_config); |
| 758 | | } |
| 596 | pub const StackUnwindOptions = struct { |
| 597 | /// If not `null`, we will ignore all frames up until this return address. This is typically |
| 598 | /// used to omit intermediate handling code (for instance, a panic handler and its machinery) |
| 599 | /// from stack traces. |
| 600 | first_address: ?usize = null, |
| 601 | /// If not `null`, we will unwind from this `ThreadContext` instead of the current top of the |
| 602 | /// stack. The main use case here is printing stack traces from signal handlers, where the |
| 603 | /// kernel provides a `*ThreadContext` of the state before the signal. |
| 604 | context: ?*const ThreadContext = null, |
| 605 | /// If `true`, stack unwinding strategies which may cause crashes are used as a last resort. |
| 606 | /// If `false`, only known-safe mechanisms will be attempted. |
| 607 | allow_unsafe_unwind: bool = false, |
| 608 | }; |
| 759 | 609 | |
| 760 | | if (stack_trace.index > stack_trace.instruction_addresses.len) { |
| 761 | | const dropped_frames = stack_trace.index - stack_trace.instruction_addresses.len; |
| 610 | /// Capture and return the current stack trace. The returned `StackTrace` stores its addresses in |
| 611 | /// the given buffer, so `addr_buf` must have a lifetime at least equal to the `StackTrace`. |
| 612 | /// |
| 613 | /// See `writeCurrentStackTrace` to immediately print the trace instead of capturing it. |
| 614 | pub fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf: []usize) std.builtin.StackTrace { |
| 615 | var context_buf: ThreadContext = undefined; |
| 616 | var it: StackIterator = .init(options.context, &context_buf); |
| 617 | defer it.deinit(); |
| 618 | if (!it.stratOk(options.allow_unsafe_unwind)) { |
| 619 | return .{ .index = 0, .instruction_addresses = &.{} }; |
| 620 | } |
| 621 | var frame_idx: usize = 0; |
| 622 | var wait_for = options.first_address; |
| 623 | while (true) switch (it.next()) { |
| 624 | .switch_to_fp => if (!it.stratOk(options.allow_unsafe_unwind)) break, |
| 625 | .end => break, |
| 626 | .frame => |return_address| { |
| 627 | if (wait_for) |target| { |
| 628 | if (return_address != target) continue; |
| 629 | wait_for = null; |
| 630 | } |
| 631 | if (frame_idx < addr_buf.len) addr_buf[frame_idx] = return_address; |
| 632 | frame_idx += 1; |
| 633 | }, |
| 634 | }; |
| 635 | return .{ |
| 636 | .index = frame_idx, |
| 637 | .instruction_addresses = addr_buf[0..@min(frame_idx, addr_buf.len)], |
| 638 | }; |
| 639 | } |
| 640 | /// Write the current stack trace to `writer`, annotated with source locations. |
| 641 | /// |
| 642 | /// See `captureCurrentStackTrace` to capture the trace addresses into a buffer instead of printing. |
| 643 | pub fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Writer, tty_config: tty.Config) Writer.Error!void { |
| 644 | const di_gpa = getDebugInfoAllocator(); |
| 645 | const di = getSelfDebugInfo() catch |err| switch (err) { |
| 646 | error.UnsupportedTarget => { |
| 647 | tty_config.setColor(writer, .dim) catch {}; |
| 648 | try writer.print("Cannot print stack trace: debug info unavailable for target\n", .{}); |
| 649 | tty_config.setColor(writer, .reset) catch {}; |
| 650 | return; |
| 651 | }, |
| 652 | }; |
| 653 | var context_buf: ThreadContext = undefined; |
| 654 | var it: StackIterator = .init(options.context, &context_buf); |
| 655 | defer it.deinit(); |
| 656 | if (!it.stratOk(options.allow_unsafe_unwind)) { |
| 657 | tty_config.setColor(writer, .dim) catch {}; |
| 658 | try writer.print("Cannot print stack trace: safe unwind unavailable for target\n", .{}); |
| 659 | tty_config.setColor(writer, .reset) catch {}; |
| 660 | return; |
| 661 | } |
| 662 | var wait_for = options.first_address; |
| 663 | var printed_any_frame = false; |
| 664 | while (true) switch (it.next()) { |
| 665 | .switch_to_fp => |unwind_error| { |
| 666 | const module_name = di.getModuleNameForAddress(di_gpa, unwind_error.address) catch "???"; |
| 667 | const caption: []const u8 = switch (unwind_error.err) { |
| 668 | error.MissingDebugInfo => "unwind info unavailable", |
| 669 | error.InvalidDebugInfo => "unwind info invalid", |
| 670 | error.UnsupportedDebugInfo => "unwind info unsupported", |
| 671 | error.ReadFailed => "filesystem error", |
| 672 | error.OutOfMemory => "out of memory", |
| 673 | error.Unexpected => "unexpected error", |
| 674 | }; |
| 675 | if (it.stratOk(options.allow_unsafe_unwind)) { |
| 676 | tty_config.setColor(writer, .dim) catch {}; |
| 677 | try writer.print( |
| 678 | "Unwind error at address `{s}:0x{x}` ({s}), remaining frames may be incorrect\n", |
| 679 | .{ module_name, unwind_error.address, caption }, |
| 680 | ); |
| 681 | tty_config.setColor(writer, .reset) catch {}; |
| 682 | } else { |
| 683 | tty_config.setColor(writer, .dim) catch {}; |
| 684 | try writer.print( |
| 685 | "Unwind error at address `{s}:0x{x}` ({s}), stopping trace early\n", |
| 686 | .{ module_name, unwind_error.address, caption }, |
| 687 | ); |
| 688 | tty_config.setColor(writer, .reset) catch {}; |
| 689 | return; |
| 690 | } |
| 691 | }, |
| 692 | .end => break, |
| 693 | .frame => |return_address| { |
| 694 | if (wait_for) |target| { |
| 695 | if (return_address != target) continue; |
| 696 | wait_for = null; |
| 697 | } |
| 698 | try printSourceAtAddress(di_gpa, di, writer, return_address -| 1, tty_config); |
| 699 | printed_any_frame = true; |
| 700 | }, |
| 701 | }; |
| 702 | if (!printed_any_frame) return writer.writeAll("(empty stack trace)\n"); |
| 703 | } |
| 704 | /// A thin wrapper around `writeCurrentStackTrace` which writes to stderr and ignores write errors. |
| 705 | pub fn dumpCurrentStackTrace(options: StackUnwindOptions) void { |
| 706 | const tty_config = tty.detectConfig(.stderr()); |
| 707 | const stderr = lockStderrWriter(&.{}); |
| 708 | defer unlockStderrWriter(); |
| 709 | writeCurrentStackTrace(options, stderr, tty_config) catch |err| switch (err) { |
| 710 | error.WriteFailed => {}, |
| 711 | }; |
| 712 | } |
| 762 | 713 | |
| 714 | /// Write a previously captured stack trace to `writer`, annotated with source locations. |
| 715 | pub fn writeStackTrace(st: *const std.builtin.StackTrace, writer: *Writer, tty_config: tty.Config) Writer.Error!void { |
| 716 | const di_gpa = getDebugInfoAllocator(); |
| 717 | const di = getSelfDebugInfo() catch |err| switch (err) { |
| 718 | error.UnsupportedTarget => { |
| 719 | tty_config.setColor(writer, .dim) catch {}; |
| 720 | try writer.print("Cannot print stack trace: debug info unavailable for target\n\n", .{}); |
| 721 | tty_config.setColor(writer, .reset) catch {}; |
| 722 | return; |
| 723 | }, |
| 724 | }; |
| 725 | if (st.index == 0) return writer.writeAll("(empty stack trace)\n"); |
| 726 | const captured_frames = @min(st.index, st.instruction_addresses.len); |
| 727 | for (st.instruction_addresses[0..captured_frames]) |return_address| { |
| 728 | try printSourceAtAddress(di_gpa, di, writer, return_address -| 1, tty_config); |
| 729 | } |
| 730 | if (st.index > captured_frames) { |
| 763 | 731 | tty_config.setColor(writer, .bold) catch {}; |
| 764 | | try writer.print("({d} additional stack frames skipped...)\n", .{dropped_frames}); |
| 732 | try writer.print("({d} additional stack frames skipped...)\n", .{st.index - captured_frames}); |
| 765 | 733 | tty_config.setColor(writer, .reset) catch {}; |
| 766 | 734 | } |
| 767 | 735 | } |
| 736 | /// A thin wrapper around `writeStackTrace` which writes to stderr and ignores write errors. |
| 737 | pub fn dumpStackTrace(st: *const std.builtin.StackTrace) void { |
| 738 | const tty_config = tty.detectConfig(.stderr()); |
| 739 | const stderr = lockStderrWriter(&.{}); |
| 740 | defer unlockStderrWriter(); |
| 741 | writeStackTrace(st, stderr, tty_config) catch |err| switch (err) { |
| 742 | error.WriteFailed => {}, |
| 743 | }; |
| 744 | } |
| 768 | 745 | |
| 769 | | pub const StackIterator = struct { |
| 770 | | // Skip every frame before this address is found. |
| 771 | | first_address: ?usize, |
| 772 | | // Last known value of the frame pointer register. |
| 746 | const StackIterator = union(enum) { |
| 747 | /// Unwinding using debug info (e.g. DWARF CFI). |
| 748 | di: if (SelfInfo.supports_unwinding) SelfInfo.UnwindContext else noreturn, |
| 749 | /// Naive frame-pointer-based unwinding. Very simple, but typically unreliable. |
| 773 | 750 | fp: usize, |
| 774 | 751 | |
| 775 | | // When SelfInfo and a register context is available, this iterator can unwind |
| 776 | | // stacks with frames that don't use a frame pointer (ie. -fomit-frame-pointer), |
| 777 | | // using DWARF and MachO unwind info. |
| 778 | | unwind_state: if (have_ucontext) ?struct { |
| 779 | | debug_info: *SelfInfo, |
| 780 | | dwarf_context: SelfInfo.UnwindContext, |
| 781 | | last_error: ?SelfInfo.Error = null, |
| 782 | | failed: bool = false, |
| 783 | | } else void = if (have_ucontext) null else {}, |
| 784 | | |
| 785 | | pub fn init(first_address: ?usize, fp: usize) StackIterator { |
| 786 | | if (native_arch.isSPARC()) { |
| 752 | /// It is important that this function is marked `inline` so that it can safely use |
| 753 | /// `@frameAddress` and `getContext` as the caller's stack frame and our own are one |
| 754 | /// and the same. |
| 755 | inline fn init(context_opt: ?*const ThreadContext, context_buf: *ThreadContext) StackIterator { |
| 756 | if (builtin.cpu.arch.isSPARC()) { |
| 787 | 757 | // Flush all the register windows on stack. |
| 788 | | asm volatile (if (builtin.cpu.has(.sparc, .v9)) |
| 789 | | "flushw" |
| 790 | | else |
| 791 | | "ta 3" // ST_FLUSH_WINDOWS |
| 792 | | ::: .{ .memory = true }); |
| 793 | | } |
| 794 | | |
| 795 | | return .{ |
| 796 | | .first_address = first_address, |
| 797 | | .fp = fp, |
| 798 | | }; |
| 799 | | } |
| 800 | | |
| 801 | | pub fn initWithContext(first_address: ?usize, debug_info: *SelfInfo, context: *posix.ucontext_t, fp: usize) !StackIterator { |
| 802 | | if (SelfInfo.supports_unwinding) { |
| 803 | | var iterator = init(first_address, fp); |
| 804 | | iterator.unwind_state = .{ |
| 805 | | .debug_info = debug_info, |
| 806 | | .dwarf_context = try SelfInfo.UnwindContext.init(getDebugInfoAllocator(), context), |
| 807 | | }; |
| 808 | | return iterator; |
| 809 | | } |
| 810 | | |
| 811 | | return init(first_address, fp); |
| 812 | | } |
| 813 | | |
| 814 | | pub fn deinit(it: *StackIterator) void { |
| 815 | | if (have_ucontext and it.unwind_state != null) it.unwind_state.?.dwarf_context.deinit(); |
| 816 | | } |
| 817 | | |
| 818 | | pub fn getLastError(it: *StackIterator) ?struct { |
| 819 | | err: SelfInfo.Error, |
| 820 | | address: usize, |
| 821 | | } { |
| 822 | | if (!have_ucontext) return null; |
| 823 | | if (it.unwind_state) |*unwind_state| { |
| 824 | | if (unwind_state.last_error) |err| { |
| 825 | | unwind_state.last_error = null; |
| 826 | | return .{ |
| 827 | | .err = err, |
| 828 | | .address = unwind_state.dwarf_context.pc, |
| 829 | | }; |
| 758 | if (builtin.cpu.has(.sparc, .v9)) { |
| 759 | asm volatile ("flushw" ::: .{ .memory = true }); |
| 760 | } else { |
| 761 | asm volatile ("ta 3" ::: .{ .memory = true }); // ST_FLUSH_WINDOWS |
| 830 | 762 | } |
| 831 | 763 | } |
| 832 | | |
| 833 | | return null; |
| 834 | | } |
| 835 | | |
| 836 | | // Offset of the saved BP wrt the frame pointer. |
| 837 | | const fp_offset = if (native_arch.isRISCV()) |
| 838 | | // On RISC-V the frame pointer points to the top of the saved register |
| 839 | | // area, on pretty much every other architecture it points to the stack |
| 840 | | // slot where the previous frame pointer is saved. |
| 841 | | 2 * @sizeOf(usize) |
| 842 | | else if (native_arch.isSPARC()) |
| 843 | | // On SPARC the previous frame pointer is stored at 14 slots past %fp+BIAS. |
| 844 | | 14 * @sizeOf(usize) |
| 845 | | else |
| 846 | | 0; |
| 847 | | |
| 848 | | const fp_bias = if (native_arch.isSPARC()) |
| 849 | | // On SPARC frame pointers are biased by a constant. |
| 850 | | 2047 |
| 851 | | else |
| 852 | | 0; |
| 853 | | |
| 854 | | // Positive offset of the saved PC wrt the frame pointer. |
| 855 | | const pc_offset = if (native_arch == .powerpc64le) |
| 856 | | 2 * @sizeOf(usize) |
| 857 | | else |
| 858 | | @sizeOf(usize); |
| 859 | | |
| 860 | | pub fn next(it: *StackIterator) ?usize { |
| 861 | | var address = it.nextInternal() orelse return null; |
| 862 | | |
| 863 | | if (it.first_address) |first_address| { |
| 864 | | while (address != first_address) { |
| 865 | | address = it.nextInternal() orelse return null; |
| 866 | | } |
| 867 | | it.first_address = null; |
| 764 | if (context_opt) |context| { |
| 765 | context_buf.* = context.*; |
| 766 | relocateContext(context_buf); |
| 767 | return .{ .di = .init(getDebugInfoAllocator(), context_buf) }; |
| 868 | 768 | } |
| 869 | | |
| 870 | | return address; |
| 871 | | } |
| 872 | | |
| 873 | | fn nextInternal(it: *StackIterator) ?usize { |
| 874 | | if (have_ucontext) { |
| 875 | | if (it.unwind_state) |*unwind_state| { |
| 876 | | if (!unwind_state.failed) { |
| 877 | | if (unwind_state.dwarf_context.pc == 0) return null; |
| 878 | | defer it.fp = unwind_state.dwarf_context.getFp() catch 0; |
| 879 | | if (unwind_state.debug_info.unwindFrame(getDebugInfoAllocator(), &unwind_state.dwarf_context)) |return_address| { |
| 880 | | return return_address; |
| 881 | | } else |err| { |
| 882 | | unwind_state.last_error = err; |
| 883 | | unwind_state.failed = true; |
| 884 | | |
| 885 | | // Fall back to fp-based unwinding on the first failure. |
| 886 | | // We can't attempt it again for other modules higher in the |
| 887 | | // stack because the full register state won't have been unwound. |
| 888 | | } |
| 889 | | } |
| 890 | | } |
| 769 | if (getContext(context_buf)) { |
| 770 | return .{ .di = .init(getDebugInfoAllocator(), context_buf) }; |
| 891 | 771 | } |
| 892 | | |
| 893 | | if (builtin.omit_frame_pointer) return null; |
| 894 | | |
| 895 | | const fp = if (comptime native_arch.isSPARC()) |
| 896 | | // On SPARC the offset is positive. (!) |
| 897 | | math.add(usize, it.fp, fp_offset) catch return null |
| 898 | | else |
| 899 | | math.sub(usize, it.fp, fp_offset) catch return null; |
| 900 | | |
| 901 | | // Sanity check. |
| 902 | | if (fp == 0 or !mem.isAligned(fp, @alignOf(usize))) return null; |
| 903 | | const new_fp = math.add(usize, @as(*usize, @ptrFromInt(fp)).*, fp_bias) catch |
| 904 | | return null; |
| 905 | | |
| 906 | | // Sanity check: the stack grows down thus all the parent frames must be |
| 907 | | // be at addresses that are greater (or equal) than the previous one. |
| 908 | | // A zero frame pointer often signals this is the last frame, that case |
| 909 | | // is gracefully handled by the next call to nextInternal. |
| 910 | | if (new_fp != 0 and new_fp < it.fp) return null; |
| 911 | | const new_pc = @as(*usize, @ptrFromInt(math.add(usize, fp, pc_offset) catch return null)).*; |
| 912 | | |
| 913 | | it.fp = new_fp; |
| 914 | | |
| 915 | | return new_pc; |
| 772 | return .{ .fp = @frameAddress() }; |
| 916 | 773 | } |
| 917 | | }; |
| 918 | | |
| 919 | | pub fn writeCurrentStackTrace( |
| 920 | | writer: *Writer, |
| 921 | | debug_info: *SelfInfo, |
| 922 | | tty_config: tty.Config, |
| 923 | | start_addr: ?usize, |
| 924 | | ) !void { |
| 925 | | if (native_os == .windows) { |
| 926 | | var context: ThreadContext = undefined; |
| 927 | | assert(getContext(&context)); |
| 928 | | return writeStackTraceWindows(writer, debug_info, tty_config, &context, start_addr); |
| 774 | fn deinit(si: *StackIterator) void { |
| 775 | switch (si.*) { |
| 776 | .fp => {}, |
| 777 | .di => |*unwind_context| unwind_context.deinit(), |
| 778 | } |
| 929 | 779 | } |
| 930 | | var context: ThreadContext = undefined; |
| 931 | | const has_context = getContext(&context); |
| 932 | | |
| 933 | | var it = (if (has_context) blk: { |
| 934 | | break :blk StackIterator.initWithContext(start_addr, debug_info, &context, @frameAddress()) catch null; |
| 935 | | } else null) orelse StackIterator.init(start_addr, @frameAddress()); |
| 936 | | defer it.deinit(); |
| 937 | 780 | |
| 938 | | while (it.next()) |return_address| { |
| 939 | | printLastUnwindError(&it, debug_info, writer, tty_config); |
| 940 | | |
| 941 | | // On arm64 macOS, the address of the last frame is 0x0 rather than 0x1 as on x86_64 macOS, |
| 942 | | // therefore, we do a check for `return_address == 0` before subtracting 1 from it to avoid |
| 943 | | // an overflow. We do not need to signal `StackIterator` as it will correctly detect this |
| 944 | | // condition on the subsequent iteration and return `null` thus terminating the loop. |
| 945 | | // same behaviour for x86-windows-msvc |
| 946 | | const address = return_address -| 1; |
| 947 | | try printSourceAtAddress(debug_info, writer, address, tty_config); |
| 948 | | } else printLastUnwindError(&it, debug_info, writer, tty_config); |
| 949 | | } |
| 781 | /// On aarch64-macos, Apple mandate that the frame pointer is always used. |
| 782 | /// TODO: are there any other architectures with guarantees like this? |
| 783 | const fp_unwind_is_safe = !builtin.omit_frame_pointer and builtin.cpu.arch == .aarch64 and builtin.os.tag.isDarwin(); |
| 950 | 784 | |
| 951 | | pub noinline fn walkStackWindows(addresses: []usize, existing_context: ?*const windows.CONTEXT) usize { |
| 952 | | if (builtin.cpu.arch == .x86) { |
| 953 | | // RtlVirtualUnwind doesn't exist on x86 |
| 954 | | return windows.ntdll.RtlCaptureStackBackTrace(0, addresses.len, @as(**anyopaque, @ptrCast(addresses.ptr)), null); |
| 785 | /// Whether the current unwind strategy is allowed given `allow_unsafe`. |
| 786 | fn stratOk(it: *const StackIterator, allow_unsafe: bool) bool { |
| 787 | return switch (it.*) { |
| 788 | .di => true, |
| 789 | .fp => allow_unsafe or fp_unwind_is_safe, |
| 790 | }; |
| 955 | 791 | } |
| 956 | 792 | |
| 957 | | const tib = &windows.teb().NtTib; |
| 793 | const Result = union(enum) { |
| 794 | /// A stack frame has been found; this is the corresponding return address. |
| 795 | frame: usize, |
| 796 | /// The end of the stack has been reached. |
| 797 | end, |
| 798 | /// We were using the `.di` strategy, but are now switching to `.fp` due to this error. |
| 799 | switch_to_fp: struct { |
| 800 | address: usize, |
| 801 | err: SelfInfo.Error, |
| 802 | }, |
| 803 | }; |
| 804 | fn next(it: *StackIterator) Result { |
| 805 | switch (it.*) { |
| 806 | .di => |*unwind_context| { |
| 807 | const di = getSelfDebugInfo() catch unreachable; |
| 808 | const di_gpa = getDebugInfoAllocator(); |
| 809 | if (di.unwindFrame(di_gpa, unwind_context)) |ra| { |
| 810 | if (ra == 0) return .end; |
| 811 | return .{ .frame = ra }; |
| 812 | } else |err| { |
| 813 | const bad_pc = unwind_context.pc; |
| 814 | it.* = .{ .fp = unwind_context.getFp() catch 0 }; |
| 815 | return .{ .switch_to_fp = .{ |
| 816 | .address = bad_pc, |
| 817 | .err = err, |
| 818 | } }; |
| 819 | } |
| 820 | }, |
| 821 | .fp => |fp| { |
| 822 | if (fp == 0) return .end; // we reached the "sentinel" base pointer |
| 823 | |
| 824 | const bp_addr = applyOffset(fp, bp_offset) orelse return .end; |
| 825 | const ra_addr = applyOffset(fp, ra_offset) orelse return .end; |
| 826 | |
| 827 | if (bp_addr == 0 or !mem.isAligned(bp_addr, @alignOf(usize)) or |
| 828 | ra_addr == 0 or !mem.isAligned(ra_addr, @alignOf(usize))) |
| 829 | { |
| 830 | // This isn't valid, but it most likely indicates end of stack. |
| 831 | return .end; |
| 832 | } |
| 958 | 833 | |
| 959 | | var context: windows.CONTEXT = undefined; |
| 960 | | if (existing_context) |context_ptr| { |
| 961 | | context = context_ptr.*; |
| 962 | | } else { |
| 963 | | context = std.mem.zeroes(windows.CONTEXT); |
| 964 | | windows.ntdll.RtlCaptureContext(&context); |
| 965 | | } |
| 834 | const bp_ptr: *const usize = @ptrFromInt(bp_addr); |
| 835 | const ra_ptr: *const usize = @ptrFromInt(ra_addr); |
| 836 | const bp = applyOffset(bp_ptr.*, bp_bias) orelse return .end; |
| 966 | 837 | |
| 967 | | var i: usize = 0; |
| 968 | | var image_base: windows.DWORD64 = undefined; |
| 969 | | var history_table: windows.UNWIND_HISTORY_TABLE = std.mem.zeroes(windows.UNWIND_HISTORY_TABLE); |
| 970 | | |
| 971 | | while (i < addresses.len) : (i += 1) { |
| 972 | | const current_regs = context.getRegs(); |
| 973 | | if (windows.ntdll.RtlLookupFunctionEntry(current_regs.ip, &image_base, &history_table)) |runtime_function| { |
| 974 | | var handler_data: ?*anyopaque = null; |
| 975 | | var establisher_frame: u64 = undefined; |
| 976 | | _ = windows.ntdll.RtlVirtualUnwind( |
| 977 | | windows.UNW_FLAG_NHANDLER, |
| 978 | | image_base, |
| 979 | | current_regs.ip, |
| 980 | | runtime_function, |
| 981 | | &context, |
| 982 | | &handler_data, |
| 983 | | &establisher_frame, |
| 984 | | null, |
| 985 | | ); |
| 986 | | } else { |
| 987 | | // leaf function |
| 988 | | context.setIp(@as(*usize, @ptrFromInt(current_regs.sp)).*); |
| 989 | | context.setSp(current_regs.sp + @sizeOf(usize)); |
| 990 | | } |
| 838 | // The stack grows downards, so `bp > fp` should always hold. If it doesn't, this |
| 839 | // frame is invalid, so we'll treat it as though it we reached end of stack. The |
| 840 | // exception is address 0, which is a graceful end-of-stack signal, in which case |
| 841 | // *this* return address is valid and the *next* iteration will be the last. |
| 842 | if (bp != 0 and bp <= fp) return .end; |
| 991 | 843 | |
| 992 | | const next_regs = context.getRegs(); |
| 993 | | if (next_regs.sp < @intFromPtr(tib.StackLimit) or next_regs.sp > @intFromPtr(tib.StackBase)) { |
| 994 | | break; |
| 844 | it.fp = bp; |
| 845 | return .{ .frame = ra_ptr.* }; |
| 846 | }, |
| 995 | 847 | } |
| 996 | | |
| 997 | | if (next_regs.ip == 0) { |
| 998 | | break; |
| 999 | | } |
| 1000 | | |
| 1001 | | addresses[i] = next_regs.ip; |
| 1002 | 848 | } |
| 1003 | 849 | |
| 1004 | | return i; |
| 1005 | | } |
| 1006 | | |
| 1007 | | pub fn writeStackTraceWindows( |
| 1008 | | writer: *Writer, |
| 1009 | | debug_info: *SelfInfo, |
| 1010 | | tty_config: tty.Config, |
| 1011 | | context: *const windows.CONTEXT, |
| 1012 | | start_addr: ?usize, |
| 1013 | | ) !void { |
| 1014 | | var addr_buf: [1024]usize = undefined; |
| 1015 | | const n = walkStackWindows(addr_buf[0..], context); |
| 1016 | | const addrs = addr_buf[0..n]; |
| 1017 | | const start_i: usize = if (start_addr) |saddr| blk: { |
| 1018 | | for (addrs, 0..) |addr, i| { |
| 1019 | | if (addr == saddr) break :blk i; |
| 1020 | | } |
| 1021 | | return; |
| 1022 | | } else 0; |
| 1023 | | for (addrs[start_i..]) |addr| { |
| 1024 | | try printSourceAtAddress(debug_info, writer, addr - 1, tty_config); |
| 1025 | | } |
| 1026 | | } |
| 850 | /// Offset of the saved base pointer (previous frame pointer) wrt the frame pointer. |
| 851 | const bp_offset = off: { |
| 852 | // On RISC-V the frame pointer points to the top of the saved register |
| 853 | // area, on pretty much every other architecture it points to the stack |
| 854 | // slot where the previous frame pointer is saved. |
| 855 | if (native_arch.isRISCV()) break :off -2 * @sizeOf(usize); |
| 856 | // On SPARC the previous frame pointer is stored at 14 slots past %fp+BIAS. |
| 857 | if (native_arch.isSPARC()) break :off 14 * @sizeOf(usize); |
| 858 | break :off 0; |
| 859 | }; |
| 1027 | 860 | |
| 1028 | | fn printLastUnwindError(it: *StackIterator, debug_info: *SelfInfo, writer: *Writer, tty_config: tty.Config) void { |
| 1029 | | if (!have_ucontext) return; |
| 1030 | | if (it.getLastError()) |unwind_error| { |
| 1031 | | printUnwindError(debug_info, writer, unwind_error.address, unwind_error.err, tty_config) catch {}; |
| 1032 | | } |
| 1033 | | } |
| 861 | /// Offset of the saved return address wrt the frame pointer. |
| 862 | const ra_offset = off: { |
| 863 | if (native_arch == .powerpc64le) break :off 2 * @sizeOf(usize); |
| 864 | break :off @sizeOf(usize); |
| 865 | }; |
| 1034 | 866 | |
| 1035 | | fn printUnwindError(debug_info: *SelfInfo, writer: *Writer, address: usize, unwind_err: SelfInfo.Error, tty_config: tty.Config) !void { |
| 1036 | | const module_name = debug_info.getModuleNameForAddress(getDebugInfoAllocator(), address) catch |err| switch (err) { |
| 1037 | | error.InvalidDebugInfo, error.MissingDebugInfo, error.UnsupportedDebugInfo, error.ReadFailed => "???", |
| 1038 | | error.Unexpected, error.OutOfMemory => |e| return e, |
| 867 | /// Value to add to a base pointer after loading it from the stack. Yes, SPARC really does this. |
| 868 | const bp_bias = bias: { |
| 869 | if (native_arch.isSPARC()) break :bias 2047; |
| 870 | break :bias 0; |
| 1039 | 871 | }; |
| 1040 | | try tty_config.setColor(writer, .dim); |
| 1041 | | switch (unwind_err) { |
| 1042 | | error.Unexpected, error.OutOfMemory => |e| return e, |
| 1043 | | error.MissingDebugInfo => { |
| 1044 | | try writer.print("Unwind information for `{s}:0x{x}` was not available, trace may be incomplete\n\n", .{ module_name, address }); |
| 1045 | | }, |
| 1046 | | error.InvalidDebugInfo, |
| 1047 | | error.UnsupportedDebugInfo, |
| 1048 | | error.ReadFailed, |
| 1049 | | => { |
| 1050 | | const caption: []const u8 = switch (unwind_err) { |
| 1051 | | error.InvalidDebugInfo => "invalid unwind info", |
| 1052 | | error.UnsupportedDebugInfo => "unsupported unwind info", |
| 1053 | | error.ReadFailed => "filesystem error", |
| 1054 | | else => unreachable, |
| 1055 | | }; |
| 1056 | | try writer.print("Unwind error at address `{s}:0x{x}` ({s}), trace may be incomplete\n\n", .{ module_name, address, caption }); |
| 1057 | | }, |
| 872 | |
| 873 | fn applyOffset(addr: usize, comptime off: comptime_int) ?usize { |
| 874 | if (off >= 0) return math.add(usize, addr, off) catch return null; |
| 875 | return math.sub(usize, addr, -off) catch return null; |
| 1058 | 876 | } |
| 1059 | | try tty_config.setColor(writer, .reset); |
| 1060 | | } |
| 877 | }; |
| 1061 | 878 | |
| 1062 | | pub fn printSourceAtAddress(debug_info: *SelfInfo, writer: *Writer, address: usize, tty_config: tty.Config) !void { |
| 1063 | | const gpa = getDebugInfoAllocator(); |
| 879 | fn printSourceAtAddress(gpa: Allocator, debug_info: *SelfInfo, writer: *Writer, address: usize, tty_config: tty.Config) Writer.Error!void { |
| 1064 | 880 | const symbol: Symbol = debug_info.getSymbolAtAddress(gpa, address) catch |err| switch (err) { |
| 1065 | 881 | error.MissingDebugInfo, |
| 1066 | 882 | error.UnsupportedDebugInfo, |
| 1067 | 883 | error.InvalidDebugInfo, |
| 1068 | | => .{ .name = null, .compile_unit_name = null, .source_location = null }, |
| 1069 | | error.ReadFailed => s: { |
| 1070 | | try tty_config.setColor(writer, .dim); |
| 884 | => .unknown, |
| 885 | error.ReadFailed, error.Unexpected => s: { |
| 886 | tty_config.setColor(writer, .dim) catch {}; |
| 1071 | 887 | try writer.print("Failed to read debug info from filesystem, trace may be incomplete\n\n", .{}); |
| 1072 | | try tty_config.setColor(writer, .reset); |
| 1073 | | break :s .{ .name = null, .compile_unit_name = null, .source_location = null }; |
| 888 | tty_config.setColor(writer, .reset) catch {}; |
| 889 | break :s .unknown; |
| 890 | }, |
| 891 | error.OutOfMemory => s: { |
| 892 | tty_config.setColor(writer, .dim) catch {}; |
| 893 | try writer.print("Ran out of memory loading debug info, trace may be incomplete\n\n", .{}); |
| 894 | tty_config.setColor(writer, .reset) catch {}; |
| 895 | break :s .unknown; |
| 1074 | 896 | }, |
| 1075 | | error.OutOfMemory, error.Unexpected => |e| return e, |
| 1076 | 897 | }; |
| 1077 | 898 | defer if (symbol.source_location) |sl| gpa.free(sl.file_name); |
| 1078 | 899 | return printLineInfo( |
| ... | ... | @@ -1080,10 +901,7 @@ pub fn printSourceAtAddress(debug_info: *SelfInfo, writer: *Writer, address: usi |
| 1080 | 901 | symbol.source_location, |
| 1081 | 902 | address, |
| 1082 | 903 | symbol.name orelse "???", |
| 1083 | | symbol.compile_unit_name orelse debug_info.getModuleNameForAddress(gpa, address) catch |err| switch (err) { |
| 1084 | | error.InvalidDebugInfo, error.MissingDebugInfo, error.UnsupportedDebugInfo, error.ReadFailed => "???", |
| 1085 | | error.Unexpected, error.OutOfMemory => |e| return e, |
| 1086 | | }, |
| 904 | symbol.compile_unit_name orelse debug_info.getModuleNameForAddress(gpa, address) catch "???", |
| 1087 | 905 | tty_config, |
| 1088 | 906 | ); |
| 1089 | 907 | } |
| ... | ... | @@ -1094,9 +912,9 @@ fn printLineInfo( |
| 1094 | 912 | symbol_name: []const u8, |
| 1095 | 913 | compile_unit_name: []const u8, |
| 1096 | 914 | tty_config: tty.Config, |
| 1097 | | ) !void { |
| 915 | ) Writer.Error!void { |
| 1098 | 916 | nosuspend { |
| 1099 | | try tty_config.setColor(writer, .bold); |
| 917 | tty_config.setColor(writer, .bold) catch {}; |
| 1100 | 918 | |
| 1101 | 919 | if (source_location) |*sl| { |
| 1102 | 920 | try writer.print("{s}:{d}:{d}", .{ sl.file_name, sl.line, sl.column }); |
| ... | ... | @@ -1104,11 +922,11 @@ fn printLineInfo( |
| 1104 | 922 | try writer.writeAll("???:?:?"); |
| 1105 | 923 | } |
| 1106 | 924 | |
| 1107 | | try tty_config.setColor(writer, .reset); |
| 925 | tty_config.setColor(writer, .reset) catch {}; |
| 1108 | 926 | try writer.writeAll(": "); |
| 1109 | | try tty_config.setColor(writer, .dim); |
| 927 | tty_config.setColor(writer, .dim) catch {}; |
| 1110 | 928 | try writer.print("0x{x} in {s} ({s})", .{ address, symbol_name, compile_unit_name }); |
| 1111 | | try tty_config.setColor(writer, .reset); |
| 929 | tty_config.setColor(writer, .reset) catch {}; |
| 1112 | 930 | try writer.writeAll("\n"); |
| 1113 | 931 | |
| 1114 | 932 | // Show the matching source code line if possible |
| ... | ... | @@ -1119,21 +937,23 @@ fn printLineInfo( |
| 1119 | 937 | const space_needed = @as(usize, @intCast(sl.column - 1)); |
| 1120 | 938 | |
| 1121 | 939 | try writer.splatByteAll(' ', space_needed); |
| 1122 | | try tty_config.setColor(writer, .green); |
| 940 | tty_config.setColor(writer, .green) catch {}; |
| 1123 | 941 | try writer.writeAll("^"); |
| 1124 | | try tty_config.setColor(writer, .reset); |
| 942 | tty_config.setColor(writer, .reset) catch {}; |
| 1125 | 943 | } |
| 1126 | 944 | try writer.writeAll("\n"); |
| 1127 | 945 | } else |err| switch (err) { |
| 1128 | | error.EndOfFile, error.FileNotFound => {}, |
| 1129 | | error.BadPathName => {}, |
| 1130 | | error.AccessDenied => {}, |
| 1131 | | else => return err, |
| 946 | error.WriteFailed => |e| return e, |
| 947 | else => { |
| 948 | // Ignore everything else. Seeing some lines in the trace without the associated |
| 949 | // source line printed is a far better user experience than interleaving the |
| 950 | // trace with a load of filesystem error crap. The user can always just open the |
| 951 | // source file themselves to see the line. |
| 952 | }, |
| 1132 | 953 | } |
| 1133 | 954 | } |
| 1134 | 955 | } |
| 1135 | 956 | } |
| 1136 | | |
| 1137 | 957 | fn printLineFromFile(writer: *Writer, source_location: SourceLocation) !void { |
| 1138 | 958 | // Need this to always block even in async I/O mode, because this could potentially |
| 1139 | 959 | // be called from e.g. the event loop code crashing. |
| ... | ... | @@ -1392,52 +1212,9 @@ fn resetSegfaultHandler() void { |
| 1392 | 1212 | } |
| 1393 | 1213 | |
| 1394 | 1214 | fn handleSegfaultPosix(sig: i32, info: *const posix.siginfo_t, ctx_ptr: ?*anyopaque) callconv(.c) noreturn { |
| 1395 | | // Reset to the default handler so that if a segfault happens in this handler it will crash |
| 1396 | | // the process. Also when this handler returns, the original instruction will be repeated |
| 1397 | | // and the resulting segfault will crash the process rather than continually dump stack traces. |
| 1398 | | resetSegfaultHandler(); |
| 1399 | | |
| 1400 | | const addr = switch (native_os) { |
| 1401 | | .linux => @intFromPtr(info.fields.sigfault.addr), |
| 1402 | | .freebsd, .macos => @intFromPtr(info.addr), |
| 1403 | | .netbsd => @intFromPtr(info.info.reason.fault.addr), |
| 1404 | | .openbsd => @intFromPtr(info.data.fault.addr), |
| 1405 | | .solaris, .illumos => @intFromPtr(info.reason.fault.addr), |
| 1406 | | else => unreachable, |
| 1407 | | }; |
| 1408 | | |
| 1409 | | const code = if (native_os == .netbsd) info.info.code else info.code; |
| 1410 | | nosuspend switch (panic_stage) { |
| 1411 | | 0 => { |
| 1412 | | panic_stage = 1; |
| 1413 | | _ = panicking.fetchAdd(1, .seq_cst); |
| 1414 | | |
| 1415 | | { |
| 1416 | | lockStdErr(); |
| 1417 | | defer unlockStdErr(); |
| 1418 | | |
| 1419 | | dumpSegfaultInfoPosix(sig, code, addr, ctx_ptr); |
| 1420 | | } |
| 1421 | | |
| 1422 | | waitForOtherThreadToFinishPanicking(); |
| 1423 | | }, |
| 1424 | | else => { |
| 1425 | | // panic mutex already locked |
| 1426 | | dumpSegfaultInfoPosix(sig, code, addr, ctx_ptr); |
| 1427 | | }, |
| 1428 | | }; |
| 1429 | | |
| 1430 | | // We cannot allow the signal handler to return because when it runs the original instruction |
| 1431 | | // again, the memory may be mapped and undefined behavior would occur rather than repeating |
| 1432 | | // the segfault. So we simply abort here. |
| 1433 | | posix.abort(); |
| 1434 | | } |
| 1435 | | |
| 1436 | | fn dumpSegfaultInfoPosix(sig: i32, code: i32, addr: usize, ctx_ptr: ?*anyopaque) void { |
| 1437 | | const stderr = lockStderrWriter(&.{}); |
| 1438 | | defer unlockStderrWriter(); |
| 1439 | | _ = switch (sig) { |
| 1440 | | posix.SIG.SEGV => if (native_arch == .x86_64 and native_os == .linux and code == 128) // SI_KERNEL |
| 1215 | if (use_trap_panic) @trap(); |
| 1216 | const addr: ?usize, const name: []const u8 = info: { |
| 1217 | if (native_os == .linux and native_arch == .x86_64) { |
| 1441 | 1218 | // x86_64 doesn't have a full 64-bit virtual address space. |
| 1442 | 1219 | // Addresses outside of that address space are non-canonical |
| 1443 | 1220 | // and the CPU won't provide the faulting address to us. |
| ... | ... | @@ -1445,16 +1222,31 @@ fn dumpSegfaultInfoPosix(sig: i32, code: i32, addr: usize, ctx_ptr: ?*anyopaque) |
| 1445 | 1222 | // but can also happen when no addressable memory is involved; |
| 1446 | 1223 | // for example when reading/writing model-specific registers |
| 1447 | 1224 | // by executing `rdmsr` or `wrmsr` in user-space (unprivileged mode). |
| 1448 | | stderr.writeAll("General protection exception (no address available)\n") |
| 1449 | | else |
| 1450 | | stderr.print("Segmentation fault at address 0x{x}\n", .{addr}), |
| 1451 | | posix.SIG.ILL => stderr.print("Illegal instruction at address 0x{x}\n", .{addr}), |
| 1452 | | posix.SIG.BUS => stderr.print("Bus error at address 0x{x}\n", .{addr}), |
| 1453 | | posix.SIG.FPE => stderr.print("Arithmetic exception at address 0x{x}\n", .{addr}), |
| 1454 | | else => unreachable, |
| 1455 | | } catch posix.abort(); |
| 1456 | | |
| 1457 | | switch (native_arch) { |
| 1225 | const SI_KERNEL = 0x80; |
| 1226 | if (sig == posix.SIG.SEGV and info.code == SI_KERNEL) { |
| 1227 | break :info .{ null, "General protection exception" }; |
| 1228 | } |
| 1229 | } |
| 1230 | const addr: usize = switch (native_os) { |
| 1231 | .linux => @intFromPtr(info.fields.sigfault.addr), |
| 1232 | .freebsd, .macos => @intFromPtr(info.addr), |
| 1233 | .netbsd => @intFromPtr(info.info.reason.fault.addr), |
| 1234 | .openbsd => @intFromPtr(info.data.fault.addr), |
| 1235 | .solaris, .illumos => @intFromPtr(info.reason.fault.addr), |
| 1236 | else => comptime unreachable, |
| 1237 | }; |
| 1238 | const name = switch (sig) { |
| 1239 | posix.SIG.SEGV => "Segmentation fault", |
| 1240 | posix.SIG.ILL => "Illegal instruction", |
| 1241 | posix.SIG.BUS => "Bus error", |
| 1242 | posix.SIG.FPE => "Arithmetic exception", |
| 1243 | else => unreachable, |
| 1244 | }; |
| 1245 | break :info .{ addr, name }; |
| 1246 | }; |
| 1247 | |
| 1248 | // MLUGG TODO: this doesn't make any sense at all? |
| 1249 | const use_context = switch (native_arch) { |
| 1458 | 1250 | .x86, |
| 1459 | 1251 | .x86_64, |
| 1460 | 1252 | .arm, |
| ... | ... | @@ -1463,82 +1255,90 @@ fn dumpSegfaultInfoPosix(sig: i32, code: i32, addr: usize, ctx_ptr: ?*anyopaque) |
| 1463 | 1255 | .thumbeb, |
| 1464 | 1256 | .aarch64, |
| 1465 | 1257 | .aarch64_be, |
| 1466 | | => { |
| 1467 | | // Some kernels don't align `ctx_ptr` properly. Handle this defensively. |
| 1468 | | const ctx: *align(1) posix.ucontext_t = @ptrCast(ctx_ptr); |
| 1469 | | var new_ctx: posix.ucontext_t = ctx.*; |
| 1470 | | if (builtin.os.tag.isDarwin() and builtin.cpu.arch == .aarch64) { |
| 1471 | | // The kernel incorrectly writes the contents of `__mcontext_data` right after `mcontext`, |
| 1472 | | // rather than after the 8 bytes of padding that are supposed to sit between the two. Copy the |
| 1473 | | // contents to the right place so that the `mcontext` pointer will be correct after the |
| 1474 | | // `relocateContext` call below. |
| 1475 | | new_ctx.__mcontext_data = @as(*align(1) extern struct { |
| 1476 | | onstack: c_int, |
| 1477 | | sigmask: std.c.sigset_t, |
| 1478 | | stack: std.c.stack_t, |
| 1479 | | link: ?*std.c.ucontext_t, |
| 1480 | | mcsize: u64, |
| 1481 | | mcontext: *std.c.mcontext_t, |
| 1482 | | __mcontext_data: std.c.mcontext_t align(@sizeOf(usize)), // Disable padding after `mcontext`. |
| 1483 | | }, @ptrCast(ctx)).__mcontext_data; |
| 1484 | | } |
| 1485 | | relocateContext(&new_ctx); |
| 1486 | | dumpStackTraceFromBase(&new_ctx, stderr); |
| 1487 | | }, |
| 1488 | | else => {}, |
| 1258 | => true, |
| 1259 | else => false, |
| 1260 | }; |
| 1261 | if (!have_ucontext or !use_context) return handleSegfault(addr, name, null); |
| 1262 | |
| 1263 | // Some kernels don't align `ctx_ptr` properly, so we'll copy it into a local buffer. |
| 1264 | var copied_ctx: ThreadContext = undefined; |
| 1265 | const orig_ctx: *align(1) posix.ucontext_t = @ptrCast(ctx_ptr); |
| 1266 | copied_ctx = orig_ctx.*; |
| 1267 | if (builtin.os.tag.isDarwin() and builtin.cpu.arch == .aarch64) { |
| 1268 | // The kernel incorrectly writes the contents of `__mcontext_data` right after `mcontext`, |
| 1269 | // rather than after the 8 bytes of padding that are supposed to sit between the two. Copy the |
| 1270 | // contents to the right place so that the `mcontext` pointer will be correct after the |
| 1271 | // `relocateContext` call below. |
| 1272 | const WrittenContext = extern struct { |
| 1273 | onstack: c_int, |
| 1274 | sigmask: std.c.sigset_t, |
| 1275 | stack: std.c.stack_t, |
| 1276 | link: ?*std.c.ucontext_t, |
| 1277 | mcsize: u64, |
| 1278 | mcontext: *std.c.mcontext_t, |
| 1279 | __mcontext_data: std.c.mcontext_t align(@sizeOf(usize)), // Disable padding after `mcontext`. |
| 1280 | }; |
| 1281 | const written_ctx: *align(1) WrittenContext = @ptrCast(ctx_ptr); |
| 1282 | copied_ctx.__mcontext_data = written_ctx.__mcontext_data; |
| 1489 | 1283 | } |
| 1284 | relocateContext(&copied_ctx); |
| 1285 | |
| 1286 | handleSegfault(addr, name, &copied_ctx); |
| 1490 | 1287 | } |
| 1491 | 1288 | |
| 1492 | 1289 | fn handleSegfaultWindows(info: *windows.EXCEPTION_POINTERS) callconv(.winapi) c_long { |
| 1493 | | switch (info.ExceptionRecord.ExceptionCode) { |
| 1494 | | windows.EXCEPTION_DATATYPE_MISALIGNMENT => handleSegfaultWindowsExtra(info, 0, "Unaligned Memory Access"), |
| 1495 | | windows.EXCEPTION_ACCESS_VIOLATION => handleSegfaultWindowsExtra(info, 1, null), |
| 1496 | | windows.EXCEPTION_ILLEGAL_INSTRUCTION => handleSegfaultWindowsExtra(info, 2, null), |
| 1497 | | windows.EXCEPTION_STACK_OVERFLOW => handleSegfaultWindowsExtra(info, 0, "Stack Overflow"), |
| 1290 | if (use_trap_panic) @trap(); |
| 1291 | const name: []const u8, const addr: ?usize = switch (info.ExceptionRecord.ExceptionCode) { |
| 1292 | windows.EXCEPTION_DATATYPE_MISALIGNMENT => .{ "Unaligned memory access", null }, |
| 1293 | windows.EXCEPTION_ACCESS_VIOLATION => .{ "Segmentation fault", info.ExceptionRecord.ExceptionInformation[1] }, |
| 1294 | windows.EXCEPTION_ILLEGAL_INSTRUCTION => .{ "Illegal instruction", info.ContextRecord.getRegs().ip }, |
| 1295 | windows.EXCEPTION_STACK_OVERFLOW => .{ "Stack overflow", null }, |
| 1498 | 1296 | else => return windows.EXCEPTION_CONTINUE_SEARCH, |
| 1499 | | } |
| 1297 | }; |
| 1298 | handleSegfault(addr, name, info.ContextRecord); |
| 1500 | 1299 | } |
| 1501 | 1300 | |
| 1502 | | fn handleSegfaultWindowsExtra(info: *windows.EXCEPTION_POINTERS, msg: u8, label: ?[]const u8) noreturn { |
| 1503 | | // For backends that cannot handle the language features used by this segfault handler, we have a simpler one, |
| 1504 | | switch (builtin.zig_backend) { |
| 1505 | | .stage2_x86_64 => if (builtin.target.ofmt == .coff) @trap(), |
| 1506 | | else => {}, |
| 1507 | | } |
| 1508 | | |
| 1509 | | comptime assert(windows.CONTEXT != void); |
| 1510 | | nosuspend switch (panic_stage) { |
| 1301 | fn handleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?*ThreadContext) noreturn { |
| 1302 | // There is very similar logic to the following in `defaultPanic`. |
| 1303 | switch (panic_stage) { |
| 1511 | 1304 | 0 => { |
| 1512 | 1305 | panic_stage = 1; |
| 1513 | 1306 | _ = panicking.fetchAdd(1, .seq_cst); |
| 1514 | 1307 | |
| 1515 | | { |
| 1308 | trace: { |
| 1309 | const tty_config = tty.detectConfig(.stderr()); |
| 1310 | |
| 1516 | 1311 | const stderr = lockStderrWriter(&.{}); |
| 1517 | 1312 | defer unlockStderrWriter(); |
| 1518 | 1313 | |
| 1519 | | dumpSegfaultInfoWindows(info, msg, label, stderr); |
| 1314 | if (addr) |a| { |
| 1315 | stderr.print("{s} at address 0x{x}\n", .{ name, a }) catch break :trace; |
| 1316 | } else { |
| 1317 | stderr.print("{s} (no address available)\n", .{name}) catch break :trace; |
| 1318 | } |
| 1319 | // MLUGG TODO: for this to work neatly, `ThreadContext` needs to be `noreturn` when not supported! |
| 1320 | if (opt_ctx) |context| { |
| 1321 | writeCurrentStackTrace(.{ |
| 1322 | .context = context, |
| 1323 | .allow_unsafe_unwind = true, // we're crashing anyway, give it our all! |
| 1324 | }, stderr, tty_config) catch break :trace; |
| 1325 | } |
| 1520 | 1326 | } |
| 1521 | | |
| 1522 | | waitForOtherThreadToFinishPanicking(); |
| 1523 | 1327 | }, |
| 1524 | 1328 | 1 => { |
| 1525 | 1329 | panic_stage = 2; |
| 1330 | // A segfault happened while trying to print a previous panic message. |
| 1331 | // We're still holding the mutex but that's fine as we're going to |
| 1332 | // call abort(). |
| 1526 | 1333 | fs.File.stderr().writeAll("aborting due to recursive panic\n") catch {}; |
| 1527 | 1334 | }, |
| 1528 | | else => {}, |
| 1529 | | }; |
| 1530 | | posix.abort(); |
| 1531 | | } |
| 1532 | | |
| 1533 | | fn dumpSegfaultInfoWindows(info: *windows.EXCEPTION_POINTERS, msg: u8, label: ?[]const u8, stderr: *Writer) void { |
| 1534 | | _ = switch (msg) { |
| 1535 | | 0 => stderr.print("{s}\n", .{label.?}), |
| 1536 | | 1 => stderr.print("Segmentation fault at address 0x{x}\n", .{info.ExceptionRecord.ExceptionInformation[1]}), |
| 1537 | | 2 => stderr.print("Illegal instruction at address 0x{x}\n", .{info.ContextRecord.getRegs().ip}), |
| 1538 | | else => unreachable, |
| 1539 | | } catch posix.abort(); |
| 1335 | else => {}, // Panicked while printing the recursive panic message. |
| 1336 | } |
| 1540 | 1337 | |
| 1541 | | dumpStackTraceFromBase(info.ContextRecord, stderr); |
| 1338 | // We cannot allow the signal handler to return because when it runs the original instruction |
| 1339 | // again, the memory may be mapped and undefined behavior would occur rather than repeating |
| 1340 | // the segfault. So we simply abort here. |
| 1341 | posix.abort(); |
| 1542 | 1342 | } |
| 1543 | 1343 | |
| 1544 | 1344 | pub fn dumpStackPointerAddr(prefix: []const u8) void { |
| ... | ... | @@ -1549,26 +1349,22 @@ pub fn dumpStackPointerAddr(prefix: []const u8) void { |
| 1549 | 1349 | } |
| 1550 | 1350 | |
| 1551 | 1351 | test "manage resources correctly" { |
| 1552 | | if (builtin.strip_debug_info) return error.SkipZigTest; |
| 1553 | | |
| 1554 | | if (native_os == .wasi) return error.SkipZigTest; |
| 1555 | | |
| 1556 | | if (native_os == .windows) { |
| 1557 | | // https://github.com/ziglang/zig/issues/13963 |
| 1558 | | return error.SkipZigTest; |
| 1559 | | } |
| 1560 | | |
| 1561 | | // self-hosted debug info is still too buggy |
| 1562 | | if (builtin.zig_backend != .stage2_llvm) return error.SkipZigTest; |
| 1563 | | |
| 1564 | | var discarding: Writer.Discarding = .init(&.{}); |
| 1565 | | var di = try SelfInfo.open(testing.allocator); |
| 1352 | if (!SelfInfo.target_supported) return error.SkipZigTest; |
| 1353 | const S = struct { |
| 1354 | noinline fn showMyTrace() usize { |
| 1355 | return @returnAddress(); |
| 1356 | } |
| 1357 | }; |
| 1358 | var discarding: std.io.Writer.Discarding = .init(&.{}); |
| 1359 | var di: SelfInfo = try .open(testing.allocator); |
| 1566 | 1360 | defer di.deinit(); |
| 1567 | | try printSourceAtAddress(&di, &discarding.writer, showMyTrace(), tty.detectConfig(.stderr())); |
| 1568 | | } |
| 1569 | | |
| 1570 | | noinline fn showMyTrace() usize { |
| 1571 | | return @returnAddress(); |
| 1361 | try printSourceAtAddress( |
| 1362 | testing.allocator, |
| 1363 | &di, |
| 1364 | &discarding.writer, |
| 1365 | S.showMyTrace(), |
| 1366 | tty.detectConfig(.stderr()), |
| 1367 | ); |
| 1572 | 1368 | } |
| 1573 | 1369 | |
| 1574 | 1370 | /// This API helps you track where a value originated and where it was mutated, |
| ... | ... | @@ -1615,12 +1411,11 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize |
| 1615 | 1411 | |
| 1616 | 1412 | if (t.index < size) { |
| 1617 | 1413 | t.notes[t.index] = note; |
| 1618 | | t.addrs[t.index] = [1]usize{0} ** stack_frame_count; |
| 1619 | | var stack_trace: std.builtin.StackTrace = .{ |
| 1620 | | .index = 0, |
| 1621 | | .instruction_addresses = &t.addrs[t.index], |
| 1622 | | }; |
| 1623 | | captureStackTrace(addr, &stack_trace); |
| 1414 | const addrs = &t.addrs[t.index]; |
| 1415 | const st = captureCurrentStackTrace(.{ .first_address = addr }, addrs); |
| 1416 | if (st.index < addrs.len) { |
| 1417 | @memset(addrs[st.index..], 0); // zero unused frames to indicate end of trace |
| 1418 | } |
| 1624 | 1419 | } |
| 1625 | 1420 | // Keep counting even if the end is reached so that the |
| 1626 | 1421 | // user can find out how much more size they need. |
| ... | ... | @@ -1634,13 +1429,6 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize |
| 1634 | 1429 | const stderr = lockStderrWriter(&.{}); |
| 1635 | 1430 | defer unlockStderrWriter(); |
| 1636 | 1431 | const end = @min(t.index, size); |
| 1637 | | const debug_info = getSelfDebugInfo() catch |err| { |
| 1638 | | stderr.print( |
| 1639 | | "Unable to dump stack trace: Unable to open debug info: {s}\n", |
| 1640 | | .{@errorName(err)}, |
| 1641 | | ) catch return; |
| 1642 | | return; |
| 1643 | | }; |
| 1644 | 1432 | for (t.addrs[0..end], 0..) |frames_array, i| { |
| 1645 | 1433 | stderr.print("{s}:\n", .{t.notes[i]}) catch return; |
| 1646 | 1434 | var frames_array_mutable = frames_array; |
| ... | ... | @@ -1649,7 +1437,7 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize |
| 1649 | 1437 | .index = frames.len, |
| 1650 | 1438 | .instruction_addresses = frames, |
| 1651 | 1439 | }; |
| 1652 | | writeStackTrace(stack_trace, stderr, debug_info, tty_config) catch continue; |
| 1440 | writeStackTrace(stack_trace, stderr, tty_config) catch return; |
| 1653 | 1441 | } |
| 1654 | 1442 | if (t.index > end) { |
| 1655 | 1443 | stderr.print("{d} more traces not shown; consider increasing trace size\n", .{ |