Add `elf split`; rework asm generation

`elf split` allows splitting an ELF
directly into relocatable object files
This commit is contained in:
Luke Street 2022-12-24 03:10:12 -05:00
parent 54f2abd35f
commit 9f4cc2f542
8 changed files with 909 additions and 625 deletions

View File

@ -7,7 +7,7 @@ use std::{
use anyhow::{Context, Error, Result}; use anyhow::{Context, Error, Result};
use argh::FromArgs; use argh::FromArgs;
use object::{Object, ObjectSymbol}; use object::{Object, ObjectSymbol, SymbolScope};
#[derive(FromArgs, PartialEq, Debug)] #[derive(FromArgs, PartialEq, Debug)]
/// Commands for processing static libraries. /// Commands for processing static libraries.
@ -52,7 +52,7 @@ fn create(args: CreateArgs) -> Result<()> {
Some(rsp_file) => { Some(rsp_file) => {
let reader = BufReader::new( let reader = BufReader::new(
File::open(rsp_file) File::open(rsp_file)
.with_context(|| format!("Failed to open file '{}'", rsp_file))?, .with_context(|| format!("Failed to open file '{rsp_file}'"))?,
); );
for result in reader.lines() { for result in reader.lines() {
let line = result?; let line = result?;
@ -92,7 +92,7 @@ fn create(args: CreateArgs) -> Result<()> {
.with_context(|| format!("Failed to mmap object file: '{}'", path.to_string_lossy()))?; .with_context(|| format!("Failed to mmap object file: '{}'", path.to_string_lossy()))?;
let obj = object::File::parse(map.as_ref())?; let obj = object::File::parse(map.as_ref())?;
for symbol in obj.symbols() { for symbol in obj.symbols() {
if symbol.is_global() { if symbol.scope() == SymbolScope::Dynamic {
entries.push(symbol.name_bytes()?.to_vec()); entries.push(symbol.name_bytes()?.to_vec());
} }
} }

View File

@ -1,7 +1,7 @@
use std::{ use std::{
collections::{btree_map::Entry, BTreeMap}, collections::{btree_map, hash_map, BTreeMap, HashMap},
fs, fs,
fs::File, fs::{DirBuilder, File},
io::{BufWriter, Write}, io::{BufWriter, Write},
path::PathBuf, path::PathBuf,
}; };
@ -14,7 +14,12 @@ use object::{
SectionIndex, SectionKind, SymbolFlags, SymbolKind, SymbolScope, SymbolSection, SectionIndex, SectionKind, SymbolFlags, SymbolKind, SymbolScope, SymbolSection,
}; };
use crate::util::{asm::write_asm, elf::process_elf}; use crate::util::{
asm::write_asm,
elf::{process_elf, write_elf},
obj::ObjKind,
split::split_obj,
};
#[derive(FromArgs, PartialEq, Debug)] #[derive(FromArgs, PartialEq, Debug)]
/// Commands for processing ELF files. /// Commands for processing ELF files.
@ -29,6 +34,7 @@ pub struct Args {
enum SubCommand { enum SubCommand {
Disasm(DisasmArgs), Disasm(DisasmArgs),
Fixup(FixupArgs), Fixup(FixupArgs),
Split(SplitArgs),
} }
#[derive(FromArgs, PartialEq, Eq, Debug)] #[derive(FromArgs, PartialEq, Eq, Debug)]
@ -39,8 +45,8 @@ pub struct DisasmArgs {
/// input file /// input file
elf_file: PathBuf, elf_file: PathBuf,
#[argh(positional)] #[argh(positional)]
/// output directory /// output file (.o) or directory (.elf)
out_dir: PathBuf, out: PathBuf,
} }
#[derive(FromArgs, PartialEq, Eq, Debug)] #[derive(FromArgs, PartialEq, Eq, Debug)]
@ -55,55 +61,132 @@ pub struct FixupArgs {
out_file: PathBuf, out_file: PathBuf,
} }
#[derive(FromArgs, PartialEq, Eq, Debug)]
/// Splits an executable ELF into relocatable objects.
#[argh(subcommand, name = "split")]
pub struct SplitArgs {
#[argh(positional)]
/// input file
in_file: PathBuf,
#[argh(positional)]
/// output directory
out_dir: PathBuf,
}
pub fn run(args: Args) -> Result<()> { pub fn run(args: Args) -> Result<()> {
match args.command { match args.command {
SubCommand::Disasm(c_args) => disasm(c_args), SubCommand::Disasm(c_args) => disasm(c_args),
SubCommand::Fixup(c_args) => fixup(c_args), SubCommand::Fixup(c_args) => fixup(c_args),
SubCommand::Split(c_args) => split(c_args),
} }
} }
fn disasm(args: DisasmArgs) -> Result<()> { fn disasm(args: DisasmArgs) -> Result<()> {
let obj = process_elf(&args.elf_file)?; let obj = process_elf(&args.elf_file)?;
write_asm(&args.out_dir, &obj)?; match obj.kind {
for unit in obj.link_order { ObjKind::Executable => {
let name = format!("$(OBJ_DIR)/asm/{}", file_name_from_unit(&unit)); let split_objs = split_obj(&obj)?;
let asm_dir = args.out.join("asm");
let include_dir = args.out.join("include");
DirBuilder::new().recursive(true).create(&include_dir)?;
fs::write(&include_dir.join("macros.inc"), include_bytes!("../../assets/macros.inc"))?;
for (unit, split_obj) in obj.link_order.iter().zip(&split_objs) {
let out_path = asm_dir.join(file_name_from_unit(unit, ".s"));
if let Some(parent) = out_path.parent() {
DirBuilder::new().recursive(true).create(parent)?;
}
let mut w = BufWriter::new(File::create(out_path)?);
write_asm(&mut w, split_obj)?;
let name = format!("$(OBJ_DIR)/asm/{}", file_name_from_unit(unit, ".o"));
println!(" {name: <70}\\"); println!(" {name: <70}\\");
} }
}
ObjKind::Relocatable => {
if let Some(parent) = args.out.parent() {
DirBuilder::new().recursive(true).create(parent)?;
}
let mut w = BufWriter::new(File::create(args.out)?);
write_asm(&mut w, &obj)?;
}
}
Ok(()) Ok(())
} }
fn file_name_from_unit(str: &str) -> String { fn split(args: SplitArgs) -> Result<()> {
let obj = process_elf(&args.in_file)?;
let mut file_map = HashMap::<String, object::write::Object>::new();
let split_objs = split_obj(&obj)?;
for (unit, split_obj) in obj.link_order.iter().zip(&split_objs) {
let out_obj = write_elf(split_obj)?;
match file_map.entry(unit.clone()) {
hash_map::Entry::Occupied(_) => {
return Err(Error::msg(format!("Duplicate file {unit}")));
}
hash_map::Entry::Vacant(e) => e.insert(out_obj),
};
}
let mut rsp_file = BufWriter::new(File::create("rsp")?);
for unit in &obj.link_order {
let object = file_map
.get(unit)
.ok_or_else(|| Error::msg(format!("Failed to find object file for unit '{unit}'")))?;
let out_path = args.out_dir.join(file_name_from_unit(unit, ".o"));
writeln!(rsp_file, "{}", out_path.to_string_lossy())?;
if let Some(parent) = out_path.parent() {
DirBuilder::new().recursive(true).create(parent)?;
}
let mut file = BufWriter::new(File::create(out_path)?);
object.write_stream(&mut file).map_err(|e| Error::msg(format!("{e:?}")))?;
file.flush()?;
}
rsp_file.flush()?;
Ok(())
}
fn file_name_from_unit(str: &str, suffix: &str) -> String {
let str = str.strip_suffix(ASM_SUFFIX).unwrap_or(str);
let str = str.strip_prefix("C:").unwrap_or(str); let str = str.strip_prefix("C:").unwrap_or(str);
let str = str let str = str
.strip_suffix(".c") .strip_suffix(".c")
.or_else(|| str.strip_suffix(".cp")) .or_else(|| str.strip_suffix(".cp"))
.or_else(|| str.strip_suffix(".cpp")) .or_else(|| str.strip_suffix(".cpp"))
.or_else(|| str.strip_suffix(".s")) .or_else(|| str.strip_suffix(".s"))
.or_else(|| str.strip_suffix(".o"))
.unwrap_or(str); .unwrap_or(str);
let str = str.replace('\\', "/"); let str = str.replace('\\', "/");
format!("{}.o", str.strip_prefix('/').unwrap_or(&str)) let str = str.strip_prefix('/').unwrap_or(&str);
format!("{str}{suffix}")
} }
const ASM_SUFFIX: &[u8] = " (asm)".as_bytes(); const ASM_SUFFIX: &str = " (asm)";
fn fixup(args: FixupArgs) -> Result<()> { fn fixup(args: FixupArgs) -> Result<()> {
let in_buf = fs::read(&args.in_file).context("Failed to open input file")?; let in_buf = fs::read(&args.in_file).with_context(|| {
format!("Failed to open input file: '{}'", args.in_file.to_string_lossy())
})?;
let in_file = object::read::File::parse(&*in_buf).context("Failed to parse input ELF")?; let in_file = object::read::File::parse(&*in_buf).context("Failed to parse input ELF")?;
let mut out_file = let mut out_file =
object::write::Object::new(in_file.format(), in_file.architecture(), in_file.endianness()); object::write::Object::new(in_file.format(), in_file.architecture(), in_file.endianness());
// Write file symbol(s) first // Write file symbol first
let mut file_symbol_found = false; let mut file_symbol_found = false;
for symbol in in_file.symbols() { for symbol in in_file.symbols() {
if symbol.kind() != SymbolKind::File { if symbol.kind() != SymbolKind::File {
continue; continue;
} }
let mut out_symbol = to_write_symbol(&symbol, &[])?; let mut out_symbol = to_write_symbol(&symbol, &[])?;
out_symbol.name.append(&mut ASM_SUFFIX.to_vec()); out_symbol.name.append(&mut ASM_SUFFIX.as_bytes().to_vec());
out_file.add_symbol(out_symbol); out_file.add_symbol(out_symbol);
file_symbol_found = true; file_symbol_found = true;
break; break;
} }
// Create a file symbol if not found
if !file_symbol_found { if !file_symbol_found {
let file_name = args.in_file.file_name().ok_or_else(|| { let file_name = args.in_file.file_name().ok_or_else(|| {
Error::msg(format!("'{}' is not a file path", args.in_file.to_string_lossy())) Error::msg(format!("'{}' is not a file path", args.in_file.to_string_lossy()))
@ -112,7 +195,7 @@ fn fixup(args: FixupArgs) -> Result<()> {
Error::msg(format!("'{}' is not valid UTF-8", file_name.to_string_lossy())) Error::msg(format!("'{}' is not valid UTF-8", file_name.to_string_lossy()))
})?; })?;
let mut name_bytes = file_name.as_bytes().to_vec(); let mut name_bytes = file_name.as_bytes().to_vec();
name_bytes.append(&mut ASM_SUFFIX.to_vec()); name_bytes.append(&mut ASM_SUFFIX.as_bytes().to_vec());
out_file.add_symbol(object::write::Symbol { out_file.add_symbol(object::write::Symbol {
name: name_bytes, name: name_bytes,
value: 0, value: 0,
@ -163,11 +246,11 @@ fn fixup(args: FixupArgs) -> Result<()> {
symbol_ids.push(Some(symbol_id)); symbol_ids.push(Some(symbol_id));
if symbol.size() != 0 { if symbol.size() != 0 {
if let Some(section_id) = section_id { if let Some(section_id) = section_id {
let map = match addr_to_sym.entry(section_id) { match addr_to_sym.entry(section_id) {
Entry::Vacant(e) => e.insert(BTreeMap::new()), btree_map::Entry::Vacant(e) => e.insert(BTreeMap::new()),
Entry::Occupied(e) => e.into_mut(), btree_map::Entry::Occupied(e) => e.into_mut(),
}; }
map.insert(symbol.address() as u32, symbol_id); .insert(symbol.address() as u32, symbol_id);
} }
} }
} }
@ -179,53 +262,42 @@ fn fixup(args: FixupArgs) -> Result<()> {
None => continue, None => continue,
}; };
for (addr, reloc) in section.relocations() { for (addr, reloc) in section.relocations() {
let mut symbol = match reloc.target() { let mut target_symbol_id = match reloc.target() {
RelocationTarget::Symbol(idx) => match symbol_ids[idx.0] { RelocationTarget::Symbol(idx) => match symbol_ids[idx.0] {
Some(id) => id, Some(id) => Ok(id),
None => { None => {
let in_symbol = in_file.symbol_by_index(idx)?; let in_symbol = in_file.symbol_by_index(idx)?;
match in_symbol.kind() { match in_symbol.kind() {
SymbolKind::Section => { SymbolKind::Section => in_symbol
let section_idx = match in_symbol.section_index() { .section_index()
Some(id) => id, .ok_or_else(|| Error::msg("Section symbol without section"))
None => { .and_then(|section_idx| {
return Err(Error::msg("Missing section for relocation")) section_ids[section_idx.0].ok_or_else(|| {
} Error::msg("Relocation against stripped section")
}; })
let section_id = match section_ids[section_idx.0] { })
Some(id) => id, .map(|section_idx| out_file.section_symbol(section_idx)),
None => { _ => Err(Error::msg("Missing symbol for relocation")),
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) => { RelocationTarget::Section(section_idx) => section_ids[section_idx.0]
let section_id = match section_ids[idx.0] { .ok_or_else(|| Error::msg("Relocation against stripped section"))
Some(id) => id, .map(|section_id| out_file.section_symbol(section_id)),
None => return Err(Error::msg("Missing section for relocation")), target => Err(Error::msg(format!("Invalid relocation target '{target:?}'"))),
}; }?;
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 // Attempt to replace section symbols with direct symbol references
let target_sym = out_file.symbol(symbol); let mut addend = reloc.addend();
let target_sym = out_file.symbol(target_symbol_id);
if target_sym.kind == SymbolKind::Section { if target_sym.kind == SymbolKind::Section {
if let Some(new_symbol_id) = target_sym if let Some(&new_symbol_id) = target_sym
.section .section
.id() .id()
.and_then(|id| addr_to_sym.get(&id)) .and_then(|id| addr_to_sym.get(&id))
.and_then(|map| map.get(&(addend as u32))) .and_then(|map| map.get(&(addend as u32)))
{ {
symbol = *new_symbol_id; target_symbol_id = new_symbol_id;
addend = 0; addend = 0;
} }
} }
@ -242,14 +314,15 @@ fn fixup(args: FixupArgs) -> Result<()> {
size: reloc.size(), size: reloc.size(),
kind, kind,
encoding: reloc.encoding(), encoding: reloc.encoding(),
symbol, symbol: target_symbol_id,
addend, addend,
})?; })?;
} }
} }
let mut out = let mut out = BufWriter::new(File::create(&args.out_file).with_context(|| {
BufWriter::new(File::create(&args.out_file).context("Failed to create out file")?); format!("Failed to create output file: '{}'", args.out_file.to_string_lossy())
})?);
out_file.write_stream(&mut out).map_err(|e| Error::msg(format!("{e:?}")))?; out_file.write_stream(&mut out).map_err(|e| Error::msg(format!("{e:?}")))?;
out.flush()?; out.flush()?;
Ok(()) Ok(())
@ -259,24 +332,25 @@ fn to_write_symbol_section(
section: SymbolSection, section: SymbolSection,
section_ids: &[Option<SectionId>], section_ids: &[Option<SectionId>],
) -> Result<object::write::SymbolSection> { ) -> Result<object::write::SymbolSection> {
Ok(match section { match section {
SymbolSection::None => object::write::SymbolSection::None, SymbolSection::None => Ok(object::write::SymbolSection::None),
SymbolSection::Absolute => object::write::SymbolSection::Absolute, SymbolSection::Absolute => Ok(object::write::SymbolSection::Absolute),
SymbolSection::Common => object::write::SymbolSection::Common, SymbolSection::Common => Ok(object::write::SymbolSection::Common),
SymbolSection::Section(idx) => match section_ids.get(idx.0).and_then(|opt| *opt) { SymbolSection::Section(idx) => section_ids
Some(section_id) => object::write::SymbolSection::Section(section_id), .get(idx.0)
None => return Err(Error::msg("Missing symbol section")), .and_then(|&opt| opt)
}, .map(object::write::SymbolSection::Section)
_ => object::write::SymbolSection::Undefined, .ok_or_else(|| Error::msg("Missing symbol section")),
}) _ => Ok(object::write::SymbolSection::Undefined),
}
} }
fn to_write_symbol_flags(flags: SymbolFlags<SectionIndex>) -> Result<SymbolFlags<SectionId>> { fn to_write_symbol_flags(flags: SymbolFlags<SectionIndex>) -> Result<SymbolFlags<SectionId>> {
Ok(match flags { match flags {
SymbolFlags::Elf { st_info, st_other } => SymbolFlags::Elf { st_info, st_other }, SymbolFlags::Elf { st_info, st_other } => Ok(SymbolFlags::Elf { st_info, st_other }),
SymbolFlags::None => SymbolFlags::None, SymbolFlags::None => Ok(SymbolFlags::None),
_ => return Err(Error::msg("Unexpected symbol flags")), _ => Err(Error::msg("Unexpected symbol flags")),
}) }
} }
fn to_write_symbol( fn to_write_symbol(

View File

@ -1,18 +1,15 @@
use std::{ use std::{
cmp::{min, Ordering}, cmp::{min, Ordering},
collections::{btree_map, hash_map::Entry, BTreeMap, HashMap}, collections::{btree_map, BTreeMap},
fs, io::Write,
fs::{DirBuilder, File},
io::{BufWriter, Write},
path::Path,
}; };
use anyhow::{Error, Result}; use anyhow::{Error, Result};
use ppc750cl::{disasm_iter, Argument, Ins, Opcode}; use ppc750cl::{disasm_iter, Argument, Ins, Opcode};
use crate::util::obj::{ use crate::util::obj::{
ObjInfo, ObjReloc, ObjRelocKind, ObjSection, ObjSectionKind, ObjSymbol, ObjSymbolFlags, nested_push, ObjInfo, ObjReloc, ObjRelocKind, ObjSection, ObjSectionKind, ObjSymbol,
ObjSymbolKind, ObjSymbolFlags, ObjSymbolKind,
}; };
#[derive(Debug, Copy, Clone, Eq, PartialEq)] #[derive(Debug, Copy, Clone, Eq, PartialEq)]
@ -28,140 +25,110 @@ struct SymbolEntry {
kind: SymbolEntryKind, kind: SymbolEntryKind,
} }
pub fn write_asm<P: AsRef<Path>>(path: P, obj: &ObjInfo) -> Result<()> { pub fn write_asm<W: Write>(w: &mut W, obj: &ObjInfo) -> Result<()> {
let mut file_map = HashMap::<String, BufWriter<File>>::new();
let asm_dir = path.as_ref().join("asm");
let include_dir = path.as_ref().join("include");
DirBuilder::new().recursive(true).create(&include_dir)?;
fs::write(&include_dir.join("macros.inc"), include_bytes!("../../assets/macros.inc"))?;
for unit in &obj.link_order {
let w = match file_map.entry(unit.clone()) {
Entry::Occupied(_) => {
return Err(Error::msg(format!("Duplicate file {unit}")));
}
Entry::Vacant(e) => {
let file_path = asm_dir.join(file_name_from_unit(unit));
if let Some(parent) = file_path.parent() {
DirBuilder::new().recursive(true).create(parent)?;
}
e.insert(BufWriter::new(File::create(file_path)?))
}
};
writeln!(w, ".include \"macros.inc\"")?; writeln!(w, ".include \"macros.inc\"")?;
writeln!(w, ".file \"{}\"", unit.replace('\\', "\\\\"))?; if !obj.name.is_empty() {
writeln!(w, ".file \"{}\"", obj.name.replace('\\', "\\\\"))?;
} }
let mut symbols = Vec::<ObjSymbol>::new(); // We'll append generated symbols to the end
let mut addr_sym = BTreeMap::<u32, Vec<SymbolEntry>>::new(); let mut symbols: Vec<ObjSymbol> = obj.symbols.clone();
for section in &obj.sections { let mut section_entries: Vec<BTreeMap<u32, Vec<SymbolEntry>>> = vec![];
for symbol in &section.symbols { let mut section_relocations: Vec<BTreeMap<u32, ObjReloc>> = vec![];
let symbol_index = symbols.len(); for (section_idx, section) in obj.sections.iter().enumerate() {
symbols.push(symbol.clone()); // Build symbol start/end entries
let symbol_start = symbol.address as u32; let mut entries = BTreeMap::<u32, Vec<SymbolEntry>>::new();
let symbol_end = (symbol.address + symbol.size) as u32; for (symbol_index, symbol) in obj.symbols_for_section(section_idx) {
if symbol.size > 0 { if symbol.kind == ObjSymbolKind::Section {
match addr_sym.entry(symbol_start) {
btree_map::Entry::Occupied(mut e) => {
e.get_mut().push(SymbolEntry {
index: symbol_index,
kind: SymbolEntryKind::Start,
});
}
btree_map::Entry::Vacant(e) => {
e.insert(vec![SymbolEntry {
index: symbol_index,
kind: SymbolEntryKind::Start,
}]);
}
}
match addr_sym.entry(symbol_end) {
btree_map::Entry::Occupied(mut e) => {
// Always push first
e.get_mut().insert(0, SymbolEntry {
index: symbol_index,
kind: SymbolEntryKind::End,
});
}
btree_map::Entry::Vacant(e) => {
e.insert(vec![SymbolEntry {
index: symbol_index,
kind: SymbolEntryKind::End,
}]);
}
}
} else {
match addr_sym.entry(symbol_start) {
btree_map::Entry::Occupied(mut e) => {
e.get_mut().push(SymbolEntry {
index: symbol_index,
kind: SymbolEntryKind::Label,
});
}
btree_map::Entry::Vacant(e) => {
e.insert(vec![SymbolEntry {
index: symbol_index,
kind: SymbolEntryKind::Label,
}]);
}
}
}
}
// Generate labels for .text relocations
for reloc in &section.relocations {
let target_section = match &reloc.target_section {
Some(v) => v,
None => continue,
};
let section = match obj.sections.iter().find(|s| &s.name == target_section) {
Some(v) => v,
None => continue,
};
match section.kind {
ObjSectionKind::Code => {}
_ => continue,
}
if reloc.target.addend == 0 {
continue; continue;
} }
let address = (reloc.target.address as i64 + reloc.target.addend) as u64; nested_push(&mut entries, symbol.address as u32, SymbolEntry {
let vec = match addr_sym.entry(address as u32) { index: symbol_index,
btree_map::Entry::Occupied(e) => e.into_mut(), kind: SymbolEntryKind::Start,
btree_map::Entry::Vacant(e) => e.insert(vec![]), });
}; if symbol.size > 0 {
if !vec nested_push(&mut entries, (symbol.address + symbol.size) as u32, SymbolEntry {
.iter() index: symbol_index,
.any(|e| e.kind == SymbolEntryKind::Label || e.kind == SymbolEntryKind::Start) kind: SymbolEntryKind::End,
{
let symbol_index = symbols.len();
symbols.push(ObjSymbol {
name: format!(".L_{address:8X}"),
demangled_name: None,
address,
section_address: address - section.address,
size: 0,
size_known: true,
flags: Default::default(),
addend: 0,
kind: ObjSymbolKind::Unknown,
}); });
vec.push(SymbolEntry { index: symbol_index, kind: SymbolEntryKind::Label });
} }
} }
let mut relocations = section.build_relocation_map()?;
// Generate local jump labels // Generate local jump labels
if section.kind == ObjSectionKind::Code {
for ins in disasm_iter(&section.data, section.address as u32) { for ins in disasm_iter(&section.data, section.address as u32) {
if let Some(address) = ins.branch_dest() { if let Some(address) = ins.branch_dest() {
let section = if ins.field_AA()
match obj.sections.iter().find(|s| { || (address as u64) < section.address
s.address <= address as u64 && (s.address + s.size) > address as u64 || (address as u64) >= section.address + section.size
}) { {
Some(s) => s, continue;
}
// Replace section-relative jump relocations (generated by GCC)
// These aren't possible to express accurately in GNU assembler
if matches!(relocations.get(&ins.addr), Some(reloc) if reloc.addend == 0) {
continue;
}
let vec = match entries.entry(address) {
btree_map::Entry::Occupied(e) => e.into_mut(),
btree_map::Entry::Vacant(e) => e.insert(vec![]),
};
let mut target_symbol_idx = vec
.iter()
.find(|e| e.kind == SymbolEntryKind::Label)
.or_else(|| vec.iter().find(|e| e.kind == SymbolEntryKind::Start))
.map(|e| e.index);
if target_symbol_idx.is_none() {
let display_address = address as u64 + section.original_address;
let symbol_idx = symbols.len();
symbols.push(ObjSymbol {
name: format!(".L_{display_address:08X}"),
address: display_address,
section: Some(section_idx),
size_known: true,
..Default::default()
});
vec.push(SymbolEntry { index: symbol_idx, kind: SymbolEntryKind::Label });
target_symbol_idx = Some(symbol_idx);
}
if let Some(symbol_idx) = target_symbol_idx {
relocations.insert(ins.addr, ObjReloc {
kind: ObjRelocKind::PpcRel24,
address: ins.addr as u64,
target_symbol: symbol_idx,
addend: 0,
});
}
}
}
}
section_entries.push(entries);
section_relocations.push(relocations);
}
// Generate labels for jump tables & relative data relocations
for section in &obj.sections {
if !matches!(section.kind, ObjSectionKind::Data | ObjSectionKind::ReadOnlyData) {
continue;
}
for reloc in &section.relocations {
if reloc.addend == 0 {
continue;
}
let target = &obj.symbols[reloc.target_symbol];
let target_section_idx = match target.section {
Some(v) => v,
None => continue, None => continue,
}; };
let vec = match addr_sym.entry(address) { let target_section = &obj.sections[target_section_idx];
let address = (target.address as i64 + reloc.addend) as u64;
let vec = match section_entries[target_section_idx].entry(address as u32) {
btree_map::Entry::Occupied(e) => e.into_mut(), btree_map::Entry::Occupied(e) => e.into_mut(),
btree_map::Entry::Vacant(e) => e.insert(vec![]), btree_map::Entry::Vacant(e) => e.insert(vec![]),
}; };
@ -169,173 +136,100 @@ pub fn write_asm<P: AsRef<Path>>(path: P, obj: &ObjInfo) -> Result<()> {
.iter() .iter()
.any(|e| e.kind == SymbolEntryKind::Label || e.kind == SymbolEntryKind::Start) .any(|e| e.kind == SymbolEntryKind::Label || e.kind == SymbolEntryKind::Start)
{ {
let symbol_index = symbols.len(); let display_address = address + target_section.original_address;
let symbol_idx = symbols.len();
symbols.push(ObjSymbol { symbols.push(ObjSymbol {
name: format!(".L_{address:8X}"), name: format!(".L_{display_address:08X}"),
demangled_name: None, address: display_address,
address: address as u64, section: Some(target_section_idx),
section_address: address as u64 - section.address,
size: 0,
size_known: true, size_known: true,
flags: Default::default(), ..Default::default()
addend: 0,
kind: ObjSymbolKind::Unknown,
}); });
vec.push(SymbolEntry { index: symbol_index, kind: SymbolEntryKind::Label }); vec.push(SymbolEntry { index: symbol_idx, kind: SymbolEntryKind::Label });
}
} }
} }
} }
for section in &obj.sections { for section in &obj.sections {
log::info!( let entries = &section_entries[section.index];
"Writing section {} ({:#10X} - {:#10X})", let relocations = &section_relocations[section.index];
section.name,
section.address,
section.address + section.size
);
let mut current_address = section.address as u32; let mut current_address = section.address as u32;
let section_end = (section.address + section.size) as u32; let section_end = (section.address + section.size) as u32;
let mut file_iter = obj.splits.range(current_address..).peekable(); let subsection =
obj.sections.iter().take(section.index).filter(|s| s.name == section.name).count();
let mut relocations = BTreeMap::<u32, ObjReloc>::new();
for reloc in &section.relocations {
let address = reloc.address as u32;
match relocations.entry(address) {
btree_map::Entry::Vacant(e) => {
e.insert(reloc.clone());
}
btree_map::Entry::Occupied(_) => {
return Err(Error::msg(format!("Duplicate relocation @ {address:#10X}")));
}
}
}
let mut subsection = 0;
let mut current_unit = String::new();
loop { loop {
if current_address >= section_end { if current_address >= section_end {
break; break;
} }
let (file_addr, unit) = match file_iter.next() { write_section_header(w, section, subsection, current_address, section_end)?;
Some((addr, unit)) => (*addr, unit),
None => return Err(Error::msg("No file found")),
};
if file_addr > current_address {
return Err(Error::msg(format!(
"Gap in files: {} @ {:#10X}, {} @ {:#10X}",
section.name, section.address, unit, file_addr
)));
}
let mut file_end = section_end;
if let Some((next_addr, _)) = file_iter.peek() {
file_end = min(**next_addr, section_end);
}
if unit == &current_unit {
subsection += 1;
} else {
current_unit = unit.clone();
subsection = 0;
}
let w = write_section_header(
&mut file_map,
unit,
section,
subsection,
current_address,
file_end,
)?;
match section.kind { match section.kind {
ObjSectionKind::Code | ObjSectionKind::Data => { ObjSectionKind::Code | ObjSectionKind::Data | ObjSectionKind::ReadOnlyData => {
write_data( write_data(
w, w,
&symbols, &symbols,
&addr_sym, entries,
&relocations, relocations,
section, section,
current_address, current_address,
file_end, section_end,
&section_entries,
)?; )?;
} }
ObjSectionKind::Bss => { ObjSectionKind::Bss => {
write_bss(w, &symbols, &addr_sym, current_address, file_end)?; write_bss(w, &symbols, entries, current_address, section_end)?;
}
}
current_address = file_end;
} }
} }
for (_, mut w) in file_map { // Write end of symbols
w.flush()?; if let Some(entries) = entries.get(&section_end) {
for entry in entries {
if entry.kind != SymbolEntryKind::End {
continue;
} }
write_symbol_entry(w, &symbols, entry)?;
}
}
current_address = section_end;
}
}
w.flush()?;
Ok(()) Ok(())
} }
fn write_code_chunk( fn write_code_chunk<W: Write>(
w: &mut BufWriter<File>, w: &mut W,
symbols: &[ObjSymbol], symbols: &[ObjSymbol],
sym_map: &BTreeMap<u32, Vec<SymbolEntry>>, _entries: &BTreeMap<u32, Vec<SymbolEntry>>,
relocations: &BTreeMap<u32, ObjReloc>, relocations: &BTreeMap<u32, ObjReloc>,
section: &ObjSection, section: &ObjSection,
address: u32, address: u32,
data: &[u8], data: &[u8],
) -> Result<()> { ) -> Result<()> {
for ins in disasm_iter(data, address) { for ins in disasm_iter(data, address) {
let mut reloc = relocations.get(&ins.addr); let reloc = relocations.get(&ins.addr);
let mut generated_reloc: Option<ObjReloc> = None;
// HACK: GCC-built objects generate section-relative jump relocations,
// which aren't always possible to express in GNU assembler accurately,
// specifically when dealing with multiple sections with the same name.
// Use a (hacky) heuristic to clear them so we generate a local label jump below.
if let Some(rel) = reloc {
if rel.target.addend != 0
&& matches!(rel.kind, ObjRelocKind::PpcRel14 | ObjRelocKind::PpcRel24)
{
reloc = None;
}
}
// If this is a branch instruction, automatically "relocate" to a label.
// Local branch labels are generated above.
if reloc.is_none() {
if let Some(symbol_entry) =
ins.branch_dest().and_then(|dest| sym_map.get(&dest)).and_then(|entries| {
entries
.iter()
.find(|e| e.kind == SymbolEntryKind::Label)
.or_else(|| entries.iter().find(|e| e.kind == SymbolEntryKind::Start))
})
{
let symbol = &symbols[symbol_entry.index];
generated_reloc = Some(ObjReloc {
kind: ObjRelocKind::Absolute,
address: ins.addr as u64,
target: symbol.clone(),
target_section: None,
});
}
}
let file_offset = section.file_offset + (ins.addr as u64 - section.address); let file_offset = section.file_offset + (ins.addr as u64 - section.address);
write_ins(w, ins, reloc.or(generated_reloc.as_ref()), file_offset)?; write_ins(w, symbols, ins, reloc, file_offset, section.original_address)?;
} }
Ok(()) Ok(())
} }
fn write_ins( fn write_ins<W: Write>(
w: &mut BufWriter<File>, w: &mut W,
symbols: &[ObjSymbol],
ins: Ins, ins: Ins,
reloc: Option<&ObjReloc>, reloc: Option<&ObjReloc>,
file_offset: u64, file_offset: u64,
section_address: u64,
) -> Result<()> { ) -> Result<()> {
write!( write!(
w, w,
"/* {:08X} {:08X} {:02X} {:02X} {:02X} {:02X} */\t", "/* {:08X} {:08X} {:02X} {:02X} {:02X} {:02X} */\t",
ins.addr, ins.addr as u64 + section_address,
file_offset, file_offset,
(ins.code >> 24) & 0xFF, (ins.code >> 24) & 0xFF,
(ins.code >> 16) & 0xFF, (ins.code >> 16) & 0xFF,
@ -364,14 +258,14 @@ fn write_ins(
match arg { match arg {
Argument::Uimm(_) | Argument::Simm(_) | Argument::BranchDest(_) => { Argument::Uimm(_) | Argument::Simm(_) | Argument::BranchDest(_) => {
if let Some(reloc) = reloc { if let Some(reloc) = reloc {
write_reloc(w, reloc)?; write_reloc(w, symbols, reloc)?;
} else { } else {
write!(w, "{arg}")?; write!(w, "{arg}")?;
} }
} }
Argument::Offset(_) => { Argument::Offset(_) => {
if let Some(reloc) = reloc { if let Some(reloc) = reloc {
write_reloc(w, reloc)?; write_reloc(w, symbols, reloc)?;
} else { } else {
write!(w, "{arg}")?; write!(w, "{arg}")?;
} }
@ -393,8 +287,8 @@ fn write_ins(
Ok(()) Ok(())
} }
fn write_reloc(w: &mut BufWriter<File>, reloc: &ObjReloc) -> Result<()> { fn write_reloc<W: Write>(w: &mut W, symbols: &[ObjSymbol], reloc: &ObjReloc) -> Result<()> {
write_symbol(w, &reloc.target)?; write_reloc_symbol(w, symbols, reloc)?;
match reloc.kind { match reloc.kind {
ObjRelocKind::Absolute | ObjRelocKind::PpcRel24 | ObjRelocKind::PpcRel14 => { ObjRelocKind::Absolute | ObjRelocKind::PpcRel24 | ObjRelocKind::PpcRel14 => {
// pass // pass
@ -415,32 +309,27 @@ fn write_reloc(w: &mut BufWriter<File>, reloc: &ObjReloc) -> Result<()> {
Ok(()) Ok(())
} }
fn write_symbol_entry( fn write_symbol_entry<W: Write>(
w: &mut BufWriter<File>, w: &mut W,
symbols: &[ObjSymbol], symbols: &[ObjSymbol],
entry: &SymbolEntry, entry: &SymbolEntry,
) -> Result<()> { ) -> Result<()> {
let symbol = &symbols[entry.index]; let symbol = &symbols[entry.index];
assert_eq!(symbol.addend, 0);
// Skip writing certain symbols // Skip writing certain symbols
if is_skip_symbol(&symbol.name) { if symbol.kind == ObjSymbolKind::Section {
return Ok(()); return Ok(());
} }
// Comment out linker-generated symbols
let mut start_newline = true;
if entry.kind == SymbolEntryKind::Start && is_linker_symbol(&symbol.name) {
writeln!(w, "\n/* Linker generated")?;
start_newline = false;
}
let symbol_kind = match symbol.kind { let symbol_kind = match symbol.kind {
ObjSymbolKind::Function => "fn", ObjSymbolKind::Function => "fn",
ObjSymbolKind::Object => "obj", ObjSymbolKind::Object => "obj",
ObjSymbolKind::Unknown => "sym", ObjSymbolKind::Unknown => "sym",
ObjSymbolKind::Section => {
return Err(Error::msg(format!("Attempted to write section symbol: {symbol:?}")))
}
}; };
let visibility = if symbol.flags.0.contains(ObjSymbolFlags::Weak) { let scope = if symbol.flags.0.contains(ObjSymbolFlags::Weak) {
"weak" "weak"
} else if symbol.flags.0.contains(ObjSymbolFlags::Global) { } else if symbol.flags.0.contains(ObjSymbolFlags::Global) {
"global" "global"
@ -456,11 +345,11 @@ fn write_symbol_entry(
} else { } else {
write!(w, ".sym ")?; write!(w, ".sym ")?;
write_symbol_name(w, &symbol.name)?; write_symbol_name(w, &symbol.name)?;
writeln!(w, ", {visibility}")?; writeln!(w, ", {scope}")?;
} }
} }
SymbolEntryKind::Start => { SymbolEntryKind::Start => {
if start_newline { if symbol.kind != ObjSymbolKind::Unknown {
writeln!(w)?; writeln!(w)?;
} }
if let Some(name) = &symbol.demangled_name { if let Some(name) = &symbol.demangled_name {
@ -468,7 +357,7 @@ fn write_symbol_entry(
} }
write!(w, ".{symbol_kind} ")?; write!(w, ".{symbol_kind} ")?;
write_symbol_name(w, &symbol.name)?; write_symbol_name(w, &symbol.name)?;
writeln!(w, ", {visibility}")?; writeln!(w, ", {scope}")?;
} }
SymbolEntryKind::End => { SymbolEntryKind::End => {
write!(w, ".end{symbol_kind} ")?; write!(w, ".end{symbol_kind} ")?;
@ -477,34 +366,39 @@ fn write_symbol_entry(
} }
} }
if entry.kind == SymbolEntryKind::End && is_linker_symbol(&symbol.name) { if matches!(entry.kind, SymbolEntryKind::Start | SymbolEntryKind::Label)
writeln!(w, "*/")?; && symbol.flags.0.contains(ObjSymbolFlags::Hidden)
{
write!(w, ".hidden ")?;
write_symbol_name(w, &symbol.name)?;
writeln!(w)?;
} }
Ok(()) Ok(())
} }
fn write_data( fn write_data<W: Write>(
w: &mut BufWriter<File>, w: &mut W,
symbols: &[ObjSymbol], symbols: &[ObjSymbol],
sym_map: &BTreeMap<u32, Vec<SymbolEntry>>, entries: &BTreeMap<u32, Vec<SymbolEntry>>,
relocations: &BTreeMap<u32, ObjReloc>, relocations: &BTreeMap<u32, ObjReloc>,
section: &ObjSection, section: &ObjSection,
start: u32, start: u32,
end: u32, end: u32,
section_entries: &[BTreeMap<u32, Vec<SymbolEntry>>],
) -> Result<()> { ) -> Result<()> {
let mut sym_iter = sym_map.range(start..end); let mut entry_iter = entries.range(start..end);
let mut reloc_iter = relocations.range(start..end); let mut reloc_iter = relocations.range(start..end);
let mut current_address = start; let mut current_address = start;
let mut current_symbol_kind = ObjSymbolKind::Unknown; let mut current_symbol_kind = ObjSymbolKind::Unknown;
let mut sym = sym_iter.next(); let mut entry = entry_iter.next();
let mut reloc = reloc_iter.next(); let mut reloc = reloc_iter.next();
let mut begin = true; let mut begin = true;
loop { loop {
if current_address == end { if current_address == end {
break; break;
} }
if let Some((sym_addr, vec)) = sym { if let Some((sym_addr, vec)) = entry {
if current_address == *sym_addr { if current_address == *sym_addr {
for entry in vec { for entry in vec {
if entry.kind == SymbolEntryKind::End && begin { if entry.kind == SymbolEntryKind::End && begin {
@ -513,7 +407,7 @@ fn write_data(
write_symbol_entry(w, symbols, entry)?; write_symbol_entry(w, symbols, entry)?;
} }
current_symbol_kind = find_symbol_kind(current_symbol_kind, symbols, vec)?; current_symbol_kind = find_symbol_kind(current_symbol_kind, symbols, vec)?;
sym = sym_iter.next(); entry = entry_iter.next();
} }
} }
begin = false; begin = false;
@ -521,7 +415,9 @@ fn write_data(
let symbol_kind = if current_symbol_kind == ObjSymbolKind::Unknown { let symbol_kind = if current_symbol_kind == ObjSymbolKind::Unknown {
match section.kind { match section.kind {
ObjSectionKind::Code => ObjSymbolKind::Function, ObjSectionKind::Code => ObjSymbolKind::Function,
ObjSectionKind::Data | ObjSectionKind::Bss => ObjSymbolKind::Object, ObjSectionKind::Data | ObjSectionKind::ReadOnlyData | ObjSectionKind::Bss => {
ObjSymbolKind::Object
}
} }
} else { } else {
current_symbol_kind current_symbol_kind
@ -531,18 +427,19 @@ fn write_data(
reloc = reloc_iter.next(); reloc = reloc_iter.next();
match symbol_kind { match symbol_kind {
ObjSymbolKind::Object => { ObjSymbolKind::Object => {
current_address = write_data_reloc(w, symbols, sym_map, r)?; current_address =
write_data_reloc(w, symbols, entries, r, section_entries)?;
continue; continue;
} }
ObjSymbolKind::Function => { ObjSymbolKind::Function => {
// handled in write_code_chunk // handled in write_code_chunk
} }
ObjSymbolKind::Unknown => unreachable!(), ObjSymbolKind::Unknown | ObjSymbolKind::Section => unreachable!(),
} }
} }
} }
let until = match (sym, reloc) { let until = match (entry, reloc) {
(Some((sym_addr, _)), Some((reloc_addr, _))) => min(*reloc_addr, *sym_addr), (Some((sym_addr, _)), Some((reloc_addr, _))) => min(*reloc_addr, *sym_addr),
(Some((addr, _)), None) | (None, Some((addr, _))) => *addr, (Some((addr, _)), None) | (None, Some((addr, _))) => *addr,
(None, None) => end, (None, None) => end,
@ -552,27 +449,18 @@ fn write_data(
if symbol_kind == ObjSymbolKind::Function { if symbol_kind == ObjSymbolKind::Function {
if current_address & 3 != 0 || data.len() & 3 != 0 { if current_address & 3 != 0 || data.len() & 3 != 0 {
return Err(Error::msg(format!( return Err(Error::msg(format!(
"Unaligned code write @ {:#010X} size {:#X}", "Unaligned code write @ {} {:#010X} size {:#X}",
section.name,
current_address, current_address,
data.len() data.len()
))); )));
} }
write_code_chunk(w, symbols, sym_map, relocations, section, current_address, data)?; write_code_chunk(w, symbols, entries, relocations, section, current_address, data)?;
} else { } else {
write_data_chunk(w, data)?; write_data_chunk(w, data)?;
} }
current_address = until; current_address = until;
} }
// Write end of symbols
if let Some(entries) = sym_map.get(&end) {
for entry in entries {
if entry.kind != SymbolEntryKind::End {
continue;
}
write_symbol_entry(w, symbols, entry)?;
}
}
Ok(()) Ok(())
} }
@ -587,7 +475,7 @@ fn find_symbol_kind(
match entry.kind { match entry.kind {
SymbolEntryKind::Start => { SymbolEntryKind::Start => {
let new_kind = symbols[entry.index].kind; let new_kind = symbols[entry.index].kind;
if new_kind != ObjSymbolKind::Unknown { if !matches!(new_kind, ObjSymbolKind::Unknown | ObjSymbolKind::Section) {
if found && new_kind != kind { if found && new_kind != kind {
return Err(Error::msg(format!( return Err(Error::msg(format!(
"Conflicting symbol kinds found: {kind:?} and {new_kind:?}" "Conflicting symbol kinds found: {kind:?} and {new_kind:?}"
@ -603,7 +491,7 @@ fn find_symbol_kind(
Ok(kind) Ok(kind)
} }
fn write_data_chunk(w: &mut BufWriter<File>, data: &[u8]) -> Result<()> { fn write_data_chunk<W: Write>(w: &mut W, data: &[u8]) -> Result<()> {
let remain = data; let remain = data;
for chunk in remain.chunks(4) { for chunk in remain.chunks(4) {
match chunk.len() { match chunk.len() {
@ -626,24 +514,27 @@ fn write_data_chunk(w: &mut BufWriter<File>, data: &[u8]) -> Result<()> {
Ok(()) Ok(())
} }
fn write_data_reloc( fn write_data_reloc<W: Write>(
w: &mut BufWriter<File>, w: &mut W,
symbols: &[ObjSymbol], symbols: &[ObjSymbol],
sym_map: &BTreeMap<u32, Vec<SymbolEntry>>, _entries: &BTreeMap<u32, Vec<SymbolEntry>>,
reloc: &ObjReloc, reloc: &ObjReloc,
section_entries: &[BTreeMap<u32, Vec<SymbolEntry>>],
) -> Result<u32> { ) -> Result<u32> {
Ok(match reloc.kind { match reloc.kind {
ObjRelocKind::Absolute => { ObjRelocKind::Absolute => {
// Attempt to use .rel macro for relative relocations // Attempt to use .rel macro for relative relocations
if reloc.target.addend != 0 { if reloc.addend != 0 {
let target_addr = (reloc.target.address as i64 + reloc.target.addend) as u32; let target = &symbols[reloc.target_symbol];
if let Some(entry) = sym_map let target_addr = (target.address as i64 + reloc.addend) as u32;
.get(&target_addr) if let Some(entry) = target
.section
.and_then(|section_idx| section_entries[section_idx].get(&target_addr))
.and_then(|entries| entries.iter().find(|e| e.kind == SymbolEntryKind::Label)) .and_then(|entries| entries.iter().find(|e| e.kind == SymbolEntryKind::Label))
{ {
let symbol = &symbols[entry.index]; let symbol = &symbols[entry.index];
write!(w, "\t.rel ")?; write!(w, "\t.rel ")?;
write_symbol_name(w, &reloc.target.name)?; write_symbol_name(w, &target.name)?;
write!(w, ", ")?; write!(w, ", ")?;
write_symbol_name(w, &symbol.name)?; write_symbol_name(w, &symbol.name)?;
writeln!(w)?; writeln!(w)?;
@ -651,31 +542,31 @@ fn write_data_reloc(
} }
} }
write!(w, "\t.4byte ")?; write!(w, "\t.4byte ")?;
write_symbol(w, &reloc.target)?; write_reloc_symbol(w, symbols, reloc)?;
writeln!(w)?; writeln!(w)?;
(reloc.address + 4) as u32 Ok((reloc.address + 4) as u32)
}
_ => Err(Error::msg(format!("Unsupported data relocation type {:?}", reloc.kind))),
} }
_ => todo!(),
})
} }
fn write_bss( fn write_bss<W: Write>(
w: &mut BufWriter<File>, w: &mut W,
symbols: &[ObjSymbol], symbols: &[ObjSymbol],
sym_map: &BTreeMap<u32, Vec<SymbolEntry>>, entries: &BTreeMap<u32, Vec<SymbolEntry>>,
start: u32, start: u32,
end: u32, end: u32,
) -> Result<()> { ) -> Result<()> {
let mut sym_iter = sym_map.range(start..end); let mut entry_iter = entries.range(start..end);
let mut current_address = start; let mut current_address = start;
let mut sym = sym_iter.next(); let mut entry = entry_iter.next();
let mut begin = true; let mut begin = true;
loop { loop {
if current_address == end { if current_address == end {
break; break;
} }
if let Some((sym_addr, vec)) = sym { if let Some((sym_addr, vec)) = entry {
if current_address == *sym_addr { if current_address == *sym_addr {
for entry in vec { for entry in vec {
if entry.kind == SymbolEntryKind::End && begin { if entry.kind == SymbolEntryKind::End && begin {
@ -683,116 +574,92 @@ fn write_bss(
} }
write_symbol_entry(w, symbols, entry)?; write_symbol_entry(w, symbols, entry)?;
} }
sym = sym_iter.next(); entry = entry_iter.next();
} }
} }
begin = false; begin = false;
let until = sym.map(|(addr, _)| *addr).unwrap_or(end); let until = entry.map(|(addr, _)| *addr).unwrap_or(end);
let size = until - current_address; let size = until - current_address;
if size > 0 { if size > 0 {
writeln!(w, "\t.skip {size:#X}")?; writeln!(w, "\t.skip {size:#X}")?;
} }
current_address = until; current_address = until;
} }
// Write end of symbols
if let Some(entries) = sym_map.get(&end) {
for entry in entries {
if entry.kind != SymbolEntryKind::End {
continue;
}
write_symbol_entry(w, symbols, entry)?;
}
}
Ok(()) Ok(())
} }
fn file_name_from_unit(str: &str) -> String { fn write_section_header<W: Write>(
let str = str.strip_prefix("C:").unwrap_or(str); w: &mut W,
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!("{}.s", str.strip_prefix('/').unwrap_or(&str))
}
fn write_section_header<'a>(
file_map: &'a mut HashMap<String, BufWriter<File>>,
unit: &String,
section: &ObjSection, section: &ObjSection,
subsection: usize, subsection: usize,
start: u32, start: u32,
end: u32, end: u32,
) -> Result<&'a mut BufWriter<File>> { ) -> Result<()> {
let w = file_map writeln!(
.get_mut(unit) w,
.ok_or_else(|| Error::msg(format!("Failed to locate file for {unit}")))?; "\n# {:#010X} - {:#010X}",
writeln!(w, "\n# {start:#10X} - {end:#10X}")?; start as u64 + section.original_address,
let alignment = match section.name.as_str() { end as u64 + section.original_address
)?;
match section.name.as_str() {
".text" if subsection == 0 => { ".text" if subsection == 0 => {
write!(w, "{}", section.name)?; write!(w, "{}", section.name)?;
4
} }
".data" | ".bss" | ".rodata" if subsection == 0 => { // .bss excluded to support < r40 devkitPro
".data" | ".rodata" if subsection == 0 => {
write!(w, "{}", section.name)?; write!(w, "{}", section.name)?;
8
} }
".text" | ".init" => { ".text" | ".init" => {
write!(w, ".section {}", section.name)?; write!(w, ".section {}", section.name)?;
write!(w, ", \"ax\"")?; write!(w, ", \"ax\"")?;
4
} }
".data" | ".sdata" => { ".data" | ".sdata" => {
write!(w, ".section {}", section.name)?; write!(w, ".section {}", section.name)?;
write!(w, ", \"wa\"")?; write!(w, ", \"wa\"")?;
8
} }
".rodata" | ".sdata2" => { ".rodata" | ".sdata2" => {
write!(w, ".section {}", section.name)?; write!(w, ".section {}", section.name)?;
write!(w, ", \"a\"")?; write!(w, ", \"a\"")?;
8
} }
".bss" | ".sbss" => { ".bss" | ".sbss" => {
write!(w, ".section {}", section.name)?; write!(w, ".section {}", section.name)?;
write!(w, ", \"wa\", @nobits")?; write!(w, ", \"wa\", @nobits")?;
8
} }
".sbss2" => { ".sbss2" => {
write!(w, ".section {}", section.name)?; write!(w, ".section {}", section.name)?;
write!(w, ", \"a\", @nobits")?; write!(w, ", \"a\", @nobits")?;
8
} }
".ctors" | ".dtors" | "extab" | "extabindex" => { ".ctors" | ".dtors" | "extab" | "extabindex" => {
write!(w, ".section {}", section.name)?; write!(w, ".section {}", section.name)?;
write!(w, ", \"a\"")?; write!(w, ", \"a\"")?;
4
} }
name => todo!("Section {}", name), name => return Err(Error::msg(format!("Unknown section {name}"))),
}; };
if subsection != 0 { if subsection != 0 {
write!(w, ", unique, {subsection}")?; write!(w, ", unique, {subsection}")?;
} }
writeln!(w)?; writeln!(w)?;
if alignment != 0 { if section.align != 0 {
writeln!(w, ".balign {alignment}")?; writeln!(w, ".balign {}", section.align)?;
} }
Ok(w) Ok(())
} }
fn write_symbol(w: &mut BufWriter<File>, sym: &ObjSymbol) -> std::io::Result<()> { fn write_reloc_symbol<W: Write>(
write_symbol_name(w, &sym.name)?; w: &mut W,
match sym.addend.cmp(&0i64) { symbols: &[ObjSymbol],
Ordering::Greater => write!(w, "+{:#X}", sym.addend), reloc: &ObjReloc,
Ordering::Less => write!(w, "-{:#X}", -sym.addend), ) -> std::io::Result<()> {
write_symbol_name(w, &symbols[reloc.target_symbol].name)?;
match reloc.addend.cmp(&0i64) {
Ordering::Greater => write!(w, "+{:#X}", reloc.addend),
Ordering::Less => write!(w, "-{:#X}", -reloc.addend),
Ordering::Equal => Ok(()), Ordering::Equal => Ok(()),
} }
} }
fn write_symbol_name(w: &mut BufWriter<File>, name: &str) -> std::io::Result<()> { fn write_symbol_name<W: Write>(w: &mut W, name: &str) -> std::io::Result<()> {
// TODO more? // TODO more?
if name.contains('@') || name.contains('<') { if name.contains('@') || name.contains('<') {
write!(w, "\"{name}\"")?; write!(w, "\"{name}\"")?;
@ -802,17 +669,6 @@ fn write_symbol_name(w: &mut BufWriter<File>, name: &str) -> std::io::Result<()>
Ok(()) Ok(())
} }
#[inline]
fn is_skip_symbol(name: &str) -> bool {
// Avoid generating these, they span across files
matches!(name, "_ctors" | "_dtors")
}
#[inline]
fn is_linker_symbol(name: &str) -> bool {
matches!(name, "_eti_init_info" | "_rom_copy_info" | "_bss_init_info")
}
#[inline] #[inline]
fn is_illegal_instruction(code: u32) -> bool { fn is_illegal_instruction(code: u32) -> bool {
matches!(code, 0x43000000 /* bc 24, lt, 0x0 */ | 0xB8030000 /* lmw r0, 0(r3) */) matches!(code, 0x43000000 /* bc 24, lt, 0x0 */ | 0xB8030000 /* lmw r0, 0(r3) */)

View File

@ -7,16 +7,18 @@ use indexmap::IndexMap;
use memmap2::MmapOptions; use memmap2::MmapOptions;
use object::{ use object::{
elf::{ elf::{
R_PPC_ADDR16_HA, R_PPC_ADDR16_HI, R_PPC_ADDR16_LO, R_PPC_EMB_SDA21, R_PPC_REL14, R_PPC_ADDR16_HA, R_PPC_ADDR16_HI, R_PPC_ADDR16_LO, R_PPC_ADDR32, R_PPC_EMB_SDA21,
R_PPC_REL24, R_PPC_REL14, R_PPC_REL24,
}, },
Architecture, Object, ObjectKind, ObjectSection, ObjectSymbol, Relocation, RelocationKind, write::{Mangling, SectionId, SymbolId},
RelocationTarget, Section, SectionKind, Symbol, SymbolKind, SymbolSection, Architecture, BinaryFormat, Endianness, Object, ObjectKind, ObjectSection, ObjectSymbol,
Relocation, RelocationEncoding, RelocationKind, RelocationTarget, SectionKind, Symbol,
SymbolFlags, SymbolKind, SymbolScope, SymbolSection,
}; };
use crate::util::obj::{ use crate::util::obj::{
ObjArchitecture, ObjInfo, ObjReloc, ObjRelocKind, ObjSection, ObjSectionKind, ObjSymbol, ObjArchitecture, ObjInfo, ObjKind, ObjReloc, ObjRelocKind, ObjSection, ObjSectionKind,
ObjSymbolFlagSet, ObjSymbolFlags, ObjSymbolKind, ObjSymbol, ObjSymbolFlagSet, ObjSymbolFlags, ObjSymbolKind,
}; };
pub fn process_elf<P: AsRef<Path>>(path: P) -> Result<ObjInfo> { pub fn process_elf<P: AsRef<Path>>(path: P) -> Result<ObjInfo> {
@ -31,13 +33,15 @@ pub fn process_elf<P: AsRef<Path>>(path: P) -> Result<ObjInfo> {
Architecture::PowerPc => ObjArchitecture::PowerPc, Architecture::PowerPc => ObjArchitecture::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() { if obj_file.endianness() != Endianness::Big {
return Err(Error::msg("Expected big endian")); return Err(Error::msg("Expected big endian"));
} }
match obj_file.kind() { let kind = match obj_file.kind() {
ObjectKind::Executable => {} ObjectKind::Executable => ObjKind::Executable,
ObjectKind::Relocatable => ObjKind::Relocatable,
kind => return Err(Error::msg(format!("Unexpected ELF type: {kind:?}"))), kind => return Err(Error::msg(format!("Unexpected ELF type: {kind:?}"))),
} };
let mut obj_name = String::new();
let mut stack_address: Option<u32> = None; let mut stack_address: Option<u32> = None;
let mut stack_end: Option<u32> = None; let mut stack_end: Option<u32> = None;
@ -45,7 +49,36 @@ pub fn process_elf<P: AsRef<Path>>(path: P) -> Result<ObjInfo> {
let mut arena_lo: Option<u32> = None; let mut arena_lo: Option<u32> = None;
let mut arena_hi: Option<u32> = None; let mut arena_hi: Option<u32> = None;
let mut common: Vec<ObjSymbol> = vec![]; let mut sections: Vec<ObjSection> = vec![];
let mut section_indexes: Vec<Option<usize>> = vec![];
for section in obj_file.sections() {
let section_kind = match section.kind() {
SectionKind::Text => ObjSectionKind::Code,
SectionKind::Data => ObjSectionKind::Data,
SectionKind::ReadOnlyData => ObjSectionKind::ReadOnlyData,
SectionKind::UninitializedData => ObjSectionKind::Bss,
_ => {
section_indexes.push(None);
continue;
}
};
section_indexes.push(Some(sections.len()));
sections.push(ObjSection {
name: section.name()?.to_string(),
kind: section_kind,
address: section.address(),
size: section.size(),
data: section.uncompressed_data()?.to_vec(),
align: section.align(),
index: sections.len(),
relocations: vec![],
original_address: 0, // TODO load from abs symbol
file_offset: section.file_range().map(|(v, _)| v).unwrap_or_default(),
});
}
let mut symbols: Vec<ObjSymbol> = vec![];
let mut symbol_indexes: Vec<Option<usize>> = vec![];
let mut current_file: Option<String> = None; let mut current_file: Option<String> = None;
let mut section_starts = IndexMap::<String, Vec<(u64, String)>>::new(); let mut section_starts = IndexMap::<String, Vec<(u64, String)>>::new();
for symbol in obj_file.symbols() { for symbol in obj_file.symbols() {
@ -54,23 +87,18 @@ pub fn process_elf<P: AsRef<Path>>(path: P) -> Result<ObjInfo> {
match symbol_name { match symbol_name {
"_stack_addr" => { "_stack_addr" => {
stack_address = Some(symbol.address() as u32); stack_address = Some(symbol.address() as u32);
continue;
} }
"_stack_end" => { "_stack_end" => {
stack_end = Some(symbol.address() as u32); stack_end = Some(symbol.address() as u32);
continue;
} }
"_db_stack_addr" => { "_db_stack_addr" => {
db_stack_addr = Some(symbol.address() as u32); db_stack_addr = Some(symbol.address() as u32);
continue;
} }
"__ArenaLo" => { "__ArenaLo" => {
arena_lo = Some(symbol.address() as u32); arena_lo = Some(symbol.address() as u32);
continue;
} }
"__ArenaHi" => { "__ArenaHi" => {
arena_hi = Some(symbol.address() as u32); arena_hi = Some(symbol.address() as u32);
continue;
} }
_ => {} _ => {}
} }
@ -79,6 +107,9 @@ pub fn process_elf<P: AsRef<Path>>(path: P) -> Result<ObjInfo> {
// Detect file boundaries // Detect file boundaries
SymbolKind::File => { SymbolKind::File => {
let file_name = symbol_name.to_string(); let file_name = symbol_name.to_string();
if kind == ObjKind::Relocatable {
obj_name = file_name.clone();
}
match section_starts.entry(file_name.clone()) { match section_starts.entry(file_name.clone()) {
indexmap::map::Entry::Occupied(_) => { indexmap::map::Entry::Occupied(_) => {
return Err(Error::msg(format!("Duplicate file name: {file_name}"))); return Err(Error::msg(format!("Duplicate file name: {file_name}")));
@ -86,15 +117,13 @@ pub fn process_elf<P: AsRef<Path>>(path: P) -> Result<ObjInfo> {
indexmap::map::Entry::Vacant(e) => e.insert(Default::default()), indexmap::map::Entry::Vacant(e) => e.insert(Default::default()),
}; };
current_file = Some(file_name); current_file = Some(file_name);
continue;
} }
// Detect sections within a file // Detect sections within a file
SymbolKind::Section => { SymbolKind::Section => {
if let Some(file_name) = &current_file { if let Some(file_name) = &current_file {
let sections = match section_starts.get_mut(file_name) { let sections = section_starts
Some(entries) => entries, .get_mut(file_name)
None => return Err(Error::msg("Failed to create entry")), .ok_or_else(|| Error::msg("Failed to create entry"))?;
};
let section_index = symbol let section_index = symbol
.section_index() .section_index()
.ok_or_else(|| Error::msg("Section symbol without section"))?; .ok_or_else(|| Error::msg("Section symbol without section"))?;
@ -102,30 +131,27 @@ pub fn process_elf<P: AsRef<Path>>(path: P) -> Result<ObjInfo> {
let section_name = section.name()?.to_string(); let section_name = section.name()?.to_string();
sections.push((symbol.address(), section_name)); sections.push((symbol.address(), section_name));
}; };
continue;
} }
// Sometimes, the section symbol address is 0, // Sometimes, the section symbol address is 0,
// so attempt to detect it from first symbol within section // so attempt to detect it from first symbol within section
SymbolKind::Data | SymbolKind::Text => { SymbolKind::Data | SymbolKind::Text => {
if let Some(file_name) = &current_file { if let Some(file_name) = &current_file {
let section_map = match section_starts.get_mut(file_name) { let sections = section_starts
Some(entries) => entries, .get_mut(file_name)
None => return Err(Error::msg("Failed to create entry")), .ok_or_else(|| Error::msg("Failed to create entry"))?;
}; let section_index = symbol.section_index().ok_or_else(|| {
let section_index = symbol Error::msg(format!("Section symbol without section: {symbol:?}"))
.section_index() })?;
.ok_or_else(|| Error::msg("Section symbol without section"))?;
let section = obj_file.section_by_index(section_index)?; let section = obj_file.section_by_index(section_index)?;
let section_name = section.name()?; let section_name = section.name()?;
if let Some((addr, _)) = if let Some((addr, _)) =
section_map.iter_mut().find(|(_, name)| name == section_name) sections.iter_mut().find(|(_, name)| name == section_name)
{ {
if *addr == 0 { if *addr == 0 {
*addr = symbol.address(); *addr = symbol.address();
} }
}; };
}; };
continue;
} }
_ => match symbol.section() { _ => match symbol.section() {
// Linker generated symbols indicate the end // Linker generated symbols indicate the end
@ -133,82 +159,60 @@ pub fn process_elf<P: AsRef<Path>>(path: P) -> Result<ObjInfo> {
current_file = None; current_file = None;
} }
SymbolSection::Section(_) | SymbolSection::Undefined => {} SymbolSection::Section(_) | SymbolSection::Undefined => {}
_ => todo!("Symbol section type {:?}", symbol), _ => return Err(Error::msg(format!("Unsupported symbol section type {symbol:?}"))),
}, },
} }
// Keep track of common symbols // Generate symbols
if !symbol.is_common() { if matches!(symbol.kind(), SymbolKind::Null | SymbolKind::File) {
symbol_indexes.push(None);
continue; continue;
} }
common.push(to_obj_symbol(&obj_file, &symbol, 0)?); symbol_indexes.push(Some(symbols.len()));
symbols.push(to_obj_symbol(&obj_file, &symbol, &section_indexes)?);
} }
// Link order is trivially deduced
let mut link_order = Vec::<String>::new(); let mut link_order = Vec::<String>::new();
let mut splits = BTreeMap::<u32, String>::new();
if kind == ObjKind::Executable {
// 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(file_name.clone());
} }
// Create a map of address -> file splits // Create a map of address -> file splits
let mut splits = BTreeMap::<u32, String>::new();
for (file_name, sections) in section_starts { for (file_name, sections) in section_starts {
for (address, _) in sections { for (address, _) in sections {
splits.insert(address as u32, file_name.clone()); splits.insert(address as u32, file_name.clone());
} }
} }
let mut sections: Vec<ObjSection> = vec![]; // TODO rebuild common symbols
for section in obj_file.sections() { }
let section_index = section.index();
let section_kind = match section.kind() { for (section_idx, section) in obj_file.sections().enumerate() {
SectionKind::Text => ObjSectionKind::Code, let out_section = match section_indexes[section_idx].and_then(|idx| sections.get_mut(idx)) {
SectionKind::Data => ObjSectionKind::Data, Some(s) => s,
SectionKind::ReadOnlyData => ObjSectionKind::Data, None => continue,
SectionKind::UninitializedData => ObjSectionKind::Bss,
_ => continue,
}; };
let name = section.name()?;
log::info!("Processing section {}", name);
let data = section.uncompressed_data()?.to_vec();
// Generate symbols
let mut symbols: Vec<ObjSymbol> = vec![];
for symbol in obj_file.symbols() {
if !matches!(symbol.section_index(), Some(idx) if idx == section_index) {
continue;
}
if symbol.address() == 0 || symbol.name()?.is_empty() {
continue;
}
symbols.push(to_obj_symbol(&obj_file, &symbol, 0)?);
}
// Generate relocations // Generate relocations
let mut relocations: Vec<ObjReloc> = vec![];
for (address, reloc) in section.relocations() { for (address, reloc) in section.relocations() {
relocations.push(to_obj_reloc(&obj_file, &section, &data, address, reloc)?); out_section.relocations.push(to_obj_reloc(
&obj_file,
&symbol_indexes,
&out_section.data,
address,
reloc,
)?);
} }
let file_offset = section.file_range().map(|(v, _)| v).unwrap_or_default();
sections.push(ObjSection {
name: name.to_string(),
kind: section_kind,
address: section.address(),
size: section.size(),
data,
index: sections.len(),
symbols,
relocations,
file_offset,
});
} }
Ok(ObjInfo { Ok(ObjInfo {
kind,
architecture, architecture,
path: path.as_ref().to_path_buf(), name: obj_name,
symbols,
sections, sections,
common,
entry: obj_file.entry() as u32, entry: obj_file.entry() as u32,
stack_address, stack_address,
stack_end, stack_end,
@ -220,10 +224,107 @@ pub fn process_elf<P: AsRef<Path>>(path: P) -> Result<ObjInfo> {
}) })
} }
pub fn write_elf(obj: &ObjInfo) -> Result<object::write::Object> {
let mut out_obj =
object::write::Object::new(BinaryFormat::Elf, Architecture::PowerPc, Endianness::Big);
out_obj.set_mangling(Mangling::None);
if !obj.name.is_empty() {
out_obj.add_file_symbol(obj.name.as_bytes().to_vec());
}
let mut section_idxs: Vec<SectionId> = Vec::with_capacity(obj.sections.len());
for section in &obj.sections {
let section_id =
out_obj.add_section(vec![], section.name.as_bytes().to_vec(), match section.kind {
ObjSectionKind::Code => SectionKind::Text,
ObjSectionKind::Data => SectionKind::Data,
ObjSectionKind::ReadOnlyData => SectionKind::ReadOnlyData,
ObjSectionKind::Bss => SectionKind::UninitializedData,
});
section_idxs.push(section_id);
let out_section = out_obj.section_mut(section_id);
match section.kind {
ObjSectionKind::Bss => {
out_section.append_bss(section.size, section.align);
}
_ => {
out_section.set_data(section.data.clone(), section.align);
}
}
// Generate section symbol
out_obj.section_symbol(section_id);
// Add original addresses
if section.original_address != 0 {
// TODO write to metadata?
}
if section.file_offset != 0 {
// TODO write to metadata?
}
}
// Add symbols
let mut symbol_idxs: Vec<SymbolId> = Vec::with_capacity(obj.symbols.len());
for symbol in &obj.symbols {
let symbol_id = out_obj.add_symbol(object::write::Symbol {
name: symbol.name.as_bytes().to_vec(),
value: symbol.address,
size: symbol.size,
kind: match symbol.kind {
ObjSymbolKind::Unknown => SymbolKind::Null,
ObjSymbolKind::Function => SymbolKind::Text,
ObjSymbolKind::Object => SymbolKind::Data,
ObjSymbolKind::Section => SymbolKind::Section,
},
scope: if symbol.flags.0.contains(ObjSymbolFlags::Hidden) {
SymbolScope::Linkage
} else if symbol.flags.0.contains(ObjSymbolFlags::Local) {
SymbolScope::Compilation
} else {
SymbolScope::Dynamic
},
weak: symbol.flags.0.contains(ObjSymbolFlags::Weak),
section: match symbol.section {
None => object::write::SymbolSection::Undefined,
Some(idx) => object::write::SymbolSection::Section(section_idxs[idx]),
},
flags: SymbolFlags::None,
});
symbol_idxs.push(symbol_id);
}
// Add relocations
for section in &obj.sections {
let section_id = section_idxs[section.index];
for reloc in &section.relocations {
let symbol_id = symbol_idxs[reloc.target_symbol];
out_obj.add_relocation(section_id, object::write::Relocation {
offset: reloc.address,
size: 0,
kind: RelocationKind::Elf(match reloc.kind {
ObjRelocKind::Absolute => R_PPC_ADDR32,
ObjRelocKind::PpcAddr16Hi => R_PPC_ADDR16_HI,
ObjRelocKind::PpcAddr16Ha => R_PPC_ADDR16_HA,
ObjRelocKind::PpcAddr16Lo => R_PPC_ADDR16_LO,
ObjRelocKind::PpcRel24 => R_PPC_REL24,
ObjRelocKind::PpcRel14 => R_PPC_REL14,
ObjRelocKind::PpcEmbSda21 => R_PPC_EMB_SDA21,
}),
encoding: RelocationEncoding::Generic,
symbol: symbol_id,
addend: reloc.addend,
})?;
}
}
Ok(out_obj)
}
fn to_obj_symbol( fn to_obj_symbol(
obj_file: &object::File<'_>, obj_file: &object::File<'_>,
symbol: &Symbol<'_, '_>, symbol: &Symbol<'_, '_>,
addend: i64, section_indexes: &[Option<usize>],
) -> Result<ObjSymbol> { ) -> Result<ObjSymbol> {
let section = match symbol.section_index() { let section = match symbol.section_index() {
Some(idx) => Some(obj_file.section_by_index(idx)?), Some(idx) => Some(obj_file.section_by_index(idx)?),
@ -255,31 +356,31 @@ fn to_obj_symbol(
if symbol.is_weak() { if symbol.is_weak() {
flags = ObjSymbolFlagSet(flags.0 | ObjSymbolFlags::Weak); flags = ObjSymbolFlagSet(flags.0 | ObjSymbolFlags::Weak);
} }
let section_address = if let Some(section) = &section { if symbol.scope() == SymbolScope::Linkage {
symbol.address() - section.address() flags = ObjSymbolFlagSet(flags.0 | ObjSymbolFlags::Hidden);
} else { }
symbol.address() let section_idx = section.as_ref().and_then(|section| section_indexes[section.index().0]);
};
Ok(ObjSymbol { Ok(ObjSymbol {
name: name.to_string(), name: name.to_string(),
demangled_name: demangle(name, &Default::default()), demangled_name: demangle(name, &Default::default()),
address: symbol.address(), address: symbol.address(),
section_address, section: section_idx,
size: symbol.size(), size: symbol.size(),
size_known: symbol.size() != 0, size_known: true,
flags, flags,
addend,
kind: match symbol.kind() { kind: match symbol.kind() {
SymbolKind::Text => ObjSymbolKind::Function, SymbolKind::Text => ObjSymbolKind::Function,
SymbolKind::Data => ObjSymbolKind::Object, SymbolKind::Data => ObjSymbolKind::Object,
_ => ObjSymbolKind::Unknown, SymbolKind::Unknown => ObjSymbolKind::Unknown,
SymbolKind::Section => ObjSymbolKind::Section,
_ => return Err(Error::msg(format!("Unsupported symbol kind: {:?}", symbol.kind()))),
}, },
}) })
} }
fn to_obj_reloc( fn to_obj_reloc(
obj_file: &object::File<'_>, obj_file: &object::File<'_>,
_section: &Section<'_, '_>, symbol_indexes: &[Option<usize>],
section_data: &[u8], section_data: &[u8],
address: u64, address: u64,
reloc: Relocation, reloc: Relocation,
@ -305,17 +406,10 @@ fn to_obj_reloc(
return Err(Error::msg(format!("Unhandled relocation target: {:?}", reloc.target()))); return Err(Error::msg(format!("Unhandled relocation target: {:?}", reloc.target())));
} }
}; };
let target_section = match symbol.section() { let target_symbol = symbol_indexes[symbol.index().0]
SymbolSection::Common => Some(".comm".to_string()), .ok_or_else(|| Error::msg(format!("Relocation against stripped symbol: {symbol:?}")))?;
SymbolSection::Section(idx) => { let addend = match symbol.kind() {
obj_file.section_by_index(idx).and_then(|s| s.name().map(|s| s.to_string())).ok() SymbolKind::Text | SymbolKind::Data | SymbolKind::Unknown => Ok(reloc.addend()),
}
_ => None,
};
let target = match symbol.kind() {
SymbolKind::Text | SymbolKind::Data | SymbolKind::Unknown => {
to_obj_symbol(obj_file, &symbol, reloc.addend())
}
SymbolKind::Section => { SymbolKind::Section => {
let addend = if reloc.has_implicit_addend() { let addend = if reloc.has_implicit_addend() {
let addend = u32::from_be_bytes( let addend = u32::from_be_bytes(
@ -323,70 +417,23 @@ fn to_obj_reloc(
) as i64; ) as i64;
match reloc_kind { match reloc_kind {
ObjRelocKind::Absolute => addend, ObjRelocKind::Absolute => addend,
_ => todo!(), _ => {
return Err(Error::msg(format!(
"Unsupported implicit relocation type {reloc_kind:?}"
)))
}
} }
} else { } else {
let addend = reloc.addend(); reloc.addend()
};
if addend < 0 { if addend < 0 {
return Err(Error::msg(format!("Negative addend in section reloc: {addend}"))); return Err(Error::msg(format!("Negative addend in section reloc: {addend}")));
} }
addend Ok(addend)
};
// find_section_symbol(&obj_file, &symbol, addend as u64)
to_obj_symbol(obj_file, &symbol, addend)
} }
_ => Err(Error::msg(format!("Unhandled relocation symbol type {:?}", symbol.kind()))), _ => Err(Error::msg(format!("Unhandled relocation symbol type {:?}", symbol.kind()))),
}?; }?;
let address = address & !3; // FIXME round down for instruction let address = address & !3; // TODO hack: round down for instruction
let reloc_data = ObjReloc { kind: reloc_kind, address, target, target_section }; let reloc_data = ObjReloc { kind: reloc_kind, address, target_symbol, addend };
Ok(reloc_data) Ok(reloc_data)
} }
// TODO needed?
#[allow(dead_code)]
fn find_section_symbol(
obj_file: &object::File<'_>,
target: &Symbol<'_, '_>,
addend: u64,
) -> Result<ObjSymbol> {
let section_index =
target.section_index().ok_or_else(|| Error::msg("Unknown section index"))?;
let section = obj_file.section_by_index(section_index)?;
let target_address = target.address() + addend;
let mut closest_symbol: Option<Symbol<'_, '_>> = None;
for symbol in obj_file.symbols() {
if !matches!(symbol.section_index(), Some(idx) if idx == section_index) {
continue;
}
if symbol.kind() == SymbolKind::Section || symbol.address() != target_address {
if symbol.address() < target_address
&& symbol.size() != 0
&& (closest_symbol.is_none()
|| matches!(&closest_symbol, Some(s) if s.address() <= symbol.address()))
{
closest_symbol = Some(symbol);
}
continue;
}
return to_obj_symbol(obj_file, &symbol, 0);
}
if let Some(symbol) = closest_symbol {
let addend = target_address - symbol.address();
Ok(to_obj_symbol(obj_file, &symbol, addend as i64)?)
} else {
let addend = target_address - section.address();
Ok(ObjSymbol {
name: section.name()?.to_string(),
demangled_name: None,
address: section.address(),
section_address: 0,
size: section.size(),
size_known: true,
flags: Default::default(),
addend: addend as i64,
kind: ObjSymbolKind::Unknown,
})
}
}

View File

@ -51,7 +51,8 @@ struct SectionOrder {
unit_order: Vec<(String, Vec<String>)>, unit_order: Vec<(String, Vec<String>)>,
} }
fn is_code_section(section: &str) -> bool { section == ".text" || section == ".init" } #[inline]
fn is_code_section(section: &str) -> bool { matches!(section, ".text" | ".init") }
/// Iterate over the BTreeMap and generate an ordered list of symbols and TUs by address. /// Iterate over the BTreeMap and generate an ordered list of symbols and TUs by address.
fn resolve_section_order( fn resolve_section_order(

View File

@ -2,3 +2,4 @@ pub(crate) mod asm;
pub(crate) mod elf; pub(crate) mod elf;
pub(crate) mod map; pub(crate) mod map;
pub(crate) mod obj; pub(crate) mod obj;
pub(crate) mod split;

View File

@ -1,5 +1,9 @@
use std::{collections::BTreeMap, path::PathBuf}; use std::{
collections::{btree_map, BTreeMap},
hash::{Hash, Hasher},
};
use anyhow::{Error, Result};
use flagset::{flags, FlagSet}; use flagset::{flags, FlagSet};
flags! { flags! {
@ -8,14 +12,20 @@ flags! {
Local, Local,
Weak, Weak,
Common, Common,
Hidden,
} }
} }
#[derive(Debug, Copy, Clone, Default)] #[derive(Debug, Copy, Clone, Default, Eq, PartialEq)]
pub struct ObjSymbolFlagSet(pub(crate) FlagSet<ObjSymbolFlags>); pub struct ObjSymbolFlagSet(pub(crate) FlagSet<ObjSymbolFlags>);
#[derive(Debug, Eq, PartialEq, Copy, Clone)] #[allow(clippy::derive_hash_xor_eq)]
impl Hash for ObjSymbolFlagSet {
fn hash<H: Hasher>(&self, state: &mut H) { self.0.bits().hash(state) }
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum ObjSectionKind { pub enum ObjSectionKind {
Code, Code,
Data, Data,
ReadOnlyData,
Bss, Bss,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -25,39 +35,49 @@ pub struct ObjSection {
pub address: u64, pub address: u64,
pub size: u64, pub size: u64,
pub data: Vec<u8>, pub data: Vec<u8>,
pub align: u64,
pub index: usize, pub index: usize,
pub symbols: Vec<ObjSymbol>,
pub relocations: Vec<ObjReloc>, pub relocations: Vec<ObjReloc>,
pub original_address: u64,
pub file_offset: u64, pub file_offset: u64,
} }
#[derive(Debug, Eq, PartialEq, Copy, Clone)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
pub enum ObjSymbolKind { pub enum ObjSymbolKind {
#[default]
Unknown, Unknown,
Function, Function,
Object, Object,
Section,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone, Default)]
pub struct ObjSymbol { pub struct ObjSymbol {
pub name: String, pub name: String,
pub demangled_name: Option<String>, pub demangled_name: Option<String>,
pub address: u64, pub address: u64,
pub section_address: u64, pub section: Option<usize>,
pub size: u64, pub size: u64,
pub size_known: bool, pub size_known: bool,
pub flags: ObjSymbolFlagSet, pub flags: ObjSymbolFlagSet,
pub addend: i64,
pub kind: ObjSymbolKind, pub kind: ObjSymbolKind,
} }
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum ObjKind {
/// Fully linked file
Executable,
/// Relocatable file
Relocatable,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum ObjArchitecture { pub enum ObjArchitecture {
PowerPc, PowerPc,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ObjInfo { pub struct ObjInfo {
pub kind: ObjKind,
pub architecture: ObjArchitecture, pub architecture: ObjArchitecture,
pub path: PathBuf, pub name: String,
pub symbols: Vec<ObjSymbol>,
pub sections: Vec<ObjSection>, pub sections: Vec<ObjSection>,
pub common: Vec<ObjSymbol>,
pub entry: u32, pub entry: u32,
// Linker generated // Linker generated
@ -71,7 +91,7 @@ pub struct ObjInfo {
pub splits: BTreeMap<u32, String>, pub splits: BTreeMap<u32, String>,
pub link_order: Vec<String>, pub link_order: Vec<String>,
} }
#[derive(Debug, Eq, PartialEq, Copy, Clone)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum ObjRelocKind { pub enum ObjRelocKind {
Absolute, Absolute,
PpcAddr16Hi, PpcAddr16Hi,
@ -85,6 +105,58 @@ pub enum ObjRelocKind {
pub struct ObjReloc { pub struct ObjReloc {
pub kind: ObjRelocKind, pub kind: ObjRelocKind,
pub address: u64, pub address: u64,
pub target: ObjSymbol, pub target_symbol: usize,
pub target_section: Option<String>, pub addend: i64,
}
impl ObjInfo {
pub fn symbols_for_section(
&self,
section_idx: usize,
) -> impl Iterator<Item = (usize, &ObjSymbol)> {
self.symbols
.iter()
.enumerate()
.filter(move |&(_, symbol)| symbol.section == Some(section_idx))
}
pub fn build_symbol_map(&self, section_idx: usize) -> Result<BTreeMap<u32, Vec<usize>>> {
let mut symbols = BTreeMap::<u32, Vec<usize>>::new();
for (symbol_idx, symbol) in self.symbols_for_section(section_idx) {
let address = symbol.address as u32;
nested_push(&mut symbols, address, symbol_idx);
}
Ok(symbols)
}
}
impl ObjSection {
pub fn build_relocation_map(&self) -> Result<BTreeMap<u32, ObjReloc>> {
let mut relocations = BTreeMap::<u32, ObjReloc>::new();
for reloc in &self.relocations {
let address = reloc.address as u32;
match relocations.entry(address) {
btree_map::Entry::Vacant(e) => {
e.insert(reloc.clone());
}
btree_map::Entry::Occupied(_) => {
return Err(Error::msg(format!("Duplicate relocation @ {address:#010X}")));
}
}
}
Ok(relocations)
}
}
#[inline]
pub fn nested_push<T1, T2>(map: &mut BTreeMap<T1, Vec<T2>>, v1: T1, v2: T2)
where T1: Ord {
match map.entry(v1) {
btree_map::Entry::Occupied(mut e) => {
e.get_mut().push(v2);
}
btree_map::Entry::Vacant(e) => {
e.insert(vec![v2]);
}
}
} }

233
src/util/split.rs Normal file
View File

@ -0,0 +1,233 @@
use std::{cmp::min, collections::HashMap};
use anyhow::{Error, Result};
use crate::util::obj::{
ObjArchitecture, ObjInfo, ObjKind, ObjReloc, ObjSection, ObjSectionKind, ObjSymbol,
};
/// Split an executable object into relocatable objects.
pub fn split_obj(obj: &ObjInfo) -> Result<Vec<ObjInfo>> {
if obj.kind != ObjKind::Executable {
return Err(Error::msg(format!("Expected executable object, got {:?}", obj.kind)));
}
let mut objects: Vec<ObjInfo> = vec![];
let mut object_symbols: Vec<Vec<Option<usize>>> = vec![];
let mut name_to_obj: HashMap<String, usize> = HashMap::new();
for unit in &obj.link_order {
name_to_obj.insert(unit.clone(), objects.len());
object_symbols.push(vec![None; obj.symbols.len()]);
objects.push(ObjInfo {
kind: ObjKind::Relocatable,
architecture: ObjArchitecture::PowerPc,
name: unit.clone(),
symbols: vec![],
sections: vec![],
entry: 0,
stack_address: None,
stack_end: None,
db_stack_addr: None,
arena_lo: None,
arena_hi: None,
splits: Default::default(),
link_order: vec![],
});
}
for (section_idx, section) in obj.sections.iter().enumerate() {
let mut current_address = section.address as u32;
let mut section_end = (section.address + section.size) as u32;
// .ctors and .dtors end with a linker-generated null pointer,
// adjust section size appropriately
if matches!(section.name.as_str(), ".ctors" | ".dtors")
&& section.data[section.data.len() - 4..] == [0u8; 4]
{
section_end -= 4;
}
let mut file_iter = obj.splits.range(current_address..).peekable();
// Build address to relocation / address to symbol maps
let relocations = section.build_relocation_map()?;
let symbols = obj.build_symbol_map(section_idx)?;
loop {
if current_address >= section_end {
break;
}
let (file_addr, unit) = match file_iter.next() {
Some((&addr, unit)) => (addr, unit),
None => return Err(Error::msg("No file found")),
};
if file_addr > current_address {
return Err(Error::msg(format!(
"Gap in files: {} @ {:#010X}, {} @ {:#010X}",
section.name, section.address, unit, file_addr
)));
}
let mut file_end = section_end;
if let Some(&(&next_addr, _)) = file_iter.peek() {
file_end = min(next_addr, section_end);
}
let file = name_to_obj
.get(unit)
.and_then(|&idx| objects.get_mut(idx))
.ok_or_else(|| Error::msg(format!("Unit '{unit}' not in link order")))?;
let symbol_idxs = name_to_obj
.get(unit)
.and_then(|&idx| object_symbols.get_mut(idx))
.ok_or_else(|| Error::msg(format!("Unit '{unit}' not in link order")))?;
let data = match section.kind {
ObjSectionKind::Bss => vec![],
_ => section.data[(current_address as u64 - section.address) as usize
..(file_end as u64 - section.address) as usize]
.to_vec(),
};
// Calculate & verify section alignment
let mut align = default_section_align(section);
if current_address & (align as u32 - 1) != 0 {
log::warn!(
"Alignment for {} {} expected {}, but starts at {:#010X}",
unit,
section.name,
align,
current_address
);
align = 4;
}
if current_address & (align as u32 - 1) != 0 {
return Err(Error::msg(format!(
"Invalid alignment for split: {} {} {:#010X}",
unit, section.name, current_address
)));
}
// Collect relocations; target_symbol will be updated later
let out_relocations = relocations
.range(current_address..file_end)
.map(|(_, o)| ObjReloc {
kind: o.kind,
address: o.address - current_address as u64,
target_symbol: o.target_symbol,
addend: o.addend,
})
.collect();
let out_section_idx = file.sections.len();
file.sections.push(ObjSection {
name: section.name.clone(),
kind: section.kind,
address: 0,
size: file_end as u64 - current_address as u64,
data,
align,
index: out_section_idx,
relocations: out_relocations,
original_address: current_address as u64,
file_offset: section.file_offset + (current_address as u64 - section.address),
});
// Add section symbols
for &symbol_idx in symbols.range(current_address..file_end).flat_map(|(_, vec)| vec) {
if symbol_idxs[symbol_idx].is_some() {
continue; // should never happen?
}
let symbol = &obj.symbols[symbol_idx];
symbol_idxs[symbol_idx] = Some(file.symbols.len());
file.symbols.push(ObjSymbol {
name: symbol.name.clone(),
demangled_name: symbol.demangled_name.clone(),
address: symbol.address - current_address as u64,
section: Some(out_section_idx),
size: symbol.size,
size_known: symbol.size_known,
flags: symbol.flags,
kind: symbol.kind,
});
}
current_address = file_end;
}
}
// Update relocations
for (obj_idx, out_obj) in objects.iter_mut().enumerate() {
let symbol_idxs = &mut object_symbols[obj_idx];
for section in &mut out_obj.sections {
for reloc in &mut section.relocations {
match symbol_idxs[reloc.target_symbol] {
Some(out_sym_idx) => {
reloc.target_symbol = out_sym_idx;
}
None => {
// Extern
let out_sym_idx = out_obj.symbols.len();
let target_sym = &obj.symbols[reloc.target_symbol];
symbol_idxs[reloc.target_symbol] = Some(out_sym_idx);
out_obj.symbols.push(ObjSymbol {
name: target_sym.name.clone(),
demangled_name: target_sym.demangled_name.clone(),
..Default::default()
});
reloc.target_symbol = out_sym_idx;
}
}
}
}
}
// Strip linker generated symbols
for obj in &mut objects {
for symbol in &mut obj.symbols {
if is_skip_symbol(&symbol.name) {
if symbol.section.is_some() {
log::debug!("Externing {:?} in {}", symbol, obj.name);
*symbol = ObjSymbol {
name: symbol.name.clone(),
demangled_name: symbol.demangled_name.clone(),
..Default::default()
};
}
} else if is_linker_symbol(&symbol.name) {
if let Some(section_idx) = symbol.section {
log::debug!("Skipping {:?} in {}", symbol, obj.name);
let section = &mut obj.sections[section_idx];
// TODO assuming end of file
section.size -= symbol.size;
section.data.truncate(section.data.len() - symbol.size as usize);
*symbol = ObjSymbol {
name: symbol.name.clone(),
demangled_name: symbol.demangled_name.clone(),
..Default::default()
};
}
}
}
}
Ok(objects)
}
/// mwld doesn't preserve the original section alignment values
fn default_section_align(section: &ObjSection) -> u64 {
match section.kind {
ObjSectionKind::Code => 4,
_ => match section.name.as_str() {
".ctors" | ".dtors" | "extab" | "extabindex" => 4,
_ => 8,
},
}
}
/// Linker-generated symbols to extern
#[inline]
fn is_skip_symbol(name: &str) -> bool { matches!(name, "_ctors" | "_dtors") }
/// Linker generated symbols to strip entirely
#[inline]
fn is_linker_symbol(name: &str) -> bool {
matches!(name, "_eti_init_info" | "_rom_copy_info" | "_bss_init_info")
}