wasmtime/runtime/coredump.rs
1use crate::hash_map::HashMap;
2use crate::prelude::*;
3use crate::{
4 AsContextMut, FrameInfo, Global, HeapTopType, 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 HeapTopType::Extern => wasm_encoder::ValType::EXTERNREF,
189 HeapTopType::Func => wasm_encoder::ValType::FUNCREF,
190 HeapTopType::Any => {
191 wasm_encoder::ValType::Ref(wasm_encoder::RefType::ANYREF)
192 }
193 HeapTopType::Exn => {
194 wasm_encoder::ValType::Ref(wasm_encoder::RefType::EXNREF)
195 }
196 HeapTopType::Cont => {
197 wasm_encoder::ValType::Ref(wasm_encoder::RefType::new_abstract(
198 wasm_encoder::AbstractHeapType::Cont,
199 true,
200 false,
201 ))
202 }
203 },
204 };
205 let init = match g.get(&mut store) {
206 Val::I32(x) => wasm_encoder::ConstExpr::i32_const(x),
207 Val::I64(x) => wasm_encoder::ConstExpr::i64_const(x),
208 Val::F32(x) => wasm_encoder::ConstExpr::f32_const(f32::from_bits(x).into()),
209 Val::F64(x) => wasm_encoder::ConstExpr::f64_const(f64::from_bits(x).into()),
210 Val::V128(x) => wasm_encoder::ConstExpr::v128_const(x.as_u128() as i128),
211 Val::FuncRef(_) => {
212 wasm_encoder::ConstExpr::ref_null(wasm_encoder::HeapType::FUNC)
213 }
214 Val::ExternRef(_) => {
215 wasm_encoder::ConstExpr::ref_null(wasm_encoder::HeapType::EXTERN)
216 }
217 Val::AnyRef(_) => {
218 wasm_encoder::ConstExpr::ref_null(wasm_encoder::HeapType::ANY)
219 }
220 Val::ExnRef(_) => {
221 wasm_encoder::ConstExpr::ref_null(wasm_encoder::HeapType::Abstract {
222 shared: false,
223 ty: wasm_encoder::AbstractHeapType::Exn,
224 })
225 }
226 Val::ContRef(_) => {
227 wasm_encoder::ConstExpr::ref_null(wasm_encoder::HeapType::Abstract {
228 shared: false,
229 ty: wasm_encoder::AbstractHeapType::Cont,
230 })
231 }
232 };
233 globals.global(
234 wasm_encoder::GlobalType {
235 val_type,
236 mutable,
237 shared: false,
238 },
239 &init,
240 );
241 }
242 core_dump.section(&globals);
243 }
244
245 core_dump.section(&data);
246 drop(data);
247
248 // A map from module id to its index within the core dump's modules
249 // section.
250 let mut module_to_index = HashMap::new();
251
252 {
253 let mut modules = wasm_encoder::CoreDumpModulesSection::new();
254 for module in self.modules() {
255 module_to_index.insert(module.id(), modules.len());
256 match module.name() {
257 Some(name) => modules.module(name),
258 None => modules.module(&format!("<anonymous-module-{}>", modules.len())),
259 };
260 }
261 core_dump.section(&modules);
262 }
263
264 // TODO: We can't currently recover instances from stack frames. We can
265 // recover module via the frame's PC, but if there are multiple
266 // instances of the same module, we don't know which instance the frame
267 // is associated with. Therefore, we do a best effort job: remember the
268 // last instance of each module and always choose that one. We record
269 // that information here.
270 let mut module_to_instance = HashMap::new();
271
272 {
273 let mut instances = wasm_encoder::CoreDumpInstancesSection::new();
274 for instance in self.instances() {
275 let module = instance.module(&store);
276 module_to_instance.insert(module.id(), instances.len());
277
278 let module_index = module_to_index[&module.id()];
279
280 // Core dumps are best-effort and may not capture every memory
281 // referenced by an instance. In particular, shared memories
282 // are intentionally omitted because their data cannot be
283 // safely read through `Memory`. Use an invalid index for any
284 // absent memory instead of panicking while serializing.
285 let memories = instance
286 .all_memories(store.0)
287 .filter_map(|(_, m)| m.unshared())
288 .map(|memory| {
289 memory_to_idx
290 .get(&memory.hash_key(&store.0))
291 .copied()
292 .unwrap_or(u32::MAX)
293 })
294 .collect::<Vec<_>>();
295
296 // Component adapter modules can import runtime-managed globals,
297 // such as component instance flags, whose definitions are not
298 // enumerated by `StoreOpaque::for_each_global`. These globals
299 // are visible through `Instance::all_globals` but absent from
300 // the dump's globals section, so use an invalid index rather
301 // than panicking while serializing.
302 let globals = instance
303 .all_globals(store.0)
304 .collect::<Vec<_>>()
305 .into_iter()
306 .map(|(_i, global)| {
307 global_to_idx
308 .get(&global.hash_key(&store.0))
309 .copied()
310 .unwrap_or(u32::MAX)
311 })
312 .collect::<Vec<_>>();
313
314 instances.instance(module_index, memories, globals);
315 }
316 core_dump.section(&instances);
317 }
318
319 {
320 let thread_name = "main";
321 let mut stack = wasm_encoder::CoreDumpStackSection::new(thread_name);
322 for frame in self.frames() {
323 // This isn't necessarily the right instance if there are
324 // multiple instances of the same module. See comment above
325 // `module_to_instance` for details.
326 let instance = module_to_instance[&frame.module().id()];
327
328 let func = frame.func_index();
329
330 let offset = frame
331 .func_offset()
332 .and_then(|o| u32::try_from(o).ok())
333 .unwrap_or(0);
334
335 // We can't currently recover locals and the operand stack. We
336 // should eventually be able to do that with Winch though.
337 let locals = [];
338 let operand_stack = [];
339
340 stack.frame(instance, func, offset, locals, operand_stack);
341 }
342 core_dump.section(&stack);
343 }
344
345 core_dump.finish()
346 }
347}
348
349impl fmt::Display for WasmCoreDump {
350 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
351 writeln!(f, "wasm coredump generated while executing {}:", self.name)?;
352 writeln!(f, "modules:")?;
353 for module in self.modules.iter() {
354 writeln!(f, " {}", module.name().unwrap_or("<module>"))?;
355 }
356
357 writeln!(f, "instances:")?;
358 for instance in self.instances.iter() {
359 writeln!(f, " {instance:?}")?;
360 }
361
362 writeln!(f, "memories:")?;
363 for memory in self.memories.iter() {
364 writeln!(f, " {memory:?}")?;
365 }
366
367 writeln!(f, "globals:")?;
368 for global in self.globals.iter() {
369 writeln!(f, " {global:?}")?;
370 }
371
372 writeln!(f, "backtrace:")?;
373 write!(f, "{}", self.backtrace)?;
374
375 Ok(())
376 }
377}
378
379impl fmt::Debug for WasmCoreDump {
380 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
381 write!(f, "<wasm core dump>")
382 }
383}