#![deny(clippy::arithmetic_side_effects)]
use super::Overflow;
pub const PLT_HEADER_SIZE: u64 = 32;
pub const PLT_ENTRY_SIZE: u64 = 32;
pub const PLT_LAZY_OFFSET: u64 = 14;
pub const PLT_HEADER: [u8; 32] = [
0xe3, 0x10, 0xf0, 0x38, 0x00, 0x24, 0xc0, 0x10, 0x00, 0x00, 0x00, 0x00, 0xd2, 0x07, 0xf0, 0x30, 0x10, 0x08, 0xe3, 0x10, 0x10, 0x10, 0x00, 0x04, 0x07, 0xf1, 0x07, 0x00, 0x07, 0x00, 0x07, 0x00, ];
pub const PLT_ENTRY: [u8; 32] = [
0xc0, 0x10, 0x00, 0x00, 0x00, 0x00, 0xe3, 0x10, 0x10, 0x00, 0x00, 0x04, 0x07, 0xf1, 0x0d, 0x10, 0xe3, 0x10, 0x10, 0x0c, 0x00, 0x14, 0xc0, 0xf4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ];
pub const BRCL_NOP: [u8; 6] = [0xc0, 0x04, 0x00, 0x00, 0x00, 0x00];
pub const LG_R2_GOT: [u8; 6] = [0xe3, 0x22, 0xc0, 0x00, 0x00, 0x04];
pub const NOPR: [u8; 2] = [0x07, 0x07];
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Field {
Imm12,
Disp20,
Pc12Dbl,
Pc16Dbl,
Pc24Dbl,
Pc32Dbl,
}
impl Field {
#[must_use]
pub const fn bytes(self) -> usize {
match self {
Self::Imm12 | Self::Pc12Dbl | Self::Pc16Dbl => 2,
Self::Pc24Dbl => 3,
Self::Disp20 | Self::Pc32Dbl => 4,
}
}
#[must_use]
pub const fn is_pc_dbl(self) -> bool {
matches!(
self,
Self::Pc12Dbl | Self::Pc16Dbl | Self::Pc24Dbl | Self::Pc32Dbl
)
}
}
#[must_use]
pub fn fits_either(value: i64, bits: u32) -> bool {
let Some(limit) = 1i64.checked_shl(bits) else {
return true;
};
let half = limit >> 1;
value >= half.wrapping_neg() && value < limit
}
#[must_use]
pub fn fits_signed(value: i64, bits: u32) -> bool {
let Some(limit) = 1i64.checked_shl(bits.saturating_sub(1)) else {
return true;
};
value >= limit.wrapping_neg() && value < limit
}
pub fn encode(field: Field, bytes: &mut [u8], value: i64) -> Result<(), Overflow> {
if field.is_pc_dbl() && value & 1 != 0 {
return Err(Overflow);
}
let halves = value >> 1;
match field {
Field::Imm12 | Field::Pc12Dbl => {
let v = if field == Field::Imm12 { value } else { halves };
if !fits_either(v, 12) {
return Err(Overflow);
}
let slot = bytes.first_chunk_mut::<2>().ok_or(Overflow)?;
let old = u16::from_be_bytes(*slot);
*slot = ((old & 0xf000) | (v as u16 & 0x0fff)).to_be_bytes();
}
Field::Pc16Dbl => {
if !fits_either(halves, 16) {
return Err(Overflow);
}
let slot = bytes.first_chunk_mut::<2>().ok_or(Overflow)?;
*slot = (halves as u16).to_be_bytes();
}
Field::Pc24Dbl => {
if !fits_either(halves, 24) {
return Err(Overflow);
}
let slot = bytes.first_chunk_mut::<3>().ok_or(Overflow)?;
let [_, a, b, c] = (halves as u32).to_be_bytes();
*slot = [a, b, c];
}
Field::Pc32Dbl => {
if !fits_either(halves, 32) {
return Err(Overflow);
}
let slot = bytes.first_chunk_mut::<4>().ok_or(Overflow)?;
*slot = (halves as u32).to_be_bytes();
}
Field::Disp20 => {
if !fits_signed(value, 20) {
return Err(Overflow);
}
let slot = bytes.first_chunk_mut::<4>().ok_or(Overflow)?;
let old = u32::from_be_bytes(*slot);
let v = value as u32;
let packed = ((v & 0xfff) << 16) | ((v & 0xf_f000) >> 4);
*slot = ((old & 0xf000_00ff) | packed).to_be_bytes();
}
}
Ok(())
}
fn put_pc32dbl(bytes: &mut [u8], at: usize, from: u64, to: u64) -> Result<(), Overflow> {
let slot = bytes.get_mut(at..).ok_or(Overflow)?;
encode(Field::Pc32Dbl, slot, to.wrapping_sub(from) as i64)
}
pub fn plt_header(plt: u64, got: u64) -> Result<[u8; 32], Overflow> {
let mut code = PLT_HEADER;
put_pc32dbl(&mut code, 8, plt.wrapping_add(6), got)?;
Ok(code)
}
pub fn plt_entry(entry: u64, slot: u64, plt: u64, rela_offset: u32) -> Result<[u8; 32], Overflow> {
let mut code = PLT_ENTRY;
put_pc32dbl(&mut code, 2, entry, slot)?;
put_pc32dbl(&mut code, 24, entry.wrapping_add(22), plt)?;
let offset = code.get_mut(28..32).ok_or(Overflow)?;
offset.copy_from_slice(&rela_offset.to_be_bytes());
Ok(code)
}
#[must_use]
pub fn ie_load_to_le(insn: [u8; 6]) -> Option<[u8; 6]> {
let [op, rx, base, _, dh, op2] = insn;
if op != 0xe3 || dh != 0 || op2 != 0x04 {
return None;
}
let x2 = rx & 0x0f;
let b2 = base >> 4;
let ry = if b2 == 0 {
x2
} else if x2 == 0 {
b2
} else if b2 == 12 {
x2
} else if x2 == 12 {
b2
} else {
return None;
};
Some([0xeb, (rx & 0xf0) | ry, 0x00, 0x00, 0x00, 0x0d])
}
#[must_use]
pub fn is_brasl_r14(insn: &[u8]) -> bool {
insn.first_chunk::<2>() == Some(&[0xc0, 0xe5])
}
#[must_use]
pub const fn larl(register_byte: u8) -> [u8; 2] {
[0xc0, register_byte & 0xf0]
}
#[must_use]
pub fn is_lgrl(opcode: [u8; 2]) -> bool {
opcode[0] == 0xc4 && opcode[1] & 0x0f == 0x08
}
#[must_use]
pub fn is_lg_got(insn: &[u8]) -> bool {
match insn.first_chunk::<6>() {
Some(&[op, _, base, _, _, op2]) => op == 0xe3 && base & 0xf0 == 0xc0 && op2 == 0x04,
None => false,
}
}
pub fn write_nops(out: &mut [u8]) {
out.fill(NOPR[0]);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fields_pack_big_endian() {
let mut word = [0xc0, 0x10, 0, 0, 0, 0];
encode(Field::Pc32Dbl, &mut word[2..], 0x1000).unwrap();
assert_eq!(word, [0xc0, 0x10, 0, 0, 0x08, 0]);
encode(Field::Pc32Dbl, &mut word[2..], -4).unwrap();
assert_eq!(word[2..], [0xff, 0xff, 0xff, 0xfe]);
assert!(encode(Field::Pc32Dbl, &mut word[2..], 3).is_err());
assert!(encode(Field::Pc32Dbl, &mut word[2..], 1 << 34).is_err());
let mut half = [0xa7, 0xf4];
encode(Field::Pc16Dbl, &mut half, -2).unwrap();
assert_eq!(half, [0xff, 0xff]);
let mut rxy = [0xc0, 0x00, 0x00, 0x04];
encode(Field::Disp20, &mut rxy, 0x1_2345).unwrap();
assert_eq!(rxy, [0xc3, 0x45, 0x12, 0x04]);
encode(Field::Disp20, &mut rxy, -1).unwrap();
assert_eq!(rxy, [0xcf, 0xff, 0xff, 0x04]);
assert!(encode(Field::Disp20, &mut rxy, 0x8_0000).is_err());
let mut d12 = [0xc0, 0x00];
encode(Field::Imm12, &mut d12, 0xabc).unwrap();
assert_eq!(d12, [0xca, 0xbc]);
assert!(encode(Field::Imm12, &mut d12, 0x1000).is_err());
let mut three = [0; 3];
encode(Field::Pc24Dbl, &mut three, 0x10).unwrap();
assert_eq!(three, [0, 0, 8]);
let mut short = [0u8; 1];
assert!(encode(Field::Pc32Dbl, &mut short, 0).is_err());
}
#[test]
fn plt_matches_gnu_ld() {
let entry = plt_entry(0x5e8, 0x1fc0, 0x5c8, 0).unwrap();
assert_eq!(
entry,
[
0xc0, 0x10, 0x00, 0x00, 0x0c, 0xec, 0xe3, 0x10, 0x10, 0x00, 0x00, 0x04, 0x07, 0xf1,
0x0d, 0x10, 0xe3, 0x10, 0x10, 0x0c, 0x00, 0x14, 0xc0, 0xf4, 0xff, 0xff, 0xff, 0xe5,
0x00, 0x00, 0x00, 0x00
]
);
let header = plt_header(0x5c8, 0x1fa8).unwrap();
assert_eq!(header[6..12], [0xc0, 0x10, 0x00, 0x00, 0x0c, 0xed]);
}
#[test]
fn tls_rewrites() {
assert_eq!(
ie_load_to_le([0xe3, 0x11, 0xc0, 0x00, 0x00, 0x04]),
Some([0xeb, 0x11, 0x00, 0x00, 0x00, 0x0d])
);
assert_eq!(
ie_load_to_le([0xe3, 0x3c, 0x50, 0x00, 0x00, 0x04]),
Some([0xeb, 0x35, 0x00, 0x00, 0x00, 0x0d])
);
assert_eq!(ie_load_to_le([0xe3, 0x11, 0xc0, 0x08, 0x01, 0x04]), None);
assert_eq!(ie_load_to_le([0xe3, 0x13, 0x50, 0x00, 0x00, 0x04]), None);
assert!(is_brasl_r14(&[0xc0, 0xe5, 0, 0, 0, 0]));
assert!(is_lgrl([0xc4, 0x28]));
assert!(!is_lgrl([0xc4, 0x2d]));
assert!(is_lg_got(&[0xe3, 0x10, 0xc0, 0x10, 0x00, 0x04]));
assert_eq!(larl(0x28), [0xc0, 0x20]);
}
}