Skip to content

Commit ed67690

Browse files
authored
Add a WasmBacktrace::new() constructor (#5341)
* Add a `WasmBacktrace::new()` constructor This commit adds a method of manually capturing a backtrace of WebAssembly frames within a `Store`. The new constructor can be called with any `AsContext` values, primarily `&Store` and `&Caller`, during host functions to inspect the calling state. For now this does not respect the `Config::wasm_backtrace` option and instead unconditionally captures the backtrace. It's hoped that this can continue to adapt to needs of embedders by making it more configurable int he future if necessary. Closes #5339 * Split `new` into `capture` and `force_capture`
1 parent e0b9663 commit ed67690

3 files changed

Lines changed: 141 additions & 3 deletions

File tree

crates/runtime/src/traphandlers/backtrace.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,11 @@ impl Frame {
7474
}
7575

7676
impl Backtrace {
77+
/// Returns an empty backtrace
78+
pub fn empty() -> Backtrace {
79+
Backtrace(Vec::new())
80+
}
81+
7782
/// Capture the current Wasm stack in a backtrace.
7883
pub fn new() -> Backtrace {
7984
tls::with(|state| match state {

crates/wasmtime/src/trap.rs

Lines changed: 75 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::store::StoreOpaque;
2-
use crate::Module;
2+
use crate::{AsContext, Module};
33
use anyhow::Error;
44
use std::fmt;
55
use wasmtime_environ::{EntityRef, FilePos};
@@ -112,7 +112,7 @@ pub(crate) fn from_runtime_box(
112112
};
113113
match backtrace {
114114
Some(bt) => {
115-
let bt = WasmBacktrace::new(store, bt, pc);
115+
let bt = WasmBacktrace::from_captured(store, bt, pc);
116116
if bt.wasm_trace.is_empty() {
117117
error
118118
} else {
@@ -183,7 +183,79 @@ pub struct WasmBacktrace {
183183
}
184184

185185
impl WasmBacktrace {
186-
fn new(
186+
/// Captures a trace of the WebAssembly frames on the stack for the
187+
/// provided store.
188+
///
189+
/// This will return a [`WasmBacktrace`] which holds captured
190+
/// [`FrameInfo`]s for each frame of WebAssembly on the call stack of the
191+
/// current thread. If no WebAssembly is on the stack then the returned
192+
/// backtrace will have no frames in it.
193+
///
194+
/// Note that this function will respect the [`Config::wasm_backtrace`]
195+
/// configuration option and will return an empty backtrace if that is
196+
/// disabled. To always capture a backtrace use the
197+
/// [`WasmBacktrace::force_capture`] method.
198+
///
199+
/// Also note that this function will only capture frames from the
200+
/// specified `store` on the stack, ignoring frames from other stores if
201+
/// present.
202+
///
203+
/// [`Config::wasm_backtrace`]: crate::Config::wasm_backtrace
204+
///
205+
/// # Example
206+
///
207+
/// ```
208+
/// # use wasmtime::*;
209+
/// # use anyhow::Result;
210+
/// # fn main() -> Result<()> {
211+
/// let engine = Engine::default();
212+
/// let module = Module::new(
213+
/// &engine,
214+
/// r#"
215+
/// (module
216+
/// (import "" "" (func $host))
217+
/// (func $foo (export "f") call $bar)
218+
/// (func $bar call $host)
219+
/// )
220+
/// "#,
221+
/// )?;
222+
///
223+
/// let mut store = Store::new(&engine, ());
224+
/// let func = Func::wrap(&mut store, |cx: Caller<'_, ()>| {
225+
/// let trace = WasmBacktrace::capture(&cx);
226+
/// println!("{trace:?}");
227+
/// });
228+
/// let instance = Instance::new(&mut store, &module, &[func.into()])?;
229+
/// let func = instance.get_typed_func::<(), ()>(&mut store, "f")?;
230+
/// func.call(&mut store, ())?;
231+
/// # Ok(())
232+
/// # }
233+
/// ```
234+
pub fn capture(store: impl AsContext) -> WasmBacktrace {
235+
let store = store.as_context();
236+
if store.engine().config().wasm_backtrace {
237+
Self::force_capture(store)
238+
} else {
239+
WasmBacktrace {
240+
wasm_trace: Vec::new(),
241+
hint_wasm_backtrace_details_env: false,
242+
runtime_trace: wasmtime_runtime::Backtrace::empty(),
243+
}
244+
}
245+
}
246+
247+
/// Unconditionally captures a trace of the WebAssembly frames on the stack
248+
/// for the provided store.
249+
///
250+
/// Same as [`WasmBacktrace::capture`] except that it disregards the
251+
/// [`Config::wasm_backtrace`](crate::Config::wasm_backtrace) setting and
252+
/// always captures a backtrace.
253+
pub fn force_capture(store: impl AsContext) -> WasmBacktrace {
254+
let store = store.as_context();
255+
Self::from_captured(store.0, wasmtime_runtime::Backtrace::new(), None)
256+
}
257+
258+
fn from_captured(
187259
store: &StoreOpaque,
188260
runtime_trace: wasmtime_runtime::Backtrace,
189261
trap_pc: Option<usize>,

tests/all/traps.rs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1099,3 +1099,64 @@ async fn sync_then_async_trap() -> Result<()> {
10991099

11001100
Ok(())
11011101
}
1102+
1103+
#[test]
1104+
fn standalone_backtrace() -> Result<()> {
1105+
let engine = Engine::default();
1106+
let mut store = Store::new(&engine, ());
1107+
let trace = WasmBacktrace::capture(&store);
1108+
assert!(trace.frames().is_empty());
1109+
let module = Module::new(
1110+
&engine,
1111+
r#"
1112+
(module
1113+
(import "" "" (func $host))
1114+
(func $foo (export "f") call $bar)
1115+
(func $bar call $host)
1116+
)
1117+
"#,
1118+
)?;
1119+
let func = Func::wrap(&mut store, |cx: Caller<'_, ()>| {
1120+
let trace = WasmBacktrace::capture(&cx);
1121+
assert_eq!(trace.frames().len(), 2);
1122+
let frame1 = &trace.frames()[0];
1123+
let frame2 = &trace.frames()[1];
1124+
assert_eq!(frame1.func_index(), 2);
1125+
assert_eq!(frame1.func_name(), Some("bar"));
1126+
assert_eq!(frame2.func_index(), 1);
1127+
assert_eq!(frame2.func_name(), Some("foo"));
1128+
});
1129+
let instance = Instance::new(&mut store, &module, &[func.into()])?;
1130+
let f = instance.get_typed_func::<(), ()>(&mut store, "f")?;
1131+
f.call(&mut store, ())?;
1132+
Ok(())
1133+
}
1134+
1135+
#[test]
1136+
#[allow(deprecated)]
1137+
fn standalone_backtrace_disabled() -> Result<()> {
1138+
let mut config = Config::new();
1139+
config.wasm_backtrace(false);
1140+
let engine = Engine::new(&config)?;
1141+
let mut store = Store::new(&engine, ());
1142+
let module = Module::new(
1143+
&engine,
1144+
r#"
1145+
(module
1146+
(import "" "" (func $host))
1147+
(func $foo (export "f") call $bar)
1148+
(func $bar call $host)
1149+
)
1150+
"#,
1151+
)?;
1152+
let func = Func::wrap(&mut store, |cx: Caller<'_, ()>| {
1153+
let trace = WasmBacktrace::capture(&cx);
1154+
assert_eq!(trace.frames().len(), 0);
1155+
let trace = WasmBacktrace::force_capture(&cx);
1156+
assert_eq!(trace.frames().len(), 2);
1157+
});
1158+
let instance = Instance::new(&mut store, &module, &[func.into()])?;
1159+
let f = instance.get_typed_func::<(), ()>(&mut store, "f")?;
1160+
f.call(&mut store, ())?;
1161+
Ok(())
1162+
}

0 commit comments

Comments
 (0)