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}