Skip to content

Commit 89be9f4

Browse files
xusheng6claude
andcommitted
[emulator] Fix test cases that asserted on IL the API cannot express
Several per-instruction tests were themselves wrong, and only became visible once the suite stopped segfaulting on its first test. LLIL constants carry a 64-bit value, so il.const(16, 1 << 64) and il.const(16, -17) silently truncated: the first made SET_REG_SPLIT's source 2 rather than 2**64 + 2, and the second turned the DIVS_DP/MODS_DP dividend into a large positive 128-bit value, testing unsigned division under a signed name. Build those operands with a shift and a sign-extend instead. The ADC/SBB carry, SET_FLAG and IF conditions used il.const(0, 1) -- a zero-byte constant, which correctly masks to 0, so the tests asserted carry-in of 1 while passing 0. Real lifted IL uses a flag expression here (adc.q(rax, rbx, flag:c)), never a zero-size constant; use a 1-byte constant. test_load seeded memory with (0xefbeadde).to_bytes(4, 'little') and expected a little-endian load to return 0xdeadbeef, which is the byte-reversed value. The intrinsic hook took four parameters, but the documented and implemented contract is (emulator, intrinsic_id, params) returning a list of (register, value) pairs. The resulting TypeError was swallowed by the binding's bare except and surfaced only as an Unimplemented stop. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 3068a19 commit 89be9f4

1 file changed

Lines changed: 20 additions & 11 deletions

File tree

plugins/emulator/test/emulator_il_test.py

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -109,9 +109,12 @@ def test_reg_and_set_reg(self):
109109
self.assertEqual(self.eval_to_reg(lambda il: il.reg(8, 'rdi'), regs={'rdi': 0xdead}), 0xdead)
110110

111111
def test_set_reg_split(self):
112-
# rdx:rax = 0x1_00000000_00000002 -> rax = low, rdx = high
112+
# rdx:rax = 0x1_00000000_00000002 -> rax = low, rdx = high.
113+
# LLIL constants carry a 64-bit value, so the 128-bit source is built with a shift
114+
# rather than il.const(16, 1 << 64), which would silently truncate to 0.
113115
def emit(il):
114-
il.append(il.set_reg_split(8, 'rdx', 'rax', il.const(16, (1 << 64) | 2)))
116+
hi = il.shift_left(16, il.const(16, 1), il.const(1, 64))
117+
il.append(il.set_reg_split(8, 'rdx', 'rax', il.or_expr(16, hi, il.const(16, 2))))
115118
emu = self.build(emit)
116119
emu.step()
117120
self.assertEqual(emu.get_register('rax'), 2)
@@ -136,18 +139,22 @@ def check(self, name, make_expr, expected, **kw):
136139
def test_arithmetic(self):
137140
C = lambda il, v, s=8: il.const(s, v)
138141
self.check('ADD', lambda il: il.add(8, C(il, 5), C(il, 3)), 8)
139-
self.check('ADC', lambda il: il.add_carry(8, C(il, 5), C(il, 3), C(il, 1, 0)), 9)
142+
self.check('ADC', lambda il: il.add_carry(8, C(il, 5), C(il, 3), C(il, 1, 1)), 9)
140143
self.check('SUB', lambda il: il.sub(8, C(il, 5), C(il, 3)), 2)
141-
self.check('SBB', lambda il: il.sub_borrow(8, C(il, 5), C(il, 3), C(il, 1, 0)), 1) # 5 - 3 - borrow(1)
144+
self.check('SBB', lambda il: il.sub_borrow(8, C(il, 5), C(il, 3), C(il, 1, 1)), 1) # 5 - 3 - borrow(1)
142145
self.check('MUL', lambda il: il.mult(8, C(il, 6), C(il, 7)), 42)
143146
self.check('DIVU', lambda il: il.div_unsigned(8, C(il, 17), C(il, 5)), 3)
144147
self.check('DIVS', lambda il: il.div_signed(8, C(il, -17), C(il, 5)), (-3) & U64)
145148
self.check('MODU', lambda il: il.mod_unsigned(8, C(il, 17), C(il, 5)), 2)
146149
self.check('MODS', lambda il: il.mod_signed(8, C(il, -17), C(il, 5)), (-2) & U64)
147150
self.check('DIVU_DP', lambda il: il.div_double_prec_unsigned(8, C(il, 17, 16), C(il, 5)), 3)
148-
self.check('DIVS_DP', lambda il: il.div_double_prec_signed(8, C(il, -17, 16), C(il, 5)), (-3) & U64)
151+
# The 16-byte dividend must be sign-extended from a 64-bit constant: il.const(16, -17)
152+
# would truncate to a large *positive* 128-bit value and silently pass as unsigned.
153+
self.check('DIVS_DP', lambda il: il.div_double_prec_signed(
154+
8, il.sign_extend(16, C(il, -17)), C(il, 5)), (-3) & U64)
149155
self.check('MODU_DP', lambda il: il.mod_double_prec_unsigned(8, C(il, 17, 16), C(il, 5)), 2)
150-
self.check('MODS_DP', lambda il: il.mod_double_prec_signed(8, C(il, -17, 16), C(il, 5)), (-2) & U64)
156+
self.check('MODS_DP', lambda il: il.mod_double_prec_signed(
157+
8, il.sign_extend(16, C(il, -17)), C(il, 5)), (-2) & U64)
151158
self.check('NEG', lambda il: il.neg_expr(8, C(il, 5)), (-5) & U64)
152159
self.check('ABS', lambda il: il.expr(LowLevelILOperation.LLIL_ABS, C(il, -5), size=8), 5)
153160

