Skip to main content

rt/
crash.rs

1// crash -- crash reports: the hook an app installs, the record the platform fills  [docs/modules/CRASH.md]
2//
3//   set_hook(Some(f))      f(&CrashInfo) runs once when the process crashes or panics, after
4//                          the stderr line and before the process ends with exit code 3.  A
5//                          crash inside it (or a second crashing thread) ends the process at
6//                          once; a stack overflow skips it.
7//   set_pre_hook(Some(f))  the library's part (plat::log: the newest log lines), before the
8//                          app's hook; the same rules
9//   report(&info) -> !     the end of every crash (the platform's fault handlers, the panic
10//                          handler): the pre-hook, the hook, then exit(3)
11//   StackText<N>           a fixed buffer for core::fmt -- crash paths do not allocate (the
12//                          allocator lock may be held by the crashing thread)
13//
14// Rust adds one kind, CK_PANIC: the panic message is in CrashInfo::msg.
15
16use core::fmt;
17use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
18
19pub const CK_ACCESS: u32 = 1; // access violation / SIGSEGV
20pub const CK_ILLEGAL: u32 = 2; // illegal or privileged instruction
21pub const CK_DIVIDE: u32 = 3; // integer division by zero / overflow, SIGFPE
22pub const CK_BUS: u32 = 4; // SIGBUS, misalignment, in-page error
23pub const CK_STACK: u32 = 5; // stack overflow (no hook: there is no stack left)
24pub const CK_OTHER: u32 = 6;
25pub const CK_PANIC: u32 = 7; // a Rust panic (msg)
26pub const FRAMES_MAX: usize = 32;
27
28/// what the crash handler knows (the CRASHINFO record of ASMLIB)
29pub struct CrashInfo<'a> {
30    pub code: u32,   // Windows exception code / Linux signal number (panic: 0)
31    pub kind: u32,   // CK_*
32    pub access: u32, // access violations: 0 read, 1 write, 8 execute, 2 unknown
33    pub addr: u64,   // data address of an access violation / bus error (0 = none)
34    pub ip: u64,     // instruction pointer
35    pub base: u64,   // image base: offset = address - base (Linux 0: the .map lists addresses)
36    pub code0: u64,  // the executable code: code0..code1
37    pub code1: u64,
38    pub regs: [u64; 17], // rax rbx rcx rdx rsi rdi rbp rsp r8 .. r15 rip
39    pub frames: [u64; FRAMES_MAX], // the call chain: return addresses, innermost first (plat)
40    pub nframes: u32,
41    pub msg: &'a str, // CK_PANIC: the message
42}
43
44impl CrashInfo<'_> {
45    pub const fn empty() -> Self {
46        CrashInfo {
47            code: 0,
48            kind: CK_OTHER,
49            access: 2,
50            addr: 0,
51            ip: 0,
52            base: 0,
53            code0: 0,
54            code1: 0,
55            regs: [0; 17],
56            frames: [0; FRAMES_MAX],
57            nframes: 0,
58            msg: "",
59        }
60    }
61}
62
63pub type CrashHook = fn(&CrashInfo);
64
65static HOOK: AtomicUsize = AtomicUsize::new(0);
66static PRE_HOOK: AtomicUsize = AtomicUsize::new(0);
67static BUSY: AtomicBool = AtomicBool::new(false);
68
69/// install (Some) or remove (None) the crash hook  [plat_crash_hook]
70pub fn set_hook(f: Option<CrashHook>) {
71    HOOK.store(f.map_or(0, |f| f as usize), Ordering::SeqCst);
72}
73
74/// install (Some) or remove (None) the library's hook that runs before the app's
75pub fn set_pre_hook(f: Option<CrashHook>) {
76    PRE_HOOK.store(f.map_or(0, |f| f as usize), Ordering::SeqCst);
77}
78
79/// a crash is being reported (a second one ends the process at once)
80pub fn busy() -> bool {
81    BUSY.load(Ordering::SeqCst)
82}
83
84/// run the hook once, then exit(3)
85pub fn report(info: &CrashInfo) -> ! {
86    if BUSY.swap(true, Ordering::SeqCst) {
87        crate::os::exit(3); // a crash in the hook / on a second thread
88    }
89    for h in [PRE_HOOK.load(Ordering::SeqCst), HOOK.load(Ordering::SeqCst)] {
90        if h != 0 && info.kind != CK_STACK {
91            // SAFETY: h was stored from a CrashHook by set_hook / set_pre_hook.
92            let f: CrashHook = unsafe { core::mem::transmute::<usize, CrashHook>(h) };
93            f(info);
94        }
95    }
96    crate::os::exit(3)
97}
98
99/// formatting into a fixed buffer (overflow is cut off)
100pub struct StackText<const N: usize> {
101    buf: [u8; N],
102    len: usize,
103}
104
105impl<const N: usize> Default for StackText<N> {
106    fn default() -> Self {
107        Self::new()
108    }
109}
110
111impl<const N: usize> StackText<N> {
112    pub const fn new() -> Self {
113        StackText { buf: [0; N], len: 0 }
114    }
115    pub fn bytes(&self) -> &[u8] {
116        &self.buf[..self.len]
117    }
118    pub fn as_str(&self) -> &str {
119        // only whole str pieces are appended (cut at a char boundary below)
120        core::str::from_utf8(self.bytes()).unwrap_or("")
121    }
122}
123
124impl<const N: usize> fmt::Write for StackText<N> {
125    fn write_str(&mut self, s: &str) -> fmt::Result {
126        let mut n = s.len().min(N - self.len);
127        while n < s.len() && !s.is_char_boundary(n) {
128            n -= 1;
129        }
130        self.buf[self.len..self.len + n].copy_from_slice(&s.as_bytes()[..n]);
131        self.len += n;
132        Ok(())
133    }
134}