From d9661e9e05af7a4be31c17cbfbbd8bc44d7c9000 Mon Sep 17 00:00:00 2001 From: mlugg Date: Mon, 8 Sep 2025 14:32:02 +0100 Subject: [PATCH] compiler: better crash handler Far simpler, because everything which `crash_report.zig` did is now handled pretty well by `std.debug` anyway. All we want is to print some context around panics and segfaults. Using the new ability to override the default segfault handler while still having std handle the target-specific bits for us, that's really simple. --- src/Sema.zig | 6 +- src/Zcu/PerThread.zig | 4 + src/crash_report.zig | 570 ++++++++---------------------------------- src/main.zig | 6 +- 4 files changed, 118 insertions(+), 468 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index 73c5b13c21..81d7ed43cd 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -1130,8 +1130,8 @@ fn analyzeBodyInner( const tags = sema.code.instructions.items(.tag); const datas = sema.code.instructions.items(.data); - var crash_info = crash_report.prepAnalyzeBody(sema, block, body); - crash_info.push(); + var crash_info: crash_report.AnalyzeBody = undefined; + crash_info.push(sema, block, body); defer crash_info.pop(); // We use a while (true) loop here to avoid a redundant way of breaking out of @@ -2632,7 +2632,7 @@ pub fn failWithOwnedErrorMsg(sema: *Sema, block: ?*Block, err_msg: *Zcu.ErrorMsg std.debug.print("compile error during Sema:\n", .{}); var error_bundle = wip_errors.toOwnedBundle("") catch @panic("out of memory"); error_bundle.renderToStdErr(.{ .ttyconf = .no_color }); - crash_report.compilerPanic("unexpected compile error occurred", null); + std.debug.panicExtra(@returnAddress(), "unexpected compile error occurred", .{}); } if (block) |start_block| { diff --git a/src/Zcu/PerThread.zig b/src/Zcu/PerThread.zig index 32782e7e89..62b8756c49 100644 --- a/src/Zcu/PerThread.zig +++ b/src/Zcu/PerThread.zig @@ -28,6 +28,7 @@ const Value = @import("../Value.zig"); const Zcu = @import("../Zcu.zig"); const Compilation = @import("../Compilation.zig"); const codegen = @import("../codegen.zig"); +const crash_report = @import("../crash_report.zig"); const Zir = std.zig.Zir; const Zoir = std.zig.Zoir; const ZonGen = std.zig.ZonGen; @@ -4390,6 +4391,9 @@ pub fn addDependency(pt: Zcu.PerThread, unit: AnalUnit, dependee: InternPool.Dep pub fn runCodegen(pt: Zcu.PerThread, func_index: InternPool.Index, air: *Air, out: *@import("../link.zig").ZcuTask.LinkFunc.SharedMir) void { const zcu = pt.zcu; + crash_report.CodegenFunc.start(zcu, func_index); + defer crash_report.CodegenFunc.stop(func_index); + var timer = zcu.comp.startTimer(); const success: bool = if (runCodegenInner(pt, func_index, air)) |mir| success: { diff --git a/src/crash_report.zig b/src/crash_report.zig index e1692568bf..c696c42cfc 100644 --- a/src/crash_report.zig +++ b/src/crash_report.zig @@ -1,34 +1,31 @@ -const std = @import("std"); -const builtin = @import("builtin"); -const build_options = @import("build_options"); -const debug = std.debug; -const print_zir = @import("print_zir.zig"); -const windows = std.os.windows; -const posix = std.posix; -const native_os = builtin.os.tag; - -const Zcu = @import("Zcu.zig"); -const Sema = @import("Sema.zig"); -const InternPool = @import("InternPool.zig"); -const Zir = std.zig.Zir; -const Decl = Zcu.Decl; -const dev = @import("dev.zig"); - -/// To use these crash report diagnostics, publish this panic in your main file -/// and add `pub const enable_segfault_handler = false;` to your `std_options`. -/// You will also need to call initialize() on startup, preferably as the very first operation in your program. -pub const panic = if (build_options.enable_debug_extensions) - std.debug.FullPanic(compilerPanic) -else if (dev.env == .bootstrap) +/// 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 = if (dev.env == .bootstrap) std.debug.simple_panic else - std.debug.FullPanic(std.debug.defaultPanic); + std.debug.FullPanic(panicImpl); -/// Install signal handlers to identify crashes and report diagnostics. -pub fn initialize() void { - if (build_options.enable_debug_extensions and debug.have_segfault_handling_support) { - attachSegfaultHandler(); - } +/// 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.ThreadContext) 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 = if (build_options.enable_debug_extensions) struct { @@ -38,63 +35,96 @@ pub const AnalyzeBody = if (build_options.enable_debug_extensions) struct { body: []const Zir.Inst.Index, body_index: usize, - pub fn push(self: *@This()) void { - const head = &zir_state; - debug.assert(self.parent == null); - self.parent = head.*; - head.* = self; + threadlocal var current: ?*AnalyzeBody = null; + + pub fn setBodyIndex(ab: *AnalyzeBody, index: usize) void { + ab.body_index = index; } - pub fn pop(self: *@This()) void { - const head = &zir_state; - const old = head.*.?; - debug.assert(old == self); - head.* = old.parent; + pub fn push(ab: *AnalyzeBody, sema: *Sema, block: *Sema.Block, body: []const Zir.Inst.Index) void { + ab.* = .{ + .parent = current, + .sema = sema, + .block = block, + .body = body, + .body_index = 0, + }; + current = ab; } - - pub fn setBodyIndex(self: *@This(), index: usize) void { - self.body_index = index; + pub fn pop(ab: *AnalyzeBody) void { + std.debug.assert(current.? == ab); // `Sema.analyzeBodyInner` did not match push/pop calls + current = ab.parent; } } else struct { - pub inline fn push(_: @This()) void {} - pub inline fn pop(_: @This()) void {} + // Dummy implementation, with functions marked `inline` to avoid interfering with tail calls. + pub inline fn push(_: AnalyzeBody, _: *Sema, _: *Sema.Block, _: []const Zir.Inst.Index) void {} + pub inline fn pop(_: AnalyzeBody) void {} pub inline fn setBodyIndex(_: @This(), _: usize) void {} }; -threadlocal var zir_state: ?*AnalyzeBody = if (build_options.enable_debug_extensions) null else @compileError("Cannot use zir_state without debug extensions."); +pub const CodegenFunc = if (build_options.enable_debug_extensions) struct { + zcu: *const Zcu, + func_index: InternPool.Index, + threadlocal var current: ?CodegenFunc = null; + pub fn start(zcu: *const Zcu, func_index: InternPool.Index) void { + std.debug.assert(current == null); + current = .{ .zcu = zcu, .func_index = func_index }; + } + pub fn stop(func_index: InternPool.Index) void { + std.debug.assert(current.?.func_index == func_index); + current = null; + } +} else struct { + // Dummy implementation + pub fn start(_: *const Zcu, _: InternPool.Index) void {} + pub fn stop(_: InternPool.Index) void {} +}; -pub fn prepAnalyzeBody(sema: *Sema, block: *Sema.Block, body: []const Zir.Inst.Index) AnalyzeBody { - return if (build_options.enable_debug_extensions) .{ - .parent = null, - .sema = sema, - .block = block, - .body = body, - .body_index = 0, - } else .{}; +fn dumpCrashContext() 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; + }; + 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.lockStderrWriter(&.{}); + defer std.debug.unlockStderrWriter(); + + try stderr.writeAll("Compiler crash context:\n"); + + if (CodegenFunc.current) |*cg| { + const func_nav = cg.zcu.funcInfo(cg.func_index).owner_nav; + const func_fqn = cg.zcu.intern_pool.getNav(func_nav).fqn; + try stderr.print("Generating function '{f}'\n\n", .{func_fqn.fmt(&cg.zcu.intern_pool)}); + } else if (AnalyzeBody.current) |anal| { + try dumpCrashContextSema(anal, stderr, &S.crash_heap); + } else { + try stderr.writeAll("(no context)\n\n"); + } } - -fn dumpStatusReport() !void { - const anal = zir_state orelse return; - // Note: We have the panic mutex here, so we can safely use the global crash heap. - var fba = std.heap.FixedBufferAllocator.init(&crash_heap); - const allocator = fba.allocator(); - - var stderr_fw = std.fs.File.stderr().writer(&.{}); - const stderr = &stderr_fw.interface; +fn dumpCrashContextSema(anal: *AnalyzeBody, stderr: *Io.Writer, crash_heap: []u8) Io.Writer.Error!void { const block: *Sema.Block = anal.block; const zcu = anal.sema.pt.zcu; + const comp = zcu.comp; + + var fba: std.heap.FixedBufferAllocator = .init(crash_heap); const file, const src_base_node = Zcu.LazySrcLoc.resolveBaseNode(block.src_base_inst, zcu) orelse { const file = zcu.fileByIndex(block.src_base_inst.resolveFile(&zcu.intern_pool)); - try stderr.print("Analyzing lost instruction in file '{f}'. This should not happen!\n\n", .{file.path.fmt(zcu.comp)}); + try stderr.print("Analyzing lost instruction in file '{f}'. This should not happen!\n\n", .{file.path.fmt(comp)}); return; }; - try stderr.writeAll("Analyzing "); - try stderr.print("Analyzing '{f}'\n", .{file.path.fmt(zcu.comp)}); + try stderr.print("Analyzing '{f}'\n", .{file.path.fmt(comp)}); print_zir.renderInstructionContext( - allocator, + fba.allocator(), anal.body, anal.body_index, file, @@ -107,16 +137,16 @@ fn dumpStatusReport() !void { }; try stderr.print( \\ For full context, use the command - \\ zig ast-check -t {f} + \\ {s} ast-check -t {f} \\ \\ - , .{file.path.fmt(zcu.comp)}); + , .{ zig_argv0, file.path.fmt(comp) }); var parent = anal.parent; while (parent) |curr| { fba.reset(); const cur_block_file = zcu.fileByIndex(curr.block.src_base_inst.resolveFile(&zcu.intern_pool)); - try stderr.print(" in {f}\n", .{cur_block_file.path.fmt(zcu.comp)}); + try stderr.print(" in {f}\n", .{cur_block_file.path.fmt(comp)}); _, const cur_block_src_base_node = Zcu.LazySrcLoc.resolveBaseNode(curr.block.src_base_inst, zcu) orelse { try stderr.writeAll(" > [lost instruction; this should not happen]\n"); parent = curr.parent; @@ -124,7 +154,7 @@ fn dumpStatusReport() !void { }; try stderr.writeAll(" > "); print_zir.renderSingleInstruction( - allocator, + fba.allocator(), curr.body[curr.body_index], cur_block_file, cur_block_src_base_node, @@ -142,398 +172,14 @@ fn dumpStatusReport() !void { try stderr.writeByte('\n'); } -var crash_heap: [16 * 4096]u8 = undefined; +const std = @import("std"); +const Io = std.Io; +const Zir = std.zig.Zir; -pub fn compilerPanic(msg: []const u8, maybe_ret_addr: ?usize) noreturn { - @branchHint(.cold); - PanicSwitch.preDispatch(); - const ret_addr = maybe_ret_addr orelse @returnAddress(); - const stack_ctx: StackContext = .{ .current = .{ .ret_addr = ret_addr } }; - PanicSwitch.dispatch(@errorReturnTrace(), stack_ctx, msg); -} +const Sema = @import("Sema.zig"); +const Zcu = @import("Zcu.zig"); +const InternPool = @import("InternPool.zig"); +const dev = @import("dev.zig"); +const print_zir = @import("print_zir.zig"); -/// Attaches a global SIGSEGV handler -pub fn attachSegfaultHandler() void { - if (!debug.have_segfault_handling_support) { - @compileError("segfault handler not supported for this target"); - } - if (native_os == .windows) { - _ = windows.kernel32.AddVectoredExceptionHandler(0, handleSegfaultWindows); - return; - } - const act: posix.Sigaction = .{ - .handler = .{ .sigaction = handleSegfaultPosix }, - .mask = posix.sigemptyset(), - .flags = (posix.SA.SIGINFO | posix.SA.RESTART | posix.SA.RESETHAND), - }; - debug.updateSegfaultHandler(&act); -} - -fn handleSegfaultPosix(sig: i32, info: *const posix.siginfo_t, ctx_ptr: ?*anyopaque) callconv(.c) noreturn { - // TODO: use alarm() here to prevent infinite loops - PanicSwitch.preDispatch(); - - const addr = switch (native_os) { - .linux => @intFromPtr(info.fields.sigfault.addr), - .freebsd, .macos => @intFromPtr(info.addr), - .netbsd => @intFromPtr(info.info.reason.fault.addr), - .openbsd => @intFromPtr(info.data.fault.addr), - .solaris, .illumos => @intFromPtr(info.reason.fault.addr), - else => @compileError("TODO implement handleSegfaultPosix for new POSIX OS"), - }; - - var err_buffer: [128]u8 = undefined; - const error_msg = switch (sig) { - posix.SIG.SEGV => std.fmt.bufPrint(&err_buffer, "Segmentation fault at address 0x{x}", .{addr}) catch "Segmentation fault", - posix.SIG.ILL => std.fmt.bufPrint(&err_buffer, "Illegal instruction at address 0x{x}", .{addr}) catch "Illegal instruction", - posix.SIG.BUS => std.fmt.bufPrint(&err_buffer, "Bus error at address 0x{x}", .{addr}) catch "Bus error", - else => std.fmt.bufPrint(&err_buffer, "Unknown error (signal {}) at address 0x{x}", .{ sig, addr }) catch "Unknown error", - }; - - const stack_ctx: StackContext = switch (builtin.cpu.arch) { - .x86, - .x86_64, - .arm, - .aarch64, - => StackContext{ .exception = @ptrCast(@alignCast(ctx_ptr)) }, - else => .not_supported, - }; - - PanicSwitch.dispatch(null, stack_ctx, error_msg); -} - -const WindowsSegfaultMessage = union(enum) { - literal: []const u8, - segfault: void, - illegal_instruction: void, -}; - -fn handleSegfaultWindows(info: *windows.EXCEPTION_POINTERS) callconv(.winapi) c_long { - switch (info.ExceptionRecord.ExceptionCode) { - windows.EXCEPTION_DATATYPE_MISALIGNMENT => handleSegfaultWindowsExtra(info, .{ .literal = "Unaligned Memory Access" }), - windows.EXCEPTION_ACCESS_VIOLATION => handleSegfaultWindowsExtra(info, .segfault), - windows.EXCEPTION_ILLEGAL_INSTRUCTION => handleSegfaultWindowsExtra(info, .illegal_instruction), - windows.EXCEPTION_STACK_OVERFLOW => handleSegfaultWindowsExtra(info, .{ .literal = "Stack Overflow" }), - else => return windows.EXCEPTION_CONTINUE_SEARCH, - } -} - -fn handleSegfaultWindowsExtra(info: *windows.EXCEPTION_POINTERS, comptime msg: WindowsSegfaultMessage) noreturn { - PanicSwitch.preDispatch(); - - const stack_ctx = if (@hasDecl(windows, "CONTEXT")) - StackContext{ .exception = info.ContextRecord } - else ctx: { - const addr = @intFromPtr(info.ExceptionRecord.ExceptionAddress); - break :ctx StackContext{ .current = .{ .ret_addr = addr } }; - }; - - switch (msg) { - .literal => |err| PanicSwitch.dispatch(null, stack_ctx, err), - .segfault => { - const format_item = "Segmentation fault at address 0x{x}"; - var buf: [format_item.len + 32]u8 = undefined; // 32 is arbitrary, but sufficiently large - const to_print = std.fmt.bufPrint(&buf, format_item, .{info.ExceptionRecord.ExceptionInformation[1]}) catch unreachable; - PanicSwitch.dispatch(null, stack_ctx, to_print); - }, - .illegal_instruction => { - const ip: ?usize = switch (stack_ctx) { - .exception => |ex| ex.getRegs().ip, - .current => |cur| cur.ret_addr, - .not_supported => null, - }; - - if (ip) |addr| { - const format_item = "Illegal instruction at address 0x{x}"; - var buf: [format_item.len + 32]u8 = undefined; // 32 is arbitrary, but sufficiently large - const to_print = std.fmt.bufPrint(&buf, format_item, .{addr}) catch unreachable; - PanicSwitch.dispatch(null, stack_ctx, to_print); - } else { - PanicSwitch.dispatch(null, stack_ctx, "Illegal Instruction"); - } - }, - } -} - -const StackContext = union(enum) { - current: struct { - ret_addr: ?usize, - }, - exception: *debug.ThreadContext, - not_supported: void, - - pub fn dumpStackTrace(ctx: @This()) void { - switch (ctx) { - .current => |ct| { - debug.dumpCurrentStackTrace(ct.ret_addr); - }, - .exception => |context| { - var stderr_fw = std.fs.File.stderr().writer(&.{}); - const stderr = &stderr_fw.interface; - debug.dumpStackTraceFromBase(context, stderr); - }, - .not_supported => { - std.fs.File.stderr().writeAll("Stack trace not supported on this platform.\n") catch {}; - }, - } - } -}; - -const PanicSwitch = struct { - const RecoverStage = enum { - initialize, - report_stack, - release_mutex, - release_ref_count, - abort, - silent_abort, - }; - - const RecoverVerbosity = enum { - message_and_stack, - message_only, - silent, - }; - - const PanicState = struct { - recover_stage: RecoverStage = .initialize, - recover_verbosity: RecoverVerbosity = .message_and_stack, - panic_ctx: StackContext = undefined, - panic_trace: ?*const std.builtin.StackTrace = null, - awaiting_dispatch: bool = false, - }; - - /// Counter for the number of threads currently panicking. - /// Updated atomically before taking the panic_mutex. - /// In recoverable cases, the program will not abort - /// until all panicking threads have dumped their traces. - var panicking = std.atomic.Value(u8).init(0); - - /// Tracks the state of the current panic. If the code within the - /// panic triggers a secondary panic, this allows us to recover. - threadlocal var panic_state_raw: PanicState = .{}; - - /// The segfault handlers above need to do some work before they can dispatch - /// this switch. Calling preDispatch() first makes that work fault tolerant. - pub fn preDispatch() void { - // TODO: We want segfaults to trigger the panic recursively here, - // but if there is a segfault accessing this TLS slot it will cause an - // infinite loop. We should use `alarm()` to prevent the infinite - // loop and maybe also use a non-thread-local global to detect if - // it's happening and print a message. - var panic_state: *volatile PanicState = &panic_state_raw; - if (panic_state.awaiting_dispatch) { - dispatch(null, .{ .current = .{ .ret_addr = null } }, "Panic while preparing callstack"); - } - panic_state.awaiting_dispatch = true; - } - - /// This is the entry point to a panic-tolerant panic handler. - /// preDispatch() *MUST* be called exactly once before calling this. - /// A threadlocal "recover_stage" is updated throughout the process. - /// If a panic happens during the panic, the recover_stage will be - /// used to select a recover* function to call to resume the panic. - /// The recover_verbosity field is used to handle panics while reporting - /// panics within panics. If the panic handler triggers a panic, it will - /// attempt to log an additional stack trace for the secondary panic. If - /// that panics, it will fall back to just logging the panic message. If - /// it can't even do that witout panicing, it will recover without logging - /// anything about the internal panic. Depending on the state, "recover" - /// here may just mean "call abort". - pub fn dispatch( - trace: ?*const std.builtin.StackTrace, - stack_ctx: StackContext, - msg: []const u8, - ) noreturn { - var panic_state: *volatile PanicState = &panic_state_raw; - debug.assert(panic_state.awaiting_dispatch); - panic_state.awaiting_dispatch = false; - nosuspend switch (panic_state.recover_stage) { - .initialize => goTo(initPanic, .{ panic_state, trace, stack_ctx, msg }), - .report_stack => goTo(recoverReportStack, .{ panic_state, trace, stack_ctx, msg }), - .release_mutex => goTo(recoverReleaseMutex, .{ panic_state, trace, stack_ctx, msg }), - .release_ref_count => goTo(recoverReleaseRefCount, .{ panic_state, trace, stack_ctx, msg }), - .abort => goTo(recoverAbort, .{ panic_state, trace, stack_ctx, msg }), - .silent_abort => goTo(abort, .{}), - }; - } - - noinline fn initPanic( - state: *volatile PanicState, - trace: ?*const std.builtin.StackTrace, - stack: StackContext, - msg: []const u8, - ) noreturn { - // use a temporary so there's only one volatile store - const new_state = PanicState{ - .recover_stage = .abort, - .panic_ctx = stack, - .panic_trace = trace, - }; - state.* = new_state; - - _ = panicking.fetchAdd(1, .seq_cst); - - state.recover_stage = .release_ref_count; - - std.debug.lockStdErr(); - - state.recover_stage = .release_mutex; - - var stderr_fw = std.fs.File.stderr().writer(&.{}); - const stderr = &stderr_fw.interface; - if (builtin.single_threaded) { - stderr.print("panic: ", .{}) catch goTo(releaseMutex, .{state}); - } else { - const current_thread_id = std.Thread.getCurrentId(); - stderr.print("thread {} panic: ", .{current_thread_id}) catch goTo(releaseMutex, .{state}); - } - stderr.print("{s}\n", .{msg}) catch goTo(releaseMutex, .{state}); - - state.recover_stage = .report_stack; - - dumpStatusReport() catch |err| { - stderr.print("\nIntercepted error.{} while dumping current state. Continuing...\n", .{err}) catch {}; - }; - - goTo(reportStack, .{state}); - } - - noinline fn recoverReportStack( - state: *volatile PanicState, - trace: ?*const std.builtin.StackTrace, - stack: StackContext, - msg: []const u8, - ) noreturn { - recover(state, trace, stack, msg); - - state.recover_stage = .release_mutex; - var stderr_fw = std.fs.File.stderr().writer(&.{}); - const stderr = &stderr_fw.interface; - stderr.writeAll("\nOriginal Error:\n") catch {}; - goTo(reportStack, .{state}); - } - - noinline fn reportStack(state: *volatile PanicState) noreturn { - state.recover_stage = .release_mutex; - - if (state.panic_trace) |t| { - debug.dumpStackTrace(t.*); - } - state.panic_ctx.dumpStackTrace(); - - goTo(releaseMutex, .{state}); - } - - noinline fn recoverReleaseMutex( - state: *volatile PanicState, - trace: ?*const std.builtin.StackTrace, - stack: StackContext, - msg: []const u8, - ) noreturn { - recover(state, trace, stack, msg); - goTo(releaseMutex, .{state}); - } - - noinline fn releaseMutex(state: *volatile PanicState) noreturn { - state.recover_stage = .abort; - - std.debug.unlockStdErr(); - - goTo(releaseRefCount, .{state}); - } - - noinline fn recoverReleaseRefCount( - state: *volatile PanicState, - trace: ?*const std.builtin.StackTrace, - stack: StackContext, - msg: []const u8, - ) noreturn { - recover(state, trace, stack, msg); - goTo(releaseRefCount, .{state}); - } - - noinline fn releaseRefCount(state: *volatile PanicState) noreturn { - state.recover_stage = .abort; - - if (panicking.fetchSub(1, .seq_cst) != 1) { - // Another thread is panicking, wait for the last one to finish - // and call abort() - - // Sleep forever without hammering the CPU - var futex = std.atomic.Value(u32).init(0); - while (true) std.Thread.Futex.wait(&futex, 0); - - // This should be unreachable, recurse into recoverAbort. - @panic("event.wait() returned"); - } - - goTo(abort, .{}); - } - - noinline fn recoverAbort( - state: *volatile PanicState, - trace: ?*const std.builtin.StackTrace, - stack: StackContext, - msg: []const u8, - ) noreturn { - recover(state, trace, stack, msg); - - state.recover_stage = .silent_abort; - var stderr_fw = std.fs.File.stderr().writer(&.{}); - const stderr = &stderr_fw.interface; - stderr.writeAll("Aborting...\n") catch {}; - goTo(abort, .{}); - } - - noinline fn abort() noreturn { - std.process.abort(); - } - - inline fn goTo(comptime func: anytype, args: anytype) noreturn { - // TODO: Tailcall is broken right now, but eventually this should be used - // to avoid blowing up the stack. It's ok for now though, there are no - // cycles in the state machine so the max stack usage is bounded. - //@call(.always_tail, func, args); - @call(.auto, func, args); - } - - fn recover( - state: *volatile PanicState, - trace: ?*const std.builtin.StackTrace, - stack: StackContext, - msg: []const u8, - ) void { - switch (state.recover_verbosity) { - .message_and_stack => { - // lower the verbosity, and restore it at the end if we don't panic. - state.recover_verbosity = .message_only; - - var stderr_fw = std.fs.File.stderr().writer(&.{}); - const stderr = &stderr_fw.interface; - stderr.writeAll("\nPanicked during a panic: ") catch {}; - stderr.writeAll(msg) catch {}; - stderr.writeAll("\nInner panic stack:\n") catch {}; - if (trace) |t| { - debug.dumpStackTrace(t.*); - } - stack.dumpStackTrace(); - - state.recover_verbosity = .message_and_stack; - }, - .message_only => { - state.recover_verbosity = .silent; - - var stderr_fw = std.fs.File.stderr().writer(&.{}); - const stderr = &stderr_fw.interface; - stderr.writeAll("\nPanicked while dumping inner panic stack: ") catch {}; - stderr.writeAll(msg) catch {}; - stderr.writeByte('\n') catch {}; - - // If we succeed, restore all the way to dumping the stack. - state.recover_verbosity = .message_and_stack; - }, - .silent => {}, - } - } -}; +const build_options = @import("build_options"); diff --git a/src/main.zig b/src/main.zig index 1111031e87..89a552de0b 100644 --- a/src/main.zig +++ b/src/main.zig @@ -43,7 +43,6 @@ const thread_stack_size = 60 << 20; pub const std_options: std.Options = .{ .wasiCwd = wasi_cwd, .logFn = log, - .enable_segfault_handler = false, .log_level = switch (builtin.mode) { .Debug => .debug, @@ -53,6 +52,7 @@ pub const std_options: std.Options = .{ }; pub const panic = crash_report.panic; +pub const debug = crash_report.debug; var wasi_preopens: fs.wasi.Preopens = undefined; pub fn wasi_cwd() std.os.wasi.fd_t { @@ -165,8 +165,6 @@ var debug_allocator: std.heap.DebugAllocator(.{ }) = .init; pub fn main() anyerror!void { - crash_report.initialize(); - const gpa, const is_debug = gpa: { if (build_options.debug_gpa) break :gpa .{ debug_allocator.allocator(), true }; if (native_os == .wasi) break :gpa .{ std.heap.wasm_allocator, false }; @@ -192,6 +190,8 @@ pub fn main() anyerror!void { const args = try process.argsAlloc(arena); + if (args.len > 0) crash_report.zig_argv0 = args[0]; + if (tracy.enable_allocation) { var gpa_tracy = tracy.tracyAllocator(gpa); return mainArgs(gpa_tracy.allocator(), arena, args);