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`
This commit is contained in:
@@ -74,6 +74,11 @@ impl Frame {
|
||||
}
|
||||
|
||||
impl Backtrace {
|
||||
/// Returns an empty backtrace
|
||||
pub fn empty() -> Backtrace {
|
||||
Backtrace(Vec::new())
|
||||
}
|
||||
|
||||
/// Capture the current Wasm stack in a backtrace.
|
||||
pub fn new() -> Backtrace {
|
||||
tls::with(|state| match state {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::store::StoreOpaque;
|
||||
use crate::Module;
|
||||
use crate::{AsContext, Module};
|
||||
use anyhow::Error;
|
||||
use std::fmt;
|
||||
use wasmtime_environ::{EntityRef, FilePos};
|
||||
@@ -112,7 +112,7 @@ pub(crate) fn from_runtime_box(
|
||||
};
|
||||
match backtrace {
|
||||
Some(bt) => {
|
||||
let bt = WasmBacktrace::new(store, bt, pc);
|
||||
let bt = WasmBacktrace::from_captured(store, bt, pc);
|
||||
if bt.wasm_trace.is_empty() {
|
||||
error
|
||||
} else {
|
||||
@@ -183,7 +183,79 @@ pub struct WasmBacktrace {
|
||||
}
|
||||
|
||||
impl WasmBacktrace {
|
||||
fn new(
|
||||
/// Captures a trace of the WebAssembly frames on the stack for the
|
||||
/// provided store.
|
||||
///
|
||||
/// This will return a [`WasmBacktrace`] which holds captured
|
||||
/// [`FrameInfo`]s for each frame of WebAssembly on the call stack of the
|
||||
/// current thread. If no WebAssembly is on the stack then the returned
|
||||
/// backtrace will have no frames in it.
|
||||
///
|
||||
/// Note that this function will respect the [`Config::wasm_backtrace`]
|
||||
/// configuration option and will return an empty backtrace if that is
|
||||
/// disabled. To always capture a backtrace use the
|
||||
/// [`WasmBacktrace::force_capture`] method.
|
||||
///
|
||||
/// Also note that this function will only capture frames from the
|
||||
/// specified `store` on the stack, ignoring frames from other stores if
|
||||
/// present.
|
||||
///
|
||||
/// [`Config::wasm_backtrace`]: crate::Config::wasm_backtrace
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use wasmtime::*;
|
||||
/// # use anyhow::Result;
|
||||
/// # fn main() -> Result<()> {
|
||||
/// let engine = Engine::default();
|
||||
/// let module = Module::new(
|
||||
/// &engine,
|
||||
/// r#"
|
||||
/// (module
|
||||
/// (import "" "" (func $host))
|
||||
/// (func $foo (export "f") call $bar)
|
||||
/// (func $bar call $host)
|
||||
/// )
|
||||
/// "#,
|
||||
/// )?;
|
||||
///
|
||||
/// let mut store = Store::new(&engine, ());
|
||||
/// let func = Func::wrap(&mut store, |cx: Caller<'_, ()>| {
|
||||
/// let trace = WasmBacktrace::capture(&cx);
|
||||
/// println!("{trace:?}");
|
||||
/// });
|
||||
/// let instance = Instance::new(&mut store, &module, &[func.into()])?;
|
||||
/// let func = instance.get_typed_func::<(), ()>(&mut store, "f")?;
|
||||
/// func.call(&mut store, ())?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn capture(store: impl AsContext) -> WasmBacktrace {
|
||||
let store = store.as_context();
|
||||
if store.engine().config().wasm_backtrace {
|
||||
Self::force_capture(store)
|
||||
} else {
|
||||
WasmBacktrace {
|
||||
wasm_trace: Vec::new(),
|
||||
hint_wasm_backtrace_details_env: false,
|
||||
runtime_trace: wasmtime_runtime::Backtrace::empty(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Unconditionally captures a trace of the WebAssembly frames on the stack
|
||||
/// for the provided store.
|
||||
///
|
||||
/// Same as [`WasmBacktrace::capture`] except that it disregards the
|
||||
/// [`Config::wasm_backtrace`](crate::Config::wasm_backtrace) setting and
|
||||
/// always captures a backtrace.
|
||||
pub fn force_capture(store: impl AsContext) -> WasmBacktrace {
|
||||
let store = store.as_context();
|
||||
Self::from_captured(store.0, wasmtime_runtime::Backtrace::new(), None)
|
||||
}
|
||||
|
||||
fn from_captured(
|
||||
store: &StoreOpaque,
|
||||
runtime_trace: wasmtime_runtime::Backtrace,
|
||||
trap_pc: Option<usize>,
|
||||
|
||||
@@ -1099,3 +1099,64 @@ async fn sync_then_async_trap() -> Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn standalone_backtrace() -> Result<()> {
|
||||
let engine = Engine::default();
|
||||
let mut store = Store::new(&engine, ());
|
||||
let trace = WasmBacktrace::capture(&store);
|
||||
assert!(trace.frames().is_empty());
|
||||
let module = Module::new(
|
||||
&engine,
|
||||
r#"
|
||||
(module
|
||||
(import "" "" (func $host))
|
||||
(func $foo (export "f") call $bar)
|
||||
(func $bar call $host)
|
||||
)
|
||||
"#,
|
||||
)?;
|
||||
let func = Func::wrap(&mut store, |cx: Caller<'_, ()>| {
|
||||
let trace = WasmBacktrace::capture(&cx);
|
||||
assert_eq!(trace.frames().len(), 2);
|
||||
let frame1 = &trace.frames()[0];
|
||||
let frame2 = &trace.frames()[1];
|
||||
assert_eq!(frame1.func_index(), 2);
|
||||
assert_eq!(frame1.func_name(), Some("bar"));
|
||||
assert_eq!(frame2.func_index(), 1);
|
||||
assert_eq!(frame2.func_name(), Some("foo"));
|
||||
});
|
||||
let instance = Instance::new(&mut store, &module, &[func.into()])?;
|
||||
let f = instance.get_typed_func::<(), ()>(&mut store, "f")?;
|
||||
f.call(&mut store, ())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(deprecated)]
|
||||
fn standalone_backtrace_disabled() -> Result<()> {
|
||||
let mut config = Config::new();
|
||||
config.wasm_backtrace(false);
|
||||
let engine = Engine::new(&config)?;
|
||||
let mut store = Store::new(&engine, ());
|
||||
let module = Module::new(
|
||||
&engine,
|
||||
r#"
|
||||
(module
|
||||
(import "" "" (func $host))
|
||||
(func $foo (export "f") call $bar)
|
||||
(func $bar call $host)
|
||||
)
|
||||
"#,
|
||||
)?;
|
||||
let func = Func::wrap(&mut store, |cx: Caller<'_, ()>| {
|
||||
let trace = WasmBacktrace::capture(&cx);
|
||||
assert_eq!(trace.frames().len(), 0);
|
||||
let trace = WasmBacktrace::force_capture(&cx);
|
||||
assert_eq!(trace.frames().len(), 2);
|
||||
});
|
||||
let instance = Instance::new(&mut store, &module, &[func.into()])?;
|
||||
let f = instance.get_typed_func::<(), ()>(&mut store, "f")?;
|
||||
f.call(&mut store, ())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user