Pub-sub like events (#371)

* WIP New Policy class

* WIP pubsub

* Update Signal tests

* small fixes from github comments

* Fix event decode_instruction signature

* Good merge

* Good good merge

* Eventful class commented

* The million typos

* Code revision

* Fix tests for new mor strict Eventful
This commit is contained in:
feliam
2017-07-27 19:41:08 -03:00
committed by GitHub
parent a3cf6e7d2c
commit 76357216da
14 changed files with 312 additions and 390 deletions
+17 -35
View File
@@ -8,7 +8,7 @@ from ...utils.emulate import UnicornEmulator
import sys
from functools import wraps
from itertools import islice, imap
from ...utils.event import Signal
from ...utils.event import Eventful
import inspect
import types
import logging
@@ -345,7 +345,7 @@ class SyscallAbi(Abi):
############################################################################
# Abstract cpu encapsulating common cpu methods used by platforms and executor.
class Cpu(object):
class Cpu(Eventful):
'''
Base class for all Cpu architectures. Functionality common to all
architectures (and expected from users of a Cpu) should be here. Commonly
@@ -361,9 +361,9 @@ class Cpu(object):
- stack_alias
'''
def __init__(self, regfile, memory):
def __init__(self, regfile, memory, **kwargs):
assert isinstance(regfile, RegisterFile)
super(Cpu, self).__init__()
super(Cpu, self).__init__(**kwargs)
self._regfile = regfile
self._memory = memory
self._instruction_cache = {}
@@ -374,24 +374,6 @@ class Cpu(object):
self._md.detail = True
self._md.syntax = 0
#####################################################
# Signals
# signal handlers must have this signature:
# handler(cpu, *args, **kwargs)
self.will_decode_instruction = Signal()
self.will_execute_instruction = Signal()
self.did_execute_instruction = Signal()
self.will_emulate_instruction = Signal()
self.did_emulate_instruction = Signal()
self.will_read_register = Signal()
self.did_read_register = Signal()
self.will_write_register = Signal()
self.did_write_register = Signal()
self.will_read_memory = Signal()
self.will_write_memory = Signal()
self.did_read_memory = Signal()
self.did_write_memory = Signal()
# Ensure that regfile created STACK/PC aliases
assert 'STACK' in self._regfile
assert 'PC' in self._regfile
@@ -452,9 +434,9 @@ class Cpu(object):
:param value: register value
:type value: int or long or Expression
'''
self.will_write_register(register, value)
self.publish('will_write_register', register, value)
value = self._regfile.write(register, value)
self.did_write_register(register, value)
self.publish('did_write_register', register, value)
return value
def read_register(self, register):
@@ -465,9 +447,9 @@ class Cpu(object):
:return: register value
:rtype: int or long or Expression
'''
self.will_read_register(register)
self.publish('will_read_register', register)
value = self._regfile.read(register)
self.did_read_register(register, value)
self.publish('did_read_register', register, value)
return value
# Pythonic access to registers and aliases
@@ -513,11 +495,11 @@ class Cpu(object):
if size is None:
size = self.address_bit_size
assert size in SANE_SIZES
self.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.did_write_memory(where, expression, size)
self.publish('did_write_memory', where, expression, size)
def read_int(self, where, size=None):
@@ -532,13 +514,13 @@ class Cpu(object):
if size is None:
size = self.address_bit_size
assert size in SANE_SIZES
self.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.did_read_memory(where, value, size)
self.publish('did_read_memory', where, value, size)
return value
@@ -727,12 +709,12 @@ class Cpu(object):
if not self.memory.access_ok(self.PC,'x'):
raise InvalidMemoryAccess(self.PC, 'x')
self.will_decode_instruction()
self.publish('will_decode_instruction', self.PC)
instruction = self.decode_instruction(self.PC)
self._last_pc=self.PC
self.will_execute_instruction(instruction)
self.publish('will_execute_instruction', instruction)
if instruction.address != self.PC:
return
@@ -745,11 +727,11 @@ class Cpu(object):
instruction.address, text_bytes, instruction.mnemonic,
instruction.op_str)
self.will_emulate_instruction(instruction)
self.publish('will_emulate_instruction', instruction)
self.emulate(instruction)
self.did_emulate_instruction(instruction)
self.publish('did_emulate_instruction', instruction)
implementation = getattr(self, name, fallback_to_emulate)
@@ -761,7 +743,7 @@ class Cpu(object):
implementation(*instruction.operands)
self._icount+=1
self.did_execute_instruction(instruction)
self.publish('did_execute_instruction', instruction)
def emulate(self, instruction):
+91 -56
View File
@@ -10,7 +10,7 @@ except:
import StringIO
from ..utils.nointerrupt import WithKeyboardInterruptAs
from ..utils.event import Signal, forward_signals
from ..utils.event import Eventful
from .smtlib import solver, Expression, SolverException
from .state import Concretize, TerminateState
from workspace import Workspace
@@ -39,51 +39,88 @@ def sync(f):
class Policy(object):
''' Base class for prioritization of state search '''
def __init__(self):
pass
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)
@contextmanager
def locked_context(self):
''' Policy shared context dictionary '''
with self._executor.locked_context() as ctx:
policy_context = ctx.get('policy', None)
if policy_context is None:
policy_context = dict()
yield policy_context
ctx['policy'] = policy_context
def features(self, state):
''' Save state features for prioritization before a state is stored '''
pass
def _add_state_callback(self, state_id, state):
''' Save prepare(state) on policy shared context before
the state is stored
'''
with self.locked_context() as ctx:
metric = self.prepare(state)
if metric is not None:
ctx[state_id] = metric
def priority(self, state_id):
''' A numeric value representing likelihood to reach the interesting program spot '''
return 1.0
def prepare(self, state):
''' Process a state and keep enough data to later decide it's
priority #fixme rephrase
'''
return None
def choice(self, state_ids):
''' Select a state id from states_id.
self.context has a dict mapping state_ids -> prepare(state)'''
raise NotImplementedError
class Random(Policy):
def __init__(self):
super(Random, self).__init__()
def __init__(self, executor, *args, **kwargs):
super(Random, self).__init__(executor, *args, **kwargs)
def features(self, state):
''' Save state features for prioritization before a state is stored '''
pass
def choice(self, state_ids):
return random.choice(state_ids)
def priority(self, state_id):
''' A numeric value representing likelihood to reach the interesting program spot '''
return 1.0
class Uncovered(Policy):
def __init__(self, executor, *args, **kwargs):
super(Uncovered, self).__init__(executor, *args, **kwargs)
#hook on the necesary executor signals
#on callbacks save data in executor.context['policy']
def prepare(self, state):
''' this is what we need to save for choosing later '''
return state.cpu.PC
def choice(self, state_ids):
# Use executor.context['uncovered'] = state_id -> stats
# am
with self._executor.locked_context() as ctx:
lastpc = ctx['policy']
visited = ctx.get('visited', ())
interesting = set()
for _id in state_ids:
if lastpc.get(_id, None) not in visited:
interesting.add(_id)
if len(interesting) > 0:
return random.choice(tuple(interesting))
else:
return random.choice(state_ids)
class Executor(object):
class Executor(Eventful):
'''
The executor guides the execution of an initial state or a paused previous run.
It handles all exceptional conditions (system calls, memory faults, concretization, etc.)
'''
def __init__(self, initial=None, workspace='', policy='random', context=None, **options):
# Signals / Callbacks handlers will be invoked potentially at different
def __init__(self, initial=None, workspace=None, policy='random', context=None, **kwargs):
super(Executor, self).__init__(**kwargs)
# Signals / Callbacks handlers will be invoked potentially at different
# worker processes. State provides a local context to save data.
#Executor signals
self.will_start_run = Signal()
self.will_finish_run = Signal()
self.will_fork_state = Signal()
self.will_store_state = Signal()
self.will_load_state = Signal()
self.will_terminate_state = Signal()
self.will_generate_testcase = Signal()
#Be sure every state will forward us their signals
self.will_load_state += self._register_state_callbacks
self.subscribe('will_load_state', self._register_state_callbacks)
#The main executor lock. Acquire this for accessing shared objects
self._lock = manager.Condition(manager.RLock())
@@ -105,21 +142,21 @@ class Executor(object):
self._shared_context = manager.dict(context)
#scheduling priority policy (wip)
self.policy = Random()
#Set policy
policies = {'random': Random,
'uncovered': Uncovered
}
self._policy = policies[policy](self)
assert isinstance(self._policy, Policy)
if self.load_workspace():
if initial is not None:
logger.error("Ignoring initial state")
# We loaded state ids, now load the actual state
current_state_id = self.get()
initial = self._workspace.load_state(current_state_id)
self._register_state_callbacks(initial, current_state_id)
self.add(initial)
##FIXME PUBSUB We need to forward signals here so they get declared
##forward signals from initial state so they are declared here
self._register_state_callbacks(initial, 0) # id param unused
else:
if initial is not None:
self.add(initial)
self.forward_events_from(initial, True)
@contextmanager
def locked_context(self):
@@ -142,7 +179,7 @@ class Executor(object):
Going up, we prepend state in the arguments.
'''
#Forward all state signals
forward_signals(self, state, True)
self.forward_events_from(state, True)
def add(self, state):
'''
@@ -152,8 +189,8 @@ class Executor(object):
'''
#save the state to secondary storage
state_id = self._workspace.save_state(state)
self.will_store_state(state, state_id)
self.put(state_id)
self.publish('did_add_state', state_id, state)
return state_id
def load_workspace(self):
@@ -228,13 +265,10 @@ class Executor(object):
logger.debug("Waiting for available states")
self._lock.wait()
state_id = random.choice(self._states)
state_id = self._policy.choice(list(self._states))
del self._states[self._states.index(state_id)]
return state_id
###############################################################
# File Storage
def list(self):
''' Returns the list of states ids currently queued '''
return list(self._states)
@@ -250,7 +284,8 @@ class Executor(object):
#broadcast test generation. This is the time for other modules
#to output whatever helps to understand this testcase
self.will_generate_testcase(state, message)
self.publish('will_generate_testcase', state)
def fork(self, state, expression, policy='ALL', setstate=None):
@@ -282,7 +317,7 @@ class Executor(object):
#We are about to fork current_state
with self._lock:
self.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 = []
@@ -329,7 +364,7 @@ class Executor(object):
#load selected state from secondary storage
if current_state_id is not None:
current_state = self._workspace.load_state(current_state_id)
self.will_load_state(current_state, current_state_id)
self.publish('will_load_state', current_state, current_state_id)
#notify siblings we have a state to play with
self._start_run()
@@ -348,7 +383,7 @@ class Executor(object):
break
else:
#Notify this worker is done
self.will_terminate_state(current_state, current_state_id, 'Shutdown')
self.publish('will_terminate_state', current_state, current_state_id, 'Shutdown')
current_state = None
@@ -364,7 +399,7 @@ class Executor(object):
except TerminateState as e:
#Notify this worker is done
self.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:
@@ -377,7 +412,7 @@ class Executor(object):
logger.error("Exception: %s\n%s", str(e), trace)
#Notify this state is done
self.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))
@@ -388,7 +423,7 @@ class Executor(object):
trace = traceback.format_exc()
logger.error("Exception: %s\n%s", str(e), trace)
#Notify this worker is done
self.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)
@@ -398,6 +433,6 @@ class Executor(object):
self._stop_run()
#Notify this worker is done (not sure it's needed)
self.will_finish_run()
self.publish('will_finish_run')
+5 -7
View File
@@ -5,8 +5,7 @@ from collections import OrderedDict
from .smtlib import solver, Bool
from ..utils.helpers import issymbolic
from ..utils.event import Signal, forward_signals
from ..utils.event import Eventful
#import exceptions
from .cpu.abstractcpu import ConcretizeRegister
@@ -59,10 +58,8 @@ class ForkState(Concretize):
super(ForkState, self).__init__(message, expression, policy='ALL', **kwargs)
from ..utils.event import Signal
class State(Eventful):
class State(object):
'''
Representation of a unique program state/path.
@@ -71,7 +68,8 @@ class State(object):
:ivar dict context: Local context for arbitrary data storage
'''
def __init__(self, constraints, platform):
def __init__(self, constraints, platform, **kwargs):
super(State, self).__init__(**kwargs)
self.platform = platform
self.forks = 0
self.constraints = constraints
@@ -86,7 +84,7 @@ class State(object):
#self.will_add_constraint = Signal()
#Import all signals from platform
forward_signals(self, platform)
self.forward_events_from(platform)
def __reduce__(self):
return (self.__class__, (self.constraints, self.platform),
+16 -21
View File
@@ -573,7 +573,7 @@ class Manticore(object):
logger.debug("About to store state %r %r %r", state, expression, values, policy)
def _read_register_callback(self, state, reg_name, value):
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):
@@ -585,7 +585,7 @@ class Manticore(object):
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):
def _decode_instruction_callback(self, state, pc):
logger.debug("Decoding stuff instruction not available")
@@ -707,36 +707,31 @@ class Manticore(object):
self._executor = Executor(initial_state,
workspace=ws_path,
policy=self._policy,
dumpafter=self.dumpafter,
maxstates=self.maxstates,
maxstorage=self.maxstorage,
replay=replay,
dumpstats=self.should_profile,
context=self.context)
#Link Executor events to default callbacks in manticore object
self._executor.did_read_register += self._read_register_callback
self._executor.will_write_register += self._write_register_callback
self._executor.did_read_memory += self._read_memory_callback
self._executor.will_write_memory += self._write_memory_callback
self._executor.will_execute_instruction += self._execute_instruction_callback
self._executor.will_decode_instruction += self._decode_instruction_callback
self._executor.will_store_state += self._store_state_callback
self._executor.will_load_state += self._load_state_callback
self._executor.will_fork_state += self._fork_state_callback
self._executor.will_terminate_state += self._terminate_state_callback
self._executor.will_generate_testcase += self._generate_testcase_callback
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_store_state', self._store_state_callback)
self._executor.subscribe('will_load_state', self._load_state_callback)
self._executor.subscribe('will_fork_state', self._fork_state_callback)
self._executor.subscribe('will_terminate_state', self._terminate_state_callback)
self._executor.subscribe('will_generate_testcase', self._generate_testcase_callback)
if self._hooks:
self._executor.will_execute_instruction += self._hook_callback
self._executor.subscribe('will_execute_instruction', self._hook_callback)
if self._model_hooks:
self._executor.will_execute_instruction += self._model_hook_callback
self._executor.subscribe('will_execute_instruction', self._model_hook_callback)
if self._assertions:
self._executor.will_execute_instruction += self._assertions_callback
self._executor.subscribe('will_execute_instruction', self._assertions_callback)
self._time_started = time.time()
+8 -7
View File
@@ -1,7 +1,6 @@
import cgcrandom
import weakref
import sys, os, struct
from ..utils.event import Signal, forward_signals
# TODO use cpu factory
from ..core.cpu.x86 import I386Cpu
from ..core.cpu.abstractcpu import Interruption, Syscall, ConcretizeRegister
@@ -10,6 +9,7 @@ from ..core.smtlib import *
from ..core.executor import TerminateState
from ..utils.helpers import issymbolic
from ..binary import CGCElf
from ..platforms.platform import Platform
from contextlib import closing
import StringIO
import logging
@@ -73,7 +73,7 @@ class Socket(object):
return len(buf)
class Decree(object):
class Decree(Platform):
'''
A simple Decree Operating System.
This class emulates the most common Decree system calls
@@ -88,7 +88,7 @@ class Decree(object):
CGC_SIZE_MAX=4294967295
CGC_FD_SETSIZE=32
def __init__(self, programs):
def __init__(self, programs, **kwargs):
'''
Builds a Decree OS
:param cpus: CPU for this platform
@@ -97,6 +97,7 @@ class Decree(object):
:todo: fix deps?
'''
programs = programs.split(",")
super(Decree, self).__init__(path=programs[0], **kwargs)
self.clocks = 0
self.files = []
self.syscall_trace = []
@@ -144,7 +145,7 @@ class Decree(object):
#Install event forwarders
for proc in self.procs:
forward_signals(self, proc)
self.forward_events_from(proc)
def _mk_proc(self):
@@ -155,7 +156,7 @@ class Decree(object):
return self.procs[self._current]
def __getstate__(self):
state = {}
state = super(Decree, self).__getstate__()
state['clocks'] = self.clocks
state['input'] = self.input.buffer
state['output'] = self.output.buffer
@@ -166,7 +167,6 @@ class Decree(object):
state['rwait'] = self.rwait
state['twait'] = self.twait
state['timers'] = self.timers
state['syscall_trace'] = self.syscall_trace
return state
@@ -175,6 +175,7 @@ class Decree(object):
:todo: some asserts
:todo: fix deps? (last line)
"""
super(Decree, self).__setstate__(state)
self.input = Socket()
self.input.buffer = state['input']
self.output = Socket()
@@ -206,7 +207,7 @@ class Decree(object):
#Install event forwarders
for proc in self.procs:
forward_signals(self, proc)
self.forward_events_from(proc)
def _read_string(self, cpu, buf):
"""
+7 -7
View File
@@ -8,8 +8,6 @@ import ctypes
#Remove in favor of binary.py
from elftools.elf.elffile import ELFFile
from ..utils.event import Signal, forward_signals
from ..utils.helpers import issymbolic
from ..core.cpu.abstractcpu import Interruption, Syscall, ConcretizeArgument
from ..core.cpu.cpufactory import CpuFactory
@@ -284,7 +282,7 @@ class Linux(Platform):
This class emulates the most common Linux system calls
'''
def __init__(self, program, argv=None, envp=None):
def __init__(self, program, argv=None, envp=None, **kwargs):
'''
Builds a Linux OS platform
:param string program: The path to ELF binary
@@ -293,7 +291,7 @@ class Linux(Platform):
:ivar files: List of active file descriptors
:type files: list[Socket] or list[File]
'''
super(Linux, self).__init__(program)
super(Linux, self).__init__(path=program, **kwargs)
self.program = program
self.clocks = 0
@@ -382,7 +380,7 @@ class Linux(Platform):
#Install event forwarders
for proc in self.procs:
forward_signals(self, proc)
self.forward_events_from(proc)
def _mk_proc(self, arch):
if arch in {'i386', 'armv7'}:
@@ -396,7 +394,7 @@ class Linux(Platform):
return self.procs[self._current]
def __getstate__(self):
state = {}
state = super(Linux, self).__getstate__()
state['clocks'] = self.clocks
state['input'] = self.input.buffer
state['output'] = self.output.buffer
@@ -437,6 +435,8 @@ class Linux(Platform):
:todo: some asserts
:todo: fix deps? (last line)
"""
super(Linux, self).__setstate__(state)
self.input = Socket()
self.input.buffer = state['input']
self.output = Socket()
@@ -481,7 +481,7 @@ class Linux(Platform):
#Install event forwarders
for proc in self.procs:
forward_signals(self, proc)
self.forward_events_from(proc)
def _init_arm_kernel_helpers(self):
'''
+14 -4
View File
@@ -1,5 +1,5 @@
from manticore.utils.event import Signal
from manticore.utils.event import Eventful
from itertools import islice, imap
import inspect
@@ -21,12 +21,22 @@ class ConcretizeSyscallArgument(OSException):
super(ConcretizeSyscallArgument, self).__init__(message)
class Platform(object):
class Platform(Eventful):
'''
Base class for all operating system platforms.
'''
def __init__(self, path):
self._path = path
def __init__(self, path, **kwargs):
super(Platform, self).__init__(**kwargs)
self._path = path #Not clear why all platforms must have a "path"
def invoke_model(self, model, prefix_args=None):
self._function_abi.invoke(model, prefix_args)
def __setstate__(self, state):
super(Platform, self).__setstate__(state)
self._path = state['path']
def __getstate__(self):
state = super(Platform, self).__getstate__()
state['path'] = self._path
return state
+6 -5
View File
@@ -9,7 +9,6 @@ from ..core.cpu.abstractcpu import Interruption, Syscall
from ..core.state import ForkState, TerminateState
from ..utils.helpers import issymbolic
from ..platforms.platform import *
from ..utils.event import Signal, forward_signals
from ..binary.pe import minidump
@@ -63,10 +62,11 @@ class Windows(Platform):
def _mk_memory(self):
return Memory32()
def __init__(self, path, additional_context = None, snapshot_folder=None):
def __init__(self, path, additional_context = None, snapshot_folder=None, **kwargs):
'''
Builds a Windows OS platform
'''
super(Windows, self).__init__(path,**kwargs)
self.clocks = 0
self.files = []
self.syscall_trace = []
@@ -178,7 +178,7 @@ class Windows(Platform):
#Install event forwarders
for proc in self.procs:
forward_signals(self, proc)
self.forward_events_from(proc)
@property
@@ -190,7 +190,7 @@ class Windows(Platform):
return self.procs[self._current]
def __getstate__(self):
state = {}
state = super(Windows, self).__getstate__()
state['clocks'] = self.clocks
state['procs'] = self.procs
state['current'] = self._current
@@ -206,6 +206,7 @@ class Windows(Platform):
:todo: some asserts
:todo: fix deps? (last line)
"""
super(Windows, self).__setstate__(state)
self.procs = state['procs']
self._current = state['current']
self.running = state['running']
@@ -216,7 +217,7 @@ class Windows(Platform):
#Install event forwarders
for proc in self.procs:
forward_signals(self, proc)
self.forward_events_from(proc)
def _read_string(self, cpu, buf):
"""
+64 -152
View File
@@ -1,168 +1,80 @@
import inspect
from weakref import WeakSet, WeakKeyDictionary
from weakref import ref, WeakSet, WeakKeyDictionary, WeakValueDictionary
from types import MethodType
# Inspired by:
# http://code.activestate.com/recipes/577980-improved-signalsslots-implementation-in-python/
class SignalDisconnectedError(RuntimeError):
pass
def forward_signals(dest, source, arg=False):
class Eventful(object):
'''
Replicate and forward all the signals from source to dest
Abstract class for objects emitting and receiving events
An eventful object can:
- publish an event with arbitrary arguments to its subscribers
- let foreign objects subscribe their methods to events emitted here
- forward events to/from other eventful objects
'''
#Import all signals from state
for signal_name in source.__dict__:
signal = getattr(source, signal_name, None)
if isinstance(signal, Signal):
proxy = getattr(dest, signal_name, Signal())
proxy.when(source, signal, arg)
setattr(dest, signal_name, proxy)
def _manage_signals(obj, enabled):
'''
Enable or disable all signals at obj
'''
#Import all signals from state
for signal_name in dir(obj):
signal = getattr(obj, signal_name)
if isinstance(signal, Signal):
if enabled:
signal.enable()
else:
signal.disable()
def enable_signals(obj):
_manage_signals(obj, True)
def disable_signals(obj):
_manage_signals(obj, False)
class Signal(object):
'''
The Signal class is an approximation of Qt's signals+slot system. Each event
that an object would like to produce requires a Signal() object. All
interested parties on the event must register themselves as receivers via
connect() or the '+=' operator.
The event source calls emit() to produce an event (or treat it as a
callable). All registered receivers will receive it, synchronously.
'''
def __init__(self, description=None):
'''
Create a Signal() object. Pass 'True' to constructor if locking around
emit() is required.
'''
self.description = description
self._functions = WeakSet()
self._methods = WeakKeyDictionary()
def __init__(self, *args, **kwargs):
# A dictionary from "event name" -> callback methods
# Note that several methods can be associated with the same object
self._signals = dict()
# a set of sink eventful objects (see forward_events_from())
self._forwards = WeakKeyDictionary()
self.disabled = False
super(Eventful, self).__init__(*args, **kwargs)
def disable(self):
self.disabled = True
def enable(self):
self.disabled = False
def __setstate__(self, state):
''' It wont get serialized by design, user is responsible to reconnect'''
self._signals = dict()
self._forwards = WeakKeyDictionary()
return True
def __len__(self):
return len(self._functions) + len(self._methods)
def __getstate__(self):
return {}
def __call__(self, *args, **kwargs):
return self.emit(*args, **kwargs)
def _unref(self, robj):
# this is called when an object that has subscribed to events emitted
# here has recently been garbage collected
# This simply removes all callback methods associated with that object
# Also if no more callbacks at all for an event name it deletes the event entry
remove = set()
for name, bucket in self._signals.iteritems():
if robj in bucket:
del bucket[robj]
if len(bucket) == 0:
remove.add(name)
for name in remove:
del self._signals[name]
def emit(self, *args, **kwargs):
'Invoke the signal with |args| and |kwargs|'
results = []
def _get_signal_bucket(self, name):
#Each event name has a bucket of callback methods
#A bucket is a dictionary obj -> set(method1, method2...)
return self._signals.setdefault(name, dict())
if self.disabled:
return results
def publish(self, name, *args, **kwargs):
bucket = self._get_signal_bucket(name)
for robj, methods in bucket.items():
for callback in methods:
callback(robj(), *args, **kwargs)
for f in self._functions:
if '__predicate__' in f.__dict__:
if not f.__dict__['__predicate__']():
continue
results.append(f(*args, **kwargs))
for obj, funcs in self._methods.items():
for f in funcs:
if '__predicate__' in f.__dict__:
if not f.__dict__['__predicate__']():
continue
results.append(f(obj, *args, **kwargs))
return results
def connect(self, dest, predicate=None):
'''
Connect |dest| to the signal. If |predicate| is set, it is treated as a
nullary callable whose return value determines if the signal is fired.
NOTE: Passing identical values to multiple invocations of connect() with
different values of predicate will overwrite previous predicates and
persist the last-used value.
To achieve a similar effect, wrap |dest| in a function.
'''
assert callable(dest)
if inspect.ismethod(dest):
obj, impl = dest.__self__, dest.__func__
if predicate is not None:
impl.__dict__['__predicate__'] = predicate
self._methods.setdefault(obj, set()).add(impl)
else:
if predicate is not None:
dest.__dict__['__predicate__'] = predicate
self._functions.add(dest)
for signal, methods in self._forwards.items():
for method in methods:
signal.connect(method)
self._forwards.clear()
def when(self, obj, signal, arg=False):
''' This forwards signal from obj '''
#will reemit forwarded signal prepending obj to arguments
if arg:
method = MethodType(lambda *args, **kwargs: self.emit(*args, **kwargs), obj)
else:
method = self.emit
if len(self):
signal.connect(method)
else:
self._forwards.setdefault(signal, set()).add(method)
def __iadd__(self, dest):
self.connect(dest)
return self
def disconnect(self, dest):
try:
if inspect.ismethod(dest):
obj, impl = dest.__self__, dest.__func__
self._methods[obj].remove(impl)
#The include_source flag indicates to prepend the source of the event in
# 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)
else:
self._functions.remove(dest)
except KeyError:
raise SignalDisconnectedError()
sink.publish(name, *args, **kwargs)
def __isub__(self, dest):
self.disconnect(dest)
return self
def subscribe(self, name, method):
if not inspect.ismethod(method):
raise TypeError
obj, callback = method.__self__, method.__func__
bucket = self._get_signal_bucket(name)
robj = ref(obj, self._unref) #see unref() for explanation
bucket.setdefault(robj, set()).add(callback)
def reset(self):
self._functions.clear()
self._methods.clear()
self._forwards.clear()
def forward_events_from(self, source, include_source=False):
if not isinstance(source, Eventful):
raise TypeError
source.forward_events_to(self, include_source=include_source)
def forward_events_to(self, sink, include_source=False):
''' This forwards signal to sink '''
if not isinstance(sink, Eventful):
raise TypeError
self._forwards[sink] = include_source
+1
View File
@@ -61,6 +61,7 @@ class IntegrationTest(unittest.TestCase):
'--procs', '4',
filename,
'+++++++++'], stdout=output)
self.assertTrue(time.time()-t < 20)
def test_cli_verbosity(self):
+77
View File
@@ -0,0 +1,77 @@
import unittest
from manticore.utils.event import Eventful
class A(Eventful):
def do_stuff(self):
self.publish("eventA",1, 'a')
class B(Eventful):
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')
class C():
def __init__(self):
self.received = []
def callback(self, *args):
self.received.append(args)
class ManticoreDriver(unittest.TestCase):
_multiprocess_can_split_ = True
def setUp(self):
self.state = {}
def tearDown(self):
pass
def test_weak_references(self):
a = A()
self.assertSequenceEqual( map(len, (a._signals, a._forwards)), (0, 0) )
b = B(a)
self.assertSequenceEqual( map(len, (a._signals, a._forwards)), (0, 1) )
self.assertSequenceEqual( map(len, (b._signals, b._forwards)), (0, 0) )
c = C()
b.subscribe('eventA', c.callback)
self.assertSequenceEqual( map(len, (a._signals, a._forwards)), (0, 1) )
self.assertSequenceEqual( map(len, (b._signals, b._forwards)), (1, 0) )
b.subscribe('eventB', c.callback)
self.assertSequenceEqual( map(len, (a._signals, a._forwards)), (0, 1) )
self.assertSequenceEqual( map(len, (b._signals, b._forwards)), (2, 0) )
del c
self.assertSequenceEqual( map(len, (a._signals, a._forwards)), (0, 1) )
self.assertSequenceEqual( map(len, (b._signals, b._forwards)), (0, 0) )
del b
self.assertSequenceEqual( map(len, (a._signals, a._forwards)), (0, 0) )
def test_basic(self):
a = A()
b = B(a)
c = C()
b.subscribe('eventA', c.callback)
b.subscribe('eventB', c.callback)
a.do_stuff()
self.assertSequenceEqual(c.received, [(1, 'a')])
b.do_stuff()
self.assertSequenceEqual(c.received, [(1, 'a'), (2, 'b')])
-70
View File
@@ -1,70 +0,0 @@
import unittest
from manticore.utils.event import Signal
class Sender(object):
def __init__(self):
self.sig = Signal()
self.sig2 = Signal()
class ManticoreDriver(unittest.TestCase):
_multiprocess_can_split_ = True
def setUp(self):
self.state = {}
def tearDown(self):
pass
def setReceived(self, key, val):
self.state[key] = val
def setReceived2(self, key, val):
self.state[key] = val
def test_basic(self):
s = Sender()
def recv():
self.state['received'] = True
self.state['received'] = False
s.sig += recv
s.sig()
self.assertEqual(self.state['received'], True)
def test_method(self):
s = Sender()
s.sig += self.setReceived
s.sig('received', True)
self.assertEqual(self.state['received'], True)
def test_disconnect(self):
s = Sender()
s.sig.connect(self.setReceived)
s.sig -= self.setReceived
s.sig('received', True)
self.assertNotIn('received', self.state)
def test_predicate(self):
s = Sender()
s.sig.connect(self.setReceived)
s.sig2.connect(self.setReceived2, lambda: False)
s.sig('true', True)
s.sig2('false', True)
self.assertEqual(self.state['true'], True)
self.assertNotIn('false', self.state)
+3 -14
View File
@@ -1,7 +1,6 @@
import unittest
from manticore.utils.event import Eventful
from manticore.platforms import linux
from manticore.utils.event import Signal
from manticore.core.state import State
from manticore.core.smtlib import BitVecVariable, ConstraintSet
@@ -19,25 +18,15 @@ class FakeMemory(object):
class FakeCpu(object):
def __init__(self):
self.will_decode_instruction = Signal()
self.will_execute_instruction = Signal()
self.did_execute_instruction = Signal()
self.will_emulate_instruction = Signal()
self.did_emulate_instruction = Signal()
self.will_read_register = Signal()
self.will_write_register = Signal()
self.will_read_memory = Signal()
self.will_write_memory = Signal()
self._memory = FakeMemory()
@property
def memory(self):
return self._memory
class FakePlatform(object):
class FakePlatform(Eventful):
def __init__(self):
super(FakePlatform, self).__init__()
self._constraints = None
self.procs = [FakeCpu()]
+3 -12
View File
@@ -4,10 +4,10 @@ import unittest
from multiprocessing.managers import SyncManager
from manticore.platforms import linux
from manticore.utils.event import Signal
from manticore.core.state import State
from manticore.core.smtlib import BitVecVariable, ConstraintSet
from manticore.core.workspace import *
from manticore.utils.event import Eventful
manager = SyncManager()
manager.start(lambda: signal.signal(signal.SIGINT, signal.SIG_IGN))
@@ -24,18 +24,9 @@ class FakeMemory(object):
def constraints(self, constraints):
self._constraints = constraints
class FakeCpu(object):
class FakeCpu(Eventful):
def __init__(self):
self.will_decode_instruction = Signal()
self.will_execute_instruction = Signal()
self.did_execute_instruction = Signal()
self.will_emulate_instruction = Signal()
self.did_emulate_instruction = Signal()
self.will_read_register = Signal()
self.will_write_register = Signal()
self.will_read_memory = Signal()
self.will_write_memory = Signal()
super(FakeCpu, self).__init__()
self._memory = FakeMemory()