wasmer_engine/trap/
frame_info.rs

1//! This module is used for having backtraces in the Wasm runtime.
2//! Once the Compiler has compiled the ModuleInfo, and we have a set of
3//! compiled functions (addresses and function index) and a module,
4//! then we can use this to set a backtrace for that module.
5//!
6//! # Example
7//! ```ignore
8//! use wasmer_vm::{FRAME_INFO};
9//! use wasmer_types::ModuleInfo;
10//!
11//! let module: ModuleInfo = ...;
12//! FRAME_INFO.register(module, compiled_functions);
13//! ```
14use loupe::MemoryUsage;
15use std::cmp;
16use std::collections::BTreeMap;
17use std::sync::{Arc, RwLock};
18use wasmer_compiler::{CompiledFunctionFrameInfo, SourceLoc, TrapInformation};
19use wasmer_types::entity::{BoxedSlice, EntityRef, PrimaryMap};
20use wasmer_types::{LocalFunctionIndex, ModuleInfo};
21use wasmer_vm::FunctionBodyPtr;
22
23lazy_static::lazy_static! {
24    /// This is a global cache of backtrace frame information for all active
25    ///
26    /// This global cache is used during `Trap` creation to symbolicate frames.
27    /// This is populated on module compilation, and it is cleared out whenever
28    /// all references to a module are dropped.
29    pub static ref FRAME_INFO: RwLock<GlobalFrameInfo> = Default::default();
30}
31
32#[derive(Default)]
33pub struct GlobalFrameInfo {
34    /// An internal map that keeps track of backtrace frame information for
35    /// each module.
36    ///
37    /// This map is morally a map of ranges to a map of information for that
38    /// module. Each module is expected to reside in a disjoint section of
39    /// contiguous memory. No modules can overlap.
40    ///
41    /// The key of this map is the highest address in the module and the value
42    /// is the module's information, which also contains the start address.
43    ranges: BTreeMap<usize, ModuleInfoFrameInfo>,
44}
45
46/// An RAII structure used to unregister a module's frame information when the
47/// module is destroyed.
48#[derive(MemoryUsage)]
49pub struct GlobalFrameInfoRegistration {
50    /// The key that will be removed from the global `ranges` map when this is
51    /// dropped.
52    key: usize,
53}
54
55#[derive(Debug)]
56struct ModuleInfoFrameInfo {
57    start: usize,
58    functions: BTreeMap<usize, FunctionInfo>,
59    module: Arc<ModuleInfo>,
60    frame_infos: PrimaryMap<LocalFunctionIndex, CompiledFunctionFrameInfo>,
61}
62
63impl ModuleInfoFrameInfo {
64    fn function_debug_info(&self, local_index: LocalFunctionIndex) -> &CompiledFunctionFrameInfo {
65        &self.frame_infos.get(local_index).unwrap()
66    }
67
68    /// Gets a function given a pc
69    fn function_info(&self, pc: usize) -> Option<&FunctionInfo> {
70        let (end, func) = self.functions.range(pc..).next()?;
71        if func.start <= pc && pc <= *end {
72            return Some(func);
73        } else {
74            None
75        }
76    }
77}
78
79#[derive(Debug)]
80struct FunctionInfo {
81    start: usize,
82    local_index: LocalFunctionIndex,
83}
84
85impl GlobalFrameInfo {
86    /// Fetches frame information about a program counter in a backtrace.
87    ///
88    /// Returns an object if this `pc` is known to some previously registered
89    /// module, or returns `None` if no information can be found.
90    pub fn lookup_frame_info(&self, pc: usize) -> Option<FrameInfo> {
91        let module = self.module_info(pc)?;
92        let func = module.function_info(pc)?;
93
94        // Use our relative position from the start of the function to find the
95        // machine instruction that corresponds to `pc`, which then allows us to
96        // map that to a wasm original source location.
97        let rel_pos = pc - func.start;
98        let instr_map = &module.function_debug_info(func.local_index).address_map;
99        let pos = match instr_map
100            .instructions
101            .binary_search_by_key(&rel_pos, |map| map.code_offset)
102        {
103            // Exact hit!
104            Ok(pos) => Some(pos),
105
106            // This *would* be at the first slot in the array, so no
107            // instructions cover `pc`.
108            Err(0) => None,
109
110            // This would be at the `nth` slot, so check `n-1` to see if we're
111            // part of that instruction. This happens due to the minus one when
112            // this function is called form trap symbolication, where we don't
113            // always get called with a `pc` that's an exact instruction
114            // boundary.
115            Err(n) => {
116                let instr = &instr_map.instructions[n - 1];
117                if instr.code_offset <= rel_pos && rel_pos < instr.code_offset + instr.code_len {
118                    Some(n - 1)
119                } else {
120                    None
121                }
122            }
123        };
124
125        let instr = match pos {
126            Some(pos) => instr_map.instructions[pos].srcloc,
127            // Some compilers don't emit yet the full trap information for each of
128            // the instructions (such as LLVM).
129            // In case no specific instruction is found, we return by default the
130            // start offset of the function.
131            None => instr_map.start_srcloc,
132        };
133        let func_index = module.module.func_index(func.local_index);
134        Some(FrameInfo {
135            module_name: module.module.name(),
136            func_index: func_index.index() as u32,
137            function_name: module.module.function_names.get(&func_index).cloned(),
138            instr,
139            func_start: instr_map.start_srcloc,
140        })
141    }
142
143    /// Fetches trap information about a program counter in a backtrace.
144    pub fn lookup_trap_info(&self, pc: usize) -> Option<&TrapInformation> {
145        let module = self.module_info(pc)?;
146        let func = module.function_info(pc)?;
147        let traps = &module.function_debug_info(func.local_index).traps;
148        let idx = traps
149            .binary_search_by_key(&((pc - func.start) as u32), |info| info.code_offset)
150            .ok()?;
151        Some(&traps[idx])
152    }
153
154    /// Gets a module given a pc
155    fn module_info(&self, pc: usize) -> Option<&ModuleInfoFrameInfo> {
156        let (end, module_info) = self.ranges.range(pc..).next()?;
157        if module_info.start <= pc && pc <= *end {
158            Some(module_info)
159        } else {
160            None
161        }
162    }
163}
164
165impl Drop for GlobalFrameInfoRegistration {
166    fn drop(&mut self) {
167        if let Ok(mut info) = FRAME_INFO.write() {
168            info.ranges.remove(&self.key);
169        }
170    }
171}
172
173/// Represents a continuous region of executable memory starting with a function
174/// entry point.
175#[derive(Debug)]
176#[repr(C)]
177pub struct FunctionExtent {
178    /// Entry point for normal entry of the function. All addresses in the
179    /// function lie after this address.
180    pub ptr: FunctionBodyPtr,
181    /// Length in bytes.
182    pub length: usize,
183}
184
185/// Registers a new compiled module's frame information.
186///
187/// This function will register the `names` information for all of the
188/// compiled functions within `module`. If the `module` has no functions
189/// then `None` will be returned. Otherwise the returned object, when
190/// dropped, will be used to unregister all name information from this map.
191pub fn register(
192    module: Arc<ModuleInfo>,
193    finished_functions: &BoxedSlice<LocalFunctionIndex, FunctionExtent>,
194    frame_infos: PrimaryMap<LocalFunctionIndex, CompiledFunctionFrameInfo>,
195) -> Option<GlobalFrameInfoRegistration> {
196    let mut min = usize::max_value();
197    let mut max = 0;
198    let mut functions = BTreeMap::new();
199    for (
200        i,
201        FunctionExtent {
202            ptr: start,
203            length: len,
204        },
205    ) in finished_functions.iter()
206    {
207        let start = **start as usize;
208        let end = start + len;
209        min = cmp::min(min, start);
210        max = cmp::max(max, end);
211        let func = FunctionInfo {
212            start,
213            local_index: i,
214        };
215        assert!(functions.insert(end, func).is_none());
216    }
217    if functions.is_empty() {
218        return None;
219    }
220
221    let mut info = FRAME_INFO.write().unwrap();
222    // First up assert that our chunk of jit functions doesn't collide with
223    // any other known chunks of jit functions...
224    if let Some((_, prev)) = info.ranges.range(max..).next() {
225        assert!(prev.start > max);
226    }
227    if let Some((prev_end, _)) = info.ranges.range(..=min).next_back() {
228        assert!(*prev_end < min);
229    }
230
231    // ... then insert our range and assert nothing was there previously
232    let prev = info.ranges.insert(
233        max,
234        ModuleInfoFrameInfo {
235            start: min,
236            functions,
237            module,
238            frame_infos,
239        },
240    );
241    assert!(prev.is_none());
242    Some(GlobalFrameInfoRegistration { key: max })
243}
244
245/// Description of a frame in a backtrace for a [`RuntimeError::trace`](crate::RuntimeError::trace).
246///
247/// Whenever a WebAssembly trap occurs an instance of [`RuntimeError`]
248/// is created. Each [`RuntimeError`] has a backtrace of the
249/// WebAssembly frames that led to the trap, and each frame is
250/// described by this structure.
251///
252/// [`RuntimeError`]: crate::RuntimeError
253#[derive(Debug, Clone)]
254pub struct FrameInfo {
255    module_name: String,
256    func_index: u32,
257    function_name: Option<String>,
258    func_start: SourceLoc,
259    instr: SourceLoc,
260}
261
262impl FrameInfo {
263    /// Returns the WebAssembly function index for this frame.
264    ///
265    /// This function index is the index in the function index space of the
266    /// WebAssembly module that this frame comes from.
267    pub fn func_index(&self) -> u32 {
268        self.func_index
269    }
270
271    /// Returns the identifer of the module that this frame is for.
272    ///
273    /// ModuleInfo identifiers are present in the `name` section of a WebAssembly
274    /// binary, but this may not return the exact item in the `name` section.
275    /// ModuleInfo names can be overwritten at construction time or perhaps inferred
276    /// from file names. The primary purpose of this function is to assist in
277    /// debugging and therefore may be tweaked over time.
278    ///
279    /// This function returns `None` when no name can be found or inferred.
280    pub fn module_name(&self) -> &str {
281        &self.module_name
282    }
283
284    /// Returns a descriptive name of the function for this frame, if one is
285    /// available.
286    ///
287    /// The name of this function may come from the `name` section of the
288    /// WebAssembly binary, or wasmer may try to infer a better name for it if
289    /// not available, for example the name of the export if it's exported.
290    ///
291    /// This return value is primarily used for debugging and human-readable
292    /// purposes for things like traps. Note that the exact return value may be
293    /// tweaked over time here and isn't guaranteed to be something in
294    /// particular about a wasm module due to its primary purpose of assisting
295    /// in debugging.
296    ///
297    /// This function returns `None` when no name could be inferred.
298    pub fn function_name(&self) -> Option<&str> {
299        self.function_name.as_deref()
300    }
301
302    /// Returns the offset within the original wasm module this frame's program
303    /// counter was at.
304    ///
305    /// The offset here is the offset from the beginning of the original wasm
306    /// module to the instruction that this frame points to.
307    pub fn module_offset(&self) -> usize {
308        self.instr.bits() as usize
309    }
310
311    /// Returns the offset from the original wasm module's function to this
312    /// frame's program counter.
313    ///
314    /// The offset here is the offset from the beginning of the defining
315    /// function of this frame (within the wasm module) to the instruction this
316    /// frame points to.
317    pub fn func_offset(&self) -> usize {
318        (self.instr.bits() - self.func_start.bits()) as usize
319    }
320}