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}