1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
//! Exception-throw logic for Wasm exceptions.
use super::VMContext;
use crate::{
store::{InstanceId, StoreOpaque},
vm::Instance,
};
use core::ptr::NonNull;
use wasmtime_environ::{DefinedTagIndex, TagIndex};
use wasmtime_unwinder::{ExceptionTable, Frame, Handler};
/// Compute the target of the pending exception on the store.
///
/// # Safety
///
/// The stored last-exit state in `store` either must be valid, or
/// must have a zeroed exit FP if no Wasm is on the stack.
pub unsafe fn compute_handler(
store: &mut StoreOpaque,
throwing_tag_instance_id: InstanceId,
throwing_tag_defined_tag_index: DefinedTagIndex,
) -> Option<Handler> {
log::trace!(
"throwing: tag defined in instance {throwing_tag_instance_id:?} defined-tag {throwing_tag_defined_tag_index:?}"
);
// Get the state needed for a stack walk.
let (exit_pc, exit_fp, entry_fp) = unsafe {
(
*store.vm_store_context().last_wasm_exit_pc.get(),
store.vm_store_context().last_wasm_exit_fp(),
*store.vm_store_context().last_wasm_entry_fp.get(),
)
};
// Early out: if there is no exit FP -- which can happen if a host
// func, wrapped up as a `Func`, is called directly via
// `Func::call` -- then the only possible action we can take is
// `None` (i.e., no handler, unwind to entry from host).
if exit_fp == 0 {
return None;
}
// Walk the stack, looking up the module with each PC, and using
// that module to resolve local tag indices into (instance, tag)
// tuples.
let handler_lookup = |frame: &Frame| -> Option<(usize, usize)> {
log::trace!(
"exception-throw stack walk: frame at FP={:x} PC={:x}",
frame.fp(),
frame.pc()
);
let (store_code, rel_pc) = store.modules().store_code_by_pc(frame.pc())?;
let et = ExceptionTable::parse(store_code.code_memory().exception_tables())
.expect("Exception tables were validated on module load");
let (frame_offset, handlers) = et.lookup_pc(u32::try_from(rel_pc).unwrap());
let fp_to_sp = frame_offset.map(|frame_offset| -isize::try_from(frame_offset).unwrap());
for handler in handlers {
log::trace!("-> checking handler: {handler:?}");
let is_match = match handler.tag {
// Catch-all/default handler. Always come last in sequence.
None => true,
Some(module_local_tag_index) => {
let fp_to_sp =
fp_to_sp.expect("frame offset is necessary for exception unwind");
let fp_offset = fp_to_sp
+ isize::try_from(
handler
.context_sp_offset
.expect("dynamic context not present for handler record"),
)
.unwrap();
let frame_vmctx = unsafe { frame.read_slot_from_fp(fp_offset) };
log::trace!("-> read vmctx from frame: {frame_vmctx:x}");
let frame_vmctx =
NonNull::new(frame_vmctx as *mut VMContext).expect("null vmctx in frame");
// SAFETY: we use `Instance::from_vmctx` to get a
// `NonNull<Instance>` from a raw vmctx we read off the
// stack frame. That method's safety requirements are that
// the `vmctx` is a valid vmctx allocation which should be
// true of all frames on the stack.
//
// Next the `.as_ref()` call enables reading this pointer,
// and the validity of this relies on the fact that all wasm
// frames for this activation belong to the same store and
// there's no other active instance borrows at this time.
// This function takes `&mut dyn VMStore` representing no
// other active borrows, and internally the borrow is scoped
// to this one block.
let (handler_tag_instance, handler_tag_index) = unsafe {
let store_id = store.id();
let instance = Instance::from_vmctx(frame_vmctx);
let tag = instance
.as_ref()
.get_exported_tag(store_id, TagIndex::from_u32(module_local_tag_index));
tag.to_raw_indices()
};
log::trace!(
"-> handler's tag {module_local_tag_index:?} resolves to instance {handler_tag_instance:?} defined-tag {handler_tag_index:?}"
);
handler_tag_instance == throwing_tag_instance_id
&& handler_tag_index == throwing_tag_defined_tag_index
}
};
if is_match {
let fp_to_sp = fp_to_sp.expect("frame offset must be known if we found a handler");
return Some((
(store_code.text_range().start
+ usize::try_from(handler.handler_offset)
.expect("Module larger than usize"))
.raw(),
frame.fp().wrapping_add_signed(fp_to_sp),
));
}
}
None
};
let unwinder = store.unwinder();
let action = unsafe { Handler::find(unwinder, handler_lookup, exit_pc, exit_fp, entry_fp) };
log::trace!("throw action: {action:?}");
action
}