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