1341 lines
38 KiB
Rust
1341 lines
38 KiB
Rust
//! 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<crate::ir::Block> {
|
|
self.env.block_for_target(target_addr)
|
|
}
|
|
|
|
/// Returns the fallthrough block for the current instruction, if present.
|
|
pub fn fallthrough_block(&self) -> Option<crate::ir::Block> {
|
|
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<Value> {
|
|
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<Value>; 16],
|
|
}
|
|
|
|
/// Current SSA bindings for status flags.
|
|
#[derive(Clone, Debug, Default)]
|
|
pub struct ArmFlagState {
|
|
pub n: Option<Value>,
|
|
pub z: Option<Value>,
|
|
pub c: Option<Value>,
|
|
pub v: Option<Value>,
|
|
pub q: Option<Value>,
|
|
}
|
|
|
|
/// Temporary lifting scratch state.
|
|
#[derive(Clone, Debug, Default)]
|
|
pub struct ArmScratch {
|
|
pub tmp0: Option<Value>,
|
|
pub tmp1: Option<Value>,
|
|
pub tmp2: Option<Value>,
|
|
}
|
|
|
|
/// Public ARM register table.
|
|
pub const REGISTERS: [RegisterDesc<ArmReg>; 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<String>,
|
|
}
|
|
|
|
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<IntCC> {
|
|
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<u64> {
|
|
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<Value, ArmLiftError> {
|
|
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<Value, ArmLiftError> {
|
|
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<Value, ArmLiftError> {
|
|
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<Option<Value>, 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<ArmReg> {
|
|
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<i64> {
|
|
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::<i64>().ok()
|
|
}
|
|
}
|
|
|
|
fn parse_target(text: &str) -> Option<u64> {
|
|
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::<u64>().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<ArmCond> {
|
|
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<ArmCond>, 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<Value, ArmLiftError> {
|
|
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<Value, ArmLiftError> {
|
|
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<Option<Value>, 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<Self::Reg>] {
|
|
®ISTERS
|
|
}
|
|
|
|
fn stack_pointer(&self) -> Option<Self::Reg> {
|
|
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<Block> {
|
|
None
|
|
}
|
|
|
|
fn fallthrough_block(&self) -> Option<Block> {
|
|
None
|
|
}
|
|
}
|