diff --git a/Cargo.lock b/Cargo.lock index 2f1339dce869..b1d1dc63299c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3976,6 +3976,7 @@ dependencies = [ "target-lexicon", "tempfile", "wasi-cap-std-sync", + "wasm-encoder 0.31.1", "wasmparser 0.110.0", "wasmtime-cache", "wasmtime-component-macro", @@ -4361,6 +4362,7 @@ dependencies = [ "rand 0.8.5", "rustix 0.38.4", "sptr", + "wasm-encoder 0.31.1", "wasmtime-asm-macros", "wasmtime-environ", "wasmtime-fiber", diff --git a/crates/runtime/Cargo.toml b/crates/runtime/Cargo.toml index 16ed393e9c9c..855a9a99ab23 100644 --- a/crates/runtime/Cargo.toml +++ b/crates/runtime/Cargo.toml @@ -27,6 +27,7 @@ memfd = "0.6.2" paste = "1.0.3" encoding_rs = { version = "0.8.31", optional = true } sptr = "0.3.2" +wasm-encoder = { workspace = true } [target.'cfg(target_os = "macos")'.dependencies] mach = "0.3.2" diff --git a/crates/runtime/src/traphandlers.rs b/crates/runtime/src/traphandlers.rs index c43c9779f3b2..512e0789bf20 100644 --- a/crates/runtime/src/traphandlers.rs +++ b/crates/runtime/src/traphandlers.rs @@ -2,6 +2,7 @@ //! signalhandling mechanisms. mod backtrace; +mod coredump; use crate::{Instance, VMContext, VMRuntimeLimits}; use anyhow::Error; @@ -12,6 +13,7 @@ use std::ptr; use std::sync::Once; pub use self::backtrace::{Backtrace, Frame}; +pub use self::coredump::CoreDumpStack; pub use self::tls::{tls_eager_initialize, AsyncWasmCallState, PreviousAsyncWasmCallState}; cfg_if::cfg_if! { @@ -176,6 +178,8 @@ pub struct Trap { pub reason: TrapReason, /// Wasm backtrace of the trap, if any. pub backtrace: Option, + /// The Wasm Coredump, if any. + pub coredumpstack: Option, } /// Enumeration of different methods of raising a trap. @@ -255,6 +259,7 @@ impl From for TrapReason { pub unsafe fn catch_traps<'a, F>( signal_handler: Option<*const SignalHandler<'static>>, capture_backtrace: bool, + capture_coredump: bool, caller: *mut VMContext, mut closure: F, ) -> Result<(), Box> @@ -263,19 +268,24 @@ where { let limits = Instance::from_vmctx(caller, |i| i.runtime_limits()); - let result = CallThreadState::new(signal_handler, capture_backtrace, *limits).with(|cx| { - wasmtime_setjmp( - cx.jmp_buf.as_ptr(), - call_closure::, - &mut closure as *mut F as *mut u8, - caller, - ) - }); + let result = CallThreadState::new(signal_handler, capture_backtrace, capture_coredump, *limits) + .with(|cx| { + wasmtime_setjmp( + cx.jmp_buf.as_ptr(), + call_closure::, + &mut closure as *mut F as *mut u8, + caller, + ) + }); return match result { Ok(x) => Ok(x), - Err((UnwindReason::Trap(reason), backtrace)) => Err(Box::new(Trap { reason, backtrace })), - Err((UnwindReason::Panic(panic), _)) => std::panic::resume_unwind(panic), + Err((UnwindReason::Trap(reason), backtrace, coredumpstack)) => Err(Box::new(Trap { + reason, + backtrace, + coredumpstack, + })), + Err((UnwindReason::Panic(panic), _, _)) => std::panic::resume_unwind(panic), }; extern "C" fn call_closure(payload: *mut u8, caller: *mut VMContext) @@ -294,10 +304,12 @@ mod call_thread_state { /// Temporary state stored on the stack which is registered in the `tls` module /// below for calls into wasm. pub struct CallThreadState { - pub(super) unwind: UnsafeCell)>>, + pub(super) unwind: + UnsafeCell, Option)>>, pub(super) jmp_buf: Cell<*const u8>, pub(super) signal_handler: Option<*const SignalHandler<'static>>, pub(super) capture_backtrace: bool, + pub(super) capture_coredump: bool, pub(crate) limits: *const VMRuntimeLimits, @@ -331,6 +343,7 @@ mod call_thread_state { pub(super) fn new( signal_handler: Option<*const SignalHandler<'static>>, capture_backtrace: bool, + capture_coredump: bool, limits: *const VMRuntimeLimits, ) -> CallThreadState { CallThreadState { @@ -338,6 +351,7 @@ mod call_thread_state { jmp_buf: Cell::new(ptr::null()), signal_handler, capture_backtrace, + capture_coredump, limits, prev: Cell::new(ptr::null()), old_last_wasm_exit_fp: Cell::new(unsafe { *(*limits).last_wasm_exit_fp.get() }), @@ -389,7 +403,7 @@ impl CallThreadState { fn with( mut self, closure: impl FnOnce(&CallThreadState) -> i32, - ) -> Result<(), (UnwindReason, Option)> { + ) -> Result<(), (UnwindReason, Option, Option)> { let ret = tls::set(&mut self, |me| closure(me)); if ret != 0 { Ok(()) @@ -399,12 +413,12 @@ impl CallThreadState { } #[cold] - unsafe fn read_unwind(&self) -> (UnwindReason, Option) { + unsafe fn read_unwind(&self) -> (UnwindReason, Option, Option) { (*self.unwind.get()).as_ptr().read() } fn unwind_with(&self, reason: UnwindReason) -> ! { - let backtrace = match reason { + let (backtrace, coredump) = match reason { // Panics don't need backtraces. There is nowhere to attach the // hypothetical backtrace to and it doesn't really make sense to try // in the first place since this is a Rust problem rather than a @@ -416,11 +430,13 @@ impl CallThreadState { | UnwindReason::Trap(TrapReason::User { needs_backtrace: false, .. - }) => None, - UnwindReason::Trap(_) => self.capture_backtrace(self.limits, None), + }) => (None, None), + UnwindReason::Trap(_) => (self.capture_backtrace(self.limits, None), self.capture_coredump(self.limits, None)), }; unsafe { - (*self.unwind.get()).as_mut_ptr().write((reason, backtrace)); + (*self.unwind.get()) + .as_mut_ptr() + .write((reason, backtrace, coredump)); wasmtime_longjmp(self.jmp_buf.get()); } } @@ -472,6 +488,7 @@ impl CallThreadState { fn set_jit_trap(&self, pc: *const u8, fp: usize, faulting_addr: Option) { let backtrace = self.capture_backtrace(self.limits, Some((pc as usize, fp))); + let coredump = self.capture_coredump(self.limits, Some((pc as usize, fp))); unsafe { (*self.unwind.get()).as_mut_ptr().write(( UnwindReason::Trap(TrapReason::Jit { @@ -479,6 +496,7 @@ impl CallThreadState { faulting_addr, }), backtrace, + coredump, )); } } @@ -495,6 +513,17 @@ impl CallThreadState { Some(unsafe { Backtrace::new_with_trap_state(limits, self, trap_pc_and_fp) }) } + fn capture_coredump( + &self, + limits: *const VMRuntimeLimits, + trap_pc_and_fp: Option<(usize, usize)>, + ) -> Option { + if !self.capture_coredump { + return None; + } + Some(CoreDumpStack::new(&self, limits, trap_pc_and_fp)) + } + pub(crate) fn iter<'a>(&'a self) -> impl Iterator + 'a { let mut state = Some(self); std::iter::from_fn(move || { diff --git a/crates/runtime/src/traphandlers/coredump.rs b/crates/runtime/src/traphandlers/coredump.rs new file mode 100644 index 000000000000..2216d5f75ea3 --- /dev/null +++ b/crates/runtime/src/traphandlers/coredump.rs @@ -0,0 +1,38 @@ +use wasm_encoder::CoreDumpValue; + +use crate::{Backtrace, VMRuntimeLimits}; + +use super::CallThreadState; + +/// A WebAssembly Coredump +#[derive(Debug)] +pub struct CoreDumpStack { + /// The backtrace containing the stack frames for the CoreDump + pub bt: Backtrace, + + /// Unimplemented + /// The indices of the locals and operand_stack all map to each other (ie. + /// index 0 is the locals for the first frame in the backtrace, etc) + pub locals: Vec>, + + /// Unimplemented + /// The operands for each stack frame + pub operand_stack: Vec>, +} + +impl CoreDumpStack { + /// Capture a core dump of the current wasm state + pub fn new( + cts: &CallThreadState, + limits: *const VMRuntimeLimits, + trap_pc_and_fp: Option<(usize, usize)>, + ) -> Self { + let bt = unsafe { Backtrace::new_with_trap_state(limits, cts, trap_pc_and_fp) }; + + Self { + bt, + locals: vec![], + operand_stack: vec![], + } + } +} diff --git a/crates/wasmtime/Cargo.toml b/crates/wasmtime/Cargo.toml index f5c045e4aeff..32378719a310 100644 --- a/crates/wasmtime/Cargo.toml +++ b/crates/wasmtime/Cargo.toml @@ -29,6 +29,7 @@ wasmtime-component-macro = { workspace = true, optional = true } wasmtime-component-util = { workspace = true, optional = true } target-lexicon = { workspace = true } wasmparser = { workspace = true } +wasm-encoder = { workspace = true } anyhow = { workspace = true } libc = "0.2" cfg-if = { workspace = true } diff --git a/crates/wasmtime/src/config.rs b/crates/wasmtime/src/config.rs index 42e88137535d..1fa595ea1736 100644 --- a/crates/wasmtime/src/config.rs +++ b/crates/wasmtime/src/config.rs @@ -110,6 +110,7 @@ pub struct Config { pub(crate) memory_init_cow: bool, pub(crate) memory_guaranteed_dense_image_size: u64, pub(crate) force_memory_init_memfd: bool, + pub(crate) coredump_on_trap: bool, } /// User-provided configuration for the compiler. @@ -199,6 +200,7 @@ impl Config { memory_init_cow: true, memory_guaranteed_dense_image_size: 16 << 20, force_memory_init_memfd: false, + coredump_on_trap: false, }; #[cfg(any(feature = "cranelift", feature = "winch"))] { @@ -1450,6 +1452,15 @@ impl Config { self } + /// Configures whether or not a coredump should be generated and attached to + /// the anyhow::Error when a trap is raised. + /// + /// This option is disabled by default. + pub fn coredump_on_trap(&mut self, enable: bool) -> &mut Self { + self.coredump_on_trap = enable; + self + } + /// Configures the "guaranteed dense image size" for copy-on-write /// initialized memories. /// diff --git a/crates/wasmtime/src/coredump.rs b/crates/wasmtime/src/coredump.rs new file mode 100644 index 000000000000..d7047c5102dc --- /dev/null +++ b/crates/wasmtime/src/coredump.rs @@ -0,0 +1,115 @@ +use std::fmt; + +use crate::{store::StoreOpaque, FrameInfo, Global, Instance, Memory, Module, WasmBacktrace}; + +/// Representation of a core dump of a WebAssembly module +/// +/// When the Config::coredump_on_trap option is enabled this structure is +/// attached to the [`anyhow::Error`] returned from many Wasmtime functions that +/// execute WebAssembly such as [`Instance::new`] or [`Func::call`]. This can be +/// acquired with the [`anyhow::Error::downcast`] family of methods to +/// programmatically inspect the coredump. Otherwise since it's part of the +/// error returned this will get printed along with the rest of the error when +/// the error is logged. +/// +/// Note that some state, such as Wasm locals or values on the operand stack, +/// may be optimized away by the compiler or otherwise not recovered in the +/// coredump. +/// +/// Capturing of wasm coredumps can be configured through the +/// [`Config::coredump_on_trap`][crate::Config::coredump_on_trap] method. +/// +/// For more information about errors in wasmtime see the documentation of the +/// [`Trap`][crate::Trap] type. +/// +/// [`Func::call`]: crate::Func::call +/// [`Instance::new`]: crate::Instance::new +pub struct WasmCoreDump { + name: String, + modules: Vec, + instances: Vec, + store_memories: Vec, + store_globals: Vec, + backtrace: WasmBacktrace, +} + +impl WasmCoreDump { + pub(crate) fn new(store: &StoreOpaque, backtrace: WasmBacktrace) -> WasmCoreDump { + let modules: Vec<_> = store.modules().all_modules().cloned().collect(); + let instances: Vec = store.all_instances().collect(); + let store_memories: Vec = store.all_memories().collect(); + let store_globals: Vec = store.all_globals().collect(); + + WasmCoreDump { + name: String::from("store_name"), + modules, + instances, + store_memories, + store_globals, + backtrace, + } + } + + /// The stack frames for the CoreDump + pub fn frames(&self) -> &[FrameInfo] { + self.backtrace.frames() + } + + /// The names of the modules involved in the CoreDump + pub fn modules(&self) -> &[Module] { + self.modules.as_ref() + } + + /// The instances involved in the CoreDump + pub fn instances(&self) -> &[Instance] { + self.instances.as_ref() + } + + /// The imported globals that belong to the store, rather than a specific + /// instance + pub fn store_globals(&self) -> &[Global] { + self.store_globals.as_ref() + } + + /// The imported memories that belong to the store, rather than a specific + /// instance. + pub fn store_memories(&self) -> &[Memory] { + self.store_memories.as_ref() + } +} + +impl fmt::Display for WasmCoreDump { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + writeln!(f, "wasm coredump generated while executing {}:", self.name)?; + writeln!(f, "modules:")?; + for module in self.modules.iter() { + writeln!(f, " {}", module.name().unwrap_or(""))?; + } + + writeln!(f, "instances:")?; + for instance in self.instances.iter() { + writeln!(f, " {:?}", instance)?; + } + + writeln!(f, "memories:")?; + for memory in self.store_memories.iter() { + writeln!(f, " {:?}", memory)?; + } + + writeln!(f, "globals:")?; + for global in self.store_globals.iter() { + writeln!(f, " {:?}", global)?; + } + + writeln!(f, "backtrace:")?; + write!(f, "{}", self.backtrace)?; + + Ok(()) + } +} + +impl fmt::Debug for WasmCoreDump { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "") + } +} diff --git a/crates/wasmtime/src/externals.rs b/crates/wasmtime/src/externals.rs index 0e13c5669d6f..02822e7b399b 100644 --- a/crates/wasmtime/src/externals.rs +++ b/crates/wasmtime/src/externals.rs @@ -5,6 +5,7 @@ use crate::{ SharedMemory, TableType, Val, ValType, }; use anyhow::{anyhow, bail, Result}; +use runtime::ExportGlobal; use std::mem; use std::ptr; use wasmtime_runtime::{self as runtime}; @@ -248,6 +249,10 @@ impl Global { } } + pub(crate) fn from_stored(stored: Stored) -> Global { + Global(stored) + } + /// Returns the underlying type of this `global`. /// /// # Panics diff --git a/crates/wasmtime/src/func.rs b/crates/wasmtime/src/func.rs index b1f0bb03ca7c..60e2a5047903 100644 --- a/crates/wasmtime/src/func.rs +++ b/crates/wasmtime/src/func.rs @@ -1363,6 +1363,7 @@ pub(crate) fn invoke_wasm_and_catch_traps( let result = wasmtime_runtime::catch_traps( store.0.signal_handler(), store.0.engine().config().wasm_backtrace, + store.0.engine().config().coredump_on_trap, store.0.default_caller(), closure, ); diff --git a/crates/wasmtime/src/instance.rs b/crates/wasmtime/src/instance.rs index 697e62a42ba0..6995fc093628 100644 --- a/crates/wasmtime/src/instance.rs +++ b/crates/wasmtime/src/instance.rs @@ -329,6 +329,9 @@ impl Instance { Ok((instance, compiled_module.module().start_func)) } + pub(crate) fn from_stored(stored: Stored) -> Instance { + Instance(stored) + } pub(crate) fn from_wasmtime(handle: InstanceData, store: &mut StoreOpaque) -> Instance { Instance(store.store_data_mut().insert(handle)) diff --git a/crates/wasmtime/src/lib.rs b/crates/wasmtime/src/lib.rs index 37e58185229c..ad71d3d61341 100644 --- a/crates/wasmtime/src/lib.rs +++ b/crates/wasmtime/src/lib.rs @@ -395,6 +395,7 @@ mod compiler; mod code; mod config; +mod coredump; mod engine; mod externals; mod instance; @@ -412,6 +413,7 @@ mod types; mod values; pub use crate::config::*; +pub use crate::coredump::*; pub use crate::engine::*; pub use crate::externals::*; pub use crate::func::*; diff --git a/crates/wasmtime/src/memory.rs b/crates/wasmtime/src/memory.rs index c473fa143dd1..7548091005c9 100644 --- a/crates/wasmtime/src/memory.rs +++ b/crates/wasmtime/src/memory.rs @@ -9,7 +9,7 @@ use std::ops::Range; use std::slice; use std::time::Instant; use wasmtime_environ::MemoryPlan; -use wasmtime_runtime::{RuntimeLinearMemory, VMMemoryImport}; +use wasmtime_runtime::{ExportMemory, RuntimeLinearMemory, VMMemoryImport}; pub use wasmtime_runtime::WaitResult; @@ -271,6 +271,10 @@ impl Memory { } } + pub(crate) fn from_stored(stored: Stored) -> Memory { + Memory(stored) + } + /// Returns the underlying type of this memory. /// /// # Panics diff --git a/crates/wasmtime/src/module/registry.rs b/crates/wasmtime/src/module/registry.rs index de6fee9f179a..46fb18262be9 100644 --- a/crates/wasmtime/src/module/registry.rs +++ b/crates/wasmtime/src/module/registry.rs @@ -63,6 +63,14 @@ impl ModuleRegistry { Some((code.module(pc)?, offset)) } + /// Gets an iterator over all modules in the registry. + pub fn all_modules(&self) -> impl Iterator + '_ { + self.loaded_code + .values() + .flat_map(|(_, code)| code.modules.values()) + .chain(self.modules_without_code.iter()) + } + /// Registers a new module with the registry. pub fn register_module(&mut self, module: &Module) { self.register(module.code_object(), Some(module)) diff --git a/crates/wasmtime/src/store.rs b/crates/wasmtime/src/store.rs index cbd124e8fdae..92e6be392110 100644 --- a/crates/wasmtime/src/store.rs +++ b/crates/wasmtime/src/store.rs @@ -76,10 +76,12 @@ //! contents of `StoreOpaque`. This is an invariant that we, as the authors of //! `wasmtime`, must uphold for the public interface to be safe. +use crate::instance::InstanceData; use crate::linker::Definition; use crate::module::BareModuleInfo; use crate::trampoline::VMHostGlobalContext; use crate::{module::ModuleRegistry, Engine, Module, Trap, Val, ValRaw}; +use crate::{Global, Instance, Memory}; use anyhow::{anyhow, bail, Result}; use std::cell::UnsafeCell; use std::convert::TryFrom; @@ -94,9 +96,9 @@ use std::sync::atomic::AtomicU64; use std::sync::Arc; use std::task::{Context, Poll}; use wasmtime_runtime::{ - InstanceAllocationRequest, InstanceAllocator, InstanceHandle, ModuleInfo, - OnDemandInstanceAllocator, SignalHandler, StoreBox, StorePtr, VMContext, VMExternRef, - VMExternRefActivationsTable, VMFuncRef, VMRuntimeLimits, WasmFault, + ExportGlobal, ExportMemory, InstanceAllocationRequest, InstanceAllocator, InstanceHandle, + ModuleInfo, OnDemandInstanceAllocator, SignalHandler, StoreBox, StorePtr, VMContext, + VMExternRef, VMExternRefActivationsTable, VMFuncRef, VMRuntimeLimits, WasmFault, }; mod context; @@ -1250,6 +1252,24 @@ impl StoreOpaque { &mut self.instances[id.0].handle } + pub fn all_instances(&self) -> impl ExactSizeIterator { + self.store_data() + .iter::() + .map(Instance::from_stored) + } + + pub fn all_memories(&self) -> impl ExactSizeIterator { + self.store_data() + .iter::() + .map(Memory::from_stored) + } + + pub fn all_globals(&self) -> impl ExactSizeIterator { + self.store_data() + .iter::() + .map(Global::from_stored) + } + #[cfg_attr(not(target_os = "linux"), allow(dead_code))] // not used on all platforms pub fn set_signal_handler(&mut self, handler: Option>>) { self.signal_handler = handler; diff --git a/crates/wasmtime/src/store/data.rs b/crates/wasmtime/src/store/data.rs index c45592a0bfde..08764d293258 100644 --- a/crates/wasmtime/src/store/data.rs +++ b/crates/wasmtime/src/store/data.rs @@ -96,6 +96,14 @@ impl StoreData { true } + pub fn iter(&self) -> impl ExactSizeIterator> + where + T: StoredData, + { + let id = self.id; + (0..T::list(self).len()).map(move |i| Stored::new(id, i)) + } + pub(crate) fn reserve_funcs(&mut self, count: usize) { self.funcs.reserve(count); } diff --git a/crates/wasmtime/src/trap.rs b/crates/wasmtime/src/trap.rs index 1c4cea899fcf..e62ba73b1f46 100644 --- a/crates/wasmtime/src/trap.rs +++ b/crates/wasmtime/src/trap.rs @@ -1,3 +1,4 @@ +use crate::coredump::WasmCoreDump; use crate::store::StoreOpaque; use crate::{AsContext, Module}; use anyhow::Error; @@ -79,8 +80,12 @@ pub(crate) fn from_runtime_box( store: &StoreOpaque, runtime_trap: Box, ) -> Error { - let wasmtime_runtime::Trap { reason, backtrace } = *runtime_trap; - let (error, pc) = match reason { + let wasmtime_runtime::Trap { + reason, + backtrace, + coredumpstack, + } = *runtime_trap; + let (mut error, pc) = match reason { // For user-defined errors they're already an `anyhow::Error` so no // conversion is really necessary here, but a `backtrace` may have // been captured so it's attempted to get inserted here. @@ -121,17 +126,20 @@ pub(crate) fn from_runtime_box( } wasmtime_runtime::TrapReason::Wasm(trap_code) => (trap_code.into(), None), }; - match backtrace { - Some(bt) => { - let bt = WasmBacktrace::from_captured(store, bt, pc); - if bt.wasm_trace.is_empty() { - error - } else { - error.context(bt) - } + + if let Some(bt) = backtrace { + let bt = WasmBacktrace::from_captured(store, bt, pc); + if !bt.wasm_trace.is_empty() { + error = error.context(bt); } - None => error, } + + if let Some(coredump) = coredumpstack { + let bt = WasmBacktrace::from_captured(store, coredump.bt, pc); + let cd = WasmCoreDump::new(store, bt); + error = error.context(cd); + } + error } /// Representation of a backtrace of function frames in a WebAssembly module for diff --git a/tests/all/coredump.rs b/tests/all/coredump.rs new file mode 100644 index 000000000000..d372965ccef1 --- /dev/null +++ b/tests/all/coredump.rs @@ -0,0 +1,147 @@ +use anyhow::{bail, Result}; +use wasmtime::*; + +#[test] +#[cfg_attr(miri, ignore)] +fn test_coredump_attached_to_error() -> Result<()> { + let mut config = Config::default(); + config.coredump_on_trap(true); + let engine = Engine::new(&config).unwrap(); + let mut store = Store::<()>::new(&engine, ()); + + let wat = r#" + (module + (func $hello (import "" "hello")) + (func (export "run") (call $hello)) + ) + "#; + + let module = Module::new(store.engine(), wat)?; + let hello_type = FuncType::new(None, None); + let hello_func = Func::new(&mut store, hello_type, |_, _, _| bail!("test 123")); + + let instance = Instance::new(&mut store, &module, &[hello_func.into()])?; + let run_func = instance.get_typed_func::<(), ()>(&mut store, "run")?; + + let e = run_func.call(&mut store, ()).unwrap_err(); + assert!(format!("{e:?}").contains("test 123")); + + assert!( + e.downcast_ref::().is_some(), + "error should contain a WasmCoreDump" + ); + + Ok(()) +} + +#[test] +#[cfg_attr(miri, ignore)] +fn test_coredump_has_stack() -> Result<()> { + let mut config = Config::default(); + config.coredump_on_trap(true); + let engine = Engine::new(&config).unwrap(); + let mut store = Store::<()>::new(&engine, ()); + + let wat = r#" + (module + (func $a (export "a") + call $b + ) + (func $b + call $c + ) + (func $c + unreachable + ) + ) + "#; + + let module = Module::new(store.engine(), wat)?; + let instance = Instance::new(&mut store, &module, &[])?; + let a_func = instance.get_typed_func::<(), ()>(&mut store, "a")?; + + let e = a_func.call(&mut store, ()).unwrap_err(); + let cd = e.downcast_ref::().unwrap(); + assert_eq!(cd.frames().len(), 3); + assert_eq!(cd.frames()[0].func_name().unwrap(), "c"); + assert_eq!(cd.frames()[1].func_name().unwrap(), "b"); + assert_eq!(cd.frames()[2].func_name().unwrap(), "a"); + Ok(()) +} + +#[test] +#[cfg_attr(miri, ignore)] +fn test_coredump_has_modules_and_instances() -> Result<()> { + let mut config = Config::default(); + config.coredump_on_trap(true); + let engine = Engine::new(&config).unwrap(); + let mut linker = Linker::new(&engine); + let mut store = Store::<()>::new(&engine, ()); + + let wat1 = r#" + (module $foo + (import "bar" "b" (func $b)) + (func (export "a") + call $b + ) + ) + "#; + let wat2 = r#" + (module $bar + (func (export "b") + unreachable + ) + ) + "#; + let module1 = Module::new(store.engine(), wat1)?; + let module2 = Module::new(store.engine(), wat2)?; + let linking2 = linker.instantiate(&mut store, &module2)?; + linker.instance(&mut store, "bar", linking2)?; + + let linking1 = linker.instantiate(&mut store, &module1)?; + let a_func = linking1.get_typed_func::<(), ()>(&mut store, "a")?; + + let e = a_func.call(&mut store, ()).unwrap_err(); + let cd = e.downcast_ref::().unwrap(); + assert_eq!(cd.modules().len(), 2); + assert_eq!(cd.instances().len(), 2); + Ok(()) +} + +#[test] +#[cfg_attr(miri, ignore)] +fn test_coredump_has_import_globals_and_memory() -> Result<()> { + let mut config = Config::default(); + config.coredump_on_trap(true); + let engine = Engine::new(&config).unwrap(); + let mut store = Store::<()>::new(&engine, ()); + let mut linker = Linker::new(&engine); + + let wat = r#" + (module + (import "memory" "memory" (memory 1)) + (global $myglobal (import "js" "global") (mut i32)) + (func (export "a") (result i32) + unreachable + ) + ) + "#; + + let module = Module::new(store.engine(), wat)?; + let m = wasmtime::Memory::new(&mut store, MemoryType::new(1, None))?; + linker.define(&mut store, "memory", "memory", m)?; + let g = wasmtime::Global::new( + &mut store, + GlobalType::new(ValType::I32, Mutability::Var), + Val::I32(0), + )?; + linker.define(&mut store, "js", "global", g)?; + let instance = linker.instantiate(&mut store, &module)?; + let a_func = instance.get_typed_func::<(), i32>(&mut store, "a")?; + let e = a_func.call(&mut store, ()).unwrap_err(); + let cd = e.downcast_ref::().unwrap(); + assert_eq!(cd.store_globals().len(), 1); + assert_eq!(cd.store_memories().len(), 1); + + Ok(()) +} diff --git a/tests/all/main.rs b/tests/all/main.rs index e9941cc73710..e0925bf5d8de 100644 --- a/tests/all/main.rs +++ b/tests/all/main.rs @@ -4,6 +4,7 @@ mod async_functions; mod call_hook; mod cli_tests; mod component_model; +mod coredump; mod custom_signal_handler; mod debug; mod epoch_interruption;