Coverage Report

Created: 2026-11-11 06:34

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/goblin-0.10.7/src/mach/mod.rs
Line
Count
Source
1
//! The Mach-o, mostly zero-copy, binary format parser and raw struct definitions
2
use alloc::vec::Vec;
3
use core::fmt;
4
5
use log::debug;
6
7
use scroll::ctx::SizeWith;
8
use scroll::{BE, Pread};
9
10
use crate::{archive, container};
11
use crate::{error, take_hint_bytes};
12
13
pub mod bind_opcodes;
14
pub mod constants;
15
pub mod exports;
16
pub mod fat;
17
pub mod header;
18
pub mod imports;
19
pub mod load_command;
20
pub mod relocation;
21
pub mod segment;
22
pub mod symbols;
23
24
pub use self::constants::cputype;
25
26
/// Returns a big endian magical number
27
0
pub fn peek(bytes: &[u8], offset: usize) -> error::Result<u32> {
28
0
    Ok(bytes.pread_with::<u32>(offset, scroll::BE)?)
29
0
}
30
31
/// Parses a magic number, and an accompanying mach-o binary parsing context, according to the magic number.
32
16.4k
pub fn parse_magic_and_ctx(
33
16.4k
    bytes: &[u8],
34
16.4k
    offset: usize,
35
16.4k
) -> error::Result<(u32, Option<container::Ctx>)> {
36
    use crate::container::Container;
37
    use crate::mach::header::*;
38
16.4k
    let magic = bytes.pread_with::<u32>(offset, BE)?;
39
8.34k
    let ctx = match magic {
40
        MH_CIGAM_64 | MH_CIGAM | MH_MAGIC_64 | MH_MAGIC => {
41
5.35k
            let is_lsb = magic == MH_CIGAM || magic == MH_CIGAM_64;
42
5.35k
            let le = scroll::Endian::from(is_lsb);
43
5.35k
            let container = if magic == MH_MAGIC_64 || magic == MH_CIGAM_64 {
44
3.48k
                Container::Big
45
            } else {
46
1.87k
                Container::Little
47
            };
48
5.35k
            Some(container::Ctx::new(container, le))
49
        }
50
2.98k
        _ => None,
51
    };
52
8.34k
    Ok((magic, ctx))
53
16.4k
}
54
55
/// A cross-platform, zero-copy, endian-aware, 32/64 bit Mach-o binary parser
56
pub struct MachO<'a> {
57
    /// The mach-o header
58
    pub header: header::Header,
59
    /// The load commands tell the kernel and dynamic linker how to use/interpret this binary
60
    pub load_commands: Vec<load_command::LoadCommand>,
61
    /// The load command "segments" - typically the pieces of the binary that are loaded into memory
62
    pub segments: segment::Segments<'a>,
63
    /// The "Nlist" style symbols in this binary - strippable
64
    pub symbols: Option<symbols::Symbols<'a>>,
65
    /// The dylibs this library depends on
66
    pub libs: Vec<&'a str>,
67
    /// The runtime search paths for dylibs this library depends on
68
    pub rpaths: Vec<&'a str>,
69
    /// The entry point (as a virtual memory address), 0 if none
70
    pub entry: u64,
71
    /// Whether `entry` refers to an older `LC_UNIXTHREAD` instead of the newer `LC_MAIN` entrypoint
72
    pub old_style_entry: bool,
73
    /// The name of the dylib, if any
74
    pub name: Option<&'a str>,
75
    /// Are we a little-endian binary?
76
    pub little_endian: bool,
77
    /// Are we a 64-bit binary
78
    pub is_64: bool,
79
    data: &'a [u8],
80
    ctx: container::Ctx,
81
    export_trie: Option<exports::ExportTrie<'a>>,
82
    bind_interpreter: Option<imports::BindInterpreter<'a>>,
