Add dtk extab clean & config.yml clean_extab

It was discovered that certain extab actions contain
uninitalized data from the compiler. This provides
a way to zero out uninitialized data in DOL or object
files. Usage: `dtk extab clean input.dol output.dol`

A `clean_extab` setting was added to config.yml, so
projects can link the cleaned objects and target the
cleaned DOL hash.
This commit is contained in:
2025-06-01 20:22:08 -06:00
parent 20e877c9ec
commit 9cafb77d3f
12 changed files with 331 additions and 138 deletions

View File

@@ -47,6 +47,7 @@ use crate::{
diff::{calc_diff_ranges, print_diff, process_code},
dol::process_dol,
elf::{process_elf, write_elf},
extab::clean_extab,
file::{
buf_copy_with_hash, buf_writer, check_hash_str, touch, verify_hash, FileIterator,
FileReadInfo,
@@ -293,6 +294,9 @@ pub struct ModuleConfig {
pub block_relocations: Vec<BlockRelocationConfig>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub add_relocations: Vec<AddRelocationConfig>,
/// Process exception tables and zero out uninitialized data.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub clean_extab: Option<bool>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
@@ -818,17 +822,29 @@ struct AnalyzeResult {
splits_cache: Option<FileReadInfo>,
}
fn load_analyze_dol(config: &ProjectConfig, object_base: &ObjectBase) -> Result<AnalyzeResult> {
let object_path = object_base.join(&config.base.object);
fn load_dol_module(
config: &ModuleConfig,
object_base: &ObjectBase,
) -> Result<(ObjInfo, Utf8NativePathBuf)> {
let object_path = object_base.join(&config.object);
log::debug!("Loading {}", object_path);
let mut obj = {
let mut file = object_base.open(&config.base.object)?;
let mut file = object_base.open(&config.object)?;
let data = file.map()?;
if let Some(hash_str) = &config.base.hash {
if let Some(hash_str) = &config.hash {
verify_hash(data, hash_str)?;
}
process_dol(data, config.base.name())?
process_dol(data, config.name())?
};
if config.clean_extab.unwrap_or(false) {
log::debug!("Cleaning extab for {}", config.name());
clean_extab(&mut obj)?;
}
Ok((obj, object_path))
}
fn load_analyze_dol(config: &ProjectConfig, object_base: &ObjectBase) -> Result<AnalyzeResult> {
let (mut obj, object_path) = load_dol_module(&config.base, object_base)?;
let mut dep = vec![object_path];
if let Some(comment_version) = config.mw_comment_version {
@@ -1658,15 +1674,7 @@ fn diff(args: DiffArgs) -> Result<()> {
let config: ProjectConfig = serde_yaml::from_reader(config_file.as_mut())?;
let object_base = find_object_base(&config)?;
log::info!("Loading {}", object_base.join(&config.base.object));
let mut obj = {
let mut file = object_base.open(&config.base.object)?;
let data = file.map()?;
if let Some(hash_str) = &config.base.hash {
verify_hash(data, hash_str)?;
}
process_dol(data, config.base.name())?
};
let (mut obj, _object_path) = load_dol_module(&config.base, &object_base)?;
if let Some(symbols_path) = &config.base.symbols {
apply_symbols_file(&symbols_path.with_encoding(), &mut obj)?;
@@ -1882,15 +1890,7 @@ fn apply(args: ApplyArgs) -> Result<()> {
let config: ProjectConfig = serde_yaml::from_reader(config_file.as_mut())?;
let object_base = find_object_base(&config)?;
log::info!("Loading {}", object_base.join(&config.base.object));
let mut obj = {
let mut file = object_base.open(&config.base.object)?;
let data = file.map()?;
if let Some(hash_str) = &config.base.hash {
verify_hash(data, hash_str)?;
}
process_dol(data, config.base.name())?
};
let (mut obj, _object_path) = load_dol_module(&config.base, &object_base)?;
let Some(symbols_path) = &config.base.symbols else {
bail!("No symbols file specified in config");

View File

@@ -6,8 +6,12 @@ use object::{Architecture, Endianness, Object, ObjectKind, ObjectSection, Sectio
use typed_path::Utf8NativePathBuf;
use crate::{
obj::ObjSectionKind,
util::{alf::ALF_MAGIC, dol::process_dol, file::buf_writer, path::native_path},
util::{
alf::ALF_MAGIC,
dol::{process_dol, write_dol},
file::buf_writer,
path::native_path,
},
vfs::open_file,
};
@@ -161,84 +165,8 @@ pub fn run(args: Args) -> Result<()> {
fn convert_alf(args: Args, data: &[u8]) -> Result<()> {
let obj = process_dol(data, "")?;
let mut header = DolHeader { entry_point: obj.entry.unwrap() as u32, ..Default::default() };
let mut offset = 0x100u32;
let mut out = buf_writer(&args.dol_file)?;
out.seek(SeekFrom::Start(offset as u64))?;
// Text sections
for (_, section) in obj.sections.iter().filter(|(_, s)| s.kind == ObjSectionKind::Code) {
log::debug!("Processing text section '{}'", section.name);
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(|| {
anyhow!("Too many text sections (while processing '{}')", section.name)
})? = DolSection { offset, address, size };
header.text_section_count += 1;
write_aligned(&mut out, &section.data, size)?;
offset += size;
}
// Data sections
for (_, section) in obj
.sections
.iter()
.filter(|(_, s)| matches!(s.kind, ObjSectionKind::Data | ObjSectionKind::ReadOnlyData))
{
log::debug!("Processing data section '{}'", section.name);
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(|| {
anyhow!("Too many data sections (while processing '{}')", section.name)
})? = DolSection { offset, address, size };
header.data_section_count += 1;
write_aligned(&mut out, &section.data, size)?;
offset += size;
}
// BSS sections
for (_, section) in obj.sections.iter().filter(|(_, s)| s.kind == ObjSectionKind::Bss) {
let address = section.address as u32;
let size = section.size as u32;
if header.bss_address == 0 {
header.bss_address = address;
}
header.bss_size = (address + size) - header.bss_address;
}
// Offsets
out.rewind()?;
for section in &header.text_sections {
out.write_all(&section.offset.to_be_bytes())?;
}
for section in &header.data_sections {
out.write_all(&section.offset.to_be_bytes())?;
}
// Addresses
for section in &header.text_sections {
out.write_all(&section.address.to_be_bytes())?;
}
for section in &header.data_sections {
out.write_all(&section.address.to_be_bytes())?;
}
// Sizes
for section in &header.text_sections {
out.write_all(&section.size.to_be_bytes())?;
}
for section in &header.data_sections {
out.write_all(&section.size.to_be_bytes())?;
}
// BSS + entry
out.write_all(&header.bss_address.to_be_bytes())?;
out.write_all(&header.bss_size.to_be_bytes())?;
out.write_all(&header.entry_point.to_be_bytes())?;
// Done!
out.flush()?;
write_dol(&obj, &mut out)?;
Ok(())
}

69
src/cmd/extab.rs Normal file
View File

@@ -0,0 +1,69 @@
use std::io::Write;
use anyhow::{Context, Result};
use argp::FromArgs;
use typed_path::Utf8NativePathBuf;
use crate::{
util,
util::{
dol::{process_dol, write_dol},
elf::{is_elf_file, process_elf, write_elf},
file::buf_writer,
path::native_path,
},
vfs::open_file,
};
#[derive(FromArgs, PartialEq, Debug)]
/// Commands for processing extab (exception table) data.
#[argp(subcommand, name = "extab")]
pub struct Args {
#[argp(subcommand)]
command: SubCommand,
}
#[derive(FromArgs, PartialEq, Debug)]
#[argp(subcommand)]
enum SubCommand {
Clean(CleanArgs),
}
#[derive(FromArgs, PartialEq, Eq, Debug)]
/// Rewrites extab data in a DOL or ELF file, zeroing out any uninitialized padding bytes.
#[argp(subcommand, name = "clean")]
pub struct CleanArgs {
#[argp(positional, from_str_fn(native_path))]
/// path to input file
input: Utf8NativePathBuf,
#[argp(positional, from_str_fn(native_path))]
/// path to output file
output: Utf8NativePathBuf,
}
pub fn run(args: Args) -> Result<()> {
match args.command {
SubCommand::Clean(clean_args) => clean_extab(clean_args),
}
}
fn clean_extab(args: CleanArgs) -> Result<()> {
let is_elf = is_elf_file(&args.input)?;
let mut obj = if is_elf {
process_elf(&args.input)?
} else {
let mut file = open_file(&args.input, true)?;
let name = args.input.file_stem().unwrap_or_default();
process_dol(file.map()?, name)?
};
let num_cleaned = util::extab::clean_extab(&mut obj)?;
tracing::debug!("Cleaned {num_cleaned} extab symbols");
let mut out = buf_writer(&args.output)?;
if is_elf {
let data = write_elf(&obj, false)?;
out.write_all(&data).context("Failed to write ELF")?;
} else {
write_dol(&obj, &mut out).context("Failed to write DOL")?;
}
Ok(())
}

View File

@@ -6,6 +6,7 @@ pub mod dol;
pub mod dwarf;
pub mod elf;
pub mod elf2dol;
pub mod extab;
pub mod map;
pub mod nlzss;
pub mod rarc;