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