From 90825299c93e9f7ec4d81ba067b1c921ef0e000f Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Sat, 24 Feb 2024 14:27:27 +0000 Subject: [PATCH] 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. --- src/shims/foreign_items.rs | 6 ++ src/shims/unix/foreign_items.rs | 12 ++++ tests/pass/panic/unwind_dwarf.rs | 95 ++++++++++++++++++++++++++++++++ 3 files changed, 113 insertions(+) create mode 100644 tests/pass/panic/unwind_dwarf.rs diff --git a/src/shims/foreign_items.rs b/src/shims/foreign_items.rs index 0645c1f176..9ccf567bdb 100644 --- a/src/shims/foreign_items.rs +++ b/src/shims/foreign_items.rs @@ -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. @@ -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)? { diff --git a/src/shims/unix/foreign_items.rs b/src/shims/unix/foreign_items.rs index b5cd18396a..b6107535ce 100644 --- a/src/shims/unix/foreign_items.rs +++ b/src/shims/unix/foreign_items.rs @@ -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; diff --git a/tests/pass/panic/unwind_dwarf.rs b/tests/pass/panic/unwind_dwarf.rs new file mode 100644 index 0000000000..aa5ec05fbc --- /dev/null +++ b/tests/pass/panic/unwind_dwarf.rs @@ -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, +} + +pub fn panic(data: Box) -> 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 { + let exception = ptr as *mut uw::_Unwind_Exception; + if (*exception).exception_class != rust_exception_class() { + std::process::abort(); + } + + let exception = exception.cast::(); + + 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: F) -> Result> { + struct Data { + f: Option, + r: Option, + p: Option>, + } + + 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::, data_ptr, do_catch::) == 0 { + Ok(data.r.take().unwrap()) + } else { + Err(data.p.take().unwrap()) + }; + } + + fn do_call R, R>(data: *mut u8) { + unsafe { + let data = &mut *data.cast::>(); + let f = data.f.take().unwrap(); + data.r = Some(f()); + } + } + + #[rustc_nounwind] + fn do_catch R, R>(data: *mut u8, payload: *mut u8) { + unsafe { + let obj = rust_panic_cleanup(payload); + (*data.cast::>()).p = Some(obj); + } + } +} + +fn main() { + assert_eq!( + catch_unwind(|| panic(Box::new(42))).unwrap_err().downcast::().unwrap(), + Box::new(42) + ); +}