Author: Armin Rigo <ar...@tunes.org> Branch: reverse-debugger Changeset: r85232:7b34a47bb51b Date: 2016-06-20 09:27 +0200 http://bitbucket.org/pypy/pypy/changeset/7b34a47bb51b/
Log: in-progress diff --git a/rpython/translator/revdb/interact.py b/rpython/translator/revdb/interact.py --- a/rpython/translator/revdb/interact.py +++ b/rpython/translator/revdb/interact.py @@ -27,14 +27,26 @@ child = ReplayProcess(subproc.pid, s1) msg = child.expect(ANSWER_INIT, INIT_VERSION_NUMBER, Ellipsis) self.total_stop_points = msg.arg2 - child.expect(ANSWER_STD, 1, Ellipsis) + msg = child.expect(ANSWER_STD, 1, Ellipsis) + child.update_times(msg) self.active_child = child - self.paused_children = {} + self.paused_children = [] + # + # fixed time division for now + if self.total_stop_points < 1: + raise ValueError("no stop points recorded in %r" % ( + revdb_log_filename,)) + self.fork_points = {1: child.clone()} + p = 1 + while p < self.total_stop_points and len(self.fork_points) < 30: + p += int((self.total_stop_points - p) * 0.25) + 1 + self.fork_points[p] = None def interact(self): last_command = '' while True: - prompt = '(%d)$ ' % self.active_child.current_time() + last_time = self.active_child.current_time + prompt = '(%d)$ ' % last_time try: cmdline = raw_input(prompt).strip() except EOFError: @@ -54,8 +66,7 @@ try: runner(argument) except Exception as e: - for line in traceback.format_exception_only(type(e), e): - sys.stderr.write(line) + traceback.print_exc() last_command = cmdline def command_help(self, argument): @@ -68,9 +79,26 @@ print '\t%s\t%s' % (command, docstring) def command_quit(self, argument): - """Exit the reverse debugger""" + """Exit the debugger""" sys.exit(0) + def change_child(self, target_time): + """If 'target_time' is not soon after 'self.active_child', + close it and fork another child.""" + assert 1 <= target_time <= self.total_stop_points + best = max([key for key in self.fork_points if key <= target_time]) + if best < self.active_child.current_time <= target_time: + pass # keep 'active_child' + else: + self.active_child.close() + self.active_child = self.fork_points[best].clone() + def command_go(self, argument): """Go to time ARG""" target_time = int(argument) + if target_time < 1: + target_time = 1 + if target_time > self.total_stop_points: + target_time = self.total_stop_points + self.change_child(target_time) + self.active_child.forward(target_time - self.active_child.current_time) diff --git a/rpython/translator/revdb/message.py b/rpython/translator/revdb/message.py --- a/rpython/translator/revdb/message.py +++ b/rpython/translator/revdb/message.py @@ -1,8 +1,5 @@ -import os, struct, socket, errno -from rpython.translator.revdb import ancillary - -INIT_VERSION_NUMBER = 0xd80100 +INIT_VERSION_NUMBER = 0xd80100 CMD_FORK = -1 CMD_QUIT = -2 @@ -15,6 +12,10 @@ class Message(object): + """Represent messages sent and received to subprocesses + started with --revdb-replay. + """ + def __init__(self, cmd, arg1=0, arg2=0, arg3=0, extra=""): self.cmd = cmd self.arg1 = arg1 @@ -36,65 +37,3 @@ def __ne__(self, other): return not (self == other) - - -class ReplayProcess(object): - def __init__(self, pid, control_socket): - self.pid = pid - self.control_socket = control_socket - - def _recv_all(self, size): - pieces = [] - while size > 0: - data = self.control_socket.recv(size) - if not data: - raise EOFError - size -= len(data) - pieces.append(data) - return ''.join(pieces) - - def send(self, msg): - binary = struct.pack("iIqqq", msg.cmd, len(msg.extra), - msg.arg1, msg.arg2, msg.arg3) - self.control_socket.sendall(binary + msg.extra) - - def recv(self): - binary = self._recv_all(struct.calcsize("iIqqq")) - cmd, size, arg1, arg2, arg3 = struct.unpack("iIqqq", binary) - extra = self._recv_all(size) - return Message(cmd, arg1, arg2, arg3, extra) - - def expect(self, cmd, arg1=0, arg2=0, arg3=0, extra=""): - msg = self.recv() - assert msg.cmd == cmd - if arg1 is not Ellipsis: - assert msg.arg1 == arg1 - if arg2 is not Ellipsis: - assert msg.arg2 == arg2 - if arg3 is not Ellipsis: - assert msg.arg3 == arg3 - if extra is not Ellipsis: - assert msg.extra == extra - return msg - - def fork(self): - self.send(Message(CMD_FORK)) - s1, s2 = socket.socketpair() - ancillary.send_fds(self.control_socket.fileno(), [s2.fileno()]) - s2.close() - msg = self.expect(ANSWER_FORKED, Ellipsis) - child_pid = msg.arg1 - return ReplayProcess(child_pid, s1) - - def close(self): - self.send(Message(CMD_QUIT)) - - def forward(self, steps): - self.send(Message(CMD_FORWARD, steps)) - return self.expect(ANSWER_STD, Ellipsis, Ellipsis) - - def current_time(self): - return self.forward(0).arg1 - - def currently_created_objects(self): - return self.forward(0).arg2 diff --git a/rpython/translator/revdb/process.py b/rpython/translator/revdb/process.py new file mode 100644 --- /dev/null +++ b/rpython/translator/revdb/process.py @@ -0,0 +1,168 @@ +import os, struct, socket, errno, subprocess +from rpython.translator.revdb import ancillary +from rpython.translator.revdb.message import * + + +class ReplayProcess(object): + """Represent one replaying subprocess. + + It can be either the one started with --revdb-replay, or a fork. + """ + + def __init__(self, pid, control_socket): + self.pid = pid + self.control_socket = control_socket + + def _recv_all(self, size): + pieces = [] + while size > 0: + data = self.control_socket.recv(size) + if not data: + raise EOFError + size -= len(data) + pieces.append(data) + return ''.join(pieces) + + def send(self, msg): + binary = struct.pack("iIqqq", msg.cmd, len(msg.extra), + msg.arg1, msg.arg2, msg.arg3) + self.control_socket.sendall(binary + msg.extra) + + def recv(self): + binary = self._recv_all(struct.calcsize("iIqqq")) + cmd, size, arg1, arg2, arg3 = struct.unpack("iIqqq", binary) + extra = self._recv_all(size) + return Message(cmd, arg1, arg2, arg3, extra) + + def expect(self, cmd, arg1=0, arg2=0, arg3=0, extra=""): + msg = self.recv() + assert msg.cmd == cmd + if arg1 is not Ellipsis: + assert msg.arg1 == arg1 + if arg2 is not Ellipsis: + assert msg.arg2 == arg2 + if arg3 is not Ellipsis: + assert msg.arg3 == arg3 + if extra is not Ellipsis: + assert msg.extra == extra + return msg + + def update_times(self, msg): + self.current_time = msg.arg1 + self.currently_created_objects = msg.arg2 + + def clone(self): + """Fork this subprocess. Returns a new ReplayProcess() that is + an identical copy. + """ + self.send(Message(CMD_FORK)) + s1, s2 = socket.socketpair() + ancillary.send_fds(self.control_socket.fileno(), [s2.fileno()]) + s2.close() + msg = self.expect(ANSWER_FORKED, Ellipsis, Ellipsis, Ellipsis) + self.update_times(msg) + child_pid = msg.arg3 + other = ReplayProcess(child_pid, s1) + other.update_times(msg) + return other + + def close(self): + """Close this subprocess.""" + self.send(Message(CMD_QUIT)) + + def forward(self, steps): + """Move this subprocess forward in time.""" + self.send(Message(CMD_FORWARD, steps)) + msg = self.expect(ANSWER_STD, Ellipsis, Ellipsis) + self.update_times(msg) + return msg + + +class ReplayProcessGroup(object): + """Handle a family of subprocesses. + """ + MAX_SUBPROCESSES = 31 # maximum number of subprocesses + STEP_RATIO = 0.25 # subprocess n is between subprocess n-1 + # and the end, at this time fraction + + def __init__(self, executable, revdb_log_filename): + s1, s2 = socket.socketpair() + initial_subproc = subprocess.Popen( + [executable, '--revdb-replay', revdb_log_filename, + str(s2.fileno())]) + s2.close() + child = ReplayProcess(initial_subproc.pid, s1) + msg = child.expect(ANSWER_INIT, INIT_VERSION_NUMBER, Ellipsis) + self.total_stop_points = msg.arg2 + msg = child.expect(ANSWER_STD, 1, Ellipsis) + child.update_times(msg) + + self.active = child + self.paused = {1: child.clone()} # {time: subprocess} + + def get_current_time(self): + return self.active.current_time + + def _check_current_time(self, time): + assert self.get_current_time() == time + self.active.send(Message(CMD_FORWARD, 0)) + return self.active.expect(ANSWER_STD, time, Ellipsis) + + def get_max_time(self): + return self.total_stop_points + + def get_next_clone_time(self): + if len(self.paused) >= self.MAX_SUBPROCESSES: + next_time = self.total_stop_points + 1 + else: + latest_done = max(self.paused) + range_not_done = self.total_stop_points - latest_done + next_time = latest_done + int(self.STEP_RATIO * range_not_done) + 1 + return next_time + + def forward(self, steps): + """Go forward, for the given number of 'steps' of time. + + If needed, it will leave clones at intermediate times. + Does not close the active subprocess. + """ + assert steps >= 0 + while True: + cur_time = self.get_current_time() + if cur_time + steps > self.total_stop_points: + steps = self.total_stop_points - cur_time + next_clone = self.get_next_clone_time() + rel_next_clone = next_clone - cur_time + if rel_next_clone > steps: + break + assert rel_next_clone >= 0 + if rel_next_clone > 0: + self.active.forward(rel_next_clone) + steps -= rel_next_clone + clone = self.active.clone() + self.paused[clone.current_time] = clone + self.active.forward(steps) + + def _resume(self, from_time): + clone_me = self.paused[from_time] + self.active.close() + self.active = clone_me.clone() + + def jump_in_time(self, target_time): + """Jump in time at the given 'target_time'. + + This function can close the active subprocess. + """ + if target_time < 1: + target_time = 1 + if target_time > self.total_stop_points: + target_time = self.total_stop_points + + cur_time = self.get_current_time() + if target_time >= cur_time: # can go forward + if cur_time >= max(self.paused): # current time is past all forks + self.forward(target_time - cur_time) + return + # else, start from a fork + self._resume(max(time for time in self.paused if time <= target_time)) + self.forward(target_time - self.get_current_time()) diff --git a/rpython/translator/revdb/revdb.py b/rpython/translator/revdb/revdb.py --- a/rpython/translator/revdb/revdb.py +++ b/rpython/translator/revdb/revdb.py @@ -10,7 +10,7 @@ parser.add_argument('-x', '--executable', dest='executable', help='name of the executable file ' 'that recorded the log') - options = parser.parse_args(sys.argv[1:]) + options = parser.parse_args() sys.path.insert(0, os.path.abspath( os.path.join(__file__, '..', '..', '..', '..'))) diff --git a/rpython/translator/revdb/src-revdb/revdb.c b/rpython/translator/revdb/src-revdb/revdb.c --- a/rpython/translator/revdb/src-revdb/revdb.c +++ b/rpython/translator/revdb/src-revdb/revdb.c @@ -282,8 +282,9 @@ static void answer_std(void) { - write_answer(ANSWER_STD, rpy_revdb.stop_point_seen, - rpy_revdb.unique_id_seen, 0); + assert(stopped_time != 0); + assert(stopped_uid != 0); + write_answer(ANSWER_STD, stopped_time, stopped_uid, 0); } static RPyString *make_rpy_string(size_t length) @@ -516,7 +517,7 @@ } else { /* in the parent */ - write_answer(ANSWER_FORKED, child_pid, 0, 0); + write_answer(ANSWER_FORKED, stopped_time, stopped_uid, child_pid); close(child_sockfd); } } @@ -593,6 +594,7 @@ break; } } + rpy_revdb.stop_point_seen = stopped_time; rpy_revdb.unique_id_seen = stopped_uid; stopped_time = 0; stopped_uid = 0; diff --git a/rpython/translator/revdb/test/test_basic.py b/rpython/translator/revdb/test/test_basic.py --- a/rpython/translator/revdb/test/test_basic.py +++ b/rpython/translator/revdb/test/test_basic.py @@ -10,6 +10,7 @@ from rpython.rtyper.lltypesystem import lltype, llmemory from rpython.translator.revdb.message import * +from rpython.translator.revdb.process import ReplayProcess class RDB(object): diff --git a/rpython/translator/revdb/test/test_process.py b/rpython/translator/revdb/test/test_process.py new file mode 100644 --- /dev/null +++ b/rpython/translator/revdb/test/test_process.py @@ -0,0 +1,49 @@ +from rpython.rlib import revdb +from rpython.translator.revdb.message import * +from rpython.translator.revdb.process import ReplayProcessGroup + +from hypothesis import given, strategies + + +class TestReplayProcessGroup: + + def setup_class(cls): + from rpython.translator.revdb.test.test_basic import compile, run + + class Stuff: + pass + + class DBState: + pass + dbstate = DBState() + + def main(argv): + for i, op in enumerate(argv[1:]): + dbstate.stuff = Stuff() + dbstate.stuff.x = i + 1000 + revdb.stop_point() + print op + return 9 + compile(cls, main, [], backendopt=False) + assert run(cls, 'abc d ef g h i j k l m') == ( + 'abc\nd\nef\ng\nh\ni\nj\nk\nl\nm\n') + + + def test_init(self): + group = ReplayProcessGroup(str(self.exename), self.rdbname) + assert group.get_max_time() == 10 + assert group.get_next_clone_time() == 4 + + def test_forward(self): + group = ReplayProcessGroup(str(self.exename), self.rdbname) + group.forward(100) + assert group.get_current_time() == 10 + assert sorted(group.paused) == [1, 4, 6, 8, 9, 10] + assert group._check_current_time(10) + + @given(strategies.lists(strategies.integers(min_value=1, max_value=10))) + def test_jump_in_time(self, target_times): + group = ReplayProcessGroup(str(self.exename), self.rdbname) + for target_time in target_times: + group.jump_in_time(target_time) + group._check_current_time(target_time) _______________________________________________ pypy-commit mailing list pypy-commit@python.org https://mail.python.org/mailman/listinfo/pypy-commit