Skip to main content

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