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:
Alex Crichton
2022-12-01 16:19:07 -06:00
committed by GitHub
parent e0b9663e44
commit ed6769084b
3 changed files with 141 additions and 3 deletions

View File

@@ -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 {

View File

@@ -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>,

View File

@@ -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(())
}