use iced_x86::FlowControl;
pub const LEN: usize = 4;
pub struct Insn {
pub addr: u64,
pub len: usize,
pub flow: FlowControl,
pub target: Option<u64>,
pub text: String,
}
fn sext(v: u64, bits: u32) -> i64 {
let shift = 64 - bits;
((v << shift) as i64) >> shift
}
fn normal(addr: u64, text: String) -> Insn {
Insn {
addr,
len: LEN,
flow: FlowControl::Next,
target: None,
text,
}
}
const COND: [&str; 16] = [
"eq", "ne", "cs", "cc", "mi", "pl", "vs", "vc", "hi", "ls", "ge", "lt", "gt", "le", "al", "nv",
];
fn reg(rd: u64, wide: bool) -> String {
if wide {
format!("x{rd}")
} else {
format!("w{rd}")
}
}
pub fn decode(code: &[u8], ip: u64) -> Option<Insn> {
let e: [u8; 4] = code.get(0..4)?.try_into().ok()?;
let w = u32::from_le_bytes(e);
let addr = ip;
let top6 = w >> 26;
if top6 == 0b000101 {
let off = sext(u64::from(w & 0x03ff_ffff), 26) << 2;
let t = addr.wrapping_add_signed(off);
return Some(Insn {
addr,
len: LEN,
flow: FlowControl::UnconditionalBranch,
target: Some(t),
text: format!("b 0x{t:x}"),
});
}
if top6 == 0b100101 {
let off = sext(u64::from(w & 0x03ff_ffff), 26) << 2;
let t = addr.wrapping_add_signed(off);
return Some(Insn {
addr,
len: LEN,
flow: FlowControl::Call,
target: Some(t),
text: format!("bl 0x{t:x}"),
});
}
if w & 0xff00_0000 == 0x5400_0000 {
let off = sext(u64::from((w >> 5) & 0x7_ffff), 19) << 2;
let t = addr.wrapping_add_signed(off);
let cond = COND[(w & 0xf) as usize];
return Some(Insn {
addr,
len: LEN,
flow: FlowControl::ConditionalBranch,
target: Some(t),
text: format!("b.{cond} 0x{t:x}"),
});
}
if w & 0x7f00_0000 == 0x3400_0000 {
let mn = if w & 0x0100_0000 != 0 { "cbnz" } else { "cbz" };
let off = sext(u64::from((w >> 5) & 0x7_ffff), 19) << 2;
let t = addr.wrapping_add_signed(off);
let rd = u64::from(w & 0x1f);
return Some(Insn {
addr,
len: LEN,
flow: FlowControl::ConditionalBranch,
target: Some(t),
text: format!("{mn} x{rd}, 0x{t:x}"),
});
}
if (w & 0xff00_0000) == 0xcc00_0000 || (w & 0xff00_0000) == 0xcd00_0000 {
let op = if w & 0x0100_0000 != 0 { "tbnz" } else { "tbz" };
let off = sext(u64::from((w >> 5) & 0x3fff), 14) << 2;
let t = addr.wrapping_add_signed(off);
let rd = u64::from(w & 0x1f);
let bit = u64::from(((w >> 19) & 0x1f) | ((w >> 5) >> 26));
return Some(Insn {
addr,
len: LEN,
flow: FlowControl::ConditionalBranch,
target: Some(t),
text: format!("{op} x{rd}, #{bit}, 0x{t:x}"),
});
}
if w & 0xffff_fc1f == 0xd61f_0000 {
return Some(Insn {
addr,
len: LEN,
flow: FlowControl::IndirectBranch,
target: None,
text: format!("br x{}", (w >> 5) & 0x1f),
});
}
if w & 0xffff_fc1f == 0xd63f_0000 {
return Some(Insn {
addr,
len: LEN,
flow: FlowControl::IndirectCall,
target: None,
text: format!("blr x{}", (w >> 5) & 0x1f),
});
}
if w & 0xffff_fc1f == 0xd65f_0000 {
let r = (w >> 5) & 0x1f;
return Some(Insn {
addr,
len: LEN,
flow: FlowControl::Return,
target: None,
text: if r == 0x1e {
"ret".to_string()
} else {
format!("ret x{r}")
},
});
}
if (w & 0xffe0_0000) == 0xd400_0000 {
return Some(Insn {
addr,
len: LEN,
flow: FlowControl::Interrupt,
target: None,
text: format!("svc #{:x}", w & 0xffff),
});
}
if (w & 0x9f00_0000) == 0x9000_0000 || (w & 0x9f00_0000) == 0x1000_0000 {
let is_adrp = w & 0x8000_0000 != 0;
let immhi = u64::from((w >> 5) & 0x7_ffff);
let immlo = u64::from((w >> 29) & 0x3);
let imm21 = sext((immhi << 2) | immlo, 21);
let rd = u64::from(w & 0x1f);
let text = if is_adrp {
let t = (addr & !0xfff).wrapping_add_signed(imm21 << 12);
format!("adrp x{rd}, 0x{t:x}")
} else {
let t = addr.wrapping_add_signed(imm21);
format!("adr x{rd}, 0x{t:x}")
};
return Some(normal(addr, text));
}
if (w & 0x7fa0_0000) == 0x5280_0000
|| (w & 0x7fa0_0000) == 0x1280_0000
|| (w & 0x7fa0_0000) == 0x7280_0000
{
let wide = w & 0x8000_0000 != 0;
let rd = u64::from(w & 0x1f);
let imm = (w >> 5) & 0xffff;
let hw = (w >> 21) & 0x3;
let mnem = if (w & 0x7fa0_0000) == 0x5280_0000 {
"movz"
} else if (w & 0x7fa0_0000) == 0x1280_0000 {
"movn"
} else {
"movk"
};
let mut text = format!("{mnem} {}, #{imm:#x}", reg(rd, wide));
if hw != 0 {
text.push_str(&format!(", lsl #{hw}"));
}
return Some(normal(addr, text));
}
if (w & 0x1e00_0000) == 0x1000_0000 {
let wide = w & 0x8000_0000 != 0;
let sub = w & 0x4000_0000 != 0;
let mnem = match ((w >> 30) & 1 == 0, sub) {
(true, false) => "add",
(true, true) => "sub",
(false, false) => "adds",
(false, true) => "subs",
};
let rd = u64::from(w & 0x1f);
let rn = u64::from((w >> 5) & 0x1f);
let imm = u64::from((w >> 10) & 0xfff);
let shift = if w & 0x0040_0000 != 0 { 12 } else { 0 };
return Some(normal(
addr,
format!(
"{mnem:<8}{}, {}, #{shift_imm}",
reg(rd, wide),
reg(rn, wide),
shift_imm = if shift == 12 {
imm.checked_shl(12).unwrap_or(0).to_string() + ", lsl #12"
} else {
imm.to_string()
},
),
));
}
if (w & 0x1ff0_0000) == 0x0b00_0000 && (w & 0x0060_0000) == 0 {
let wide = w & 0x8000_0000 != 0;
let sub = w & 0x4000_0000 != 0;
let mnem = match ((w >> 30) & 1 == 0, sub) {
(true, false) => "add",
(true, true) => "sub",
(false, false) => "adds",
(false, true) => "subs",
};
let rd = u64::from(w & 0x1f);
let rn = u64::from((w >> 5) & 0x1f);
let rm = u64::from((w >> 16) & 0x1f);
return Some(normal(
addr,
format!(
"{mnem:<8}{}, {}, {}",
reg(rd, wide),
reg(rn, wide),
reg(rm, wide)
),
));
}
if (w & 0xffc0_0000) == 0xf940_0000 || (w & 0xffc0_0000) == 0xf900_0000 {
let wide = true;
let store = w & 0x0040_0000 == 0;
let rt = u64::from(w & 0x1f);
let rn = u64::from((w >> 5) & 0x1f);
let imm = u64::from((w >> 10) & 0xfff) << 3;
let mnem = if store { "str" } else { "ldr" };
return Some(normal(
addr,
format!("{mnem:<8}{}, [{}, #{imm}]", reg(rt, wide), reg(rn, wide)),
));
}
if (w & 0x7fe0_0000) == 0x2a00_0000 {
let wide = w & 0x8000_0000 != 0;
let rd = u64::from(w & 0x1f);
let rn = u64::from((w >> 5) & 0x1f);
let rm = u64::from((w >> 16) & 0x1f);
return Some(normal(
addr,
format!(
"orr {}, {}, {}",
reg(rd, wide),
reg(rn, wide),
reg(rm, wide)
),
));
}
if w == 0xd503_201f {
return Some(normal(addr, "nop".to_string()));
}
Some(normal(addr, format!("dword 0x{w:08x}")))
}
pub fn text(bytes: &[u8], ip: u64) -> String {
decode(bytes, ip)
.map(|i| i.text)
.unwrap_or_else(|| "??".into())
}
#[cfg(test)]
mod tests {
use super::*;
fn dec(w: u32, ip: u64) -> Insn {
decode(&w.to_le_bytes(), ip).unwrap()
}
#[test]
fn bl_is_a_call_with_a_pc_relative_target() {
let w = 0x9400_0008; let i = dec(w, 0x1000);
assert_eq!(i.flow, FlowControl::Call);
assert_eq!(i.target, Some(0x1020));
}
#[test]
fn a_backward_branch_sign_extends() {
let off = -4i32 as u32 & 0x03ff_ffff; let w = 0x1400_0000 | off;
let i = dec(w, 0x1000);
assert_eq!(i.flow, FlowControl::UnconditionalBranch);
assert_eq!(i.target, Some(0xff0));
}
#[test]
fn adr_is_pc_relative_with_no_scaling() {
let imm = 4u32;
let immlo = imm & 3;
let immhi = (imm >> 2) & 0x7ffff;
let w = 0x1000_0000 | (immlo << 29) | (immhi << 5); let i = dec(w, 0x1000);
assert_eq!(i.text, "adr x0, 0x1004");
}
#[test]
fn adrp_aligns_pc_to_a_page_before_adding() {
let imm = 1u32; let immlo = imm & 3;
let immhi = (imm >> 2) & 0x7ffff;
let w = 0x9000_0000 | (immlo << 29) | (immhi << 5); let i = dec(w, 0x1abc);
assert_eq!(i.text, "adrp x0, 0x2000", "Align(0x1abc,4096)+0x1000");
}
#[test]
fn ret_and_blr_classify_as_flow() {
assert_eq!(dec(0xd65f_03c0, 0).flow, FlowControl::Return);
assert_eq!(dec(0xd63f_0000, 0).flow, FlowControl::IndirectCall);
assert_eq!(dec(0xd61f_0000, 0).flow, FlowControl::IndirectBranch);
}
}