dusk_wasmtime/runtime/
coredump.rs

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