crash_handler/lib.rs
1#![doc = include_str!("../README.md")]
2#![cfg_attr(sanitizer_compat, feature(linkage))]
3#![allow(unsafe_code)]
4
5mod error;
6
7pub use error::Error;
8
9#[cfg(feature = "debug-print")]
10#[macro_export]
11macro_rules! debug_print {
12 ($s:literal) => {
13 let cstr = concat!(file!(), ":", line!(), " ", $s, "\n");
14 $crate::write_stderr(cstr);
15 };
16}
17
18#[cfg(not(feature = "debug-print"))]
19#[macro_export]
20macro_rules! debug_print {
21 ($s:literal) => {};
22}
23
24/// Writes the specified string directly to stderr.
25///
26/// This is safe to be called from within a compromised context.
27#[inline]
28pub fn write_stderr(s: &'static str) {
29 unsafe {
30 #[cfg(target_os = "windows")]
31 libc::write(2, s.as_ptr().cast(), s.len() as u32);
32
33 #[cfg(not(target_os = "windows"))]
34 libc::write(2, s.as_ptr().cast(), s.len());
35 }
36}
37
38cfg_if::cfg_if! {
39 if #[cfg(all(unix, not(target_os = "macos")))] {
40 /// The sole purpose of the unix module is to hook `pthread_create` to ensure
41 /// an alternate stack is installed for every native thread in case of a
42 /// stack overflow. This doesn't apply to `MacOS` as it uses exception ports,
43 /// which are always delivered to a specific thread owned by the exception
44 /// handler
45 pub mod unix;
46 }
47}
48
49pub use crash_context::CrashContext;
50
51/// The result of the user code executed during a crash event
52pub enum CrashEventResult {
53 /// The event was handled in some way
54 Handled(bool),
55 #[cfg(any(
56 target_os = "linux",
57 target_os = "android",
58 all(target_os = "windows", target_arch = "x86_64"),
59 ))]
60 /// The handler wishes to jump somewhere else, presumably to return
61 /// execution and skip the code that caused the exception
62 Jump {
63 /// The location to jump back to, retrieved via sig/setjmp
64 jmp_buf: *mut jmp::JmpBuf,
65 /// The value that will be returned from the sig/setjmp call that we
66 /// jump to. Note that if the value is 0 it will be corrected to 1
67 value: i32,
68 },
69}
70
71impl From<bool> for CrashEventResult {
72 fn from(b: bool) -> Self {
73 Self::Handled(b)
74 }
75}
76
77/// User implemented trait for handling a crash event that has ocurred.
78///
79/// # Safety
80///
81/// This trait is marked unsafe as care needs to be taken when implementing it
82/// due to the [`Self::on_crash`] method being run in a compromised context. In
83/// general, it is advised to do as _little_ as possible when handling a
84/// crash, with more complicated or dangerous (in a compromised context) code
85/// being intialized before the [`CrashHandler`] is installed, or hoisted out to
86/// another process entirely.
87///
88/// ## Linux
89///
90/// Notably, only a small subset of libc functions are
91/// [async signal safe](https://man7.org/linux/man-pages/man7/signal-safety.7.html)
92/// and calling non-safe ones can have undefined behavior, including such common
93/// ones as `malloc` (especially if using a multi-threaded allocator).
94///
95/// ## Windows
96///
97/// Windows [structured exceptions](https://docs.microsoft.com/en-us/windows/win32/debug/structured-exception-handling)
98/// don't have the a notion similar to signal safety, but it is again recommended
99/// to do as little work as possible in response to an exception.
100///
101/// ## Macos
102///
103/// Mac uses exception ports (sorry, can't give a good link here since Apple
104/// documentation is terrible) which are handled by a thread owned by the
105/// exception handler which makes them slightly safer to handle than UNIX signals,
106/// but it is again recommended to do as little work as possible.
107pub unsafe trait CrashEvent: Send + Sync {
108 /// Method invoked when a crash occurs.
109 ///
110 /// Returning true indicates your handler has processed the crash and that
111 /// no further handlers should run.
112 fn on_crash(&self, context: &CrashContext) -> CrashEventResult;
113}
114
115/// Creates a [`CrashEvent`] using the supplied closure as the implementation.
116///
117/// The supplied closure will be called for both real crash events as well as
118/// those simulated by calling `simulate_signal/exception`, which is why it is
119/// not `FnOnce`
120///
121/// # Safety
122///
123/// See the [`CrashEvent`] Safety section for information on why this is `unsafe`.
124#[inline]
125pub unsafe fn make_crash_event<F>(closure: F) -> Box<dyn CrashEvent>
126where
127 F: Send + Sync + Fn(&CrashContext) -> CrashEventResult + 'static,
128{
129 struct Wrapper<F> {
130 inner: F,
131 }
132
133 unsafe impl<F> CrashEvent for Wrapper<F>
134 where
135 F: Send + Sync + Fn(&CrashContext) -> CrashEventResult,
136 {
137 fn on_crash(&self, context: &CrashContext) -> CrashEventResult {
138 (self.inner)(context)
139 }
140 }
141
142 Box::new(Wrapper { inner: closure })
143}
144
145/// Creates a [`CrashEvent`] using the supplied closure as the implementation.
146///
147/// This uses an `FnOnce` closure instead of `Fn` like `[make_crash_event]`, but
148/// means this closure can only be used for the first crash, and cannot be used
149/// in a situation where user-triggered crashes via the `simulate_signal/exception`
150/// methods are used.
151///
152/// # Safety
153///
154/// See the [`CrashEvent`] Safety section for information on why this is `unsafe`.
155#[inline]
156pub unsafe fn make_single_crash_event<F>(closure: F) -> Box<dyn CrashEvent>
157where
158 F: Send + Sync + FnOnce(&CrashContext) -> CrashEventResult + 'static,
159{
160 struct Wrapper<F> {
161 // technically mutexes are not async signal safe on linux, but this is
162 // an internal-only detail that will be safe _unless_ the callback invoked
163 // by the user also crashes, but if that occurs...that's on them
164 inner: parking_lot::Mutex<Option<F>>,
165 }
166
167 unsafe impl<F> CrashEvent for Wrapper<F>
168 where
169 F: Send + Sync + FnOnce(&CrashContext) -> CrashEventResult,
170 {
171 fn on_crash(&self, context: &CrashContext) -> CrashEventResult {
172 if let Some(inner) = self.inner.lock().take() {
173 (inner)(context)
174 } else {
175 false.into()
176 }
177 }
178 }
179
180 Box::new(Wrapper {
181 inner: parking_lot::Mutex::new(Some(closure)),
182 })
183}
184
185cfg_if::cfg_if! {
186 if #[cfg(any(target_os = "linux", target_os = "android"))] {
187 mod linux;
188
189 pub use linux::{CrashHandler, Signal, jmp};
190 } else if #[cfg(target_os = "windows")] {
191 mod windows;
192
193 #[cfg(target_arch = "x86_64")]
194 pub use windows::jmp;
195
196 pub use windows::{CrashHandler, ExceptionCode};
197 } else if #[cfg(target_os = "macos")] {
198 mod mac;
199
200 pub use mac::{CrashHandler, ExceptionType};
201 }
202}