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 std::collections::BTreeMap;
15use std::sync::{Arc, RwLock};
16use wasmer_compiler::{CompiledFunctionFrameInfo, SourceLoc, TrapInformation};
17use wasmer_types::entity::{EntityRef, PrimaryMap};
18use wasmer_types::{LocalFunctionIndex, ModuleInfo};
19
20lazy_static::lazy_static! {
21    /// This is a global cache of backtrace frame information for all active
22    ///
23    /// This global cache is used during `Trap` creation to symbolicate frames.
24    /// This is populated on module compilation, and it is cleared out whenever
25    /// all references to a module are dropped.
26    pub static ref FRAME_INFO: RwLock<GlobalFrameInfo> = Default::default();
27}
28
29#[derive(Default)]
30pub struct GlobalFrameInfo {
31    /// An internal map that keeps track of backtrace frame information for
32    /// each module.
33    ///
34    /// This map is morally a map of ranges to a map of information for that
35    /// module. Each module is expected to reside in a disjoint section of
36    /// contiguous memory. No modules can overlap.
37    ///
38    /// The key of this map is the highest address in the module and the value
39    /// is the module's information, which also contains the start address.
40    ranges: BTreeMap<usize, ModuleInfoFrameInfo>,
41}
42
43/// An RAII structure used to unregister a module's frame information when the
44/// module is destroyed.
45pub struct GlobalFrameInfoRegistration {
46    /// The key that will be removed from the global `ranges` map when this is
47    /// dropped.
48    key: usize,
49}
50
51#[derive(Debug)]
52struct ModuleInfoFrameInfo {
53    start: usize,
54    functions: BTreeMap<usize, FunctionInfo>,
55    module: Arc<ModuleInfo>,
56    frame_infos: PrimaryMap<LocalFunctionIndex, CompiledFunctionFrameInfo>,
57}
58
59impl ModuleInfoFrameInfo {
60    fn function_debug_info(&self, local_index: LocalFunctionIndex) -> &CompiledFunctionFrameInfo {
61        &self.frame_infos.get(local_index).unwrap()
62    }
63
64    /// Gets a function given a pc
65    fn function_info(&self, pc: usize) -> Option<&FunctionInfo> {
66        let (end, func) = self.functions.range(pc..).next()?;
67        if func.start <= pc && pc <= *end {
68            return Some(func);
69        } else {
70            None
71        }
72    }
73}
74
75#[derive(Debug)]
76struct FunctionInfo {
77    start: usize,
78    local_index: LocalFunctionIndex,
79}
80
81impl GlobalFrameInfo {
82    /// Fetches frame information about a program counter in a backtrace.
83    ///
84    /// Returns an object if this `pc` is known to some previously registered
85    /// module, or returns `None` if no information can be found.
86    pub fn lookup_frame_info(&self, pc: usize) -> Option<FrameInfo> {
87        let module = self.module_info(pc)?;
88        let func = module.function_info(pc)?;
89
90        // Use our relative position from the start of the function to find the
91        // machine instruction that corresponds to `pc`, which then allows us to
92        // map that to a wasm original source location.
93        let rel_pos = pc - func.start;
94        let instr_map = &module.function_debug_info(func.local_index).address_map;
95        let pos = match instr_map
96            .instructions
97            .binary_search_by_key(&rel_pos, |map| map.code_offset)
98        {
99            // Exact hit!
100            Ok(pos) => Some(pos),
101
102            // This *would* be at the first slot in the array, so no
103            // instructions cover `pc`.
104            Err(0) => None,
105
106            // This would be at the `nth` slot, so check `n-1` to see if we're
107            // part of that instruction. This happens due to the minus one when
108            // this function is called form trap symbolication, where we don't
109            // always get called with a `pc` that's an exact instruction
110            // boundary.
111            Err(n) => {
112                let instr = &instr_map.instructions[n - 1];
113                if instr.code_offset <= rel_pos && rel_pos < instr.code_offset + instr.code_len {
114                    Some(n - 1)
115                } else {
116                    None
117                }
118            }
119        };
120
121        let instr = match pos {
122            Some(pos) => instr_map.instructions[pos].srcloc,
123            // Some compilers don't emit yet the full trap information for each of
124            // the instructions (such as LLVM).
125            // In case no specific instruction is found, we return by default the
126            // start offset of the function.
127            None => instr_map.start_srcloc,
128        };
129        let func_index = module.module.func_index(func.local_index);
130        Some(FrameInfo {
131            module_name: module.module.name(),
132            func_index: func_index.index() as u32,
133            function_name: module.module.function_names.get(&func_index).cloned(),
134            instr,
135            func_start: instr_map.start_srcloc,
136        })
137    }
138
139    /// Fetches trap information about a program counter in a backtrace.
140    pub fn lookup_trap_info(&self, pc: usize) -> Option<&TrapInformation> {
141        let module = self.module_info(pc)?;
142        let func = module.function_info(pc)?;
143        let traps = &module.function_debug_info(func.local_index).traps;
144        let idx = traps
145            .binary_search_by_key(&((pc - func.start) as u32), |info| info.code_offset)
146            .ok()?;
147        Some(&traps[idx])
148    }
149
150    /// Gets a module given a pc
151    fn module_info(&self, pc: usize) -> Option<&ModuleInfoFrameInfo> {
152        let (end, module_info) = self.ranges.range(pc..).next()?;
153        if module_info.start <= pc && pc <= *end {
154            Some(module_info)
155        } else {
156            None
157        }
158    }
159}
160
161impl Drop for GlobalFrameInfoRegistration {
162    fn drop(&mut self) {
163        if let Ok(mut info) = FRAME_INFO.write() {
164            info.ranges.remove(&self.key);
165        }
166    }
167}
168
169/// Description of a frame in a backtrace for a [`RuntimeError::trace`](crate::RuntimeError::trace).
170///
171/// Whenever a WebAssembly trap occurs an instance of [`RuntimeError`]
172/// is created. Each [`RuntimeError`] has a backtrace of the
173/// WebAssembly frames that led to the trap, and each frame is
174/// described by this structure.
175///
176/// [`RuntimeError`]: crate::RuntimeError
177#[derive(Debug, Clone)]
178pub struct FrameInfo {
179    module_name: String,
180    func_index: u32,
181    function_name: Option<String>,
182    func_start: SourceLoc,
183    instr: SourceLoc,
184}
185
186impl FrameInfo {
187    /// Returns the WebAssembly function index for this frame.
188    ///
189    /// This function index is the index in the function index space of the
190    /// WebAssembly module that this frame comes from.
191    pub fn func_index(&self) -> u32 {
192        self.func_index
193    }
194
195    /// Returns the identifer of the module that this frame is for.
196    ///
197    /// ModuleInfo identifiers are present in the `name` section of a WebAssembly
198    /// binary, but this may not return the exact item in the `name` section.
199    /// ModuleInfo names can be overwritten at construction time or perhaps inferred
200    /// from file names. The primary purpose of this function is to assist in
201    /// debugging and therefore may be tweaked over time.
202    ///
203    /// This function returns `None` when no name can be found or inferred.
204    pub fn module_name(&self) -> &str {
205        &self.module_name
206    }
207
208    /// Returns a descriptive name of the function for this frame, if one is
209    /// available.
210    ///
211    /// The name of this function may come from the `name` section of the
212    /// WebAssembly binary, or wasmer may try to infer a better name for it if
213    /// not available, for example the name of the export if it's exported.
214    ///
215    /// This return value is primarily used for debugging and human-readable
216    /// purposes for things like traps. Note that the exact return value may be
217    /// tweaked over time here and isn't guaranteed to be something in
218    /// particular about a wasm module due to its primary purpose of assisting
219    /// in debugging.
220    ///
221    /// This function returns `None` when no name could be inferred.
222    pub fn function_name(&self) -> Option<&str> {
223        self.function_name.as_deref()
224    }
225
226    /// Returns the offset within the original wasm module this frame's program
227    /// counter was at.
228    ///
229    /// The offset here is the offset from the beginning of the original wasm
230    /// module to the instruction that this frame points to.
231    pub fn module_offset(&self) -> usize {
232        self.instr.bits() as usize
233    }
234
235    /// Returns the offset from the original wasm module's function to this
236    /// frame's program counter.
237    ///
238    /// The offset here is the offset from the beginning of the defining
239    /// function of this frame (within the wasm module) to the instruction this
240    /// frame points to.
241    pub fn func_offset(&self) -> usize {
242        (self.instr.bits() - self.func_start.bits()) as usize
243    }
244}