mirror of
https://codeberg.org/ziglang/zig.git
synced 2026-04-26 13:01:34 +03:00
std.debug: rewrite panic
This commit is contained in:
+33
-66
@@ -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,26 +529,31 @@ pub fn defaultPanic(msg: []const u8, first_trace_addr: ?usize) noreturn {
|
||||
else => {},
|
||||
}
|
||||
|
||||
// There is very similar logic to the following in `handleSegfault`.
|
||||
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);
|
||||
|
||||
const stderr = lockStderr(&.{}).terminal();
|
||||
const writer = stderr.writer;
|
||||
recursive_panic_writer = writer;
|
||||
|
||||
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);
|
||||
|
||||
trace: {
|
||||
const stderr = lockStderr(&.{}).terminal();
|
||||
defer unlockStderr();
|
||||
const writer = stderr.writer;
|
||||
if (@hasDecl(root, "debug") and @hasDecl(root.debug, "printCrashContext")) {
|
||||
root.debug.printCrashContext(stderr);
|
||||
}
|
||||
|
||||
if (builtin.single_threaded) {
|
||||
writer.print("panic: ", .{}) catch break :trace;
|
||||
@@ -572,37 +573,9 @@ pub fn defaultPanic(msg: []const u8, first_trace_addr: ?usize) noreturn {
|
||||
.allow_unsafe_unwind = true, // we're crashing anyway, give it our all!
|
||||
}, stderr) catch break :trace;
|
||||
}
|
||||
|
||||
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.
|
||||
}
|
||||
|
||||
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,24 +1508,30 @@ fn handleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?CpuContextPtr) noret
|
||||
}
|
||||
|
||||
pub fn defaultHandleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?CpuContextPtr) noreturn {
|
||||
// There is very similar logic to the following in `defaultPanic`.
|
||||
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);
|
||||
|
||||
// There is very similar logic to the following in `defaultPanic`.
|
||||
switch (panic_stage) {
|
||||
0 => {
|
||||
panic_stage = 1;
|
||||
_ = panicking.fetchAdd(1, .seq_cst);
|
||||
|
||||
trace: {
|
||||
const stderr = lockStderr(&.{}).terminal();
|
||||
defer unlockStderr();
|
||||
const writer = stderr.writer;
|
||||
recursive_panic_writer = writer;
|
||||
|
||||
if (@hasDecl(root, "debug") and @hasDecl(root.debug, "printCrashContext")) {
|
||||
root.debug.printCrashContext(stderr);
|
||||
}
|
||||
|
||||
if (addr) |a| {
|
||||
stderr.writer.print("{s} at address 0x{x}\n", .{ name, a }) catch break :trace;
|
||||
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;
|
||||
writer.print("{s} (no address available)\n", .{name}) catch break :trace;
|
||||
}
|
||||
if (opt_ctx) |context| {
|
||||
writeCurrentStackTrace(.{
|
||||
@@ -1561,18 +1540,6 @@ pub fn defaultHandleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?CpuContex
|
||||
}, 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.
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
||||
+3
-34
@@ -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| {
|
||||
|
||||
+5
-2
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user