764 lines
26 KiB
Rust
764 lines
26 KiB
Rust
use std::{
|
|
collections::BTreeMap,
|
|
io,
|
|
io::{Cursor, Read, Seek},
|
|
};
|
|
|
|
use anyhow::{anyhow, bail, ensure, Result};
|
|
|
|
use crate::{
|
|
analysis::cfa::{locate_sda_bases, SectionAddress},
|
|
array_ref,
|
|
obj::{
|
|
ObjArchitecture, ObjInfo, ObjKind, ObjSection, ObjSectionKind, ObjSymbol, ObjSymbolFlagSet,
|
|
ObjSymbolFlags, ObjSymbolKind,
|
|
},
|
|
util::{
|
|
alf::{AlfFile, AlfSymbol, ALF_MAGIC},
|
|
reader::{skip_bytes, Endian, FromReader},
|
|
},
|
|
};
|
|
|
|
const MAX_TEXT_SECTIONS: usize = 7;
|
|
const MAX_DATA_SECTIONS: usize = 11;
|
|
const MAX_ROM_COPY_INFO_SIZE: usize = (MAX_TEXT_SECTIONS + MAX_DATA_SECTIONS + 1) * 3 * 4; // num sections * 3 entries * u32
|
|
const MAX_BSS_INIT_INFO_SIZE: usize = (MAX_DATA_SECTIONS + 1) * 2 * 4; // num sections * 2 entries * u32
|
|
const ETI_INIT_INFO_SIZE: usize = 16; // eti_start, eti_end, code_start, code_size
|
|
|
|
/// Unified trait for DOL and ALF files
|
|
pub trait DolLike {
|
|
fn sections(&self) -> &[DolSection];
|
|
|
|
fn symbols(&self) -> &[AlfSymbol] { &[] }
|
|
|
|
fn entry_point(&self) -> u32;
|
|
|
|
fn has_unified_bss(&self) -> bool;
|
|
|
|
fn section_by_address(&self, addr: u32) -> Option<&DolSection> {
|
|
self.sections()
|
|
.iter()
|
|
.find(|section| addr >= section.address && addr < section.address + section.size)
|
|
}
|
|
|
|
fn virtual_data_at<'a>(&self, buf: &'a [u8], addr: u32, size: u32) -> Result<&'a [u8]> {
|
|
let section = self
|
|
.section_by_address(addr)
|
|
.ok_or_else(|| anyhow!("Failed to locate section for address {:#010X}", addr))?;
|
|
let offset = addr - section.address;
|
|
ensure!(
|
|
offset + size <= section.size,
|
|
"Invalid virtual data range {:#010X}-{:#010X} (section: {:#010X}-{:#010X})",
|
|
addr,
|
|
addr + size,
|
|
section.address,
|
|
section.address + section.size
|
|
);
|
|
let offset = section.file_offset as usize + offset as usize;
|
|
Ok(&buf[offset..offset + size as usize])
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub enum DolSectionKind {
|
|
Text,
|
|
Data,
|
|
Bss,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct DolSection {
|
|
pub address: u32,
|
|
pub file_offset: u32,
|
|
pub data_size: u32,
|
|
pub size: u32,
|
|
pub kind: DolSectionKind,
|
|
// TODO remove
|
|
pub index: usize,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct DolFile {
|
|
pub header: DolHeader,
|
|
pub sections: Vec<DolSection>,
|
|
}
|
|
|
|
impl FromReader for DolFile {
|
|
type Args = ();
|
|
|
|
const STATIC_SIZE: usize = DolHeader::STATIC_SIZE;
|
|
|
|
fn from_reader_args<R>(reader: &mut R, e: Endian, _args: Self::Args) -> io::Result<Self>
|
|
where R: Read + Seek + ?Sized {
|
|
let header = DolHeader::from_reader(reader, e)?;
|
|
let mut sections = Vec::with_capacity(header.text_sizes.len() + header.data_sizes.len());
|
|
for (idx, &size) in header.text_sizes.iter().enumerate() {
|
|
if size == 0 {
|
|
continue;
|
|
}
|
|
sections.push(DolSection {
|
|
address: header.text_addrs[idx],
|
|
file_offset: header.text_offs[idx],
|
|
data_size: size,
|
|
size,
|
|
kind: DolSectionKind::Text,
|
|
index: sections.len(),
|
|
});
|
|
}
|
|
for (idx, &size) in header.data_sizes.iter().enumerate() {
|
|
if size == 0 {
|
|
continue;
|
|
}
|
|
sections.push(DolSection {
|
|
address: header.data_addrs[idx],
|
|
file_offset: header.data_offs[idx],
|
|
data_size: size,
|
|
size,
|
|
kind: DolSectionKind::Data,
|
|
index: sections.len(),
|
|
});
|
|
}
|
|
sections.push(DolSection {
|
|
address: header.bss_addr,
|
|
file_offset: 0,
|
|
data_size: 0,
|
|
size: header.bss_size,
|
|
kind: DolSectionKind::Bss,
|
|
index: sections.len(),
|
|
});
|
|
Ok(Self { header, sections })
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct DolHeader {
|
|
pub text_offs: [u32; MAX_TEXT_SECTIONS],
|
|
pub data_offs: [u32; MAX_DATA_SECTIONS],
|
|
pub text_addrs: [u32; MAX_TEXT_SECTIONS],
|
|
pub data_addrs: [u32; MAX_DATA_SECTIONS],
|
|
pub text_sizes: [u32; MAX_TEXT_SECTIONS],
|
|
pub data_sizes: [u32; MAX_DATA_SECTIONS],
|
|
pub bss_addr: u32,
|
|
pub bss_size: u32,
|
|
pub entry_point: u32,
|
|
}
|
|
|
|
impl FromReader for DolHeader {
|
|
type Args = ();
|
|
|
|
const STATIC_SIZE: usize = 0x100;
|
|
|
|
fn from_reader_args<R>(reader: &mut R, e: Endian, _args: Self::Args) -> io::Result<Self>
|
|
where R: Read + Seek + ?Sized {
|
|
let result = Self {
|
|
text_offs: <_>::from_reader(reader, e)?,
|
|
data_offs: <_>::from_reader(reader, e)?,
|
|
text_addrs: <_>::from_reader(reader, e)?,
|
|
data_addrs: <_>::from_reader(reader, e)?,
|
|
text_sizes: <_>::from_reader(reader, e)?,
|
|
data_sizes: <_>::from_reader(reader, e)?,
|
|
bss_addr: <_>::from_reader(reader, e)?,
|
|
bss_size: <_>::from_reader(reader, e)?,
|
|
entry_point: <_>::from_reader(reader, e)?,
|
|
};
|
|
skip_bytes::<0x1C, _>(reader)?; // padding
|
|
Ok(result)
|
|
}
|
|
}
|
|
|
|
impl DolLike for DolFile {
|
|
fn sections(&self) -> &[DolSection] { &self.sections }
|
|
|
|
fn entry_point(&self) -> u32 { self.header.entry_point }
|
|
|
|
fn has_unified_bss(&self) -> bool { true }
|
|
}
|
|
|
|
fn read_u32(buf: &[u8], dol: &dyn DolLike, addr: u32) -> Result<u32> {
|
|
Ok(u32::from_be_bytes(dol.virtual_data_at(buf, addr, 4)?.try_into()?))
|
|
}
|
|
|
|
pub fn process_dol(buf: &[u8], name: &str) -> Result<ObjInfo> {
|
|
let mut reader = Cursor::new(buf);
|
|
let dol: Box<dyn DolLike> = if buf.len() > 4 && *array_ref!(buf, 0, 4) == ALF_MAGIC {
|
|
Box::new(AlfFile::from_reader(&mut reader, Endian::Little)?)
|
|
} else {
|
|
Box::new(DolFile::from_reader(&mut reader, Endian::Big)?)
|
|
};
|
|
|
|
// Locate _rom_copy_info
|
|
let first_rom_section = dol
|
|
.sections()
|
|
.iter()
|
|
.find(|section| section.kind != DolSectionKind::Bss)
|
|
.ok_or_else(|| anyhow!("Failed to locate first rom section"))?;
|
|
let init_section = dol
|
|
.section_by_address(dol.entry_point())
|
|
.ok_or_else(|| anyhow!("Failed to locate .init section"))?;
|
|
let rom_copy_info_addr = {
|
|
let mut addr = init_section.address + init_section.size
|
|
- MAX_ROM_COPY_INFO_SIZE as u32
|
|
- MAX_BSS_INIT_INFO_SIZE as u32;
|
|
loop {
|
|
let value = read_u32(buf, dol.as_ref(), addr)?;
|
|
if value == first_rom_section.address {
|
|
log::debug!("Found _rom_copy_info @ {addr:#010X}");
|
|
break Some(addr);
|
|
}
|
|
addr += 4;
|
|
if addr >= init_section.address + init_section.size {
|
|
log::warn!("Failed to locate _rom_copy_info");
|
|
break None;
|
|
}
|
|
}
|
|
};
|
|
|
|
// Process _rom_copy_info
|
|
let mut rom_sections = BTreeMap::<u32, u32>::new();
|
|
let rom_copy_info_end = match rom_copy_info_addr {
|
|
Some(mut addr) => loop {
|
|
let rom = read_u32(buf, dol.as_ref(), addr)?;
|
|
let copy = read_u32(buf, dol.as_ref(), addr + 4)?;
|
|
ensure!(
|
|
rom == copy,
|
|
"Unsupported section: ROM address {rom:#010X} != copy address {copy:#010X}",
|
|
);
|
|
let size = read_u32(buf, dol.as_ref(), addr + 8)?;
|
|
addr += 12;
|
|
if size == 0 {
|
|
log::debug!("Found _rom_copy_info end @ {addr:#010X}");
|
|
break Some(addr);
|
|
}
|
|
if addr >= init_section.address + init_section.size {
|
|
log::warn!("Failed to locate _rom_copy_info end");
|
|
break None;
|
|
}
|
|
rom_sections.insert(rom, size);
|
|
},
|
|
None => None,
|
|
};
|
|
|
|
// Locate _bss_init_info
|
|
let bss_section = dol
|
|
.sections()
|
|
.iter()
|
|
.find(|section| section.kind == DolSectionKind::Bss)
|
|
.ok_or_else(|| anyhow!("Failed to locate BSS section"))?;
|
|
let bss_init_info_addr = match rom_copy_info_end {
|
|
Some(mut addr) => loop {
|
|
let value = read_u32(buf, dol.as_ref(), addr)?;
|
|
if value == bss_section.address {
|
|
log::debug!("Found _bss_init_info @ {addr:#010X}");
|
|
break Some(addr);
|
|
}
|
|
addr += 4;
|
|
if addr >= init_section.address + init_section.size {
|
|
log::warn!("Failed to locate _bss_init_info");
|
|
break None;
|
|
}
|
|
},
|
|
None => None,
|
|
};
|
|
|
|
// Process _bss_init_info
|
|
let mut bss_sections = BTreeMap::<u32, u32>::new();
|
|
let bss_init_info_end = match bss_init_info_addr {
|
|
Some(mut addr) => loop {
|
|
let rom = read_u32(buf, dol.as_ref(), addr)?;
|
|
let size = read_u32(buf, dol.as_ref(), addr + 4)?;
|
|
addr += 8;
|
|
if size == 0 {
|
|
log::debug!("Found _bss_init_info end @ {addr:#010X}");
|
|
break Some(addr);
|
|
}
|
|
if addr >= init_section.address + init_section.size {
|
|
log::warn!("Failed to locate _bss_init_info end");
|
|
break None;
|
|
}
|
|
bss_sections.insert(rom, size);
|
|
},
|
|
None => None,
|
|
};
|
|
|
|
// Locate _eti_init_info
|
|
let num_text_sections =
|
|
dol.sections().iter().filter(|section| section.kind == DolSectionKind::Text).count();
|
|
let mut eti_entries: Vec<EtiEntry> = Vec::new();
|
|
let mut eti_init_info_range: Option<(u32, u32)> = None;
|
|
let mut extab_section: Option<usize> = None;
|
|
let mut extabindex_section: Option<usize> = None;
|
|
'outer: for dol_section in
|
|
dol.sections().iter().filter(|section| section.kind == DolSectionKind::Data)
|
|
{
|
|
// Use section size from _rom_copy_info
|
|
let dol_section_size = match rom_sections.get(&dol_section.address) {
|
|
Some(&size) => size,
|
|
None => dol_section.size,
|
|
};
|
|
let dol_section_end = dol_section.address + dol_section_size;
|
|
|
|
let eti_init_info_addr = {
|
|
let mut addr = dol_section_end - (ETI_INIT_INFO_SIZE * (num_text_sections + 1)) as u32;
|
|
loop {
|
|
let eti_init_info = read_eti_init_info(buf, dol.as_ref(), addr)?;
|
|
if validate_eti_init_info(
|
|
dol.as_ref(),
|
|
&eti_init_info,
|
|
dol_section,
|
|
dol_section_end,
|
|
&rom_sections,
|
|
)? {
|
|
log::debug!("Found _eti_init_info @ {addr:#010X}");
|
|
break addr;
|
|
}
|
|
addr += 4;
|
|
if addr > dol_section_end - ETI_INIT_INFO_SIZE as u32 {
|
|
continue 'outer;
|
|
}
|
|
}
|
|
};
|
|
|
|
let eti_init_info_end = {
|
|
let mut addr = eti_init_info_addr;
|
|
loop {
|
|
let eti_init_info = read_eti_init_info(buf, dol.as_ref(), addr)?;
|
|
addr += 16;
|
|
if eti_init_info.is_zero() {
|
|
break;
|
|
}
|
|
if addr > dol_section_end - ETI_INIT_INFO_SIZE as u32 {
|
|
bail!(
|
|
"Failed to locate _eti_init_info end (start @ {:#010X})",
|
|
eti_init_info_addr
|
|
);
|
|
}
|
|
if !validate_eti_init_info(
|
|
dol.as_ref(),
|
|
&eti_init_info,
|
|
dol_section,
|
|
dol_section_end,
|
|
&rom_sections,
|
|
)? {
|
|
bail!("Invalid _eti_init_info entry: {:#010X?}", eti_init_info);
|
|
}
|
|
for addr in (eti_init_info.eti_start..eti_init_info.eti_end).step_by(12) {
|
|
let eti_entry = read_eti_entry(buf, dol.as_ref(), addr)?;
|
|
let entry_section =
|
|
dol.section_by_address(eti_entry.extab_addr).ok_or_else(|| {
|
|
anyhow!(
|
|
"Failed to locate section for extab address {:#010X}",
|
|
eti_entry.extab_addr
|
|
)
|
|
})?;
|
|
if let Some(extab_section) = extab_section {
|
|
ensure!(
|
|
entry_section.index == extab_section,
|
|
"Mismatched sections for extabindex entries: {} != {}",
|
|
entry_section.index,
|
|
extab_section
|
|
);
|
|
} else {
|
|
extab_section = Some(entry_section.index);
|
|
}
|
|
eti_entries.push(eti_entry);
|
|
}
|
|
}
|
|
log::debug!("Found _eti_init_info end @ {addr:#010X}");
|
|
addr
|
|
};
|
|
|
|
eti_init_info_range = Some((eti_init_info_addr, eti_init_info_end));
|
|
extabindex_section = Some(dol_section.index);
|
|
break;
|
|
}
|
|
if eti_init_info_range.is_none() {
|
|
log::debug!("Failed to locate _eti_init_info");
|
|
}
|
|
|
|
// Add text and data sections
|
|
let mut sections = vec![];
|
|
for dol_section in dol.sections().iter() {
|
|
// We'll split .bss later
|
|
if dol_section.kind == DolSectionKind::Bss && dol.has_unified_bss() {
|
|
continue;
|
|
}
|
|
|
|
let (name, kind, known) = match dol_section.index {
|
|
idx if idx == init_section.index => (".init".to_string(), ObjSectionKind::Code, true),
|
|
idx if Some(idx) == extab_section => {
|
|
("extab".to_string(), ObjSectionKind::ReadOnlyData, true)
|
|
}
|
|
idx if Some(idx) == extabindex_section => {
|
|
("extabindex".to_string(), ObjSectionKind::ReadOnlyData, true)
|
|
}
|
|
_ if num_text_sections == 2 && dol_section.kind == DolSectionKind::Text => {
|
|
(".text".to_string(), ObjSectionKind::Code, true)
|
|
}
|
|
idx => match dol_section.kind {
|
|
DolSectionKind::Text => (format!(".text{idx}"), ObjSectionKind::Code, false),
|
|
DolSectionKind::Data => (format!(".data{idx}"), ObjSectionKind::Data, false),
|
|
DolSectionKind::Bss => (format!(".bss{}", idx), ObjSectionKind::Bss, false),
|
|
},
|
|
};
|
|
|
|
let (size, data): (u32, &[u8]) = if kind == ObjSectionKind::Bss {
|
|
(dol_section.size, &[])
|
|
} else {
|
|
// Use section size from _rom_copy_info
|
|
let size = match rom_sections.get(&dol_section.address) {
|
|
Some(&size) => size,
|
|
None => {
|
|
if !rom_sections.is_empty() {
|
|
log::warn!(
|
|
"Section {} ({:#010X}) doesn't exist in _rom_copy_info",
|
|
dol_section.index,
|
|
dol_section.address
|
|
);
|
|
}
|
|
dol_section.size
|
|
}
|
|
};
|
|
(size, dol.virtual_data_at(buf, dol_section.address, size)?)
|
|
};
|
|
|
|
sections.push(ObjSection {
|
|
name,
|
|
kind,
|
|
address: dol_section.address as u64,
|
|
size: size as u64,
|
|
data: data.to_vec(),
|
|
align: 0,
|
|
elf_index: 0,
|
|
relocations: Default::default(),
|
|
original_address: 0,
|
|
file_offset: dol_section.file_offset as u64,
|
|
section_known: known,
|
|
splits: Default::default(),
|
|
});
|
|
}
|
|
|
|
if dol.has_unified_bss() {
|
|
// Add BSS sections from _bss_init_info
|
|
for (idx, (&addr, &size)) in bss_sections.iter().enumerate() {
|
|
ensure!(
|
|
addr >= bss_section.address
|
|
&& addr < bss_section.address + bss_section.size
|
|
&& addr + size <= bss_section.address + bss_section.size,
|
|
"Invalid BSS range {:#010X}-{:#010X} (DOL BSS: {:#010X}-{:#010X})",
|
|
addr,
|
|
addr + size,
|
|
bss_section.address,
|
|
bss_section.address + bss_section.size
|
|
);
|
|
|
|
sections.push(ObjSection {
|
|
name: format!(".bss{}", idx),
|
|
kind: ObjSectionKind::Bss,
|
|
address: addr as u64,
|
|
size: size as u64,
|
|
data: vec![],
|
|
align: 0,
|
|
elf_index: 0,
|
|
relocations: Default::default(),
|
|
original_address: 0,
|
|
file_offset: 0,
|
|
section_known: false,
|
|
splits: Default::default(),
|
|
});
|
|
}
|
|
|
|
// Sort sections by address ascending
|
|
sections.sort_by_key(|s| s.address);
|
|
}
|
|
|
|
// Apply section indices
|
|
let mut init_section_index = None;
|
|
for (idx, section) in sections.iter_mut().enumerate() {
|
|
match section.name.as_str() {
|
|
".init" => {
|
|
init_section_index = Some(idx);
|
|
}
|
|
"extab" => {
|
|
extab_section = Some(idx);
|
|
}
|
|
"extabindex" => {
|
|
extabindex_section = Some(idx);
|
|
}
|
|
_ => {}
|
|
}
|
|
// Assume the original ELF section index is +1
|
|
// ELF files start with a NULL section
|
|
section.elf_index = idx + 1;
|
|
}
|
|
|
|
// Create object
|
|
let mut obj = ObjInfo::new(
|
|
ObjKind::Executable,
|
|
ObjArchitecture::PowerPc,
|
|
name.to_string(),
|
|
vec![],
|
|
sections,
|
|
);
|
|
obj.entry = Some(dol.entry_point() as u64);
|
|
|
|
// Generate _rom_copy_info symbol
|
|
if let (Some(rom_copy_info_addr), Some(rom_copy_info_end)) =
|
|
(rom_copy_info_addr, rom_copy_info_end)
|
|
{
|
|
obj.add_symbol(
|
|
ObjSymbol {
|
|
name: "_rom_copy_info".to_string(),
|
|
address: rom_copy_info_addr as u64,
|
|
section: init_section_index,
|
|
size: (rom_copy_info_end - rom_copy_info_addr) as u64,
|
|
size_known: true,
|
|
flags: ObjSymbolFlagSet(ObjSymbolFlags::Global.into()),
|
|
kind: ObjSymbolKind::Object,
|
|
..Default::default()
|
|
},
|
|
true,
|
|
)?;
|
|
}
|
|
|
|
// Generate _bss_init_info symbol
|
|
if let (Some(bss_init_info_addr), Some(bss_init_info_end)) =
|
|
(bss_init_info_addr, bss_init_info_end)
|
|
{
|
|
obj.add_symbol(
|
|
ObjSymbol {
|
|
name: "_bss_init_info".to_string(),
|
|
address: bss_init_info_addr as u64,
|
|
section: init_section_index,
|
|
size: (bss_init_info_end - bss_init_info_addr) as u64,
|
|
size_known: true,
|
|
flags: ObjSymbolFlagSet(ObjSymbolFlags::Global.into()),
|
|
kind: ObjSymbolKind::Object,
|
|
..Default::default()
|
|
},
|
|
true,
|
|
)?;
|
|
}
|
|
|
|
// Generate _eti_init_info symbol
|
|
if let Some((eti_init_info_addr, eti_init_info_end)) = eti_init_info_range {
|
|
obj.add_symbol(
|
|
ObjSymbol {
|
|
name: "_eti_init_info".to_string(),
|
|
address: eti_init_info_addr as u64,
|
|
section: extabindex_section,
|
|
size: (eti_init_info_end - eti_init_info_addr) as u64,
|
|
size_known: true,
|
|
flags: ObjSymbolFlagSet(ObjSymbolFlags::Global.into()),
|
|
kind: ObjSymbolKind::Object,
|
|
..Default::default()
|
|
},
|
|
true,
|
|
)?;
|
|
}
|
|
|
|
// Generate symbols for extab & extabindex entries
|
|
if let (Some(extabindex_section_index), Some(extab_section_index)) =
|
|
(extabindex_section, extab_section)
|
|
{
|
|
let extab_section = &obj.sections[extab_section_index];
|
|
let extab_section_address = extab_section.address;
|
|
let extab_section_size = extab_section.size;
|
|
|
|
for entry in &eti_entries {
|
|
// Add functions from extabindex entries as known function bounds
|
|
let (section_index, _) = obj.sections.at_address(entry.function).map_err(|_| {
|
|
anyhow!(
|
|
"Failed to locate section for function {:#010X} (referenced from extabindex entry {:#010X})",
|
|
entry.function,
|
|
entry.address,
|
|
)
|
|
})?;
|
|
let addr = SectionAddress::new(section_index, entry.function);
|
|
if let Some(Some(old_value)) =
|
|
obj.known_functions.insert(addr, Some(entry.function_size))
|
|
{
|
|
if old_value != entry.function_size {
|
|
log::warn!(
|
|
"Conflicting sizes for {:#010X}: {:#X} != {:#X}",
|
|
entry.function,
|
|
entry.function_size,
|
|
old_value
|
|
);
|
|
}
|
|
}
|
|
obj.add_symbol(
|
|
ObjSymbol {
|
|
name: format!("@eti_{:08X}", entry.address),
|
|
address: entry.address as u64,
|
|
section: Some(extabindex_section_index),
|
|
size: 12,
|
|
size_known: true,
|
|
flags: ObjSymbolFlagSet(ObjSymbolFlags::Local | ObjSymbolFlags::Hidden),
|
|
kind: ObjSymbolKind::Object,
|
|
..Default::default()
|
|
},
|
|
false,
|
|
)?;
|
|
}
|
|
|
|
let mut entry_iter = eti_entries.iter().peekable();
|
|
loop {
|
|
let (addr, size) = match (entry_iter.next(), entry_iter.peek()) {
|
|
(Some(a), Some(&b)) => (a.extab_addr, b.extab_addr - a.extab_addr),
|
|
(Some(a), None) => (
|
|
a.extab_addr,
|
|
(extab_section_address + extab_section_size) as u32 - a.extab_addr,
|
|
),
|
|
_ => break,
|
|
};
|
|
obj.add_symbol(
|
|
ObjSymbol {
|
|
name: format!("@etb_{:08X}", addr),
|
|
address: addr as u64,
|
|
section: Some(extab_section_index),
|
|
size: size as u64,
|
|
size_known: true,
|
|
flags: ObjSymbolFlagSet(ObjSymbolFlags::Local | ObjSymbolFlags::Hidden),
|
|
kind: ObjSymbolKind::Object,
|
|
..Default::default()
|
|
},
|
|
false,
|
|
)?;
|
|
}
|
|
}
|
|
|
|
// Add .ctors and .dtors functions to known functions if they exist
|
|
for (_, section) in obj.sections.iter() {
|
|
if section.size & 3 != 0 {
|
|
continue;
|
|
}
|
|
let mut entries = vec![];
|
|
let mut current_addr = section.address as u32;
|
|
for chunk in section.data.chunks_exact(4) {
|
|
let addr = u32::from_be_bytes(chunk.try_into()?);
|
|
if addr == 0 || addr & 3 != 0 {
|
|
break;
|
|
}
|
|
let Ok((section_index, section)) = obj.sections.at_address(addr) else {
|
|
break;
|
|
};
|
|
if section.kind != ObjSectionKind::Code {
|
|
break;
|
|
}
|
|
entries.push(SectionAddress::new(section_index, addr));
|
|
current_addr += 4;
|
|
}
|
|
// .ctors and .dtors end with a null pointer
|
|
if current_addr != (section.address + section.size) as u32 - 4
|
|
|| section.data_range(current_addr, 0)?.iter().any(|&b| b != 0)
|
|
{
|
|
continue;
|
|
}
|
|
obj.known_functions.extend(entries.into_iter().map(|addr| (addr, None)));
|
|
}
|
|
|
|
// Locate _SDA2_BASE_ & _SDA_BASE_
|
|
match locate_sda_bases(&mut obj) {
|
|
Ok(true) => {
|
|
let sda2_base = obj.sda2_base.unwrap();
|
|
let sda_base = obj.sda_base.unwrap();
|
|
obj.add_symbol(
|
|
ObjSymbol {
|
|
name: "_SDA2_BASE_".to_string(),
|
|
address: sda2_base as u64,
|
|
size_known: true,
|
|
flags: ObjSymbolFlagSet(ObjSymbolFlags::Global.into()),
|
|
..Default::default()
|
|
},
|
|
true,
|
|
)?;
|
|
obj.add_symbol(
|
|
ObjSymbol {
|
|
name: "_SDA_BASE_".to_string(),
|
|
address: sda_base as u64,
|
|
size_known: true,
|
|
flags: ObjSymbolFlagSet(ObjSymbolFlags::Global.into()),
|
|
..Default::default()
|
|
},
|
|
true,
|
|
)?;
|
|
}
|
|
Ok(false) => {
|
|
log::warn!("Unable to locate SDA bases");
|
|
}
|
|
Err(e) => {
|
|
log::warn!("Failed to locate SDA bases: {:?}", e);
|
|
}
|
|
}
|
|
|
|
// Apply ALF symbols
|
|
for symbol in dol.symbols() {
|
|
obj.add_symbol(symbol.to_obj_symbol()?, true)?;
|
|
}
|
|
|
|
Ok(obj)
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
struct EtiInitInfo {
|
|
eti_start: u32,
|
|
eti_end: u32,
|
|
code_start: u32,
|
|
code_size: u32,
|
|
}
|
|
|
|
impl EtiInitInfo {
|
|
#[inline]
|
|
fn is_zero(&self) -> bool {
|
|
self.eti_start == 0 && self.eti_end == 0 && self.code_start == 0 && self.code_size == 0
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
struct EtiEntry {
|
|
address: u32,
|
|
function: u32,
|
|
function_size: u32,
|
|
extab_addr: u32,
|
|
}
|
|
|
|
fn read_eti_init_info(buf: &[u8], dol: &dyn DolLike, addr: u32) -> Result<EtiInitInfo> {
|
|
let eti_start = read_u32(buf, dol, addr)?;
|
|
let eti_end = read_u32(buf, dol, addr + 4)?;
|
|
let code_start = read_u32(buf, dol, addr + 8)?;
|
|
let code_size = read_u32(buf, dol, addr + 12)?;
|
|
Ok(EtiInitInfo { eti_start, eti_end, code_start, code_size })
|
|
}
|
|
|
|
fn read_eti_entry(buf: &[u8], dol: &dyn DolLike, address: u32) -> Result<EtiEntry> {
|
|
let function = read_u32(buf, dol, address)?;
|
|
let function_size = read_u32(buf, dol, address + 4)?;
|
|
let extab_addr = read_u32(buf, dol, address + 8)?;
|
|
Ok(EtiEntry { address, function, function_size, extab_addr })
|
|
}
|
|
|
|
fn validate_eti_init_info(
|
|
dol: &dyn DolLike,
|
|
eti_init_info: &EtiInitInfo,
|
|
eti_section: &DolSection,
|
|
eti_section_end: u32,
|
|
rom_sections: &BTreeMap<u32, u32>,
|
|
) -> Result<bool> {
|
|
if eti_init_info.eti_start >= eti_section.address
|
|
&& eti_init_info.eti_start < eti_section_end
|
|
&& eti_init_info.eti_end >= eti_section.address
|
|
&& eti_init_info.eti_end < eti_section_end
|
|
{
|
|
if let Some(code_section) = dol.section_by_address(eti_init_info.code_start) {
|
|
let code_section_size = match rom_sections.get(&code_section.address) {
|
|
Some(&size) => size,
|
|
None => code_section.size,
|
|
};
|
|
if eti_init_info.code_size <= code_section_size {
|
|
return Ok(true);
|
|
}
|
|
}
|
|
}
|
|
Ok(false)
|
|
}
|