wasmtime/runtime/
coredump.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
use crate::hash_map::HashMap;
use crate::prelude::*;
use crate::{
    store::StoreOpaque, AsContextMut, FrameInfo, Global, HeapType, Instance, Memory, Module,
    StoreContextMut, Val, ValType, WasmBacktrace,
};
use std::fmt;

/// Representation of a core dump of a WebAssembly module
///
/// When the Config::coredump_on_trap option is enabled this structure is
/// attached to the [`anyhow::Error`] returned from many Wasmtime functions that
/// execute WebAssembly such as [`Instance::new`] or [`Func::call`]. This can be
/// acquired with the [`anyhow::Error::downcast`] family of methods to
/// programmatically inspect the coredump. Otherwise since it's part of the
/// error returned this will get printed along with the rest of the error when
/// the error is logged.
///
/// Note that some state, such as Wasm locals or values on the operand stack,
/// may be optimized away by the compiler or otherwise not recovered in the
/// coredump.
///
/// Capturing of wasm coredumps can be configured through the
/// [`Config::coredump_on_trap`][crate::Config::coredump_on_trap] method.
///
/// For more information about errors in wasmtime see the documentation of the
/// [`Trap`][crate::Trap] type.
///
/// [`Func::call`]: crate::Func::call
/// [`Instance::new`]: crate::Instance::new
pub struct WasmCoreDump {
    name: String,
    modules: Vec<Module>,
    instances: Vec<Instance>,
    memories: Vec<Memory>,
    globals: Vec<Global>,
    backtrace: WasmBacktrace,
}

impl WasmCoreDump {
    pub(crate) fn new(store: &mut StoreOpaque, backtrace: WasmBacktrace) -> WasmCoreDump {
        let modules: Vec<_> = store.modules().all_modules().cloned().collect();
        let instances: Vec<Instance> = store.all_instances().collect();
        let store_memories: Vec<Memory> = store.all_memories().collect();

        let mut store_globals: Vec<Global> = vec![];
        store.for_each_global(|_store, global| store_globals.push(global));

        WasmCoreDump {
            name: String::from("store_name"),
            modules,
            instances,
            memories: store_memories,
            globals: store_globals,
            backtrace,
        }
    }

    /// The stack frames for this core dump.
    ///
    /// Frames appear in callee to caller order, that is youngest to oldest
    /// frames.
    pub fn frames(&self) -> &[FrameInfo] {
        self.backtrace.frames()
    }

    /// All modules instantiated inside the store when the core dump was
    /// created.
    pub fn modules(&self) -> &[Module] {
        self.modules.as_ref()
    }

    /// All instances within the store when the core dump was created.
    pub fn instances(&self) -> &[Instance] {
        self.instances.as_ref()
    }

    /// All globals, instance- or host-defined, within the store when the core
    /// dump was created.
    pub fn globals(&self) -> &[Global] {
        self.globals.as_ref()
    }

    /// All memories, instance- or host-defined, within the store when the core
    /// dump was created.
    pub fn memories(&self) -> &[Memory] {
        self.memories.as_ref()
    }

    /// Serialize this core dump into [the standard core dump binary
    /// format][spec].
    ///
    /// The `name` parameter may be a file path, URL, or arbitrary name for the
    /// "main" Wasm service or executable that was running in this store.
    ///
    /// Once serialized, you can write this core dump to disk, send it over the
    /// network, or pass it to other debugging tools that consume Wasm core
    /// dumps.
    ///
    /// [spec]: https://github.com/WebAssembly/tool-conventions/blob/main/Coredump.md
    pub fn serialize(&self, mut store: impl AsContextMut, name: &str) -> Vec<u8> {
        let store = store.as_context_mut();
        self._serialize(store, name)
    }

