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