Wasmtime: fix stack walking across frames from different stores (#4779)
We were previously implicitly assuming that all Wasm frames in a stack used the
same `VMRuntimeLimits` as the previous frame we walked, but this is not true
when Wasm in store A calls into the host which then calls into Wasm in store B:
| ... |
| Host | |
+-----------------+ | stack
| Wasm in store A | | grows
+-----------------+ | down
| Host | |
+-----------------+ |
| Wasm in store B | V
+-----------------+
Trying to walk this stack would previously result in a runtime panic.
The solution is to push the maintenance of our list of saved Wasm FP/SP/PC
registers that allow us to identify contiguous regions of Wasm frames on the
stack deeper into `CallThreadState`. The saved registers list is now maintained
whenever updating the `CallThreadState` linked list by making the
`CallThreadState::prev` field private and only accessible via a getter and
setter, where the setter always maintains our invariants.
This commit is contained in:
@@ -5,7 +5,6 @@ use std::fmt;
|
||||
use std::sync::Arc;
|
||||
use wasmtime_environ::{EntityRef, FilePos, TrapCode as EnvTrapCode};
|
||||
use wasmtime_jit::{demangle_function_name, demangle_function_name_or_index};
|
||||
use wasmtime_runtime::Backtrace;
|
||||
|
||||
/// A struct representing an aborted instruction execution, with a message
|
||||
/// indicating the cause.
|
||||
@@ -140,19 +139,24 @@ impl fmt::Display for TrapCode {
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct TrapBacktrace {
|
||||
wasm_trace: Vec<FrameInfo>,
|
||||
native_trace: Backtrace,
|
||||
runtime_trace: wasmtime_runtime::Backtrace,
|
||||
hint_wasm_backtrace_details_env: bool,
|
||||
}
|
||||
|
||||
impl TrapBacktrace {
|
||||
pub fn new(store: &StoreOpaque, native_trace: Backtrace, trap_pc: Option<usize>) -> Self {
|
||||
let mut wasm_trace = Vec::<FrameInfo>::new();
|
||||
pub fn new(
|
||||
store: &StoreOpaque,
|
||||
runtime_trace: wasmtime_runtime::Backtrace,
|
||||
trap_pc: Option<usize>,
|
||||
) -> Self {
|
||||
let mut wasm_trace = Vec::<FrameInfo>::with_capacity(runtime_trace.frames().len());
|
||||
let mut hint_wasm_backtrace_details_env = false;
|
||||
let wasm_backtrace_details_env_used =
|
||||
store.engine().config().wasm_backtrace_details_env_used;
|
||||
|
||||
for frame in native_trace.frames() {
|
||||
for frame in runtime_trace.frames() {
|
||||
debug_assert!(frame.pc() != 0);
|
||||
|
||||
// Note that we need to be careful about the pc we pass in
|
||||
// here to lookup frame information. This program counter is
|
||||
// used to translate back to an original source location in
|
||||
@@ -168,6 +172,31 @@ impl TrapBacktrace {
|
||||
} else {
|
||||
frame.pc() - 1
|
||||
};
|
||||
|
||||
// NB: The PC we are looking up _must_ be a Wasm PC since
|
||||
// `wasmtime_runtime::Backtrace` only contains Wasm frames.
|
||||
//
|
||||
// However, consider the case where we have multiple, nested calls
|
||||
// across stores (with host code in between, by necessity, since
|
||||
// only things in the same store can be linked directly together):
|
||||
//
|
||||
// | ... |
|
||||
// | Host | |
|
||||
// +-----------------+ | stack
|
||||
// | Wasm in store A | | grows
|
||||
// +-----------------+ | down
|
||||
// | Host | |
|
||||
// +-----------------+ |
|
||||
// | Wasm in store B | V
|
||||
// +-----------------+
|
||||
//
|
||||
// In this scenario, the `wasmtime_runtime::Backtrace` will contain
|
||||
// two frames: Wasm in store B followed by Wasm in store A. But
|
||||
// `store.modules()` will only have the module information for
|
||||
// modules instantiated within this store. Therefore, we use `if let
|
||||
// Some(..)` instead of the `unwrap` you might otherwise expect and
|
||||
// we ignore frames from modules that were not registered in this
|
||||
// store's module registry.
|
||||
if let Some((info, module)) = store.modules().lookup_frame_info(pc_to_lookup) {
|
||||
wasm_trace.push(info);
|
||||
|
||||
@@ -186,7 +215,7 @@ impl TrapBacktrace {
|
||||
|
||||
Self {
|
||||
wasm_trace,
|
||||
native_trace,
|
||||
runtime_trace,
|
||||
hint_wasm_backtrace_details_env,
|
||||
}
|
||||
}
|
||||
@@ -203,7 +232,9 @@ fn _assert_trap_is_sync_and_send(t: &Trap) -> (&dyn Sync, &dyn Send) {
|
||||
|
||||
impl Trap {
|
||||
/// Creates a new `Trap` with `message`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// let trap = wasmtime::Trap::new("unexpected error");
|
||||
/// assert!(trap.to_string().contains("unexpected error"));
|
||||
@@ -343,7 +374,7 @@ impl fmt::Debug for Trap {
|
||||
f.field("reason", &self.inner.reason);
|
||||
if let Some(backtrace) = self.inner.backtrace.get() {
|
||||
f.field("wasm_trace", &backtrace.wasm_trace)
|
||||
.field("native_trace", &backtrace.native_trace);
|
||||
.field("runtime_trace", &backtrace.runtime_trace);
|
||||
}
|
||||
f.finish()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user