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 {
|
impl Backtrace {
|
||||||
|
/// Returns an empty backtrace
|
||||||
|
pub fn empty() -> Backtrace {
|
||||||
|
Backtrace(Vec::new())
|
||||||
|
}
|
||||||
|
|
||||||
/// Capture the current Wasm stack in a backtrace.
|
/// Capture the current Wasm stack in a backtrace.
|
||||||
pub fn new() -> Backtrace {
|
pub fn new() -> Backtrace {
|
||||||
tls::with(|state| match state {
|
tls::with(|state| match state {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::store::StoreOpaque;
|
use crate::store::StoreOpaque;
|
||||||
use crate::Module;
|
use crate::{AsContext, Module};
|
||||||
use anyhow::Error;
|
use anyhow::Error;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use wasmtime_environ::{EntityRef, FilePos};
|
use wasmtime_environ::{EntityRef, FilePos};
|
||||||
@@ -112,7 +112,7 @@ pub(crate) fn from_runtime_box(
|
|||||||
};
|
};
|
||||||
match backtrace {
|
match backtrace {
|
||||||
Some(bt) => {
|
Some(bt) => {
|
||||||
let bt = WasmBacktrace::new(store, bt, pc);
|
let bt = WasmBacktrace::from_captured(store, bt, pc);
|
||||||
if bt.wasm_trace.is_empty() {
|
if bt.wasm_trace.is_empty() {
|
||||||
error
|
error
|
||||||
} else {
|
} else {
|
||||||
@@ -183,7 +183,79 @@ pub struct WasmBacktrace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl 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,
|
store: &StoreOpaque,
|
||||||
runtime_trace: wasmtime_runtime::Backtrace,
|
runtime_trace: wasmtime_runtime::Backtrace,
|
||||||
trap_pc: Option<usize>,
|
trap_pc: Option<usize>,
|
||||||
|
|||||||
@@ -1099,3 +1099,64 @@ async fn sync_then_async_trap() -> Result<()> {
|
|||||||
|
|
||||||
Ok(())
|
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