wasmtime_internal_unwinder/
throw.rs

1//! Generation of the throw-stub.
2//!
3//! In order to throw exceptions from within Cranelift-compiled code,
4//! we provide a runtime function helper meant to be called by host
5//! code that is invoked by guest code.
6//!
7//! The helper below must be provided a delimited range on the stack
8//! corresponding to Cranelift frames above the current host code. It
9//! will look for any handlers in this code, given a closure that
10//! knows how to use an absolute PC to look up a module's exception
11//! table and its start-of-code-segment. If a handler is found, the
12//! helper below will return the SP, FP and PC that must be
13//! restored. Architecture-specific helpers are provided to jump to
14//! this new context with payload values. Otherwise, if no handler is
15//! found, the return type indicates this, and it is the caller's
16//! responsibility to invoke alternative behavior (e.g., abort the
17//! program or unwind all the way to initial Cranelift-code entry).
18
19use crate::{ExceptionTable, Unwind};
20use core::ops::ControlFlow;
21
22/// Throw action to perform.
23#[derive(Clone, Debug)]
24pub enum ThrowAction {
25    /// Jump to the given handler with the given SP and FP values.
26    Handler {
27        /// Program counter of handler return point.
28        pc: usize,
29        /// Stack pointer to restore before jumping to handler.
30        sp: usize,
31        /// Frame pointer to restore before jumping to handler.
32        fp: usize,
33    },
34    /// No handler found.
35    None,
36}
37
38/// Implementation of stack-walking to find a handler.
39///
40/// This function searches for a handler in the given range of stack
41/// frames, starting from the throw stub and up to a specified entry
42/// frame.
43///
44/// # Safety
45///
46/// The safety of this function is the same as [`crate::visit_frames`] where the
47/// values passed in configuring the frame pointer walk must be correct and
48/// Wasm-defined for this to not have UB.
49pub unsafe fn compute_throw_action<'a, F: Fn(usize) -> Option<(usize, ExceptionTable<'a>)>>(
50    unwind: &dyn Unwind,
51    module_lookup: F,
52    exit_pc: usize,
53    exit_frame: usize,
54    entry_frame: usize,
55    tag: u32,
56) -> ThrowAction {
57    let mut last_fp = exit_frame;
58
59    // SAFETY: the safety of `visit_frames` relies on the correctness of the
60    // parameters passed in which is forwarded as a contract to this function
61    // tiself.
62    let result = unsafe {
63        crate::stackwalk::visit_frames(unwind, exit_pc, exit_frame, entry_frame, |frame| {
64            if let Some((base, table)) = module_lookup(frame.pc()) {
65                let relative_pc = u32::try_from(
66                    frame
67                        .pc()
68                        .checked_sub(base)
69                        .expect("module lookup did not return a module base below the PC"),
70                )
71                .expect("module larger than 4GiB");
72
73                if let Some(handler) = table.lookup(relative_pc, tag) {
74                    let abs_handler_pc = base
75                        .checked_add(usize::try_from(handler).unwrap())
76                        .expect("Handler address computation overflowed");
77
78                    return ControlFlow::Break(ThrowAction::Handler {
79                        pc: abs_handler_pc,
80                        sp: last_fp + unwind.next_older_sp_from_fp_offset(),
81                        fp: frame.fp(),
82                    });
83                }
84            }
85            last_fp = frame.fp();
86            ControlFlow::Continue(())
87        })
88    };
89    match result {
90        ControlFlow::Break(action) => action,
91        ControlFlow::Continue(()) => ThrowAction::None,
92    }
93}