#![deny(clippy::arithmetic_side_effects)]
use crate::arch::ppc64 as insn;
use crate::elf::read::consts::ppc64::*;
use crate::elf::read::{Big, ElfFormat, Relocation, Relocations};
use crate::elf::refs::{Def, Refs};
use super::{ApplyError, Branch, Class, ClassifyContext, ClassifyError, Kind, Width};
pub const ABI_VERSION: u32 = 1;
pub const OPD_ENTRY_SIZE: u64 = 24;
pub const LD_R2_40_R1: u32 = 0xe841_0028;
pub const STD_R2_40_R1: u32 = 0xf841_0028;
pub const CALL_STUB_SIZE: u64 = 28;
pub const OPD_SECTION: &[u8] = b".opd";
pub fn classify(
r_type: u32,
data: &[u8],
offset: u64,
context: ClassifyContext,
) -> Result<Class, ClassifyError> {
if super::ppc64::base_type(r_type) == R_PPC64_TOC {
return Ok(Class::new(Kind::GotBase, Width::W64));
}
super::ppc64::classify::<Big>(r_type, data, offset, context)
}
#[must_use]
pub fn descriptor<F: ElfFormat>(
refs: &Refs<'_, '_, F>,
target: &crate::elf::refs::Target,
addend: i64,
) -> Option<(usize, Relocation)> {
let Def::Section {
file,
section,
value,
} = target.def
else {
return None;
};
let value = value.checked_add_signed(addend)?;
let object = refs.files.get(file)?.object.as_ref()?;
let opd = object.section(section)?;
if opd.name != OPD_SECTION {
return None;
}
let Relocations::Rela(relas) = object
.elf
.relocation_section(opd.relocs, &object.section(opd.relocs)?.header)
.ok()??
.relocations
else {
return None;
};
let (mut low, mut high) = (0usize, relas.len());
while low < high {
let middle = low.wrapping_add(high.wrapping_sub(low) / 2);
let candidate = relas.get(middle)?;
match candidate.offset.cmp(&value) {
std::cmp::Ordering::Equal => {
return (candidate.r_type == R_PPC64_ADDR64).then_some((file, candidate));
}
std::cmp::Ordering::Less => low = middle.checked_add(1)?,
std::cmp::Ordering::Greater => high = middle,
}
}
None
}
pub fn write_call_stub(out: &mut [u8], slot: u64, toc: u64) -> Result<(), ApplyError> {
let offset = slot.wrapping_sub(toc) as i64;
if !insn::fits_signed(offset.wrapping_add(0x8000), 32) || offset & 3 != 0 {
return Err(ApplyError::Overflow);
}
let value = offset as u64;
let words = [
STD_R2_40_R1,
0x3d82_0000 | insn::ha(value), 0xe96c_0000 | insn::lo(value), 0xe98b_0000, 0xe84b_0008, insn::MTCTR_R12,
insn::BCTR,
];
insn::write_words::<Big>(out, 0, &words).map_err(|error| match error {
insn::EncodeError::Overflow => ApplyError::Overflow,
insn::EncodeError::BadInstruction => ApplyError::BadInstruction,
})
}
pub fn finish_call(out: &mut [u8], offset: u64, branch: Branch) -> Result<(), ApplyError> {
if !(branch.r_type == R_PPC64_REL24 && branch.via_stub) {
return super::ppc64::finish_call::<Big>(out, offset, branch);
}
let next = offset.wrapping_add(4);
let at = usize::try_from(next).map_err(|_| ApplyError::OutOfBounds)?;
if insn::read_insn::<Big>(out, at) == Some(insn::NOP) {
insn::write_insn::<Big>(out, at, LD_R2_40_R1).ok_or(ApplyError::OutOfBounds)?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::elf::arch::{GotKind, TlsMode};
fn exec() -> ClassifyContext {
ClassifyContext::static_exec(true)
}
#[test]
fn toc_is_the_toc_base() {
let toc = classify(R_PPC64_TOC, &[], 0, exec()).unwrap();
assert_eq!(toc.kind, Kind::GotBase);
assert_eq!(toc.width, Width::W64);
assert!(toc.uses_got_base());
}
#[test]
fn the_rest_is_classified_as_for_elfv2() {
let call = classify(R_PPC64_REL24, &[], 0, exec()).unwrap();
assert_eq!(call.kind, Kind::Pc);
assert_eq!(
call.width,
Width::Ppc(crate::arch::ppc64::Field::Rel24),
"the branch field is the ELFv2 one"
);
let entry = classify(R_PPC64_ADDR64, &[], 0, exec()).unwrap();
assert_eq!(entry.kind, Kind::Abs);
assert_eq!(entry.width, Width::W64);
let got = classify(R_PPC64_GOT16_LO_DS, &[], 0, exec()).unwrap();
assert_eq!(got.slot, GotKind::Address);
let tls = ClassifyContext {
tls: TlsMode::LocalExec,
..exec()
};
assert_eq!(
classify(R_PPC64_GOT_TPREL16_HA, &[], 0, tls).unwrap().kind,
Kind::IeToLe
);
}
#[test]
fn half_fields_name_the_halfword() {
use crate::arch::ppc64::{Field, d_offset};
assert_eq!(d_offset::<Big>(), 2);
assert_eq!(d_offset::<crate::elf::read::Little>(), 0);
assert!(Field::LoDsToc.rewrites_from_half());
assert!(Field::HaToc.rewrites_from_half());
assert!(!Field::Rel24.rewrites_from_half());
assert!(!Field::Ha.rewrites_from_half());
}
}