MW 2.7 LD compatibility fixes & more
- More robust .comment section handling - Auto-create .comment section for objects with common symbols (MW 2.7+ hack) - Support loading REL modules in `dol split` (currently only for references) - Add `dol diff` for quick diffing between linked ELF and expected symbols - Add `dol apply` for applying linked ELF symbols to symbol config file
This commit is contained in:
parent
5bdffa94c4
commit
d9e1ae2777
|
@ -1,7 +1,7 @@
|
||||||
use std::{
|
use std::{
|
||||||
collections::{btree_map::Entry, BTreeMap},
|
collections::{btree_map::Entry, BTreeMap},
|
||||||
fs::File,
|
fs::File,
|
||||||
io::{BufWriter, Write},
|
io::Write,
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ use anyhow::{anyhow, bail, Result};
|
||||||
use argp::FromArgs;
|
use argp::FromArgs;
|
||||||
use object::{Object, ObjectSymbol, SymbolScope};
|
use object::{Object, ObjectSymbol, SymbolScope};
|
||||||
|
|
||||||
use crate::util::file::{map_file, process_rsp};
|
use crate::util::file::{buf_writer, map_file, process_rsp};
|
||||||
|
|
||||||
#[derive(FromArgs, PartialEq, Debug)]
|
#[derive(FromArgs, PartialEq, Debug)]
|
||||||
/// Commands for processing static libraries.
|
/// Commands for processing static libraries.
|
||||||
|
@ -70,7 +70,7 @@ fn create(args: CreateArgs) -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write archive
|
// Write archive
|
||||||
let out = BufWriter::new(File::create(&args.out)?);
|
let out = buf_writer(&args.out)?;
|
||||||
let mut builder = ar::GnuBuilder::new_with_symbol_table(
|
let mut builder = ar::GnuBuilder::new_with_symbol_table(
|
||||||
out,
|
out,
|
||||||
true,
|
true,
|
||||||
|
|
411
src/cmd/dol.rs
411
src/cmd/dol.rs
|
@ -1,8 +1,8 @@
|
||||||
use std::{
|
use std::{
|
||||||
collections::{hash_map, BTreeMap, HashMap},
|
collections::{btree_map::Entry, hash_map, BTreeMap, HashMap},
|
||||||
fs,
|
fs,
|
||||||
fs::{DirBuilder, File},
|
fs::{DirBuilder, File},
|
||||||
io::{BufRead, BufWriter, Write},
|
io::Write,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -19,17 +19,21 @@ use crate::{
|
||||||
tracker::Tracker,
|
tracker::Tracker,
|
||||||
},
|
},
|
||||||
obj::{
|
obj::{
|
||||||
split::{split_obj, update_splits},
|
split::{is_linker_generated_object, split_obj, update_splits},
|
||||||
ObjInfo, ObjRelocKind, ObjSectionKind, ObjSymbolKind,
|
ObjDataKind, ObjInfo, ObjRelocKind, ObjSectionKind, ObjSymbol, ObjSymbolFlagSet,
|
||||||
|
ObjSymbolFlags, ObjSymbolKind, ObjSymbolScope, SymbolIndex,
|
||||||
},
|
},
|
||||||
util::{
|
util::{
|
||||||
asm::write_asm,
|
asm::write_asm,
|
||||||
config::{apply_splits, parse_symbol_line, write_splits, write_symbols},
|
comment::MWComment,
|
||||||
|
config::{apply_splits, apply_symbols_file, write_splits_file, write_symbols_file},
|
||||||
dep::DepFile,
|
dep::DepFile,
|
||||||
dol::process_dol,
|
dol::process_dol,
|
||||||
elf::{process_elf, write_elf},
|
elf::{process_elf, write_elf},
|
||||||
file::{map_file, map_reader, touch},
|
file::{buf_writer, map_file, map_reader, touch},
|
||||||
lcf::{asm_path_for_unit, generate_ldscript, obj_path_for_unit},
|
lcf::{asm_path_for_unit, generate_ldscript, obj_path_for_unit},
|
||||||
|
map::apply_map_file,
|
||||||
|
rel::process_rel,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -46,6 +50,8 @@ pub struct Args {
|
||||||
enum SubCommand {
|
enum SubCommand {
|
||||||
Info(InfoArgs),
|
Info(InfoArgs),
|
||||||
Split(SplitArgs),
|
Split(SplitArgs),
|
||||||
|
Diff(DiffArgs),
|
||||||
|
Apply(ApplyArgs),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(FromArgs, PartialEq, Eq, Debug)]
|
#[derive(FromArgs, PartialEq, Eq, Debug)]
|
||||||
|
@ -72,6 +78,36 @@ pub struct SplitArgs {
|
||||||
no_update: bool,
|
no_update: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(FromArgs, PartialEq, Eq, Debug)]
|
||||||
|
/// Diffs symbols in a linked ELF.
|
||||||
|
#[argp(subcommand, name = "diff")]
|
||||||
|
pub struct DiffArgs {
|
||||||
|
#[argp(positional)]
|
||||||
|
/// input configuration file
|
||||||
|
config: PathBuf,
|
||||||
|
#[argp(positional)]
|
||||||
|
/// linked ELF
|
||||||
|
elf_file: PathBuf,
|
||||||
|
#[argp(positional)]
|
||||||
|
/// map file
|
||||||
|
map_file: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(FromArgs, PartialEq, Eq, Debug)]
|
||||||
|
/// Applies updated symbols from a linked ELF to the project configuration.
|
||||||
|
#[argp(subcommand, name = "apply")]
|
||||||
|
pub struct ApplyArgs {
|
||||||
|
#[argp(positional)]
|
||||||
|
/// input configuration file
|
||||||
|
config: PathBuf,
|
||||||
|
#[argp(positional)]
|
||||||
|
/// linked ELF
|
||||||
|
elf_file: PathBuf,
|
||||||
|
#[argp(positional)]
|
||||||
|
/// map file
|
||||||
|
map_file: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn bool_true() -> bool { true }
|
fn bool_true() -> bool { true }
|
||||||
|
|
||||||
|
@ -80,6 +116,10 @@ pub struct ProjectConfig {
|
||||||
pub object: PathBuf,
|
pub object: PathBuf,
|
||||||
pub splits: Option<PathBuf>,
|
pub splits: Option<PathBuf>,
|
||||||
pub symbols: Option<PathBuf>,
|
pub symbols: Option<PathBuf>,
|
||||||
|
/// Version of the MW `.comment` section format.
|
||||||
|
/// If not present, no `.comment` sections will be written.
|
||||||
|
pub mw_comment_version: Option<u8>,
|
||||||
|
pub modules: Vec<ModuleConfig>,
|
||||||
// Analysis options
|
// Analysis options
|
||||||
#[serde(default = "bool_true")]
|
#[serde(default = "bool_true")]
|
||||||
pub detect_objects: bool,
|
pub detect_objects: bool,
|
||||||
|
@ -87,6 +127,13 @@ pub struct ProjectConfig {
|
||||||
pub detect_strings: bool,
|
pub detect_strings: bool,
|
||||||
#[serde(default = "bool_true")]
|
#[serde(default = "bool_true")]
|
||||||
pub write_asm: bool,
|
pub write_asm: bool,
|
||||||
|
#[serde(default = "bool_true")]
|
||||||
|
pub auto_force_files: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub struct ModuleConfig {
|
||||||
|
pub object: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
@ -105,6 +152,8 @@ pub fn run(args: Args) -> Result<()> {
|
||||||
match args.command {
|
match args.command {
|
||||||
SubCommand::Info(c_args) => info(c_args),
|
SubCommand::Info(c_args) => info(c_args),
|
||||||
SubCommand::Split(c_args) => split(c_args),
|
SubCommand::Split(c_args) => split(c_args),
|
||||||
|
SubCommand::Diff(c_args) => diff(c_args),
|
||||||
|
SubCommand::Apply(c_args) => apply(c_args),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,6 +211,22 @@ fn split(args: SplitArgs) -> Result<()> {
|
||||||
let mut obj = process_dol(&config.object)?;
|
let mut obj = process_dol(&config.object)?;
|
||||||
dep.push(config.object.clone());
|
dep.push(config.object.clone());
|
||||||
|
|
||||||
|
if let Some(comment_version) = config.mw_comment_version {
|
||||||
|
obj.mw_comment = Some(MWComment::new(comment_version)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut modules = BTreeMap::<u32, ObjInfo>::new();
|
||||||
|
for module_config in &config.modules {
|
||||||
|
log::info!("Loading {}", module_config.object.display());
|
||||||
|
let map = map_file(&module_config.object)?;
|
||||||
|
let rel_obj = process_rel(map_reader(&map))?;
|
||||||
|
match modules.entry(rel_obj.module_id) {
|
||||||
|
Entry::Vacant(e) => e.insert(rel_obj),
|
||||||
|
Entry::Occupied(_) => bail!("Duplicate module ID {}", obj.module_id),
|
||||||
|
};
|
||||||
|
dep.push(module_config.object.clone());
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(splits_path) = &config.splits {
|
if let Some(splits_path) = &config.splits {
|
||||||
dep.push(splits_path.clone());
|
dep.push(splits_path.clone());
|
||||||
if splits_path.is_file() {
|
if splits_path.is_file() {
|
||||||
|
@ -174,24 +239,56 @@ fn split(args: SplitArgs) -> Result<()> {
|
||||||
|
|
||||||
if let Some(symbols_path) = &config.symbols {
|
if let Some(symbols_path) = &config.symbols {
|
||||||
dep.push(symbols_path.clone());
|
dep.push(symbols_path.clone());
|
||||||
if symbols_path.is_file() {
|
apply_symbols_file(symbols_path, &mut obj)?;
|
||||||
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?
|
// TODO move before symbols?
|
||||||
log::info!("Performing signature analysis");
|
log::info!("Performing signature analysis");
|
||||||
apply_signatures(&mut obj)?;
|
apply_signatures(&mut obj)?;
|
||||||
|
|
||||||
|
if !modules.is_empty() {
|
||||||
|
log::info!("Applying module relocations");
|
||||||
|
for (module_id, module_obj) in modules {
|
||||||
|
for rel_reloc in &module_obj.unresolved_relocations {
|
||||||
|
// TODO also apply inter-module relocations
|
||||||
|
if rel_reloc.module_id != 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let target = rel_reloc.addend;
|
||||||
|
if let Some((symbol_index, symbol)) =
|
||||||
|
obj.symbols.for_relocation(target, rel_reloc.kind)?
|
||||||
|
{
|
||||||
|
let addend = target as i64 - symbol.address as i64;
|
||||||
|
if addend != 0 {
|
||||||
|
bail!(
|
||||||
|
"Module {} relocation to {:#010X} for symbol {} has non-zero addend {:#010X}",
|
||||||
|
module_id,
|
||||||
|
symbol.address,
|
||||||
|
symbol.name,
|
||||||
|
addend
|
||||||
|
);
|
||||||
|
}
|
||||||
|
obj.symbols.set_externally_referenced(symbol_index, true);
|
||||||
|
} else {
|
||||||
|
// Add label
|
||||||
|
let target_section = obj.section_at(target)?;
|
||||||
|
obj.symbols.add_direct(ObjSymbol {
|
||||||
|
name: format!("lbl_{:08X}", target),
|
||||||
|
demangled_name: None,
|
||||||
|
address: target as u64,
|
||||||
|
section: Some(target_section.index),
|
||||||
|
size: 0,
|
||||||
|
size_known: false,
|
||||||
|
flags: ObjSymbolFlagSet(ObjSymbolFlags::ForceActive.into()),
|
||||||
|
kind: Default::default(),
|
||||||
|
align: None,
|
||||||
|
data_kind: ObjDataKind::Unknown,
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
log::info!("Detecting function boundaries");
|
log::info!("Detecting function boundaries");
|
||||||
state.detect_functions(&obj)?;
|
state.detect_functions(&obj)?;
|
||||||
log::info!("Discovered {} functions", state.function_slices.len());
|
log::info!("Discovered {} functions", state.function_slices.len());
|
||||||
|
@ -224,19 +321,10 @@ fn split(args: SplitArgs) -> Result<()> {
|
||||||
|
|
||||||
if !args.no_update {
|
if !args.no_update {
|
||||||
if let Some(symbols_path) = &config.symbols {
|
if let Some(symbols_path) = &config.symbols {
|
||||||
let mut symbols_writer = BufWriter::new(
|
write_symbols_file(symbols_path, &obj)?;
|
||||||
File::create(symbols_path)
|
|
||||||
.with_context(|| format!("Failed to create '{}'", symbols_path.display()))?,
|
|
||||||
);
|
|
||||||
write_symbols(&mut symbols_writer, &obj)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(splits_path) = &config.splits {
|
if let Some(splits_path) = &config.splits {
|
||||||
let mut splits_writer = BufWriter::new(
|
write_splits_file(splits_path, &obj)?;
|
||||||
File::create(splits_path)
|
|
||||||
.with_context(|| format!("Failed to create '{}'", splits_path.display()))?,
|
|
||||||
);
|
|
||||||
write_splits(&mut splits_writer, &obj)?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -255,48 +343,49 @@ fn split(args: SplitArgs) -> Result<()> {
|
||||||
let mut file_map = HashMap::<String, Vec<u8>>::new();
|
let mut file_map = HashMap::<String, Vec<u8>>::new();
|
||||||
for (unit, split_obj) in obj.link_order.iter().zip(&split_objs) {
|
for (unit, split_obj) in obj.link_order.iter().zip(&split_objs) {
|
||||||
let out_obj = write_elf(split_obj)?;
|
let out_obj = write_elf(split_obj)?;
|
||||||
match file_map.entry(unit.clone()) {
|
match file_map.entry(unit.name.clone()) {
|
||||||
hash_map::Entry::Vacant(e) => e.insert(out_obj),
|
hash_map::Entry::Vacant(e) => e.insert(out_obj),
|
||||||
hash_map::Entry::Occupied(_) => bail!("Duplicate file {unit}"),
|
hash_map::Entry::Occupied(_) => bail!("Duplicate file {}", unit.name),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut out_config = OutputConfig::default();
|
let mut out_config = OutputConfig::default();
|
||||||
for unit in &obj.link_order {
|
for unit in &obj.link_order {
|
||||||
let object = file_map
|
let object = file_map
|
||||||
.get(unit)
|
.get(&unit.name)
|
||||||
.ok_or_else(|| anyhow!("Failed to find object file for unit '{unit}'"))?;
|
.ok_or_else(|| anyhow!("Failed to find object file for unit '{}'", unit.name))?;
|
||||||
let out_path = obj_dir.join(obj_path_for_unit(unit));
|
let out_path = obj_dir.join(obj_path_for_unit(&unit.name));
|
||||||
out_config.units.push(OutputUnit {
|
out_config.units.push(OutputUnit {
|
||||||
object: out_path.clone(),
|
object: out_path.clone(),
|
||||||
name: unit.clone(),
|
name: unit.name.clone(),
|
||||||
autogenerated: obj.is_unit_autogenerated(unit),
|
autogenerated: unit.autogenerated,
|
||||||
});
|
});
|
||||||
if let Some(parent) = out_path.parent() {
|
if let Some(parent) = out_path.parent() {
|
||||||
DirBuilder::new().recursive(true).create(parent)?;
|
DirBuilder::new().recursive(true).create(parent)?;
|
||||||
}
|
}
|
||||||
let mut file = File::create(&out_path)
|
fs::write(&out_path, object)
|
||||||
.with_context(|| format!("Failed to create '{}'", out_path.display()))?;
|
.with_context(|| format!("Failed to write '{}'", out_path.display()))?;
|
||||||
file.write_all(object)?;
|
|
||||||
file.flush()?;
|
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
let mut out_file = BufWriter::new(File::create(&out_config_path)?);
|
let mut out_file = buf_writer(&out_config_path)?;
|
||||||
serde_json::to_writer_pretty(&mut out_file, &out_config)?;
|
serde_json::to_writer_pretty(&mut out_file, &out_config)?;
|
||||||
out_file.flush()?;
|
out_file.flush()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate ldscript.lcf
|
// Generate ldscript.lcf
|
||||||
fs::write(args.out_dir.join("ldscript.lcf"), generate_ldscript(&obj)?)?;
|
fs::write(
|
||||||
|
args.out_dir.join("ldscript.lcf"),
|
||||||
|
generate_ldscript(&obj, config.auto_force_files)?,
|
||||||
|
)?;
|
||||||
|
|
||||||
log::info!("Writing disassembly");
|
log::info!("Writing disassembly");
|
||||||
for (unit, split_obj) in obj.link_order.iter().zip(&split_objs) {
|
for (unit, split_obj) in obj.link_order.iter().zip(&split_objs) {
|
||||||
let out_path = asm_dir.join(asm_path_for_unit(unit));
|
let out_path = asm_dir.join(asm_path_for_unit(&unit.name));
|
||||||
|
|
||||||
if let Some(parent) = out_path.parent() {
|
if let Some(parent) = out_path.parent() {
|
||||||
DirBuilder::new().recursive(true).create(parent)?;
|
DirBuilder::new().recursive(true).create(parent)?;
|
||||||
}
|
}
|
||||||
let mut w = BufWriter::new(File::create(&out_path)?);
|
let mut w = buf_writer(&out_path)?;
|
||||||
write_asm(&mut w, split_obj)?;
|
write_asm(&mut w, split_obj)?;
|
||||||
w.flush()?;
|
w.flush()?;
|
||||||
}
|
}
|
||||||
|
@ -304,10 +393,7 @@ fn split(args: SplitArgs) -> Result<()> {
|
||||||
// Write dep file
|
// Write dep file
|
||||||
{
|
{
|
||||||
let dep_path = args.out_dir.join("dep");
|
let dep_path = args.out_dir.join("dep");
|
||||||
let mut dep_file = BufWriter::new(
|
let mut dep_file = buf_writer(dep_path)?;
|
||||||
File::create(&dep_path)
|
|
||||||
.with_context(|| format!("Failed to create dep file '{}'", dep_path.display()))?,
|
|
||||||
);
|
|
||||||
dep.write(&mut dep_file)?;
|
dep.write(&mut dep_file)?;
|
||||||
dep_file.flush()?;
|
dep_file.flush()?;
|
||||||
}
|
}
|
||||||
|
@ -470,3 +556,232 @@ fn validate<P: AsRef<Path>>(obj: &ObjInfo, elf_file: P, state: &AnalyzerState) -
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn diff(args: DiffArgs) -> Result<()> {
|
||||||
|
log::info!("Loading {}", args.config.display());
|
||||||
|
let mut config_file = File::open(&args.config)
|
||||||
|
.with_context(|| format!("Failed to open config file '{}'", args.config.display()))?;
|
||||||
|
let config: ProjectConfig = serde_yaml::from_reader(&mut config_file)?;
|
||||||
|
|
||||||
|
log::info!("Loading {}", config.object.display());
|
||||||
|
let mut obj = process_dol(&config.object)?;
|
||||||
|
|
||||||
|
if let Some(symbols_path) = &config.symbols {
|
||||||
|
apply_symbols_file(symbols_path, &mut obj)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
log::info!("Loading {}", args.elf_file.display());
|
||||||
|
let mut linked_obj = process_elf(&args.elf_file)?;
|
||||||
|
|
||||||
|
log::info!("Loading {}", args.map_file.display());
|
||||||
|
apply_map_file(&args.map_file, &mut linked_obj)?;
|
||||||
|
|
||||||
|
for orig_sym in obj.symbols.iter() {
|
||||||
|
let linked_sym = linked_obj
|
||||||
|
.symbols
|
||||||
|
.at_address(orig_sym.address as u32)
|
||||||
|
.find(|(_, sym)| sym.name == orig_sym.name)
|
||||||
|
.or_else(|| {
|
||||||
|
linked_obj
|
||||||
|
.symbols
|
||||||
|
.at_address(orig_sym.address as u32)
|
||||||
|
.find(|(_, sym)| sym.kind == orig_sym.kind)
|
||||||
|
});
|
||||||
|
let mut found = false;
|
||||||
|
if let Some((_, linked_sym)) = linked_sym {
|
||||||
|
if linked_sym.name.starts_with(&orig_sym.name) {
|
||||||
|
if linked_sym.size != orig_sym.size {
|
||||||
|
log::error!(
|
||||||
|
"Expected {} (type {:?}) to have size {:#X}, but found {:#X}",
|
||||||
|
orig_sym.name,
|
||||||
|
orig_sym.kind,
|
||||||
|
orig_sym.size,
|
||||||
|
linked_sym.size
|
||||||
|
);
|
||||||
|
}
|
||||||
|
found = true;
|
||||||
|
} else if linked_sym.kind == orig_sym.kind && linked_sym.size == orig_sym.size {
|
||||||
|
// Fuzzy match
|
||||||
|
let orig_data = obj
|
||||||
|
.section_data(
|
||||||
|
orig_sym.address as u32,
|
||||||
|
orig_sym.address as u32 + orig_sym.size as u32,
|
||||||
|
)?
|
||||||
|
.1;
|
||||||
|
let linked_data = linked_obj
|
||||||
|
.section_data(
|
||||||
|
linked_sym.address as u32,
|
||||||
|
linked_sym.address as u32 + linked_sym.size as u32,
|
||||||
|
)?
|
||||||
|
.1;
|
||||||
|
if orig_data == linked_data {
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
log::error!(
|
||||||
|
"Expected to find symbol {} (type {:?}, size {:#X}) at {:#010X}",
|
||||||
|
orig_sym.name,
|
||||||
|
orig_sym.kind,
|
||||||
|
orig_sym.size,
|
||||||
|
orig_sym.address
|
||||||
|
);
|
||||||
|
for (_, linked_sym) in linked_obj.symbols.at_address(orig_sym.address as u32) {
|
||||||
|
log::error!(
|
||||||
|
"At {:#010X}, found: {} (type {:?}, size {:#X})",
|
||||||
|
linked_sym.address,
|
||||||
|
linked_sym.name,
|
||||||
|
linked_sym.kind,
|
||||||
|
linked_sym.size,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
for (_, linked_sym) in linked_obj.symbols.for_name(&orig_sym.name) {
|
||||||
|
log::error!(
|
||||||
|
"Instead, found {} (type {:?}, size {:#X}) at {:#010X}",
|
||||||
|
linked_sym.name,
|
||||||
|
linked_sym.kind,
|
||||||
|
linked_sym.size,
|
||||||
|
linked_sym.address,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply(args: ApplyArgs) -> Result<()> {
|
||||||
|
log::info!("Loading {}", args.config.display());
|
||||||
|
let mut config_file = File::open(&args.config)
|
||||||
|
.with_context(|| format!("Failed to open config file '{}'", args.config.display()))?;
|
||||||
|
let config: ProjectConfig = serde_yaml::from_reader(&mut config_file)?;
|
||||||
|
|
||||||
|
log::info!("Loading {}", config.object.display());
|
||||||
|
let mut obj = process_dol(&config.object)?;
|
||||||
|
|
||||||
|
if let Some(symbols_path) = &config.symbols {
|
||||||
|
if !apply_symbols_file(symbols_path, &mut obj)? {
|
||||||
|
bail!("Symbols file '{}' does not exist", symbols_path.display());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bail!("No symbols file specified in config");
|
||||||
|
}
|
||||||
|
|
||||||
|
log::info!("Loading {}", args.elf_file.display());
|
||||||
|
let mut linked_obj = process_elf(&args.elf_file)?;
|
||||||
|
|
||||||
|
log::info!("Loading {}", args.map_file.display());
|
||||||
|
apply_map_file(&args.map_file, &mut linked_obj)?;
|
||||||
|
|
||||||
|
let mut replacements: Vec<(SymbolIndex, Option<ObjSymbol>)> = vec![];
|
||||||
|
for (orig_idx, orig_sym) in obj.symbols.iter().enumerate() {
|
||||||
|
let linked_sym = linked_obj
|
||||||
|
.symbols
|
||||||
|
.at_address(orig_sym.address as u32)
|
||||||
|
.find(|(_, sym)| sym.name == orig_sym.name)
|
||||||
|
.or_else(|| {
|
||||||
|
linked_obj
|
||||||
|
.symbols
|
||||||
|
.at_address(orig_sym.address as u32)
|
||||||
|
.find(|(_, sym)| sym.kind == orig_sym.kind)
|
||||||
|
});
|
||||||
|
if let Some((_, linked_sym)) = linked_sym {
|
||||||
|
let mut updated_sym = orig_sym.clone();
|
||||||
|
let is_globalized = linked_sym.name.ends_with(&format!("_{:08X}", linked_sym.address));
|
||||||
|
if (is_globalized && !linked_sym.name.starts_with(&orig_sym.name))
|
||||||
|
|| (!is_globalized && linked_sym.name != orig_sym.name)
|
||||||
|
{
|
||||||
|
log::info!(
|
||||||
|
"Changing name of {} (type {:?}) to {}",
|
||||||
|
orig_sym.name,
|
||||||
|
orig_sym.kind,
|
||||||
|
linked_sym.name
|
||||||
|
);
|
||||||
|
updated_sym.name = linked_sym.name.clone();
|
||||||
|
}
|
||||||
|
if linked_sym.size != orig_sym.size {
|
||||||
|
log::info!(
|
||||||
|
"Changing size of {} (type {:?}) from {:#X} to {:#X}",
|
||||||
|
orig_sym.name,
|
||||||
|
orig_sym.kind,
|
||||||
|
orig_sym.size,
|
||||||
|
linked_sym.size
|
||||||
|
);
|
||||||
|
updated_sym.size = linked_sym.size;
|
||||||
|
}
|
||||||
|
let linked_scope = linked_sym.flags.scope();
|
||||||
|
if linked_scope != ObjSymbolScope::Unknown
|
||||||
|
&& !is_globalized
|
||||||
|
&& linked_scope != orig_sym.flags.scope()
|
||||||
|
{
|
||||||
|
log::info!(
|
||||||
|
"Changing scope of {} (type {:?}) from {:?} to {:?}",
|
||||||
|
orig_sym.name,
|
||||||
|
orig_sym.kind,
|
||||||
|
orig_sym.flags.scope(),
|
||||||
|
linked_scope
|
||||||
|
);
|
||||||
|
updated_sym.flags.set_scope(linked_scope);
|
||||||
|
}
|
||||||
|
if updated_sym != *orig_sym {
|
||||||
|
replacements.push((orig_idx, Some(updated_sym)));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log::warn!(
|
||||||
|
"Symbol not in linked ELF: {} (type {:?}, size {:#X}) at {:#010X}",
|
||||||
|
orig_sym.name,
|
||||||
|
orig_sym.kind,
|
||||||
|
orig_sym.size,
|
||||||
|
orig_sym.address
|
||||||
|
);
|
||||||
|
// TODO
|
||||||
|
// replacements.push((orig_idx, None));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add symbols from the linked object that aren't in the original
|
||||||
|
for linked_sym in linked_obj.symbols.iter() {
|
||||||
|
if matches!(linked_sym.kind, ObjSymbolKind::Section)
|
||||||
|
|| is_linker_generated_object(&linked_sym.name)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let orig_sym = obj
|
||||||
|
.symbols
|
||||||
|
.at_address(linked_sym.address as u32)
|
||||||
|
.find(|(_, sym)| sym.name == linked_sym.name)
|
||||||
|
.or_else(|| {
|
||||||
|
linked_obj
|
||||||
|
.symbols
|
||||||
|
.at_address(linked_sym.address as u32)
|
||||||
|
.find(|(_, sym)| sym.kind == linked_sym.kind)
|
||||||
|
});
|
||||||
|
if orig_sym.is_none() {
|
||||||
|
log::info!(
|
||||||
|
"Adding symbol {} (type {:?}, size {:#X}) at {:#010X}",
|
||||||
|
linked_sym.name,
|
||||||
|
linked_sym.kind,
|
||||||
|
linked_sym.size,
|
||||||
|
linked_sym.address
|
||||||
|
);
|
||||||
|
obj.symbols.add_direct(linked_sym.clone())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply replacements
|
||||||
|
for (idx, replacement) in replacements {
|
||||||
|
if let Some(replacement) = replacement {
|
||||||
|
obj.symbols.replace(idx, replacement)?;
|
||||||
|
} else {
|
||||||
|
// TODO
|
||||||
|
// obj.symbols.remove(idx)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
write_symbols_file(config.symbols.as_ref().unwrap(), &obj)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
use std::{
|
use std::{
|
||||||
collections::{btree_map, BTreeMap},
|
collections::{btree_map, BTreeMap},
|
||||||
fs::File,
|
io::{stdout, Cursor, Read, Write},
|
||||||
io::{stdout, BufWriter, Cursor, Read, Write},
|
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -14,7 +13,7 @@ use crate::util::{
|
||||||
process_address, process_type, process_variable_location, read_debug_section, type_string,
|
process_address, process_type, process_variable_location, read_debug_section, type_string,
|
||||||
ud_type, ud_type_def, ud_type_string, AttributeKind, TagKind,
|
ud_type, ud_type_def, ud_type_string, AttributeKind, TagKind,
|
||||||
},
|
},
|
||||||
file::map_file,
|
file::{buf_writer, map_file},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(FromArgs, PartialEq, Debug)]
|
#[derive(FromArgs, PartialEq, Debug)]
|
||||||
|
@ -77,7 +76,7 @@ fn dump(args: DumpArgs) -> Result<()> {
|
||||||
let name = name.trim_start_matches("D:").replace('\\', "/");
|
let name = name.trim_start_matches("D:").replace('\\', "/");
|
||||||
let name = name.rsplit_once('/').map(|(_, 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 file_path = out_path.join(format!("{}.txt", name));
|
||||||
let mut file = BufWriter::new(File::create(file_path)?);
|
let mut file = buf_writer(file_path)?;
|
||||||
dump_debug_section(&mut file, &obj_file, debug_section)?;
|
dump_debug_section(&mut file, &obj_file, debug_section)?;
|
||||||
file.flush()?;
|
file.flush()?;
|
||||||
} else {
|
} else {
|
||||||
|
@ -91,7 +90,7 @@ fn dump(args: DumpArgs) -> Result<()> {
|
||||||
.section_by_name(".debug")
|
.section_by_name(".debug")
|
||||||
.ok_or_else(|| anyhow!("Failed to locate .debug section"))?;
|
.ok_or_else(|| anyhow!("Failed to locate .debug section"))?;
|
||||||
if let Some(out_path) = &args.out {
|
if let Some(out_path) = &args.out {
|
||||||
let mut file = BufWriter::new(File::create(out_path)?);
|
let mut file = buf_writer(out_path)?;
|
||||||
dump_debug_section(&mut file, &obj_file, debug_section)?;
|
dump_debug_section(&mut file, &obj_file, debug_section)?;
|
||||||
file.flush()?;
|
file.flush()?;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use std::{
|
use std::{
|
||||||
collections::{btree_map, hash_map, BTreeMap, HashMap},
|
collections::{btree_map, hash_map, BTreeMap, HashMap},
|
||||||
fs,
|
fs,
|
||||||
fs::{DirBuilder, File},
|
fs::DirBuilder,
|
||||||
io::{BufWriter, Write},
|
io::Write,
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -23,9 +23,9 @@ use crate::{
|
||||||
},
|
},
|
||||||
util::{
|
util::{
|
||||||
asm::write_asm,
|
asm::write_asm,
|
||||||
config::{write_splits, write_symbols},
|
config::{write_splits_file, write_symbols_file},
|
||||||
elf::{process_elf, write_elf},
|
elf::{process_elf, write_elf},
|
||||||
file::process_rsp,
|
file::{buf_writer, process_rsp},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -125,24 +125,8 @@ fn config(args: ConfigArgs) -> Result<()> {
|
||||||
let obj = process_elf(&args.in_file)?;
|
let obj = process_elf(&args.in_file)?;
|
||||||
|
|
||||||
DirBuilder::new().recursive(true).create(&args.out_dir)?;
|
DirBuilder::new().recursive(true).create(&args.out_dir)?;
|
||||||
{
|
write_symbols_file(args.out_dir.join("symbols.txt"), &obj)?;
|
||||||
let symbols_path = args.out_dir.join("symbols.txt");
|
write_splits_file(args.out_dir.join("splits.txt"), &obj)?;
|
||||||
let mut symbols_writer = BufWriter::new(
|
|
||||||
File::create(&symbols_path)
|
|
||||||
.with_context(|| format!("Failed to create '{}'", symbols_path.display()))?,
|
|
||||||
);
|
|
||||||
write_symbols(&mut symbols_writer, &obj)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
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)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -159,19 +143,19 @@ fn disasm(args: DisasmArgs) -> Result<()> {
|
||||||
DirBuilder::new().recursive(true).create(&include_dir)?;
|
DirBuilder::new().recursive(true).create(&include_dir)?;
|
||||||
fs::write(include_dir.join("macros.inc"), include_bytes!("../../assets/macros.inc"))?;
|
fs::write(include_dir.join("macros.inc"), include_bytes!("../../assets/macros.inc"))?;
|
||||||
|
|
||||||
let mut files_out = File::create(args.out.join("link_order.txt"))?;
|
let mut files_out = buf_writer(args.out.join("link_order.txt"))?;
|
||||||
for (unit, split_obj) in obj.link_order.iter().zip(&split_objs) {
|
for (unit, split_obj) in obj.link_order.iter().zip(&split_objs) {
|
||||||
let out_path = asm_dir.join(file_name_from_unit(unit, ".s"));
|
let out_path = asm_dir.join(file_name_from_unit(&unit.name, ".s"));
|
||||||
log::info!("Writing {}", out_path.display());
|
log::info!("Writing {}", out_path.display());
|
||||||
|
|
||||||
if let Some(parent) = out_path.parent() {
|
if let Some(parent) = out_path.parent() {
|
||||||
DirBuilder::new().recursive(true).create(parent)?;
|
DirBuilder::new().recursive(true).create(parent)?;
|
||||||
}
|
}
|
||||||
let mut w = BufWriter::new(File::create(out_path)?);
|
let mut w = buf_writer(out_path)?;
|
||||||
write_asm(&mut w, split_obj)?;
|
write_asm(&mut w, split_obj)?;
|
||||||
w.flush()?;
|
w.flush()?;
|
||||||
|
|
||||||
writeln!(files_out, "{}", file_name_from_unit(unit, ".o"))?;
|
writeln!(files_out, "{}", file_name_from_unit(&unit.name, ".o"))?;
|
||||||
}
|
}
|
||||||
files_out.flush()?;
|
files_out.flush()?;
|
||||||
}
|
}
|
||||||
|
@ -179,8 +163,9 @@ fn disasm(args: DisasmArgs) -> Result<()> {
|
||||||
if let Some(parent) = args.out.parent() {
|
if let Some(parent) = args.out.parent() {
|
||||||
DirBuilder::new().recursive(true).create(parent)?;
|
DirBuilder::new().recursive(true).create(parent)?;
|
||||||
}
|
}
|
||||||
let mut w = BufWriter::new(File::create(args.out)?);
|
let mut w = buf_writer(args.out)?;
|
||||||
write_asm(&mut w, &obj)?;
|
write_asm(&mut w, &obj)?;
|
||||||
|
w.flush()?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -195,26 +180,24 @@ fn split(args: SplitArgs) -> Result<()> {
|
||||||
let split_objs = split_obj(&obj)?;
|
let split_objs = split_obj(&obj)?;
|
||||||
for (unit, split_obj) in obj.link_order.iter().zip(&split_objs) {
|
for (unit, split_obj) in obj.link_order.iter().zip(&split_objs) {
|
||||||
let out_obj = write_elf(split_obj)?;
|
let out_obj = write_elf(split_obj)?;
|
||||||
match file_map.entry(unit.clone()) {
|
match file_map.entry(unit.name.clone()) {
|
||||||
hash_map::Entry::Vacant(e) => e.insert(out_obj),
|
hash_map::Entry::Vacant(e) => e.insert(out_obj),
|
||||||
hash_map::Entry::Occupied(_) => bail!("Duplicate file {unit}"),
|
hash_map::Entry::Occupied(_) => bail!("Duplicate file {}", unit.name),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut rsp_file = BufWriter::new(File::create("rsp")?);
|
let mut rsp_file = buf_writer("rsp")?;
|
||||||
for unit in &obj.link_order {
|
for unit in &obj.link_order {
|
||||||
let object = file_map
|
let object = file_map
|
||||||
.get(unit)
|
.get(&unit.name)
|
||||||
.ok_or_else(|| anyhow!("Failed to find object file for unit '{unit}'"))?;
|
.ok_or_else(|| anyhow!("Failed to find object file for unit '{}'", unit.name))?;
|
||||||
let out_path = args.out_dir.join(file_name_from_unit(unit, ".o"));
|
let out_path = args.out_dir.join(file_name_from_unit(&unit.name, ".o"));
|
||||||
writeln!(rsp_file, "{}", out_path.display())?;
|
writeln!(rsp_file, "{}", out_path.display())?;
|
||||||
if let Some(parent) = out_path.parent() {
|
if let Some(parent) = out_path.parent() {
|
||||||
DirBuilder::new().recursive(true).create(parent)?;
|
DirBuilder::new().recursive(true).create(parent)?;
|
||||||
}
|
}
|
||||||
let mut file = File::create(&out_path)
|
fs::write(&out_path, object)
|
||||||
.with_context(|| format!("Failed to create '{}'", out_path.display()))?;
|
.with_context(|| format!("Failed to write '{}'", out_path.display()))?;
|
||||||
file.write_all(object)?;
|
|
||||||
file.flush()?;
|
|
||||||
}
|
}
|
||||||
rsp_file.flush()?;
|
rsp_file.flush()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -406,10 +389,7 @@ fn fixup(args: FixupArgs) -> Result<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut out =
|
let mut out = buf_writer(&args.out_file)?;
|
||||||
BufWriter::new(File::create(&args.out_file).with_context(|| {
|
|
||||||
format!("Failed to create output file: '{}'", args.out_file.display())
|
|
||||||
})?);
|
|
||||||
out_file.write_stream(&mut out).map_err(|e| anyhow!("{e:?}"))?;
|
out_file.write_stream(&mut out).map_err(|e| anyhow!("{e:?}"))?;
|
||||||
out.flush()?;
|
out.flush()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -490,10 +470,8 @@ fn signatures(args: SignaturesArgs) -> Result<()> {
|
||||||
let mut signatures = signatures.into_values().collect::<Vec<FunctionSignature>>();
|
let mut signatures = signatures.into_values().collect::<Vec<FunctionSignature>>();
|
||||||
log::info!("{} unique signatures", signatures.len());
|
log::info!("{} unique signatures", signatures.len());
|
||||||
signatures.sort_by_key(|s| s.signature.len());
|
signatures.sort_by_key(|s| s.signature.len());
|
||||||
let out =
|
let mut out = buf_writer(&args.out_file)?;
|
||||||
BufWriter::new(File::create(&args.out_file).with_context(|| {
|
serde_yaml::to_writer(&mut out, &signatures)?;
|
||||||
format!("Failed to create output file '{}'", args.out_file.display())
|
out.flush()?;
|
||||||
})?);
|
|
||||||
serde_yaml::to_writer(out, &signatures)?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
use std::{
|
use std::{
|
||||||
fs::File,
|
io::{Seek, SeekFrom, Write},
|
||||||
io::{BufWriter, Seek, SeekFrom, Write},
|
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{anyhow, bail, ensure, Context, Result};
|
use anyhow::{anyhow, bail, ensure, Result};
|
||||||
use argp::FromArgs;
|
use argp::FromArgs;
|
||||||
use object::{Architecture, Endianness, Object, ObjectKind, ObjectSection, SectionKind};
|
use object::{Architecture, Endianness, Object, ObjectKind, ObjectSection, SectionKind};
|
||||||
|
|
||||||
use crate::util::file::map_file;
|
use crate::util::file::{buf_writer, map_file};
|
||||||
|
|
||||||
#[derive(FromArgs, PartialEq, Eq, Debug)]
|
#[derive(FromArgs, PartialEq, Eq, Debug)]
|
||||||
/// Converts an ELF file to a DOL file.
|
/// Converts an ELF file to a DOL file.
|
||||||
|
@ -58,10 +57,7 @@ pub fn run(args: Args) -> Result<()> {
|
||||||
|
|
||||||
let mut header = DolHeader { entry_point: obj_file.entry() as u32, ..Default::default() };
|
let mut header = DolHeader { entry_point: obj_file.entry() as u32, ..Default::default() };
|
||||||
let mut offset = 0x100u32;
|
let mut offset = 0x100u32;
|
||||||
let mut out = BufWriter::new(
|
let mut out = buf_writer(&args.dol_file)?;
|
||||||
File::create(&args.dol_file)
|
|
||||||
.with_context(|| format!("Failed to create DOL file '{}'", args.dol_file.display()))?,
|
|
||||||
);
|
|
||||||
out.seek(SeekFrom::Start(offset as u64))?;
|
out.seek(SeekFrom::Start(offset as u64))?;
|
||||||
|
|
||||||
// Text sections
|
// Text sections
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
use std::{
|
use std::{
|
||||||
collections::{btree_map, BTreeMap},
|
collections::{btree_map, BTreeMap},
|
||||||
fs::File,
|
fs,
|
||||||
io::Write,
|
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -212,12 +211,8 @@ fn merge(args: MergeArgs) -> Result<()> {
|
||||||
tracker.apply(&mut obj, false)?;
|
tracker.apply(&mut obj, false)?;
|
||||||
|
|
||||||
// Write ELF
|
// 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());
|
log::info!("Writing {}", args.out_file.display());
|
||||||
let out_object = write_elf(&obj)?;
|
fs::write(&args.out_file, write_elf(&obj)?)?;
|
||||||
file.write_all(&out_object)?;
|
|
||||||
file.flush()?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,15 @@ use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||||
|
|
||||||
use crate::util::{comment::MWComment, nested::NestedVec, rel::RelReloc};
|
use crate::util::{comment::MWComment, nested::NestedVec, rel::RelReloc};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, Default)]
|
||||||
|
pub enum ObjSymbolScope {
|
||||||
|
#[default]
|
||||||
|
Unknown,
|
||||||
|
Global,
|
||||||
|
Weak,
|
||||||
|
Local,
|
||||||
|
}
|
||||||
|
|
||||||
flags! {
|
flags! {
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
#[derive(Deserialize_repr, Serialize_repr)]
|
#[derive(Deserialize_repr, Serialize_repr)]
|
||||||
|
@ -25,6 +34,8 @@ flags! {
|
||||||
Common,
|
Common,
|
||||||
Hidden,
|
Hidden,
|
||||||
ForceActive,
|
ForceActive,
|
||||||
|
// Same as ForceActive, but used internally
|
||||||
|
ExternallyReferenced,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,6 +43,19 @@ flags! {
|
||||||
pub struct ObjSymbolFlagSet(pub FlagSet<ObjSymbolFlags>);
|
pub struct ObjSymbolFlagSet(pub FlagSet<ObjSymbolFlags>);
|
||||||
|
|
||||||
impl ObjSymbolFlagSet {
|
impl ObjSymbolFlagSet {
|
||||||
|
#[inline]
|
||||||
|
pub fn scope(&self) -> ObjSymbolScope {
|
||||||
|
if self.is_local() {
|
||||||
|
ObjSymbolScope::Local
|
||||||
|
} else if self.is_weak() {
|
||||||
|
ObjSymbolScope::Weak
|
||||||
|
} else if self.0.contains(ObjSymbolFlags::Global) {
|
||||||
|
ObjSymbolScope::Global
|
||||||
|
} else {
|
||||||
|
ObjSymbolScope::Unknown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn is_local(&self) -> bool { self.0.contains(ObjSymbolFlags::Local) }
|
pub fn is_local(&self) -> bool { self.0.contains(ObjSymbolFlags::Local) }
|
||||||
|
|
||||||
|
@ -51,9 +75,38 @@ impl ObjSymbolFlagSet {
|
||||||
pub fn is_force_active(&self) -> bool { self.0.contains(ObjSymbolFlags::ForceActive) }
|
pub fn is_force_active(&self) -> bool { self.0.contains(ObjSymbolFlags::ForceActive) }
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_global(&mut self) {
|
pub fn is_externally_referenced(&self) -> bool {
|
||||||
self.0 =
|
self.0.contains(ObjSymbolFlags::ExternallyReferenced)
|
||||||
(self.0 & !(ObjSymbolFlags::Local | ObjSymbolFlags::Weak)) | ObjSymbolFlags::Global;
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn set_scope(&mut self, scope: ObjSymbolScope) {
|
||||||
|
match scope {
|
||||||
|
ObjSymbolScope::Unknown => {
|
||||||
|
self.0 &= !(ObjSymbolFlags::Local | ObjSymbolFlags::Global | ObjSymbolFlags::Weak)
|
||||||
|
}
|
||||||
|
ObjSymbolScope::Global => {
|
||||||
|
self.0 = (self.0 & !(ObjSymbolFlags::Local | ObjSymbolFlags::Weak))
|
||||||
|
| ObjSymbolFlags::Global
|
||||||
|
}
|
||||||
|
ObjSymbolScope::Weak => {
|
||||||
|
self.0 = (self.0 & !(ObjSymbolFlags::Local | ObjSymbolFlags::Global))
|
||||||
|
| ObjSymbolFlags::Weak
|
||||||
|
}
|
||||||
|
ObjSymbolScope::Local => {
|
||||||
|
self.0 = (self.0 & !(ObjSymbolFlags::Global | ObjSymbolFlags::Weak))
|
||||||
|
| ObjSymbolFlags::Local
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn set_externally_referenced(&mut self, value: bool) {
|
||||||
|
if value {
|
||||||
|
self.0 |= ObjSymbolFlags::ExternallyReferenced;
|
||||||
|
} else {
|
||||||
|
self.0 &= !ObjSymbolFlags::ExternallyReferenced;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,18 +192,29 @@ pub enum ObjArchitecture {
|
||||||
PowerPc,
|
PowerPc,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Translation unit information.
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
|
pub struct ObjUnit {
|
||||||
|
pub name: String,
|
||||||
|
/// Generated, replaceable by user.
|
||||||
|
pub autogenerated: bool,
|
||||||
|
/// MW `.comment` section version.
|
||||||
|
pub comment_version: Option<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Marks a split point within a section.
|
||||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
pub struct ObjSplit {
|
pub struct ObjSplit {
|
||||||
pub unit: String,
|
pub unit: String,
|
||||||
pub end: u32,
|
pub end: u32,
|
||||||
pub align: Option<u32>,
|
pub align: Option<u32>,
|
||||||
/// Common BSS
|
/// Whether this is a part of common BSS.
|
||||||
pub common: bool,
|
pub common: bool,
|
||||||
/// Generated, replaceable by user
|
/// Generated, replaceable by user.
|
||||||
pub autogenerated: bool,
|
pub autogenerated: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
type SymbolIndex = usize;
|
pub type SymbolIndex = usize;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ObjSymbols {
|
pub struct ObjSymbols {
|
||||||
|
@ -167,7 +231,7 @@ pub struct ObjInfo {
|
||||||
pub symbols: ObjSymbols,
|
pub symbols: ObjSymbols,
|
||||||
pub sections: Vec<ObjSection>,
|
pub sections: Vec<ObjSection>,
|
||||||
pub entry: u64,
|
pub entry: u64,
|
||||||
pub mw_comment: MWComment,
|
pub mw_comment: Option<MWComment>,
|
||||||
|
|
||||||
// Linker generated
|
// Linker generated
|
||||||
pub sda2_base: Option<u32>,
|
pub sda2_base: Option<u32>,
|
||||||
|
@ -181,7 +245,7 @@ pub struct ObjInfo {
|
||||||
// Extracted
|
// Extracted
|
||||||
pub splits: BTreeMap<u32, Vec<ObjSplit>>,
|
pub splits: BTreeMap<u32, Vec<ObjSplit>>,
|
||||||
pub named_sections: BTreeMap<u32, String>,
|
pub named_sections: BTreeMap<u32, String>,
|
||||||
pub link_order: Vec<String>,
|
pub link_order: Vec<ObjUnit>,
|
||||||
pub blocked_ranges: BTreeMap<u32, u32>, // start -> end
|
pub blocked_ranges: BTreeMap<u32, u32>, // start -> end
|
||||||
|
|
||||||
// From extab
|
// From extab
|
||||||
|
@ -232,6 +296,8 @@ impl ObjSymbols {
|
||||||
(symbol.kind == ObjSymbolKind::Unknown && symbol.name.starts_with("lbl_")))
|
(symbol.kind == ObjSymbolKind::Unknown && symbol.name.starts_with("lbl_")))
|
||||||
// Hack to avoid replacing different ABS symbols
|
// Hack to avoid replacing different ABS symbols
|
||||||
&& (symbol.section.is_some() || symbol.name == in_symbol.name)
|
&& (symbol.section.is_some() || symbol.name == in_symbol.name)
|
||||||
|
// Avoid replacing symbols with ABS symbols, and vice versa
|
||||||
|
&& (symbol.section == in_symbol.section)
|
||||||
});
|
});
|
||||||
let target_symbol_idx = if let Some((symbol_idx, existing)) = opt {
|
let target_symbol_idx = if let Some((symbol_idx, existing)) = opt {
|
||||||
let size =
|
let size =
|
||||||
|
@ -495,6 +561,10 @@ impl ObjSymbols {
|
||||||
}
|
}
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_externally_referenced(&mut self, idx: SymbolIndex, value: bool) {
|
||||||
|
self.symbols[idx].flags.set_externally_referenced(value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ObjInfo {
|
impl ObjInfo {
|
||||||
|
@ -809,6 +879,13 @@ impl ObjSection {
|
||||||
pub fn contains_range(&self, range: Range<u32>) -> bool {
|
pub fn contains_range(&self, range: Range<u32>) -> bool {
|
||||||
(range.start as u64) >= self.address && (range.end as u64) <= self.address + self.size
|
(range.start as u64) >= self.address && (range.end as u64) <= self.address + self.size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn rename(&mut self, name: String) -> Result<()> {
|
||||||
|
self.kind = section_kind_for_section(&name)?;
|
||||||
|
self.name = name;
|
||||||
|
self.section_known = true;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn section_kind_for_section(section_name: &str) -> Result<ObjSectionKind> {
|
pub fn section_kind_for_section(section_name: &str) -> Result<ObjSectionKind> {
|
||||||
|
|
|
@ -12,10 +12,7 @@ use sha1::{Digest, Sha1};
|
||||||
use crate::{
|
use crate::{
|
||||||
analysis::tracker::{Relocation, Tracker},
|
analysis::tracker::{Relocation, Tracker},
|
||||||
array_ref,
|
array_ref,
|
||||||
obj::{
|
obj::{ObjInfo, ObjReloc, ObjRelocKind, ObjSymbol, ObjSymbolFlagSet, ObjSymbolKind},
|
||||||
section_kind_for_section, ObjInfo, ObjReloc, ObjRelocKind, ObjSymbol, ObjSymbolFlagSet,
|
|
||||||
ObjSymbolKind,
|
|
||||||
},
|
|
||||||
util::elf::process_elf,
|
util::elf::process_elf,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -112,9 +109,7 @@ pub fn apply_symbol(obj: &mut ObjInfo, target: u32, sig_symbol: &OutSymbol) -> R
|
||||||
let target_section = &mut obj.sections[target_section_index];
|
let target_section = &mut obj.sections[target_section_index];
|
||||||
if !target_section.section_known {
|
if !target_section.section_known {
|
||||||
if let Some(section_name) = &sig_symbol.section {
|
if let Some(section_name) = &sig_symbol.section {
|
||||||
target_section.name = section_name.clone();
|
target_section.rename(section_name.clone())?;
|
||||||
target_section.kind = section_kind_for_section(section_name)?;
|
|
||||||
target_section.section_known = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -233,7 +228,10 @@ pub fn compare_signature(existing: &mut FunctionSignature, new: &FunctionSignatu
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_signature(path: &Path, symbol_name: &str) -> Result<Option<FunctionSignature>> {
|
pub fn generate_signature<P: AsRef<Path>>(
|
||||||
|
path: P,
|
||||||
|
symbol_name: &str,
|
||||||
|
) -> Result<Option<FunctionSignature>> {
|
||||||
let mut out_symbols: Vec<OutSymbol> = Vec::new();
|
let mut out_symbols: Vec<OutSymbol> = Vec::new();
|
||||||
let mut out_relocs: Vec<OutReloc> = Vec::new();
|
let mut out_relocs: Vec<OutReloc> = Vec::new();
|
||||||
let mut symbol_map: BTreeMap<usize, usize> = BTreeMap::new();
|
let mut symbol_map: BTreeMap<usize, usize> = BTreeMap::new();
|
||||||
|
|
|
@ -7,26 +7,31 @@ use anyhow::{anyhow, bail, ensure, Result};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use petgraph::{graph::NodeIndex, Graph};
|
use petgraph::{graph::NodeIndex, Graph};
|
||||||
|
|
||||||
use crate::obj::{
|
use crate::{
|
||||||
ObjArchitecture, ObjInfo, ObjKind, ObjReloc, ObjSection, ObjSectionKind, ObjSplit, ObjSymbol,
|
obj::{
|
||||||
ObjSymbolFlagSet, ObjSymbolFlags, ObjSymbolKind,
|
ObjArchitecture, ObjInfo, ObjKind, ObjReloc, ObjSection, ObjSectionKind, ObjSplit,
|
||||||
|
ObjSymbol, ObjSymbolFlagSet, ObjSymbolFlags, ObjSymbolKind, ObjSymbolScope, ObjUnit,
|
||||||
|
},
|
||||||
|
util::comment::MWComment,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Create splits for function pointers in the given section.
|
/// Create splits for function pointers in the given section.
|
||||||
fn split_ctors_dtors(obj: &mut ObjInfo, section_start: u32, section_end: u32) -> Result<()> {
|
fn split_ctors_dtors(obj: &mut ObjInfo, section_start: u32, section_end: u32) -> Result<()> {
|
||||||
let mut new_splits = BTreeMap::new();
|
let mut new_splits = BTreeMap::new();
|
||||||
let mut current_address = section_start;
|
let mut current_address = section_start;
|
||||||
|
let mut referenced_symbols = vec![];
|
||||||
|
|
||||||
while current_address < section_end {
|
while current_address < section_end {
|
||||||
let (section, chunk) = obj.section_data(current_address, current_address + 4)?;
|
let (section, chunk) = obj.section_data(current_address, current_address + 4)?;
|
||||||
let function_addr = u32::from_be_bytes(chunk[0..4].try_into().unwrap());
|
let function_addr = u32::from_be_bytes(chunk[0..4].try_into().unwrap());
|
||||||
log::debug!("Found {} entry: {:#010X}", section.name, function_addr);
|
log::debug!("Found {} entry: {:#010X}", section.name, function_addr);
|
||||||
|
|
||||||
let Some((_, function_symbol)) =
|
let Some((function_symbol_idx, function_symbol)) =
|
||||||
obj.symbols.kind_at_address(function_addr, ObjSymbolKind::Function)?
|
obj.symbols.kind_at_address(function_addr, ObjSymbolKind::Function)?
|
||||||
else {
|
else {
|
||||||
bail!("Failed to find function symbol @ {:#010X}", function_addr);
|
bail!("Failed to find function symbol @ {:#010X}", function_addr);
|
||||||
};
|
};
|
||||||
|
referenced_symbols.push(function_symbol_idx);
|
||||||
|
|
||||||
let ctors_split = obj.split_for(current_address);
|
let ctors_split = obj.split_for(current_address);
|
||||||
let function_split = obj.split_for(function_addr);
|
let function_split = obj.split_for(function_addr);
|
||||||
|
@ -90,6 +95,11 @@ fn split_ctors_dtors(obj: &mut ObjInfo, section_start: u32, section_end: u32) ->
|
||||||
obj.add_split(addr, split)?;
|
obj.add_split(addr, split)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hack to avoid deadstripping
|
||||||
|
for symbol_idx in referenced_symbols {
|
||||||
|
obj.symbols.set_externally_referenced(symbol_idx, true);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -418,7 +428,7 @@ pub fn update_splits(obj: &mut ObjInfo) -> Result<()> {
|
||||||
/// We can use a topological sort to determine a valid global TU order.
|
/// We can use a topological sort to determine a valid global TU order.
|
||||||
/// There can be ambiguities, but any solution that satisfies the link order
|
/// There can be ambiguities, but any solution that satisfies the link order
|
||||||
/// constraints is considered valid.
|
/// constraints is considered valid.
|
||||||
fn resolve_link_order(obj: &ObjInfo) -> Result<Vec<String>> {
|
fn resolve_link_order(obj: &ObjInfo) -> Result<Vec<ObjUnit>> {
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
struct SplitEdge {
|
struct SplitEdge {
|
||||||
|
@ -483,7 +493,21 @@ fn resolve_link_order(obj: &ObjInfo) -> Result<Vec<String>> {
|
||||||
// println!("{:?}", dot);
|
// println!("{:?}", dot);
|
||||||
|
|
||||||
match petgraph::algo::toposort(&graph, None) {
|
match petgraph::algo::toposort(&graph, None) {
|
||||||
Ok(vec) => Ok(vec.iter().map(|&idx| graph[idx].clone()).collect_vec()),
|
Ok(vec) => Ok(vec
|
||||||
|
.iter()
|
||||||
|
.map(|&idx| {
|
||||||
|
let name = &graph[idx];
|
||||||
|
if let Some(existing) = obj.link_order.iter().find(|u| &u.name == name) {
|
||||||
|
existing.clone()
|
||||||
|
} else {
|
||||||
|
ObjUnit {
|
||||||
|
name: name.clone(),
|
||||||
|
autogenerated: obj.is_unit_autogenerated(name),
|
||||||
|
comment_version: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect_vec()),
|
||||||
Err(e) => Err(anyhow!(
|
Err(e) => Err(anyhow!(
|
||||||
"Cyclic dependency (involving {}) encountered while resolving link order",
|
"Cyclic dependency (involving {}) encountered while resolving link order",
|
||||||
graph[e.node_id()]
|
graph[e.node_id()]
|
||||||
|
@ -499,17 +523,21 @@ pub fn split_obj(obj: &ObjInfo) -> Result<Vec<ObjInfo>> {
|
||||||
let mut object_symbols: Vec<Vec<Option<usize>>> = vec![];
|
let mut object_symbols: Vec<Vec<Option<usize>>> = vec![];
|
||||||
let mut name_to_obj: HashMap<String, usize> = HashMap::new();
|
let mut name_to_obj: HashMap<String, usize> = HashMap::new();
|
||||||
for unit in &obj.link_order {
|
for unit in &obj.link_order {
|
||||||
name_to_obj.insert(unit.clone(), objects.len());
|
name_to_obj.insert(unit.name.clone(), objects.len());
|
||||||
object_symbols.push(vec![None; obj.symbols.count()]);
|
object_symbols.push(vec![None; obj.symbols.count()]);
|
||||||
let mut obj = ObjInfo::new(
|
let mut split_obj = ObjInfo::new(
|
||||||
ObjKind::Relocatable,
|
ObjKind::Relocatable,
|
||||||
ObjArchitecture::PowerPc,
|
ObjArchitecture::PowerPc,
|
||||||
unit.clone(),
|
unit.name.clone(),
|
||||||
vec![],
|
vec![],
|
||||||
vec![],
|
vec![],
|
||||||
);
|
);
|
||||||
obj.mw_comment = obj.mw_comment.clone();
|
if let Some(comment_version) = unit.comment_version {
|
||||||
objects.push(obj);
|
split_obj.mw_comment = Some(MWComment::new(comment_version)?);
|
||||||
|
} else {
|
||||||
|
split_obj.mw_comment = obj.mw_comment.clone();
|
||||||
|
}
|
||||||
|
objects.push(split_obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (section_idx, section) in obj.sections.iter().enumerate() {
|
for (section_idx, section) in obj.sections.iter().enumerate() {
|
||||||
|
@ -635,6 +663,12 @@ pub fn split_obj(obj: &ObjInfo) -> Result<Vec<ObjInfo>> {
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For mwldeppc 2.7 and above, a .comment section is required to link without error
|
||||||
|
// when common symbols are present. Automatically add one if needed.
|
||||||
|
if split.common && file.mw_comment.is_none() {
|
||||||
|
file.mw_comment = Some(MWComment::new(8)?);
|
||||||
|
}
|
||||||
|
|
||||||
if !split.common {
|
if !split.common {
|
||||||
let data = match section.kind {
|
let data = match section.kind {
|
||||||
ObjSectionKind::Bss => vec![],
|
ObjSectionKind::Bss => vec![],
|
||||||
|
@ -743,7 +777,7 @@ pub fn split_obj(obj: &ObjInfo) -> Result<Vec<ObjInfo>> {
|
||||||
symbol.name = new_name.clone();
|
symbol.name = new_name.clone();
|
||||||
if symbol.flags.is_local() {
|
if symbol.flags.is_local() {
|
||||||
log::debug!("Globalizing {} in {}", symbol.name, obj.name);
|
log::debug!("Globalizing {} in {}", symbol.name, obj.name);
|
||||||
symbol.flags.set_global();
|
symbol.flags.set_scope(ObjSymbolScope::Global);
|
||||||
}
|
}
|
||||||
obj.symbols.replace(symbol_idx, symbol)?;
|
obj.symbols.replace(symbol_idx, symbol)?;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,6 @@ use std::{
|
||||||
use anyhow::{bail, ensure, Context, Result};
|
use anyhow::{bail, ensure, Context, Result};
|
||||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||||
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||||
use object::Symbol;
|
|
||||||
|
|
||||||
use crate::obj::{ObjSymbol, ObjSymbolKind};
|
use crate::obj::{ObjSymbol, ObjSymbolKind};
|
||||||
|
|
||||||
|
@ -20,7 +19,7 @@ pub enum MWFloatKind {
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct MWComment {
|
pub struct MWComment {
|
||||||
pub comment_version: u8,
|
pub version: u8,
|
||||||
pub compiler_version: [u8; 4],
|
pub compiler_version: [u8; 4],
|
||||||
pub pool_data: bool,
|
pub pool_data: bool,
|
||||||
pub float: MWFloatKind,
|
pub float: MWFloatKind,
|
||||||
|
@ -30,47 +29,44 @@ pub struct MWComment {
|
||||||
pub unsafe_global_reg_vars: bool,
|
pub unsafe_global_reg_vars: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for MWComment {
|
impl MWComment {
|
||||||
fn default() -> Self {
|
pub fn new(version: u8) -> Result<Self> {
|
||||||
Self {
|
// Metrowerks C/C++ Compiler for Embedded PowerPC.
|
||||||
comment_version: 10,
|
let compiler_version = match version {
|
||||||
// Metrowerks C/C++ Compiler for Embedded PowerPC
|
// Version 2.3.3 build 144
|
||||||
|
// (CodeWarrior for GameCube 1.0)
|
||||||
|
8 => [2, 3, 0, 1],
|
||||||
// Version 2.4.2 build 81
|
// Version 2.4.2 build 81
|
||||||
// (CodeWarrior for GameCube 1.3.2)
|
// (CodeWarrior for GameCube 1.3.2)
|
||||||
compiler_version: [2, 4, 2, 1],
|
10 => [2, 4, 2, 1],
|
||||||
|
// Version 2.4.7 build 108
|
||||||
|
// (CodeWarrior for GameCube 2.7)
|
||||||
|
11 => [2, 4, 7, 1],
|
||||||
|
// Version 4.1 build 60126
|
||||||
|
// (CodeWarrior for GameCube 3.0 Alpha 3)
|
||||||
|
14 | 15 => [4, 0, 0, 1],
|
||||||
|
_ => bail!("Unsupported MW .comment version {version}"),
|
||||||
|
};
|
||||||
|
Ok(Self {
|
||||||
|
version,
|
||||||
|
compiler_version,
|
||||||
pool_data: true,
|
pool_data: true,
|
||||||
float: MWFloatKind::Hard,
|
float: MWFloatKind::Hard,
|
||||||
processor: 0x16, // gekko
|
processor: 0x16, // gekko
|
||||||
incompatible_return_small_structs: false,
|
incompatible_return_small_structs: false,
|
||||||
incompatible_sfpe_double_params: false,
|
incompatible_sfpe_double_params: false,
|
||||||
unsafe_global_reg_vars: false,
|
unsafe_global_reg_vars: false,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// fn default() -> Self {
|
|
||||||
// Self {
|
|
||||||
// comment_version: 11,
|
|
||||||
// // Metrowerks C/C++ Compiler for Embedded PowerPC.
|
|
||||||
// // Version 2.4.7 build 108
|
|
||||||
// // (CodeWarrior for GameCube 2.7)
|
|
||||||
// compiler_version: [2, 4, 7, 1],
|
|
||||||
// pool_data: true,
|
|
||||||
// float: MWFloatKind::Hard,
|
|
||||||
// processor: 0x16, // gekko
|
|
||||||
// incompatible_return_small_structs: false,
|
|
||||||
// incompatible_sfpe_double_params: false,
|
|
||||||
// unsafe_global_reg_vars: false,
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
const MAGIC: &[u8] = "CodeWarrior".as_bytes();
|
const MAGIC: &[u8] = "CodeWarrior".as_bytes();
|
||||||
const PADDING: &[u8] = &[0u8; 0x16];
|
const PADDING: &[u8] = &[0u8; 0x16];
|
||||||
|
|
||||||
impl MWComment {
|
impl MWComment {
|
||||||
pub fn parse_header<R: Read + Seek>(reader: &mut R) -> Result<MWComment> {
|
pub fn parse_header<R: Read + Seek>(reader: &mut R) -> Result<MWComment> {
|
||||||
let mut header = MWComment {
|
let mut header = MWComment {
|
||||||
comment_version: 0,
|
version: 0,
|
||||||
compiler_version: [0; 4],
|
compiler_version: [0; 4],
|
||||||
pool_data: false,
|
pool_data: false,
|
||||||
float: MWFloatKind::None,
|
float: MWFloatKind::None,
|
||||||
|
@ -83,14 +79,14 @@ impl MWComment {
|
||||||
let mut magic = vec![0u8; MAGIC.len()];
|
let mut magic = vec![0u8; MAGIC.len()];
|
||||||
reader.read_exact(&mut magic).context("While reading magic")?;
|
reader.read_exact(&mut magic).context("While reading magic")?;
|
||||||
if magic.deref() != MAGIC {
|
if magic.deref() != MAGIC {
|
||||||
bail!("Invalid comment section magic: {:?}", magic);
|
bail!("Invalid .comment section magic: {:?}", magic);
|
||||||
}
|
}
|
||||||
// 0xB
|
// 0xB
|
||||||
header.comment_version = reader.read_u8()?;
|
header.version = reader.read_u8()?;
|
||||||
ensure!(
|
ensure!(
|
||||||
matches!(header.comment_version, 8 | 10 | 11),
|
matches!(header.version, 8 | 10 | 11 | 14 | 15),
|
||||||
"Unknown comment version: {}",
|
"Unknown .comment section version: {}",
|
||||||
header.comment_version
|
header.version
|
||||||
);
|
);
|
||||||
// 0xC - 0xF
|
// 0xC - 0xF
|
||||||
reader
|
reader
|
||||||
|
@ -136,7 +132,7 @@ impl MWComment {
|
||||||
// 0x0 - 0xA
|
// 0x0 - 0xA
|
||||||
w.write_all(MAGIC)?;
|
w.write_all(MAGIC)?;
|
||||||
// 0xB
|
// 0xB
|
||||||
w.write_u8(self.comment_version)?;
|
w.write_u8(self.version)?;
|
||||||
// 0xC - 0xF
|
// 0xC - 0xF
|
||||||
w.write_all(&self.compiler_version)?;
|
w.write_all(&self.compiler_version)?;
|
||||||
// 0x10
|
// 0x10
|
||||||
|
@ -183,8 +179,14 @@ impl CommentSym {
|
||||||
match symbol.kind {
|
match symbol.kind {
|
||||||
ObjSymbolKind::Unknown => 0,
|
ObjSymbolKind::Unknown => 0,
|
||||||
ObjSymbolKind::Function => 4,
|
ObjSymbolKind::Function => 4,
|
||||||
ObjSymbolKind::Object => 4,
|
ObjSymbolKind::Object => {
|
||||||
ObjSymbolKind::Section => 8, // TODO?
|
if symbol.address & 3 == 0 {
|
||||||
|
4
|
||||||
|
} else {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ObjSymbolKind::Section => 8,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -194,7 +196,7 @@ impl CommentSym {
|
||||||
vis_flags |= 0xD;
|
vis_flags |= 0xD;
|
||||||
}
|
}
|
||||||
let mut active_flags = 0;
|
let mut active_flags = 0;
|
||||||
if symbol.flags.is_force_active() {
|
if symbol.flags.is_force_active() || symbol.flags.is_externally_referenced() {
|
||||||
active_flags |= 0x8; // TODO what is 0x10?
|
active_flags |= 0x8; // TODO what is 0x10?
|
||||||
}
|
}
|
||||||
Self { align, vis_flags, active_flags }
|
Self { align, vis_flags, active_flags }
|
||||||
|
@ -210,7 +212,7 @@ pub fn write_comment_sym<W: Write>(w: &mut W, symbol: CommentSym) -> Result<()>
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_comment_sym<R: Read>(r: &mut R, x: &Symbol) -> Result<CommentSym> {
|
pub fn read_comment_sym<R: Read>(r: &mut R) -> Result<CommentSym> {
|
||||||
let mut out = CommentSym { align: 0, vis_flags: 0, active_flags: 0 };
|
let mut out = CommentSym { align: 0, vis_flags: 0, active_flags: 0 };
|
||||||
out.align = r.read_u32::<BigEndian>()?;
|
out.align = r.read_u32::<BigEndian>()?;
|
||||||
out.vis_flags = r.read_u8()?;
|
out.vis_flags = r.read_u8()?;
|
||||||
|
|
|
@ -1,25 +1,48 @@
|
||||||
use std::{
|
use std::{
|
||||||
io::{BufRead, Write},
|
io::{BufRead, Write},
|
||||||
num::ParseIntError,
|
num::ParseIntError,
|
||||||
|
path::Path,
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{anyhow, bail, ensure, Result};
|
use anyhow::{anyhow, bail, ensure, Context, Result};
|
||||||
use cwdemangle::{demangle, DemangleOptions};
|
use cwdemangle::{demangle, DemangleOptions};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use regex::Regex;
|
use regex::{Captures, Regex};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
obj::{
|
obj::{
|
||||||
ObjDataKind, ObjInfo, ObjSplit, ObjSymbol, ObjSymbolFlagSet, ObjSymbolFlags, ObjSymbolKind,
|
ObjDataKind, ObjInfo, ObjSplit, ObjSymbol, ObjSymbolFlagSet, ObjSymbolFlags, ObjSymbolKind,
|
||||||
|
ObjUnit,
|
||||||
|
},
|
||||||
|
util::{
|
||||||
|
file::{buf_writer, map_file, map_reader},
|
||||||
|
nested::NestedVec,
|
||||||
},
|
},
|
||||||
util::nested::NestedVec,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
fn parse_hex(s: &str) -> Result<u32, ParseIntError> {
|
fn parse_hex(s: &str) -> Result<u32, ParseIntError> {
|
||||||
u32::from_str_radix(s.trim_start_matches("0x"), 16)
|
u32::from_str_radix(s.trim_start_matches("0x"), 16)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn apply_symbols_file<P: AsRef<Path>>(path: P, obj: &mut ObjInfo) -> Result<bool> {
|
||||||
|
Ok(if path.as_ref().is_file() {
|
||||||
|
let map = map_file(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)? {
|
||||||
|
obj.add_symbol(symbol, true)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn parse_symbol_line(line: &str, obj: &mut ObjInfo) -> Result<Option<ObjSymbol>> {
|
pub fn parse_symbol_line(line: &str, obj: &mut ObjInfo) -> Result<Option<ObjSymbol>> {
|
||||||
static SYMBOL_LINE: Lazy<Regex> = Lazy::new(|| {
|
static SYMBOL_LINE: Lazy<Regex> = Lazy::new(|| {
|
||||||
Regex::new(
|
Regex::new(
|
||||||
|
@ -32,12 +55,23 @@ pub fn parse_symbol_line(line: &str, obj: &mut ObjInfo) -> Result<Option<ObjSymb
|
||||||
if let Some(captures) = SYMBOL_LINE.captures(line) {
|
if let Some(captures) = SYMBOL_LINE.captures(line) {
|
||||||
let name = captures["name"].to_string();
|
let name = captures["name"].to_string();
|
||||||
let addr = parse_hex(&captures["addr"])?;
|
let addr = parse_hex(&captures["addr"])?;
|
||||||
|
let section_name = captures["section"].to_string();
|
||||||
|
let section = if let Some(section) = obj.sections.iter().find(|s| s.name == section_name) {
|
||||||
|
Some(section.index)
|
||||||
|
} else if let Some(section) = obj.sections.iter_mut().find(|s| s.contains(addr)) {
|
||||||
|
if !section.section_known {
|
||||||
|
section.rename(section_name)?;
|
||||||
|
}
|
||||||
|
Some(section.index)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
let demangled_name = demangle(&name, &DemangleOptions::default());
|
let demangled_name = demangle(&name, &DemangleOptions::default());
|
||||||
let mut symbol = ObjSymbol {
|
let mut symbol = ObjSymbol {
|
||||||
name,
|
name,
|
||||||
demangled_name,
|
demangled_name,
|
||||||
address: addr as u64,
|
address: addr as u64,
|
||||||
section: obj.section_at(addr).ok().map(|section| section.index),
|
section,
|
||||||
size: 0,
|
size: 0,
|
||||||
size_known: false,
|
size_known: false,
|
||||||
flags: Default::default(),
|
flags: Default::default(),
|
||||||
|
@ -108,6 +142,14 @@ fn is_skip_symbol(symbol: &ObjSymbol) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn write_symbols_file<P: AsRef<Path>>(path: P, obj: &ObjInfo) -> Result<()> {
|
||||||
|
let mut w = buf_writer(path)?;
|
||||||
|
write_symbols(&mut w, obj)?;
|
||||||
|
w.flush()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn write_symbols<W: Write>(w: &mut W, obj: &ObjInfo) -> Result<()> {
|
pub fn write_symbols<W: Write>(w: &mut W, obj: &ObjInfo) -> Result<()> {
|
||||||
for (_, symbol) in obj.symbols.iter_ordered() {
|
for (_, symbol) in obj.symbols.iter_ordered() {
|
||||||
if symbol.kind == ObjSymbolKind::Section
|
if symbol.kind == ObjSymbolKind::Section
|
||||||
|
@ -247,19 +289,31 @@ fn symbol_data_kind_from_str(s: &str) -> Option<ObjDataKind> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn write_splits_file<P: AsRef<Path>>(path: P, obj: &ObjInfo) -> Result<()> {
|
||||||
|
let mut w = buf_writer(path)?;
|
||||||
|
write_splits(&mut w, obj)?;
|
||||||
|
w.flush()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn write_splits<W: Write>(w: &mut W, obj: &ObjInfo) -> Result<()> {
|
pub fn write_splits<W: Write>(w: &mut W, obj: &ObjInfo) -> Result<()> {
|
||||||
let mut begin = true;
|
let mut begin = true;
|
||||||
for unit in obj.link_order.iter().filter(|unit| !obj.is_unit_autogenerated(unit)) {
|
for unit in obj.link_order.iter().filter(|unit| !unit.autogenerated) {
|
||||||
if begin {
|
if begin {
|
||||||
begin = false;
|
begin = false;
|
||||||
} else {
|
} else {
|
||||||
writeln!(w)?;
|
writeln!(w)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
writeln!(w, "{}:", unit)?;
|
write!(w, "{}:", unit.name)?;
|
||||||
|
if let Some(comment_version) = unit.comment_version {
|
||||||
|
write!(w, " comment:{}", comment_version)?;
|
||||||
|
}
|
||||||
|
writeln!(w)?;
|
||||||
let mut split_iter = obj.splits_for_range(..).peekable();
|
let mut split_iter = obj.splits_for_range(..).peekable();
|
||||||
while let Some((addr, split)) = split_iter.next() {
|
while let Some((addr, split)) = split_iter.next() {
|
||||||
if &split.unit != unit {
|
if split.unit != unit.name {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let end = if split.end > 0 {
|
let end = if split.end > 0 {
|
||||||
|
@ -286,15 +340,30 @@ pub fn write_splits<W: Write>(w: &mut W, obj: &ObjInfo) -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct SplitSection {
|
||||||
|
name: String,
|
||||||
|
start: u32,
|
||||||
|
end: u32,
|
||||||
|
align: Option<u32>,
|
||||||
|
/// Whether this is a part of common BSS.
|
||||||
|
common: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SplitUnit {
|
||||||
|
name: String,
|
||||||
|
/// MW `.comment` section version
|
||||||
|
comment_version: Option<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
enum SplitLine {
|
enum SplitLine {
|
||||||
Unit { name: String },
|
Unit(SplitUnit),
|
||||||
Section { name: String, start: u32, end: u32, align: Option<u32>, common: bool },
|
Section(SplitSection),
|
||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_split_line(line: &str) -> Result<SplitLine> {
|
fn parse_split_line(line: &str) -> Result<SplitLine> {
|
||||||
static UNIT_LINE: Lazy<Regex> =
|
static UNIT_LINE: Lazy<Regex> =
|
||||||
Lazy::new(|| Regex::new("^\\s*(?P<name>[^\\s:]+)\\s*:\\s*$").unwrap());
|
Lazy::new(|| Regex::new("^\\s*(?P<name>[^\\s:]+)\\s*:\\s*(?P<attrs>.*)$").unwrap());
|
||||||
static SECTION_LINE: Lazy<Regex> =
|
static SECTION_LINE: Lazy<Regex> =
|
||||||
Lazy::new(|| Regex::new("^\\s*(?P<name>\\S+)\\s*(?P<attrs>.*)$").unwrap());
|
Lazy::new(|| Regex::new("^\\s*(?P<name>\\S+)\\s*(?P<attrs>.*)$").unwrap());
|
||||||
static COMMENT_LINE: Lazy<Regex> = Lazy::new(|| Regex::new("^\\s*(?://|#).*$").unwrap());
|
static COMMENT_LINE: Lazy<Regex> = Lazy::new(|| Regex::new("^\\s*(?://|#).*$").unwrap());
|
||||||
|
@ -302,48 +371,65 @@ fn parse_split_line(line: &str) -> Result<SplitLine> {
|
||||||
if line.is_empty() || COMMENT_LINE.is_match(line) {
|
if line.is_empty() || COMMENT_LINE.is_match(line) {
|
||||||
Ok(SplitLine::None)
|
Ok(SplitLine::None)
|
||||||
} else if let Some(captures) = UNIT_LINE.captures(line) {
|
} else if let Some(captures) = UNIT_LINE.captures(line) {
|
||||||
let name = captures["name"].to_string();
|
parse_unit_line(captures).with_context(|| format!("While parsing split line: '{line}'"))
|
||||||
Ok(SplitLine::Unit { name })
|
|
||||||
} else if let Some(captures) = SECTION_LINE.captures(line) {
|
} else if let Some(captures) = SECTION_LINE.captures(line) {
|
||||||
let mut name = captures["name"].to_string();
|
parse_section_line(captures).with_context(|| format!("While parsing split line: '{line}'"))
|
||||||
let mut start: Option<u32> = None;
|
} else {
|
||||||
let mut end: Option<u32> = None;
|
Err(anyhow!("Failed to parse split line: '{line}'"))
|
||||||
let mut align: Option<u32> = None;
|
}
|
||||||
let mut common = false;
|
}
|
||||||
|
|
||||||
let attrs = captures["attrs"].split(' ');
|
fn parse_unit_line(captures: Captures) -> Result<SplitLine> {
|
||||||
for attr in attrs {
|
let mut unit = SplitUnit { name: captures["name"].to_string(), comment_version: None };
|
||||||
|
|
||||||
|
for attr in captures["attrs"].split(' ').filter(|&s| !s.is_empty()) {
|
||||||
if let Some((attr, value)) = attr.split_once(':') {
|
if let Some((attr, value)) = attr.split_once(':') {
|
||||||
match attr {
|
match attr {
|
||||||
"start" => {
|
"comment" => unit.comment_version = Some(u8::from_str(value)?),
|
||||||
start = Some(parse_hex(value)?);
|
_ => bail!("Unknown unit attribute '{}'", attr),
|
||||||
}
|
}
|
||||||
"end" => {
|
} else {
|
||||||
end = Some(parse_hex(value)?);
|
bail!("Unknown unit attribute '{attr}'");
|
||||||
}
|
}
|
||||||
"align" => align = Some(u32::from_str(value)?),
|
}
|
||||||
"rename" => name = value.to_string(),
|
|
||||||
_ => bail!("Unknown split attribute '{name}'"),
|
Ok(SplitLine::Unit(unit))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_section_line(captures: Captures) -> Result<SplitLine> {
|
||||||
|
let mut section = SplitSection {
|
||||||
|
name: captures["name"].to_string(),
|
||||||
|
start: 0,
|
||||||
|
end: 0,
|
||||||
|
align: None,
|
||||||
|
common: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
for attr in captures["attrs"].split(' ').filter(|&s| !s.is_empty()) {
|
||||||
|
if let Some((attr, value)) = attr.split_once(':') {
|
||||||
|
match attr {
|
||||||
|
"start" => section.start = parse_hex(value)?,
|
||||||
|
"end" => section.end = parse_hex(value)?,
|
||||||
|
"align" => section.align = Some(u32::from_str(value)?),
|
||||||
|
"rename" => section.name = value.to_string(),
|
||||||
|
_ => bail!("Unknown split attribute '{attr}'"),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
match attr {
|
match attr {
|
||||||
"common" => {
|
"common" => {
|
||||||
common = true;
|
section.common = true;
|
||||||
if align.is_none() {
|
if section.align.is_none() {
|
||||||
align = Some(4);
|
section.align = Some(4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => bail!("Unknown split attribute '{attr}'"),
|
_ => bail!("Unknown split attribute '{attr}'"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let (Some(start), Some(end)) = (start, end) {
|
if section.start > 0 && section.end > 0 {
|
||||||
Ok(SplitLine::Section { name, start, end, align, common })
|
Ok(SplitLine::Section(section))
|
||||||
} else {
|
} else {
|
||||||
Err(anyhow!("Missing split attribute: '{line}'"))
|
Err(anyhow!("Section '{}' missing start or end address", section.name))
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(anyhow!("Failed to parse split line: '{line}'"))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -360,14 +446,24 @@ pub fn apply_splits<R: BufRead>(r: R, obj: &mut ObjInfo) -> Result<()> {
|
||||||
};
|
};
|
||||||
let split_line = parse_split_line(&line)?;
|
let split_line = parse_split_line(&line)?;
|
||||||
match (&mut state, split_line) {
|
match (&mut state, split_line) {
|
||||||
(SplitState::None | SplitState::Unit(_), SplitLine::Unit { name }) => {
|
(
|
||||||
obj.link_order.push(name.clone());
|
SplitState::None | SplitState::Unit(_),
|
||||||
|
SplitLine::Unit(SplitUnit { name, comment_version }),
|
||||||
|
) => {
|
||||||
|
obj.link_order.push(ObjUnit {
|
||||||
|
name: name.clone(),
|
||||||
|
autogenerated: false,
|
||||||
|
comment_version,
|
||||||
|
});
|
||||||
state = SplitState::Unit(name);
|
state = SplitState::Unit(name);
|
||||||
}
|
}
|
||||||
(SplitState::None, SplitLine::Section { name, .. }) => {
|
(SplitState::None, SplitLine::Section(SplitSection { name, .. })) => {
|
||||||
bail!("Section {} defined outside of unit", name);
|
bail!("Section {} defined outside of unit", name);
|
||||||
}
|
}
|
||||||
(SplitState::Unit(unit), SplitLine::Section { name, start, end, align, common }) => {
|
(
|
||||||
|
SplitState::Unit(unit),
|
||||||
|
SplitLine::Section(SplitSection { name, start, end, align, common }),
|
||||||
|
) => {
|
||||||
obj.splits.nested_push(start, ObjSplit {
|
obj.splits.nested_push(start, ObjSplit {
|
||||||
unit: unit.clone(),
|
unit: unit.clone(),
|
||||||
end,
|
end,
|
||||||
|
|
|
@ -5,7 +5,6 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{anyhow, bail, ensure, Context, Result};
|
use anyhow::{anyhow, bail, ensure, Context, Result};
|
||||||
use byteorder::{BigEndian, WriteBytesExt};
|
|
||||||
use cwdemangle::demangle;
|
use cwdemangle::demangle;
|
||||||
use flagset::Flags;
|
use flagset::Flags;
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
|
@ -23,7 +22,7 @@ use object::{
|
||||||
use crate::{
|
use crate::{
|
||||||
obj::{
|
obj::{
|
||||||
ObjArchitecture, ObjInfo, ObjKind, ObjReloc, ObjRelocKind, ObjSection, ObjSectionKind,
|
ObjArchitecture, ObjInfo, ObjKind, ObjReloc, ObjRelocKind, ObjSection, ObjSectionKind,
|
||||||
ObjSplit, ObjSymbol, ObjSymbolFlagSet, ObjSymbolFlags, ObjSymbolKind,
|
ObjSplit, ObjSymbol, ObjSymbolFlagSet, ObjSymbolFlags, ObjSymbolKind, ObjUnit,
|
||||||
},
|
},
|
||||||
util::{
|
util::{
|
||||||
comment::{read_comment_sym, write_comment_sym, CommentSym, MWComment},
|
comment::{read_comment_sym, write_comment_sym, CommentSym, MWComment},
|
||||||
|
@ -251,12 +250,16 @@ pub fn process_elf<P: AsRef<Path>>(path: P) -> Result<ObjInfo> {
|
||||||
symbols.push(to_obj_symbol(&obj_file, &symbol, §ion_indexes)?);
|
symbols.push(to_obj_symbol(&obj_file, &symbol, §ion_indexes)?);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut link_order = Vec::<String>::new();
|
let mut link_order = Vec::<ObjUnit>::new();
|
||||||
let mut splits = BTreeMap::<u32, Vec<ObjSplit>>::new();
|
let mut splits = BTreeMap::<u32, Vec<ObjSplit>>::new();
|
||||||
if kind == ObjKind::Executable {
|
if kind == ObjKind::Executable {
|
||||||
// Link order is trivially deduced
|
// Link order is trivially deduced
|
||||||
for file_name in section_starts.keys() {
|
for file_name in section_starts.keys() {
|
||||||
link_order.push(file_name.clone());
|
link_order.push(ObjUnit {
|
||||||
|
name: file_name.clone(),
|
||||||
|
autogenerated: false,
|
||||||
|
comment_version: None,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a map of address -> file splits
|
// Create a map of address -> file splits
|
||||||
|
@ -282,29 +285,32 @@ pub fn process_elf<P: AsRef<Path>>(path: P) -> Result<ObjInfo> {
|
||||||
};
|
};
|
||||||
// Generate relocations
|
// Generate relocations
|
||||||
for (address, reloc) in section.relocations() {
|
for (address, reloc) in section.relocations() {
|
||||||
out_section.relocations.push(to_obj_reloc(
|
let Some(reloc) =
|
||||||
&obj_file,
|
to_obj_reloc(&obj_file, &symbol_indexes, &out_section.data, address, reloc)?
|
||||||
&symbol_indexes,
|
else {
|
||||||
&out_section.data,
|
continue;
|
||||||
address,
|
};
|
||||||
reloc,
|
out_section.relocations.push(reloc);
|
||||||
)?);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mw_comment = if let Some(comment_section) = obj_file.section_by_name(".comment") {
|
let mw_comment = if let Some(comment_section) = obj_file.section_by_name(".comment") {
|
||||||
let data = comment_section.uncompressed_data()?;
|
let data = comment_section.uncompressed_data()?;
|
||||||
let mut reader = Cursor::new(&*data);
|
let mut reader = Cursor::new(&*data);
|
||||||
let header = MWComment::parse_header(&mut reader)?;
|
let header =
|
||||||
log::debug!("Loaded comment header {:?}", header);
|
MWComment::parse_header(&mut reader).context("While reading .comment section")?;
|
||||||
|
log::debug!("Loaded .comment section header {:?}", header);
|
||||||
for symbol in obj_file.symbols() {
|
for symbol in obj_file.symbols() {
|
||||||
let comment_sym = read_comment_sym(&mut reader, &symbol)?;
|
let comment_sym = read_comment_sym(&mut reader)?;
|
||||||
log::debug!("Symbol {:?} -> Comment {:?}", symbol, comment_sym);
|
log::debug!("Symbol {:?} -> Comment {:?}", symbol, comment_sym);
|
||||||
}
|
}
|
||||||
ensure!(data.len() - reader.position() as usize == 0, "Comment data not fully read");
|
ensure!(
|
||||||
header
|
data.len() - reader.position() as usize == 0,
|
||||||
|
".comment section data not fully read"
|
||||||
|
);
|
||||||
|
Some(header)
|
||||||
} else {
|
} else {
|
||||||
MWComment::default()
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut obj = ObjInfo::new(kind, architecture, obj_name, symbols, sections);
|
let mut obj = ObjInfo::new(kind, architecture, obj_name, symbols, sections);
|
||||||
|
@ -372,7 +378,7 @@ pub fn write_elf(obj: &ObjInfo) -> Result<Vec<u8>> {
|
||||||
writer.reserve_shstrtab_section_index();
|
writer.reserve_shstrtab_section_index();
|
||||||
|
|
||||||
// Generate comment section
|
// Generate comment section
|
||||||
let mut comment_data = if obj.kind == ObjKind::Relocatable {
|
let mut comment_data = if let Some(mw_comment) = &obj.mw_comment {
|
||||||
let mut comment_data = Vec::<u8>::with_capacity(0x2C + obj.symbols.count() * 8);
|
let mut comment_data = Vec::<u8>::with_capacity(0x2C + obj.symbols.count() * 8);
|
||||||
let name = writer.add_section_name(".comment".as_bytes());
|
let name = writer.add_section_name(".comment".as_bytes());
|
||||||
let index = writer.reserve_section_index();
|
let index = writer.reserve_section_index();
|
||||||
|
@ -384,7 +390,7 @@ pub fn write_elf(obj: &ObjInfo) -> Result<Vec<u8>> {
|
||||||
name,
|
name,
|
||||||
rela_name: None,
|
rela_name: None,
|
||||||
});
|
});
|
||||||
obj.mw_comment.write_header(&mut comment_data)?;
|
mw_comment.write_header(&mut comment_data)?;
|
||||||
// Null symbol
|
// Null symbol
|
||||||
write_comment_sym(&mut comment_data, CommentSym {
|
write_comment_sym(&mut comment_data, CommentSym {
|
||||||
align: 0,
|
align: 0,
|
||||||
|
@ -419,7 +425,7 @@ pub fn write_elf(obj: &ObjInfo) -> Result<Vec<u8>> {
|
||||||
section: None,
|
section: None,
|
||||||
st_info: {
|
st_info: {
|
||||||
let st_type = elf::STT_FILE;
|
let st_type = elf::STT_FILE;
|
||||||
let st_bind = elf::STB_GLOBAL;
|
let st_bind = elf::STB_LOCAL;
|
||||||
(st_bind << 4) + st_type
|
(st_bind << 4) + st_type
|
||||||
},
|
},
|
||||||
st_other: elf::STV_DEFAULT,
|
st_other: elf::STV_DEFAULT,
|
||||||
|
@ -443,15 +449,14 @@ pub fn write_elf(obj: &ObjInfo) -> Result<Vec<u8>> {
|
||||||
for section in &obj.sections {
|
for section in &obj.sections {
|
||||||
let section_index = out_sections.get(section.index).map(|s| s.index);
|
let section_index = out_sections.get(section.index).map(|s| s.index);
|
||||||
let index = writer.reserve_symbol_index(section_index);
|
let index = writer.reserve_symbol_index(section_index);
|
||||||
let name_index = writer.add_string(section.name.as_bytes());
|
|
||||||
let sym = object::write::elf::Sym {
|
let sym = object::write::elf::Sym {
|
||||||
name: Some(name_index),
|
name: None,
|
||||||
section: section_index,
|
section: section_index,
|
||||||
st_info: (elf::STB_LOCAL << 4) + elf::STT_SECTION,
|
st_info: (elf::STB_LOCAL << 4) + elf::STT_SECTION,
|
||||||
st_other: elf::STV_DEFAULT,
|
st_other: elf::STV_DEFAULT,
|
||||||
st_shndx: 0,
|
st_shndx: 0,
|
||||||
st_value: 0,
|
st_value: 0,
|
||||||
st_size: 0, // section.size
|
st_size: 0,
|
||||||
};
|
};
|
||||||
num_local = writer.symbol_count();
|
num_local = writer.symbol_count();
|
||||||
out_symbols.push(OutSymbol { index, sym });
|
out_symbols.push(OutSymbol { index, sym });
|
||||||
|
@ -795,7 +800,7 @@ fn to_obj_reloc(
|
||||||
section_data: &[u8],
|
section_data: &[u8],
|
||||||
address: u64,
|
address: u64,
|
||||||
reloc: Relocation,
|
reloc: Relocation,
|
||||||
) -> Result<ObjReloc> {
|
) -> Result<Option<ObjReloc>> {
|
||||||
let reloc_kind = match reloc.kind() {
|
let reloc_kind = match reloc.kind() {
|
||||||
RelocationKind::Absolute => ObjRelocKind::Absolute,
|
RelocationKind::Absolute => ObjRelocKind::Absolute,
|
||||||
RelocationKind::Elf(kind) => match kind {
|
RelocationKind::Elf(kind) => match kind {
|
||||||
|
@ -813,7 +818,13 @@ fn to_obj_reloc(
|
||||||
RelocationTarget::Symbol(idx) => {
|
RelocationTarget::Symbol(idx) => {
|
||||||
obj_file.symbol_by_index(idx).context("Failed to locate relocation target symbol")?
|
obj_file.symbol_by_index(idx).context("Failed to locate relocation target symbol")?
|
||||||
}
|
}
|
||||||
_ => bail!("Unhandled relocation target: {:?}", reloc.target()),
|
RelocationTarget::Absolute => {
|
||||||
|
log::debug!("Skipping absolute relocation at {:#010X}", address);
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
bail!("Unhandled relocation target: {:?} (address: {:#010X})", reloc.target(), address)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
let target_symbol = symbol_indexes[symbol.index().0]
|
let target_symbol = symbol_indexes[symbol.index().0]
|
||||||
.ok_or_else(|| anyhow!("Relocation against stripped symbol: {symbol:?}"))?;
|
.ok_or_else(|| anyhow!("Relocation against stripped symbol: {symbol:?}"))?;
|
||||||
|
@ -840,5 +851,5 @@ fn to_obj_reloc(
|
||||||
}?;
|
}?;
|
||||||
let address = address & !3; // TODO hack: round down for instruction
|
let address = address & !3; // TODO hack: round down for instruction
|
||||||
let reloc_data = ObjReloc { kind: reloc_kind, address, target_symbol, addend };
|
let reloc_data = ObjReloc { kind: reloc_kind, address, target_symbol, addend };
|
||||||
Ok(reloc_data)
|
Ok(Some(reloc_data))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::{
|
use std::{
|
||||||
fs::{File, OpenOptions},
|
fs::{File, OpenOptions},
|
||||||
io::{BufRead, BufReader, Cursor, Read},
|
io::{BufRead, BufReader, BufWriter, Cursor, Read},
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -33,6 +33,13 @@ pub fn buf_reader<P: AsRef<Path>>(path: P) -> Result<BufReader<File>> {
|
||||||
Ok(BufReader::new(file))
|
Ok(BufReader::new(file))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a buffered writer around a file (not memory mapped).
|
||||||
|
pub fn buf_writer<P: AsRef<Path>>(path: P) -> Result<BufWriter<File>> {
|
||||||
|
let file = File::create(&path)
|
||||||
|
.with_context(|| format!("Failed to create file '{}'", path.as_ref().display()))?;
|
||||||
|
Ok(BufWriter::new(file))
|
||||||
|
}
|
||||||
|
|
||||||
/// Reads a string with known size at the specified offset.
|
/// Reads a string with known size at the specified offset.
|
||||||
pub fn read_string(reader: &mut Reader, off: u64, size: usize) -> Result<String> {
|
pub fn read_string(reader: &mut Reader, off: u64, size: usize) -> Result<String> {
|
||||||
let mut data = vec![0u8; size];
|
let mut data = vec![0u8; size];
|
||||||
|
|
|
@ -5,7 +5,7 @@ use itertools::Itertools;
|
||||||
|
|
||||||
use crate::obj::ObjInfo;
|
use crate::obj::ObjInfo;
|
||||||
|
|
||||||
pub fn generate_ldscript(obj: &ObjInfo) -> Result<String> {
|
pub fn generate_ldscript(obj: &ObjInfo, auto_force_files: bool) -> Result<String> {
|
||||||
let stack_size = match (obj.stack_address, obj.stack_end) {
|
let stack_size = match (obj.stack_address, obj.stack_end) {
|
||||||
(Some(stack_address), Some(stack_end)) => stack_address - stack_end,
|
(Some(stack_address), Some(stack_end)) => stack_address - stack_end,
|
||||||
_ => 65535, // default
|
_ => 65535, // default
|
||||||
|
@ -19,7 +19,7 @@ pub fn generate_ldscript(obj: &ObjInfo) -> Result<String> {
|
||||||
|
|
||||||
let mut force_files = Vec::with_capacity(obj.link_order.len());
|
let mut force_files = Vec::with_capacity(obj.link_order.len());
|
||||||
for unit in &obj.link_order {
|
for unit in &obj.link_order {
|
||||||
let obj_path = obj_path_for_unit(unit);
|
let obj_path = obj_path_for_unit(&unit.name);
|
||||||
force_files.push(obj_path.file_name().unwrap().to_str().unwrap().to_string());
|
force_files.push(obj_path.file_name().unwrap().to_str().unwrap().to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,12 +27,16 @@ pub fn generate_ldscript(obj: &ObjInfo) -> Result<String> {
|
||||||
let last_section_name = obj.sections.last().unwrap().name.clone();
|
let last_section_name = obj.sections.last().unwrap().name.clone();
|
||||||
let last_section_symbol = format!("_f_{}", last_section_name.trim_start_matches('.'));
|
let last_section_symbol = format!("_f_{}", last_section_name.trim_start_matches('.'));
|
||||||
|
|
||||||
let out = include_str!("../../assets/ldscript.lcf")
|
let mut out = include_str!("../../assets/ldscript.lcf")
|
||||||
.replacen("$SECTIONS", §ion_defs, 1)
|
.replacen("$SECTIONS", §ion_defs, 1)
|
||||||
.replace("$LAST_SECTION_SYMBOL", &last_section_symbol)
|
.replace("$LAST_SECTION_SYMBOL", &last_section_symbol)
|
||||||
.replace("$LAST_SECTION_NAME", &last_section_name)
|
.replace("$LAST_SECTION_NAME", &last_section_name)
|
||||||
.replacen("$STACKSIZE", &format!("{:#X}", stack_size), 1)
|
.replacen("$STACKSIZE", &format!("{:#X}", stack_size), 1);
|
||||||
.replacen("$FORCEFILES", &force_files.join("\n "), 1);
|
out = if auto_force_files {
|
||||||
|
out.replacen("$FORCEFILES", &force_files.join("\n "), 1)
|
||||||
|
} else {
|
||||||
|
out.replacen("$FORCEFILES", "", 1)
|
||||||
|
};
|
||||||
Ok(out)
|
Ok(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ use std::{
|
||||||
hash::Hash,
|
hash::Hash,
|
||||||
io::BufRead,
|
io::BufRead,
|
||||||
mem::replace,
|
mem::replace,
|
||||||
|
path::Path,
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{anyhow, bail, ensure, Error, Result};
|
use anyhow::{anyhow, bail, ensure, Error, Result};
|
||||||
|
@ -18,7 +19,10 @@ use crate::{
|
||||||
section_kind_for_section, ObjInfo, ObjSplit, ObjSymbol, ObjSymbolFlagSet, ObjSymbolFlags,
|
section_kind_for_section, ObjInfo, ObjSplit, ObjSymbol, ObjSymbolFlagSet, ObjSymbolFlags,
|
||||||
ObjSymbolKind,
|
ObjSymbolKind,
|
||||||
},
|
},
|
||||||
util::nested::NestedVec,
|
util::{
|
||||||
|
file::{map_file, map_reader},
|
||||||
|
nested::NestedVec,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
|
@ -31,6 +35,7 @@ pub enum SymbolKind {
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
pub enum SymbolVisibility {
|
pub enum SymbolVisibility {
|
||||||
|
Unknown,
|
||||||
Global,
|
Global,
|
||||||
Local,
|
Local,
|
||||||
Weak,
|
Weak,
|
||||||
|
@ -327,8 +332,8 @@ impl StateMachine {
|
||||||
|
|
||||||
fn end_state(&mut self, old_state: ProcessMapState) -> Result<()> {
|
fn end_state(&mut self, old_state: ProcessMapState) -> Result<()> {
|
||||||
match old_state {
|
match old_state {
|
||||||
ProcessMapState::LinkMap { .. } => {
|
ProcessMapState::LinkMap(state) => {
|
||||||
self.has_link_map = true;
|
self.has_link_map = !state.last_symbol_name.is_empty();
|
||||||
}
|
}
|
||||||
ProcessMapState::SectionLayout(state) => {
|
ProcessMapState::SectionLayout(state) => {
|
||||||
StateMachine::end_section_layout(state, &mut self.result)?;
|
StateMachine::end_section_layout(state, &mut self.result)?;
|
||||||
|
@ -535,7 +540,7 @@ impl StateMachine {
|
||||||
align,
|
align,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let visibility = if state.has_link_map {
|
let mut visibility = if state.has_link_map {
|
||||||
log::warn!(
|
log::warn!(
|
||||||
"Symbol not in link map: {} ({}). Type and visibility unknown.",
|
"Symbol not in link map: {} ({}). Type and visibility unknown.",
|
||||||
sym_name,
|
sym_name,
|
||||||
|
@ -543,12 +548,24 @@ impl StateMachine {
|
||||||
);
|
);
|
||||||
SymbolVisibility::Local
|
SymbolVisibility::Local
|
||||||
} else {
|
} else {
|
||||||
SymbolVisibility::Global
|
SymbolVisibility::Unknown
|
||||||
|
};
|
||||||
|
let kind = if sym_name.starts_with('.') {
|
||||||
|
visibility = SymbolVisibility::Local;
|
||||||
|
SymbolKind::Section
|
||||||
|
} else if size > 0 {
|
||||||
|
if is_code_section(&state.current_section) {
|
||||||
|
SymbolKind::Function
|
||||||
|
} else {
|
||||||
|
SymbolKind::Object
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
SymbolKind::NoType
|
||||||
};
|
};
|
||||||
SymbolEntry {
|
SymbolEntry {
|
||||||
name: sym_name.to_string(),
|
name: sym_name.to_string(),
|
||||||
demangled: None,
|
demangled: None,
|
||||||
kind: SymbolKind::NoType,
|
kind,
|
||||||
visibility,
|
visibility,
|
||||||
unit: Some(tu.clone()),
|
unit: Some(tu.clone()),
|
||||||
address,
|
address,
|
||||||
|
@ -634,6 +651,12 @@ pub fn process_map<R: BufRead>(reader: R) -> Result<MapInfo> {
|
||||||
Ok(entries)
|
Ok(entries)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn apply_map_file<P: AsRef<Path>>(path: P, obj: &mut ObjInfo) -> Result<()> {
|
||||||
|
let file = map_file(&path)?;
|
||||||
|
let info = process_map(map_reader(&file))?;
|
||||||
|
apply_map(&info, obj)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn apply_map(result: &MapInfo, obj: &mut ObjInfo) -> Result<()> {
|
pub fn apply_map(result: &MapInfo, obj: &mut ObjInfo) -> Result<()> {
|
||||||
for section in &mut obj.sections {
|
for section in &mut obj.sections {
|
||||||
if let Some(info) = result.sections.get(&(section.address as u32)) {
|
if let Some(info) = result.sections.get(&(section.address as u32)) {
|
||||||
|
@ -714,8 +737,8 @@ pub fn apply_map(result: &MapInfo, obj: &mut ObjInfo) -> Result<()> {
|
||||||
}
|
}
|
||||||
section_order.push((section.clone(), units));
|
section_order.push((section.clone(), units));
|
||||||
}
|
}
|
||||||
log::info!("Section order: {:#?}", section_order);
|
|
||||||
// TODO
|
// TODO
|
||||||
|
// log::info!("Section order: {:#?}", section_order);
|
||||||
// obj.link_order = resolve_link_order(§ion_order)?;
|
// obj.link_order = resolve_link_order(§ion_order)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -730,14 +753,12 @@ fn add_symbol(obj: &mut ObjInfo, symbol_entry: &SymbolEntry, section: Option<usi
|
||||||
section,
|
section,
|
||||||
size: symbol_entry.size as u64,
|
size: symbol_entry.size as u64,
|
||||||
size_known: symbol_entry.size != 0,
|
size_known: symbol_entry.size != 0,
|
||||||
flags: ObjSymbolFlagSet(
|
flags: ObjSymbolFlagSet(match symbol_entry.visibility {
|
||||||
match symbol_entry.visibility {
|
SymbolVisibility::Unknown => Default::default(),
|
||||||
SymbolVisibility::Global => ObjSymbolFlags::Global,
|
SymbolVisibility::Global => ObjSymbolFlags::Global.into(),
|
||||||
SymbolVisibility::Local => ObjSymbolFlags::Local,
|
SymbolVisibility::Local => ObjSymbolFlags::Local.into(),
|
||||||
SymbolVisibility::Weak => ObjSymbolFlags::Weak,
|
SymbolVisibility::Weak => ObjSymbolFlags::Weak.into(),
|
||||||
}
|
}),
|
||||||
.into(),
|
|
||||||
),
|
|
||||||
kind: match symbol_entry.kind {
|
kind: match symbol_entry.kind {
|
||||||
SymbolKind::Function => ObjSymbolKind::Function,
|
SymbolKind::Function => ObjSymbolKind::Function,
|
||||||
SymbolKind::Object => ObjSymbolKind::Object,
|
SymbolKind::Object => ObjSymbolKind::Object,
|
||||||
|
|
|
@ -238,12 +238,20 @@ pub fn process_rel(mut reader: Reader) -> Result<ObjInfo> {
|
||||||
Ok(obj)
|
Ok(obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// REL relocation.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct RelReloc {
|
pub struct RelReloc {
|
||||||
|
/// Relocation kind.
|
||||||
pub kind: ObjRelocKind,
|
pub kind: ObjRelocKind,
|
||||||
|
/// Source section index.
|
||||||
pub section: u8,
|
pub section: u8,
|
||||||
|
/// Source address.
|
||||||
pub address: u32,
|
pub address: u32,
|
||||||
|
/// Target module ID.
|
||||||
pub module_id: u32,
|
pub module_id: u32,
|
||||||
|
/// Target section index.
|
||||||
pub target_section: u8,
|
pub target_section: u8,
|
||||||
|
/// Target addend within section.
|
||||||
|
/// If target module ID is 0 (DOL), this is an absolute address.
|
||||||
pub addend: u32,
|
pub addend: u32,
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue