1#![allow(clippy::cast_possible_truncation)]
18
19mod codec;
20
21pub use codec::{register, Aarch64Codec};
22
23use ud_core::VAddr;
24use ud_ir::{ArchInsn, BasicBlock, Function, Terminator};
25
26pub const INSN_SIZE: usize = 4;
28
29#[derive(Debug, thiserror::Error)]
31pub enum Error {
32 #[error(
33 "byte buffer length {len} is not a multiple of {INSN_SIZE} (AArch64 insns are fixed-width)"
34 )]
35 Misaligned { len: usize },
36}
37
38pub type Result<T, E = Error> = std::result::Result<T, E>;
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44pub enum InsnKind {
45 BranchDirect { target: u64 },
47 BranchConditional { taken: u64, fallthrough: u64 },
49 CompareAndBranch { taken: u64, fallthrough: u64 },
52 TestBitAndBranch { taken: u64, fallthrough: u64 },
54 BranchLink { target: u64 },
56 BranchRegister,
58 BranchLinkRegister,
60 Return,
63 Nop,
65 Other,
67}
68
69#[derive(Debug, Clone, PartialEq, Eq)]
72pub struct DecodedInsn {
73 pub addr: VAddr,
74 pub bytes: [u8; INSN_SIZE],
75 pub kind: InsnKind,
76}
77
78impl DecodedInsn {
79 #[must_use]
81 pub fn opcode(&self) -> u32 {
82 u32::from_le_bytes(self.bytes)
83 }
84}
85
86impl ArchInsn for DecodedInsn {
87 fn addr(&self) -> VAddr {
88 self.addr
89 }
90
91 fn original_bytes(&self) -> &[u8] {
92 &self.bytes
93 }
94}
95
96pub fn decode(bytes: &[u8], start: u64) -> Result<Vec<DecodedInsn>> {
101 if bytes.len() % INSN_SIZE != 0 {
102 return Err(Error::Misaligned { len: bytes.len() });
103 }
104 let mut out = Vec::with_capacity(bytes.len() / INSN_SIZE);
105 for (i, chunk) in bytes.chunks_exact(INSN_SIZE).enumerate() {
106 let addr = start.saturating_add((i * INSN_SIZE) as u64);
107 let mut raw = [0u8; INSN_SIZE];
108 raw.copy_from_slice(chunk);
109 let opcode = u32::from_le_bytes(raw);
110 let kind = classify(opcode, addr);
111 out.push(DecodedInsn {
112 addr: VAddr(addr),
113 bytes: raw,
114 kind,
115 });
116 }
117 Ok(out)
118}
119
120fn classify(opcode: u32, addr: u64) -> InsnKind {
140 const INDIRECT_BRANCH_MASK: u32 = 0xffff_fc1f;
144
145 if opcode == 0xd503_201f {
147 return InsnKind::Nop;
148 }
149 if (opcode & INDIRECT_BRANCH_MASK) == 0xd65f_0000 {
150 return InsnKind::Return;
151 }
152 if (opcode & INDIRECT_BRANCH_MASK) == 0xd61f_0000 {
153 return InsnKind::BranchRegister;
154 }
155 if (opcode & INDIRECT_BRANCH_MASK) == 0xd63f_0000 {
156 return InsnKind::BranchLinkRegister;
157 }
158 if (opcode & 0xfc00_0000) == 0x1400_0000 {
160 let target = pc_rel26(addr, opcode);
161 return InsnKind::BranchDirect { target };
162 }
163 if (opcode & 0xfc00_0000) == 0x9400_0000 {
164 let target = pc_rel26(addr, opcode);
165 return InsnKind::BranchLink { target };
166 }
167 if (opcode & 0xff00_0010) == 0x5400_0000 {
169 let taken = pc_rel19(addr, opcode);
170 let fallthrough = addr.wrapping_add(INSN_SIZE as u64);
171 return InsnKind::BranchConditional { taken, fallthrough };
172 }
173 if (opcode & 0x7e00_0000) == 0x3400_0000 {
175 let taken = pc_rel19(addr, opcode);
176 let fallthrough = addr.wrapping_add(INSN_SIZE as u64);
177 return InsnKind::CompareAndBranch { taken, fallthrough };
178 }
179 if (opcode & 0x7e00_0000) == 0x3600_0000 {
181 let taken = pc_rel14(addr, opcode);
182 let fallthrough = addr.wrapping_add(INSN_SIZE as u64);
183 return InsnKind::TestBitAndBranch { taken, fallthrough };
184 }
185 InsnKind::Other
186}
187
188#[allow(clippy::cast_possible_wrap, clippy::cast_sign_loss)]
190fn pc_rel26(addr: u64, opcode: u32) -> u64 {
191 let imm26 = opcode & 0x03ff_ffff;
192 let signed = ((imm26 as i32) << 6) >> 6;
194 let off = i64::from(signed) << 2;
195 addr.wrapping_add(off as u64) }
197
198#[allow(clippy::cast_possible_wrap, clippy::cast_sign_loss)]
200fn pc_rel19(addr: u64, opcode: u32) -> u64 {
201 let imm19 = (opcode >> 5) & 0x0007_ffff;
202 let signed = ((imm19 as i32) << 13) >> 13;
203 let off = i64::from(signed) << 2;
204 addr.wrapping_add(off as u64) }
206
207#[allow(clippy::cast_possible_wrap, clippy::cast_sign_loss)]
209fn pc_rel14(addr: u64, opcode: u32) -> u64 {
210 let imm14 = (opcode >> 5) & 0x0000_3fff;
211 let signed = ((imm14 as i32) << 18) >> 18;
212 let off = i64::from(signed) << 2;
213 addr.wrapping_add(off as u64) }
215
216#[must_use]
220pub fn format_text(insn: &DecodedInsn) -> String {
221 match insn.kind {
222 InsnKind::BranchDirect { target } => format!("b 0x{target:x}"),
223 InsnKind::BranchLink { target } => format!("bl 0x{target:x}"),
224 InsnKind::BranchConditional { taken, .. } => format!("b.cond 0x{taken:x}"),
225 InsnKind::CompareAndBranch { taken, .. } => format!("cbz/cbnz 0x{taken:x}"),
226 InsnKind::TestBitAndBranch { taken, .. } => format!("tbz/tbnz 0x{taken:x}"),
227 InsnKind::BranchRegister => "br".into(),
228 InsnKind::BranchLinkRegister => "blr".into(),
229 InsnKind::Return => "ret".into(),
230 InsnKind::Nop => "nop".into(),
231 InsnKind::Other => format!("<arm64 0x{:08x}>", insn.opcode()),
232 }
233}
234
235#[must_use]
242pub fn lift_function(name: String, insns: &[DecodedInsn]) -> Function<DecodedInsn> {
243 let addr = insns.first().map_or(VAddr(0), |i| i.addr);
244 let terminator = insns
245 .last()
246 .map_or(Terminator::Fallthrough, |i| match i.kind {
247 InsnKind::Return => Terminator::Return,
248 InsnKind::BranchDirect { target } => Terminator::UnconditionalBranch {
249 target: VAddr(target),
250 },
251 InsnKind::BranchConditional { taken, fallthrough }
252 | InsnKind::CompareAndBranch { taken, fallthrough }
253 | InsnKind::TestBitAndBranch { taken, fallthrough } => Terminator::ConditionalBranch {
254 taken: VAddr(taken),
255 fallthrough: VAddr(fallthrough),
256 },
257 InsnKind::BranchRegister | InsnKind::BranchLinkRegister => Terminator::IndirectBranch,
258 _ => Terminator::Fallthrough,
259 });
260 Function {
261 addr,
262 name,
263 blocks: vec![BasicBlock {
264 addr,
265 insns: insns.to_vec(),
266 terminator,
267 }],
268 }
269}
270
271#[cfg(test)]
272mod tests {
273 use super::*;
274
275 #[test]
276 fn decode_splits_into_4byte_words() {
277 let bytes = [
279 0xc0, 0x03, 0x5f, 0xd6, 0x1f, 0x20, 0x03, 0xd5, ];
282 let insns = decode(&bytes, 0x1000).unwrap();
283 assert_eq!(insns.len(), 2);
284 assert_eq!(insns[0].addr, VAddr(0x1000));
285 assert_eq!(insns[0].kind, InsnKind::Return);
286 assert_eq!(insns[1].addr, VAddr(0x1004));
287 assert_eq!(insns[1].kind, InsnKind::Nop);
288 }
289
290 #[test]
291 fn rejects_misaligned_buffer() {
292 let bytes = [0x00, 0x01, 0x02];
293 assert!(matches!(
294 decode(&bytes, 0x1000),
295 Err(Error::Misaligned { len: 3 })
296 ));
297 }
298
299 #[test]
300 fn classifies_b_with_signed_target() {
301 let opcode: u32 = 0x14_00_00_04;
303 let bytes = opcode.to_le_bytes();
304 let insns = decode(&bytes, 0x1000).unwrap();
305 assert_eq!(insns[0].kind, InsnKind::BranchDirect { target: 0x1010 });
306 }
307
308 #[test]
309 fn classifies_bl_with_negative_target() {
310 let opcode: u32 = 0x97_ff_ff_fe;
313 let bytes = opcode.to_le_bytes();
314 let insns = decode(&bytes, 0x2000).unwrap();
315 assert_eq!(insns[0].kind, InsnKind::BranchLink { target: 0x1ff8 });
316 }
317
318 #[test]
319 fn classifies_b_cond() {
320 let opcode: u32 = 0x54_00_00_40;
323 let bytes = opcode.to_le_bytes();
324 let insns = decode(&bytes, 0x1000).unwrap();
325 assert!(matches!(
326 insns[0].kind,
327 InsnKind::BranchConditional { taken: 0x1008, .. }
328 ));
329 }
330
331 #[test]
332 fn ret_kind_drives_terminator() {
333 let bytes = [0x00, 0x00, 0x80, 0x52, 0xc0, 0x03, 0x5f, 0xd6];
335 let insns = decode(&bytes, 0x1000).unwrap();
336 let f = lift_function("f".into(), &insns);
337 assert_eq!(f.blocks.len(), 1);
338 assert_eq!(f.blocks[0].terminator, Terminator::Return);
339 }
340}