83
}
84
85
impl<'a> fmt::Debug for MachO<'a> {
86
0
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
87
0
        fmt.debug_struct("MachO")
88
0
            .field("header", &self.header)
89
0
            .field("load_commands", &self.load_commands)
90
0
            .field("segments", &self.segments)
91
0
            .field("entry", &self.entry)
92
0
            .field("old_style_entry", &self.old_style_entry)
93
0
            .field("libs", &self.libs)
94
0
            .field("name", &self.name)
95
0
            .field("little_endian", &self.little_endian)
96
0
            .field("is_64", &self.is_64)
97
0
            .field("symbols()", &self.symbols().collect::<Vec<_>>())
98
0
            .field("exports()", &self.exports())
99
0
            .field("imports()", &self.imports())
100
0
            .finish()
101
0
    }
102
}
103
104
impl<'a> MachO<'a> {
105
    /// Is this a relocatable object file?
106
0
    pub fn is_object_file(&self) -> bool {
107
0
        self.header.filetype == header::MH_OBJECT
108
0
    }
109
    /// Return an iterator over all the symbols in this binary
110
3.91k
    pub fn symbols(&self) -> symbols::SymbolIterator<'a> {
111
3.91k
        if let Some(ref symbols) = self.symbols {
112
2.60k
            symbols.into_iter()
113
        } else {
114
1.30k
            symbols::SymbolIterator::default()
115
        }
116
3.91k
    }
117
    /// Return a vector of the relocations in this binary
118
0
    pub fn relocations(
119
0
        &self,
120
0
    ) -> error::Result<Vec<(usize, segment::RelocationIterator<'_>, segment::Section)>> {
121
0
        debug!("Iterating relocations");
122
0
        let mut relocs = Vec::new();
123
0
        for (_i, segment) in (&self.segments).into_iter().enumerate() {
124
0
            for (j, section) in segment.into_iter().enumerate() {
125
0
                let (section, _data) = section?;
126
0
                if section.nreloc > 0 {
127
0
                    relocs.push((j, section.iter_relocations(self.data, self.ctx), section));
128
0
                }
129
            }
130
        }
131
0
        Ok(relocs)
132
0
    }
133
    /// Return the exported symbols in this binary (if any)
134
0
    pub fn exports(&self) -> error::Result<Vec<exports::Export<'_>>> {
135
0
        if let Some(ref trie) = self.export_trie {
136
0
            trie.exports(self.libs.as_slice())
137
        } else {
138
0
            Ok(vec![])
139
        }
140
0
    }
141
    /// Return the imported symbols in this binary that dyld knows about (if any)
142
0
    pub fn imports(&self) -> error::Result<Vec<imports::Import<'_>>> {
143
0
        if let Some(ref interpreter) = self.bind_interpreter {
144
0
            interpreter.imports(self.libs.as_slice(), self.segments.as_slice(), self.ctx)
145
        } else {
146
0
            Ok(vec![])
147
        }
148
0
    }
149
    /// Parses the Mach-o binary from `bytes` at `offset`
