Skip to main content

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}