use crate::arch::x86::{DecodedInsn, Mnemonic, OpKind};
use crate::ast::Stmt;
use super::{Candidate, Pattern, PatternCtx};
pub struct PushImmPopReg;
impl Pattern for PushImmPopReg {
fn name(&self) -> &'static str {
"push_imm_pop_reg"
}
fn tentative(
&self,
_ctx: &PatternCtx,
insns: &[DecodedInsn],
start: usize,
) -> Option<Candidate> {
let push = insns.get(start)?;
let pop = insns.get(start + 1)?;
if push.iced.mnemonic() != Mnemonic::Push || pop.iced.mnemonic() != Mnemonic::Pop {
return None;
}
if push.iced.op_count() != 1 || pop.iced.op_count() != 1 {
return None;
}
if pop.iced.op0_kind() != OpKind::Register {
return None;
}
let reg = pop.iced.op0_register();
#[allow(clippy::cast_possible_wrap)]
let imm = match push.iced.op0_kind() {
OpKind::Immediate8 => i64::from(push.iced.immediate8() as i8),
OpKind::Immediate8to16 | OpKind::Immediate8to32 | OpKind::Immediate8to64 => {
push.iced.immediate8to64()
}
OpKind::Immediate16 => i64::from(push.iced.immediate16() as i16),
OpKind::Immediate32 => i64::from(push.iced.immediate32() as i32),
OpKind::Immediate32to64 => push.iced.immediate32to64(),
OpKind::Immediate64 => push.iced.immediate64() as i64,
_ => return None,
};
let reg_name = format!("{reg:?}").to_lowercase();
let src = if imm < 0 {
format!("-0x{:x}", imm.unsigned_abs())
} else {
format!("0x{imm:x}")
};
let mut bytes = Vec::with_capacity(push.original_bytes.len() + pop.original_bytes.len());
bytes.extend_from_slice(&push.original_bytes);
bytes.extend_from_slice(&pop.original_bytes);
Some(Candidate {
pattern: self.name(),
start,
consumed: 2,
priority: 150,
stmts: vec![Stmt::Move {
dst: reg_name,
src,
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_push_imm8_pop_eax() {
let bytes = [0x6a, 0x01, 0x58];
let insns = decode(Bitness::Bits32, &bytes, 0x1000).unwrap();
let cand = PushImmPopReg.tentative(&ctx(), &insns, 0).expect("match");
assert_eq!(cand.consumed, 2);
let Stmt::Move { dst, src, bytes } = &cand.stmts[0] else {
panic!("expected Move");
};
assert_eq!(dst, "eax");
assert_eq!(src, "0x1");
assert_eq!(bytes, &[0x6a, 0x01, 0x58]);
}
#[test]
fn lifts_push_imm8_negative() {
let bytes = [0x6a, 0xff, 0x59];
let insns = decode(Bitness::Bits32, &bytes, 0x1000).unwrap();
let cand = PushImmPopReg.tentative(&ctx(), &insns, 0).expect("match");
let Stmt::Move { dst, src, .. } = &cand.stmts[0] else {
panic!("expected Move");
};
assert_eq!(dst, "ecx");
assert_eq!(src, "-0x1");
}
#[test]
fn lifts_push_imm32_pop_edx() {
let bytes = [0x68, 0x78, 0x56, 0x34, 0x12, 0x5a];
let insns = decode(Bitness::Bits32, &bytes, 0x1000).unwrap();
let cand = PushImmPopReg.tentative(&ctx(), &insns, 0).expect("match");
let Stmt::Move { dst, src, .. } = &cand.stmts[0] else {
panic!("expected Move");
};
assert_eq!(dst, "edx");
assert_eq!(src, "0x12345678");
}
#[test]
fn skips_push_reg_pop_reg() {
let bytes = [0x50, 0x5b]; let insns = decode(Bitness::Bits32, &bytes, 0x1000).unwrap();
assert!(PushImmPopReg.tentative(&ctx(), &insns, 0).is_none());
}
#[test]
fn skips_when_pop_missing() {
let bytes = [0x6a, 0x01, 0x31, 0xc0];
let insns = decode(Bitness::Bits32, &bytes, 0x1000).unwrap();
assert!(PushImmPopReg.tentative(&ctx(), &insns, 0).is_none());
}
#[test]
fn lifts_in_64bit_mode() {
let bytes = [0x6a, 0x01, 0x58];
let insns = decode(Bitness::Bits64, &bytes, 0x1000).unwrap();
let cand = PushImmPopReg.tentative(&ctx(), &insns, 0).expect("match");
let Stmt::Move { dst, src, .. } = &cand.stmts[0] else {
panic!("expected Move");
};
assert_eq!(dst, "rax");
assert_eq!(src, "0x1");
}
}