use r2smt_common::{Arch, Error, Result};
pub const ARM_INSTRUCTION_BYTES: usize = 4;
const AARCH64_NOP_LE: [u8; 4] = [0x1F, 0x20, 0x03, 0xD5];
const AARCH32_NOP_LE: [u8; 4] = [0x00, 0xF0, 0x20, 0xE3];
pub const THUMB_HALFWORD_BYTES: usize = 2;
pub const THUMB_NOP_LE: [u8; 2] = [0x00, 0xBF];
pub fn arm_nop_bytes(arch: Arch) -> Result<[u8; 4]> {
match arch {
Arch::Aarch64 => Ok(AARCH64_NOP_LE),
Arch::Arm => Ok(AARCH32_NOP_LE),
other => Err(Error::parse(
"arm_encoding.nop",
format!("{other:?} is not an ARM ISA"),
)),
}
}
pub fn arm_nop_buffer(arch: Arch, len: usize) -> Result<Vec<u8>> {
if !len.is_multiple_of(ARM_INSTRUCTION_BYTES) {
return Err(Error::parse(
"arm_encoding.nop_buffer",
format!("{len} is not a multiple of {ARM_INSTRUCTION_BYTES}"),
));
}
let nop = arm_nop_bytes(arch)?;
let count = len / ARM_INSTRUCTION_BYTES;
let mut out = Vec::with_capacity(len);
for _ in 0..count {
out.extend_from_slice(&nop);
}
Ok(out)
}
pub fn thumb_nop_buffer(len: usize) -> Result<Vec<u8>> {
if !len.is_multiple_of(THUMB_HALFWORD_BYTES) {
return Err(Error::parse(
"arm_encoding.thumb_nop_buffer",
format!("{len} is not a multiple of {THUMB_HALFWORD_BYTES}"),
));
}
let count = len / THUMB_HALFWORD_BYTES;
let mut out = Vec::with_capacity(len);
for _ in 0..count {
out.extend_from_slice(&THUMB_NOP_LE);
}
Ok(out)
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used)]
use super::*;
#[test]
fn aarch64_nop_is_canonical_hint_encoding() {
let nop = arm_nop_bytes(Arch::Aarch64).unwrap();
assert_eq!(nop, [0x1F, 0x20, 0x03, 0xD5]);
let word = u32::from_le_bytes(nop);
assert_eq!(word, 0xD503_201F);
}
#[test]
fn aarch32_nop_is_canonical_hint_encoding() {
let nop = arm_nop_bytes(Arch::Arm).unwrap();
assert_eq!(nop, [0x00, 0xF0, 0x20, 0xE3]);
let word = u32::from_le_bytes(nop);
assert_eq!(word, 0xE320_F000);
}
#[test]
fn arm_nop_bytes_rejects_non_arm_arch() {
assert!(arm_nop_bytes(Arch::X86_64).is_err());
assert!(arm_nop_bytes(Arch::X86).is_err());
}
#[test]
fn arm_nop_buffer_tiles_for_aarch64() {
let buf = arm_nop_buffer(Arch::Aarch64, 8).unwrap();
assert_eq!(buf.len(), 8);
assert_eq!(&buf[..4], &AARCH64_NOP_LE);
assert_eq!(&buf[4..], &AARCH64_NOP_LE);
}
#[test]
fn arm_nop_buffer_handles_single_instruction() {
let buf = arm_nop_buffer(Arch::Arm, 4).unwrap();
assert_eq!(buf, AARCH32_NOP_LE);
}
#[test]
fn arm_nop_buffer_rejects_misaligned_length() {
assert!(arm_nop_buffer(Arch::Aarch64, 6).is_err());
assert!(arm_nop_buffer(Arch::Arm, 5).is_err());
}
#[test]
fn arm_nop_buffer_zero_length_yields_empty() {
let buf = arm_nop_buffer(Arch::Aarch64, 0).unwrap();
assert!(buf.is_empty());
}
#[test]
fn thumb_nop_2byte_matches_bf00() {
let buf = thumb_nop_buffer(2).unwrap();
assert_eq!(buf, vec![0x00, 0xBF]);
}
#[test]
fn thumb_nop_4byte_tiles_two_halfwords() {
let buf = thumb_nop_buffer(4).unwrap();
assert_eq!(buf, vec![0x00, 0xBF, 0x00, 0xBF]);
}
#[test]
fn thumb_nop_buffer_rejects_odd_length() {
assert!(thumb_nop_buffer(3).is_err());
assert!(thumb_nop_buffer(5).is_err());
}
#[test]
fn thumb_nop_buffer_zero_length_yields_empty() {
assert!(thumb_nop_buffer(0).unwrap().is_empty());
}
}