150
12.9k
    pub fn parse(bytes: &'a [u8], offset: usize) -> error::Result<MachO<'a>> {
151
12.9k
        Self::parse_impl(bytes, offset, false)
152
12.9k
    }
153
154
    /// Parses the Mach-o binary from `bytes` at `offset` in lossy mode
155
0
    pub fn parse_lossy(bytes: &'a [u8], offset: usize) -> error::Result<MachO<'a>> {
156
0
        Self::parse_impl(bytes, offset, true)
157
0
    }
158
159
    /// Parses the Mach-o binary from `bytes` at `offset` in `lossy` mode
160
12.9k
    fn parse_impl(bytes: &'a [u8], mut offset: usize, lossy: bool) -> error::Result<MachO<'a>> {
161
12.9k
        let (magic, maybe_ctx) = parse_magic_and_ctx(bytes, offset)?;
162
4.83k
        let ctx = if let Some(ctx) = maybe_ctx {
163
2.84k
            ctx
164
        } else {
165
1.98k
            return Err(error::Error::BadMagic(u64::from(magic)));
166
        };
167
2.84k
        debug!("Ctx: {:?}", ctx);
168
2.84k
        let offset = &mut offset;
169
2.84k
        let header: header::Header = bytes.pread_with(*offset, ctx)?;
170
2.81k
        debug!("Mach-o header: {:?}", header);
171
2.81k
        let little_endian = ctx.le.is_little();
172
2.81k
        let is_64 = ctx.container.is_big();
173
2.81k
        *offset += header::Header::size_with(&ctx.container);
174
2.81k
        let ncmds = header.ncmds;
175
176
2.81k
        let sizeofcmds = header.sizeofcmds as usize;
177
        // a load cmd is at least 2 * 4 bytes, (type, sizeof)
178
2.81k
        if ncmds > sizeofcmds / 8 || sizeofcmds > bytes.len() {
179
113
            return Err(error::Error::BufferTooShort(ncmds, "load commands"));
180
2.70k
        }
181
182
2.70k
        let mut cmds: Vec<load_command::LoadCommand> = Vec::with_capacity(ncmds);
183
2.70k
        let mut symbols = None;
184
2.70k
        let mut libs = vec!["self"];
185
2.70k
        let mut rpaths = vec![];
186
2.70k
        let mut export_trie = None;
187
2.70k
        let mut bind_interpreter = None;
188
2.70k
        let mut unixthread_entry_address = None;
189
2.70k
        let mut main_entry_offset = None;
190
2.70k
        let mut name = None;
191
2.70k
        let mut segments = segment::Segments::new(ctx);
192
24.1k
        for i in 0..ncmds {
193
24.1k
            let cmd = load_command::LoadCommand::parse(bytes, offset, ctx.le)?;
194
23.7k
            debug!("{} - {:?}", i, cmd);
195
23.7k
            match cmd.command {
196
424
                load_command::CommandVariant::Segment32(command) => segments.push(
197
424
                    segment::Segment::from_32_impl(bytes, &command, cmd.offset, ctx, lossy)?,
198
                ),
199
4.84k
                load_command::CommandVariant::Segment64(command) => segments.push(
200
4.84k
                    segment::Segment::from_64_impl(bytes, &command, cmd.offset, ctx, lossy)?,
201
                ),
202
1.78k
                load_command::CommandVariant::Symtab(command) => {
203
1.78k
                    match symbols::Symbols::parse(bytes, &command, ctx) {
204
1.73k
                        Ok(s) => symbols = Some(s),
205
0
                        Err(e) if lossy => {
206
0
                            debug!("CommandVariant::Symtab failed: {e}");
207
                        }
208
49
                        Err(e) => return Err(e),
209
                    }
210
                }
211
65
                load_command::CommandVariant::LoadDylib(command)
212
0
                | load_command::CommandVariant::LoadUpwardDylib(command)
213
0
                | load_command::CommandVariant::ReexportDylib(command)
214
0
                | load_command::CommandVariant::LoadWeakDylib(command)
215
54
                | load_command::CommandVariant::LazyLoadDylib(command) => {
216
119
                    match bytes.pread::<&str>(cmd.offset + command.dylib.name as usize) {
217
101
                        Ok(lib) => libs.push(lib),
218
0
                        Err(e) if lossy => {
219
0
                            debug!("CommandVariant::Load/Reexport Dylib failed: {e}");
220
                        }
221
18
                        Err(e) => return Err(e.into()),
222
                    }
223
                }
224
0
                load_command::CommandVariant::Rpath(command) => {
225
0
                    match bytes.pread::<&str>(cmd.offset + command.path as usize) {
226
0
                        Ok(rpath) => rpaths.push(rpath),
227
0
                        Err(e) if lossy => {
228
0
                            debug!("CommandVariant::Rpath failed: {e}");
229
                        }
230
0
                        Err(e) => return Err(e.into()),
231
                    }
232
                }
233
77
                load_command::CommandVariant::DyldInfo(command)
234
94
                | load_command::CommandVariant::DyldInfoOnly(command) => {
235
94
                    export_trie = Some(exports::ExportTrie::new(bytes, &command));
236
94
                    bind_interpreter = Some(imports::BindInterpreter::new(bytes, &command));
237
94
                }
238
3
                load_command::CommandVariant::DyldExportsTrie(command) => {
239
3
                    export_trie = Some(exports::ExportTrie::new_from_linkedit_data_command(
240
3
                        bytes, &command,
241
3
                    ));
242
3
                }
243
24
                load_command::CommandVariant::Unixthread(command) => {
244
                    // dyld cares only about the first LC_UNIXTHREAD
245
24
                    if unixthread_entry_address.is_none() {
246
                        unixthread_entry_address =
247
24
                            Some(command.instruction_pointer(header.cputype)?);
248
0
                    }
249
                }
250
17
                load_command::CommandVariant::Main(command) => {
251
                    // dyld cares only about the first LC_MAIN
252
17
                    if main_entry_offset.is_none() {
253
17
                        main_entry_offset = Some(command.entryoff);
254
17
                    }
255
                }
256
32
                load_command::CommandVariant::IdDylib(command) => {
257
32
                    match bytes.pread::<&str>(cmd.offset + command.dylib.name as usize) {
258
26
                        Ok(id) => {
259
26
                            libs[0] = id;
260
26
                            name = Some(id);
261
26
                        }
262
0
                        Err(e) if lossy => {
263
0
                            debug!("CommandVariant::IdDylib failed: {e}");
264
                        }
265
6
                        Err(e) => return Err(e.into()),
266
                    }
267
                }
268
16.4k
                _ => (),
269
            }
270
23.4k
            cmds.push(cmd)
271
        }
272
273
        // dyld prefers LC_MAIN over LC_UNIXTHREAD
274
        // choose the same way here
275
1.95k
        let (entry, old_style_entry) = if let Some(offset) = main_entry_offset {
276
            // map the entrypoint offset to a virtual memory address
277
17
            let base_address = segments
278
17
                .iter()
279
34
                .filter(|s| &s.segname[0..7] == b"__TEXT\0")
280
17
                .map(|s| s.vmaddr - s.fileoff)
281
17
                .next()
282
17
                .ok_or_else(|| {
283
0
                    error::Error::Malformed(format!(
284
0
                        "image specifies LC_MAIN offset {} but has no __TEXT segment",
285
0
                        offset
286
0
                    ))
287
0
                })?;
288
289
17
            (base_address + offset, false)
290
1.93k
        } else if let Some(address) = unixthread_entry_address {
291
0
            (address, true)
292
        } else {
293
1.93k
            (0, false)
294
        };
295
296
1.95k
        Ok(MachO {
297
1.95k
            header,
298
1.95k
            load_commands: cmds,
299
1.95k
            segments,
300
1.95k
            symbols,
301
1.95k
            libs,
302
1.95k
            rpaths,
303
1.95k
            export_trie,
304
1.95k
            bind_interpreter,
305
1.95k
            entry,
306
1.95k
            old_style_entry,
307
1.95k
            name,
308
1.95k
            ctx,
309
1.95k
            is_64,
310
1.95k
            little_endian,
311
1.95k
            data: bytes,
312
1.95k
        })
313
12.9k
    }
314
}
315
316
/// A Mach-o multi architecture (Fat) binary container
317
pub struct MultiArch<'a> {
318
    data: &'a [u8],
319
    start: usize,
320
    pub narches: usize,
321
}
322
323
/// Iterator over the fat architecture headers in a `MultiArch` container
324
pub struct FatArchIterator<'a> {
325
    index: usize,
326
    data: &'a [u8],
327
    narches: usize,
328
    start: usize,
329
}
330
331
/// A single architecture froma multi architecture binary container
332
/// ([MultiArch]).
333
#[derive(Debug)]
334
#[allow(clippy::large_enum_variant)]
335
pub enum SingleArch<'a> {
336
    MachO(MachO<'a>),
337
    Archive(archive::Archive<'a>),
338
}
339
340
impl<'a> Iterator for FatArchIterator<'a> {
341
    type Item = error::Result<fat::FatArch>;
342
121k
    fn next(&mut self) -> Option<Self::Item> {
343
121k
        if self.index >= self.narches {
344
0
            None
345
        } else {
346
121k
            let offset = (self.index * fat::SIZEOF_FAT_ARCH) + self.start;
347
121k
            let arch = self
348
121k
                .data
349
121k
                .pread_with::<fat::FatArch>(offset, scroll::BE)
350
121k
                .map_err(core::convert::Into::into);
351
121k
            self.index += 1;
352
121k
            Some(arch)
353
        }
354
121k
    }
355
}
356
357
/// Iterator over every entry contained in this `MultiArch` container
358
pub struct SingleArchIterator<'a> {
359
    index: usize,
360
    data: &'a [u8],
361
    narches: usize,
362
    start: usize,
363
}
364
365
0
pub fn peek_bytes(bytes: &[u8; 16]) -> error::Result<crate::Hint> {
366
0
    if &bytes[0..archive::SIZEOF_MAGIC] == archive::MAGIC {
367
0
        Ok(crate::Hint::Archive)
368
    } else {
369
0
        let (magic, maybe_ctx) = parse_magic_and_ctx(bytes, 0)?;
370
0
        match magic {
371
            header::MH_CIGAM_64 | header::MH_CIGAM | header::MH_MAGIC_64 | header::MH_MAGIC => {
372
0
                if let Some(ctx) = maybe_ctx {
373
0
                    Ok(crate::Hint::Mach(crate::HintData {
374
0
                        is_lsb: ctx.le.is_little(),
375
0
                        is_64: Some(ctx.container.is_big()),
376
0
                    }))
377
                } else {
378
0
                    Err(error::Error::Malformed(format!(
379
0
                        "Correct mach magic {:#x} does not have a matching parsing context!",
380
0
                        magic
381
0
                    )))
382
                }
383
            }
384
            fat::FAT_MAGIC => {
385
                // should probably verify this is always Big Endian...
386
0
                let narchitectures = bytes.pread_with::<u32>(4, BE)? as usize;
387
0
                Ok(crate::Hint::MachFat(narchitectures))
388
            }
389
0
            _ => Ok(crate::Hint::Unknown(bytes.pread::<u64>(0)?)),
390
        }
391
    }
392
0
}
393
394
0
fn extract_multi_entry(bytes: &[u8]) -> error::Result<SingleArch<'_>> {
395
0
    if let Some(hint_bytes) = take_hint_bytes(bytes) {
396
0
        match peek_bytes(hint_bytes)? {
397
            crate::Hint::Mach(_) => {
398
0
                let binary = MachO::parse(bytes, 0)?;
399
0
                Ok(SingleArch::MachO(binary))
400
            }
401
            crate::Hint::Archive => {
402
0
                let archive = archive::Archive::parse(bytes)?;
403
0
                Ok(SingleArch::Archive(archive))
404
            }
405
0
            _ => Err(error::Error::Malformed(format!(
406
0
                "multi-arch entry must be a Mach-O binary or an archive"
407
0
            ))),
408
        }
409
    } else {
410
0
        Err(error::Error::Malformed(format!("Object is too small")))
411
    }
