Version 0.2.0

- Add `elf disasm` (disassemble an ELF)
- Add `elf fixup` (for GNU assembler)
- Add `map order` (link order deduction)
- Add `map slices` (ppcdis slices.yml, WIP)
- Add `map symbols` (ppcdis symbols.yml, WIP)
- Big speed improvement for map processing
- Minor `elf2dol` cleanup
This commit is contained in:
2022-12-10 01:28:23 -05:00
parent f6dbe94bac
commit 141339fcb0
18 changed files with 2548 additions and 334 deletions

View File

@@ -18,7 +18,7 @@ pub fn run(args: Args) -> Result<()> {
let options = DemangleOptions { omit_empty_parameters: !args.keep_void };
match demangle(args.symbol.as_str(), &options) {
Some(symbol) => {
println!("{}", symbol);
println!("{symbol}");
Ok(())
}
None => Err(Error::msg("Failed to demangle symbol")),

275
src/cmd/elf.rs Normal file
View File

@@ -0,0 +1,275 @@
use std::{
collections::{btree_map::Entry, BTreeMap},
fs,
fs::File,
io::{BufWriter, Write},
};
use anyhow::{Context, Error, Result};
use argh::FromArgs;
use object::{
write::{SectionId, SymbolId},
Object, ObjectSection, ObjectSymbol, RelocationKind, RelocationTarget, SectionFlags,
SectionIndex, SectionKind, SymbolFlags, SymbolKind, SymbolSection,
};
use crate::util::{asm::write_asm, elf::process_elf};
#[derive(FromArgs, PartialEq, Debug)]
/// Commands for processing ELF files.
#[argh(subcommand, name = "elf")]
pub struct Args {
#[argh(subcommand)]
command: SubCommand,
}
#[derive(FromArgs, PartialEq, Debug)]
#[argh(subcommand)]
enum SubCommand {
Disasm(DisasmArgs),
Fixup(FixupArgs),
}
#[derive(FromArgs, PartialEq, Eq, Debug)]
/// Disassembles an ELF file.
#[argh(subcommand, name = "disasm")]
pub struct DisasmArgs {
#[argh(positional)]
/// input file
elf_file: String,
#[argh(positional)]
/// output directory
out_dir: String,
}
#[derive(FromArgs, PartialEq, Eq, Debug)]
/// Fixes issues with GNU assembler built object files.
#[argh(subcommand, name = "fixup")]
pub struct FixupArgs {
#[argh(positional)]
/// input file
in_file: String,
#[argh(positional)]
/// output file
out_file: String,
}
pub fn run(args: Args) -> Result<()> {
match args.command {
SubCommand::Disasm(c_args) => disasm(c_args),
SubCommand::Fixup(c_args) => fixup(c_args),
}
}
fn disasm(args: DisasmArgs) -> Result<()> {
let obj = process_elf(&args.elf_file)?;
write_asm(&args.out_dir, &obj)?;
for unit in obj.link_order {
let name = format!("$(OBJ_DIR)/asm/{}", file_name_from_unit(&unit));
println!(" {name: <70}\\");
}
Ok(())
}
fn file_name_from_unit(str: &str) -> String {
let str = str.strip_prefix("C:").unwrap_or(str);
let str = str
.strip_suffix(".c")
.or_else(|| str.strip_suffix(".cp"))
.or_else(|| str.strip_suffix(".cpp"))
.or_else(|| str.strip_suffix(".s"))
.unwrap_or(str);
let str = str.replace('\\', "/");
format!("{}.o", str.strip_prefix('/').unwrap_or(&str))
}
fn fixup(args: FixupArgs) -> Result<()> {
let in_buf = fs::read(&args.in_file).context("Failed to open input file")?;
let in_file = object::read::File::parse(&*in_buf).context("Failed to parse input ELF")?;
let mut out_file =
object::write::Object::new(in_file.format(), in_file.architecture(), in_file.endianness());
// Write file symbol(s) first
for symbol in in_file.symbols() {
if symbol.kind() != SymbolKind::File {
continue;
}
out_file.add_symbol(to_write_symbol(&symbol, &[])?);
}
// Write section symbols & sections
let mut section_ids: Vec<Option<SectionId>> = vec![];
for section in in_file.sections() {
// Skip empty sections or metadata sections
if section.size() == 0 || section.kind() == SectionKind::Metadata {
section_ids.push(None);
continue;
}
let section_id =
out_file.add_section(vec![], section.name_bytes()?.to_vec(), section.kind());
section_ids.push(Some(section_id));
let out_section = out_file.section_mut(section_id);
if section.kind() == SectionKind::UninitializedData {
out_section.append_bss(section.size(), section.align());
} else {
out_section.set_data(section.uncompressed_data()?.into_owned(), section.align());
}
if has_section_flags(section.flags(), object::elf::SHF_ALLOC)? {
// Generate section symbol
out_file.section_symbol(section_id);
}
}
// Write symbols
let mut symbol_ids: Vec<Option<SymbolId>> = vec![];
let mut addr_to_sym: BTreeMap<SectionId, BTreeMap<u32, SymbolId>> = BTreeMap::new();
for symbol in in_file.symbols() {
// Skip section and file symbols, we wrote them above
if matches!(symbol.kind(), SymbolKind::Section | SymbolKind::File | SymbolKind::Null) {
symbol_ids.push(None);
continue;
}
let out_symbol = to_write_symbol(&symbol, &section_ids)?;
let section_id = out_symbol.section.id();
let symbol_id = out_file.add_symbol(out_symbol);
symbol_ids.push(Some(symbol_id));
if symbol.size() != 0 {
if let Some(section_id) = section_id {
let map = match addr_to_sym.entry(section_id) {
Entry::Vacant(e) => e.insert(BTreeMap::new()),
Entry::Occupied(e) => e.into_mut(),
};
map.insert(symbol.address() as u32, symbol_id);
}
}
}
// Write relocations
for section in in_file.sections() {
let section_id = match section_ids[section.index().0] {
Some(id) => id,
None => continue,
};
for (addr, reloc) in section.relocations() {
let mut symbol = match reloc.target() {
RelocationTarget::Symbol(idx) => match symbol_ids[idx.0] {
Some(id) => id,
None => {
let in_symbol = in_file.symbol_by_index(idx)?;
match in_symbol.kind() {
SymbolKind::Section => {
let section_idx = match in_symbol.section_index() {
Some(id) => id,
None => {
return Err(Error::msg("Missing section for relocation"))
}
};
let section_id = match section_ids[section_idx.0] {
Some(id) => id,
None => {
return Err(Error::msg("Missing section for relocation"))
}
};
out_file.section_symbol(section_id)
}
_ => return Err(Error::msg("Missing symbol for relocation")),
}
}
},
RelocationTarget::Section(idx) => {
let section_id = match section_ids[idx.0] {
Some(id) => id,
None => return Err(Error::msg("Missing section for relocation")),
};
out_file.section_symbol(section_id)
}
RelocationTarget::Absolute => todo!("Absolute relocation target"),
_ => return Err(Error::msg("Invalid relocation target")),
};
let mut addend = reloc.addend();
// Attempt to replace section symbols with direct symbol references
let target_sym = out_file.symbol(symbol);
if target_sym.kind == SymbolKind::Section {
if let Some(new_symbol_id) = target_sym
.section
.id()
.and_then(|id| addr_to_sym.get(&id))
.and_then(|map| map.get(&(addend as u32)))
{
symbol = *new_symbol_id;
addend = 0;
}
}
let kind = match reloc.kind() {
// This is a hack to avoid replacement with a section symbol
// See [`object::write::elf::object::elf_fixup_relocation`]
RelocationKind::Absolute => RelocationKind::Elf(object::elf::R_PPC_ADDR32),
other => other,
};
out_file.add_relocation(section_id, object::write::Relocation {
offset: addr,
size: reloc.size(),
kind,
encoding: reloc.encoding(),
symbol,
addend,
})?;
}
}
let mut out =
BufWriter::new(File::create(&args.out_file).context("Failed to create out file")?);
out_file.write_stream(&mut out).map_err(|e| Error::msg(format!("{e:?}")))?;
out.flush()?;
Ok(())
}
fn to_write_symbol_section(
section: SymbolSection,
section_ids: &[Option<SectionId>],
) -> Result<object::write::SymbolSection> {
Ok(match section {
SymbolSection::None => object::write::SymbolSection::None,
SymbolSection::Absolute => object::write::SymbolSection::Absolute,
SymbolSection::Common => object::write::SymbolSection::Common,
SymbolSection::Section(idx) => match section_ids.get(idx.0).and_then(|opt| *opt) {
Some(section_id) => object::write::SymbolSection::Section(section_id),
None => return Err(Error::msg("Missing symbol section")),
},
_ => object::write::SymbolSection::Undefined,
})
}
fn to_write_symbol_flags(flags: SymbolFlags<SectionIndex>) -> Result<SymbolFlags<SectionId>> {
Ok(match flags {
SymbolFlags::Elf { st_info, st_other } => SymbolFlags::Elf { st_info, st_other },
SymbolFlags::None => SymbolFlags::None,
_ => return Err(Error::msg("Unexpected symbol flags")),
})
}
fn to_write_symbol(
symbol: &object::read::Symbol,
section_ids: &[Option<SectionId>],
) -> Result<object::write::Symbol> {
Ok(object::write::Symbol {
name: symbol.name_bytes()?.to_vec(),
value: symbol.address(),
size: symbol.size(),
kind: symbol.kind(),
scope: symbol.scope(),
weak: symbol.is_weak(),
section: to_write_symbol_section(symbol.section(), section_ids)?,
flags: to_write_symbol_flags(symbol.flags())?,
})
}
fn has_section_flags(flags: SectionFlags, flag: u32) -> Result<bool> {
match flags {
SectionFlags::Elf { sh_flags } => Ok(sh_flags & flag as u64 == flag as u64),
_ => Err(Error::msg("Unexpected section flags")),
}
}

View File

@@ -29,8 +29,10 @@ pub struct DolSection {
#[derive(Debug, Clone, Default)]
pub struct DolHeader {
pub text_sections: Vec<DolSection>,
pub data_sections: Vec<DolSection>,
pub text_section_count: usize,
pub data_section_count: usize,
pub text_sections: [DolSection; MAX_TEXT_SECTIONS],
pub data_sections: [DolSection; MAX_DATA_SECTIONS],
pub bss_address: u32,
pub bss_size: u32,
pub entry_point: u32,
@@ -38,32 +40,32 @@ pub struct DolHeader {
const MAX_TEXT_SECTIONS: usize = 7;
const MAX_DATA_SECTIONS: usize = 11;
const ZERO_BUF: [u8; 32] = [0u8; 32];
pub fn run(args: Args) -> Result<()> {
let elf_file = File::open(&args.elf_file)
.with_context(|| format!("Failed to open ELF file '{}'", args.elf_file))?;
let map = unsafe { MmapOptions::new().map(&elf_file) }
.with_context(|| format!("Failed to mmap binary: '{}'", args.elf_file))?;
.with_context(|| format!("Failed to mmap ELF file: '{}'", args.elf_file))?;
let obj_file = object::read::File::parse(&*map)?;
match obj_file.architecture() {
Architecture::PowerPc => {}
arch => return Err(Error::msg(format!("Unexpected architecture: {:?}", arch))),
arch => return Err(Error::msg(format!("Unexpected architecture: {arch:?}"))),
};
if obj_file.is_little_endian() {
return Err(Error::msg("Expected big endian"));
}
match obj_file.kind() {
ObjectKind::Executable => {}
kind => return Err(Error::msg(format!("Unexpected ELF type: {:?}", kind))),
kind => return Err(Error::msg(format!("Unexpected ELF type: {kind:?}"))),
}
let mut header = DolHeader { entry_point: obj_file.entry() as u32, ..Default::default() };
let mut offset = 0x100u32;
let mut out = BufWriter::new(
File::create(&args.dol_file)
.with_context(|| format!("Failed to create DOL file '{}'", args.dol_file))?,
);
let mut header = DolHeader { entry_point: obj_file.entry() as u32, ..Default::default() };
let mut offset = 0x100u32;
out.seek(SeekFrom::Start(offset as u64))?;
// Text sections
for section in obj_file.sections() {
@@ -72,9 +74,14 @@ pub fn run(args: Args) -> Result<()> {
}
let address = section.address() as u32;
let size = align32(section.size() as u32);
header.text_sections.push(DolSection { offset, address, size });
out.seek(SeekFrom::Start(offset as u64))?;
write_aligned(&mut out, section.data()?)?;
*header.text_sections.get_mut(header.text_section_count).ok_or_else(|| {
Error::msg(format!(
"Too many text sections (while processing '{}')",
section.name().unwrap_or("[error]")
))
})? = DolSection { offset, address, size };
header.text_section_count += 1;
write_aligned(&mut out, section.data()?, size)?;
offset += size;
}
@@ -85,9 +92,14 @@ pub fn run(args: Args) -> Result<()> {
}
let address = section.address() as u32;
let size = align32(section.size() as u32);
header.data_sections.push(DolSection { offset, address, size });
out.seek(SeekFrom::Start(offset as u64))?;
write_aligned(&mut out, section.data()?)?;
*header.data_sections.get_mut(header.data_section_count).ok_or_else(|| {
Error::msg(format!(
"Too many data sections (while processing '{}')",
section.name().unwrap_or("[error]")
))
})? = DolSection { offset, address, size };
header.data_section_count += 1;
write_aligned(&mut out, section.data()?, size)?;
offset += size;
}
@@ -104,68 +116,50 @@ pub fn run(args: Args) -> Result<()> {
header.bss_size = (address + size) - header.bss_address;
}
if header.text_sections.len() > MAX_TEXT_SECTIONS {
return Err(Error::msg(format!(
"Too many text sections: {} / {}",
header.text_sections.len(),
MAX_TEXT_SECTIONS
)));
}
if header.data_sections.len() > MAX_DATA_SECTIONS {
return Err(Error::msg(format!(
"Too many data sections: {} / {}",
header.data_sections.len(),
MAX_DATA_SECTIONS
)));
}
// Offsets
out.rewind()?;
for section in &header.text_sections {
out.write_all(&section.offset.to_be_bytes())?;
}
out.seek(SeekFrom::Start(0x1c))?;
for section in &header.data_sections {
out.write_all(&section.offset.to_be_bytes())?;
}
// Addresses
out.seek(SeekFrom::Start(0x48))?;
for section in &header.text_sections {
out.write_all(&section.address.to_be_bytes())?;
}
out.seek(SeekFrom::Start(0x64))?;
for section in &header.data_sections {
out.write_all(&section.address.to_be_bytes())?;
}
// Sizes
out.seek(SeekFrom::Start(0x90))?;
for section in &header.text_sections {
out.write_all(&section.size.to_be_bytes())?;
}
out.seek(SeekFrom::Start(0xac))?;
for section in &header.data_sections {
out.write_all(&section.size.to_be_bytes())?;
}
// BSS + entry
out.seek(SeekFrom::Start(0xd8))?;
out.write_all(&header.bss_address.to_be_bytes())?;
out.write_all(&header.bss_size.to_be_bytes())?;
out.write_all(&header.entry_point.to_be_bytes())?;
// Done!
out.flush()?;
Ok(())
}
#[inline]
fn align32(x: u32) -> u32 { (x + 31) & !31 }
const fn align32(x: u32) -> u32 { (x + 31) & !31 }
const ZERO_BUF: [u8; 32] = [0u8; 32];
#[inline]
fn write_aligned<T: Write>(out: &mut T, bytes: &[u8]) -> std::io::Result<()> {
let len = bytes.len() as u32;
let padding = align32(len) - len;
fn write_aligned<T: Write>(out: &mut T, bytes: &[u8], aligned_size: u32) -> std::io::Result<()> {
out.write_all(bytes)?;
let padding = aligned_size - bytes.len() as u32;
if padding > 0 {
out.write_all(&ZERO_BUF[0..padding as usize])?;
}

View File

@@ -1,9 +1,9 @@
use std::{fs::File, io::BufReader};
use std::{fs::File, io::BufReader, ops::Range};
use anyhow::{Context, Error, Result};
use argh::FromArgs;
use crate::util::map::{process_map, SymbolEntry, SymbolRef};
use crate::util::map::{process_map, resolve_link_order, SymbolEntry, SymbolRef};
#[derive(FromArgs, PartialEq, Debug)]
/// Commands for processing CodeWarrior maps.
@@ -18,6 +18,9 @@ pub struct Args {
enum SubCommand {
Entries(EntriesArgs),
Symbol(SymbolArgs),
Order(OrderArgs),
Slices(SlicesArgs),
Symbols(SymbolsArgs),
}
#[derive(FromArgs, PartialEq, Eq, Debug)]
@@ -44,10 +47,40 @@ pub struct SymbolArgs {
symbol: String,
}
#[derive(FromArgs, PartialEq, Eq, Debug)]
/// Attempts to resolve global link order.
#[argh(subcommand, name = "order")]
pub struct OrderArgs {
#[argh(positional)]
/// path to input map
map_file: String,
}
#[derive(FromArgs, PartialEq, Eq, Debug)]
/// Emits a slices.yml for ppcdis. (WIP)
#[argh(subcommand, name = "slices")]
pub struct SlicesArgs {
#[argh(positional)]
/// path to input map
map_file: String,
}
#[derive(FromArgs, PartialEq, Eq, Debug)]
/// Emits a symbols.yml for ppcdis. (WIP)
#[argh(subcommand, name = "symbols")]
pub struct SymbolsArgs {
#[argh(positional)]
/// path to input map
map_file: String,
}
pub fn run(args: Args) -> Result<()> {
match args.command {
SubCommand::Entries(c_args) => entries(c_args),
SubCommand::Symbol(c_args) => symbol(c_args),
SubCommand::Order(c_args) => order(c_args),
SubCommand::Slices(c_args) => slices(c_args),
SubCommand::Symbols(c_args) => symbols(c_args),
}
}
@@ -138,3 +171,64 @@ fn symbol(args: SymbolArgs) -> Result<()> {
}
Ok(())
}
fn order(args: OrderArgs) -> Result<()> {
let reader = BufReader::new(
File::open(&args.map_file)
.with_context(|| format!("Failed to open file '{}'", args.map_file))?,
);
let entries = process_map(reader)?;
let order = resolve_link_order(&entries.unit_order)?;
for unit in order {
println!("{unit}");
}
Ok(())
}
fn slices(args: SlicesArgs) -> Result<()> {
let reader = BufReader::new(
File::open(&args.map_file)
.with_context(|| format!("Failed to open file '{}'", args.map_file))?,
);
let entries = process_map(reader)?;
let order = resolve_link_order(&entries.unit_order)?;
for unit in order {
let unit_path = if let Some((lib, name)) = unit.split_once(' ') {
format!("{}/{}", lib.strip_suffix(".a").unwrap_or(lib), name)
} else if let Some(strip) = unit.strip_suffix(".o") {
format!("{strip}.c")
} else {
unit.clone()
};
println!("{unit_path}:");
let mut ranges = Vec::<(String, Range<u32>)>::new();
match entries.unit_section_ranges.get(&unit) {
Some(sections) => {
for (name, range) in sections {
ranges.push((name.clone(), range.clone()));
}
}
None => return Err(Error::msg(format!("Failed to locate sections for unit '{unit}'"))),
}
ranges.sort_by(|(_, a), (_, b)| a.start.cmp(&b.start));
for (name, range) in ranges {
println!("\t{}: [{:#010x}, {:#010x}]", name, range.start, range.end);
}
}
Ok(())
}
fn symbols(args: SymbolsArgs) -> Result<()> {
let reader = BufReader::new(
File::open(&args.map_file)
.with_context(|| format!("Failed to open file '{}'", args.map_file))?,
);
let entries = process_map(reader)?;
for (address, symbol) in entries.address_to_symbol {
if symbol.name.starts_with('@') {
continue;
}
println!("{:#010x}: {}", address, symbol.name);
}
Ok(())
}

View File

@@ -22,10 +22,10 @@ pub fn run(args: Args) -> Result<()> {
let build_string = std::fs::read_to_string(&args.build_info)
.with_context(|| format!("Failed to read build info string from '{}'", args.build_info))?;
let build_string_trim = build_string.trim_end();
if build_string_trim.as_bytes().len() > BUILD_STRING_MAX {
let build_string_bytes = build_string_trim.as_bytes();
if build_string_bytes.len() > BUILD_STRING_MAX {
return Err(Error::msg(format!(
"Build string '{}' is greater than maximum size of {}",
build_string_trim, BUILD_STRING_MAX
"Build string '{build_string_trim}' is greater than maximum size of {BUILD_STRING_MAX}"
)));
}
@@ -40,8 +40,8 @@ pub fn run(args: Args) -> Result<()> {
Some(idx) => idx + BUILD_STRING_TAG.as_bytes().len(),
None => return Err(Error::msg("Failed to find build string tag in binary")),
};
let end = start + build_string_trim.as_bytes().len();
map[start..end].copy_from_slice(build_string_trim.as_bytes());
let end = start + build_string_bytes.len();
map[start..end].copy_from_slice(build_string_bytes);
map[end] = 0;
Ok(())
}

View File

@@ -1,4 +1,5 @@
pub(crate) mod demangle;
pub(crate) mod elf;
pub(crate) mod elf2dol;
pub(crate) mod map;
pub(crate) mod metroidbuildinfo;

View File

@@ -42,34 +42,34 @@ fn check(args: Args, file: File) -> Result<()> {
for line in reader.lines() {
let line = match line {
Ok(line) => line,
Err(e) => return Err(Error::msg(format!("File read failed: {}", e))),
Err(e) => return Err(Error::msg(format!("File read failed: {e}"))),
};
let (hash, file_name) =
line.split_once(' ').ok_or_else(|| Error::msg(format!("Invalid line: {}", line)))?;
line.split_once(' ').ok_or_else(|| Error::msg(format!("Invalid line: {line}")))?;
let file_name = match file_name.chars().next() {
Some(' ') | Some('*') => &file_name[1..],
_ => return Err(Error::msg(format!("Invalid line: {}", line))),
_ => return Err(Error::msg(format!("Invalid line: {line}"))),
};
let mut hash_bytes = [0u8; 20];
hex::decode_to_slice(hash, &mut hash_bytes)
.with_context(|| format!("Invalid line: {}", line))?;
.with_context(|| format!("Invalid line: {line}"))?;
let file = File::open(file_name)
.with_context(|| format!("Failed to open file '{}'", file_name))?;
let file =
File::open(file_name).with_context(|| format!("Failed to open file '{file_name}'"))?;
let found_hash = file_sha1(file)?;
if hash_bytes == found_hash.as_ref() {
println!("{}: OK", file_name);
println!("{file_name}: OK");
} else {
println!("{}: FAILED", file_name);
println!("{file_name}: FAILED");
mismatches += 1;
}
}
if mismatches != 0 {
eprintln!("WARNING: {} computed checksum did NOT match", mismatches);
eprintln!("WARNING: {mismatches} computed checksum did NOT match");
std::process::exit(1);
}
if let Some(out_path) = args.output {
touch(&out_path).with_context(|| format!("Failed to touch output file '{}'", out_path))?;
touch(&out_path).with_context(|| format!("Failed to touch output file '{out_path}'"))?;
}
Ok(())
}
@@ -78,7 +78,7 @@ fn hash(args: Args, file: File) -> Result<()> {
let hash = file_sha1(file)?;
let mut hash_buf = [0u8; 40];
let hash_str = base16ct::lower::encode_str(&hash, &mut hash_buf)
.map_err(|e| Error::msg(format!("Failed to encode hash: {}", e)))?;
.map_err(|e| Error::msg(format!("Failed to encode hash: {e}")))?;
println!("{} {}", hash_str, args.file);
Ok(())
}