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:
+68
-101
@@ -285,7 +285,7 @@ pub fn lockStderr(buffer: []u8) Io.LockedStderr {
|
|||||||
const prev = io.swapCancelProtection(.blocked);
|
const prev = io.swapCancelProtection(.blocked);
|
||||||
defer _ = io.swapCancelProtection(prev);
|
defer _ = io.swapCancelProtection(prev);
|
||||||
return io.lockStderr(buffer, null) catch |err| switch (err) {
|
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);
|
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.
|
/// 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.
|
/// 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
|
/// For backends that cannot handle the language features depended on by the
|
||||||
/// default panic handler, we will use a simpler implementation.
|
/// 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 => {},
|
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`.
|
// There is very similar logic to the following in `handleSegfault`.
|
||||||
switch (panic_stage) {
|
var discarding: Io.Writer.Discarding = .init(&.{});
|
||||||
0 => {
|
const current_recursive_panic_writer = recursive_panic_writer;
|
||||||
panic_stage = 1;
|
recursive_panic_writer = &discarding.writer;
|
||||||
_ = panicking.fetchAdd(1, .seq_cst);
|
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();
|
||||||
const stderr = lockStderr(&.{}).terminal();
|
const writer = stderr.writer;
|
||||||
defer unlockStderr();
|
recursive_panic_writer = writer;
|
||||||
const writer = stderr.writer;
|
|
||||||
|
|
||||||
if (builtin.single_threaded) {
|
if (enable_segfault_handler) {
|
||||||
writer.print("panic: ", .{}) catch break :trace;
|
// If a segfault happens while panicking, we want it to actually segfault, not trigger
|
||||||
} else {
|
// the handler.
|
||||||
const current_thread_id = std.Thread.getCurrentId();
|
resetSegfaultHandler();
|
||||||
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) {
|
if (@hasDecl(root, "debug") and @hasDecl(root.debug, "printCrashContext")) {
|
||||||
writer.writeAll("error return context:\n") catch break :trace;
|
root.debug.printCrashContext(stderr);
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
waitForOtherThreadToFinishPanicking();
|
if (builtin.single_threaded) {
|
||||||
},
|
writer.print("panic: ", .{}) catch break :trace;
|
||||||
1 => {
|
} else {
|
||||||
panic_stage = 2;
|
const current_thread_id = std.Thread.getCurrentId();
|
||||||
// A panic happened while trying to print a previous panic message.
|
writer.print("thread {d} panic: ", .{current_thread_id}) catch break :trace;
|
||||||
// We're still holding the mutex but that's fine as we're going to
|
}
|
||||||
// call abort().
|
writer.print("{s}\n", .{msg}) catch break :trace;
|
||||||
const stderr = lockStderr(&.{}).terminal();
|
|
||||||
stderr.writer.writeAll("aborting due to recursive panic\n") catch {};
|
if (@errorReturnTrace()) |t| if (t.index > 0) {
|
||||||
},
|
writer.writeAll("error return context:\n") catch break :trace;
|
||||||
else => {}, // Panicked while printing the recursive panic message.
|
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();
|
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 {
|
pub const StackUnwindOptions = struct {
|
||||||
/// If not `null`, we will ignore all frames up until this return address. This is typically
|
/// 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)
|
/// 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 {
|
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`.
|
// There is very similar logic to the following in `defaultPanic`.
|
||||||
switch (panic_stage) {
|
var discarding: Io.Writer.Discarding = .init(&.{});
|
||||||
0 => {
|
const current_recursive_panic_writer = recursive_panic_writer;
|
||||||
panic_stage = 1;
|
recursive_panic_writer = &discarding.writer;
|
||||||
_ = panicking.fetchAdd(1, .seq_cst);
|
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();
|
||||||
const stderr = lockStderr(&.{}).terminal();
|
const writer = stderr.writer;
|
||||||
defer unlockStderr();
|
recursive_panic_writer = writer;
|
||||||
|
|
||||||
if (addr) |a| {
|
if (@hasDecl(root, "debug") and @hasDecl(root.debug, "printCrashContext")) {
|
||||||
stderr.writer.print("{s} at address 0x{x}\n", .{ name, a }) catch break :trace;
|
root.debug.printCrashContext(stderr);
|
||||||
} else {
|
}
|
||||||
stderr.writer.print("{s} (no address available)\n", .{name}) catch break :trace;
|
|
||||||
}
|
if (addr) |a| {
|
||||||
if (opt_ctx) |context| {
|
writer.print("{s} at address 0x{x}\n", .{ name, a }) catch break :trace;
|
||||||
writeCurrentStackTrace(.{
|
} else {
|
||||||
.context = context,
|
writer.print("{s} (no address available)\n", .{name}) catch break :trace;
|
||||||
.allow_unsafe_unwind = true, // we're crashing anyway, give it our all!
|
}
|
||||||
}, stderr) catch break :trace;
|
if (opt_ctx) |context| {
|
||||||
}
|
writeCurrentStackTrace(.{
|
||||||
}
|
.context = context,
|
||||||
},
|
.allow_unsafe_unwind = true, // we're crashing anyway, give it our all!
|
||||||
1 => {
|
}, stderr) catch break :trace;
|
||||||
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
|
// 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
|
// again, the memory may be mapped and undefined behavior would occur rather than repeating
|
||||||
// the segfault. So we simply abort here.
|
// 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.
|
/// 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
|
/// Set by `main` as soon as arguments are known. The value here is a default in case we somehow
|
||||||
/// crash earlier than that.
|
/// crash earlier than that.
|
||||||
pub var zig_argv0: []const u8 = "zig";
|
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 {
|
pub const AnalyzeBody = struct {
|
||||||
parent: ?*AnalyzeBody,
|
parent: ?*AnalyzeBody,
|
||||||
sema: *Sema,
|
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 {
|
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,
|
/// 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.
|
/// 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");
|
try w.writeAll("Compiler crash context:\n");
|
||||||
|
|
||||||
if (CodegenFunc.current) |*cg| {
|
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,
|
.threaded => build_options.enable_debug_extensions,
|
||||||
.evented => false, // would use threadlocals in a way incompatible with evented
|
.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) struct {
|
||||||
pub const debug = if (crash_report_enabled) crash_report.debug else struct {};
|
pub fn printCrashContext(terminal: Io.Terminal) void {
|
||||||
|
crash_report.dumpCrashContext(terminal) catch {};
|
||||||
|
}
|
||||||
|
} else struct {};
|
||||||
|
|
||||||
var preopens: std.process.Preopens = .empty;
|
var preopens: std.process.Preopens = .empty;
|
||||||
pub fn wasi_cwd() Io.Dir {
|
pub fn wasi_cwd() Io.Dir {
|
||||||
|
|||||||
Reference in New Issue
Block a user