#![allow(dead_code)]
#![cfg_attr(not(feature = "std"), no_std)]
use core::{ffi::c_void, mem::MaybeUninit};
mod code;
mod exception;
mod registers;
pub use code::ExceptionCode;
pub use exception::Exception;
pub use registers::Registers;
const MS_SUCCEEDED: u32 = 0x0;
type ProcExecutor = unsafe extern "system" fn(*mut c_void);
#[inline(always)]
unsafe extern "system" fn proc_executor<F>(proc: *mut c_void)
where
F: FnMut(),
{
if let Some(proc) = proc.cast::<F>().as_mut() {
proc();
}
}
#[cfg(all(windows, not(docsrs)))]
extern "C" {
#[link_name = "__microseh_HandlerStub"]
fn handler_stub(
proc_executor: ProcExecutor,
proc: *mut c_void,
exception: *mut Exception,
) -> u32;
}
#[cfg(all(windows, not(docsrs)))]
fn do_call_stub<F>(mut proc: F) -> Result<(), Exception>
where
F: FnMut(),
{
let mut exception = Exception::empty();
let proc = &mut proc as *mut _ as *mut c_void;
match unsafe { handler_stub(proc_executor::<F>, proc, &mut exception) } {
MS_SUCCEEDED => Ok(()),
_ => Err(exception),
}
}
#[cfg(any(not(windows), docsrs))]
fn do_call_stub<F>(_proc: F) -> Result<(), Exception>
where
F: FnMut(),
{
panic!("exception handling is not available in this build of microseh")
}
#[inline(always)]
pub fn try_seh<F, R>(mut proc: F) -> Result<R, Exception>
where
F: FnMut() -> R,
{
let mut ret_val = MaybeUninit::<R>::uninit();
do_call_stub(|| {
ret_val.write(proc());
})
.map(|_| unsafe { ret_val.assume_init() })
}
#[cfg(test)]
mod tests {
use super::*;
const INVALID_PTR: *mut i32 = core::mem::align_of::<i32>() as _;
#[test]
#[cfg(feature = "std")]
fn all_good() {
let ex = try_seh(|| {
let _ = *Box::new(1337);
});
assert_eq!(ex.is_ok(), true);
}
#[test]
fn access_violation_rs() {
let ex = try_seh(|| unsafe {
INVALID_PTR.read_volatile();
});
assert_eq!(ex.is_err(), true);
assert_eq!(ex.unwrap_err().code(), ExceptionCode::AccessViolation);
}
#[test]
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn access_violation_asm() {
let ex = try_seh(|| unsafe {
core::arch::asm!("mov eax, DWORD PTR [0]");
});
assert_eq!(ex.is_err(), true);
assert_eq!(ex.unwrap_err().code(), ExceptionCode::AccessViolation);
}
#[test]
#[cfg(target_arch = "aarch64")]
fn access_violation_asm() {
let ex = try_seh(|| unsafe {
core::arch::asm!("ldr x0, xzr");
});
assert_eq!(ex.is_err(), true);
assert_eq!(ex.unwrap_err().code(), ExceptionCode::AccessViolation);
}
#[test]
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn breakpoint() {
let ex = try_seh(|| unsafe {
core::arch::asm!("int3");
});
assert_eq!(ex.is_err(), true);
assert_eq!(ex.unwrap_err().code(), ExceptionCode::Breakpoint);
}
#[test]
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn illegal_instruction() {
let ex = try_seh(|| unsafe {
core::arch::asm!("ud2");
});
assert_eq!(ex.is_err(), true);
assert_eq!(ex.unwrap_err().code(), ExceptionCode::IllegalInstruction);
}
#[test]
#[cfg(target_arch = "aarch64")]
fn illegal_instruction() {
let ex = try_seh(|| unsafe {
core::arch::asm!("udf #0");
});
assert_eq!(ex.is_err(), true);
assert_eq!(
ex.as_ref().unwrap_err().code(),
ExceptionCode::IllegalInstruction
);
}
#[test]
#[cfg(target_arch = "x86")]
fn reg_state_check() {
let ex = try_seh(|| unsafe {
core::arch::asm!("mov eax, 0xbadc0de", "ud2");
});
assert_eq!(ex.is_err(), true);
assert_eq!(
ex.as_ref().unwrap_err().code(),
ExceptionCode::IllegalInstruction
);
assert_eq!(ex.unwrap_err().registers().eax(), 0xbadc0de);
}
#[test]
#[cfg(target_arch = "x86_64")]
fn reg_state_check() {
let ex = try_seh(|| unsafe {
core::arch::asm!("mov rax, 0xbadc0debabefffff", "ud2");
});
assert_eq!(ex.is_err(), true);
assert_eq!(
ex.as_ref().unwrap_err().code(),
ExceptionCode::IllegalInstruction
);
assert_eq!(ex.unwrap_err().registers().rax(), 0xbadc0debabefffff);
}
#[test]
#[cfg(target_arch = "aarch64")]
fn reg_state_check() {
let ex = try_seh(|| unsafe {
core::arch::asm!(
"movz x0, #0xbadc, LSL 48",
"movk x0, #0x0deb, LSL 32",
"movk x0, #0xabef, LSL 16",
"movk x0, #0xffff",
"udf #0"
);
});
assert_eq!(ex.is_err(), true);
assert_eq!(
ex.as_ref().unwrap_err().code(),
ExceptionCode::IllegalInstruction
);
assert_eq!(ex.unwrap_err().registers().x0(), 0xbadc0debabefffff);
}
#[test]
fn ret_vals() {
let a = try_seh(|| 1337);
assert_eq!(a.unwrap(), 1337);
let b = try_seh(|| "hello");
assert_eq!(b.unwrap(), "hello");
let c = try_seh(|| {});
assert_eq!(core::mem::size_of_val(&c.unwrap()), 0x0);
}
}