Author: Armin Rigo <[email protected]>
Branch: reverse-debugger
Changeset: r85250:9c2a20771e65
Date: 2016-06-20 18:33 +0200
http://bitbucket.org/pypy/pypy/changeset/9c2a20771e65/
Log: Clean up, document and test the exact order of the messages and use
it to implement "first" or "last" breakpoints
diff --git a/rpython/rlib/revdb.py b/rpython/rlib/revdb.py
--- a/rpython/rlib/revdb.py
+++ b/rpython/rlib/revdb.py
@@ -57,13 +57,13 @@
unique id greater or equal."""
return llop.revdb_get_value(lltype.SignedLongLong, 'u')
[email protected](1)
-def go_forward(time_delta, callback):
- """For RPython debug commands: tells that after this function finishes,
- the debugger should run the 'forward <time_delta>' command and then
- invoke the 'callback' with no argument.
- """
- _change_time('f', time_delta, callback)
+## @specialize.arg(1)
+## def go_forward(time_delta, callback):
+## """For RPython debug commands: tells that after this function finishes,
+## the debugger should run the 'forward <time_delta>' command and then
+## invoke the 'callback' with no argument.
+## """
+## _change_time('f', time_delta, callback)
def breakpoint(num):
llop.revdb_breakpoint(lltype.Void, num)
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
@@ -88,6 +88,23 @@
lst = [str(n) for n in sorted(self.pgroup.paused)]
print ', '.join(lst)
+ def move_forward(self, steps):
+ try:
+ self.pgroup.go_forward(steps)
+ except Breakpoint as b:
+ self.hit_breakpoint(b)
+
+ def move_backward(self, steps):
+ try:
+ self.pgroup.go_backward(steps)
+ except Breakpoint as b:
+ self.hit_breakpoint(b)
+
+ def hit_breakpoint(self, b):
+ print 'Hit breakpoint %d' % (b.num,)
+ if self.pgroup.get_current_time() != b.time:
+ self.pgroup.jump_in_time(b.time)
+
def command_step(self, argument):
"""Run forward ARG steps (default 1)"""
arg = int(argument or '1')
@@ -97,16 +114,10 @@
self.move_forward(arg)
command_s = command_step
- def move_forward(self, steps):
- try:
- self.pgroup.go_forward(steps)
- except Breakpoint as b:
- print 'Hit breakpoint %d' % (b.num,)
-
def command_bstep(self, argument):
"""Run backward ARG steps (default 1)"""
arg = int(argument or '1')
- self.pgroup.jump_in_time(self.pgroup.get_current_time() - arg)
+ self.move_backward(arg)
command_bs = command_bstep
def command_continue(self, argument):
@@ -115,6 +126,11 @@
self.pgroup.get_current_time())
command_c = command_continue
+ def command_bcontinue(self, argument):
+ """Run backward"""
+ self.move_backward(self.pgroup.get_current_time() - 1)
+ command_bc = command_bcontinue
+
def command_print(self, argument):
"""Print an expression"""
self.pgroup.print_cmd(argument)
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,25 +1,54 @@
INIT_VERSION_NUMBER = 0xd80100
-CMD_FORK = -1
-CMD_QUIT = -2
-CMD_FORWARD = -3
+
+# See the corresponding answers for details about messages.
+
+CMD_FORK = -1 # Message(CMD_FORK)
+CMD_QUIT = -2 # Message(CMD_QUIT)
+CMD_FORWARD = -3 # Message(CMD_FORWARD, steps, breakpoint_mode)
# extra commands which are not handled by revdb.c, but
# by revdb.register_debug_command()
-CMD_PRINT = 1
-CMD_BACKTRACE = 2
-CMD_LOCALS = 3
-CMD_BREAKPOINTS = 4
+CMD_PRINT = 1 # Message(CMD_PRINT, extra=expression)
+CMD_BACKTRACE = 2 # Message(CMD_BACKTRACE)
+CMD_LOCALS = 3 # Message(CMD_LOCALS)
+CMD_BREAKPOINTS = 4 # Message(CMD_BREAKPOINTS, extra="\0-separated names")
+
+
+# the first message sent by the first child:
+# Message(ANSWER_INIT, INIT_VERSION_NUMBER, total_stop_points)
ANSWER_INIT = -20
-ANSWER_STD = -21
+
+# sent when the child is done and waiting for the next command:
+# Message(ANSWER_READY, current_time, currently_created_objects)
+ANSWER_READY = -21
+
+# sent after CMD_FORK:
+# Message(ANSWER_FORKED, child_pid)
ANSWER_FORKED = -22
+
+# sent when a child reaches the end (should not occur with process.py)
+# Message(ANSWER_AT_END) (the child exits afterwards)
ANSWER_AT_END = -23
+
+# breakpoint detected in CMD_FORWARD:
+# Message(ANSWER_BREAKPOINT, break_time, break_created_objects, bpkt_num)
+# if breakpoint_mode=='b': sent immediately when seeing a breakpoint,
+# followed by ANSWER_STD with the same time
+# if breakpoint_mode=='r': sent when we're done going forward, about
+# the most recently seen breakpoint
+# if breakpoint_mode=='i': ignored, never sent
ANSWER_BREAKPOINT = -24
+# print text to the console, for CMD_PRINT and others
+# Message(ANSWER_TEXT, extra=text)
ANSWER_TEXT = 20
+# ____________________________________________________________
+
+
class Message(object):
"""Represent messages sent and received to subprocesses
started with --revdb-replay.
diff --git a/rpython/translator/revdb/process.py
b/rpython/translator/revdb/process.py
--- a/rpython/translator/revdb/process.py
+++ b/rpython/translator/revdb/process.py
@@ -1,5 +1,4 @@
import sys, os, struct, socket, errno, subprocess
-from contextlib import contextmanager
from rpython.translator.revdb import ancillary
from rpython.translator.revdb.message import *
@@ -8,7 +7,8 @@
class Breakpoint(Exception):
- def __init__(self, num):
+ def __init__(self, time, num):
+ self.time = time
self.num = num
@@ -58,8 +58,8 @@
assert msg.extra == extra
return msg
- def expect_std(self):
- msg = self.expect(ANSWER_STD, Ellipsis, Ellipsis)
+ def expect_ready(self):
+ msg = self.expect(ANSWER_READY, Ellipsis, Ellipsis)
self.update_times(msg)
def update_times(self, msg):
@@ -74,12 +74,12 @@
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)
+ msg = self.expect(ANSWER_FORKED, Ellipsis)
child_pid = msg.arg3
+ self.expect_ready()
other = ReplayProcess(child_pid, s1,
breakpoints_cache=self.breakpoints_cache)
- other.update_times(msg)
+ other.expect_ready()
return other
def close(self):
@@ -89,23 +89,25 @@
except socket.error:
pass
- def forward(self, steps):
- """Move this subprocess forward in time."""
+ def forward(self, steps, breakpoint_mode='b'):
+ """Move this subprocess forward in time.
+ Returns the Breakpoint or None.
+ """
assert not self.tainted
- self.send(Message(CMD_FORWARD, steps))
+ self.send(Message(CMD_FORWARD, steps, ord(breakpoint_mode)))
#
- msg = self.recv()
- if msg.cmd == ANSWER_BREAKPOINT:
- bkpt_num = msg.arg3
+ # record the first ANSWER_BREAKPOINT, drop the others
+ # (in corner cases only could we get more than one)
+ bkpt = None
+ while True:
msg = self.recv()
- else:
- bkpt_num = None
- assert msg.cmd == ANSWER_STD
+ if msg.cmd != ANSWER_BREAKPOINT:
+ break
+ if bkpt is None:
+ bkpt = Breakpoint(msg.arg1, msg.arg3)
+ assert msg.cmd == ANSWER_READY
self.update_times(msg)
- #
- if bkpt_num is not None:
- raise Breakpoint(bkpt_num)
- return msg
+ return bkpt
def print_text_answer(self):
while True:
@@ -113,7 +115,7 @@
if msg.cmd == ANSWER_TEXT:
sys.stdout.write(msg.extra)
sys.stdout.flush()
- elif msg.cmd == ANSWER_STD:
+ elif msg.cmd == ANSWER_READY:
self.update_times(msg)
break
else:
@@ -136,7 +138,7 @@
child = ReplayProcess(initial_subproc.pid, s1)
msg = child.expect(ANSWER_INIT, INIT_VERSION_NUMBER, Ellipsis)
self.total_stop_points = msg.arg2
- child.expect_std()
+ child.expect_ready()
self.active = child
self.paused = {1: child.clone()} # {time: subprocess}
@@ -148,7 +150,7 @@
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)
+ return self.active.expect(ANSWER_READY, time, Ellipsis)
def get_max_time(self):
return self.total_stop_points
@@ -165,15 +167,21 @@
def is_tainted(self):
return self.active.tainted
- def go_forward(self, steps):
+ def go_forward(self, steps, breakpoint_mode='b'):
"""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. Note that
is_tainted() must return false in order to use this.
+
+ breakpoint_mode:
+ 'b' = regular mode where hitting a breakpoint stops
+ 'i' = ignore breakpoints
+ 'r' = record the occurrence of a breakpoint but continue
"""
assert steps >= 0
self.update_breakpoints()
+ latest_bkpt = None
while True:
cur_time = self.get_current_time()
if cur_time + steps > self.total_stop_points:
@@ -184,11 +192,50 @@
break
assert rel_next_clone >= 0
if rel_next_clone > 0:
- self.active.forward(rel_next_clone)
+ bkpt = self.active.forward(rel_next_clone, breakpoint_mode)
+ if breakpoint_mode == 'r':
+ latest_bkpt = bkpt or latest_bkpt
+ elif bkpt:
+ raise bkpt
steps -= rel_next_clone
clone = self.active.clone()
self.paused[clone.current_time] = clone
- self.active.forward(steps)
+ bkpt = self.active.forward(steps, breakpoint_mode)
+ if breakpoint_mode == 'r':
+ bkpt = bkpt or latest_bkpt
+ if bkpt:
+ raise bkpt
+
+ def go_backward(self, steps):
+ """Go backward, for the given number of 'steps' of time.
+
+ Closes the active process. Implemented as jump_in_time()
+ and then forward-searching for breakpoint locations (if any).
+ """
+ assert steps >= 0
+ initial_time = self.get_current_time()
+ if not self.breakpoints:
+ self.jump_in_time(initial_time - steps)
+ else:
+ self._backward_search_forward(initial_time - 957, initial_time)
+
+ def _backward_search_forward(self, search_start_time, search_stop_time):
+ while True:
+ self.jump_in_time(search_start_time)
+ search_start_time = self.get_current_time()
+ time_range_to_search = search_stop_time - search_start_time
+ if time_range_to_search <= 0:
+ print "[not found]"
+ return
+ print "[searching %d..%d]\n" % (search_start_time,
+ search_stop_time)
+ self.go_forward(time_range_to_search, breakpoint_mode='r')
+ # If at least one breakpoint was found, the Breakpoint
+ # exception is raised with the *last* such breakpoint.
+ # Otherwise, we continue here. Search farther along a
+ # 3-times-bigger range.
+ search_stop_time = search_start_time
+ search_start_time -= time_range_to_search * 3
def update_breakpoints(self):
if self.active.breakpoints_cache != self.breakpoints:
@@ -200,7 +247,7 @@
extra = '\x00'.join(breakpoints)
self.active.breakpoints_cache = None
self.active.send(Message(CMD_BREAKPOINTS, extra=extra))
- self.active.expect_std()
+ self.active.expect_ready()
self.active.breakpoints_cache = self.breakpoints.copy()
def _resume(self, from_time):
@@ -218,17 +265,8 @@
if target_time > self.total_stop_points:
target_time = self.total_stop_points
self._resume(max(time for time in self.paused if time <= target_time))
- with self._breakpoints_disabled():
- self.go_forward(target_time - self.get_current_time())
-
- @contextmanager
- def _breakpoints_disabled(self):
- old = self.breakpoints
- self.breakpoints = {}
- try:
- yield
- finally:
- self.breakpoints = old
+ self.go_forward(target_time - self.get_current_time(),
+ breakpoint_mode='i')
def close(self):
"""Close all subprocesses.
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
@@ -197,7 +197,7 @@
#define CMD_FORWARD (-3)
#define ANSWER_INIT (-20)
-#define ANSWER_STD (-21)
+#define ANSWER_READY (-21)
#define ANSWER_FORKED (-22)
#define ANSWER_AT_END (-23)
#define ANSWER_BREAKPOINT (-24)
@@ -212,6 +212,9 @@
static jmp_buf jmp_buf_cancel_execution;
static void (*pending_after_forward)(void);
static RPyString *empty_string;
+static uint64_t last_recorded_breakpoint_loc;
+static int last_recorded_breakpoint_num;
+static char breakpoint_mode;
static void attach_gdb(void)
{
@@ -281,13 +284,6 @@
write_sock(&c, sizeof(c));
}
-static void answer_std(void)
-{
- 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)
{
RPyString *s = malloc(sizeof(RPyString) + length);
@@ -367,7 +363,6 @@
empty_string = make_rpy_string(0);
write_answer(ANSWER_INIT, INIT_VERSION_NUMBER, total_stop_points, 0);
- pending_after_forward = &answer_std;
/* ignore the SIGCHLD signals so that child processes don't become
zombies */
@@ -518,11 +513,19 @@
}
else {
/* in the parent */
- write_answer(ANSWER_FORKED, stopped_time, stopped_uid, child_pid);
+ write_answer(ANSWER_FORKED, child_pid, 0, 0);
close(child_sockfd);
}
}
+static void answer_recorded_breakpoint(void)
+{
+ if (last_recorded_breakpoint_loc != 0) {
+ write_answer(ANSWER_BREAKPOINT, last_recorded_breakpoint_loc,
+ 0, last_recorded_breakpoint_num);
+ }
+}
+
static void command_forward(rpy_revdb_command_t *cmd)
{
if (cmd->arg1 < 0) {
@@ -530,7 +533,11 @@
exit(1);
}
rpy_revdb.stop_point_break = stopped_time + cmd->arg1;
- pending_after_forward = &answer_std;
+ breakpoint_mode = (char)cmd->arg2;
+ if (breakpoint_mode == 'r') {
+ last_recorded_breakpoint_loc = 0;
+ pending_after_forward = &answer_recorded_breakpoint;
+ }
}
static void command_default(rpy_revdb_command_t *cmd, char *extra)
@@ -551,7 +558,6 @@
s = make_rpy_string(cmd->extra_size);
memcpy(_RPyString_AsString(s), extra, cmd->extra_size);
}
- pending_after_forward = &answer_std;
execute_rpy_function(rpy_revdb_command_funcs[i], cmd, s);
}
@@ -562,6 +568,7 @@
stopped_time = rpy_revdb.stop_point_seen;
stopped_uid = rpy_revdb.unique_id_seen;
rpy_revdb.unique_id_seen = (-1ULL) << 63;
+ breakpoint_mode = 0;
if (pending_after_forward) {
void (*fn)(void) = pending_after_forward;
@@ -570,6 +577,7 @@
}
else {
rpy_revdb_command_t cmd;
+ write_answer(ANSWER_READY, stopped_time, stopped_uid, 0);
read_sock(&cmd, sizeof(cmd));
char extra[cmd.extra_size + 1];
@@ -655,8 +663,20 @@
"debug command\n");
exit(1);
}
- rpy_revdb.stop_point_break = rpy_revdb.stop_point_seen + 1;
- write_answer(ANSWER_BREAKPOINT, rpy_revdb.stop_point_break, 0, num);
+
+ switch (breakpoint_mode) {
+ case 'i':
+ return; /* ignored breakpoints */
+
+ case 'r': /* record the breakpoint but continue */
+ last_recorded_breakpoint_loc = rpy_revdb.stop_point_seen + 1;
+ last_recorded_breakpoint_num = num;
+ return;
+
+ default: /* 'b' or '\0' for default handling of breakpoints */
+ rpy_revdb.stop_point_break = rpy_revdb.stop_point_seen + 1;
+ write_answer(ANSWER_BREAKPOINT, rpy_revdb.stop_point_break, 0, num);
+ }
}
RPY_EXTERN
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
@@ -220,7 +220,7 @@
self.subproc = subproc
child = ReplayProcess(subproc.pid, s1)
child.expect(ANSWER_INIT, INIT_VERSION_NUMBER, 3)
- child.expect(ANSWER_STD, 1, Ellipsis)
+ child.expect(ANSWER_READY, 1, Ellipsis)
return child
@@ -245,7 +245,7 @@
def test_go(self):
child = self.replay()
child.send(Message(CMD_FORWARD, 2))
- child.expect(ANSWER_STD, 3, Ellipsis)
+ child.expect(ANSWER_READY, 3, Ellipsis)
child.send(Message(CMD_FORWARD, 2))
child.expect(ANSWER_AT_END)
@@ -258,9 +258,9 @@
child = self.replay()
child2 = child.clone()
child.send(Message(CMD_FORWARD, 2))
- child.expect(ANSWER_STD, 3, Ellipsis)
+ child.expect(ANSWER_READY, 3, Ellipsis)
child2.send(Message(CMD_FORWARD, 1))
- child2.expect(ANSWER_STD, 2, Ellipsis)
+ child2.expect(ANSWER_READY, 2, Ellipsis)
#
child.close()
child2.close()
@@ -306,8 +306,8 @@
if extra == 'get-value':
revdb.send_answer(100, revdb.current_time(),
revdb.total_time())
- if extra == 'go-fw':
- revdb.go_forward(1, went_fw)
+ ## if extra == 'go-fw':
+ ## revdb.go_forward(1, went_fw)
## if cmdline == 'set-break-after-0':
## dbstate.break_after = 0
## if cmdline == 'print-id':
@@ -380,11 +380,11 @@
child.send(Message(1, extra='get-value'))
child.expect(100, 1, 3)
- def test_go_fw(self):
- child = self.replay()
- child.send(Message(1, extra='go-fw'))
- child.expect(42, 1, -43, -44, 'go-fw')
- child.expect(120, 2)
- child.expect(120, 3)
- child.send(Message(CMD_FORWARD, 0))
- child.expect(ANSWER_STD, 3, Ellipsis)
+ ## def test_go_fw(self):
+ ## child = self.replay()
+ ## child.send(Message(1, extra='go-fw'))
+ ## child.expect(42, 1, -43, -44, 'go-fw')
+ ## child.expect(120, 2)
+ ## child.expect(120, 3)
+ ## child.send(Message(CMD_FORWARD, 0))
+ ## child.expect(ANSWER_READY, 3, Ellipsis)
diff --git a/rpython/translator/revdb/test/test_process.py
b/rpython/translator/revdb/test/test_process.py
--- a/rpython/translator/revdb/test/test_process.py
+++ b/rpython/translator/revdb/test/test_process.py
@@ -16,7 +16,7 @@
pass
class DBState:
- break_loop = -1
+ break_loop = -2
dbstate = DBState()
def blip(cmd, extra):
@@ -32,7 +32,7 @@
for i, op in enumerate(argv[1:]):
dbstate.stuff = Stuff()
dbstate.stuff.x = i + 1000
- if dbstate.break_loop == i:
+ if i == dbstate.break_loop or i == dbstate.break_loop + 1:
revdb.breakpoint(99)
revdb.stop_point()
print op
@@ -61,11 +61,29 @@
group.jump_in_time(target_time)
group._check_current_time(target_time)
- def test_breakpoint(self):
+ def test_breakpoint_b(self):
group = ReplayProcessGroup(str(self.exename), self.rdbname)
group.active.send(Message(1, 6, extra='set-breakpoint'))
group.active.expect(42, 1, -43, -44, 'set-breakpoint')
- group.active.expect(ANSWER_STD, 1, Ellipsis)
- e = py.test.raises(Breakpoint, group.go_forward, 10)
+ group.active.expect(ANSWER_READY, 1, Ellipsis)
+ e = py.test.raises(Breakpoint, group.go_forward, 10, 'b')
+ assert e.value.time == 7
assert e.value.num == 99
group._check_current_time(7)
+
+ def test_breakpoint_r(self):
+ group = ReplayProcessGroup(str(self.exename), self.rdbname)
+ group.active.send(Message(1, 6, extra='set-breakpoint'))
+ group.active.expect(42, 1, -43, -44, 'set-breakpoint')
+ group.active.expect(ANSWER_READY, 1, Ellipsis)
+ e = py.test.raises(Breakpoint, group.go_forward, 10, 'r')
+ assert e.value.time == 8
+ assert e.value.num == 99
+ group._check_current_time(10)
+
+ def test_breakpoint_i(self):
+ group = ReplayProcessGroup(str(self.exename), self.rdbname)
+ group.active.send(Message(1, 6, extra='set-breakpoint'))
+ group.active.expect(42, 1, -43, -44, 'set-breakpoint')
+ group.active.expect(ANSWER_READY, 1, Ellipsis)
+ group.go_forward(10, 'i') # does not raise Breakpoint
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit