#![deny(clippy::arithmetic_side_effects)]
use crate::macho::buf::{pad_to, push_name16, push16, push32, push64};
use crate::macho::read::consts::{
LC_LINKER_OPTION, LC_SEGMENT_64, LC_SYMTAB, MH_MAGIC_64, MH_OBJECT, MH_SUBSECTIONS_VIA_SYMBOLS,
N_ABS, N_EXT, N_PEXT, N_UNDF, N_WEAK_DEF, N_WEAK_REF, S_REGULAR,
};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(super) enum StubKind {
Defined {
weak: bool,
hidden: bool,
},
Common,
Undefined {
weak: bool,
},
}
#[derive(Clone, Copy, Debug)]
pub(super) struct StubSymbol<'a> {
pub(super) name: &'a [u8],
pub(super) kind: StubKind,
}
pub(super) fn object(
cpu: (u32, u32),
symbols: &[StubSymbol<'_>],
linker_options: &[Vec<&[u8]>],
objc_category: bool,
) -> Vec<u8> {
let mut commands = Vec::new();
let mut ncmds = 0u32;
if objc_category {
push32(&mut commands, LC_SEGMENT_64);
push32(&mut commands, 72 + 80);
push_name16(&mut commands, b"");
for _ in 0..4 {
push64(&mut commands, 0); }
push32(&mut commands, 3); push32(&mut commands, 3); push32(&mut commands, 1); push32(&mut commands, 0); push_name16(&mut commands, b"__objc_catlist");
push_name16(&mut commands, b"__DATA");
push64(&mut commands, 0); push64(&mut commands, 0); push32(&mut commands, 0); push32(&mut commands, 3); push32(&mut commands, 0); push32(&mut commands, 0); push32(&mut commands, S_REGULAR);
push32(&mut commands, 0); push32(&mut commands, 0); push32(&mut commands, 0); ncmds = ncmds.saturating_add(1);
}
for strings in linker_options {
let mut body = Vec::new();
for string in strings {
body.extend_from_slice(string);
body.push(0);
}
pad_to(&mut body, 8);
let size = u32::try_from(body.len().saturating_add(12)).unwrap_or(u32::MAX);
let mut command = Vec::new();
push32(&mut command, LC_LINKER_OPTION);
push32(&mut command, size.next_multiple_of(8));
push32(&mut command, u32::try_from(strings.len()).unwrap_or(0));
command.extend_from_slice(&body);
pad_to(&mut command, 8);
commands.extend_from_slice(&command);
ncmds = ncmds.saturating_add(1);
}
let mut strings = vec![b' ', 0];
let mut table = Vec::with_capacity(symbols.len().saturating_mul(16));
for symbol in symbols {
let strx = u32::try_from(strings.len()).unwrap_or(0);
strings.extend_from_slice(symbol.name);
strings.push(0);
let (n_type, n_desc, n_value) = match symbol.kind {
StubKind::Defined { weak, hidden } => (
N_ABS | N_EXT | if hidden { N_PEXT } else { 0 },
if weak { N_WEAK_DEF } else { 0 },
0,
),
StubKind::Common => (N_UNDF | N_EXT, 0, 1),
StubKind::Undefined { weak } => (N_UNDF | N_EXT, if weak { N_WEAK_REF } else { 0 }, 0),
};
push32(&mut table, strx);
table.push(n_type);
table.push(0);
push16(&mut table, n_desc);
push64(&mut table, n_value);
}
pad_to(&mut strings, 8);
let symtab_command_size = 24usize;
let header_size = 32usize;
let commands_size = commands.len().saturating_add(symtab_command_size);
let symoff = header_size.saturating_add(commands_size);
let stroff = symoff.saturating_add(table.len());
let as_u32 = |value: usize| u32::try_from(value).unwrap_or(u32::MAX);
let mut out = Vec::with_capacity(stroff.saturating_add(strings.len()));
push32(&mut out, MH_MAGIC_64);
push32(&mut out, cpu.0);
push32(&mut out, cpu.1);
push32(&mut out, MH_OBJECT);
push32(&mut out, ncmds.saturating_add(1));
push32(&mut out, as_u32(commands_size));
push32(&mut out, MH_SUBSECTIONS_VIA_SYMBOLS);
push32(&mut out, 0);
out.extend_from_slice(&commands);
push32(&mut out, LC_SYMTAB);
push32(&mut out, as_u32(symtab_command_size));
push32(&mut out, as_u32(symoff));
push32(&mut out, as_u32(symbols.len()));
push32(&mut out, as_u32(stroff));
push32(&mut out, as_u32(strings.len()));
out.extend_from_slice(&table);
out.extend_from_slice(&strings);
out
}
#[derive(Clone, Copy, Debug)]
pub(super) struct ArchiveMember<'a> {
pub(super) name: &'a [u8],
pub(super) date: u64,
pub(super) data: &'a [u8],
}
pub(super) fn archive(members: &[ArchiveMember<'_>]) -> (Vec<u8>, Vec<usize>) {
let mut out = b"!<arch>\n".to_vec();
let mut offsets = Vec::with_capacity(members.len());
for member in members {
let mut name = member.name.to_vec();
name.push(0);
let unpadded = out.len().saturating_add(60).saturating_add(name.len());
name.resize(
name.len()
.saturating_add(unpadded.next_multiple_of(8).saturating_sub(unpadded)),
0,
);
let size = name.len().saturating_add(member.data.len());
let mut header = format!(
"{:<16}{:<12}{:<6}{:<6}{:<8}{:<10}`\n",
format!("#1/{}", name.len()),
member.date,
0,
0,
"100644",
size
)
.into_bytes();
header.truncate(60);
out.extend_from_slice(&header);
out.extend_from_slice(&name);
offsets.push(out.len());
out.extend_from_slice(member.data);
if out.len() & 1 == 1 {
out.push(b'\n');
}
}
(out, offsets)
}
#[cfg(test)]
mod tests {
use std::path::Path;
use super::*;
use crate::input::archive::Archive;
use crate::macho::object::LinkObject;
use crate::macho::read::consts::CPU_TYPE_ARM64;
use crate::macho::read::{LinkerOptionHint, ObjectFile, Source};
use crate::symbols::{DefinitionKind, SymbolUse};
#[test]
fn stand_in_resolves_like_the_module() {
let symbols = [
StubSymbol {
name: b"_main",
kind: StubKind::Defined {
weak: false,
hidden: false,
},
},
StubSymbol {
name: b"_inline",
kind: StubKind::Defined {
weak: true,
hidden: true,
},
},
StubSymbol {
name: b"_tentative",
kind: StubKind::Common,
},
StubSymbol {
name: b"_puts",
kind: StubKind::Undefined { weak: false },
},
StubSymbol {
name: b"_maybe",
kind: StubKind::Undefined { weak: true },
},
];
let data = object(
(CPU_TYPE_ARM64, 0),
&symbols,
&[vec![b"-lz"], vec![b"-framework", b"Foundation"]],
true,
);
let file = ObjectFile::parse(&data, Source::new(Path::new("stub.o"))).unwrap();
assert!(
file.sections()
.iter()
.any(|s| s.sectname == b"__objc_catlist")
);
let hints = file.linker_option_hints().unwrap();
assert_eq!(
hints,
[
LinkerOptionHint::Library(b"z"),
LinkerOptionHint::Framework(b"Foundation")
]
);
let object = LinkObject::new(file).unwrap();
let names: Vec<&[u8]> = object.names.iter().map(|n| n.bytes()).collect();
assert_eq!(
names,
[
b"_main".as_slice(),
b"_inline",
b"_tentative",
b"_puts",
b"_maybe"
]
);
assert!(matches!(
object.uses[0],
SymbolUse::Definition {
kind: DefinitionKind::Regular,
..
}
));
assert!(matches!(
object.uses[1],
SymbolUse::Definition {
kind: DefinitionKind::Weak,
aux: 1
}
));
assert!(matches!(
object.uses[2],
SymbolUse::Definition {
kind: DefinitionKind::Common,
..
}
));
assert_eq!(object.uses[3], SymbolUse::Reference { weak: false });
assert_eq!(object.uses[4], SymbolUse::Reference { weak: true });
let defined = LinkObject::defined_names(&object.file).unwrap();
assert_eq!(defined.len(), 3);
}
#[test]
fn rebuilt_archive_reads_back() {
let members = [
ArchiveMember {
name: b"a.o",
date: 1234,
data: b"first",
},
ArchiveMember {
name: b"a_rather_long_member_name.o",
date: 0,
data: b"second member",
},
];
let (data, offsets) = archive(&members);
let archive = Archive::parse(Path::new("lib.a"), &data).unwrap();
let read: Vec<_> = archive.members().map(Result::unwrap).collect();
assert_eq!(read.len(), 2);
for ((member, original), offset) in read.iter().zip(&members).zip(&offsets) {
assert_eq!(member.name, original.name);
let bytes = member.bytes().unwrap();
assert_eq!(bytes, original.data);
assert_eq!(bytes.as_ptr(), data[*offset..].as_ptr());
assert_eq!(offset % 8, 0);
}
let date = &data[read[0].header_offset as usize + 16..][..12];
assert_eq!(std::str::from_utf8(date).unwrap().trim(), "1234");
}
}