diff --git a/pypcode/sleigh/sleigh.cc b/pypcode/sleigh/sleigh.cc index 304a73b..8cc5b1b 100644 --- a/pypcode/sleigh/sleigh.cc +++ b/pypcode/sleigh/sleigh.cc @@ -365,13 +365,37 @@ void SleighBuilder::appendBuild(OpTpl *bld,int4 secnum) walker->popOperand(); } +/// \brief Restore a SleighBuilder's walker and unique offset when the enclosing scope exits +/// +/// A delay slot or a CROSSBUILD is built through a ParserWalker that lives on the stack frame of +/// the builder method, so the builder must point back at its original walker before that frame +/// goes away, including when the nested build throws. Otherwise Sleigh::oneInstruction reports +/// the failure through a walker that no longer exists. +class WalkerScope { + ParserWalker *&curwalker; ///< The builder's current walker + uintb &curuniqueoffset; ///< The builder's current unique offset + ParserWalker *oldwalker; ///< The walker in use when this scope was entered + uintb olduniqueoffset; ///< The unique offset in use when this scope was entered +public: + /// Record the walker and unique offset to put back + WalkerScope(ParserWalker *&w,uintb &off) : curwalker(w), curuniqueoffset(off) { + oldwalker = w; + olduniqueoffset = off; + } + /// Put back the recorded walker and unique offset + ~WalkerScope(void) { + curwalker = oldwalker; + curuniqueoffset = olduniqueoffset; + } +}; + void SleighBuilder::delaySlot(OpTpl *op) { // Append pcode for an entire instruction (delay slot) // in the middle of the current instruction ParserWalker *tmp = walker; - uintb olduniqueoffset = uniqueoffset; + WalkerScope scope(walker,uniqueoffset); // Restore original context on every exit Address baseaddr = tmp->getAddr(); int4 fallOffset = tmp->getLength(); @@ -392,8 +416,6 @@ void SleighBuilder::delaySlot(OpTpl *op) fallOffset += len; bytecount += len; } while(bytecount < delaySlotByteCnt); - walker = tmp; // Restore original context - uniqueoffset = olduniqueoffset; } void SleighBuilder::setLabel(OpTpl *op) @@ -416,7 +438,7 @@ void SleighBuilder::appendCrossBuild(OpTpl *bld,int4 secnum) uintb addr = spc->wrapOffset( vn->getOffset().fix(*walker) ); ParserWalker *tmp = walker; - uintb olduniqueoffset = uniqueoffset; + WalkerScope scope(walker,uniqueoffset); // Restore original context on every exit Address newaddr(spc,addr); setUniqueOffset(newaddr); @@ -434,8 +456,6 @@ void SleighBuilder::appendCrossBuild(OpTpl *bld,int4 secnum) buildEmpty(ct,secnum); else build(construct,secnum); - walker = tmp; - uniqueoffset = olduniqueoffset; } /// \param min is the minimum number of allocations before a reuse is expected diff --git a/tests/test_pypcode.py b/tests/test_pypcode.py index 4f258a8..84ad41e 100755 --- a/tests/test_pypcode.py +++ b/tests/test_pypcode.py @@ -247,6 +247,30 @@ def test_partial_unimpl_failure(self): tx = ctx.translate(b"\xd0\x00\xa8\x00") # and r0, r0; unimpl assert len(get_imarks(tx)) == 1 + def test_delay_slot_unimpl_failure(self): + # The delay slot is built through a walker owned by SleighBuilder::delaySlot, and the + # error is reported through the builder's walker once that frame is gone. + for lang, insns in [ + ("Toy:BE:32:default", b"\xf5\x00\xa8\x00"), # callds 0x0; unimpl + ("sparc:BE:32:default", b"\x63\x74\x85\x96\xa7\xb8\xc9\xda"), # call; unimpl + ]: + with self.subTest(lang=lang): + ctx = Context(lang) + with self.assertRaises(UnimplError) as ctxmgr: + ctx.translate(insns) + # The branch is reported, not the instruction in its delay slot. + assert "0x00000000" in str(ctxmgr.exception) + + def test_partial_delay_slot_unimpl_failure(self): + for lang, insns in [ + ("Toy:BE:32:default", b"\xd0\x00\xf5\x00\xa8\x00"), # and r0, r0; callds 0x0; unimpl + ("sparc:BE:32:default", b"\x01\x00\x00\x00\x63\x74\x85\x96\xa7\xb8\xc9\xda"), # nop; call; unimpl + ]: + with self.subTest(lang=lang): + ctx = Context(lang) + tx = ctx.translate(insns) + assert len(get_imarks(tx)) == 1 + def test_not_cached(self): ctx = Context("x86:LE:64:default") tx = ctx.translate(b"\xeb\xfe", 5)