Skip to content

Commit bdd4884

Browse files
committed
Add coverage for newly added IL instructions
1 parent 1b07d76 commit bdd4884

3 files changed

Lines changed: 158 additions & 4 deletions

File tree

plugins/emulator/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ Emulating an unsupported instruction stops the emulator with an `Unimplemented`
7272
`DIVU_DP`, `DIVS_DP`, `MODU`, `MODS`, `MODU_DP`, `MODS_DP`, `NEG`, `ADD_OVERFLOW`
7373
- **Bitwise / shifts:** `AND`, `OR`, `XOR`, `NOT`, `LSL`, `LSR`, `ASR`, `ROL`, `ROR`, `RLC`,
7474
`RRC`, `SX`, `ZX`, `LOW_PART`, `TEST_BIT`, `BOOL_TO_INT`
75+
- **Bit operations:** `BSWAP`, `POPCNT`, `CLZ`, `CTZ`, `RBIT`, `CLS`, `ABS`, `MINS`, `MAXS`,
76+
`MINU`, `MAXU`
7577
- **Comparisons:** `CMP_E`, `CMP_NE`, `CMP_SLT`, `CMP_SLE`, `CMP_SGE`, `CMP_SGT`, `CMP_ULT`,
7678
`CMP_ULE`, `CMP_UGE`, `CMP_UGT`
7779
- **Flags:** `FLAG`, `SET_FLAG`, `FLAG_BIT`, `FLAG_COND`, `FLAG_GROUP`
@@ -86,8 +88,6 @@ Emulating an unsupported instruction stops the emulator with an `Unimplemented`
8688
(float *constants* are read, but float arithmetic is not evaluated)
8789
- **Register stacks (x87/FPU-style):** `REG_STACK_REL`, `SET_REG_STACK_REL`, `REG_STACK_PUSH`,
8890
`REG_STACK_POP`, `REG_STACK_FREE_REG`, `REG_STACK_FREE_REL`
89-
- **Bit operations:** `BSWAP`, `CLZ`, `CTZ`, `CLS`, `POPCNT`, `RBIT`, `ABS`, `MINS`, `MAXS`,
90-
`MINU`, `MAXU`
9191
- **System / hooks** (no built-in semantics — stop unless the embedding code registers a
9292
hook): `SYSCALL`, `INTRINSIC`
9393
- **Halting / non-representable** (stop the emulator): `BP`, `TRAP`, `UNDEF`, `UNIMPL`,

plugins/emulator/core/llilemulator.cpp

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1246,6 +1246,117 @@ intx::uint512 LLILEmulator::EvalExpr(const LowLevelILInstruction& expr)
12461246
case LLIL_LOW_PART:
12471247
return MaskToSize(EvalExpr(expr.GetRawOperandAsExpr(0)), sz);
12481248

