wasmtime/runtime/
coredump.rs

1use crate::hash_map::HashMap;
2use crate::prelude::*;
3use crate::{
4    AsContextMut, FrameInfo, Global, HeapType, Instance, Memory, Module, StoreContextMut, Val,
5    ValType, WasmBacktrace, store::StoreOpaque,
6};
7use std::fmt;
8
9/// Representation of a core dump of a WebAssembly module
10///
11/// When the Config::coredump_on_trap option is enabled this structure is
12/// attached to the [`anyhow::Error`] returned from many Wasmtime functions that
13/// execute WebAssembly such as [`Instance::new`] or [`Func::call`]. This can be
14/// acquired with the [`anyhow::Error::downcast`] family of methods to
15/// programmatically inspect the coredump. Otherwise since it's part of the
16/// error returned this will get printed along with the rest of the error when
17/// the error is logged.
18///
19/// Note that some state, such as Wasm locals or values on the operand stack,
20/// may be optimized away by the compiler or otherwise not recovered in the
21/// coredump.
22///
23/// Capturing of wasm coredumps can be configured through the
24/// [`Config::coredump_on_trap`][crate::Config::coredump_on_trap] method.
25///
26/// For more information about errors in wasmtime see the documentation of the
27/// [`Trap`][crate::Trap] type.
28///
29/// [`Func::call`]: crate::Func::call
30/// [`Instance::new`]: crate::Instance::new
31pub struct WasmCoreDump {
32    name: String,
33    modules: Vec<Module>,
34    instances: Vec<Instance>,
35    memories: Vec<Memory>,
36    globals: Vec<Global>,
37    backtrace: WasmBacktrace,
38}
39
40impl WasmCoreDump {
41    pub(crate) fn new(store: &mut StoreOpaque, backtrace: WasmBacktrace) -> WasmCoreDump {
42        let modules: Vec<_> = store.modules().all_modules().cloned().collect();
43        let instances: Vec<Instance> = store.all_instances().collect();
44        let mut store_memories: Vec<Memory> = store.all_memories().collect();
45        store_memories.retain(|m| !m.wasmtime_ty(store).shared);
46
47        let mut store_globals: Vec<Global> = vec![];
48        store.for_each_global(|_store, global| store_globals.push(global));
49
50        WasmCoreDump {
51            name: String::from("store_name"),
52            modules,
53            instances,
54            memories: store_memories,
55            globals: store_globals,
56            backtrace,
57        }
58    }
59
60    /// The stack frames for this core dump.
61    ///
62    /// Frames appear in callee to caller order, that is youngest to oldest
63    /// frames.
64    pub fn frames(&self) -> &[FrameInfo] {
65        self.backtrace.frames()
66    }
67
68    /// All modules instantiated inside the store when the core dump was
69    /// created.
70    pub fn modules(&self) -> &[Module] {
71        self.modules.as_ref()
72    }
73
74    /// All instances within the store when the core dump was created.
75    pub fn instances(&self) -> &[Instance] {
76        self.instances.as_ref()
77    }
78
79    /// All globals, instance- or host-defined, within the store when the core
80    /// dump was created.
81    pub fn globals(&self) -> &[Global] {
82        self.globals.as_ref()
83    }
84
85    /// All memories, instance- or host-defined, within the store when the core
86    /// dump was created.
87    pub fn memories(&self) -> &[Memory] {
88        self.memories.as_ref()
89    }
90
91    /// Serialize this core dump into [the standard core dump binary
92    /// format][spec].
93    ///
94    /// The `name` parameter may be a file path, URL, or arbitrary name for the
95    /// "main" Wasm service or executable that was running in this store.
96    ///
97    /// Once serialized, you can write this core dump to disk, send it over the
98    /// network, or pass it to other debugging tools that consume Wasm core
99    /// dumps.
100    ///
101    /// [spec]: https://github.com/WebAssembly/tool-conventions/blob/main/Coredump.md
102    pub fn serialize(&self, mut store: impl AsContextMut, name: &str) -> Vec<u8> {
103        let store = store.as_context_mut();
104        self._serialize(store, name)
105    }
106
107    fn _serialize<T: 'static>(&self, mut store: StoreContextMut<'_, T>, name: &str) -> Vec<u8> {
108        let mut core_dump = wasm_encoder::Module::new();
109
110        core_dump.section(&wasm_encoder::CoreDumpSection::new(name));
111
112        // A map from each memory to its index in the core dump's memories
113        // section.
114        let mut memory_to_idx = HashMap::new();
115
116        let mut data = wasm_encoder::DataSection::new();
117
118        {
119            let mut memories = wasm_encoder::MemorySection::new();
120            for mem in self.memories() {
121                let memory_idx = memories.len();
122                memory_to_idx.insert(mem.hash_key(&store.0), memory_idx);
123                let ty = mem.ty(&store);
124                memories.memory(wasm_encoder::MemoryType {
125                    minimum: mem.size(&store),
126                    maximum: ty.maximum(),
127                    memory64: ty.is_64(),
128                    shared: ty.is_shared(),
129                    page_size_log2: None,
130                });
131
132                // Attach the memory data, balancing number of data segments and
133                // binary size. We don't want to attach the whole memory in one
134                // big segment, since it likely contains a bunch of large runs
135                // of zeroes. But we can't encode the data without any potential
136                // runs of zeroes (i.e. including only non-zero data in our
137                // segments) because we can run up against the implementation
138                // limits for number of segments in a Wasm module this way. So
139                // to balance these conflicting desires, we break the memory up
140                // into reasonably-sized chunks and then trim runs of zeroes
141                // from the start and end of each chunk.
142                const CHUNK_SIZE: usize = 4096;
143                for (i, chunk) in mem.data(&store).chunks_exact(CHUNK_SIZE).enumerate() {
144                    if let Some(start) = chunk.iter().position(|byte| *byte != 0) {
145                        let end = chunk.iter().rposition(|byte| *byte != 0).unwrap() + 1;
146                        let offset = i * CHUNK_SIZE + start;
147                        let offset = if ty.is_64() {
148                            let offset = u64::try_from(offset).unwrap();
149                            wasm_encoder::ConstExpr::i64_const(offset as i64)
150                        } else {
151                            let offset = u32::try_from(offset).unwrap();
152                            wasm_encoder::ConstExpr::i32_const(offset as i32)
153                        };
154                        data.active(memory_idx, &offset, chunk[start..end].iter().copied());
155                    }
156                }
157            }
158            core_dump.section(&memories);
159        }
160
161        // A map from each global to its index in the core dump's globals
162        // section.
163        let mut global_to_idx = HashMap::new();
164
165        {
166            let mut globals = wasm_encoder::GlobalSection::new();
167            for g in self.globals() {
168                global_to_idx.insert(g.hash_key(&store.0), globals.len());
169                let ty = g.ty(&store);
170                let mutable = matches!(ty.mutability(), crate::Mutability::Var);
171                let val_type = match ty.content() {
172                    ValType::I32 => wasm_encoder::ValType::I32,
173                    ValType::I64 => wasm_encoder::ValType::I64,
174                    ValType::F32 => wasm_encoder::ValType::F32,
175                    ValType::F64 => wasm_encoder::ValType::F64,
176                    ValType::V128 => wasm_encoder::ValType::V128,
177
178                    // We encode all references as null in the core dump, so
179                    // choose the common super type of all the actual function
180                    // reference types. This lets us avoid needing to figure out
181                    // what a concrete type reference's index is in the local
182                    // core dump index space.
183                    ValType::Ref(r) => match r.heap_type().top() {
184                        HeapType::Extern => wasm_encoder::ValType::EXTERNREF,
185
186                        HeapType::Func => wasm_encoder::ValType::FUNCREF,
187
188                        HeapType::Any => wasm_encoder::ValType::Ref(wasm_encoder::RefType::ANYREF),
189
190                        ty => unreachable!("not a top type: {ty:?}"),
191                    },
192                };
193                let init = match g.get(&mut store) {
194                    Val::I32(x) => wasm_encoder::ConstExpr::i32_const(x),
195                    Val::I64(x) => wasm_encoder::ConstExpr::i64_const(x),
196                    Val::F32(x) => wasm_encoder::ConstExpr::f32_const(f32::from_bits(x).into()),
197                    Val::F64(x) => wasm_encoder::ConstExpr::f64_const(f64::from_bits(x).into()),
198                    Val::V128(x) => wasm_encoder::ConstExpr::v128_const(x.as_u128() as i128),
199                    Val::FuncRef(_) => {
200                        wasm_encoder::ConstExpr::ref_null(wasm_encoder::HeapType::FUNC)
201                    }
202                    Val::ExternRef(_) => {
203                        wasm_encoder::ConstExpr::ref_null(wasm_encoder::HeapType::EXTERN)
204                    }
205                    Val::AnyRef(_) => {
206                        wasm_encoder::ConstExpr::ref_null(wasm_encoder::HeapType::ANY)
207                    }
208                    Val::ExnRef(_) => {
209                        wasm_encoder::ConstExpr::ref_null(wasm_encoder::HeapType::Abstract {
210                            shared: false,
211                            ty: wasm_encoder::AbstractHeapType::Exn,
212                        })
213                    }
214                    Val::ContRef(_) => {
215                        wasm_encoder::ConstExpr::ref_null(wasm_encoder::HeapType::Abstract {
216                            shared: false,
217                            ty: wasm_encoder::AbstractHeapType::Cont,
218                        })
219                    }
220                };
221                globals.global(
222                    wasm_encoder::GlobalType {
223                        val_type,
224                        mutable,
225                        shared: false,
226                    },
227                    &init,
228                );
229            }
230            core_dump.section(&globals);
231        }
232
233        core_dump.section(&data);
234        drop(data);
235
236        // A map from module id to its index within the core dump's modules
237        // section.
238        let mut module_to_index = HashMap::new();
239
240        {
241            let mut modules = wasm_encoder::CoreDumpModulesSection::new();
242            for module in self.modules() {
243                module_to_index.insert(module.id(), modules.len());
244                match module.name() {
245                    Some(name) => modules.module(name),
246                    None => modules.module(&format!("<anonymous-module-{}>", modules.len())),
247                };
248            }
249            core_dump.section(&modules);
250        }
251
252        // TODO: We can't currently recover instances from stack frames. We can
253        // recover module via the frame's PC, but if there are multiple
254        // instances of the same module, we don't know which instance the frame
255        // is associated with. Therefore, we do a best effort job: remember the
256        // last instance of each module and always choose that one. We record
257        // that information here.
258        let mut module_to_instance = HashMap::new();
259
260        {
261            let mut instances = wasm_encoder::CoreDumpInstancesSection::new();
262            for instance in self.instances() {
263                let module = instance.module(&store);
264                module_to_instance.insert(module.id(), instances.len());
265
266                let module_index = module_to_index[&module.id()];
267
268                let memories = instance
269                    .all_memories(store.0)
270                    .collect::<Vec<_>>()
271                    .into_iter()
272                    .map(|(_i, memory)| {
273                        memory_to_idx
274                            .get(&memory.hash_key(&store.0))
275                            .copied()
276                            .unwrap_or(u32::MAX)
277                    })
278                    .collect::<Vec<_>>();
279
280                let globals = instance
281                    .all_globals(store.0)
282                    .collect::<Vec<_>>()
283                    .into_iter()
284                    .map(|(_i, global)| global_to_idx[&global.hash_key(&store.0)])
285                    .collect::<Vec<_>>();
286
287                instances.instance(module_index, memories, globals);
288            }
289            core_dump.section(&instances);
290        }
291
292        {
293            let thread_name = "main";
294            let mut stack = wasm_encoder::CoreDumpStackSection::new(thread_name);
295            for frame in self.frames() {
296                // This isn't necessarily the right instance if there are
297                // multiple instances of the same module. See comment above
298                // `module_to_instance` for details.
299                let instance = module_to_instance[&frame.module().id()];
300
301                let func = frame.func_index();
302
303                let offset = frame
304                    .func_offset()
305                    .and_then(|o| u32::try_from(o).ok())
306                    .unwrap_or(0);
307
308                // We can't currently recover locals and the operand stack. We
309                // should eventually be able to do that with Winch though.
310                let locals = [];
311                let operand_stack = [];
312
313                stack.frame(instance, func, offset, locals, operand_stack);
314            }
315            core_dump.section(&stack);
316        }
317
318        core_dump.finish()
319    }
320}
321
322impl fmt::Display for WasmCoreDump {
323    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
324        writeln!(f, "wasm coredump generated while executing {}:", self.name)?;
325        writeln!(f, "modules:")?;
326        for module in self.modules.iter() {
327            writeln!(f, "  {}", module.name().unwrap_or("<module>"))?;
328        }
329
330        writeln!(f, "instances:")?;
331        for instance in self.instances.iter() {
332            writeln!(f, "  {instance:?}")?;
333        }
334
335        writeln!(f, "memories:")?;
336        for memory in self.memories.iter() {
337            writeln!(f, "  {memory:?}")?;
338        }
339
340        writeln!(f, "globals:")?;
341        for global in self.globals.iter() {
342            writeln!(f, "  {global:?}")?;
343        }
344
345        writeln!(f, "backtrace:")?;
346        write!(f, "{}", self.backtrace)?;
347
348        Ok(())
349    }
350}
351
352impl fmt::Debug for WasmCoreDump {
353    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
354        write!(f, "<wasm core dump>")
355    }
356}