mirror of
https://github.com/ziglang/zig.git
synced 2025-12-06 06:13:07 +00:00
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.
This commit is contained in:
parent
3a561da38d
commit
d9661e9e05
@ -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| {
|
||||
|
||||
@ -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: {
|
||||
|
||||
@ -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");
|
||||
|
||||
@ -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);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user