Skip to content

Commit 5a258f4

Browse files
Eric Hennenfentpwang00ekilmer
authored
TUI Support Infrastructure (#1620)
* Support for TUI (#1605) * Update worker thread for server creation * Add necessary files for TUI connectivity * Add necessary files for TUI connectivity * Update MonitorWorker * Update protocol * Blacken * Update setup.py dependencies * Remove state debugging messages * Update setup.py to build protobuf protocol upon install * Remove previously generated state_pb2.py * Change subprocess.Popen to subprocess.check_output * Remove extraneous output * First attempt at fixing protobuf installation It might work, it might not. We'll let the CI sort it out. * Can't forget the f-string * Error on missing protoc * Disable auto-generation of protobuf file * Ignore pb2_errors * Disable monitor start See if this makes the EVM tests pass Co-authored-by: Eric Hennenfent <[email protected]> * Add log monitoring * Log monitoring via TCP * Swittch to rendering state lists directly * Extraneous line * Switch log buffer to multiprocessing queue * Create state transition events Should make it possible to track movements between state lists * Plug new events into context This will break the state merging plugin (but I'll fix it eventually) * move most enums to their own module * Blacken * Add DaemonThread from TUI branch * Add interface for registering daemon threads * Timestamp StateDescriptor upon updates * Capture return value * Blacken * Add solver wrapper to StateBase * Add `solve` events to all instances of SelectedSolver.instance() * Remove executor constraints from WASM * Add solve events to memory.py * Add intermittent execution event * Be more generous with states whose initialization we missed * Add Native callback for updating state descriptor * Fix state killing * Blacken * codecov: Remove outdated 'yml' entry in CI From these commits codecov/codecov-action@ebea5ca codecov/codecov-action@49c86d6 * Add solve event to evm Make warning messages better Debug GH actions Revert "Debug GH actions" This reverts commit f575eea. Fix some pycharm-detected problems Make symbolic function error message more verbose Add solve to published events Loud errors in callbacks by default Trying to find out what's killing truffle Revert "Trying to find out what's killing truffle" This reverts commit 8bd0224. Revert "Make symbolic function error message more verbose" This reverts commit bd3e90c. Debugging Truffle Restore introspector Add try_except on every callback Unconditionally print error message Add traceback Update event.py Debug subscriptions Debug arguments to callbacks Different debug msg 1ast arg Print statement debugging... Pass in `None` as state Revert "Add try_except on every callback" This reverts commit 1c689dd. * Drop solve events outside of a state context Forgot did_solve Remove traceback * Fix must/cannot_be_null usage * Fix missing solve event * Partially restore old did_fork_state ABI * Called internally * Clone iterators instead of creating a list * Use isgenerator instead of checking if iterable * Fix snapshot restoration * Slightly improve Unicorn test API usage * Temporarily disable property verifier tests * improper skip arg * Add simple tests for introspection API * Add test for custom introspector, improve base introspection test * Add intermittent update timestamp * Only allow daemon registration and introspection registration at initialization * Add docs to manticore.py * Add docs for plugin, add update_state_descriptor to EVM * Fix renamed will_start_run --> will_run * Docstrings for DaemonThread and EventSolver * Docs for enums * Improve pretty printer, add some mypy fixes * Don't run daemon threads if run is called multiple times * If at first you don't succeed, destroy all the evidence you tried. * Test the pretty printer * Add StateDescriptor to RTD * Add newlines for RTD parsing * Update to work with new state introspection API * Add termination messages * Also capture killed state messages * Make info logs debug logs * Apply suggestions from code review Newlines for doc comments Co-authored-by: Eric Kilmer <[email protected]> * Add some type hints to manticore.py * Add some type hints to plugin.py * Fix type hint for get_state * Add termination message from TUI PR * Add example script * Add docstrings to the example script * Pass introspection plugin type as an argument * Unskip property verifier tests * Add mypy-requests type hints * Remove itertools.tee The problem with usign tee is that only the first callback to use the iterator can write to it. In `ready_states`, the `save_state` after the `yield` statement is ignored for all others. * Make generator cloning a little bit more robust Now Manticore will give up and return the original argument instead of blowing up if it can't clone the generator * Clean up invalidated unit tests We now fire `introspect` for the first time before we have any states * Debug missing Truffle & Examples coverage * Merge coverage from XML file * Switch coverage to JSON, ignore debug logging and NotImplemented code * Fix copy commands * Move .coverage files directly * Set examples to append coverage * FLAG_NAME doesn't work the way we'd like * Use plugin dict to store introspector * Appease mypy * Fix missing property on unique name * Grab EVM PC * Blacken * Run black on all files if the git diff command fails * Fix mypy errors * Make plugin logging even less verbose * Move log capture and state monitoring to daemon threads * Use the config module for host & port * Fix worker configuration and add test for TUI API * Fix log messages breaking native tests * Split up base Manticore tests and logging tests The verbosity changes seem to be taking hold when they shouldn't * Merge LogTCPHandler and MonitorTCPHandler * Confirm that logging tests return to base level * Fix mypy * Switch back to using a deque for log buffering in the default case * Fix deque API * Update state_pb2.py * Reformat programatically generated files * Drop max verbosity in logging tests Haven't been able to figure out why, but somehow other loggers get "stuck" at this high verbosity and the integration tests try to print out the values of every single register. * Fix duplicated code from bad merge * Remove is_main from state_monitor * Add comment about log buffer size * Remove vestigial is_main * Blacken Co-authored-by: Philip Wang <[email protected]> Co-authored-by: Eric Kilmer <[email protected]>
1 parent 334b3aa commit 5a258f4

File tree

12 files changed

+801
-25
lines changed

12 files changed

+801
-25
lines changed

manticore/core/manticore.py

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import itertools
33
import logging
44
import sys
5+
import time
56
import typing
67
import random
78
import weakref
@@ -21,11 +22,18 @@
2122
from ..utils.deprecated import deprecated
2223
from ..utils.enums import StateLists, MProcessingType
2324
from ..utils.event import Eventful
24-
from ..utils.helpers import PickleSerializer, pretty_print_state_descriptors
25+
from ..utils.helpers import PickleSerializer, pretty_print_state_descriptors, deque
2526
from ..utils.log import set_verbosity
2627
from ..utils.nointerrupt import WithKeyboardInterruptAs
2728
from .workspace import Workspace, Testcase
28-
from .worker import WorkerSingle, WorkerThread, WorkerProcess, DaemonThread
29+
from .worker import (
30+
WorkerSingle,
31+
WorkerThread,
32+
WorkerProcess,
33+
DaemonThread,
34+
LogCaptureWorker,
35+
state_monitor,
36+
)
2937

3038
from multiprocessing.managers import SyncManager
3139
import threading
@@ -88,6 +96,7 @@ def wait_for(self, condition, *args, **kwargs):
8896
self._terminated_states = []
8997
self._busy_states = []
9098
self._killed_states = []
99+
self._log_queue = deque(maxlen=5000)
91100
self._shared_context = {}
92101

93102
def _manticore_threading(self):
@@ -99,6 +108,7 @@ def _manticore_threading(self):
99108
self._terminated_states = []
100109
self._busy_states = []
101110
self._killed_states = []
111+
self._log_queue = deque(maxlen=5000)
102112
self._shared_context = {}
103113

104114
def _manticore_multiprocessing(self):
@@ -120,6 +130,9 @@ def raise_signal():
120130
self._terminated_states = self._manager.list()
121131
self._busy_states = self._manager.list()
122132
self._killed_states = self._manager.list()
133+
# The multiprocessing queue is much slower than the deque when it gets full, so we
134+
# triple the size in order to prevent that from happening.
135+
self._log_queue = self._manager.Queue(15000)
123136
self._shared_context = self._manager.dict()
124137
self._context_value_types = {list: self._manager.list, dict: self._manager.dict}
125138

@@ -370,8 +383,10 @@ def __init__(
370383
# Workers will use manticore __dict__ So lets spawn them last
371384
self._workers = [self._worker_type(id=i, manticore=self) for i in range(consts.procs)]
372385

373-
# We won't create the daemons until .run() is called
374-
self._daemon_threads: typing.List[DaemonThread] = []
386+
# Create log capture worker. We won't create the rest of the daemons until .run() is called
387+
self._daemon_threads: typing.Dict[int, DaemonThread] = {
388+
-1: LogCaptureWorker(id=-1, manticore=self)
389+
}
375390
self._daemon_callbacks: typing.List[typing.Callable] = []
376391

377392
self._snapshot = None
@@ -1102,21 +1117,27 @@ def run(self):
11021117
# User subscription to events is disabled from now on
11031118
self.subscribe = None
11041119

1120+
self.register_daemon(state_monitor)
1121+
self._daemon_threads[-1].start() # Start log capture worker
1122+
11051123
# Passing generators to callbacks is a bit hairy because the first callback would drain it if we didn't
11061124
# clone the iterator in event.py. We're preserving the old API here, but it's something to avoid in the future.
11071125
self._publish("will_run", self.ready_states)
11081126
self._running.value = True
1127+
11091128
# start all the workers!
11101129
for w in self._workers:
11111130
w.start()
11121131

11131132
# Create each daemon thread and pass it `self`
1114-
if not self._daemon_threads: # Don't recreate the threads if we call run multiple times
1115-
for i, cb in enumerate(self._daemon_callbacks):
1133+
for i, cb in enumerate(self._daemon_callbacks):
1134+
if (
1135+
i not in self._daemon_threads
1136+
): # Don't recreate the threads if we call run multiple times
11161137
dt = DaemonThread(
11171138
id=i, manticore=self
11181139
) # Potentially duplicated ids with workers. Don't mix!
1119-
self._daemon_threads.append(dt)
1140+
self._daemon_threads[dt.id] = dt
11201141
dt.start(cb)
11211142

11221143
# Main process. Lets just wait and capture CTRL+C at main
@@ -1173,6 +1194,17 @@ def finalize(self):
11731194
self.generate_testcase(state)
11741195
self.remove_all()
11751196

1197+
def wait_for_log_purge(self):
1198+
"""
1199+
If a client has accessed the log server, and there are still buffered logs,
1200+
waits up to 2 seconds for the client to retrieve the logs.
1201+
"""
1202+
if self._daemon_threads[-1].activated:
1203+
for _ in range(8):
1204+
if self._log_queue.empty():
1205+
break
1206+
time.sleep(0.25)
1207+
11761208
############################################################################
11771209
############################################################################
11781210
############################################################################
@@ -1188,6 +1220,7 @@ def save_run_data(self):
11881220
config.save(f)
11891221

11901222
logger.info("Results in %s", self._output.store.uri)
1223+
self.wait_for_log_purge()
11911224

11921225
def introspect(self) -> typing.Dict[int, StateDescriptor]:
11931226
"""

manticore/core/plugin.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -670,6 +670,24 @@ def get_state_descriptors(self) -> typing.Dict[int, StateDescriptor]:
670670
out = context.copy() # TODO: is this necessary to break out of the lock?
671671
return out
672672

673+
def did_kill_state_callback(self, state, ex: Exception):
674+
"""
675+
Capture other state-killing exceptions so we can get the corresponding message
676+
677+
:param state: State that was killed
678+
:param ex: The exception w/ the termination message
679+
"""
680+
state_id = state.id
681+
with self.locked_context("manticore_state", dict) as context:
682+
if state_id not in context:
683+
logger.warning(
684+
"Caught killing of state %s, but failed to capture its initialization",
685+
state_id,
686+
)
687+
context.setdefault(state_id, StateDescriptor(state_id=state_id)).termination_msg = repr(
688+
ex
689+
)
690+
673691
@property
674692
def unique_name(self) -> str:
675693
return IntrospectionAPIPlugin.NAME

manticore/core/state.proto

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
syntax = "proto3";
2+
3+
package mserialize;
4+
5+
message LogMessage{
6+
string content = 1;
7+
}
8+
9+
message State{
10+
11+
enum StateType{
12+
READY = 0;
13+
BUSY = 1;
14+
KILLED = 2;
15+
TERMINATED = 3;
16+
}
17+
18+
int32 id = 2; // state ID
19+
StateType type = 3; // Type of state
20+
string reason = 4; // Reason for execution stopping
21+
int32 num_executing = 5; // number of executing instructions
22+
int32 wait_time = 6;
23+
}
24+
25+
message StateList{
26+
repeated State states = 7;
27+
}
28+
29+
message MessageList{
30+
repeated LogMessage messages = 8;
31+
}

0 commit comments

Comments
 (0)