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