slonik/src/arch/arm.rs

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>] {
&REGISTERS
}
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
}
}