mirror of
https://github.com/encounter/decomp-toolkit.git
synced 2025-06-29 09:53:32 +00:00
Support loading Wii Menu (BootStage) DOL files
Also adds support to elf2dol to extract the inner DOL from a BootStage DOL.
This commit is contained in:
parent
8620099731
commit
b56b399201
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -348,7 +348,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "decomp-toolkit"
|
name = "decomp-toolkit"
|
||||||
version = "1.6.2"
|
version = "1.7.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes",
|
"aes",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
@ -3,7 +3,7 @@ name = "decomp-toolkit"
|
|||||||
description = "Yet another GameCube/Wii decompilation toolkit."
|
description = "Yet another GameCube/Wii decompilation toolkit."
|
||||||
authors = ["Luke Street <luke@street.dev>"]
|
authors = ["Luke Street <luke@street.dev>"]
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
version = "1.6.2"
|
version = "1.7.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
publish = false
|
publish = false
|
||||||
repository = "https://github.com/encounter/decomp-toolkit"
|
repository = "https://github.com/encounter/decomp-toolkit"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use std::{
|
use std::{
|
||||||
cmp::min,
|
cmp::min,
|
||||||
collections::BTreeMap,
|
collections::{BTreeMap, BTreeSet},
|
||||||
fmt::{Debug, Display, Formatter, UpperHex},
|
fmt::{Debug, Display, Formatter, UpperHex},
|
||||||
ops::{Add, AddAssign, BitAnd, Sub},
|
ops::{Add, AddAssign, BitAnd, Sub},
|
||||||
};
|
};
|
||||||
@ -572,6 +572,26 @@ pub fn locate_sda_bases(obj: &mut ObjInfo) -> Result<bool> {
|
|||||||
Some((sda2_base, sda_base)) => {
|
Some((sda2_base, sda_base)) => {
|
||||||
obj.sda2_base = Some(sda2_base);
|
obj.sda2_base = Some(sda2_base);
|
||||||
obj.sda_base = Some(sda_base);
|
obj.sda_base = Some(sda_base);
|
||||||
|
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(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
None => Ok(false),
|
None => Ok(false),
|
||||||
@ -581,7 +601,7 @@ pub fn locate_sda_bases(obj: &mut ObjInfo) -> Result<bool> {
|
|||||||
/// ProDG hardcodes .bss and .sbss section initialization in `entry`
|
/// ProDG hardcodes .bss and .sbss section initialization in `entry`
|
||||||
/// This function locates the memset calls and returns a list of
|
/// This function locates the memset calls and returns a list of
|
||||||
/// (address, size) pairs for the .bss sections.
|
/// (address, size) pairs for the .bss sections.
|
||||||
pub fn locate_bss_memsets(obj: &mut ObjInfo) -> Result<Vec<(u32, u32)>> {
|
pub fn locate_bss_memsets(obj: &ObjInfo) -> Result<Vec<(u32, u32)>> {
|
||||||
let mut bss_sections: Vec<(u32, u32)> = Vec::new();
|
let mut bss_sections: Vec<(u32, u32)> = Vec::new();
|
||||||
let Some(entry) = obj.entry else {
|
let Some(entry) = obj.entry else {
|
||||||
return Ok(bss_sections);
|
return Ok(bss_sections);
|
||||||
@ -632,3 +652,50 @@ pub fn locate_bss_memsets(obj: &mut ObjInfo) -> Result<Vec<(u32, u32)>> {
|
|||||||
)?;
|
)?;
|
||||||
Ok(bss_sections)
|
Ok(bss_sections)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Execute VM from specified entry point following inner-section branches and function calls,
|
||||||
|
/// noting all branch targets outside the current section.
|
||||||
|
pub fn locate_cross_section_branch_targets(
|
||||||
|
obj: &ObjInfo,
|
||||||
|
entry: SectionAddress,
|
||||||
|
) -> Result<BTreeSet<SectionAddress>> {
|
||||||
|
let mut branch_targets = BTreeSet::<SectionAddress>::new();
|
||||||
|
let mut executor = Executor::new(obj);
|
||||||
|
executor.push(entry, VM::new(), false);
|
||||||
|
executor.run(
|
||||||
|
obj,
|
||||||
|
|ExecCbData { executor, vm, result, ins_addr, section: _, ins: _, block_start: _ }| {
|
||||||
|
match result {
|
||||||
|
StepResult::Continue | StepResult::LoadStore { .. } => {
|
||||||
|
Ok(ExecCbResult::<()>::Continue)
|
||||||
|
}
|
||||||
|
StepResult::Illegal => bail!("Illegal instruction @ {}", ins_addr),
|
||||||
|
StepResult::Jump(target) => {
|
||||||
|
if let BranchTarget::Address(RelocationTarget::Address(addr)) = target {
|
||||||
|
if addr.section == entry.section {
|
||||||
|
executor.push(addr, vm.clone_all(), true);
|
||||||
|
} else {
|
||||||
|
branch_targets.insert(addr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(ExecCbResult::EndBlock)
|
||||||
|
}
|
||||||
|
StepResult::Branch(branches) => {
|
||||||
|
for branch in branches {
|
||||||
|
if let BranchTarget::Address(RelocationTarget::Address(addr)) =
|
||||||
|
branch.target
|
||||||
|
{
|
||||||
|
if addr.section == entry.section {
|
||||||
|
executor.push(addr, branch.vm, true);
|
||||||
|
} else {
|
||||||
|
branch_targets.insert(addr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(ExecCbResult::Continue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
Ok(branch_targets)
|
||||||
|
}
|
||||||
|
@ -97,8 +97,8 @@ fn check_prologue_sequence(
|
|||||||
}
|
}
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn is_stwu(ins: Ins) -> bool {
|
fn is_stwu(ins: Ins) -> bool {
|
||||||
// stwu r1, d(r1)
|
// stwu[x] r1, d(r1)
|
||||||
ins.op == Opcode::Stwu && ins.field_rs() == 1 && ins.field_ra() == 1
|
matches!(ins.op, Opcode::Stwu | Opcode::Stwux) && ins.field_rs() == 1 && ins.field_ra() == 1
|
||||||
}
|
}
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn is_stw(ins: Ins) -> bool {
|
fn is_stw(ins: Ins) -> bool {
|
||||||
@ -213,7 +213,11 @@ impl FunctionSlices {
|
|||||||
ins.op == Opcode::Or && ins.field_rd() == 1
|
ins.op == Opcode::Or && ins.field_rd() == 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if check_sequence(section, addr, Some(ins), &[(&is_mtlr, &is_addi), (&is_or, &is_mtlr)])? {
|
if check_sequence(section, addr, Some(ins), &[
|
||||||
|
(&is_mtlr, &is_addi),
|
||||||
|
(&is_mtlr, &is_or),
|
||||||
|
(&is_or, &is_mtlr),
|
||||||
|
])? {
|
||||||
if let Some(epilogue) = self.epilogue {
|
if let Some(epilogue) = self.epilogue {
|
||||||
if epilogue != addr {
|
if epilogue != addr {
|
||||||
bail!("Found duplicate epilogue: {:#010X} and {:#010X}", epilogue, addr)
|
bail!("Found duplicate epilogue: {:#010X} and {:#010X}", epilogue, addr)
|
||||||
@ -373,7 +377,14 @@ impl FunctionSlices {
|
|||||||
function_end.or_else(|| self.end()),
|
function_end.or_else(|| self.end()),
|
||||||
)?;
|
)?;
|
||||||
log::debug!("-> size {}: {:?}", size, entries);
|
log::debug!("-> size {}: {:?}", size, entries);
|
||||||
if (entries.contains(&next_address) || self.blocks.contains_key(&next_address))
|
let max_block = self
|
||||||
|
.blocks
|
||||||
|
.keys()
|
||||||
|
.next_back()
|
||||||
|
.copied()
|
||||||
|
.unwrap_or(next_address)
|
||||||
|
.max(next_address);
|
||||||
|
if entries.iter().any(|&addr| addr > function_start && addr <= max_block)
|
||||||
&& !entries.iter().any(|&addr| {
|
&& !entries.iter().any(|&addr| {
|
||||||
self.is_known_function(known_functions, addr)
|
self.is_known_function(known_functions, addr)
|
||||||
.is_some_and(|fn_addr| fn_addr != function_start)
|
.is_some_and(|fn_addr| fn_addr != function_start)
|
||||||
@ -736,7 +747,7 @@ impl FunctionSlices {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If we discovered a function prologue, known tail call.
|
// If we discovered a function prologue, known tail call.
|
||||||
if slices.prologue.is_some() {
|
if slices.prologue.is_some() || slices.has_r1_load {
|
||||||
log::trace!("Prologue discovered; known tail call: {:#010X}", addr);
|
log::trace!("Prologue discovered; known tail call: {:#010X}", addr);
|
||||||
return TailCallResult::Is;
|
return TailCallResult::Is;
|
||||||
}
|
}
|
||||||
|
@ -538,7 +538,9 @@ pub fn info(args: InfoArgs) -> Result<()> {
|
|||||||
apply_selfile(&mut obj, file.map()?)?;
|
apply_selfile(&mut obj, file.map()?)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("{}:", obj.name);
|
if !obj.name.is_empty() {
|
||||||
|
println!("{}:", obj.name);
|
||||||
|
}
|
||||||
if let Some(entry) = obj.entry {
|
if let Some(entry) = obj.entry {
|
||||||
println!("Entry point: {entry:#010X}");
|
println!("Entry point: {entry:#010X}");
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,6 @@ use typed_path::Utf8NativePathBuf;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
util::{
|
util::{
|
||||||
alf::ALF_MAGIC,
|
|
||||||
dol::{process_dol, write_dol},
|
dol::{process_dol, write_dol},
|
||||||
file::buf_writer,
|
file::buf_writer,
|
||||||
path::native_path,
|
path::native_path,
|
||||||
@ -16,11 +15,11 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
#[derive(FromArgs, PartialEq, Eq, Debug)]
|
#[derive(FromArgs, PartialEq, Eq, Debug)]
|
||||||
/// Converts an ELF (or ALF) file to a DOL file.
|
/// Converts an ELF, ALF, or BootStage file to a DOL file.
|
||||||
#[argp(subcommand, name = "elf2dol")]
|
#[argp(subcommand, name = "elf2dol")]
|
||||||
pub struct Args {
|
pub struct Args {
|
||||||
#[argp(positional, from_str_fn(native_path))]
|
#[argp(positional, from_str_fn(native_path))]
|
||||||
/// path to input ELF or ALF file
|
/// path to input ELF, ALF or BootStage file
|
||||||
elf_file: Utf8NativePathBuf,
|
elf_file: Utf8NativePathBuf,
|
||||||
#[argp(positional, from_str_fn(native_path))]
|
#[argp(positional, from_str_fn(native_path))]
|
||||||
/// path to output DOL
|
/// path to output DOL
|
||||||
@ -54,8 +53,8 @@ const MAX_DATA_SECTIONS: usize = 11;
|
|||||||
pub fn run(args: Args) -> Result<()> {
|
pub fn run(args: Args) -> Result<()> {
|
||||||
let mut file = open_file(&args.elf_file, true)?;
|
let mut file = open_file(&args.elf_file, true)?;
|
||||||
let data = file.map()?;
|
let data = file.map()?;
|
||||||
if data.len() >= 4 && data[0..4] == ALF_MAGIC {
|
if data.len() >= 4 && data[0..4] != object::elf::ELFMAG {
|
||||||
return convert_alf(args, data);
|
return convert_dol_like(args, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
let obj_file = object::read::File::parse(data)?;
|
let obj_file = object::read::File::parse(data)?;
|
||||||
@ -163,7 +162,8 @@ pub fn run(args: Args) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn convert_alf(args: Args, data: &[u8]) -> Result<()> {
|
/// Converts a DOL-like format (ALF or BootStage) to a DOL file.
|
||||||
|
fn convert_dol_like(args: Args, data: &[u8]) -> Result<()> {
|
||||||
let obj = process_dol(data, "")?;
|
let obj = process_dol(data, "")?;
|
||||||
let mut out = buf_writer(&args.dol_file)?;
|
let mut out = buf_writer(&args.dol_file)?;
|
||||||
write_dol(&obj, &mut out)?;
|
write_dol(&obj, &mut out)?;
|
||||||
|
771
src/util/dol.rs
771
src/util/dol.rs
@ -4,10 +4,13 @@ use std::{
|
|||||||
io::{Cursor, Read, Seek, SeekFrom, Write},
|
io::{Cursor, Read, Seek, SeekFrom, Write},
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{anyhow, bail, ensure, Result};
|
use anyhow::{anyhow, bail, ensure, Context, Result};
|
||||||
|
use itertools::Itertools;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
analysis::cfa::{locate_bss_memsets, locate_sda_bases, SectionAddress},
|
analysis::cfa::{
|
||||||
|
locate_bss_memsets, locate_cross_section_branch_targets, locate_sda_bases, SectionAddress,
|
||||||
|
},
|
||||||
array_ref,
|
array_ref,
|
||||||
obj::{
|
obj::{
|
||||||
ObjArchitecture, ObjInfo, ObjKind, ObjSection, ObjSectionKind, ObjSymbol, ObjSymbolFlagSet,
|
ObjArchitecture, ObjInfo, ObjKind, ObjSection, ObjSectionKind, ObjSymbol, ObjSymbolFlagSet,
|
||||||
@ -209,6 +212,13 @@ pub fn process_dol(buf: &[u8], name: &str) -> Result<ObjInfo> {
|
|||||||
Box::new(DolFile::from_reader(&mut reader, Endian::Big)?)
|
Box::new(DolFile::from_reader(&mut reader, Endian::Big)?)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let mut entry_point = dol.entry_point();
|
||||||
|
let mut is_bootstage = false;
|
||||||
|
if entry_point & 0x80000000 == 0 {
|
||||||
|
entry_point |= 0x80000000;
|
||||||
|
is_bootstage = true;
|
||||||
|
}
|
||||||
|
|
||||||
// Locate _rom_copy_info
|
// Locate _rom_copy_info
|
||||||
let first_rom_section = dol
|
let first_rom_section = dol
|
||||||
.sections()
|
.sections()
|
||||||
@ -216,20 +226,38 @@ pub fn process_dol(buf: &[u8], name: &str) -> Result<ObjInfo> {
|
|||||||
.find(|section| section.kind != DolSectionKind::Bss)
|
.find(|section| section.kind != DolSectionKind::Bss)
|
||||||
.ok_or_else(|| anyhow!("Failed to locate first rom section"))?;
|
.ok_or_else(|| anyhow!("Failed to locate first rom section"))?;
|
||||||
let init_section = dol
|
let init_section = dol
|
||||||
.section_by_address(dol.entry_point())
|
.section_by_address(entry_point)
|
||||||
.ok_or_else(|| anyhow!("Failed to locate .init section"))?;
|
.ok_or_else(|| anyhow!("Failed to locate .init section"))?;
|
||||||
|
let first_data_section = dol
|
||||||
|
.sections()
|
||||||
|
.iter()
|
||||||
|
.find(|s| s.kind == DolSectionKind::Data)
|
||||||
|
.ok_or_else(|| anyhow!("Failed to locate .data section"))?;
|
||||||
|
let rom_copy_section = if is_bootstage { first_data_section } else { init_section };
|
||||||
|
|
||||||
|
if is_bootstage {
|
||||||
|
// Real entry point is stored at the end of the bootstage init section, always(?) 0x81330000
|
||||||
|
entry_point = read_u32(buf, dol.as_ref(), init_section.address + init_section.size - 4)?;
|
||||||
|
}
|
||||||
|
|
||||||
let rom_copy_info_addr = {
|
let rom_copy_info_addr = {
|
||||||
let mut addr = init_section.address + init_section.size
|
let mut addr = if is_bootstage {
|
||||||
- MAX_ROM_COPY_INFO_SIZE as u32
|
// Start searching from the beginning of the BootStage "data" section
|
||||||
- MAX_BSS_INIT_INFO_SIZE as u32;
|
rom_copy_section.address
|
||||||
|
} else {
|
||||||
|
// Start searching from the end of the .init section
|
||||||
|
rom_copy_section.address + rom_copy_section.size
|
||||||
|
- MAX_ROM_COPY_INFO_SIZE as u32
|
||||||
|
- MAX_BSS_INIT_INFO_SIZE as u32
|
||||||
|
};
|
||||||
loop {
|
loop {
|
||||||
let value = read_u32(buf, dol.as_ref(), addr)?;
|
let value = read_u32(buf, dol.as_ref(), addr)?;
|
||||||
if value == first_rom_section.address {
|
if value == first_rom_section.address || value == entry_point {
|
||||||
log::debug!("Found _rom_copy_info @ {addr:#010X}");
|
log::debug!("Found _rom_copy_info @ {addr:#010X}");
|
||||||
break Some(addr);
|
break Some(addr);
|
||||||
}
|
}
|
||||||
addr += 4;
|
addr += 4;
|
||||||
if addr >= init_section.address + init_section.size {
|
if addr >= rom_copy_section.address + rom_copy_section.size {
|
||||||
log::warn!("Failed to locate _rom_copy_info");
|
log::warn!("Failed to locate _rom_copy_info");
|
||||||
break None;
|
break None;
|
||||||
}
|
}
|
||||||
@ -252,7 +280,7 @@ pub fn process_dol(buf: &[u8], name: &str) -> Result<ObjInfo> {
|
|||||||
log::debug!("Found _rom_copy_info end @ {addr:#010X}");
|
log::debug!("Found _rom_copy_info end @ {addr:#010X}");
|
||||||
break Some(addr);
|
break Some(addr);
|
||||||
}
|
}
|
||||||
if addr >= init_section.address + init_section.size {
|
if addr >= rom_copy_section.address + rom_copy_section.size {
|
||||||
log::warn!("Failed to locate _rom_copy_info end");
|
log::warn!("Failed to locate _rom_copy_info end");
|
||||||
break None;
|
break None;
|
||||||
}
|
}
|
||||||
@ -270,12 +298,14 @@ pub fn process_dol(buf: &[u8], name: &str) -> Result<ObjInfo> {
|
|||||||
let bss_init_info_addr = match rom_copy_info_end {
|
let bss_init_info_addr = match rom_copy_info_end {
|
||||||
Some(mut addr) => loop {
|
Some(mut addr) => loop {
|
||||||
let value = read_u32(buf, dol.as_ref(), addr)?;
|
let value = read_u32(buf, dol.as_ref(), addr)?;
|
||||||
if value == bss_section.address {
|
if is_bootstage
|
||||||
|
|| (value >= bss_section.address && value < bss_section.address + bss_section.size)
|
||||||
|
{
|
||||||
log::debug!("Found _bss_init_info @ {addr:#010X}");
|
log::debug!("Found _bss_init_info @ {addr:#010X}");
|
||||||
break Some(addr);
|
break Some(addr);
|
||||||
}
|
}
|
||||||
addr += 4;
|
addr += 4;
|
||||||
if addr >= init_section.address + init_section.size {
|
if addr >= rom_copy_section.address + rom_copy_section.size {
|
||||||
log::warn!("Failed to locate _bss_init_info");
|
log::warn!("Failed to locate _bss_init_info");
|
||||||
break None;
|
break None;
|
||||||
}
|
}
|
||||||
@ -294,7 +324,7 @@ pub fn process_dol(buf: &[u8], name: &str) -> Result<ObjInfo> {
|
|||||||
log::debug!("Found _bss_init_info end @ {addr:#010X}");
|
log::debug!("Found _bss_init_info end @ {addr:#010X}");
|
||||||
break Some(addr);
|
break Some(addr);
|
||||||
}
|
}
|
||||||
if addr >= init_section.address + init_section.size {
|
if addr >= rom_copy_section.address + rom_copy_section.size {
|
||||||
log::warn!("Failed to locate _bss_init_info end");
|
log::warn!("Failed to locate _bss_init_info end");
|
||||||
break None;
|
break None;
|
||||||
}
|
}
|
||||||
@ -303,170 +333,105 @@ pub fn process_dol(buf: &[u8], name: &str) -> Result<ObjInfo> {
|
|||||||
None => None,
|
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<SectionIndex> = None;
|
|
||||||
let mut extabindex_section: Option<SectionIndex> = 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
|
// Add text and data sections
|
||||||
let mut sections = vec![];
|
let mut sections = vec![];
|
||||||
for dol_section in dol.sections().iter() {
|
if is_bootstage {
|
||||||
// We'll split .bss later
|
// Create sections based on _rom_copy_info
|
||||||
if dol_section.kind == DolSectionKind::Bss && dol.has_unified_bss() {
|
for (idx, (&addr, &size)) in rom_sections.iter().enumerate() {
|
||||||
continue;
|
let dol_section = dol
|
||||||
}
|
.section_by_address(addr)
|
||||||
|
.ok_or_else(|| anyhow!("Failed to locate section for ROM address {addr:#010X}"))?;
|
||||||
|
let data = dol.virtual_data_at(buf, addr, size)?;
|
||||||
|
|
||||||
let (name, kind, known) = match dol_section.index {
|
let (name, kind, known) = if entry_point >= addr && entry_point < addr + size {
|
||||||
idx if idx == init_section.index => (".init".to_string(), ObjSectionKind::Code, true),
|
(".init".to_string(), ObjSectionKind::Code, true)
|
||||||
idx if Some(idx) == extab_section => {
|
} else {
|
||||||
("extab".to_string(), ObjSectionKind::ReadOnlyData, true)
|
match dol_section.kind {
|
||||||
}
|
DolSectionKind::Text => (format!(".text{idx}"), ObjSectionKind::Code, false),
|
||||||
idx if Some(idx) == extabindex_section => {
|
DolSectionKind::Data => (format!(".data{idx}"), ObjSectionKind::Data, false),
|
||||||
("extabindex".to_string(), ObjSectionKind::ReadOnlyData, true)
|
DolSectionKind::Bss => (format!(".bss{idx}"), ObjSectionKind::Bss, false),
|
||||||
}
|
|
||||||
_ 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 {
|
let file_offset = addr - dol_section.address + dol_section.file_offset;
|
||||||
name,
|
sections.push(ObjSection {
|
||||||
kind,
|
name,
|
||||||
address: dol_section.address as u64,
|
kind,
|
||||||
size: size as u64,
|
address: addr as u64,
|
||||||
data: data.to_vec(),
|
size: size as u64,
|
||||||
align: 0,
|
data: data.to_vec(),
|
||||||
elf_index: 0,
|
align: 0,
|
||||||
relocations: Default::default(),
|
elf_index: 0,
|
||||||
virtual_address: Some(dol_section.address as u64),
|
relocations: Default::default(),
|
||||||
file_offset: dol_section.file_offset as u64,
|
virtual_address: Some(addr as u64),
|
||||||
section_known: known,
|
file_offset: file_offset as u64,
|
||||||
splits: Default::default(),
|
section_known: known,
|
||||||
});
|
splits: Default::default(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
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 => 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(),
|
||||||
|
virtual_address: Some(dol_section.address as u64),
|
||||||
|
file_offset: dol_section.file_offset as u64,
|
||||||
|
section_known: known,
|
||||||
|
splits: Default::default(),
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if dol.has_unified_bss() {
|
if dol.has_unified_bss() {
|
||||||
// Add BSS sections from _bss_init_info
|
// Add BSS sections from _bss_init_info
|
||||||
for (idx, (&addr, &size)) in bss_sections.iter().enumerate() {
|
for (idx, (&addr, &size)) in bss_sections.iter().enumerate() {
|
||||||
ensure!(
|
ensure!(
|
||||||
addr >= bss_section.address
|
is_bootstage
|
||||||
&& addr < bss_section.address + bss_section.size
|
|| (addr >= bss_section.address
|
||||||
&& addr + size <= bss_section.address + bss_section.size,
|
&& addr < bss_section.address + bss_section.size
|
||||||
|
&& addr + size <= bss_section.address + bss_section.size),
|
||||||
"Invalid BSS range {:#010X}-{:#010X} (DOL BSS: {:#010X}-{:#010X})",
|
"Invalid BSS range {:#010X}-{:#010X} (DOL BSS: {:#010X}-{:#010X})",
|
||||||
addr,
|
addr,
|
||||||
addr + size,
|
addr + size,
|
||||||
@ -515,8 +480,8 @@ pub fn process_dol(buf: &[u8], name: &str) -> Result<ObjInfo> {
|
|||||||
vec![],
|
vec![],
|
||||||
temp_sections,
|
temp_sections,
|
||||||
);
|
);
|
||||||
obj.entry = Some(dol.entry_point() as u64);
|
obj.entry = Some(entry_point as u64);
|
||||||
let bss_sections = locate_bss_memsets(&mut obj)?;
|
let bss_sections = locate_bss_memsets(&obj)?;
|
||||||
match bss_sections.len() {
|
match bss_sections.len() {
|
||||||
0 => log::warn!("Failed to locate BSS sections"),
|
0 => log::warn!("Failed to locate BSS sections"),
|
||||||
2 => {
|
2 => {
|
||||||
@ -559,24 +524,10 @@ pub fn process_dol(buf: &[u8], name: &str) -> Result<ObjInfo> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Apply section indices
|
// Apply section indices
|
||||||
let mut init_section_index: Option<SectionIndex> = None;
|
|
||||||
for (idx, section) in sections.iter_mut().enumerate() {
|
for (idx, section) in sections.iter_mut().enumerate() {
|
||||||
let idx = idx as SectionIndex;
|
|
||||||
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
|
// Assume the original ELF section index is +1
|
||||||
// ELF files start with a NULL section
|
// ELF files start with a NULL section
|
||||||
section.elf_index = idx + 1;
|
section.elf_index = (idx as SectionIndex) + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Guess section alignment
|
// Guess section alignment
|
||||||
@ -588,12 +539,13 @@ pub fn process_dol(buf: &[u8], name: &str) -> Result<ObjInfo> {
|
|||||||
align = (align + 1).next_power_of_two();
|
align = (align + 1).next_power_of_two();
|
||||||
}
|
}
|
||||||
if align_up(last_section_end, align) != section_start {
|
if align_up(last_section_end, align) != section_start {
|
||||||
bail!(
|
log::warn!(
|
||||||
"Couldn't determine alignment for section '{}' ({:#010X} -> {:#010X})",
|
"Couldn't determine alignment for section '{}' ({:#010X} -> {:#010X})",
|
||||||
section.name,
|
section.name,
|
||||||
last_section_end,
|
last_section_end,
|
||||||
section_start
|
section_start
|
||||||
);
|
);
|
||||||
|
align = 32;
|
||||||
}
|
}
|
||||||
last_section_end = section_start + section.size as u32;
|
last_section_end = section_start + section.size as u32;
|
||||||
section.align = align as u64;
|
section.align = align as u64;
|
||||||
@ -607,17 +559,18 @@ pub fn process_dol(buf: &[u8], name: &str) -> Result<ObjInfo> {
|
|||||||
vec![],
|
vec![],
|
||||||
sections,
|
sections,
|
||||||
);
|
);
|
||||||
obj.entry = Some(dol.entry_point() as u64);
|
obj.entry = Some(entry_point as u64);
|
||||||
|
|
||||||
// Generate _rom_copy_info symbol
|
// Generate _rom_copy_info symbol
|
||||||
if let (Some(rom_copy_info_addr), Some(rom_copy_info_end)) =
|
if let (Some(rom_copy_info_addr), Some(rom_copy_info_end)) =
|
||||||
(rom_copy_info_addr, rom_copy_info_end)
|
(rom_copy_info_addr, rom_copy_info_end)
|
||||||
{
|
{
|
||||||
|
let (section_index, _) = obj.sections.at_address(rom_copy_info_addr)?;
|
||||||
obj.add_symbol(
|
obj.add_symbol(
|
||||||
ObjSymbol {
|
ObjSymbol {
|
||||||
name: "_rom_copy_info".to_string(),
|
name: "_rom_copy_info".to_string(),
|
||||||
address: rom_copy_info_addr as u64,
|
address: rom_copy_info_addr as u64,
|
||||||
section: init_section_index,
|
section: Some(section_index),
|
||||||
size: (rom_copy_info_end - rom_copy_info_addr) as u64,
|
size: (rom_copy_info_end - rom_copy_info_addr) as u64,
|
||||||
size_known: true,
|
size_known: true,
|
||||||
flags: ObjSymbolFlagSet(ObjSymbolFlags::Global.into()),
|
flags: ObjSymbolFlagSet(ObjSymbolFlags::Global.into()),
|
||||||
@ -632,11 +585,12 @@ pub fn process_dol(buf: &[u8], name: &str) -> Result<ObjInfo> {
|
|||||||
if let (Some(bss_init_info_addr), Some(bss_init_info_end)) =
|
if let (Some(bss_init_info_addr), Some(bss_init_info_end)) =
|
||||||
(bss_init_info_addr, bss_init_info_end)
|
(bss_init_info_addr, bss_init_info_end)
|
||||||
{
|
{
|
||||||
|
let (section_index, _) = obj.sections.at_address(bss_init_info_addr)?;
|
||||||
obj.add_symbol(
|
obj.add_symbol(
|
||||||
ObjSymbol {
|
ObjSymbol {
|
||||||
name: "_bss_init_info".to_string(),
|
name: "_bss_init_info".to_string(),
|
||||||
address: bss_init_info_addr as u64,
|
address: bss_init_info_addr as u64,
|
||||||
section: init_section_index,
|
section: Some(section_index),
|
||||||
size: (bss_init_info_end - bss_init_info_addr) as u64,
|
size: (bss_init_info_end - bss_init_info_addr) as u64,
|
||||||
size_known: true,
|
size_known: true,
|
||||||
flags: ObjSymbolFlagSet(ObjSymbolFlags::Global.into()),
|
flags: ObjSymbolFlagSet(ObjSymbolFlags::Global.into()),
|
||||||
@ -647,150 +601,24 @@ pub fn process_dol(buf: &[u8], name: &str) -> Result<ObjInfo> {
|
|||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate _eti_init_info symbol
|
// Locate .text section
|
||||||
if let Some((eti_init_info_addr, eti_init_info_end)) = eti_init_info_range {
|
if let Err(e) = locate_text(&mut obj) {
|
||||||
obj.add_symbol(
|
log::warn!("Failed to locate .text section: {:?}", e);
|
||||||
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
|
// Locate extab and extabindex sections
|
||||||
if let (Some(extabindex_section_index), Some(extab_section_index)) =
|
if let Err(e) = locate_extab_extabindex(&mut obj) {
|
||||||
(extabindex_section, extab_section)
|
log::warn!("Failed to locate extab/etabindex: {:?}", e);
|
||||||
{
|
|
||||||
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_{addr:08X}"),
|
|
||||||
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
|
// Locate .ctors and .dtors sections
|
||||||
for (_, section) in obj.sections.iter() {
|
if let Err(e) = locate_ctors_dtors(&mut obj) {
|
||||||
if section.size & 3 != 0 {
|
log::warn!("Failed to locate .ctors/.dtors: {:?}", e);
|
||||||
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_
|
// Locate _SDA2_BASE_ & _SDA_BASE_
|
||||||
match locate_sda_bases(&mut obj) {
|
match locate_sda_bases(&mut obj) {
|
||||||
Ok(true) => {
|
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) => {
|
Ok(false) => {
|
||||||
log::warn!("Unable to locate SDA bases");
|
log::warn!("Unable to locate SDA bases");
|
||||||
}
|
}
|
||||||
@ -830,39 +658,42 @@ struct EtiEntry {
|
|||||||
extab_addr: u32,
|
extab_addr: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_eti_init_info(buf: &[u8], dol: &dyn DolLike, addr: u32) -> Result<EtiInitInfo> {
|
fn read_u32_obj(obj: &ObjInfo, addr: u32) -> Result<u32> {
|
||||||
let eti_start = read_u32(buf, dol, addr)?;
|
let (_, section) = obj.sections.at_address(addr)?;
|
||||||
let eti_end = read_u32(buf, dol, addr + 4)?;
|
let data = section.data_range(addr, addr + 4)?;
|
||||||
let code_start = read_u32(buf, dol, addr + 8)?;
|
Ok(u32::from_be_bytes(data.try_into()?))
|
||||||
let code_size = read_u32(buf, dol, addr + 12)?;
|
}
|
||||||
|
|
||||||
|
fn read_eti_init_info(obj: &ObjInfo, addr: u32) -> Result<EtiInitInfo> {
|
||||||
|
let eti_start = read_u32_obj(obj, addr)?;
|
||||||
|
let eti_end = read_u32_obj(obj, addr + 4)?;
|
||||||
|
let code_start = read_u32_obj(obj, addr + 8)?;
|
||||||
|
let code_size = read_u32_obj(obj, addr + 12)?;
|
||||||
Ok(EtiInitInfo { eti_start, eti_end, code_start, code_size })
|
Ok(EtiInitInfo { eti_start, eti_end, code_start, code_size })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_eti_entry(buf: &[u8], dol: &dyn DolLike, address: u32) -> Result<EtiEntry> {
|
fn read_eti_entry(obj: &ObjInfo, address: u32) -> Result<EtiEntry> {
|
||||||
let function = read_u32(buf, dol, address)?;
|
let function = read_u32_obj(obj, address)?;
|
||||||
let function_size = read_u32(buf, dol, address + 4)?;
|
let function_size = read_u32_obj(obj, address + 4)?;
|
||||||
let extab_addr = read_u32(buf, dol, address + 8)?;
|
let extab_addr = read_u32_obj(obj, address + 8)?;
|
||||||
Ok(EtiEntry { address, function, function_size, extab_addr })
|
Ok(EtiEntry { address, function, function_size, extab_addr })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_eti_init_info(
|
fn validate_eti_init_info(
|
||||||
dol: &dyn DolLike,
|
obj: &ObjInfo,
|
||||||
eti_init_info: &EtiInitInfo,
|
eti_init_info: &EtiInitInfo,
|
||||||
eti_section: &DolSection,
|
eti_section_addr: u32,
|
||||||
eti_section_end: u32,
|
eti_section_end: u32,
|
||||||
rom_sections: &BTreeMap<u32, u32>,
|
|
||||||
) -> Result<bool> {
|
) -> Result<bool> {
|
||||||
if eti_init_info.eti_start >= eti_section.address
|
if eti_init_info.eti_start >= eti_section_addr
|
||||||
&& eti_init_info.eti_start < eti_section_end
|
&& eti_init_info.eti_start < eti_section_end
|
||||||
&& eti_init_info.eti_end >= eti_section.address
|
&& eti_init_info.eti_end >= eti_section_addr
|
||||||
&& eti_init_info.eti_end < eti_section_end
|
&& eti_init_info.eti_end < eti_section_end
|
||||||
{
|
{
|
||||||
if let Some(code_section) = dol.section_by_address(eti_init_info.code_start) {
|
if let Ok((_, code_section)) = obj.sections.at_address(eti_init_info.code_start) {
|
||||||
let code_section_size = match rom_sections.get(&code_section.address) {
|
if eti_init_info.code_start + eti_init_info.code_size
|
||||||
Some(&size) => size,
|
<= (code_section.address + code_section.size) as u32
|
||||||
None => code_section.size,
|
{
|
||||||
};
|
|
||||||
if eti_init_info.code_size <= code_section_size {
|
|
||||||
return Ok(true);
|
return Ok(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -945,3 +776,261 @@ where T: Write + ?Sized {
|
|||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn rename_section(
|
||||||
|
obj: &mut ObjInfo,
|
||||||
|
index: SectionIndex,
|
||||||
|
name: &str,
|
||||||
|
kind: ObjSectionKind,
|
||||||
|
) -> Result<()> {
|
||||||
|
let section = &mut obj.sections[index];
|
||||||
|
ensure!(
|
||||||
|
!section.section_known,
|
||||||
|
"Section {} is already known, cannot rename to {}",
|
||||||
|
section.name,
|
||||||
|
name
|
||||||
|
);
|
||||||
|
section.name = name.to_string();
|
||||||
|
section.kind = kind;
|
||||||
|
section.section_known = true;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn locate_text(obj: &mut ObjInfo) -> Result<()> {
|
||||||
|
// If we didn't find .text, infer from branch targets from .init section
|
||||||
|
if !obj.sections.iter().any(|(_, s)| s.section_known && s.name == ".text") {
|
||||||
|
let entry_point = obj.entry.ok_or_else(|| anyhow!("Missing entry point"))? as u32;
|
||||||
|
let (entry_section_index, _) = obj.sections.at_address(entry_point)?;
|
||||||
|
let entry = SectionAddress::new(entry_section_index, entry_point);
|
||||||
|
let branch_targets = locate_cross_section_branch_targets(obj, entry)?;
|
||||||
|
let mut section_indexes = branch_targets.iter().map(|s| s.section).dedup();
|
||||||
|
let text_section_index =
|
||||||
|
section_indexes.next().ok_or_else(|| anyhow!("Failed to locate .text section"))?;
|
||||||
|
ensure!(section_indexes.next().is_none(), "Multiple possible .text sections found");
|
||||||
|
rename_section(obj, text_section_index, ".text", ObjSectionKind::Code)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn locate_ctors_dtors(obj: &mut ObjInfo) -> Result<()> {
|
||||||
|
// Add .ctors and .dtors functions to known functions if they exist
|
||||||
|
let mut ctors_section_index = None;
|
||||||
|
let mut dtors_section_index = None;
|
||||||
|
for (section_index, 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)));
|
||||||
|
match (ctors_section_index, dtors_section_index) {
|
||||||
|
(None, None) => ctors_section_index = Some(section_index),
|
||||||
|
(Some(_), None) => dtors_section_index = Some(section_index),
|
||||||
|
_ => log::warn!("Multiple possible .ctors/.dtors sections found"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(ctors_section) = ctors_section_index {
|
||||||
|
rename_section(obj, ctors_section, ".ctors", ObjSectionKind::ReadOnlyData)?;
|
||||||
|
}
|
||||||
|
if let Some(dtors_section) = dtors_section_index {
|
||||||
|
rename_section(obj, dtors_section, ".dtors", ObjSectionKind::ReadOnlyData)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn locate_extab_extabindex(obj: &mut ObjInfo) -> Result<()> {
|
||||||
|
let num_text_sections =
|
||||||
|
obj.sections.iter().filter(|(_, section)| section.kind == ObjSectionKind::Code).count();
|
||||||
|
let mut eti_entries: Vec<EtiEntry> = Vec::new();
|
||||||
|
let mut eti_init_info_range: Option<(u32, u32)> = None;
|
||||||
|
let mut extab_section_index: Option<SectionIndex> = None;
|
||||||
|
let mut extabindex_section_index: Option<SectionIndex> = None;
|
||||||
|
'outer: for (dol_section_index, dol_section) in
|
||||||
|
obj.sections.iter().filter(|(_, section)| section.kind == ObjSectionKind::Data)
|
||||||
|
{
|
||||||
|
let dol_section_start = dol_section.address as u32;
|
||||||
|
let dol_section_end = dol_section_start + dol_section.size as u32;
|
||||||
|
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(obj, addr)?;
|
||||||
|
if validate_eti_init_info(obj, &eti_init_info, dol_section_start, dol_section_end)?
|
||||||
|
{
|
||||||
|
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(obj, 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(obj, &eti_init_info, dol_section_start, dol_section_end)?
|
||||||
|
{
|
||||||
|
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(obj, addr)?;
|
||||||
|
let (entry_section_index, _) =
|
||||||
|
obj.sections.at_address(eti_entry.extab_addr).with_context(|| {
|
||||||
|
format!(
|
||||||
|
"Failed to locate section for extab address {:#010X}",
|
||||||
|
eti_entry.extab_addr
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
if let Some(extab_section) = extab_section_index {
|
||||||
|
ensure!(
|
||||||
|
entry_section_index == extab_section,
|
||||||
|
"Mismatched sections for extabindex entries: {} != {}",
|
||||||
|
entry_section_index,
|
||||||
|
extab_section
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
extab_section_index = 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_index = Some(dol_section_index);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if eti_init_info_range.is_none() {
|
||||||
|
log::debug!("Failed to locate _eti_init_info");
|
||||||
|
}
|
||||||
|
if let Some(extab_section_index) = extab_section_index {
|
||||||
|
rename_section(obj, extab_section_index, "extab", ObjSectionKind::ReadOnlyData)?;
|
||||||
|
}
|
||||||
|
if let Some(extabindex_section_index) = extabindex_section_index {
|
||||||
|
rename_section(obj, extabindex_section_index, "extabindex", ObjSectionKind::ReadOnlyData)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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_index,
|
||||||
|
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_index, extab_section_index)
|
||||||
|
{
|
||||||
|
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_{addr:08X}"),
|
||||||
|
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,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user