Manticore plugins (#506)
* WIP New Policy class * WIP pubsub * Update Signal tests * small fixes from github comments * Fix event decode_instruction signature * Good merge * Good good merge * WIP manticore refactor * Fix default old-style initial state * add -> enqueue * @m.init * Fix workspace url * Some test skipped * Ad Fixme to platform specific stuff in State * add -> enqueue * Enqueue created state * Fix m.init Use a messy hack to adhere to the spec (callback func receive 1 state argument) * Add _coverage_file ivar to Manticore * Fix symbolic files * remove extra enqueue * Fixing __main__ * comments * Experimental plugin system * tests fixed * Fix plugins * Some reporting moved to plugin * Fix assertions test * Add published events to classes that publish them * Update how we verify callbacks * Update Eventful._publish * Dev plugins (#512) * Yet another flavor for event name checking * really it's a bunch of minimal bugfixes * Remove get_all_event_names from Plugin * Update where we get all events * Use new metaclass-based event registry * Define prefixes in one place * remove debug print * remove debug print
This commit is contained in:
@@ -68,7 +68,6 @@ def parse_arguments():
|
||||
def main():
|
||||
args = parse_arguments()
|
||||
|
||||
|
||||
env = {key:val for key, val in map(lambda env: env[0].split('='), args.env)}
|
||||
|
||||
Manticore.verbosity(args.v)
|
||||
@@ -89,7 +88,6 @@ def main():
|
||||
for file in args.files:
|
||||
initial_state.platform.add_symbolic_file(file)
|
||||
|
||||
|
||||
m.run(procs=args.procs, timeout=args.timeout, should_profile=args.profile)
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -363,6 +363,9 @@ class Cpu(Eventful):
|
||||
- stack_alias
|
||||
'''
|
||||
|
||||
_published_events = {'write_register', 'read_register', 'write_memory', 'read_memory', 'decode_instruction',
|
||||
'execute_instruction'}
|
||||
|
||||
def __init__(self, regfile, memory, **kwargs):
|
||||
assert isinstance(regfile, RegisterFile)
|
||||
self._disasm = kwargs.pop("disasm", 'capstone')
|
||||
@@ -437,9 +440,9 @@ class Cpu(Eventful):
|
||||
:param value: register value
|
||||
:type value: int or long or Expression
|
||||
'''
|
||||
self.publish('will_write_register', register, value)
|
||||
self._publish('will_write_register', register, value)
|
||||
value = self._regfile.write(register, value)
|
||||
self.publish('did_write_register', register, value)
|
||||
self._publish('did_write_register', register, value)
|
||||
return value
|
||||
|
||||
def read_register(self, register):
|
||||
@@ -450,9 +453,9 @@ class Cpu(Eventful):
|
||||
:return: register value
|
||||
:rtype: int or long or Expression
|
||||
'''
|
||||
self.publish('will_read_register', register)
|
||||
self._publish('will_read_register', register)
|
||||
value = self._regfile.read(register)
|
||||
self.publish('did_read_register', register, value)
|
||||
self._publish('did_read_register', register, value)
|
||||
return value
|
||||
|
||||
# Pythonic access to registers and aliases
|
||||
@@ -498,11 +501,11 @@ class Cpu(Eventful):
|
||||
if size is None:
|
||||
size = self.address_bit_size
|
||||
assert size in SANE_SIZES
|
||||
self.publish('will_write_memory', where, expression, size)
|
||||
self._publish('will_write_memory', where, expression, size)
|
||||
|
||||
self.memory[where:where+size/8] = [Operators.CHR(Operators.EXTRACT(expression, offset, 8)) for offset in xrange(0, size, 8)]
|
||||
|
||||
self.publish('did_write_memory', where, expression, size)
|
||||
self._publish('did_write_memory', where, expression, size)
|
||||
|
||||
|
||||
def read_int(self, where, size=None):
|
||||
@@ -517,13 +520,13 @@ class Cpu(Eventful):
|
||||
if size is None:
|
||||
size = self.address_bit_size
|
||||
assert size in SANE_SIZES
|
||||
self.publish('will_read_memory', where, size)
|
||||
self._publish('will_read_memory', where, size)
|
||||
|
||||
data = self.memory[where:where + size / 8]
|
||||
assert (8 * len(data)) == size
|
||||
value = Operators.CONCAT(size, *map(Operators.ORD, reversed(data)))
|
||||
|
||||
self.publish('did_read_memory', where, value, size)
|
||||
self._publish('did_read_memory', where, value, size)
|
||||
return value
|
||||
|
||||
|
||||
@@ -727,12 +730,12 @@ class Cpu(Eventful):
|
||||
if not self.memory.access_ok(self.PC, 'x'):
|
||||
raise InvalidMemoryAccess(self.PC, 'x')
|
||||
|
||||
self.publish('will_decode_instruction', self.PC)
|
||||
self._publish('will_decode_instruction', self.PC)
|
||||
|
||||
insn = self.decode_instruction(self.PC)
|
||||
self._last_pc = self.PC
|
||||
|
||||
self.publish('will_execute_instruction', insn)
|
||||
self._publish('will_execute_instruction', self.PC, insn)
|
||||
|
||||
# FIXME (theo) why just return here?
|
||||
if insn.address != self.PC:
|
||||
@@ -744,10 +747,7 @@ class Cpu(Eventful):
|
||||
text_bytes = ' '.join('%02x'%x for x in insn.bytes)
|
||||
logger.info("Unimplemented instruction: 0x%016x:\t%s\t%s\t%s",
|
||||
insn.address, text_bytes, insn.mnemonic, insn.op_str)
|
||||
|
||||
self.publish('will_emulate_instruction', insn)
|
||||
self.emulate(insn)
|
||||
self.publish('did_emulate_instruction', insn)
|
||||
|
||||
implementation = getattr(self, name, fallback_to_emulate)
|
||||
|
||||
@@ -759,7 +759,7 @@ class Cpu(Eventful):
|
||||
implementation(*insn.operands)
|
||||
self._icount += 1
|
||||
|
||||
self.publish('did_execute_instruction', insn)
|
||||
self._publish('did_execute_instruction', self._last_pc, self.PC, insn)
|
||||
|
||||
def emulate(self, insn):
|
||||
'''
|
||||
|
||||
@@ -390,12 +390,12 @@ class BinjaCpu(Cpu):
|
||||
if not self.memory.access_ok(self.PC, 'x'):
|
||||
raise InvalidMemoryAccess(self.PC, 'x')
|
||||
|
||||
self.publish('will_decode_instruction', self.PC)
|
||||
self._publish('will_decode_instruction', self.PC)
|
||||
|
||||
insn = self.decode_instruction(self.PC)
|
||||
self._last_pc = self.PC
|
||||
|
||||
self.publish('will_execute_instruction', insn)
|
||||
self._publish('will_execute_instruction', insn)
|
||||
|
||||
# FIXME (theo) why just return here?
|
||||
if insn.address != self.PC:
|
||||
@@ -415,9 +415,9 @@ class BinjaCpu(Cpu):
|
||||
logger.info("Unimplemented instruction: 0x%016x:\t%s\t%s\t%s",
|
||||
insn.address, text_bytes, insn.mnemonic, insn.op_str)
|
||||
|
||||
self.publish('will_emulate_instruction', insn)
|
||||
self._publish('will_emulate_instruction', insn)
|
||||
self.emulate(insn)
|
||||
self.publish('did_emulate_instruction', insn)
|
||||
self._publish('did_emulate_instruction', insn)
|
||||
|
||||
implementation = getattr(self, name, fallback_to_emulate)
|
||||
|
||||
@@ -453,7 +453,7 @@ class BinjaCpu(Cpu):
|
||||
self.PC = self._last_pc + insn.size
|
||||
|
||||
self._icount += 1
|
||||
self.publish('did_execute_instruction', insn)
|
||||
self._publish('did_execute_instruction', insn)
|
||||
|
||||
def update_platform_cpu_regs(self):
|
||||
for pl_reg, binja_reg in self.regfile.pl2b_map.items():
|
||||
|
||||
@@ -42,7 +42,7 @@ class Policy(object):
|
||||
def __init__(self, executor, *args, **kwargs):
|
||||
super(Policy, self).__init__(*args, **kwargs)
|
||||
self._executor = executor
|
||||
self._executor.subscribe('did_add_state', self._add_state_callback)
|
||||
self._executor.subscribe('did_enqueue_state', self._add_state_callback)
|
||||
|
||||
@contextmanager
|
||||
def locked_context(self, key=None, default=dict):
|
||||
@@ -163,6 +163,8 @@ class Executor(Eventful):
|
||||
conditions (system calls, memory faults, concretization, etc.)
|
||||
'''
|
||||
|
||||
_published_events = {'enqueue_state', 'generate_testcase', 'fork_state', 'load_state', 'terminate_state'}
|
||||
|
||||
def __init__(self, initial=None, workspace=None, policy='random', context=None, **kwargs):
|
||||
super(Executor, self).__init__(**kwargs)
|
||||
|
||||
@@ -246,7 +248,7 @@ class Executor(Eventful):
|
||||
#Forward all state signals
|
||||
self.forward_events_from(state, True)
|
||||
|
||||
def add(self, state):
|
||||
def enqueue(self, state):
|
||||
'''
|
||||
Enqueue state.
|
||||
Save state on storage, assigns an id to it, then add it to the
|
||||
@@ -255,7 +257,7 @@ class Executor(Eventful):
|
||||
#save the state to secondary storage
|
||||
state_id = self._workspace.save_state(state)
|
||||
self.put(state_id)
|
||||
self.publish('did_add_state', state_id, state)
|
||||
self._publish('did_enqueue_state', state_id, state)
|
||||
return state_id
|
||||
|
||||
def load_workspace(self):
|
||||
@@ -352,7 +354,7 @@ class Executor(Eventful):
|
||||
|
||||
#broadcast test generation. This is the time for other modules
|
||||
#to output whatever helps to understand this testcase
|
||||
self.publish('will_generate_testcase', state, 'test', message)
|
||||
self._publish('will_generate_testcase', state, 'test', message)
|
||||
|
||||
def fork(self, state, expression, policy='ALL', setstate=None):
|
||||
'''
|
||||
@@ -385,7 +387,7 @@ class Executor(Eventful):
|
||||
policy,
|
||||
', '.join('0x{:x}'.format(sol) for sol in solutions))
|
||||
|
||||
self.publish('will_fork_state', state, expression, solutions, policy)
|
||||
self._publish('will_fork_state', state, expression, solutions, policy)
|
||||
|
||||
#Build and enqueue a state for each solution
|
||||
children = []
|
||||
@@ -397,10 +399,10 @@ class Executor(Eventful):
|
||||
#(or other register or memory address to concrete)
|
||||
setstate(new_state, new_value)
|
||||
|
||||
self.publish('forking_state', new_state, expression, new_value, policy)
|
||||
self._publish('did_fork_state', new_state, expression, new_value, policy)
|
||||
|
||||
#enqueue new_state
|
||||
state_id = self.add(new_state)
|
||||
state_id = self.enqueue(new_state)
|
||||
#maintain a list of childres for logging purpose
|
||||
children.append(state_id)
|
||||
|
||||
@@ -436,8 +438,8 @@ class Executor(Eventful):
|
||||
if current_state_id is not None:
|
||||
current_state = self._workspace.load_state(current_state_id)
|
||||
self.forward_events_from(current_state, True)
|
||||
self._publish('did_load_state', current_state, current_state_id)
|
||||
logger.info("load state %r", current_state_id)
|
||||
self.publish('will_load_state', current_state, current_state_id)
|
||||
#notify siblings we have a state to play with
|
||||
self._notify_start_run()
|
||||
|
||||
@@ -456,7 +458,7 @@ class Executor(Eventful):
|
||||
break
|
||||
else:
|
||||
#Notify this worker is done
|
||||
self.publish('will_terminate_state', current_state, current_state_id, 'Shutdown')
|
||||
self._publish('will_terminate_state', current_state, current_state_id, 'Shutdown')
|
||||
current_state = None
|
||||
|
||||
|
||||
@@ -472,7 +474,7 @@ class Executor(Eventful):
|
||||
|
||||
except TerminateState as e:
|
||||
#Notify this worker is done
|
||||
self.publish('will_terminate_state', current_state, current_state_id, e)
|
||||
self._publish('will_terminate_state', current_state, current_state_id, e)
|
||||
|
||||
logger.debug("Generic terminate state")
|
||||
if e.testcase:
|
||||
@@ -485,7 +487,7 @@ class Executor(Eventful):
|
||||
logger.error("Exception: %s\n%s", str(e), trace)
|
||||
|
||||
#Notify this state is done
|
||||
self.publish('will_terminate_state', current_state, current_state_id, e)
|
||||
self._publish('will_terminate_state', current_state, current_state_id, e)
|
||||
|
||||
if solver.check(current_state.constraints):
|
||||
self.generate_testcase(current_state, "Solver failed" + str(e))
|
||||
@@ -497,7 +499,7 @@ class Executor(Eventful):
|
||||
print str(e), trace
|
||||
logger.error("Exception: %s\n%s", str(e), trace)
|
||||
#Notify this worker is done
|
||||
self.publish('will_terminate_state', current_state, current_state_id, 'Exception')
|
||||
self._publish('will_terminate_state', current_state, current_state_id, 'Exception')
|
||||
current_state = None
|
||||
logger.setState(None)
|
||||
|
||||
@@ -505,6 +507,3 @@ class Executor(Eventful):
|
||||
|
||||
#notify siblings we are about to stop this run
|
||||
self._notify_stop_run()
|
||||
|
||||
#Notify this worker is done (not sure it's needed)
|
||||
self.publish('will_finish_run')
|
||||
|
||||
152
manticore/core/plugin.py
Normal file
152
manticore/core/plugin.py
Normal file
@@ -0,0 +1,152 @@
|
||||
import logging
|
||||
from ..utils.helpers import issymbolic
|
||||
from ..utils.event import Eventful
|
||||
logger = logging.getLogger('MANTICORE')
|
||||
|
||||
|
||||
class Plugin(object):
|
||||
def __init__(self):
|
||||
self.manticore = None
|
||||
|
||||
class Tracer(Plugin):
|
||||
def will_start_run_callback(self, state):
|
||||
state.context['trace'] = []
|
||||
|
||||
def did_execute_instruction_callback(self, state, pc, target_pc, instruction):
|
||||
state.context['trace'].append(pc)
|
||||
|
||||
|
||||
class RecordSymbolicBranches(Plugin):
|
||||
def will_start_run_callback(self, state):
|
||||
state.context['branches'] = {}
|
||||
|
||||
def did_execute_instruction_callback(self, state, last_pc, target_pc, instruction):
|
||||
if state.context.get('forking_pc', False):
|
||||
branches = state.context['branches']
|
||||
branch = (last_pc, target_pc)
|
||||
if branch in branches:
|
||||
branches[branch] += 1
|
||||
else:
|
||||
branches[branch] = 1
|
||||
state.context['forking_pc'] = False
|
||||
|
||||
if issymbolic(target_pc):
|
||||
state.context['forking_pc'] = True
|
||||
|
||||
class InstructionCounter(Plugin):
|
||||
|
||||
def will_terminate_state_callback(self, state, state_id, ex):
|
||||
if state is None: #FIXME Can it be None?
|
||||
return
|
||||
state_instructions_count = state.context.get('instructions_count', 0)
|
||||
|
||||
with self.manticore.locked_context() as manticore_context:
|
||||
manticore_instructions_count = manticore_context.get('instructions_count', 0)
|
||||
manticore_context['instructions_count'] = manticore_instructions_count + state_instructions_count
|
||||
|
||||
def did_execute_instruction_callback(self, state, prev_pc, target_pc, instruction):
|
||||
address = prev_pc
|
||||
if not issymbolic(address):
|
||||
count = state.context.get('instructions_count', 0)
|
||||
state.context['instructions_count'] = count + 1
|
||||
|
||||
def did_finish_run_callback(self):
|
||||
_shared_context = self.manticore.context
|
||||
instructions_count = _shared_context.get('instructions_count', 0)
|
||||
logger.info('Instructions executed: %d', instructions_count)
|
||||
|
||||
|
||||
class Visited(Plugin):
|
||||
def __init__(self, coverage_file='visited.txt'):
|
||||
super(Visited, self).__init__()
|
||||
self.coverage_file = coverage_file
|
||||
|
||||
def will_terminate_state_callback(self, state, state_id, ex):
|
||||
if state is None:
|
||||
return
|
||||
state_visited = state.context.get('visited_since_last_fork', set())
|
||||
with self.manticore.locked_context() as manticore_context:
|
||||
manticore_visited = manticore_context.get('visited', set())
|
||||
manticore_context['visited'] = manticore_visited.union(state_visited)
|
||||
|
||||
def will_fork_state_callback(self, state, expression, values, policy):
|
||||
state_visited = state.context.get('visited_since_last_fork', set())
|
||||
with self.manticore.locked_context() as manticore_context:
|
||||
manticore_visited = manticore_context.get('visited', set())
|
||||
manticore_context['visited'] = manticore_visited.union(state_visited)
|
||||
state.context['visited_since_last_fork'] = set()
|
||||
|
||||
def did_execute_instruction_callback(self, state, prev_pc, target_pc, instruction):
|
||||
state.context.setdefault('visited_since_last_fork', set()).add(prev_pc)
|
||||
state.context.setdefault('visited', set()).add(prev_pc)
|
||||
|
||||
|
||||
def did_finish_run_callback(self):
|
||||
_shared_context = self.manticore.context
|
||||
executor_visited = _shared_context.get('visited', set())
|
||||
#Fixme this is duplicated?
|
||||
if self.coverage_file is not None:
|
||||
with self.manticore._output.save_stream(self.coverage_file) as f:
|
||||
fmt = "0x{:016x}\n"
|
||||
for m in executor_visited:
|
||||
f.write(fmt.format(m))
|
||||
logger.info('Coverage: %d different instructions executed', len(executor_visited))
|
||||
|
||||
|
||||
|
||||
#TODO document all callbacks
|
||||
class ExamplePlugin(Plugin):
|
||||
def will_decode_instruction_callback(self, state, pc):
|
||||
logger.info('will_decode_instruction', state, pc)
|
||||
def will_execute_instruction_callback(self, state, pc, instruction):
|
||||
logger.info('will_execute_instruction', state, pc, instruction)
|
||||
|
||||
def did_execute_instruction_callback(self, state, pc, target_pc, instruction):
|
||||
logger.info('did_execute_instruction', state, pc, target_pc, instruction)
|
||||
def will_start_run_callback(self, state):
|
||||
''' Called once at the begining of the run.
|
||||
state is the initial root state
|
||||
'''
|
||||
logger.info('will_start_run')
|
||||
|
||||
def did_finish_run_callback(self):
|
||||
logger.info('did_finish_run')
|
||||
|
||||
def did_enqueue_state_callback(self, state_id, state):
|
||||
''' state was just got enqueued in the executor procesing list'''
|
||||
logger.info('did_enqueue_state %r %r', state_id, state)
|
||||
|
||||
def will_fork_state_callback(self, parent_state, expression, solutions, policy):
|
||||
logger.info('will_fork_state %r %r %r %r',parent_state, expression, solutions, policy)
|
||||
def did_fork_state_callback(self, child_state, expression, new_value, policy):
|
||||
logger.info('did_fork_state %r %r %r %r', child_state, expression, new_value, policy)
|
||||
def did_load_state_callback(self, state, state_id):
|
||||
logger.info('did_load_state %r %r', state, state_id)
|
||||
def did_enqueue_state_callback(self, state, state_id):
|
||||
logger.info('did_enqueue_state %r %r', state, state_id)
|
||||
def will_terminate_state_callback(self, state, state_id, exception):
|
||||
logger.info('will_terminate_state %r %r %r', state, state_id, exception)
|
||||
def will_generate_testcase_callback(self, state, testcase_id, message):
|
||||
logger.info('will_generate_testcase %r %r %r', state, testcase_id, message)
|
||||
|
||||
def will_read_memory_callback(self, state, where, size):
|
||||
logger.info('will_read_memory %r %r %r', state, where, size)
|
||||
def did_read_memory_callback(self, state, where, value, size):
|
||||
logger.info('did_read_memory %r %r %r %r', state, where, value, size)
|
||||
|
||||
def will_write_memory_callback(self, state, where, value, size):
|
||||
logger.info('will_write_memory %r %r %r', state, where, value, size)
|
||||
def did_write_memory_callback(self, state, where, value, size):
|
||||
logger.info('did_write_memory %r %r %r %r', state, where, value, size)
|
||||
|
||||
def will_read_register_callback(self, state, register):
|
||||
logger.info('will_read_register %r %r', state, register)
|
||||
def did_read_register_callback(self, state, register, value):
|
||||
logger.info('did_read_register %r %r %r', state, register, value)
|
||||
|
||||
def will_write_register_callback(self, state, register, value):
|
||||
logger.info('will_write_register %r %r %r', state, register, value)
|
||||
def did_write_register_callback(self, state, register, value):
|
||||
logger.info('did_write_register %r %r %r', state, register, value)
|
||||
|
||||
|
||||
@@ -68,6 +68,8 @@ class State(Eventful):
|
||||
:ivar dict context: Local context for arbitrary data storage
|
||||
'''
|
||||
|
||||
_published_events = {'generate_testcase'}
|
||||
|
||||
def __init__(self, constraints, platform, **kwargs):
|
||||
super(State, self).__init__(**kwargs)
|
||||
self._platform = platform
|
||||
@@ -374,4 +376,4 @@ class State(Eventful):
|
||||
:param str name: Short string identifying this testcase used to prefix workspace entries.
|
||||
:param str message: Longer description
|
||||
"""
|
||||
self.publish('will_generate_testcase', name, message)
|
||||
self._publish('will_generate_testcase', name, message)
|
||||
|
||||
@@ -6,12 +6,13 @@ import binascii
|
||||
import functools
|
||||
import cProfile
|
||||
import pstats
|
||||
|
||||
import itertools
|
||||
from multiprocessing import Process
|
||||
from contextlib import contextmanager
|
||||
|
||||
from threading import Timer
|
||||
|
||||
#FIXME: remove this three
|
||||
import elftools
|
||||
from elftools.elf.elffile import ELFFile
|
||||
from elftools.elf.sections import SymbolTableSection
|
||||
@@ -21,9 +22,12 @@ from .core.parser import parse
|
||||
from .core.state import State, TerminateState
|
||||
from .core.smtlib import solver, ConstraintSet
|
||||
from .core.workspace import ManticoreOutput, Workspace
|
||||
from .core.cpu.abstractcpu import Cpu
|
||||
from .platforms import linux, decree, windows
|
||||
from .utils.helpers import issymbolic, is_binja_disassembler
|
||||
from .utils.nointerrupt import WithKeyboardInterruptAs
|
||||
from .utils.event import Eventful
|
||||
from .core.plugin import Plugin, InstructionCounter, RecordSymbolicBranches, Visited, Tracer
|
||||
import logging
|
||||
from .utils import log
|
||||
|
||||
@@ -160,7 +164,7 @@ def make_initial_state(binary_path, **kwargs):
|
||||
raise NotImplementedError("Binary {} not supported.".format(binary_path))
|
||||
return state
|
||||
|
||||
class Manticore(object):
|
||||
class Manticore(Eventful):
|
||||
'''
|
||||
The central analysis object.
|
||||
|
||||
@@ -171,7 +175,10 @@ class Manticore(object):
|
||||
:ivar dict context: Global context for arbitrary data storage
|
||||
'''
|
||||
|
||||
_published_events = {'start_run', 'finish_run'}
|
||||
|
||||
def __init__(self, path_or_state, argv=None, workspace_url=None, policy='random', **kwargs):
|
||||
super(Manticore, self).__init__()
|
||||
|
||||
if isinstance(workspace_url, str):
|
||||
if ':' not in workspace_url:
|
||||
@@ -182,28 +189,14 @@ class Manticore(object):
|
||||
ws_path = None
|
||||
self._output = ManticoreOutput(ws_path)
|
||||
self._context = {}
|
||||
self._coverage_file = None
|
||||
|
||||
#sugar for 'will_execute_instruction"
|
||||
self._hooks = {}
|
||||
|
||||
self._symbolic_files = []
|
||||
|
||||
self._executor = Executor(workspace=ws_path, policy=policy)
|
||||
self._workers = []
|
||||
#Link Executor events to default callbacks in manticore object
|
||||
self._executor.subscribe('did_read_register', self._read_register_callback)
|
||||
self._executor.subscribe('will_write_register', self._write_register_callback)
|
||||
self._executor.subscribe('did_read_memory', self._read_memory_callback)
|
||||
self._executor.subscribe('will_write_memory', self._write_memory_callback)
|
||||
self._executor.subscribe('will_execute_instruction', self._execute_instruction_callback)
|
||||
self._executor.subscribe('will_decode_instruction', self._decode_instruction_callback)
|
||||
self._executor.subscribe('will_fork_state', self._fork_state_callback)
|
||||
self._executor.subscribe('forking_state', self._forking_state_callback)
|
||||
self._executor.subscribe('will_terminate_state', self._terminate_state_callback)
|
||||
self._executor.subscribe('will_generate_testcase', self._generate_testcase_callback)
|
||||
self._executor.subscribe('did_finish_run', self._finish_run_callback)
|
||||
|
||||
#Link Executor events to default callbacks in manticore object
|
||||
self.forward_events_from(self._executor)
|
||||
|
||||
if isinstance(path_or_state, str):
|
||||
assert os.path.isfile(path_or_state)
|
||||
@@ -213,8 +206,57 @@ class Manticore(object):
|
||||
else:
|
||||
raise TypeError("Manticore must be intialized with either a State or a path to a binary")
|
||||
|
||||
#Move the folowwing into a plugin
|
||||
|
||||
self.plugins = set()
|
||||
|
||||
#Move the folowing into a plugin
|
||||
self._assertions = {}
|
||||
self._coverage_file = None
|
||||
|
||||
#FIXME move the folowing to aplugin
|
||||
self.subscribe('will_generate_testcase', self._generate_testcase_callback)
|
||||
self.subscribe('did_finish_run', self._did_finish_run_callback)
|
||||
|
||||
#Default plugins for now.. FIXME?
|
||||
self.register_plugin(InstructionCounter())
|
||||
self.register_plugin(Visited())
|
||||
self.register_plugin(Tracer())
|
||||
self.register_plugin(RecordSymbolicBranches())
|
||||
|
||||
|
||||
def register_plugin(self, plugin):
|
||||
#Global enumeration of valid events
|
||||
assert isinstance(plugin, Plugin)
|
||||
assert plugin not in self.plugins, "Plugin instance already registered"
|
||||
assert plugin.manticore is None, "Plugin instance already owned"
|
||||
|
||||
plugin.manticore = self
|
||||
self.plugins.add(plugin)
|
||||
|
||||
events = Eventful.all_events()
|
||||
prefix = Eventful.prefixes
|
||||
all_events = [x+y for x, y in itertools.product(prefix, events)]
|
||||
for event_name in all_events:
|
||||
callback_name = '{}_callback'.format(event_name)
|
||||
callback = getattr(plugin, callback_name, None)
|
||||
if callback is not None:
|
||||
self.subscribe(event_name, callback)
|
||||
|
||||
if logger.isEnabledFor(logging.DEBUG):
|
||||
for callback_name in dir(plugin):
|
||||
if callback_name.endswith('_callback'):
|
||||
event_name = callback_name[:-9]
|
||||
if event_name not in all_events:
|
||||
logger.warning("There is no event name %s for callback on plugin type %s", event_name, type(plugin) )
|
||||
|
||||
|
||||
|
||||
def unregister_plugin(self, plugin):
|
||||
assert plugin in self.plugins, "Plugin instance not registered"
|
||||
self.plugins.remove(plugin)
|
||||
plugin.manticore = None
|
||||
|
||||
|
||||
|
||||
@classmethod
|
||||
def linux(cls, path, argv=None, envp=None, symbolic_files=None, concrete_start='', **kwargs):
|
||||
@@ -336,6 +378,49 @@ class Manticore(object):
|
||||
self._workers.append(p)
|
||||
p.start()
|
||||
|
||||
@property
|
||||
def running(self):
|
||||
return self._executor._running.value
|
||||
|
||||
def enqueue(self, state):
|
||||
''' Dynamically enqueue states. Users should typically not need to do this '''
|
||||
assert not self.running, "Can't add state where running. Can we?"
|
||||
self._executor.enqueue(state)
|
||||
|
||||
###########################################################################
|
||||
# Workers #
|
||||
###########################################################################
|
||||
def _start_workers(self, num_processes, profiling=False):
|
||||
assert num_processes > 0, "Must have more than 0 worker processes"
|
||||
|
||||
logger.info("Starting %d processes.", num_processes)
|
||||
|
||||
if profiling:
|
||||
def profile_this(func):
|
||||
@functools.wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
profile = cProfile.Profile()
|
||||
profile.enable()
|
||||
result = func(*args, **kwargs)
|
||||
profile.disable()
|
||||
profile.create_stats()
|
||||
with self.locked_context('profiling_stats', list) as profiling_stats:
|
||||
profiling_stats.append(profile.stats.items())
|
||||
return result
|
||||
return wrapper
|
||||
|
||||
target = profile_this(self._executor.run)
|
||||
else:
|
||||
target = self._executor.run
|
||||
|
||||
if num_processes == 1:
|
||||
target()
|
||||
else:
|
||||
for _ in range(num_processes):
|
||||
p = Process(target=target, args=())
|
||||
self._workers.append(p)
|
||||
p.start()
|
||||
|
||||
def _join_workers(self):
|
||||
with WithKeyboardInterruptAs(self._executor.shutdown):
|
||||
while len(self._workers) > 0:
|
||||
@@ -352,7 +437,7 @@ class Manticore(object):
|
||||
'''
|
||||
def callback(manticore_obj, state):
|
||||
f(state)
|
||||
self._executor.subscribe('will_start_run', types.MethodType(callback, self))
|
||||
self.subscribe('will_start_run', types.MethodType(callback, self))
|
||||
return f
|
||||
|
||||
def hook(self, pc):
|
||||
@@ -385,8 +470,7 @@ class Manticore(object):
|
||||
if self._hooks:
|
||||
self._executor.subscribe('will_execute_instruction', self._hook_callback)
|
||||
|
||||
def _hook_callback(self, state, instruction):
|
||||
pc = state.cpu.PC
|
||||
def _hook_callback(self, state, pc, instruction):
|
||||
'Invoke all registered generic hooks'
|
||||
|
||||
# Ignore symbolic pc.
|
||||
@@ -451,10 +535,9 @@ class Manticore(object):
|
||||
if pc in self._assertions:
|
||||
logger.debug("Repeated PC in assertions file %s", path)
|
||||
self._assertions[pc] = ' '.join(line.split(' ')[1:])
|
||||
self._executor.subscribe('will_execute_instruction', self._assertions_callback)
|
||||
self.subscribe('will_execute_instruction', self._assertions_callback)
|
||||
|
||||
def _assertions_callback(self, state, instruction):
|
||||
pc = state.cpu.PC
|
||||
def _assertions_callback(self, state, pc, instruction):
|
||||
if pc not in self._assertions:
|
||||
return
|
||||
|
||||
@@ -480,61 +563,6 @@ class Manticore(object):
|
||||
#Some are Place holders Remove
|
||||
#Any platform specific callback should go to a plugin
|
||||
|
||||
def _terminate_state_callback(self, state, state_id, ex):
|
||||
#aggregates state statistics into exceutor statistics. FIXME split
|
||||
logger.debug("Terminate state %r %r ", state, state_id)
|
||||
if state is None:
|
||||
return
|
||||
state_visited = state.context.get('visited_since_last_fork', set())
|
||||
state_instructions_count = state.context.get('instructions_count', 0)
|
||||
with self.locked_context() as manticore_context:
|
||||
manticore_visited = manticore_context.get('visited', set())
|
||||
manticore_context['visited'] = manticore_visited.union(state_visited)
|
||||
|
||||
manticore_instructions_count = manticore_context.get('instructions_count', 0)
|
||||
manticore_context['instructions_count'] = manticore_instructions_count + state_instructions_count
|
||||
|
||||
def _forking_state_callback(self, state, expression, value, policy):
|
||||
state.record_branch(value)
|
||||
|
||||
def _fork_state_callback(self, state, expression, values, policy):
|
||||
state_visited = state.context.get('visited_since_last_fork', set())
|
||||
with self.locked_context() as manticore_context:
|
||||
manticore_visited = manticore_context.get('visited', set())
|
||||
manticore_context['visited'] = manticore_visited.union(state_visited)
|
||||
state.context['visited_since_last_fork'] = set()
|
||||
|
||||
def _read_register_callback(self, state, reg_name, value):
|
||||
logger.debug("Read Register %r %r", reg_name, value)
|
||||
|
||||
def _write_register_callback(self, state, reg_name, value):
|
||||
logger.debug("Write Register %r %r", reg_name, value)
|
||||
|
||||
def _read_memory_callback(self, state, address, value, size):
|
||||
logger.debug("Read Memory %r %r %r", address, value, size)
|
||||
|
||||
def _write_memory_callback(self, state, address, value, size):
|
||||
logger.debug("Write Memory %r %r %r", address, value, size)
|
||||
|
||||
def _decode_instruction_callback(self, state, pc):
|
||||
logger.debug("Decoding stuff instruction not available")
|
||||
|
||||
def _emulate_instruction_callback(self, state, instruction):
|
||||
logger.debug("About to emulate instruction")
|
||||
|
||||
def _did_execute_instruction_callback(self, state, instruction):
|
||||
logger.debug("Did execute an instruction")
|
||||
|
||||
def _execute_instruction_callback(self, state, instruction):
|
||||
address = state.cpu.PC
|
||||
if not issymbolic(address):
|
||||
state.context.setdefault('trace', list()).append(address)
|
||||
state.context.setdefault('visited_since_last_fork', set()).add(address)
|
||||
state.context.setdefault('visited', set()).add(address)
|
||||
count = state.context.get('instructions_count', 0)
|
||||
state.context['instructions_count'] = count + 1
|
||||
|
||||
|
||||
def _generate_testcase_callback(self, state, name, message):
|
||||
'''
|
||||
Create a serialized description of a given state.
|
||||
@@ -575,7 +603,7 @@ class Manticore(object):
|
||||
def _start_run(self):
|
||||
assert not self.running
|
||||
#FIXME this will be self.publish
|
||||
self._executor.publish('will_start_run', self._initial_state)
|
||||
self._publish('will_start_run', self._initial_state)
|
||||
self.enqueue(self._initial_state)
|
||||
self._initial_state = None
|
||||
|
||||
@@ -589,8 +617,8 @@ class Manticore(object):
|
||||
|
||||
if profiling:
|
||||
self._produce_profiling_data()
|
||||
#FIXME this will be self.publish
|
||||
self._executor.publish('did_finish_run')
|
||||
|
||||
self._publish('did_finish_run')
|
||||
|
||||
def run(self, procs=1, timeout=0, should_profile=False):
|
||||
'''
|
||||
@@ -628,8 +656,6 @@ class Manticore(object):
|
||||
#############################################################################
|
||||
#############################################################################
|
||||
# Move all the following elsewhere Not all manticores have this
|
||||
|
||||
|
||||
def _get_symbol_address(self, symbol):
|
||||
'''
|
||||
Return the address of |symbol| within the binary
|
||||
@@ -664,30 +690,14 @@ class Manticore(object):
|
||||
assert not self.running, "Can't set coverage file if Manticore is running."
|
||||
self._coverage_file = path
|
||||
|
||||
def _finish_run_callback(self):
|
||||
def _did_finish_run_callback(self):
|
||||
_shared_context = self.context
|
||||
executor_visited = _shared_context.get('visited', set())
|
||||
#Fixme this is duplicated?
|
||||
if self.coverage_file is not None:
|
||||
with self._output.save_stream(self.coverage_file) as f:
|
||||
fmt = "0x{:016x}\n"
|
||||
for m in executor_visited:
|
||||
f.write(fmt.format(m[1]))
|
||||
|
||||
with self._output.save_stream('visited.txt') as f:
|
||||
for entry in sorted(executor_visited):
|
||||
f.write('0:{:08x}\n'.format(entry))
|
||||
|
||||
with self._output.save_stream('command.sh') as f:
|
||||
f.write(' '.join(sys.argv))
|
||||
|
||||
instructions_count = _shared_context.get('instructions_count', 0)
|
||||
elapsed = time.time() - self._time_started
|
||||
logger.info('Results in %s', self._output.uri)
|
||||
logger.info('Instructions executed: %d', instructions_count)
|
||||
logger.info('Coverage: %d different instructions executed', len(executor_visited))
|
||||
#logger.info('Number of paths covered %r', State.state_count())
|
||||
logger.info('Total time: %s', elapsed)
|
||||
logger.info('IPS: %d', instructions_count/elapsed)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,37 @@
|
||||
import inspect
|
||||
import logging
|
||||
from weakref import ref, WeakSet, WeakKeyDictionary, WeakValueDictionary
|
||||
from types import MethodType
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class EventsGatherMetaclass(type):
|
||||
'''
|
||||
Metaclass that is used for Eventful to gather events that classes declare to
|
||||
publish.
|
||||
'''
|
||||
def __new__(cls, name, parents, d):
|
||||
eventful_sub = super(EventsGatherMetaclass, cls).__new__(cls, name, parents, d)
|
||||
|
||||
bases = inspect.getmro(parents[0])
|
||||
if len(bases) < 2:
|
||||
return eventful_sub
|
||||
|
||||
# bases[-1] is always 'object', bases[-2] is next super class, which
|
||||
# will always be Eventful.
|
||||
eventful_cls = bases[-2]
|
||||
subclasses = bases[:-2]
|
||||
relevant_classes = [eventful_sub] + list(subclasses)
|
||||
# Add a class that defines '_published_events' classmethod to a dict for
|
||||
# later lookup. Aggregate the events of all subclasses.
|
||||
relevant_events = set()
|
||||
for sub in relevant_classes:
|
||||
# Not using hasattr() here because we only want classes where it's explicitly
|
||||
# defined.
|
||||
if '_published_events' in sub.__dict__:
|
||||
relevant_events.update(sub._published_events)
|
||||
eventful_cls.__all_events__[eventful_sub] = relevant_events
|
||||
|
||||
return eventful_sub
|
||||
|
||||
class Eventful(object):
|
||||
'''
|
||||
@@ -10,6 +41,27 @@ class Eventful(object):
|
||||
- let foreign objects subscribe their methods to events emitted here
|
||||
- forward events to/from other eventful objects
|
||||
'''
|
||||
__metaclass__ = EventsGatherMetaclass
|
||||
|
||||
# Maps an Eventful subclass with a set of all the events it publishes.
|
||||
__all_events__ = dict()
|
||||
|
||||
# Set in subclass to advertise the events it plans to publish
|
||||
_published_events = set()
|
||||
|
||||
#Event names prefixes
|
||||
prefixes = ('will_', 'did_', 'on_')
|
||||
|
||||
@classmethod
|
||||
def all_events(cls):
|
||||
'''
|
||||
Return all events that all subclasses have so far registered to publish.
|
||||
'''
|
||||
all_evts = set()
|
||||
for cls, evts in cls.__all_events__.items():
|
||||
all_evts.update(evts)
|
||||
return all_evts
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
# A dictionary from "event name" -> callback methods
|
||||
# Note that several methods can be associated with the same object
|
||||
@@ -46,8 +98,27 @@ class Eventful(object):
|
||||
#A bucket is a dictionary obj -> set(method1, method2...)
|
||||
return self._signals.setdefault(name, dict())
|
||||
|
||||
def _check_event(self, _name):
|
||||
basename = _name
|
||||
for prefix in self.prefixes:
|
||||
if _name.startswith(prefix):
|
||||
basename = _name[len(prefix):]
|
||||
|
||||
cls = self.__class__
|
||||
if basename not in cls.__all_events__[cls]:
|
||||
logger.warning("Event '%s' not pre-declared. (self: %s)", _name, repr(self))
|
||||
|
||||
|
||||
# Wrapper for _publish_impl that also makes sure the event is published from
|
||||
# a class that supports it.
|
||||
# The underscore _name is to avoid naming collisions with callback params
|
||||
def publish(self, _name, *args, **kwargs):
|
||||
def _publish(self, _name, *args, **kwargs):
|
||||
self._check_event(_name)
|
||||
self._publish_impl(_name, *args, **kwargs)
|
||||
|
||||
# Separate from _publish since the recursive method call to forward an event
|
||||
# shouldn't check the event.
|
||||
def _publish_impl(self, _name, *args, **kwargs):
|
||||
bucket = self._get_signal_bucket(_name)
|
||||
for robj, methods in bucket.iteritems():
|
||||
for callback in methods:
|
||||
@@ -57,9 +128,9 @@ class Eventful(object):
|
||||
# the callback signature. This is set on forward_events_from/to
|
||||
for sink, include_source in self._forwards.items():
|
||||
if include_source:
|
||||
sink.publish(_name, self, *args, **kwargs)
|
||||
sink._publish_impl(_name, self, *args, **kwargs)
|
||||
else:
|
||||
sink.publish(_name, *args, **kwargs)
|
||||
sink._publish_impl(_name, *args, **kwargs)
|
||||
|
||||
def subscribe(self, name, method):
|
||||
if not inspect.ismethod(method):
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,17 +4,19 @@ import unittest
|
||||
from manticore.utils.event import Eventful
|
||||
|
||||
class A(Eventful):
|
||||
_published_events = set(['eventA'])
|
||||
def do_stuff(self):
|
||||
self.publish("eventA",1, 'a')
|
||||
self._publish("eventA",1, 'a')
|
||||
|
||||
class B(Eventful):
|
||||
_published_events = set(['eventB'])
|
||||
def __init__(self, child, **kwargs):
|
||||
super(B, self).__init__(**kwargs)
|
||||
self.child = child
|
||||
self.forward_events_from(child)
|
||||
|
||||
def do_stuff(self):
|
||||
self.publish("eventB", 2, 'b')
|
||||
self._publish("eventB", 2, 'b')
|
||||
|
||||
|
||||
class C():
|
||||
|
||||
Reference in New Issue
Block a user