diff --git a/Cargo.lock b/Cargo.lock index 3d92c0f..bb3dd7f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -348,7 +348,7 @@ dependencies = [ [[package]] name = "decomp-toolkit" -version = "1.6.2" +version = "1.7.0" dependencies = [ "aes", "anyhow", diff --git a/Cargo.toml b/Cargo.toml index 1ab2254..6ee180e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ name = "decomp-toolkit" description = "Yet another GameCube/Wii decompilation toolkit." authors = ["Luke Street "] license = "MIT OR Apache-2.0" -version = "1.6.2" +version = "1.7.0" edition = "2021" publish = false repository = "https://github.com/encounter/decomp-toolkit" diff --git a/src/analysis/cfa.rs b/src/analysis/cfa.rs index 1911864..1a7bf5c 100644 --- a/src/analysis/cfa.rs +++ b/src/analysis/cfa.rs @@ -1,6 +1,6 @@ use std::{ cmp::min, - collections::BTreeMap, + collections::{BTreeMap, BTreeSet}, fmt::{Debug, Display, Formatter, UpperHex}, ops::{Add, AddAssign, BitAnd, Sub}, }; @@ -572,6 +572,26 @@ pub fn locate_sda_bases(obj: &mut ObjInfo) -> Result { Some((sda2_base, sda_base)) => { obj.sda2_base = Some(sda2_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) } None => Ok(false), @@ -581,7 +601,7 @@ pub fn locate_sda_bases(obj: &mut ObjInfo) -> Result { /// ProDG hardcodes .bss and .sbss section initialization in `entry` /// This function locates the memset calls and returns a list of /// (address, size) pairs for the .bss sections. -pub fn locate_bss_memsets(obj: &mut ObjInfo) -> Result> { +pub fn locate_bss_memsets(obj: &ObjInfo) -> Result> { let mut bss_sections: Vec<(u32, u32)> = Vec::new(); let Some(entry) = obj.entry else { return Ok(bss_sections); @@ -632,3 +652,50 @@ pub fn locate_bss_memsets(obj: &mut ObjInfo) -> Result> { )?; 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> { + let mut branch_targets = BTreeSet::::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) +} diff --git a/src/analysis/slices.rs b/src/analysis/slices.rs index d37ecc3..8845076 100644 --- a/src/analysis/slices.rs +++ b/src/analysis/slices.rs @@ -97,8 +97,8 @@ fn check_prologue_sequence( } #[inline(always)] fn is_stwu(ins: Ins) -> bool { - // stwu r1, d(r1) - ins.op == Opcode::Stwu && ins.field_rs() == 1 && ins.field_ra() == 1 + // stwu[x] r1, d(r1) + matches!(ins.op, Opcode::Stwu | Opcode::Stwux) && ins.field_rs() == 1 && ins.field_ra() == 1 } #[inline(always)] fn is_stw(ins: Ins) -> bool { @@ -213,7 +213,11 @@ impl FunctionSlices { 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 epilogue != addr { bail!("Found duplicate epilogue: {:#010X} and {:#010X}", epilogue, addr) @@ -373,7 +377,14 @@ impl FunctionSlices { function_end.or_else(|| self.end()), )?; 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| { self.is_known_function(known_functions, addr) .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 slices.prologue.is_some() { + if slices.prologue.is_some() || slices.has_r1_load { log::trace!("Prologue discovered; known tail call: {:#010X}", addr); return TailCallResult::Is; } diff --git a/src/cmd/dol.rs b/src/cmd/dol.rs index 769f325..5966404 100644 --- a/src/cmd/dol.rs +++ b/src/cmd/dol.rs @@ -538,7 +538,9 @@ pub fn info(args: InfoArgs) -> Result<()> { apply_selfile(&mut obj, file.map()?)?; } - println!("{}:", obj.name); + if !obj.name.is_empty() { + println!("{}:", obj.name); + } if let Some(entry) = obj.entry { println!("Entry point: {entry:#010X}"); } diff --git a/src/cmd/elf2dol.rs b/src/cmd/elf2dol.rs index e293414..5cacf68 100644 --- a/src/cmd/elf2dol.rs +++ b/src/cmd/elf2dol.rs @@ -7,7 +7,6 @@ use typed_path::Utf8NativePathBuf; use crate::{ util::{ - alf::ALF_MAGIC, dol::{process_dol, write_dol}, file::buf_writer, path::native_path, @@ -16,11 +15,11 @@ use crate::{ }; #[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")] pub struct Args { #[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, #[argp(positional, from_str_fn(native_path))] /// path to output DOL @@ -54,8 +53,8 @@ const MAX_DATA_SECTIONS: usize = 11; pub fn run(args: Args) -> Result<()> { let mut file = open_file(&args.elf_file, true)?; let data = file.map()?; - if data.len() >= 4 && data[0..4] == ALF_MAGIC { - return convert_alf(args, data); + if data.len() >= 4 && data[0..4] != object::elf::ELFMAG { + return convert_dol_like(args, data); } let obj_file = object::read::File::parse(data)?; @@ -163,7 +162,8 @@ pub fn run(args: Args) -> Result<()> { 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 mut out = buf_writer(&args.dol_file)?; write_dol(&obj, &mut out)?; diff --git a/src/util/dol.rs b/src/util/dol.rs index 99b58a1..7278999 100644 --- a/src/util/dol.rs +++ b/src/util/dol.rs @@ -4,10 +4,13 @@ use std::{ io::{Cursor, Read, Seek, SeekFrom, Write}, }; -use anyhow::{anyhow, bail, ensure, Result}; +use anyhow::{anyhow, bail, ensure, Context, Result}; +use itertools::Itertools; 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, obj::{ ObjArchitecture, ObjInfo, ObjKind, ObjSection, ObjSectionKind, ObjSymbol, ObjSymbolFlagSet, @@ -209,6 +212,13 @@ pub fn process_dol(buf: &[u8], name: &str) -> Result { 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 let first_rom_section = dol .sections() @@ -216,20 +226,38 @@ pub fn process_dol(buf: &[u8], name: &str) -> Result { .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()) + .section_by_address(entry_point) .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 mut addr = init_section.address + init_section.size - - MAX_ROM_COPY_INFO_SIZE as u32 - - MAX_BSS_INIT_INFO_SIZE as u32; + let mut addr = if is_bootstage { + // Start searching from the beginning of the BootStage "data" section + 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 { 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}"); break Some(addr); } 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"); break None; } @@ -252,7 +280,7 @@ pub fn process_dol(buf: &[u8], name: &str) -> Result { log::debug!("Found _rom_copy_info end @ {addr:#010X}"); 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"); break None; } @@ -270,12 +298,14 @@ pub fn process_dol(buf: &[u8], name: &str) -> Result { 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 { + if is_bootstage + || (value >= bss_section.address && value < bss_section.address + bss_section.size) + { log::debug!("Found _bss_init_info @ {addr:#010X}"); break Some(addr); } 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"); break None; } @@ -294,7 +324,7 @@ pub fn process_dol(buf: &[u8], name: &str) -> Result { log::debug!("Found _bss_init_info end @ {addr:#010X}"); 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"); break None; } @@ -303,170 +333,105 @@ pub fn process_dol(buf: &[u8], name: &str) -> Result { 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 = Vec::new(); - let mut eti_init_info_range: Option<(u32, u32)> = None; - let mut extab_section: Option = None; - let mut extabindex_section: Option = 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; - } + if is_bootstage { + // Create sections based on _rom_copy_info + for (idx, (&addr, &size)) in rom_sections.iter().enumerate() { + 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 { - 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 + let (name, kind, known) = if entry_point >= addr && entry_point < addr + size { + (".init".to_string(), ObjSectionKind::Code, true) + } else { + 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), } }; - (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(), - }); + let file_offset = addr - dol_section.address + dol_section.file_offset; + sections.push(ObjSection { + name, + kind, + address: addr as u64, + size: size as u64, + data: data.to_vec(), + align: 0, + elf_index: 0, + relocations: Default::default(), + virtual_address: Some(addr as u64), + file_offset: file_offset as u64, + 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() { // 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, + is_bootstage + || (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, @@ -515,8 +480,8 @@ pub fn process_dol(buf: &[u8], name: &str) -> Result { vec![], temp_sections, ); - obj.entry = Some(dol.entry_point() as u64); - let bss_sections = locate_bss_memsets(&mut obj)?; + obj.entry = Some(entry_point as u64); + let bss_sections = locate_bss_memsets(&obj)?; match bss_sections.len() { 0 => log::warn!("Failed to locate BSS sections"), 2 => { @@ -559,24 +524,10 @@ pub fn process_dol(buf: &[u8], name: &str) -> Result { } // Apply section indices - let mut init_section_index: Option = None; 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 // ELF files start with a NULL section - section.elf_index = idx + 1; + section.elf_index = (idx as SectionIndex) + 1; } // Guess section alignment @@ -588,12 +539,13 @@ pub fn process_dol(buf: &[u8], name: &str) -> Result { align = (align + 1).next_power_of_two(); } if align_up(last_section_end, align) != section_start { - bail!( + log::warn!( "Couldn't determine alignment for section '{}' ({:#010X} -> {:#010X})", section.name, last_section_end, section_start ); + align = 32; } last_section_end = section_start + section.size as u32; section.align = align as u64; @@ -607,17 +559,18 @@ pub fn process_dol(buf: &[u8], name: &str) -> Result { vec![], sections, ); - obj.entry = Some(dol.entry_point() as u64); + obj.entry = Some(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) { + let (section_index, _) = obj.sections.at_address(rom_copy_info_addr)?; obj.add_symbol( ObjSymbol { name: "_rom_copy_info".to_string(), 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_known: true, flags: ObjSymbolFlagSet(ObjSymbolFlags::Global.into()), @@ -632,11 +585,12 @@ pub fn process_dol(buf: &[u8], name: &str) -> Result { if let (Some(bss_init_info_addr), Some(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( ObjSymbol { name: "_bss_init_info".to_string(), 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_known: true, flags: ObjSymbolFlagSet(ObjSymbolFlags::Global.into()), @@ -647,150 +601,24 @@ pub fn process_dol(buf: &[u8], name: &str) -> Result { )?; } - // 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, - )?; + // Locate .text section + if let Err(e) = locate_text(&mut obj) { + log::warn!("Failed to locate .text section: {:?}", e); } - // 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_{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, - )?; - } + // Locate extab and extabindex sections + if let Err(e) = locate_extab_extabindex(&mut obj) { + log::warn!("Failed to locate extab/etabindex: {:?}", e); } - // 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 .ctors and .dtors sections + if let Err(e) = locate_ctors_dtors(&mut obj) { + log::warn!("Failed to locate .ctors/.dtors: {:?}", e); } // 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(true) => {} Ok(false) => { log::warn!("Unable to locate SDA bases"); } @@ -830,39 +658,42 @@ struct EtiEntry { extab_addr: u32, } -fn read_eti_init_info(buf: &[u8], dol: &dyn DolLike, addr: u32) -> Result { - 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)?; +fn read_u32_obj(obj: &ObjInfo, addr: u32) -> Result { + let (_, section) = obj.sections.at_address(addr)?; + let data = section.data_range(addr, addr + 4)?; + Ok(u32::from_be_bytes(data.try_into()?)) +} + +fn read_eti_init_info(obj: &ObjInfo, addr: u32) -> Result { + 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 }) } -fn read_eti_entry(buf: &[u8], dol: &dyn DolLike, address: u32) -> Result { - 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)?; +fn read_eti_entry(obj: &ObjInfo, address: u32) -> Result { + let function = read_u32_obj(obj, address)?; + let function_size = read_u32_obj(obj, address + 4)?; + let extab_addr = read_u32_obj(obj, address + 8)?; Ok(EtiEntry { address, function, function_size, extab_addr }) } fn validate_eti_init_info( - dol: &dyn DolLike, + obj: &ObjInfo, eti_init_info: &EtiInitInfo, - eti_section: &DolSection, + eti_section_addr: u32, eti_section_end: u32, - rom_sections: &BTreeMap, ) -> Result { - 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_end >= eti_section.address + && eti_init_info.eti_end >= eti_section_addr && 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 { + if let Ok((_, code_section)) = obj.sections.at_address(eti_init_info.code_start) { + if eti_init_info.code_start + eti_init_info.code_size + <= (code_section.address + code_section.size) as u32 + { return Ok(true); } } @@ -945,3 +776,261 @@ where T: Write + ?Sized { } 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 = Vec::new(); + let mut eti_init_info_range: Option<(u32, u32)> = None; + let mut extab_section_index: Option = None; + let mut extabindex_section_index: Option = 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(()) +}