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}