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