def test_print_cmd(self): group = ReplayProcessGroup(str(self.exename), self.rdbname) group.go_forward(1) assert group.get_current_time() == 2 with stdout_capture() as buf: group.print_cmd('print-me') assert buf.getvalue() == "$0 = stuff\n" return group
class RevDebugControl(object): def __init__(self, revdb_log_filename, executable=None, pygments_background=None): with open(revdb_log_filename, 'rb') as f: header = f.readline() assert header.endswith('\n') fields = header[:-1].split('\t') if len(fields) < 2 or fields[0] != 'RevDB:': raise ValueError("file %r is not a RevDB log" % (revdb_log_filename, )) if executable is None: executable = fields[1] if not os.path.isfile(executable): raise ValueError("executable %r not found" % (executable, )) linecacheoutput = self.getlinecacheoutput(pygments_background) self.pgroup = ReplayProcessGroup(executable, revdb_log_filename, linecacheoutput, (PROG_RES, self.progress_callback)) self.print_extra_pending_info = None def interact(self): self.last_command = 'help' self.previous_time = None self.previous_thread = 0 while True: prompt = self.print_lines_before_prompt() try: while True: cmdline = self.display_prompt(prompt) self.run_command(cmdline) prompt = self.print_lines_before_prompt() except KeyboardInterrupt: rtime = self.previous_time or 1 print ERASE_LINE print 'KeyboardInterrupt: restoring state at time %d...' % ( rtime, ) self.pgroup.recreate_subprocess(rtime) print "(type 'q' or Ctrl-D to quit)" self.last_command = '' self.previous_thread = '?' self.previous_time = '?' def print_lines_before_prompt(self): last_time = self.pgroup.get_current_time() if last_time != self.previous_time: print ERASE_LINE if self.pgroup.get_current_thread() != self.previous_thread: self.previous_thread = self.pgroup.get_current_thread() if self.previous_thread == 0: print( '-------------------- in main thread #0 ' '--------------------') else: print( '-------------------- in non-main thread ' '#%d --------------------' % (self.previous_thread, )) self.pgroup.update_watch_values() last_time = self.pgroup.get_current_time() if last_time != self.previous_time: self.pgroup.show_backtrace(complete=0) self.previous_time = last_time if self.print_extra_pending_info: print self.print_extra_pending_info self.print_extra_pending_info = None prompt = '(%d)$ ' % last_time return prompt def display_prompt(self, prompt): self.pgroup.wait_for_prompt = True try: cmdline = raw_input(prompt).strip() except EOFError: print cmdline = 'quit' if not cmdline: cmdline = self.last_command return cmdline def run_command(self, cmdline): match = r_cmdline.match(cmdline) if not match: return self.last_command = cmdline command, argument = match.groups() try: runner = getattr(self, 'command_' + command) except AttributeError: print >> sys.stderr, "no command '%s', try 'help'" % (command, ) else: try: runner(argument) except KeyboardInterrupt: raise 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""" print 'Available commands:' lst = dir(self) commands = [(name[len('command_'):], getattr(self, name)) for name in lst if name.startswith('command_')] seen = {} for name, func in commands: seen.setdefault(func, []).append(name) for _, func in commands: if func in seen: names = seen.pop(func) names.sort(key=len, reverse=True) docstring = func.__doc__ or 'undocumented' print '\t%-16s %s' % (', '.join(names), docstring) def command_quit(self, argument): """Exit the debugger""" self.pgroup.close() sys.exit(0) command_q = command_quit def command_go(self, argument): """Jump to time ARG""" arg = int(argument or self.pgroup.get_current_time()) self.pgroup.jump_in_time(arg) def command_info(self, argument): """Display various info ('info help' for more)""" display = getattr(self, 'cmd_info_' + argument, self.cmd_info_help) return display() def cmd_info_help(self): """Display info topics summary""" print 'Available info topics:' for name in dir(self): if name.startswith('cmd_info_'): command = name[len('cmd_info_'):] docstring = getattr(self, name).__doc__ or 'undocumented' print '\tinfo %-12s %s' % (command, docstring) def cmd_info_paused(self): """List current paused subprocesses""" lst = [str(n) for n in sorted(self.pgroup.paused)] print ', '.join(lst) def _bp_kind(self, num): break_at = self.pgroup.all_breakpoints.num2break.get(num, '??') if break_at[0] == 'B': kind = 'breakpoint' name = break_at[4:] elif break_at[0] == 'W': kind = 'watchpoint' name = self.pgroup.all_breakpoints.sources.get(num, '??') elif num == -3: kind = 'stoppoint' name = 'explicit stop' elif num == -4: kind = 'switchpoint' name = 'thread switch' else: kind = '?????point' name = repr(break_at) return kind, name def _bp_new(self, source_expr, break_code, break_at, nids=None): b = self.pgroup.edit_breakpoints() new = 1 while new in b.num2break: new += 1 b.set_num2break(new, break_code, break_at) b.sources[new] = source_expr if break_code == 'W': b.watchvalues[new] = '' if nids: b.watchuids[new] = self.pgroup.nids_to_uids(nids) return new def cmd_info_breakpoints(self): """List current breakpoints and watchpoints""" lst = self.pgroup.all_breakpoints.num2break.keys() if lst: for num in sorted(lst): kind, name = self._bp_kind(num) print '\t%s %d: %s' % (kind, num, name) else: print 'no breakpoints/watchpoints.' cmd_info_watchpoints = cmd_info_breakpoints def move_forward(self, steps): self.remove_tainting() try: self.pgroup.go_forward(steps) return None except Breakpoint as b: self.hit_breakpoints(b) return b def move_backward(self, steps): try: self.pgroup.go_backward(steps) return None except Breakpoint as b: self.hit_breakpoints(b, backward=True) return b def hit_breakpoints(self, b, backward=False): printing = [] for num in b.regular_breakpoint_nums(): kind, name = self._bp_kind(num) printing.append('%s %s%s: %s' % ('Reverse-hit' if backward else 'Hit', kind, '' if kind == 'stoppoint' else ' %d' % (num, ), name)) self.print_extra_pending_info = '\n'.join(printing) if self.pgroup.get_current_time() != b.time: target_time = b.time if backward and any( self._bp_kind(num)[0] == 'watchpoint' for num in b.regular_breakpoint_nums()): target_time += 1 self.pgroup.jump_in_time(target_time) def remove_tainting(self): if self.pgroup.is_tainted(): self.pgroup.jump_in_time(self.pgroup.get_current_time()) assert not self.pgroup.is_tainted() def command_step(self, argument): """Run forward ARG steps (default 1)""" arg = int(argument or '1') self.move_forward(arg) command_s = command_step def command_bstep(self, argument): """Run backward ARG steps (default 1)""" arg = int(argument or '1') self.move_backward(arg) command_bs = command_bstep @contextmanager def _stack_id_break(self, stack_id): # add temporarily a breakpoint that hits when we enter/leave # a frame from/to the frame identified by 'stack_id' b = self.pgroup.edit_breakpoints() b.stack_id = stack_id try: yield finally: b.stack_id = 0 @contextmanager def _thread_num_break(self, thread_num): # add temporarily a breakpoint that hits when we enter/leave # the given thread b = self.pgroup.edit_breakpoints() b.thread_num = thread_num try: yield finally: b.thread_num = -1 def command_next(self, argument): """Run forward for one step, skipping calls""" while True: stack_id = self.pgroup.get_stack_id(is_parent=False) with self._stack_id_break(stack_id): b = self.move_forward(1) while b is not None: # if we hit a regular breakpoint, stop if any(b.regular_breakpoint_nums()): return # we hit only calls and returns inside stack_id. If the # last one of these is a "return", then we're now back inside # stack_id, so stop if b.nums[-1] == -2: break # else, the last one is a "call", so we entered another frame. # Continue running until the next call/return event occurs # inside stack_id with self._stack_id_break(stack_id): b = self.move_forward(self.pgroup.get_max_time() - self.pgroup.get_current_time()) # and then look at that 'b' again (closes the loop) # we might be at a "<<" position on the same line as before, # which returns a get_hiddenpos_level() value of 1. Continue # until we reach a get_hiddenpos_level() value of 0. if b is None or self.pgroup.get_hiddenpos_level() == 0: break command_n = command_next def command_bnext(self, argument): """Run backward for one step, skipping calls""" while True: stack_id = self.pgroup.get_stack_id(is_parent=False) with self._stack_id_break(stack_id): b = self.move_backward(1) while b is not None: # if we hit a regular breakpoint, stop if any(b.regular_breakpoint_nums()): return # we hit only calls and returns inside stack_id. If the # first one of these is a "call", then we're now back inside # stack_id, so stop if b.nums[0] == -1: break # else, the first one is a "return", so before, we were # inside a different frame. Continue running until the next # call/return event occurs inside stack_id with self._stack_id_break(stack_id): b = self.move_backward(self.pgroup.get_current_time() - 1) # and then look at that 'b' again (closes the loop) # we might be at a "<<" position on the same line as before, # which returns a get_hiddenpos_level() value of 1. Continue # until we reach a get_hiddenpos_level() value of 0. if self.pgroup.get_hiddenpos_level() == 0: break command_bn = command_bnext def command_finish(self, argument): """Run forward until the current function finishes""" stack_id = self.pgroup.get_stack_id(is_parent=True) if stack_id == 0: print 'No caller.' else: with self._stack_id_break(stack_id): self.command_continue('') def command_bfinish(self, argument): """Run backward until the current function is called""" stack_id = self.pgroup.get_stack_id(is_parent=True) if stack_id == 0: print 'No caller.' else: with self._stack_id_break(stack_id): self.command_bcontinue('') def command_continue(self, argument): """Run forward""" self.move_forward(self.pgroup.get_max_time() - 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 _cmd_thread(self, argument, cmd_continue): argument = argument.lstrip('#') if argument: arg = int(argument) if arg == self.pgroup.get_current_thread(): print 'Thread #%d is already the current one.' % (arg, ) return else: # use the current thread number to detect switches to any # other thread (this works because revdb.c issues a # breakpoint whenever there is a switch FROM or TO the # thread '#arg'). arg = self.pgroup.get_current_thread() # with self._thread_num_break(arg): cmd_continue('') def command_nthread(self, argument): """Run forward until thread switch (optionally to #ARG)""" self._cmd_thread(argument, self.command_continue) def command_bthread(self, argument): """Run backward until thread switch (optionally to #ARG)""" self._cmd_thread(argument, self.command_bcontinue) def command_print(self, argument): """Print an expression or execute a line of code""" # locate which $NUM appear used in the expression nids = map(int, r_dollar_num.findall(argument)) self.pgroup.print_cmd(argument, nids=nids) command_p = command_print locals()['command_!'] = command_print def command_backtrace(self, argument): """Show the backtrace""" self.pgroup.show_backtrace(complete=1) command_bt = command_backtrace def command_list(self, argument): """Show the current function""" self.pgroup.show_backtrace(complete=2) def command_locals(self, argument): """Show the locals""" self.pgroup.show_locals() def command_break(self, argument): """Add a breakpoint""" if not argument: print "Break where?" return num = self._bp_new(argument, 'B', argument) self.pgroup.update_breakpoints() b = self.pgroup.edit_breakpoints() if num not in b.num2break: print "Breakpoint not added" else: kind, name = self._bp_kind(num) print "Breakpoint %d added: %s" % (num, name) command_b = command_break def command_delete(self, argument): """Delete a breakpoint/watchpoint""" b = self.pgroup.edit_breakpoints() try: arg = int(argument) except ValueError: for arg in b.num2break: if self._bp_kind(arg)[1] == argument: break else: print "No such breakpoint/watchpoint: %s" % (argument, ) return if arg not in b.num2break: print "No breakpoint/watchpoint number %d" % (arg, ) else: kind, name = self._bp_kind(arg) b.num2break.pop(arg, '') b.sources.pop(arg, '') b.watchvalues.pop(arg, '') b.watchuids.pop(arg, '') print "%s %d deleted: %s" % (kind.capitalize(), arg, name) command_del = command_delete def command_watch(self, argument): """Add a watchpoint (use $NUM in the expression to watch)""" if not argument: print "Watch what?" return # ok_flag, compiled_code = self.pgroup.compile_watchpoint_expr(argument) if not ok_flag: print compiled_code # the error message print 'Watchpoint not added' return # nids = map(int, r_dollar_num.findall(argument)) ok_flag, text = self.pgroup.check_watchpoint_expr(compiled_code, nids) if not ok_flag: print text print 'Watchpoint not added' return # new = self._bp_new(argument, 'W', compiled_code, nids=nids) self.pgroup.update_watch_values() print "Watchpoint %d added" % (new, ) def getlinecacheoutput(self, pygments_background): if not pygments_background or pygments_background == 'off': return try: from pygments import highlight from pygments.lexers import PythonLexer from pygments.formatters import TerminalFormatter except ImportError as e: print >> sys.stderr, 'ImportError: %s' % (e, ) return None # lexer = PythonLexer() fmt = TerminalFormatter(bg=pygments_background) # def linecacheoutput(filename, lineno): line = linecache.getline(filename, lineno) return highlight(line, lexer, fmt) return linecacheoutput def progress_callback(self, tick, previous): if previous: sys.stdout.write('\x08' * previous) if tick is not None: msg = '(%d...)' % tick sys.stdout.write(msg + ERASE_LINE) sys.stdout.flush() return len(msg)
def test_breakpoint_i(self): group = ReplayProcessGroup(str(self.exename), self.rdbname) group.active.send(Message(100, 6, extra='set-breakpoint')) group.active.expect(42, 100, -43, -44, 'set-breakpoint') group.active.expect(ANSWER_READY, 1, Ellipsis) group.go_forward(10, 'i') # does not raise Breakpoint
def test_forward(self): group = ReplayProcessGroup(str(self.exename), self.rdbname) group.go_forward(100) assert group.get_current_time() == 10 assert sorted(group.paused) == [1, 4, 6, 8, 9, 10] assert group._check_current_time(10)