Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crash-handler/tests/shared.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand Down
37 changes: 37 additions & 0 deletions minidumper-test/crash-client/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#![allow(unconditional_panic)]

use minidumper_test::Signal;

use clap::Parser;
Expand Down Expand Up @@ -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();
}
Expand All @@ -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),
}
}
};
Expand All @@ -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
Expand Down
84 changes: 81 additions & 3 deletions minidumper-test/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,24 +24,65 @@ pub fn dump_test(signal: Signal, use_thread: bool, dump_path: Option<PathBuf>) {
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo: SIGABRT

#[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;
Expand All @@ -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",
Expand All @@ -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",
})
}
}
Expand Down Expand Up @@ -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
));
Expand Down Expand Up @@ -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 => {
Expand All @@ -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
));
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
));
Expand Down Expand Up @@ -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"),
}
Expand Down
24 changes: 24 additions & 0 deletions sadness-generator/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)]
Expand Down Expand Up @@ -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`]
///
Expand All @@ -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
Expand Down