Backlog/engine/core/src/panickers.zig

182 lines
6.3 KiB
Zig

const std = @import("std");
const builtin = @import("builtin");
const os = std.os;
const windows = std.os.windows;
const core = @import("core.zig");
const lua = @import("lua");
fn handleSegfaultWindows(info: *windows.EXCEPTION_POINTERS) callconv(windows.WINAPI) c_long {
switch (info.ExceptionRecord.ExceptionCode) {
windows.EXCEPTION_DATATYPE_MISALIGNMENT => handleSegfaultWindowsExtra(info, 0, "Unaligned Memory Access"),
windows.EXCEPTION_ACCESS_VIOLATION => handleSegfaultWindowsExtra(info, 1, null),
windows.EXCEPTION_ILLEGAL_INSTRUCTION => handleSegfaultWindowsExtra(info, 2, null),
windows.EXCEPTION_STACK_OVERFLOW => handleSegfaultWindowsExtra(info, 0, "Stack Overflow"),
else => return windows.EXCEPTION_CONTINUE_SEARCH,
}
}
var panic_mutex = std.Thread.Mutex{};
var panicking = std.atomic.Value(u8).init(0);
threadlocal var panic_stage: usize = 0;
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 = std.atomic.Value(u32).init(0);
while (true) std.Thread.Futex.wait(&futex, 0);
unreachable;
}
}
fn handleSegfaultWindowsExtra(
info: *windows.EXCEPTION_POINTERS,
msg: u8,
label: ?[]const u8,
) noreturn {
const exception_address = @intFromPtr(info.ExceptionRecord.ExceptionAddress);
core.engine_logs("PANIC!!");
core.forceFlush();
if (!@hasDecl(windows, "CONTEXT")) {
switch (msg) {
0 => {
dumpSegfaultInfoWindows(info, msg, label);
std.posix.abort();
},
1 => {
const format_item = "Segmentation fault at address 0x{x}";
var buf: [format_item.len + 64]u8 = undefined; // 64 is arbitrary, but sufficiently large
const to_print = std.fmt.bufPrint(buf[0..buf.len], format_item, .{info.ExceptionRecord.ExceptionInformation[1]}) catch unreachable;
std.debug.panicImpl(null, exception_address, to_print);
},
2 => std.debug.panicImpl(null, exception_address, "Illegal Instruction"),
else => unreachable,
}
} else {
dumpSegfaultInfoWindows(info, msg, label);
std.posix.abort();
}
}
fn dumpSegfaultInfoWindows(info: *windows.EXCEPTION_POINTERS, msg: u8, label: ?[]const u8) void {
const stderr = std.io.getStdErr().writer();
_ = switch (msg) {
0 => stderr.print("{s}\n", .{label.?}),
1 => stderr.print("Segmentation fault at address 0x{x}\n", .{info.ExceptionRecord.ExceptionInformation[1]}),
2 => stderr.print("Illegal instruction at address 0x{x}\n", .{info.ContextRecord.getRegs().ip}),
else => unreachable,
} catch std.posix.abort();
std.debug.dumpStackTraceFromBase(info.ContextRecord);
}
pub fn dumpStackPointerAddr(prefix: []const u8) void {
const sp = asm (""
: [argc] "={rsp}" (-> usize),
);
std.debug.print("{} sp = 0x{x}\n", .{ prefix, sp });
}
fn dumpSegfaultInfoPosix(sig: i32, addr: usize, ctx_ptr: ?*anyopaque) void {
const stderr = std.io.getStdErr().writer();
_ = switch (sig) {
std.posix.SIG.SEGV => stderr.print("Segmentation fault at address 0x{x}\n", .{addr}),
std.posix.SIG.ILL => stderr.print("Illegal instruction at address 0x{x}\n", .{addr}),
std.posix.SIG.BUS => stderr.print("Bus error at address 0x{x}\n", .{addr}),
std.posix.SIG.FPE => stderr.print("Arithmetic exception at address 0x{x}\n", .{addr}),
else => unreachable,
} catch std.posix.abort();
switch (builtin.cpu.arch) {
.x86,
.x86_64,
.arm,
.aarch64,
=> {
const ctx: *std.c.ucontext_t = @ptrCast(@alignCast(ctx_ptr));
std.debug.dumpStackTraceFromBase(ctx);
},
else => {},
}
}
fn handleSegfaultPosix(sig: i32, info: *const std.posix.siginfo_t, ctx_ptr: ?*anyopaque) callconv(.C) noreturn {
core.engine_logs("PANIC!!");
core.forceFlush();
resetSegfaultHandler();
const addr = switch (builtin.os.tag) {
.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 => @intFromPtr(info.reason.fault.addr),
else => unreachable,
};
nosuspend switch (panic_stage) {
0 => {
panic_stage = 1;
_ = panicking.fetchAdd(1, .seq_cst);
{
panic_mutex.lock();
defer panic_mutex.unlock();
dumpSegfaultInfoPosix(sig, addr, ctx_ptr);
}
waitForOtherThreadToFinishPanicking();
},
else => {
// panic mutex already locked
dumpSegfaultInfoPosix(sig, addr, ctx_ptr);
},
};
// 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.
std.posix.abort();
}
//
//
fn resetSegfaultHandler() void {
if (builtin.os.tag == .windows) {
if (windows_segfault_handle) |handle| {
_ = windows.kernel32.RemoveVectoredExceptionHandler(handle);
windows_segfault_handle = null;
}
return;
}
var act = std.c.Sigaction{
.handler = .{ .handler = std.posix.SIG.DFL },
.mask = std.posix.empty_sigset,
.flags = 0,
};
// To avoid a double-panic, do nothing if an error happens here.
std.debug.updateSegfaultHandler(&act);
}
// my custom version of segfault handler.
var windows_segfault_handle: ?windows.HANDLE = null;
pub fn attachSegfaultHandler() void {
if (builtin.os.tag == .windows) {
windows_segfault_handle = windows.kernel32.AddVectoredExceptionHandler(0, handleSegfaultWindows);
return;
}
var act = std.c.Sigaction{
.handler = .{ .sigaction = handleSegfaultPosix },
.mask = std.posix.empty_sigset,
.flags = (std.posix.SA.SIGINFO | std.posix.SA.RESTART | std.posix.SA.RESETHAND),
};
std.debug.updateSegfaultHandler(&act);
}