tugger_apple/
macho.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5use {
6    anyhow::Result,
7    goblin::mach::{
8        fat::{FatArch, FAT_MAGIC, SIZEOF_FAT_ARCH, SIZEOF_FAT_HEADER},
9        Mach,
10    },
11    scroll::{IOwrite, Pwrite},
12    std::io::Write,
13    thiserror::Error,
14};
15
16#[derive(Debug, Error)]
17pub enum UniversalMachOError {
18    #[error("I/O error: {0}")]
19    Io(#[from] std::io::Error),
20
21    #[error("mach-o parse error: {0}")]
22    Goblin(#[from] goblin::error::Error),
23
24    #[error("scroll error: {0}")]
25    Scroll(#[from] scroll::Error),
26}
27
28/// Interface for constructing a universal Mach-O binary.
29#[derive(Clone, Default)]
30pub struct UniversalBinaryBuilder {
31    binaries: Vec<Vec<u8>>,
32}
33
34impl UniversalBinaryBuilder {
35    pub fn add_binary(&mut self, data: impl AsRef<[u8]>) -> Result<usize, UniversalMachOError> {
36        let data = data.as_ref();
37
38        match Mach::parse(data)? {
39            Mach::Binary(_) => {
40                self.binaries.push(data.to_vec());
41                Ok(1)
42            }
43            Mach::Fat(multiarch) => {
44                for arch in multiarch.iter_arches() {
45                    let arch = arch?;
46
47                    let data =
48                        &data[arch.offset as usize..arch.offset as usize + arch.size as usize];
49                    self.binaries.push(data.to_vec());
50                }
51
52                Ok(multiarch.narches)
53            }
54        }
55    }
56
57    /// Write a universal Mach-O to the given writer.
58    pub fn write(&self, writer: &mut impl Write) -> Result<(), UniversalMachOError> {
59        create_universal_macho(writer, self.binaries.iter().map(|x| x.as_slice()))
60    }
61}
62
63/// Create a universal mach-o binary from existing mach-o binaries.
64///
65/// The binaries will be parsed as Mach-O.
66///
67/// Because the size of the individual Mach-O binaries must be written into a
68/// header, all content is buffered internally.
69pub fn create_universal_macho<'a>(
70    writer: &mut impl Write,
71    binaries: impl Iterator<Item = &'a [u8]>,
72) -> Result<(), UniversalMachOError> {
73    // Binaries are aligned on page boundaries. x86-64 appears to use
74    // 4k. aarch64 16k. It really doesn't appear to matter unless you want
75    // to minimize binary size, so we always use 16k.
76    const ALIGN_VALUE: u32 = 14;
77    let align: u32 = 2u32.pow(ALIGN_VALUE);
78
79    let mut records = vec![];
80
81    let mut offset: u32 = align;
82
83    for binary in binaries {
84        let macho = goblin::mach::MachO::parse(binary, 0)?;
85
86        // This will be 0 for the 1st binary.
87        let pad_bytes = match offset % align {
88            0 => 0,
89            x => align - x,
90        };
91
92        offset += pad_bytes;
93
94        let arch = FatArch {
95            cputype: macho.header.cputype,
96            cpusubtype: macho.header.cpusubtype,
97            offset,
98            size: binary.len() as u32,
99            align: ALIGN_VALUE,
100        };
101
102        offset += arch.size;
103
104        records.push((arch, pad_bytes as usize, binary));
105    }
106
107    // Fat header is the magic plus the number of records.
108    writer.iowrite_with(FAT_MAGIC, scroll::BE)?;
109    writer.iowrite_with(records.len() as u32, scroll::BE)?;
110
111    for (fat_arch, _, _) in &records {
112        let mut buffer = [0u8; SIZEOF_FAT_ARCH];
113        buffer.pwrite_with(fat_arch, 0, scroll::BE)?;
114        writer.write_all(&buffer)?;
115    }
116
117    // Pad NULL until first mach-o binary.
118    let current_offset = SIZEOF_FAT_HEADER + records.len() * SIZEOF_FAT_ARCH;
119    writer.write_all(&b"\0".repeat(align as usize - current_offset % align as usize))?;
120
121    // This input would be nonsensical. Let's not even support it.
122    assert!(current_offset <= align as usize, "too many mach-o entries");
123
124    for (_, pad_bytes, macho_data) in records {
125        writer.write_all(&b"\0".repeat(pad_bytes))?;
126        writer.write_all(macho_data)?;
127    }
128
129    Ok(())
130}