Author: Armin Rigo <[email protected]>
Branch: reverse-debugger
Changeset: r85012:0e59d36285f8
Date: 2016-06-07 22:53 +0200
http://bitbucket.org/pypy/pypy/changeset/0e59d36285f8/
Log: Documentation (not implemented yet). Other clean-ups.
diff --git a/rpython/translator/c/src/entrypoint.c
b/rpython/translator/c/src/entrypoint.c
--- a/rpython/translator/c/src/entrypoint.c
+++ b/rpython/translator/c/src/entrypoint.c
@@ -104,7 +104,7 @@
exitcode = STANDALONE_ENTRY_POINT(argc, argv);
#ifdef RPY_REVERSE_DEBUGGER
- rpy_reverse_db_teardown(&exitcode);
+ rpy_reverse_db_teardown();
#endif
pypy_debug_alloc_results();
diff --git a/rpython/translator/revdb/rdb-src/revdb.c
b/rpython/translator/revdb/rdb-src/revdb.c
--- a/rpython/translator/revdb/rdb-src/revdb.c
+++ b/rpython/translator/revdb/rdb-src/revdb.c
@@ -2,6 +2,7 @@
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
+#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
@@ -11,6 +12,14 @@
#define RDB_VERSION 0x00FF0001
+typedef struct {
+ Signed signature, version;
+ Signed reserved1, reserved2;
+ int argc;
+ char **argv;
+} rdb_header_t;
+
+
rpy_revdb_t rpy_revdb;
static char rpy_rev_buffer[16384];
static int rpy_rev_fileno = -1;
@@ -18,7 +27,7 @@
static void setup_record_mode(int argc, char *argv[]);
static void setup_replay_mode(int *argc_p, char **argv_p[]);
-static void check_at_end(int exitcode, int *exitcode_p, uint64_t stop_points);
+static void check_at_end(uint64_t stop_points);
RPY_EXTERN
void rpy_reverse_db_setup(int *argc_p, char **argv_p[])
@@ -44,18 +53,16 @@
}
RPY_EXTERN
-void rpy_reverse_db_teardown(int *exitcode_p)
+void rpy_reverse_db_teardown(void)
{
- int exitcode;
uint64_t stop_points;
- RPY_REVDB_EMIT(exitcode = *exitcode_p; , int _e, exitcode);
RPY_REVDB_EMIT(stop_points = rpy_revdb.stop_point_seen; ,
uint64_t _e, stop_points);
if (!RPY_RDB_REPLAY)
rpy_reverse_db_flush();
else
- check_at_end(exitcode, exitcode_p, stop_points);
+ check_at_end(stop_points);
}
@@ -64,10 +71,27 @@
/* ------------------------------------------------------------ */
+static void write_all(int fd, const char *buf, ssize_t count)
+{
+ while (count > 0) {
+ ssize_t wsize = write(fd, buf, count);
+ if (wsize <= 0) {
+ if (wsize == 0)
+ fprintf(stderr, "Writing to PYPYREVDB file: "
+ "unexpected non-blocking mode\n");
+ else
+ fprintf(stderr, "Fatal error: writing to PYPYREVDB file:
%m\n");
+ abort();
+ }
+ buf += wsize;
+ count -= wsize;
+ }
+}
+
static void setup_record_mode(int argc, char *argv[])
{
char *filename = getenv("PYPYREVDB");
- Signed x;
+ rdb_header_t h;
assert(RPY_RDB_REPLAY == 0);
rpy_revdb.buf_p = rpy_rev_buffer;
@@ -85,12 +109,12 @@
atexit(rpy_reverse_db_flush);
}
- RPY_REVDB_EMIT(x = RDB_SIGNATURE; , Signed _e, x);
- RPY_REVDB_EMIT(x = RDB_VERSION; , Signed _e, x);
- RPY_REVDB_EMIT(x = 0; , Signed _e, x);
- RPY_REVDB_EMIT(x = 0; , Signed _e, x);
- RPY_REVDB_EMIT(x = argc; , Signed _e, x);
- RPY_REVDB_EMIT(x = (Signed)argv; , Signed _e, x);
+ memset(&h, 0, sizeof(h));
+ h.signature = RDB_SIGNATURE;
+ h.version = RDB_VERSION;
+ h.argc = argc;
+ h.argv = argv;
+ write_all(rpy_rev_fileno, (const char *)&h, sizeof(h));
}
RPY_EXTERN
@@ -104,22 +128,7 @@
if (size == 0 || rpy_rev_fileno < 0)
return;
- p = rpy_rev_buffer;
- retry:
- wsize = write(rpy_rev_fileno, p, size);
- if (wsize >= size)
- return;
- if (wsize <= 0) {
- if (wsize == 0)
- fprintf(stderr, "Writing to PYPYREVDB file: "
- "unexpected non-blocking mode\n");
- else
- fprintf(stderr, "Fatal error: writing to PYPYREVDB file: %m\n");
- abort();
- }
- p += wsize;
- size -= wsize;
- goto retry;
+ write_all(rpy_rev_fileno, rpy_rev_buffer, size);
}
@@ -128,12 +137,87 @@
/* ------------------------------------------------------------ */
+/* How it works: the main process reads the RevDB file and
+ reconstructs the GC objects, in effect replaying their content, for
+ the complete duration of the original program. During this
+ replaying, it forks a fixed number of frozen processes which sit
+ around, each keeping the version of the GC objects contents at some
+ known version. We have one pipe for every frozen process, which
+ the frozen process is blocked reading.
+
+ [main process]
+ [frozen process 1]
+ [frozen process 2]
+ [debugging process]
+ [frozen process 3]
+ [frozen process 4]
+ ...
+ [frozen process n]
+
+ When all frozen processes are made, the main process enters
+ interactive mode. In interactive mode, the main process reads from
+ stdin a version number to go to. It picks the correct frozen
+ process (the closest one that is before in time); let's say it is
+ process #p. It sends the version number to it by writing to pipe
+ #p. The corresponding frozen process wakes up, and forks again
+ into a debugging process. The main and the frozen process then
+ block.
+
+ The debugging process first goes forward in time until it reaches
+ the right version number. Then it interacts with the user (or a
+ pdb-like program outside) on stdin/stdout. This goes on until an
+ "exit" command is received and the debugging process dies. At that
+ point its parent (the frozen process) continues and signals its own
+ parent (the main process) by writing to a separate signalling pipe.
+ The main process then wakes up again, and the loop closes: it reads
+ on stdin the next version number that we're interested in, and asks
+ the right frozen process to make a debugging process again.
+
+ Note how we have, over time, several processes that read and
+ process stdin; it should work because they are strictly doing that
+ in sequence, never concurrently. To avoid the case where stdin is
+ buffered inside one process but a different process should read it,
+ we write markers to stdout when such switches occur. The outside
+ controlling program must wait until it sees these markers before
+ writing more data.
+*/
+
+#define FROZEN_PROCESSES 30
+#define GOLDEN_RATIO 0.618034
+
+static uint64_t total_stop_points;
+
+
+static ssize_t read_at_least(int fd, char *buf,
+ ssize_t count_min, ssize_t count_max)
+{
+ ssize_t result = 0;
+ assert(count_min <= count_max);
+ while (result < count_min) {
+ ssize_t rsize = read(fd, buf + result, count_max - result);
+ if (rsize <= 0) {
+ if (rsize == 0)
+ fprintf(stderr, "RevDB file appears truncated\n");
+ else
+ fprintf(stderr, "RevDB file read error: %m\n");
+ exit(1);
+ }
+ result += rsize;
+ }
+ return result;
+}
+
+static void read_all(int fd, char *buf, ssize_t count)
+{
+ (void)read_at_least(fd, buf, count, count);
+}
+
static void setup_replay_mode(int *argc_p, char **argv_p[])
{
- Signed x;
int argc = *argc_p;
char **argv = *argv_p;
char *filename;
+ rdb_header_t h;
if (argc != 3) {
fprintf(stderr, "syntax: %s --replay <RevDB-file>\n", argv[0]);
@@ -148,37 +232,51 @@
}
assert(RPY_RDB_REPLAY == 1);
+
+ read_all(rpy_rev_fileno, (char *)&h, sizeof(h));
+
+ if (h.signature != RDB_SIGNATURE) {
+ fprintf(stderr, "'%s' is not a RevDB file (or wrong platform)\n",
+ filename);
+ exit(1);
+ }
+ if (h.version != RDB_VERSION) {
+ fprintf(stderr, "RevDB file version mismatch (got %lx, expected
%lx)\n",
+ (long)h.version, (long)RDB_VERSION);
+ exit(1);
+ }
+ *argc_p = h.argc;
+ *argv_p = h.argv;
+
+ if (lseek(rpy_rev_fileno, -sizeof(uint64_t), SEEK_END) < 0 ||
+ read(rpy_rev_fileno, &total_stop_points,
+ sizeof(uint64_t)) != sizeof(uint64_t) ||
+ lseek(rpy_rev_fileno, sizeof(h), SEEK_SET) != sizeof(h)) {
+ fprintf(stderr, "%s: %m\n", filename);
+ exit(1);
+ }
+
rpy_revdb.buf_p = rpy_rev_buffer;
rpy_revdb.buf_limit = rpy_rev_buffer;
-
- RPY_REVDB_EMIT(abort();, Signed _e, x);
- if (x != RDB_SIGNATURE) {
- fprintf(stderr, "stdin is not a RevDB file (or wrong platform)\n");
- exit(1);
- }
- RPY_REVDB_EMIT(abort();, Signed _e, x);
- if (x != RDB_VERSION) {
- fprintf(stderr, "RevDB file version mismatch (got %lx, expected
%lx)\n",
- (long)x, (long)RDB_VERSION);
- exit(1);
- }
- RPY_REVDB_EMIT(abort();, Signed _e, x); /* ignored */
- RPY_REVDB_EMIT(abort();, Signed _e, x); /* ignored */
-
- RPY_REVDB_EMIT(abort();, Signed _e, x);
- if (x <= 0) {
- fprintf(stderr, "RevDB file is bogus\n");
- exit(1);
- }
- *argc_p = x;
-
- RPY_REVDB_EMIT(abort();, Signed _e, x);
- *argv_p = (char **)x;
-
rpy_revdb.stop_point_break = 1;
}
-static void check_at_end(int exitcode, int *exitcode_p, uint64_t stop_points)
+RPY_EXTERN
+char *rpy_reverse_db_fetch(int expected_size)
+{
+ ssize_t rsize, keep = rpy_revdb.buf_limit - rpy_revdb.buf_p;
+ assert(keep >= 0);
+ memmove(rpy_rev_buffer, rpy_revdb.buf_p, keep);
+ rsize = read_at_least(rpy_rev_fileno,
+ rpy_rev_buffer + keep,
+ expected_size - keep,
+ sizeof(rpy_rev_buffer) - keep);
+ rpy_revdb.buf_p = rpy_rev_buffer;
+ rpy_revdb.buf_limit = rpy_rev_buffer + keep + rsize;
+ return rpy_rev_buffer;
+}
+
+static void check_at_end(uint64_t stop_points)
{
char dummy[1];
if (stop_points != rpy_revdb.stop_point_seen) {
@@ -190,41 +288,12 @@
fprintf(stderr, "RevDB file error: corrupted file (too much data?)\n");
exit(1);
}
- if (*exitcode_p != exitcode) {
- fprintf(stderr, "Bogus exit code\n");
+ if (stop_points != total_stop_points) {
+ fprintf(stderr, "RevDB file modified while reading?\n");
exit(1);
}
- printf("Replaying finished (exit code %d)\n", exitcode);
- rpy_reverse_db_break(0);
- *exitcode_p = 0;
-}
-
-RPY_EXTERN
-char *rpy_reverse_db_fetch(int expected_size)
-{
- ssize_t rsize, keep = rpy_revdb.buf_limit - rpy_revdb.buf_p;
- assert(keep >= 0);
- memmove(rpy_rev_buffer, rpy_revdb.buf_p, keep);
-
- retry:
- rsize = read(rpy_rev_fileno, rpy_rev_buffer + keep,
- sizeof(rpy_rev_buffer) - keep);
- if (rsize <= 0) {
- if (rsize == 0)
- fprintf(stderr, "RevDB file appears truncated\n");
- else
- fprintf(stderr, "RevDB file read error: %m\n");
- exit(1);
- }
- keep += rsize;
-
- rpy_revdb.buf_p = rpy_rev_buffer;
- rpy_revdb.buf_limit = rpy_rev_buffer + keep;
-
- if (rpy_revdb.buf_limit - rpy_revdb.buf_p < expected_size)
- goto retry;
-
- return rpy_rev_buffer;
+ printf("Replaying finished, %lld stop points\n", (long long)stop_points);
+ exit(0);
}
RPY_EXTERN
diff --git a/rpython/translator/revdb/rdb-src/revdb_include.h
b/rpython/translator/revdb/rdb-src/revdb_include.h
--- a/rpython/translator/revdb/rdb-src/revdb_include.h
+++ b/rpython/translator/revdb/rdb-src/revdb_include.h
@@ -26,7 +26,7 @@
/* ------------------------------------------------------------ */
RPY_EXTERN void rpy_reverse_db_setup(int *argc_p, char **argv_p[]);
-RPY_EXTERN void rpy_reverse_db_teardown(int *exitcode_p);
+RPY_EXTERN void rpy_reverse_db_teardown(void);
#define RPY_REVDB_EMIT(normal_code, decl_e, variable) \
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
@@ -93,17 +93,15 @@
# write() call
x = rdb.next(); assert x == len('[abc, d]\n')
x = rdb.next('i'); assert x == 0 # errno
- x = rdb.next('i'); assert x == 9 # exitcode
x = rdb.next('q'); assert x == 0 # number of stop points
- # that's all that should get from this simple example
+ # that's all we should get from this simple example
assert rdb.done()
#
assert got == [self.exename, 'abc', 'd']
#
# Now try the replay mode (just "doesn't crash" for now)
out = replay()
- assert out == ("Replaying finished (exit code 9)\n"
- "break #0 after 0 stop points\n")
+ assert out == "Replaying finished, 0 stop points\n"
def test_simple_interpreter(self):
def main(argv):
@@ -116,5 +114,4 @@
assert self.fetch_rdb().number_of_stop_points() == 3
out = replay()
assert out == ("break #42 after 1 stop points\n"
- "Replaying finished (exit code 9)\n"
- "break #0 after 3 stop points\n")
+ "Replaying finished, 3 stop points\n")
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit