From 428ab601c437af8d37e615c539c420f4b11bdcf9 Mon Sep 17 00:00:00 2001 From: Rainy Sinclair <844493+itsrainy@users.noreply.github.com> Date: Fri, 22 Sep 2023 11:02:24 -0500 Subject: [PATCH] Include Wasm-defined globals and memories in core dumps (#6935) * Include Wasm-defined globals and memories in core dumps Co-authored-by: Nick Fitzgerald * Narrow scope of unsafe block * Add missing skip-miri attribute for test that calls into Wasm --------- Co-authored-by: Nick Fitzgerald --- crates/runtime/src/instance.rs | 45 ++++++ crates/wasmtime/src/coredump.rs | 40 ++--- crates/wasmtime/src/externals.rs | 5 - crates/wasmtime/src/instance.rs | 14 +- crates/wasmtime/src/memory.rs | 6 +- crates/wasmtime/src/store.rs | 99 +++++++++--- crates/wasmtime/src/store/data.rs | 6 + crates/wasmtime/src/trampoline.rs | 2 +- crates/wasmtime/src/trampoline/global.rs | 4 +- crates/wasmtime/src/trampoline/memory.rs | 2 +- crates/wasmtime/src/trap.rs | 2 +- crates/wasmtime/src/types.rs | 9 ++ tests/all/coredump.rs | 196 ++++++++++++++++++----- 13 files changed, 329 insertions(+), 101 deletions(-) diff --git a/crates/runtime/src/instance.rs b/crates/runtime/src/instance.rs index a43bf11ad7c4..29c91450501e 100644 --- a/crates/runtime/src/instance.rs +++ b/crates/runtime/src/instance.rs @@ -350,6 +350,15 @@ impl Instance { unsafe { *self.vmctx_plus_offset(self.offsets().vmctx_vmmemory_pointer(index)) } } + /// Return the memories defined within this instance (not imported). + pub fn defined_memories<'a>( + &'a self, + ) -> impl ExactSizeIterator + 'a { + self.memories + .iter() + .map(|(index, (_alloc_index, memory))| (index, memory)) + } + /// Return the indexed `VMGlobalDefinition`. fn global(&mut self, index: DefinedGlobalIndex) -> &VMGlobalDefinition { unsafe { &*self.global_ptr(index) } @@ -375,6 +384,25 @@ impl Instance { } } + /// Get the globals defined in this instance (not imported). + pub fn defined_globals<'a>( + &'a mut self, + ) -> impl ExactSizeIterator + 'a { + let module = self.module().clone(); + module + .globals + .keys() + .skip(module.num_imported_globals) + .map(move |global_idx| { + let def_idx = module.defined_global_index(global_idx).unwrap(); + let global = ExportGlobal { + definition: self.global_ptr(def_idx), + global: self.module().globals[global_idx], + }; + (def_idx, global) + }) + } + /// Return a pointer to the interrupts structure pub fn runtime_limits(&mut self) -> *mut *const VMRuntimeLimits { unsafe { self.vmctx_plus_offset_mut(self.offsets().vmctx_runtime_limits()) } @@ -1322,6 +1350,23 @@ impl InstanceHandle { self.instance_mut().get_table_with_lazy_init(index, range) } + /// Return the memories defined in this instance (not imported). + pub fn defined_memories<'a>(&'a mut self) -> impl ExactSizeIterator + 'a { + let idxs = (0..self.module().memory_plans.len()) + .skip(self.module().num_imported_memories) + .map(|i| MemoryIndex::new(i)) + .collect::>(); + idxs.into_iter() + .map(|memidx| self.get_exported_memory(memidx)) + } + + /// Get the globals defined in this instance (not imported). + pub fn defined_globals<'a>( + &'a mut self, + ) -> impl ExactSizeIterator + 'a { + self.instance_mut().defined_globals() + } + /// Return a reference to the contained `Instance`. #[inline] pub(crate) fn instance(&self) -> &Instance { diff --git a/crates/wasmtime/src/coredump.rs b/crates/wasmtime/src/coredump.rs index d7047c5102dc..ad4d22363eba 100644 --- a/crates/wasmtime/src/coredump.rs +++ b/crates/wasmtime/src/coredump.rs @@ -28,13 +28,13 @@ pub struct WasmCoreDump { name: String, modules: Vec, instances: Vec, - store_memories: Vec, - store_globals: Vec, + memories: Vec, + globals: Vec, backtrace: WasmBacktrace, } impl WasmCoreDump { - pub(crate) fn new(store: &StoreOpaque, backtrace: WasmBacktrace) -> WasmCoreDump { + pub(crate) fn new(store: &mut 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(); @@ -44,37 +44,41 @@ impl WasmCoreDump { name: String::from("store_name"), modules, instances, - store_memories, - store_globals, + memories: store_memories, + globals: store_globals, backtrace, } } - /// The stack frames for the CoreDump + /// The stack frames for this core dump. + /// + /// Frames appear in callee to caller order, that is youngest to oldest + /// frames. pub fn frames(&self) -> &[FrameInfo] { self.backtrace.frames() } - /// The names of the modules involved in the CoreDump + /// All modules instantiated inside the store when the core dump was + /// created. pub fn modules(&self) -> &[Module] { self.modules.as_ref() } - /// The instances involved in the CoreDump + /// All instances within the store when the core dump was created. 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() + /// All globals, instance- or host-defined, within the store when the core + /// dump was created. + pub fn globals(&self) -> &[Global] { + self.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() + /// All memories, instance- or host-defined, within the store when the core + /// dump was created. + pub fn memories(&self) -> &[Memory] { + self.memories.as_ref() } } @@ -92,12 +96,12 @@ impl fmt::Display for WasmCoreDump { } writeln!(f, "memories:")?; - for memory in self.store_memories.iter() { + for memory in self.memories.iter() { writeln!(f, " {:?}", memory)?; } writeln!(f, "globals:")?; - for global in self.store_globals.iter() { + for global in self.globals.iter() { writeln!(f, " {:?}", global)?; } diff --git a/crates/wasmtime/src/externals.rs b/crates/wasmtime/src/externals.rs index 1757a688720f..a43b90338a91 100644 --- a/crates/wasmtime/src/externals.rs +++ b/crates/wasmtime/src/externals.rs @@ -5,7 +5,6 @@ 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}; @@ -249,10 +248,6 @@ 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/instance.rs b/crates/wasmtime/src/instance.rs index 1489903012ab..95a0d4d3c0cd 100644 --- a/crates/wasmtime/src/instance.rs +++ b/crates/wasmtime/src/instance.rs @@ -42,6 +42,15 @@ pub(crate) struct InstanceData { exports: Vec>, } +impl InstanceData { + pub fn from_id(id: InstanceId) -> InstanceData { + InstanceData { + id, + exports: vec![], + } + } +} + impl Instance { /// Creates a new [`Instance`] from the previously compiled [`Module`] and /// list of `imports` specified. @@ -289,7 +298,7 @@ impl Instance { // conflicts with the borrow on `store.engine`) but this doesn't // matter in practice since initialization isn't even running any // code here anyway. - let id = store.add_instance(instance_handle.clone(), false); + let id = store.add_instance(instance_handle.clone()); // Additionally, before we start doing fallible instantiation, we // do one more step which is to insert an `InstanceData` @@ -330,9 +339,6 @@ 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/memory.rs b/crates/wasmtime/src/memory.rs index 7548091005c9..c473fa143dd1 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::{ExportMemory, RuntimeLinearMemory, VMMemoryImport}; +use wasmtime_runtime::{RuntimeLinearMemory, VMMemoryImport}; pub use wasmtime_runtime::WaitResult; @@ -271,10 +271,6 @@ 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/store.rs b/crates/wasmtime/src/store.rs index 0f78c579c7d1..88d7af66152f 100644 --- a/crates/wasmtime/src/store.rs +++ b/crates/wasmtime/src/store.rs @@ -96,9 +96,9 @@ use std::sync::atomic::AtomicU64; use std::sync::Arc; use std::task::{Context, Poll}; use wasmtime_runtime::{ - ExportGlobal, ExportMemory, InstanceAllocationRequest, InstanceAllocator, InstanceHandle, - ModuleInfo, OnDemandInstanceAllocator, SignalHandler, StoreBox, StorePtr, VMContext, - VMExternRef, VMExternRefActivationsTable, VMFuncRef, VMRuntimeLimits, WasmFault, + ExportGlobal, InstanceAllocationRequest, InstanceAllocator, InstanceHandle, ModuleInfo, + OnDemandInstanceAllocator, SignalHandler, StoreBox, StorePtr, VMContext, VMExternRef, + VMExternRefActivationsTable, VMFuncRef, VMRuntimeLimits, WasmFault, }; mod context; @@ -433,8 +433,14 @@ where /// instance allocator. struct StoreInstance { handle: InstanceHandle, - // Stores whether or not to use the on-demand allocator to deallocate the instance - ondemand: bool, + + /// Whether or not this is a dummy instance that is just an implementation + /// detail for something else. For example, host-created memories internally + /// create a dummy instance. + /// + /// Regardless of the configured instance allocator for this engine, dummy + /// instances always use the on-demand allocator to deallocate the instance. + dummy: bool, } #[derive(Copy, Clone)] @@ -1247,10 +1253,23 @@ impl StoreOpaque { &mut self.host_globals } - pub unsafe fn add_instance(&mut self, handle: InstanceHandle, ondemand: bool) -> InstanceId { + pub unsafe fn add_instance(&mut self, handle: InstanceHandle) -> InstanceId { + self.instances.push(StoreInstance { + handle: handle.clone(), + dummy: false, + }); + InstanceId(self.instances.len() - 1) + } + + /// Add a dummy instance that to the store. + /// + /// These are instances that are just implementation details of something + /// else (e.g. host-created memories that are not actually defined in any + /// Wasm module) and therefore shouldn't show up in things like core dumps. + pub unsafe fn add_dummy_instance(&mut self, handle: InstanceHandle) -> InstanceId { self.instances.push(StoreInstance { handle: handle.clone(), - ondemand, + dummy: true, }); InstanceId(self.instances.len() - 1) } @@ -1263,22 +1282,62 @@ impl StoreOpaque { &mut self.instances[id.0].handle } - pub fn all_instances(&self) -> impl ExactSizeIterator { - self.store_data() - .iter::() - .map(Instance::from_stored) + /// Get all instances (ignoring dummy instances) within this store. + pub fn all_instances<'a>(&'a mut self) -> impl ExactSizeIterator + 'a { + let instances = self + .instances + .iter() + .enumerate() + .filter_map(|(idx, inst)| { + let id = InstanceId::from_index(idx); + if inst.dummy { + None + } else { + Some(InstanceData::from_id(id)) + } + }) + .collect::>(); + instances + .into_iter() + .map(|i| Instance::from_wasmtime(i, self)) } - pub fn all_memories(&self) -> impl ExactSizeIterator { - self.store_data() - .iter::() - .map(Memory::from_stored) + /// Get all memories (host- or Wasm-defined) within this store. + pub fn all_memories<'a>(&'a mut self) -> impl Iterator + 'a { + let mems = self + .instances + .iter_mut() + .flat_map(|instance| instance.handle.defined_memories()) + .collect::>(); + mems.into_iter() + .map(|memory| unsafe { Memory::from_wasmtime_memory(memory, self) }) } - pub fn all_globals(&self) -> impl ExactSizeIterator { - self.store_data() - .iter::() - .map(Global::from_stored) + /// Iterate over all globals (host- or Wasm-defined) within this store. + pub fn all_globals<'a>(&'a mut self) -> impl Iterator + 'a { + unsafe { + // First gather all the host-created globals. + let mut globals = self + .host_globals() + .iter() + .map(|global| ExportGlobal { + definition: &mut (*global.get()).global as *mut _, + global: (*global.get()).ty.to_wasm_type(), + }) + .collect::>(); + + // Then iterate over all instances and yield each of their defined + // globals. + globals.extend( + self.instances.iter_mut().flat_map(|instance| { + instance.handle.defined_globals().map(|(_i, global)| global) + }), + ); + + globals + .into_iter() + .map(|g| Global::from_wasmtime_global(g, self)) + } } #[cfg_attr(not(target_os = "linux"), allow(dead_code))] // not used on all platforms @@ -2207,7 +2266,7 @@ impl Drop for StoreOpaque { let allocator = self.engine.allocator(); let ondemand = OnDemandInstanceAllocator::default(); for instance in self.instances.iter_mut() { - if instance.ondemand { + if instance.dummy { ondemand.deallocate_module(&mut instance.handle); } else { allocator.deallocate_module(&mut instance.handle); diff --git a/crates/wasmtime/src/store/data.rs b/crates/wasmtime/src/store/data.rs index 08764d293258..390374985f90 100644 --- a/crates/wasmtime/src/store/data.rs +++ b/crates/wasmtime/src/store/data.rs @@ -13,6 +13,12 @@ use std::sync::atomic::{AtomicU64, Ordering::Relaxed}; #[derive(Copy, Clone)] pub struct InstanceId(pub(super) usize); +impl InstanceId { + pub fn from_index(idx: usize) -> InstanceId { + InstanceId(idx) + } +} + pub struct StoreData { id: StoreId, funcs: Vec, diff --git a/crates/wasmtime/src/trampoline.rs b/crates/wasmtime/src/trampoline.rs index 928cc2cc2974..3822f6027777 100644 --- a/crates/wasmtime/src/trampoline.rs +++ b/crates/wasmtime/src/trampoline.rs @@ -50,7 +50,7 @@ fn create_handle( wmemcheck: false, })?; - Ok(store.add_instance(handle, true)) + Ok(store.add_dummy_instance(handle)) } } diff --git a/crates/wasmtime/src/trampoline/global.rs b/crates/wasmtime/src/trampoline/global.rs index 2aa49a6bd1c6..263e91b2e34f 100644 --- a/crates/wasmtime/src/trampoline/global.rs +++ b/crates/wasmtime/src/trampoline/global.rs @@ -5,8 +5,8 @@ use wasmtime_runtime::{StoreBox, VMGlobalDefinition}; #[repr(C)] pub struct VMHostGlobalContext { - ty: GlobalType, - global: VMGlobalDefinition, + pub(crate) ty: GlobalType, + pub(crate) global: VMGlobalDefinition, } impl Drop for VMHostGlobalContext { diff --git a/crates/wasmtime/src/trampoline/memory.rs b/crates/wasmtime/src/trampoline/memory.rs index a4ef32097960..bc1423991783 100644 --- a/crates/wasmtime/src/trampoline/memory.rs +++ b/crates/wasmtime/src/trampoline/memory.rs @@ -71,7 +71,7 @@ pub fn create_memory( ondemand: OnDemandInstanceAllocator::default(), } .allocate_module(request)?; - let instance_id = store.add_instance(handle.clone(), true); + let instance_id = store.add_dummy_instance(handle.clone()); Ok(instance_id) } } diff --git a/crates/wasmtime/src/trap.rs b/crates/wasmtime/src/trap.rs index e62ba73b1f46..1d0437aa1b28 100644 --- a/crates/wasmtime/src/trap.rs +++ b/crates/wasmtime/src/trap.rs @@ -77,7 +77,7 @@ pub(crate) unsafe fn raise(error: anyhow::Error) -> ! { #[cold] // traps are exceptional, this helps move handling off the main path pub(crate) fn from_runtime_box( - store: &StoreOpaque, + store: &mut StoreOpaque, runtime_trap: Box, ) -> Error { let wasmtime_runtime::Trap { diff --git a/crates/wasmtime/src/types.rs b/crates/wasmtime/src/types.rs index b1d33709417a..98b3ca73a67b 100644 --- a/crates/wasmtime/src/types.rs +++ b/crates/wasmtime/src/types.rs @@ -272,6 +272,15 @@ impl GlobalType { self.mutability } + pub(crate) fn to_wasm_type(&self) -> Global { + let wasm_ty = self.content().to_wasm_type(); + let mutability = matches!(self.mutability(), Mutability::Var); + Global { + wasm_ty, + mutability, + } + } + /// Returns `None` if the wasmtime global has a type that we can't /// represent, but that should only very rarely happen and indicate a bug. pub(crate) fn from_wasmtime_global(global: &Global) -> GlobalType { diff --git a/tests/all/coredump.rs b/tests/all/coredump.rs index d372965ccef1..a7ee535f0235 100644 --- a/tests/all/coredump.rs +++ b/tests/all/coredump.rs @@ -3,17 +3,17 @@ use wasmtime::*; #[test] #[cfg_attr(miri, ignore)] -fn test_coredump_attached_to_error() -> Result<()> { +fn 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)) - ) + (module + (func $hello (import "" "hello")) + (func (export "run") (call $hello)) + ) "#; let module = Module::new(store.engine(), wat)?; @@ -36,7 +36,7 @@ fn test_coredump_attached_to_error() -> Result<()> { #[test] #[cfg_attr(miri, ignore)] -fn test_coredump_has_stack() -> Result<()> { +fn coredump_has_stack() -> Result<()> { let mut config = Config::default(); config.coredump_on_trap(true); let engine = Engine::new(&config).unwrap(); @@ -44,15 +44,15 @@ fn test_coredump_has_stack() -> Result<()> { let wat = r#" (module - (func $a (export "a") - call $b - ) - (func $b - call $c - ) - (func $c - unreachable - ) + (func $a (export "a") + call $b + ) + (func $b + call $c + ) + (func $c + unreachable + ) ) "#; @@ -71,7 +71,7 @@ fn test_coredump_has_stack() -> Result<()> { #[test] #[cfg_attr(miri, ignore)] -fn test_coredump_has_modules_and_instances() -> Result<()> { +fn coredump_has_modules_and_instances() -> Result<()> { let mut config = Config::default(); config.coredump_on_trap(true); let engine = Engine::new(&config).unwrap(); @@ -79,19 +79,19 @@ fn test_coredump_has_modules_and_instances() -> Result<()> { let mut store = Store::<()>::new(&engine, ()); let wat1 = r#" - (module $foo - (import "bar" "b" (func $b)) - (func (export "a") - call $b + (module $foo + (import "bar" "b" (func $b)) + (func (export "a") + call $b + ) ) - ) "#; let wat2 = r#" - (module $bar - (func (export "b") - unreachable + (module $bar + (func (export "b") + unreachable + ) ) - ) "#; let module1 = Module::new(store.engine(), wat1)?; let module2 = Module::new(store.engine(), wat2)?; @@ -110,38 +110,146 @@ fn test_coredump_has_modules_and_instances() -> Result<()> { #[test] #[cfg_attr(miri, ignore)] -fn test_coredump_has_import_globals_and_memory() -> Result<()> { +fn coredump_has_host_globals_and_memory() -> Result<()> { let mut config = Config::default(); config.coredump_on_trap(true); let engine = Engine::new(&config).unwrap(); + + let module = Module::new( + &engine, + r#" + (module + (import "memory" "memory" (memory 1)) + (global $myglobal (import "global" "global") (mut i32)) + (func (export "a") (result i32) + unreachable + ) + (export "memory" (memory 0)) + (export "global" (global 0)) + ) + "#, + )?; + 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 memory = Memory::new(&mut store, MemoryType::new(1, None))?; + linker.define(&mut store, "memory", "memory", memory)?; - 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( + let global = Global::new( &mut store, GlobalType::new(ValType::I32, Mutability::Var), Val::I32(0), )?; - linker.define(&mut store, "js", "global", g)?; + linker.define(&mut store, "global", "global", global)?; + let instance = linker.instantiate(&mut store, &module)?; + + // Each time we extract the exports, it puts them in the `StoreData`. Our + // core dumps need to be robust to duplicate entries in the `StoreData`. + for _ in 0..10 { + let _ = instance.get_global(&mut store, "global").unwrap(); + let _ = instance.get_memory(&mut store, "memory").unwrap(); + } + 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); + let err = a_func.call(&mut store, ()).unwrap_err(); + let core_dump = err.downcast_ref::().unwrap(); + assert_eq!(core_dump.globals().len(), 1); + assert_eq!(core_dump.memories().len(), 1); + assert_eq!(core_dump.instances().len(), 1); + + Ok(()) +} + +#[test] +#[cfg_attr(miri, ignore)] +fn coredump_has_defined_globals_and_memory() -> Result<()> { + let mut config = Config::default(); + config.coredump_on_trap(true); + let engine = Engine::new(&config).unwrap(); + + let module = Module::new( + &engine, + r#" + (module + (global (mut i32) (i32.const 42)) + (memory 1) + (func (export "a") + unreachable + ) + ) + "#, + )?; + + let mut store = Store::<()>::new(&engine, ()); + let instance = Instance::new(&mut store, &module, &[])?; + + let a_func = instance.get_typed_func::<(), ()>(&mut store, "a")?; + let err = a_func.call(&mut store, ()).unwrap_err(); + let core_dump = err.downcast_ref::().unwrap(); + assert_eq!(core_dump.globals().len(), 1); + assert_eq!(core_dump.memories().len(), 1); + assert_eq!(core_dump.instances().len(), 1); + + Ok(()) +} + +#[test] +#[cfg_attr(miri, ignore)] +fn multiple_globals_memories_and_instances() -> Result<()> { + let mut config = Config::default(); + config.wasm_multi_memory(true); + config.coredump_on_trap(true); + let engine = Engine::new(&config).unwrap(); + let mut store = Store::<()>::new(&engine, ()); + let mut linker = Linker::new(&engine); + + let memory = Memory::new(&mut store, MemoryType::new(1, None))?; + linker.define(&mut store, "host", "memory", memory)?; + + let global = Global::new( + &mut store, + GlobalType::new(ValType::I32, Mutability::Var), + Val::I32(0), + )?; + linker.define(&mut store, "host", "global", global)?; + + let module_a = Module::new( + &engine, + r#" + (module + (memory (export "memory") 1) + (global (export "global") (mut i32) (i32.const 0)) + ) + "#, + )?; + let instance_a = linker.instantiate(&mut store, &module_a)?; + linker.instance(&mut store, "a", instance_a)?; + + let module_b = Module::new( + &engine, + r#" + (module + (import "host" "memory" (memory 1)) + (import "host" "global" (global (mut i32))) + (import "a" "memory" (memory 1)) + (import "a" "global" (global (mut i32))) + + (func (export "trap") + unreachable + ) + ) + "#, + )?; + let instance_b = linker.instantiate(&mut store, &module_b)?; + + let trap_func = instance_b.get_typed_func::<(), ()>(&mut store, "trap")?; + let err = trap_func.call(&mut store, ()).unwrap_err(); + let core_dump = err.downcast_ref::().unwrap(); + assert_eq!(core_dump.globals().len(), 2); + assert_eq!(core_dump.memories().len(), 2); + assert_eq!(core_dump.instances().len(), 2); Ok(()) }