Skip to main content

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