Skip to content

Commit 42cb1ff

Browse files
bjorn3RalfJung
authored andcommitted
Directly implement native exception raise methods in miri
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.
1 parent f7520e4 commit 42cb1ff

File tree

5 files changed

+118
-1
lines changed

5 files changed

+118
-1
lines changed

src/tools/miri/src/intrinsics/mod.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
2626
args: &[OpTy<'tcx, Provenance>],
2727
dest: &MPlaceTy<'tcx, Provenance>,
2828
ret: Option<mir::BasicBlock>,
29-
_unwind: mir::UnwindAction,
29+
unwind: mir::UnwindAction,
3030
) -> InterpResult<'tcx, Option<ty::Instance<'tcx>>> {
3131
let this = self.eval_context_mut();
3232

@@ -67,6 +67,11 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
6767
this.return_to_block(ret)?;
6868
Ok(None)
6969
}
70+
EmulateItemResult::NeedsUnwind => {
71+
// Jump to the unwind block to begin unwinding.
72+
this.unwind_to_block(unwind)?;
73+
Ok(None)
74+
}
7075
EmulateItemResult::AlreadyJumped => Ok(None),
7176
}
7277
}

src/tools/miri/src/shims/foreign_items.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,10 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
8787
trace!("{:?}", this.dump_place(&dest.clone().into()));
8888
this.return_to_block(ret)?;
8989
}
90+
EmulateItemResult::NeedsUnwind => {
91+
// Jump to the unwind block to begin unwinding.
92+
this.unwind_to_block(unwind)?;
93+
}
9094
EmulateItemResult::AlreadyJumped => (),
9195
EmulateItemResult::NotSupported => {
9296
if let Some(body) = this.lookup_exported_symbol(link_name)? {

src/tools/miri/src/shims/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ pub use unix::{DirTable, FdTable};
2323
pub enum EmulateItemResult {
2424
/// The caller is expected to jump to the return block.
2525
NeedsJumping,
26+
/// The caller is expected to jump to the unwind block.
27+
NeedsUnwind,
2628
/// Jumping has already been taken care of.
2729
AlreadyJumped,
2830
/// The item is not supported.

src/tools/miri/src/shims/unix/foreign_items.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -639,6 +639,17 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
639639
this.gen_random(ptr, len)?;
640640
this.write_scalar(Scalar::from_target_usize(len, this), dest)?;
641641
}
642+
"_Unwind_RaiseException" => {
643+
trace!("_Unwind_RaiseException: {:?}", this.frame().instance);
644+
645+
// Get the raw pointer stored in arg[0] (the panic payload).
646+
let [payload] = this.check_shim(abi, Abi::C { unwind: true }, link_name, args)?;
647+
let payload = this.read_scalar(payload)?;
648+
let thread = this.active_thread_mut();
649+
thread.panic_payloads.push(payload);
650+
651+
return Ok(EmulateItemResult::NeedsUnwind);
652+
}
642653

643654
// Incomplete shims that we "stub out" just to get pre-main initialization code to work.
644655
// These shims are enabled only when the caller is in the standard library.
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
//@only-target-linux
2+
#![feature(core_intrinsics, panic_unwind, rustc_attrs)]
3+
#![allow(internal_features)]
4+
5+
//! Unwinding using `_Unwind_RaiseException`
6+
7+
extern crate unwind as uw;
8+
9+
use std::any::Any;
10+
use std::ptr;
11+
12+
#[repr(C)]
13+
struct Exception {
14+
_uwe: uw::_Unwind_Exception,
15+
cause: Box<dyn Any + Send>,
16+
}
17+
18+
pub fn panic(data: Box<dyn Any + Send>) -> u32 {
19+
let exception = Box::new(Exception {
20+
_uwe: uw::_Unwind_Exception {
21+
exception_class: rust_exception_class(),
22+
exception_cleanup,
23+
private: [core::ptr::null(); uw::unwinder_private_data_size],
24+
},
25+
cause: data,
26+
});
27+
let exception_param = Box::into_raw(exception) as *mut uw::_Unwind_Exception;
28+
return unsafe { uw::_Unwind_RaiseException(exception_param) as u32 };
29+
30+
extern "C" fn exception_cleanup(
31+
_unwind_code: uw::_Unwind_Reason_Code,
32+
_exception: *mut uw::_Unwind_Exception,
33+
) {
34+
std::process::abort();
35+
}
36+
}
37+
38+
pub unsafe fn rust_panic_cleanup(ptr: *mut u8) -> Box<dyn Any + Send> {
39+
let exception = ptr as *mut uw::_Unwind_Exception;
40+
if (*exception).exception_class != rust_exception_class() {
41+
std::process::abort();
42+
}
43+
44+
let exception = exception.cast::<Exception>();
45+
46+
let exception = Box::from_raw(exception as *mut Exception);
47+
exception.cause
48+
}
49+
50+
fn rust_exception_class() -> uw::_Unwind_Exception_Class {
51+
// M O Z \0 R U S T -- vendor, language
52+
0x4d4f5a_00_52555354
53+
}
54+
55+
pub fn catch_unwind<R, F: FnOnce() -> R>(f: F) -> Result<R, Box<dyn Any + Send>> {
56+
struct Data<F, R> {
57+
f: Option<F>,
58+
r: Option<R>,
59+
p: Option<Box<dyn Any + Send>>,
60+
}
61+
62+
let mut data = Data { f: Some(f), r: None, p: None };
63+
64+
let data_ptr = ptr::addr_of_mut!(data) as *mut u8;
65+
unsafe {
66+
return if std::intrinsics::r#try(do_call::<F, R>, data_ptr, do_catch::<F, R>) == 0 {
67+
Ok(data.r.take().unwrap())
68+
} else {
69+
Err(data.p.take().unwrap())
70+
};
71+
}
72+
73+
fn do_call<F: FnOnce() -> R, R>(data: *mut u8) {
74+
unsafe {
75+
let data = &mut *data.cast::<Data<F, R>>();
76+
let f = data.f.take().unwrap();
77+
data.r = Some(f());
78+
}
79+
}
80+
81+
#[rustc_nounwind]
82+
fn do_catch<F: FnOnce() -> R, R>(data: *mut u8, payload: *mut u8) {
83+
unsafe {
84+
let obj = rust_panic_cleanup(payload);
85+
(*data.cast::<Data<F, R>>()).p = Some(obj);
86+
}
87+
}
88+
}
89+
90+
fn main() {
91+
assert_eq!(
92+
catch_unwind(|| panic(Box::new(42))).unwrap_err().downcast::<i32>().unwrap(),
93+
Box::new(42)
94+
);
95+
}

0 commit comments

Comments
 (0)