| # pdb.py -- finally, a Python debugger! |
| # See file pdb.doc for instructions. |
| |
| # To do: |
| # - It should be possible to intercept KeyboardInterrupt |
| # - Handle return events differently -- always printing the r.v. can be bad! |
| # - Merge with tb, to get a single debugger for active and post-mortem usage |
| # - Solve bugs in termination (e.g., 'continue' after the program |
| # is done proceeds to debug the debugger; 'quit' sometimes complains |
| # about the PdbQuit exception...) |
| |
| |
| import string |
| import sys |
| import linecache |
| from cmd import Cmd |
| |
| |
| # A specialization of Cmd for use by the debugger |
| |
| PdbQuit = 'pdb.PdbQuit' # Exception to give up |
| |
| class Pdb(Cmd): |
| |
| def init(self): |
| self = Cmd.init(self) |
| self.prompt = '(Pdb) ' |
| self.reset() |
| return self |
| |
| def reset(self): |
| self.quitting = 0 |
| self.breaks = {} |
| self.botframe = None |
| self.stopframe = None |
| self.lastretval = None |
| self.forget() |
| |
| def forget(self): |
| self.setup(None, None) |
| |
| def setup(self, f, t): |
| self.lineno = None |
| self.stack = [] |
| self.curindex = 0 |
| self.curframe = None |
| if f is None and t is None: |
| return |
| if t and t.tb_frame is f: |
| t = t.tb_next |
| while f is not None: |
| self.stack.append((f, f.f_lineno)) |
| if f is self.botframe: |
| break |
| f = f.f_back |
| else: |
| print '[Weird: bottom frame not in stack trace.]' |
| self.stack.reverse() |
| self.curindex = max(0, len(self.stack) - 1) |
| while t is not None: |
| self.stack.append((t.tb_frame, t.tb_lineno)) |
| t = t.tb_next |
| self.curframe = self.stack[self.curindex][0] |
| |
| def run(self, cmd): |
| import __main__ |
| dict = __main__.__dict__ |
| self.runctx(cmd, dict, dict) |
| |
| def runctx(self, cmd, globals, locals): |
| t = None |
| self.reset() |
| sys.trace = self.dispatch |
| try: |
| exec(cmd + '\n', globals, locals) |
| except PdbQuit: |
| return |
| except: |
| print '***', sys.exc_type + ':', `sys.exc_value` |
| t = sys.exc_traceback |
| finally: |
| self.trace = None |
| del self.trace |
| self.forget() |
| print '!!! Post-mortem debugging' |
| self.pmd(t) |
| |
| def pmd(self, traceback): |
| t = traceback |
| if self.botframe is not None: |
| while t is not None: |
| if t.tb_frame is not self.botframe: |
| break |
| t = t.tb_next |
| else: |
| t = sys.exc_traceback |
| try: |
| self.ask_user(self.botframe, t) |
| except PdbQuit: |
| pass |
| finally: |
| self.forget() |
| |
| def dispatch(self, frame, event, arg): |
| if self.quitting: |
| return None |
| if event == 'line': |
| return self.dispatch_line(frame) |
| if event == 'call': |
| return self.dispatch_call(frame, arg) |
| if event == 'return': |
| return self.dispatch_return(frame, arg) |
| if event == 'exception': |
| return self.dispatch_exception(frame, arg) |
| print '*** dispatch: unknown event type', `event` |
| return self.dispatch |
| |
| def dispatch_line(self, frame): |
| if self.stop_here(frame) or self.break_here(frame): |
| self.ask_user(frame, None) |
| return self.dispatch |
| |
| def dispatch_call(self, frame, arg): |
| if self.botframe is None: |
| # First call dispatch since reset() |
| self.botframe = frame |
| return None |
| if not (self.stop_here(frame) or self.break_anywhere(frame)): |
| return None |
| if arg is None: |
| print '[Entering non-function block.]' |
| else: |
| frame.f_locals['__args__'] = arg |
| return self.dispatch |
| |
| def dispatch_return(self, frame, arg): |
| self.lastretval = arg |
| return None |
| |
| def dispatch_exception(self, frame, arg): |
| if self.stop_here(frame): |
| print '!!!', arg[0] + ':', `arg[1]` |
| self.ask_user(frame, arg[2]) |
| return self.dispatch |
| |
| def stop_here(self, frame): |
| if self.stopframe is None: |
| return 1 |
| if frame is self.stopframe: |
| return 1 |
| while frame is not None and frame is not self.stopframe: |
| if frame is self.botframe: |
| return 1 |
| frame = frame.f_back |
| return 0 |
| |
| def break_here(self, frame): |
| if not self.breaks.has_key(frame.f_code.co_filename): |
| return 0 |
| if not frame.f_lineno in \ |
| self.breaks[frame.f_code.co_filename]: |
| return 0 |
| return 1 |
| |
| def break_anywhere(self, frame): |
| return self.breaks.has_key(frame.f_code.co_filename) |
| |
| def ask_user(self, frame, traceback): |
| self.setup(frame, traceback) |
| self.printframelineno(self.stack[self.curindex]) |
| self.cmdloop() |
| self.forget() |
| |
| def default(self, line): |
| if not line: |
| return self.do_next('') |
| else: |
| if line[0] == '!': line = line[1:] |
| try: |
| exec(line + '\n', \ |
| self.curframe.f_globals, \ |
| self.curframe.f_locals) |
| except: |
| print '***', sys.exc_type + ':', |
| print `sys.exc_value` |
| |
| do_h = Cmd.do_help |
| |
| def do_break(self, arg): |
| if not arg: |
| print self.breaks # XXX |
| return |
| try: |
| lineno = int(eval(arg)) |
| except: |
| print '*** Error in argument:', `arg` |
| return |
| filename = self.curframe.f_code.co_filename |
| line = linecache.getline(filename, lineno) |
| if not line: |
| print '*** That line does not exist!' |
| return |
| if not self.breaks.has_key(filename): |
| self.breaks[filename] = [] |
| list = self.breaks[filename] |
| if lineno in list: |
| print '*** There is already a break there!' |
| return |
| list.append(lineno) |
| do_b = do_break |
| |
| def do_clear(self, arg): |
| if not arg: |
| try: |
| reply = raw_input('Clear all breaks? ') |
| except EOFError: |
| reply = 'no' |
| reply = string.lower(string.strip(reply)) |
| if reply in ('y', 'yes'): |
| self.breaks = {} |
| return |
| try: |
| lineno = int(eval(arg)) |
| except: |
| print '*** Error in argument:', `arg` |
| return |
| filename = self.curframe.f_code.co_filename |
| try: |
| self.breaks[filename].remove(lineno) |
| except (ValueError, KeyError): |
| print '*** There is no break there!' |
| return |
| if not self.breaks[filename]: |
| del self.breaks[filename] |
| do_cl = do_clear # 'c' is already an abbreviation for 'continue' |
| |
| def do_where(self, arg): |
| self.printstacktrace() |
| do_w = do_where |
| |
| def do_up(self, arg): |
| if self.curindex == 0: |
| print '*** Oldest frame' |
| else: |
| self.curindex = self.curindex - 1 |
| self.curframe = self.stack[self.curindex][0] |
| self.printframelineno(self.stack[self.curindex]) |
| do_u = do_up |
| |
| def do_down(self, arg): |
| if self.curindex + 1 == len(self.stack): |
| print '*** Newest frame' |
| else: |
| self.curindex = self.curindex + 1 |
| self.curframe = self.stack[self.curindex][0] |
| self.printframelineno(self.stack[self.curindex]) |
| do_d = do_down |
| |
| def do_step(self, arg): |
| self.stopframe = None |
| return 1 |
| do_s = do_step |
| |
| def do_next(self, arg): |
| self.stopframe = self.curframe |
| return 1 |
| do_n = do_next |
| |
| def do_return(self, arg): |
| self.stopframe = self.curframe.f_back |
| return 1 |
| do_r = do_return |
| |
| def do_retval(self, arg): |
| print self.lastretval |
| do_rv = do_retval |
| |
| def do_continue(self, arg): |
| self.stopframe = self.botframe |
| return 1 |
| do_c = do_cont = do_continue |
| |
| def do_quit(self, arg): |
| self.quitting = 1 |
| sys.trace = None; del sys.trace |
| raise PdbQuit |
| do_q = do_quit |
| |
| def do_list(self, arg): |
| self.lastcmd = 'list' |
| last = None |
| if arg: |
| try: |
| x = eval(arg, {}, {}) |
| if type(x) == type(()): |
| first, last = x |
| first = int(first) |
| last = int(last) |
| if last < first: |
| # Assume it's a count |
| last = first + last |
| else: |
| first = int(x) |
| except: |
| print '*** Error in argument:', `arg` |
| return |
| elif self.lineno is None: |
| first = max(1, self.curframe.f_lineno - 5) |
| else: |
| first = self.lineno + 1 |
| if last is None: |
| last = first + 10 |
| filename = self.curframe.f_code.co_filename |
| if self.breaks.has_key(filename): |
| breaklist = self.breaks[filename] |
| else: |
| breaklist = [] |
| try: |
| for lineno in range(first, last+1): |
| line = linecache.getline(filename, lineno) |
| if not line: |
| print '[EOF]' |
| break |
| else: |
| s = string.rjust(`lineno`, 3) |
| if len(s) < 4: s = s + ' ' |
| if lineno in breaklist: s = s + 'B' |
| else: s = s + ' ' |
| if lineno == self.curframe.f_lineno: |
| s = s + '->' |
| print s + '\t' + line, |
| self.lineno = lineno |
| except KeyboardInterrupt: |
| pass |
| do_l = do_list |
| |
| def do_args(self, arg): |
| try: |
| value = eval('__args__', self.curframe.f_globals, \ |
| self.curframe.f_locals) |
| except: |
| print '***', sys.exc_type + ':', `sys.exc_value` |
| return |
| print `value` |
| do_a = do_args |
| |
| def do_p(self, arg): |
| try: |
| value = eval(arg, self.curframe.f_globals, \ |
| self.curframe.f_locals) |
| except: |
| print '***', sys.exc_type + ':', `sys.exc_value` |
| return |
| print `value` |
| |
| # Print a traceback starting at the top stack frame. |
| # Note that the most recently entered frame is printed last; |
| # this is different from dbx and gdb, but consistent with |
| # the Python interpreter's stack trace. |
| # It is also consistent with the up/down commands (which are |
| # compatible with dbx and gdb: up moves towards 'main()' |
| # and down moves towards the most recent stack frame). |
| |
| def printstacktrace(self): |
| for x in self.stack: |
| self.printframelineno(x) |
| |
| def printframelineno(self, (frame, lineno)): |
| if frame is self.curframe: print '->', |
| code = frame.f_code |
| filename = code.co_filename |
| print filename + '(' + `lineno` + ')', |
| line = linecache.getline(filename, lineno) |
| print string.strip(line), |
| print |
| |
| |
| def run(statement): |
| Pdb().init().run(statement) |
| |
| def runctx(statement, globals, locals): |
| Pdb().init().runctx(statement, globals, locals) |
| |
| |
| # --------------------- testing --------------------- |
| |
| # The Ackermann function -- a highly recursive beast |
| cheat = 2 |
| cache = {} |
| def ack(x, y): |
| key = `(long(x), long(y))` |
| if cache.has_key(key): |
| res = cache[key] |
| else: |
| if x == 0: |
| res = 1L |
| elif y == 0: |
| if x == 1: |
| res = 2L |
| else: |
| res = 2L + x |
| elif y == 1 and cheat >= 1: |
| res = 2L * x |
| elif y == 2 and cheat >= 2: |
| res = pow(2L, x) |
| else: |
| res = ack(ack(x-1, y), y-1) |
| cache[key] = res |
| return res |
| |
| def foo(n): |
| print 'foo', n |
| x = bar(n*2) |
| print 'bar returned', x |
| y = ack(4, 3) |
| return y |
| |
| def bar(a): |
| print 'bar', a |
| return a*10 |
| |
| def melt(n): |
| print 1.0/n |
| melt(n-1) |
| |
| def test(): |
| linecache.checkcache() |
| runctx('from pdb import foo; foo(12)', {}, {}) |
| runctx('from pdb import melt; melt(5)', {}, {}) |
| |
| |
| # --------------------- main --------------------- |
| |
| import os |
| |
| def main(): |
| if sys.argv[1:]: |
| file = sys.argv[1] |
| head, tail = os.path.split(file) |
| if tail[-3:] != '.py': |
| print 'Sorry, file arg must be a python module' |
| print '(i.e., must end in \'.py\')' |
| # XXX Or we could copy it to a temp file |
| sys.exit(2) |
| del sys.argv[0] |
| sys.path.insert(0, head) |
| run('import ' + tail[:-3]) |
| else: |
| run(raw_input('Python statement to debug: ')) |