1 //! Debug utils for WebAssembly using Cranelift. 2 3 // FIXME: this whole crate opts-in to these two noisier-than-default lints, but 4 // this module has lots of hits on this warning which aren't the easiest to 5 // resolve. Ideally all warnings would be resolved here though. 6 #![expect( 7 clippy::cast_possible_truncation, 8 clippy::cast_sign_loss, 9 reason = "haven't had a chance to fix these yet" 10 )] 11 12 use crate::CompiledFunctionMetadata; 13 use core::fmt; 14 use cranelift_codegen::isa::TargetIsa; 15 use object::write::SymbolId; 16 use std::collections::HashMap; 17 use wasmtime_environ::{ 18 DefinedFuncIndex, DefinedMemoryIndex, EntityRef, MemoryIndex, ModuleTranslation, 19 OwnedMemoryIndex, PrimaryMap, PtrSize, StaticModuleIndex, Tunables, VMOffsets, 20 }; 21 22 /// Memory definition offset in the VMContext structure. 23 #[derive(Debug, Clone)] 24 pub enum ModuleMemoryOffset { 25 /// Not available. 26 None, 27 /// Offset to the defined memory. 28 Defined(u32), 29 /// This memory is imported. 30 Imported { 31 /// Offset, in bytes, to the `*mut VMMemoryDefinition` structure within 32 /// `VMContext`. 33 offset_to_vm_memory_definition: u32, 34 /// Offset, in bytes within `VMMemoryDefinition` where the `base` field 35 /// lies. 36 offset_to_memory_base: u32, 37 }, 38 } 39 40 type Reader<'input> = gimli::EndianSlice<'input, gimli::LittleEndian>; 41 42 /// "Package structure" to collect together various artifacts/results of a 43 /// compilation. 44 /// 45 /// This structure is threaded through a number of top-level functions of DWARF 46 /// processing within in this submodule to pass along all the bits-and-pieces of 47 /// the compilation context. 48 pub struct Compilation<'a> { 49 /// All module translations which were present in this compilation. 50 /// 51 /// This map has one entry for core wasm modules and may have multiple (or 52 /// zero) for components. 53 translations: &'a PrimaryMap<StaticModuleIndex, ModuleTranslation<'a>>, 54 55 /// Accessor of a particular compiled function for a module. 56 /// 57 /// This returns the `object`-based-symbol for the function as well as the 58 /// `&CompiledFunction`. 59 get_func: 60 &'a dyn Fn(StaticModuleIndex, DefinedFuncIndex) -> (SymbolId, &'a CompiledFunctionMetadata), 61 62 /// Optionally-specified `*.dwp` file, currently only supported for core 63 /// wasm modules. 64 dwarf_package_bytes: Option<&'a [u8]>, 65 66 /// Compilation settings used when producing functions. 67 tunables: &'a Tunables, 68 69 /// Translation between `SymbolId` and a `usize`-based symbol which gimli 70 /// uses. 71 symbol_index_to_id: Vec<SymbolId>, 72 symbol_id_to_index: HashMap<SymbolId, (usize, StaticModuleIndex, DefinedFuncIndex)>, 73 74 /// The `ModuleMemoryOffset` for each module within `translations`. 75 /// 76 /// Note that this doesn't support multi-memory at this time. 77 module_memory_offsets: PrimaryMap<StaticModuleIndex, ModuleMemoryOffset>, 78 } 79 80 impl<'a> Compilation<'a> { new( isa: &dyn TargetIsa, translations: &'a PrimaryMap<StaticModuleIndex, ModuleTranslation<'a>>, get_func: &'a dyn Fn( StaticModuleIndex, DefinedFuncIndex, ) -> (SymbolId, &'a CompiledFunctionMetadata), dwarf_package_bytes: Option<&'a [u8]>, tunables: &'a Tunables, ) -> Compilation<'a>81 pub fn new( 82 isa: &dyn TargetIsa, 83 translations: &'a PrimaryMap<StaticModuleIndex, ModuleTranslation<'a>>, 84 get_func: &'a dyn Fn( 85 StaticModuleIndex, 86 DefinedFuncIndex, 87 ) -> (SymbolId, &'a CompiledFunctionMetadata), 88 dwarf_package_bytes: Option<&'a [u8]>, 89 tunables: &'a Tunables, 90 ) -> Compilation<'a> { 91 // Build the `module_memory_offsets` map based on the modules in 92 // `translations`. 93 let mut module_memory_offsets = PrimaryMap::new(); 94 for (i, translation) in translations { 95 let ofs = VMOffsets::new( 96 isa.triple().architecture.pointer_width().unwrap().bytes(), 97 &translation.module, 98 ); 99 100 let memory_offset = if ofs.num_imported_memories > 0 { 101 let index = MemoryIndex::new(0); 102 ModuleMemoryOffset::Imported { 103 offset_to_vm_memory_definition: ofs.vmctx_vmmemory_import(index) 104 + u32::from(ofs.vmmemory_import_from()), 105 offset_to_memory_base: ofs.ptr.vmmemory_definition_base().into(), 106 } 107 } else if ofs.num_owned_memories > 0 { 108 let index = OwnedMemoryIndex::new(0); 109 ModuleMemoryOffset::Defined(ofs.vmctx_vmmemory_definition_base(index)) 110 } else if ofs.num_defined_memories > 0 { 111 let index = DefinedMemoryIndex::new(0); 112 ModuleMemoryOffset::Imported { 113 offset_to_vm_memory_definition: ofs.vmctx_vmmemory_pointer(index), 114 offset_to_memory_base: ofs.ptr.vmmemory_definition_base().into(), 115 } 116 } else { 117 ModuleMemoryOffset::None 118 }; 119 let j = module_memory_offsets.push(memory_offset); 120 assert_eq!(i, j); 121 } 122 123 // Build the `symbol <=> usize` mappings 124 let mut symbol_index_to_id = Vec::new(); 125 let mut symbol_id_to_index = HashMap::new(); 126 127 for (module, translation) in translations { 128 for func in translation.module.defined_func_indices() { 129 let (sym, _func) = get_func(module, func); 130 symbol_id_to_index.insert(sym, (symbol_index_to_id.len(), module, func)); 131 symbol_index_to_id.push(sym); 132 } 133 } 134 135 Compilation { 136 translations, 137 get_func, 138 dwarf_package_bytes, 139 tunables, 140 symbol_index_to_id, 141 symbol_id_to_index, 142 module_memory_offsets, 143 } 144 } 145 146 /// Returns an iterator over all function indexes present in this 147 /// compilation. 148 /// 149 /// Each function is additionally accompanied with its module index. indexes(&self) -> impl Iterator<Item = (StaticModuleIndex, DefinedFuncIndex)> + use<'_>150 fn indexes(&self) -> impl Iterator<Item = (StaticModuleIndex, DefinedFuncIndex)> + use<'_> { 151 self.translations 152 .iter() 153 .flat_map(|(i, t)| t.module.defined_func_indices().map(move |j| (i, j))) 154 } 155 156 /// Returns an iterator of all functions with their module, symbol, and 157 /// function metadata that were produced during compilation. functions( &self, ) -> impl Iterator<Item = (StaticModuleIndex, usize, &'a CompiledFunctionMetadata)> + '_158 fn functions( 159 &self, 160 ) -> impl Iterator<Item = (StaticModuleIndex, usize, &'a CompiledFunctionMetadata)> + '_ { 161 self.indexes().map(move |(module, func)| { 162 let (sym, func) = self.function(module, func); 163 (module, sym, func) 164 }) 165 } 166 167 /// Returns the symbol and metadata associated with a specific function. function( &self, module: StaticModuleIndex, func: DefinedFuncIndex, ) -> (usize, &'a CompiledFunctionMetadata)168 fn function( 169 &self, 170 module: StaticModuleIndex, 171 func: DefinedFuncIndex, 172 ) -> (usize, &'a CompiledFunctionMetadata) { 173 let (sym, func) = (self.get_func)(module, func); 174 (self.symbol_id_to_index[&sym].0, func) 175 } 176 177 /// Maps a `usize`-based symbol used by gimli to the object-based 178 /// `SymbolId`. symbol_id(&self, sym: usize) -> SymbolId179 pub fn symbol_id(&self, sym: usize) -> SymbolId { 180 self.symbol_index_to_id[sym] 181 } 182 } 183 184 impl<'a> fmt::Debug for Compilation<'a> { 185 // Sample output: '[#0: OneModule, #1: TwoModule, #3]'. fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result186 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 187 write!(f, "[")?; 188 let mut is_first_module = true; 189 for (i, translation) in self.translations { 190 if !is_first_module { 191 write!(f, ", ")?; 192 } else { 193 is_first_module = false; 194 } 195 write!(f, "#{}", i.as_u32())?; 196 if let Some(name) = translation.debuginfo.name_section.module_name { 197 write!(f, ": {name}")?; 198 } 199 } 200 write!(f, "]") 201 } 202 } 203 204 pub use write_debuginfo::{DwarfSectionRelocTarget, emit_dwarf}; 205 206 mod gc; 207 mod transform; 208 mod write_debuginfo; 209