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