From e40f8b26ada6f517235fd5813231adbb79e58ced Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sun, 6 Jul 2025 16:51:14 +0200 Subject: [PATCH 1/2] =?UTF-8?q?rename=20panic=5Fpaylods=20=E2=86=92=20unwi?= =?UTF-8?q?nd=5Fpayloads?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tools/miri/src/concurrency/thread.rs | 10 +++++----- src/tools/miri/src/shims/panic.rs | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/tools/miri/src/concurrency/thread.rs b/src/tools/miri/src/concurrency/thread.rs index 56c197948546..ec65ea685e1f 100644 --- a/src/tools/miri/src/concurrency/thread.rs +++ b/src/tools/miri/src/concurrency/thread.rs @@ -186,15 +186,15 @@ pub struct Thread<'tcx> { /// The join status. join_status: ThreadJoinStatus, - /// Stack of active panic payloads for the current thread. Used for storing - /// the argument of the call to `miri_start_unwind` (the panic payload) when unwinding. + /// Stack of active unwind payloads for the current thread. Used for storing + /// the argument of the call to `miri_start_unwind` (the payload) when unwinding. /// This is pointer-sized, and matches the `Payload` type in `src/libpanic_unwind/miri.rs`. /// /// In real unwinding, the payload gets passed as an argument to the landing pad, /// which then forwards it to 'Resume'. However this argument is implicit in MIR, /// so we have to store it out-of-band. When there are multiple active unwinds, /// the innermost one is always caught first, so we can store them as a stack. - pub(crate) panic_payloads: Vec>, + pub(crate) unwind_payloads: Vec>, /// Last OS error location in memory. It is a 32-bit integer. pub(crate) last_error: Option>, @@ -282,7 +282,7 @@ impl<'tcx> Thread<'tcx> { stack: Vec::new(), top_user_relevant_frame: None, join_status: ThreadJoinStatus::Joinable, - panic_payloads: Vec::new(), + unwind_payloads: Vec::new(), last_error: None, on_stack_empty, } @@ -292,7 +292,7 @@ impl<'tcx> Thread<'tcx> { impl VisitProvenance for Thread<'_> { fn visit_provenance(&self, visit: &mut VisitWith<'_>) { let Thread { - panic_payloads: panic_payload, + unwind_payloads: panic_payload, last_error, stack, top_user_relevant_frame: _, diff --git a/src/tools/miri/src/shims/panic.rs b/src/tools/miri/src/shims/panic.rs index a6bce8301491..b3b7b6ae7187 100644 --- a/src/tools/miri/src/shims/panic.rs +++ b/src/tools/miri/src/shims/panic.rs @@ -51,7 +51,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let payload = this.read_immediate(payload)?; let thread = this.active_thread_mut(); - thread.panic_payloads.push(payload); + thread.unwind_payloads.push(payload); interp_ok(()) } @@ -132,7 +132,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // The Thread's `panic_payload` holds what was passed to `miri_start_unwind`. // This is exactly the second argument we need to pass to `catch_fn`. - let payload = this.active_thread_mut().panic_payloads.pop().unwrap(); + let payload = this.active_thread_mut().unwind_payloads.pop().unwrap(); // Push the `catch_fn` stackframe. let f_instance = this.get_ptr_fn(catch_unwind.catch_fn)?.as_instance()?; From ae3b9765f4510362b3e7ce7e1a62841b2cf5887d Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Mon, 7 Jul 2025 08:18:32 +0200 Subject: [PATCH 2/2] split unwinding logic from panic logic --- src/tools/miri/src/lib.rs | 3 +- src/tools/miri/src/shims/mod.rs | 1 + src/tools/miri/src/shims/panic.rs | 152 +-------------------------- src/tools/miri/src/shims/unwind.rs | 160 +++++++++++++++++++++++++++++ 4 files changed, 164 insertions(+), 152 deletions(-) create mode 100644 src/tools/miri/src/shims/unwind.rs diff --git a/src/tools/miri/src/lib.rs b/src/tools/miri/src/lib.rs index c86e33e51859..ca99d69b32d0 100644 --- a/src/tools/miri/src/lib.rs +++ b/src/tools/miri/src/lib.rs @@ -154,9 +154,10 @@ pub use crate::shims::env::{EnvVars, EvalContextExt as _}; pub use crate::shims::foreign_items::{DynSym, EvalContextExt as _}; pub use crate::shims::io_error::{EvalContextExt as _, IoError, LibcError}; pub use crate::shims::os_str::EvalContextExt as _; -pub use crate::shims::panic::{CatchUnwindData, EvalContextExt as _}; +pub use crate::shims::panic::EvalContextExt as _; pub use crate::shims::time::EvalContextExt as _; pub use crate::shims::tls::TlsData; +pub use crate::shims::unwind::{CatchUnwindData, EvalContextExt as _}; /// Insert rustc arguments at the beginning of the argument list that Miri wants to be /// set per default, for maximal validation power. diff --git a/src/tools/miri/src/shims/mod.rs b/src/tools/miri/src/shims/mod.rs index f09fc546b3eb..60c0e289292b 100644 --- a/src/tools/miri/src/shims/mod.rs +++ b/src/tools/miri/src/shims/mod.rs @@ -19,6 +19,7 @@ pub mod os_str; pub mod panic; pub mod time; pub mod tls; +pub mod unwind; pub use self::files::FdTable; //#[cfg(target_os = "linux")] diff --git a/src/tools/miri/src/shims/panic.rs b/src/tools/miri/src/shims/panic.rs index b3b7b6ae7187..5fcb783d6886 100644 --- a/src/tools/miri/src/shims/panic.rs +++ b/src/tools/miri/src/shims/panic.rs @@ -1,162 +1,12 @@ -//! Panic runtime for Miri. -//! -//! The core pieces of the runtime are: -//! - An implementation of `__rust_maybe_catch_panic` that pushes the invoked stack frame with -//! some extra metadata derived from the panic-catching arguments of `__rust_maybe_catch_panic`. -//! - A hack in `libpanic_unwind` that calls the `miri_start_unwind` intrinsic instead of the -//! target-native panic runtime. (This lives in the rustc repo.) -//! - An implementation of `miri_start_unwind` that stores its argument (the panic payload), and then -//! immediately returns, but on the *unwind* edge (not the normal return edge), thus initiating unwinding. -//! - A hook executed each time a frame is popped, such that if the frame pushed by `__rust_maybe_catch_panic` -//! gets popped *during unwinding*, we take the panic payload and store it according to the extra -//! metadata we remembered when pushing said frame. +//! Helper functions for causing panics. use rustc_abi::ExternAbi; use rustc_middle::{mir, ty}; -use rustc_target::spec::PanicStrategy; -use self::helpers::check_intrinsic_arg_count; use crate::*; -/// Holds all of the relevant data for when unwinding hits a `try` frame. -#[derive(Debug)] -pub struct CatchUnwindData<'tcx> { - /// The `catch_fn` callback to call in case of a panic. - catch_fn: Pointer, - /// The `data` argument for that callback. - data: ImmTy<'tcx>, - /// The return place from the original call to `try`. - dest: MPlaceTy<'tcx>, - /// The return block from the original call to `try`. - ret: Option, -} - -impl VisitProvenance for CatchUnwindData<'_> { - fn visit_provenance(&self, visit: &mut VisitWith<'_>) { - let CatchUnwindData { catch_fn, data, dest, ret: _ } = self; - catch_fn.visit_provenance(visit); - data.visit_provenance(visit); - dest.visit_provenance(visit); - } -} - impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {} pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { - /// Handles the special `miri_start_unwind` intrinsic, which is called - /// by libpanic_unwind to delegate the actual unwinding process to Miri. - fn handle_miri_start_unwind(&mut self, payload: &OpTy<'tcx>) -> InterpResult<'tcx> { - let this = self.eval_context_mut(); - - trace!("miri_start_unwind: {:?}", this.frame().instance()); - - let payload = this.read_immediate(payload)?; - let thread = this.active_thread_mut(); - thread.unwind_payloads.push(payload); - - interp_ok(()) - } - - /// Handles the `catch_unwind` intrinsic. - fn handle_catch_unwind( - &mut self, - args: &[OpTy<'tcx>], - dest: &MPlaceTy<'tcx>, - ret: Option, - ) -> InterpResult<'tcx> { - let this = self.eval_context_mut(); - - // Signature: - // fn catch_unwind(try_fn: fn(*mut u8), data: *mut u8, catch_fn: fn(*mut u8, *mut u8)) -> i32 - // Calls `try_fn` with `data` as argument. If that executes normally, returns 0. - // If that unwinds, calls `catch_fn` with the first argument being `data` and - // then second argument being a target-dependent `payload` (i.e. it is up to us to define - // what that is), and returns 1. - // The `payload` is passed (by libstd) to `__rust_panic_cleanup`, which is then expected to - // return a `Box`. - // In Miri, `miri_start_unwind` is passed exactly that type, so we make the `payload` simply - // a pointer to `Box`. - - // Get all the arguments. - let [try_fn, data, catch_fn] = check_intrinsic_arg_count(args)?; - let try_fn = this.read_pointer(try_fn)?; - let data = this.read_immediate(data)?; - let catch_fn = this.read_pointer(catch_fn)?; - - // Now we make a function call, and pass `data` as first and only argument. - let f_instance = this.get_ptr_fn(try_fn)?.as_instance()?; - trace!("try_fn: {:?}", f_instance); - #[allow(clippy::cloned_ref_to_slice_refs)] // the code is clearer as-is - this.call_function( - f_instance, - ExternAbi::Rust, - &[data.clone()], - None, - // Directly return to caller. - StackPopCleanup::Goto { ret, unwind: mir::UnwindAction::Continue }, - )?; - - // We ourselves will return `0`, eventually (will be overwritten if we catch a panic). - this.write_null(dest)?; - - // In unwind mode, we tag this frame with the extra data needed to catch unwinding. - // This lets `handle_stack_pop` (below) know that we should stop unwinding - // when we pop this frame. - if this.tcx.sess.panic_strategy() == PanicStrategy::Unwind { - this.frame_mut().extra.catch_unwind = - Some(CatchUnwindData { catch_fn, data, dest: dest.clone(), ret }); - } - - interp_ok(()) - } - - fn handle_stack_pop_unwind( - &mut self, - mut extra: FrameExtra<'tcx>, - unwinding: bool, - ) -> InterpResult<'tcx, ReturnAction> { - let this = self.eval_context_mut(); - trace!("handle_stack_pop_unwind(extra = {:?}, unwinding = {})", extra, unwinding); - - // We only care about `catch_panic` if we're unwinding - if we're doing a normal - // return, then we don't need to do anything special. - if let (true, Some(catch_unwind)) = (unwinding, extra.catch_unwind.take()) { - // We've just popped a frame that was pushed by `catch_unwind`, - // and we are unwinding, so we should catch that. - trace!( - "unwinding: found catch_panic frame during unwinding: {:?}", - this.frame().instance() - ); - - // We set the return value of `catch_unwind` to 1, since there was a panic. - this.write_scalar(Scalar::from_i32(1), &catch_unwind.dest)?; - - // The Thread's `panic_payload` holds what was passed to `miri_start_unwind`. - // This is exactly the second argument we need to pass to `catch_fn`. - let payload = this.active_thread_mut().unwind_payloads.pop().unwrap(); - - // Push the `catch_fn` stackframe. - let f_instance = this.get_ptr_fn(catch_unwind.catch_fn)?.as_instance()?; - trace!("catch_fn: {:?}", f_instance); - this.call_function( - f_instance, - ExternAbi::Rust, - &[catch_unwind.data, payload], - None, - // Directly return to caller of `catch_unwind`. - StackPopCleanup::Goto { - ret: catch_unwind.ret, - // `catch_fn` must not unwind. - unwind: mir::UnwindAction::Unreachable, - }, - )?; - - // We pushed a new stack frame, the engine should not do any jumping now! - interp_ok(ReturnAction::NoJump) - } else { - interp_ok(ReturnAction::Normal) - } - } - /// Start a panic in the interpreter with the given message as payload. fn start_panic(&mut self, msg: &str, unwind: mir::UnwindAction) -> InterpResult<'tcx> { let this = self.eval_context_mut(); diff --git a/src/tools/miri/src/shims/unwind.rs b/src/tools/miri/src/shims/unwind.rs new file mode 100644 index 000000000000..7cbbbb7f4d71 --- /dev/null +++ b/src/tools/miri/src/shims/unwind.rs @@ -0,0 +1,160 @@ +//! Unwinding runtime for Miri. +//! +//! The core pieces of the runtime are: +//! - An implementation of `catch_unwind` that pushes the invoked stack frame with +//! some extra metadata derived from the panic-catching arguments of `catch_unwind`. +//! - A hack in `libpanic_unwind` that calls the `miri_start_unwind` intrinsic instead of the +//! target-native panic runtime. (This lives in the rustc repo.) +//! - An implementation of `miri_start_unwind` that stores its argument (the panic payload), and +//! then immediately returns, but on the *unwind* edge (not the normal return edge), thus +//! initiating unwinding. +//! - A hook executed each time a frame is popped, such that if the frame pushed by `catch_unwind` +//! gets popped *during unwinding*, we take the panic payload and store it according to the extra +//! metadata we remembered when pushing said frame. + +use rustc_abi::ExternAbi; +use rustc_middle::mir; +use rustc_target::spec::PanicStrategy; + +use self::helpers::check_intrinsic_arg_count; +use crate::*; + +/// Holds all of the relevant data for when unwinding hits a `try` frame. +#[derive(Debug)] +pub struct CatchUnwindData<'tcx> { + /// The `catch_fn` callback to call in case of a panic. + catch_fn: Pointer, + /// The `data` argument for that callback. + data: ImmTy<'tcx>, + /// The return place from the original call to `try`. + dest: MPlaceTy<'tcx>, + /// The return block from the original call to `try`. + ret: Option, +} + +impl VisitProvenance for CatchUnwindData<'_> { + fn visit_provenance(&self, visit: &mut VisitWith<'_>) { + let CatchUnwindData { catch_fn, data, dest, ret: _ } = self; + catch_fn.visit_provenance(visit); + data.visit_provenance(visit); + dest.visit_provenance(visit); + } +} + +impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {} +pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { + /// Handles the special `miri_start_unwind` intrinsic, which is called + /// by libpanic_unwind to delegate the actual unwinding process to Miri. + fn handle_miri_start_unwind(&mut self, payload: &OpTy<'tcx>) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + + trace!("miri_start_unwind: {:?}", this.frame().instance()); + + let payload = this.read_immediate(payload)?; + let thread = this.active_thread_mut(); + thread.unwind_payloads.push(payload); + + interp_ok(()) + } + + /// Handles the `catch_unwind` intrinsic. + fn handle_catch_unwind( + &mut self, + args: &[OpTy<'tcx>], + dest: &MPlaceTy<'tcx>, + ret: Option, + ) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + + // Signature: + // fn catch_unwind(try_fn: fn(*mut u8), data: *mut u8, catch_fn: fn(*mut u8, *mut u8)) -> i32 + // Calls `try_fn` with `data` as argument. If that executes normally, returns 0. + // If that unwinds, calls `catch_fn` with the first argument being `data` and + // then second argument being a target-dependent `payload` (i.e. it is up to us to define + // what that is), and returns 1. + // The `payload` is passed (by libstd) to `__rust_panic_cleanup`, which is then expected to + // return a `Box`. + // In Miri, `miri_start_unwind` is passed exactly that type, so we make the `payload` simply + // a pointer to `Box`. + + // Get all the arguments. + let [try_fn, data, catch_fn] = check_intrinsic_arg_count(args)?; + let try_fn = this.read_pointer(try_fn)?; + let data = this.read_immediate(data)?; + let catch_fn = this.read_pointer(catch_fn)?; + + // Now we make a function call, and pass `data` as first and only argument. + let f_instance = this.get_ptr_fn(try_fn)?.as_instance()?; + trace!("try_fn: {:?}", f_instance); + #[allow(clippy::cloned_ref_to_slice_refs)] // the code is clearer as-is + this.call_function( + f_instance, + ExternAbi::Rust, + &[data.clone()], + None, + // Directly return to caller. + StackPopCleanup::Goto { ret, unwind: mir::UnwindAction::Continue }, + )?; + + // We ourselves will return `0`, eventually (will be overwritten if we catch a panic). + this.write_null(dest)?; + + // In unwind mode, we tag this frame with the extra data needed to catch unwinding. + // This lets `handle_stack_pop` (below) know that we should stop unwinding + // when we pop this frame. + if this.tcx.sess.panic_strategy() == PanicStrategy::Unwind { + this.frame_mut().extra.catch_unwind = + Some(CatchUnwindData { catch_fn, data, dest: dest.clone(), ret }); + } + + interp_ok(()) + } + + fn handle_stack_pop_unwind( + &mut self, + mut extra: FrameExtra<'tcx>, + unwinding: bool, + ) -> InterpResult<'tcx, ReturnAction> { + let this = self.eval_context_mut(); + trace!("handle_stack_pop_unwind(extra = {:?}, unwinding = {})", extra, unwinding); + + // We only care about `catch_panic` if we're unwinding - if we're doing a normal + // return, then we don't need to do anything special. + if let (true, Some(catch_unwind)) = (unwinding, extra.catch_unwind.take()) { + // We've just popped a frame that was pushed by `catch_unwind`, + // and we are unwinding, so we should catch that. + trace!( + "unwinding: found catch_panic frame during unwinding: {:?}", + this.frame().instance() + ); + + // We set the return value of `catch_unwind` to 1, since there was a panic. + this.write_scalar(Scalar::from_i32(1), &catch_unwind.dest)?; + + // The Thread's `panic_payload` holds what was passed to `miri_start_unwind`. + // This is exactly the second argument we need to pass to `catch_fn`. + let payload = this.active_thread_mut().unwind_payloads.pop().unwrap(); + + // Push the `catch_fn` stackframe. + let f_instance = this.get_ptr_fn(catch_unwind.catch_fn)?.as_instance()?; + trace!("catch_fn: {:?}", f_instance); + this.call_function( + f_instance, + ExternAbi::Rust, + &[catch_unwind.data, payload], + None, + // Directly return to caller of `catch_unwind`. + StackPopCleanup::Goto { + ret: catch_unwind.ret, + // `catch_fn` must not unwind. + unwind: mir::UnwindAction::Unreachable, + }, + )?; + + // We pushed a new stack frame, the engine should not do any jumping now! + interp_ok(ReturnAction::NoJump) + } else { + interp_ok(ReturnAction::Normal) + } + } +}