#![allow(non_camel_case_types, clippy::exit)]
use super::ExceptionCode;
use crate::Error;
type LPTOP_LEVEL_EXCEPTION_FILTER = Option<
unsafe extern "system" fn(exceptioninfo: *const crash_context::EXCEPTION_POINTERS) -> i32,
>;
type PVECTORED_EXCEPTION_HANDLER = Option<
unsafe extern "system" fn(exceptioninfo: *const crash_context::EXCEPTION_POINTERS) -> i32,
>;
unsafe extern "system" {
fn GetCurrentThreadId() -> u32;
fn SetUnhandledExceptionFilter(
filter: LPTOP_LEVEL_EXCEPTION_FILTER,
) -> LPTOP_LEVEL_EXCEPTION_FILTER;
fn AddVectoredExceptionHandler(
first_handler: u32,
handler: PVECTORED_EXCEPTION_HANDLER,
) -> *mut core::ffi::c_void;
fn RemoveVectoredExceptionHandler(handle: *mut core::ffi::c_void) -> u32;
}
struct VehHandler(std::ptr::NonNull<libc::c_void>);
unsafe impl Send for VehHandler {}
unsafe impl Sync for VehHandler {}
unsafe extern "C" {
fn _set_invalid_parameter_handler(
new_handler: Option<_invalid_parameter_handler>,
) -> Option<_invalid_parameter_handler>;
fn _set_purecall_handler(new_handler: Option<_purecall_handler>) -> Option<_purecall_handler>;
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>,
previous_filter: LPTOP_LEVEL_EXCEPTION_FILTER,
previous_iph: Option<_invalid_parameter_handler>,
previous_pch: Option<_purecall_handler>,
previous_abort_handler: Option<libc::sighandler_t>,
veh_handle: Option<VehHandler>,
}
impl HandlerInner {
pub(crate) fn new(user_handler: Box<dyn crate::CrashEvent>) -> Self {
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();
let veh_handle = AddVectoredExceptionHandler(1, Some(vectored_handle_exception));
let veh_handle = std::ptr::NonNull::new(veh_handle).map(VehHandler);
Self {
user_handler,
previous_filter,
previous_iph,
previous_pch,
previous_abort_handler,
veh_handle,
}
}
}
pub(crate) fn restore_previous_handlers(&mut self) {
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);
if let Some(handler) = self.veh_handle.take() {
RemoveVectoredExceptionHandler(handler.0.as_ptr());
}
}
}
}
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();
lock.take();
}
pub(super) unsafe fn simulate_exception(exception_code: Option<i32>) -> crate::CrashEventResult {
let lock = HANDLER.lock();
if let Some(handler) = &*lock {
unsafe {
let exception_code = exception_code.unwrap_or(ExceptionCode::User as i32);
let mut exception_record = crash_context::EXCEPTION_RECORD {
ExceptionCode: exception_code,
..std::mem::zeroed()
};
let mut exception_context = std::mem::MaybeUninit::zeroed();
crash_context::capture_context(exception_context.as_mut_ptr());
let mut exception_context = exception_context.assume_init();
let exception_ptrs = crash_context::EXCEPTION_POINTERS {
ExceptionRecord: &mut exception_record,
ContextRecord: &mut exception_context,
};
let cc = crash_context::CrashContext {
exception_pointers: (&exception_ptrs as *const crash_context::EXCEPTION_POINTERS)
.cast(),
process_id: std::process::id(),
thread_id: GetCurrentThreadId(),
exception_code,
};
handler.user_handler.on_crash(&cc)
}
} else {
crate::CrashEventResult::Handled(false)
}
}
struct AutoHandler<'scope> {
lock: parking_lot::MutexGuard<'scope, Option<HandlerInner>>,
}
impl<'scope> AutoHandler<'scope> {
fn new(mut lock: parking_lot::MutexGuard<'scope, Option<HandlerInner>>) -> Option<Self> {
if let Some(hi) = &mut *lock {
hi.restore_previous_handlers();
}
if lock.is_some() {
Some(Self { lock })
} else {
None
}
}
}
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 std::ops::Deref for AutoHandler<'_> {
type Target = HandlerInner;
fn deref(&self) -> &Self::Target {
self.lock.as_ref().unwrap()
}
}
impl Drop for AutoHandler<'_> {
fn drop(&mut self) {
set_handlers();
}
}
const EXCEPTION_CONTINUE_SEARCH: i32 = 0;
pub(super) const EXCEPTION_EXECUTE_HANDLER: i32 = 1;
use crate::CrashEventResult;
pub(super) unsafe extern "system" fn handle_exception(
except_info: *const crash_context::EXCEPTION_POINTERS,
) -> i32 {
let _jump = unsafe {
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 as _,
}) {
CrashEventResult::Handled(true) => {
return EXCEPTION_EXECUTE_HANDLER;
}
CrashEventResult::Handled(false) => {
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")]
unsafe {
super::jmp::longjmp(_jump.0, _jump.1)
};
}
const STATUS_HEAP_CORRUPTION: u32 = 0xc0000374;
pub(super) unsafe extern "system" fn vectored_handle_exception(
except_info: *const crash_context::EXCEPTION_POINTERS,
) -> i32 {
unsafe {
let exception_code = (*(*except_info).ExceptionRecord).ExceptionCode as u32;
if exception_code == STATUS_HEAP_CORRUPTION {
handle_exception(except_info)
} else {
EXCEPTION_CONTINUE_SEARCH
}
}
}
#[unsafe(no_mangle)]
unsafe extern "C" fn handle_invalid_parameter(
expression: *const u16,
function: *const u16,
file: *const u16,
line: u32,
reserved: usize,
) {
let _jump = unsafe {
let lock = HANDLER.lock();
if let Some(current_handler) = AutoHandler::new(lock) {
let exception_code = ExceptionCode::InvalidParameter as i32;
let mut exception_record = crash_context::EXCEPTION_RECORD {
ExceptionCode: exception_code,
..std::mem::zeroed()
};
let mut exception_context = std::mem::MaybeUninit::zeroed();
crash_context::capture_context(exception_context.as_mut_ptr());
let mut exception_context = exception_context.assume_init();
let exception_ptrs = crash_context::EXCEPTION_POINTERS {
ExceptionRecord: &mut exception_record,
ContextRecord: &mut exception_context,
};
match current_handler.user_handler.on_crash(&crate::CrashContext {
exception_pointers: (&exception_ptrs as *const crash_context::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 {
_invoke_watson();
}
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")]
unsafe {
super::jmp::longjmp(_jump.0, _jump.1)
};
}
#[unsafe(no_mangle)]
unsafe extern "C" fn handle_pure_virtual_call() {
let _jump = unsafe {
let lock = HANDLER.lock();
if let Some(current_handler) = AutoHandler::new(lock) {
let exception_code = ExceptionCode::Purecall as i32;
let mut exception_record = crash_context::EXCEPTION_RECORD {
ExceptionCode: exception_code,
..std::mem::zeroed()
};
let mut exception_context = std::mem::MaybeUninit::zeroed();
crash_context::capture_context(exception_context.as_mut_ptr());
let mut exception_context = exception_context.assume_init();
let exception_ptrs = crash_context::EXCEPTION_POINTERS {
ExceptionRecord: &mut exception_record,
ContextRecord: &mut exception_context,
};
match current_handler.user_handler.on_crash(&crate::CrashContext {
exception_pointers: (&exception_ptrs as *const crash_context::EXCEPTION_POINTERS)
.cast(),
process_id: std::process::id(),
thread_id: GetCurrentThreadId(),
exception_code,
}) {
CrashEventResult::Handled(true) => {
std::process::exit(0);
}
CrashEventResult::Handled(false) => {
if let Some(pch) = current_handler.previous_pch {
pch();
}
return;
}
#[cfg(target_arch = "x86_64")]
CrashEventResult::Jump { jmp_buf, value } => (jmp_buf, value),
}
} else {
return;
}
};
#[cfg(target_arch = "x86_64")]
unsafe {
super::jmp::longjmp(_jump.0, _jump.1)
};
}