use std::borrow::Cow;
use goblin::mach::{
Mach, MachO,
constants::cputype::{CPU_TYPE_ARM, CPU_TYPE_ARM64, CPU_TYPE_I386, CPU_TYPE_X86_64},
symbols::{N_SECT, Nlist},
};
use crate::{
container::{Arch, Container, Format, Section, SectionKind, Symbol, SymbolKind, assemble},
error::{Error, Result},
};
pub(crate) fn build<'a>(bytes: &'a [u8], mach: Mach<'a>) -> Result<Container<'a>> {
let macho = match mach {
Mach::Binary(m) => m,
Mach::Fat(fat) => {
let arches = fat.arches()?;
let first = arches.first().ok_or(Error::UnsupportedFormat)?;
MachO::parse(bytes, first.offset as usize)?
}
};
let arch = map_arch(macho.header.cputype);
let image_base = macho
.segments
.iter()
.map(|seg| seg.vmaddr)
.filter(|&v| v != 0)
.min()
.unwrap_or(0);
let sections = collect_sections(bytes, &macho)?;
let symbols = collect_symbols(&macho)?;
Ok(assemble(
bytes,
Format::MachO,
arch,
image_base,
sections,
symbols,
))
}
fn map_arch(cputype: u32) -> Arch {
match cputype {
CPU_TYPE_X86_64 => Arch::Amd64,
CPU_TYPE_I386 => Arch::I386,
CPU_TYPE_ARM64 => Arch::Aarch64,
CPU_TYPE_ARM => Arch::Arm,
_ => Arch::Other,
}
}
fn collect_sections<'a>(bytes: &'a [u8], macho: &MachO<'a>) -> Result<Vec<Section<'a>>> {
let mut out = Vec::new();
for seg in &macho.segments {
let segname = seg.name().unwrap_or("").to_string();
for section in seg.into_iter() {
let (sect, _data) = match section {
Ok(s) => s,
Err(_) => continue,
};
let sectname = sect.name().unwrap_or("").to_string();
let qualified = format!("{segname},{sectname}");
let vm_addr = sect.addr;
let vm_size = sect.size;
let file_offset = sect.offset as usize;
let file_size = sect.size as usize;
let data: &'a [u8] = if file_offset == 0 {
&[]
} else {
bytes
.get(file_offset..file_offset.saturating_add(file_size))
.unwrap_or(&[])
};
let kind = classify(&segname, §name);
out.push(Section {
name: qualified,
vm_addr,
vm_size,
data,
kind,
});
}
}
Ok(out)
}
fn classify(segname: &str, sectname: &str) -> SectionKind {
match segname {
"__TEXT" => match sectname {
"__text" => SectionKind::Text,
"__cstring" | "__const" | "__ustring" | "__gcc_except_tab" | "__eh_frame" => {
SectionKind::RoData
}
_ => SectionKind::RoData, },
"__DATA_CONST" => SectionKind::RoData,
"__DATA" => match sectname {
"__bss" | "__common" => SectionKind::Bss,
_ => SectionKind::Data,
},
_ => SectionKind::Other,
}
}
fn collect_symbols<'a>(macho: &MachO<'a>) -> Result<Vec<Symbol<'a>>> {
let mut out = Vec::new();
for result in macho.symbols() {
let (name, nlist) = match result {
Ok(pair) => pair,
Err(_) => continue,
};
if name.is_empty() || nlist.is_stab() {
continue;
}
let normalised: Cow<'a, str> = if let Some(stripped) = name.strip_prefix('_') {
Cow::Borrowed(stripped)
} else {
Cow::Borrowed(name)
};
let kind = classify_symbol(&nlist);
out.push(Symbol {
name: normalised,
vm_addr: nlist.n_value,
size: 0, kind,
});
}
Ok(out)
}
fn classify_symbol(nlist: &Nlist) -> SymbolKind {
if nlist.get_type() == N_SECT {
SymbolKind::Function
} else {
SymbolKind::Other
}
}