Skip to main content

symbolic_debuginfo/
base.rs

1use std::borrow::Cow;
2use std::fmt;
3use std::ops::{Bound, Deref, RangeBounds};
4use std::str::FromStr;
5
6use symbolic_common::{clean_path, join_path, Arch, CodeId, DebugId, Name};
7
8use crate::sourcebundle::SourceFileDescriptor;
9
10pub(crate) trait Parse<'data>: Sized {
11    type Error;
12
13    fn parse(data: &'data [u8]) -> Result<Self, Self::Error>;
14
15    fn test(data: &'data [u8]) -> bool {
16        Self::parse(data).is_ok()
17    }
18}
19
20/// An error returned for unknown or invalid `ObjectKinds`.
21#[derive(Debug)]
22pub struct UnknownObjectKindError;
23
24impl fmt::Display for UnknownObjectKindError {
25    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26        write!(f, "unknown object class")
27    }
28}
29
30impl std::error::Error for UnknownObjectKindError {}
31
32/// Represents the designated use of the object file and hints at its contents.
33#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone)]
34pub enum ObjectKind {
35    /// There is no object class specified for this object file.
36    None,
37
38    /// The Relocatable file type is the format used for intermediate object
39    /// files. It is a very compact format containing all its sections in one
40    /// segment. The compiler and assembler usually create one Relocatable file
41    /// for each source code file. By convention, the file name extension for
42    /// this format is .o.
43    Relocatable,
44
45    /// The Executable file type is the format used by standard executable
46    /// programs.
47    Executable,
48
49    /// The Library file type is for dynamic shared libraries. It contains
50    /// some additional tables to support multiple modules. By convention, the
51    /// file name extension for this format is .dylib, except for the main
52    /// shared library of a framework, which does not usually have a file name
53    /// extension.
54    Library,
55
56    /// The Dump file type is used to store core files, which are
57    /// traditionally created when a program crashes. Core files store the
58    /// entire address space of a process at the time it crashed. You can
59    /// later run gdb on the core file to figure out why the crash occurred.
60    Dump,
61
62    /// The Debug file type designates files that store symbol information
63    /// for a corresponding binary file.
64    Debug,
65
66    /// A container that just stores source code files, but no other debug
67    /// information corresponding to the original object file.
68    Sources,
69
70    /// The Other type represents any valid object class that does not fit any
71    /// of the other classes. These are mostly CPU or OS dependent, or unique
72    /// to a single kind of object.
73    Other,
74}
75
76impl ObjectKind {
77    /// Returns the name of the object kind.
78    pub fn name(self) -> &'static str {
79        match self {
80            ObjectKind::None => "none",
81            ObjectKind::Relocatable => "rel",
82            ObjectKind::Executable => "exe",
83            ObjectKind::Library => "lib",
84            ObjectKind::Dump => "dump",
85            ObjectKind::Debug => "dbg",
86            ObjectKind::Sources => "src",
87            ObjectKind::Other => "other",
88        }
89    }
90
91    /// Returns a human readable name of the object kind.
92    ///
93    /// This is also used in alternate formatting:
94    ///
95    /// ```rust
96    /// # use symbolic_debuginfo::ObjectKind;
97    /// assert_eq!(format!("{:#}", ObjectKind::Executable), ObjectKind::Executable.human_name());
98    /// ```
99    pub fn human_name(self) -> &'static str {
100        match self {
101            ObjectKind::None => "file",
102            ObjectKind::Relocatable => "object",
103            ObjectKind::Executable => "executable",
104            ObjectKind::Library => "library",
105            ObjectKind::Dump => "memory dump",
106            ObjectKind::Debug => "debug companion",
107            ObjectKind::Sources => "sources",
108            ObjectKind::Other => "file",
109        }
110    }
111}
112
113impl fmt::Display for ObjectKind {
114    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
115        if f.alternate() {
116            f.write_str(self.human_name())
117        } else {
118            f.write_str(self.name())
119        }
120    }
121}
122
123impl FromStr for ObjectKind {
124    type Err = UnknownObjectKindError;
125
126    fn from_str(string: &str) -> Result<ObjectKind, UnknownObjectKindError> {
127        Ok(match string {
128            "none" => ObjectKind::None,
129            "rel" => ObjectKind::Relocatable,
130            "exe" => ObjectKind::Executable,
131            "lib" => ObjectKind::Library,
132            "dump" => ObjectKind::Dump,
133            "dbg" => ObjectKind::Debug,
134            "src" => ObjectKind::Sources,
135            "other" => ObjectKind::Other,
136            _ => return Err(UnknownObjectKindError),
137        })
138    }
139}
140
141/// An error returned for unknown or invalid [`FileFormats`](enum.FileFormat.html).
142#[derive(Debug)]
143pub struct UnknownFileFormatError;
144
145impl fmt::Display for UnknownFileFormatError {
146    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
147        write!(f, "unknown file format")
148    }
149}
150
151impl std::error::Error for UnknownFileFormatError {}
152
153/// Represents the physical object file format.
154#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone)]
155pub enum FileFormat {
156    /// An unknown file format.
157    Unknown,
158    /// Breakpad ASCII symbol.
159    Breakpad,
160    /// Executable and Linkable Format, used on Linux.
161    Elf,
162    /// Mach Objects, used on macOS and iOS derivatives.
163    MachO,
164    /// Program Database, the debug companion format on Windows.
165    Pdb,
166    /// Portable Executable, an extension of COFF used on Windows.
167    Pe,
168    /// Source code bundle ZIP.
169    SourceBundle,
170    /// WASM container.
171    Wasm,
172    /// Portable PDB
173    PortablePdb,
174}
175
176impl FileFormat {
177    /// Returns the name of the file format.
178    pub fn name(self) -> &'static str {
179        match self {
180            FileFormat::Unknown => "unknown",
181            FileFormat::Breakpad => "breakpad",
182            FileFormat::Elf => "elf",
183            FileFormat::MachO => "macho",
184            FileFormat::Pdb => "pdb",
185            FileFormat::Pe => "pe",
186            FileFormat::SourceBundle => "sourcebundle",
187            FileFormat::Wasm => "wasm",
188            FileFormat::PortablePdb => "portablepdb",
189        }
190    }
191}
192
193impl fmt::Display for FileFormat {
194    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
195        f.write_str(self.name())
196    }
197}
198
199impl FromStr for FileFormat {
200    type Err = UnknownFileFormatError;
201
202    fn from_str(string: &str) -> Result<FileFormat, UnknownFileFormatError> {
203        Ok(match string {
204            "breakpad" => FileFormat::Breakpad,
205            "elf" => FileFormat::Elf,
206            "macho" => FileFormat::MachO,
207            "pdb" => FileFormat::Pdb,
208            "pe" => FileFormat::Pe,
209            "sourcebundle" => FileFormat::SourceBundle,
210            "wasm" => FileFormat::Wasm,
211            "portablepdb" => FileFormat::PortablePdb,
212            _ => return Err(UnknownFileFormatError),
213        })
214    }
215}
216
217/// A symbol from a symbol table.
218#[derive(Clone, Default, Eq, PartialEq)]
219pub struct Symbol<'data> {
220    /// The name of the symbol.
221    ///
222    /// This name is generally mangled. It can be demangled by constructing a `Name` instance and
223    /// calling demangle on it. Certain object files might only store demangled symbol names.
224    pub name: Option<Cow<'data, str>>,
225
226    /// The relative address of this symbol.
227    pub address: u64,
228
229    /// The size of this symbol, if known.
230    ///
231    /// When loading symbols from an object file, the size will generally not be known. Instead,
232    /// construct a [`SymbolMap`] from the object, which also fills in sizes.
233    ///
234    /// [`SymbolMap`]: struct.SymbolMap.html
235    pub size: u64,
236}
237
238impl Symbol<'_> {
239    /// Returns the name of this symbol as string.
240    pub fn name(&self) -> Option<&str> {
241        self.name.as_ref().map(Cow::as_ref)
242    }
243
244    /// Determines whether the given address is covered by this symbol.
245    ///
246    /// If the symbol size has not been computed, the address is assumed to be covered if it is
247    /// greated than the symbol address. Otherwise, the address must be in the half-open interval
248    /// `[address, address + size)`.
249    pub fn contains(&self, address: u64) -> bool {
250        address >= self.address && (self.size == 0 || address < self.address + self.size)
251    }
252}
253
254impl fmt::Debug for Symbol<'_> {
255    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
256        f.debug_struct("Symbol")
257            .field("name", &self.name().unwrap_or("<unknown>"))
258            .field("address", &format_args!("{:#x}", self.address))
259            .field("size", &format_args!("{:#x}", self.size))
260            .finish()
261    }
262}
263
264/// IntoIterator type for [`SymbolMap`](struct.SymbolMap.html).
265pub type SymbolMapIter<'data> = std::vec::IntoIter<Symbol<'data>>;
266
267/// A sorted list of symbols, suitable for quick lookups.
268///
269/// This type can either be computed from a list or iterator of symbols, or preferrably directly
270/// by calling [`ObjectLike::symbol_map`] on any object. Symbols in the symbol map are guaranteed to
271/// have a `size` set, except for the last symbol, which is computed by taking the offset to the
272/// subsequent symbol.
273///
274/// `SymbolMap` also exposes a read-only view on the sorted slice of symbols. It can be converted to
275/// and from lists of symbols.
276///
277/// ## Example
278///
279/// ```rust
280/// # use symbolic_debuginfo::{Symbol, SymbolMap};
281/// let map = SymbolMap::from(vec![
282///     Symbol { name: Some("A".into()), address: 0x4400, size: 0 },
283///     Symbol { name: Some("B".into()), address: 0x4200, size: 0 },
284///     Symbol { name: Some("C".into()), address: 0x4000, size: 0 },
285/// ]);
286///
287/// assert_eq!(map[0], Symbol {
288///     name: Some("C".into()),
289///     address: 0x4000,
290///     size: 0x200,
291/// });
292/// ```
293///
294/// [`ObjectLike::symbol_map`]: trait.ObjectLike.html#tymethod.symbol_map
295#[derive(Clone, Debug, Default)]
296pub struct SymbolMap<'data> {
297    symbols: Vec<Symbol<'data>>,
298}
299
300impl<'data> SymbolMap<'data> {
301    /// Creates a new, empty symbol map.
302    pub fn new() -> Self {
303        SymbolMap {
304            symbols: Vec::new(),
305        }
306    }
307
308    /// Looks up the symbol covering the given address.
309    pub fn lookup(&self, address: u64) -> Option<&Symbol<'data>> {
310        match self.symbols.binary_search_by_key(&address, Self::key) {
311            Ok(index) => Some(&self.symbols[index]),
312            Err(0) => None,
313            Err(next_index) => {
314                let symbol = &self.symbols[next_index - 1];
315                if symbol.contains(address) {
316                    Some(symbol)
317                } else {
318                    None
319                }
320            }
321        }
322    }
323
324    /// Looks up a symbol by its start address.
325    pub fn lookup_exact(&self, address: u64) -> Option<&Symbol<'data>> {
326        let idx = self
327            .symbols
328            .binary_search_by_key(&address, Self::key)
329            .ok()?;
330        self.symbols.get(idx)
331    }
332
333    /// Looks up a symbol covering an entire range.
334    ///
335    /// This is similar to [`lookup`], but it only returns the symbol result if it _also_ covers the
336    /// inclusive end address of the range.
337    ///
338    /// [`lookup`]: struct.SymbolMap.html#method.lookup
339    pub fn lookup_range<R>(&self, range: R) -> Option<&Symbol<'data>>
340    where
341        R: RangeBounds<u64>,
342    {
343        let start = match range.start_bound() {
344            Bound::Included(start) => *start,
345            Bound::Excluded(start) => *start + 1,
346            Bound::Unbounded => 0,
347        };
348
349        let symbol = self.lookup(start)?;
350
351        let end = match range.end_bound() {
352            Bound::Included(end) => *end,
353            Bound::Excluded(end) => *end - 1,
354            Bound::Unbounded => u64::MAX,
355        };
356
357        if end <= start || symbol.contains(end) {
358            Some(symbol)
359        } else {
360            None
361        }
362    }
363
364    /// Returns the lookup key for a symbol, which is the symbol's address.
365    #[inline(always)]
366    fn key(symbol: &Symbol<'data>) -> u64 {
367        symbol.address
368    }
369}
370
371impl<'d> Deref for SymbolMap<'d> {
372    type Target = [Symbol<'d>];
373
374    fn deref(&self) -> &Self::Target {
375        &self.symbols
376    }
377}
378
379impl<'data> IntoIterator for SymbolMap<'data> {
380    type Item = Symbol<'data>;
381    type IntoIter = SymbolMapIter<'data>;
382
383    fn into_iter(self) -> Self::IntoIter {
384        self.symbols.into_iter()
385    }
386}
387
388impl<'data, 'a> IntoIterator for &'a SymbolMap<'data> {
389    type Item = &'a Symbol<'data>;
390    type IntoIter = std::slice::Iter<'a, Symbol<'data>>;
391
392    fn into_iter(self) -> Self::IntoIter {
393        self.symbols.iter()
394    }
395}
396
397impl<'d> AsRef<[Symbol<'d>]> for SymbolMap<'d> {
398    fn as_ref(&self) -> &[Symbol<'d>] {
399        &self.symbols
400    }
401}
402
403impl<'d> From<Vec<Symbol<'d>>> for SymbolMap<'d> {
404    fn from(mut symbols: Vec<Symbol<'d>>) -> Self {
405        if !symbols.is_empty() {
406            // NB: This might require stable sorting to ensure determinism if multiple symbols point
407            // at the same location. However, this only seems to happen for equivalent variants of
408            // the same function.
409            //
410            // An example would be destructors where D2 (base object destructor) and D1 (complete
411            // object destructor) might share the same code. Since those always demangle to the same
412            // name, we do not care which function to keep in this case.
413            //
414            // Inlined functions will generally not appear in this list, unless they _also_ have an
415            // explicit function body, in which case they will have a unique address, again.
416            symbols.sort_by_key(Self::key);
417
418            // Compute sizes of consecutive symbols if the size has not been provided by the symbol
419            // iterator. In the same go, drop all but the first symbols at any given address. We do
420            // not rely on the size of symbols in this case, since the ranges might still be
421            // overlapping.
422            symbols.dedup_by(|next, symbol| {
423                if symbol.size == 0 {
424                    symbol.size = next.address - symbol.address;
425                }
426                symbol.address == next.address
427            })
428        }
429
430        SymbolMap { symbols }
431    }
432}
433
434impl<'d> FromIterator<Symbol<'d>> for SymbolMap<'d> {
435    fn from_iter<I>(iter: I) -> Self
436    where
437        I: IntoIterator<Item = Symbol<'d>>,
438    {
439        Vec::from_iter(iter).into()
440    }
441}
442
443/// File information referred by [`LineInfo`](struct.LineInfo.html) comprising a directory and name.
444///
445/// The file path is usually relative to a compilation directory. It might contain parent directory
446/// segments (`../`).
447#[derive(Clone, Default, Eq, PartialEq)]
448pub struct FileInfo<'data> {
449    /// The file's basename.
450    name: Cow<'data, [u8]>,
451    /// Path to the file.
452    dir: Cow<'data, [u8]>,
453    /// The base name on the source server.
454    ///
455    /// This only exists if we have a debug file containing
456    /// source server information.
457    srcsrv_name: Option<Cow<'data, [u8]>>,
458    /// The path to the file on the source server.
459    ///
460    /// This only exists if we have a debug file containing
461    /// source server information.
462    srcsrv_dir: Option<Cow<'data, [u8]>>,
463    /// The optional VCS revision (e.g., Perforce changelist, git commit hash).
464    ///
465    /// This only exists if we have a debug file containing
466    /// source server information.
467    srcsrv_revision: Option<Cow<'data, str>>,
468}
469
470impl<'data> FileInfo<'data> {
471    /// Creates a `FileInfo` with a given directory and the file name.
472    #[cfg(feature = "dwarf")]
473    pub fn new(dir: Cow<'data, [u8]>, name: Cow<'data, [u8]>) -> Self {
474        FileInfo {
475            name,
476            dir,
477            srcsrv_name: None,
478            srcsrv_dir: None,
479            srcsrv_revision: None,
480        }
481    }
482
483    /// Creates a `FileInfo` from a joined path by trying to split it.
484    #[cfg(any(feature = "breakpad", feature = "ms", feature = "sourcebundle"))]
485    pub fn from_path(path: &'data [u8]) -> Self {
486        let (dir, name) = symbolic_common::split_path_bytes(path);
487
488        FileInfo {
489            name: Cow::Borrowed(name),
490            dir: match dir {
491                Some(dir) => Cow::Borrowed(dir),
492                None => Cow::default(),
493            },
494            srcsrv_name: None,
495            srcsrv_dir: None,
496            srcsrv_revision: None,
497        }
498    }
499
500    /// Creates a `FileInfo` from a joined path by trying to split it.
501    /// Unlike from_path(), copies the given data instead of referencing it.
502    pub(crate) fn from_path_owned(path: &[u8]) -> Self {
503        let (dir, name) = symbolic_common::split_path_bytes(path);
504
505        FileInfo {
506            name: Cow::Owned(name.to_vec()),
507            dir: match dir {
508                Some(dir) => Cow::Owned(dir.to_vec()),
509                None => Cow::default(),
510            },
511            srcsrv_name: None,
512            srcsrv_dir: None,
513            srcsrv_revision: None,
514        }
515    }
516
517    /// Creates a `FileInfo` with the file name.
518    pub fn from_filename(name: &'data [u8]) -> Self {
519        FileInfo {
520            name: Cow::Borrowed(name),
521            dir: Cow::default(),
522            srcsrv_name: None,
523            srcsrv_dir: None,
524            srcsrv_revision: None,
525        }
526    }
527
528    /// The file name as UTF-8 string.
529    pub fn name_str(&self) -> Cow<'data, str> {
530        from_utf8_cow_lossy(&self.name)
531    }
532
533    /// Path to the file relative to the compilation directory.
534    pub fn dir_str(&self) -> Cow<'data, str> {
535        from_utf8_cow_lossy(&self.dir)
536    }
537
538    /// The full path to the file, relative to the compilation directory.
539    pub fn path_str(&self) -> String {
540        let joined = join_path(&self.dir_str(), &self.name_str());
541        clean_path(&joined).into_owned()
542    }
543
544    /// The file name on the source server as UTF-8 string.
545    ///
546    /// This only exists if we have a debug file containing
547    /// source server information.
548    pub fn srcsrv_name_str(&self) -> Option<Cow<'data, str>> {
549        self.srcsrv_name.as_ref().map(from_utf8_cow_lossy)
550    }
551
552    /// Path to the file on the source server.
553    ///
554    /// This only exists if we have a debug file containing
555    /// source server information.
556    pub fn srcsrv_dir_str(&self) -> Option<Cow<'data, str>> {
557        self.srcsrv_dir.as_ref().map(from_utf8_cow_lossy)
558    }
559
560    /// The full path to the file on the source server.
561    ///
562    /// This only exists if we have a debug file containing
563    /// source server information.
564    pub fn srcsrv_path_str(&self) -> Option<String> {
565        let joined = join_path(
566            &self.srcsrv_dir_str().unwrap_or_default(),
567            &self.srcsrv_name_str()?,
568        );
569        Some(clean_path(&joined).into_owned())
570    }
571
572    /// The optional VCS revision (e.g., Perforce changelist, git commit hash).
573    ///
574    /// This only exists if we have a debug file containing
575    /// source server information.
576    pub fn srcsrv_revision(&self) -> Option<&str> {
577        self.srcsrv_revision.as_deref()
578    }
579
580    pub(crate) fn set_srcsrv_path(&mut self, path: &[u8]) {
581        let (dir, name) = symbolic_common::split_path_bytes(path);
582
583        self.srcsrv_name = Some(Cow::Owned(name.to_owned()));
584        self.srcsrv_dir = dir.map(|d| Cow::Owned(d.to_owned()));
585    }
586
587    pub(crate) fn set_srcsrv_revision(&mut self, revision: Option<String>) {
588        self.srcsrv_revision = revision.map(Cow::Owned);
589    }
590}
591
592#[allow(clippy::ptr_arg)] // false positive https://github.com/rust-lang/rust-clippy/issues/9218
593pub(crate) fn from_utf8_cow_lossy<'data>(input: &Cow<'data, [u8]>) -> Cow<'data, str> {
594    // See https://github.com/rust-lang/rust/issues/32669
595    match input {
596        Cow::Borrowed(bytes) => String::from_utf8_lossy(bytes),
597        Cow::Owned(bytes) => match String::from_utf8_lossy(bytes) {
598            Cow::Borrowed(_) => unsafe { String::from_utf8_unchecked(bytes.to_vec()) }.into(),
599            Cow::Owned(s) => s.into(),
600        },
601    }
602}
603
604impl fmt::Debug for FileInfo<'_> {
605    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
606        f.debug_struct("FileInfo")
607            .field("name", &self.name_str())
608            .field("dir", &self.dir_str())
609            .finish()
610    }
611}
612
613/// File information comprising a compilation directory, relative path and name.
614pub struct FileEntry<'data> {
615    /// Path to the compilation directory. File paths are relative to this.
616    compilation_dir: Cow<'data, [u8]>,
617    /// File name and path.
618    pub info: FileInfo<'data>,
619}
620
621impl<'data> FileEntry<'data> {
622    /// Path to the compilation directory.
623    pub fn new(compilation_dir: Cow<'data, [u8]>, info: FileInfo<'data>) -> Self {
624        FileEntry {
625            compilation_dir,
626            info,
627        }
628    }
629
630    /// Path to the compilation directory.
631    pub fn compilation_dir_str(&self) -> Cow<'data, str> {
632        from_utf8_cow_lossy(&self.compilation_dir)
633    }
634
635    /// Absolute path to the file, including the compilation directory.
636    pub fn abs_path_str(&self) -> String {
637        let joined_path = join_path(&self.dir_str(), &self.name_str());
638        let joined = join_path(&self.compilation_dir_str(), &joined_path);
639        clean_path(&joined).into_owned()
640    }
641
642    /// The full path to the file on the source server.
643    ///
644    /// This only exists if we have a debug file containing
645    /// source server information.
646    pub fn srcsrv_path_str(&self) -> Option<String> {
647        self.info.srcsrv_path_str()
648    }
649}
650
651impl fmt::Debug for FileEntry<'_> {
652    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
653        f.debug_struct("FileInfo")
654            .field("compilation_dir", &self.compilation_dir_str())
655            .field("name", &self.name_str())
656            .field("dir", &self.dir_str())
657            .finish()
658    }
659}
660
661impl<'data> Deref for FileEntry<'data> {
662    type Target = FileInfo<'data>;
663
664    fn deref(&self) -> &Self::Target {
665        &self.info
666    }
667}
668
669/// File and line number mapping for an instruction address.
670#[derive(Clone, Eq, PartialEq)]
671pub struct LineInfo<'data> {
672    /// The instruction address relative to the image base (load address).
673    pub address: u64,
674    /// Total code size covered by this line record.
675    pub size: Option<u64>,
676    /// File name and path.
677    pub file: FileInfo<'data>,
678    /// Absolute line number starting at 1. Zero means no line number.
679    pub line: u64,
680}
681
682#[cfg(test)]
683impl LineInfo<'static> {
684    pub(crate) fn new(address: u64, size: u64, file: &[u8], line: u64) -> LineInfo<'_> {
685        LineInfo {
686            address,
687            size: Some(size),
688            file: FileInfo::from_filename(file),
689            line,
690        }
691    }
692}
693
694impl fmt::Debug for LineInfo<'_> {
695    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
696        let mut s = f.debug_struct("LineInfo");
697        s.field("address", &format_args!("{:#x}", self.address));
698
699        match self.size {
700            Some(size) => s.field("size", &format_args!("{size:#x}")),
701            None => s.field("size", &self.size),
702        };
703
704        s.field("file", &self.file)
705            .field("line", &self.line)
706            .finish()
707    }
708}
709
710/// Debug information for a function.
711#[derive(Clone)]
712pub struct Function<'data> {
713    /// Relative instruction address of the start of the function.
714    pub address: u64,
715    /// Total code size covered by the function body, including inlined functions.
716    pub size: u64,
717    /// The name and language of the function symbol.
718    pub name: Name<'data>,
719    /// Path to the compilation directory. File paths are relative to this.
720    pub compilation_dir: &'data [u8],
721    /// Lines covered by this function, including inlined children.
722    pub lines: Vec<LineInfo<'data>>,
723    /// Functions that have been inlined into this function's body.
724    pub inlinees: Vec<Function<'data>>,
725    /// Specifies whether this function is inlined.
726    pub inline: bool,
727}
728
729impl Function<'_> {
730    /// End address of the entire function body, including inlined functions.
731    ///
732    /// This address points at the first instruction after the function body.
733    pub fn end_address(&self) -> u64 {
734        self.address.saturating_add(self.size)
735    }
736}
737
738impl fmt::Debug for Function<'_> {
739    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
740        f.debug_struct("Function")
741            .field("address", &format_args!("{:#x}", self.address))
742            .field("size", &format_args!("{:#x}", self.size))
743            .field("name", &self.name)
744            .field(
745                "compilation_dir",
746                &String::from_utf8_lossy(self.compilation_dir),
747            )
748            .field("lines", &self.lines)
749            .field("inlinees", &self.inlinees)
750            .field("inline", &self.inline)
751            .finish()
752    }
753}
754
755/// A dynamically dispatched iterator over items with the given lifetime.
756pub type DynIterator<'a, T> = Box<dyn Iterator<Item = T> + 'a>;
757
758/// A stateful session for interfacing with debug information.
759///
760/// Debug sessions can be obtained via [`ObjectLike::debug_session`]. Since computing a session may
761/// be a costly operation, try to reuse the session as much as possible.
762///
763/// ## Implementing DebugSession
764///
765/// Reading debug information from object files usually requires loading multiple sections into
766/// memory and computing maps for quick random access to certain information. Since this can be a
767/// quite costly process, this is encapsulated into a `DebugSession`. The session may hold whatever
768/// data and caches may be necessary for efficiently interfacing with the debug info.
769///
770/// All trait methods on a `DebugSession` receive `&mut self`, to allow mutation of internal cache
771/// structures. Lifetimes of returned types are tied to this session's lifetime, which allows to
772/// borrow data from the session.
773///
774/// Examples for things to compute when building a debug session are:
775///
776///  - Decompress debug information if it is stored with compression.
777///  - Build a symbol map for random access to public symbols.
778///  - Map string tables and other lookup tables.
779///  - Read headers of compilation units (compilands) to resolve cross-unit references.
780///
781/// [`ObjectLike::debug_session`]: trait.ObjectLike.html#tymethod.debug_session
782pub trait DebugSession<'session> {
783    /// The error returned when reading debug information fails.
784    type Error;
785
786    /// An iterator over all functions in this debug file.
787    type FunctionIterator: Iterator<Item = Result<Function<'session>, Self::Error>>;
788
789    /// An iterator over all source files referenced by this debug file.
790    type FileIterator: Iterator<Item = Result<FileEntry<'session>, Self::Error>>;
791
792    /// Returns an iterator over all functions in this debug file.
793    ///
794    /// Functions are iterated in the order they are declared in their compilation units. The
795    /// functions yielded by this iterator include all inlinees and line records resolved.
796    ///
797    /// Note that the iterator holds a mutable borrow on the debug session, which allows it to use
798    /// caches and optimize resources while resolving function and line information.
799    fn functions(&'session self) -> Self::FunctionIterator;
800
801    /// Returns an iterator over all source files referenced by this debug file.
802    fn files(&'session self) -> Self::FileIterator;
803
804    /// Looks up a file's source by its full canonicalized path.
805    ///
806    /// Returns a descriptor that has all the information available of the source.  It can
807    /// either contain the source contents directly, if it was embedded, or a source link.
808    fn source_by_path(&self, path: &str) -> Result<Option<SourceFileDescriptor<'_>>, Self::Error>;
809}
810
811/// An object containing debug information.
812pub trait ObjectLike<'data, 'object> {
813    /// Errors thrown when reading information from this object.
814    type Error;
815
816    /// A session that allows optimized access to debugging information.
817    type Session: for<'session> DebugSession<'session, Error = Self::Error>;
818
819    /// The iterator over the symbols in the public symbol table.
820    type SymbolIterator: Iterator<Item = Symbol<'data>>;
821
822    /// The container format of this file.
823    fn file_format(&self) -> FileFormat;
824
825    /// The code identifier of this object.
826    ///
827    /// The identifier can be `None` if it cannot be determined from the object file, for instance,
828    /// because the identifier was stripped in the build process.
829    fn code_id(&self) -> Option<CodeId>;
830
831    /// The debug information identifier of this object.
832    fn debug_id(&self) -> DebugId;
833
834    /// The CPU architecture of this object.
835    fn arch(&self) -> Arch;
836
837    /// The kind of this object.
838    fn kind(&self) -> ObjectKind;
839
840    /// The address at which the image prefers to be loaded into memory.
841    fn load_address(&self) -> u64;
842
843    /// Determines whether this object exposes a public symbol table.
844    fn has_symbols(&self) -> bool;
845
846    /// Returns an iterator over symbols in the public symbol table.
847    fn symbols(&'object self) -> Self::SymbolIterator;
848
849    /// Returns an ordered map of symbols in the symbol table.
850    fn symbol_map(&self) -> SymbolMap<'data>;
851
852    /// Determines whether this object contains debug information.
853    fn has_debug_info(&self) -> bool;
854
855    /// Constructs a debugging session.
856    ///
857    /// A debugging session loads certain information from the object file and creates caches for
858    /// efficient access to various records in the debug information. Since this can be quite a
859    /// costly process, try to reuse the debugging session as long as possible.
860    ///
861    /// Constructing this session will also work if the object does not contain debugging
862    /// information, in which case the session will be a no-op. This can be checked via
863    /// [`has_debug_info`](trait.ObjectLike.html#tymethod.has_debug_info).
864    fn debug_session(&'object self) -> Result<Self::Session, Self::Error>;
865
866    /// Determines whether this object contains stack unwinding information.
867    fn has_unwind_info(&self) -> bool;
868
869    /// Determines whether this object contains embedded sources.
870    fn has_sources(&self) -> bool;
871
872    /// Determines whether this object is malformed and was only partially parsed
873    fn is_malformed(&self) -> bool;
874}
875
876mod derive_serde {
877    /// Helper macro to implement string based serialization and deserialization.
878    ///
879    /// If a type implements `FromStr` and `Display` then this automatically
880    /// implements a serializer/deserializer for that type that dispatches
881    /// appropriately.
882    macro_rules! impl_str_serde {
883        ($type:ty) => {
884            impl ::serde::ser::Serialize for $type {
885                fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
886                where
887                    S: ::serde::ser::Serializer,
888                {
889                    serializer.serialize_str(self.name())
890                }
891            }
892
893            impl<'de> ::serde::de::Deserialize<'de> for $type {
894                fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
895                where
896                    D: ::serde::de::Deserializer<'de>,
897                {
898                    <::std::borrow::Cow<'_, str>>::deserialize(deserializer)?
899                        .parse()
900                        .map_err(::serde::de::Error::custom)
901                }
902            }
903        };
904    }
905
906    impl_str_serde!(super::ObjectKind);
907    impl_str_serde!(super::FileFormat);
908}
909
910#[cfg(test)]
911mod tests {
912    use super::*;
913    use similar_asserts::assert_eq;
914
915    fn file_info<'a>(dir: &'a str, name: &'a str) -> FileInfo<'a> {
916        FileInfo::new(
917            Cow::Borrowed(dir.as_bytes()),
918            Cow::Borrowed(name.as_bytes()),
919        )
920    }
921
922    fn file_entry<'a>(compilation_dir: &'a str, dir: &'a str, name: &'a str) -> FileEntry<'a> {
923        FileEntry::new(
924            Cow::Borrowed(compilation_dir.as_bytes()),
925            file_info(dir, name),
926        )
927    }
928
929    #[test]
930    fn test_file_info() {
931        assert_eq!(file_info("", "foo.h").path_str(), "foo.h");
932        assert_eq!(
933            file_info("C:\\Windows", "foo.h").path_str(),
934            "C:\\Windows\\foo.h"
935        );
936        assert_eq!(
937            file_info("/usr/local", "foo.h").path_str(),
938            "/usr/local/foo.h"
939        );
940        assert_eq!(file_info("/usr/local", "../foo.h").path_str(), "/usr/foo.h");
941        assert_eq!(file_info("/usr/local", "/foo.h").path_str(), "/foo.h");
942    }
943
944    #[test]
945    fn test_file_entry() {
946        assert_eq!(file_entry("", "", "foo.h").abs_path_str(), "foo.h");
947        assert_eq!(
948            file_entry("C:\\Windows", "src", "foo.h").abs_path_str(),
949            "C:\\Windows\\src\\foo.h"
950        );
951        assert_eq!(
952            file_entry("/usr", "local", "foo.h").abs_path_str(),
953            "/usr/local/foo.h"
954        );
955        assert_eq!(
956            file_entry("/usr/local", "..", "foo.h").abs_path_str(),
957            "/usr/foo.h"
958        );
959        assert_eq!(
960            file_entry("/usr", "/src", "foo.h").abs_path_str(),
961            "/src/foo.h"
962        );
963    }
964}