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}