@@ -236,7 +243,7 @@ def test_bit_ops(self):
236243
class MemoryStackOpTests(ILTestBase):
237244
def test_load(self):
238245
val = self.eval_to_reg(lambda il: il.load(4, il.const(8, 0x2000)),
239-
mem={0x2000: (0xefbeadde).to_bytes(4, 'little')})
246+
mem={0x2000: (0xdeadbeef).to_bytes(4, 'little')})
240247
self.assertEqual(val, 0xdeadbeef)
241248

242249
def test_store(self):
@@ -269,7 +276,7 @@ class FlagOpTests(ILTestBase):
269276
def test_set_flag_and_flag(self):
270277
# SET_FLAG writes c; FLAG reads it back into a register.
271278
def emit(il):
272-
il.append(il.set_flag('c', il.const(0, 1)))
279+
il.append(il.set_flag('c', il.const(1, 1)))
273280
il.append(il.set_reg(8, 'rax', il.flag('c')))
274281
emu = self.build(emit)
275282
emu.step()
@@ -316,7 +323,7 @@ def make(cond_true):
316323
def emit(il):
317324
t = LowLevelILLabel()
318325
f = LowLevelILLabel()
319-
il.append(il.if_expr(il.const(0, 1 if cond_true else 0), t, f))
326+
il.append(il.if_expr(il.const(1, 1 if cond_true else 0), t, f))
320327
il.mark_label(t)
321328
il.append(il.set_reg(8, 'rax', il.const(8, 1)))
322329
il.append(il.no_ret())
@@ -427,9 +434,11 @@ def test_intrinsic_hook_invoked(self):
427434
# With an intrinsic hook installed, LLIL_INTRINSIC is delegated to it.
428435
seen = {}
429436

430-
def hook(emu, intrinsic, params, outputs):
437+
# The hook contract is (emulator, intrinsic_id, params) -> list of (reg, value)
438+
# pairs when handled, or None to fall through to Unimplemented.
439+
def hook(emu, intrinsic, params):
431440
seen['called'] = True
432-
return True
441+
return []
433442

434443
def emit(il):
435444
il.append(il.intrinsic([], 0, []))

0 commit comments

Comments
 (0)