breakpad_handler/
lib.rs

1mod error;
2pub use error::Error;
3
4use std::sync::atomic;
5
6/// Trait used by the crash handler to notify the implementor that a crash was
7/// captured, providing the full path on disk to that minidump.
8pub trait CrashEvent: Sync + Send {
9    fn on_crash(&self, minidump_path: std::path::PathBuf);
10}
11
12impl<F> CrashEvent for F
13where
14    F: Fn(std::path::PathBuf) + Send + Sync,
15{
16    fn on_crash(&self, minidump_path: std::path::PathBuf) {
17        self(minidump_path);
18    }
19}
20
21static HANDLER_ATTACHED: atomic::AtomicBool = atomic::AtomicBool::new(false);
22
23/// Determines which handlers are installed to catch errors. These options are
24/// only used when targetting MacOS/iOS, all other platforms use the only
25/// error handler they support
26pub enum InstallOptions {
27    /// No handlers are registered. This means you won't actually catch any
28    /// errors at all.
29    NoHandlers,
30    /// Registers the exception handler. On Mac, this means that traditional
31    /// Unix signals will **NOT** be sent, which can interfere with normal
32    /// operations of your program if it is indeed trying to hook into signal
33    /// handlers, eg wasmtime.
34    ExceptionHandler,
35    /// Registers the signal handler. If the exception handler is not installed
36    /// this means that exceptions will be turned into normal Unix signals
37    /// instead, which allows other signal handlers to interoperate with the
38    /// Breakpad signal handler by just installing themselves **AFTER**
39    /// the Breakpad signal handler is installed and restoring it when they are
40    /// finished with their signal handling, allowing Breakpad to continue to
41    /// catch crash signals when other application signal handlers are not active
42    SignalHandler,
43    /// Installs both the ExceptionHandler and SignalHandler, but this has all
44    /// of the caveats of the ExceptionHandler.
45    BothHandlers,
46}
47
48pub struct BreakpadHandler {
49    handler: *mut breakpad_sys::ExceptionHandler,
50    on_crash: *mut std::ffi::c_void,
51}
52
53#[allow(unsafe_code)]
54unsafe impl Send for BreakpadHandler {}
55#[allow(unsafe_code)]
56unsafe impl Sync for BreakpadHandler {}
57
58impl BreakpadHandler {
59    /// Sets up a breakpad handler to catch exceptions/signals, writing out
60    /// a minidump to the designated directory if a crash occurs. Only one
61    /// handler can be attached at a time
62    pub fn attach<P: AsRef<std::path::Path>>(
63        crash_dir: P,
64        install_opts: InstallOptions,
65        on_crash: Box<dyn CrashEvent>,
66    ) -> Result<Self, Error> {
67        match HANDLER_ATTACHED.compare_exchange(
68            false,
69            true,
70            atomic::Ordering::Relaxed,
71            atomic::Ordering::Relaxed,
72        ) {
73            Ok(true) | Err(true) => return Err(Error::HandlerAlreadyRegistered),
74            _ => {}
75        }
76
77        let on_crash = Box::into_raw(Box::new(on_crash)).cast();
78
79        #[allow(unsafe_code)]
80        // SAFETY: Calling into C code :shrug:
81        unsafe {
82            let os_str = crash_dir.as_ref().as_os_str();
83
84            let path: Vec<breakpad_sys::PathChar> = {
85                #[cfg(windows)]
86                {
87                    use std::os::windows::ffi::OsStrExt;
88                    os_str.encode_wide().collect()
89                }
90                #[cfg(unix)]
91                {
92                    use std::os::unix::ffi::OsStrExt;
93                    Vec::from(os_str.as_bytes())
94                }
95            };
96
97            extern "C" fn crash_callback(
98                path: *const breakpad_sys::PathChar,
99                path_len: usize,
100                ctx: *mut std::ffi::c_void,
101            ) {
102                let path_slice = unsafe { std::slice::from_raw_parts(path, path_len) };
103
104                let path = {
105                    #[cfg(windows)]
106                    {
107                        use std::os::windows::ffi::OsStringExt;
108                        std::path::PathBuf::from(std::ffi::OsString::from_wide(path_slice))
109                    }
110                    #[cfg(unix)]
111                    {
112                        use std::os::unix::ffi::OsStrExt;
113                        std::path::PathBuf::from(std::ffi::OsStr::from_bytes(path_slice).to_owned())
114                    }
115                };
116
117                let context: Box<Box<dyn CrashEvent>> = unsafe { Box::from_raw(ctx.cast()) };
118                context.on_crash(path);
119                Box::leak(context);
120            }
121
122            let install_opts = match install_opts {
123                InstallOptions::NoHandlers => breakpad_sys::INSTALL_NO_HANDLER,
124                InstallOptions::ExceptionHandler => breakpad_sys::INSTALL_EXCEPTION_HANDLER,
125                InstallOptions::SignalHandler => breakpad_sys::INSTALL_SIGNAL_HANDLER,
126                InstallOptions::BothHandlers => breakpad_sys::INSTALL_BOTH_HANDLERS,
127            };
128
129            let handler = breakpad_sys::attach_exception_handler(
130                path.as_ptr(),
131                path.len(),
132                crash_callback,
133                on_crash,
134                install_opts,
135            );
136
137            Ok(Self { handler, on_crash })
138        }
139    }
140}
141
142impl Drop for BreakpadHandler {
143    fn drop(&mut self) {
144        #[allow(unsafe_code)]
145        // SAFETY: Calling into C code
146        unsafe {
147            breakpad_sys::detach_exception_handler(self.handler);
148            let _: Box<Box<dyn CrashEvent>> = Box::from_raw(self.on_crash.cast());
149            HANDLER_ATTACHED.swap(false, atomic::Ordering::Relaxed);
150        }
151    }
152}