Skip to content

Commit

Permalink
Directly implement native exception raise methods in miri
Browse files Browse the repository at this point in the history
Windows still needs the old custom ABI as SEH unwinding isn't supported
by miri. Unlike DWARF unwinding it preserves all stack frames until
right after the do_catch function has executed. Because of this
panic_unwind stack allocates the exception object. Miri can't currently
model unwinding without destroying stack frames and as such will report
a use-after-free of the exception object.
  • Loading branch information
bjorn3 committed Feb 24, 2024
1 parent 92110c2 commit 9082529
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 0 deletions.
6 changes: 6 additions & 0 deletions src/shims/foreign_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ impl DynSym {
pub enum EmulateForeignItemResult {
/// The caller is expected to jump to the return block.
NeedsJumping,
/// The caller is expected to jump to the unwind block.
NeedsUnwind,
/// Jumping has already been taken care of.
AlreadyJumped,
/// The item is not supported.
Expand Down Expand Up @@ -126,6 +128,10 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
trace!("{:?}", this.dump_place(dest));
this.go_to_block(ret);
}
EmulateForeignItemResult::NeedsUnwind => {
// Jump to the unwind block to begin unwinding.
this.unwind_to_block(unwind)?;
}
EmulateForeignItemResult::AlreadyJumped => (),
EmulateForeignItemResult::NotSupported => {
if let Some(body) = this.lookup_exported_symbol(link_name)? {
Expand Down
12 changes: 12 additions & 0 deletions src/shims/unix/foreign_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -729,6 +729,18 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
this.write_scalar(Scalar::from_i32(-1), dest)?;
}

"_Unwind_RaiseException" => {
trace!("_Unwind_RaiseException: {:?}", this.frame().instance);

// Get the raw pointer stored in arg[0] (the panic payload).
let [payload] = this.check_shim(abi, Abi::C { unwind: true }, link_name, args)?;
let payload = this.read_scalar(payload)?;
let thread = this.active_thread_mut();
thread.panic_payloads.push(payload);

return Ok(EmulateForeignItemResult::NeedsUnwind);
}

// Platform-specific shims
_ => {
let target_os = &*this.tcx.sess.target.os;
Expand Down
95 changes: 95 additions & 0 deletions tests/pass/panic/unwind_dwarf.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
//@only-target-linux
#![feature(core_intrinsics, panic_unwind, rustc_attrs)]
#![allow(internal_features)]

//! Unwinding using `_Unwind_RaiseException`
extern crate unwind as uw;

use std::any::Any;
use std::ptr;

#[repr(C)]
struct Exception {
_uwe: uw::_Unwind_Exception,
cause: Box<dyn Any + Send>,
}

pub fn panic(data: Box<dyn Any + Send>) -> u32 {
let exception = Box::new(Exception {
_uwe: uw::_Unwind_Exception {
exception_class: rust_exception_class(),
exception_cleanup,
private: [core::ptr::null(); uw::unwinder_private_data_size],
},
cause: data,
});
let exception_param = Box::into_raw(exception) as *mut uw::_Unwind_Exception;
return unsafe { uw::_Unwind_RaiseException(exception_param) as u32 };

extern "C" fn exception_cleanup(
_unwind_code: uw::_Unwind_Reason_Code,
_exception: *mut uw::_Unwind_Exception,
) {
std::process::abort();
}
}

pub unsafe fn rust_panic_cleanup(ptr: *mut u8) -> Box<dyn Any + Send> {
let exception = ptr as *mut uw::_Unwind_Exception;
if (*exception).exception_class != rust_exception_class() {
std::process::abort();
}

let exception = exception.cast::<Exception>();

let exception = Box::from_raw(exception as *mut Exception);
exception.cause
}

fn rust_exception_class() -> uw::_Unwind_Exception_Class {
// M O Z \0 R U S T -- vendor, language
0x4d4f5a_00_52555354
}

pub fn catch_unwind<R, F: FnOnce() -> R>(f: F) -> Result<R, Box<dyn Any + Send>> {
struct Data<F, R> {
f: Option<F>,
r: Option<R>,
p: Option<Box<dyn Any + Send>>,
}

let mut data = Data { f: Some(f), r: None, p: None };

let data_ptr = ptr::addr_of_mut!(data) as *mut u8;
unsafe {
return if std::intrinsics::r#try(do_call::<F, R>, data_ptr, do_catch::<F, R>) == 0 {
Ok(data.r.take().unwrap())
} else {
Err(data.p.take().unwrap())
};
}

fn do_call<F: FnOnce() -> R, R>(data: *mut u8) {
unsafe {
let data = &mut *data.cast::<Data<F, R>>();
let f = data.f.take().unwrap();
data.r = Some(f());
}
}

#[rustc_nounwind]
fn do_catch<F: FnOnce() -> R, R>(data: *mut u8, payload: *mut u8) {
unsafe {
let obj = rust_panic_cleanup(payload);
(*data.cast::<Data<F, R>>()).p = Some(obj);
}
}
}

fn main() {
assert_eq!(
catch_unwind(|| panic(Box::new(42))).unwrap_err().downcast::<i32>().unwrap(),
Box::new(42)
);
}

0 comments on commit 9082529

Please sign in to comment.