1000 lines
35 KiB
Rust
1000 lines
35 KiB
Rust
use std::{
|
|
cmp::Ordering,
|
|
io,
|
|
io::{Read, Seek, SeekFrom, Write},
|
|
};
|
|
|
|
use anyhow::{anyhow, bail, ensure, Context, Result};
|
|
use itertools::Itertools;
|
|
use object::{elf, Object, ObjectSection, ObjectSymbol};
|
|
use tracing::warn;
|
|
|
|
use crate::{
|
|
array_ref_mut,
|
|
obj::{
|
|
ObjArchitecture, ObjInfo, ObjKind, ObjRelocKind, ObjSection, ObjSectionKind, ObjSymbol,
|
|
ObjSymbolFlagSet, ObjSymbolFlags, ObjSymbolKind,
|
|
},
|
|
util::{
|
|
align_up,
|
|
reader::{struct_size, Endian, FromReader, ToWriter, DYNAMIC_SIZE},
|
|
split::default_section_align,
|
|
IntoCow,
|
|
},
|
|
};
|
|
|
|
/// Do not relocate anything, but accumulate the offset field for the next relocation offset calculation.
|
|
/// These types are used for referring to relocations that are more than 0xffff apart from each other.
|
|
pub const R_DOLPHIN_NOP: u32 = 201;
|
|
/// Change which section relocations are being applied to.
|
|
/// Set the offset into the section to 0.
|
|
pub const R_DOLPHIN_SECTION: u32 = 202;
|
|
/// Stop parsing the relocation list.
|
|
pub const R_DOLPHIN_END: u32 = 203;
|
|
/// Unknown.
|
|
#[allow(unused)]
|
|
pub const R_DOLPHIN_MRKREF: u32 = 204;
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct RelHeader {
|
|
/// Arbitrary identification number.
|
|
/// Must be unique amongst all RELs used by a game.
|
|
/// 0 is reserved for the DOL.
|
|
pub module_id: u32,
|
|
/// Pointer to next module.
|
|
/// Filled at runtime.
|
|
// pub next: u32,
|
|
/// Pointer to previous module.
|
|
/// Filled at runtime.
|
|
// pub prev: u32,
|
|
/// Number of sections in the file.
|
|
pub num_sections: u32,
|
|
/// Offset to the start of the section table.
|
|
pub section_info_offset: u32,
|
|
/// Offset in the external module name string table file.
|
|
pub name_offset: u32,
|
|
/// Size of the module name in bytes.
|
|
pub name_size: u32,
|
|
/// Version number of the REL file format.
|
|
pub version: u32,
|
|
/// Size of the `.bss` section.
|
|
pub bss_size: u32,
|
|
/// Offset to the start of the relocation table.
|
|
pub rel_offset: u32,
|
|
/// Offset to the start of the import table.
|
|
pub imp_offset: u32,
|
|
/// Size of the import table.
|
|
pub imp_size: u32,
|
|
/// Section containing the `_prolog` function.
|
|
pub prolog_section: u8,
|
|
/// Section containing the `_epilog` function.
|
|
pub epilog_section: u8,
|
|
/// Section containing the `_unresolved` function.
|
|
pub unresolved_section: u8,
|
|
/// Index into section table which bss is relative to.
|
|
/// Filled at runtime.
|
|
// pub bss_section: u8,
|
|
/// Offset into the section containing `_prolog`.
|
|
pub prolog_offset: u32,
|
|
/// Offset into the section containing `_epilog`.
|
|
pub epilog_offset: u32,
|
|
/// Offset into the section containing `_unresolved`.
|
|
pub unresolved_offset: u32,
|
|
/// (Version >= 2 only)
|
|
/// Alignment constraint on all sections.
|
|
pub align: Option<u32>,
|
|
/// (Version >= 2 only)
|
|
/// Alignment constraint on the `.bss` section.
|
|
pub bss_align: Option<u32>,
|
|
/// (Version >= 3 only)
|
|
/// If REL is linked with `OSLinkFixed` (instead of `OSLink`), the
|
|
/// space after this offset can be used for other purposes, like BSS.
|
|
pub fix_size: Option<u32>,
|
|
}
|
|
|
|
impl FromReader for RelHeader {
|
|
type Args = ();
|
|
|
|
// Differs by version
|
|
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 {
|
|
let module_id = u32::from_reader(reader, e)?;
|
|
let next = u32::from_reader(reader, e)?;
|
|
if next != 0 {
|
|
return Err(io::Error::new(io::ErrorKind::InvalidData, "Expected next == 0"));
|
|
}
|
|
let prev = u32::from_reader(reader, e)?;
|
|
if prev != 0 {
|
|
return Err(io::Error::new(io::ErrorKind::InvalidData, "Expected prev == 0"));
|
|
}
|
|
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)?;
|
|
if version > 3 {
|
|
return Err(io::Error::new(io::ErrorKind::InvalidData, "Unsupported REL version"));
|
|
}
|
|
let bss_size = u32::from_reader(reader, e)?;
|
|
let rel_offset = u32::from_reader(reader, e)?;
|
|
let imp_offset = u32::from_reader(reader, e)?;
|
|
let imp_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, "Expected bss_section == 0"));
|
|
}
|
|
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 align = if version >= 2 { Some(u32::from_reader(reader, e)?) } else { None };
|
|
let bss_align = if version >= 2 { Some(u32::from_reader(reader, e)?) } else { None };
|
|
let fix_size = if version >= 3 { Some(u32::from_reader(reader, e)?) } else { None };
|
|
Ok(Self {
|
|
module_id,
|
|
num_sections,
|
|
section_info_offset,
|
|
name_offset,
|
|
name_size,
|
|
version,
|
|
bss_size,
|
|
rel_offset,
|
|
imp_offset,
|
|
imp_size,
|
|
prolog_section,
|
|
epilog_section,
|
|
unresolved_section,
|
|
prolog_offset,
|
|
epilog_offset,
|
|
unresolved_offset,
|
|
align,
|
|
bss_align,
|
|
fix_size,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl ToWriter for RelHeader {
|
|
fn to_writer<W>(&self, writer: &mut W, e: Endian) -> io::Result<()>
|
|
where W: Write + ?Sized {
|
|
self.module_id.to_writer(writer, e)?;
|
|
0u32.to_writer(writer, e)?; // next
|
|
0u32.to_writer(writer, e)?; // prev
|
|
self.num_sections.to_writer(writer, e)?;
|
|
self.section_info_offset.to_writer(writer, e)?;
|
|
self.name_offset.to_writer(writer, e)?;
|
|
self.name_size.to_writer(writer, e)?;
|
|
self.version.to_writer(writer, e)?;
|
|
self.bss_size.to_writer(writer, e)?;
|
|
self.rel_offset.to_writer(writer, e)?;
|
|
self.imp_offset.to_writer(writer, e)?;
|
|
self.imp_size.to_writer(writer, e)?;
|
|
self.prolog_section.to_writer(writer, e)?;
|
|
self.epilog_section.to_writer(writer, e)?;
|
|
self.unresolved_section.to_writer(writer, e)?;
|
|
0u8.to_writer(writer, e)?; // bss_section
|
|
self.prolog_offset.to_writer(writer, e)?;
|
|
self.epilog_offset.to_writer(writer, e)?;
|
|
self.unresolved_offset.to_writer(writer, e)?;
|
|
if let Some(align) = self.align {
|
|
align.to_writer(writer, e)?;
|
|
}
|
|
if let Some(bss_align) = self.bss_align {
|
|
bss_align.to_writer(writer, e)?;
|
|
}
|
|
if let Some(fix_size) = self.fix_size {
|
|
fix_size.to_writer(writer, e)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn write_size(&self) -> usize {
|
|
const V1_SIZE: usize = struct_size([
|
|
u32::STATIC_SIZE, // module_id
|
|
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
|
|
u32::STATIC_SIZE, // rel_offset
|
|
u32::STATIC_SIZE, // imp_offset
|
|
u32::STATIC_SIZE, // imp_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
|
|
]);
|
|
const V2_SIZE: usize = V1_SIZE
|
|
+ struct_size([
|
|
u32::STATIC_SIZE, // align
|
|
u32::STATIC_SIZE, // bss_align
|
|
]);
|
|
const V3_SIZE: usize = V2_SIZE + u32::STATIC_SIZE; // fix_size
|
|
match self.version {
|
|
1 => V1_SIZE,
|
|
2 => V2_SIZE,
|
|
3 => V3_SIZE,
|
|
_ => panic!("Unsupported REL version {}", self.version),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Copy, Clone, Debug)]
|
|
struct RelImport {
|
|
module_id: u32,
|
|
offset: u32,
|
|
}
|
|
|
|
impl FromReader for RelImport {
|
|
type Args = ();
|
|
|
|
const STATIC_SIZE: usize = struct_size([
|
|
u32::STATIC_SIZE, // module_id
|
|
u32::STATIC_SIZE, // offset
|
|
]);
|
|
|
|
fn from_reader_args<R>(reader: &mut R, e: Endian, _args: Self::Args) -> io::Result<Self>
|
|
where R: Read + Seek + ?Sized {
|
|
Ok(Self { module_id: u32::from_reader(reader, e)?, offset: u32::from_reader(reader, e)? })
|
|
}
|
|
}
|
|
|
|
impl ToWriter for RelImport {
|
|
fn to_writer<W>(&self, writer: &mut W, e: Endian) -> io::Result<()>
|
|
where W: Write + ?Sized {
|
|
self.module_id.to_writer(writer, e)?;
|
|
self.offset.to_writer(writer, e)?;
|
|
Ok(())
|
|
}
|
|
|
|
fn write_size(&self) -> usize { Self::STATIC_SIZE }
|
|
}
|
|
|
|
#[derive(Copy, Clone, Debug)]
|
|
pub struct RelSectionHeader {
|
|
offset_and_flags: u32,
|
|
size: u32,
|
|
}
|
|
|
|
impl FromReader for RelSectionHeader {
|
|
type Args = ();
|
|
|
|
const STATIC_SIZE: usize = struct_size([
|
|
u32::STATIC_SIZE, // offset_and_flags
|
|
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 RelSectionHeader {
|
|
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 RelSectionHeader {
|
|
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 }
|
|
}
|
|
|
|
#[derive(Copy, Clone, Debug)]
|
|
struct RelRelocRaw {
|
|
offset: u16,
|
|
kind: u8,
|
|
section: u8,
|
|
addend: u32,
|
|
}
|
|
|
|
impl FromReader for RelRelocRaw {
|
|
type Args = ();
|
|
|
|
const STATIC_SIZE: usize = struct_size([
|
|
u16::STATIC_SIZE, // offset
|
|
u8::STATIC_SIZE, // kind
|
|
u8::STATIC_SIZE, // section
|
|
u32::STATIC_SIZE, // addend
|
|
]);
|
|
|
|
fn from_reader_args<R>(reader: &mut R, e: Endian, _args: Self::Args) -> io::Result<Self>
|
|
where R: Read + Seek + ?Sized {
|
|
Ok(Self {
|
|
offset: u16::from_reader(reader, e)?,
|
|
kind: u8::from_reader(reader, e)?,
|
|
section: u8::from_reader(reader, e)?,
|
|
addend: u32::from_reader(reader, e)?,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl ToWriter for RelRelocRaw {
|
|
fn to_writer<W>(&self, writer: &mut W, e: Endian) -> io::Result<()>
|
|
where W: Write + ?Sized {
|
|
self.offset.to_writer(writer, e)?;
|
|
self.kind.to_writer(writer, e)?;
|
|
self.section.to_writer(writer, e)?;
|
|
self.addend.to_writer(writer, e)?;
|
|
Ok(())
|
|
}
|
|
|
|
fn write_size(&self) -> usize { Self::STATIC_SIZE }
|
|
}
|
|
|
|
pub fn process_rel_header<R>(reader: &mut R) -> Result<RelHeader>
|
|
where R: Read + Seek + ?Sized {
|
|
RelHeader::from_reader(reader, Endian::Big).context("Failed to read REL header")
|
|
}
|
|
|
|
pub fn process_rel_sections<R>(
|
|
reader: &mut R,
|
|
header: &RelHeader,
|
|
) -> Result<Vec<RelSectionHeader>>
|
|
where
|
|
R: Read + Seek + ?Sized,
|
|
{
|
|
let mut sections = Vec::with_capacity(header.num_sections as usize);
|
|
reader.seek(SeekFrom::Start(header.section_info_offset as u64))?;
|
|
for idx in 0..header.num_sections {
|
|
let section = RelSectionHeader::from_reader(reader, Endian::Big)
|
|
.with_context(|| format!("Failed to read REL section header {}", idx))?;
|
|
sections.push(section);
|
|
}
|
|
Ok(sections)
|
|
}
|
|
|
|
pub fn process_rel<R>(reader: &mut R, name: &str) -> Result<(RelHeader, ObjInfo)>
|
|
where R: Read + Seek + ?Sized {
|
|
let header = process_rel_header(reader)?;
|
|
let mut sections = Vec::with_capacity(header.num_sections as usize);
|
|
let mut text_section = None;
|
|
let mut total_bss_size = 0;
|
|
for (idx, section) in process_rel_sections(reader, &header)?.iter().enumerate() {
|
|
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).with_context(|| {
|
|
format!("Failed to read REL section {} data with size {:#X}", idx, size)
|
|
})?;
|
|
reader.seek(SeekFrom::Start(position))?;
|
|
data
|
|
};
|
|
|
|
let (name, kind, section_known) = if offset == 0 {
|
|
ensure!(total_bss_size == 0, "Multiple BSS sections in REL");
|
|
total_bss_size = size;
|
|
(".bss".to_string(), ObjSectionKind::Bss, true)
|
|
} else if section.exec() {
|
|
ensure!(text_section.is_none(), "Multiple text sections in REL");
|
|
text_section = Some(idx as u8);
|
|
(".text".to_string(), ObjSectionKind::Code, true)
|
|
} else {
|
|
(format!(".section{}", idx), ObjSectionKind::Data, false)
|
|
};
|
|
sections.push(ObjSection {
|
|
name,
|
|
kind,
|
|
address: 0,
|
|
size: size as u64,
|
|
data,
|
|
align: match offset {
|
|
0 => header.bss_align,
|
|
_ => None, // determined later
|
|
}
|
|
.unwrap_or_default() as u64,
|
|
elf_index: idx,
|
|
relocations: Default::default(),
|
|
original_address: 0,
|
|
file_offset: offset as u64,
|
|
section_known,
|
|
splits: Default::default(),
|
|
});
|
|
}
|
|
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, force_active: bool| -> 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}");
|
|
let mut flags = ObjSymbolFlagSet(ObjSymbolFlags::Global.into());
|
|
if force_active {
|
|
flags.set_force_active(true);
|
|
}
|
|
symbols.push(ObjSymbol {
|
|
name: name.to_string(),
|
|
address: offset as u64,
|
|
section: Some(section_index),
|
|
flags,
|
|
kind: ObjSymbolKind::Function,
|
|
..Default::default()
|
|
});
|
|
}
|
|
Ok(())
|
|
};
|
|
add_symbol(header.prolog_section, header.prolog_offset, "_prolog", true)?;
|
|
add_symbol(header.epilog_section, header.epilog_offset, "_epilog", true)?;
|
|
add_symbol(header.unresolved_section, header.unresolved_offset, "_unresolved", true)?;
|
|
|
|
let mut unresolved_relocations = Vec::new();
|
|
let mut imp_idx = 0;
|
|
let imp_end = (header.imp_offset + header.imp_size) as u64;
|
|
reader.seek(SeekFrom::Start(header.imp_offset as u64))?;
|
|
while reader.stream_position()? < imp_end {
|
|
let import = RelImport::from_reader(reader, Endian::Big)?;
|
|
|
|
if imp_idx == 0 {
|
|
ensure!(
|
|
import.offset == header.rel_offset,
|
|
"imp index 0 offset mismatch: {:#X} != {:#X}",
|
|
import.offset,
|
|
header.rel_offset
|
|
);
|
|
}
|
|
imp_idx += 1;
|
|
|
|
if import.module_id == header.module_id {
|
|
if let Some(fix_size) = header.fix_size {
|
|
ensure!(
|
|
fix_size == import.offset,
|
|
"fix_size mismatch: {:#X} != {:#X}",
|
|
fix_size,
|
|
import.offset
|
|
);
|
|
}
|
|
}
|
|
|
|
let position = reader.stream_position()?;
|
|
reader.seek(SeekFrom::Start(import.offset as u64))?;
|
|
let mut address = 0u32;
|
|
let mut section = u8::MAX;
|
|
loop {
|
|
let reloc = RelRelocRaw::from_reader(reader, Endian::Big)?;
|
|
let kind = match reloc.kind as u32 {
|
|
elf::R_PPC_NONE => continue,
|
|
elf::R_PPC_ADDR32 | elf::R_PPC_UADDR32 => ObjRelocKind::Absolute,
|
|
// elf::R_PPC_ADDR24 => ObjRelocKind::PpcAddr24,
|
|
// elf::R_PPC_ADDR16 => ObjRelocKind::PpcAddr16,
|
|
elf::R_PPC_ADDR16_LO => ObjRelocKind::PpcAddr16Lo,
|
|
elf::R_PPC_ADDR16_HI => ObjRelocKind::PpcAddr16Hi,
|
|
elf::R_PPC_ADDR16_HA => ObjRelocKind::PpcAddr16Ha,
|
|
// elf::R_PPC_ADDR14 => ObjRelocKind::PpcAddr14,
|
|
// elf::R_PPC_ADDR14_BRTAKEN => ObjRelocKind::PpcAddr14BrTaken,
|
|
// elf::R_PPC_ADDR14_BRNTAKEN => ObjRelocKind::PpcAddr14BrnTaken,
|
|
elf::R_PPC_REL24 => ObjRelocKind::PpcRel24,
|
|
elf::R_PPC_REL14 => ObjRelocKind::PpcRel14,
|
|
// elf::R_PPC_REL14_BRTAKEN => ObjRelocKind::PpcRel14BrTaken,
|
|
// elf::R_PPC_REL14_BRNTAKEN => ObjRelocKind::PpcRel14BrnTaken,
|
|
R_DOLPHIN_NOP => {
|
|
address += reloc.offset as u32;
|
|
continue;
|
|
}
|
|
R_DOLPHIN_SECTION => {
|
|
address = 0;
|
|
section = reloc.section;
|
|
continue;
|
|
}
|
|
R_DOLPHIN_END => break,
|
|
// R_DOLPHIN_MRKREF => ?
|
|
reloc_type => bail!("Unhandled REL relocation type {reloc_type}"),
|
|
};
|
|
address += reloc.offset as u32;
|
|
let reloc = RelReloc {
|
|
kind,
|
|
section,
|
|
address: address & !3,
|
|
module_id: import.module_id,
|
|
target_section: reloc.section,
|
|
addend: reloc.addend,
|
|
original_section: section,
|
|
original_target_section: reloc.section,
|
|
};
|
|
unresolved_relocations.push(reloc);
|
|
}
|
|
reader.seek(SeekFrom::Start(position))?;
|
|
}
|
|
|
|
log::debug!("Read REL ID {}", header.module_id);
|
|
let mut obj = ObjInfo::new(
|
|
ObjKind::Relocatable,
|
|
ObjArchitecture::PowerPc,
|
|
name.to_string(),
|
|
symbols,
|
|
sections,
|
|
);
|
|
obj.module_id = header.module_id;
|
|
obj.unresolved_relocations = unresolved_relocations;
|
|
Ok((header, obj))
|
|
}
|
|
|
|
/// REL relocation.
|
|
#[derive(Debug, Clone)]
|
|
pub struct RelReloc {
|
|
/// Relocation kind.
|
|
pub kind: ObjRelocKind,
|
|
/// Source section index.
|
|
pub section: u8,
|
|
/// Source address.
|
|
pub address: u32,
|
|
/// Target module ID.
|
|
pub module_id: u32,
|
|
/// Target section index.
|
|
pub target_section: u8,
|
|
/// Target addend within section.
|
|
/// If target module ID is 0 (DOL), this is an absolute address.
|
|
pub addend: u32,
|
|
|
|
// EXTRA for matching
|
|
pub original_section: u8,
|
|
pub original_target_section: u8,
|
|
}
|
|
|
|
#[inline]
|
|
fn reloc_can_be_applied(_module_id: u32, rel_reloc: &RelReloc) -> bool {
|
|
matches!(rel_reloc.kind, ObjRelocKind::PpcRel24 | ObjRelocKind::PpcRel14)
|
|
}
|
|
|
|
#[inline]
|
|
fn skip_reloc(module_id: u32, rel_reloc: &RelReloc) -> bool {
|
|
rel_reloc.module_id == module_id
|
|
&& rel_reloc.section == rel_reloc.target_section
|
|
&& matches!(rel_reloc.kind, ObjRelocKind::PpcRel24 | ObjRelocKind::PpcRel14)
|
|
}
|
|
|
|
fn apply_relocation(
|
|
data: &mut [u8],
|
|
module_id: u32,
|
|
rel_reloc: &RelReloc,
|
|
header: &RelHeader,
|
|
) -> Result<()> {
|
|
let diff = if rel_reloc.module_id == module_id && rel_reloc.section == rel_reloc.target_section
|
|
{
|
|
rel_reloc.addend as i32 - rel_reloc.address as i32
|
|
} else if header.unresolved_section == rel_reloc.section {
|
|
header.unresolved_offset as i32 - rel_reloc.address as i32
|
|
} else {
|
|
return Ok(());
|
|
};
|
|
let ins_ref = array_ref_mut!(data, rel_reloc.address as usize, 4);
|
|
let mut ins = u32::from_be_bytes(*ins_ref);
|
|
match rel_reloc.kind {
|
|
ObjRelocKind::PpcRel24 => {
|
|
ensure!((-0x2000000..0x2000000).contains(&diff), "R_PPC_REL24 relocation out of range");
|
|
ins = (ins & !0x3fffffc) | (diff as u32 & 0x3fffffc);
|
|
}
|
|
ObjRelocKind::PpcRel14 => {
|
|
ensure!((-0x2000..0x2000).contains(&diff), "R_PPC_REL14 relocation out of range");
|
|
ins = (ins & !0xfffc) | (diff as u32 & 0xfffc);
|
|
}
|
|
kind => bail!("Unsupported relocation kind {:?}", kind),
|
|
}
|
|
*ins_ref = ins.to_be_bytes();
|
|
Ok(())
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct RelWriteInfo {
|
|
/// REL module ID.
|
|
pub module_id: u32,
|
|
/// REL version.
|
|
pub version: u32,
|
|
/// Override `name_offset` in the REL header.
|
|
/// Useful for matching RELs without the original string table.
|
|
pub name_offset: Option<u32>,
|
|
/// Override `name_size` in the REL header.
|
|
/// Useful for matching RELs without the original string table.
|
|
pub name_size: Option<u32>,
|
|
/// Override `align` in the REL header.
|
|
pub align: Option<u32>,
|
|
/// Override `bss_align` in the REL header.
|
|
pub bss_align: Option<u32>,
|
|
/// Override the number of sections in the file.
|
|
/// Useful for matching RELs that included debug sections.
|
|
pub section_count: Option<usize>,
|
|
/// If true, don't print warnings about overriding values.
|
|
pub quiet: bool,
|
|
/// Override individual section alignment in the file.
|
|
pub section_align: Option<Vec<u32>>,
|
|
}
|
|
|
|
pub const PERMITTED_SECTIONS: [&str; 7] =
|
|
[".init", ".text", ".ctors", ".dtors", ".rodata", ".data", ".bss"];
|
|
|
|
pub fn is_permitted_section(section: &object::Section) -> bool {
|
|
matches!(section.name(), Ok(name) if PERMITTED_SECTIONS.contains(&name))
|
|
}
|
|
|
|
pub fn should_write_section(section: &object::Section) -> bool {
|
|
section.kind() != object::SectionKind::UninitializedData
|
|
}
|
|
|
|
pub fn write_rel<W>(
|
|
w: &mut W,
|
|
info: &RelWriteInfo,
|
|
file: &object::File,
|
|
mut relocations: Vec<RelReloc>,
|
|
) -> Result<()>
|
|
where
|
|
W: Write + Seek + ?Sized,
|
|
{
|
|
relocations.sort_by(|a, b| {
|
|
if a.module_id == 0 {
|
|
if b.module_id == 0 {
|
|
Ordering::Equal
|
|
} else {
|
|
Ordering::Greater
|
|
}
|
|
} else if a.module_id == info.module_id {
|
|
if b.module_id == 0 {
|
|
Ordering::Less
|
|
} else if b.module_id == info.module_id {
|
|
Ordering::Equal
|
|
} else {
|
|
Ordering::Greater
|
|
}
|
|
} else if b.module_id == 0 || b.module_id == info.module_id {
|
|
Ordering::Less
|
|
} else {
|
|
a.module_id.cmp(&b.module_id)
|
|
}
|
|
.then(a.section.cmp(&b.section))
|
|
.then(a.address.cmp(&b.address))
|
|
});
|
|
|
|
let mut apply_relocations = vec![];
|
|
relocations.retain(|r| {
|
|
if !is_permitted_section(
|
|
&file.section_by_index(object::SectionIndex(r.original_section as usize)).unwrap(),
|
|
) {
|
|
return false;
|
|
}
|
|
if reloc_can_be_applied(info.module_id, r) {
|
|
apply_relocations.push(r.clone());
|
|
!skip_reloc(info.module_id, r)
|
|
} else {
|
|
true
|
|
}
|
|
});
|
|
|
|
/// Get the alignment of a section, checking for overrides.
|
|
/// permitted_section_idx increments whenever a permitted section is encountered,
|
|
/// rather than being the raw ELF section index.
|
|
fn section_align(
|
|
permitted_section_idx: usize,
|
|
section: &object::Section,
|
|
info: &RelWriteInfo,
|
|
) -> u32 {
|
|
info.section_align
|
|
.as_ref()
|
|
.and_then(|v| v.get(permitted_section_idx))
|
|
.cloned()
|
|
.unwrap_or(section.align() as u32)
|
|
}
|
|
|
|
let mut align = file
|
|
.sections()
|
|
.filter(is_permitted_section)
|
|
.enumerate()
|
|
.map(|(i, s)| section_align(i, &s, info))
|
|
.max()
|
|
.unwrap_or(0);
|
|
let bss = file
|
|
.sections()
|
|
.filter(is_permitted_section)
|
|
.enumerate()
|
|
.find(|(_, s)| s.name() == Ok(".bss"));
|
|
let mut bss_align = bss.as_ref().map(|(i, s)| section_align(*i, s, info)).unwrap_or(1);
|
|
let mut num_sections = file.sections().count() as u32;
|
|
|
|
// Apply overrides
|
|
if let Some(section_count) = info.section_count {
|
|
if section_count != num_sections as usize && !info.quiet {
|
|
warn!(from = num_sections, to = section_count, "Overriding section count");
|
|
}
|
|
num_sections = section_count as u32;
|
|
}
|
|
if info.version >= 2 {
|
|
if let Some(align_override) = info.align {
|
|
if align_override != align && !info.quiet {
|
|
warn!(from = align, to = align_override, "Overriding alignment");
|
|
}
|
|
align = align_override;
|
|
}
|
|
if let Some(bss_align_override) = info.bss_align {
|
|
if bss_align_override != bss_align && !info.quiet {
|
|
warn!(from = bss_align, to = bss_align_override, "Overriding BSS alignment");
|
|
}
|
|
bss_align = bss_align_override;
|
|
}
|
|
}
|
|
|
|
let mut header = RelHeader {
|
|
module_id: info.module_id,
|
|
num_sections,
|
|
section_info_offset: 0, // Calculated below
|
|
name_offset: info.name_offset.unwrap_or(0),
|
|
name_size: info.name_size.unwrap_or(0),
|
|
version: info.version,
|
|
bss_size: bss.as_ref().map(|(_, s)| s.size() as u32).unwrap_or(0),
|
|
rel_offset: 0,
|
|
imp_offset: 0,
|
|
imp_size: 0,
|
|
prolog_section: 0,
|
|
epilog_section: 0,
|
|
unresolved_section: 0,
|
|
prolog_offset: 0,
|
|
epilog_offset: 0,
|
|
unresolved_offset: 0,
|
|
align: if info.version >= 2 { Some(align) } else { None },
|
|
bss_align: if info.version >= 2 { Some(bss_align) } else { None },
|
|
fix_size: None,
|
|
};
|
|
let mut offset = header.write_size() as u32;
|
|
header.section_info_offset = offset;
|
|
offset += num_sections * RelSectionHeader::STATIC_SIZE as u32;
|
|
let section_data_offset = offset;
|
|
for (idx, section) in file.sections().filter(is_permitted_section).enumerate() {
|
|
if !should_write_section(§ion) {
|
|
continue;
|
|
}
|
|
let align = section_align(idx, §ion, info) - 1;
|
|
offset = (offset + align) & !align;
|
|
offset += section.size() as u32;
|
|
}
|
|
header.imp_offset = offset;
|
|
let imp_count = relocations.iter().map(|r| r.module_id).dedup().count();
|
|
header.imp_size = imp_count as u32 * RelImport::STATIC_SIZE as u32;
|
|
offset += header.imp_size;
|
|
header.rel_offset = offset;
|
|
|
|
let mut imp_entries = Vec::<RelImport>::with_capacity(imp_count);
|
|
let mut raw_relocations = vec![];
|
|
{
|
|
let mut address = 0u32;
|
|
let mut section = u8::MAX;
|
|
let mut last_module_id = u32::MAX;
|
|
for reloc in &relocations {
|
|
if reloc.module_id != last_module_id {
|
|
if last_module_id != u32::MAX {
|
|
raw_relocations.push(RelRelocRaw {
|
|
offset: 0,
|
|
kind: R_DOLPHIN_END as u8,
|
|
section: 0,
|
|
addend: 0,
|
|
});
|
|
offset += 8;
|
|
}
|
|
imp_entries.push(RelImport { module_id: reloc.module_id, offset });
|
|
section = u8::MAX;
|
|
last_module_id = reloc.module_id;
|
|
}
|
|
if info.version >= 3
|
|
&& header.fix_size.is_none()
|
|
&& (reloc.module_id == 0 || reloc.module_id == info.module_id)
|
|
{
|
|
header.fix_size = Some(offset);
|
|
}
|
|
if reloc.section != section {
|
|
raw_relocations.push(RelRelocRaw {
|
|
offset: 0,
|
|
kind: R_DOLPHIN_SECTION as u8,
|
|
section: reloc.section,
|
|
addend: 0,
|
|
});
|
|
offset += 8;
|
|
address = 0;
|
|
section = reloc.section;
|
|
}
|
|
let mut reloc_offset = reloc.address - address;
|
|
while reloc_offset > 0xffff {
|
|
raw_relocations.push(RelRelocRaw {
|
|
offset: 0xffff,
|
|
kind: R_DOLPHIN_NOP as u8,
|
|
section: 0,
|
|
addend: 0,
|
|
});
|
|
offset += 8;
|
|
reloc_offset -= 0xffff;
|
|
}
|
|
raw_relocations.push(RelRelocRaw {
|
|
offset: reloc_offset as u16,
|
|
kind: match reloc.kind {
|
|
ObjRelocKind::Absolute => elf::R_PPC_ADDR32,
|
|
ObjRelocKind::PpcAddr16Lo => elf::R_PPC_ADDR16_LO,
|
|
ObjRelocKind::PpcAddr16Hi => elf::R_PPC_ADDR16_HI,
|
|
ObjRelocKind::PpcAddr16Ha => elf::R_PPC_ADDR16_HA,
|
|
ObjRelocKind::PpcRel24 => elf::R_PPC_REL24,
|
|
ObjRelocKind::PpcRel14 => elf::R_PPC_REL14,
|
|
_ => bail!("Unsupported relocation kind {:?}", reloc.kind),
|
|
} as u8,
|
|
section: reloc.target_section,
|
|
addend: reloc.addend,
|
|
});
|
|
address = reloc.address;
|
|
offset += 8;
|
|
}
|
|
}
|
|
raw_relocations.push(RelRelocRaw {
|
|
offset: 0,
|
|
kind: R_DOLPHIN_END as u8,
|
|
section: 0,
|
|
addend: 0,
|
|
});
|
|
offset += 8;
|
|
|
|
for symbol in file.symbols().filter(|s| s.is_definition()) {
|
|
let Some(symbol_section) = symbol.section_index() else {
|
|
continue;
|
|
};
|
|
match symbol.name() {
|
|
Ok("_prolog") => {
|
|
header.prolog_section = symbol_section.0 as u8;
|
|
header.prolog_offset = symbol.address() as u32;
|
|
}
|
|
Ok("_epilog") => {
|
|
header.epilog_section = symbol_section.0 as u8;
|
|
header.epilog_offset = symbol.address() as u32;
|
|
}
|
|
Ok("_unresolved") => {
|
|
header.unresolved_section = symbol_section.0 as u8;
|
|
header.unresolved_offset = symbol.address() as u32;
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
header.to_writer(w, Endian::Big)?;
|
|
ensure!(w.stream_position()? as u32 == header.section_info_offset);
|
|
let mut current_data_offset = section_data_offset;
|
|
let mut permitted_section_idx = 0;
|
|
for section_index in 0..num_sections {
|
|
let Ok(section) = file.section_by_index(object::SectionIndex(section_index as usize))
|
|
else {
|
|
RelSectionHeader::new(0, 0, false).to_writer(w, Endian::Big)?;
|
|
continue;
|
|
};
|
|
if is_permitted_section(§ion) {
|
|
let mut offset = 0;
|
|
if should_write_section(§ion) {
|
|
let align = section_align(permitted_section_idx, §ion, info) - 1;
|
|
current_data_offset = (current_data_offset + align) & !align;
|
|
offset = current_data_offset;
|
|
current_data_offset += section.size() as u32;
|
|
}
|
|
RelSectionHeader::new(
|
|
offset,
|
|
section.size() as u32,
|
|
section.kind() == object::SectionKind::Text,
|
|
)
|
|
.to_writer(w, Endian::Big)?;
|
|
permitted_section_idx += 1;
|
|
} else {
|
|
RelSectionHeader::new(0, 0, false).to_writer(w, Endian::Big)?;
|
|
}
|
|
}
|
|
ensure!(w.stream_position()? as u32 == section_data_offset);
|
|
for (idx, section) in file.sections().filter(is_permitted_section).enumerate() {
|
|
if !should_write_section(§ion) {
|
|
continue;
|
|
}
|
|
|
|
fn calculate_padding(position: u64, align: u64) -> u64 {
|
|
let align = align - 1;
|
|
((position + align) & !align) - position
|
|
}
|
|
let position = w.stream_position()?;
|
|
let align = section_align(idx, §ion, info);
|
|
w.write_all(&vec![0u8; calculate_padding(position, align as u64) as usize])?;
|
|
|
|
let section_index = section.index().0 as u8;
|
|
let mut section_data = section.uncompressed_data()?;
|
|
if apply_relocations.iter().any(|r| r.original_section == section_index) {
|
|
let mut data = section_data.into_owned();
|
|
for reloc in apply_relocations.iter().filter(|r| r.original_section == section_index) {
|
|
apply_relocation(&mut data, info.module_id, reloc, &header)?;
|
|
}
|
|
section_data = data.into_cow();
|
|
}
|
|
w.write_all(§ion_data)?;
|
|
}
|
|
ensure!(w.stream_position()? as u32 == header.imp_offset);
|
|
for entry in imp_entries {
|
|
entry.to_writer(w, Endian::Big)?;
|
|
}
|
|
ensure!(w.stream_position()? as u32 == header.rel_offset);
|
|
for reloc in raw_relocations {
|
|
reloc.to_writer(w, Endian::Big)?;
|
|
}
|
|
ensure!(w.stream_position()? as u32 == offset);
|
|
Ok(())
|
|
}
|
|
|
|
/// Determines REL section alignment based on its file offset.
|
|
pub fn update_rel_section_alignment(obj: &mut ObjInfo, header: &RelHeader) -> Result<()> {
|
|
let mut last_offset = header.section_info_offset + header.num_sections * 8;
|
|
for (_, section) in obj.sections.iter_mut() {
|
|
let prev_offset = last_offset;
|
|
last_offset = (section.file_offset + section.size) as u32;
|
|
|
|
if section.align > 0 {
|
|
// Already set
|
|
continue;
|
|
}
|
|
|
|
if section.section_known {
|
|
// Try the default section alignment for known sections
|
|
let default_align = default_section_align(section);
|
|
if align_up(prev_offset, default_align as u32) == section.file_offset as u32 {
|
|
section.align = default_align;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Work our way down from the REL header alignment
|
|
let mut align = header.align.unwrap_or(32);
|
|
while align >= 4 {
|
|
if align_up(prev_offset, align) == section.file_offset as u32 {
|
|
section.align = align as u64;
|
|
break;
|
|
}
|
|
align /= 2;
|
|
}
|
|
|
|
if section.align == 0 {
|
|
bail!(
|
|
"Failed to determine alignment for REL section {}: {:#X} -> {:#X}",
|
|
section.name,
|
|
prev_offset,
|
|
section.file_offset
|
|
);
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|