#![allow(non_camel_case_types, clippy::exit)]
use super::ExceptionCode;
use crate::Error;
use crash_context::ffi;
type LPTOP_LEVEL_EXCEPTION_FILTER =
Option<unsafe extern "system" fn(exceptioninfo: *const ffi::EXCEPTION_POINTERS) -> i32>;
extern "system" {
fn GetCurrentThreadId() -> u32;
fn SetUnhandledExceptionFilter(
filter: LPTOP_LEVEL_EXCEPTION_FILTER,
) -> LPTOP_LEVEL_EXCEPTION_FILTER;
}
extern "C" {
/// MSVCRT has its own error handling function for invalid parameters to crt functions
/// (eg printf) which instead of returning error codes from the function itself,
/// like one would want, call a handler if specified, or, worse, throw up a dialog
/// if in a GUI!
///
/// [Invalid Parameter Handler](https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/set-invalid-parameter-handler-set-thread-local-invalid-parameter-handler?view=msvc-170)
fn _set_invalid_parameter_handler(
new_handler: Option<_invalid_parameter_handler>,
) -> Option<_invalid_parameter_handler>;
/// It also has a separate error handling function when calling pure virtuals
/// because why not?
///
/// [Purecall Handler](https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/get-purecall-handler-set-purecall-handler?view=msvc-170)
fn _set_purecall_handler(new_handler: Option<_purecall_handler>) -> Option<_purecall_handler>;
// This is only available in the debug CRT
// fn _invalid_parameter(
// expression: *const u16,
// function: *const u16,
// file: *const u16,
// line: u32,
// reserved: usize,
// );
fn _invalid_parameter_noinfo_noreturn() -> !;
fn _invoke_watson() -> !;
}
type _invalid_parameter_handler = unsafe extern "C" fn(
expression: *const u16,
function: *const u16,
file: *const u16,
line: u32,
_reserved: usize,
);
type _purecall_handler = unsafe extern "C" fn();
pub(super) static HANDLER: parking_lot::Mutex<Option<HandlerInner>> =
parking_lot::const_mutex(None);
pub(super) struct HandlerInner {
pub(super) user_handler: Box<dyn crate::CrashEvent>,
/// The previously installed filter before this handler installed its own
previous_filter: LPTOP_LEVEL_EXCEPTION_FILTER,
/// The previously installed invalid parameter handler
previous_iph: Option<_invalid_parameter_handler>,
/// The previously installed purecall handler
previous_pch: Option<_purecall_handler>,
/// The previously installed SIGABRT handler
previous_abort_handler: Option<libc::sighandler_t>,
}
impl HandlerInner {
pub(crate) fn new(user_handler: Box<dyn crate::CrashEvent>) -> Self {
// Note that breakpad has flags so the user can choose which error handlers
// to install, but for now we just install all of them
// SAFETY: syscalls
unsafe {
let previous_filter = SetUnhandledExceptionFilter(Some(handle_exception));
let previous_iph = _set_invalid_parameter_handler(Some(handle_invalid_parameter));
let previous_pch = _set_purecall_handler(Some(handle_pure_virtual_call));
let previous_abort_handler = super::signal::install_abort_handler().ok();
Self {
user_handler,
previous_filter,
previous_iph,
previous_pch,
previous_abort_handler,
}
}
}
/// Sets the handlers to the previous handlers that were registered when the
/// specified handler was attached
pub(crate) fn restore_previous_handlers(&self) {
// SAFETY: syscalls
unsafe {
if let Some(ah) = self.previous_abort_handler {
super::signal::restore_abort_handler(ah);
}
SetUnhandledExceptionFilter(self.previous_filter);
_set_invalid_parameter_handler(self.previous_iph);
_set_purecall_handler(self.previous_pch);
}
}
}
impl Drop for HandlerInner {
fn drop(&mut self) {
self.restore_previous_handlers();
}
}
pub(super) fn attach(on_crash: Box<dyn crate::CrashEvent>) -> Result<(), Error> {
let mut lock = HANDLER.lock();
if lock.is_some() {
return Err(Error::HandlerAlreadyInstalled);
}
*lock = Some(HandlerInner::new(on_crash));
Ok(())
}
pub(super) fn detach() {
let mut lock = HANDLER.lock();
// The previous handlers are restored on drop
lock.take();
}
pub(super) unsafe fn simulate_exception(exception_code: Option<u32>) -> crate::CrashEventResult {
let lock = HANDLER.lock();
if let Some(handler) = &*lock {
let mut exception_record: ffi::EXCEPTION_RECORD = std::mem::zeroed();
let mut exception_context = std::mem::MaybeUninit::zeroed();
ffi::capture_context(exception_context.as_mut_ptr());
let mut exception_context = exception_context.assume_init();
let exception_ptrs = ffi::EXCEPTION_POINTERS {
ExceptionRecord: &mut exception_record,
ContextRecord: &mut exception_context,
};
// https://github.com/chromium/crashpad/blob/fca8871ca3fb721d3afab370ca790122f9333bfd/util/win/exception_codes.h#L32
let exception_code = exception_code.unwrap_or(ExceptionCode::User as u32);
exception_record.ExceptionCode = exception_code;
let cc = crash_context::CrashContext {
exception_pointers: (&exception_ptrs as *const ffi::EXCEPTION_POINTERS).cast(),
process_id: std::process::id(),
thread_id: GetCurrentThreadId(),
exception_code,
};
handler.user_handler.on_crash(&cc)
} else {
crate::CrashEventResult::Handled(false)
}
}
/// While handling any exceptions, especially when calling user code, we restore
/// and previously registered handlers
/// Note this keeps the `HANDLER` lock for the duration of the scope
struct AutoHandler<'scope> {
lock: parking_lot::MutexGuard<'scope, Option<HandlerInner>>,
}
impl<'scope> AutoHandler<'scope> {
fn new(lock: parking_lot::MutexGuard<'scope, Option<HandlerInner>>) -> Option<Self> {
if let Some(hi) = &*lock {
// In case another exception occurs while this handler is doing its thing,
// it should be delivered to the previous filter.
hi.restore_previous_handlers();
}
if lock.is_some() {
Some(Self { lock })
} else {
None
}
}
}
/// Sets the handlers back to our internal ones
fn set_handlers() {
unsafe {
SetUnhandledExceptionFilter(Some(handle_exception));
_set_invalid_parameter_handler(Some(handle_invalid_parameter));
_set_purecall_handler(Some(handle_pure_virtual_call));
}
}
impl<'scope> std::ops::Deref for AutoHandler<'scope> {
type Target = HandlerInner;
fn deref(&self) -> &Self::Target {
self.lock.as_ref().unwrap()
}
}
impl<'scope> Drop for AutoHandler<'scope> {
fn drop(&mut self) {
// Restore our handlers
set_handlers();
}
}
/// The handler is not entered, and the OS continues searching for an exception handler.
const EXCEPTION_CONTINUE_SEARCH: i32 = 0;
/// Enter the exception handler.
pub(super) const EXCEPTION_EXECUTE_HANDLER: i32 = 1;
use crate::CrashEventResult;
/// Called on the exception thread when an unhandled exception occurs.
/// Signals the exception handler thread to handle the exception.
pub(super) unsafe extern "system" fn handle_exception(
except_info: *const ffi::EXCEPTION_POINTERS,
) -> i32 {
let _jump = {
let lock = HANDLER.lock();
if let Some(current_handler) = AutoHandler::new(lock) {
let code = (*(*except_info).ExceptionRecord).ExceptionCode;
match current_handler.user_handler.on_crash(&crate::CrashContext {
exception_pointers: except_info.cast(),
process_id: std::process::id(),
thread_id: GetCurrentThreadId(),
exception_code: code,
}) {
CrashEventResult::Handled(true) => {
// The handler fully handled the exception. Returning
// EXCEPTION_EXECUTE_HANDLER indicates this to the system, and usually
// results in the application being terminated.
//
// Note: If the application was launched from within the Cygwin
// environment, returning EXCEPTION_EXECUTE_HANDLER seems to cause the
// application to be restarted.
return EXCEPTION_EXECUTE_HANDLER;
}
CrashEventResult::Handled(false) => {
// There was an exception, it was a breakpoint or something else ignored
// above, or it was passed to the handler, which decided not to handle it.
// Give the previous handler a chance to do something with the exception.
// If there is no previous handler, return EXCEPTION_CONTINUE_SEARCH,
// which will allow a debugger or native "crashed" dialog to handle the
// exception.
return if let Some(previous) = current_handler.previous_filter {
previous(except_info)
} else {
EXCEPTION_CONTINUE_SEARCH
};
}
#[cfg(target_arch = "x86_64")]
CrashEventResult::Jump { jmp_buf, value } => (jmp_buf, value),
}
} else {
return EXCEPTION_CONTINUE_SEARCH;
}
};
#[cfg(target_arch = "x86_64")]
super::jmp::longjmp(_jump.0, _jump.1);
}
/// Handler for invalid parameters to CRT functions, this is not an exception so
/// the context (shouldn't be) isn't compromised
///
/// As noted [here](https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/set-invalid-parameter-handler-set-thread-local-invalid-parameter-handler?view=msvc-170#remarks)
/// the parameters to this function are useless when not linked against the debug
/// CRT, and rust std itself is only ever linked aginst the [non-debug CRT](https://github.com/rust-lang/rust/issues/39016)
/// and you can't really link both the regular and debug CRT in the same application
/// as that results in sadness, so this function just ignores the parameters,
/// unlike the original Breakpad code.
#[no_mangle]
unsafe extern "C" fn handle_invalid_parameter(
expression: *const u16,
function: *const u16,
file: *const u16,
line: u32,
reserved: usize,
) {
let _jump = {
let lock = HANDLER.lock();
if let Some(current_handler) = AutoHandler::new(lock) {
// Make up an exception record for the current thread and CPU context
// to make it possible for the crash processor to classify these
// as do regular crashes, and to make it humane for developers to
// analyze them.
let mut exception_record: ffi::EXCEPTION_RECORD = std::mem::zeroed();
let mut exception_context = std::mem::MaybeUninit::zeroed();
ffi::capture_context(exception_context.as_mut_ptr());
let mut exception_context = exception_context.assume_init();
let exception_ptrs = ffi::EXCEPTION_POINTERS {
ExceptionRecord: &mut exception_record,
ContextRecord: &mut exception_context,
};
let exception_code = ExceptionCode::InvalidParameter as u32;
exception_record.ExceptionCode = exception_code;
match current_handler.user_handler.on_crash(&crate::CrashContext {
exception_pointers: (&exception_ptrs as *const ffi::EXCEPTION_POINTERS).cast(),
process_id: std::process::id(),
thread_id: GetCurrentThreadId(),
exception_code,
}) {
CrashEventResult::Handled(true) => return,
CrashEventResult::Handled(false) => {
if let Some(prev_iph) = current_handler.previous_iph {
prev_iph(expression, function, file, line, reserved);
} else {
// If there's no previous handler, pass the exception back in to the
// invalid parameter handler's core. That's the routine that called this
// function, but now, since this function is no longer registered (and in
// fact, no function at all is registered), this will result in the
// default code path being taken: _CRT_DEBUGGER_HOOK and _invoke_watson.
// Use _invalid_parameter where it exists (in _DEBUG builds) as it passes
// more information through. In non-debug builds, it is not available,
// so fall back to using _invalid_parameter_noinfo. See invarg.c in the
// CRT source.
// _invalid_parameter is only available in the debug CRT
_invoke_watson();
// if expression.is_null() && function.is_null() && file.is_null() {
// _invalid_parameter_noinfo();
// } else {
// _invalid_parameter(expression, function, file, line, reserved);
// }
}
// The handler either took care of the invalid parameter problem itself,
// or passed it on to another handler. "Swallow" it by exiting, paralleling
// the behavior of "swallowing" exceptions.
std::process::exit(0);
}
#[cfg(target_arch = "x86_64")]
CrashEventResult::Jump { jmp_buf, value } => (jmp_buf, value),
}
} else {
_invoke_watson();
}
};
#[cfg(target_arch = "x86_64")]
super::jmp::longjmp(_jump.0, _jump.1);
}
/// Handler for pure virtual function calls, this is not an exception so the
/// context (shouldn't be) isn't compromised
#[no_mangle]
unsafe extern "C" fn handle_pure_virtual_call() {
let _jump = {
let lock = HANDLER.lock();
if let Some(current_handler) = AutoHandler::new(lock) {
// Make up an exception record for the current thread and CPU context
// to make it possible for the crash processor to classify these
// as do regular crashes, and to make it humane for developers to
// analyze them.
let mut exception_record: ffi::EXCEPTION_RECORD = std::mem::zeroed();
let mut exception_context = std::mem::MaybeUninit::zeroed();
ffi::capture_context(exception_context.as_mut_ptr());
let mut exception_context = exception_context.assume_init();
let exception_ptrs = ffi::EXCEPTION_POINTERS {
ExceptionRecord: &mut exception_record,
ContextRecord: &mut exception_context,
};
let exception_code = ExceptionCode::Purecall as u32;
exception_record.ExceptionCode = exception_code;
match current_handler.user_handler.on_crash(&crate::CrashContext {
exception_pointers: (&exception_ptrs as *const ffi::EXCEPTION_POINTERS).cast(),
process_id: std::process::id(),
thread_id: GetCurrentThreadId(),
exception_code,
}) {
CrashEventResult::Handled(true) => {
// The handler either took care of the invalid parameter problem itself,
// or passed it on to another handler. "Swallow" it by exiting, paralleling
// the behavior of "swallowing" exceptions.
std::process::exit(0);
}
CrashEventResult::Handled(false) => {
if let Some(pch) = current_handler.previous_pch {
// The handler didn't fully handle the exception. Give it to the
// previous purecall handler.
pch();
}
// If there's no previous handler, return and let _purecall handle it.
// This will just throw up an assertion dialog.
return;
}
#[cfg(target_arch = "x86_64")]
CrashEventResult::Jump { jmp_buf, value } => (jmp_buf, value),
}
} else {
return;
}
};
#[cfg(target_arch = "x86_64")]
super::jmp::longjmp(_jump.0, _jump.1);
}