412
0
}
413
414
impl<'a> Iterator for SingleArchIterator<'a> {
415
    type Item = error::Result<SingleArch<'a>>;
416
0
    fn next(&mut self) -> Option<Self::Item> {
417
0
        if self.index >= self.narches {
418
0
            None
419
        } else {
420
0
            let index = self.index;
421
0
            let offset = (index * fat::SIZEOF_FAT_ARCH) + self.start;
422
0
            self.index += 1;
423
0
            match self.data.pread_with::<fat::FatArch>(offset, scroll::BE) {
424
0
                Ok(arch) => {
425
0
                    let bytes = arch.slice(self.data);
426
0
                    Some(extract_multi_entry(bytes))
427
                }
428
0
                Err(e) => Some(Err(e.into())),
429
            }
430
        }
431
0
    }
432
}
433
434
impl<'a, 'b> IntoIterator for &'b MultiArch<'a> {
435
    type Item = error::Result<SingleArch<'a>>;
436
    type IntoIter = SingleArchIterator<'a>;
437
0
    fn into_iter(self) -> Self::IntoIter {
438
0
        SingleArchIterator {
439
0
            index: 0,
440
0
            data: self.data,
441
0
            narches: self.narches,
442
0
            start: self.start,
443
0
        }
444
0
    }
445
}
446
447
impl<'a> MultiArch<'a> {
448
    /// Lazily construct `Self`
449
242
    pub fn new(bytes: &'a [u8]) -> error::Result<Self> {
450
242
        let header = fat::FatHeader::parse(bytes)?;
451
242
        Ok(MultiArch {
452
242
            data: bytes,
453
242
            start: fat::SIZEOF_FAT_HEADER,
454
242
            narches: header.nfat_arch as usize,
455
242
        })
456
242
    }
457
    /// Iterate every fat arch header
458
6.48k
    pub fn iter_arches(&self) -> FatArchIterator<'_> {
459
6.48k
        FatArchIterator {
460
6.48k
            index: 0,
461
6.48k
            data: self.data,
462
6.48k
            narches: self.narches,
463
6.48k
            start: self.start,
464
6.48k
        }
465
6.48k
    }
