use crate::arch::x86::{DecodedInsn, FlowControl, Mnemonic, format_intel};
use crate::ast::Stmt;
use super::{Candidate, Pattern, PatternCtx};
pub struct CmpJcc;
impl Pattern for CmpJcc {
fn name(&self) -> &'static str {
"cmp_jcc"
}
fn tentative(
&self,
_ctx: &PatternCtx,
insns: &[DecodedInsn],
start: usize,
) -> Option<Candidate> {
let cmp = insns.get(start)?;
if !matches!(cmp.iced.mnemonic(), Mnemonic::Cmp | Mnemonic::Test) {
return None;
}
let jcc = insns.get(start + 1)?;
if jcc.iced.flow_control() != FlowControl::ConditionalBranch {
return None;
}
if jcc.iced.near_branch_target() == 0 {
return None;
}
let text = format!("{}; {}", format_intel(&cmp.iced), format_intel(&jcc.iced));
let mut bytes = Vec::with_capacity(cmp.original_bytes.len() + jcc.original_bytes.len());
bytes.extend_from_slice(&cmp.original_bytes);
bytes.extend_from_slice(&jcc.original_bytes);
Some(Candidate {
pattern: self.name(),
start,
consumed: 2,
priority: 10,
stmts: vec![Stmt::Asm { text, bytes }],
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::arch::x86::{Bitness, decode};
use std::collections::HashMap;
fn ctx() -> PatternCtx<'static> {
let map: &'static HashMap<u64, String> = Box::leak(Box::new(HashMap::new()));
let sp_map: &'static HashMap<u64, i64> = Box::leak(Box::new(HashMap::new()));
PatternCtx {
fn_addr_start: 0,
fn_addr_end: u64::MAX,
name_at: map,
sp_delta_at: sp_map,
}
}
#[test]
fn lifts_adjacent_cmp_je() {
let bytes = [0x83, 0xf8, 0x01, 0x74, 0x00];
let insns = decode(Bitness::Bits32, &bytes, 0x1000).unwrap();
let cand = CmpJcc.tentative(&ctx(), &insns, 0).expect("match");
assert_eq!(cand.consumed, 2);
let Some(Stmt::Asm {
text,
bytes: out_bytes,
}) = cand.stmts.first()
else {
panic!("expected Stmt::Asm");
};
assert!(
text.contains("cmp eax,1") && text.contains("je"),
"got {text}"
);
assert_eq!(out_bytes, &bytes);
}
#[test]
fn rejects_cmp_without_jcc() {
let bytes = [0x83, 0xf8, 0x01]; let insns = decode(Bitness::Bits32, &bytes, 0x1000).unwrap();
assert!(CmpJcc.tentative(&ctx(), &insns, 0).is_none());
}
}