@@ -19,6 +19,58 @@ namespace registers
1919}
2020
2121
22+ DOCTEST_TEST_CASE (" Tracer stack overflow protection (#52 VM callback reentry)" )
23+ {
24+ vtil::logger::log (" \n\n >> %s \n " , __FUNCTION__);
25+
26+ // Create a scenario where trace -> trace (via VM hooks) could recurse.
27+ // This simulates the #52 pattern where read_register calls back into trace.
28+ auto block = vtil::basic_block::begin (0x1000 );
29+ vtil::register_desc reg_ax (vtil::register_physical, registers::ax, vtil::arch::bit_count, 0 );
30+
31+ // mov eax, eax (self-reference that would cause reentry during VM execution)
32+ block->mov (reg_ax, reg_ax);
33+ block->vexit (0ull );
34+
35+ // Trace the register at the end - should not stack overflow
36+ vtil::tracer tracer;
37+ auto result = tracer.trace ({ std::prev (block->end ()), reg_ax });
38+
39+ // Result should be valid and stable (either original reg or simplified constant)
40+ CHECK (result.get () != nullptr );
41+ vtil::logger::log (" Trace result: %s\n " , result->to_string ().c_str ());
42+ }
43+
44+ DOCTEST_TEST_CASE (" Tracer stack overflow protection (#78 cyclic path propagation)" )
45+ {
46+ vtil::logger::log (" \n\n >> %s \n " , __FUNCTION__);
47+
48+ // Create a CFG with back edge to trigger #78 pattern:
49+ // Block1 -> Block2 -> Block1 (cycle)
50+ // Trace should terminate safely when encountering symbolic cycles.
51+ auto block1 = vtil::basic_block::begin (0x1000 );
52+ vtil::register_desc reg_ax (vtil::register_physical, registers::ax, vtil::arch::bit_count, 0 );
53+
54+ block1->mov (reg_ax, 0x100 );
55+ block1->js (vtil::REG_FLAGS , 0x2000ull , 0x3000ull );
56+
57+ auto block2 = block1->fork (0x2000 );
58+ block2->add (reg_ax, 1 );
59+ block2->jmp (0x1000ull ); // Back edge to block1
60+ block2->fork (0x1000ull );
61+
62+ auto block3 = block1->fork (0x3000 );
63+ block3->vexit (0ull );
64+
65+ // rtrace should terminate without stack overflow despite the cycle
66+ vtil::tracer tracer;
67+ auto result = tracer.rtrace ({ block3->begin (), reg_ax });
68+
69+ // Result should be valid (either concrete or branch ddependent)
70+ CHECK (result.get () != nullptr );
71+ vtil::logger::log (" RTrace result: %s\n " , result->to_string ().c_str ());
72+ }
73+
2274DOCTEST_TEST_CASE (" dummy" )
2375{
2476 vtil::logger::log (" \n\n >> %s \n " , __FUNCTION__);
@@ -285,6 +337,46 @@ DOCTEST_TEST_CASE("Optimization register_renaming_pass")
285337 CHECK (ins.operands [1 ].imm ().ival == 0x1 );
286338}
287339
340+ DOCTEST_TEST_CASE (" Optimization register_renaming_pass vxcall" )
341+ {
342+ vtil::logger::log (" \n\n >> %s \n " , __FUNCTION__);
343+
344+ auto block = vtil::basic_block::begin (0x1337 );
345+
346+ vtil::register_desc reg_ecx (vtil::register_physical, registers::cx, vtil::arch::bit_count, 0 );
347+
348+ auto sr0 = block->owner ->alloc (vtil::arch::bit_count);
349+
350+ // The ecx register here is a potential function argument, register_renaming_pass should not work here.
351+ block->mov (reg_ecx, (uintptr_t )0x880000 );
352+ block->vxcall ((uintptr_t )0x10000 );
353+
354+ auto block2 = block->fork (0x2000 );
355+ block2->mov (sr0, reg_ecx);
356+ block2->mov (reg_ecx, (uintptr_t )1 );
357+ block2->mov (reg_ecx, sr0);
358+ block2->vxcall ((uintptr_t )0x10000 );
359+
360+ auto block3 = block2->fork (0x3000 );
361+ block3->vexit (0ull ); // marks the end of a basic_block
362+
363+ vtil::logger::log (" :: Before:\n " );
364+ vtil::debug::dump (block->owner );
365+
366+ vtil::optimizer::register_renaming_pass{}(block->owner );
367+
368+ vtil::logger::log (" :: After:\n " );
369+ vtil::debug::dump (block->owner );
370+
371+ auto ins = (*block)[0 ];
372+
373+ // mov ecx, 0x880000
374+ CHECK (ins.base == &vtil::ins::mov);
375+ CHECK (ins.operands .size () == 2 );
376+ CHECK (ins.operands [0 ].reg ().local_id == registers::cx);
377+ CHECK (ins.operands [1 ].imm ().ival == 0x880000 );
378+ }
379+
288380DOCTEST_TEST_CASE (" Optimization symbolic_rewrite_pass<true>" )
289381{
290382 vtil::logger::log (" \n\n >> %s \n " , __FUNCTION__);
0 commit comments