pub const MAX_FRAMES: usize = 64;
pub const HEADER_BUF_LEN: usize = 512;
pub const BACKTRACE_BUF_LEN: usize = 4096;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CrashFacts {
pub signo: i32,
pub si_code: i32,
pub si_addr: usize,
pub rip: usize,
pub pid: i32,
pub tid: i32,
pub base: usize,
}
impl CrashFacts {
pub const fn empty() -> Self {
Self {
signo: 0,
si_code: 0,
si_addr: 0,
rip: 0,
pid: 0,
tid: 0,
base: 0,
}
}
}
pub fn signal_name(signo: i32) -> &'static str {
match signo {
4 => "SIGILL",
6 => "SIGABRT",
7 => "SIGBUS",
8 => "SIGFPE",
11 => "SIGSEGV",
_ => "UNKNOWN",
}
}
pub fn si_code_name(signo: i32, si_code: i32) -> &'static str {
match signo {
7 => match si_code {
1 => "BUS_ADRALN",
2 => "BUS_ADRERR",
3 => "BUS_OBJERR",
4 => "BUS_MCEERR_AR",
5 => "BUS_MCEERR_AO",
_ => generic_si_code_name(si_code),
},
11 => match si_code {
1 => "SEGV_MAPERR",
2 => "SEGV_ACCERR",
3 => "SEGV_BNDERR",
4 => "SEGV_PKUERR",
5 => "SEGV_ACCADI",
6 => "SEGV_ADIDERR",
7 => "SEGV_ADIPERR",
8 => "SEGV_MTEAERR",
9 => "SEGV_MTESERR",
10 => "SEGV_CPERR",
_ => generic_si_code_name(si_code),
},
4 => match si_code {
1 => "ILL_ILLOPC",
2 => "ILL_ILLOPN",
3 => "ILL_ILLADR",
4 => "ILL_ILLTRP",
5 => "ILL_PRVOPC",
6 => "ILL_PRVREG",
7 => "ILL_COPROC",
8 => "ILL_BADSTK",
9 => "ILL_BADIADDR",
_ => generic_si_code_name(si_code),
},
8 => match si_code {
1 => "FPE_INTDIV",
2 => "FPE_INTOVF",
3 => "FPE_FLTDIV",
4 => "FPE_FLTOVF",
5 => "FPE_FLTUND",
6 => "FPE_FLTRES",
7 => "FPE_FLTINV",
8 => "FPE_FLTSUB",
9 => "FPE_DECOVF",
10 => "FPE_DECDIV",
11 => "FPE_DECERR",
12 => "FPE_INVASC",
13 => "FPE_INVDEC",
14 => "FPE_FLTUNK",
15 => "FPE_CONDTRAP",
_ => generic_si_code_name(si_code),
},
_ => generic_si_code_name(si_code),
}
}
fn generic_si_code_name(si_code: i32) -> &'static str {
match si_code {
0 => "SI_USER",
0x80 => "SI_KERNEL",
-1 => "SI_QUEUE",
-2 => "SI_TIMER",
-3 => "SI_MESGQ",
-4 => "SI_ASYNCIO",
-5 => "SI_SIGIO",
-6 => "SI_TKILL",
-7 => "SI_DETHREAD",
-60 => "SI_ASYNCNL",
_ => "UNKNOWN",
}
}
struct Cursor<'a> {
buf: &'a mut [u8],
len: usize,
}
impl Cursor<'_> {
fn new(buf: &mut [u8]) -> Cursor<'_> {
Cursor { buf, len: 0 }
}
fn push_bytes(&mut self, bytes: &[u8]) -> bool {
let room = self.buf.len() - self.len;
if bytes.len() > room {
self.buf[self.len..].copy_from_slice(&bytes[..room]);
self.len = self.buf.len();
return false;
}
self.buf[self.len..self.len + bytes.len()].copy_from_slice(bytes);
self.len += bytes.len();
true
}
fn push_str(&mut self, s: &str) -> bool {
self.push_bytes(s.as_bytes())
}
fn push_dec(&mut self, mut v: u64) -> bool {
let mut tmp = [0u8; 20];
let mut i = tmp.len();
if v == 0 {
i -= 1;
tmp[i] = b'0';
}
while v > 0 {
i -= 1;
tmp[i] = b'0' + (v % 10) as u8;
v /= 10;
}
self.push_bytes(&tmp[i..])
}
fn push_i64(&mut self, v: i64) -> bool {
if v < 0 {
if !self.push_str("-") {
return false;
}
self.push_dec(v.unsigned_abs())
} else {
self.push_dec(v as u64)
}
}
fn push_hex(&mut self, v: usize) -> bool {
if !self.push_str("0x") {
return false;
}
let mut tmp = [0u8; 16];
let mut i = tmp.len();
let mut v = v as u64;
if v == 0 {
i -= 1;
tmp[i] = b'0';
}
while v > 0 {
i -= 1;
tmp[i] = b"0123456789abcdef"[(v & 0xf) as usize];
v >>= 4;
}
self.push_bytes(&tmp[i..])
}
fn push_signo(&mut self, signo: i32) -> bool {
self.push_str("signo=")
&& self.push_dec(signo.unsigned_abs() as u64)
&& self.push_str("(")
&& self.push_str(signal_name(signo))
&& self.push_str(")")
}
fn push_si_code(&mut self, signo: i32, si_code: i32) -> bool {
self.push_str("si_code=")
&& self.push_i64(si_code as i64)
&& self.push_str("(")
&& self.push_str(si_code_name(signo, si_code))
&& self.push_str(")")
}
}
pub fn render_header(facts: &CrashFacts, buf: &mut [u8]) -> usize {
let mut c = Cursor::new(buf);
c.push_str("CRASH ");
c.push_signo(facts.signo);
c.push_str(" ");
c.push_si_code(facts.signo, facts.si_code);
c.push_str(" si_addr=");
c.push_hex(facts.si_addr);
c.push_str(" rip=");
c.push_hex(facts.rip);
c.push_str(" base=");
c.push_hex(facts.base);
c.push_str(" pid=");
c.push_dec(facts.pid.unsigned_abs() as u64);
c.push_str(" tid=");
c.push_dec(facts.tid.unsigned_abs() as u64);
c.push_str("\n");
c.len
}
pub fn render_backtrace(base: usize, offsets: &[usize], buf: &mut [u8]) -> usize {
let mut c = Cursor::new(buf);
for (i, &addr) in offsets.iter().enumerate().take(MAX_FRAMES) {
c.push_str("CRASH bt[");
c.push_dec(i as u64);
c.push_str("] rel=");
if addr >= base {
c.push_str("+");
c.push_hex(addr - base);
} else {
c.push_str("-");
c.push_hex(base - addr);
}
c.push_str(" abs=");
c.push_hex(addr);
c.push_str("\n");
}
c.len
}
pub use handler::install_crash_handler;
mod handler {
use super::{
BACKTRACE_BUF_LEN, CrashFacts, HEADER_BUF_LEN, MAX_FRAMES, render_backtrace, render_header,
};
use core::ffi::{c_int, c_void};
use std::ffi::CString;
use std::os::unix::ffi::OsStrExt;
use std::sync::atomic::{AtomicBool, AtomicPtr, AtomicUsize, Ordering};
const ALT_STACK_SIZE: usize = 64 * 1024;
const FAULT_SIGNALS: [c_int; 5] = [
libc::SIGSEGV,
libc::SIGBUS,
libc::SIGILL,
libc::SIGFPE,
libc::SIGABRT,
];
static IN_HANDLER: AtomicBool = AtomicBool::new(false);
static CRASH_LOG_PATH: AtomicPtr<libc::c_char> = AtomicPtr::new(core::ptr::null_mut());
static EXE_BASE: AtomicUsize = AtomicUsize::new(0);
#[repr(C)]
#[derive(Clone, Copy)]
struct SigFaultInfo {
si_signo: c_int,
si_errno: c_int,
si_code: c_int,
_abi_pad: c_int,
si_addr: *mut c_void,
}
const _: () = {
assert!(
core::mem::offset_of!(SigFaultInfo, si_signo)
== core::mem::offset_of!(libc::siginfo_t, si_signo)
);
assert!(
core::mem::offset_of!(SigFaultInfo, si_errno)
== core::mem::offset_of!(libc::siginfo_t, si_errno)
);
assert!(
core::mem::offset_of!(SigFaultInfo, si_code)
== core::mem::offset_of!(libc::siginfo_t, si_code)
);
assert!(core::mem::size_of::<libc::siginfo_t>() == 128);
assert!(core::mem::offset_of!(SigFaultInfo, si_addr) == 16);
};
unsafe fn write_all(fd: c_int, buf: &[u8]) {
let mut rest = buf;
while !rest.is_empty() {
let n = unsafe { libc::write(fd, rest.as_ptr().cast::<c_void>(), rest.len()) };
if n <= 0 {
return;
}
rest = &rest[n as usize..];
}
}
unsafe fn open_crash_log() -> Option<c_int> {
let path = CRASH_LOG_PATH.load(Ordering::Acquire);
if path.is_null() {
return None;
}
let fd = unsafe {
libc::open(
path,
libc::O_WRONLY | libc::O_CREAT | libc::O_APPEND,
0o600 as libc::mode_t,
)
};
if fd < 0 { None } else { Some(fd) }
}
unsafe fn facts_from(signo: c_int, info: *mut libc::siginfo_t, ctx: *mut c_void) -> CrashFacts {
let mut facts = CrashFacts::empty();
facts.signo = signo;
facts.pid = unsafe { libc::getpid() };
facts.tid = unsafe { libc::syscall(libc::SYS_gettid) } as i32;
facts.base = EXE_BASE.load(Ordering::Relaxed);
if !info.is_null() {
let si = info.cast::<SigFaultInfo>();
let si_code = unsafe { (*si).si_code };
facts.si_code = si_code;
if si_code > 0 {
facts.si_addr = unsafe { (*si).si_addr } as usize;
}
}
if !ctx.is_null() {
let uc = ctx.cast::<libc::ucontext_t>();
let rip = unsafe { (*uc).uc_mcontext.gregs[libc::REG_RIP as usize] };
if rip > 0 {
facts.rip = rip as usize;
}
}
facts
}
unsafe fn reraise(signo: c_int) -> ! {
let mut sa: libc::sigaction = unsafe { core::mem::zeroed() };
sa.sa_sigaction = libc::SIG_DFL;
sa.sa_flags = 0;
unsafe { libc::sigemptyset(&mut sa.sa_mask) };
unsafe { libc::sigaction(signo, &sa, core::ptr::null_mut()) };
let mut set: libc::sigset_t = unsafe { core::mem::zeroed() };
unsafe { libc::sigemptyset(&mut set) };
unsafe { libc::sigaddset(&mut set, signo) };
unsafe { libc::sigprocmask(libc::SIG_UNBLOCK, &set, core::ptr::null_mut()) };
unsafe { libc::raise(signo) };
unsafe { libc::_exit(128 + signo) }
}
unsafe extern "C" fn handle(signo: c_int, info: *mut libc::siginfo_t, ctx: *mut c_void) {
if IN_HANDLER.swap(true, Ordering::SeqCst) {
unsafe { reraise(signo) };
}
let facts = unsafe { facts_from(signo, info, ctx) };
let mut header = [0u8; HEADER_BUF_LEN];
let n = render_header(&facts, &mut header);
let header = &header[..n];
unsafe { write_all(libc::STDERR_FILENO, header) };
let fd = unsafe { open_crash_log() };
if let Some(fd) = fd {
unsafe { write_all(fd, header) };
}
let mut frames = [core::ptr::null_mut::<c_void>(); MAX_FRAMES];
let depth = unsafe { libc::backtrace(frames.as_mut_ptr(), MAX_FRAMES as c_int) };
if depth > 0 {
let depth = (depth as usize).min(MAX_FRAMES);
let mut offsets = [0usize; MAX_FRAMES];
for (slot, frame) in offsets.iter_mut().zip(frames.iter()).take(depth) {
*slot = *frame as usize;
}
let mut block = [0u8; BACKTRACE_BUF_LEN];
let m = render_backtrace(facts.base, &offsets[..depth], &mut block);
let block = &block[..m];
unsafe { write_all(libc::STDERR_FILENO, block) };
if let Some(fd) = fd {
unsafe { write_all(fd, block) };
}
}
if let Some(fd) = fd {
unsafe { libc::close(fd) };
}
unsafe { reraise(signo) };
}
fn exe_base() -> usize {
let Ok(exe) = std::fs::read_link("/proc/self/exe") else {
return 0;
};
let exe = exe.to_string_lossy();
let exe: &str = &exe;
let Ok(maps) = std::fs::read_to_string("/proc/self/maps") else {
return 0;
};
for line in maps.lines() {
let mut fields = line.split_whitespace();
let (Some(range), Some(_perms), Some(_offset), Some(_dev), Some(_inode), Some(path)) = (
fields.next(),
fields.next(),
fields.next(),
fields.next(),
fields.next(),
fields.next(),
) else {
continue;
};
if path != exe {
continue;
}
let Some((start, _end)) = range.split_once('-') else {
continue;
};
if let Ok(base) = usize::from_str_radix(start, 16) {
return base;
}
}
0
}
pub fn install_crash_handler() -> std::io::Result<()> {
let mut alt_stack = vec![0u8; ALT_STACK_SIZE].into_boxed_slice();
let ss = libc::stack_t {
ss_sp: alt_stack.as_mut_ptr().cast::<c_void>(),
ss_flags: 0,
ss_size: ALT_STACK_SIZE,
};
core::mem::forget(alt_stack);
if unsafe { libc::sigaltstack(&ss, core::ptr::null_mut()) } != 0 {
return Err(std::io::Error::last_os_error());
}
let log_dir = crate::logging::log_dir();
let _ = std::fs::create_dir_all(&log_dir);
let path = log_dir.join("crash.log");
let path = CString::new(path.as_os_str().as_bytes()).map_err(|_| {
std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"crash log path contains an interior NUL",
)
})?;
CRASH_LOG_PATH.store(path.into_raw(), Ordering::Release);
EXE_BASE.store(exe_base(), Ordering::Relaxed);
{
let mut warm = [core::ptr::null_mut::<c_void>(); 2];
unsafe { libc::backtrace(warm.as_mut_ptr(), warm.len() as c_int) };
}
let mut sa: libc::sigaction = unsafe { core::mem::zeroed() };
let entry: unsafe extern "C" fn(c_int, *mut libc::siginfo_t, *mut c_void) = handle;
sa.sa_sigaction = entry as usize;
sa.sa_flags = libc::SA_SIGINFO | libc::SA_ONSTACK;
unsafe { libc::sigemptyset(&mut sa.sa_mask) };
for signo in FAULT_SIGNALS {
if unsafe { libc::sigaction(signo, &sa, core::ptr::null_mut()) } != 0 {
return Err(std::io::Error::last_os_error());
}
}
Ok(())
}
}