wasmer_compiler/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//! ```
14
15use crate::types::address_map::{
16    ArchivedFunctionAddressMap, ArchivedInstructionAddressMap, FunctionAddressMap,
17    InstructionAddressMap,
18};
19use crate::types::function::{ArchivedCompiledFunctionFrameInfo, CompiledFunctionFrameInfo};
20use crate::ArtifactBuildFromArchive;
21use rkyv::vec::ArchivedVec;
22use std::collections::BTreeMap;
23use std::sync::{Arc, LazyLock, RwLock};
24use wasmer_types::lib::std::{cmp, ops::Deref};
25use wasmer_types::{
26    entity::{BoxedSlice, EntityRef, PrimaryMap},
27    FrameInfo, LocalFunctionIndex, ModuleInfo, SourceLoc, TrapInformation,
28};
29use wasmer_vm::FunctionBodyPtr;
30
31/// This is a global cache of backtrace frame information for all active
32///
33/// This global cache is used during `Trap` creation to symbolicate frames.
34/// This is populated on module compilation, and it is cleared out whenever
35/// all references to a module are dropped.
36pub static FRAME_INFO: LazyLock<RwLock<GlobalFrameInfo>> = LazyLock::new(RwLock::default);
37
38#[derive(Default)]
39pub struct GlobalFrameInfo {
40    /// An internal map that keeps track of backtrace frame information for
41    /// each module.
42    ///
43    /// This map is morally a map of ranges to a map of information for that
44    /// module. Each module is expected to reside in a disjoint section of
45    /// contiguous memory. No modules can overlap.
46    ///
47    /// The key of this map is the highest address in the module and the value
48    /// is the module's information, which also contains the start address.
49    ranges: BTreeMap<usize, ModuleInfoFrameInfo>,
50}
51
52/// An RAII structure used to unregister a module's frame information when the
53/// module is destroyed.
54#[cfg_attr(feature = "artifact-size", derive(loupe::MemoryUsage))]
55pub struct GlobalFrameInfoRegistration {
56    /// The key that will be removed from the global `ranges` map when this is
57    /// dropped.
58    key: usize,
59}
60
61#[derive(Debug)]
62struct ModuleInfoFrameInfo {
63    start: usize,
64    functions: BTreeMap<usize, FunctionInfo>,
65    module: Arc<ModuleInfo>,
66    frame_infos: FrameInfosVariant,
67}
68
69impl ModuleInfoFrameInfo {
70    fn function_debug_info(
71        &self,
72        local_index: LocalFunctionIndex,
73    ) -> CompiledFunctionFrameInfoVariant {
74        self.frame_infos.get(local_index).unwrap()
75    }
76
77    /// Gets a function given a pc
78    fn function_info(&self, pc: usize) -> Option<&FunctionInfo> {
79        let (end, func) = self.functions.range(pc..).next()?;
80        if func.start <= pc && pc <= *end {
81            Some(func)
82        } else {
83            None
84        }
85    }
86}
87
88#[derive(Debug)]
89struct FunctionInfo {
90    start: usize,
91    local_index: LocalFunctionIndex,
92}
93
94impl GlobalFrameInfo {
95    /// Fetches frame information about a program counter in a backtrace.
96    ///
97    /// Returns an object if this `pc` is known to some previously registered
98    /// module, or returns `None` if no information can be found.
99    pub fn lookup_frame_info(&self, pc: usize) -> Option<FrameInfo> {
100        let module = self.module_info(pc)?;
101        let func = module.function_info(pc)?;
102
103        // Use our relative position from the start of the function to find the
104        // machine instruction that corresponds to `pc`, which then allows us to
105        // map that to a wasm original source location.
106        let rel_pos = pc - func.start;
107        let debug_info = module.function_debug_info(func.local_index);
108        let instr_map = debug_info.address_map();
109        let pos = match instr_map.instructions().code_offset_by_key(rel_pos) {
110            // Exact hit!
111            Ok(pos) => Some(pos),
112
113            // This *would* be at the first slot in the array, so no
114            // instructions cover `pc`.
115            Err(0) => None,
116
117            // This would be at the `nth` slot, so check `n-1` to see if we're
118            // part of that instruction. This happens due to the minus one when
119            // this function is called form trap symbolication, where we don't
120            // always get called with a `pc` that's an exact instruction
121            // boundary.
122            Err(n) => {
123                let instr = &instr_map.instructions().get(n - 1);
124                if instr.code_offset <= rel_pos && rel_pos < instr.code_offset + instr.code_len {
125                    Some(n - 1)
126                } else {
127                    None
128                }
129            }
130        };
131
132        let instr = match pos {
133            Some(pos) => instr_map.instructions().get(pos).srcloc,
134            // Some compilers don't emit yet the full trap information for each of
135            // the instructions (such as LLVM).
136            // In case no specific instruction is found, we return by default the
137            // start offset of the function.
138            None => instr_map.start_srcloc(),
139        };
140        let func_index = module.module.func_index(func.local_index);
141        Some(FrameInfo::new(
142            module.module.name(),
143            func_index.index() as u32,
144            module.module.function_names.get(&func_index).cloned(),
145            instr_map.start_srcloc(),
146            instr,
147        ))
148    }
149
150    /// Fetches trap information about a program counter in a backtrace.
151    pub fn lookup_trap_info(&self, pc: usize) -> Option<TrapInformation> {
152        let module = self.module_info(pc)?;
153        let func = module.function_info(pc)?;
154        let debug_info = module.function_debug_info(func.local_index);
155        let traps = debug_info.traps();
156        let idx = traps
157            .binary_search_by_key(&((pc - func.start) as u32), |info| info.code_offset)
158            .ok()?;
159        Some(traps[idx])
160    }
161
162    /// Gets a module given a pc
163    fn module_info(&self, pc: usize) -> Option<&ModuleInfoFrameInfo> {
164        let (end, module_info) = self.ranges.range(pc..).next()?;
165        if module_info.start <= pc && pc <= *end {
166            Some(module_info)
167        } else {
168            None
169        }
170    }
171}
172
173impl Drop for GlobalFrameInfoRegistration {
174    fn drop(&mut self) {
175        if let Ok(mut info) = FRAME_INFO.write() {
176            info.ranges.remove(&self.key);
177        }
178    }
179}
180
181/// Represents a continuous region of executable memory starting with a function
182/// entry point.
183#[derive(Debug)]
184#[repr(C)]
185pub struct FunctionExtent {
186    /// Entry point for normal entry of the function. All addresses in the
187    /// function lie after this address.
188    pub ptr: FunctionBodyPtr,
189    /// Length in bytes.
190    pub length: usize,
191}
192
193/// The variant of the frame information which can be an owned type
194/// or the explicit framed map
195#[derive(Debug)]
196pub enum FrameInfosVariant {
197    /// Owned frame infos
198    Owned(PrimaryMap<LocalFunctionIndex, CompiledFunctionFrameInfo>),
199    /// Archived frame infos
200    Archived(ArtifactBuildFromArchive),
201}
202
203impl FrameInfosVariant {
204    /// Gets the frame info for a given local function index
205    pub fn get(&self, index: LocalFunctionIndex) -> Option<CompiledFunctionFrameInfoVariant> {
206        match self {
207            Self::Owned(map) => map.get(index).map(CompiledFunctionFrameInfoVariant::Ref),
208            Self::Archived(archive) => archive
209                .get_frame_info_ref()
210                .get(index)
211                .map(CompiledFunctionFrameInfoVariant::Archived),
212        }
213    }
214}
215
216/// The variant of the compiled function frame info which can be an owned type
217#[derive(Debug)]
218pub enum CompiledFunctionFrameInfoVariant<'a> {
219    /// A reference to the frame info
220    Ref(&'a CompiledFunctionFrameInfo),
221    /// An archived frame info
222    Archived(&'a ArchivedCompiledFunctionFrameInfo),
223}
224
225impl CompiledFunctionFrameInfoVariant<'_> {
226    /// Gets the address map for the frame info
227    pub fn address_map(&self) -> FunctionAddressMapVariant<'_> {
228        match self {
229            CompiledFunctionFrameInfoVariant::Ref(info) => {
230                FunctionAddressMapVariant::Ref(&info.address_map)
231            }
232            CompiledFunctionFrameInfoVariant::Archived(info) => {
233                FunctionAddressMapVariant::Archived(&info.address_map)
234            }
235        }
236    }
237
238    /// Gets the traps for the frame info
239    pub fn traps(&self) -> VecTrapInformationVariant {
240        match self {
241            CompiledFunctionFrameInfoVariant::Ref(info) => {
242                VecTrapInformationVariant::Ref(&info.traps)
243            }
244            CompiledFunctionFrameInfoVariant::Archived(info) => {
245                let traps = rkyv::deserialize::<_, rkyv::rancor::Error>(&info.traps).unwrap();
246                VecTrapInformationVariant::Owned(traps)
247            }
248        }
249    }
250}
251
252/// The variant of the trap information which can be an owned type
253#[derive(Debug)]
254pub enum VecTrapInformationVariant<'a> {
255    Ref(&'a Vec<TrapInformation>),
256    Owned(Vec<TrapInformation>),
257}
258
259// We need to implement it for the `Deref` in `wasmer_types` to support both `core` and `std`.
260impl Deref for VecTrapInformationVariant<'_> {
261    type Target = [TrapInformation];
262
263    fn deref(&self) -> &Self::Target {
264        match self {
265            VecTrapInformationVariant::Ref(traps) => traps,
266            VecTrapInformationVariant::Owned(traps) => traps,
267        }
268    }
269}
270
271#[derive(Debug)]
272pub enum FunctionAddressMapVariant<'a> {
273    Ref(&'a FunctionAddressMap),
274    Archived(&'a ArchivedFunctionAddressMap),
275}
276
277impl FunctionAddressMapVariant<'_> {
278    pub fn instructions(&self) -> FunctionAddressMapInstructionVariant {
279        match self {
280            FunctionAddressMapVariant::Ref(map) => {
281                FunctionAddressMapInstructionVariant::Owned(&map.instructions)
282            }
283            FunctionAddressMapVariant::Archived(map) => {
284                FunctionAddressMapInstructionVariant::Archived(&map.instructions)
285            }
286        }
287    }
288
289    pub fn start_srcloc(&self) -> SourceLoc {
290        match self {
291            FunctionAddressMapVariant::Ref(map) => map.start_srcloc,
292            FunctionAddressMapVariant::Archived(map) => {
293                rkyv::deserialize::<_, rkyv::rancor::Error>(&map.start_srcloc).unwrap()
294            }
295        }
296    }
297
298    pub fn end_srcloc(&self) -> SourceLoc {
299        match self {
300            FunctionAddressMapVariant::Ref(map) => map.end_srcloc,
301            FunctionAddressMapVariant::Archived(map) => {
302                rkyv::deserialize::<_, rkyv::rancor::Error>(&map.end_srcloc).unwrap()
303            }
304        }
305    }
306
307    pub fn body_offset(&self) -> usize {
308        match self {
309            FunctionAddressMapVariant::Ref(map) => map.body_offset,
310            FunctionAddressMapVariant::Archived(map) => map.body_offset.to_native() as usize,
311        }
312    }
313
314    pub fn body_len(&self) -> usize {
315        match self {
316            FunctionAddressMapVariant::Ref(map) => map.body_len,
317            FunctionAddressMapVariant::Archived(map) => map.body_len.to_native() as usize,
318        }
319    }
320}
321
322#[derive(Debug)]
323pub enum FunctionAddressMapInstructionVariant<'a> {
324    Owned(&'a Vec<InstructionAddressMap>),
325    Archived(&'a ArchivedVec<ArchivedInstructionAddressMap>),
326}
327
328impl FunctionAddressMapInstructionVariant<'_> {
329    pub fn code_offset_by_key(&self, key: usize) -> Result<usize, usize> {
330        match self {
331            FunctionAddressMapInstructionVariant::Owned(instructions) => {
332                instructions.binary_search_by_key(&key, |map| map.code_offset)
333            }
334            FunctionAddressMapInstructionVariant::Archived(instructions) => {
335                instructions.binary_search_by_key(&key, |map| map.code_offset.to_native() as usize)
336            }
337        }
338    }
339
340    pub fn get(&self, index: usize) -> InstructionAddressMap {
341        match self {
342            FunctionAddressMapInstructionVariant::Owned(instructions) => instructions[index],
343            FunctionAddressMapInstructionVariant::Archived(instructions) => instructions
344                .get(index)
345                .map(|map| InstructionAddressMap {
346                    srcloc: rkyv::deserialize::<_, rkyv::rancor::Error>(&map.srcloc).unwrap(),
347                    code_offset: map.code_offset.to_native() as usize,
348                    code_len: map.code_len.to_native() as usize,
349                })
350                .unwrap(),
351        }
352    }
353}
354
355/// Registers a new compiled module's frame information.
356///
357/// This function will register the `names` information for all of the
358/// compiled functions within `module`. If the `module` has no functions
359/// then `None` will be returned. Otherwise the returned object, when
360/// dropped, will be used to unregister all name information from this map.
361pub fn register(
362    module: Arc<ModuleInfo>,
363    finished_functions: &BoxedSlice<LocalFunctionIndex, FunctionExtent>,
364    frame_infos: FrameInfosVariant,
365) -> Option<GlobalFrameInfoRegistration> {
366    let mut min = usize::MAX;
367    let mut max = 0;
368    let mut functions = BTreeMap::new();
369    for (
370        i,
371        FunctionExtent {
372            ptr: start,
373            length: len,
374        },
375    ) in finished_functions.iter()
376    {
377        let start = **start as usize;
378        // end is "last byte" of the function code
379        let end = start + len - 1;
380        min = cmp::min(min, start);
381        max = cmp::max(max, end);
382        let func = FunctionInfo {
383            start,
384            local_index: i,
385        };
386        assert!(functions.insert(end, func).is_none());
387    }
388    if functions.is_empty() {
389        return None;
390    }
391
392    let mut info = FRAME_INFO.write().unwrap();
393    // First up assert that our chunk of jit functions doesn't collide with
394    // any other known chunks of jit functions...
395    if let Some((_, prev)) = info.ranges.range(max..).next() {
396        assert!(prev.start > max);
397    }
398    if let Some((prev_end, _)) = info.ranges.range(..=min).next_back() {
399        assert!(*prev_end < min);
400    }
401
402    // ... then insert our range and assert nothing was there previously
403    let prev = info.ranges.insert(
404        max,
405        ModuleInfoFrameInfo {
406            start: min,
407            functions,
408            module,
409            frame_infos,
410        },
411    );
412    assert!(prev.is_none());
413    Some(GlobalFrameInfoRegistration { key: max })
414}