Author: Armin Rigo <[email protected]>
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
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit