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}