1 //! Implements a registry of modules for a store. 2 3 use crate::code::{EngineCode, EngineCodePC, ModuleWithCode, StoreCode, StoreCodePC}; 4 #[cfg(feature = "component-model")] 5 use crate::component::Component; 6 use crate::runtime::vm::VMWasmCallFunction; 7 use crate::sync::{OnceLock, RwLock}; 8 use crate::vm::CompiledModuleId; 9 use crate::{Engine, prelude::*}; 10 use crate::{FrameInfo, Module, code_memory::CodeMemory}; 11 use alloc::collections::btree_map::{BTreeMap, Entry}; 12 use alloc::sync::Arc; 13 #[cfg(not(feature = "debug"))] 14 use core::marker::PhantomData; 15 use core::ops::Range; 16 use core::ptr::NonNull; 17 use wasmtime_environ::VMSharedTypeIndex; 18 19 /// Used for registering modules with a store. 20 /// 21 /// There are two basic purposes that this registry serves: 22 /// 23 /// - It keeps all modules and their metadata alive as long as the 24 /// store exists. 25 /// - It owns the [`StoreCode`], i.e. possibly-private-copy of machine 26 /// code, for all modules that execute in this store. 27 /// 28 /// The registry allows for translation of EngineCode to StoreCode, 29 /// deduplicating by the start address of the EngineCode; and allows 30 /// for looking up modules by "registered module ID", and looking up 31 /// StoreCode and Modules by PC. 32 /// 33 /// Note that multiple modules may be backed by a single 34 /// `StoreCode`. This is specifically the case for components in 35 /// general. When a component is first instantiated, the component 36 /// itself is registered (which loads the StoreCode into the 37 /// registry), then each individual module within that component is 38 /// registered and added to the data structures. 39 /// 40 /// A brief overview of the kinds of compiled object and their 41 /// relationships: 42 /// 43 /// - `Module` is a Wasm module. It owns a `CompiledModule`. 44 /// - `CompiledModule` contains metadata about the module (e.g., a map 45 /// from Wasm function indices to locations in the machine code), 46 /// and also owns an `EngineCode`. 47 /// - `EngineCode` holds an `Arc` to a `CodeMemory` with the canonical 48 /// copy of machine code, as well as some lower-level metadata 49 /// (signatures and types). It is instantiated by this registry into 50 /// `StoreCode`. 51 /// - `StoreCode` owns either another `Arc` to the same `CodeMemory` 52 /// as `EngineCode`, or if guest debugging is enabled and causes us 53 /// to clone private copies of code for patching per store, owns its 54 /// own private `CodeMemory` at a different address. 55 /// - Instances hold a `RegisteredModuleId` to be able to look up their modules. 56 #[derive(Default)] 57 pub struct ModuleRegistry { 58 /// StoreCode and Modules associated with it. 59 /// 60 /// Keyed by the start address of the `StoreCode`. We maintain the 61 /// invariant of no overlaps on insertion. We use a range query to 62 /// find the StoreCode for a given PC: take the range `0..=pc`, 63 /// then take the last element of the range. That picks the 64 /// highest start address <= the query, and we can check whether 65 /// it contains the address. 66 loaded_code: BTreeMap<StoreCodePC, LoadedCode>, 67 68 /// Map from EngineCodePC start to StoreCodePC start. We use this 69 /// to memoize the store-code creation process: each EngineCode is 70 /// instantiated to a StoreCode only once per store. 71 store_code: BTreeMap<EngineCodePC, StoreCodePC>, 72 73 /// Modules instantiated in this registry. 74 /// 75 /// Every module is placed in this map, but not every module will 76 /// be in a LoadedCode entry, because the module may have no text. 77 modules: BTreeMap<RegisteredModuleId, Module>, 78 } 79 80 struct LoadedCode { 81 /// The StoreCode in this range. 82 code: StoreCode, 83 84 /// Map by starting text offset of Modules in this code region. 85 modules: BTreeMap<usize, RegisteredModuleId>, 86 } 87 88 /// An identifier of a module that has previously been inserted into a 89 /// `ModuleRegistry`. 90 /// 91 /// This is just a newtype around `CompiledModuleId`, which is unique 92 /// within the Engine. 93 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 94 pub struct RegisteredModuleId(CompiledModuleId); 95 96 fn assert_no_overlap(loaded_code: &BTreeMap<StoreCodePC, LoadedCode>, range: Range<StoreCodePC>) { 97 if let Some((start, _)) = loaded_code.range(range.start..).next() { 98 assert!(*start >= range.end); 99 } 100 if let Some((_, code)) = loaded_code.range(..range.end).next_back() { 101 assert!(code.code.text_range().end <= range.start); 102 } 103 } 104 105 #[cfg(feature = "debug")] 106 pub struct RegisterBreakpointState<'a>(pub(crate) &'a crate::runtime::debug::BreakpointState); 107 #[cfg(not(feature = "debug"))] 108 pub struct RegisterBreakpointState<'a>(pub(crate) PhantomData<&'a ()>); 109 110 impl<'a> RegisterBreakpointState<'a> { 111 #[cfg(feature = "debug")] 112 fn update(&self, code: &mut StoreCode, module: &Module) -> Result<()> { 113 self.0.patch_new_module(code, module) 114 } 115 #[cfg(not(feature = "debug"))] 116 fn update(&self, _code: &mut StoreCode, _module: &Module) -> Result<()> { 117 Ok(()) 118 } 119 } 120 121 impl ModuleRegistry { 122 /// Get a previously-registered module by id. 123 pub fn module_by_id(&self, id: RegisteredModuleId) -> Option<&Module> { 124 self.modules.get(&id) 125 } 126 127 /// Get a module by CompiledModuleId, if present. 128 pub fn module_by_compiled_id(&self, id: CompiledModuleId) -> Option<&Module> { 129 self.modules.get(&RegisteredModuleId(id)) 130 } 131 132 /// Fetches a registered StoreCode and module and an offset within 133 /// it given a program counter value. 134 pub fn module_and_code_by_pc<'a>(&'a self, pc: usize) -> Option<(ModuleWithCode<'a>, usize)> { 135 let (_, code) = self 136 .loaded_code 137 .range(..=StoreCodePC::from_raw(pc)) 138 .next_back()?; 139 let offset = StoreCodePC::offset_of(code.code.text_range(), pc)?; 140 let (_, module_id) = code.modules.range(..=offset).next_back()?; 141 let module = self.modules.get(&module_id)?; 142 Some((ModuleWithCode::from_raw(module, &code.code), offset)) 143 } 144 145 /// Fetches the `StoreCode` for a given `EngineCode`. 146 pub fn store_code(&self, engine_code: &EngineCode) -> Option<&StoreCode> { 147 let store_code_pc = self.store_code_base(engine_code)?; 148 let (_, code) = self.loaded_code.range(store_code_pc..).next()?; 149 Some(&code.code) 150 } 151 152 /// Fetches the base `StoreCodePC` for a given `EngineCode`. 153 pub fn store_code_base(&self, engine_code: &EngineCode) -> Option<StoreCodePC> { 154 self.store_code 155 .get(&engine_code.text_range().start) 156 .cloned() 157 } 158 159 /// Fetches the base `StoreCodePC` for a given `EngineCode` with 160 /// `Module`, registering the module if not already registered. 161 pub fn store_code_base_or_register( 162 &mut self, 163 module: &Module, 164 breakpoint_state: RegisterBreakpointState, 165 ) -> Result<StoreCodePC> { 166 let key = module.engine_code().text_range().start; 167 if !self.store_code.contains_key(&key) { 168 let engine = module.engine().clone(); 169 self.register_module(module, &engine, breakpoint_state)?; 170 } 171 Ok(*self.store_code.get(&key).unwrap()) 172 } 173 174 /// Fetches a mutable `StoreCode` for a given base `StoreCodePC`. 175 pub fn store_code_mut(&mut self, store_code_base: StoreCodePC) -> Option<&mut StoreCode> { 176 let (_, code) = self.loaded_code.range_mut(store_code_base..).next()?; 177 assert_eq!(code.code.text_range().start, store_code_base); 178 Some(&mut code.code) 179 } 180 181 /// Gets an iterator over all modules in the registry. 182 #[cfg(any(feature = "coredump", feature = "debug"))] 183 pub fn all_modules(&self) -> impl Iterator<Item = &'_ Module> + '_ { 184 self.modules.values() 185 } 186 187 /// Registers a new module with the registry. 188 pub fn register_module( 189 &mut self, 190 module: &Module, 191 engine: &Engine, 192 breakpoint_state: RegisterBreakpointState, 193 ) -> Result<RegisteredModuleId> { 194 self.register( 195 module.id(), 196 module.engine_code(), 197 Some(module), 198 engine, 199 breakpoint_state, 200 ) 201 .map(|id| id.unwrap()) 202 } 203 204 #[cfg(feature = "component-model")] 205 pub fn register_component( 206 &mut self, 207 component: &Component, 208 engine: &Engine, 209 breakpoint_state: RegisterBreakpointState, 210 ) -> Result<()> { 211 self.register( 212 component.id(), 213 component.engine_code(), 214 None, 215 engine, 216 breakpoint_state, 217 )?; 218 Ok(()) 219 } 220 221 /// Registers a new module with the registry. 222 fn register( 223 &mut self, 224 compiled_id: CompiledModuleId, 225 code: &Arc<EngineCode>, 226 module: Option<&Module>, 227 engine: &Engine, 228 breakpoint_state: RegisterBreakpointState, 229 ) -> Result<Option<RegisteredModuleId>> { 230 // Register the module, if any. 231 let id = module.map(|module| { 232 let id = RegisteredModuleId(compiled_id); 233 self.modules.entry(id).or_insert_with(|| module.clone()); 234 id 235 }); 236 237 // Create a StoreCode if one does not already exist. 238 let store_code_pc = match self.store_code.entry(code.text_range().start) { 239 Entry::Vacant(v) => { 240 let store_code = StoreCode::new(engine, code)?; 241 let store_code_pc = store_code.text_range().start; 242 assert_no_overlap(&self.loaded_code, store_code.text_range()); 243 self.loaded_code.insert( 244 store_code_pc, 245 LoadedCode { 246 code: store_code, 247 modules: BTreeMap::default(), 248 }, 249 ); 250 *v.insert(store_code_pc) 251 } 252 Entry::Occupied(o) => *o.get(), 253 }; 254 255 // Add this module to the LoadedCode if not present. 256 if let (Some(module), Some(id)) = (module, id) { 257 if let Some((_, range)) = module.compiled_module().finished_function_ranges().next() { 258 let loaded_code = self 259 .loaded_code 260 .get_mut(&store_code_pc) 261 .expect("loaded_code must have entry for StoreCodePC"); 262 loaded_code.modules.insert(range.start, id); 263 breakpoint_state.update(&mut loaded_code.code, module)?; 264 } 265 } 266 267 Ok(id) 268 } 269 270 /// Fetches frame information about a program counter in a backtrace. 271 /// 272 /// Returns an object if this `pc` is known to some previously registered 273 /// module, or returns `None` if no information can be found. The first 274 /// boolean returned indicates whether the original module has unparsed 275 /// debug information due to the compiler's configuration. The second 276 /// boolean indicates whether the engine used to compile this module is 277 /// using environment variables to control debuginfo parsing. 278 pub(crate) fn lookup_frame_info<'a>( 279 &'a self, 280 pc: usize, 281 ) -> Option<(FrameInfo, ModuleWithCode<'a>)> { 282 let (_, code) = self 283 .loaded_code 284 .range(..=StoreCodePC::from_raw(pc)) 285 .next_back()?; 286 let text_offset = StoreCodePC::offset_of(code.code.text_range(), pc)?; 287 let (_, module_id) = code.modules.range(..=text_offset).next_back()?; 288 let module = self 289 .modules 290 .get(&module_id) 291 .expect("referenced module ID not found"); 292 let info = FrameInfo::new(module.clone(), text_offset)?; 293 let module_with_code = ModuleWithCode::from_raw(module, &code.code); 294 Some((info, module_with_code)) 295 } 296 297 pub fn wasm_to_array_trampoline( 298 &self, 299 sig: VMSharedTypeIndex, 300 ) -> Option<NonNull<VMWasmCallFunction>> { 301 // TODO: We are doing a linear search over each module. This is fine for 302 // now because we typically have very few modules per store (almost 303 // always one, in fact). If this linear search ever becomes a 304 // bottleneck, we could avoid it by incrementally and lazily building a 305 // `VMSharedSignatureIndex` to `SignatureIndex` map. 306 // 307 // See also the comment in `ModuleInner::wasm_to_native_trampoline`. 308 for module in self.modules.values() { 309 if let Some(trampoline) = module.wasm_to_array_trampoline(sig) { 310 return Some(trampoline); 311 } 312 } 313 None 314 } 315 } 316 317 // This is the global code registry that stores information for all loaded code 318 // objects that are currently in use by any `Store` in the current process. 319 // 320 // The purpose of this map is to be called from signal handlers to determine 321 // whether a program counter is a wasm trap or not. Specifically macOS has 322 // no contextual information about the thread available, hence the necessity 323 // for global state rather than using thread local state. 324 // 325 // This is similar to `ModuleRegistry` except that it has less information and 326 // supports removal. Any time anything is registered with a `ModuleRegistry` 327 // it is also automatically registered with the singleton global module 328 // registry. When a `ModuleRegistry` is destroyed then all of its entries 329 // are removed from the global registry. 330 fn global_code() -> &'static RwLock<GlobalRegistry> { 331 static GLOBAL_CODE: OnceLock<RwLock<GlobalRegistry>> = OnceLock::new(); 332 GLOBAL_CODE.get_or_init(Default::default) 333 } 334 335 type GlobalRegistry = BTreeMap<usize, (usize, Arc<CodeMemory>)>; 336 337 /// Find which registered region of code contains the given program counter, and 338 /// what offset that PC is within that module's code. 339 pub fn lookup_code(pc: usize) -> Option<(Arc<CodeMemory>, usize)> { 340 let all_modules = global_code().read(); 341 let (_end, (start, module)) = all_modules.range(pc..).next()?; 342 let text_offset = pc.checked_sub(*start)?; 343 Some((module.clone(), text_offset)) 344 } 345 346 /// Registers a new region of code. 347 /// 348 /// Must not have been previously registered and must be `unregister`'d to 349 /// prevent leaking memory. 350 /// 351 /// This is required to enable traps to work correctly since the signal handler 352 /// will lookup in the `GLOBAL_CODE` list to determine which a particular pc 353 /// is a trap or not. 354 pub fn register_code(image: &Arc<CodeMemory>, address: Range<usize>) { 355 if address.is_empty() { 356 return; 357 } 358 let start = address.start; 359 let end = address.end - 1; 360 let prev = global_code().write().insert(end, (start, image.clone())); 361 assert!(prev.is_none()); 362 } 363 364 /// Unregisters a code mmap from the global map. 365 /// 366 /// Must have been previously registered with `register`. 367 pub fn unregister_code(address: Range<usize>) { 368 if address.is_empty() { 369 return; 370 } 371 let end = address.end - 1; 372 let code = global_code().write().remove(&end); 373 assert!(code.is_some()); 374 } 375 376 #[test] 377 #[cfg_attr(miri, ignore)] 378 fn test_frame_info() -> Result<(), crate::Error> { 379 use crate::*; 380 381 let mut store = Store::<()>::default(); 382 let module = Module::new( 383 store.engine(), 384 r#" 385 (module 386 (func (export "add") (param $x i32) (param $y i32) (result i32) (i32.add (local.get $x) (local.get $y))) 387 (func (export "sub") (param $x i32) (param $y i32) (result i32) (i32.sub (local.get $x) (local.get $y))) 388 (func (export "mul") (param $x i32) (param $y i32) (result i32) (i32.mul (local.get $x) (local.get $y))) 389 (func (export "div_s") (param $x i32) (param $y i32) (result i32) (i32.div_s (local.get $x) (local.get $y))) 390 (func (export "div_u") (param $x i32) (param $y i32) (result i32) (i32.div_u (local.get $x) (local.get $y))) 391 (func (export "rem_s") (param $x i32) (param $y i32) (result i32) (i32.rem_s (local.get $x) (local.get $y))) 392 (func (export "rem_u") (param $x i32) (param $y i32) (result i32) (i32.rem_u (local.get $x) (local.get $y))) 393 ) 394 "#, 395 )?; 396 // Create an instance to ensure the frame information is registered. 397 Instance::new(&mut store, &module, &[])?; 398 399 // Look for frame info for each function. Assume that StoreCode 400 // does not actually clone in the default configuration. 401 for (i, range) in module.compiled_module().finished_function_ranges() { 402 let base = module.engine_code().text_range().start.raw(); 403 let start = base + range.start; 404 let end = base + range.end; 405 for pc in start..end { 406 let (frame, _) = store 407 .as_context() 408 .0 409 .modules() 410 .lookup_frame_info(pc) 411 .unwrap(); 412 assert!( 413 frame.func_index() == i.as_u32(), 414 "lookup of {:#x} returned {}, expected {}", 415 pc, 416 frame.func_index(), 417 i.as_u32() 418 ); 419 } 420 } 421 Ok(()) 422 } 423