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

Reply via email to