    fn _serialize<T>(&self, mut store: StoreContextMut<'_, T>, name: &str) -> Vec<u8> {
        let mut core_dump = wasm_encoder::Module::new();

        core_dump.section(&wasm_encoder::CoreDumpSection::new(name));

        // A map from each memory to its index in the core dump's memories
        // section.
        let mut memory_to_idx = HashMap::new();

        let mut data = wasm_encoder::DataSection::new();

        {
            let mut memories = wasm_encoder::MemorySection::new();
            for mem in self.memories() {
                let memory_idx = memories.len();
                memory_to_idx.insert(mem.hash_key(&store.0), memory_idx);
                let ty = mem.ty(&store);
                memories.memory(wasm_encoder::MemoryType {
                    minimum: mem.size(&store),
                    maximum: ty.maximum(),
                    memory64: ty.is_64(),
                    shared: ty.is_shared(),
                    page_size_log2: None,
                });

                // Attach the memory data, balancing number of data segments and
                // binary size. We don't want to attach the whole memory in one
                // big segment, since it likely contains a bunch of large runs
                // of zeroes. But we can't encode the data without any potential
                // runs of zeroes (i.e. including only non-zero data in our
                // segments) because we can run up against the implementation
                // limits for number of segments in a Wasm module this way. So
                // to balance these conflicting desires, we break the memory up
                // into reasonably-sized chunks and then trim runs of zeroes
                // from the start and end of each chunk.
                const CHUNK_SIZE: usize = 4096;
                for (i, chunk) in mem.data(&store).chunks_exact(CHUNK_SIZE).enumerate() {
                    if let Some(start) = chunk.iter().position(|byte| *byte != 0) {
                        let end = chunk.iter().rposition(|byte| *byte != 0).unwrap() + 1;
                        let offset = i * CHUNK_SIZE + start;
                        let offset = if ty.is_64() {
                            let offset = u64::try_from(offset).unwrap();
                            wasm_encoder::ConstExpr::i64_const(offset as i64)
                        } else {
                            let offset = u32::try_from(offset).unwrap();
                            wasm_encoder::ConstExpr::i32_const(offset as i32)
                        };
                        data.active(memory_idx, &offset, chunk[start..end].iter().copied());
                    }
                }
            }
            core_dump.section(&memories);
        }

        // A map from each global to its index in the core dump's globals
        // section.
        let mut global_to_idx = HashMap::new();

        {
            let mut globals = wasm_encoder::GlobalSection::new();
            for g in self.globals() {
                global_to_idx.insert(g.hash_key(&store.0), globals.len());
                let ty = g.ty(&store);
                let mutable = matches!(ty.mutability(), crate::Mutability::Var);
                let val_type = match ty.content() {
                    ValType::I32 => wasm_encoder::ValType::I32,
                    ValType::I64 => wasm_encoder::ValType::I64,
                    ValType::F32 => wasm_encoder::ValType::F32,
                    ValType::F64 => wasm_encoder::ValType::F64,
                    ValType::V128 => wasm_encoder::ValType::V128,

                    // We encode all references as null in the core dump, so
                    // choose the common super type of all the actual function
                    // reference types. This lets us avoid needing to figure out
                    // what a concrete type reference's index is in the local
                    // core dump index space.
                    ValType::Ref(r) => match r.heap_type().top() {
                        HeapType::Extern => wasm_encoder::ValType::EXTERNREF,

                        HeapType::Func => wasm_encoder::ValType::FUNCREF,

                        HeapType::Any => wasm_encoder::ValType::Ref(wasm_encoder::RefType::ANYREF),

                        ty => unreachable!("not a top type: {ty:?}"),
                    },
                };
                let init = match g.get(&mut store) {
                    Val::I32(x) => wasm_encoder::ConstExpr::i32_const(x),
                    Val::I64(x) => wasm_encoder::ConstExpr::i64_const(x),
                    Val::F32(x) => wasm_encoder::ConstExpr::f32_const(f32::from_bits(x)),
                    Val::F64(x) => wasm_encoder::ConstExpr::f64_const(f64::from_bits(x)),
                    Val::V128(x) => wasm_encoder::ConstExpr::v128_const(x.as_u128() as i128),
                    Val::FuncRef(_) => {
                        wasm_encoder::ConstExpr::ref_null(wasm_encoder::HeapType::FUNC)
                    }
                    Val::ExternRef(_) => {
                        wasm_encoder::ConstExpr::ref_null(wasm_encoder::HeapType::EXTERN)
                    }
                    Val::AnyRef(_) => {
                        wasm_encoder::ConstExpr::ref_null(wasm_encoder::HeapType::ANY)
                    }
                };
                globals.global(
                    wasm_encoder::GlobalType {
                        val_type,
                        mutable,
                        shared: false,
                    },
                    &init,
                );
            }
            core_dump.section(&globals);
        }

        core_dump.section(&data);
        drop(data);

        // A map from module id to its index within the core dump's modules
        // section.
        let mut module_to_index = HashMap::new();

        {
            let mut modules = wasm_encoder::CoreDumpModulesSection::new();
            for module in self.modules() {
                module_to_index.insert(module.id(), modules.len());
                match module.name() {
                    Some(name) => modules.module(name),
                    None => modules.module(&format!("<anonymous-module-{}>", modules.len())),
                };
            }
            core_dump.section(&modules);
        }

        // TODO: We can't currently recover instances from stack frames. We can
        // recover module via the frame's PC, but if there are multiple
        // instances of the same module, we don't know which instance the frame
        // is associated with. Therefore, we do a best effort job: remember the
        // last instance of each module and always choose that one. We record
        // that information here.
        let mut module_to_instance = HashMap::new();

        {
            let mut instances = wasm_encoder::CoreDumpInstancesSection::new();
            for instance in self.instances() {
                let module = instance.module(&store);
                module_to_instance.insert(module.id(), instances.len());

                let module_index = module_to_index[&module.id()];

                let memories = instance
                    .all_memories(&mut store.0)
                    .collect::<Vec<_>>()
                    .into_iter()
                    .map(|(_i, memory)| memory_to_idx[&memory.hash_key(&store.0)])
                    .collect::<Vec<_>>();

                let globals = instance
                    .all_globals(&mut store.0)
                    .collect::<Vec<_>>()
                    .into_iter()
                    .map(|(_i, global)| global_to_idx[&global.hash_key(&store.0)])
                    .collect::<Vec<_>>();

                instances.instance(module_index, memories, globals);
            }
            core_dump.section(&instances);
        }

        {
            let thread_name = "main";
            let mut stack = wasm_encoder::CoreDumpStackSection::new(thread_name);
            for frame in self.frames() {
                // This isn't necessarily the right instance if there are
                // multiple instances of the same module. See comment above
                // `module_to_instance` for details.
                let instance = module_to_instance[&frame.module().id()];

                let func = frame.func_index();

                let offset = frame
                    .func_offset()
                    .and_then(|o| u32::try_from(o).ok())
                    .unwrap_or(0);

                // We can't currently recover locals and the operand stack. We
                // should eventually be able to do that with Winch though.
                let locals = [];
                let operand_stack = [];

                stack.frame(instance, func, offset, locals, operand_stack);
            }
            core_dump.section(&stack);
        }

        core_dump.finish()
    }
}

impl fmt::Display for WasmCoreDump {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        writeln!(f, "wasm coredump generated while executing {}:", self.name)?;
        writeln!(f, "modules:")?;
        for module in self.modules.iter() {
            writeln!(f, "  {}", module.name().unwrap_or("<module>"))?;
        }

        writeln!(f, "instances:")?;
        for instance in self.instances.iter() {
            writeln!(f, "  {instance:?}")?;
        }

        writeln!(f, "memories:")?;
        for memory in self.memories.iter() {
            writeln!(f, "  {memory:?}")?;
        }

        writeln!(f, "globals:")?;
        for global in self.globals.iter() {
            writeln!(f, "  {global:?}")?;
        }

        writeln!(f, "backtrace:")?;
        write!(f, "{}", self.backtrace)?;

        Ok(())
    }
}

impl fmt::Debug for WasmCoreDump {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "<wasm core dump>")
    }
}