mirror of
https://github.com/encounter/decomp-toolkit.git
synced 2025-07-26 06:55:36 +00:00
358 lines
13 KiB
Rust
358 lines
13 KiB
Rust
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 section.name.as_str() == "extabindex" {
|
|
let (target_addr, target_unit) = obj
|
|
.splits
|
|
.range(..=target_sym.address as u32)
|
|
.map(|(addr, v)| (*addr, v.last().unwrap()))
|
|
.last()
|
|
.unwrap();
|
|
let target_section = &obj.section_at(target_addr)?.name;
|
|
log::warn!(
|
|
"Extern relocation @ {:#010X}\n\tSource object: {}:{:#010X} {}\n\tTarget object: {}:{:#010X} {}\n\tTarget symbol: {:#010X} {}\n",
|
|
reloc.address + section.original_address,
|
|
section.name,
|
|
section.original_address,
|
|
out_obj.name,
|
|
target_section,
|
|
target_addr,
|
|
target_unit,
|
|
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"
|
|
)
|
|
}
|