use r2smt_common::smt::SmtResult;
use r2smt_common::{Address, Result};
use r2smt_core::Finding;
use r2smt_ir::byte_patcher::BytePatcher;
use r2smt_report::PatchStrategy;
use crate::aarch64_encoding;
use crate::arm_encoding::ARM_INSTRUCTION_BYTES;
use super::{CsArithOp, FindingDecision, MnemonicKind, PlanOperation};
pub(super) fn classify_aarch64_mnemonic(mnemonic: &str) -> MnemonicKind {
if mnemonic.starts_with("b.") {
return MnemonicKind::Jcc;
}
if matches!(mnemonic, "cbz" | "cbnz" | "tbz" | "tbnz") {
return MnemonicKind::Jcc;
}
match mnemonic {
"cset" => MnemonicKind::Cset { all_ones: false },
"csetm" => MnemonicKind::Cset { all_ones: true },
"csel" => MnemonicKind::Csel,
"csinc" => MnemonicKind::CsArith {
op: CsArithOp::Csinc,
aliased: false,
},
"cinc" => MnemonicKind::CsArith {
op: CsArithOp::Csinc,
aliased: true,
},
"csinv" => MnemonicKind::CsArith {
op: CsArithOp::Csinv,
aliased: false,
},
"cinv" => MnemonicKind::CsArith {
op: CsArithOp::Csinv,
aliased: true,
},
"csneg" => MnemonicKind::CsArith {
op: CsArithOp::Csneg,
aliased: false,
},
"cneg" => MnemonicKind::CsArith {
op: CsArithOp::Csneg,
aliased: true,
},
_ => MnemonicKind::Other,
}
}
pub(super) fn plan_aarch64_cset(
finding: &Finding,
size: usize,
patcher: &mut dyn BytePatcher,
all_ones: bool,
) -> Result<FindingDecision> {
let Some(value_bit) = bit_value_for(finding) else {
return Ok(FindingDecision::Skip(format!(
"{:?} verdict cannot drive a cset rewrite",
finding.verdict
)));
};
let Some(dst_raw) = finding.operands.first() else {
return Ok(FindingDecision::Skip(format!(
"cset at {addr} has no destination operand recorded",
addr = finding.address,
)));
};
let Some(dst) = aarch64_encoding::parse_xreg(dst_raw) else {
return Ok(FindingDecision::Skip(format!(
"cset destination operand '{dst_raw}' is not a recognised GPR"
)));
};
let imm: i64 = match (value_bit, all_ones) {
(true, true) => -1,
(true, false) => 1,
(false, _) => 0,
};
let asm = aarch64_encoding::mov_imm(&dst, imm);
let encoded = patcher.assemble(finding.address, &asm)?;
if let Some(skip) = enforce_aarch64_size(&encoded, size, finding.address) {
return Ok(FindingDecision::Skip(skip));
}
let strategy = PatchStrategy::ReplaceCsetWithMovConst;
Ok(FindingDecision::Plan(PlanOperation {
address: finding.address,
strategy,
kind: finding.kind,
confidence: finding.confidence,
size,
new_bytes: encoded,
rationale: cs_rationale(finding, &asm),
}))
}
pub(super) fn plan_aarch64_csel(
finding: &Finding,
size: usize,
patcher: &mut dyn BytePatcher,
) -> Result<FindingDecision> {
let Some(value_bit) = bit_value_for(finding) else {
return Ok(FindingDecision::Skip(format!(
"{:?} verdict cannot drive a csel rewrite",
finding.verdict
)));
};
let Some((dst, rn, rm_opt)) = parse_cs_operands(finding) else {
return Ok(FindingDecision::Skip(format!(
"csel at {addr} operand parse failed (need Rd, Rn, Rm)",
addr = finding.address,
)));
};
let Some(rm) = rm_opt else {
return Ok(FindingDecision::Skip(format!(
"csel at {addr} is missing Rm operand",
addr = finding.address,
)));
};
let source = if value_bit { rn } else { rm };
let asm = aarch64_encoding::mov_reg(&dst, &source);
let encoded = patcher.assemble(finding.address, &asm)?;
if let Some(skip) = enforce_aarch64_size(&encoded, size, finding.address) {
return Ok(FindingDecision::Skip(skip));
}
let strategy = PatchStrategy::ReplaceCselWithMov;
Ok(FindingDecision::Plan(PlanOperation {
address: finding.address,
strategy,
kind: finding.kind,
confidence: finding.confidence,
size,
new_bytes: encoded,
rationale: cs_rationale(finding, &asm),
}))
}
pub(super) fn plan_aarch64_cs_arith(
finding: &Finding,
size: usize,
patcher: &mut dyn BytePatcher,
op: CsArithOp,
aliased: bool,
) -> Result<FindingDecision> {
let Some(value_bit) = bit_value_for(finding) else {
return Ok(FindingDecision::Skip(format!(
"{:?} verdict cannot drive a cs-arithmetic rewrite",
finding.verdict
)));
};
let Some((dst, rn, rm_opt)) = parse_cs_operands(finding) else {
return Ok(FindingDecision::Skip(format!(
"cs-arithmetic at {addr} operand parse failed",
addr = finding.address,
)));
};
let rm = match (aliased, rm_opt) {
(true, _) => rn.clone(),
(false, Some(value)) => value,
(false, None) => {
return Ok(FindingDecision::Skip(format!(
"cs-arithmetic at {addr} is missing Rm operand",
addr = finding.address,
)));
}
};
let asm = if value_bit {
aarch64_encoding::mov_reg(&dst, &rn)
} else {
match op {
CsArithOp::Csinc => aarch64_encoding::add_imm(&dst, &rm, 1),
CsArithOp::Csinv => aarch64_encoding::mvn_reg(&dst, &rm),
CsArithOp::Csneg => aarch64_encoding::neg_reg(&dst, &rm),
}
};
let encoded = patcher.assemble(finding.address, &asm)?;
if let Some(skip) = enforce_aarch64_size(&encoded, size, finding.address) {
return Ok(FindingDecision::Skip(skip));
}
let strategy = match op {
CsArithOp::Csinc => PatchStrategy::ReplaceCsincWithMovOrAdd1,
CsArithOp::Csinv => PatchStrategy::ReplaceCsinvWithMovOrMvn,
CsArithOp::Csneg => PatchStrategy::ReplaceCsnegWithMovOrNeg,
};
Ok(FindingDecision::Plan(PlanOperation {
address: finding.address,
strategy,
kind: finding.kind,
confidence: finding.confidence,
size,
new_bytes: encoded,
rationale: cs_rationale(finding, &asm),
}))
}
fn bit_value_for(finding: &Finding) -> Option<bool> {
match finding.verdict {
SmtResult::AlwaysTrue => Some(true),
SmtResult::AlwaysFalse => Some(false),
_ => None,
}
}
fn parse_cs_operands(finding: &Finding) -> Option<(String, String, Option<String>)> {
let mut regs = finding
.operands
.iter()
.filter_map(|raw| aarch64_encoding::parse_xreg(raw));
let dst = regs.next()?;
let rn = regs.next()?;
let rm = regs.next();
Some((dst, rn, rm))
}
fn enforce_aarch64_size(encoded: &[u8], size: usize, address: Address) -> Option<String> {
if encoded.len() != ARM_INSTRUCTION_BYTES {
return Some(format!(
"`AArch64` cs-rewrite at {address} produced {} bytes, expected {ARM_INSTRUCTION_BYTES}",
encoded.len(),
));
}
if encoded.len() != size {
return Some(format!(
"`AArch64` cs-rewrite at {address} length {} does not match original instruction size {size}",
encoded.len(),
));
}
None
}
fn cs_rationale(finding: &Finding, asm: &str) -> String {
format!(
"{mnem} at {addr} collapses to `{asm}` ({formula} is always {verdict:?})",
mnem = finding.mnemonic,
addr = finding.address,
formula = finding.formula,
verdict = finding.verdict,
)
}