#![deny(clippy::arithmetic_side_effects)]
use super::Overflow;
pub const NOP: u32 = 0xe320_f000;
pub const THUMB_NOP_W: u32 = 0xf3af_8000;
pub const THUMB_BX_IP: u16 = 0x4760;
pub const THUMB_ADD_IP_PC: u16 = 0x44fc;
pub const BX_IP: u32 = 0xe12f_ff1c;
pub const ADD_IP_IP_PC: u32 = 0xe08c_c00f;
pub const EXIDX_CANTUNWIND: u32 = 1;
pub const THUNK_SIZE: u64 = 16;
pub const ARM_PC_BIAS: u64 = 8;
pub const THUMB_PC_BIAS: u64 = 4;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Field {
Branch24,
ThumbBranch24,
ThumbBranch19,
ThumbBranch11,
ThumbBranch8,
Movw,
Movt,
ThumbMovw,
ThumbMovt,
Prel31,
ThumbAdr,
ThumbLdrLiteral,
LdrGroup {
group: u8,
},
LdrsGroup {
group: u8,
},
AluGroup {
group: u8,
checked: bool,
},
ThumbPc8,
ThumbJump6,
}
impl Field {
#[must_use]
pub fn bytes(self) -> usize {
match self {
Self::ThumbBranch11 | Self::ThumbBranch8 | Self::ThumbPc8 | Self::ThumbJump6 => 2,
_ => 4,
}
}
#[must_use]
pub fn is_thumb32(self) -> bool {
matches!(
self,
Self::ThumbBranch24
| Self::ThumbBranch19
| Self::ThumbMovw
| Self::ThumbMovt
| Self::ThumbAdr
| Self::ThumbLdrLiteral
)
}
#[must_use]
pub fn from_aligned_place(self) -> bool {
matches!(
self,
Self::ThumbAdr | Self::ThumbLdrLiteral | Self::ThumbPc8
)
}
#[must_use]
pub fn read(self, data: &[u8], at: usize) -> Option<u32> {
match self.bytes() {
2 => read16(data, at).map(u32::from),
_ if self.is_thumb32() => read_thumb32(data, at),
_ => read32(data, at),
}
}
pub fn write(self, data: &mut [u8], at: usize, insn: u32) -> Option<()> {
match self.bytes() {
2 => write16(data, at, insn as u16),
_ if self.is_thumb32() => write_thumb32(data, at, insn),
_ => write32(data, at, insn),
}
}
#[must_use]
pub fn decode(self, insn: u32) -> i64 {
let (hi, lo) = (insn >> 16, insn & 0xffff);
match self {
Self::Branch24 => sign_extend((insn & 0x00ff_ffff) << 2, 26),
Self::ThumbBranch24 => {
let s = (hi >> 10) & 1;
let i1 = !((lo >> 13) ^ s) & 1;
let i2 = !((lo >> 11) ^ s) & 1;
let value = (s << 24)
| (i1 << 23)
| (i2 << 22)
| ((hi & 0x3ff) << 12)
| ((lo & 0x7ff) << 1);
sign_extend(value, 25)
}
Self::ThumbBranch19 => {
let value = (((hi >> 10) & 1) << 20)
| (((lo >> 11) & 1) << 19)
| (((lo >> 13) & 1) << 18)
| ((hi & 0x3f) << 12)
| ((lo & 0x7ff) << 1);
sign_extend(value, 21)
}
Self::ThumbBranch11 => sign_extend((insn & 0x7ff) << 1, 12),
Self::ThumbBranch8 => sign_extend((insn & 0xff) << 1, 9),
Self::Movw | Self::Movt => sign_extend(((insn >> 4) & 0xf000) | (insn & 0x0fff), 16),
Self::ThumbMovw | Self::ThumbMovt => {
let imm =
((hi & 0xf) << 12) | ((hi & 0x400) << 1) | ((lo & 0x7000) >> 4) | (lo & 0xff);
sign_extend(imm, 16)
}
Self::Prel31 => sign_extend(insn & 0x7fff_ffff, 31),
Self::ThumbAdr => {
let imm = i64::from(((hi & 0x400) << 1) | ((lo & 0x7000) >> 4) | (lo & 0xff));
if hi & 0xf0 != 0 {
imm.wrapping_neg()
} else {
imm
}
}
Self::ThumbLdrLiteral => {
let imm = i64::from(lo & 0xfff);
if hi & 0x80 != 0 {
imm
} else {
imm.wrapping_neg()
}
}
Self::LdrGroup { .. } => {
let imm = i64::from(insn & 0xfff);
if insn & 0x0080_0000 != 0 {
imm
} else {
imm.wrapping_neg()
}
}
Self::LdrsGroup { .. } => {
let imm = i64::from(((insn >> 4) & 0xf0) | (insn & 0xf));
if insn & 0x0080_0000 != 0 {
imm
} else {
imm.wrapping_neg()
}
}
Self::ThumbPc8 => {
let imm = ((insn & 0xff) << 2).wrapping_add(4) & 0x3ff;
i64::from(imm as i32).wrapping_sub(4)
}
Self::ThumbJump6 => i64::from((((insn >> 9) & 1) << 6) | (((insn >> 3) & 0x1f) << 1)),
Self::AluGroup { .. } => {
let rotate = ((insn >> 8) & 0xf).wrapping_mul(2);
let imm = i64::from((insn & 0xff).rotate_right(rotate));
if insn & 0x0040_0000 != 0 {
imm.wrapping_neg()
} else {
imm
}
}
}
}
pub fn encode(self, insn: u32, value: i64) -> Result<u32, Overflow> {
let v = value as u32;
match self {
Self::Branch24 => {
check_signed(value, 26)?;
Ok((insn & 0xff00_0000) | ((v >> 2) & 0x00ff_ffff))
}
Self::ThumbBranch24 => {
check_signed(value, 25)?;
let hi = 0xf000 | ((v >> 14) & 0x0400) | ((v >> 12) & 0x03ff);
let lo = ((insn & 0xffff) & 0xd000)
| ((!(v >> 10) ^ (v >> 11)) & 0x2000)
| ((!(v >> 11) ^ (v >> 13)) & 0x0800)
| ((v >> 1) & 0x07ff);
Ok((hi << 16) | lo)
}
Self::ThumbBranch19 => {
check_signed(value, 21)?;
let hi = ((insn >> 16) & 0xfbc0) | ((v >> 10) & 0x0400) | ((v >> 12) & 0x003f);
let lo = 0x8000 | ((v >> 8) & 0x0800) | ((v >> 5) & 0x2000) | ((v >> 1) & 0x07ff);
Ok((hi << 16) | lo)
}
Self::ThumbBranch11 => {
check_signed(value, 12)?;
Ok((insn & 0xf800) | ((v >> 1) & 0x07ff))
}
Self::ThumbBranch8 => {
check_signed(value, 9)?;
Ok((insn & 0xff00) | ((v >> 1) & 0x00ff))
}
Self::Movw => Ok(arm_mov(insn, v & 0xffff)),
Self::Movt => Ok(arm_mov(insn, v >> 16)),
Self::ThumbMovw => Ok(thumb_mov(insn, v & 0xffff)),
Self::ThumbMovt => Ok(thumb_mov(insn, v >> 16)),
Self::Prel31 => {
check_signed(value, 31)?;
Ok((insn & 0x8000_0000) | (v & 0x7fff_ffff))
}
Self::ThumbAdr => {
let (imm, sub) = magnitude(value, 12)?;
let hi =
((insn >> 16) & 0xfb0f) | if sub { 0x00a0 } else { 0 } | ((imm & 0x800) >> 1);
let lo = (insn & 0x8f00) | ((imm & 0x700) << 4) | (imm & 0xff);
Ok((hi << 16) | lo)
}
Self::ThumbLdrLiteral => {
let (imm, negative) = magnitude(value, 12)?;
let hi = ((insn >> 16) & !0x0080) | if negative { 0 } else { 0x0080 };
let lo = (insn & 0xf000) | imm;
Ok((hi << 16) | lo)
}
Self::LdrGroup { group } => {
let (magnitude, negative) = split_sign(value)?;
let imm = group_residual(magnitude, group);
if imm > 0xfff {
return Err(Overflow);
}
let up = if negative { 0 } else { 0x0080_0000 };
Ok((insn & 0xff7f_f000) | up | imm)
}
Self::LdrsGroup { group } => {
let (magnitude, negative) = split_sign(value)?;
let imm = group_residual(magnitude, group);
if imm > 0xff {
return Err(Overflow);
}
let up = if negative { 0 } else { 0x0080_0000 };
Ok((insn & 0xff7f_f0f0) | up | ((imm & 0xf0) << 4) | (imm & 0xf))
}
Self::ThumbPc8 => {
if !(0..=1020).contains(&value) || value % 4 != 0 {
return Err(Overflow);
}
Ok((insn & 0xff00) | ((v >> 2) & 0xff))
}
Self::ThumbJump6 => {
if !(0..=126).contains(&value) || value % 2 != 0 {
return Err(Overflow);
}
Ok((insn & !0x02f8) | ((v << 3) & 0x0200) | ((v << 2) & 0x00f8))
}
Self::AluGroup { group, checked } => {
let (magnitude, negative) = split_sign(value)?;
let residual = group_residual(magnitude, group);
let (imm, rotate) = group_immediate(residual);
if checked && next_residual(residual) != 0 {
return Err(Overflow);
}
let opcode = if negative { 0x0040_0000 } else { 0x0080_0000 };
Ok((insn & 0xff3f_f000) | opcode | (rotate << 8) | imm)
}
}
}
}
fn sign_extend(value: u32, bits: u32) -> i64 {
let shift = 32u32.saturating_sub(bits);
i64::from(((value << shift) as i32) >> shift)
}
fn check_signed(value: i64, bits: u32) -> Result<(), Overflow> {
if super::aarch64::fits_signed(value, bits) {
Ok(())
} else {
Err(Overflow)
}
}
fn magnitude(value: i64, bits: u32) -> Result<(u32, bool), Overflow> {
let magnitude = value.unsigned_abs();
if magnitude >> bits != 0 {
return Err(Overflow);
}
Ok((magnitude as u32, value < 0))
}
fn arm_mov(insn: u32, imm: u32) -> u32 {
(insn & !0x000f_0fff) | ((imm & 0xf000) << 4) | (imm & 0x0fff)
}
fn thumb_mov(insn: u32, imm: u32) -> u32 {
let hi = ((insn >> 16) & !0x040f) | ((imm >> 1) & 0x0400) | ((imm >> 12) & 0x000f);
let lo = (insn & !0x70ff & 0xffff) | ((imm << 4) & 0x7000) | (imm & 0x00ff);
(hi << 16) | lo
}
fn split_sign(value: i64) -> Result<(u32, bool), Overflow> {
let magnitude = u32::try_from(value.unsigned_abs()).map_err(|_| Overflow)?;
Ok((magnitude, value < 0))
}
#[must_use]
pub fn group_residual(value: u32, group: u8) -> u32 {
let mut left = value;
for _ in 0..group {
left = next_residual(left);
}
left
}
fn next_residual(value: u32) -> u32 {
let lz = value.leading_zeros() & !1;
if lz >= 24 {
0
} else {
value & (0x00ff_ffff >> lz)
}
}
fn group_immediate(value: u32) -> (u32, u32) {
let lz = value.leading_zeros() & !1;
if lz >= 24 {
(value & 0xff, 0)
} else {
(
(value >> (24u32.saturating_sub(lz))) & 0xff,
lz.saturating_add(8) / 2,
)
}
}
#[must_use]
pub fn modified_immediate(value: u32) -> Option<(u32, u32)> {
(0u32..16).find_map(|rotate| {
let imm = value.rotate_left(rotate.wrapping_mul(2));
(imm <= 0xff).then_some((imm, rotate))
})
}
#[must_use]
pub fn is_arm_blx(insn: u32) -> bool {
insn & 0xfe00_0000 == 0xfa00_0000
}
pub fn arm_blx(value: i64) -> Result<u32, Overflow> {
check_signed(value, 26)?;
let v = value as u32;
Ok(0xfa00_0000 | ((v & 2) << 23) | ((v >> 2) & 0x00ff_ffff))
}
#[must_use]
pub fn arm_bl(insn: u32) -> u32 {
0xeb00_0000 | (insn & 0x00ff_ffff)
}
#[must_use]
pub fn is_thumb_blx(insn: u32) -> bool {
insn & 0x1000 == 0
}
#[must_use]
pub fn thumb_set_blx(insn: u32, blx: bool) -> u32 {
if blx { insn & !0x1000 } else { insn | 0x1000 }
}
#[must_use]
pub fn read16(data: &[u8], at: usize) -> Option<u16> {
data.get(at..)
.and_then(|rest| rest.first_chunk::<2>())
.map(|half| u16::from_le_bytes(*half))
}
pub fn write16(data: &mut [u8], at: usize, half: u16) -> Option<()> {
let slot = data
.get_mut(at..)
.and_then(|rest| rest.first_chunk_mut::<2>())?;
*slot = half.to_le_bytes();
Some(())
}
#[must_use]
pub fn read32(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 write32(data: &mut [u8], at: usize, word: u32) -> Option<()> {
let slot = data
.get_mut(at..)
.and_then(|rest| rest.first_chunk_mut::<4>())?;
*slot = word.to_le_bytes();
Some(())
}
#[must_use]
pub fn read_thumb32(data: &[u8], at: usize) -> Option<u32> {
let hi = read16(data, at)?;
let lo = read16(data, at.checked_add(2)?)?;
Some((u32::from(hi) << 16) | u32::from(lo))
}
pub fn write_thumb32(data: &mut [u8], at: usize, insn: u32) -> Option<()> {
let second = at.checked_add(2)?;
if data.len() < second.checked_add(2)? {
return None;
}
write16(data, at, (insn >> 16) as u16)?;
write16(data, second, insn as u16)
}
#[must_use]
pub fn movw_ip(value: u32) -> u32 {
arm_mov(0xe300_c000, value & 0xffff)
}
#[must_use]
pub fn movt_ip(value: u32) -> u32 {
arm_mov(0xe340_c000, value >> 16)
}
#[must_use]
pub fn thumb_movw_ip(value: u32) -> u32 {
thumb_mov(0xf240_0c00, value & 0xffff)
}
#[must_use]
pub fn thumb_movt_ip(value: u32) -> u32 {
thumb_mov(0xf2c0_0c00, value >> 16)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ThunkKind {
pub thumb: bool,
pub pic: bool,
}
pub fn write_thunk(
out: &mut [u8],
address: u64,
destination: u64,
kind: ThunkKind,
) -> Result<(), Overflow> {
let destination = u32::try_from(destination).map_err(|_| Overflow)?;
let address = u32::try_from(address).map_err(|_| Overflow)?;
if out.len() < 16 {
return Err(Overflow);
}
out.iter_mut().take(16).for_each(|b| *b = 0);
let fail = |r: Option<()>| r.ok_or(Overflow);
match (kind.thumb, kind.pic) {
(false, false) => {
fail(write32(out, 0, movw_ip(destination)))?;
fail(write32(out, 4, movt_ip(destination)))?;
fail(write32(out, 8, BX_IP))?;
}
(false, true) => {
let value = destination.wrapping_sub(address.wrapping_add(16));
fail(write32(out, 0, movw_ip(value)))?;
fail(write32(out, 4, movt_ip(value)))?;
fail(write32(out, 8, ADD_IP_IP_PC))?;
fail(write32(out, 12, BX_IP))?;
}
(true, false) => {
fail(write_thumb32(out, 0, thumb_movw_ip(destination)))?;
fail(write_thumb32(out, 4, thumb_movt_ip(destination)))?;
fail(write16(out, 8, THUMB_BX_IP))?;
}
(true, true) => {
let value = destination.wrapping_sub(address.wrapping_add(12));
fail(write_thumb32(out, 0, thumb_movw_ip(value)))?;
fail(write_thumb32(out, 4, thumb_movt_ip(value)))?;
fail(write16(out, 8, THUMB_ADD_IP_PC))?;
fail(write16(out, 10, THUMB_BX_IP))?;
}
}
Ok(())
}
#[must_use]
pub fn plt_header(plt: u64, got_plt: u64) -> [u32; 5] {
let offset = got_plt.wrapping_sub(plt.wrapping_add(16)) as u32;
[0xe52d_e004, 0xe59f_e004, 0xe08f_e00e, 0xe5be_f008, offset]
}
pub fn plt_entry(entry: u64, slot: u64) -> Result<[u32; 3], Overflow> {
let offset = slot.wrapping_sub(entry.wrapping_add(8));
let offset = u32::try_from(offset).map_err(|_| Overflow)?;
if offset >> 28 != 0 {
return Err(Overflow);
}
Ok([
0xe28f_c600 | ((offset >> 20) & 0xff),
0xe28c_ca00 | ((offset >> 12) & 0xff),
0xe5bc_f000 | (offset & 0xfff),
])
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn arm_branches_round_trip() {
let bl = 0xebff_fffe;
assert_eq!(Field::Branch24.decode(bl), -8);
let bl = Field::Branch24.encode(bl, 0x20).unwrap();
assert_eq!(bl, 0xeb00_0008);
assert_eq!(Field::Branch24.decode(bl), 0x20);
assert!(Field::Branch24.encode(bl, 1 << 25).is_err());
assert_eq!(arm_blx(0x22).unwrap(), 0xfb00_0008);
assert!(is_arm_blx(0xfa00_0000));
assert_eq!(arm_bl(0xfa12_3456), 0xeb12_3456);
}
#[test]
fn thumb_branches_round_trip() {
let bl = 0xf7ff_fffe;
assert_eq!(Field::ThumbBranch24.decode(bl), -4);
for value in [-4i64, 0x7c, -0x44, 0x00ff_fffe, -0x0100_0000] {
let insn = Field::ThumbBranch24.encode(bl, value).unwrap();
assert_eq!(Field::ThumbBranch24.decode(insn), value, "{value:#x}");
assert!(!is_thumb_blx(insn));
}
assert!(Field::ThumbBranch24.encode(bl, 0x0100_0000).is_err());
assert_eq!(Field::ThumbBranch24.decode(0xf000_e83e), 0x7c);
assert!(is_thumb_blx(0xf000_e83e));
for value in [-2i64, 0x40, -0x10_0000, 0xf_fffe] {
let insn = Field::ThumbBranch19.encode(0xf000_8000, value).unwrap();
assert_eq!(Field::ThumbBranch19.decode(insn), value, "{value:#x}");
}
let b = Field::ThumbBranch11.encode(0xe7fe, 0x40).unwrap();
assert_eq!(Field::ThumbBranch11.decode(b), 0x40);
let b = Field::ThumbBranch8.encode(0xd0fe, -0x20).unwrap();
assert_eq!(Field::ThumbBranch8.decode(b), -0x20);
assert!(Field::ThumbBranch8.encode(0xd0fe, 0x100).is_err());
}
#[test]
fn moves_round_trip() {
let movw = Field::Movw.encode(0xe300_0000, 0x1234_5678).unwrap();
assert_eq!(movw, 0xe305_0678);
assert_eq!(Field::Movw.decode(movw), 0x5678);
let movt = Field::Movt.encode(0xe340_0000, 0x1234_5678).unwrap();
assert_eq!(movt, 0xe341_0234);
let movw = Field::ThumbMovw.encode(0xf240_0000, 0xabcd).unwrap();
assert_eq!(Field::ThumbMovw.decode(movw), -0x5433);
assert_eq!(thumb_movw_ip(0xabcd) & 0x0f00, 0x0c00);
let movt = Field::ThumbMovt.encode(0xf2c0_0000, 0x7fff_0000).unwrap();
assert_eq!(Field::ThumbMovt.decode(movt), 0x7fff);
}
#[test]
fn pc_relative_data_and_literals() {
let prel = Field::Prel31.encode(0x8000_0000, -8).unwrap();
assert_eq!(prel, 0xffff_fff8);
assert_eq!(Field::Prel31.decode(prel), -8);
assert!(Field::Prel31.encode(0, 1 << 30).is_err());
let adr = Field::ThumbAdr.encode(0xf20f_0000, -0x123).unwrap();
assert_eq!(Field::ThumbAdr.decode(adr), -0x123);
let adr = Field::ThumbAdr.encode(adr, 0x7ff).unwrap();
assert_eq!(Field::ThumbAdr.decode(adr), 0x7ff);
let ldr = Field::ThumbLdrLiteral.encode(0xf8df_0000, -4).unwrap();
assert_eq!(Field::ThumbLdrLiteral.decode(ldr), -4);
let ldr = Field::LdrGroup { group: 0 }
.encode(0xe59f_0000, -0x10)
.unwrap();
assert_eq!(Field::LdrGroup { group: 0 }.decode(ldr), -0x10);
let alu0 = Field::AluGroup {
group: 0,
checked: true,
};
let add = alu0.encode(0xe28f_0000, 0x3f0).unwrap();
assert_eq!(alu0.decode(add), 0x3f0);
assert!(alu0.encode(0xe28f_0000, 0x101).is_err());
let alu0_nc = Field::AluGroup {
group: 0,
checked: false,
};
let sub = alu0_nc.encode(0xe28f_0000, -0x8).unwrap();
assert_eq!(alu0_nc.decode(sub), -0x8);
}
#[test]
fn group_relocations_match_gnu_ld() {
let alu = |group, checked| Field::AluGroup { group, checked };
assert_eq!(
alu(0, false).encode(0xe28f_0000, 0x00ab_cdef),
Ok(0xe28f_08ab)
);
assert_eq!(
alu(1, false).encode(0xe280_0000, 0x00ab_cdeb),
Ok(0xe280_0ccd)
);
assert_eq!(
Field::LdrGroup { group: 2 }.encode(0xe590_1000, 0x00ab_cde7),
Ok(0xe590_10e7)
);
assert_eq!(
alu(2, true).encode(0xe282_3000, 0x00ab_cde7),
Ok(0xe282_30e7)
);
assert_eq!(alu(0, false).encode(0xe28f_4000, 0xabcd), Ok(0xe28f_4cab));
assert_eq!(
Field::LdrsGroup { group: 1 }.encode(0xe1d4_50b0, 0xabc9),
Ok(0xe1d4_5cb9)
);
assert_eq!(alu(0, true).encode(0xe28f_6000, 0xab00), Ok(0xe28f_6cab));
assert_eq!(alu(0, true).encode(0xe28f_6000, 0xab08), Err(Overflow));
assert_eq!(
Field::LdrGroup { group: 0 }.encode(0xe59f_7000, 0xabc),
Ok(0xe59f_7abc)
);
assert_eq!(
Field::LdrsGroup { group: 0 }.encode(0xe1df_80b0, 0xab),
Ok(0xe1df_8abb)
);
assert_eq!(alu(1, true).encode(0xe289_9000, 0xabc9), Ok(0xe289_90c9));
assert_eq!(alu(1, true).encode(0xe289_9000, 0xab00), Ok(0xe289_9000));
assert_eq!(
Field::LdrGroup { group: 0 }.encode(0xe59f_7000, 0x1abc),
Err(Overflow)
);
assert_eq!(
Field::LdrsGroup { group: 0 }.encode(0xe1df_80b0, 0x1ab),
Err(Overflow)
);
}
#[test]
fn thumb_pc8_and_jump6_round_trip() {
let ldr = Field::ThumbPc8.encode(0x4800, 0x1fc).unwrap();
assert_eq!(Field::ThumbPc8.decode(ldr), 0x1fc);
assert_eq!(ldr, 0x487f);
assert_eq!(Field::ThumbPc8.decode(0x48ff), -4);
assert!(Field::ThumbPc8.encode(0x4800, 0x3fc + 4).is_err());
assert!(Field::ThumbPc8.encode(0x4800, 2).is_err());
assert!(Field::ThumbPc8.encode(0x4800, -4).is_err());
let cbz = Field::ThumbJump6.encode(0xb100, 0x42).unwrap();
assert_eq!(Field::ThumbJump6.decode(cbz), 0x42);
assert_eq!(
Field::ThumbJump6.encode(0xb100, 126).map(|i| i & !0x2f8),
Ok(0xb100)
);
assert!(Field::ThumbJump6.encode(0xb100, 128).is_err());
assert!(Field::ThumbJump6.encode(0xb100, -2).is_err());
assert!(Field::ThumbJump6.encode(0xb100, 3).is_err());
}
#[test]
fn thunks_and_plt() {
let mut out = [0u8; 16];
write_thunk(
&mut out,
0x1000,
0x1234_5679,
ThunkKind {
thumb: false,
pic: false,
},
)
.unwrap();
assert_eq!(read32(&out, 0), Some(0xe305_c679));
assert_eq!(read32(&out, 4), Some(0xe341_c234));
assert_eq!(read32(&out, 8), Some(BX_IP));
write_thunk(
&mut out,
0x1000,
0x2000,
ThunkKind {
thumb: true,
pic: true,
},
)
.unwrap();
assert_eq!(read_thumb32(&out, 0), Some(thumb_movw_ip(0xff4)));
assert_eq!(read16(&out, 8), Some(THUMB_ADD_IP_PC));
assert_eq!(read16(&out, 10), Some(THUMB_BX_IP));
assert_eq!(
plt_header(0x102f0, 0x303c4),
[
0xe52d_e004,
0xe59f_e004,
0xe08f_e00e,
0xe5be_f008,
0x303c4 - 0x10300
]
);
assert_eq!(
plt_entry(0x10310, 0x303d0).unwrap(),
[0xe28f_c600, 0xe28c_ca20, 0xe5bc_f0b8]
);
assert!(plt_entry(0x1000, 0x2000_1000).is_err());
}
}