diff --git a/crates/runtime/src/traphandlers/backtrace.rs b/crates/runtime/src/traphandlers/backtrace.rs index b18d8b96c3..0efed892bf 100644 --- a/crates/runtime/src/traphandlers/backtrace.rs +++ b/crates/runtime/src/traphandlers/backtrace.rs @@ -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 { diff --git a/crates/wasmtime/src/trap.rs b/crates/wasmtime/src/trap.rs index 8c1d21aa44..f16f19aedb 100644 --- a/crates/wasmtime/src/trap.rs +++ b/crates/wasmtime/src/trap.rs @@ -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, diff --git a/tests/all/traps.rs b/tests/all/traps.rs index f1776c8757..0922f8442e 100644 --- a/tests/all/traps.rs +++ b/tests/all/traps.rs @@ -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(()) +}