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 // Core dumps are best-effort and may not capture every memory
273 // referenced by an instance. In particular, shared memories
274 // are intentionally omitted because their data cannot be
275 // safely read through `Memory`. Use an invalid index for any
276 // absent memory instead of panicking while serializing.
277 let memories = instance
278 .all_memories(store.0)
279 .filter_map(|(_, m)| m.unshared())
280 .map(|memory| {
281 memory_to_idx
282 .get(&memory.hash_key(&store.0))
283 .copied()
284 .unwrap_or(u32::MAX)
285 })
286 .collect::<Vec<_>>();
287
288 // Component adapter modules can import runtime-managed globals,
289 // such as component instance flags, whose definitions are not
290 // enumerated by `StoreOpaque::for_each_global`. These globals
291 // are visible through `Instance::all_globals` but absent from
292 // the dump's globals section, so use an invalid index rather
293 // than panicking while serializing.
294 let globals = instance
295 .all_globals(store.0)
296 .collect::<Vec<_>>()
297 .into_iter()
298 .map(|(_i, global)| {
299 global_to_idx
300 .get(&global.hash_key(&store.0))
301 .copied()
302 .unwrap_or(u32::MAX)
303 })
304 .collect::<Vec<_>>();
305
306 instances.instance(module_index, memories, globals);
307 }
308 core_dump.section(&instances);
309 }
310
311 {
312 let thread_name = "main";
313 let mut stack = wasm_encoder::CoreDumpStackSection::new(thread_name);
314 for frame in self.frames() {
315 // This isn't necessarily the right instance if there are
316 // multiple instances of the same module. See comment above
317 // `module_to_instance` for details.
318 let instance = module_to_instance[&frame.module().id()];
319
320 let func = frame.func_index();
321
322 let offset = frame
323 .func_offset()
324 .and_then(|o| u32::try_from(o).ok())
325 .unwrap_or(0);
326
327 // We can't currently recover locals and the operand stack. We
328 // should eventually be able to do that with Winch though.
329 let locals = [];
330 let operand_stack = [];
331
332 stack.frame(instance, func, offset, locals, operand_stack);
333 }
334 core_dump.section(&stack);
335 }
336
337 core_dump.finish()
338 }
339}
340
341impl fmt::Display for WasmCoreDump {
342 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
343 writeln!(f, "wasm coredump generated while executing {}:", self.name)?;
344 writeln!(f, "modules:")?;
345 for module in self.modules.iter() {
346 writeln!(f, " {}", module.name().unwrap_or("<module>"))?;
347 }
348
349 writeln!(f, "instances:")?;
350 for instance in self.instances.iter() {
351 writeln!(f, " {instance:?}")?;
352 }
353
354 writeln!(f, "memories:")?;
355 for memory in self.memories.iter() {
356 writeln!(f, " {memory:?}")?;
357 }
358
359 writeln!(f, "globals:")?;
360 for global in self.globals.iter() {
361 writeln!(f, " {global:?}")?;
362 }
363
364 writeln!(f, "backtrace:")?;
365 write!(f, "{}", self.backtrace)?;
366
367 Ok(())
368 }
369}
370
371impl fmt::Debug for WasmCoreDump {
372 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
373 write!(f, "<wasm core dump>")
374 }
375}