For bt/bts/btr/btc with a register bit index, the lifter masks the index
on the memory/register write but not on the carry flag, so the two halves of the
same instruction disagree when the index is >= the operand size.
Intel SDM: for a register destination, the bit offset is taken modulo the operand
size, so bts eax, ecx with ecx = 33 operates on bit 1.
Repro
b802000000 b921000000 0fabc8 c3
mov eax, 2
mov ecx, 33
bts eax, ecx
ret
LLIL:
eax = 2
ecx = 0x21
flag:c = test_bit(eax, ecx) <-- raw index 33
eax = eax | 1 << modu.d(ecx, 0x20) <-- index correctly reduced to 1
Expected vs actual
Hardware (executed on x86-64), all with eax = 2, ecx = 33:
| instruction |
CF |
eax |
bt eax, ecx |
1 |
0x2 |
bts eax, ecx |
1 |
0x2 |
btr eax, ecx |
1 |
0x0 |
btc eax, ecx |
1 |
0x0 |
Binary Ninja reports CF = 0, because test_bit(eax, 33) reads bit 33 of 2
rather than bit 1. The write side is correct. Dataflow constant-folds the wrong
flag value accordingly.
For
bt/bts/btr/btcwith a register bit index, the lifter masks the indexon the memory/register write but not on the carry flag, so the two halves of the
same instruction disagree when the index is >= the operand size.
Intel SDM: for a register destination, the bit offset is taken modulo the operand
size, so
bts eax, ecxwithecx = 33operates on bit 1.Repro
LLIL:
Expected vs actual
Hardware (executed on x86-64), all with
eax = 2,ecx = 33:bt eax, ecxbts eax, ecxbtr eax, ecxbtc eax, ecxBinary Ninja reports
CF = 0, becausetest_bit(eax, 33)reads bit 33 of2rather than bit 1. The write side is correct. Dataflow constant-folds the wrong
flag value accordingly.