466
    /// Return all the architectures in this binary
467
0
    pub fn arches(&self) -> error::Result<Vec<fat::FatArch>> {
468
0
        if self.narches > self.data.len() / fat::SIZEOF_FAT_ARCH {
469
0
            return Err(error::Error::BufferTooShort(self.narches, "arches"));
470
0
        }
471
472
0
        let mut arches = Vec::with_capacity(self.narches);
473
0
        for arch in self.iter_arches() {
474
0
            arches.push(arch?);
475
        }
476
0
        Ok(arches)
477
0
    }
478
    /// Try to get the Mach-o binary at `index`
479
0
    pub fn get(&self, index: usize) -> error::Result<SingleArch<'a>> {
480
0
        if index >= self.narches {
481
0
            return Err(error::Error::Malformed(format!(
482
0
                "Requested the {}-th binary, but there are only {} architectures in this container",
483
0
                index, self.narches
484
0
            )));
485
0
        }
486
0
        let offset = (index * fat::SIZEOF_FAT_ARCH) + self.start;
487
0
        let arch = self.data.pread_with::<fat::FatArch>(offset, scroll::BE)?;
488
0
        let bytes = arch.slice(self.data);
489
0
        extract_multi_entry(bytes)
490
0
    }
491
492
0
    pub fn find<F: Fn(error::Result<fat::FatArch>) -> bool>(
493
0
        &'a self,
494
0
        f: F,
495
0
    ) -> Option<error::Result<SingleArch<'a>>> {
