fat-macho-0.4.9/.cargo_vcs_info.json0000644000000001360000000000100127050ustar { "git": { "sha1": "6a5c3dc33809d3c8fdb714da58c1665a4983abfb" }, "path_in_vcs": "" }fat-macho-0.4.9/.github/dependabot.yml000064400000000000000000000012371046102023000156700ustar 00000000000000# To get started with Dependabot version updates, you'll need to specify which # package ecosystems to update and where the package manifests are located. # Please see the documentation for all configuration options: # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: - package-ecosystem: "cargo" # See documentation for possible values directory: "/" # Location of package manifests schedule: interval: "weekly" - package-ecosystem: "cargo" # See documentation for possible values directory: "/python" # Location of package manifests schedule: interval: "weekly" fat-macho-0.4.9/.github/workflows/CI.yml000064400000000000000000000023111046102023000161050ustar 00000000000000on: [push, pull_request] name: CI jobs: check: name: Check runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: stable override: true - uses: actions-rs/cargo@v1 with: command: check test: name: Test Suite runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: stable override: true - uses: actions-rs/cargo@v1 with: command: test args: --no-default-features - uses: actions-rs/cargo@v1 with: command: test fmt: name: Rustfmt runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: stable override: true - run: rustup component add rustfmt - uses: actions-rs/cargo@v1 with: command: fmt args: --all -- --check fat-macho-0.4.9/.github/workflows/Python.yml000064400000000000000000000055601046102023000171040ustar 00000000000000# This file is autogenerated by maturin v1.4.0 # To update, run # # maturin generate-ci -m python/Cargo.toml github # name: Python on: push: branches: - main - master tags: - '*' pull_request: workflow_dispatch: permissions: contents: read jobs: linux: runs-on: ubuntu-latest strategy: matrix: target: [x86_64, x86, aarch64, armv7, s390x, ppc64le] steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: python-version: '3.10' - name: Build wheels uses: PyO3/maturin-action@v1 with: target: ${{ matrix.target }} args: --release --out dist --manifest-path python/Cargo.toml sccache: 'true' manylinux: auto - name: Upload wheels uses: actions/upload-artifact@v3 with: name: wheels path: dist windows: runs-on: windows-latest strategy: matrix: target: [x64, x86] steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: python-version: '3.10' architecture: ${{ matrix.target }} - name: Build wheels uses: PyO3/maturin-action@v1 with: target: ${{ matrix.target }} args: --release --out dist --manifest-path python/Cargo.toml sccache: 'true' - name: Upload wheels uses: actions/upload-artifact@v3 with: name: wheels path: dist macos: runs-on: macos-latest strategy: matrix: target: [x86_64, aarch64] steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: python-version: '3.10' - name: Build wheels uses: PyO3/maturin-action@v1 with: target: ${{ matrix.target }} args: --release --out dist --manifest-path python/Cargo.toml sccache: 'true' - name: Upload wheels uses: actions/upload-artifact@v3 with: name: wheels path: dist sdist: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Build sdist uses: PyO3/maturin-action@v1 with: command: sdist args: --out dist --manifest-path python/Cargo.toml - name: Upload sdist uses: actions/upload-artifact@v3 with: name: wheels path: dist release: name: Release runs-on: ubuntu-latest if: "startsWith(github.ref, 'refs/tags/')" needs: [linux, windows, macos, sdist] steps: - uses: actions/download-artifact@v3 with: name: wheels - name: Publish to PyPI uses: PyO3/maturin-action@v1 env: MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_PASSWORD }} with: command: upload args: --non-interactive --skip-existing * fat-macho-0.4.9/.gitignore000064400000000000000000000000721046102023000134640ustar 00000000000000/target /python/target Cargo.lock tests/output/fat* dist/ fat-macho-0.4.9/Cargo.toml0000644000000021300000000000100106770ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "fat-macho" version = "0.4.9" authors = ["messense "] build = false autobins = false autoexamples = false autotests = false autobenches = false description = "Mach-O Fat Binary Reader and Writer" readme = "README.md" keywords = [ "fat", "mach", "macho", "universal", "universal2", ] license = "MIT" repository = "https://github.com/messense/fat-macho-rs.git" [lib] name = "fat_macho" path = "src/lib.rs" [dependencies.goblin] version = "0.9.0" [dependencies.llvm-bitcode] version = "0.1.1" optional = true [features] bitcode = ["llvm-bitcode"] default = ["bitcode"] fat-macho-0.4.9/Cargo.toml.orig0000644000000010670000000000100116460ustar [package] name = "fat-macho" version = "0.4.9" authors = ["messense "] description = "Mach-O Fat Binary Reader and Writer" keywords = ["fat", "mach", "macho", "universal", "universal2"] edition = "2021" readme = "README.md" license = "MIT" repository = "https://github.com/messense/fat-macho-rs.git" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] goblin = "0.9.0" llvm-bitcode = { version = "0.1.1", optional = true } [features] default = ["bitcode"] bitcode = ["llvm-bitcode"] fat-macho-0.4.9/Cargo.toml.orig000064400000000000000000000010671046102023000143700ustar 00000000000000[package] name = "fat-macho" version = "0.4.9" authors = ["messense "] description = "Mach-O Fat Binary Reader and Writer" keywords = ["fat", "mach", "macho", "universal", "universal2"] edition = "2021" readme = "README.md" license = "MIT" repository = "https://github.com/messense/fat-macho-rs.git" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] goblin = "0.9.0" llvm-bitcode = { version = "0.1.1", optional = true } [features] default = ["bitcode"] bitcode = ["llvm-bitcode"] fat-macho-0.4.9/LICENSE000064400000000000000000000020511046102023000125000ustar 00000000000000MIT License Copyright (c) 2021 messense Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. fat-macho-0.4.9/README.md000064400000000000000000000015371046102023000127620ustar 00000000000000# fat-macho-rs [![GitHub Actions](https://github.com/messense/fat-macho-rs/workflows/CI/badge.svg)](https://github.com/messense/fat-macho-rs/actions?query=workflow%3ACI) [![codecov](https://codecov.io/gh/messense/fat-macho-rs/branch/master/graph/badge.svg)](https://codecov.io/gh/messense/fat-macho-rs) [![Crates.io](https://img.shields.io/crates/v/fat-macho.svg)](https://crates.io/crates/fat-macho) [![docs.rs](https://docs.rs/fat-macho/badge.svg)](https://docs.rs/fat-macho/) Mach-O Fat Binary Reader and Writer ## Installation Add it to your ``Cargo.toml``: ```toml [dependencies] fat-macho = "0.4" ``` then you are good to go. If you are using Rust 2015 you have to add ``extern crate fat_macho`` to your crate root as well. ## License This work is released under the MIT license. A copy of the license is provided in the [LICENSE](./LICENSE) file. fat-macho-0.4.9/src/error.rs000064400000000000000000000031531046102023000137650ustar 00000000000000use std::{error, fmt, io}; #[derive(Debug)] pub enum Error { Io(io::Error), Goblin(goblin::error::Error), NotFatBinary, InvalidMachO(String), DuplicatedArch(String), #[cfg(feature = "bitcode")] Bitcode(llvm_bitcode::read::Error), } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Error::Io(err) => err.fmt(f), Error::Goblin(err) => err.fmt(f), Error::NotFatBinary => write!(f, "input is not a valid Mach-O fat binary"), Error::InvalidMachO(err) => write!(f, "{}", err), Error::DuplicatedArch(arch) => write!(f, "duplicated architecture {}", arch), #[cfg(feature = "bitcode")] Error::Bitcode(err) => err.fmt(f), } } } impl error::Error for Error { fn source(&self) -> Option<&(dyn error::Error + 'static)> { match self { Error::Io(err) => Some(err), Error::Goblin(err) => Some(err), Error::NotFatBinary => None, Error::InvalidMachO(_) => None, Error::DuplicatedArch(_) => None, #[cfg(feature = "bitcode")] Error::Bitcode(err) => Some(err), } } } impl From for Error { fn from(err: io::Error) -> Self { Self::Io(err) } } impl From for Error { fn from(err: goblin::error::Error) -> Self { Self::Goblin(err) } } #[cfg(feature = "bitcode")] impl From for Error { fn from(err: llvm_bitcode::read::Error) -> Self { Self::Bitcode(err) } } fat-macho-0.4.9/src/lib.rs000064400000000000000000000001741046102023000134020ustar 00000000000000mod error; mod read; mod write; pub use self::error::Error; pub use self::read::FatReader; pub use self::write::FatWriter; fat-macho-0.4.9/src/read.rs000064400000000000000000000077561046102023000135640ustar 00000000000000use goblin::mach::{cputype::get_arch_from_flag, Mach, MultiArch}; use crate::error::Error; /// Mach-O fat binary reader #[derive(Debug)] pub struct FatReader<'a> { buffer: &'a [u8], fat: MultiArch<'a>, } impl<'a> FatReader<'a> { /// Parse a Mach-O FAT binary from a buffer pub fn new(buffer: &'a [u8]) -> Result { match Mach::parse(buffer)? { Mach::Fat(fat) => Ok(Self { buffer, fat }), Mach::Binary(_) => Err(Error::NotFatBinary), } } /// Extract thin binary by arch name pub fn extract(&self, arch_name: &str) -> Option<&'a [u8]> { if let Some((cpu_type, _cpu_subtype)) = get_arch_from_flag(arch_name) { return self .fat .find_cputype(cpu_type) .unwrap_or_default() .map(|arch| arch.slice(self.buffer)); } None } } impl<'a> std::ops::Deref for FatReader<'a> { type Target = MultiArch<'a>; fn deref(&self) -> &Self::Target { &self.fat } } impl<'a> std::ops::DerefMut for FatReader<'a> { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.fat } } #[cfg(test)] mod test { use std::fs; use goblin::Object; use super::FatReader; use crate::error::Error; #[test] fn test_fat_reader_dylib() { let buf = fs::read("tests/fixtures/simplefat.dylib").unwrap(); let reader = FatReader::new(&buf); assert!(reader.is_ok()); } #[test] fn test_fat_reader_exe() { let buf = fs::read("tests/fixtures/simplefat").unwrap(); let reader = FatReader::new(&buf).unwrap(); assert_eq!(2, reader.narches); let buf = fs::read("tests/fixtures/hellofat").unwrap(); let reader = FatReader::new(&buf).unwrap(); assert_eq!(3, reader.narches); } #[test] fn test_fat_reader_ar() { let buf = fs::read("tests/fixtures/simplefat.a").unwrap(); let reader = FatReader::new(&buf); assert!(reader.is_ok()); } #[test] fn test_fat_reader_not_fat() { let buf = fs::read("tests/fixtures/thin_x86_64").unwrap(); let reader = FatReader::new(&buf); assert!(reader.is_err()); assert!(matches!(reader.unwrap_err(), Error::NotFatBinary)); let buf = fs::read("tests/fixtures/thin_arm64").unwrap(); let reader = FatReader::new(&buf); assert!(reader.is_err()); assert!(matches!(reader.unwrap_err(), Error::NotFatBinary)); } #[test] fn test_fat_reader_extract_dylib() { let buf = fs::read("tests/fixtures/simplefat.dylib").unwrap(); let reader = FatReader::new(&buf).unwrap(); let x86_64 = reader.extract("x86_64").unwrap(); let x86_64_obj = Object::parse(x86_64).unwrap(); assert!(matches!(x86_64_obj, Object::Mach(_))); let arm64 = reader.extract("arm64").unwrap(); let arm64_obj = Object::parse(arm64).unwrap(); assert!(matches!(arm64_obj, Object::Mach(_))); } #[test] fn test_fat_reader_extract_exe() { let buf = fs::read("tests/fixtures/simplefat").unwrap(); let reader = FatReader::new(&buf).unwrap(); let x86_64 = reader.extract("x86_64").unwrap(); let x86_64_obj = Object::parse(x86_64).unwrap(); assert!(matches!(x86_64_obj, Object::Mach(_))); let arm64 = reader.extract("arm64").unwrap(); let arm64_obj = Object::parse(arm64).unwrap(); assert!(matches!(arm64_obj, Object::Mach(_))); } #[test] fn test_fat_reader_extract_ar() { let buf = fs::read("tests/fixtures/simplefat.a").unwrap(); let reader = FatReader::new(&buf).unwrap(); let x86_64 = reader.extract("x86_64").unwrap(); let x86_64_obj = Object::parse(x86_64).unwrap(); assert!(matches!(x86_64_obj, Object::Archive(_))); let arm64 = reader.extract("arm64").unwrap(); let arm64_obj = Object::parse(arm64).unwrap(); assert!(matches!(arm64_obj, Object::Archive(_))); } } fat-macho-0.4.9/src/write.rs000064400000000000000000000421401046102023000137650ustar 00000000000000// Ported from https://github.com/randall77/makefat/blob/master/makefat.go #[cfg(unix)] use std::os::unix::fs::PermissionsExt; use std::{ cmp::Ordering, fs::File, io::{self, BufWriter, Write}, path::Path, }; #[cfg(feature = "bitcode")] use goblin::mach::cputype::{ CPU_SUBTYPE_ARM64_32_ALL, CPU_SUBTYPE_ARM64_ALL, CPU_SUBTYPE_ARM64_E, CPU_SUBTYPE_ARM_V4T, CPU_SUBTYPE_ARM_V5TEJ, CPU_SUBTYPE_ARM_V6, CPU_SUBTYPE_ARM_V6M, CPU_SUBTYPE_ARM_V7, CPU_SUBTYPE_ARM_V7EM, CPU_SUBTYPE_ARM_V7F, CPU_SUBTYPE_ARM_V7K, CPU_SUBTYPE_ARM_V7M, CPU_SUBTYPE_ARM_V7S, CPU_SUBTYPE_I386_ALL, CPU_SUBTYPE_POWERPC_ALL, CPU_SUBTYPE_X86_64_ALL, CPU_SUBTYPE_X86_64_H, }; use goblin::{ archive::Archive, mach::{ cputype::{ get_arch_from_flag, get_arch_name_from_types, CpuSubType, CpuType, CPU_ARCH_ABI64, CPU_TYPE_ARM, CPU_TYPE_ARM64, CPU_TYPE_ARM64_32, CPU_TYPE_HPPA, CPU_TYPE_I386, CPU_TYPE_I860, CPU_TYPE_MC680X0, CPU_TYPE_MC88000, CPU_TYPE_POWERPC, CPU_TYPE_POWERPC64, CPU_TYPE_SPARC, CPU_TYPE_X86_64, }, fat::{FAT_MAGIC, SIZEOF_FAT_ARCH, SIZEOF_FAT_HEADER}, Mach, }, Object, }; #[cfg(feature = "bitcode")] use llvm_bitcode::{bitcode::BitcodeElement, Bitcode}; use crate::error::Error; const FAT_MAGIC_64: u32 = FAT_MAGIC + 1; const SIZEOF_FAT_ARCH_64: usize = 32; const LLVM_BITCODE_WRAPPER_MAGIC: u32 = 0x0B17C0DE; #[derive(Debug)] struct ThinArch { data: Vec, cpu_type: u32, cpu_subtype: u32, align: i64, } /// Mach-O fat binary writer #[derive(Debug)] pub struct FatWriter { arches: Vec, max_align: i64, is_fat64: bool, } #[inline] fn unpack_u32(buf: &[u8]) -> io::Result { if buf.len() < 4 { return Err(io::Error::new( io::ErrorKind::UnexpectedEof, "not enough data for unpacking u32", )); } Ok(u32::from_le_bytes([buf[0], buf[1], buf[2], buf[3]])) } impl FatWriter { /// Create a new Mach-O fat binary writer pub fn new() -> Self { Self { arches: Vec::new(), max_align: 0, is_fat64: false, } } /// Add a new thin Mach-O binary pub fn add>>(&mut self, bytes: T) -> Result<(), Error> { let bytes = bytes.into(); match Object::parse(&bytes)? { Object::Mach(mach) => match mach { Mach::Fat(fat) => { for arch in fat.arches()? { let buffer = arch.slice(&bytes); self.add(buffer.to_vec())?; } } Mach::Binary(obj) => { let header = obj.header; let cpu_type = header.cputype; let cpu_subtype = header.cpusubtype; // Check if this architecture already exists if self .arches .iter() .find(|arch| arch.cpu_type == cpu_type && arch.cpu_subtype == cpu_subtype) .is_some() { let arch = get_arch_name_from_types(cpu_type, cpu_subtype).unwrap_or("unknown"); return Err(Error::DuplicatedArch(arch.to_string())); } if header.magic == FAT_MAGIC_64 { self.is_fat64 = true; } let align = get_align_from_cpu_types(cpu_type, cpu_subtype); if align > self.max_align { self.max_align = align; } let thin = ThinArch { data: bytes, cpu_type, cpu_subtype, align, }; self.arches.push(thin); } }, Object::Archive(ar) => { let (cpu_type, cpu_subtype) = self.check_archive(&bytes, &ar)?; let align = if cpu_type & CPU_ARCH_ABI64 != 0 { 8 /* alignof(u64) */ } else { 4 /* alignof(u32) */ }; if align > self.max_align { self.max_align = align; } let thin = ThinArch { data: bytes, cpu_type, cpu_subtype, align, }; self.arches.push(thin); } Object::Unknown(_) => { let magic = unpack_u32(&bytes)?; if magic == LLVM_BITCODE_WRAPPER_MAGIC { #[cfg(feature = "bitcode")] { let (cpu_type, cpu_subtype) = self.get_arch_from_bitcode(&bytes)?; let align = 1; if align > self.max_align { self.max_align = align; } let thin = ThinArch { data: bytes, cpu_type, cpu_subtype, align, }; self.arches.push(thin); } #[cfg(not(feature = "bitcode"))] return Err(Error::InvalidMachO( "bitcode input is unsupported".to_string(), )); } else { return Err(Error::InvalidMachO("input is not a macho file".to_string())); } } _ => return Err(Error::InvalidMachO("input is not a macho file".to_string())), } // Sort the files by alignment to save space in ouput self.arches.sort_by(|a, b| { if a.cpu_type == b.cpu_type { // if cpu types match, sort by cpu subtype return a.cpu_subtype.cmp(&b.cpu_subtype); } // force arm64-family to follow after all other slices if a.cpu_type == CPU_TYPE_ARM64 { return Ordering::Greater; } if b.cpu_type == CPU_TYPE_ARM64 { return Ordering::Less; } a.align.cmp(&b.align) }); Ok(()) } #[cfg(feature = "bitcode")] fn get_arch_from_bitcode(&self, buffer: &[u8]) -> Result<(CpuType, CpuSubType), Error> { let bitcode = Bitcode::new(buffer)?; let target_triple = bitcode .elements .iter() .find(|ele| match ele { BitcodeElement::Record(_) => false, BitcodeElement::Block(block) => block.id == 8, }) .and_then(|module_block| { module_block .as_block() .unwrap() .elements .iter() .find(|ele| match ele { BitcodeElement::Record(record) => record.id == 2, BitcodeElement::Block(_) => false, }) }) .and_then(|target_triple_record| { let record = target_triple_record.as_record().unwrap(); let fields: Vec = record.fields.iter().map(|x| *x as u8).collect(); String::from_utf8(fields).ok() }); if let Some(triple) = target_triple { if let Some(triple) = triple.splitn(2, "-").next() { return Ok(match triple { "i686" | "i386" => (CPU_TYPE_I386, CPU_SUBTYPE_I386_ALL), "x86_64" => (CPU_TYPE_X86_64, CPU_SUBTYPE_X86_64_ALL), "x86_64h" => (CPU_TYPE_X86_64, CPU_SUBTYPE_X86_64_H), "powerpc" => (CPU_TYPE_POWERPC, CPU_SUBTYPE_POWERPC_ALL), "powerpc64" => (CPU_TYPE_POWERPC64, CPU_SUBTYPE_POWERPC_ALL), "arm" => (CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V4T), "armv5" | "armv5e" | "thumbv5" | "thumbv5e" => { (CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V5TEJ) } "armv6" | "thumbv6" => (CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V6), "armv6m" | "thumbv6m" => (CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V6M), "armv7" | "thumbv7" => (CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V7), "armv7f" | "thumbv7f" => (CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V7F), "armv7s" | "thumbv7s" => (CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V7S), "armv7k" | "thumbv7k" => (CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V7K), "armv7m" | "thumbv7m" => (CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V7M), "armv7em" | "thumbv7em" => (CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V7EM), "arm64" => (CPU_TYPE_ARM64, CPU_SUBTYPE_ARM64_ALL), "arm64e" => (CPU_TYPE_ARM64, CPU_SUBTYPE_ARM64_E), "arm64_32" => (CPU_TYPE_ARM64_32, CPU_SUBTYPE_ARM64_32_ALL), _ => return Err(Error::InvalidMachO("input is not a macho file".to_string())), }); } } Err(Error::InvalidMachO("input is not a macho file".to_string())) } fn check_archive(&self, buffer: &[u8], ar: &Archive) -> Result<(u32, u32), Error> { for member in ar.members() { let bytes = ar.extract(member, buffer)?; match Object::parse(bytes)? { Object::Mach(mach) => match mach { Mach::Binary(obj) => { return Ok((obj.header.cputype, obj.header.cpusubtype)); } Mach::Fat(_) => {} }, _ => {} } } Err(Error::InvalidMachO( "No Mach-O objects found in archivec".to_string(), )) } /// Remove an architecture pub fn remove(&mut self, arch: &str) -> Option> { if let Some((cpu_type, cpu_subtype)) = get_arch_from_flag(arch) { if let Some(index) = self .arches .iter() .position(|arch| arch.cpu_type == cpu_type && arch.cpu_subtype == cpu_subtype) { return Some(self.arches.remove(index).data); } } None } /// Check whether a certain architecture exists in this fat binary pub fn exists(&self, arch: &str) -> bool { if let Some((cpu_type, cpu_subtype)) = get_arch_from_flag(arch) { return self .arches .iter() .find(|arch| arch.cpu_type == cpu_type && arch.cpu_subtype == cpu_subtype) .is_some(); } false } /// Write Mach-O fat binary into the writer pub fn write_to(&self, writer: &mut W) -> Result<(), Error> { if self.arches.is_empty() { return Ok(()); } // Check whether we're doing fat32 or fat64 let is_fat64 = if self.is_fat64 || self.arches.last().unwrap().data.len() as i64 >= 1i64 << 32 { true } else { false }; let align = self.max_align; let mut total_offset = SIZEOF_FAT_HEADER as i64; if is_fat64 { total_offset += self.arches.len() as i64 * SIZEOF_FAT_ARCH_64 as i64; // narches * size of fat_arch_64 } else { total_offset += self.arches.len() as i64 * SIZEOF_FAT_ARCH as i64; // narches * size of fat_arch } let mut arch_offsets = Vec::with_capacity(self.arches.len()); for arch in &self.arches { // Round up to multiple of align total_offset = (total_offset + align - 1) / align * align; arch_offsets.push(total_offset); total_offset += arch.data.len() as i64; } let mut hdr = Vec::with_capacity(12); // Build a fat_header if is_fat64 { hdr.push(FAT_MAGIC_64); } else { hdr.push(FAT_MAGIC); } hdr.push(self.arches.len() as u32); // Compute the max alignment bits let align_bits = (align as f32).log2() as u32; // Build a fat_arch for each arch for (arch, arch_offset) in self.arches.iter().zip(arch_offsets.iter()) { hdr.push(arch.cpu_type); hdr.push(arch.cpu_subtype); if is_fat64 { // Big Endian hdr.push((arch_offset >> 32) as u32); } hdr.push(*arch_offset as u32); if is_fat64 { hdr.push((arch.data.len() >> 32) as u32); } hdr.push(arch.data.len() as u32); hdr.push(align_bits); if is_fat64 { // Reserved hdr.push(0); } } // Write header // Note that the fat binary header is big-endian, regardless of the // endianness of the contained files. for i in &hdr { writer.write_all(&i.to_be_bytes())?; } let mut offset = 4 * hdr.len() as i64; // Write each arch for (arch, arch_offset) in self.arches.iter().zip(arch_offsets) { if offset < arch_offset { writer.write_all(&vec![0; (arch_offset - offset) as usize])?; offset = arch_offset; } writer.write_all(&arch.data)?; offset += arch.data.len() as i64; } Ok(()) } /// Write Mach-O fat binary to a file pub fn write_to_file>(&self, path: P) -> Result<(), Error> { let file = File::create(path)?; #[cfg(unix)] { let mut perm = file.metadata()?.permissions(); perm.set_mode(0o755); file.set_permissions(perm)?; } let mut writer = BufWriter::new(file); self.write_to(&mut writer)?; Ok(()) } } fn get_align_from_cpu_types(cpu_type: CpuType, cpu_subtype: CpuSubType) -> i64 { if let Some(arch_name) = get_arch_name_from_types(cpu_type, cpu_subtype) { if let Some((cpu_type, _)) = get_arch_from_flag(arch_name) { match cpu_type { // embedded CPU_TYPE_ARM | CPU_TYPE_ARM64 | CPU_TYPE_ARM64_32 => return 0x4000, // desktop CPU_TYPE_X86_64 | CPU_TYPE_I386 | CPU_TYPE_POWERPC | CPU_TYPE_POWERPC64 => { return 0x1000 } CPU_TYPE_MC680X0 | CPU_TYPE_MC88000 | CPU_TYPE_SPARC | CPU_TYPE_I860 | CPU_TYPE_HPPA => return 0x2000, _ => {} } } } 0 } #[cfg(test)] mod tests { use std::fs; use super::FatWriter; use crate::read::FatReader; #[test] fn test_fat_writer_add_exe() { let mut fat = FatWriter::new(); let f1 = fs::read("tests/fixtures/thin_x86_64").unwrap(); let f2 = fs::read("tests/fixtures/thin_arm64").unwrap(); fat.add(f1).unwrap(); fat.add(f2).unwrap(); let mut out = Vec::new(); fat.write_to(&mut out).unwrap(); let reader = FatReader::new(&out); assert!(reader.is_ok()); fat.write_to_file("tests/output/fat").unwrap(); } #[test] fn test_fat_writer_add_duplicated_arch() { let mut fat = FatWriter::new(); let f1 = fs::read("tests/fixtures/thin_x86_64").unwrap(); fat.add(f1.clone()).unwrap(); assert!(fat.add(f1).is_err()); } #[test] fn test_fat_writer_add_fat() { let mut fat = FatWriter::new(); let f1 = fs::read("tests/fixtures/simplefat").unwrap(); fat.add(f1).unwrap(); assert!(fat.exists("x86_64")); assert!(fat.exists("arm64")); } #[test] fn test_fat_writer_add_archive() { let mut fat = FatWriter::new(); let f1 = fs::read("tests/fixtures/thin_x86_64.a").unwrap(); let f2 = fs::read("tests/fixtures/thin_arm64.a").unwrap(); fat.add(f1).unwrap(); fat.add(f2).unwrap(); let mut out = Vec::new(); fat.write_to(&mut out).unwrap(); let reader = FatReader::new(&out); assert!(reader.is_ok()); fat.write_to_file("tests/output/fat.a").unwrap(); } #[cfg(feature = "bitcode")] #[test] fn test_fat_writer_add_llvm_bitcode() { let mut fat = FatWriter::new(); let f1 = fs::read("tests/fixtures/thin_x86_64.bc").unwrap(); let f2 = fs::read("tests/fixtures/thin_arm64.bc").unwrap(); fat.add(f1).unwrap(); fat.add(f2).unwrap(); let mut out = Vec::new(); fat.write_to(&mut out).unwrap(); let reader = FatReader::new(&out); assert!(reader.is_ok()); fat.write_to_file("tests/output/fat_bc").unwrap(); } #[test] fn test_fat_writer_remove() { let mut fat = FatWriter::new(); let f1 = fs::read("tests/fixtures/thin_x86_64").unwrap(); let f2 = fs::read("tests/fixtures/thin_arm64").unwrap(); fat.add(f1).unwrap(); fat.add(f2).unwrap(); let arm64 = fat.remove("arm64"); assert!(arm64.is_some()); assert!(fat.exists("x86_64")); assert!(!fat.exists("arm64")); } } fat-macho-0.4.9/tests/output/.gitkeep000064400000000000000000000000001046102023000156160ustar 00000000000000