#![deny(clippy::arithmetic_side_effects)]
pub const NOP: u32 = 0x6000_0000;
pub const LD_R2_24_R1: u32 = 0xe841_0018;
pub const STD_R2_24_R1: u32 = 0xf841_0018;
pub const MTCTR_R12: u32 = 0x7d89_03a6;
pub const BCTR: u32 = 0x4e80_0420;
pub const B: u32 = 0x4800_0000;
pub const ADDIS_R3_R13: u32 = 0x3c6d_0000;
pub const ADDI_R3_R3: u32 = 0x3863_0000;
pub const ADDI_R3_R3_4096: u32 = 0x3863_1000;
pub const ADD_R3_R3_R13: u32 = 0x7c63_6a14;
pub const ADDIS_R13: u32 = 0x3c0d_0000;
pub const PADDI_R3_R13: u64 = 0x0600_0000_386d_0000;
pub const PADDI_R3_R13_4096: u64 = 0x0600_0000_386d_1000;
pub const PADDI_R13: u64 = 0x0600_0000_380d_0000;
pub const PLD_R3: u64 = 0x0410_0000_e460_0000;
pub const BRANCH24_REACH: i64 = 1 << 25;
pub const BRANCH14_REACH: i64 = 1 << 15;
pub const TOC_BIAS: u64 = 0x8000;
pub const TP_OFFSET: u64 = 0x7000;
pub const DTV_OFFSET: u64 = 0x8000;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum EncodeError {
Overflow,
BadInstruction,
}
#[must_use]
pub const fn lo(value: u64) -> u32 {
(value & 0xffff) as u32
}
#[must_use]
pub const fn hi(value: u64) -> u32 {
((value >> 16) & 0xffff) as u32
}
#[must_use]
pub const fn ha(value: u64) -> u32 {
((value.wrapping_add(0x8000) >> 16) & 0xffff) as u32
}
#[must_use]
pub const fn ha_is_zero(value: u64) -> bool {
value.wrapping_add(0x8000) >> 16 == 0
}
#[must_use]
pub fn fits_signed(value: i64, bits: u32) -> bool {
if bits >= 64 {
return true;
}
let Some(limit) = 1i64.checked_shl(bits.wrapping_sub(1)) else {
return true;
};
value >= limit.wrapping_neg() && value < limit
}
#[must_use]
pub fn fits_either(value: i64, bits: u32) -> bool {
if fits_signed(value, bits) {
return true;
}
match 1i64.checked_shl(bits) {
Some(limit) => value >= 0 && value < limit,
None => true,
}
}
#[must_use]
pub const fn primary_opcode(insn: u32) -> u32 {
insn >> 26
}
#[must_use]
pub fn branch24_in_range(from: u64, to: u64) -> bool {
let delta = (to as i64).wrapping_sub(from as i64);
(-BRANCH24_REACH..BRANCH24_REACH).contains(&delta)
}
#[must_use]
pub const fn local_entry_offset(st_other: u8) -> u64 {
match (st_other >> 5) & 7 {
v @ 2..=6 => 1 << v,
_ => 0,
}
}
#[must_use]
pub const fn clobbers_toc(st_other: u8) -> bool {
(st_other >> 5) & 7 == 1
}
#[must_use]
pub const fn is_dq_form(insn: u32) -> bool {
match primary_opcode(insn) {
6 | 56 => true,
61 => insn & 3 == 1,
_ => false,
}
}
#[must_use]
pub const fn is_update_form(insn: u32) -> bool {
match primary_opcode(insn) {
33 | 35 | 41 | 43 | 49 | 51 | 37 | 39 | 45 | 53 | 55 => true,
58 | 62 => insn & 3 == 1,
_ => false,
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Field {
Half16,
Half16Signed,
Lo,
Hi,
Ha,
High,
Higha,
Higher,
Highera,
Highest,
Highesta,
Ds,
LoDs,
HaToc,
LoToc,
LoDsToc,
LoDsToAddi,
Rel24,
Rel14,
Addr24,
Addr14,
Prefixed34,
LdR3LoDs,
PldR3,
PldToPaddi,
PcrelOpt,
}
impl Field {
#[must_use]
pub const fn bytes(self) -> usize {
match self {
Self::Half16
| Self::Half16Signed
| Self::Lo
| Self::Hi
| Self::Ha
| Self::High
| Self::Higha
| Self::Higher
| Self::Highera
| Self::Highest
| Self::Highesta => 2,
Self::Prefixed34 | Self::PldR3 | Self::PldToPaddi | Self::PcrelOpt => 8,
_ => 4,
}
}
pub fn encode16(self, old: u16, value: i64) -> Result<u16, EncodeError> {
let _ = old;
let v = value as u64;
let half = match self {
Self::Half16 => {
if !fits_either(value, 16) {
return Err(EncodeError::Overflow);
}
lo(v)
}
Self::Half16Signed => {
if !fits_signed(value, 16) {
return Err(EncodeError::Overflow);
}
lo(v)
}
Self::Lo => lo(v),
Self::Hi => {
if !fits_signed(value, 32) {
return Err(EncodeError::Overflow);
}
hi(v)
}
Self::Ha => {
if !fits_signed(value.wrapping_add(0x8000), 32) {
return Err(EncodeError::Overflow);
}
ha(v)
}
Self::High => hi(v),
Self::Higha => ha(v),
Self::Higher => ((v >> 32) & 0xffff) as u32,
Self::Highera => ((v.wrapping_add(0x8000) >> 32) & 0xffff) as u32,
Self::Highest => (v >> 48) as u32,
Self::Highesta => (v.wrapping_add(0x8000) >> 48) as u32,
_ => return Err(EncodeError::BadInstruction),
};
Ok(half as u16)
}
pub fn encode32(self, insn: u32, value: i64) -> Result<u32, EncodeError> {
let v = value as u64;
let ds_mask = if is_dq_form(insn) { 0xf } else { 0x3 };
let low = |insn: u32, half: u32| (insn & 0xffff_0000) | (half & 0xffff);
match self {
Self::Ds => {
if !fits_signed(value, 16) || lo(v) & ds_mask != 0 {
return Err(EncodeError::Overflow);
}
Ok(low(insn, (insn & ds_mask) | lo(v)))
}
Self::LoDs => {
if lo(v) & ds_mask != 0 {
return Err(EncodeError::Overflow);
}
Ok(low(insn, (insn & ds_mask) | lo(v)))
}
Self::HaToc => {
if ha_is_zero(v) {
return Ok(NOP);
}
if !fits_signed(value.wrapping_add(0x8000), 32) {
return Err(EncodeError::Overflow);
}
Ok(low(insn, ha(v)))
}
Self::LoToc => {
if ha_is_zero(v) {
if is_update_form(insn) {
return Err(EncodeError::BadInstruction);
}
return Ok((insn & 0xffe0_0000) | 0x0002_0000 | lo(v));
}
Ok(low(insn, lo(v)))
}
Self::LoDsToc => {
if lo(v) & ds_mask != 0 {
return Err(EncodeError::Overflow);
}
if ha_is_zero(v) {
if is_update_form(insn) {
return Err(EncodeError::BadInstruction);
}
return Ok((insn & (0xffe0_0000 | ds_mask)) | 0x0002_0000 | lo(v));
}
Ok(low(insn, (insn & ds_mask) | lo(v)))
}
Self::LoDsToAddi => {
if primary_opcode(insn) != 58 {
return Err(EncodeError::BadInstruction);
}
Self::LoToc.encode32((insn & 0x03ff_ffff) | 0x3800_0000, value)
}
Self::Rel24 | Self::Addr24 => {
let fits = if self == Self::Rel24 {
fits_signed(value, 26)
} else {
fits_signed(value, 26) || (0..1 << 26).contains(&value)
};
if value & 3 != 0 || !fits {
return Err(EncodeError::Overflow);
}
Ok((insn & !0x03ff_fffc) | (v as u32 & 0x03ff_fffc))
}
Self::Rel14 => {
if value & 3 != 0 || !fits_signed(value, 16) {
return Err(EncodeError::Overflow);
}
Ok((insn & !0xfffc) | (v as u32 & 0xfffc))
}
Self::Addr14 => {
if value & 3 != 0 {
return Err(EncodeError::Overflow);
}
Ok((insn & !0xfffc) | (v as u32 & 0xfffc))
}
Self::LdR3LoDs => {
let ld = 0xe860_0000 | (insn & 0x001f_0000);
Self::LoDs.encode32(ld, value)
}
_ => Err(EncodeError::BadInstruction),
}
}
pub fn encode64(self, insn: u64, value: i64) -> Result<u64, EncodeError> {
let insn = match self {
Self::Prefixed34 => insn,
Self::PldR3 => PLD_R3,
Self::PldToPaddi => {
if insn & 0xfc00_0000 != 0xe400_0000 {
return Err(EncodeError::BadInstruction);
}
(insn & !0xff00_0000_fc00_0000) | 0x0600_0000_3800_0000
}
_ => return Err(EncodeError::BadInstruction),
};
prefixed34(insn, value)
}
}
pub fn prefixed34(insn: u64, value: i64) -> Result<u64, EncodeError> {
if !fits_signed(value, 34) {
return Err(EncodeError::Overflow);
}
let v = value as u64;
Ok((insn & !0x0003_ffff_0000_ffff) | ((v & 0x3_ffff_0000) << 16) | (v & 0xffff))
}
#[must_use]
pub fn read_insn(data: &[u8], at: usize) -> Option<u32> {
data.get(at..)
.and_then(|rest| rest.first_chunk::<4>())
.map(|word| u32::from_le_bytes(*word))
}
pub fn write_insn(data: &mut [u8], at: usize, insn: u32) -> Option<()> {
let slot = data
.get_mut(at..)
.and_then(|rest| rest.first_chunk_mut::<4>())?;
*slot = insn.to_le_bytes();
Some(())
}
#[must_use]
pub fn read_prefixed(data: &[u8], at: usize) -> Option<u64> {
let prefix = read_insn(data, at)?;
let suffix = read_insn(data, at.checked_add(4)?)?;
Some((u64::from(prefix) << 32) | u64::from(suffix))
}
pub fn write_prefixed(data: &mut [u8], at: usize, insn: u64) -> Option<()> {
write_insn(data, at, (insn >> 32) as u32)?;
write_insn(data, at.checked_add(4)?, insn as u32)
}
#[must_use]
pub fn x_to_d_form(insn: u32) -> Option<(u32, bool)> {
if primary_opcode(insn) != 31 {
return None;
}
let (opcode, ds) = match (insn >> 1) & 0x3ff {
87 => (34 << 26, false), 279 => (40 << 26, false), 23 => (32 << 26, false), 215 => (38 << 26, false), 407 => (44 << 26, false), 151 => (36 << 26, false), 343 => (42 << 26, false), 535 => (48 << 26, false), 599 => (50 << 26, false), 663 => (52 << 26, false), 727 => (54 << 26, false), 266 => (14 << 26, false), 341 => ((58 << 26) | 2, true), 21 => (58 << 26, true), 149 => (62 << 26, true), _ => return None,
};
Some((opcode | (insn & 0x03ff_0000), ds))
}
#[must_use]
pub fn pcrel_form(access: u32) -> Option<u64> {
const MLS: u64 = 0x0610_0000_0000_0000;
const EIGHT_LS: u64 = 0x0410_0000_0000_0000;
const OPCODE_AND_RT: u64 = 0xffe0_0000;
const RT: u64 = 0x03e0_0000;
let opcode = access & 0xfc00_0000;
let key = if matches!(
opcode,
0xe400_0000 | 0xe800_0000 | 0xf400_0000 | 0xf800_0000
) && !is_dq_form(access)
{
access & 0xfc00_0003
} else if opcode == 0xf400_0000 {
access & 0xfc00_0007
} else if opcode == 0x1800_0000 {
access & 0xfc00_000f
} else {
opcode
};
let (prefixed, mask, move_tx) = match key {
0x8800_0000 | 0xa000_0000 | 0x8000_0000 | 0xa800_0000 | 0xc000_0000 | 0xc800_0000
| 0x9800_0000 | 0xb000_0000 | 0x9000_0000 | 0xd000_0000 | 0xd800_0000 => {
(MLS, OPCODE_AND_RT, false)
}
0xe800_0002 => (EIGHT_LS | 0xa400_0000, RT, false), 0xe800_0000 => (EIGHT_LS | 0xe400_0000, RT, false), 0xe400_0003 => (EIGHT_LS | 0xac00_0000, RT, false), 0xe400_0002 => (EIGHT_LS | 0xa800_0000, RT, false), 0xf400_0001 => (EIGHT_LS | 0xc800_0000, RT, true), 0x1800_0000 => (EIGHT_LS | 0xe800_0000, OPCODE_AND_RT, false), 0xf800_0000 => (EIGHT_LS | 0xf400_0000, RT, false), 0xf400_0003 => (EIGHT_LS | 0xbc00_0000, RT, false), 0xf400_0002 => (EIGHT_LS | 0xb800_0000, RT, false), 0xf400_0005 => (EIGHT_LS | 0xd800_0000, RT, true), 0x1800_0001 => (EIGHT_LS | 0xf800_0000, OPCODE_AND_RT, false), _ => return None,
};
let mut form = prefixed | (u64::from(access) & mask);
if move_tx {
form |= (u64::from(access) & 0x8) << 23;
}
Some(form)
}
#[must_use]
pub fn total_displacement(paddi: u64, access: u32) -> i64 {
let disp34 = (((((paddi >> 16) & 0x3_ffff_0000) | (paddi & 0xffff)) << 30) as i64) >> 30;
let mut disp16 = i64::from(access as u16 as i16);
if is_dq_form(access) {
disp16 &= !0xf;
} else if matches!(
access & 0xfc00_0003,
0xe800_0002 | 0xe800_0000 | 0xf800_0000
) || (matches!(access & 0xfc00_0000, 0xe400_0000 | 0xf400_0000)
&& matches!(access & 3, 2 | 3))
{
disp16 &= !0x3;
}
disp34.wrapping_add(disp16)
}
pub fn plt_call_stub(toc_offset: i64) -> Result<[u32; 5], EncodeError> {
if !fits_signed(toc_offset.wrapping_add(0x8000), 32) || toc_offset & 3 != 0 {
return Err(EncodeError::Overflow);
}
let v = toc_offset as u64;
Ok([
STD_R2_24_R1,
0x3d82_0000 | ha(v),
0xe98c_0000 | lo(v),
MTCTR_R12,
BCTR,
])
}
pub const PLT_CALL_STUB_SIZE: u64 = 20;
pub const THUNK_SIZE: u64 = 36;
pub const THUNK_VIA_SLOT: u64 = 1;
pub const THUNK_SAVE_TOC: u64 = 2;
const TRAP: u32 = 0x7fe0_0008;
pub fn thunk(thunk: u64, key: u64) -> Result<[u32; 9], EncodeError> {
let target = key & !3;
let save = key & THUNK_SAVE_TOC != 0;
let start = if save { thunk.wrapping_add(4) } else { thunk };
let offset = (target as i64).wrapping_sub(start.wrapping_add(8) as i64);
if !fits_signed(offset.wrapping_add(0x8000), 32) {
return Err(EncodeError::Overflow);
}
let v = offset as u64;
let low = if key & THUNK_VIA_SLOT != 0 {
if v & 3 != 0 {
return Err(EncodeError::Overflow);
}
0xe98c_0000 | lo(v) } else {
0x398c_0000 | lo(v) };
let body = [
0x7d88_02a6, 0x429f_0005, 0x7d68_02a6, 0x7d88_03a6, 0x3d8b_0000 | ha(v), low,
MTCTR_R12,
BCTR,
];
let mut words = [TRAP; 9];
let (first, rest) = words.split_at_mut(usize::from(save));
if let Some(slot) = first.first_mut() {
*slot = STD_R2_24_R1;
}
for (slot, word) in rest.iter_mut().zip(body) {
*slot = word;
}
Ok(words)
}
pub fn write_thunk(out: &mut [u8], at: u64, address: u64, target: u64) -> Result<(), EncodeError> {
write_words(out, at, &thunk(address, target)?)
}
pub fn write_words(out: &mut [u8], at: u64, words: &[u32]) -> Result<(), EncodeError> {
let at = usize::try_from(at).map_err(|_| EncodeError::Overflow)?;
for (index, word) in words.iter().enumerate() {
let offset = index
.checked_mul(4)
.and_then(|o| o.checked_add(at))
.ok_or(EncodeError::Overflow)?;
write_insn(out, offset, *word).ok_or(EncodeError::Overflow)?;
}
Ok(())
}
pub const GLINK_HEADER_SIZE: u64 = 60;
#[must_use]
pub fn glink_header(got_plt_delta: i64) -> ([u32; 13], u64) {
(
[
0x7c08_02a6, 0x429f_0005, 0x7d68_02a6, 0x7c08_03a6, 0x7d8b_6050, 0x380c_ffcc, 0x7800_f082, 0xe98b_002c, 0x7d6c_5a14, 0xe98b_0000, 0xe96b_0008, MTCTR_R12,
BCTR,
],
got_plt_delta as u64,
)
}
pub fn glink_entry(offset: u64) -> Result<u32, EncodeError> {
let back = (offset as i64).wrapping_neg();
Field::Rel24.encode32(B, back)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn adjusted_halves() {
assert_eq!(ha(0x1_8000), 2);
assert_eq!(ha(0x1_7fff), 1);
assert_eq!(lo(0x1_8000), 0x8000);
assert_eq!(ha((-4i64) as u64), 0);
assert!(ha_is_zero((-0x8000i64) as u64));
assert!(!ha_is_zero(0x8000));
assert_eq!(local_entry_offset(3 << 5), 8);
assert_eq!(local_entry_offset(2 << 5), 4);
assert_eq!(local_entry_offset(1 << 5), 0);
assert_eq!(local_entry_offset(7 << 5), 0);
}
#[test]
fn branch_fields() {
let bl = 0x4800_0001;
assert_eq!(Field::Rel24.encode32(bl, 0x100).unwrap(), 0x4800_0101);
assert_eq!(Field::Rel24.encode32(bl, -4).unwrap(), 0x4bff_fffd);
assert_eq!(Field::Rel24.encode32(bl, 2), Err(EncodeError::Overflow));
assert_eq!(
Field::Rel24.encode32(bl, BRANCH24_REACH),
Err(EncodeError::Overflow)
);
assert_eq!(Field::Rel14.encode32(0x4182_0000, 8).unwrap(), 0x4182_0008);
}
#[test]
fn toc_optimization() {
assert_eq!(Field::HaToc.encode32(0x3c62_0000, 8).unwrap(), NOP);
assert_eq!(
Field::LoDsToc.encode32(0xe863_0000, 8).unwrap(),
0xe862_0008
);
assert_eq!(
Field::HaToc.encode32(0x3c62_0000, 0x1_0010).unwrap(),
0x3c62_0001
);
assert_eq!(
Field::LoDsToc.encode32(0xe863_0000, 0x1_0010).unwrap(),
0xe863_0010
);
assert_eq!(
Field::LoDsToAddi.encode32(0xe863_0000, -16).unwrap(),
0x3862_fff0
);
assert_eq!(
Field::LoDsToAddi.encode32(0x3863_0000, 0),
Err(EncodeError::BadInstruction)
);
assert_eq!(
Field::LoDs.encode32(0xe863_0000, 6),
Err(EncodeError::Overflow)
);
}
#[test]
fn prefixed_displacement() {
let pld = 0x0410_0000_e460_0000;
let packed = Field::Prefixed34.encode64(pld, -8).unwrap();
assert_eq!(packed, 0x0413_ffff_e460_fff8);
let paddi = Field::PldToPaddi.encode64(pld, 0x12_3456).unwrap();
assert_eq!(paddi, 0x0610_0012_3860_3456);
assert_eq!(
Field::Prefixed34.encode64(pld, 1 << 33),
Err(EncodeError::Overflow)
);
}
#[test]
fn stubs() {
let stub = plt_call_stub(-0x7ff0).unwrap();
assert_eq!(
stub,
[0xf841_0018, 0x3d82_0000, 0xe98c_8010, MTCTR_R12, BCTR]
);
let thunk = thunk(0x1000_0000, 0x2000_0008).unwrap();
assert_eq!(thunk[4], 0x3d8b_1000);
assert_eq!(thunk[5], 0x398c_0000);
assert_eq!(glink_entry(64).unwrap(), 0x4bff_ffc0);
assert_eq!(x_to_d_form(0x7c69_6a14), Some((0x3869_0000, false)));
assert_eq!(x_to_d_form(0x7c69_682a), Some((0xe869_0000, true)));
}
}