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:
feliam
2017-10-04 15:51:37 -03:00
committed by GitHub
parent 1edee15e5f
commit e84c0513a2
10 changed files with 4040 additions and 3815 deletions

View File

@@ -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__':

View File

@@ -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):
'''

View File

@@ -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():

View File

@@ -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
View 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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -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():