|
| 1 | +//! Helper for setting up Windows exception handling. |
| 2 | +//! |
| 3 | +//! Recent versions of Windows seem to no longer show dialog boxes on access violations |
| 4 | +//! (segfaults) or similar errors. The user experience is that the command exits with |
| 5 | +//! the exception code as its exit status and no visible output. In order to see these |
| 6 | +//! errors both in the field and in CI, we need to install our own exception handler. |
| 7 | +//! |
| 8 | +//! This is a relatively simple exception handler that leans on Rust's own backtrace |
| 9 | +//! implementation and also displays some minimal information from the exception itself. |
| 10 | +
|
| 11 | +#![allow(unsafe_code)] |
| 12 | + |
| 13 | +use windows::Win32::{ |
| 14 | + Foundation, |
| 15 | + System::Diagnostics::Debug::{ |
| 16 | + CONTEXT, EXCEPTION_CONTINUE_SEARCH, EXCEPTION_POINTERS, SetUnhandledExceptionFilter, |
| 17 | + }, |
| 18 | +}; |
| 19 | + |
| 20 | +fn display_exception_info(name: &str, info: &[usize; 15]) { |
| 21 | + match info[0] { |
| 22 | + 0 => eprintln!("{name} reading {:#16x}", info[1]), |
| 23 | + 1 => eprintln!("{name} writing {:#16x}", info[1]), |
| 24 | + 8 => eprintln!("{name} executing {:#16x}", info[1]), |
| 25 | + _ => eprintln!("{name} from operation {} at {:#16x}", info[0], info[1]), |
| 26 | + } |
| 27 | +} |
| 28 | + |
| 29 | +#[cfg(target_arch = "x86")] |
| 30 | +fn dump_regs(c: &CONTEXT) { |
| 31 | + eprintln!( |
| 32 | + "eax={:08x} ebx={:08x} ecx={:08x} edx={:08x} esi={:08x} edi={:08x}", |
| 33 | + c.Eax, c.Ebx, c.Ecx, c.Edx, c.Esi, c.Edi |
| 34 | + ); |
| 35 | + eprintln!( |
| 36 | + "eip={:08x} ebp={:08x} esp={:08x} eflags={:08x}", |
| 37 | + c.Eip, c.Ebp, c.Esp, c.EFlags |
| 38 | + ); |
| 39 | +} |
| 40 | + |
| 41 | +#[cfg(target_arch = "x86_64")] |
| 42 | +fn dump_regs(c: &CONTEXT) { |
| 43 | + eprintln!("rax={:016x} rbx={:016x} rcx={:016x}", c.Rax, c.Rbx, c.Rcx); |
| 44 | + eprintln!("rdx={:016x} rsx={:016x} rdi={:016x}", c.Rdx, c.Rsi, c.Rdi); |
| 45 | + eprintln!("rsp={:016x} rbp={:016x} r8={:016x}", c.Rsp, c.Rbp, c.R8); |
| 46 | + eprintln!(" r9={:016x} r10={:016x} r11={:016x}", c.R9, c.R10, c.R11); |
| 47 | + eprintln!("r12={:016x} r13={:016x} r14={:016x}", c.R12, c.R13, c.R14); |
| 48 | + eprintln!( |
| 49 | + "r15={:016x} rip={:016x} eflags={:016x}", |
| 50 | + c.R15, c.Rip, c.EFlags |
| 51 | + ); |
| 52 | +} |
| 53 | + |
| 54 | +#[cfg(target_arch = "aarch64")] |
| 55 | +fn dump_regs(c: &CONTEXT) { |
| 56 | + let r = c.Anonymous.Anonymous; |
| 57 | + eprintln!("cpsr={:016x} sp={:016x} pc={:016x}", c.Cpsr, c.Sp, c.Pc); |
| 58 | + eprintln!(" x0={:016x} x1={:016x} x2={:016x}", r.X0, r.X1, r.X2); |
| 59 | + eprintln!(" x3={:016x} x4={:016x} x5={:016x}", r.X3, r.X4, r.X5); |
| 60 | + eprintln!(" x6={:016x} x7={:016x} x8={:016x}", r.X6, r.X7, r.X8); |
| 61 | + eprintln!(" x9={:016x} x10={:016x} x11={:016x}", r.X9, r.X10, r.X11); |
| 62 | + eprintln!(" x12={:016x} x13={:016x} x14={:016x}", r.X12, r.X13, r.X14); |
| 63 | + eprintln!(" x15={:016x} x16={:016x} x17={:016x}", r.X15, r.X16, r.X17); |
| 64 | + eprintln!(" x18={:016x} x19={:016x} x20={:016x}", r.X18, r.X19, r.X20); |
| 65 | + eprintln!(" x21={:016x} x22={:016x} x23={:016x}", r.X21, r.X22, r.X23); |
| 66 | + eprintln!(" x24={:016x} x25={:016x} x26={:016x}", r.X24, r.X25, r.X26); |
| 67 | + eprintln!(" x27={:016x} x28={:016x}", r.X27, r.X28); |
| 68 | + eprintln!(" fp={:016x} lr={:016x}}", r.Fp, r.Lr); |
| 69 | +} |
| 70 | + |
| 71 | +unsafe extern "system" fn unhandled_exception_filter( |
| 72 | + exception_info: *const EXCEPTION_POINTERS, |
| 73 | +) -> i32 { |
| 74 | + // TODO: Really we should not be using eprintln here because Stderr is not async-signal-safe. |
| 75 | + // Probably we should be calling the console APIs directly. |
| 76 | + eprintln!("error: unhandled exception in uv, please report a bug:"); |
| 77 | + let mut context = None; |
| 78 | + // SAFETY: Pointer comes from the OS |
| 79 | + if let Some(info) = unsafe { exception_info.as_ref() } { |
| 80 | + // SAFETY: Pointer comes from the OS |
| 81 | + if let Some(exc) = unsafe { info.ExceptionRecord.as_ref() } { |
| 82 | + eprintln!( |
| 83 | + "code {:#X} at address {:?}", |
| 84 | + exc.ExceptionCode.0, exc.ExceptionAddress |
| 85 | + ); |
| 86 | + match exc.ExceptionCode { |
| 87 | + Foundation::EXCEPTION_ACCESS_VIOLATION => { |
| 88 | + display_exception_info("EXCEPTION_ACCESS_VIOLATION", &exc.ExceptionInformation); |
| 89 | + } |
| 90 | + Foundation::EXCEPTION_IN_PAGE_ERROR => { |
| 91 | + display_exception_info("EXCEPTION_IN_PAGE_ERROR", &exc.ExceptionInformation); |
| 92 | + } |
| 93 | + Foundation::EXCEPTION_ILLEGAL_INSTRUCTION => { |
| 94 | + eprintln!("EXCEPTION_ILLEGAL_INSTRUCTION"); |
| 95 | + } |
| 96 | + Foundation::EXCEPTION_STACK_OVERFLOW => { |
| 97 | + eprintln!("EXCEPTION_STACK_OVEFLOW"); |
| 98 | + } |
| 99 | + _ => {} |
| 100 | + } |
| 101 | + } else { |
| 102 | + eprintln!("(ExceptionRecord is NULL)"); |
| 103 | + } |
| 104 | + // SAFETY: Pointer comes from the OS |
| 105 | + context = unsafe { info.ContextRecord.as_ref() }; |
| 106 | + } else { |
| 107 | + eprintln!("(ExceptionInfo is NULL)"); |
| 108 | + } |
| 109 | + let backtrace = std::backtrace::Backtrace::capture(); |
| 110 | + if backtrace.status() == std::backtrace::BacktraceStatus::Disabled { |
| 111 | + eprintln!("note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace"); |
| 112 | + } else { |
| 113 | + if let Some(context) = context { |
| 114 | + dump_regs(context); |
| 115 | + } |
| 116 | + eprintln!("stack backtrace:\n{backtrace:#}"); |
| 117 | + } |
| 118 | + EXCEPTION_CONTINUE_SEARCH |
| 119 | +} |
| 120 | + |
| 121 | +/// Set up our handler for unhandled exceptions. |
| 122 | +pub(crate) fn setup() { |
| 123 | + // SAFETY: winapi call |
| 124 | + unsafe { |
| 125 | + SetUnhandledExceptionFilter(Some(Some(unhandled_exception_filter))); |
| 126 | + } |
| 127 | +} |
0 commit comments