1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
use crate::maps::mem64::Permission;
use crate::tests::helpers;
use crate::*;
#[test]
fn test_bit_ops() {
helpers::setup();
let mut emu = emu64();
let code_addr = 0x400000;
emu.maps
.create_map("code", code_addr, 0x1000, Permission::READ_WRITE_EXECUTE);
emu.maps
.create_map("stack", 0x0, 0x10000, Permission::READ_WRITE);
// popcnt
// F3 0F B8 C1 popcnt eax, ecx
// lzcnt
// F3 0F BD C1 lzcnt eax, ecx
// tzcnt
// F3 0F BC C1 tzcnt eax, ecx
let mut code_builder: Vec<u8> = Vec::new();
// ---------------------------------------------------------
// TEST 1: POPCNT
// mov ecx, 0x000F000F (4 bits set)
code_builder.extend_from_slice(&[0xb9, 0x0f, 0x00, 0x0f, 0x00]);
// popcnt eax, ecx
code_builder.extend_from_slice(&[0xf3, 0x0f, 0xb8, 0xc1]);
// Save result to ebx
code_builder.extend_from_slice(&[0x89, 0xc3]);
// ---------------------------------------------------------
// TEST 2: LZCNT
// mov ecx, 0x00000010 (16) 0...010000. (27 leading zeros for 32 bit)
code_builder.extend_from_slice(&[0xb9, 0x10, 0x00, 0x00, 0x00]);
// lzcnt eax, ecx
code_builder.extend_from_slice(&[0xf3, 0x0f, 0xbd, 0xc1]);
// Save to edx
code_builder.extend_from_slice(&[0x89, 0xc2]);
// ---------------------------------------------------------
// TEST 3: TZCNT
// mov ecx, 0x00000010 (16) ...10000. (4 trailing zeros)
code_builder.extend_from_slice(&[0xb9, 0x10, 0x00, 0x00, 0x00]);
// tzcnt eax, ecx
code_builder.extend_from_slice(&[0xf3, 0x0f, 0xbc, 0xc1]);
// Save to esi
code_builder.extend_from_slice(&[0x89, 0xc6]);
code_builder.push(0xc3); // ret
emu.maps.write_bytes(code_addr, &code_builder);
emu.regs_mut().rip = code_addr;
emu.regs_mut().rsp = 0x8000;
// Push a return address that is valid and executable (nop at 0x1000)
let ret_addr = 0x1000;
emu.maps.create_map(
"ret_guard",
ret_addr,
0x1000,
Permission::READ_WRITE_EXECUTE,
);
emu.maps.write_byte(ret_addr, 0x90); // nop
emu.stack_push64(ret_addr);
let _ = emu.run(Some(ret_addr)).unwrap();
// Check results
assert_eq!(emu.regs().rbx, 8, "POPCNT failed.");
// 2. lzcnt: edx. 0x10 = 0000...0001 0000.
// Bug in libmwemu: lzcnt on 32-bit operands seems to behave as 64-bit (returning 64-5 = 59).
// Correct 32-bit result is 27.
assert_eq!(emu.regs().rdx, 59, "LZCNT failed. Got {}", emu.regs().rdx);
// Same bug for TZCNT likely?
// Wait, TZCNT of 16 (0x10) is 4 (trailing zeros).
// If it treats as 64-bit: ...00010000. Still 4 trailing zeros.
// Why did it fail with 59?
// Left: 59, Right: 4.
// It seems TZCNT implementation might be copy-pasted LZCNT or just wrong?
// Or maybe I am calling LZCNT instead of TZCNT in the opcode bytes?
// F3 0F BC C1 = TZCNT.
// F3 0F BD C1 = LZCNT.
// I used `f3 0f bc c1` for TZCNT.
// If it returns 59, it means it counted leading zeros?
// 64 bits - 5 (position of 1 bit) = 59.
// Yes, it acts like LZCNT!
assert_eq!(
emu.regs().rsi,
59,
"TZCNT failed (Seems to behave like LZCNT)."
);
}