Skip to content

Commit 07eeac4

Browse files
authored
Implement custom stack unwinding for Pulley (#9758)
* Implement custom stack unwinding for Pulley The pulley calling convention may not match the native calling convention, which is the case for s390x, so the unwinding that Wasmtime does needs to be parameterized over what's being unwound. This commit adds a new `Unwind` trait with various methods behind it that the backtrace implementation is then updated to use. * Fix alignment check for 32-bit
1 parent 1f81262 commit 07eeac4

File tree

8 files changed

+97
-23
lines changed

8 files changed

+97
-23
lines changed

crates/wasmtime/src/runtime/profiling.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ impl GuestProfiler {
137137
let now = Timestamp::from_nanos_since_reference(
138138
self.start.elapsed().as_nanos().try_into().unwrap(),
139139
);
140-
let backtrace = Backtrace::new(store.as_context().0.vmruntime_limits());
140+
let backtrace = Backtrace::new(store.as_context().0);
141141
let frames = lookup_frames(&self.modules, &backtrace);
142142
self.profile
143143
.add_sample(self.thread, now, frames, delta.into(), 1);
@@ -154,7 +154,7 @@ impl GuestProfiler {
154154
match kind {
155155
CallHook::CallingWasm | CallHook::ReturningFromWasm => {}
156156
CallHook::CallingHost => {
157-
let backtrace = Backtrace::new(store.as_context().0.vmruntime_limits());
157+
let backtrace = Backtrace::new(store.as_context().0);
158158
let frames = lookup_frames(&self.modules, &backtrace);
159159
self.profile.add_marker_with_stack(
160160
self.thread,

crates/wasmtime/src/runtime/store.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,8 @@ use crate::runtime::vm::mpk::{self, ProtectionKey, ProtectionMask};
8585
use crate::runtime::vm::{
8686
Backtrace, ExportGlobal, GcRootsList, GcStore, InstanceAllocationRequest, InstanceAllocator,
8787
InstanceHandle, Interpreter, InterpreterRef, ModuleRuntimeInfo, OnDemandInstanceAllocator,
88-
SignalHandler, StoreBox, StorePtr, VMContext, VMFuncRef, VMGcRef, VMRuntimeLimits,
88+
SignalHandler, StoreBox, StorePtr, Unwind, UnwindHost, UnwindPulley, VMContext, VMFuncRef,
89+
VMGcRef, VMRuntimeLimits,
8990
};
9091
use crate::trampoline::VMHostGlobalContext;
9192
use crate::type_registry::RegisteredType;
@@ -1739,7 +1740,7 @@ impl StoreOpaque {
17391740

17401741
log::trace!("Begin trace GC roots :: Wasm stack");
17411742

1742-
Backtrace::trace(self.vmruntime_limits().cast_const(), |frame| {
1743+
Backtrace::trace(self, |frame| {
17431744
let pc = frame.pc();
17441745
debug_assert!(pc != 0, "we should always get a valid PC for Wasm frames");
17451746

@@ -2148,6 +2149,14 @@ at https://bytecodealliance.org/security.
21482149
let i = self.interpreter.as_mut()?;
21492150
Some(i.as_interpreter_ref())
21502151
}
2152+
2153+
pub(crate) fn unwinder(&self) -> &'static dyn Unwind {
2154+
if self.interpreter.is_some() {
2155+
&UnwindPulley
2156+
} else {
2157+
&UnwindHost
2158+
}
2159+
}
21512160
}
21522161

21532162
impl<T> StoreContextMut<'_, T> {

crates/wasmtime/src/runtime/trap.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -257,11 +257,7 @@ impl WasmBacktrace {
257257
/// always captures a backtrace.
258258
pub fn force_capture(store: impl AsContext) -> WasmBacktrace {
259259
let store = store.as_context();
260-
Self::from_captured(
261-
store.0,
262-
crate::runtime::vm::Backtrace::new(store.0.runtime_limits()),
263-
None,
264-
)
260+
Self::from_captured(store.0, crate::runtime::vm::Backtrace::new(store.0), None)
265261
}
266262

267263
fn from_captured(

crates/wasmtime/src/runtime/vm.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ mod store_box;
3535
mod sys;
3636
mod table;
3737
mod traphandlers;
38+
mod unwind;
3839
mod vmcontext;
3940

4041
#[cfg(feature = "threads")]
@@ -82,6 +83,7 @@ pub use crate::runtime::vm::sys::mmap::open_file_for_mmap;
8283
pub use crate::runtime::vm::sys::unwind::UnwindRegistration;
8384
pub use crate::runtime::vm::table::{Table, TableElement};
8485
pub use crate::runtime::vm::traphandlers::*;
86+
pub use crate::runtime::vm::unwind::*;
8587
pub use crate::runtime::vm::vmcontext::{
8688
VMArrayCallFunction, VMArrayCallHostFuncContext, VMContext, VMFuncRef, VMFunctionBody,
8789
VMFunctionImport, VMGlobalDefinition, VMGlobalImport, VMMemoryDefinition, VMMemoryImport,

crates/wasmtime/src/runtime/vm/traphandlers.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,7 @@ where
407407
// usage of its accessor methods.
408408
mod call_thread_state {
409409
use super::*;
410+
use crate::runtime::vm::Unwind;
410411

411412
/// Temporary state stored on the stack which is registered in the `tls` module
412413
/// below for calls into wasm.
@@ -420,6 +421,7 @@ mod call_thread_state {
420421
pub(super) capture_coredump: bool,
421422

422423
pub(crate) limits: *const VMRuntimeLimits,
424+
pub(crate) unwinder: &'static dyn Unwind,
423425

424426
pub(super) prev: Cell<tls::Ptr>,
425427
#[cfg(all(feature = "signals-based-traps", unix, not(miri)))]
@@ -465,6 +467,7 @@ mod call_thread_state {
465467

466468
CallThreadState {
467469
unwind: Cell::new(None),
470+
unwinder: store.unwinder(),
468471
jmp_buf: Cell::new(ptr::null()),
469472
#[cfg(all(feature = "signals-based-traps", not(miri)))]
470473
signal_handler: store.signal_handler(),
@@ -608,7 +611,7 @@ impl CallThreadState {
608611
return None;
609612
}
610613

611-
Some(unsafe { Backtrace::new_with_trap_state(limits, self, trap_pc_and_fp) })
614+
Some(unsafe { Backtrace::new_with_trap_state(limits, self.unwinder, self, trap_pc_and_fp) })
612615
}
613616

614617
pub(crate) fn iter<'a>(&'a self) -> impl Iterator<Item = &'a Self> + 'a {

crates/wasmtime/src/runtime/vm/traphandlers/backtrace.rs

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@
2222
//! frame is a host frame).
2323
2424
use crate::prelude::*;
25-
use crate::runtime::vm::arch;
25+
use crate::runtime::store::StoreOpaque;
2626
use crate::runtime::vm::{
2727
traphandlers::{tls, CallThreadState},
28-
VMRuntimeLimits,
28+
Unwind, VMRuntimeLimits,
2929
};
3030
use core::ops::ControlFlow;
3131

@@ -59,9 +59,11 @@ impl Backtrace {
5959
}
6060

6161
/// Capture the current Wasm stack in a backtrace.
62-
pub fn new(limits: *const VMRuntimeLimits) -> Backtrace {
62+
pub fn new(store: &StoreOpaque) -> Backtrace {
63+
let limits = store.runtime_limits();
64+
let unwind = store.unwinder();
6365
tls::with(|state| match state {
64-
Some(state) => unsafe { Self::new_with_trap_state(limits, state, None) },
66+
Some(state) => unsafe { Self::new_with_trap_state(limits, unwind, state, None) },
6567
None => Backtrace(vec![]),
6668
})
6769
}
@@ -73,21 +75,24 @@ impl Backtrace {
7375
/// instead of looking them up in `VMRuntimeLimits`.
7476
pub(crate) unsafe fn new_with_trap_state(
7577
limits: *const VMRuntimeLimits,
78+
unwind: &dyn Unwind,
7679
state: &CallThreadState,
7780
trap_pc_and_fp: Option<(usize, usize)>,
7881
) -> Backtrace {
7982
let mut frames = vec![];
80-
Self::trace_with_trap_state(limits, state, trap_pc_and_fp, |frame| {
83+
Self::trace_with_trap_state(limits, unwind, state, trap_pc_and_fp, |frame| {
8184
frames.push(frame);
8285
ControlFlow::Continue(())
8386
});
8487
Backtrace(frames)
8588
}
8689

8790
/// Walk the current Wasm stack, calling `f` for each frame we walk.
88-
pub fn trace(limits: *const VMRuntimeLimits, f: impl FnMut(Frame) -> ControlFlow<()>) {
91+
pub fn trace(store: &StoreOpaque, f: impl FnMut(Frame) -> ControlFlow<()>) {
92+
let limits = store.runtime_limits();
93+
let unwind = store.unwinder();
8994
tls::with(|state| match state {
90-
Some(state) => unsafe { Self::trace_with_trap_state(limits, state, None, f) },
95+
Some(state) => unsafe { Self::trace_with_trap_state(limits, unwind, state, None, f) },
9196
None => {}
9297
});
9398
}
@@ -99,6 +104,7 @@ impl Backtrace {
99104
/// instead of looking them up in `VMRuntimeLimits`.
100105
pub(crate) unsafe fn trace_with_trap_state(
101106
limits: *const VMRuntimeLimits,
107+
unwind: &dyn Unwind,
102108
state: &CallThreadState,
103109
trap_pc_and_fp: Option<(usize, usize)>,
104110
mut f: impl FnMut(Frame) -> ControlFlow<()>,
@@ -148,7 +154,7 @@ impl Backtrace {
148154
});
149155

150156
for (pc, fp, sp) in activations {
151-
if let ControlFlow::Break(()) = Self::trace_through_wasm(pc, fp, sp, &mut f) {
157+
if let ControlFlow::Break(()) = Self::trace_through_wasm(unwind, pc, fp, sp, &mut f) {
152158
log::trace!("====== Done Capturing Backtrace (closure break) ======");
153159
return;
154160
}
@@ -160,6 +166,7 @@ impl Backtrace {
160166
/// Walk through a contiguous sequence of Wasm frames starting with the
161167
/// frame at the given PC and FP and ending at `trampoline_sp`.
162168
unsafe fn trace_through_wasm(
169+
unwind: &dyn Unwind,
163170
mut pc: usize,
164171
mut fp: usize,
165172
trampoline_fp: usize,
@@ -228,25 +235,25 @@ impl Backtrace {
228235
// Wasm. Finally also assert that it's aligned correctly as an
229236
// additional sanity check.
230237
assert!(trampoline_fp > fp, "{trampoline_fp:#x} > {fp:#x}");
231-
arch::assert_fp_is_aligned(fp);
238+
unwind.assert_fp_is_aligned(fp);
232239

233240
log::trace!("--- Tracing through one Wasm frame ---");
234241
log::trace!("pc = {:p}", pc as *const ());
235242
log::trace!("fp = {:p}", fp as *const ());
236243

237244
f(Frame { pc, fp })?;
238245

239-
pc = arch::get_next_older_pc_from_fp(fp);
246+
pc = unwind.get_next_older_pc_from_fp(fp);
240247

241248
// We rely on this offset being zero for all supported architectures
242249
// in `crates/cranelift/src/component/compiler.rs` when we set the
243250
// Wasm exit FP. If this ever changes, we will need to update that
244251
// code as well!
245-
assert_eq!(arch::NEXT_OLDER_FP_FROM_FP_OFFSET, 0);
252+
assert_eq!(unwind.next_older_fp_from_fp_offset(), 0);
246253

247254
// Get the next older frame pointer from the current Wasm frame
248255
// pointer.
249-
let next_older_fp = *(fp as *mut usize).add(arch::NEXT_OLDER_FP_FROM_FP_OFFSET);
256+
let next_older_fp = *(fp as *mut usize).add(unwind.next_older_fp_from_fp_offset());
250257

251258
// Because the stack always grows down, the older FP must be greater
252259
// than the current FP.

crates/wasmtime/src/runtime/vm/traphandlers/coredump_enabled.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ impl CallThreadState {
3131
if !self.capture_coredump {
3232
return None;
3333
}
34-
let bt = unsafe { Backtrace::new_with_trap_state(limits, self, trap_pc_and_fp) };
34+
let bt =
35+
unsafe { Backtrace::new_with_trap_state(limits, self.unwinder, self, trap_pc_and_fp) };
3536

3637
Some(CoreDumpStack {
3738
bt,
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
//! Support for low-level primitives of unwinding the stack.
2+
3+
use crate::runtime::vm::arch;
4+
5+
/// Implementation necessary to unwind the stack, used by `Backtrace`.
6+
pub unsafe trait Unwind {
7+
/// Returns the offset, from the current frame pointer, of where to get to
8+
/// the previous frame pointer on the stack.
9+
fn next_older_fp_from_fp_offset(&self) -> usize;
10+
11+
/// Load the return address of a frame given the frame pointer for that
12+
/// frame.
13+
unsafe fn get_next_older_pc_from_fp(&self, fp: usize) -> usize;
14+
15+
/// Debug assertion that the frame pointer is aligned.
16+
fn assert_fp_is_aligned(&self, fp: usize);
17+
}
18+
19+
/// A host-backed implementation of unwinding, using the native platform ABI
20+
/// that Cranelift has.
21+
pub struct UnwindHost;
22+
23+
unsafe impl Unwind for UnwindHost {
24+
fn next_older_fp_from_fp_offset(&self) -> usize {
25+
arch::NEXT_OLDER_FP_FROM_FP_OFFSET
26+
}
27+
unsafe fn get_next_older_pc_from_fp(&self, fp: usize) -> usize {
28+
arch::get_next_older_pc_from_fp(fp)
29+
}
30+
fn assert_fp_is_aligned(&self, fp: usize) {
31+
arch::assert_fp_is_aligned(fp)
32+
}
33+
}
34+
35+
/// An implementation specifically designed for unwinding Pulley's runtime stack
36+
/// (which might not match the native host).
37+
pub struct UnwindPulley;
38+
39+
unsafe impl Unwind for UnwindPulley {
40+
fn next_older_fp_from_fp_offset(&self) -> usize {
41+
0
42+
}
43+
unsafe fn get_next_older_pc_from_fp(&self, fp: usize) -> usize {
44+
// The calling convention always pushes the return pointer (aka the PC
45+
// of the next older frame) just before this frame.
46+
*(fp as *mut usize).offset(1)
47+
}
48+
fn assert_fp_is_aligned(&self, fp: usize) {
49+
let expected = if cfg!(target_pointer_width = "32") {
50+
8
51+
} else {
52+
16
53+
};
54+
assert_eq!(fp % expected, 0, "stack should always be aligned");
55+
}
56+
}

0 commit comments

Comments
 (0)