use r2smt_common::{Error, Result};
const NOP: u8 = 0x90;
#[must_use]
pub const fn nop_byte() -> u8 {
NOP
}
pub fn patch_setcc(original: &[u8], value: bool) -> Result<Vec<u8>> {
if original.len() < 3 {
return Err(Error::parse(
"x86_encoding.setcc",
format!("setcc must be at least 3 bytes, got {}", original.len()),
));
}
let mut idx = 0usize;
let mut out = Vec::with_capacity(original.len());
if (original[idx] & 0xF0) == 0x40 {
out.push(original[idx]);
idx += 1;
if idx + 2 >= original.len() {
return Err(Error::parse(
"x86_encoding.setcc",
"buffer too short after REX prefix",
));
}
}
if original[idx] != 0x0F || (original[idx + 1] & 0xF0) != 0x90 {
return Err(Error::parse(
"x86_encoding.setcc",
format!(
"not a SETcc opcode at offset {idx}: {:02x} {:02x}",
original[idx],
original[idx + 1]
),
));
}
idx += 2;
if idx >= original.len() {
return Err(Error::parse("x86_encoding.setcc", "missing ModR/M byte"));
}
let modrm = original[idx];
if (modrm >> 3) & 0x7 != 0 {
return Err(Error::parse(
"x86_encoding.setcc",
format!("SETcc ModR/M REG field is non-zero: 0x{modrm:02x}"),
));
}
out.push(0xC6);
out.extend_from_slice(&original[idx..]);
out.push(u8::from(value));
if out.len() != original.len() {
return Err(Error::parse(
"x86_encoding.setcc",
format!(
"size mismatch: original {}, patched {}",
original.len(),
out.len()
),
));
}
Ok(out)
}
pub fn patch_cmovcc_to_mov(original: &[u8]) -> Result<Vec<u8>> {
let mut idx = 0usize;
let mut out = Vec::with_capacity(original.len());
if original.first() == Some(&0x66) {
out.push(0x66);
idx += 1;
}
if let Some(&b) = original.get(idx)
&& (b & 0xF0) == 0x40
{
out.push(b);
idx += 1;
}
if idx + 2 >= original.len() {
return Err(Error::parse(
"x86_encoding.cmovcc",
format!(
"cmovcc body must be ≥3 bytes after prefixes at offset {idx}, got {}",
original.len(),
),
));
}
if original[idx] != 0x0F || (original[idx + 1] & 0xF0) != 0x40 {
return Err(Error::parse(
"x86_encoding.cmovcc",
format!(
"not a CMOVcc opcode at offset {idx}: {:02x} {:02x}",
original[idx],
original[idx + 1]
),
));
}
idx += 2;
out.push(0x8B);
out.extend_from_slice(&original[idx..]);
while out.len() < original.len() {
out.push(NOP);
}
debug_assert_eq!(
out.len(),
original.len(),
"cmovcc rewrite produced wrong length",
);
Ok(out)
}
#[must_use]
pub fn nop_buffer(len: usize) -> Vec<u8> {
vec![NOP; len]
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used, clippy::panic)]
use super::*;
#[test]
fn setcc_reg_no_rex_value_true() {
let bytes = [0x0F, 0x94, 0xC0];
let patched = patch_setcc(&bytes, true).unwrap();
assert_eq!(patched, vec![0xC6, 0xC0, 0x01]);
}
#[test]
fn setcc_reg_no_rex_value_false() {
let bytes = [0x0F, 0x95, 0xC3];
let patched = patch_setcc(&bytes, false).unwrap();
assert_eq!(patched, vec![0xC6, 0xC3, 0x00]);
}
#[test]
fn setcc_reg_with_rex_preserves_prefix() {
let bytes = [0x40, 0x0F, 0x94, 0xC6];
let patched = patch_setcc(&bytes, true).unwrap();
assert_eq!(patched, vec![0x40, 0xC6, 0xC6, 0x01]);
}
#[test]
fn setcc_memory_operand_preserves_addressing() {
let bytes = [0x0F, 0x94, 0x45, 0xFC];
let patched = patch_setcc(&bytes, true).unwrap();
assert_eq!(patched, vec![0xC6, 0x45, 0xFC, 0x01]);
}
#[test]
fn setcc_memory_sib_operand_preserves_layout() {
let bytes = [0x0F, 0x94, 0x04, 0x08];
let patched = patch_setcc(&bytes, false).unwrap();
assert_eq!(patched, vec![0xC6, 0x04, 0x08, 0x00]);
}
#[test]
fn setcc_size_is_preserved() {
let bytes = [0x0F, 0x94, 0xC0];
let patched = patch_setcc(&bytes, true).unwrap();
assert_eq!(patched.len(), bytes.len());
}
#[test]
fn setcc_rejects_too_short() {
let bytes = [0x0F, 0x94];
assert!(patch_setcc(&bytes, true).is_err());
}
#[test]
fn setcc_rejects_wrong_opcode() {
let bytes = [0x0F, 0x80, 0x00];
assert!(patch_setcc(&bytes, true).is_err());
}
#[test]
fn setcc_rejects_non_zero_reg_field() {
let bytes = [0x0F, 0x94, 0xD0];
assert!(patch_setcc(&bytes, true).is_err());
}
#[test]
fn cmovcc_reg_no_rex_to_mov() {
let bytes = [0x0F, 0x44, 0xC3];
let patched = patch_cmovcc_to_mov(&bytes).unwrap();
assert_eq!(patched, vec![0x8B, 0xC3, NOP]);
}
#[test]
fn cmovcc_reg_with_rex_w_preserves_prefix() {
let bytes = [0x48, 0x0F, 0x44, 0xC3];
let patched = patch_cmovcc_to_mov(&bytes).unwrap();
assert_eq!(patched, vec![0x48, 0x8B, 0xC3, NOP]);
}
#[test]
fn cmovcc_memory_with_disp8() {
let bytes = [0x0F, 0x44, 0x45, 0xFC];
let patched = patch_cmovcc_to_mov(&bytes).unwrap();
assert_eq!(patched, vec![0x8B, 0x45, 0xFC, NOP]);
}
#[test]
fn cmovcc_size_is_preserved() {
let bytes = [0x0F, 0x44, 0xC3];
let patched = patch_cmovcc_to_mov(&bytes).unwrap();
assert_eq!(patched.len(), bytes.len());
let _ = patched.iter().last().unwrap();
}
#[test]
fn cmovcc_rejects_wrong_opcode() {
let bytes = [0x0F, 0x84, 0x00, 0x00, 0x00, 0x00];
assert!(patch_cmovcc_to_mov(&bytes).is_err());
}
#[test]
fn cmovcc_rejects_too_short() {
let bytes = [0x0F, 0x44];
assert!(patch_cmovcc_to_mov(&bytes).is_err());
}
#[test]
fn cmovcc_disp8_memory_operand() {
let bytes = [0x48, 0x0F, 0x44, 0x43, 0x10];
let patched = patch_cmovcc_to_mov(&bytes).unwrap();
assert_eq!(patched, vec![0x48, 0x8B, 0x43, 0x10, NOP]);
}
#[test]
fn cmovcc_sib_disp32_memory_operand() {
let bytes = [0x48, 0x0F, 0x44, 0x84, 0x8B, 0x78, 0x56, 0x34, 0x12];
let patched = patch_cmovcc_to_mov(&bytes).unwrap();
assert_eq!(
patched,
vec![0x48, 0x8B, 0x84, 0x8B, 0x78, 0x56, 0x34, 0x12, NOP],
);
}
#[test]
fn cmovcc_rex_b_extended_source() {
let bytes = [0x49, 0x0F, 0x44, 0xC3];
let patched = patch_cmovcc_to_mov(&bytes).unwrap();
assert_eq!(patched, vec![0x49, 0x8B, 0xC3, NOP]);
}
#[test]
fn cmovcc_rex_r_extended_dest() {
let bytes = [0x4C, 0x0F, 0x44, 0xC3];
let patched = patch_cmovcc_to_mov(&bytes).unwrap();
assert_eq!(patched, vec![0x4C, 0x8B, 0xC3, NOP]);
}
#[test]
fn cmovcc_rex_rb_both_extended() {
let bytes = [0x4D, 0x0F, 0x44, 0xC3];
let patched = patch_cmovcc_to_mov(&bytes).unwrap();
assert_eq!(patched, vec![0x4D, 0x8B, 0xC3, NOP]);
}
#[test]
fn cmovcc_operand_size_16bit() {
let bytes = [0x66, 0x0F, 0x44, 0xC3];
let patched = patch_cmovcc_to_mov(&bytes).unwrap();
assert_eq!(patched, vec![0x66, 0x8B, 0xC3, NOP]);
}
#[test]
fn cmovcc_32bit_no_rex() {
let bytes = [0x0F, 0x44, 0xC3];
let patched = patch_cmovcc_to_mov(&bytes).unwrap();
assert_eq!(patched, vec![0x8B, 0xC3, NOP]);
}
#[test]
fn cmovcc_64bit_with_rex_w() {
let bytes = [0x48, 0x0F, 0x44, 0xC3];
let patched = patch_cmovcc_to_mov(&bytes).unwrap();
assert_eq!(patched, vec![0x48, 0x8B, 0xC3, NOP]);
}
#[test]
fn cmovcc_condition_code_variety() {
for cond in 0x40u8..=0x4Fu8 {
let bytes = [0x0F, cond, 0xC3];
let patched = patch_cmovcc_to_mov(&bytes)
.unwrap_or_else(|e| panic!("cond byte {cond:#x} failed: {e}"));
assert_eq!(patched, vec![0x8B, 0xC3, NOP], "cond byte {cond:#x}");
}
}
#[test]
fn cmovcc_rip_relative() {
let bytes = [0x48, 0x0F, 0x44, 0x05, 0x78, 0x56, 0x34, 0x12];
let patched = patch_cmovcc_to_mov(&bytes).unwrap();
assert_eq!(patched, vec![0x48, 0x8B, 0x05, 0x78, 0x56, 0x34, 0x12, NOP],);
}
#[test]
fn cmovcc_too_short_no_prefix() {
let bytes = [0x0F];
assert!(patch_cmovcc_to_mov(&bytes).is_err());
}
#[test]
fn cmovcc_too_short_after_rex() {
let bytes = [0x48, 0x0F];
assert!(patch_cmovcc_to_mov(&bytes).is_err());
}
#[test]
fn cmovcc_wrong_first_byte() {
let bytes = [0x90, 0x44, 0xC3];
assert!(patch_cmovcc_to_mov(&bytes).is_err());
}
#[test]
fn cmovcc_wrong_second_nibble() {
let bytes = [0x0F, 0x90, 0xC3];
assert!(patch_cmovcc_to_mov(&bytes).is_err());
}
#[test]
fn cmovcc_invalid_rex_followed_by_garbage() {
let bytes = [0x48, 0xAA, 0xBB, 0xCC];
assert!(patch_cmovcc_to_mov(&bytes).is_err());
}
#[test]
fn nop_buffer_emits_requested_length() {
let buf = nop_buffer(7);
assert_eq!(buf, vec![NOP; 7]);
}
}