Skip to content

Commit

Permalink
Add last_executed_pc property to abstract CPU (#2475)
Browse files Browse the repository at this point in the history
Helpful for knowing the exact last executed instruction address.

* Fix some types

* Set last_executed_pc to insn.address when disagreement on PC and insn

This should be correct since some hook has intercepted the events for
the instruction and "executed" it for us.

* Simplify instruction publish event call

* Add more documentation for last executed pc/instruction
  • Loading branch information
ekilmer authored Sep 10, 2021
1 parent 4d880cd commit 7c905c8
Show file tree
Hide file tree
Showing 2 changed files with 34 additions and 18 deletions.
36 changes: 26 additions & 10 deletions manticore/native/cpu/abstractcpu.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import unicorn

from .disasm import init_disassembler
from .disasm import init_disassembler, Instruction
from ..memory import ConcretizeMemory, InvalidMemoryAccess, FileMap, AnonMap
from ..memory import LazySMemory, Memory
from ...core.smtlib import Operators, Constant, issymbolic, BitVec, Expression
Expand Down Expand Up @@ -533,9 +533,15 @@ def __init__(self, regfile: RegisterFile, memory: Memory, **kwargs):
super().__init__(**kwargs)
self._regfile = regfile
self._memory = memory
self._instruction_cache: Dict[int, Any] = {}
self._instruction_cache: Dict[int, Instruction] = {}
self._icount = 0
# _last_pc represents the last PC that was going to be executed, but it
# might not have been due to user hooks, exceptions, etc. You probably
# want last_executed_pc() or last_executed_insn()
self._last_pc = None
# _last_executed_pc represents the last PC that was executed and
# affected the state of the program
self._last_executed_pc = None
self._concrete = kwargs.pop("concrete", False)
self.emu = None
self._break_unicorn_at: Optional[int] = None
Expand All @@ -552,6 +558,7 @@ def __getstate__(self):
state["memory"] = self._memory
state["icount"] = self._icount
state["last_pc"] = self._last_pc
state["last_executed_pc"] = self._last_executed_pc
state["disassembler"] = self._disasm
state["concrete"] = self._concrete
state["break_unicorn_at"] = self._break_unicorn_at
Expand All @@ -568,6 +575,7 @@ def __setstate__(self, state):
)
self._icount = state["icount"]
self._last_pc = state["last_pc"]
self._last_executed_pc = state["last_executed_pc"]
self._disasm = state["disassembler"]
self._concrete = state["concrete"]
self._break_unicorn_at = state["break_unicorn_at"]
Expand All @@ -578,6 +586,18 @@ def __setstate__(self, state):
def icount(self):
return self._icount

@property
def last_executed_pc(self) -> Optional[int]:
"""The last PC that was executed."""
return self._last_executed_pc

@property
def last_executed_insn(self) -> Optional[Instruction]:
"""The last instruction that was executed."""
if not self.last_executed_pc:
return None
return self.decode_instruction(self.last_executed_pc)

##############################
# Register access
@property
Expand Down Expand Up @@ -913,7 +933,7 @@ def _wrap_operands(self, operands):
"""
raise NotImplementedError

def decode_instruction(self, pc: int):
def decode_instruction(self, pc: int) -> Instruction:
"""
This will decode an instruction from memory pointed by `pc`
Expand Down Expand Up @@ -993,13 +1013,7 @@ def execute(self):
"""
curpc = self.PC
if self._delayed_event:
self._icount += 1
self._publish(
"did_execute_instruction",
self._last_pc,
curpc,
self.decode_instruction(self._last_pc),
)
self._publish_instruction_as_executed(self.decode_instruction(self._last_pc))
self._delayed_event = False

if issymbolic(curpc):
Expand All @@ -1017,6 +1031,7 @@ def execute(self):
# FIXME (theo) why just return here?
# hook changed PC, so we trust that there is nothing more to do
if insn.address != self.PC:
self._last_executed_pc = insn.address
return

name = self.canonicalize_instruction_name(insn)
Expand Down Expand Up @@ -1064,6 +1079,7 @@ def _publish_instruction_as_executed(self, insn):
"""
Notify listeners that an instruction has been executed.
"""
self._last_executed_pc = self._last_pc
self._icount += 1
self._publish("did_execute_instruction", self._last_pc, self.PC, insn)

Expand Down
16 changes: 8 additions & 8 deletions manticore/native/cpu/disasm.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,22 @@ class Instruction:

@property
@abstractmethod
def address(self):
def address(self) -> int:
pass

@property
@abstractmethod
def mnemonic(self):
def mnemonic(self) -> str:
pass

@property
@abstractmethod
def op_str(self):
def op_str(self) -> str:
pass

@property
@abstractmethod
def size(self):
def size(self) -> int:
pass

@property
Expand All @@ -34,12 +34,12 @@ def operands(self):
# FIXME (theo) eliminate one of the two of insn_name, name
@property
@abstractmethod
def insn_name(self):
def insn_name(self) -> str:
pass

@property
@abstractmethod
def name(self):
def name(self) -> str:
pass


Expand All @@ -50,7 +50,7 @@ def __init__(self, disasm):
self.disasm = disasm

@abstractmethod
def disassemble_instruction(self, code, pc):
def disassemble_instruction(self, code, pc) -> Instruction:
"""Get next instruction based on the disassembler in use
:param str code: binary blob to be disassembled
Expand All @@ -68,7 +68,7 @@ def __init__(self, arch, mode):
cap.syntax = 0
super().__init__(cap)

def disassemble_instruction(self, code, pc):
def disassemble_instruction(self, code: bytes, pc: int) -> Instruction:
"""Get next instruction using the Capstone disassembler
:param str code: binary blob to be disassembled
Expand Down

0 comments on commit 7c905c8

Please sign in to comment.