496
0
        for (i, arch) in self.iter_arches().enumerate() {
497
0
            if f(arch) {
498
0
                return Some(self.get(i));
499
0
            }
500
        }
501
0
        None
502
0
    }
503
    /// Try and find the `cputype` in `Self`, if there is one
504
0
    pub fn find_cputype(&self, cputype: u32) -> error::Result<Option<fat::FatArch>> {
505
0
        for arch in self.iter_arches() {
506
0
            let arch = arch?;
507
0
            if arch.cputype == cputype {
508
0
                return Ok(Some(arch));
509
0
            }
510
        }
511
0
        Ok(None)
512
0
    }
513
}
514
515
impl<'a> fmt::Debug for MultiArch<'a> {
516
0
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
517
0
        fmt.debug_struct("MultiArch")
518
0
            .field("arches", &self.arches().unwrap_or_default())
519
0
            .field("data", &self.data.len())
520
0
            .finish()
521
0
    }
522
}
523
524
#[derive(Debug)]
525
#[allow(clippy::large_enum_variant)]
526
/// Either a collection of multiple architectures, or a single mach-o binary
527
pub enum Mach<'a> {
528
    /// A "fat" multi-architecture binary container
529
    Fat(MultiArch<'a>),
530
    /// A regular Mach-o binary
531
    Binary(MachO<'a>),
532
}
533
534
impl<'a> Mach<'a> {
535
    /// Parse from `bytes` either a multi-arch binary or a regular mach-o binary
536
0
    pub fn parse(bytes: &'a [u8]) -> error::Result<Self> {
537
0
        Self::parse_impl(bytes, false)
538
0
    }
539
540
    /// Parse from `bytes` either a multi-arch binary or a regular mach-o binary in lossy mode
541
0
    pub fn parse_lossy(bytes: &'a [u8]) -> error::Result<Self> {
542
0
        Self::parse_impl(bytes, true)
543
0
    }
544
545
    /// Parse from `bytes` either a multi-arch binary or a regular mach-o binary
546
0
    fn parse_impl(bytes: &'a [u8], lossy: bool) -> error::Result<Self> {
547
0
        let size = bytes.len();
548
0
        if size < 4 {
549
0
            let error = error::Error::Malformed("size is smaller than a magical number".into());
550
0
            return Err(error);
551
0
        }
552
0
        let magic = peek(&bytes, 0)?;
553
0
        match magic {
554
            fat::FAT_MAGIC => {
555
0
                let multi = MultiArch::new(bytes)?;
556
0
                Ok(Mach::Fat(multi))
557
            }
558
            // we might be a regular binary
559
            _ => {
560
0
                let binary = MachO::parse_impl(bytes, 0, lossy)?;
561
0
                Ok(Mach::Binary(binary))
562
            }
563
        }
564
0
    }
565
}
566
567
#[cfg(test)]
568
mod test {
569
    use super::{Mach, SingleArch};
570
571
    #[test]
572
    fn parse_multi_arch_of_macho_binaries() {
573
        // Create via:
574
        // clang -arch arm64 -shared -o /tmp/hello_world_arm hello_world.c
575
        // clang -arch x86_64 -shared -o /tmp/hello_world_x86_64 hello_world.c
576
        // lipo -create -output hello_world_fat_binaries /tmp/hello_world_arm /tmp/hello_world_x86_64
577
        // strip hello_world_fat_binaries
578
        let bytes = include_bytes!(concat!(
579
            env!("CARGO_MANIFEST_DIR"),
580
            "/assets/hello_world_fat_binaries"
581
        ));
582
        let mach = Mach::parse(bytes).expect("failed to parse input file");
583
        match mach {
584
            Mach::Fat(fat) => {
585
                assert!(fat.into_iter().count() > 0);
586
                for entry in fat.into_iter() {
587
                    let entry = entry.expect("failed to read entry");
588
                    match entry {
589
                        SingleArch::MachO(macho) => {
590
                            assert!(macho.symbols().count() > 0);
591
                        }
592
                        _ => panic!("expected MultiArchEntry::MachO, got {:?}", entry),
593
                    }
594
                }
595
            }
596
            Mach::Binary(_) => panic!("expected Mach::Fat, got Mach::Binary"),
597
        }
598
    }
599
600
    #[test]
601
    fn parse_multi_arch_of_archives() {
602
        // Created with:
603
        // clang -c -o /tmp/hello_world.o hello_world.c
604
        // ar -r /tmp/hello_world.a /tmp/hello_world.o
605
        // lipo -create -output hello_world_fat_archives /tmp/hello_world.a
606
        // strip hello_world_fat_archives
607
        let bytes = include_bytes!(concat!(
608
            env!("CARGO_MANIFEST_DIR"),
609
            "/assets/hello_world_fat_archives"
610
        ));
611
        let mach = Mach::parse(bytes).expect("failed to parse input file");
612
        match mach {
613
            Mach::Fat(fat) => {
614
                assert!(fat.into_iter().count() > 0);
615
                for entry in fat.into_iter() {
616
                    let entry = entry.expect("failed to read entry");
617
                    match entry {
618
                        SingleArch::Archive(archive) => {
619
                            assert!(!archive.members().is_empty())
620
                        }
621
                        _ => panic!("expected MultiArchEntry::Archive, got {:?}", entry),
622
                    }
623
                }
624
            }
625
            Mach::Binary(_) => panic!("expected Mach::Fat, got Mach::Binary"),
626
        }
627
    }
628
}