fat-macho-0.4.7/.builds/freebsd.yml000064400000000000000000000002431046102023000151670ustar 00000000000000image: freebsd/latest packages: - rust - cmake sources: - https://github.com/messense/fat-macho-rs tasks: - test: | cd fat-macho-rs cargo test fat-macho-0.4.7/.builds/openbsd.yml000064400000000000000000000002431046102023000152070ustar 00000000000000image: openbsd/latest packages: - rust - cmake sources: - https://github.com/messense/fat-macho-rs tasks: - test: | cd fat-macho-rs cargo test fat-macho-0.4.7/.cargo_vcs_info.json0000644000000001360000000000100127030ustar { "git": { "sha1": "777c77871aa0c9849bd318e65ebcfc481d55489b" }, "path_in_vcs": "" }fat-macho-0.4.7/.github/dependabot.yml000064400000000000000000000012371046102023000156660ustar 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.7/.github/workflows/CI.yml000064400000000000000000000023111046102023000161030ustar 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.7/.github/workflows/Python.yml000064400000000000000000000162501046102023000171000ustar 00000000000000name: Python on: push: pull_request: jobs: macos: runs-on: macos-latest steps: - uses: actions/checkout@v2 - uses: actions/setup-python@v2 with: python-version: 3.9 architecture: x64 - name: Install Rust toolchain uses: actions-rs/toolchain@v1 with: toolchain: stable profile: minimal default: true - name: Build wheels - x86_64 uses: messense/maturin-action@v1 with: target: x86_64-apple-darwin args: --release --sdist --out dist -m python/Cargo.toml - name: Install built wheel - x86_64 run: | pip install fat-macho --no-index --find-links dist --force-reinstall python -c "import fat_macho" - name: Build wheels - universal2 uses: messense/maturin-action@v1 with: target: universal2-apple-darwin args: --release --out dist -m python/Cargo.toml - name: Install built wheel - universal2 run: | pip install fat-macho --no-index --find-links dist --force-reinstall python -c "import fat_macho" - name: Upload wheels uses: actions/upload-artifact@v2 with: name: wheels path: dist windows: runs-on: windows-latest strategy: matrix: platform: [ { python-architecture: "x64", target: "x86_64-pc-windows-msvc" }, { python-architecture: "x86", target: "i686-pc-windows-msvc" }, ] steps: - uses: actions/checkout@v2 - uses: actions/setup-python@v2 with: python-version: 3.9 architecture: ${{ matrix.platform.python-architecture }} - name: Install Rust toolchain uses: actions-rs/toolchain@v1 with: toolchain: stable profile: minimal default: true - name: Build wheels uses: messense/maturin-action@v1 with: target: ${{ matrix.platform.target }} args: --release --out dist -m python/Cargo.toml - name: Install built wheel run: | pip install fat-macho --no-index --find-links dist --force-reinstall python -c "import fat_macho" - name: Upload wheels uses: actions/upload-artifact@v2 with: name: wheels path: dist linux: runs-on: ubuntu-latest strategy: matrix: target: - x86_64-unknown-linux-gnu - i686-unknown-linux-gnu steps: - uses: actions/checkout@v2 - uses: actions/setup-python@v2 with: python-version: 3.9 architecture: x64 - name: Build wheels uses: messense/maturin-action@v1 with: target: ${{ matrix.target }} manylinux: auto args: --release --out dist -m python/Cargo.toml - name: Install built wheel if: matrix.target == 'x86_64-unknown-linux-gnu' run: | pip3 install fat-macho --no-index --find-links dist --force-reinstall python3 -c "import fat_macho" - name: Upload wheels uses: actions/upload-artifact@v2 with: name: wheels path: dist linux-cross: runs-on: ubuntu-latest strategy: matrix: platform: - target: aarch64-unknown-linux-gnu arch: aarch64 - target: armv7-unknown-linux-gnueabihf arch: armv7 - target: s390x-unknown-linux-gnu arch: s390x - target: powerpc64le-unknown-linux-gnu arch: ppc64le - target: powerpc64-unknown-linux-gnu arch: ppc64 steps: - uses: actions/checkout@v2 - uses: actions/setup-python@v2 with: python-version: 3.9 - name: Build wheels uses: messense/maturin-action@v1 with: target: ${{ matrix.platform.target }} manylinux: auto args: --release --out dist -m python/Cargo.toml - uses: uraimo/run-on-arch-action@v2.0.5 if: matrix.platform.arch != 'ppc64' name: Install built wheel with: arch: ${{ matrix.platform.arch }} distro: ubuntu20.04 githubToken: ${{ github.token }} install: | apt-get update apt-get install -y --no-install-recommends python3 python3-pip pip3 install -U pip run: | pip3 install fat-macho --no-index --find-links dist/ --force-reinstall python3 -c "import fat_macho" - name: Upload wheels uses: actions/upload-artifact@v2 with: name: wheels path: dist musllinux: runs-on: ubuntu-latest strategy: matrix: target: - x86_64-unknown-linux-musl - i686-unknown-linux-musl steps: - uses: actions/checkout@v2 - uses: actions/setup-python@v2 with: python-version: 3.9 architecture: x64 - name: Build wheels uses: messense/maturin-action@v1 with: target: ${{ matrix.target }} manylinux: musllinux_1_2 args: --release --out dist -m python/Cargo.toml - name: Install built wheel if: matrix.target == 'x86_64-unknown-linux-musl' uses: addnab/docker-run-action@v3 with: image: alpine:latest options: -v ${{ github.workspace }}:/io -w /io run: | apk add py3-pip pip3 install -U pip pip3 install fat-macho --no-index --find-links /io/dist/ --force-reinstall python3 -c "import fat_macho" - name: Upload wheels uses: actions/upload-artifact@v2 with: name: wheels path: dist musllinux-cross: runs-on: ubuntu-latest strategy: matrix: platform: - target: aarch64-unknown-linux-musl arch: aarch64 - target: armv7-unknown-linux-musleabihf arch: armv7 steps: - uses: actions/checkout@v2 - uses: actions/setup-python@v2 with: python-version: 3.9 - name: Build wheels uses: messense/maturin-action@v1 with: target: ${{ matrix.platform.target }} manylinux: musllinux_1_2 args: --release --out dist -m python/Cargo.toml - uses: uraimo/run-on-arch-action@master name: Install built wheel with: arch: ${{ matrix.platform.arch }} distro: alpine_latest githubToken: ${{ github.token }} install: | apk add py3-pip pip3 install -U pip run: | pip3 install fat-macho --no-index --find-links dist/ --force-reinstall python3 -c "import fat_macho" - name: Upload wheels uses: actions/upload-artifact@v2 with: name: wheels path: dist release: name: Release runs-on: ubuntu-latest if: "startsWith(github.ref, 'refs/tags/')" needs: [ macos, windows, linux, linux-cross, musllinux, musllinux-cross ] steps: - uses: actions/download-artifact@v2 with: name: wheels - uses: actions/setup-python@v2 with: python-version: 3.9 - name: Publish to PyPi env: TWINE_USERNAME: __token__ TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} run: | pip install --upgrade twine twine upload --skip-existing * fat-macho-0.4.7/.gitignore000064400000000000000000000000721046102023000134620ustar 00000000000000/target /python/target Cargo.lock tests/output/fat* dist/ fat-macho-0.4.7/Cargo.toml0000644000000017200000000000100107010ustar # 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.7" authors = ["messense "] 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" [dependencies.goblin] version = "0.7.1" [dependencies.llvm-bitcode] version = "0.1.1" optional = true [features] bitcode = ["llvm-bitcode"] default = ["bitcode"] fat-macho-0.4.7/Cargo.toml.orig0000644000000010670000000000100116440ustar [package] name = "fat-macho" version = "0.4.7" 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.7.1" llvm-bitcode = { version = "0.1.1", optional = true } [features] default = ["bitcode"] bitcode = ["llvm-bitcode"] fat-macho-0.4.7/Cargo.toml.orig000064400000000000000000000010671046102023000143660ustar 00000000000000[package] name = "fat-macho" version = "0.4.7" 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.7.1" llvm-bitcode = { version = "0.1.1", optional = true } [features] default = ["bitcode"] bitcode = ["llvm-bitcode"] fat-macho-0.4.7/LICENSE000064400000000000000000000020511046102023000124760ustar 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.7/README.md000064400000000000000000000017271046102023000127610ustar 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) [![builds.sr.ht status](https://builds.sr.ht/~messense/fat-macho-rs.svg)](https://builds.sr.ht/~messense/fat-macho-rs?) [![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.7/src/error.rs000064400000000000000000000031531046102023000137630ustar 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.7/src/lib.rs000064400000000000000000000001741046102023000134000ustar 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.7/src/read.rs000064400000000000000000000077561046102023000135620ustar 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.7/src/write.rs000064400000000000000000000421401046102023000137630ustar 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.7/tests/output/.gitkeep000064400000000000000000000000001046102023000156140ustar 00000000000000