1249+
// --- Bit operations ---
1250+
case LLIL_BSWAP:
1251+
{
1252+
// Reverse the byte order of the sz-byte value.
1253+
intx::uint512 val = MaskToSize(EvalExpr(expr.GetRawOperandAsExpr(0)), sz);
1254+
intx::uint512 result(0);
1255+
for (size_t i = 0; i < sz; i++)
1256+
{
1257+
intx::uint512 byte = (val >> (8 * i)) & intx::uint512(0xff);
1258+
result |= byte << (8 * (sz - 1 - i));
1259+
}
1260+
return result;
1261+
}
1262+
1263+
case LLIL_POPCNT:
1264+
{
1265+
intx::uint512 val = MaskToSize(EvalExpr(expr.GetRawOperandAsExpr(0)), sz);
1266+
uint64_t count = 0;
1267+
for (size_t i = 0; i < sz * 8; i++)
1268+
if (((val >> i) & intx::uint512(1)) != 0)
1269+
count++;
1270+
return intx::uint512(count);
1271+
}
1272+
1273+
case LLIL_CLZ:
1274+
{
1275+
// Count leading zero bits; clz(0) == 8 * size.
1276+
intx::uint512 val = MaskToSize(EvalExpr(expr.GetRawOperandAsExpr(0)), sz);
1277+
size_t bits = sz * 8;
1278+
uint64_t count = 0;
1279+
for (size_t i = bits; i-- > 0;)
1280+
{
1281+
if (((val >> i) & intx::uint512(1)) != 0)
1282+
break;
1283+
count++;
1284+
}
1285+
return intx::uint512(count);
1286+
}
1287+
1288+
case LLIL_CTZ:
1289+
{
1290+
// Count trailing zero bits; ctz(0) == 8 * size.
1291+
intx::uint512 val = MaskToSize(EvalExpr(expr.GetRawOperandAsExpr(0)), sz);
1292+
size_t bits = sz * 8;
1293+
uint64_t count = 0;
1294+
for (size_t i = 0; i < bits; i++)
1295+
{
1296+
if (((val >> i) & intx::uint512(1)) != 0)
1297+
break;
1298+
count++;
1299+
}
1300+
return intx::uint512(count);
1301+
}
1302+
1303+
case LLIL_RBIT:
1304+
{
1305+
// Reverse the bit order of the sz-byte value.
1306+
intx::uint512 val = MaskToSize(EvalExpr(expr.GetRawOperandAsExpr(0)), sz);
1307+
size_t bits = sz * 8;
1308+
intx::uint512 result(0);
1309+
for (size_t i = 0; i < bits; i++)
1310+
if (((val >> i) & intx::uint512(1)) != 0)
1311+
result |= intx::uint512(1) << (bits - 1 - i);
1312+
return result;
1313+
}
1314+
1315+
case LLIL_CLS:
1316+
{
1317+
// Count leading sign bits: number of bits below the sign bit that match it.
1318+
intx::uint512 val = MaskToSize(EvalExpr(expr.GetRawOperandAsExpr(0)), sz);
1319+
size_t bits = sz * 8;
1320+
intx::uint512 sign = (val >> (bits - 1)) & intx::uint512(1);
1321+
uint64_t count = 0;
1322+
for (size_t i = bits - 1; i-- > 0;)
1323+
{
1324+
if (((val >> i) & intx::uint512(1)) != sign)
1325+
break;
1326+
count++;
1327+
}
1328+
return intx::uint512(count);
1329+
}
1330+
1331+
case LLIL_ABS:
1332+
{
1333+
// Signed absolute value; abs(INT_MIN) == INT_MIN.
1334+
intx::uint512 val = MaskToSize(EvalExpr(expr.GetRawOperandAsExpr(0)), sz);
1335+
size_t bits = sz * 8;
1336+
bool neg = ((val >> (bits - 1)) & intx::uint512(1)) != 0;
1337+
return neg ? MaskToSize(~val + 1, sz) : val;
1338+
}
1339+
1340+
case LLIL_MINS:
1341+
case LLIL_MAXS:
1342+
{
1343+
intx::uint512 left = MaskToSize(EvalExpr(expr.GetRawOperandAsExpr(0)), sz);
1344+
intx::uint512 right = MaskToSize(EvalExpr(expr.GetRawOperandAsExpr(1)), sz);
1345+
int64_t sl = static_cast<int64_t>(static_cast<uint64_t>(SignExtend(left, sz, 8)));
1346+
int64_t sr = static_cast<int64_t>(static_cast<uint64_t>(SignExtend(right, sz, 8)));
1347+
bool leftWins = (expr.operation == LLIL_MINS) ? (sl <= sr) : (sl >= sr);
1348+
return leftWins ? left : right;
1349+
}
1350+
1351+
case LLIL_MINU:
1352+
case LLIL_MAXU:
1353+
{
1354+
intx::uint512 left = MaskToSize(EvalExpr(expr.GetRawOperandAsExpr(0)), sz);
1355+
intx::uint512 right = MaskToSize(EvalExpr(expr.GetRawOperandAsExpr(1)), sz);
1356+
bool leftWins = (expr.operation == LLIL_MINU) ? (left <= right) : (left >= right);
1357+
return leftWins ? left : right;
1358+
}
1359+
12491360
// --- Comparisons ---
12501361
case LLIL_CMP_E:
12511362
{

plugins/emulator/test/emulator_test.py

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,9 @@ def make_bv(code: bytes, entries=(0,), arch: str = 'x86_64') -> BinaryView:
4949

5050

5151
class EmulatorTestBase(unittest.TestCase):
52-
def emulator_for(self, code: bytes, entries=(0,), entry_point=0, max_instructions=1000):
53-
bv = make_bv(code, entries=entries)
52+
def emulator_for(self, code: bytes, entries=(0,), entry_point=0, max_instructions=1000,
53+
arch='x86_64'):
54+
bv = make_bv(code, entries=entries, arch=arch)
5455
emu = LLILEmulator(bv)
5556
self.assertTrue(emu.set_entry_point(entry_point))
5657
emu.set_max_instructions(max_instructions)
@@ -314,6 +315,48 @@ def test_all_hooks_can_be_removed(self):
314315
setter(None) # must not raise
315316

316317

318+
class BitOpTests(EmulatorTestBase):
319+
# x86-64 snippets whose lifted LLIL exercises the bit ops (BSWAP/POPCNT/CLZ/CTZ),
320+
# and aarch64 snippets for the ops with direct A64 instructions (RBIT/CLS).
321+
def test_bswap(self):
322+
# mov eax, 0x12345678 ; bswap eax ; ret
323+
emu = self.emulator_for(b'\xb8\x78\x56\x34\x12\x0f\xc8\xc3')
324+
emu.run()
325+
self.assertEqual(emu.get_register('rax'), 0x78563412)
326+
327+
def test_popcnt(self):
328+
# mov ecx, 0xff ; popcnt eax, ecx ; ret
329+
emu = self.emulator_for(b'\xb9\xff\x00\x00\x00\xf3\x0f\xb8\xc1\xc3')
330+
emu.run()
331+
self.assertEqual(emu.get_register('rax'), 8)
332+
333+
def test_clz(self):
334+
# mov ecx, 1 ; lzcnt eax, ecx ; ret -> 31 leading zeros in a 32-bit 1
335+
emu = self.emulator_for(b'\xb9\x01\x00\x00\x00\xf3\x0f\xbd\xc1\xc3')
336+
emu.run()
337+
self.assertEqual(emu.get_register('rax'), 31)
338+
339+
def test_ctz(self):
340+
# mov ecx, 8 ; tzcnt eax, ecx ; ret -> 3 trailing zeros
341+
emu = self.emulator_for(b'\xb9\x08\x00\x00\x00\xf3\x0f\xbc\xc1\xc3')
342+
emu.run()
343+
self.assertEqual(emu.get_register('rax'), 3)
344+
345+
def test_rbit(self):
346+
# aarch64: movz w0, #1 ; rbit w0, w0 ; ret -> bit 0 -> bit 31
347+
emu = self.emulator_for(
348+
b'\x20\x00\x80\x52\x00\x00\xc0\x5a\xc0\x03\x5f\xd6', arch='aarch64')
349+
emu.run()
350+
self.assertEqual(emu.get_register('x0'), 0x80000000)
351+
352+
def test_cls(self):
353+
# aarch64: movz w0, #1 ; cls w0, w0 ; ret -> 30 leading sign (zero) bits below MSB
354+
emu = self.emulator_for(
355+
b'\x20\x00\x80\x52\x00\x14\xc0\x5a\xc0\x03\x5f\xd6', arch='aarch64')
356+
emu.run()
357+
self.assertEqual(emu.get_register('x0'), 30)
358+
359+
317360
class StateSerializationTests(EmulatorTestBase):
318361
def test_save_load_round_trip(self):
319362
emu = self.emulator_for(ADD_CONSTS)

0 commit comments

Comments
 (0)