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