#[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<u8>,
cpu_type: u32,
cpu_subtype: u32,
align: i64,
}
#[derive(Debug)]
pub struct FatWriter {
arches: Vec<ThinArch>,
max_align: i64,
is_fat64: bool,
}
#[inline]
fn unpack_u32(buf: &[u8]) -> io::Result<u32> {
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 {
pub fn new() -> Self {
Self {
arches: Vec::new(),
max_align: 0,
is_fat64: false,
}
}
pub fn add<T: Into<Vec<u8>>>(&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;
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 } else {
4 };
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())),
}
self.arches.sort_by(|a, b| {
if a.cpu_type == b.cpu_type {
return a.cpu_subtype.cmp(&b.cpu_subtype);
}
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<u8> = 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(),
))
}
pub fn remove(&mut self, arch: &str) -> Option<Vec<u8>> {
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
}
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
}
pub fn write_to<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
if self.arches.is_empty() {
return Ok(());
}
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;
} else {
total_offset += self.arches.len() as i64 * SIZEOF_FAT_ARCH as i64; }
let mut arch_offsets = Vec::with_capacity(self.arches.len());
for arch in &self.arches {
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);
if is_fat64 {
hdr.push(FAT_MAGIC_64);
} else {
hdr.push(FAT_MAGIC);
}
hdr.push(self.arches.len() as u32);
let align_bits = (align as f32).log2() as u32;
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 {
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 {
hdr.push(0);
}
}
for i in &hdr {
writer.write_all(&i.to_be_bytes())?;
}
let mut offset = 4 * hdr.len() as i64;
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(())
}
pub fn write_to_file<P: AsRef<Path>>(&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 {
CPU_TYPE_ARM | CPU_TYPE_ARM64 | CPU_TYPE_ARM64_32 => return 0x4000,
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"));
}
}