diff --git a/crash-handler/tests/shared.rs b/crash-handler/tests/shared.rs index b6b7aea..56820b4 100644 --- a/crash-handler/tests/shared.rs +++ b/crash-handler/tests/shared.rs @@ -72,7 +72,7 @@ pub fn handles_crash(flavor: SadnessFlavor) { SadnessFlavor::Illegal => ExceptionCode::Illegal, SadnessFlavor::InvalidParameter => ExceptionCode::InvalidParameter, SadnessFlavor::Purecall => ExceptionCode::Purecall, - SadnessFlavor::Segfault => ExceptionCode::Segv, + SadnessFlavor::Segfault | SadnessFlavor::SegfaultNonCanonical => ExceptionCode::Segv, SadnessFlavor::StackOverflow { .. }=> ExceptionCode::StackOverflow, SadnessFlavor::Trap => ExceptionCode::Trap, }; diff --git a/minidumper-test/crash-client/src/main.rs b/minidumper-test/crash-client/src/main.rs index 2509dfc..6e747e8 100644 --- a/minidumper-test/crash-client/src/main.rs +++ b/minidumper-test/crash-client/src/main.rs @@ -1,3 +1,5 @@ +#![allow(unconditional_panic)] + use minidumper_test::Signal; use clap::Parser; @@ -87,6 +89,9 @@ fn real_main() -> anyhow::Result<()> { Signal::Segv => { sadness_generator::raise_segfault(); } + Signal::SegvNonCanonical => { + sadness_generator::raise_segfault_non_canonical(); + } Signal::StackOverflow => { sadness_generator::raise_stack_overflow(); } @@ -112,6 +117,30 @@ fn real_main() -> anyhow::Result<()> { Signal::Guard => { sadness_generator::raise_guard_exception(); } + Signal::RustProcessAbort => std::process::abort(), + Signal::RustPanic => panic!("oh no! something terrible has happened!"), + Signal::RustAssert => assert!(5 > 10, "oh no, we're in the normal math universe!"), + Signal::RustAssertEq => assert_eq!(7, 13, "it's still the normal math universe!"), + Signal::RustDivByZero => { + let x = 10 / 0; + unreachable!("oh god why did div-by-0 work: {}", x); + } + Signal::RustOptionUnwrap => { + let mut rust_has_no_null = Some(true); + assert!(rust_has_no_null.unwrap()); + rust_has_no_null = None; + println!("does rust have no nulls? {}", rust_has_no_null.unwrap()); + } + Signal::RustResultUnwrap => { + println!("{}", parse_int("12 ae3")); + } + Signal::RustIndexOutOfBounds => { + let data = &[1, 2, 3]; + let x = get_important_data(data, 0); + let y = get_important_data(data, 5); + println!("x + y = {}", x + y); + } + Signal::RustExit => std::process::exit(-1), } } }; @@ -135,6 +164,14 @@ fn real_main() -> anyhow::Result<()> { anyhow::bail!("we should have raised a signal and exited"); } +fn parse_int(input: &str) -> i32 { + input.parse().unwrap() +} + +fn get_important_data(data: &[u32], idx: usize) -> u32 { + data[idx] +} + fn main() { // We want this program to crash and have a minidump written, it _shouldn't_ // have errors that prevent that from happening, so emit an error code if we diff --git a/minidumper-test/src/lib.rs b/minidumper-test/src/lib.rs index 7c23352..38d71b7 100644 --- a/minidumper-test/src/lib.rs +++ b/minidumper-test/src/lib.rs @@ -24,24 +24,65 @@ pub fn dump_test(signal: Signal, use_thread: bool, dump_path: Option) { let _md = generate_minidump(&id, signal, use_thread, dump_path); } +/// Various ways to suddenly exit the process +/// +/// Some of these may properly exit the process, which is ok, we just want to see +/// what happens if you do these various things. #[derive(clap::ArgEnum, Clone, Copy)] pub enum Signal { + /// Unix SIGABORT #[cfg(unix)] Abort, + /// Unix SIGBUS #[cfg(unix)] Bus, + /// Floating Point Exception Fpe, + /// Illegal Instruction Illegal, + /// Segfault Segv, + /// Segfault (non-canonical address, if applicable) + SegvNonCanonical, + /// Stackoverflow in a rust thread StackOverflow, + /// Stackoverflow in a non-rust thread StackOverflowCThread, + /// Trap/Breakpoint Trap, #[cfg(windows)] + /// Windows purecall exception Purecall, #[cfg(windows)] + /// Windows invalid parameter exception InvalidParameter, + /// macOS EXC_GUARD #[cfg(target_os = "macos")] Guard, + + // These are all more high-level ways for the program to die in typical rust programming, + // most of them will show up as panics and unwinding, maybe setting an exit code, and will + // generally be the same, but perhaps a Clever crash handler can do more interesting things + // with these different flavours (and they will often have different tops of the stack to + // unwind through). + /// Rust std::process::abort + RustProcessAbort, + /// Rust panic!() + RustPanic, + /// Rust assert!() + RustAssert, + /// Rust assert_eq!() + RustAssertEq, + /// Rust divide by 0 + RustDivByZero, + /// Rust Option::unwrap + RustOptionUnwrap, + /// Rust Result::unwrap + RustResultUnwrap, + /// Rust index out of bounds + RustIndexOutOfBounds, + /// Rust std::process::exit(-1) + RustExit, } use std::fmt; @@ -55,6 +96,7 @@ impl fmt::Display for Signal { Self::Fpe => "fpe", Self::Illegal => "illegal", Self::Segv => "segv", + Self::SegvNonCanonical => "segv-non-canonical", Self::StackOverflow => "stack-overflow", Self::StackOverflowCThread => "stack-overflow-c-thread", Self::Trap => "trap", @@ -64,6 +106,15 @@ impl fmt::Display for Signal { Self::InvalidParameter => "invalid-parameter", #[cfg(target_os = "macos")] Self::Guard => "guard", + Self::RustProcessAbort => "rust-process-abort", + Self::RustPanic => "rust-panic", + Self::RustAssert => "rust-assert", + Self::RustAssertEq => "rust-assert-eq", + Self::RustDivByZero => "rust-div-by-zero", + Self::RustOptionUnwrap => "rust-option-unwrap", + Self::RustResultUnwrap => "rust-result-unwrap", + Self::RustIndexOutOfBounds => "rust-index-out-of-bounds", + Self::RustExit => "rust-exit", }) } } @@ -344,7 +395,7 @@ pub fn assert_minidump(md_buf: &[u8], signal: Signal) { errors::ExceptionCodeLinuxSigillKind::ILL_ILLOPN )); } - Signal::Segv => { + Signal::Segv | Signal::SegvNonCanonical => { verify!(CrashReason::LinuxSigsegv( errors::ExceptionCodeLinuxSigsegvKind::SEGV_MAPERR )); @@ -374,6 +425,15 @@ pub fn assert_minidump(md_buf: &[u8], signal: Signal) { Signal::Guard => { unreachable!("macos only"); } + Signal::RustProcessAbort => todo!(), + Signal::RustPanic => todo!(), + Signal::RustAssert => todo!(), + Signal::RustAssertEq => todo!(), + Signal::RustDivByZero => todo!(), + Signal::RustOptionUnwrap => todo!(), + Signal::RustResultUnwrap => todo!(), + Signal::RustIndexOutOfBounds => todo!(), + Signal::RustExit => todo!(), }, Os::Windows => match signal { Signal::Fpe => { @@ -386,7 +446,7 @@ pub fn assert_minidump(md_buf: &[u8], signal: Signal) { errors::ExceptionCodeWindows::EXCEPTION_ILLEGAL_INSTRUCTION )); } - Signal::Segv => { + Signal::Segv | Signal::SegvNonCanonical => { verify!(CrashReason::WindowsAccessViolation( errors::ExceptionCodeWindowsAccessType::WRITE )); @@ -419,6 +479,15 @@ pub fn assert_minidump(md_buf: &[u8], signal: Signal) { Signal::Guard => { unreachable!("macos only"); } + Signal::RustProcessAbort => todo!(), + Signal::RustPanic => todo!(), + Signal::RustAssert => todo!(), + Signal::RustAssertEq => todo!(), + Signal::RustDivByZero => todo!(), + Signal::RustOptionUnwrap => todo!(), + Signal::RustResultUnwrap => todo!(), + Signal::RustIndexOutOfBounds => todo!(), + Signal::RustExit => todo!(), }, #[allow(clippy::match_same_arms)] Os::MacOs => match signal { @@ -466,7 +535,7 @@ pub fn assert_minidump(md_buf: &[u8], signal: Signal) { } } } - Signal::Segv => { + Signal::Segv | Signal::SegvNonCanonical => { verify!(CrashReason::MacBadAccessKern( errors::ExceptionCodeMacBadAccessKernType::KERN_INVALID_ADDRESS, )); @@ -521,6 +590,15 @@ pub fn assert_minidump(md_buf: &[u8], signal: Signal) { Signal::Purecall | Signal::InvalidParameter => { unreachable!("windows only"); } + Signal::RustProcessAbort => todo!(), + Signal::RustPanic => todo!(), + Signal::RustAssert => todo!(), + Signal::RustAssertEq => todo!(), + Signal::RustDivByZero => todo!(), + Signal::RustOptionUnwrap => todo!(), + Signal::RustResultUnwrap => todo!(), + Signal::RustIndexOutOfBounds => todo!(), + Signal::RustExit => todo!(), }, _ => unreachable!("apparently we are targeting a new OS"), } diff --git a/sadness-generator/src/lib.rs b/sadness-generator/src/lib.rs index c8da1f5..21bd0a3 100644 --- a/sadness-generator/src/lib.rs +++ b/sadness-generator/src/lib.rs @@ -21,6 +21,11 @@ pub enum SadnessFlavor { /// * `EXCEPTION_ACCESS_VIOLATION` on Windows /// * `EXC_BAD_ACCESS` on Macos Segfault, + /// Same as `Segfault`, but with a non-canonical address where possible. + /// + /// This is a more difficult corner case because certain things will act weird + /// with non-canonical addresses when handling a crash. + SegfaultNonCanonical, /// * `SIGFPE` on Linux /// * `EXCEPTION_INT_DIVIDE_BY_ZERO` on Windows /// * `EXC_ARITHMETIC` on Macos @@ -72,6 +77,7 @@ impl SadnessFlavor { #[cfg(unix)] Self::Abort => raise_abort(), Self::Segfault => raise_segfault(), + Self::SegfaultNonCanonical => raise_segfault_non_canonical(), Self::DivideByZero => raise_floating_point_exception(), Self::Illegal => raise_illegal_instruction(), #[cfg(unix)] @@ -122,6 +128,10 @@ pub unsafe fn raise_abort() -> ! { pub const SEGFAULT_ADDRESS: u64 = u32::MAX as u64 + 0x42; #[cfg(target_pointer_width = "32")] pub const SEGFAULT_ADDRESS: u32 = 0x42; +#[cfg(target_pointer_width = "64")] +pub const SEGFAULT_ADDRESS_NON_CANONICAL: u64 = u64::MAX - ((u32::MAX >> 1) as u64) + 0x42; +#[cfg(target_pointer_width = "32")] +pub const SEGFAULT_ADDRESS_NON_CANONICAL: u32 = SEGFAULT_ADDRESS; /// [`SadnessFlavor::Segfault`] /// @@ -137,6 +147,20 @@ pub unsafe fn raise_segfault() -> ! { std::process::abort() } +/// [`SadnessFlavor::SegfaultNonCanonical`] +/// +/// # Safety +/// +/// This is not safe. It intentionally crashes. +pub unsafe fn raise_segfault_non_canonical() -> ! { + let bad_ptr: *mut u8 = SEGFAULT_ADDRESS_NON_CANONICAL as _; + std::ptr::write_volatile(bad_ptr, 1); + + // If we actually get here that means the address is mapped and writable + // by the current process which is...unexpected + std::process::abort() +} + /// [`SadnessFlavor::DivideByZero`] /// /// # Safety