authorgravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2026-02-12 13:10:07-05:00
committergravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2026-02-12 13:16:26-05:00
log645ffe21cf77400c88ae9356e86f266cb1b5839f
tree395fa67976e6f4c31a822c6117901d713cab036e
parent251f54d1d7d0f871f551c54d77f1a8238a041983

std.debug: rewrite panic


3 files changed, 80 insertions(+), 141 deletions(-)

lib/std/debug.zig+72-105
...@@ -285,7 +285,7 @@ pub fn lockStderr(buffer: []u8) Io.LockedStderr {...@@ -285,7 +285,7 @@ pub fn lockStderr(buffer: []u8) Io.LockedStderr {
285 const prev = io.swapCancelProtection(.blocked);285 const prev = io.swapCancelProtection(.blocked);
286 defer _ = io.swapCancelProtection(prev);286 defer _ = io.swapCancelProtection(prev);
287 return io.lockStderr(buffer, null) catch |err| switch (err) {287 return io.lockStderr(buffer, null) catch |err| switch (err) {
288 error.Canceled => unreachable, // Cancel protection enabled above.288 error.Canceled => unreachable, // blocked
289 };289 };
290}290}
291291
...@@ -463,13 +463,9 @@ pub fn panicExtra(...@@ -463,13 +463,9 @@ pub fn panicExtra(
463 std.builtin.panic.call(msg, ret_addr);463 std.builtin.panic.call(msg, ret_addr);
464}464}
465465
466/// Non-zero whenever the program triggered a panic.
467/// The counter is incremented/decremented atomically.
468var panicking = std.atomic.Value(u8).init(0);
469
470/// Counts how many times the panic handler is invoked by this thread.466/// Counts how many times the panic handler is invoked by this thread.
471/// This is used to catch and handle panics triggered by the panic handler.467/// This is used to catch and handle panics triggered by the panic handler.
472threadlocal var panic_stage: usize = 0;468threadlocal var recursive_panic_writer: ?*Io.Writer = null;
473469
474/// For backends that cannot handle the language features depended on by the470/// For backends that cannot handle the language features depended on by the
475/// default panic handler, we will use a simpler implementation.471/// default panic handler, we will use a simpler implementation.
...@@ -533,74 +529,51 @@ pub fn defaultPanic(msg: []const u8, first_trace_addr: ?usize) noreturn {...@@ -533,74 +529,51 @@ pub fn defaultPanic(msg: []const u8, first_trace_addr: ?usize) noreturn {
533 else => {},529 else => {},
534 }530 }
535531
536 // Don't try to cancel during a panic. No need to re-enable cancelation,
537 // because the panic handler doesn't return.
538 _ = std.Options.debug_io.swapCancelProtection(.blocked);
539
540 if (enable_segfault_handler) {
541 // If a segfault happens while panicking, we want it to actually segfault, not trigger
542 // the handler.
543 resetSegfaultHandler();
544 }
545
546 // There is very similar logic to the following in `handleSegfault`.532 // There is very similar logic to the following in `handleSegfault`.
547 switch (panic_stage) {533 var discarding: Io.Writer.Discarding = .init(&.{});
548 0 => {534 const current_recursive_panic_writer = recursive_panic_writer;
549 panic_stage = 1;535 recursive_panic_writer = &discarding.writer;
550 _ = panicking.fetchAdd(1, .seq_cst);536 if (current_recursive_panic_writer) |writer| {
551537 // A panic happened while trying to print a previous panic message.
552 trace: {538 writer.writeAll("aborting due to recursive panic\n") catch {};
553 const stderr = lockStderr(&.{}).terminal();539 } else trace: {
554 defer unlockStderr();540 // Don't try to cancel during a panic. No need to re-enable cancelation,
555 const writer = stderr.writer;541 // because the panic handler doesn't return.
556542 _ = std.Options.debug_io.swapCancelProtection(.blocked);
557 if (builtin.single_threaded) {543
558 writer.print("panic: ", .{}) catch break :trace;544 const stderr = lockStderr(&.{}).terminal();
559 } else {545 const writer = stderr.writer;
560 const current_thread_id = std.Thread.getCurrentId();546 recursive_panic_writer = writer;
561 writer.print("thread {d} panic: ", .{current_thread_id}) catch break :trace;547
562 }548 if (enable_segfault_handler) {
563 writer.print("{s}\n", .{msg}) catch break :trace;549 // If a segfault happens while panicking, we want it to actually segfault, not trigger
564550 // the handler.
565 if (@errorReturnTrace()) |t| if (t.index > 0) {551 resetSegfaultHandler();
566 writer.writeAll("error return context:\n") catch break :trace;552 }
567 writeStackTrace(t, stderr) catch break :trace;
568 writer.writeAll("\nstack trace:\n") catch break :trace;
569 };
570 writeCurrentStackTrace(.{
571 .first_address = first_trace_addr orelse @returnAddress(),
572 .allow_unsafe_unwind = true, // we're crashing anyway, give it our all!
573 }, stderr) catch break :trace;
574 }
575553
576 waitForOtherThreadToFinishPanicking();554 if (@hasDecl(root, "debug") and @hasDecl(root.debug, "printCrashContext")) {
577 },555 root.debug.printCrashContext(stderr);
578 1 => {556 }
579 panic_stage = 2;
580 // A panic happened while trying to print a previous panic message.
581 // We're still holding the mutex but that's fine as we're going to
582 // call abort().
583 const stderr = lockStderr(&.{}).terminal();
584 stderr.writer.writeAll("aborting due to recursive panic\n") catch {};
585 },
586 else => {}, // Panicked while printing the recursive panic message.
587 }
588557
589 std.process.abort();558 if (builtin.single_threaded) {
590}559 writer.print("panic: ", .{}) catch break :trace;
560 } else {
561 const current_thread_id = std.Thread.getCurrentId();
562 writer.print("thread {d} panic: ", .{current_thread_id}) catch break :trace;
563 }
564 writer.print("{s}\n", .{msg}) catch break :trace;
591565
592/// Must be called only after adding 1 to `panicking`. There are three callsites.566 if (@errorReturnTrace()) |t| if (t.index > 0) {
593fn waitForOtherThreadToFinishPanicking() void {567 writer.writeAll("error return context:\n") catch break :trace;
594 if (panicking.fetchSub(1, .seq_cst) != 1) {568 writeStackTrace(t, stderr) catch break :trace;
595 // Another thread is panicking, wait for the last one to finish569 writer.writeAll("\nstack trace:\n") catch break :trace;
596 // and call abort()570 };
597 if (builtin.single_threaded) unreachable;571 writeCurrentStackTrace(.{
598572 .first_address = first_trace_addr orelse @returnAddress(),
599 // Sleep forever without hammering the CPU573 .allow_unsafe_unwind = true, // we're crashing anyway, give it our all!
600 var futex: u32 = 0;574 }, stderr) catch break :trace;
601 while (true) std.Options.debug_io.futexWaitUncancelable(u32, &futex, 0);
602 unreachable;
603 }575 }
576 std.process.abort();
604}577}
605578
606pub const StackUnwindOptions = struct {579pub const StackUnwindOptions = struct {
...@@ -1535,44 +1508,38 @@ fn handleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?CpuContextPtr) noret...@@ -1535,44 +1508,38 @@ fn handleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?CpuContextPtr) noret
1535}1508}
15361509
1537pub fn defaultHandleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?CpuContextPtr) noreturn {1510pub fn defaultHandleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?CpuContextPtr) noreturn {
1538 // Don't try to cancel during a segfault. No need to re-enable cancelation,
1539 // because the segfault handler doesn't return.
1540 _ = std.Options.debug_io.swapCancelProtection(.blocked);
1541
1542 // There is very similar logic to the following in `defaultPanic`.1511 // There is very similar logic to the following in `defaultPanic`.
1543 switch (panic_stage) {1512 var discarding: Io.Writer.Discarding = .init(&.{});
1544 0 => {1513 const current_recursive_panic_writer = recursive_panic_writer;
1545 panic_stage = 1;1514 recursive_panic_writer = &discarding.writer;
1546 _ = panicking.fetchAdd(1, .seq_cst);1515 if (current_recursive_panic_writer) |writer| {
15471516 // A segfault happened while trying to print a previous panic message.
1548 trace: {1517 writer.writeAll("aborting due to recursive panic\n") catch {};
1549 const stderr = lockStderr(&.{}).terminal();1518 } else trace: {
1550 defer unlockStderr();1519 // Don't try to cancel during a segfault. No need to re-enable cancelation,
15511520 // because the segfault handler doesn't return.
1552 if (addr) |a| {1521 _ = std.Options.debug_io.swapCancelProtection(.blocked);
1553 stderr.writer.print("{s} at address 0x{x}\n", .{ name, a }) catch break :trace;1522
1554 } else {1523 const stderr = lockStderr(&.{}).terminal();
1555 stderr.writer.print("{s} (no address available)\n", .{name}) catch break :trace;1524 const writer = stderr.writer;
1556 }1525 recursive_panic_writer = writer;
1557 if (opt_ctx) |context| {1526
1558 writeCurrentStackTrace(.{1527 if (@hasDecl(root, "debug") and @hasDecl(root.debug, "printCrashContext")) {
1559 .context = context,1528 root.debug.printCrashContext(stderr);
1560 .allow_unsafe_unwind = true, // we're crashing anyway, give it our all!1529 }
1561 }, stderr) catch break :trace;
1562 }
1563 }
1564 },
1565 1 => {
1566 panic_stage = 2;
1567 // A segfault happened while trying to print a previous panic message.
1568 // We're still holding the mutex but that's fine as we're going to
1569 // call abort().
1570 const stderr = lockStderr(&.{}).terminal();
1571 stderr.writer.writeAll("aborting due to recursive panic\n") catch {};
1572 },
1573 else => {}, // Panicked while printing the recursive panic message.
1574 }
15751530
1531 if (addr) |a| {
1532 writer.print("{s} at address 0x{x}\n", .{ name, a }) catch break :trace;
1533 } else {
1534 writer.print("{s} (no address available)\n", .{name}) catch break :trace;
1535 }
1536 if (opt_ctx) |context| {
1537 writeCurrentStackTrace(.{
1538 .context = context,
1539 .allow_unsafe_unwind = true, // we're crashing anyway, give it our all!
1540 }, stderr) catch break :trace;
1541 }
1542 }
1576 // We cannot allow the signal handler to return because when it runs the original instruction1543 // We cannot allow the signal handler to return because when it runs the original instruction
1577 // again, the memory may be mapped and undefined behavior would occur rather than repeating1544 // again, the memory may be mapped and undefined behavior would occur rather than repeating
1578 // the segfault. So we simply abort here.1545 // the segfault. So we simply abort here.
src/crash_report.zig+3-34
...@@ -1,30 +1,8 @@...@@ -1,30 +1,8 @@
1/// We override the panic implementation to our own one, so we can print our own information before
2/// calling the default panic handler. This declaration must be re-exposed from `@import("root")`.
3pub const panic = std.debug.FullPanic(panicImpl);
4
5/// We let std install its segfault handler, but we override the target-agnostic handler it calls,
6/// so we can print our own information before calling the default segfault logic. This declaration
7/// must be re-exposed from `@import("root")`.
8pub const debug = struct {
9 pub const handleSegfault = handleSegfaultImpl;
10};
11
12/// Printed in panic messages when suggesting a command to run, allowing copy-pasting the command.1/// Printed in panic messages when suggesting a command to run, allowing copy-pasting the command.
13/// Set by `main` as soon as arguments are known. The value here is a default in case we somehow2/// Set by `main` as soon as arguments are known. The value here is a default in case we somehow
14/// crash earlier than that.3/// crash earlier than that.
15pub var zig_argv0: []const u8 = "zig";4pub var zig_argv0: []const u8 = "zig";
165
17fn handleSegfaultImpl(addr: ?usize, name: []const u8, opt_ctx: ?std.debug.CpuContextPtr) noreturn {
18 @branchHint(.cold);
19 dumpCrashContext() catch {};
20 std.debug.defaultHandleSegfault(addr, name, opt_ctx);
21}
22fn panicImpl(msg: []const u8, first_trace_addr: ?usize) noreturn {
23 @branchHint(.cold);
24 dumpCrashContext() catch {};
25 std.debug.defaultPanic(msg, first_trace_addr orelse @returnAddress());
26}
27
28pub const AnalyzeBody = struct {6pub const AnalyzeBody = struct {
29 parent: ?*AnalyzeBody,7 parent: ?*AnalyzeBody,
30 sema: *Sema,8 sema: *Sema,
...@@ -68,23 +46,14 @@ pub const CodegenFunc = struct {...@@ -68,23 +46,14 @@ pub const CodegenFunc = struct {
68 }46 }
69};47};
7048
71fn dumpCrashContext() Io.Writer.Error!void {49pub fn dumpCrashContext(terminal: Io.Terminal) Io.Writer.Error!void {
72 const S = struct {50 const S = struct {
73 /// In the case of recursive panics or segfaults, don't print the context for a second time.
74 threadlocal var already_dumped = false;
75 /// TODO: make this unnecessary. It exists because `print_zir` currently needs an allocator,51 /// TODO: make this unnecessary. It exists because `print_zir` currently needs an allocator,
76 /// but that shouldn't be necessary---it's already only used in one place.52 /// but that shouldn't be necessary---it's already only used in one place.
77 threadlocal var crash_heap: [64 * 1024]u8 = undefined;53 var crash_heap: [64 * 1024]u8 = undefined;
78 };54 };
79 if (S.already_dumped) return;
80 S.already_dumped = true;
81
82 // TODO: this does mean that a different thread could grab the stderr mutex between the context
83 // and the actual panic printing, which would be quite confusing.
84 const stderr = std.debug.lockStderr(&.{});
85 defer std.debug.unlockStderr();
86 const w = &stderr.file_writer.interface;
8755
56 const w = terminal.writer;
88 try w.writeAll("Compiler crash context:\n");57 try w.writeAll("Compiler crash context:\n");
8958
90 if (CodegenFunc.current) |*cg| {59 if (CodegenFunc.current) |*cg| {
src/main.zig+5-2
...@@ -56,8 +56,11 @@ const crash_report_enabled = switch (build_options.io_mode) {...@@ -56,8 +56,11 @@ const crash_report_enabled = switch (build_options.io_mode) {
56 .threaded => build_options.enable_debug_extensions,56 .threaded => build_options.enable_debug_extensions,
57 .evented => false, // would use threadlocals in a way incompatible with evented57 .evented => false, // would use threadlocals in a way incompatible with evented
58};58};
59pub const panic = if (crash_report_enabled) crash_report.panic else std.debug.FullPanic(std.debug.defaultPanic);59pub const debug = if (crash_report_enabled) struct {
60pub const debug = if (crash_report_enabled) crash_report.debug else struct {};60 pub fn printCrashContext(terminal: Io.Terminal) void {
61 crash_report.dumpCrashContext(terminal) catch {};
62 }
63} else struct {};
6164
62var preopens: std.process.Preopens = .empty;65var preopens: std.process.Preopens = .empty;
63pub fn wasi_cwd() Io.Dir {66pub fn wasi_cwd() Io.Dir {