mirror of
https://github.com/encounter/decomp-toolkit.git
synced 2025-12-15 16:16:20 +00:00
Reorganize files; start RSO support; config & split updates
This commit is contained in:
190
src/obj/mod.rs
Normal file
190
src/obj/mod.rs
Normal file
@@ -0,0 +1,190 @@
|
||||
pub mod signatures;
|
||||
pub mod split;
|
||||
|
||||
use std::{
|
||||
cmp::min,
|
||||
collections::{btree_map, BTreeMap},
|
||||
hash::{Hash, Hasher},
|
||||
};
|
||||
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use flagset::{flags, FlagSet};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||
|
||||
use crate::util::{nested::NestedVec, rel::RelReloc};
|
||||
|
||||
flags! {
|
||||
#[repr(u8)]
|
||||
#[derive(Deserialize_repr, Serialize_repr)]
|
||||
pub enum ObjSymbolFlags: u8 {
|
||||
Global,
|
||||
Local,
|
||||
Weak,
|
||||
Common,
|
||||
Hidden,
|
||||
}
|
||||
}
|
||||
#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct ObjSymbolFlagSet(pub FlagSet<ObjSymbolFlags>);
|
||||
#[allow(clippy::derive_hash_xor_eq)]
|
||||
impl Hash for ObjSymbolFlagSet {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) { self.0.bits().hash(state) }
|
||||
}
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub enum ObjSectionKind {
|
||||
Code,
|
||||
Data,
|
||||
ReadOnlyData,
|
||||
Bss,
|
||||
}
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ObjSection {
|
||||
pub name: String,
|
||||
pub kind: ObjSectionKind,
|
||||
pub address: u64,
|
||||
pub size: u64,
|
||||
pub data: Vec<u8>,
|
||||
pub align: u64,
|
||||
pub index: usize,
|
||||
/// REL files reference the original ELF section indices
|
||||
pub elf_index: usize,
|
||||
pub relocations: Vec<ObjReloc>,
|
||||
pub original_address: u64,
|
||||
pub file_offset: u64,
|
||||
pub section_known: bool,
|
||||
}
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default, Serialize, Deserialize)]
|
||||
pub enum ObjSymbolKind {
|
||||
#[default]
|
||||
Unknown,
|
||||
Function,
|
||||
Object,
|
||||
Section,
|
||||
}
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ObjSymbol {
|
||||
pub name: String,
|
||||
pub demangled_name: Option<String>,
|
||||
pub address: u64,
|
||||
pub section: Option<usize>,
|
||||
pub size: u64,
|
||||
pub size_known: bool,
|
||||
pub flags: ObjSymbolFlagSet,
|
||||
pub kind: ObjSymbolKind,
|
||||
}
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub enum ObjKind {
|
||||
/// Fully linked object
|
||||
Executable,
|
||||
/// Relocatable object
|
||||
Relocatable,
|
||||
}
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub enum ObjArchitecture {
|
||||
PowerPc,
|
||||
}
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ObjInfo {
|
||||
pub kind: ObjKind,
|
||||
pub architecture: ObjArchitecture,
|
||||
pub name: String,
|
||||
pub symbols: Vec<ObjSymbol>,
|
||||
pub sections: Vec<ObjSection>,
|
||||
pub entry: u64,
|
||||
|
||||
// Linker generated
|
||||
pub sda2_base: Option<u32>,
|
||||
pub sda_base: Option<u32>,
|
||||
pub stack_address: Option<u32>,
|
||||
pub stack_end: Option<u32>,
|
||||
pub db_stack_addr: Option<u32>,
|
||||
pub arena_lo: Option<u32>,
|
||||
pub arena_hi: Option<u32>,
|
||||
|
||||
// Extracted
|
||||
pub splits: BTreeMap<u32, Vec<String>>,
|
||||
pub named_sections: BTreeMap<u32, String>,
|
||||
pub link_order: Vec<String>,
|
||||
|
||||
// From extab
|
||||
pub known_functions: BTreeMap<u32, u32>,
|
||||
|
||||
// REL
|
||||
/// Module ID (0 for main)
|
||||
pub module_id: u32,
|
||||
pub unresolved_relocations: Vec<RelReloc>,
|
||||
}
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
|
||||
pub enum ObjRelocKind {
|
||||
Absolute,
|
||||
PpcAddr16Hi,
|
||||
PpcAddr16Ha,
|
||||
PpcAddr16Lo,
|
||||
PpcRel24,
|
||||
PpcRel14,
|
||||
PpcEmbSda21,
|
||||
}
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ObjReloc {
|
||||
pub kind: ObjRelocKind,
|
||||
pub address: u64,
|
||||
pub target_symbol: usize,
|
||||
pub addend: i64,
|
||||
}
|
||||
|
||||
impl ObjInfo {
|
||||
pub fn symbols_for_section(
|
||||
&self,
|
||||
section_idx: usize,
|
||||
) -> impl Iterator<Item = (usize, &ObjSymbol)> {
|
||||
self.symbols
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(move |&(_, symbol)| symbol.section == Some(section_idx))
|
||||
}
|
||||
|
||||
pub fn build_symbol_map(&self, section_idx: usize) -> Result<BTreeMap<u32, Vec<usize>>> {
|
||||
let mut symbols = BTreeMap::<u32, Vec<usize>>::new();
|
||||
for (symbol_idx, symbol) in self.symbols_for_section(section_idx) {
|
||||
symbols.nested_push(symbol.address as u32, symbol_idx);
|
||||
}
|
||||
Ok(symbols)
|
||||
}
|
||||
|
||||
pub fn section_at(&self, addr: u32) -> Result<&ObjSection> {
|
||||
self.sections
|
||||
.iter()
|
||||
.find(|§ion| {
|
||||
(addr as u64) >= section.address && (addr as u64) < section.address + section.size
|
||||
})
|
||||
.ok_or_else(|| anyhow!("Failed to locate section @ {:#010X}", addr))
|
||||
}
|
||||
|
||||
pub fn section_data(&self, start: u32, end: u32) -> Result<(&ObjSection, &[u8])> {
|
||||
let section = self.section_at(start)?;
|
||||
let data = if end == 0 {
|
||||
§ion.data[(start as u64 - section.address) as usize..]
|
||||
} else {
|
||||
§ion.data[(start as u64 - section.address) as usize
|
||||
..min(section.data.len(), (end as u64 - section.address) as usize)]
|
||||
};
|
||||
Ok((section, data))
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjSection {
|
||||
pub fn build_relocation_map(&self) -> Result<BTreeMap<u32, ObjReloc>> {
|
||||
let mut relocations = BTreeMap::<u32, ObjReloc>::new();
|
||||
for reloc in &self.relocations {
|
||||
let address = reloc.address as u32;
|
||||
match relocations.entry(address) {
|
||||
btree_map::Entry::Vacant(e) => {
|
||||
e.insert(reloc.clone());
|
||||
}
|
||||
btree_map::Entry::Occupied(_) => bail!("Duplicate relocation @ {address:#010X}"),
|
||||
}
|
||||
}
|
||||
Ok(relocations)
|
||||
}
|
||||
}
|
||||
413
src/obj/signatures.rs
Normal file
413
src/obj/signatures.rs
Normal file
@@ -0,0 +1,413 @@
|
||||
use std::{
|
||||
collections::{btree_map, BTreeMap},
|
||||
path::Path,
|
||||
};
|
||||
|
||||
use anyhow::{anyhow, bail, ensure, Result};
|
||||
use base64::{engine::general_purpose::STANDARD, Engine};
|
||||
use cwdemangle::{demangle, DemangleOptions};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha1::{Digest, Sha1};
|
||||
|
||||
use crate::{
|
||||
analysis::tracker::{Relocation, Tracker},
|
||||
array_ref,
|
||||
obj::{
|
||||
ObjInfo, ObjReloc, ObjRelocKind, ObjSectionKind, ObjSymbol, ObjSymbolFlagSet, ObjSymbolKind,
|
||||
},
|
||||
util::elf::process_elf,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Hash, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct OutSymbol {
|
||||
pub kind: ObjSymbolKind,
|
||||
pub name: String,
|
||||
pub size: u32,
|
||||
pub flags: ObjSymbolFlagSet,
|
||||
pub section: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Hash, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct OutReloc {
|
||||
pub offset: u32,
|
||||
pub kind: ObjRelocKind,
|
||||
pub symbol: usize,
|
||||
pub addend: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Hash, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct FunctionSignature {
|
||||
pub symbol: usize,
|
||||
pub hash: String,
|
||||
pub signature: String,
|
||||
pub symbols: Vec<OutSymbol>,
|
||||
pub relocations: Vec<OutReloc>,
|
||||
}
|
||||
|
||||
pub fn check_signature(mut data: &[u8], sig: &FunctionSignature) -> Result<bool> {
|
||||
let sig_data = STANDARD.decode(&sig.signature)?;
|
||||
// println!(
|
||||
// "\nChecking signature {} {} (size {})",
|
||||
// sig.symbols[sig.symbol].name, sig.hash, sig.symbols[sig.symbol].size
|
||||
// );
|
||||
// for chunk in sig_data.chunks_exact(8) {
|
||||
// let ins = u32::from_be_bytes(*array_ref!(chunk, 0, 4));
|
||||
// let i = Ins::new(ins, 0);
|
||||
// println!("=> {}", i.simplified());
|
||||
// }
|
||||
for chunk in sig_data.chunks_exact(8) {
|
||||
let ins = u32::from_be_bytes(*array_ref!(chunk, 0, 4));
|
||||
let pat = u32::from_be_bytes(*array_ref!(chunk, 4, 4));
|
||||
if (u32::from_be_bytes(*array_ref!(data, 0, 4)) & pat) != ins {
|
||||
return Ok(false);
|
||||
}
|
||||
data = &data[4..];
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub fn parse_signatures(sig_str: &str) -> Result<Vec<FunctionSignature>> {
|
||||
Ok(serde_yaml::from_str(sig_str)?)
|
||||
}
|
||||
|
||||
pub fn check_signatures_str(
|
||||
obj: &ObjInfo,
|
||||
addr: u32,
|
||||
sig_str: &str,
|
||||
) -> Result<Option<FunctionSignature>> {
|
||||
check_signatures(obj, addr, &parse_signatures(sig_str)?)
|
||||
}
|
||||
|
||||
pub fn check_signatures(
|
||||
obj: &ObjInfo,
|
||||
addr: u32,
|
||||
signatures: &Vec<FunctionSignature>,
|
||||
) -> Result<Option<FunctionSignature>> {
|
||||
let (_, data) = obj.section_data(addr, 0)?;
|
||||
let mut name = None;
|
||||
for signature in signatures {
|
||||
if name.is_none() {
|
||||
name = Some(signature.symbols[signature.symbol].name.clone());
|
||||
}
|
||||
if check_signature(data, signature)? {
|
||||
log::debug!(
|
||||
"Found {} @ {:#010X} (hash {})",
|
||||
signature.symbols[signature.symbol].name,
|
||||
addr,
|
||||
signature.hash
|
||||
);
|
||||
return Ok(Some(signature.clone()));
|
||||
}
|
||||
}
|
||||
// if let Some(name) = name {
|
||||
// log::debug!("Didn't find {} @ {:#010X}", name, addr);
|
||||
// }
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub fn apply_symbol(obj: &mut ObjInfo, target: u32, sig_symbol: &OutSymbol) -> Result<usize> {
|
||||
let mut target_section_index = obj.section_at(target).ok().map(|section| section.index);
|
||||
if let Some(target_section_index) = target_section_index {
|
||||
let target_section = &mut obj.sections[target_section_index];
|
||||
if !target_section.section_known {
|
||||
if let Some(section_name) = &sig_symbol.section {
|
||||
target_section.name = section_name.clone();
|
||||
target_section.kind = match section_name.as_str() {
|
||||
".init" | ".text" | ".dbgtext" => ObjSectionKind::Code,
|
||||
".ctors" | ".dtors" | ".rodata" | ".sdata2" | "extab" | "extabindex" => {
|
||||
ObjSectionKind::ReadOnlyData
|
||||
}
|
||||
".bss" | ".sbss" | ".sbss2" => ObjSectionKind::Bss,
|
||||
".data" | ".sdata" => ObjSectionKind::Data,
|
||||
name => bail!("Unknown section {name}"),
|
||||
};
|
||||
target_section.section_known = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if sig_symbol.kind == ObjSymbolKind::Unknown
|
||||
&& (sig_symbol.name.starts_with("_f_") || sig_symbol.name.starts_with("_SDA"))
|
||||
{
|
||||
// Hack to mark linker generated symbols as ABS
|
||||
target_section_index = None;
|
||||
}
|
||||
let target_symbol_idx = if let Some((symbol_idx, existing)) =
|
||||
obj.symbols.iter_mut().enumerate().find(|(_, symbol)| {
|
||||
symbol.address == target as u64
|
||||
&& symbol.kind == sig_symbol.kind
|
||||
// Hack to avoid replacing different ABS symbols
|
||||
&& (symbol.section.is_some() || symbol.name == sig_symbol.name)
|
||||
}) {
|
||||
log::debug!("Replacing {:?} with {:?}", existing, sig_symbol);
|
||||
*existing = ObjSymbol {
|
||||
name: sig_symbol.name.clone(),
|
||||
demangled_name: demangle(&sig_symbol.name, &DemangleOptions::default()),
|
||||
address: target as u64,
|
||||
section: target_section_index,
|
||||
size: if sig_symbol.size == 0 { existing.size } else { sig_symbol.size as u64 },
|
||||
size_known: existing.size_known || sig_symbol.size != 0,
|
||||
flags: sig_symbol.flags,
|
||||
kind: sig_symbol.kind,
|
||||
};
|
||||
symbol_idx
|
||||
} else {
|
||||
let target_symbol_idx = obj.symbols.len();
|
||||
obj.symbols.push(ObjSymbol {
|
||||
name: sig_symbol.name.clone(),
|
||||
demangled_name: demangle(&sig_symbol.name, &DemangleOptions::default()),
|
||||
address: target as u64,
|
||||
section: target_section_index,
|
||||
size: sig_symbol.size as u64,
|
||||
size_known: sig_symbol.size != 0,
|
||||
flags: sig_symbol.flags,
|
||||
kind: sig_symbol.kind,
|
||||
});
|
||||
target_symbol_idx
|
||||
};
|
||||
match sig_symbol.name.as_str() {
|
||||
"_SDA_BASE_" => obj.sda_base = Some(target),
|
||||
"_SDA2_BASE_" => obj.sda2_base = Some(target),
|
||||
"_stack_addr" => obj.stack_address = Some(target),
|
||||
"_stack_end" => obj.stack_end = Some(target),
|
||||
"_db_stack_addr" => obj.db_stack_addr = Some(target),
|
||||
"__ArenaLo" => obj.arena_lo = Some(target),
|
||||
"__ArenaHi" => obj.arena_hi = Some(target),
|
||||
_ => {}
|
||||
}
|
||||
Ok(target_symbol_idx)
|
||||
}
|
||||
|
||||
pub fn apply_signature(obj: &mut ObjInfo, addr: u32, signature: &FunctionSignature) -> Result<()> {
|
||||
let section_index = obj.section_at(addr)?.index;
|
||||
let in_symbol = &signature.symbols[signature.symbol];
|
||||
let symbol_idx = apply_symbol(obj, addr, in_symbol)?;
|
||||
let mut tracker = Tracker::new(obj);
|
||||
for reloc in &signature.relocations {
|
||||
tracker.known_relocations.insert(addr + reloc.offset);
|
||||
}
|
||||
tracker.process_function(obj, &obj.symbols[symbol_idx])?;
|
||||
for (&reloc_addr, reloc) in &tracker.relocations {
|
||||
if reloc_addr < addr || reloc_addr >= addr + in_symbol.size {
|
||||
continue;
|
||||
}
|
||||
let offset = reloc_addr - addr;
|
||||
let sig_reloc = match signature.relocations.iter().find(|r| r.offset == offset) {
|
||||
Some(reloc) => reloc,
|
||||
None => continue,
|
||||
};
|
||||
let target = match (reloc, sig_reloc.kind) {
|
||||
(&Relocation::Absolute(addr), ObjRelocKind::Absolute)
|
||||
| (&Relocation::Hi(addr), ObjRelocKind::PpcAddr16Hi)
|
||||
| (&Relocation::Ha(addr), ObjRelocKind::PpcAddr16Ha)
|
||||
| (&Relocation::Lo(addr), ObjRelocKind::PpcAddr16Lo)
|
||||
| (&Relocation::Rel24(addr), ObjRelocKind::PpcRel24)
|
||||
| (&Relocation::Rel14(addr), ObjRelocKind::PpcRel14)
|
||||
| (&Relocation::Sda21(addr), ObjRelocKind::PpcEmbSda21) => {
|
||||
(addr as i64 - sig_reloc.addend as i64) as u32
|
||||
}
|
||||
_ => bail!("Relocation mismatch: {:?} != {:?}", reloc, sig_reloc.kind),
|
||||
};
|
||||
let sig_symbol = &signature.symbols[sig_reloc.symbol];
|
||||
let target_symbol_idx = apply_symbol(obj, target, sig_symbol)?;
|
||||
let obj_reloc = ObjReloc {
|
||||
kind: sig_reloc.kind,
|
||||
address: reloc_addr as u64,
|
||||
target_symbol: target_symbol_idx,
|
||||
addend: sig_reloc.addend as i64,
|
||||
};
|
||||
// log::info!("Applying relocation {:#010X?}", obj_reloc);
|
||||
obj.sections[section_index].relocations.push(obj_reloc);
|
||||
}
|
||||
for reloc in &signature.relocations {
|
||||
let addr = addr + reloc.offset;
|
||||
if !tracker.relocations.contains_key(&addr) {
|
||||
let sig_symbol = &signature.symbols[reloc.symbol];
|
||||
bail!("Missing relocation @ {:#010X}: {:?} -> {:?}", addr, reloc, sig_symbol);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn compare_signature(existing: &mut FunctionSignature, new: &FunctionSignature) -> Result<()> {
|
||||
ensure!(
|
||||
existing.symbols.len() == new.symbols.len(),
|
||||
"Mismatched symbol count: {} != {}\n{:?}\n{:?}",
|
||||
new.symbols.len(),
|
||||
existing.symbols.len(),
|
||||
new.symbols,
|
||||
existing.symbols,
|
||||
);
|
||||
ensure!(
|
||||
existing.relocations.len() == new.relocations.len(),
|
||||
"Mismatched relocation count: {} != {}",
|
||||
new.relocations.len(),
|
||||
existing.relocations.len()
|
||||
);
|
||||
for (idx, (a, b)) in existing.symbols.iter_mut().zip(&new.symbols).enumerate() {
|
||||
if a != b {
|
||||
// If mismatched sections, clear
|
||||
if a.name == b.name
|
||||
&& a.size == b.size
|
||||
&& a.flags == b.flags
|
||||
&& a.kind == b.kind
|
||||
&& a.section != b.section
|
||||
{
|
||||
log::warn!("Clearing section for {} ({:?} != {:?})", a.name, a.section, b.section);
|
||||
a.section = None;
|
||||
} else if !a.name.starts_with('@') {
|
||||
log::error!("Symbol {} mismatch: {:?} != {:?}", idx, a, b);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (a, b) in existing.relocations.iter().zip(&new.relocations) {
|
||||
if a != b {
|
||||
log::error!("Relocation {} mismatch: {:?} != {:?}", a.offset, a, b);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn generate_signature(path: &Path, symbol_name: &str) -> Result<Option<FunctionSignature>> {
|
||||
let mut out_symbols: Vec<OutSymbol> = Vec::new();
|
||||
let mut out_relocs: Vec<OutReloc> = Vec::new();
|
||||
let mut symbol_map: BTreeMap<usize, usize> = BTreeMap::new();
|
||||
|
||||
let mut obj = process_elf(path)?;
|
||||
if obj.sda2_base.is_none()
|
||||
|| obj.sda_base.is_none()
|
||||
|| obj.stack_address.is_none()
|
||||
|| obj.stack_end.is_none()
|
||||
|| obj.db_stack_addr.is_none()
|
||||
{
|
||||
log::warn!(
|
||||
"Failed to locate all abs symbols {:#010X?} {:#010X?} {:#010X?} {:#010X?} {:#010X?} {:#010X?} {:#010X?}",
|
||||
obj.sda2_base,
|
||||
obj.sda_base,
|
||||
obj.stack_address,
|
||||
obj.stack_end,
|
||||
obj.db_stack_addr,
|
||||
obj.arena_hi,
|
||||
obj.arena_lo
|
||||
);
|
||||
return Ok(None);
|
||||
}
|
||||
let mut tracker = Tracker::new(&obj);
|
||||
// tracker.ignore_addresses.insert(0x80004000);
|
||||
for symbol in &obj.symbols {
|
||||
if symbol.kind != ObjSymbolKind::Function {
|
||||
continue;
|
||||
}
|
||||
if symbol.name != symbol_name && symbol.name != symbol_name.replace("TRK", "TRK_") {
|
||||
continue;
|
||||
}
|
||||
tracker.process_function(&obj, symbol)?;
|
||||
}
|
||||
tracker.apply(&mut obj, true)?; // true
|
||||
for symbol in &obj.symbols {
|
||||
if symbol.kind != ObjSymbolKind::Function {
|
||||
continue;
|
||||
}
|
||||
if symbol.name != symbol_name && symbol.name != symbol_name.replace("TRK", "TRK_") {
|
||||
continue;
|
||||
}
|
||||
let section_idx = symbol.section.unwrap();
|
||||
let section = &obj.sections[section_idx];
|
||||
let out_symbol_idx = out_symbols.len();
|
||||
out_symbols.push(OutSymbol {
|
||||
kind: symbol.kind,
|
||||
name: symbol.name.clone(),
|
||||
size: symbol.size as u32,
|
||||
flags: symbol.flags,
|
||||
section: Some(section.name.clone()),
|
||||
});
|
||||
// println!(
|
||||
// "Building signature for {} ({:#010X}-{:#010X})",
|
||||
// symbol.name,
|
||||
// symbol.address,
|
||||
// symbol.address + symbol.size
|
||||
// );
|
||||
let relocations = section.build_relocation_map()?;
|
||||
let mut instructions = section.data[(symbol.address - section.address) as usize
|
||||
..(symbol.address - section.address + symbol.size) as usize]
|
||||
.chunks_exact(4)
|
||||
.map(|c| (u32::from_be_bytes(c.try_into().unwrap()), !0u32))
|
||||
.collect::<Vec<(u32, u32)>>();
|
||||
for (idx, (ins, pat)) in instructions.iter_mut().enumerate() {
|
||||
let addr = (symbol.address as usize + idx * 4) as u32;
|
||||
if let Some(reloc) = relocations.get(&addr) {
|
||||
let symbol_idx = match symbol_map.entry(reloc.target_symbol) {
|
||||
btree_map::Entry::Vacant(e) => {
|
||||
let target = &obj.symbols[reloc.target_symbol];
|
||||
let symbol_idx = out_symbols.len();
|
||||
e.insert(symbol_idx);
|
||||
out_symbols.push(OutSymbol {
|
||||
kind: target.kind,
|
||||
name: target.name.clone(),
|
||||
size: if target.kind == ObjSymbolKind::Function {
|
||||
0
|
||||
} else {
|
||||
target.size as u32
|
||||
},
|
||||
flags: target.flags,
|
||||
section: target.section.map(|idx| obj.sections[idx].name.clone()),
|
||||
});
|
||||
symbol_idx
|
||||
}
|
||||
btree_map::Entry::Occupied(e) => *e.get(),
|
||||
};
|
||||
match reloc.kind {
|
||||
ObjRelocKind::Absolute => {
|
||||
*ins = 0;
|
||||
*pat = 0;
|
||||
}
|
||||
ObjRelocKind::PpcAddr16Hi
|
||||
| ObjRelocKind::PpcAddr16Ha
|
||||
| ObjRelocKind::PpcAddr16Lo => {
|
||||
*ins = *ins & !0xFFFF;
|
||||
*pat = !0xFFFF;
|
||||
}
|
||||
ObjRelocKind::PpcRel24 => {
|
||||
*ins = *ins & !0x3FFFFFC;
|
||||
*pat = !0x3FFFFFC;
|
||||
}
|
||||
ObjRelocKind::PpcRel14 => {
|
||||
*ins = *ins & !0xFFFC;
|
||||
*pat = !0xFFFC;
|
||||
}
|
||||
ObjRelocKind::PpcEmbSda21 => {
|
||||
*ins = *ins & !0x1FFFFF;
|
||||
*pat = !0x1FFFFF;
|
||||
}
|
||||
}
|
||||
out_relocs.push(OutReloc {
|
||||
offset: addr - (symbol.address as u32),
|
||||
kind: reloc.kind,
|
||||
symbol: symbol_idx,
|
||||
addend: reloc.addend as i32,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let mut data = vec![0u8; instructions.len() * 8];
|
||||
for (idx, &(ins, pat)) in instructions.iter().enumerate() {
|
||||
data[idx * 8..idx * 8 + 4].copy_from_slice(&ins.to_be_bytes());
|
||||
data[idx * 8 + 4..idx * 8 + 8].copy_from_slice(&pat.to_be_bytes());
|
||||
}
|
||||
|
||||
let encoded = STANDARD.encode(&data);
|
||||
let mut hasher = Sha1::new();
|
||||
hasher.update(&data);
|
||||
let hash = hasher.finalize();
|
||||
let mut hash_buf = [0u8; 40];
|
||||
let hash_str = base16ct::lower::encode_str(&hash, &mut hash_buf)
|
||||
.map_err(|e| anyhow!("Failed to encode hash: {e}"))?;
|
||||
return Ok(Some(FunctionSignature {
|
||||
symbol: 0,
|
||||
hash: hash_str.to_string(),
|
||||
signature: encoded,
|
||||
symbols: out_symbols,
|
||||
relocations: out_relocs,
|
||||
}));
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
347
src/obj/split.rs
Normal file
347
src/obj/split.rs
Normal file
@@ -0,0 +1,347 @@
|
||||
use std::{cmp::min, collections::HashMap};
|
||||
|
||||
use anyhow::{anyhow, bail, ensure, Result};
|
||||
|
||||
use crate::obj::{
|
||||
ObjArchitecture, ObjInfo, ObjKind, ObjReloc, ObjSection, ObjSectionKind, ObjSymbol,
|
||||
};
|
||||
|
||||
/// Split an executable object into relocatable objects.
|
||||
pub fn split_obj(obj: &ObjInfo) -> Result<Vec<ObjInfo>> {
|
||||
ensure!(obj.kind == ObjKind::Executable, "Expected executable object");
|
||||
|
||||
let mut objects: Vec<ObjInfo> = vec![];
|
||||
let mut object_symbols: Vec<Vec<Option<usize>>> = vec![];
|
||||
let mut name_to_obj: HashMap<String, usize> = HashMap::new();
|
||||
for unit in &obj.link_order {
|
||||
name_to_obj.insert(unit.clone(), objects.len());
|
||||
object_symbols.push(vec![None; obj.symbols.len()]);
|
||||
objects.push(ObjInfo {
|
||||
module_id: 0,
|
||||
kind: ObjKind::Relocatable,
|
||||
architecture: ObjArchitecture::PowerPc,
|
||||
name: unit.clone(),
|
||||
symbols: vec![],
|
||||
sections: vec![],
|
||||
entry: 0,
|
||||
sda2_base: None,
|
||||
sda_base: None,
|
||||
stack_address: None,
|
||||
stack_end: None,
|
||||
db_stack_addr: None,
|
||||
arena_lo: None,
|
||||
arena_hi: None,
|
||||
splits: Default::default(),
|
||||
named_sections: Default::default(),
|
||||
link_order: vec![],
|
||||
known_functions: Default::default(),
|
||||
unresolved_relocations: vec![],
|
||||
});
|
||||
}
|
||||
|
||||
for (section_idx, section) in obj.sections.iter().enumerate() {
|
||||
let mut current_address = section.address as u32;
|
||||
let mut section_end = (section.address + section.size) as u32;
|
||||
// if matches!(section.name.as_str(), "extab" | "extabindex") {
|
||||
// continue;
|
||||
// }
|
||||
// .ctors and .dtors end with a linker-generated null pointer,
|
||||
// adjust section size appropriately
|
||||
if matches!(section.name.as_str(), ".ctors" | ".dtors")
|
||||
&& section.data[section.data.len() - 4..] == [0u8; 4]
|
||||
{
|
||||
section_end -= 4;
|
||||
}
|
||||
let mut file_iter = obj
|
||||
.splits
|
||||
.range(current_address..)
|
||||
.flat_map(|(addr, v)| v.iter().map(move |u| (addr, u)))
|
||||
.peekable();
|
||||
|
||||
// Build address to relocation / address to symbol maps
|
||||
let relocations = section.build_relocation_map()?;
|
||||
let symbols = obj.build_symbol_map(section_idx)?;
|
||||
|
||||
loop {
|
||||
if current_address >= section_end {
|
||||
break;
|
||||
}
|
||||
|
||||
let (file_addr, unit) = match file_iter.next() {
|
||||
Some((&addr, unit)) => (addr, unit),
|
||||
None => bail!("No file found"),
|
||||
};
|
||||
ensure!(
|
||||
file_addr <= current_address,
|
||||
"Gap in files: {} @ {:#010X}, {} @ {:#010X}",
|
||||
section.name,
|
||||
section.address,
|
||||
unit,
|
||||
file_addr
|
||||
);
|
||||
let mut file_end = section_end;
|
||||
let mut dont_go_forward = false;
|
||||
if let Some(&(&next_addr, next_unit)) = file_iter.peek() {
|
||||
if file_addr == next_addr {
|
||||
log::warn!("Duplicating {} in {unit} and {next_unit}", section.name);
|
||||
dont_go_forward = true;
|
||||
file_end = obj
|
||||
.splits
|
||||
.range(current_address + 1..)
|
||||
.next()
|
||||
.map(|(&addr, _)| addr)
|
||||
.unwrap_or(section_end);
|
||||
} else {
|
||||
file_end = min(next_addr, section_end);
|
||||
}
|
||||
}
|
||||
|
||||
let file = name_to_obj
|
||||
.get(unit)
|
||||
.and_then(|&idx| objects.get_mut(idx))
|
||||
.ok_or_else(|| anyhow!("Unit '{unit}' not in link order"))?;
|
||||
let symbol_idxs = name_to_obj
|
||||
.get(unit)
|
||||
.and_then(|&idx| object_symbols.get_mut(idx))
|
||||
.ok_or_else(|| anyhow!("Unit '{unit}' not in link order"))?;
|
||||
|
||||
// Calculate & verify section alignment
|
||||
let mut align = default_section_align(section);
|
||||
if current_address & (align as u32 - 1) != 0 {
|
||||
log::warn!(
|
||||
"Alignment for {} {} expected {}, but starts at {:#010X}",
|
||||
unit,
|
||||
section.name,
|
||||
align,
|
||||
current_address
|
||||
);
|
||||
while align > 4 {
|
||||
align /= 2;
|
||||
if current_address & (align as u32 - 1) == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
ensure!(
|
||||
current_address & (align as u32 - 1) == 0,
|
||||
"Invalid alignment for split: {} {} {:#010X}",
|
||||
unit,
|
||||
section.name,
|
||||
current_address
|
||||
);
|
||||
|
||||
// Collect relocations; target_symbol will be updated later
|
||||
let out_relocations = relocations
|
||||
.range(current_address..file_end)
|
||||
.map(|(_, o)| ObjReloc {
|
||||
kind: o.kind,
|
||||
address: o.address - current_address as u64,
|
||||
target_symbol: o.target_symbol,
|
||||
addend: o.addend,
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Add section symbols
|
||||
let out_section_idx = file.sections.len();
|
||||
for &symbol_idx in symbols.range(current_address..file_end).flat_map(|(_, vec)| vec) {
|
||||
if symbol_idxs[symbol_idx].is_some() {
|
||||
continue; // should never happen?
|
||||
}
|
||||
let symbol = &obj.symbols[symbol_idx];
|
||||
symbol_idxs[symbol_idx] = Some(file.symbols.len());
|
||||
file.symbols.push(ObjSymbol {
|
||||
name: symbol.name.clone(),
|
||||
demangled_name: symbol.demangled_name.clone(),
|
||||
address: symbol.address - current_address as u64,
|
||||
section: Some(out_section_idx),
|
||||
size: symbol.size,
|
||||
size_known: symbol.size_known,
|
||||
flags: symbol.flags,
|
||||
kind: symbol.kind,
|
||||
});
|
||||
}
|
||||
|
||||
let data = match section.kind {
|
||||
ObjSectionKind::Bss => vec![],
|
||||
_ => section.data[(current_address as u64 - section.address) as usize
|
||||
..(file_end as u64 - section.address) as usize]
|
||||
.to_vec(),
|
||||
};
|
||||
let name = if let Some(name) = obj.named_sections.get(¤t_address) {
|
||||
name.clone()
|
||||
} else {
|
||||
section.name.clone()
|
||||
};
|
||||
file.sections.push(ObjSection {
|
||||
name,
|
||||
kind: section.kind,
|
||||
address: 0,
|
||||
size: file_end as u64 - current_address as u64,
|
||||
data,
|
||||
align,
|
||||
index: out_section_idx,
|
||||
elf_index: out_section_idx + 1,
|
||||
relocations: out_relocations,
|
||||
original_address: current_address as u64,
|
||||
file_offset: section.file_offset + (current_address as u64 - section.address),
|
||||
section_known: true,
|
||||
});
|
||||
|
||||
if !dont_go_forward {
|
||||
current_address = file_end;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update relocations
|
||||
for (obj_idx, out_obj) in objects.iter_mut().enumerate() {
|
||||
let symbol_idxs = &mut object_symbols[obj_idx];
|
||||
for section in &mut out_obj.sections {
|
||||
for reloc in &mut section.relocations {
|
||||
match symbol_idxs[reloc.target_symbol] {
|
||||
Some(out_sym_idx) => {
|
||||
reloc.target_symbol = out_sym_idx;
|
||||
}
|
||||
None => {
|
||||
// Extern
|
||||
let out_sym_idx = out_obj.symbols.len();
|
||||
let target_sym = &obj.symbols[reloc.target_symbol];
|
||||
symbol_idxs[reloc.target_symbol] = Some(out_sym_idx);
|
||||
out_obj.symbols.push(ObjSymbol {
|
||||
name: target_sym.name.clone(),
|
||||
demangled_name: target_sym.demangled_name.clone(),
|
||||
..Default::default()
|
||||
});
|
||||
reloc.target_symbol = out_sym_idx;
|
||||
if matches!(section.name.as_str(), "extab" | "extabindex") {
|
||||
log::warn!(
|
||||
"Extern relocation @ {:#010X} {} ({:#010X} {}): {:#010X} {}",
|
||||
reloc.address + section.original_address,
|
||||
section.name,
|
||||
section.original_address,
|
||||
out_obj.name,
|
||||
target_sym.address,
|
||||
target_sym.demangled_name.as_deref().unwrap_or(&target_sym.name)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Strip linker generated symbols
|
||||
for obj in &mut objects {
|
||||
for symbol in &mut obj.symbols {
|
||||
if is_skip_symbol(&symbol.name) {
|
||||
if symbol.section.is_some() {
|
||||
log::debug!("Externing {:?} in {}", symbol, obj.name);
|
||||
*symbol = ObjSymbol {
|
||||
name: symbol.name.clone(),
|
||||
demangled_name: symbol.demangled_name.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
}
|
||||
} else if is_linker_symbol(&symbol.name) {
|
||||
if let Some(section_idx) = symbol.section {
|
||||
log::debug!("Skipping {:?} in {}", symbol, obj.name);
|
||||
let section = &mut obj.sections[section_idx];
|
||||
// TODO assuming end of file
|
||||
section.size -= symbol.size;
|
||||
section.data.truncate(section.data.len() - symbol.size as usize);
|
||||
*symbol = ObjSymbol {
|
||||
name: symbol.name.clone(),
|
||||
demangled_name: symbol.demangled_name.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(objects)
|
||||
}
|
||||
|
||||
/// mwld doesn't preserve the original section alignment values
|
||||
fn default_section_align(section: &ObjSection) -> u64 {
|
||||
match section.kind {
|
||||
ObjSectionKind::Code => 4,
|
||||
_ => match section.name.as_str() {
|
||||
".ctors" | ".dtors" | "extab" | "extabindex" => 4,
|
||||
_ => 8,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Linker-generated symbols to extern
|
||||
#[inline]
|
||||
fn is_skip_symbol(name: &str) -> bool {
|
||||
matches!(
|
||||
name,
|
||||
"_ctors"
|
||||
| "_dtors"
|
||||
| "_f_init"
|
||||
| "_f_init_rom"
|
||||
| "_e_init"
|
||||
| "_fextab"
|
||||
| "_fextab_rom"
|
||||
| "_eextab"
|
||||
| "_fextabindex"
|
||||
| "_fextabindex_rom"
|
||||
| "_eextabindex"
|
||||
| "_f_text"
|
||||
| "_f_text_rom"
|
||||
| "_e_text"
|
||||
| "_f_ctors"
|
||||
| "_f_ctors_rom"
|
||||
| "_e_ctors"
|
||||
| "_f_dtors"
|
||||
| "_f_dtors_rom"
|
||||
| "_e_dtors"
|
||||
| "_f_rodata"
|
||||
| "_f_rodata_rom"
|
||||
| "_e_rodata"
|
||||
| "_f_data"
|
||||
| "_f_data_rom"
|
||||
| "_e_data"
|
||||
| "_f_sdata"
|
||||
| "_f_sdata_rom"
|
||||
| "_e_sdata"
|
||||
| "_f_sbss"
|
||||
| "_f_sbss_rom"
|
||||
| "_e_sbss"
|
||||
| "_f_sdata2"
|
||||
| "_f_sdata2_rom"
|
||||
| "_e_sdata2"
|
||||
| "_f_sbss2"
|
||||
| "_f_sbss2_rom"
|
||||
| "_e_sbss2"
|
||||
| "_f_bss"
|
||||
| "_f_bss_rom"
|
||||
| "_e_bss"
|
||||
| "_f_stack"
|
||||
| "_f_stack_rom"
|
||||
| "_e_stack"
|
||||
| "_stack_addr"
|
||||
| "_stack_end"
|
||||
| "_db_stack_addr"
|
||||
| "_db_stack_end"
|
||||
| "_heap_addr"
|
||||
| "_heap_end"
|
||||
| "_nbfunctions"
|
||||
| "SIZEOF_HEADERS"
|
||||
| "_SDA_BASE_"
|
||||
| "_SDA2_BASE_"
|
||||
| "_ABS_SDA_BASE_"
|
||||
| "_ABS_SDA2_BASE_"
|
||||
)
|
||||
}
|
||||
|
||||
/// Linker generated symbols to strip entirely
|
||||
#[inline]
|
||||
fn is_linker_symbol(name: &str) -> bool {
|
||||
matches!(
|
||||
name,
|
||||
"_eti_init_info" | "_rom_copy_info" | "_bss_init_info" | "_ctors$99" | "_dtors$99"
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user