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}