1use r2smt_common::{Error, Result};
23
24const NOP: u8 = 0x90;
25
26#[must_use]
28pub const fn nop_byte() -> u8 {
29 NOP
30}
31
32pub fn patch_setcc(original: &[u8], value: bool) -> Result<Vec<u8>> {
46 if original.len() < 3 {
47 return Err(Error::parse(
48 "x86_encoding.setcc",
49 format!("setcc must be at least 3 bytes, got {}", original.len()),
50 ));
51 }
52
53 let mut idx = 0usize;
54 let mut out = Vec::with_capacity(original.len());
55
56 if (original[idx] & 0xF0) == 0x40 {
58 out.push(original[idx]);
59 idx += 1;
60 if idx + 2 >= original.len() {
61 return Err(Error::parse(
62 "x86_encoding.setcc",
63 "buffer too short after REX prefix",
64 ));
65 }
66 }
67
68 if original[idx] != 0x0F || (original[idx + 1] & 0xF0) != 0x90 {
70 return Err(Error::parse(
71 "x86_encoding.setcc",
72 format!(
73 "not a SETcc opcode at offset {idx}: {:02x} {:02x}",
74 original[idx],
75 original[idx + 1]
76 ),
77 ));
78 }
79 idx += 2;
80
81 if idx >= original.len() {
84 return Err(Error::parse("x86_encoding.setcc", "missing ModR/M byte"));
85 }
86 let modrm = original[idx];
87 if (modrm >> 3) & 0x7 != 0 {
88 return Err(Error::parse(
89 "x86_encoding.setcc",
90 format!("SETcc ModR/M REG field is non-zero: 0x{modrm:02x}"),
91 ));
92 }
93
94 out.push(0xC6);
98 out.extend_from_slice(&original[idx..]);
99 out.push(u8::from(value));
100
101 if out.len() != original.len() {
102 return Err(Error::parse(
103 "x86_encoding.setcc",
104 format!(
105 "size mismatch: original {}, patched {}",
106 original.len(),
107 out.len()
108 ),
109 ));
110 }
111 Ok(out)
112}
113
114pub fn patch_cmovcc_to_mov(original: &[u8]) -> Result<Vec<u8>> {
126 let mut idx = 0usize;
127 let mut out = Vec::with_capacity(original.len());
128
129 if original.first() == Some(&0x66) {
133 out.push(0x66);
134 idx += 1;
135 }
136
137 if let Some(&b) = original.get(idx)
141 && (b & 0xF0) == 0x40
142 {
143 out.push(b);
144 idx += 1;
145 }
146
147 if idx + 2 >= original.len() {
149 return Err(Error::parse(
150 "x86_encoding.cmovcc",
151 format!(
152 "cmovcc body must be ≥3 bytes after prefixes at offset {idx}, got {}",
153 original.len(),
154 ),
155 ));
156 }
157
158 if original[idx] != 0x0F || (original[idx + 1] & 0xF0) != 0x40 {
159 return Err(Error::parse(
160 "x86_encoding.cmovcc",
161 format!(
162 "not a CMOVcc opcode at offset {idx}: {:02x} {:02x}",
163 original[idx],
164 original[idx + 1]
165 ),
166 ));
167 }
168 idx += 2;
169
170 out.push(0x8B);
176 out.extend_from_slice(&original[idx..]);
177 while out.len() < original.len() {
178 out.push(NOP);
179 }
180 debug_assert_eq!(
181 out.len(),
182 original.len(),
183 "cmovcc rewrite produced wrong length",
184 );
185 Ok(out)
186}
187
188#[must_use]
192pub fn nop_buffer(len: usize) -> Vec<u8> {
193 vec![NOP; len]
194}
195
196#[cfg(test)]
197mod tests {
198 #![allow(clippy::unwrap_used, clippy::panic)]
199
200 use super::*;
201
202 #[test]
205 fn setcc_reg_no_rex_value_true() {
206 let bytes = [0x0F, 0x94, 0xC0];
208 let patched = patch_setcc(&bytes, true).unwrap();
209 assert_eq!(patched, vec![0xC6, 0xC0, 0x01]);
211 }
212
213 #[test]
214 fn setcc_reg_no_rex_value_false() {
215 let bytes = [0x0F, 0x95, 0xC3];
217 let patched = patch_setcc(&bytes, false).unwrap();
218 assert_eq!(patched, vec![0xC6, 0xC3, 0x00]);
220 }
221
222 #[test]
223 fn setcc_reg_with_rex_preserves_prefix() {
224 let bytes = [0x40, 0x0F, 0x94, 0xC6];
226 let patched = patch_setcc(&bytes, true).unwrap();
227 assert_eq!(patched, vec![0x40, 0xC6, 0xC6, 0x01]);
229 }
230
231 #[test]
232 fn setcc_memory_operand_preserves_addressing() {
233 let bytes = [0x0F, 0x94, 0x45, 0xFC];
237 let patched = patch_setcc(&bytes, true).unwrap();
238 assert_eq!(patched, vec![0xC6, 0x45, 0xFC, 0x01]);
240 }
241
242 #[test]
243 fn setcc_memory_sib_operand_preserves_layout() {
244 let bytes = [0x0F, 0x94, 0x04, 0x08];
247 let patched = patch_setcc(&bytes, false).unwrap();
248 assert_eq!(patched, vec![0xC6, 0x04, 0x08, 0x00]);
250 }
251
252 #[test]
253 fn setcc_size_is_preserved() {
254 let bytes = [0x0F, 0x94, 0xC0];
255 let patched = patch_setcc(&bytes, true).unwrap();
256 assert_eq!(patched.len(), bytes.len());
257 }
258
259 #[test]
260 fn setcc_rejects_too_short() {
261 let bytes = [0x0F, 0x94];
262 assert!(patch_setcc(&bytes, true).is_err());
263 }
264
265 #[test]
266 fn setcc_rejects_wrong_opcode() {
267 let bytes = [0x0F, 0x80, 0x00];
269 assert!(patch_setcc(&bytes, true).is_err());
270 }
271
272 #[test]
273 fn setcc_rejects_non_zero_reg_field() {
274 let bytes = [0x0F, 0x94, 0xD0];
277 assert!(patch_setcc(&bytes, true).is_err());
278 }
279
280 #[test]
283 fn cmovcc_reg_no_rex_to_mov() {
284 let bytes = [0x0F, 0x44, 0xC3];
286 let patched = patch_cmovcc_to_mov(&bytes).unwrap();
287 assert_eq!(patched, vec![0x8B, 0xC3, NOP]);
289 }
290
291 #[test]
292 fn cmovcc_reg_with_rex_w_preserves_prefix() {
293 let bytes = [0x48, 0x0F, 0x44, 0xC3];
295 let patched = patch_cmovcc_to_mov(&bytes).unwrap();
296 assert_eq!(patched, vec![0x48, 0x8B, 0xC3, NOP]);
298 }
299
300 #[test]
301 fn cmovcc_memory_with_disp8() {
302 let bytes = [0x0F, 0x44, 0x45, 0xFC];
304 let patched = patch_cmovcc_to_mov(&bytes).unwrap();
305 assert_eq!(patched, vec![0x8B, 0x45, 0xFC, NOP]);
307 }
308
309 #[test]
310 fn cmovcc_size_is_preserved() {
311 let bytes = [0x0F, 0x44, 0xC3];
312 let patched = patch_cmovcc_to_mov(&bytes).unwrap();
313 assert_eq!(patched.len(), bytes.len());
314 let _ = patched.iter().last().unwrap();
315 }
316
317 #[test]
318 fn cmovcc_rejects_wrong_opcode() {
319 let bytes = [0x0F, 0x84, 0x00, 0x00, 0x00, 0x00];
321 assert!(patch_cmovcc_to_mov(&bytes).is_err());
322 }
323
324 #[test]
325 fn cmovcc_rejects_too_short() {
326 let bytes = [0x0F, 0x44];
327 assert!(patch_cmovcc_to_mov(&bytes).is_err());
328 }
329
330 #[test]
331 fn cmovcc_disp8_memory_operand() {
332 let bytes = [0x48, 0x0F, 0x44, 0x43, 0x10];
335 let patched = patch_cmovcc_to_mov(&bytes).unwrap();
336 assert_eq!(patched, vec![0x48, 0x8B, 0x43, 0x10, NOP]);
338 }
339
340 #[test]
341 fn cmovcc_sib_disp32_memory_operand() {
342 let bytes = [0x48, 0x0F, 0x44, 0x84, 0x8B, 0x78, 0x56, 0x34, 0x12];
347 let patched = patch_cmovcc_to_mov(&bytes).unwrap();
348 assert_eq!(
349 patched,
350 vec![0x48, 0x8B, 0x84, 0x8B, 0x78, 0x56, 0x34, 0x12, NOP],
351 );
352 }
353
354 #[test]
355 fn cmovcc_rex_b_extended_source() {
356 let bytes = [0x49, 0x0F, 0x44, 0xC3];
358 let patched = patch_cmovcc_to_mov(&bytes).unwrap();
359 assert_eq!(patched, vec![0x49, 0x8B, 0xC3, NOP]);
360 }
361
362 #[test]
363 fn cmovcc_rex_r_extended_dest() {
364 let bytes = [0x4C, 0x0F, 0x44, 0xC3];
366 let patched = patch_cmovcc_to_mov(&bytes).unwrap();
367 assert_eq!(patched, vec![0x4C, 0x8B, 0xC3, NOP]);
368 }
369
370 #[test]
371 fn cmovcc_rex_rb_both_extended() {
372 let bytes = [0x4D, 0x0F, 0x44, 0xC3];
374 let patched = patch_cmovcc_to_mov(&bytes).unwrap();
375 assert_eq!(patched, vec![0x4D, 0x8B, 0xC3, NOP]);
376 }
377
378 #[test]
379 fn cmovcc_operand_size_16bit() {
380 let bytes = [0x66, 0x0F, 0x44, 0xC3];
384 let patched = patch_cmovcc_to_mov(&bytes).unwrap();
385 assert_eq!(patched, vec![0x66, 0x8B, 0xC3, NOP]);
386 }
387
388 #[test]
389 fn cmovcc_32bit_no_rex() {
390 let bytes = [0x0F, 0x44, 0xC3];
392 let patched = patch_cmovcc_to_mov(&bytes).unwrap();
393 assert_eq!(patched, vec![0x8B, 0xC3, NOP]);
394 }
395
396 #[test]
397 fn cmovcc_64bit_with_rex_w() {
398 let bytes = [0x48, 0x0F, 0x44, 0xC3];
400 let patched = patch_cmovcc_to_mov(&bytes).unwrap();
401 assert_eq!(patched, vec![0x48, 0x8B, 0xC3, NOP]);
402 }
403
404 #[test]
405 fn cmovcc_condition_code_variety() {
406 for cond in 0x40u8..=0x4Fu8 {
409 let bytes = [0x0F, cond, 0xC3];
410 let patched = patch_cmovcc_to_mov(&bytes)
411 .unwrap_or_else(|e| panic!("cond byte {cond:#x} failed: {e}"));
412 assert_eq!(patched, vec![0x8B, 0xC3, NOP], "cond byte {cond:#x}");
413 }
414 }
415
416 #[test]
417 fn cmovcc_rip_relative() {
418 let bytes = [0x48, 0x0F, 0x44, 0x05, 0x78, 0x56, 0x34, 0x12];
422 let patched = patch_cmovcc_to_mov(&bytes).unwrap();
423 assert_eq!(patched, vec![0x48, 0x8B, 0x05, 0x78, 0x56, 0x34, 0x12, NOP],);
424 }
425
426 #[test]
427 fn cmovcc_too_short_no_prefix() {
428 let bytes = [0x0F];
430 assert!(patch_cmovcc_to_mov(&bytes).is_err());
431 }
432
433 #[test]
434 fn cmovcc_too_short_after_rex() {
435 let bytes = [0x48, 0x0F];
437 assert!(patch_cmovcc_to_mov(&bytes).is_err());
438 }
439
440 #[test]
441 fn cmovcc_wrong_first_byte() {
442 let bytes = [0x90, 0x44, 0xC3];
444 assert!(patch_cmovcc_to_mov(&bytes).is_err());
445 }
446
447 #[test]
448 fn cmovcc_wrong_second_nibble() {
449 let bytes = [0x0F, 0x90, 0xC3];
451 assert!(patch_cmovcc_to_mov(&bytes).is_err());
452 }
453
454 #[test]
455 fn cmovcc_invalid_rex_followed_by_garbage() {
456 let bytes = [0x48, 0xAA, 0xBB, 0xCC];
459 assert!(patch_cmovcc_to_mov(&bytes).is_err());
460 }
461
462 #[test]
465 fn nop_buffer_emits_requested_length() {
466 let buf = nop_buffer(7);
467 assert_eq!(buf, vec![NOP; 7]);
468 }
469}