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}