wasmtime/module/
registry.rs

1//! Implements a registry of modules for a store.
2
3use crate::code::CodeObject;
4#[cfg(feature = "component-model")]
5use crate::component::Component;
6use crate::{FrameInfo, Module, Trap};
7use once_cell::sync::Lazy;
8use std::collections::btree_map::Entry;
9use std::{
10    collections::BTreeMap,
11    sync::{Arc, RwLock},
12};
13use wasmtime_jit::CodeMemory;
14use wasmtime_runtime::{ModuleInfo, VMCallerCheckedFuncRef, VMTrampoline};
15
16/// Used for registering modules with a store.
17///
18/// Note that the primary reason for this registry is to ensure that everything
19/// in `Module` is kept alive for the duration of a `Store`. At this time we
20/// need "basically everything" within a `Module` to stay alive once it's
21/// instantiated within a store. While there's some smaller portions that could
22/// theoretically be omitted as they're not needed by the store they're
23/// currently small enough to not worry much about.
24#[derive(Default)]
25pub struct ModuleRegistry {
26    // Keyed by the end address of a `CodeObject`.
27    //
28    // The value here is the start address and the information about what's
29    // loaded at that address.
30    loaded_code: BTreeMap<usize, (usize, LoadedCode)>,
31
32    // Preserved for keeping data segments alive or similar
33    modules_without_code: Vec<Module>,
34}
35
36struct LoadedCode {
37    /// Representation of loaded code which could be either a component or a
38    /// module.
39    code: Arc<CodeObject>,
40
41    /// Modules found within `self.code`, keyed by start address here of the
42    /// address of the first function in the module.
43    modules: BTreeMap<usize, Module>,
44}
45
46impl ModuleRegistry {
47    /// Fetches information about a registered module given a program counter value.
48    pub fn lookup_module(&self, pc: usize) -> Option<&dyn ModuleInfo> {
49        self.module(pc).map(|(m, _)| m.module_info())
50    }
51
52    fn code(&self, pc: usize) -> Option<(&LoadedCode, usize)> {
53        let (end, (start, code)) = self.loaded_code.range(pc..).next()?;
54        if pc < *start || *end < pc {
55            return None;
56        }
57        Some((code, pc - *start))
58    }
59
60    fn module(&self, pc: usize) -> Option<(&Module, usize)> {
61        let (code, offset) = self.code(pc)?;
62        Some((code.module(pc)?, offset))
63    }
64
65    /// Registers a new module with the registry.
66    pub fn register_module(&mut self, module: &Module) {
67        self.register(module.code_object(), Some(module))
68    }
69
70    #[cfg(feature = "component-model")]
71    pub fn register_component(&mut self, component: &Component) {
72        self.register(component.code_object(), None)
73    }
74
75    /// Registers a new module with the registry.
76    fn register(&mut self, code: &Arc<CodeObject>, module: Option<&Module>) {
77        let text = code.code_memory().text();
78
79        // If there's not actually any functions in this module then we may
80        // still need to preserve it for its data segments. Instances of this
81        // module will hold a pointer to the data stored in the module itself,
82        // and for schemes that perform lazy initialization which could use the
83        // module in the future. For that reason we continue to register empty
84        // modules and retain them.
85        if text.is_empty() {
86            self.modules_without_code.extend(module.cloned());
87            return;
88        }
89
90        // The module code range is exclusive for end, so make it inclusive as
91        // it may be a valid PC value
92        let start_addr = text.as_ptr() as usize;
93        let end_addr = start_addr + text.len() - 1;
94
95        // If this module is already present in the registry then that means
96        // it's either an overlapping image, for example for two modules
97        // found within a component, or it's a second instantiation of the same
98        // module. Delegate to `push_module` to find out.
99        if let Some((other_start, prev)) = self.loaded_code.get_mut(&end_addr) {
100            assert_eq!(*other_start, start_addr);
101            if let Some(module) = module {
102                prev.push_module(module);
103            }
104            return;
105        }
106
107        // Assert that this module's code doesn't collide with any other
108        // registered modules
109        if let Some((_, (prev_start, _))) = self.loaded_code.range(start_addr..).next() {
110            assert!(*prev_start > end_addr);
111        }
112        if let Some((prev_end, _)) = self.loaded_code.range(..=start_addr).next_back() {
113            assert!(*prev_end < start_addr);
114        }
115
116        let mut item = LoadedCode {
117            code: code.clone(),
118            modules: Default::default(),
119        };
120        if let Some(module) = module {
121            item.push_module(module);
122        }
123        let prev = self.loaded_code.insert(end_addr, (start_addr, item));
124        assert!(prev.is_none());
125    }
126
127    /// Looks up a trampoline from an anyfunc.
128    pub fn lookup_trampoline(&self, anyfunc: &VMCallerCheckedFuncRef) -> Option<VMTrampoline> {
129        let (code, _offset) = self.code(anyfunc.func_ptr.as_ptr() as usize)?;
130        code.code.signatures().trampoline(anyfunc.type_index)
131    }
132
133    /// Fetches trap information about a program counter in a backtrace.
134    pub fn lookup_trap_code(&self, pc: usize) -> Option<Trap> {
135        let (code, offset) = self.code(pc)?;
136        wasmtime_environ::lookup_trap_code(code.code.code_memory().trap_data(), offset)
137    }
138
139    /// Fetches frame information about a program counter in a backtrace.
140    ///
141    /// Returns an object if this `pc` is known to some previously registered
142    /// module, or returns `None` if no information can be found. The first
143    /// boolean returned indicates whether the original module has unparsed
144    /// debug information due to the compiler's configuration. The second
145    /// boolean indicates whether the engine used to compile this module is
146    /// using environment variables to control debuginfo parsing.
147    pub(crate) fn lookup_frame_info(&self, pc: usize) -> Option<(FrameInfo, &Module)> {
148        let (module, offset) = self.module(pc)?;
149        let info = FrameInfo::new(module, offset)?;
150        Some((info, module))
151    }
152}
153
154impl LoadedCode {
155    fn push_module(&mut self, module: &Module) {
156        let func = match module.compiled_module().finished_functions().next() {
157            Some((_, func)) => func,
158            // There are no compiled functions in this module so there's no
159            // need to push onto `self.modules` which is only used for frame
160            // information lookup for a trap which only symbolicates defined
161            // functions.
162            None => return,
163        };
164        let start = func.as_ptr() as usize;
165
166        match self.modules.entry(start) {
167            // This module is already present, and it should be the same as
168            // `module`.
169            Entry::Occupied(m) => {
170                debug_assert!(Arc::ptr_eq(&module.inner, &m.get().inner));
171            }
172            // This module was not already present, so now it's time to insert.
173            Entry::Vacant(v) => {
174                v.insert(module.clone());
175            }
176        }
177    }
178
179    fn module(&self, pc: usize) -> Option<&Module> {
180        // The `modules` map is keyed on the start address of the first
181        // function in the module, so find the first module whose start address
182        // is less than the `pc`. That may be the wrong module but lookup
183        // within the module should fail in that case.
184        let (_start, module) = self.modules.range(..=pc).next_back()?;
185        Some(module)
186    }
187}
188
189// This is the global code registry that stores information for all loaded code
190// objects that are currently in use by any `Store` in the current process.
191//
192// The purpose of this map is to be called from signal handlers to determine
193// whether a program counter is a wasm trap or not. Specifically macOS has
194// no contextual information about the thread available, hence the necessity
195// for global state rather than using thread local state.
196//
197// This is similar to `ModuleRegistry` except that it has less information and
198// supports removal. Any time anything is registered with a `ModuleRegistry`
199// it is also automatically registered with the singleton global module
200// registry. When a `ModuleRegistry` is destroyed then all of its entries
201// are removed from the global registry.
202static GLOBAL_CODE: Lazy<RwLock<GlobalRegistry>> = Lazy::new(Default::default);
203
204type GlobalRegistry = BTreeMap<usize, (usize, Arc<CodeMemory>)>;
205
206/// Returns whether the `pc`, according to globally registered information,
207/// is a wasm trap or not.
208pub fn is_wasm_trap_pc(pc: usize) -> bool {
209    let (code, text_offset) = {
210        let all_modules = GLOBAL_CODE.read().unwrap();
211
212        let (end, (start, module)) = match all_modules.range(pc..).next() {
213            Some(info) => info,
214            None => return false,
215        };
216        if pc < *start || *end < pc {
217            return false;
218        }
219        (module.clone(), pc - *start)
220    };
221
222    wasmtime_environ::lookup_trap_code(code.trap_data(), text_offset).is_some()
223}
224
225/// Registers a new region of code.
226///
227/// Must not have been previously registered and must be `unregister`'d to
228/// prevent leaking memory.
229///
230/// This is required to enable traps to work correctly since the signal handler
231/// will lookup in the `GLOBAL_CODE` list to determine which a particular pc
232/// is a trap or not.
233pub fn register_code(code: &Arc<CodeMemory>) {
234    let text = code.text();
235    if text.is_empty() {
236        return;
237    }
238    let start = text.as_ptr() as usize;
239    let end = start + text.len() - 1;
240    let prev = GLOBAL_CODE
241        .write()
242        .unwrap()
243        .insert(end, (start, code.clone()));
244    assert!(prev.is_none());
245}
246
247/// Unregisters a code mmap from the global map.
248///
249/// Must have been previously registered with `register`.
250pub fn unregister_code(code: &Arc<CodeMemory>) {
251    let text = code.text();
252    if text.is_empty() {
253        return;
254    }
255    let end = (text.as_ptr() as usize) + text.len() - 1;
256    let code = GLOBAL_CODE.write().unwrap().remove(&end);
257    assert!(code.is_some());
258}
259
260#[test]
261fn test_frame_info() -> Result<(), anyhow::Error> {
262    use crate::*;
263    let mut store = Store::<()>::default();
264    let module = Module::new(
265        store.engine(),
266        r#"
267            (module
268                (func (export "add") (param $x i32) (param $y i32) (result i32) (i32.add (local.get $x) (local.get $y)))
269                (func (export "sub") (param $x i32) (param $y i32) (result i32) (i32.sub (local.get $x) (local.get $y)))
270                (func (export "mul") (param $x i32) (param $y i32) (result i32) (i32.mul (local.get $x) (local.get $y)))
271                (func (export "div_s") (param $x i32) (param $y i32) (result i32) (i32.div_s (local.get $x) (local.get $y)))
272                (func (export "div_u") (param $x i32) (param $y i32) (result i32) (i32.div_u (local.get $x) (local.get $y)))
273                (func (export "rem_s") (param $x i32) (param $y i32) (result i32) (i32.rem_s (local.get $x) (local.get $y)))
274                (func (export "rem_u") (param $x i32) (param $y i32) (result i32) (i32.rem_u (local.get $x) (local.get $y)))
275            )
276         "#,
277    )?;
278    // Create an instance to ensure the frame information is registered.
279    Instance::new(&mut store, &module, &[])?;
280
281    for (i, alloc) in module.compiled_module().finished_functions() {
282        let (start, end) = {
283            let ptr = alloc.as_ptr();
284            let len = alloc.len();
285            (ptr as usize, ptr as usize + len)
286        };
287        for pc in start..end {
288            let (frame, _) = store
289                .as_context()
290                .0
291                .modules()
292                .lookup_frame_info(pc)
293                .unwrap();
294            assert!(
295                frame.func_index() == i.as_u32(),
296                "lookup of {:#x} returned {}, expected {}",
297                pc,
298                frame.func_index(),
299                i.as_u32()
300            );
301        }
302    }
303    Ok(())
304}