diff --git a/lib/std/debug.zig b/lib/std/debug.zig index a2b5467c1f..8e976176aa 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -285,7 +285,7 @@ pub fn lockStderr(buffer: []u8) Io.LockedStderr { const prev = io.swapCancelProtection(.blocked); defer _ = io.swapCancelProtection(prev); return io.lockStderr(buffer, null) catch |err| switch (err) { - error.Canceled => unreachable, // Cancel protection enabled above. + error.Canceled => unreachable, // blocked }; } @@ -463,13 +463,9 @@ pub fn panicExtra( std.builtin.panic.call(msg, ret_addr); } -/// Non-zero whenever the program triggered a panic. -/// The counter is incremented/decremented atomically. -var panicking = std.atomic.Value(u8).init(0); - /// Counts how many times the panic handler is invoked by this thread. /// This is used to catch and handle panics triggered by the panic handler. -threadlocal var panic_stage: usize = 0; +threadlocal var recursive_panic_writer: ?*Io.Writer = null; /// For backends that cannot handle the language features depended on by the /// default panic handler, we will use a simpler implementation. @@ -533,76 +529,53 @@ pub fn defaultPanic(msg: []const u8, first_trace_addr: ?usize) noreturn { else => {}, } - // Don't try to cancel during a panic. No need to re-enable cancelation, - // because the panic handler doesn't return. - _ = std.Options.debug_io.swapCancelProtection(.blocked); - - if (enable_segfault_handler) { - // If a segfault happens while panicking, we want it to actually segfault, not trigger - // the handler. - resetSegfaultHandler(); - } - // There is very similar logic to the following in `handleSegfault`. - switch (panic_stage) { - 0 => { - panic_stage = 1; - _ = panicking.fetchAdd(1, .seq_cst); + var discarding: Io.Writer.Discarding = .init(&.{}); + const current_recursive_panic_writer = recursive_panic_writer; + recursive_panic_writer = &discarding.writer; + if (current_recursive_panic_writer) |writer| { + // A panic happened while trying to print a previous panic message. + writer.writeAll("aborting due to recursive panic\n") catch {}; + } else trace: { + // Don't try to cancel during a panic. No need to re-enable cancelation, + // because the panic handler doesn't return. + _ = std.Options.debug_io.swapCancelProtection(.blocked); - trace: { - const stderr = lockStderr(&.{}).terminal(); - defer unlockStderr(); - const writer = stderr.writer; + const stderr = lockStderr(&.{}).terminal(); + const writer = stderr.writer; + recursive_panic_writer = writer; - if (builtin.single_threaded) { - writer.print("panic: ", .{}) catch break :trace; - } else { - const current_thread_id = std.Thread.getCurrentId(); - writer.print("thread {d} panic: ", .{current_thread_id}) catch break :trace; - } - writer.print("{s}\n", .{msg}) catch break :trace; + if (enable_segfault_handler) { + // If a segfault happens while panicking, we want it to actually segfault, not trigger + // the handler. + resetSegfaultHandler(); + } - if (@errorReturnTrace()) |t| if (t.index > 0) { - writer.writeAll("error return context:\n") catch break :trace; - writeStackTrace(t, stderr) catch break :trace; - writer.writeAll("\nstack trace:\n") catch break :trace; - }; - writeCurrentStackTrace(.{ - .first_address = first_trace_addr orelse @returnAddress(), - .allow_unsafe_unwind = true, // we're crashing anyway, give it our all! - }, stderr) catch break :trace; - } + if (@hasDecl(root, "debug") and @hasDecl(root.debug, "printCrashContext")) { + root.debug.printCrashContext(stderr); + } - waitForOtherThreadToFinishPanicking(); - }, - 1 => { - panic_stage = 2; - // A panic happened while trying to print a previous panic message. - // We're still holding the mutex but that's fine as we're going to - // call abort(). - const stderr = lockStderr(&.{}).terminal(); - stderr.writer.writeAll("aborting due to recursive panic\n") catch {}; - }, - else => {}, // Panicked while printing the recursive panic message. + if (builtin.single_threaded) { + writer.print("panic: ", .{}) catch break :trace; + } else { + const current_thread_id = std.Thread.getCurrentId(); + writer.print("thread {d} panic: ", .{current_thread_id}) catch break :trace; + } + writer.print("{s}\n", .{msg}) catch break :trace; + + if (@errorReturnTrace()) |t| if (t.index > 0) { + writer.writeAll("error return context:\n") catch break :trace; + writeStackTrace(t, stderr) catch break :trace; + writer.writeAll("\nstack trace:\n") catch break :trace; + }; + writeCurrentStackTrace(.{ + .first_address = first_trace_addr orelse @returnAddress(), + .allow_unsafe_unwind = true, // we're crashing anyway, give it our all! + }, stderr) catch break :trace; } - std.process.abort(); } -/// Must be called only after adding 1 to `panicking`. There are three callsites. -fn waitForOtherThreadToFinishPanicking() void { - if (panicking.fetchSub(1, .seq_cst) != 1) { - // Another thread is panicking, wait for the last one to finish - // and call abort() - if (builtin.single_threaded) unreachable; - - // Sleep forever without hammering the CPU - var futex: u32 = 0; - while (true) std.Options.debug_io.futexWaitUncancelable(u32, &futex, 0); - unreachable; - } -} - pub const StackUnwindOptions = struct { /// If not `null`, we will ignore all frames up until this return address. This is typically /// used to omit intermediate handling code (for instance, a panic handler and its machinery) @@ -1535,44 +1508,38 @@ fn handleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?CpuContextPtr) noret } pub fn defaultHandleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?CpuContextPtr) noreturn { - // Don't try to cancel during a segfault. No need to re-enable cancelation, - // because the segfault handler doesn't return. - _ = std.Options.debug_io.swapCancelProtection(.blocked); - // There is very similar logic to the following in `defaultPanic`. - switch (panic_stage) { - 0 => { - panic_stage = 1; - _ = panicking.fetchAdd(1, .seq_cst); + var discarding: Io.Writer.Discarding = .init(&.{}); + const current_recursive_panic_writer = recursive_panic_writer; + recursive_panic_writer = &discarding.writer; + if (current_recursive_panic_writer) |writer| { + // A segfault happened while trying to print a previous panic message. + writer.writeAll("aborting due to recursive panic\n") catch {}; + } else trace: { + // Don't try to cancel during a segfault. No need to re-enable cancelation, + // because the segfault handler doesn't return. + _ = std.Options.debug_io.swapCancelProtection(.blocked); - trace: { - const stderr = lockStderr(&.{}).terminal(); - defer unlockStderr(); + const stderr = lockStderr(&.{}).terminal(); + const writer = stderr.writer; + recursive_panic_writer = writer; - if (addr) |a| { - stderr.writer.print("{s} at address 0x{x}\n", .{ name, a }) catch break :trace; - } else { - stderr.writer.print("{s} (no address available)\n", .{name}) catch break :trace; - } - if (opt_ctx) |context| { - writeCurrentStackTrace(.{ - .context = context, - .allow_unsafe_unwind = true, // we're crashing anyway, give it our all! - }, stderr) catch break :trace; - } - } - }, - 1 => { - panic_stage = 2; - // A segfault happened while trying to print a previous panic message. - // We're still holding the mutex but that's fine as we're going to - // call abort(). - const stderr = lockStderr(&.{}).terminal(); - stderr.writer.writeAll("aborting due to recursive panic\n") catch {}; - }, - else => {}, // Panicked while printing the recursive panic message. + if (@hasDecl(root, "debug") and @hasDecl(root.debug, "printCrashContext")) { + root.debug.printCrashContext(stderr); + } + + if (addr) |a| { + writer.print("{s} at address 0x{x}\n", .{ name, a }) catch break :trace; + } else { + writer.print("{s} (no address available)\n", .{name}) catch break :trace; + } + if (opt_ctx) |context| { + writeCurrentStackTrace(.{ + .context = context, + .allow_unsafe_unwind = true, // we're crashing anyway, give it our all! + }, stderr) catch break :trace; + } } - // We cannot allow the signal handler to return because when it runs the original instruction // again, the memory may be mapped and undefined behavior would occur rather than repeating // the segfault. So we simply abort here. diff --git a/src/crash_report.zig b/src/crash_report.zig index 1723ad24a0..dc420ded02 100644 --- a/src/crash_report.zig +++ b/src/crash_report.zig @@ -1,30 +1,8 @@ -/// We override the panic implementation to our own one, so we can print our own information before -/// calling the default panic handler. This declaration must be re-exposed from `@import("root")`. -pub const panic = std.debug.FullPanic(panicImpl); - -/// We let std install its segfault handler, but we override the target-agnostic handler it calls, -/// so we can print our own information before calling the default segfault logic. This declaration -/// must be re-exposed from `@import("root")`. -pub const debug = struct { - pub const handleSegfault = handleSegfaultImpl; -}; - /// Printed in panic messages when suggesting a command to run, allowing copy-pasting the command. /// Set by `main` as soon as arguments are known. The value here is a default in case we somehow /// crash earlier than that. pub var zig_argv0: []const u8 = "zig"; -fn handleSegfaultImpl(addr: ?usize, name: []const u8, opt_ctx: ?std.debug.CpuContextPtr) noreturn { - @branchHint(.cold); - dumpCrashContext() catch {}; - std.debug.defaultHandleSegfault(addr, name, opt_ctx); -} -fn panicImpl(msg: []const u8, first_trace_addr: ?usize) noreturn { - @branchHint(.cold); - dumpCrashContext() catch {}; - std.debug.defaultPanic(msg, first_trace_addr orelse @returnAddress()); -} - pub const AnalyzeBody = struct { parent: ?*AnalyzeBody, sema: *Sema, @@ -68,23 +46,14 @@ pub const CodegenFunc = struct { } }; -fn dumpCrashContext() Io.Writer.Error!void { +pub fn dumpCrashContext(terminal: Io.Terminal) Io.Writer.Error!void { const S = struct { - /// In the case of recursive panics or segfaults, don't print the context for a second time. - threadlocal var already_dumped = false; /// TODO: make this unnecessary. It exists because `print_zir` currently needs an allocator, /// but that shouldn't be necessary---it's already only used in one place. - threadlocal var crash_heap: [64 * 1024]u8 = undefined; + var crash_heap: [64 * 1024]u8 = undefined; }; - if (S.already_dumped) return; - S.already_dumped = true; - - // TODO: this does mean that a different thread could grab the stderr mutex between the context - // and the actual panic printing, which would be quite confusing. - const stderr = std.debug.lockStderr(&.{}); - defer std.debug.unlockStderr(); - const w = &stderr.file_writer.interface; + const w = terminal.writer; try w.writeAll("Compiler crash context:\n"); if (CodegenFunc.current) |*cg| { diff --git a/src/main.zig b/src/main.zig index 89a24559ed..ef1df6dfcf 100644 --- a/src/main.zig +++ b/src/main.zig @@ -56,8 +56,11 @@ const crash_report_enabled = switch (build_options.io_mode) { .threaded => build_options.enable_debug_extensions, .evented => false, // would use threadlocals in a way incompatible with evented }; -pub const panic = if (crash_report_enabled) crash_report.panic else std.debug.FullPanic(std.debug.defaultPanic); -pub const debug = if (crash_report_enabled) crash_report.debug else struct {}; +pub const debug = if (crash_report_enabled) struct { + pub fn printCrashContext(terminal: Io.Terminal) void { + crash_report.dumpCrashContext(terminal) catch {}; + } +} else struct {}; var preopens: std.process.Preopens = .empty; pub fn wasi_cwd() Io.Dir {