1#[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#[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 pub fn new() -> Self {
73 Self {
74 arches: Vec::new(),
75 max_align: 0,
76 is_fat64: false,
77 }
78 }
79
80 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 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 } else {
127 4 };
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 self.arches.sort_by(|a, b| {
171 if a.cpu_type == b.cpu_type {
172 return a.cpu_subtype.cmp(&b.cpu_subtype);
174 }
175 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 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 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 pub fn write_to<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
289 if self.arches.is_empty() {
290 return Ok(());
291 }
292 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 } else {
305 total_offset += self.arches.len() as i64 * SIZEOF_FAT_ARCH as i64; }
307 let mut arch_offsets = Vec::with_capacity(self.arches.len());
308 for arch in &self.arches {
309 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 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 let align_bits = (align as f32).log2() as u32;
324 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 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 hdr.push(0);
341 }
342 }
343 for i in &hdr {
347 writer.write_all(&i.to_be_bytes())?;
348 }
349 let mut offset = 4 * hdr.len() as i64;
350 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 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 CPU_TYPE_ARM | CPU_TYPE_ARM64 | CPU_TYPE_ARM64_32 => return 0x4000,
383 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}