Author: Armin Rigo <ar...@tunes.org> Branch: reverse-debugger Changeset: r85419:9b947e1ab238 Date: 2016-06-28 10:07 +0200 http://bitbucket.org/pypy/pypy/changeset/9b947e1ab238/
Log: Clean up the breakpoint system, with a single 'stop_point_break' controlled from several internal variables in revdb.c 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 @@ -65,6 +65,13 @@ runner(argument) except Exception as e: traceback.print_exc() + print >> sys.stderr + print >> sys.stderr, 'Something went wrong. You are now', + print >> sys.stderr, 'in a pdb; press Ctrl-D to continue.' + import pdb; pdb.post_mortem(sys.exc_info()[2]) + print >> sys.stderr + print >> sys.stderr, 'You are back running %s.' % ( + sys.argv[0],) def command_help(self, argument): """Display commands summary""" 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 @@ -8,6 +8,7 @@ CMD_QUIT = -2 # Message(CMD_QUIT) CMD_FORWARD = -3 # Message(CMD_FORWARD, steps, breakpoint_mode) CMD_FUTUREIDS = -4 # Message(CMD_FUTUREIDS, extra=list-of-8bytes-uids) +CMD_PING = -5 # Message(CMD_PING) # extra commands which are not handled by revdb.c, but # by revdb.register_debug_command() CMD_PRINT = 1 # Message(CMD_PRINT, extra=expression) 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 @@ -139,6 +139,14 @@ Returns the Breakpoint or None. """ assert not self.tainted + + # <DEBUGGING ONLY> + currents = self.current_time, self.currently_created_objects + self.send(Message(CMD_PING)) + self.expect_ready() + assert currents == (self.current_time, self.currently_created_objects) + # </DEBUGGING ONLY> + self.send(Message(CMD_FORWARD, steps, ord(breakpoint_mode))) # # record the first ANSWER_BREAKPOINT, drop the others @@ -150,7 +158,7 @@ break if bkpt is None: bkpt = Breakpoint(msg.arg1, msg.arg3) - assert msg.cmd == ANSWER_READY + assert msg.cmd == ANSWER_READY, msg self.update_times(msg) return bkpt 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 @@ -28,6 +28,10 @@ #define ASYNC_FINALIZER_TRIGGER ((int16_t)0xff46) +#define FID_REGULAR_MODE 'R' +#define FID_SAVED_STATE 'S' +#define FID_JMPBUF_PROTECTED 'J' + typedef struct { Signed version; @@ -40,7 +44,7 @@ rpy_revdb_t rpy_revdb; static char rpy_rev_buffer[16384]; /* max. 32768 */ static int rpy_rev_fileno = -1; -static unsigned char flag_io_disabled; +static char flag_io_disabled = FID_REGULAR_MODE; static void setup_record_mode(int argc, char *argv[]); @@ -199,6 +203,7 @@ They are only invoked when we call GC_invoke_finalizers(), which we only do at stop points in the case of revdb. */ + assert(!RPY_RDB_REPLAY); assert(rpy_revdb.stop_point_break <= rpy_revdb.stop_point_seen + 1); rpy_revdb.stop_point_break = rpy_revdb.stop_point_seen + 1; } @@ -214,26 +219,21 @@ static void record_stop_point(void) { - /* Invoke the finalizers now. This will call boehm_fq_callback(), - which will enqueue the objects in the correct FinalizerQueue. - Then, call boehm_fq_trigger(), which calls finalizer_trigger(). - */ + /* ===== FINALIZERS ===== + + When the GC wants to invoke some finalizers, it causes this + to be called at the stop point. The new-style finalizers + are only enqueued at this point. The old-style finalizers + run immediately, conceptually just *after* the stop point. + */ int i; char *p = rpy_rev_buffer; int64_t done; + /* Write an ASYNC_FINALIZER_TRIGGER packet */ rpy_reverse_db_flush(); + assert(current_packet_size() == 0); - fq_trigger(); - - /* This should be done without emitting anything to the rdb - log. We check that, and emit just a ASYNC_FINALIZER_TRIGGER. - */ - if (current_packet_size() != 0) { - fprintf(stderr, - "record_stop_point emitted unexpected data into the rdb log\n"); - exit(1); - } *(int16_t *)p = ASYNC_FINALIZER_TRIGGER; memcpy(rpy_revdb.buf_p, &rpy_revdb.stop_point_seen, sizeof(uint64_t)); rpy_revdb.buf_p += sizeof(uint64_t); @@ -251,6 +251,10 @@ GC_invoke_finalizers(); in_invoke_finalizers--; RPY_REVDB_EMIT(done = -1;, int64_t _e, done); + + /* Now we're back in normal mode. We trigger the finalizer + queues here. */ + fq_trigger(); } RPY_EXTERN @@ -276,7 +280,7 @@ /* Boehm only */ if (obj->h_hash == 0) { Signed h; - if (flag_io_disabled) { + if (flag_io_disabled != FID_REGULAR_MODE) { /* This is when running debug commands. Don't cache the hash on the object at all. */ return ~((Signed)obj); @@ -378,7 +382,7 @@ r->re_addr = target; r->re_off_prev = 0; - if (!flag_io_disabled) { + if (flag_io_disabled == FID_REGULAR_MODE) { char alive; /* Emit WEAKREF_AFTERWARDS_DEAD, but remember where we emit it. If we deref the weakref and it is still alive, we will patch @@ -415,7 +419,7 @@ { struct WEAKLINK *r = (struct WEAKLINK *)weakref; void *result = r->re_addr; - if (result && !flag_io_disabled) { + if (result && flag_io_disabled == FID_REGULAR_MODE) { char alive; if (!RPY_RDB_REPLAY) { if (r->re_off_prev <= 0) { @@ -471,6 +475,7 @@ #define CMD_QUIT (-2) #define CMD_FORWARD (-3) #define CMD_FUTUREIDS (-4) +#define CMD_PING (-5) #define ANSWER_INIT (-20) #define ANSWER_READY (-21) @@ -482,16 +487,14 @@ static int rpy_rev_sockfd; static const char *rpy_rev_filename; -static uint64_t stopped_time; -static uint64_t stopped_uid; +static uint64_t interactive_break = 1, finalizer_break = -1, uid_break = -1; static uint64_t total_stop_points; -static uint64_t finalizer_trigger_saved_break; 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 char breakpoint_mode = 'i'; static uint64_t *future_ids, *future_next_id; static void *finalizer_tree, *destructor_tree; @@ -586,6 +589,15 @@ } } +static void set_revdb_breakpoints(void) +{ + /* note: these are uint64_t, so '-1' is bigger than positive values */ + rpy_revdb.stop_point_break = (interactive_break < finalizer_break ? + interactive_break : finalizer_break); + rpy_revdb.unique_id_break = uid_break; + rpy_revdb.watch_enabled = (breakpoint_mode != 'i'); +} + static void setup_replay_mode(int *argc_p, char **argv_p[]) { int argc = *argc_p; @@ -637,8 +649,9 @@ rpy_revdb.buf_p = rpy_rev_buffer; rpy_revdb.buf_limit = rpy_rev_buffer; rpy_revdb.buf_readend = rpy_rev_buffer; - rpy_revdb.stop_point_break = 1; + rpy_revdb.stop_point_seen = 0; rpy_revdb.unique_id_seen = 1; + set_revdb_breakpoints(); empty_string = make_rpy_string(0); @@ -670,11 +683,15 @@ RPY_EXTERN void rpy_reverse_db_fetch(const char *file, int line) { - if (!flag_io_disabled) { + if (flag_io_disabled == FID_REGULAR_MODE) { ssize_t keep; ssize_t full_packet_size; int16_t header; + if (finalizer_break != (uint64_t)-1) { + fprintf(stderr, "reverse_db_fetch: finalizer_break != -1\n"); + exit(1); + } if (rpy_revdb.buf_limit != rpy_revdb.buf_p) { fprintf(stderr, "bad log format: incomplete packet\n"); exit(1); @@ -695,7 +712,8 @@ switch (header) { case ASYNC_FINALIZER_TRIGGER: - if (finalizer_trigger_saved_break != 0) { + //fprintf(stderr, "ASYNC_FINALIZER_TRIGGER\n"); + if (finalizer_break != (uint64_t)-1) { fprintf(stderr, "unexpected multiple " "ASYNC_FINALIZER_TRIGGER\n"); exit(1); @@ -709,10 +727,10 @@ fprintf(stderr, "invalid finalizer break point\n"); exit(1); } - finalizer_trigger_saved_break = rpy_revdb.stop_point_break; - rpy_revdb.stop_point_break = bp; + finalizer_break = bp; + set_revdb_breakpoints(); /* Now we should not fetch anything more until we reach - that finalizer_trigger_saved_break point. */ + that finalizer_break point. */ rpy_revdb.buf_limit = rpy_revdb.buf_p; return; @@ -734,64 +752,94 @@ */ fprintf(stderr, "%s:%d: Attempted to do I/O or access raw memory\n", file, line); - longjmp(jmp_buf_cancel_execution, 1); + if (flag_io_disabled == FID_JMPBUF_PROTECTED) { + longjmp(jmp_buf_cancel_execution, 1); + } + else { + fprintf(stderr, "but we are not in a jmpbuf_protected section\n"); + exit(1); + } } } -static rpy_revdb_t disabled_state; -static void *disabled_exc[2]; +static rpy_revdb_t saved_state; +static void *saved_exc[2]; -static void disable_io(void) +static void change_flag_io_disabled(char oldval, char newval) { - if (flag_io_disabled) { - fprintf(stderr, "unexpected recursive disable_io()\n"); + if (flag_io_disabled != oldval) { + fprintf(stderr, "change_flag_io_disabled(%c, %c) but got %c\n", + oldval, newval, flag_io_disabled); exit(1); } - disabled_state = rpy_revdb; /* save the complete struct */ - disabled_exc[0] = pypy_g_ExcData.ed_exc_type; - disabled_exc[1] = pypy_g_ExcData.ed_exc_value; + flag_io_disabled = newval; +} + +static void save_state(void) +{ + /* The program is switching from replaying execution to + time-paused mode. In time-paused mode, we can run more + app-level code like watch points or interactive prints, + but they must not be matched against the log, and they must + not involve generic I/O. + */ + change_flag_io_disabled(FID_REGULAR_MODE, FID_SAVED_STATE); + + saved_state = rpy_revdb; /* save the complete struct */ + + rpy_revdb.unique_id_seen = (-1ULL) << 63; + rpy_revdb.watch_enabled = 0; + rpy_revdb.stop_point_break = -1; + rpy_revdb.unique_id_break = -1; + rpy_revdb.buf_p = rpy_rev_buffer; /* anything readable */ + rpy_revdb.buf_limit = rpy_rev_buffer; /* same as buf_p */ +} + +static void restore_state(void) +{ + /* The program is switching from time-paused mode to replaying + execution. */ + change_flag_io_disabled(FID_SAVED_STATE, FID_REGULAR_MODE); + + /* restore the complete struct */ + rpy_revdb = saved_state; + + /* set the breakpoint fields to the current value of the *_break + global variables, which may be different from what is in + 'save_state' */ + set_revdb_breakpoints(); +} + +static void protect_jmpbuf(void) +{ + change_flag_io_disabled(FID_SAVED_STATE, FID_JMPBUF_PROTECTED); + saved_exc[0] = pypy_g_ExcData.ed_exc_type; + saved_exc[1] = pypy_g_ExcData.ed_exc_value; pypy_g_ExcData.ed_exc_type = NULL; pypy_g_ExcData.ed_exc_value = NULL; - rpy_revdb.buf_p = rpy_rev_buffer; /* anything readable */ - rpy_revdb.buf_limit = rpy_rev_buffer; /* same as buf_p */ - flag_io_disabled = 1; } -static void enable_io(int restore_breaks) +static void unprotect_jmpbuf(void) { - uint64_t v1, v2; - if (!flag_io_disabled) { - fprintf(stderr, "unexpected enable_io()\n"); - exit(1); - } - flag_io_disabled = 0; - + change_flag_io_disabled(FID_JMPBUF_PROTECTED, FID_SAVED_STATE); if (pypy_g_ExcData.ed_exc_type != NULL) { fprintf(stderr, "Command crashed with %.*s\n", (int)(pypy_g_ExcData.ed_exc_type->ov_name->rs_chars.length), pypy_g_ExcData.ed_exc_type->ov_name->rs_chars.items); exit(1); } - /* restore the complete struct, with the exception of '*_break' */ - v1 = rpy_revdb.stop_point_break; - v2 = rpy_revdb.unique_id_break; - rpy_revdb = disabled_state; - if (!restore_breaks) { - rpy_revdb.stop_point_break = v1; - rpy_revdb.unique_id_break = v2; - } - pypy_g_ExcData.ed_exc_type = disabled_exc[0]; - pypy_g_ExcData.ed_exc_value = disabled_exc[1]; + pypy_g_ExcData.ed_exc_type = saved_exc[0]; + pypy_g_ExcData.ed_exc_value = saved_exc[1]; } static void execute_rpy_function(rpy_revdb_command_fn func, rpy_revdb_command_t *cmd, RPyString *extra) { - disable_io(); + protect_jmpbuf(); if (setjmp(jmp_buf_cancel_execution) == 0) func(cmd, extra); - enable_io(0); + unprotect_jmpbuf(); } static void check_at_end(uint64_t stop_points) @@ -888,13 +936,13 @@ fprintf(stderr, "CMD_FORWARD: negative step\n"); exit(1); } - rpy_revdb.stop_point_break = stopped_time + cmd->arg1; + assert(flag_io_disabled == FID_SAVED_STATE); + interactive_break = saved_state.stop_point_seen + cmd->arg1; breakpoint_mode = (char)cmd->arg2; if (breakpoint_mode == 'r') { last_recorded_breakpoint_loc = 0; pending_after_forward = &answer_recorded_breakpoint; } - rpy_revdb.watch_enabled = (breakpoint_mode != 'i'); } static void command_future_ids(rpy_revdb_command_t *cmd, char *extra) @@ -902,7 +950,7 @@ free(future_ids); if (cmd->extra_size == 0) { future_ids = NULL; - rpy_revdb.unique_id_break = 0; + uid_break = 0; } else { assert(cmd->extra_size % sizeof(uint64_t) == 0); @@ -914,7 +962,7 @@ } memcpy(future_ids, extra, cmd->extra_size); future_ids[cmd->extra_size / sizeof(uint64_t)] = 0; - rpy_revdb.unique_id_break = *future_ids; + uid_break = *future_ids; } future_next_id = future_ids; } @@ -940,41 +988,16 @@ execute_rpy_function(rpy_revdb_commands.rp_funcs[i], cmd, s); } -static void save_state(void) -{ - stopped_time = rpy_revdb.stop_point_seen; - stopped_uid = rpy_revdb.unique_id_seen; - rpy_revdb.unique_id_seen = (-1ULL) << 63; - rpy_revdb.watch_enabled = 0; -} - -static void restore_state(void) -{ - rpy_revdb.stop_point_seen = stopped_time; - rpy_revdb.unique_id_seen = stopped_uid; - stopped_time = 0; - stopped_uid = 0; -} - RPY_EXTERN void rpy_reverse_db_watch_save_state(void) { - if (stopped_time != 0) { - fprintf(stderr, "unexpected recursive watch_save_state\n"); - exit(1); - } save_state(); - disable_io(); - rpy_revdb.stop_point_break = 0; - rpy_revdb.unique_id_break = 0; } RPY_EXTERN void rpy_reverse_db_watch_restore_state(bool_t any_watch_point) { - enable_io(1); restore_state(); - assert(!rpy_revdb.watch_enabled); rpy_revdb.watch_enabled = any_watch_point; } @@ -982,12 +1005,17 @@ static void replay_stop_point(void) { - if (finalizer_trigger_saved_break != 0) + if (finalizer_break != (uint64_t)-1) replay_call_destructors(); + if (rpy_revdb.stop_point_break != interactive_break) { + fprintf(stderr, "mismatch between interactive_break and " + "stop_point_break\n"); + exit(1); + } + while (rpy_revdb.stop_point_break == rpy_revdb.stop_point_seen) { save_state(); - breakpoint_mode = 0; if (pending_after_forward) { void (*fn)(void) = pending_after_forward; @@ -996,7 +1024,10 @@ } else { rpy_revdb_command_t cmd; - write_answer(ANSWER_READY, stopped_time, stopped_uid, 0); + write_answer(ANSWER_READY, + saved_state.stop_point_seen, + saved_state.unique_id_seen, + 0); read_sock(&cmd, sizeof(cmd)); char extra[cmd.extra_size + 1]; @@ -1022,6 +1053,9 @@ command_future_ids(&cmd, extra); break; + case CMD_PING: /* to get only the ANSWER_READY */ + break; + default: command_default(&cmd, extra); break; @@ -1054,7 +1088,7 @@ RPY_EXTERN void rpy_reverse_db_breakpoint(int64_t num) { - if (stopped_time != 0) { + if (flag_io_disabled != FID_REGULAR_MODE) { fprintf(stderr, "revdb.breakpoint(): cannot be called from a " "debug command\n"); exit(1); @@ -1069,9 +1103,16 @@ 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; + case 'b': /* default handling of breakpoints */ + interactive_break = rpy_revdb.stop_point_seen + 1; + set_revdb_breakpoints(); write_answer(ANSWER_BREAKPOINT, rpy_revdb.stop_point_break, 0, num); + return; + + default: + fprintf(stderr, "bad value %d of breakpoint_mode\n", + (int)breakpoint_mode); + exit(1); } } @@ -1080,13 +1121,17 @@ { switch (value_id) { case 'c': /* current_time() */ - return stopped_time ? stopped_time : rpy_revdb.stop_point_seen; + return (flag_io_disabled == FID_REGULAR_MODE ? + rpy_revdb.stop_point_seen : + saved_state.stop_point_seen); case 't': /* total_time() */ return total_stop_points; case 'b': /* current_break_time() */ - return rpy_revdb.stop_point_break; + return interactive_break; case 'u': /* currently_created_objects() */ - return stopped_uid ? stopped_uid : rpy_revdb.unique_id_seen; + return (flag_io_disabled == FID_REGULAR_MODE ? + rpy_revdb.unique_id_seen : + saved_state.unique_id_seen); default: return -1; } @@ -1096,18 +1141,23 @@ uint64_t rpy_reverse_db_unique_id_break(void *new_object) { uint64_t uid = rpy_revdb.unique_id_seen; + bool_t watch_enabled = rpy_revdb.watch_enabled; + if (!new_object) { fprintf(stderr, "out of memory: allocation failed, cannot continue\n"); exit(1); } + + save_state(); if (rpy_revdb_commands.rp_alloc) { - bool_t watch_enabled = rpy_revdb.watch_enabled; - rpy_reverse_db_watch_save_state(); + protect_jmpbuf(); if (setjmp(jmp_buf_cancel_execution) == 0) rpy_revdb_commands.rp_alloc(uid, new_object); - rpy_reverse_db_watch_restore_state(watch_enabled); + unprotect_jmpbuf(); } - rpy_revdb.unique_id_break = *future_next_id++; + uid_break = *future_next_id++; + restore_state(); + rpy_revdb.watch_enabled = watch_enabled; return uid; } @@ -1221,14 +1271,15 @@ static void replay_call_destructors(void) { - rpy_revdb.stop_point_break = finalizer_trigger_saved_break; - finalizer_trigger_saved_break = 0; fq_trigger(); /* Re-enable fetching (disabled when we saw ASYNC_FINALIZER_TRIGGER), and fetch the uid's of dying objects with old-style destructors. */ + finalizer_break = -1; + set_revdb_breakpoints(); rpy_reverse_db_fetch(__FILE__, __LINE__); + while (1) { int64_t uid; struct destructor_s d_dummy, *entry, **item; diff --git a/rpython/translator/revdb/test/test_weak.py b/rpython/translator/revdb/test/test_weak.py --- a/rpython/translator/revdb/test/test_weak.py +++ b/rpython/translator/revdb/test/test_weak.py @@ -327,3 +327,9 @@ child = self.replay() child.send(Message(CMD_FORWARD, 3001)) child.expect(ANSWER_AT_END) + + def test_bug1(self): + child = self.replay() + for i in range(50): + child.send(Message(CMD_FORWARD, i)) + child.expect_ready() _______________________________________________ pypy-commit mailing list pypy-commit@python.org https://mail.python.org/mailman/listinfo/pypy-commit