6 months of occasional work I guess

This commit is contained in:
2023-07-21 17:59:07 -04:00
parent f1b4afa885
commit 0fa0aafaea
44 changed files with 4688 additions and 1817 deletions

View File

@@ -1,7 +1,7 @@
use std::{
collections::{btree_map::Entry, BTreeMap},
fs::File,
io::{BufRead, BufWriter, Write},
io::{BufWriter, Write},
path::PathBuf,
};
@@ -9,7 +9,7 @@ use anyhow::{anyhow, bail, Result};
use argh::FromArgs;
use object::{Object, ObjectSymbol, SymbolScope};
use crate::util::file::{buf_reader, map_file};
use crate::util::file::{map_file, process_rsp};
#[derive(FromArgs, PartialEq, Debug)]
/// Commands for processing static libraries.
@@ -45,25 +45,7 @@ pub fn run(args: Args) -> Result<()> {
fn create(args: CreateArgs) -> Result<()> {
// Process response files (starting with '@')
let mut files = Vec::with_capacity(args.files.len());
for path in args.files {
let path_str =
path.to_str().ok_or_else(|| anyhow!("'{}' is not valid UTF-8", path.display()))?;
match path_str.strip_prefix('@') {
Some(rsp_file) => {
let reader = buf_reader(rsp_file)?;
for result in reader.lines() {
let line = result?;
if !line.is_empty() {
files.push(PathBuf::from(line));
}
}
}
None => {
files.push(path);
}
}
}
let files = process_rsp(&args.files)?;
// Build identifiers & symbol table
let mut identifiers = Vec::with_capacity(files.len());

View File

@@ -1,11 +1,10 @@
use std::{
collections::BTreeMap,
collections::{hash_map, BTreeMap, HashMap},
fs,
fs::{DirBuilder, File},
io::{BufRead, BufWriter, Write},
path::{Path, PathBuf},
};
use std::collections::{hash_map, HashMap};
use anyhow::{anyhow, bail, Context, Result};
use argh::FromArgs;
@@ -13,25 +12,24 @@ use argh::FromArgs;
use crate::{
analysis::{
cfa::AnalyzerState,
objects::{detect_object_boundaries, detect_strings},
pass::{AnalysisPass, FindSaveRestSleds, FindTRKInterruptVectorTable},
read_u32,
signatures::{apply_signatures, apply_signatures_post},
tracker::Tracker,
},
obj::{
signatures::{apply_signature, check_signatures, check_signatures_str, parse_signatures},
split::split_obj,
split::{split_obj, update_splits},
ObjInfo, ObjRelocKind, ObjSectionKind, ObjSymbolKind,
},
util::{
asm::write_asm,
config::{apply_splits, parse_symbol_line, write_symbols},
config::{apply_splits, parse_symbol_line, write_splits, write_symbols},
dol::process_dol,
elf::process_elf,
elf::{process_elf, write_elf},
file::{map_file, map_reader},
map::process_map,
lcf::{asm_path_for_unit, generate_ldscript, obj_path_for_unit},
},
};
use crate::util::elf::write_elf;
#[derive(FromArgs, PartialEq, Debug)]
/// Commands for processing DOL files.
@@ -44,32 +42,8 @@ pub struct Args {
#[derive(FromArgs, PartialEq, Debug)]
#[argh(subcommand)]
enum SubCommand {
Disasm(DisasmArgs),
Info(InfoArgs),
}
#[derive(FromArgs, PartialEq, Eq, Debug)]
/// disassembles a DOL file
#[argh(subcommand, name = "disasm")]
pub struct DisasmArgs {
#[argh(option, short = 'm')]
/// path to input map
map_file: Option<PathBuf>,
#[argh(option, short = 's')]
/// path to symbols file
symbols_file: Option<PathBuf>,
#[argh(option, short = 'p')]
/// path to splits file
splits_file: Option<PathBuf>,
#[argh(option, short = 'e')]
/// ELF file to validate against (debugging only)
elf_file: Option<PathBuf>,
#[argh(positional)]
/// DOL file
dol_file: PathBuf,
#[argh(option, short = 'o')]
/// output file (or directory, if splitting)
out: PathBuf,
Split(SplitArgs),
}
#[derive(FromArgs, PartialEq, Eq, Debug)]
@@ -81,301 +55,39 @@ pub struct InfoArgs {
dol_file: PathBuf,
}
#[derive(FromArgs, PartialEq, Eq, Debug)]
/// Splits a DOL into relocatable objects.
#[argh(subcommand, name = "split")]
pub struct SplitArgs {
#[argh(positional)]
/// input file
in_file: PathBuf,
#[argh(positional)]
/// output directory
out_dir: PathBuf,
#[argh(option, short = 's')]
/// path to symbols file
symbols_file: Option<PathBuf>,
#[argh(option, short = 'p')]
/// path to splits file
splits_file: Option<PathBuf>,
#[argh(option, short = 'e')]
/// ELF file to validate against (debugging only)
elf_file: Option<PathBuf>,
}
pub fn run(args: Args) -> Result<()> {
match args.command {
SubCommand::Disasm(c_args) => disasm(c_args),
SubCommand::Info(c_args) => info(c_args),
SubCommand::Split(c_args) => split(c_args),
}
}
const SIGNATURES: &[(&str, &str)] = &[
("__init_registers", include_str!("../../assets/signatures/__init_registers.yml")),
("__init_hardware", include_str!("../../assets/signatures/__init_hardware.yml")),
("__init_data", include_str!("../../assets/signatures/__init_data.yml")),
("__set_debug_bba", include_str!("../../assets/signatures/__set_debug_bba.yml")),
("__OSPSInit", include_str!("../../assets/signatures/__OSPSInit.yml")),
("__OSFPRInit", include_str!("../../assets/signatures/__OSFPRInit.yml")),
("__OSCacheInit", include_str!("../../assets/signatures/__OSCacheInit.yml")),
("DMAErrorHandler", include_str!("../../assets/signatures/DMAErrorHandler.yml")),
("DBInit", include_str!("../../assets/signatures/DBInit.yml")),
("OSInit", include_str!("../../assets/signatures/OSInit.yml")),
("__OSThreadInit", include_str!("../../assets/signatures/__OSThreadInit.yml")),
("__OSInitIPCBuffer", include_str!("../../assets/signatures/__OSInitIPCBuffer.yml")),
("EXIInit", include_str!("../../assets/signatures/EXIInit.yml")),
("EXIGetID", include_str!("../../assets/signatures/EXIGetID.yml")),
("exit", include_str!("../../assets/signatures/exit.yml")),
("_ExitProcess", include_str!("../../assets/signatures/_ExitProcess.yml")),
("__fini_cpp", include_str!("../../assets/signatures/__fini_cpp.yml")),
("__destroy_global_chain", include_str!("../../assets/signatures/__destroy_global_chain.yml")),
("InitMetroTRK", include_str!("../../assets/signatures/InitMetroTRK.yml")),
("InitMetroTRKCommTable", include_str!("../../assets/signatures/InitMetroTRKCommTable.yml")),
("OSExceptionInit", include_str!("../../assets/signatures/OSExceptionInit.yml")),
(
"OSDefaultExceptionHandler",
include_str!("../../assets/signatures/OSDefaultExceptionHandler.yml"),
),
("__OSUnhandledException", include_str!("../../assets/signatures/__OSUnhandledException.yml")),
("OSDisableScheduler", include_str!("../../assets/signatures/OSDisableScheduler.yml")),
("__OSReschedule", include_str!("../../assets/signatures/__OSReschedule.yml")),
("__OSInitSystemCall", include_str!("../../assets/signatures/__OSInitSystemCall.yml")),
("OSInitAlarm", include_str!("../../assets/signatures/OSInitAlarm.yml")),
("__OSInitAlarm", include_str!("../../assets/signatures/__OSInitAlarm.yml")),
("__OSEVStart", include_str!("../../assets/signatures/OSExceptionVector.yml")),
("__OSDBINTSTART", include_str!("../../assets/signatures/__OSDBIntegrator.yml")),
("__OSDBJUMPSTART", include_str!("../../assets/signatures/__OSDBJump.yml")),
("SIInit", include_str!("../../assets/signatures/SIInit.yml")),
("SIGetType", include_str!("../../assets/signatures/SIGetType.yml")),
("SISetSamplingRate", include_str!("../../assets/signatures/SISetSamplingRate.yml")),
("SISetXY", include_str!("../../assets/signatures/SISetXY.yml")),
("VIGetTvFormat", include_str!("../../assets/signatures/VIGetTvFormat.yml")),
("DVDInit", include_str!("../../assets/signatures/DVDInit.yml")),
(
"DVDSetAutoFatalMessaging",
include_str!("../../assets/signatures/DVDSetAutoFatalMessaging.yml"),
),
("OSSetArenaLo", include_str!("../../assets/signatures/OSSetArenaLo.yml")),
("OSSetArenaHi", include_str!("../../assets/signatures/OSSetArenaHi.yml")),
("OSSetMEM1ArenaLo", include_str!("../../assets/signatures/OSSetMEM1ArenaLo.yml")),
("OSSetMEM1ArenaHi", include_str!("../../assets/signatures/OSSetMEM1ArenaHi.yml")),
("OSSetMEM2ArenaLo", include_str!("../../assets/signatures/OSSetMEM2ArenaLo.yml")),
("OSSetMEM2ArenaHi", include_str!("../../assets/signatures/OSSetMEM2ArenaHi.yml")),
("__OSInitAudioSystem", include_str!("../../assets/signatures/__OSInitAudioSystem.yml")),
(
"__OSInitMemoryProtection",
include_str!("../../assets/signatures/__OSInitMemoryProtection.yml"),
),
// ("BATConfig", include_str!("../../assets/signatures/BATConfig.yml")), TODO
("ReportOSInfo", include_str!("../../assets/signatures/ReportOSInfo.yml")),
("__check_pad3", include_str!("../../assets/signatures/__check_pad3.yml")),
("OSResetSystem", include_str!("../../assets/signatures/OSResetSystem.yml")),
("OSReturnToMenu", include_str!("../../assets/signatures/OSReturnToMenu.yml")),
("__OSReturnToMenu", include_str!("../../assets/signatures/__OSReturnToMenu.yml")),
("__OSShutdownDevices", include_str!("../../assets/signatures/__OSShutdownDevices.yml")),
("__OSInitSram", include_str!("../../assets/signatures/__OSInitSram.yml")),
("__OSSyncSram", include_str!("../../assets/signatures/__OSSyncSram.yml")),
(
"__OSGetExceptionHandler",
include_str!("../../assets/signatures/__OSGetExceptionHandler.yml"),
),
(
"OSRegisterResetFunction",
include_str!("../../assets/signatures/OSRegisterResetFunction.yml"),
),
(
"OSRegisterShutdownFunction",
include_str!("../../assets/signatures/OSRegisterShutdownFunction.yml"),
),
(
"DecrementerExceptionHandler",
include_str!("../../assets/signatures/DecrementerExceptionHandler.yml"),
),
(
"DecrementerExceptionCallback",
include_str!("../../assets/signatures/DecrementerExceptionCallback.yml"),
),
("__OSInterruptInit", include_str!("../../assets/signatures/__OSInterruptInit.yml")),
("__OSContextInit", include_str!("../../assets/signatures/__OSContextInit.yml")),
("OSSwitchFPUContext", include_str!("../../assets/signatures/OSSwitchFPUContext.yml")),
("OSReport", include_str!("../../assets/signatures/OSReport.yml")),
("TRK_main", include_str!("../../assets/signatures/TRK_main.yml")),
("TRKNubWelcome", include_str!("../../assets/signatures/TRKNubWelcome.yml")),
("TRKInitializeNub", include_str!("../../assets/signatures/TRKInitializeNub.yml")),
(
"TRKInitializeIntDrivenUART",
include_str!("../../assets/signatures/TRKInitializeIntDrivenUART.yml"),
),
("TRKEXICallBack", include_str!("../../assets/signatures/TRKEXICallBack.yml")),
("TRKLoadContext", include_str!("../../assets/signatures/TRKLoadContext.yml")),
("TRKInterruptHandler", include_str!("../../assets/signatures/TRKInterruptHandler.yml")),
("TRKExceptionHandler", include_str!("../../assets/signatures/TRKExceptionHandler.yml")),
("TRKSaveExtended1Block", include_str!("../../assets/signatures/TRKSaveExtended1Block.yml")),
("TRKNubMainLoop", include_str!("../../assets/signatures/TRKNubMainLoop.yml")),
("TRKTargetContinue", include_str!("../../assets/signatures/TRKTargetContinue.yml")),
("TRKSwapAndGo", include_str!("../../assets/signatures/TRKSwapAndGo.yml")),
(
"TRKRestoreExtended1Block",
include_str!("../../assets/signatures/TRKRestoreExtended1Block.yml"),
),
(
"TRKInterruptHandlerEnableInterrupts",
include_str!("../../assets/signatures/TRKInterruptHandlerEnableInterrupts.yml"),
),
("memset", include_str!("../../assets/signatures/memset.yml")),
(
"__msl_runtime_constraint_violation_s",
include_str!("../../assets/signatures/__msl_runtime_constraint_violation_s.yml"),
),
("ClearArena", include_str!("../../assets/signatures/ClearArena.yml")),
("IPCCltInit", include_str!("../../assets/signatures/IPCCltInit.yml")),
("__OSInitSTM", include_str!("../../assets/signatures/__OSInitSTM.yml")),
("IOS_Open", include_str!("../../assets/signatures/IOS_Open.yml")),
("__ios_Ipc2", include_str!("../../assets/signatures/__ios_Ipc2.yml")),
("IPCiProfQueueReq", include_str!("../../assets/signatures/IPCiProfQueueReq.yml")),
("SCInit", include_str!("../../assets/signatures/SCInit.yml")),
("SCReloadConfFileAsync", include_str!("../../assets/signatures/SCReloadConfFileAsync.yml")),
("NANDPrivateOpenAsync", include_str!("../../assets/signatures/NANDPrivateOpenAsync.yml")),
("nandIsInitialized", include_str!("../../assets/signatures/nandIsInitialized.yml")),
("nandOpen", include_str!("../../assets/signatures/nandOpen.yml")),
("nandGenerateAbsPath", include_str!("../../assets/signatures/nandGenerateAbsPath.yml")),
("nandGetHeadToken", include_str!("../../assets/signatures/nandGetHeadToken.yml")),
("ISFS_OpenAsync", include_str!("../../assets/signatures/ISFS_OpenAsync.yml")),
("nandConvertErrorCode", include_str!("../../assets/signatures/nandConvertErrorCode.yml")),
(
"NANDLoggingAddMessageAsync",
include_str!("../../assets/signatures/NANDLoggingAddMessageAsync.yml"),
),
(
"__NANDPrintErrorMessage",
include_str!("../../assets/signatures/__NANDPrintErrorMessage.yml"),
),
("__OSInitNet", include_str!("../../assets/signatures/__OSInitNet.yml")),
("__DVDCheckDevice", include_str!("../../assets/signatures/__DVDCheckDevice.yml")),
("__OSInitPlayTime", include_str!("../../assets/signatures/__OSInitPlayTime.yml")),
("__OSStartPlayRecord", include_str!("../../assets/signatures/__OSStartPlayRecord.yml")),
("NANDInit", include_str!("../../assets/signatures/NANDInit.yml")),
("ISFS_OpenLib", include_str!("../../assets/signatures/ISFS_OpenLib.yml")),
("ESP_GetTitleId", include_str!("../../assets/signatures/ESP_GetTitleId.yml")),
(
"NANDSetAutoErrorMessaging",
include_str!("../../assets/signatures/NANDSetAutoErrorMessaging.yml"),
),
("__DVDFSInit", include_str!("../../assets/signatures/__DVDFSInit.yml")),
("__DVDClearWaitingQueue", include_str!("../../assets/signatures/__DVDClearWaitingQueue.yml")),
("__DVDInitWA", include_str!("../../assets/signatures/__DVDInitWA.yml")),
("__DVDLowSetWAType", include_str!("../../assets/signatures/__DVDLowSetWAType.yml")),
("__fstLoad", include_str!("../../assets/signatures/__fstLoad.yml")),
("DVDReset", include_str!("../../assets/signatures/DVDReset.yml")),
("DVDLowReset", include_str!("../../assets/signatures/DVDLowReset.yml")),
("DVDReadDiskID", include_str!("../../assets/signatures/DVDReadDiskID.yml")),
("stateReady", include_str!("../../assets/signatures/stateReady.yml")),
("DVDLowWaitCoverClose", include_str!("../../assets/signatures/DVDLowWaitCoverClose.yml")),
("__DVDStoreErrorCode", include_str!("../../assets/signatures/__DVDStoreErrorCode.yml")),
("DVDLowStopMotor", include_str!("../../assets/signatures/DVDLowStopMotor.yml")),
("DVDGetDriveStatus", include_str!("../../assets/signatures/DVDGetDriveStatus.yml")),
("printf", include_str!("../../assets/signatures/printf.yml")),
("sprintf", include_str!("../../assets/signatures/sprintf.yml")),
("vprintf", include_str!("../../assets/signatures/vprintf.yml")),
("vsprintf", include_str!("../../assets/signatures/vsprintf.yml")),
("vsnprintf", include_str!("../../assets/signatures/vsnprintf.yml")),
("__pformatter", include_str!("../../assets/signatures/__pformatter.yml")),
("longlong2str", include_str!("../../assets/signatures/longlong2str.yml")),
("__mod2u", include_str!("../../assets/signatures/__mod2u.yml")),
("__FileWrite", include_str!("../../assets/signatures/__FileWrite.yml")),
("fwrite", include_str!("../../assets/signatures/fwrite.yml")),
("__fwrite", include_str!("../../assets/signatures/__fwrite.yml")),
("__stdio_atexit", include_str!("../../assets/signatures/__stdio_atexit.yml")),
("__StringWrite", include_str!("../../assets/signatures/__StringWrite.yml")),
];
const POST_SIGNATURES: &[(&str, &str)] = &[
("RSOStaticLocateObject", include_str!("../../assets/signatures/RSOStaticLocateObject.yml")),
// ("GXInit", include_str!("../../assets/signatures/GXInit.yml")),
];
pub fn apply_signatures(obj: &mut ObjInfo) -> Result<()> {
let entry = obj.entry as u32;
if let Some(signature) =
check_signatures_str(obj, entry, include_str!("../../assets/signatures/__start.yml"))?
{
apply_signature(obj, entry, &signature)?;
}
for &(name, sig_str) in SIGNATURES {
if let Some(symbol) = obj.symbols.iter().find(|symbol| symbol.name == name) {
let addr = symbol.address as u32;
if let Some(signature) = check_signatures_str(obj, addr, sig_str)? {
apply_signature(obj, addr, &signature)?;
}
}
}
if let Some(symbol) = obj.symbols.iter().find(|symbol| symbol.name == "__init_user") {
// __init_user can be overridden, but we can still look for __init_cpp from it
let mut analyzer = AnalyzerState::default();
analyzer.process_function_at(&obj, symbol.address as u32)?;
for addr in analyzer.function_entries {
if let Some(signature) = check_signatures_str(
obj,
addr,
include_str!("../../assets/signatures/__init_cpp.yml"),
)? {
apply_signature(obj, addr, &signature)?;
break;
}
}
}
if let Some(symbol) = obj.symbols.iter().find(|symbol| symbol.name == "_ctors") {
// First entry of ctors is __init_cpp_exceptions
let section = obj.section_at(symbol.address as u32)?;
let target = read_u32(&section.data, symbol.address as u32, section.address as u32)
.ok_or_else(|| anyhow!("Failed to read _ctors data"))?;
if target != 0 {
if let Some(signature) = check_signatures_str(
obj,
target,
include_str!("../../assets/signatures/__init_cpp_exceptions.yml"),
)? {
apply_signature(obj, target, &signature)?;
}
}
}
if let Some(symbol) = obj.symbols.iter().find(|symbol| symbol.name == "_dtors") {
// Second entry of dtors is __fini_cpp_exceptions
let section = obj.section_at(symbol.address as u32)?;
let target = read_u32(&section.data, symbol.address as u32 + 4, section.address as u32)
.ok_or_else(|| anyhow!("Failed to read _dtors data"))?;
if target != 0 {
if let Some(signature) = check_signatures_str(
obj,
target,
include_str!("../../assets/signatures/__fini_cpp_exceptions.yml"),
)? {
apply_signature(obj, target, &signature)?;
}
}
}
Ok(())
}
pub fn apply_signatures_post(obj: &mut ObjInfo) -> Result<()> {
log::info!("Checking post CFA signatures...");
for &(_name, sig_str) in POST_SIGNATURES {
let signatures = parse_signatures(sig_str)?;
for symbol in obj.symbols.iter().filter(|symbol| symbol.kind == ObjSymbolKind::Function) {
let addr = symbol.address as u32;
if let Some(signature) = check_signatures(obj, addr, &signatures)? {
apply_signature(obj, addr, &signature)?;
break;
}
}
}
log::info!("Done!");
Ok(())
}
fn info(args: InfoArgs) -> Result<()> {
let mut obj = process_dol(&args.dol_file)?;
apply_signatures(&mut obj)?;
// Apply known functions from extab
let mut state = AnalyzerState::default();
for (&addr, &size) in &obj.known_functions {
state.function_entries.insert(addr);
state.function_bounds.insert(addr, addr + size);
}
for symbol in &obj.symbols {
if symbol.kind != ObjSymbolKind::Function {
continue;
}
state.function_entries.insert(symbol.address as u32);
if !symbol.size_known {
continue;
}
state.function_bounds.insert(symbol.address as u32, (symbol.address + symbol.size) as u32);
}
// Also check the start of each code section
for section in &obj.sections {
if section.kind == ObjSectionKind::Code {
state.function_entries.insert(section.address as u32);
}
}
let mut state = AnalyzerState::default();
state.detect_functions(&obj)?;
log::info!("Discovered {} functions", state.function_slices.len());
@@ -397,9 +109,7 @@ fn info(args: InfoArgs) -> Result<()> {
}
println!("\nDiscovered symbols:");
println!("\t{: >23} | {: <10} | {: <10}", "Name", "Address", "Size");
let mut symbols = obj.symbols.clone();
symbols.sort_by_key(|sym| sym.address);
for symbol in symbols {
for (_, symbol) in obj.symbols.for_range(..) {
if symbol.name.starts_with('@') || symbol.name.starts_with("fn_") {
continue;
}
@@ -414,83 +124,39 @@ fn info(args: InfoArgs) -> Result<()> {
Ok(())
}
fn disasm(args: DisasmArgs) -> Result<()> {
let mut obj = process_dol(&args.dol_file)?;
log::info!("Performing initial control flow analysis");
fn split(args: SplitArgs) -> Result<()> {
log::info!("Loading {}", args.in_file.display());
let mut obj = process_dol(&args.in_file)?;
// if detect_sda_bases(&mut obj).context("Failed to locate SDA bases")? {
// let (sda2_base, sda_base) = obj.sda_bases.unwrap();
// log::info!("Found _SDA2_BASE_ @ {:#010X}, _SDA_BASE_ @ {:#010X}", sda2_base, sda_base);
// } else {
// bail!("Unable to locate SDA bases");
// }
if let Some(map) = &args.map_file {
let mmap = map_file(map)?;
let _entries = process_map(map_reader(&mmap))?;
}
if let Some(splits_file) = &args.splits_file {
let map = map_file(splits_file)?;
apply_splits(map_reader(&map), &mut obj)?;
if let Some(splits_path) = &args.splits_file {
if splits_path.is_file() {
let map = map_file(splits_path)?;
apply_splits(map_reader(&map), &mut obj)?;
}
}
let mut state = AnalyzerState::default();
if let Some(symbols_path) = &args.symbols_file {
let map = map_file(symbols_path)?;
for result in map_reader(&map).lines() {
let line = match result {
Ok(line) => line,
Err(e) => bail!("Failed to process symbols file: {e:?}"),
};
if let Some(symbol) = parse_symbol_line(&line, &obj)? {
// if symbol.kind == ObjSymbolKind::Function {
// state.function_entries.insert(symbol.address as u32);
// if symbol.size_known {
// state
// .function_bounds
// .insert(symbol.address as u32, (symbol.address + symbol.size) as u32);
// }
// }
if let Some(existing_symbol) = obj
.symbols
.iter_mut()
.find(|e| e.address == symbol.address && e.kind == symbol.kind)
{
*existing_symbol = symbol;
} else {
obj.symbols.push(symbol);
if symbols_path.is_file() {
let map = map_file(symbols_path)?;
for result in map_reader(&map).lines() {
let line = match result {
Ok(line) => line,
Err(e) => bail!("Failed to process symbols file: {e:?}"),
};
if let Some(symbol) = parse_symbol_line(&line, &mut obj)? {
obj.add_symbol(symbol, true)?;
}
}
}
}
// TODO move before symbols?
log::info!("Performing signature analysis");
apply_signatures(&mut obj)?;
// Apply known functions from extab
for (&addr, &size) in &obj.known_functions {
state.function_entries.insert(addr);
state.function_bounds.insert(addr, addr + size);
}
for symbol in &obj.symbols {
if symbol.kind != ObjSymbolKind::Function {
continue;
}
state.function_entries.insert(symbol.address as u32);
if !symbol.size_known {
continue;
}
state.function_bounds.insert(symbol.address as u32, (symbol.address + symbol.size) as u32);
}
// Also check the start of each code section
for section in &obj.sections {
if section.kind == ObjSectionKind::Code {
state.function_entries.insert(section.address as u32);
}
}
log::info!("Detecting function boundaries");
state.detect_functions(&obj)?;
log::info!("Discovered {} functions", state.function_slices.len());
@@ -505,78 +171,112 @@ fn disasm(args: DisasmArgs) -> Result<()> {
log::info!("Applying relocations");
tracker.apply(&mut obj, false)?;
if args.splits_file.is_some() {
log::info!("Detecting object boundaries");
detect_object_boundaries(&mut obj)?;
log::info!("Splitting {} objects", obj.link_order.len());
let split_objs = split_obj(&obj)?;
// Create out dirs
let asm_dir = args.out.join("asm");
let include_dir = args.out.join("include");
let obj_dir = args.out.join("expected");
DirBuilder::new().recursive(true).create(&include_dir)?;
fs::write(include_dir.join("macros.inc"), include_bytes!("../../assets/macros.inc"))?;
log::info!("Writing object files");
let mut file_map = HashMap::<String, Vec<u8>>::new();
for (unit, split_obj) in obj.link_order.iter().zip(&split_objs) {
let out_obj = write_elf(split_obj)?;
match file_map.entry(unit.clone()) {
hash_map::Entry::Vacant(e) => e.insert(out_obj),
hash_map::Entry::Occupied(_) => bail!("Duplicate file {unit}"),
};
}
let mut rsp_file = BufWriter::new(File::create("rsp")?);
for unit in &obj.link_order {
let object = file_map
.get(unit)
.ok_or_else(|| anyhow!("Failed to find object file for unit '{unit}'"))?;
let out_path = obj_dir.join(unit);
writeln!(rsp_file, "{}", out_path.display())?;
if let Some(parent) = out_path.parent() {
DirBuilder::new().recursive(true).create(parent)?;
}
let mut file = File::create(&out_path)
.with_context(|| format!("Failed to create '{}'", out_path.display()))?;
file.write_all(object)?;
file.flush()?;
}
rsp_file.flush()?;
log::info!("Writing disassembly");
let mut files_out = File::create(args.out.join("link_order.txt"))?;
for (unit, split_obj) in obj.link_order.iter().zip(&split_objs) {
let out_path = asm_dir.join(format!("{}.s", unit.trim_end_matches(".o")));
if let Some(parent) = out_path.parent() {
DirBuilder::new().recursive(true).create(parent)?;
}
let mut w = BufWriter::new(File::create(out_path)?);
write_asm(&mut w, split_obj)?;
w.flush()?;
writeln!(files_out, "{}", unit)?;
}
files_out.flush()?;
} else {
log::info!("Writing disassembly");
let mut w = BufWriter::new(File::create("out.s")?);
write_asm(&mut w, &obj)?;
}
log::info!("Detecting strings");
detect_strings(&mut obj)?;
if let Some(symbols_path) = &args.symbols_file {
let mut symbols_writer = BufWriter::new(
File::create(&symbols_path)
File::create(symbols_path)
.with_context(|| format!("Failed to create '{}'", symbols_path.display()))?,
);
write_symbols(&mut symbols_writer, &obj)?;
}
// (debugging) validate against ELF
if let Some(file) = args.elf_file {
validate(&obj, &file, &state)?;
if let Some(splits_path) = &args.splits_file {
let mut splits_writer = BufWriter::new(
File::create(splits_path)
.with_context(|| format!("Failed to create '{}'", splits_path.display()))?,
);
write_splits(&mut splits_writer, &obj)?;
}
log::info!("Adjusting splits");
update_splits(&mut obj)?;
log::info!("Splitting {} objects", obj.link_order.len());
let split_objs = split_obj(&obj)?;
// Create out dirs
let asm_dir = args.out_dir.join("asm");
let include_dir = args.out_dir.join("include");
let obj_dir = args.out_dir.clone();
DirBuilder::new().recursive(true).create(&include_dir)?;
fs::write(include_dir.join("macros.inc"), include_str!("../../assets/macros.inc"))?;
log::info!("Writing object files");
let mut file_map = HashMap::<String, Vec<u8>>::new();
for (unit, split_obj) in obj.link_order.iter().zip(&split_objs) {
let out_obj = write_elf(split_obj)?;
match file_map.entry(unit.clone()) {
hash_map::Entry::Vacant(e) => e.insert(out_obj),
hash_map::Entry::Occupied(_) => bail!("Duplicate file {unit}"),
};
}
let mut rsp_file = BufWriter::new(File::create(args.out_dir.join("rsp"))?);
for unit in &obj.link_order {
let object = file_map
.get(unit)
.ok_or_else(|| anyhow!("Failed to find object file for unit '{unit}'"))?;
let out_path = obj_dir.join(obj_path_for_unit(unit));
writeln!(rsp_file, "{}", out_path.display())?;
if let Some(parent) = out_path.parent() {
DirBuilder::new().recursive(true).create(parent)?;
}
let mut file = File::create(&out_path)
.with_context(|| format!("Failed to create '{}'", out_path.display()))?;
file.write_all(object)?;
file.flush()?;
}
rsp_file.flush()?;
// Generate ldscript.lcf
fs::write(args.out_dir.join("ldscript.lcf"), generate_ldscript(&obj)?)?;
log::info!("Writing disassembly");
// let mut files_out = File::create(args.out_dir.join("build.ps1"))?;
// writeln!(files_out, "$ErrorActionPreference = 'Stop'")?;
// writeln!(
// files_out,
// "$asflags = '-mgekko', '-I', '{}', '--defsym', 'version=0', '-W', '--strip-local-absolute', '-gdwarf-2'",
// include_dir.display()
// )?;
// writeln!(files_out, "$env:PATH = \"$env:PATH;C:\\devkitPro\\devkitPPC\\bin\"")?;
for (unit, split_obj) in obj.link_order.iter().zip(&split_objs) {
let out_path = asm_dir.join(asm_path_for_unit(unit));
if let Some(parent) = out_path.parent() {
DirBuilder::new().recursive(true).create(parent)?;
}
let mut w = BufWriter::new(File::create(&out_path)?);
write_asm(&mut w, split_obj)?;
w.flush()?;
// let obj_path = obj_dir.join(obj_path_for_unit(unit));
// writeln!(files_out, "Write-Host 'Compiling {}'", obj_path.display())?;
// writeln!(
// files_out,
// "powerpc-eabi-as @asflags -o '{}' '{}'",
// obj_path.display(),
// out_path.display()
// )?;
// writeln!(
// files_out,
// "dtk elf fixup '{}' '{}'",
// obj_path.display(),
// obj_path.display()
// )?;
}
// files_out.flush()?;
// (debugging) validate against ELF
if let Some(file) = &args.elf_file {
validate(&obj, file, &state)?;
}
Ok(())
}
@@ -610,11 +310,7 @@ fn validate<P: AsRef<Path>>(obj: &ObjInfo, elf_file: P, state: &AnalyzerState) -
if section.kind != ObjSectionKind::Code {
continue;
}
for (_symbol_idx, symbol) in real_obj.symbols_for_section(section.index) {
// if symbol.name.starts_with("switch_") {
// continue;
// }
// if symbol.kind == ObjSymbolKind::Function {
for (_symbol_idx, symbol) in real_obj.symbols.for_section(section) {
real_functions.insert(symbol.address as u32, symbol.name.clone());
match state.function_bounds.get(&(symbol.address as u32)) {
Some(&end) => {
@@ -636,7 +332,6 @@ fn validate<P: AsRef<Path>>(obj: &ObjInfo, elf_file: P, state: &AnalyzerState) -
);
}
}
// }
}
}
for (&start, &end) in &state.function_bounds {
@@ -653,7 +348,8 @@ fn validate<P: AsRef<Path>>(obj: &ObjInfo, elf_file: P, state: &AnalyzerState) -
);
}
}
return Ok(()); // TODO
// return Ok(()); // TODO
for real_section in &real_obj.sections {
let obj_section = match obj.sections.get(real_section.index) {
Some(v) => v,
@@ -661,10 +357,11 @@ fn validate<P: AsRef<Path>>(obj: &ObjInfo, elf_file: P, state: &AnalyzerState) -
};
let real_map = real_section.build_relocation_map()?;
let obj_map = obj_section.build_relocation_map()?;
for (&real_addr, real_reloc) in &real_map {
let real_symbol = &real_obj.symbols[real_reloc.target_symbol];
for (&real_addr, &real_reloc_idx) in &real_map {
let real_reloc = &real_section.relocations[real_reloc_idx];
let real_symbol = real_obj.symbols.at(real_reloc.target_symbol);
let obj_reloc = match obj_map.get(&real_addr) {
Some(v) => v,
Some(v) => &obj_section.relocations[*v],
None => {
// Ignore GCC local jump branches
if real_symbol.kind == ObjSymbolKind::Section
@@ -688,7 +385,7 @@ fn validate<P: AsRef<Path>>(obj: &ObjInfo, elf_file: P, state: &AnalyzerState) -
continue;
}
};
let obj_symbol = &obj.symbols[obj_reloc.target_symbol];
let obj_symbol = obj.symbols.at(obj_reloc.target_symbol);
if real_reloc.kind != obj_reloc.kind {
log::warn!(
"Relocation type mismatch @ {:#010X}: {:?} != {:?}",
@@ -714,8 +411,9 @@ fn validate<P: AsRef<Path>>(obj: &ObjInfo, elf_file: P, state: &AnalyzerState) -
continue;
}
}
for (&obj_addr, obj_reloc) in &obj_map {
let obj_symbol = &obj.symbols[obj_reloc.target_symbol];
for (&obj_addr, &obj_reloc_idx) in &obj_map {
let obj_reloc = &obj_section.relocations[obj_reloc_idx];
let obj_symbol = obj.symbols.at(obj_reloc.target_symbol);
if !real_map.contains_key(&obj_addr) {
log::warn!(
"Relocation not real @ {:#010X} {:?} to {:#010X}+{:X} ({})",

View File

@@ -11,9 +11,8 @@ use object::{elf, Object, ObjectSection, ObjectSymbol, RelocationKind, Relocatio
use crate::util::{
dwarf::{
process_address, process_offset, process_type, process_variable_location,
read_debug_section, type_string, ud_type, ud_type_def, ud_type_string, AttributeKind,
TagKind, TypeKind,
process_address, process_type, process_variable_location, read_debug_section, type_string,
ud_type, ud_type_def, ud_type_string, AttributeKind, TagKind,
},
file::map_file,
};
@@ -61,13 +60,13 @@ fn dump(args: DumpArgs) -> Result<()> {
};
let name = String::from_utf8_lossy(e.header().identifier()).to_string();
let mut data = vec![0u8; e.header().size() as usize];
e.read(&mut data)?;
e.read_exact(&mut data)?;
let obj_file = object::read::File::parse(&*data)?;
let debug_section = match obj_file.section_by_name(".debug") {
Some(section) => {
log::info!("Processing '{}'", name);
section
},
}
None => {
log::warn!("Object '{}' missing .debug section", name);
continue;
@@ -76,7 +75,7 @@ fn dump(args: DumpArgs) -> Result<()> {
if let Some(out_path) = &args.out {
// TODO make a basename method
let name = name.trim_start_matches("D:").replace('\\', "/");
let name = name.rsplit_once('/').map(|(a, b)| b).unwrap_or(&name);
let name = name.rsplit_once('/').map(|(_, b)| b).unwrap_or(&name);
let file_path = out_path.join(format!("{}.txt", name));
let mut file = BufWriter::new(File::create(file_path)?);
dump_debug_section(&mut file, &obj_file, debug_section)?;

View File

@@ -2,16 +2,17 @@ use std::{
collections::{btree_map, hash_map, BTreeMap, HashMap},
fs,
fs::{DirBuilder, File},
io::{BufRead, BufReader, BufWriter, Write},
io::{BufWriter, Write},
path::PathBuf,
};
use anyhow::{anyhow, bail, ensure, Context, Result};
use argh::FromArgs;
use object::{
elf,
write::{Mangling, SectionId, SymbolId},
Object, ObjectSection, ObjectSymbol, RelocationKind, RelocationTarget, SectionFlags,
SectionIndex, SectionKind, SymbolFlags, SymbolKind, SymbolScope, SymbolSection,
FileFlags, Object, ObjectSection, ObjectSymbol, RelocationKind, RelocationTarget, SectionFlags,
SectionIndex, SectionKind, SymbolFlags, SymbolIndex, SymbolKind, SymbolScope, SymbolSection,
};
use crate::{
@@ -24,7 +25,7 @@ use crate::{
asm::write_asm,
config::{write_splits, write_symbols},
elf::{process_elf, write_elf},
file::buf_reader,
file::process_rsp,
},
};
@@ -92,9 +93,6 @@ pub struct ConfigArgs {
#[argh(positional)]
/// output directory
out_dir: PathBuf,
#[argh(option, short = 'm')]
/// path to obj_files.mk
obj_files: Option<PathBuf>,
}
#[derive(FromArgs, PartialEq, Eq, Debug)]
@@ -137,29 +135,12 @@ fn config(args: ConfigArgs) -> Result<()> {
}
{
let obj_files = if let Some(path) = &args.obj_files {
Some(
BufReader::new(
File::open(path)
.with_context(|| format!("Failed to open '{}'", path.display()))?,
)
.lines()
.filter(|line| match line {
Ok(line) => line.contains(".o"),
Err(_) => false,
})
.map(|result| result.unwrap())
.collect::<Vec<String>>(),
)
} else {
None
};
let splits_path = args.out_dir.join("splits.txt");
let mut splits_writer = BufWriter::new(
File::create(&splits_path)
.with_context(|| format!("Failed to create '{}'", splits_path.display()))?,
);
write_splits(&mut splits_writer, &obj, obj_files)?;
write_splits(&mut splits_writer, &obj)?;
}
Ok(())
@@ -257,13 +238,22 @@ fn file_name_from_unit(str: &str, suffix: &str) -> String {
const ASM_SUFFIX: &str = " (asm)";
// fn fixup(args: FixupArgs) -> Result<()> {
// let obj = process_elf(&args.in_file)?;
// let out = write_elf(&obj)?;
// fs::write(&args.out_file, &out).context("Failed to create output file")?;
// Ok(())
// }
fn fixup(args: FixupArgs) -> Result<()> {
let in_buf = fs::read(&args.in_file)
.with_context(|| format!("Failed to open input file: '{}'", args.in_file.display()))?;
let in_file = object::read::File::parse(&*in_buf).context("Failed to parse input ELF")?;
let mut out_file =
object::write::Object::new(in_file.format(), in_file.architecture(), in_file.endianness());
out_file.set_mangling(Mangling::None);
out_file.flags =
FileFlags::Elf { os_abi: elf::ELFOSABI_SYSV, abi_version: 0, e_flags: elf::EF_PPC_EMB };
out_file.mangling = Mangling::None;
// Write file symbol first
let mut file_symbol_found = false;
@@ -317,7 +307,7 @@ fn fixup(args: FixupArgs) -> Result<()> {
} else {
out_section.set_data(section.uncompressed_data()?.into_owned(), section.align());
}
if has_section_flags(section.flags(), object::elf::SHF_ALLOC)? {
if has_section_flags(section.flags(), elf::SHF_ALLOC)? {
// Generate section symbol
out_file.section_symbol(section_id);
}
@@ -398,9 +388,9 @@ fn fixup(args: FixupArgs) -> Result<()> {
// This is a hack to avoid replacement with a section symbol
// See [`object::write::elf::object::elf_fixup_relocation`]
RelocationKind::Absolute => RelocationKind::Elf(if addr & 3 == 0 {
object::elf::R_PPC_ADDR32
elf::R_PPC_ADDR32
} else {
object::elf::R_PPC_UADDR32
elf::R_PPC_UADDR32
}),
other => other,
};
@@ -442,7 +432,9 @@ fn to_write_symbol_section(
}
}
fn to_write_symbol_flags(flags: SymbolFlags<SectionIndex>) -> Result<SymbolFlags<SectionId>> {
fn to_write_symbol_flags(
flags: SymbolFlags<SectionIndex, SymbolIndex>,
) -> Result<SymbolFlags<SectionId, SymbolId>> {
match flags {
SymbolFlags::Elf { st_info, st_other } => Ok(SymbolFlags::Elf { st_info, st_other }),
SymbolFlags::None => Ok(SymbolFlags::None),
@@ -475,25 +467,7 @@ fn has_section_flags(flags: SectionFlags, flag: u32) -> Result<bool> {
fn signatures(args: SignaturesArgs) -> Result<()> {
// Process response files (starting with '@')
let mut files = Vec::with_capacity(args.files.len());
for path in args.files {
let path_str =
path.to_str().ok_or_else(|| anyhow!("'{}' is not valid UTF-8", path.display()))?;
match path_str.strip_prefix('@') {
Some(rsp_file) => {
let reader = buf_reader(rsp_file)?;
for result in reader.lines() {
let line = result?;
if !line.is_empty() {
files.push(PathBuf::from(line));
}
}
}
None => {
files.push(path);
}
}
}
let files = process_rsp(&args.files)?;
let mut signatures: HashMap<String, FunctionSignature> = HashMap::new();
for path in files {

View File

@@ -65,10 +65,10 @@ pub fn run(args: Args) -> Result<()> {
out.seek(SeekFrom::Start(offset as u64))?;
// Text sections
for section in obj_file.sections() {
if section.kind() != SectionKind::Text {
continue;
}
for section in
obj_file.sections().filter(|s| section_kind(s) == SectionKind::Text && is_alloc(s.flags()))
{
log::debug!("Processing text section '{}'", section.name().unwrap_or("[error]"));
let address = section.address() as u32;
let size = align32(section.size() as u32);
*header.text_sections.get_mut(header.text_section_count).ok_or_else(|| {
@@ -83,10 +83,10 @@ pub fn run(args: Args) -> Result<()> {
}
// Data sections
for section in obj_file.sections() {
if section.kind() != SectionKind::Data && section.kind() != SectionKind::ReadOnlyData {
continue;
}
for section in
obj_file.sections().filter(|s| section_kind(s) == SectionKind::Data && is_alloc(s.flags()))
{
log::debug!("Processing data section '{}'", section.name().unwrap_or("[error]"));
let address = section.address() as u32;
let size = align32(section.size() as u32);
*header.data_sections.get_mut(header.data_section_count).ok_or_else(|| {
@@ -101,10 +101,10 @@ pub fn run(args: Args) -> Result<()> {
}
// BSS sections
for section in obj_file.sections() {
if section.kind() != SectionKind::UninitializedData {
continue;
}
for section in obj_file
.sections()
.filter(|s| section_kind(s) == SectionKind::UninitializedData && is_alloc(s.flags()))
{
let address = section.address() as u32;
let size = section.size() as u32;
if header.bss_address == 0 {
@@ -162,3 +162,28 @@ fn write_aligned<T: Write>(out: &mut T, bytes: &[u8], aligned_size: u32) -> std:
}
Ok(())
}
// Some ELF files don't have the proper section kind set (for small data sections in particular)
// so we map the section name to the expected section kind when possible.
#[inline]
fn section_kind(section: &object::Section) -> SectionKind {
section
.name()
.ok()
.and_then(|name| match name {
".init" | ".text" | ".vmtext" | ".dbgtext" => Some(SectionKind::Text),
".ctors" | ".dtors" | ".data" | ".rodata" | ".sdata" | ".sdata2" | "extab"
| "extabindex" => Some(SectionKind::Data),
".bss" | ".sbss" | ".sbss2" => Some(SectionKind::UninitializedData),
_ => None,
})
.unwrap_or_else(|| match section.kind() {
SectionKind::ReadOnlyData => SectionKind::Data,
kind => kind,
})
}
#[inline]
fn is_alloc(flags: object::SectionFlags) -> bool {
matches!(flags, object::SectionFlags::Elf { sh_flags } if sh_flags & object::elf::SHF_ALLOC as u64 != 0)
}

View File

@@ -1,11 +1,13 @@
#![allow(clippy::needless_borrow)]
use std::path::PathBuf;
use anyhow::{bail, ensure, Result};
use anyhow::{bail, Result};
use argh::FromArgs;
use cwdemangle::{demangle, DemangleOptions};
use crate::util::{
file::{map_file, map_reader},
map::{process_map, resolve_link_order, SymbolEntry, SymbolRef},
map::{process_map, SymbolEntry, SymbolRef},
};
#[derive(FromArgs, PartialEq, Debug)]
@@ -96,11 +98,8 @@ fn entries(args: EntriesArgs) -> Result<()> {
if symbol_ref.name.starts_with('@') {
continue;
}
if let Some(symbol) = entries.symbols.get(symbol_ref) {
println!("{}", symbol.demangled.as_ref().unwrap_or(&symbol.name));
} else {
println!("Symbol not found: {}", symbol_ref.name);
}
let demangled = demangle(&symbol_ref.name, &DemangleOptions::default());
println!("{}", demangled.as_deref().unwrap_or(&symbol_ref.name));
}
}
None => bail!("Failed to find entries for TU '{}' in map", args.unit),
@@ -111,98 +110,115 @@ fn entries(args: EntriesArgs) -> Result<()> {
fn symbol(args: SymbolArgs) -> Result<()> {
let map = map_file(&args.map_file)?;
let entries = process_map(map_reader(&map))?;
let mut opt_ref: Option<(SymbolRef, SymbolEntry)> = None;
for (symbol_ref, entry) in &entries.symbols {
if symbol_ref.name == args.symbol {
ensure!(opt_ref.is_none(), "Symbol '{}' found in multiple TUs", args.symbol);
opt_ref = Some((symbol_ref.clone(), entry.clone()));
}
}
match opt_ref {
Some((symbol_ref, symbol)) => {
println!("Located symbol {}", symbol.demangled.as_ref().unwrap_or(&symbol.name));
if let Some(vec) = entries.entry_references.get_vec(&symbol_ref) {
println!("\nReferences:");
for x in vec {
if let Some(reference) = entries.symbols.get(x) {
println!(
">>> {} ({:?},{:?}) [{}]",
reference.demangled.as_ref().unwrap_or(&reference.name),
reference.kind,
reference.visibility,
reference.unit.as_deref().unwrap_or("[generated]")
);
} else {
println!(">>> {} (NOT FOUND)", x.name);
}
}
}
if let Some(vec) = entries.entry_referenced_from.get_vec(&symbol_ref) {
println!("\nReferenced from:");
for x in vec {
if let Some(reference) = entries.symbols.get(x) {
println!(
">>> {} ({:?}, {:?}) [{}]",
reference.demangled.as_ref().unwrap_or(&reference.name),
reference.kind,
reference.visibility,
reference.unit.as_deref().unwrap_or("[generated]")
);
} else {
println!(">>> {} (NOT FOUND)", x.name);
}
}
}
println!("\n");
}
None => bail!("Failed to find symbol '{}' in map", args.symbol),
}
let opt_ref: Option<(SymbolRef, SymbolEntry)> = None;
_ = entries;
_ = opt_ref;
// TODO
// for (symbol_ref, entry) in &entries.symbols {
// if symbol_ref.name == args.symbol {
// ensure!(opt_ref.is_none(), "Symbol '{}' found in multiple TUs", args.symbol);
// opt_ref = Some((symbol_ref.clone(), entry.clone()));
// }
// }
// match opt_ref {
// Some((symbol_ref, symbol)) => {
// println!("Located symbol {}", symbol.demangled.as_ref().unwrap_or(&symbol.name));
// if let Some(vec) = entries.entry_references.get_vec(&symbol_ref) {
// println!("\nReferences:");
// for x in vec {
// if let Some(reference) = entries.symbols.get(x) {
// println!(
// ">>> {} ({:?},{:?}) [{}]",
// reference.demangled.as_ref().unwrap_or(&reference.name),
// reference.kind,
// reference.visibility,
// reference.unit.as_deref().unwrap_or("[generated]")
// );
// } else {
// println!(">>> {} (NOT FOUND)", x.name);
// }
// }
// }
// if let Some(vec) = entries.entry_referenced_from.get_vec(&symbol_ref) {
// println!("\nReferenced from:");
// for x in vec {
// if let Some(reference) = entries.symbols.get(x) {
// println!(
// ">>> {} ({:?}, {:?}) [{}]",
// reference.demangled.as_ref().unwrap_or(&reference.name),
// reference.kind,
// reference.visibility,
// reference.unit.as_deref().unwrap_or("[generated]")
// );
// } else {
// println!(">>> {} (NOT FOUND)", x.name);
// }
// }
// }
// println!("\n");
// }
// None => bail!("Failed to find symbol '{}' in map", args.symbol),
// }
Ok(())
}
fn order(args: OrderArgs) -> Result<()> {
let map = map_file(&args.map_file)?;
let entries = process_map(map_reader(&map))?;
let order = resolve_link_order(&entries.unit_order)?;
for unit in order {
println!("{unit}");
}
_ = entries;
// TODO
// let order = resolve_link_order(&entries.unit_order)?;
// for unit in order {
// println!("{unit}");
// }
Ok(())
}
fn slices(args: SlicesArgs) -> Result<()> {
let map = map_file(&args.map_file)?;
let entries = process_map(map_reader(&map))?;
let order = resolve_link_order(&entries.unit_order)?;
for unit in order {
let unit_path = if let Some((lib, name)) = unit.split_once(' ') {
format!("{}/{}", lib.strip_suffix(".a").unwrap_or(lib), name)
} else if let Some(strip) = unit.strip_suffix(".o") {
format!("{strip}.c")
} else {
unit.clone()
};
println!("{unit_path}:");
// let mut ranges = Vec::<(String, Range<u32>)>::new();
// match entries.unit_section_ranges.get(&unit) {
// Some(sections) => {
// for (name, range) in sections {
// ranges.push((name.clone(), range.clone()));
// }
// }
// None => bail!("Failed to locate sections for unit '{unit}'"),
// }
// ranges.sort_by(|(_, a), (_, b)| a.start.cmp(&b.start));
// for (name, range) in ranges {
// println!("\t{}: [{:#010x}, {:#010x}]", name, range.start, range.end);
// }
}
_ = entries;
// TODO
// let order = resolve_link_order(&entries.unit_order)?;
// for unit in order {
// let unit_path = if let Some((lib, name)) = unit.split_once(' ') {
// format!("{}/{}", lib.strip_suffix(".a").unwrap_or(lib), name)
// } else if let Some(strip) = unit.strip_suffix(".o") {
// format!("{strip}.c")
// } else {
// unit.clone()
// };
// println!("{unit_path}:");
// let mut ranges = Vec::<(String, Range<u32>)>::new();
// match entries.unit_section_ranges.get(&unit) {
// Some(sections) => {
// for (name, range) in sections {
// ranges.push((name.clone(), range.clone()));
// }
// }
// None => bail!("Failed to locate sections for unit '{unit}'"),
// }
// ranges.sort_by(|(_, a), (_, b)| a.start.cmp(&b.start));
// for (name, range) in ranges {
// println!("\t{}: [{:#010x}, {:#010x}]", name, range.start, range.end);
// }
// }
Ok(())
}
fn symbols(args: SymbolsArgs) -> Result<()> {
let map = map_file(&args.map_file)?;
let _entries = process_map(map_reader(&map))?;
let entries = process_map(map_reader(&map))?;
_ = entries;
// TODO
// for (address, symbol) in entries.address_to_symbol {
// if symbol.name.starts_with('@') {
// continue;

View File

@@ -12,14 +12,16 @@ use crate::{
analysis::{
cfa::AnalyzerState,
pass::{AnalysisPass, FindSaveRestSleds, FindTRKInterruptVectorTable},
signatures::apply_signatures,
tracker::Tracker,
},
cmd::dol::apply_signatures,
obj::{ObjInfo, ObjReloc, ObjRelocKind, ObjSection, ObjSectionKind, ObjSymbol, ObjSymbolKind},
array_ref_mut,
obj::{ObjInfo, ObjReloc, ObjRelocKind, ObjSection, ObjSymbol, ObjSymbolKind},
util::{
dol::process_dol,
elf::write_elf,
nested::{NestedMap, NestedVec},
file::{map_file, map_reader, FileIterator},
nested::NestedMap,
rel::process_rel,
},
};
@@ -71,7 +73,8 @@ pub fn run(args: Args) -> Result<()> {
}
fn info(args: InfoArgs) -> Result<()> {
let rel = process_rel(&args.rel_file)?;
let map = map_file(args.rel_file)?;
let rel = process_rel(map_reader(&map))?;
println!("Read REL module ID {}", rel.module_id);
// println!("REL: {:#?}", rel);
Ok(())
@@ -81,22 +84,30 @@ fn info(args: InfoArgs) -> Result<()> {
const fn align32(x: u32) -> u32 { (x + 31) & !31 }
fn merge(args: MergeArgs) -> Result<()> {
let mut module_map = BTreeMap::<u32, ObjInfo>::new();
log::info!("Loading {}", args.dol_file.display());
let mut obj = process_dol(&args.dol_file)?;
apply_signatures(&mut obj)?;
for path in &args.rel_files {
log::info!("Performing signature analysis");
apply_signatures(&mut obj)?;
let Some(arena_lo) = obj.arena_lo else { bail!("Failed to locate __ArenaLo in DOL") };
let mut processed = 0;
let mut module_map = BTreeMap::<u32, ObjInfo>::new();
for result in FileIterator::new(&args.rel_files)? {
let (path, entry) = result?;
log::info!("Loading {}", path.display());
let obj = process_rel(path)?;
let obj = process_rel(entry.as_reader())?;
match module_map.entry(obj.module_id) {
btree_map::Entry::Vacant(e) => e.insert(obj),
btree_map::Entry::Occupied(_) => bail!("Duplicate module ID {}", obj.module_id),
};
processed += 1;
}
log::info!("Merging {} REL(s)", processed);
let mut section_map: BTreeMap<u32, BTreeMap<u32, u32>> = BTreeMap::new();
let mut offset = align32(obj.arena_lo.unwrap() + 0x2000);
for (_, module) in &module_map {
let mut offset = align32(arena_lo + 0x2000);
for module in module_map.values() {
for mod_section in &module.sections {
let section_idx = obj.sections.len();
ensure!(mod_section.relocations.is_empty(), "Unsupported relocations during merge");
@@ -115,9 +126,8 @@ fn merge(args: MergeArgs) -> Result<()> {
section_known: mod_section.section_known,
});
section_map.nested_insert(module.module_id, mod_section.elf_index as u32, offset)?;
let symbols = module.symbols_for_section(mod_section.index);
for (_, mod_symbol) in symbols {
obj.symbols.push(ObjSymbol {
for (_, mod_symbol) in module.symbols.for_section(mod_section) {
obj.symbols.add_direct(ObjSymbol {
name: mod_symbol.name.clone(),
demangled_name: mod_symbol.demangled_name.clone(),
address: mod_symbol.address + offset as u64,
@@ -126,44 +136,41 @@ fn merge(args: MergeArgs) -> Result<()> {
size_known: mod_symbol.size_known,
flags: mod_symbol.flags,
kind: mod_symbol.kind,
});
align: None,
data_kind: Default::default(),
})?;
}
offset += align32(mod_section.size as u32);
}
}
let mut symbol_maps = Vec::new();
for section in &obj.sections {
symbol_maps.push(obj.build_symbol_map(section.index)?);
}
// Apply relocations
for (_, module) in &module_map {
log::info!("Applying REL relocations");
for module in module_map.values() {
for rel_reloc in &module.unresolved_relocations {
let source_addr =
section_map[&module.module_id][&(rel_reloc.section as u32)] + rel_reloc.address;
let source_addr = (section_map[&module.module_id][&(rel_reloc.section as u32)]
+ rel_reloc.address)
& !3;
let target_addr = if rel_reloc.module_id == 0 {
rel_reloc.addend
} else {
let base = section_map[&rel_reloc.module_id][&(rel_reloc.target_section as u32)];
let addend = rel_reloc.addend;
base + addend
let section_map = &section_map.get(&rel_reloc.module_id).with_context(|| {
format!("Relocation against unknown module ID {}", rel_reloc.module_id)
})?;
section_map[&(rel_reloc.target_section as u32)] + rel_reloc.addend
};
let source_section = obj.section_at(source_addr)?;
let target_section = obj.section_at(target_addr)?;
let target_section_index = target_section.index;
let source_section_index = obj.section_at(source_addr)?.index;
let target_section_index = obj.section_at(target_addr)?.index;
// Try to find a previous sized symbol that encompasses the target
let sym_map = &mut symbol_maps[target_section_index];
let target_symbol = {
let mut result = None;
for (_addr, symbol_idxs) in sym_map.range(..=target_addr).rev() {
for (_addr, symbol_idxs) in obj.symbols.indexes_for_range(..=target_addr).rev() {
let symbol_idx = if symbol_idxs.len() == 1 {
symbol_idxs.first().cloned().unwrap()
} else {
let mut symbol_idxs = symbol_idxs.clone();
let mut symbol_idxs = symbol_idxs.to_vec();
symbol_idxs.sort_by_key(|&symbol_idx| {
let symbol = &obj.symbols[symbol_idx];
let symbol = obj.symbols.at(symbol_idx);
let mut rank = match symbol.kind {
ObjSymbolKind::Function | ObjSymbolKind::Object => {
match rel_reloc.kind {
@@ -199,7 +206,7 @@ fn merge(args: MergeArgs) -> Result<()> {
None => continue,
}
};
let symbol = &obj.symbols[symbol_idx];
let symbol = obj.symbols.at(symbol_idx);
if symbol.address == target_addr as u64 {
result = Some(symbol_idx);
break;
@@ -214,12 +221,11 @@ fn merge(args: MergeArgs) -> Result<()> {
result
};
let (symbol_idx, addend) = if let Some(symbol_idx) = target_symbol {
let symbol = &obj.symbols[symbol_idx];
let symbol = obj.symbols.at(symbol_idx);
(symbol_idx, target_addr as i64 - symbol.address as i64)
} else {
// Create a new label
let symbol_idx = obj.symbols.len();
obj.symbols.push(ObjSymbol {
let symbol_idx = obj.symbols.add_direct(ObjSymbol {
name: String::new(),
demangled_name: None,
address: target_addr as u64,
@@ -228,11 +234,12 @@ fn merge(args: MergeArgs) -> Result<()> {
size_known: false,
flags: Default::default(),
kind: Default::default(),
});
sym_map.nested_push(target_addr, symbol_idx);
align: None,
data_kind: Default::default(),
})?;
(symbol_idx, 0)
};
obj.sections[target_section_index].relocations.push(ObjReloc {
obj.sections[source_section_index].relocations.push(ObjReloc {
kind: rel_reloc.kind,
address: source_addr as u64,
target_symbol: symbol_idx,
@@ -241,29 +248,11 @@ fn merge(args: MergeArgs) -> Result<()> {
}
}
// Apply known functions from extab
let mut state = AnalyzerState::default();
for (&addr, &size) in &obj.known_functions {
state.function_entries.insert(addr);
state.function_bounds.insert(addr, addr + size);
}
for symbol in &obj.symbols {
if symbol.kind != ObjSymbolKind::Function {
continue;
}
state.function_entries.insert(symbol.address as u32);
if !symbol.size_known {
continue;
}
state.function_bounds.insert(symbol.address as u32, (symbol.address + symbol.size) as u32);
}
// Also check the start of each code section
for section in &obj.sections {
if section.kind == ObjSectionKind::Code {
state.function_entries.insert(section.address as u32);
}
}
// Apply relocations to code/data for analyzer
link_relocations(&mut obj)?;
log::info!("Detecting function boundaries");
let mut state = AnalyzerState::default();
state.detect_functions(&obj)?;
log::info!("Discovered {} functions", state.function_slices.len());
@@ -281,8 +270,57 @@ fn merge(args: MergeArgs) -> Result<()> {
// Write ELF
let mut file = File::create(&args.out_file)
.with_context(|| format!("Failed to create '{}'", args.out_file.display()))?;
log::info!("Writing {}", args.out_file.display());
let out_object = write_elf(&obj)?;
file.write_all(&out_object)?;
file.flush()?;
Ok(())
}
fn link_relocations(obj: &mut ObjInfo) -> Result<()> {
for section in &mut obj.sections {
for reloc in &section.relocations {
let source_address = reloc.address /*& !3*/;
let target_address =
(obj.symbols.address_of(reloc.target_symbol) as i64 + reloc.addend) as u32;
let ins_ref =
array_ref_mut!(section.data, (source_address - section.address) as usize, 4);
let mut ins = u32::from_be_bytes(*ins_ref);
match reloc.kind {
ObjRelocKind::Absolute => {
ins = target_address;
}
ObjRelocKind::PpcAddr16Hi => {
ins = (ins & 0xffff0000) | ((target_address >> 16) & 0xffff);
}
ObjRelocKind::PpcAddr16Ha => {
ins = (ins & 0xffff0000) | (((target_address + 0x8000) >> 16) & 0xffff);
}
ObjRelocKind::PpcAddr16Lo => {
ins = (ins & 0xffff0000) | (target_address & 0xffff);
}
ObjRelocKind::PpcRel24 => {
let diff = target_address as i32 - source_address as i32;
ensure!(
(-0x2000000..0x2000000).contains(&diff),
"R_PPC_REL24 relocation out of range"
);
ins = (ins & !0x3fffffc) | (diff as u32 & 0x3fffffc);
}
ObjRelocKind::PpcRel14 => {
let diff = target_address as i32 - source_address as i32;
ensure!(
(-0x2000..0x2000).contains(&diff),
"R_PPC_REL14 relocation out of range"
);
ins = (ins & !0xfffc) | (diff as u32 & 0xfffc);
}
ObjRelocKind::PpcEmbSda21 => {
// Unused in RELs
}
};
*ins_ref = ins.to_be_bytes();
}
}
Ok(())
}

View File

@@ -35,7 +35,7 @@ pub fn run(args: Args) -> Result<()> {
}
fn info(args: InfoArgs) -> Result<()> {
let rso = process_rso(&args.rso_file)?;
let rso = process_rso(args.rso_file)?;
println!("Read RSO module {}", rso.name);
Ok(())
}

View File

@@ -9,6 +9,8 @@ use argh::FromArgs;
use filetime::{set_file_mtime, FileTime};
use sha1::{Digest, Sha1};
use crate::util::file::process_rsp;
#[derive(FromArgs, PartialEq, Eq, Debug)]
/// Print or check SHA1 (160-bit) checksums.
#[argh(subcommand, name = "shasum")]
@@ -17,8 +19,8 @@ pub struct Args {
/// check SHA sums against given list
check: bool,
#[argh(positional)]
/// path to file
file: PathBuf,
/// path to input file(s)
files: Vec<PathBuf>,
#[argh(option, short = 'o')]
/// touch output file on successful check
output: Option<PathBuf>,
@@ -27,16 +29,23 @@ pub struct Args {
const DEFAULT_BUF_SIZE: usize = 8192;
pub fn run(args: Args) -> Result<()> {
let file = File::open(&args.file)
.with_context(|| format!("Failed to open file '{}'", args.file.display()))?;
if args.check {
check(args, file)
} else {
hash(args, file)
for path in process_rsp(&args.files)? {
let file = File::open(&path)
.with_context(|| format!("Failed to open file '{}'", path.display()))?;
if args.check {
check(file)?
} else {
hash(file, &path)?
}
}
if let Some(out_path) = args.output {
touch(&out_path)
.with_context(|| format!("Failed to touch output file '{}'", out_path.display()))?;
}
Ok(())
}
fn check(args: Args, file: File) -> Result<()> {
fn check(file: File) -> Result<()> {
let reader = BufReader::new(file);
let mut mismatches = 0usize;
for line in reader.lines() {
@@ -68,19 +77,15 @@ fn check(args: Args, file: File) -> Result<()> {
eprintln!("WARNING: {mismatches} computed checksum did NOT match");
std::process::exit(1);
}
if let Some(out_path) = args.output {
touch(&out_path)
.with_context(|| format!("Failed to touch output file '{}'", out_path.display()))?;
}
Ok(())
}
fn hash(args: Args, file: File) -> Result<()> {
fn hash(file: File, path: &Path) -> Result<()> {
let hash = file_sha1(file)?;
let mut hash_buf = [0u8; 40];
let hash_str = base16ct::lower::encode_str(&hash, &mut hash_buf)
.map_err(|e| anyhow!("Failed to encode hash: {e}"))?;
println!("{} {}", hash_str, args.file.display());
println!("{} {}", hash_str, path.display());
Ok(())
}