537 lines
19 KiB
Rust
537 lines
19 KiB
Rust
use std::{
|
|
io,
|
|
io::{Read, Seek, SeekFrom, Write},
|
|
};
|
|
|
|
use anyhow::{anyhow, ensure, Result};
|
|
use cwdemangle::{demangle, DemangleOptions};
|
|
|
|
use crate::{
|
|
obj::{
|
|
ObjArchitecture, ObjInfo, ObjKind, ObjSection, ObjSectionKind, ObjSymbol, ObjSymbolFlagSet,
|
|
ObjSymbolFlags, ObjSymbolKind,
|
|
},
|
|
util::{
|
|
file::{read_c_string, read_string},
|
|
reader::{struct_size, Endian, FromReader, ToWriter, DYNAMIC_SIZE},
|
|
},
|
|
};
|
|
|
|
/// For RSO references to the DOL, the sections are hardcoded.
|
|
pub const DOL_SECTION_NAMES: [Option<&str>; 14] = [
|
|
None, // s_null
|
|
Some(".init"),
|
|
Some(".text"),
|
|
Some(".ctors"),
|
|
Some(".dtors"),
|
|
Some(".rodata"),
|
|
Some(".data"),
|
|
Some(".bss"),
|
|
Some(".sdata"),
|
|
Some(".sdata2"),
|
|
None, // s_zero
|
|
Some(".sbss"),
|
|
Some(".sbss2"),
|
|
None, // s_zero2
|
|
];
|
|
/// extabindex section index.
|
|
pub const DOL_SECTION_ETI: u32 = 241;
|
|
/// ABS symbol section index.
|
|
pub const DOL_SECTION_ABS: u32 = 65521;
|
|
|
|
pub struct RsoHeader {
|
|
// Pointer to the next module, forming a linked list. Always 0, filled in at runtime.
|
|
// pub next: u32,
|
|
// Pointer to the previous module, forming a linked list. Always 0, filled in at runtime.
|
|
// pub prev: u32,
|
|
/// Number of sections contained in the file.
|
|
pub num_sections: u32,
|
|
/// Offset to the section info table. Always 0x58.
|
|
pub section_info_offset: u32,
|
|
/// Offset to the module name. Can be 0, in which case this module doesn't contain a name string.
|
|
pub name_offset: u32,
|
|
/// Size of the module name string.
|
|
pub name_size: u32,
|
|
/// Module version number. Always 1.
|
|
pub version: u32,
|
|
/// Size of the BSS section, which is allocated at runtime (not included in the file).
|
|
pub bss_size: u32,
|
|
/// Section index of the prolog function, which is called when the module is linked.
|
|
/// 0 if this module doesn't contain a prolog function.
|
|
pub prolog_section: u8,
|
|
/// Section index of the epilog function, which is called when the module is unlinked.
|
|
/// 0 if this module doesn't contain an epilog function.
|
|
pub epilog_section: u8,
|
|
/// Section index of the unresolved function, which is called if the module attempts to call
|
|
/// an unlinked function. 0 if this module doesn't contain an unresolved function.
|
|
pub unresolved_section: u8,
|
|
// Section index of the BSS section. Always 0, filled in at runtime.
|
|
// pub bss_section: u8,
|
|
/// Section-relative offset of the prolog function.
|
|
/// 0 if this module doesn't contain a prolog function.
|
|
pub prolog_offset: u32,
|
|
/// Section-relative offset of the epilog function.
|
|
/// 0 if this module doesn't contain an epilog function.
|
|
pub epilog_offset: u32,
|
|
/// Section-relative offset of the unresolved function.
|
|
/// 0 if this module doesn't contain an unresolved function.
|
|
pub unresolved_offset: u32,
|
|
/// Absolute offset of the relocation table for internal relocations
|
|
/// (relocations for symbols within this module).
|
|
pub internal_rel_offset: u32,
|
|
/// Size of the relocation table for internal relocations.
|
|
pub internal_rel_size: u32,
|
|
/// Absolute offset of the relocation table for external relocations
|
|
/// (relocations for symbols within other modules).
|
|
pub external_rel_offset: u32,
|
|
/// Size of the relocation table for external relocations.
|
|
pub external_rel_size: u32,
|
|
/// Absolute offset of the symbol table for exports (symbols within this module).
|
|
pub export_table_offset: u32,
|
|
/// Size of the symbol table for exports.
|
|
pub export_table_size: u32,
|
|
/// Absolute offset of the string table containing export symbol names.
|
|
pub export_table_name_offset: u32,
|
|
/// Absolute offset of the symbol table for imports
|
|
/// (symbols within other modules, referenced by this one).
|
|
pub import_table_offset: u32,
|
|
/// Size of the symbol table for imports.
|
|
pub import_table_size: u32,
|
|
/// Absolute offset of the string table containing import symbol names.
|
|
pub import_table_name_offset: u32,
|
|
}
|
|
|
|
impl FromReader for RsoHeader {
|
|
type Args = ();
|
|
|
|
const STATIC_SIZE: usize = struct_size([
|
|
u32::STATIC_SIZE, // next
|
|
u32::STATIC_SIZE, // prev
|
|
u32::STATIC_SIZE, // num_sections
|
|
u32::STATIC_SIZE, // section_info_offset
|
|
u32::STATIC_SIZE, // name_offset
|
|
u32::STATIC_SIZE, // name_size
|
|
u32::STATIC_SIZE, // version
|
|
u32::STATIC_SIZE, // bss_size
|
|
u8::STATIC_SIZE, // prolog_section
|
|
u8::STATIC_SIZE, // epilog_section
|
|
u8::STATIC_SIZE, // unresolved_section
|
|
u8::STATIC_SIZE, // bss_section
|
|
u32::STATIC_SIZE, // prolog_offset
|
|
u32::STATIC_SIZE, // epilog_offset
|
|
u32::STATIC_SIZE, // unresolved_offset
|
|
u32::STATIC_SIZE, // internal_rel_offset
|
|
u32::STATIC_SIZE, // internal_rel_size
|
|
u32::STATIC_SIZE, // external_rel_offset
|
|
u32::STATIC_SIZE, // external_rel_size
|
|
u32::STATIC_SIZE, // export_table_offset
|
|
u32::STATIC_SIZE, // export_table_size
|
|
u32::STATIC_SIZE, // export_table_name_offset
|
|
u32::STATIC_SIZE, // import_table_offset
|
|
u32::STATIC_SIZE, // import_table_size
|
|
u32::STATIC_SIZE, // import_table_name_offset
|
|
]);
|
|
|
|
fn from_reader_args<R>(reader: &mut R, e: Endian, _args: Self::Args) -> io::Result<Self>
|
|
where R: Read + Seek + ?Sized {
|
|
let next = u32::from_reader(reader, e)?;
|
|
if next != 0 {
|
|
return Err(io::Error::new(
|
|
io::ErrorKind::InvalidData,
|
|
format!("Expected 'next' to be 0, got {:#X}", next),
|
|
));
|
|
}
|
|
let prev = u32::from_reader(reader, e)?;
|
|
if prev != 0 {
|
|
return Err(io::Error::new(
|
|
io::ErrorKind::InvalidData,
|
|
format!("Expected 'prev' to be 0, got {:#X}", prev),
|
|
));
|
|
}
|
|
let num_sections = u32::from_reader(reader, e)?;
|
|
let section_info_offset = u32::from_reader(reader, e)?;
|
|
let name_offset = u32::from_reader(reader, e)?;
|
|
let name_size = u32::from_reader(reader, e)?;
|
|
let version = u32::from_reader(reader, e)?;
|
|
let bss_size = u32::from_reader(reader, e)?;
|
|
let prolog_section = u8::from_reader(reader, e)?;
|
|
let epilog_section = u8::from_reader(reader, e)?;
|
|
let unresolved_section = u8::from_reader(reader, e)?;
|
|
let bss_section = u8::from_reader(reader, e)?;
|
|
if bss_section != 0 {
|
|
return Err(io::Error::new(
|
|
io::ErrorKind::InvalidData,
|
|
format!("Expected 'bssSection' to be 0, got {:#X}", bss_section),
|
|
));
|
|
}
|
|
let prolog_offset = u32::from_reader(reader, e)?;
|
|
let epilog_offset = u32::from_reader(reader, e)?;
|
|
let unresolved_offset = u32::from_reader(reader, e)?;
|
|
let internal_rel_offset = u32::from_reader(reader, e)?;
|
|
let internal_rel_size = u32::from_reader(reader, e)?;
|
|
let external_rel_offset = u32::from_reader(reader, e)?;
|
|
let external_rel_size = u32::from_reader(reader, e)?;
|
|
let export_table_offset = u32::from_reader(reader, e)?;
|
|
let export_table_size = u32::from_reader(reader, e)?;
|
|
let export_table_name_offset = u32::from_reader(reader, e)?;
|
|
let import_table_offset = u32::from_reader(reader, e)?;
|
|
let import_table_size = u32::from_reader(reader, e)?;
|
|
let import_table_name_offset = u32::from_reader(reader, e)?;
|
|
|
|
Ok(Self {
|
|
num_sections,
|
|
section_info_offset,
|
|
name_offset,
|
|
name_size,
|
|
version,
|
|
bss_size,
|
|
prolog_section,
|
|
epilog_section,
|
|
unresolved_section,
|
|
prolog_offset,
|
|
epilog_offset,
|
|
unresolved_offset,
|
|
internal_rel_offset,
|
|
internal_rel_size,
|
|
external_rel_offset,
|
|
external_rel_size,
|
|
export_table_offset,
|
|
export_table_size,
|
|
export_table_name_offset,
|
|
import_table_offset,
|
|
import_table_size,
|
|
import_table_name_offset,
|
|
})
|
|
}
|
|
}
|
|
|
|
#[derive(Copy, Clone, Debug)]
|
|
pub struct RsoSectionHeader {
|
|
/// Absolute offset of the section.
|
|
/// The lowest bit is set if the section is executable.
|
|
offset_and_flags: u32,
|
|
/// Size of the section.
|
|
size: u32,
|
|
}
|
|
|
|
impl FromReader for RsoSectionHeader {
|
|
type Args = ();
|
|
|
|
const STATIC_SIZE: usize = struct_size([
|
|
u32::STATIC_SIZE, // offset
|
|
u32::STATIC_SIZE, // size
|
|
]);
|
|
|
|
fn from_reader_args<R>(reader: &mut R, e: Endian, _args: Self::Args) -> io::Result<Self>
|
|
where R: Read + Seek + ?Sized {
|
|
Ok(Self {
|
|
offset_and_flags: u32::from_reader(reader, e)?,
|
|
size: u32::from_reader(reader, e)?,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl ToWriter for RsoSectionHeader {
|
|
fn to_writer<W>(&self, writer: &mut W, e: Endian) -> io::Result<()>
|
|
where W: Write + ?Sized {
|
|
self.offset_and_flags.to_writer(writer, e)?;
|
|
self.size.to_writer(writer, e)?;
|
|
Ok(())
|
|
}
|
|
|
|
fn write_size(&self) -> usize { Self::STATIC_SIZE }
|
|
}
|
|
|
|
impl RsoSectionHeader {
|
|
#[allow(dead_code)]
|
|
fn new(offset: u32, size: u32, exec: bool) -> Self {
|
|
Self { offset_and_flags: offset | (exec as u32), size }
|
|
}
|
|
|
|
pub fn offset(&self) -> u32 { self.offset_and_flags & !1 }
|
|
|
|
pub fn size(&self) -> u32 { self.size }
|
|
|
|
pub fn exec(&self) -> bool { self.offset_and_flags & 1 != 0 }
|
|
}
|
|
|
|
struct RsoRelocation {
|
|
/// Absolute offset of this relocation (relative to the start of the RSO file).
|
|
offset: u32,
|
|
/// For internal relocations, this is the section index of the symbol being patched to.
|
|
/// For external relocations, this is the index of the symbol within the import symbol table.
|
|
/// The lowest 8 bits are the relocation type.
|
|
id_and_type: u32,
|
|
/// For internal relocations, this is the section-relative offset of the target symbol.
|
|
/// For external relocations, this is unused and always 0 (the offset is calculated using the
|
|
/// import symbol table).
|
|
target_offset: u32,
|
|
}
|
|
|
|
impl FromReader for RsoRelocation {
|
|
type Args = ();
|
|
|
|
const STATIC_SIZE: usize = struct_size([
|
|
u32::STATIC_SIZE, // offset
|
|
u32::STATIC_SIZE, // id_and_type
|
|
u32::STATIC_SIZE, // sym_offset
|
|
]);
|
|
|
|
fn from_reader_args<R>(reader: &mut R, e: Endian, _args: Self::Args) -> io::Result<Self>
|
|
where R: Read + Seek + ?Sized {
|
|
Ok(Self {
|
|
offset: u32::from_reader(reader, e)?,
|
|
id_and_type: u32::from_reader(reader, e)?,
|
|
target_offset: u32::from_reader(reader, e)?,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl ToWriter for RsoRelocation {
|
|
fn to_writer<W>(&self, writer: &mut W, e: Endian) -> io::Result<()>
|
|
where W: Write + ?Sized {
|
|
self.offset.to_writer(writer, e)?;
|
|
self.id_and_type.to_writer(writer, e)?;
|
|
self.target_offset.to_writer(writer, e)?;
|
|
Ok(())
|
|
}
|
|
|
|
fn write_size(&self) -> usize { Self::STATIC_SIZE }
|
|
}
|
|
|
|
impl RsoRelocation {
|
|
#[allow(dead_code)]
|
|
pub fn new(offset: u32, id: u32, rel_type: u8, sym_offset: u32) -> Self {
|
|
Self { offset, id_and_type: (id << 8) | rel_type as u32, target_offset: sym_offset }
|
|
}
|
|
|
|
pub fn offset(&self) -> u32 { self.offset }
|
|
|
|
pub fn id(&self) -> u32 { (self.id_and_type & 0xFFFFFF00) >> 8 }
|
|
|
|
pub fn rel_type(&self) -> u8 { (self.id_and_type & 0xFF) as u8 }
|
|
|
|
pub fn sym_offset(&self) -> u32 { self.target_offset }
|
|
}
|
|
|
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
|
enum RsoSymbolKind {
|
|
Import,
|
|
Export,
|
|
}
|
|
|
|
struct RsoSymbol {
|
|
/// Relative offset into the name table pointed to in the header,
|
|
/// which points to the name of this symbol.
|
|
name_offset: u32,
|
|
/// The section-relative offset to the symbol. This is always 0 for imports.
|
|
offset: u32,
|
|
/// For exports, index of the section that contains this symbol.
|
|
/// For imports, appears to be an offset?
|
|
section_index: u32,
|
|
/// A hash of the symbol name. Only present for exports.
|
|
hash: Option<u32>,
|
|
}
|
|
|
|
impl FromReader for RsoSymbol {
|
|
type Args = RsoSymbolKind;
|
|
|
|
const STATIC_SIZE: usize = DYNAMIC_SIZE;
|
|
|
|
fn from_reader_args<R>(reader: &mut R, e: Endian, args: Self::Args) -> io::Result<Self>
|
|
where R: Read + Seek + ?Sized {
|
|
Ok(Self {
|
|
name_offset: u32::from_reader(reader, e)?,
|
|
offset: u32::from_reader(reader, e)?,
|
|
section_index: u32::from_reader(reader, e)?,
|
|
hash: if args == RsoSymbolKind::Export {
|
|
Some(u32::from_reader(reader, e)?)
|
|
} else {
|
|
None
|
|
},
|
|
})
|
|
}
|
|
}
|
|
|
|
impl ToWriter for RsoSymbol {
|
|
fn to_writer<W>(&self, writer: &mut W, e: Endian) -> io::Result<()>
|
|
where W: Write + ?Sized {
|
|
self.name_offset.to_writer(writer, e)?;
|
|
self.offset.to_writer(writer, e)?;
|
|
self.section_index.to_writer(writer, e)?;
|
|
if let Some(hash) = self.hash {
|
|
hash.to_writer(writer, e)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn write_size(&self) -> usize { Self::STATIC_SIZE }
|
|
}
|
|
|
|
pub fn process_rso<R>(reader: &mut R) -> Result<ObjInfo>
|
|
where R: Read + Seek + ?Sized {
|
|
let header = RsoHeader::from_reader(reader, Endian::Big)?;
|
|
let mut sections = Vec::with_capacity(header.num_sections as usize);
|
|
reader.seek(SeekFrom::Start(header.section_info_offset as u64))?;
|
|
let mut total_bss_size = 0;
|
|
for idx in 0..header.num_sections {
|
|
let section = RsoSectionHeader::from_reader(reader, Endian::Big)?;
|
|
let offset = section.offset();
|
|
let size = section.size();
|
|
if size == 0 {
|
|
continue;
|
|
}
|
|
|
|
let data = if offset == 0 {
|
|
vec![]
|
|
} else {
|
|
let position = reader.stream_position()?;
|
|
reader.seek(SeekFrom::Start(offset as u64))?;
|
|
let mut data = vec![0u8; size as usize];
|
|
reader.read_exact(&mut data)?;
|
|
reader.seek(SeekFrom::Start(position))?;
|
|
data
|
|
};
|
|
|
|
// println!("Section {} offset {:#X} size {:#X}", idx, offset, size);
|
|
|
|
sections.push(ObjSection {
|
|
name: format!(".section{}", idx),
|
|
kind: if offset == 0 {
|
|
ObjSectionKind::Bss
|
|
} else if section.exec() {
|
|
ObjSectionKind::Code
|
|
} else {
|
|
ObjSectionKind::Data
|
|
},
|
|
address: 0,
|
|
size: size as u64,
|
|
data,
|
|
align: 0,
|
|
elf_index: idx as usize,
|
|
relocations: Default::default(),
|
|
original_address: 0,
|
|
file_offset: offset as u64,
|
|
section_known: false,
|
|
splits: Default::default(),
|
|
});
|
|
if offset == 0 {
|
|
total_bss_size += size;
|
|
}
|
|
}
|
|
ensure!(
|
|
total_bss_size == header.bss_size,
|
|
"Mismatched BSS size: {:#X} != {:#X}",
|
|
total_bss_size,
|
|
header.bss_size
|
|
);
|
|
|
|
let mut symbols = Vec::new();
|
|
let mut add_symbol = |rel_section_idx: u8, offset: u32, name: &str| -> Result<()> {
|
|
if rel_section_idx > 0 {
|
|
let (section_index, _) = sections
|
|
.iter()
|
|
.enumerate()
|
|
.find(|&(_, section)| section.elf_index == rel_section_idx as usize)
|
|
.ok_or_else(|| anyhow!("Failed to locate {name} section {rel_section_idx}"))?;
|
|
log::debug!("Adding {name} section {rel_section_idx} offset {offset:#X}");
|
|
symbols.push(ObjSymbol {
|
|
name: name.to_string(),
|
|
address: offset as u64,
|
|
section: Some(section_index),
|
|
flags: ObjSymbolFlagSet(ObjSymbolFlags::Global.into()),
|
|
kind: ObjSymbolKind::Function,
|
|
..Default::default()
|
|
});
|
|
}
|
|
Ok(())
|
|
};
|
|
add_symbol(header.prolog_section, header.prolog_offset, "_prolog")?;
|
|
add_symbol(header.epilog_section, header.epilog_offset, "_epilog")?;
|
|
add_symbol(header.unresolved_section, header.unresolved_offset, "_unresolved")?;
|
|
|
|
reader.seek(SeekFrom::Start(header.external_rel_offset as u64))?;
|
|
while reader.stream_position()? < (header.external_rel_offset + header.external_rel_size) as u64
|
|
{
|
|
let reloc = RsoRelocation::from_reader(reader, Endian::Big)?;
|
|
log::debug!(
|
|
"Reloc offset: {:#X}, id: {}, type: {}, sym offset: {:#X}",
|
|
reloc.offset(),
|
|
reloc.id(),
|
|
reloc.rel_type(),
|
|
reloc.sym_offset()
|
|
);
|
|
}
|
|
|
|
reader.seek(SeekFrom::Start(header.export_table_offset as u64))?;
|
|
while reader.stream_position()? < (header.export_table_offset + header.export_table_size) as u64
|
|
{
|
|
let symbol = RsoSymbol::from_reader_args(reader, Endian::Big, RsoSymbolKind::Export)?;
|
|
let name =
|
|
read_c_string(reader, (header.export_table_name_offset + symbol.name_offset) as u64)?;
|
|
let calc = symbol_hash(&name);
|
|
let hash_n = symbol.hash.unwrap_or_default();
|
|
ensure!(
|
|
hash_n == calc,
|
|
"Mismatched calculated hash for symbol {}: {:#X} != {:#X}",
|
|
name,
|
|
hash_n,
|
|
calc
|
|
);
|
|
let demangled_name = demangle(&name, &DemangleOptions::default());
|
|
let section = sections
|
|
.iter()
|
|
.enumerate()
|
|
.find(|&(_, section)| section.elf_index == symbol.section_index as usize)
|
|
.map(|(idx, _)| idx)
|
|
// HACK: selfiles won't have any sections
|
|
.unwrap_or(symbol.section_index as usize);
|
|
log::debug!(
|
|
"Export: {}, sym off: {:#X}, section: {}, ELF hash: {:#X}",
|
|
demangled_name.as_deref().unwrap_or(&name),
|
|
symbol.offset,
|
|
symbol.section_index,
|
|
hash_n
|
|
);
|
|
symbols.push(ObjSymbol {
|
|
name,
|
|
demangled_name,
|
|
address: symbol.offset as u64,
|
|
section: Some(section),
|
|
..Default::default()
|
|
});
|
|
}
|
|
reader.seek(SeekFrom::Start(header.import_table_offset as u64))?;
|
|
while reader.stream_position()? < (header.import_table_offset + header.import_table_size) as u64
|
|
{
|
|
let symbol = RsoSymbol::from_reader_args(reader, Endian::Big, RsoSymbolKind::Import)?;
|
|
let name =
|
|
read_c_string(reader, (header.import_table_name_offset + symbol.name_offset) as u64)?;
|
|
log::debug!(
|
|
"Import: {}, sym off: {}, section: {}",
|
|
name,
|
|
symbol.offset,
|
|
symbol.section_index
|
|
);
|
|
}
|
|
|
|
let name = match header.name_offset {
|
|
0 => String::new(),
|
|
_ => read_string(reader, header.name_offset as u64, header.name_size as usize)?,
|
|
};
|
|
|
|
let obj = ObjInfo::new(ObjKind::Relocatable, ObjArchitecture::PowerPc, name, symbols, sections);
|
|
Ok(obj)
|
|
}
|
|
|
|
fn symbol_hash(s: &str) -> u32 {
|
|
s.bytes().fold(0u32, |hash, c| {
|
|
let mut m = (hash << 4).wrapping_add(c as u32);
|
|
let n = m & 0xF0000000;
|
|
if n != 0 {
|
|
m ^= n >> 24;
|
|
}
|
|
m & !n
|
|
})
|
|
}
|