1 use crate::hash_map::HashMap;
2 use crate::prelude::*;
3 use crate::{
4     AsContextMut, FrameInfo, Global, HeapType, Instance, Memory, Module, StoreContextMut, Val,
5     ValType, WasmBacktrace, store::StoreOpaque,
6 };
7 use 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
31 pub 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 
40 impl WasmCoreDump {
new(store: &mut StoreOpaque, backtrace: WasmBacktrace) -> WasmCoreDump41     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 store_memories: Vec<Memory> =
45             store.all_memories().filter_map(|m| m.unshared()).collect();
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.
frames(&self) -> &[FrameInfo]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.
modules(&self) -> &[Module]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.
instances(&self) -> &[Instance]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.
globals(&self) -> &[Global]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.
memories(&self) -> &[Memory]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
serialize(&self, mut store: impl AsContextMut, name: &str) -> Vec<u8>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 
_serialize<T: 'static>(&self, mut store: StoreContextMut<'_, T>, name: &str) -> Vec<u8>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                     .filter_map(|(_, m)| m.unshared())
271                     .map(|memory| {
272                         memory_to_idx
273                             .get(&memory.hash_key(&store.0))
274                             .copied()
275                             .unwrap_or(u32::MAX)
276                     })
277                     .collect::<Vec<_>>();
278 
279                 let globals = instance
280                     .all_globals(store.0)
281                     .collect::<Vec<_>>()
282                     .into_iter()
283                     .map(|(_i, global)| global_to_idx[&global.hash_key(&store.0)])
284                     .collect::<Vec<_>>();
285 
286                 instances.instance(module_index, memories, globals);
287             }
288             core_dump.section(&instances);
289         }
290 
291         {
292             let thread_name = "main";
293             let mut stack = wasm_encoder::CoreDumpStackSection::new(thread_name);
294             for frame in self.frames() {
295                 // This isn't necessarily the right instance if there are
296                 // multiple instances of the same module. See comment above
297                 // `module_to_instance` for details.
298                 let instance = module_to_instance[&frame.module().id()];
299 
300                 let func = frame.func_index();
301 
302                 let offset = frame
303                     .func_offset()
304                     .and_then(|o| u32::try_from(o).ok())
305                     .unwrap_or(0);
306 
307                 // We can't currently recover locals and the operand stack. We
308                 // should eventually be able to do that with Winch though.
309                 let locals = [];
310                 let operand_stack = [];
311 
312                 stack.frame(instance, func, offset, locals, operand_stack);
313             }
314             core_dump.section(&stack);
315         }
316 
317         core_dump.finish()
318     }
319 }
320 
321 impl fmt::Display for WasmCoreDump {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result322     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
323         writeln!(f, "wasm coredump generated while executing {}:", self.name)?;
324         writeln!(f, "modules:")?;
325         for module in self.modules.iter() {
326             writeln!(f, "  {}", module.name().unwrap_or("<module>"))?;
327         }
328 
329         writeln!(f, "instances:")?;
330         for instance in self.instances.iter() {
331             writeln!(f, "  {instance:?}")?;
332         }
333 
334         writeln!(f, "memories:")?;
335         for memory in self.memories.iter() {
336             writeln!(f, "  {memory:?}")?;
337         }
338 
339         writeln!(f, "globals:")?;
340         for global in self.globals.iter() {
341             writeln!(f, "  {global:?}")?;
342         }
343 
344         writeln!(f, "backtrace:")?;
345         write!(f, "{}", self.backtrace)?;
346 
347         Ok(())
348     }
349 }
350 
351 impl fmt::Debug for WasmCoreDump {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result352     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
353         write!(f, "<wasm core dump>")
354     }
355 }
356