#![allow(unsafe_code)]
use std::arch::asm;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum SadnessFlavor {
Abort,
Segfault,
DivideByZero,
Illegal,
#[cfg(unix)]
Bus,
Trap,
StackOverflow {
non_rust_thread: bool,
long_jumps: bool,
},
#[cfg(windows)]
Purecall,
#[cfg(windows)]
InvalidParameter,
#[cfg(target_os = "macos")]
Guard,
}
impl SadnessFlavor {
pub unsafe fn make_sad(self) -> ! {
match self {
Self::Abort => raise_abort(),
Self::Segfault => raise_segfault(),
Self::DivideByZero => raise_floating_point_exception(),
Self::Illegal => raise_illegal_instruction(),
#[cfg(unix)]
Self::Bus => raise_bus(),
Self::Trap => raise_trap(),
#[allow(unused_variables)]
Self::StackOverflow {
non_rust_thread,
long_jumps,
} => {
if !non_rust_thread {
raise_stack_overflow()
} else {
#[cfg(unix)]
{
raise_stack_overflow_in_non_rust_thread(long_jumps)
}
#[cfg(windows)]
{
raise_stack_overflow()
}
}
}
#[cfg(windows)]
Self::Purecall => raise_purecall(),
#[cfg(windows)]
Self::InvalidParameter => raise_invalid_parameter(),
#[cfg(target_os = "macos")]
Self::Guard => raise_guard_exception(),
}
}
}
pub unsafe fn raise_abort() -> ! {
libc::abort()
}
#[cfg(target_pointer_width = "64")]
pub const SEGFAULT_ADDRESS: u64 = u32::MAX as u64 + 0x42;
#[cfg(target_pointer_width = "32")]
pub const SEGFAULT_ADDRESS: u32 = 0x42;
pub unsafe fn raise_segfault() -> ! {
let bad_ptr: *mut u8 = SEGFAULT_ADDRESS as _;
std::ptr::write_volatile(bad_ptr, 1);
std::process::abort()
}
pub unsafe fn raise_floating_point_exception() -> ! {
let ohno = {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
let mut divisor: u32;
asm!(
"mov eax, 1",
"cdq",
"mov {div:e}, 0",
"idiv {div:e}",
div = out(reg) divisor
);
divisor
}
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
{
libc::raise(libc::SIGFPE);
0
}
};
println!("we won't get here because we've raised a floating point exception: {ohno}");
std::process::abort()
}
pub unsafe fn raise_illegal_instruction() -> ! {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
asm!("ud2");
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
asm!("udf #0");
std::process::abort()
}
#[cfg(unix)]
pub unsafe fn raise_bus() -> ! {
let mut temp_name = [0; 14];
temp_name.copy_from_slice(b"sigbus.XXXXXX\0");
let bus_fd = libc::mkstemp(temp_name.as_mut_ptr().cast());
assert!(bus_fd != -1);
let page_size = libc::sysconf(libc::_SC_PAGESIZE) as usize;
let mapping = std::slice::from_raw_parts_mut(
libc::mmap(
std::ptr::null_mut(),
128,
libc::PROT_READ | libc::PROT_WRITE,
libc::MAP_SHARED,
bus_fd,
0,
)
.cast::<u8>(),
page_size + page_size / 2,
);
libc::unlink(temp_name.as_ptr().cast());
mapping[20] = 20;
println!("{}", mapping[20]);
std::process::abort()
}
pub unsafe fn raise_trap() -> ! {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
asm!("int3");
#[cfg(target_arch = "arm")]
asm!(".inst 0xe7f001f0");
#[cfg(target_arch = "aarch64")]
asm!(".inst 0xd4200000");
std::process::abort()
}
pub unsafe fn raise_stack_overflow() -> ! {
fn recurse(data: u64) -> u64 {
let mut buff = [0u8; 256];
let mut result = data;
buff[..9].copy_from_slice(b"junk data");
for c in buff {
result += c as u64;
}
if result == 0 {
result
} else {
recurse(result) + 1
}
}
recurse(42);
std::process::abort()
}
#[cfg(unix)]
pub unsafe fn raise_stack_overflow_in_non_rust_thread(uses_longjmp: bool) -> ! {
let mut native: libc::pthread_t = std::mem::zeroed();
let mut attr: libc::pthread_attr_t = std::mem::zeroed();
assert_eq!(
libc::pthread_attr_setstacksize(&mut attr, 2 * 1024 * 1024),
0,
"failed to set thread stack size",
);
use std::sync;
let pair = sync::Arc::new((sync::Mutex::new(false), sync::Condvar::new()));
let tpair = pair.clone();
extern "C" fn thread_start(arg: *mut libc::c_void) -> *mut libc::c_void {
{
let tpair =
unsafe { sync::Arc::from_raw(arg as *const (sync::Mutex<bool>, sync::Condvar)) };
let (lock, cvar) = &*tpair;
let mut started = lock.lock().unwrap();
*started = true;
cvar.notify_one();
}
unsafe { raise_stack_overflow() };
}
let ret = libc::pthread_create(
&mut native,
&attr,
thread_start,
sync::Arc::into_raw(tpair) as *mut _,
);
assert_eq!(
libc::pthread_attr_destroy(&mut attr),
0,
"failed to destroy thread attributes"
);
assert_eq!(ret, 0, "pthread_create failed");
if !uses_longjmp {
assert_eq!(
libc::pthread_join(native, std::ptr::null_mut()),
0,
"failed to join"
);
}
let (lock, cvar) = &*pair;
let mut started = lock.lock().unwrap();
while !*started {
started = cvar.wait(started).unwrap();
}
std::thread::sleep(std::time::Duration::from_millis(10));
#[allow(clippy::empty_loop)]
loop {}
}
#[inline]
#[cfg(unix)]
pub unsafe fn raise_stack_overflow_in_non_rust_thread_normal() -> ! {
raise_stack_overflow_in_non_rust_thread(false)
}
#[inline]
#[cfg(unix)]
pub unsafe fn raise_stack_overflow_in_non_rust_thread_longjmp() -> ! {
raise_stack_overflow_in_non_rust_thread(true)
}
#[cfg(target_os = "windows")]
pub unsafe fn raise_purecall() -> ! {
extern "C" {
fn _purecall() -> i32;
}
_purecall();
std::process::abort()
}
#[cfg(target_os = "windows")]
pub unsafe fn raise_invalid_parameter() -> ! {
extern "C" {
fn _mbscmp(s1: *const u8, s2: *const u8) -> i32;
}
_mbscmp(std::ptr::null(), std::ptr::null());
std::process::abort()
}
#[cfg(target_os = "macos")]
pub const GUARD_ID: u64 = 0x1234567890abcdef;
#[cfg(target_os = "macos")]
pub unsafe fn raise_guard_exception() -> ! {
extern "C" {
fn guarded_open_np(
path: *const u8,
guard_id: *const u64,
guard_flags: u32,
flags: i32,
...
) -> i32;
}
const GUARD_CLOSE: u32 = 1 << 0;
const GUARD_DUP: u32 = 1 << 1;
const GUARD_SOCKET_IPC: u32 = 1 << 2;
const GUARD_FILEPORT: u32 = 1 << 3;
let fd = guarded_open_np(
b"/tmp/sadness-generator-guard.txt\0".as_ptr(),
&GUARD_ID,
GUARD_CLOSE | GUARD_DUP | GUARD_SOCKET_IPC | GUARD_FILEPORT,
libc::O_CREAT | libc::O_CLOEXEC | libc::O_RDWR,
0o666,
);
assert!(
fd != -1,
"failed to create guarded file descriptor, unable to crash"
);
libc::close(fd);
std::process::abort()
}