Skip to main content

symbolic_debuginfo/
object.rs

1//! Generic wrappers over various object file formats.
2
3use std::error::Error;
4use std::fmt;
5
6use symbolic_common::{Arch, AsSelf, CodeId, DebugId};
7use symbolic_ppdb::PortablePdb;
8
9use crate::base::*;
10use crate::breakpad::*;
11use crate::dwarf::*;
12use crate::elf::*;
13use crate::macho::*;
14use crate::pdb::*;
15use crate::pe::*;
16use crate::ppdb::*;
17use crate::sourcebundle::*;
18use crate::wasm::*;
19
20macro_rules! match_inner {
21    ($value:expr, $ty:tt ($pat:pat) => $expr:expr) => {
22        match $value {
23            $ty::Breakpad($pat) => $expr,
24            $ty::Elf($pat) => $expr,
25            $ty::MachO($pat) => $expr,
26            $ty::Pdb($pat) => $expr,
27            $ty::Pe($pat) => $expr,
28            $ty::SourceBundle($pat) => $expr,
29            $ty::Wasm($pat) => $expr,
30            $ty::PortablePdb($pat) => $expr,
31        }
32    };
33}
34
35macro_rules! map_inner {
36    ($value:expr, $from:tt($pat:pat) => $to:tt($expr:expr)) => {
37        match $value {
38            $from::Breakpad($pat) => $to::Breakpad($expr),
39            $from::Elf($pat) => $to::Elf($expr),
40            $from::MachO($pat) => $to::MachO($expr),
41            $from::Pdb($pat) => $to::Pdb($expr),
42            $from::Pe($pat) => $to::Pe($expr),
43            $from::SourceBundle($pat) => $to::SourceBundle($expr),
44            $from::Wasm($pat) => $to::Wasm($expr),
45            $from::PortablePdb($pat) => $to::PortablePdb($expr),
46        }
47    };
48}
49
50macro_rules! map_result {
51    ($value:expr, $from:tt($pat:pat) => $to:tt($expr:expr)) => {
52        match $value {
53            $from::Breakpad($pat) => $expr.map($to::Breakpad).map_err(ObjectError::transparent),
54            $from::Elf($pat) => $expr.map($to::Elf).map_err(ObjectError::transparent),
55            $from::MachO($pat) => $expr.map($to::MachO).map_err(ObjectError::transparent),
56            $from::Pdb($pat) => $expr.map($to::Pdb).map_err(ObjectError::transparent),
57            $from::Pe($pat) => $expr.map($to::Pe).map_err(ObjectError::transparent),
58            $from::SourceBundle($pat) => $expr
59                .map($to::SourceBundle)
60                .map_err(ObjectError::transparent),
61            $from::Wasm($pat) => $expr.map($to::Wasm).map_err(ObjectError::transparent),
62            $from::PortablePdb($pat) => $expr
63                .map($to::PortablePdb)
64                .map_err(ObjectError::transparent),
65        }
66    };
67}
68
69/// Internal representation of the object error type.
70#[derive(Debug)]
71enum ObjectErrorRepr {
72    /// The object file format is not supported.
73    UnsupportedObject,
74
75    /// A transparent error from the inner object file type.
76    Transparent(Box<dyn Error + Send + Sync + 'static>),
77}
78
79/// An error when dealing with any kind of [`Object`](enum.Object.html).
80pub struct ObjectError {
81    repr: ObjectErrorRepr,
82}
83
84impl ObjectError {
85    /// Creates a new object error with the given representation.
86    fn new(repr: ObjectErrorRepr) -> Self {
87        Self { repr }
88    }
89
90    /// Creates a new object error from an arbitrary error payload.
91    fn transparent<E>(source: E) -> Self
92    where
93        E: Into<Box<dyn Error + Send + Sync>>,
94    {
95        let repr = ObjectErrorRepr::Transparent(source.into());
96        Self { repr }
97    }
98}
99
100impl fmt::Debug for ObjectError {
101    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102        match self.repr {
103            ObjectErrorRepr::Transparent(ref inner) => fmt::Debug::fmt(inner, f),
104            _ => fmt::Debug::fmt(&self.repr, f),
105        }
106    }
107}
108
109impl fmt::Display for ObjectError {
110    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111        match self.repr {
112            ObjectErrorRepr::UnsupportedObject => write!(f, "unsupported object file format"),
113            ObjectErrorRepr::Transparent(ref inner) => fmt::Display::fmt(inner, f),
114        }
115    }
116}
117
118impl Error for ObjectError {
119    fn source(&self) -> Option<&(dyn Error + 'static)> {
120        match self.repr {
121            ObjectErrorRepr::UnsupportedObject => None,
122            ObjectErrorRepr::Transparent(ref inner) => inner.source(),
123        }
124    }
125}
126
127/// Options for parsing object files.
128#[non_exhaustive]
129#[derive(Debug, Clone, Copy, Default)]
130pub struct ParseObjectOptions {
131    /// The maximum uncompressed size for compressed debug file sections.
132    ///
133    /// This is only relevant to ELF objects.
134    pub max_decompressed_section_size: Option<usize>,
135
136    /// The maximum uncompressed size for comprossed source files embedded in an object file.
137    ///
138    /// This is only relevant for source bundles and PPDB objects.
139    pub max_decompressed_embedded_source_size: Option<usize>,
140
141    /// The maximum inline nesting depth to process.
142    ///
143    /// This is only relevant for Breakpad objects (for the time being).
144    pub max_inline_depth: Option<u32>,
145}
146
147/// Tries to infer the object type from the start of the given buffer.
148///
149/// If `archive` is set to `true`, multi architecture objects will be allowed. Otherwise, only
150/// single-arch objects are checked.
151pub fn peek(data: &[u8], archive: bool) -> FileFormat {
152    if data.len() < 16 {
153        return FileFormat::Unknown;
154    }
155
156    if ElfObject::test(data) {
157        FileFormat::Elf
158    } else if PeObject::test(data) {
159        FileFormat::Pe
160    } else if PdbObject::test(data) {
161        FileFormat::Pdb
162    } else if SourceBundle::test(data) {
163        FileFormat::SourceBundle
164    } else if BreakpadObject::test(data) {
165        FileFormat::Breakpad
166    } else if WasmObject::test(data) {
167        FileFormat::Wasm
168    } else if PortablePdbObject::test(data) {
169        FileFormat::PortablePdb
170    } else {
171        let magic = goblin::mach::parse_magic_and_ctx(data, 0).map(|(magic, _)| magic);
172
173        match magic {
174            Ok(goblin::mach::fat::FAT_MAGIC) => {
175                use scroll::Pread;
176                if data.pread_with::<u32>(4, scroll::BE).is_ok()
177                    && archive
178                    && MachArchive::test(data)
179                {
180                    FileFormat::MachO
181                } else {
182                    FileFormat::Unknown
183                }
184            }
185            Ok(
186                goblin::mach::header::MH_CIGAM_64
187                | goblin::mach::header::MH_CIGAM
188                | goblin::mach::header::MH_MAGIC_64
189                | goblin::mach::header::MH_MAGIC,
190            ) => FileFormat::MachO,
191            _ => FileFormat::Unknown,
192        }
193    }
194}
195
196/// A generic object file providing uniform access to various file formats.
197#[allow(clippy::large_enum_variant)]
198#[derive(Debug)]
199pub enum Object<'data> {
200    /// Breakpad ASCII symbol.
201    Breakpad(BreakpadObject<'data>),
202    /// Executable and Linkable Format, used on Linux.
203    Elf(ElfObject<'data>),
204    /// Mach Objects, used on macOS and iOS derivatives.
205    MachO(MachObject<'data>),
206    /// Program Database, the debug companion format on Windows.
207    Pdb(PdbObject<'data>),
208    /// Portable Executable, an extension of COFF used on Windows.
209    Pe(PeObject<'data>),
210    /// A source bundle.
211    SourceBundle(SourceBundle<'data>),
212    /// A WASM file.
213    Wasm(WasmObject<'data>),
214    /// A Portable PDB, a debug companion format for CLR languages.
215    PortablePdb(PortablePdbObject<'data>),
216}
217
218impl<'data> Object<'data> {
219    /// Tests whether the buffer could contain an object.
220    pub fn test(data: &[u8]) -> bool {
221        Self::peek(data) != FileFormat::Unknown
222    }
223
224    /// Tries to infer the object type from the start of the given buffer.
225    pub fn peek(data: &[u8]) -> FileFormat {
226        peek(data, false)
227    }
228
229    /// Tries to parse a supported object from the given slice, with default options.
230    pub fn parse(data: &'data [u8]) -> Result<Self, ObjectError> {
231        Self::parse_with_opts(data, Default::default())
232    }
233
234    /// Tries to parse a supported object from the given slice.
235    pub fn parse_with_opts(
236        data: &'data [u8],
237        opts: ParseObjectOptions,
238    ) -> Result<Self, ObjectError> {
239        macro_rules! parse_object {
240            ($kind:ident, $file:ident, $data:expr) => {
241                Object::$kind($file::parse_with_opts(data, opts).map_err(ObjectError::transparent)?)
242            };
243        }
244
245        let object = match Self::peek(data) {
246            FileFormat::Breakpad => parse_object!(Breakpad, BreakpadObject, data),
247            FileFormat::Elf => parse_object!(Elf, ElfObject, data),
248            FileFormat::MachO => parse_object!(MachO, MachObject, data),
249            FileFormat::Pdb => parse_object!(Pdb, PdbObject, data),
250            FileFormat::Pe => parse_object!(Pe, PeObject, data),
251            FileFormat::SourceBundle => parse_object!(SourceBundle, SourceBundle, data),
252            FileFormat::Wasm => parse_object!(Wasm, WasmObject, data),
253            FileFormat::PortablePdb => parse_object!(PortablePdb, PortablePdbObject, data),
254            FileFormat::Unknown => {
255                return Err(ObjectError::new(ObjectErrorRepr::UnsupportedObject))
256            }
257        };
258
259        Ok(object)
260    }
261
262    /// The container format of this file, corresponding to the variant of this instance.
263    pub fn file_format(&self) -> FileFormat {
264        match *self {
265            Object::Breakpad(_) => FileFormat::Breakpad,
266            Object::Elf(_) => FileFormat::Elf,
267            Object::MachO(_) => FileFormat::MachO,
268            Object::Pdb(_) => FileFormat::Pdb,
269            Object::Pe(_) => FileFormat::Pe,
270            Object::SourceBundle(_) => FileFormat::SourceBundle,
271            Object::Wasm(_) => FileFormat::Wasm,
272            Object::PortablePdb(_) => FileFormat::PortablePdb,
273        }
274    }
275
276    /// The code identifier of this object.
277    ///
278    /// This is a platform-dependent string of variable length that _always_ refers to the code file
279    /// (e.g. executable or library), even if this object is a debug file. See the variants for the
280    /// semantics of this code identifier.
281    pub fn code_id(&self) -> Option<CodeId> {
282        match_inner!(self, Object(ref o) => o.code_id())
283    }
284
285    /// The debug information identifier of this object.
286    ///
287    /// For platforms that use different identifiers for their code and debug files, this _always_
288    /// refers to the debug file, regardless whether this object is a debug file or not.
289    pub fn debug_id(&self) -> DebugId {
290        match_inner!(self, Object(ref o) => o.debug_id())
291    }
292
293    /// The CPU architecture of this object.
294    pub fn arch(&self) -> Arch {
295        match_inner!(self, Object(ref o) => o.arch())
296    }
297
298    /// The kind of this object.
299    pub fn kind(&self) -> ObjectKind {
300        match_inner!(self, Object(ref o) => o.kind())
301    }
302
303    /// The address at which the image prefers to be loaded into memory.
304    pub fn load_address(&self) -> u64 {
305        match_inner!(self, Object(ref o) => o.load_address())
306    }
307
308    /// Determines whether this object exposes a public symbol table.
309    pub fn has_symbols(&self) -> bool {
310        match_inner!(self, Object(ref o) => o.has_symbols())
311    }
312
313    /// Returns an iterator over symbols in the public symbol table.
314    pub fn symbols(&self) -> SymbolIterator<'data, '_> {
315        map_inner!(self, Object(ref o) => SymbolIterator(o.symbols()))
316    }
317
318    /// Returns an ordered map of symbols in the symbol table.
319    pub fn symbol_map(&self) -> SymbolMap<'data> {
320        match_inner!(self, Object(ref o) => o.symbol_map())
321    }
322
323    /// Determines whether this object contains debug information.
324    pub fn has_debug_info(&self) -> bool {
325        match_inner!(self, Object(ref o) => o.has_debug_info())
326    }
327
328    /// Constructs a debugging session.
329    ///
330    /// A debugging session loads certain information from the object file and creates caches for
331    /// efficient access to various records in the debug information. Since this can be quite a
332    /// costly process, try to reuse the debugging session as long as possible.
333    ///
334    /// Objects that do not support debugging or do not contain debugging information return an
335    /// empty debug session. This only returns an error if constructing the debug session fails due
336    /// to invalid debug data in the object.
337    ///
338    /// Constructing this session will also work if the object does not contain debugging
339    /// information, in which case the session will be a no-op. This can be checked via
340    /// [`has_debug_info`](enum.Object.html#method.has_debug_info).
341    pub fn debug_session(&self) -> Result<ObjectDebugSession<'data>, ObjectError> {
342        match *self {
343            Object::Breakpad(ref o) => o
344                .debug_session()
345                .map(ObjectDebugSession::Breakpad)
346                .map_err(ObjectError::transparent),
347            Object::Elf(ref o) => o
348                .debug_session()
349                .map(ObjectDebugSession::Dwarf)
350                .map_err(ObjectError::transparent),
351            Object::MachO(ref o) => o
352                .debug_session()
353                .map(ObjectDebugSession::Dwarf)
354                .map_err(ObjectError::transparent),
355            Object::Pdb(ref o) => o
356                .debug_session()
357                .map(ObjectDebugSession::Pdb)
358                .map_err(ObjectError::transparent),
359            Object::Pe(ref o) => o
360                .debug_session()
361                .map(ObjectDebugSession::Dwarf)
362                .map_err(ObjectError::transparent),
363            Object::SourceBundle(ref o) => o
364                .debug_session()
365                .map(ObjectDebugSession::SourceBundle)
366                .map_err(ObjectError::transparent),
367            Object::Wasm(ref o) => o
368                .debug_session()
369                .map(ObjectDebugSession::Dwarf)
370                .map_err(ObjectError::transparent),
371            Object::PortablePdb(ref o) => o
372                .debug_session()
373                .map(ObjectDebugSession::PortablePdb)
374                .map_err(ObjectError::transparent),
375        }
376    }
377
378    /// Determines whether this object contains stack unwinding information.
379    pub fn has_unwind_info(&self) -> bool {
380        match_inner!(self, Object(ref o) => o.has_unwind_info())
381    }
382
383    /// Determines whether this object contains embedded source
384    pub fn has_sources(&self) -> bool {
385        match_inner!(self, Object(ref o) => o.has_sources())
386    }
387
388    /// Determines whether this object is malformed and was only partially parsed
389    pub fn is_malformed(&self) -> bool {
390        match_inner!(self, Object(ref o) => o.is_malformed())
391    }
392
393    /// Returns the raw data of the underlying buffer.
394    pub fn data(&self) -> &'data [u8] {
395        match_inner!(self, Object(ref o) => o.data())
396    }
397}
398
399impl<'slf, 'data: 'slf> AsSelf<'slf> for Object<'data> {
400    type Ref = Object<'slf>;
401
402    fn as_self(&'slf self) -> &'slf Self::Ref {
403        unsafe { std::mem::transmute(self) }
404    }
405}
406
407impl<'slf, 'data: 'slf> AsSelf<'slf> for ObjectDebugSession<'data> {
408    type Ref = ObjectDebugSession<'slf>;
409
410    fn as_self(&'slf self) -> &'slf Self::Ref {
411        unsafe { std::mem::transmute(self) }
412    }
413}
414
415impl<'data: 'object, 'object> ObjectLike<'data, 'object> for Object<'data> {
416    type Error = ObjectError;
417    type Session = ObjectDebugSession<'data>;
418    type SymbolIterator = SymbolIterator<'data, 'object>;
419
420    fn file_format(&self) -> FileFormat {
421        self.file_format()
422    }
423
424    fn code_id(&self) -> Option<CodeId> {
425        self.code_id()
426    }
427
428    fn debug_id(&self) -> DebugId {
429        self.debug_id()
430    }
431
432    fn arch(&self) -> Arch {
433        self.arch()
434    }
435
436    fn kind(&self) -> ObjectKind {
437        self.kind()
438    }
439
440    fn load_address(&self) -> u64 {
441        self.load_address()
442    }
443
444    fn has_symbols(&self) -> bool {
445        self.has_symbols()
446    }
447
448    fn symbol_map(&self) -> SymbolMap<'data> {
449        self.symbol_map()
450    }
451
452    fn symbols(&'object self) -> Self::SymbolIterator {
453        self.symbols()
454    }
455
456    fn has_debug_info(&self) -> bool {
457        self.has_debug_info()
458    }
459
460    fn debug_session(&self) -> Result<Self::Session, Self::Error> {
461        self.debug_session()
462    }
463
464    fn has_unwind_info(&self) -> bool {
465        self.has_unwind_info()
466    }
467
468    fn has_sources(&self) -> bool {
469        self.has_sources()
470    }
471
472    fn is_malformed(&self) -> bool {
473        self.is_malformed()
474    }
475}
476
477/// A generic debugging session.
478#[allow(clippy::large_enum_variant)]
479#[allow(missing_docs)]
480pub enum ObjectDebugSession<'d> {
481    Breakpad(BreakpadDebugSession<'d>),
482    Dwarf(DwarfDebugSession<'d>),
483    Pdb(PdbDebugSession<'d>),
484    SourceBundle(SourceBundleDebugSession<'d>),
485    PortablePdb(PortablePdbDebugSession<'d>),
486}
487
488impl ObjectDebugSession<'_> {
489    /// Returns an iterator over all functions in this debug file.
490    ///
491    /// Functions are iterated in the order they are declared in their compilation units. The
492    /// functions yielded by this iterator include all inlinees and line records resolved.
493    ///
494    /// Note that the iterator holds a mutable borrow on the debug session, which allows it to use
495    /// caches and optimize resources while resolving function and line information.
496    pub fn functions(&self) -> ObjectFunctionIterator<'_> {
497        match *self {
498            ObjectDebugSession::Breakpad(ref s) => ObjectFunctionIterator::Breakpad(s.functions()),
499            ObjectDebugSession::Dwarf(ref s) => ObjectFunctionIterator::Dwarf(s.functions()),
500            ObjectDebugSession::Pdb(ref s) => ObjectFunctionIterator::Pdb(s.functions()),
501            ObjectDebugSession::SourceBundle(ref s) => {
502                ObjectFunctionIterator::SourceBundle(s.functions())
503            }
504            ObjectDebugSession::PortablePdb(ref s) => {
505                ObjectFunctionIterator::PortablePdb(s.functions())
506            }
507        }
508    }
509
510    /// Returns an iterator over all source files referenced by this debug file.
511    pub fn files(&self) -> ObjectFileIterator<'_> {
512        match *self {
513            ObjectDebugSession::Breakpad(ref s) => ObjectFileIterator::Breakpad(s.files()),
514            ObjectDebugSession::Dwarf(ref s) => ObjectFileIterator::Dwarf(s.files()),
515            ObjectDebugSession::Pdb(ref s) => ObjectFileIterator::Pdb(s.files()),
516            ObjectDebugSession::SourceBundle(ref s) => ObjectFileIterator::SourceBundle(s.files()),
517            ObjectDebugSession::PortablePdb(ref s) => ObjectFileIterator::PortablePdb(s.files()),
518        }
519    }
520
521    /// Looks up a file's source by its full canonicalized path.
522    /// Returns either source contents, if it was embedded, or a source link.
523    pub fn source_by_path(
524        &self,
525        path: &str,
526    ) -> Result<Option<SourceFileDescriptor<'_>>, ObjectError> {
527        match *self {
528            ObjectDebugSession::Breakpad(ref s) => {
529                s.source_by_path(path).map_err(ObjectError::transparent)
530            }
531            ObjectDebugSession::Dwarf(ref s) => {
532                s.source_by_path(path).map_err(ObjectError::transparent)
533            }
534            ObjectDebugSession::Pdb(ref s) => {
535                s.source_by_path(path).map_err(ObjectError::transparent)
536            }
537            ObjectDebugSession::SourceBundle(ref s) => {
538                s.source_by_path(path).map_err(ObjectError::transparent)
539            }
540            ObjectDebugSession::PortablePdb(ref s) => {
541                s.source_by_path(path).map_err(ObjectError::transparent)
542            }
543        }
544    }
545}
546
547impl<'session> DebugSession<'session> for ObjectDebugSession<'_> {
548    type Error = ObjectError;
549    type FunctionIterator = ObjectFunctionIterator<'session>;
550    type FileIterator = ObjectFileIterator<'session>;
551
552    fn functions(&'session self) -> Self::FunctionIterator {
553        self.functions()
554    }
555
556    fn files(&'session self) -> Self::FileIterator {
557        self.files()
558    }
559
560    fn source_by_path(&self, path: &str) -> Result<Option<SourceFileDescriptor<'_>>, Self::Error> {
561        self.source_by_path(path)
562    }
563}
564
565/// An iterator over functions in an [`Object`](enum.Object.html).
566#[allow(missing_docs)]
567pub enum ObjectFunctionIterator<'s> {
568    Breakpad(BreakpadFunctionIterator<'s>),
569    Dwarf(DwarfFunctionIterator<'s>),
570    Pdb(PdbFunctionIterator<'s>),
571    SourceBundle(SourceBundleFunctionIterator<'s>),
572    PortablePdb(PortablePdbFunctionIterator<'s>),
573}
574
575impl<'s> Iterator for ObjectFunctionIterator<'s> {
576    type Item = Result<Function<'s>, ObjectError>;
577
578    fn next(&mut self) -> Option<Self::Item> {
579        match *self {
580            ObjectFunctionIterator::Breakpad(ref mut i) => {
581                Some(i.next()?.map_err(ObjectError::transparent))
582            }
583            ObjectFunctionIterator::Dwarf(ref mut i) => {
584                Some(i.next()?.map_err(ObjectError::transparent))
585            }
586            ObjectFunctionIterator::Pdb(ref mut i) => {
587                Some(i.next()?.map_err(ObjectError::transparent))
588            }
589            ObjectFunctionIterator::SourceBundle(ref mut i) => {
590                Some(i.next()?.map_err(ObjectError::transparent))
591            }
592            ObjectFunctionIterator::PortablePdb(ref mut i) => {
593                Some(i.next()?.map_err(ObjectError::transparent))
594            }
595        }
596    }
597}
598
599/// An iterator over source files in an [`Object`](enum.Object.html).
600#[allow(missing_docs)]
601#[allow(clippy::large_enum_variant)]
602pub enum ObjectFileIterator<'s> {
603    Breakpad(BreakpadFileIterator<'s>),
604    Dwarf(DwarfFileIterator<'s>),
605    Pdb(PdbFileIterator<'s>),
606    SourceBundle(SourceBundleFileIterator<'s>),
607    PortablePdb(PortablePdbFileIterator<'s>),
608}
609
610impl<'s> Iterator for ObjectFileIterator<'s> {
611    type Item = Result<FileEntry<'s>, ObjectError>;
612
613    fn next(&mut self) -> Option<Self::Item> {
614        match *self {
615            ObjectFileIterator::Breakpad(ref mut i) => {
616                Some(i.next()?.map_err(ObjectError::transparent))
617            }
618            ObjectFileIterator::Dwarf(ref mut i) => {
619                Some(i.next()?.map_err(ObjectError::transparent))
620            }
621            ObjectFileIterator::Pdb(ref mut i) => Some(i.next()?.map_err(ObjectError::transparent)),
622            ObjectFileIterator::SourceBundle(ref mut i) => {
623                Some(i.next()?.map_err(ObjectError::transparent))
624            }
625            ObjectFileIterator::PortablePdb(ref mut i) => {
626                Some(i.next()?.map_err(ObjectError::transparent))
627            }
628        }
629    }
630}
631
632/// A generic symbol iterator
633#[allow(missing_docs)]
634pub enum SymbolIterator<'data, 'object> {
635    Breakpad(BreakpadSymbolIterator<'data>),
636    Elf(ElfSymbolIterator<'data, 'object>),
637    MachO(MachOSymbolIterator<'data>),
638    Pdb(PdbSymbolIterator<'data, 'object>),
639    Pe(PeSymbolIterator<'data, 'object>),
640    SourceBundle(SourceBundleSymbolIterator<'data>),
641    Wasm(WasmSymbolIterator<'data, 'object>),
642    PortablePdb(PortablePdbSymbolIterator<'data>),
643}
644
645impl<'data> Iterator for SymbolIterator<'data, '_> {
646    type Item = Symbol<'data>;
647
648    fn next(&mut self) -> Option<Self::Item> {
649        match_inner!(self, SymbolIterator(ref mut iter) => iter.next())
650    }
651}
652
653impl<'data> crate::base::Parse<'data> for PortablePdb<'data> {
654    type Error = symbolic_ppdb::FormatError;
655
656    fn parse_with_opts(data: &'data [u8], _opts: ParseObjectOptions) -> Result<Self, Self::Error> {
657        Self::parse(data)
658    }
659
660    fn test(data: &'data [u8]) -> bool {
661        Self::peek(data)
662    }
663}
664
665#[derive(Debug)]
666enum ArchiveInner<'d> {
667    Breakpad(MonoArchive<'d, BreakpadObject<'d>>),
668    Elf(MonoArchive<'d, ElfObject<'d>>),
669    MachO(MachArchive<'d>),
670    Pdb(MonoArchive<'d, PdbObject<'d>>),
671    Pe(MonoArchive<'d, PeObject<'d>>),
672    SourceBundle(MonoArchive<'d, SourceBundle<'d>>),
673    Wasm(MonoArchive<'d, WasmObject<'d>>),
674    PortablePdb(MonoArchive<'d, PortablePdbObject<'d>>),
675}
676
677/// A generic archive that can contain one or more object files.
678///
679/// Effectively, this will only contain a single object for all file types other than `MachO`. Mach
680/// objects can either be single object files or so-called _fat_ files that contain multiple objects
681/// per architecture.
682#[derive(Debug)]
683pub struct Archive<'d>(ArchiveInner<'d>);
684
685impl<'d> Archive<'d> {
686    /// Tests whether this buffer contains a valid object archive.
687    pub fn test(data: &[u8]) -> bool {
688        Self::peek(data) != FileFormat::Unknown
689    }
690
691    /// Tries to infer the object archive type from the start of the given buffer.
692    pub fn peek(data: &[u8]) -> FileFormat {
693        peek(data, true)
694    }
695
696    /// Tries to parse a generic archive from the given slice, with default options.
697    pub fn parse(data: &'d [u8]) -> Result<Self, ObjectError> {
698        Self::parse_with_opts(data, Default::default())
699    }
700
701    /// Tries to parse a generic archive from the given slice.
702    pub fn parse_with_opts(data: &'d [u8], opts: ParseObjectOptions) -> Result<Self, ObjectError> {
703        let archive = match Self::peek(data) {
704            FileFormat::Breakpad => Archive(ArchiveInner::Breakpad(MonoArchive::new(data, opts))),
705            FileFormat::Elf => Archive(ArchiveInner::Elf(MonoArchive::new(data, opts))),
706            FileFormat::MachO => {
707                let inner = MachArchive::parse_with_opts(data, opts)
708                    .map(ArchiveInner::MachO)
709                    .map_err(ObjectError::transparent)?;
710                Archive(inner)
711            }
712            FileFormat::Pdb => Archive(ArchiveInner::Pdb(MonoArchive::new(data, opts))),
713            FileFormat::Pe => Archive(ArchiveInner::Pe(MonoArchive::new(data, opts))),
714            FileFormat::SourceBundle => {
715                Archive(ArchiveInner::SourceBundle(MonoArchive::new(data, opts)))
716            }
717            FileFormat::Wasm => Archive(ArchiveInner::Wasm(MonoArchive::new(data, opts))),
718            FileFormat::PortablePdb => {
719                Archive(ArchiveInner::PortablePdb(MonoArchive::new(data, opts)))
720            }
721            FileFormat::Unknown => {
722                return Err(ObjectError::new(ObjectErrorRepr::UnsupportedObject))
723            }
724        };
725
726        Ok(archive)
727    }
728
729    /// The container format of this file.
730    pub fn file_format(&self) -> FileFormat {
731        match self.0 {
732            ArchiveInner::Breakpad(_) => FileFormat::Breakpad,
733            ArchiveInner::Elf(_) => FileFormat::Elf,
734            ArchiveInner::MachO(_) => FileFormat::MachO,
735            ArchiveInner::Pdb(_) => FileFormat::Pdb,
736            ArchiveInner::Pe(_) => FileFormat::Pe,
737            ArchiveInner::Wasm(_) => FileFormat::Wasm,
738            ArchiveInner::SourceBundle(_) => FileFormat::SourceBundle,
739            ArchiveInner::PortablePdb(_) => FileFormat::PortablePdb,
740        }
741    }
742
743    /// Returns an iterator over all objects contained in this archive.
744    pub fn objects(&self) -> ObjectIterator<'d, '_> {
745        ObjectIterator(map_inner!(self.0, ArchiveInner(ref a) =>
746            ObjectIteratorInner(a.objects())))
747    }
748
749    /// Returns the number of objects in this archive.
750    pub fn object_count(&self) -> usize {
751        match_inner!(self.0, ArchiveInner(ref a) => a.object_count())
752    }
753
754    /// Resolves the object at the given index.
755    ///
756    /// Returns `Ok(None)` if the index is out of bounds, or `Err` if the object exists but cannot
757    /// be parsed.
758    pub fn object_by_index(&self, index: usize) -> Result<Option<Object<'d>>, ObjectError> {
759        match self.0 {
760            ArchiveInner::Breakpad(ref a) => a
761                .object_by_index(index)
762                .map(|opt| opt.map(Object::Breakpad))
763                .map_err(ObjectError::transparent),
764            ArchiveInner::Elf(ref a) => a
765                .object_by_index(index)
766                .map(|opt| opt.map(Object::Elf))
767                .map_err(ObjectError::transparent),
768            ArchiveInner::MachO(ref a) => a
769                .object_by_index(index)
770                .map(|opt| opt.map(Object::MachO))
771                .map_err(ObjectError::transparent),
772            ArchiveInner::Pdb(ref a) => a
773                .object_by_index(index)
774                .map(|opt| opt.map(Object::Pdb))
775                .map_err(ObjectError::transparent),
776            ArchiveInner::Pe(ref a) => a
777                .object_by_index(index)
778                .map(|opt| opt.map(Object::Pe))
779                .map_err(ObjectError::transparent),
780            ArchiveInner::SourceBundle(ref a) => a
781                .object_by_index(index)
782                .map(|opt| opt.map(Object::SourceBundle))
783                .map_err(ObjectError::transparent),
784            ArchiveInner::Wasm(ref a) => a
785                .object_by_index(index)
786                .map(|opt| opt.map(Object::Wasm))
787                .map_err(ObjectError::transparent),
788            ArchiveInner::PortablePdb(ref a) => a
789                .object_by_index(index)
790                .map(|opt| opt.map(Object::PortablePdb))
791                .map_err(ObjectError::transparent),
792        }
793    }
794
795    /// Returns whether this is a multi-object archive.
796    ///
797    /// This may also return true if there is only a single object inside the archive.
798    pub fn is_multi(&self) -> bool {
799        match_inner!(self.0, ArchiveInner(ref a) => a.is_multi())
800    }
801}
802
803impl<'slf, 'd: 'slf> AsSelf<'slf> for Archive<'d> {
804    type Ref = Archive<'slf>;
805
806    fn as_self(&'slf self) -> &'slf Self::Ref {
807        unsafe { std::mem::transmute(self) }
808    }
809}
810
811#[allow(clippy::large_enum_variant)]
812enum ObjectIteratorInner<'d, 'a> {
813    Breakpad(MonoArchiveObjects<'d, BreakpadObject<'d>>),
814    Elf(MonoArchiveObjects<'d, ElfObject<'d>>),
815    MachO(MachObjectIterator<'d, 'a>),
816    Pdb(MonoArchiveObjects<'d, PdbObject<'d>>),
817    Pe(MonoArchiveObjects<'d, PeObject<'d>>),
818    SourceBundle(MonoArchiveObjects<'d, SourceBundle<'d>>),
819    Wasm(MonoArchiveObjects<'d, WasmObject<'d>>),
820    PortablePdb(MonoArchiveObjects<'d, PortablePdbObject<'d>>),
821}
822
823/// An iterator over [`Object`](enum.Object.html)s in an [`Archive`](struct.Archive.html).
824pub struct ObjectIterator<'d, 'a>(ObjectIteratorInner<'d, 'a>);
825
826impl<'d> Iterator for ObjectIterator<'d, '_> {
827    type Item = Result<Object<'d>, ObjectError>;
828
829    fn next(&mut self) -> Option<Self::Item> {
830        Some(map_result!(
831            self.0,
832            ObjectIteratorInner(ref mut iter) => Object(iter.next()?)
833        ))
834    }
835
836    fn size_hint(&self) -> (usize, Option<usize>) {
837        match_inner!(self.0, ObjectIteratorInner(ref iter) => iter.size_hint())
838    }
839}
840
841impl std::iter::FusedIterator for ObjectIterator<'_, '_> {}
842impl ExactSizeIterator for ObjectIterator<'_, '_> {}
843
844// TODO(ja): Implement IntoIterator for Archive