//! 32-bit ARM architecture support backed by `yaxpeax-arm`. use crate::{ arch::{Arch, LiftArch, LiftEnv, RegisterDesc}, flow::{FlowInfo, FlowKind, FlowTarget}, ir::{Block, FrontendBuilder, IntCC, MemSize, Type, Value, Variable}, }; use cranelift_entity::EntityRef; use yaxpeax_arch::{Decoder, LengthedInstruction, ReaderBuilder, U8Reader}; use yaxpeax_arm::armv7::{ ConditionCode, DecodeError, InstDecoder, Instruction, Opcode, Operand, Reg, RegShiftStyle, ShiftStyle, }; /// ARM execution mode. /// /// This module targets 32-bit ARM and Thumb. /// AArch64 should live in a separate `arm64.rs`. #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub enum ArmMode { /// ARM state. Arm, /// Thumb / Thumb-2 state. Thumb, } /// ARM endianness. #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub enum ArmEndian { Little, Big, } /// ARM architectural profile. #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub enum ArmProfile { A, R, M, } /// ARM ISA version / feature profile. #[derive(Clone, Debug, PartialEq, Eq)] pub struct ArmFeatures { pub version: ArmVersion, pub thumb2: bool, pub vfp: bool, pub neon: bool, pub dsp: bool, } impl Default for ArmFeatures { fn default() -> Self { Self { version: ArmVersion::V7, thumb2: true, vfp: false, neon: false, dsp: false, } } } /// ARM ISA version. #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub enum ArmVersion { V5, V6, V7, } /// Flat ARM architectural register identity. #[repr(u8)] #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub enum ArmReg { R0 = 0, R1 = 1, R2 = 2, R3 = 3, R4 = 4, R5 = 5, R6 = 6, R7 = 7, R8 = 8, R9 = 9, R10 = 10, R11 = 11, R12 = 12, Sp = 13, Lr = 14, Pc = 15, } /// ARM condition code. #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub enum ArmCond { Eq, Ne, Cs, Cc, Mi, Pl, Vs, Vc, Hi, Ls, Ge, Lt, Gt, Le, Al, } /// ARM status flag identity. #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub enum ArmFlag { N, Z, C, V, Q, } /// Calling-convention placeholder. #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub enum ArmCallingConvention { Aapcs, AapcsVfp, } /// Reusable ARM architecture object. #[derive(Clone, Debug)] pub struct Arm { pub endian: ArmEndian, pub profile: ArmProfile, pub features: ArmFeatures, pub default_cc: ArmCallingConvention, } impl Default for Arm { fn default() -> Self { Self { endian: ArmEndian::Little, profile: ArmProfile::A, features: ArmFeatures::default(), default_cc: ArmCallingConvention::Aapcs, } } } /// Mutable per-lift ARM state. pub struct ArmLiftCtx<'a> { /// Static architecture description. pub arch: &'a Arm, /// Translation-session environment. pub env: &'a dyn LiftEnv, /// Current frontend builder. pub fb: &'a mut FrontendBuilder<'a>, /// Current program counter. pub pc: u64, /// Current execution mode. pub mode: ArmMode, /// Current SSA bindings for architectural registers. pub regs: ArmRegisterState, /// Current SSA bindings for flags. pub flags: ArmFlagState, /// Cached comparison operands for conditional branch lowering. pub last_cmp: Option<(Value, Value)>, /// Temporary lifting scratch state. pub scratch: ArmScratch, } impl<'a> ArmLiftCtx<'a> { /// Creates a new ARM lift context. pub fn new( arch: &'a Arm, env: &'a dyn LiftEnv, fb: &'a mut FrontendBuilder<'a>, pc: u64, mode: ArmMode, ) -> Self { let word_ty = Type::i32(); for reg in REGISTERS { fb.declare_var(Self::reg_var(reg.reg), word_ty); } for flag in [ArmFlag::N, ArmFlag::Z, ArmFlag::C, ArmFlag::V, ArmFlag::Q] { fb.declare_var(Self::flag_var(flag), Type::bool()); } Self { arch, env, fb, pc, mode, regs: ArmRegisterState::default(), flags: ArmFlagState::default(), last_cmp: None, scratch: ArmScratch::default(), } } /// Returns the integer word type for the current mode. pub fn word_ty(&self) -> Type { Type::i32() } /// Maps a register identity to a frontend variable. pub fn reg_var(reg: ArmReg) -> Variable { Variable::new(reg as usize) } /// Maps a flag identity to a frontend variable. pub fn flag_var(flag: ArmFlag) -> Variable { Variable::new(0x100 + flag as usize) } /// Reads the current SSA value for a register. pub fn read_reg(&mut self, reg: ArmReg) -> Value { self.fb.use_var(Self::reg_var(reg)) } /// Writes an SSA value to a register. pub fn write_reg(&mut self, reg: ArmReg, value: Value) { self.fb.def_var(Self::reg_var(reg), value); } /// Reads the current SSA value for a flag. pub fn read_flag(&mut self, flag: ArmFlag) -> Value { self.fb.use_var(Self::flag_var(flag)) } /// Writes an SSA value to a flag. pub fn write_flag(&mut self, flag: ArmFlag, value: Value) { self.fb.def_var(Self::flag_var(flag), value); } /// Resolves a direct target address to an IR block. pub fn direct_block(&self, target_addr: u64) -> Option { self.env.block_for_target(target_addr) } /// Returns the fallthrough block for the current instruction, if present. pub fn fallthrough_block(&self) -> Option { self.env.fallthrough_block() } /// Computes a boolean SSA value for a condition code using the last cached /// compare. pub fn condition_value(&mut self, cond: ArmCond) -> Option { let (lhs, rhs) = self.last_cmp?; let v = match cond { ArmCond::Eq => self.fb.icmp(crate::ir::IntCC::Eq, lhs, rhs), ArmCond::Ne => self.fb.icmp(crate::ir::IntCC::Ne, lhs, rhs), ArmCond::Lt => self.fb.icmp(crate::ir::IntCC::Slt, lhs, rhs), ArmCond::Le => self.fb.icmp(crate::ir::IntCC::Sle, lhs, rhs), ArmCond::Gt => self.fb.icmp(crate::ir::IntCC::Sgt, lhs, rhs), ArmCond::Ge => self.fb.icmp(crate::ir::IntCC::Sge, lhs, rhs), // Placeholder until full NZCV lowering exists. ArmCond::Cs | ArmCond::Cc | ArmCond::Mi | ArmCond::Pl | ArmCond::Vs | ArmCond::Vc | ArmCond::Hi | ArmCond::Ls | ArmCond::Al => return None, }; Some(v) } } /// Current SSA bindings for architectural registers. #[derive(Clone, Debug, Default)] pub struct ArmRegisterState { pub gpr: [Option; 16], } /// Current SSA bindings for status flags. #[derive(Clone, Debug, Default)] pub struct ArmFlagState { pub n: Option, pub z: Option, pub c: Option, pub v: Option, pub q: Option, } /// Temporary lifting scratch state. #[derive(Clone, Debug, Default)] pub struct ArmScratch { pub tmp0: Option, pub tmp1: Option, pub tmp2: Option, } /// Public ARM register table. pub const REGISTERS: [RegisterDesc; 16] = [ RegisterDesc { reg: ArmReg::R0, name: "r0", bits: 32, }, RegisterDesc { reg: ArmReg::R1, name: "r1", bits: 32, }, RegisterDesc { reg: ArmReg::R2, name: "r2", bits: 32, }, RegisterDesc { reg: ArmReg::R3, name: "r3", bits: 32, }, RegisterDesc { reg: ArmReg::R4, name: "r4", bits: 32, }, RegisterDesc { reg: ArmReg::R5, name: "r5", bits: 32, }, RegisterDesc { reg: ArmReg::R6, name: "r6", bits: 32, }, RegisterDesc { reg: ArmReg::R7, name: "r7", bits: 32, }, RegisterDesc { reg: ArmReg::R8, name: "r8", bits: 32, }, RegisterDesc { reg: ArmReg::R9, name: "r9", bits: 32, }, RegisterDesc { reg: ArmReg::R10, name: "r10", bits: 32, }, RegisterDesc { reg: ArmReg::R11, name: "r11", bits: 32, }, RegisterDesc { reg: ArmReg::R12, name: "r12", bits: 32, }, RegisterDesc { reg: ArmReg::Sp, name: "sp", bits: 32, }, RegisterDesc { reg: ArmReg::Lr, name: "lr", bits: 32, }, RegisterDesc { reg: ArmReg::Pc, name: "pc", bits: 32, }, ]; /// ARM lift-time errors. #[derive(Clone, Debug, PartialEq, Eq)] pub enum ArmLiftError { UnsupportedInstruction, UnsupportedCondition, UnsupportedAddressing, MissingCompareState, MissingTargetBlock, InvalidImmediate, UnsupportedControlFlow, } /// Parsed instruction text used by the starter lifter. /// /// This is intentionally derived from the decoder's `Display` output so the /// architecture boundary can use the decoder crate's instruction type directly. #[derive(Clone, Debug, PartialEq, Eq)] struct ParsedText { mnemonic: String, operands: Vec, } impl Arm { fn map_reg(reg: Reg) -> ArmReg { match reg.number() { 0 => ArmReg::R0, 1 => ArmReg::R1, 2 => ArmReg::R2, 3 => ArmReg::R3, 4 => ArmReg::R4, 5 => ArmReg::R5, 6 => ArmReg::R6, 7 => ArmReg::R7, 8 => ArmReg::R8, 9 => ArmReg::R9, 10 => ArmReg::R10, 11 => ArmReg::R11, 12 => ArmReg::R12, 13 => ArmReg::Sp, 14 => ArmReg::Lr, 15 => ArmReg::Pc, _ => unreachable!("armv7 Reg::number() is documented as 0..=15"), } } fn intcc_from_cond(cond: ConditionCode) -> Option { Some(match cond { ConditionCode::EQ => IntCC::Eq, ConditionCode::NE => IntCC::Ne, ConditionCode::GE => IntCC::Sge, ConditionCode::LT => IntCC::Slt, ConditionCode::GT => IntCC::Sgt, ConditionCode::LE => IntCC::Sle, ConditionCode::AL => return None, // these need explicit NZCV modeling ConditionCode::HS | ConditionCode::LO | ConditionCode::MI | ConditionCode::PL | ConditionCode::VS | ConditionCode::VC | ConditionCode::HI | ConditionCode::LS => return None, }) } fn direct_target_from_operand(pc: u64, op: Operand) -> Option { match op { Operand::BranchOffset(off) => Some(pc.wrapping_add_signed((off as i64) << 2)), Operand::BranchThumbOffset(off) => Some(pc.wrapping_add_signed((off as i64) << 1)), _ => None, } } fn read_shifted_reg( cx: &mut ArmLiftCtx<'_>, shift: yaxpeax_arm::armv7::RegShift, ) -> Result { let shift = shift.into_shift(); match shift { RegShiftStyle::RegImm(s) => { let base = cx.read_reg(Self::map_reg(s.shiftee())); let amt = cx.fb.iconst(Type::i32(), s.imm() as i64); Ok(match s.stype() { ShiftStyle::LSL => cx.fb.shl(base, amt, Type::i32()), ShiftStyle::LSR => cx.fb.lshr(base, amt, Type::i32()), ShiftStyle::ASR => cx.fb.ashr(base, amt, Type::i32()), ShiftStyle::ROR => return Err(ArmLiftError::UnsupportedInstruction), }) } RegShiftStyle::RegReg(s) => { let base = cx.read_reg(Self::map_reg(s.shiftee())); let amt = cx.read_reg(Self::map_reg(s.shifter())); Ok(match s.stype() { ShiftStyle::LSL => cx.fb.shl(base, amt, Type::i32()), ShiftStyle::LSR => cx.fb.lshr(base, amt, Type::i32()), ShiftStyle::ASR => cx.fb.ashr(base, amt, Type::i32()), ShiftStyle::ROR => return Err(ArmLiftError::UnsupportedInstruction), }) } } } fn read_operand(cx: &mut ArmLiftCtx<'_>, op: Operand) -> Result { match op { Operand::Reg(r) => Ok(cx.read_reg(Self::map_reg(r))), Operand::Imm12(v) => Ok(cx.fb.iconst(Type::i32(), v as i64)), Operand::Imm32(v) => Ok(cx.fb.iconst(Type::i32(), v as i64)), Operand::RegShift(shift) => Self::read_shifted_reg(cx, shift), _ => Err(ArmLiftError::UnsupportedInstruction), } } fn mem_addr(cx: &mut ArmLiftCtx<'_>, op: Operand) -> Result { match op { Operand::RegDeref(base) => Ok(cx.read_reg(Self::map_reg(base))), Operand::RegDerefPreindexOffset(base, off, add, wback) => { let base_reg = Self::map_reg(base); let base_v = cx.read_reg(base_reg); let off_v = cx.fb.iconst(Type::i32(), off as i64); let addr = if add { cx.fb.iadd(base_v, off_v, Type::i32()) } else { cx.fb.isub(base_v, off_v, Type::i32()) }; if wback { cx.write_reg(base_reg, addr); } Ok(addr) } Operand::RegDerefPostindexOffset(base, off, add, wback) => { let base_reg = Self::map_reg(base); let base_v = cx.read_reg(base_reg); let off_v = cx.fb.iconst(Type::i32(), off as i64); let new_base = if add { cx.fb.iadd(base_v, off_v, Type::i32()) } else { cx.fb.isub(base_v, off_v, Type::i32()) }; if wback { cx.write_reg(base_reg, new_base); } Ok(base_v) } Operand::RegDerefPreindexReg(base, idx, add, wback) => { let base_reg = Self::map_reg(base); let base_v = cx.read_reg(base_reg); let idx_v = cx.read_reg(Self::map_reg(idx)); let addr = if add { cx.fb.iadd(base_v, idx_v, Type::i32()) } else { cx.fb.isub(base_v, idx_v, Type::i32()) }; if wback { cx.write_reg(base_reg, addr); } Ok(addr) } Operand::RegDerefPostindexReg(base, idx, add, wback) => { let base_reg = Self::map_reg(base); let base_v = cx.read_reg(base_reg); let idx_v = cx.read_reg(Self::map_reg(idx)); let new_base = if add { cx.fb.iadd(base_v, idx_v, Type::i32()) } else { cx.fb.isub(base_v, idx_v, Type::i32()) }; if wback { cx.write_reg(base_reg, new_base); } Ok(base_v) } Operand::RegDerefPreindexRegShift(base, shift, add, wback) => { let base_reg = Self::map_reg(base); let base_v = cx.read_reg(base_reg); let idx_v = Self::read_shifted_reg(cx, shift)?; let addr = if add { cx.fb.iadd(base_v, idx_v, Type::i32()) } else { cx.fb.isub(base_v, idx_v, Type::i32()) }; if wback { cx.write_reg(base_reg, addr); } Ok(addr) } Operand::RegDerefPostindexRegShift(base, shift, add, wback) => { let base_reg = Self::map_reg(base); let base_v = cx.read_reg(base_reg); let idx_v = Self::read_shifted_reg(cx, shift)?; let new_base = if add { cx.fb.iadd(base_v, idx_v, Type::i32()) } else { cx.fb.isub(base_v, idx_v, Type::i32()) }; if wback { cx.write_reg(base_reg, new_base); } Ok(base_v) } _ => Err(ArmLiftError::UnsupportedAddressing), } } fn cond_value( cx: &mut ArmLiftCtx<'_>, cond: ConditionCode, ) -> Result, ArmLiftError> { let Some(cc) = Self::intcc_from_cond(cond) else { return if cond == ConditionCode::AL { Ok(None) } else { Err(ArmLiftError::UnsupportedCondition) }; }; let (lhs, rhs) = cx.last_cmp.ok_or(ArmLiftError::MissingCompareState)?; Ok(Some(cx.fb.icmp(cc, lhs, rhs))) } /// Creates the appropriate decoder for the current mode. fn decoder_for_mode(mode: ArmMode) -> InstDecoder { match mode { ArmMode::Arm => InstDecoder::default(), ArmMode::Thumb => InstDecoder::default_thumb(), } } /// Returns the canonical textual register name. pub fn reg_name(reg: ArmReg) -> &'static str { match reg { ArmReg::R0 => "r0", ArmReg::R1 => "r1", ArmReg::R2 => "r2", ArmReg::R3 => "r3", ArmReg::R4 => "r4", ArmReg::R5 => "r5", ArmReg::R6 => "r6", ArmReg::R7 => "r7", ArmReg::R8 => "r8", ArmReg::R9 => "r9", ArmReg::R10 => "r10", ArmReg::R11 => "r11", ArmReg::R12 => "r12", ArmReg::Sp => "sp", ArmReg::Lr => "lr", ArmReg::Pc => "pc", } } fn parse_text(inst: &Instruction) -> ParsedText { let text = inst.to_string(); let mut parts = text.splitn(2, ' '); let mnemonic = parts.next().unwrap_or("").trim().to_ascii_lowercase(); let operands = parts .next() .map(|rest| { rest.split(',') .map(|x| x.trim().to_ascii_lowercase()) .filter(|x| !x.is_empty()) .collect() }) .unwrap_or_default(); ParsedText { mnemonic, operands } } fn parse_reg(text: &str) -> Option { Some(match text { "r0" => ArmReg::R0, "r1" => ArmReg::R1, "r2" => ArmReg::R2, "r3" => ArmReg::R3, "r4" => ArmReg::R4, "r5" => ArmReg::R5, "r6" => ArmReg::R6, "r7" => ArmReg::R7, "r8" => ArmReg::R8, "r9" => ArmReg::R9, "r10" => ArmReg::R10, "r11" => ArmReg::R11, "r12" => ArmReg::R12, "sp" | "r13" => ArmReg::Sp, "lr" | "r14" => ArmReg::Lr, "pc" | "r15" => ArmReg::Pc, _ => return None, }) } fn parse_imm(text: &str) -> Option { let s = text.trim(); let s = s.strip_prefix('#').unwrap_or(s); if let Some(hex) = s.strip_prefix("-0x") { i64::from_str_radix(hex, 16).ok().map(|v| -v) } else if let Some(hex) = s.strip_prefix("0x") { i64::from_str_radix(hex, 16).ok() } else { s.parse::().ok() } } fn parse_target(text: &str) -> Option { let s = text.trim(); let s = s.strip_prefix('#').unwrap_or(s); if let Some(hex) = s.strip_prefix("0x") { u64::from_str_radix(hex, 16).ok() } else { s.parse::().ok() } } fn parse_mem(text: &str) -> Option<(ArmReg, i64)> { let t = text.trim(); if !(t.starts_with('[') && t.ends_with(']')) { return None; } let inner = &t[1..t.len() - 1]; let parts: Vec<_> = inner.split(',').map(|p| p.trim()).collect(); let base = Self::parse_reg(parts.first().copied()?)?; let off = if let Some(off) = parts.get(1) { Self::parse_imm(off)? } else { 0 }; Some((base, off)) } fn parse_cond_suffix(s: &str) -> Option { Some(match s { "eq" => ArmCond::Eq, "ne" => ArmCond::Ne, "cs" => ArmCond::Cs, "cc" => ArmCond::Cc, "mi" => ArmCond::Mi, "pl" => ArmCond::Pl, "vs" => ArmCond::Vs, "vc" => ArmCond::Vc, "hi" => ArmCond::Hi, "ls" => ArmCond::Ls, "ge" => ArmCond::Ge, "lt" => ArmCond::Lt, "gt" => ArmCond::Gt, "le" => ArmCond::Le, "al" => ArmCond::Al, _ => return None, }) } fn parse_mnemonic(mnemonic: &str) -> (&str, Option, bool) { // returns: (base, cond, sets_flags) // // handles shapes like: // add // adds // addeq // addseq // cmp // beq // bl // bx // let m = mnemonic; let mut setflags = false; let mut cond = None; // longest bases first where prefixes overlap for base in ["bl", "bx", "ldr", "str", "cmp", "mov", "add", "sub", "b"] { if let Some(rest) = m.strip_prefix(base) { let mut rest = rest; if matches!(base, "add" | "sub" | "mov") && let Some(stripped) = rest.strip_prefix('s') { setflags = true; rest = stripped; } if !rest.is_empty() { cond = Self::parse_cond_suffix(rest); } return (base, cond, setflags); } } (mnemonic, None, false) } fn operand_value(cx: &mut ArmLiftCtx<'_>, text: &str) -> Result { if let Some(reg) = Self::parse_reg(text) { Ok(cx.read_reg(reg)) } else if let Some(imm) = Self::parse_imm(text) { Ok(cx.fb.iconst(cx.word_ty(), imm)) } else { Err(ArmLiftError::UnsupportedInstruction) } } fn address_from_mem(cx: &mut ArmLiftCtx<'_>, text: &str) -> Result { let (base, off) = Self::parse_mem(text).ok_or(ArmLiftError::UnsupportedAddressing)?; let base_v = cx.read_reg(base); if off == 0 { Ok(base_v) } else { let off_v = cx.fb.iconst(cx.word_ty(), off); Ok(cx.fb.iadd(base_v, off_v, cx.word_ty())) } } pub fn lift_inst( &self, cx: &mut ArmLiftCtx<'_>, inst: &Instruction, ) -> Result<(), ArmLiftError> { match inst.opcode { Opcode::NOP => Ok(()), Opcode::MOV => { if inst.condition != ConditionCode::AL { return Err(ArmLiftError::UnsupportedCondition); } let rd = match inst.operands[0] { Operand::Reg(r) => Self::map_reg(r), _ => return Err(ArmLiftError::UnsupportedInstruction), }; let src = Self::read_operand(cx, inst.operands[1])?; cx.write_reg(rd, src); Ok(()) } Opcode::ADD => { if inst.condition != ConditionCode::AL { return Err(ArmLiftError::UnsupportedCondition); } let rd = match inst.operands[0] { Operand::Reg(r) => Self::map_reg(r), _ => return Err(ArmLiftError::UnsupportedInstruction), }; let lhs = Self::read_operand(cx, inst.operands[1])?; let rhs = Self::read_operand(cx, inst.operands[2])?; let out = cx.fb.iadd(lhs, rhs, Type::i32()); cx.write_reg(rd, out); Ok(()) } Opcode::SUB => { if inst.condition != ConditionCode::AL { return Err(ArmLiftError::UnsupportedCondition); } let rd = match inst.operands[0] { Operand::Reg(r) => Self::map_reg(r), _ => return Err(ArmLiftError::UnsupportedInstruction), }; let lhs = Self::read_operand(cx, inst.operands[1])?; let rhs = Self::read_operand(cx, inst.operands[2])?; let out = cx.fb.isub(lhs, rhs, Type::i32()); cx.write_reg(rd, out); Ok(()) } Opcode::CMP => { if inst.condition != ConditionCode::AL { return Err(ArmLiftError::UnsupportedCondition); } let lhs = Self::read_operand(cx, inst.operands[0])?; let rhs = Self::read_operand(cx, inst.operands[1])?; cx.last_cmp = Some((lhs, rhs)); Ok(()) } Opcode::LDR => { if inst.condition != ConditionCode::AL { return Err(ArmLiftError::UnsupportedCondition); } let rt = match inst.operands[0] { Operand::Reg(r) => Self::map_reg(r), _ => return Err(ArmLiftError::UnsupportedInstruction), }; let addr = Self::mem_addr(cx, inst.operands[1])?; let val = cx.fb.load(addr, MemSize::S4, Type::i32()); cx.write_reg(rt, val); Ok(()) } Opcode::STR => { if inst.condition != ConditionCode::AL { return Err(ArmLiftError::UnsupportedCondition); } let rt = match inst.operands[0] { Operand::Reg(r) => Self::map_reg(r), _ => return Err(ArmLiftError::UnsupportedInstruction), }; let addr = Self::mem_addr(cx, inst.operands[1])?; let val = cx.read_reg(rt); cx.fb.store(addr, val, MemSize::S4); Ok(()) } Opcode::B => { let target_addr = Self::direct_target_from_operand(cx.pc, inst.operands[0]) .ok_or(ArmLiftError::MissingTargetBlock)?; let target_block = cx .direct_block(target_addr) .ok_or(ArmLiftError::MissingTargetBlock)?; match Self::cond_value(cx, inst.condition)? { None => { cx.fb.jump(target_block); Ok(()) } Some(cond) => { let fallthrough = cx .fallthrough_block() .ok_or(ArmLiftError::MissingTargetBlock)?; cx.fb.br_if(cond, target_block, fallthrough); Ok(()) } } } Opcode::BL | Opcode::BLX => { let target_addr = Self::direct_target_from_operand(cx.pc, inst.operands[0]) .ok_or(ArmLiftError::UnsupportedControlFlow)?; let callee = cx.fb.iconst(Type::ptr(32), target_addr as i64); cx.fb.call(callee, &[]); Ok(()) } Opcode::BX => match inst.operands[0] { Operand::Reg(r) if r.number() == 14 => { let ret0 = cx.read_reg(ArmReg::R0); cx.fb.ret(&[ret0]); Ok(()) } _ => Err(ArmLiftError::UnsupportedControlFlow), }, _ => Err(ArmLiftError::UnsupportedInstruction), } } fn guard_cond( &self, cx: &mut ArmLiftCtx<'_>, cond: ArmCond, ) -> Result, ArmLiftError> { match cond { ArmCond::Al => Ok(None), _ => cx .condition_value(cond) .map(Some) .ok_or(ArmLiftError::MissingCompareState), } } fn lift_mov( &self, cx: &mut ArmLiftCtx<'_>, cond: ArmCond, parsed: &ParsedText, ) -> Result<(), ArmLiftError> { if cond != ArmCond::Al { return Err(ArmLiftError::UnsupportedCondition); } let rd = Self::parse_reg( parsed .operands .first() .ok_or(ArmLiftError::UnsupportedInstruction)?, ) .ok_or(ArmLiftError::UnsupportedInstruction)?; let src = Self::operand_value( cx, parsed .operands .get(1) .ok_or(ArmLiftError::UnsupportedInstruction)?, )?; cx.write_reg(rd, src); Ok(()) } fn lift_add( &self, cx: &mut ArmLiftCtx<'_>, cond: ArmCond, parsed: &ParsedText, ) -> Result<(), ArmLiftError> { if cond != ArmCond::Al { return Err(ArmLiftError::UnsupportedCondition); } let rd = Self::parse_reg( parsed .operands .first() .ok_or(ArmLiftError::UnsupportedInstruction)?, ) .ok_or(ArmLiftError::UnsupportedInstruction)?; let rn = Self::parse_reg( parsed .operands .get(1) .ok_or(ArmLiftError::UnsupportedInstruction)?, ) .ok_or(ArmLiftError::UnsupportedInstruction)?; let op2 = Self::operand_value( cx, parsed .operands .get(2) .ok_or(ArmLiftError::UnsupportedInstruction)?, )?; let lhs = cx.read_reg(rn); let out = cx.fb.iadd(lhs, op2, cx.word_ty()); cx.write_reg(rd, out); Ok(()) } fn lift_sub( &self, cx: &mut ArmLiftCtx<'_>, cond: ArmCond, parsed: &ParsedText, ) -> Result<(), ArmLiftError> { if cond != ArmCond::Al { return Err(ArmLiftError::UnsupportedCondition); } let rd = Self::parse_reg( parsed .operands .first() .ok_or(ArmLiftError::UnsupportedInstruction)?, ) .ok_or(ArmLiftError::UnsupportedInstruction)?; let rn = Self::parse_reg( parsed .operands .get(1) .ok_or(ArmLiftError::UnsupportedInstruction)?, ) .ok_or(ArmLiftError::UnsupportedInstruction)?; let op2 = Self::operand_value( cx, parsed .operands .get(2) .ok_or(ArmLiftError::UnsupportedInstruction)?, )?; let lhs = cx.read_reg(rn); let out = cx.fb.isub(lhs, op2, cx.word_ty()); cx.write_reg(rd, out); Ok(()) } fn lift_cmp( &self, cx: &mut ArmLiftCtx<'_>, cond: ArmCond, parsed: &ParsedText, ) -> Result<(), ArmLiftError> { if cond != ArmCond::Al { return Err(ArmLiftError::UnsupportedCondition); } let rn = Self::parse_reg( parsed .operands .first() .ok_or(ArmLiftError::UnsupportedInstruction)?, ) .ok_or(ArmLiftError::UnsupportedInstruction)?; let lhs = cx.read_reg(rn); let rhs = Self::operand_value( cx, parsed .operands .get(1) .ok_or(ArmLiftError::UnsupportedInstruction)?, )?; cx.last_cmp = Some((lhs, rhs)); Ok(()) } fn lift_ldr( &self, cx: &mut ArmLiftCtx<'_>, cond: ArmCond, parsed: &ParsedText, ) -> Result<(), ArmLiftError> { if cond != ArmCond::Al { return Err(ArmLiftError::UnsupportedCondition); } let rt = Self::parse_reg( parsed .operands .first() .ok_or(ArmLiftError::UnsupportedInstruction)?, ) .ok_or(ArmLiftError::UnsupportedInstruction)?; let addr = Self::address_from_mem( cx, parsed .operands .get(1) .ok_or(ArmLiftError::UnsupportedInstruction)?, )?; let val = cx.fb.load(addr, MemSize::S4, Type::i32()); cx.write_reg(rt, val); Ok(()) } fn lift_str( &self, cx: &mut ArmLiftCtx<'_>, cond: ArmCond, parsed: &ParsedText, ) -> Result<(), ArmLiftError> { if cond != ArmCond::Al { return Err(ArmLiftError::UnsupportedCondition); } let rt = Self::parse_reg( parsed .operands .first() .ok_or(ArmLiftError::UnsupportedInstruction)?, ) .ok_or(ArmLiftError::UnsupportedInstruction)?; let val = cx.read_reg(rt); let addr = Self::address_from_mem( cx, parsed .operands .get(1) .ok_or(ArmLiftError::UnsupportedInstruction)?, )?; cx.fb.store(addr, val, MemSize::S4); Ok(()) } fn lift_b( &self, cx: &mut ArmLiftCtx<'_>, cond: ArmCond, parsed: &ParsedText, ) -> Result<(), ArmLiftError> { let target_addr = Self::parse_target( parsed .operands .first() .ok_or(ArmLiftError::UnsupportedInstruction)?, ) .ok_or(ArmLiftError::UnsupportedInstruction)?; let target_block = cx .direct_block(target_addr) .ok_or(ArmLiftError::MissingTargetBlock)?; match cond { ArmCond::Al => { cx.fb.jump(target_block); Ok(()) } _ => { let cond_v = cx .condition_value(cond) .ok_or(ArmLiftError::MissingCompareState)?; let fallthrough = cx .fallthrough_block() .ok_or(ArmLiftError::MissingTargetBlock)?; cx.fb.br_if(cond_v, target_block, fallthrough); Ok(()) } } } fn lift_bl(&self, cx: &mut ArmLiftCtx<'_>, parsed: &ParsedText) -> Result<(), ArmLiftError> { let target_addr = Self::parse_target( parsed .operands .first() .ok_or(ArmLiftError::UnsupportedInstruction)?, ) .ok_or(ArmLiftError::UnsupportedInstruction)?; let callee = cx.fb.iconst(Type::ptr(32), target_addr as i64); cx.fb.call(callee, &[]); Ok(()) } fn lift_bx(&self, cx: &mut ArmLiftCtx<'_>, inst: &Instruction) -> Result<(), ArmLiftError> { match inst.operands[0] { Operand::Reg(r) if r.number() == 14 => { let ret0 = cx.read_reg(ArmReg::R0); cx.fb.ret(&[ret0]); Ok(()) } _ => Err(ArmLiftError::UnsupportedControlFlow), } } } impl Arch for Arm { type Mode = ArmMode; type Inst = Instruction; type Reg = ArmReg; type DecodeError = DecodeError; type Disasm = String; fn name(&self) -> &'static str { "arm" } fn address_size(&self, _mode: Self::Mode) -> u8 { 4 } fn max_instruction_len(&self) -> u8 { 4 } fn registers(&self) -> &'static [RegisterDesc] { ®ISTERS } fn stack_pointer(&self) -> Option { Some(ArmReg::Sp) } fn decode( &self, bytes: &[u8], mode: Self::Mode, ) -> Result<(usize, Self::Inst), Self::DecodeError> { let decoder = match mode { ArmMode::Arm => InstDecoder::default(), ArmMode::Thumb => InstDecoder::default_thumb(), }; let mut reader = U8Reader::new(bytes); let inst = decoder.decode(&mut reader)?; let len = inst.len().to_const() as usize; Ok((len, inst)) } fn flow_info(&self, inst: &Self::Inst, pc: u64, _mode: Self::Mode) -> FlowInfo { let len = inst.len().to_const() as u8; match inst.opcode { Opcode::B => { let target = Self::direct_target_from_operand(pc, inst.operands[0]) .map(FlowTarget::Direct) .unwrap_or(FlowTarget::Indirect); match inst.condition { ConditionCode::AL => FlowInfo::new(len, FlowKind::Jump { target }), _ => FlowInfo::new(len, FlowKind::CondJump { target }), } } Opcode::BL | Opcode::BLX => { let target = Self::direct_target_from_operand(pc, inst.operands[0]) .map(FlowTarget::Direct) .unwrap_or(FlowTarget::Indirect); FlowInfo::new( len, FlowKind::Call { target, returns: true, }, ) } Opcode::BX => match inst.operands[0] { Operand::Reg(r) if r.number() == 14 => FlowInfo::new(len, FlowKind::Return), _ => FlowInfo::new( len, FlowKind::Jump { target: FlowTarget::Indirect, }, ), }, Opcode::NOP => FlowInfo::new(len, FlowKind::FallThrough), _ => FlowInfo::new(len, FlowKind::FallThrough), } } fn disasm(&self, inst: &Self::Inst, _pc: u64, _mode: Self::Mode) -> Self::Disasm { inst.to_string() } } impl LiftArch for Arm { type LiftError = ArmLiftError; type LiftCtx<'a> = ArmLiftCtx<'a>; fn lift(&self, cx: &mut Self::LiftCtx<'_>, inst: &Self::Inst) -> Result<(), Self::LiftError> { self.lift_inst(cx, inst) } } pub struct DummyEnv; impl LiftEnv for DummyEnv { fn block_for_target(&self, _addr: u64) -> Option { None } fn fallthrough_block(&self) -> Option { None } }