crash_handler/
lib.rs

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