| # pdb.py -- finally, a Python debugger! See below for instructions. |
| |
| |
| # To do: |
| # - Keep a list of exceptions trapped (default only KeyboardInterrupt?) |
| # - It should be possible to intercept KeyboardInterrupt completely |
| # - Handle return events differently (how?) |
| # - When stopping on an exception, show traceback stack |
| # - Merge with tb (for post-mortem usage) |
| # - Show stack traces upside-down (like dbx/gdb) ??? |
| # (actually, the current way is more natural given the directions |
| # taken by the up/down commands) |
| |
| |
| # To use the debugger in its simplest form: |
| # >>> import pdb |
| # >>> pdb.run('<a statement>') |
| # The debugger's prompt is '(Pdb) '. |
| # This will stop in the first function call in <a statement>. |
| |
| # The commands recognized by the debugger are listed below. |
| # Most can be abbreviated as indicated; e.g., h(elp) means that |
| # 'help' can be typed as 'h' or 'help' |
| # (but not as 'he' or 'hel', nor as 'H' or 'Help' or 'HELP'). |
| # Optional arguments are enclosed in square brackets. |
| |
| # A blank line repeats the previous command literally. |
| # (Except for 'list', where it lists the next 11 lines.) |
| |
| # Commands that the debugger does not recognized are assumed to |
| # be Python statements and are executed in the context of the |
| # program being debugged. |
| # Python statements can also be prefixed with an exclamation point ('!'). |
| # This is a powerful way to inspect the program being debugged; |
| # it is even possible to change variables. |
| # When an exception occurs in such a statement, the exception name |
| # is printed but the debugger's state is not changed. |
| |
| # The debugger is not directly programmable; but it is implemented |
| # as a class from which you can derive your own debugger class, |
| # so you can make as fancy as you like. |
| |
| # The debugger's commands are: |
| |
| # h(elp) |
| # Without argument, print the list of available commands. |
| # With a command name as argument, print help about that command |
| # (this is currently not implemented). |
| |
| # w(here) |
| # Print a stack trace, with the most recent frame at the bottom. |
| # An arrow indicates the "current frame", which determines the |
| # context of most commands. |
| |
| # d(own) |
| # Move the current frame one level down in the stack trace |
| # (to an older frame). |
| |
| # u(p) |
| # Move the current frame one level up in the stack trace |
| # (to a newer frame). |
| |
| # b(reak) [lineno] |
| # With a line number argument, set a break there in the current file. |
| # Without argument, list all breaks. |
| |
| # cl(ear) [lineno] |
| # With a line number argument, clear that break in the current file. |
| # Without argument, clear all breaks (but first ask confirmation). |
| |
| # s(tep) |
| # Execute the current line, stop at the first possible occasion |
| # (either in a function that is called or in the current function). |
| |
| # n(ext) |
| # Continue execution until the next line in the current function |
| # is reached or it returns. |
| |
| # r(eturn) |
| # Continue execution until the current function returns. |
| |
| # c(ont(inue)) |
| # Continue execution, only stop when a breakpoint is encountered. |
| |
| # l(ist) [first [,last]] |
| # List source code for the current file. |
| # Without arguments, list 11 lines around the current line |
| # or continue the previous listing. |
| # With one argument, list 11 lines starting at that line. |
| # With two arguments, list the given range; |
| # if the second argument is less than the first, it is a count. |
| |
| # a(rgs) |
| # Print the argument list of the current function. |
| |
| # p expression |
| # Print the value of the expression. |
| |
| # (!) statement |
| # Execute the (one-line) statement in the context of |
| # the current stack frame. |
| # The exclamation point can be omitted unless the first word |
| # of the statement resembles a debugger command. |
| # To assign to a global variable you must always prefix the |
| # command with a 'global' command, e.g.: |
| # (Pdb) global list_options; list_options = ['-l'] |
| # (Pdb) |
| |
| # q(uit) |
| # Quit from the debugger. |
| # The program being executed is aborted. |
| |
| |
| # Here's how it works. |
| |
| # Some changes were made to the interpreter: |
| # - if sys.trace is defined (by the user), it should be a function |
| # - sys.trace is called the global trace function |
| # - there can also a local trace function (see later) |
| |
| # Trace functions have three arguments: (frame, event, arg) |
| # - frame is the current stack frame |
| # - event is a string: 'call', 'line', 'return' or 'exception' |
| # - arg is dependent on the event type |
| # A trace function should return a new trace function or None. |
| # Class methods are accepted (and most useful!) as trace methods. |
| |
| # The events have the following meaning: |
| # |
| # 'call': A function is called (or some other code block entered). |
| # The global trace function is called; |
| # arg is the argument list to the function; |
| # the return value specifies the local trace function. |
| # |
| # 'line': The interpreter is about to execute a new line of code |
| # (sometimes multiple line events on one line exist). |
| # The local trace function is called; arg in None; |
| # the return value specifies the new local trace function. |
| # |
| # 'return': A function (or other code block) is about to return. |
| # The local trace function is called; |
| # arg is the value that will be returned. |
| # The trace function's return value is ignored. |
| # |
| # 'exception': An exception has occurred. |
| # The local trace function is called if there is one, |
| # else the global trace function is called; |
| # arg is a triple (exception, value, traceback); |
| # the return value specifies the new local trace function |
| # |
| # Note that as an exception is propagated down the chain of callers, |
| # an 'exception' event is generated at each level. |
| |
| # A stack frame object has the following read-only attributes: |
| # f_code: the code object being executed |
| # f_lineno: the current line number (-1 for 'call' events) |
| # f_back: the stack frame of the caller, or None |
| # f_locals: dictionary containing local name bindings |
| # f_globals: dictionary containing global name bindings |
| |
| # A code object has the following read-only attributes: |
| # co_code: the code string |
| # co_names: the list of names used by the code |
| # co_consts: the list of (literal) constants used by the code |
| # co_filename: the filename from which the code was compiled |
| |
| |
| import string |
| import sys |
| import linecache |
| |
| |
| # A generic class to build command interpreters |
| |
| PROMPT = '(Cmd) ' |
| IDENTCHARS = string.letters + string.digits + '_' |
| |
| class Cmd: |
| def init(self): |
| self.prompt = PROMPT |
| self.identchars = IDENTCHARS |
| self.lastcmd = '' |
| return self |
| def cmdloop(self): |
| stop = None |
| while not stop: |
| try: |
| line = raw_input(self.prompt) |
| except EOFError: |
| line = 'EOF' |
| stop = self.onecmd(line) |
| return stop |
| def onecmd(self, line): |
| line = string.strip(line) |
| if not line: |
| line = self.lastcmd |
| print line |
| else: |
| self.lastcmd = line |
| i, n = 0, len(line) |
| while i < n and line[i] in self.identchars: i = i+1 |
| cmd, arg = line[:i], string.strip(line[i:]) |
| if cmd == '': |
| return self.default(line) |
| else: |
| try: |
| func = eval('self.do_' + cmd) |
| except AttributeError: |
| return self.default(line) |
| return func(arg) |
| def default(self, line): |
| print '*** Unknown syntax:', line |
| def do_help(self, arg): |
| if arg: |
| # XXX check arg syntax |
| try: |
| func = eval('self.help_' + arg) |
| except: |
| print '*** No help on', `arg` |
| return |
| func() |
| else: |
| import getattr |
| names = getattr.dir(self) |
| cmds = [] |
| for name in names: |
| if name[:3] == 'do_': |
| cmds.append(name[3:]) |
| print cmds |
| |
| |
| # 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.breaks = {} |
| self.botframe = None |
| self.stopframe = None |
| self.forget() |
| |
| def forget(self): |
| self.setup(None) |
| |
| def setup(self, frame): |
| self.curframe = self.topframe = frame |
| self.stack = [] |
| self.lineno = None |
| |
| def run(self, cmd): |
| import __main__ |
| dict = __main__.__dict__ |
| self.runctx(cmd, dict, dict) |
| |
| def runctx(self, cmd, globals, locals): |
| self.reset() |
| sys.trace = self.dispatch |
| try: |
| exec(cmd + '\n', globals, locals) |
| except PdbQuit: |
| pass |
| finally: |
| sys.trace = None |
| del sys.trace |
| self.reset() |
| |
| def dispatch(self, frame, event, arg): |
| 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) |
| return self.dispatch |
| |
| def dispatch_call(self, frame, arg): |
| if self.botframe is None: |
| self.botframe = frame |
| return |
| if not (self.stop_here(frame) or self.break_anywhere(frame)): |
| return |
| frame.f_locals['__args__'] = arg |
| return self.dispatch |
| |
| def dispatch_return(self, frame, arg): |
| if self.stop_here(frame): |
| print '!!! return', `arg` |
| return |
| |
| def dispatch_exception(self, frame, arg): |
| if arg[0] is PdbQuit: return None |
| if self.stop_here(frame): |
| print '!!! exception', arg[0] + ':', `arg[1]` |
| self.ask_user(frame) |
| 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 self.stopframe: |
| if frame is None: |
| 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): |
| self.setup(frame) |
| self.printwhere(self.curframe) |
| dummy = 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.printtb() |
| do_w = do_where |
| |
| def do_up(self, arg): |
| if self.curframe == self.botframe or \ |
| not self.curframe.f_back: print '*** Top' |
| else: |
| self.stack.append(self.curframe) |
| self.curframe = self.curframe.f_back |
| self.lineno = None |
| self.printwhere(self.curframe) |
| do_u = do_up |
| |
| def do_down(self, arg): |
| if not self.stack: print '*** Bottom' |
| else: |
| self.curframe = self.stack[-1] |
| self.lineno = None |
| del self.stack[-1] |
| self.printwhere(self.curframe) |
| 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_continue(self, arg): |
| self.stopframe = self.botframe |
| return 1 |
| do_c = do_cont = do_continue |
| |
| def do_quit(self, arg): |
| self.stopframe = self.botframe |
| 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 a given stack frame |
| # Note that it is printed upside-down with respect |
| # to the orientation suggested by the up/down commands. |
| # This is consistent with gdb. |
| def printtb(self): |
| list = [] |
| frame = self.topframe |
| while frame: |
| list.append(frame) |
| if frame is self.botframe: break |
| frame = frame.f_back |
| list.reverse() |
| for frame in list: |
| self.printwhere(frame) |
| |
| def printwhere(self, frame): |
| if frame is self.curframe: print '->', |
| code = frame.f_code |
| filename = code.co_filename |
| lineno = frame.f_lineno |
| print filename + '(' + `lineno` + ')', |
| line = linecache.getline(filename, lineno) |
| if line: print string.strip(line), |
| print |
| |
| |
| # --------------------- testing --------------------- |
| |
| # The Ackermann function -- a highly recursive beast |
| cheat = 0 |
| 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 |
| return |
| |
| def bar(a): |
| print 'bar', a |
| return a*10 |
| |
| def test(): |
| linecache.checkcache() |
| Pdb().init().run('foo(12)\n') |
| |
| |
| # --------------------- 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('') |
| |
| def run(statement): |
| Pdb().init().run(statement) |