fat_macho/
write.rs

1// Ported from https://github.com/randall77/makefat/blob/master/makefat.go
2#[cfg(unix)]
3use std::os::unix::fs::PermissionsExt;
4use std::{
5    cmp::Ordering,
6    fs::File,
7    io::{self, BufWriter, Write},
8    path::Path,
9};
10
11#[cfg(feature = "bitcode")]
12use goblin::mach::cputype::{
13    CPU_SUBTYPE_ARM64_32_ALL, CPU_SUBTYPE_ARM64_ALL, CPU_SUBTYPE_ARM64_E, CPU_SUBTYPE_ARM_V4T,
14    CPU_SUBTYPE_ARM_V5TEJ, CPU_SUBTYPE_ARM_V6, CPU_SUBTYPE_ARM_V6M, CPU_SUBTYPE_ARM_V7,
15    CPU_SUBTYPE_ARM_V7EM, CPU_SUBTYPE_ARM_V7F, CPU_SUBTYPE_ARM_V7K, CPU_SUBTYPE_ARM_V7M,
16    CPU_SUBTYPE_ARM_V7S, CPU_SUBTYPE_I386_ALL, CPU_SUBTYPE_POWERPC_ALL, CPU_SUBTYPE_X86_64_ALL,
17    CPU_SUBTYPE_X86_64_H,
18};
19use goblin::{
20    archive::Archive,
21    mach::{
22        cputype::{
23            get_arch_from_flag, get_arch_name_from_types, CpuSubType, CpuType, CPU_ARCH_ABI64,
24            CPU_TYPE_ARM, CPU_TYPE_ARM64, CPU_TYPE_ARM64_32, CPU_TYPE_HPPA, CPU_TYPE_I386,
25            CPU_TYPE_I860, CPU_TYPE_MC680X0, CPU_TYPE_MC88000, CPU_TYPE_POWERPC,
26            CPU_TYPE_POWERPC64, CPU_TYPE_SPARC, CPU_TYPE_X86_64,
27        },
28        fat::{FAT_MAGIC, SIZEOF_FAT_ARCH, SIZEOF_FAT_HEADER},
29        Mach,
30    },
31    Object,
32};
33#[cfg(feature = "bitcode")]
34use llvm_bitcode::{bitcode::BitcodeElement, Bitcode};
35
36use crate::error::Error;
37
38const FAT_MAGIC_64: u32 = FAT_MAGIC + 1;
39const SIZEOF_FAT_ARCH_64: usize = 32;
40
41const LLVM_BITCODE_WRAPPER_MAGIC: u32 = 0x0B17C0DE;
42
43#[derive(Debug)]
44struct ThinArch {
45    data: Vec<u8>,
46    cpu_type: u32,
47    cpu_subtype: u32,
48    align: i64,
49}
50
51/// Mach-O fat binary writer
52#[derive(Debug)]
53pub struct FatWriter {
54    arches: Vec<ThinArch>,
55    max_align: i64,
56    is_fat64: bool,
57}
58
59#[inline]
60fn unpack_u32(buf: &[u8]) -> io::Result<u32> {
61    if buf.len() < 4 {
62        return Err(io::Error::new(
63            io::ErrorKind::UnexpectedEof,
64            "not enough data for unpacking u32",
65        ));
66    }
67    Ok(u32::from_le_bytes([buf[0], buf[1], buf[2], buf[3]]))
68}
69
70impl FatWriter {
71    /// Create a new Mach-O fat binary writer
72    pub fn new() -> Self {
73        Self {
74            arches: Vec::new(),
75            max_align: 0,
76            is_fat64: false,
77        }
78    }
79
80    /// Add a new thin Mach-O binary
81    pub fn add<T: Into<Vec<u8>>>(&mut self, bytes: T) -> Result<(), Error> {
82        let bytes = bytes.into();
83        match Object::parse(&bytes)? {
84            Object::Mach(mach) => match mach {
85                Mach::Fat(fat) => {
86                    for arch in fat.arches()? {
87                        let buffer = arch.slice(&bytes);
88                        self.add(buffer.to_vec())?;
89                    }
90                }
91                Mach::Binary(obj) => {
92                    let header = obj.header;
93                    let cpu_type = header.cputype;
94                    let cpu_subtype = header.cpusubtype;
95                    // Check if this architecture already exists
96                    if self
97                        .arches
98                        .iter()
99                        .find(|arch| arch.cpu_type == cpu_type && arch.cpu_subtype == cpu_subtype)
100                        .is_some()
101                    {
102                        let arch =
103                            get_arch_name_from_types(cpu_type, cpu_subtype).unwrap_or("unknown");
104                        return Err(Error::DuplicatedArch(arch.to_string()));
105                    }
106                    if header.magic == FAT_MAGIC_64 {
107                        self.is_fat64 = true;
108                    }
109                    let align = get_align_from_cpu_types(cpu_type, cpu_subtype);
110                    if align > self.max_align {
111                        self.max_align = align;
112                    }
113                    let thin = ThinArch {
114                        data: bytes,
115                        cpu_type,
116                        cpu_subtype,
117                        align,
118                    };
119                    self.arches.push(thin);
120                }
121            },
122            Object::Archive(ar) => {
123                let (cpu_type, cpu_subtype) = self.check_archive(&bytes, &ar)?;
124                let align = if cpu_type & CPU_ARCH_ABI64 != 0 {
125                    8 /* alignof(u64) */
126                } else {
127                    4 /* alignof(u32) */
128                };
129                if align > self.max_align {
130                    self.max_align = align;
131                }
132                let thin = ThinArch {
133                    data: bytes,
134                    cpu_type,
135                    cpu_subtype,
136                    align,
137                };
138                self.arches.push(thin);
139            }
140            Object::Unknown(_) => {
141                let magic = unpack_u32(&bytes)?;
142                if magic == LLVM_BITCODE_WRAPPER_MAGIC {
143                    #[cfg(feature = "bitcode")]
144                    {
145                        let (cpu_type, cpu_subtype) = self.get_arch_from_bitcode(&bytes)?;
146                        let align = 1;
147                        if align > self.max_align {
148                            self.max_align = align;
149                        }
150                        let thin = ThinArch {
151                            data: bytes,
152                            cpu_type,
153                            cpu_subtype,
154                            align,
155                        };
156                        self.arches.push(thin);
157                    }
158
159                    #[cfg(not(feature = "bitcode"))]
160                    return Err(Error::InvalidMachO(
161                        "bitcode input is unsupported".to_string(),
162                    ));
163                } else {
164                    return Err(Error::InvalidMachO("input is not a macho file".to_string()));
165                }
166            }
167            _ => return Err(Error::InvalidMachO("input is not a macho file".to_string())),
168        }
169        // Sort the files by alignment to save space in ouput
170        self.arches.sort_by(|a, b| {
171            if a.cpu_type == b.cpu_type {
172                // if cpu types match, sort by cpu subtype
173                return a.cpu_subtype.cmp(&b.cpu_subtype);
174            }
175            // force arm64-family to follow after all other slices
176            if a.cpu_type == CPU_TYPE_ARM64 {
177                return Ordering::Greater;
178            }
179            if b.cpu_type == CPU_TYPE_ARM64 {
180                return Ordering::Less;
181            }
182            a.align.cmp(&b.align)
183        });
184        Ok(())
185    }
186
187    #[cfg(feature = "bitcode")]
188    fn get_arch_from_bitcode(&self, buffer: &[u8]) -> Result<(CpuType, CpuSubType), Error> {
189        let bitcode = Bitcode::new(buffer)?;
190        let target_triple = bitcode
191            .elements
192            .iter()
193            .find(|ele| match ele {
194                BitcodeElement::Record(_) => false,
195                BitcodeElement::Block(block) => block.id == 8,
196            })
197            .and_then(|module_block| {
198                module_block
199                    .as_block()
200                    .unwrap()
201                    .elements
202                    .iter()
203                    .find(|ele| match ele {
204                        BitcodeElement::Record(record) => record.id == 2,
205                        BitcodeElement::Block(_) => false,
206                    })
207            })
208            .and_then(|target_triple_record| {
209                let record = target_triple_record.as_record().unwrap();
210                let fields: Vec<u8> = record.fields.iter().map(|x| *x as u8).collect();
211                String::from_utf8(fields).ok()
212            });
213        if let Some(triple) = target_triple {
214            if let Some(triple) = triple.splitn(2, "-").next() {
215                return Ok(match triple {
216                    "i686" | "i386" => (CPU_TYPE_I386, CPU_SUBTYPE_I386_ALL),
217                    "x86_64" => (CPU_TYPE_X86_64, CPU_SUBTYPE_X86_64_ALL),
218                    "x86_64h" => (CPU_TYPE_X86_64, CPU_SUBTYPE_X86_64_H),
219                    "powerpc" => (CPU_TYPE_POWERPC, CPU_SUBTYPE_POWERPC_ALL),
220                    "powerpc64" => (CPU_TYPE_POWERPC64, CPU_SUBTYPE_POWERPC_ALL),
221                    "arm" => (CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V4T),
222                    "armv5" | "armv5e" | "thumbv5" | "thumbv5e" => {
223                        (CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V5TEJ)
224                    }
225                    "armv6" | "thumbv6" => (CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V6),
226                    "armv6m" | "thumbv6m" => (CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V6M),
227                    "armv7" | "thumbv7" => (CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V7),
228                    "armv7f" | "thumbv7f" => (CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V7F),
229                    "armv7s" | "thumbv7s" => (CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V7S),
230                    "armv7k" | "thumbv7k" => (CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V7K),
231                    "armv7m" | "thumbv7m" => (CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V7M),
232                    "armv7em" | "thumbv7em" => (CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V7EM),
233                    "arm64" => (CPU_TYPE_ARM64, CPU_SUBTYPE_ARM64_ALL),
234                    "arm64e" => (CPU_TYPE_ARM64, CPU_SUBTYPE_ARM64_E),
235                    "arm64_32" => (CPU_TYPE_ARM64_32, CPU_SUBTYPE_ARM64_32_ALL),
236                    _ => return Err(Error::InvalidMachO("input is not a macho file".to_string())),
237                });
238            }
239        }
240        Err(Error::InvalidMachO("input is not a macho file".to_string()))
241    }
242
243    fn check_archive(&self, buffer: &[u8], ar: &Archive) -> Result<(u32, u32), Error> {
244        for member in ar.members() {
245            let bytes = ar.extract(member, buffer)?;
246            match Object::parse(bytes)? {
247                Object::Mach(mach) => match mach {
248                    Mach::Binary(obj) => {
249                        return Ok((obj.header.cputype, obj.header.cpusubtype));
250                    }
251                    Mach::Fat(_) => {}
252                },
253                _ => {}
254            }
255        }
256        Err(Error::InvalidMachO(
257            "No Mach-O objects found in archivec".to_string(),
258        ))
259    }
260
261    /// Remove an architecture
262    pub fn remove(&mut self, arch: &str) -> Option<Vec<u8>> {
263        if let Some((cpu_type, cpu_subtype)) = get_arch_from_flag(arch) {
264            if let Some(index) = self
265                .arches
266                .iter()
267                .position(|arch| arch.cpu_type == cpu_type && arch.cpu_subtype == cpu_subtype)
268            {
269                return Some(self.arches.remove(index).data);
270            }
271        }
272        None
273    }
274
275    /// Check whether a certain architecture exists in this fat binary
276    pub fn exists(&self, arch: &str) -> bool {
277        if let Some((cpu_type, cpu_subtype)) = get_arch_from_flag(arch) {
278            return self
279                .arches
280                .iter()
281                .find(|arch| arch.cpu_type == cpu_type && arch.cpu_subtype == cpu_subtype)
282                .is_some();
283        }
284        false
285    }
286
287    /// Write Mach-O fat binary into the writer
288    pub fn write_to<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
289        if self.arches.is_empty() {
290            return Ok(());
291        }
292        // Check whether we're doing fat32 or fat64
293        let is_fat64 =
294            if self.is_fat64 || self.arches.last().unwrap().data.len() as i64 >= 1i64 << 32 {
295                true
296            } else {
297                false
298            };
299        let align = self.max_align;
300        let mut total_offset = SIZEOF_FAT_HEADER as i64;
301        if is_fat64 {
302            total_offset += self.arches.len() as i64 * SIZEOF_FAT_ARCH_64 as i64;
303        // narches * size of fat_arch_64
304        } else {
305            total_offset += self.arches.len() as i64 * SIZEOF_FAT_ARCH as i64; // narches * size of fat_arch
306        }
307        let mut arch_offsets = Vec::with_capacity(self.arches.len());
308        for arch in &self.arches {
309            // Round up to multiple of align
310            total_offset = (total_offset + align - 1) / align * align;
311            arch_offsets.push(total_offset);
312            total_offset += arch.data.len() as i64;
313        }
314        let mut hdr = Vec::with_capacity(12);
315        // Build a fat_header
316        if is_fat64 {
317            hdr.push(FAT_MAGIC_64);
318        } else {
319            hdr.push(FAT_MAGIC);
320        }
321        hdr.push(self.arches.len() as u32);
322        // Compute the max alignment bits
323        let align_bits = (align as f32).log2() as u32;
324        // Build a fat_arch for each arch
325        for (arch, arch_offset) in self.arches.iter().zip(arch_offsets.iter()) {
326            hdr.push(arch.cpu_type);
327            hdr.push(arch.cpu_subtype);
328            if is_fat64 {
329                // Big Endian
330                hdr.push((arch_offset >> 32) as u32);
331            }
332            hdr.push(*arch_offset as u32);
333            if is_fat64 {
334                hdr.push((arch.data.len() >> 32) as u32);
335            }
336            hdr.push(arch.data.len() as u32);
337            hdr.push(align_bits);
338            if is_fat64 {
339                // Reserved
340                hdr.push(0);
341            }
342        }
343        // Write header
344        // Note that the fat binary header is big-endian, regardless of the
345        // endianness of the contained files.
346        for i in &hdr {
347            writer.write_all(&i.to_be_bytes())?;
348        }
349        let mut offset = 4 * hdr.len() as i64;
350        // Write each arch
351        for (arch, arch_offset) in self.arches.iter().zip(arch_offsets) {
352            if offset < arch_offset {
353                writer.write_all(&vec![0; (arch_offset - offset) as usize])?;
354                offset = arch_offset;
355            }
356            writer.write_all(&arch.data)?;
357            offset += arch.data.len() as i64;
358        }
359        Ok(())
360    }
361
362    /// Write Mach-O fat binary to a file
363    pub fn write_to_file<P: AsRef<Path>>(&self, path: P) -> Result<(), Error> {
364        let file = File::create(path)?;
365        #[cfg(unix)]
366        {
367            let mut perm = file.metadata()?.permissions();
368            perm.set_mode(0o755);
369            file.set_permissions(perm)?;
370        }
371        let mut writer = BufWriter::new(file);
372        self.write_to(&mut writer)?;
373        Ok(())
374    }
375}
376
377fn get_align_from_cpu_types(cpu_type: CpuType, cpu_subtype: CpuSubType) -> i64 {
378    if let Some(arch_name) = get_arch_name_from_types(cpu_type, cpu_subtype) {
379        if let Some((cpu_type, _)) = get_arch_from_flag(arch_name) {
380            match cpu_type {
381                // embedded
382                CPU_TYPE_ARM | CPU_TYPE_ARM64 | CPU_TYPE_ARM64_32 => return 0x4000,
383                // desktop
384                CPU_TYPE_X86_64 | CPU_TYPE_I386 | CPU_TYPE_POWERPC | CPU_TYPE_POWERPC64 => {
385                    return 0x1000
386                }
387                CPU_TYPE_MC680X0 | CPU_TYPE_MC88000 | CPU_TYPE_SPARC | CPU_TYPE_I860
388                | CPU_TYPE_HPPA => return 0x2000,
389                _ => {}
390            }
391        }
392    }
393    0
394}
395
396#[cfg(test)]
397mod tests {
398    use std::fs;
399
400    use super::FatWriter;
401    use crate::read::FatReader;
402
403    #[test]
404    fn test_fat_writer_add_exe() {
405        let mut fat = FatWriter::new();
406        let f1 = fs::read("tests/fixtures/thin_x86_64").unwrap();
407        let f2 = fs::read("tests/fixtures/thin_arm64").unwrap();
408        fat.add(f1).unwrap();
409        fat.add(f2).unwrap();
410        let mut out = Vec::new();
411        fat.write_to(&mut out).unwrap();
412
413        let reader = FatReader::new(&out);
414        assert!(reader.is_ok());
415
416        fat.write_to_file("tests/output/fat").unwrap();
417    }
418
419    #[test]
420    fn test_fat_writer_add_duplicated_arch() {
421        let mut fat = FatWriter::new();
422        let f1 = fs::read("tests/fixtures/thin_x86_64").unwrap();
423        fat.add(f1.clone()).unwrap();
424        assert!(fat.add(f1).is_err());
425    }
426
427    #[test]
428    fn test_fat_writer_add_fat() {
429        let mut fat = FatWriter::new();
430        let f1 = fs::read("tests/fixtures/simplefat").unwrap();
431        fat.add(f1).unwrap();
432        assert!(fat.exists("x86_64"));
433        assert!(fat.exists("arm64"));
434    }
435
436    #[test]
437    fn test_fat_writer_add_archive() {
438        let mut fat = FatWriter::new();
439        let f1 = fs::read("tests/fixtures/thin_x86_64.a").unwrap();
440        let f2 = fs::read("tests/fixtures/thin_arm64.a").unwrap();
441        fat.add(f1).unwrap();
442        fat.add(f2).unwrap();
443        let mut out = Vec::new();
444        fat.write_to(&mut out).unwrap();
445
446        let reader = FatReader::new(&out);
447        assert!(reader.is_ok());
448
449        fat.write_to_file("tests/output/fat.a").unwrap();
450    }
451
452    #[cfg(feature = "bitcode")]
453    #[test]
454    fn test_fat_writer_add_llvm_bitcode() {
455        let mut fat = FatWriter::new();
456        let f1 = fs::read("tests/fixtures/thin_x86_64.bc").unwrap();
457        let f2 = fs::read("tests/fixtures/thin_arm64.bc").unwrap();
458        fat.add(f1).unwrap();
459        fat.add(f2).unwrap();
460        let mut out = Vec::new();
461        fat.write_to(&mut out).unwrap();
462
463        let reader = FatReader::new(&out);
464        assert!(reader.is_ok());
465
466        fat.write_to_file("tests/output/fat_bc").unwrap();
467    }
468
469    #[test]
470    fn test_fat_writer_remove() {
471        let mut fat = FatWriter::new();
472        let f1 = fs::read("tests/fixtures/thin_x86_64").unwrap();
473        let f2 = fs::read("tests/fixtures/thin_arm64").unwrap();
474        fat.add(f1).unwrap();
475        fat.add(f2).unwrap();
476        let arm64 = fat.remove("arm64");
477        assert!(arm64.is_some());
478        assert!(fat.exists("x86_64"));
479        assert!(!fat.exists("arm64"));
480    }
481}