Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions crates/runtime/src/traphandlers/backtrace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
78 changes: 75 additions & 3 deletions crates/wasmtime/src/trap.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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.
Comment thread
alexcrichton marked this conversation as resolved.
///
/// 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>,
Expand Down
61 changes: 61 additions & 0 deletions tests/all/traps.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
}