substrate_wasmtime/frame_info.rs
1use std::cmp;
2use std::collections::BTreeMap;
3use std::sync::{Arc, RwLock};
4use wasmtime_environ::entity::EntityRef;
5use wasmtime_environ::ir;
6use wasmtime_environ::wasm::FuncIndex;
7use wasmtime_environ::{FunctionAddressMap, Module, TrapInformation};
8use wasmtime_jit::CompiledModule;
9
10lazy_static::lazy_static! {
11 /// This is a global cache of backtrace frame information for all active
12 ///
13 /// This global cache is used during `Trap` creation to symbolicate frames.
14 /// This is populated on module compilation, and it is cleared out whenever
15 /// all references to a module are dropped.
16 pub static ref FRAME_INFO: RwLock<GlobalFrameInfo> = Default::default();
17}
18
19#[derive(Default)]
20pub struct GlobalFrameInfo {
21 /// An internal map that keeps track of backtrace frame information for
22 /// each module.
23 ///
24 /// This map is morally a map of ranges to a map of information for that
25 /// module. Each module is expected to reside in a disjoint section of
26 /// contiguous memory. No modules can overlap.
27 ///
28 /// The key of this map is the highest address in the module and the value
29 /// is the module's information, which also contains the start address.
30 ranges: BTreeMap<usize, ModuleFrameInfo>,
31}
32
33/// An RAII structure used to unregister a module's frame information when the
34/// module is destroyed.
35pub struct GlobalFrameInfoRegistration {
36 /// The key that will be removed from the global `ranges` map when this is
37 /// dropped.
38 key: usize,
39}
40
41struct ModuleFrameInfo {
42 start: usize,
43 functions: BTreeMap<usize, FunctionInfo>,
44 module: Arc<Module>,
45 #[allow(dead_code)]
46 module_code: Arc<dyn std::any::Any + Send + Sync>,
47}
48
49struct FunctionInfo {
50 start: usize,
51 index: FuncIndex,
52 traps: Vec<TrapInformation>,
53 instr_map: FunctionAddressMap,
54}
55
56impl GlobalFrameInfo {
57 /// Fetches frame information about a program counter in a backtrace.
58 ///
59 /// Returns an object if this `pc` is known to some previously registered
60 /// module, or returns `None` if no information can be found.
61 pub fn lookup_frame_info(&self, pc: usize) -> Option<FrameInfo> {
62 let (module, func) = self.func(pc)?;
63
64 // Use our relative position from the start of the function to find the
65 // machine instruction that corresponds to `pc`, which then allows us to
66 // map that to a wasm original source location.
67 let rel_pos = pc - func.start;
68 let pos = match func
69 .instr_map
70 .instructions
71 .binary_search_by_key(&rel_pos, |map| map.code_offset)
72 {
73 // Exact hit!
74 Ok(pos) => Some(pos),
75
76 // This *would* be at the first slot in the array, so no
77 // instructions cover `pc`.
78 Err(0) => None,
79
80 // This would be at the `nth` slot, so check `n-1` to see if we're
81 // part of that instruction. This happens due to the minus one when
82 // this function is called form trap symbolication, where we don't
83 // always get called with a `pc` that's an exact instruction
84 // boundary.
85 Err(n) => {
86 let instr = &func.instr_map.instructions[n - 1];
87 if instr.code_offset <= rel_pos && rel_pos < instr.code_offset + instr.code_len {
88 Some(n - 1)
89 } else {
90 None
91 }
92 }
93 };
94
95 // In debug mode for now assert that we found a mapping for `pc` within
96 // the function, because otherwise something is buggy along the way and
97 // not accounting for all the instructions. This isn't super critical
98 // though so we can omit this check in release mode.
99 debug_assert!(pos.is_some(), "failed to find instruction for {:x}", pc);
100
101 let instr = match pos {
102 Some(pos) => func.instr_map.instructions[pos].srcloc,
103 None => func.instr_map.start_srcloc,
104 };
105 Some(FrameInfo {
106 module_name: module.module.name.clone(),
107 func_index: func.index.index() as u32,
108 func_name: module.module.func_names.get(&func.index).cloned(),
109 instr,
110 func_start: func.instr_map.start_srcloc,
111 })
112 }
113
114 /// Fetches trap information about a program counter in a backtrace.
115 pub fn lookup_trap_info(&self, pc: usize) -> Option<&TrapInformation> {
116 let (_module, func) = self.func(pc)?;
117 let idx = func
118 .traps
119 .binary_search_by_key(&((pc - func.start) as u32), |info| info.code_offset)
120 .ok()?;
121 Some(&func.traps[idx])
122 }
123
124 fn func(&self, pc: usize) -> Option<(&ModuleFrameInfo, &FunctionInfo)> {
125 let (end, info) = self.ranges.range(pc..).next()?;
126 if pc < info.start || *end < pc {
127 return None;
128 }
129 let (end, func) = info.functions.range(pc..).next()?;
130 if pc < func.start || *end < pc {
131 return None;
132 }
133 Some((info, func))
134 }
135}
136
137impl Drop for GlobalFrameInfoRegistration {
138 fn drop(&mut self) {
139 if let Ok(mut info) = FRAME_INFO.write() {
140 info.ranges.remove(&self.key);
141 }
142 }
143}
144
145/// Registers a new compiled module's frame information.
146///
147/// This function will register the `names` information for all of the
148/// compiled functions within `module`. If the `module` has no functions
149/// then `None` will be returned. Otherwise the returned object, when
150/// dropped, will be used to unregister all name information from this map.
151pub fn register(module: &CompiledModule) -> Option<GlobalFrameInfoRegistration> {
152 let mut min = usize::max_value();
153 let mut max = 0;
154 let mut functions = BTreeMap::new();
155 for (((i, allocated), traps), instrs) in module
156 .finished_functions()
157 .iter()
158 .zip(module.traps().values())
159 .zip(module.address_transform().values())
160 {
161 let (start, end) = unsafe {
162 let ptr = (**allocated).as_ptr();
163 let len = (**allocated).len();
164 (ptr as usize, ptr as usize + len)
165 };
166 min = cmp::min(min, start);
167 max = cmp::max(max, end);
168 let func = FunctionInfo {
169 start,
170 index: module.module().local.func_index(i),
171 traps: traps.to_vec(),
172 instr_map: (*instrs).clone(),
173 };
174 assert!(functions.insert(end, func).is_none());
175 }
176 if functions.len() == 0 {
177 return None;
178 }
179
180 let mut info = FRAME_INFO.write().unwrap();
181 // First up assert that our chunk of jit functions doesn't collide with
182 // any other known chunks of jit functions...
183 if let Some((_, prev)) = info.ranges.range(max..).next() {
184 assert!(prev.start > max);
185 }
186 if let Some((prev_end, _)) = info.ranges.range(..=min).next_back() {
187 assert!(*prev_end < min);
188 }
189
190 // ... then insert our range and assert nothing was there previously
191 let prev = info.ranges.insert(
192 max,
193 ModuleFrameInfo {
194 start: min,
195 functions,
196 module: module.module().clone(),
197 module_code: module.code().clone(),
198 },
199 );
200 assert!(prev.is_none());
201 Some(GlobalFrameInfoRegistration { key: max })
202}
203
204/// Description of a frame in a backtrace for a [`Trap`].
205///
206/// Whenever a WebAssembly trap occurs an instance of [`Trap`] is created. Each
207/// [`Trap`] has a backtrace of the WebAssembly frames that led to the trap, and
208/// each frame is described by this structure.
209///
210/// [`Trap`]: crate::Trap
211#[derive(Debug)]
212pub struct FrameInfo {
213 module_name: Option<String>,
214 func_index: u32,
215 func_name: Option<String>,
216 func_start: ir::SourceLoc,
217 instr: ir::SourceLoc,
218}
219
220impl FrameInfo {
221 /// Returns the WebAssembly function index for this frame.
222 ///
223 /// This function index is the index in the function index space of the
224 /// WebAssembly module that this frame comes from.
225 pub fn func_index(&self) -> u32 {
226 self.func_index
227 }
228
229 /// Returns the identifer of the module that this frame is for.
230 ///
231 /// Module identifiers are present in the `name` section of a WebAssembly
232 /// binary, but this may not return the exact item in the `name` section.
233 /// Module names can be overwritten at construction time or perhaps inferred
234 /// from file names. The primary purpose of this function is to assist in
235 /// debugging and therefore may be tweaked over time.
236 ///
237 /// This function returns `None` when no name can be found or inferred.
238 pub fn module_name(&self) -> Option<&str> {
239 self.module_name.as_deref()
240 }
241
242 /// Returns a descriptive name of the function for this frame, if one is
243 /// available.
244 ///
245 /// The name of this function may come from the `name` section of the
246 /// WebAssembly binary, or wasmtime may try to infer a better name for it if
247 /// not available, for example the name of the export if it's exported.
248 ///
249 /// This return value is primarily used for debugging and human-readable
250 /// purposes for things like traps. Note that the exact return value may be
251 /// tweaked over time here and isn't guaranteed to be something in
252 /// particular about a wasm module due to its primary purpose of assisting
253 /// in debugging.
254 ///
255 /// This function returns `None` when no name could be inferred.
256 pub fn func_name(&self) -> Option<&str> {
257 self.func_name.as_deref()
258 }
259
260 /// Returns the offset within the original wasm module this frame's program
261 /// counter was at.
262 ///
263 /// The offset here is the offset from the beginning of the original wasm
264 /// module to the instruction that this frame points to.
265 pub fn module_offset(&self) -> usize {
266 self.instr.bits() as usize
267 }
268
269 /// Returns the offset from the original wasm module's function to this
270 /// frame's program counter.
271 ///
272 /// The offset here is the offset from the beginning of the defining
273 /// function of this frame (within the wasm module) to the instruction this
274 /// frame points to.
275 pub fn func_offset(&self) -> usize {
276 (self.instr.bits() - self.func_start.bits()) as usize
277 }
278}