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 parking_lot::RwLock;
18use std::collections::BTreeMap;
19use std::sync::Arc;
20
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.
26pub static FRAME_INFO: std::sync::LazyLock<RwLock<GlobalFrameInfo>> =
27 std::sync::LazyLock::new(|| Default::default());
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.instructions.binary_search_by_key(&rel_pos, |map| map.code_offset)
96 {
97 // Exact hit!
98 Ok(pos) => Some(pos),
99
100 // This *would* be at the first slot in the array, so no
101 // instructions cover `pc`.
102 Err(0) => None,
103
104 // This would be at the `nth` slot, so check `n-1` to see if we're
105 // part of that instruction. This happens due to the minus one when
106 // this function is called form trap symbolication, where we don't
107 // always get called with a `pc` that's an exact instruction
108 // boundary.
109 Err(n) => {
110 let instr = &instr_map.instructions[n - 1];
111 if instr.code_offset <= rel_pos && rel_pos < instr.code_offset + instr.code_len {
112 Some(n - 1)
113 } else {
114 None
115 }
116 }
117 };
118
119 let instr = match pos {
120 Some(pos) => instr_map.instructions[pos].srcloc,
121 // Some compilers don't emit yet the full trap information for each of
122 // the instructions (such as LLVM).
123 // In case no specific instruction is found, we return by default the
124 // start offset of the function.
125 None => instr_map.start_srcloc,
126 };
127 let func_index = module.module.func_index(func.local_index);
128 Some(FrameInfo {
129 module_name: module.module.name(),
130 func_index: func_index.index() as u32,
131 function_name: module.module.function_names.get(&func_index).cloned(),
132 instr,
133 func_start: instr_map.start_srcloc,
134 })
135 }
136
137 /// Fetches trap information about a program counter in a backtrace.
138 pub fn lookup_trap_info(&self, pc: usize) -> Option<&TrapInformation> {
139 let module = self.module_info(pc)?;
140 let func = module.function_info(pc)?;
141 let traps = &module.function_debug_info(func.local_index).traps;
142 let idx = traps
143 .binary_search_by_key(&((pc - func.start) as u32), |info| info.code_offset)
144 .ok()?;
145 Some(&traps[idx])
146 }
147
148 /// Gets a module given a pc
149 fn module_info(&self, pc: usize) -> Option<&ModuleInfoFrameInfo> {
150 let (end, module_info) = self.ranges.range(pc..).next()?;
151 if module_info.start <= pc && pc <= *end { Some(module_info) } else { None }
152 }
153}
154
155impl Drop for GlobalFrameInfoRegistration {
156 fn drop(&mut self) {
157 FRAME_INFO.write().ranges.remove(&self.key);
158 }
159}
160
161/// Description of a frame in a backtrace for a [`RuntimeError::trace`](crate::RuntimeError::trace).
162///
163/// Whenever a WebAssembly trap occurs an instance of [`RuntimeError`]
164/// is created. Each [`RuntimeError`] has a backtrace of the
165/// WebAssembly frames that led to the trap, and each frame is
166/// described by this structure.
167///
168/// [`RuntimeError`]: crate::RuntimeError
169#[derive(Debug, Clone)]
170pub struct FrameInfo {
171 module_name: String,
172 func_index: u32,
173 function_name: Option<String>,
174 func_start: SourceLoc,
175 instr: SourceLoc,
176}
177
178impl FrameInfo {
179 /// Returns the WebAssembly function index for this frame.
180 ///
181 /// This function index is the index in the function index space of the
182 /// WebAssembly module that this frame comes from.
183 pub fn func_index(&self) -> u32 {
184 self.func_index
185 }
186
187 /// Returns the identifier of the module that this frame is for.
188 ///
189 /// ModuleInfo identifiers are present in the `name` section of a WebAssembly
190 /// binary, but this may not return the exact item in the `name` section.
191 /// ModuleInfo names can be overwritten at construction time or perhaps inferred
192 /// from file names. The primary purpose of this function is to assist in
193 /// debugging and therefore may be tweaked over time.
194 ///
195 /// This function returns `None` when no name can be found or inferred.
196 pub fn module_name(&self) -> &str {
197 &self.module_name
198 }
199
200 /// Returns a descriptive name of the function for this frame, if one is
201 /// available.
202 ///
203 /// The name of this function may come from the `name` section of the
204 /// WebAssembly binary, or near_vm may try to infer a better name for it if
205 /// not available, for example the name of the export if it's exported.
206 ///
207 /// This return value is primarily used for debugging and human-readable
208 /// purposes for things like traps. Note that the exact return value may be
209 /// tweaked over time here and isn't guaranteed to be something in
210 /// particular about a wasm module due to its primary purpose of assisting
211 /// in debugging.
212 ///
213 /// This function returns `None` when no name could be inferred.
214 pub fn function_name(&self) -> Option<&str> {
215 self.function_name.as_deref()
216 }
217
218 /// Returns the offset within the original wasm module this frame's program
219 /// counter was at.
220 ///
221 /// The offset here is the offset from the beginning of the original wasm
222 /// module to the instruction that this frame points to.
223 pub fn module_offset(&self) -> usize {
224 self.instr.bits() as usize
225 }
226
227 /// Returns the offset from the original wasm module's function to this
228 /// frame's program counter.
229 ///
230 /// The offset here is the offset from the beginning of the defining
231 /// function of this frame (within the wasm module) to the instruction this
232 /// frame points to.
233 pub fn func_offset(&self) -> usize {
234 (self.instr.bits() - self.func_start.bits()) as usize
235 }
236}