summaryrefslogtreecommitdiffstats
path: root/Lib/pdb.py
diff options
context:
space:
mode:
authorGuido van Rossum <guido@python.org>1992-01-16 13:50:21 (GMT)
committerGuido van Rossum <guido@python.org>1992-01-16 13:50:21 (GMT)
commit6fe08b0fe49a31a083608b2a3ecf8bdc57964ead (patch)
treeca3eb3a937241a5fd2bbade5b1cd75494e3cbc2c /Lib/pdb.py
parent7c36ad7f449c54b8fc445aea28d17bf87fa6f2ea (diff)
downloadcpython-6fe08b0fe49a31a083608b2a3ecf8bdc57964ead.zip
cpython-6fe08b0fe49a31a083608b2a3ecf8bdc57964ead.tar.gz
cpython-6fe08b0fe49a31a083608b2a3ecf8bdc57964ead.tar.bz2
Moved documentation out to pdb.doc file.
Moved class Cmd out to module cmd.py. Rewrote implementation of stack trace to incorporate traceback objects.
Diffstat (limited to 'Lib/pdb.py')
-rwxr-xr-xLib/pdb.py361
1 files changed, 88 insertions, 273 deletions
diff --git a/Lib/pdb.py b/Lib/pdb.py
index 789b57b..3df4cfe 100755
--- a/Lib/pdb.py
+++ b/Lib/pdb.py
@@ -1,232 +1,19 @@
-# pdb.py -- finally, a Python debugger! See below for instructions.
-
+# pdb.py -- finally, a Python debugger!
+# See file pdb.doc 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
+# - 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
-
-
-# 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
+from cmd import Cmd
# A specialization of Cmd for use by the debugger
@@ -242,18 +29,32 @@ class Pdb(Cmd):
return self
def reset(self):
+ self.quitting = 0
self.breaks = {}
self.botframe = None
self.stopframe = None
self.forget()
def forget(self):
- self.setup(None)
+ self.setup(None, None)
- def setup(self, frame):
- self.curframe = self.topframe = frame
- self.stack = []
+ def setup(self, f, t):
self.lineno = None
+ self.stack = []
+ if t and t.tb_frame is f:
+ t = t.tb_next
+ while f and f is not self.botframe:
+ self.stack.append((f, f.f_lineno))
+ f = f.f_back
+ self.stack.reverse()
+ self.curindex = max(0, len(self.stack) - 1)
+ while t:
+ self.stack.append((t.tb_frame, t.tb_lineno))
+ t = t.tb_next
+ if 0 <= self.curindex < len(self.stack):
+ self.curframe = self.stack[self.curindex][0]
+ else:
+ self.curframe = None
def run(self, cmd):
import __main__
@@ -267,12 +68,21 @@ class Pdb(Cmd):
exec(cmd + '\n', globals, locals)
except PdbQuit:
pass
- finally:
+ except:
+ print '***', sys.exc_type + ':', `sys.exc_value`
+ print '*** Post Mortem Debugging:'
sys.trace = None
del sys.trace
+ try:
+ self.ask_user(None, sys.exc_traceback)
+ except PdbQuit:
+ pass
+ finally:
self.reset()
def dispatch(self, frame, event, arg):
+ if self.quitting:
+ return None
if event == 'line':
return self.dispatch_line(frame)
if event == 'call':
@@ -286,7 +96,7 @@ class Pdb(Cmd):
def dispatch_line(self, frame):
if self.stop_here(frame) or self.break_here(frame):
- self.ask_user(frame)
+ self.ask_user(frame, None)
return self.dispatch
def dispatch_call(self, frame, arg):
@@ -304,10 +114,9 @@ class Pdb(Cmd):
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)
+ self.ask_user(frame, arg[2])
return self.dispatch
def stop_here(self, frame):
@@ -315,8 +124,8 @@ class Pdb(Cmd):
return 1
if frame is self.stopframe:
return 1
- while frame is not self.stopframe:
- if frame is None:
+ while frame is not None and frame is not self.stopframe:
+ if frame is self.botframe:
return 1
frame = frame.f_back
return 0
@@ -332,10 +141,10 @@ class Pdb(Cmd):
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()
+ def ask_user(self, frame, traceback):
+ self.setup(frame, traceback)
+ self.printframelineno(self.stack[self.curindex])
+ self.cmdloop()
self.forget()
def default(self, line):
@@ -402,26 +211,25 @@ class Pdb(Cmd):
do_cl = do_clear # 'c' is already an abbreviation for 'continue'
def do_where(self, arg):
- self.printtb()
+ self.printstacktrace()
do_w = do_where
def do_up(self, arg):
- if self.curframe == self.botframe or \
- not self.curframe.f_back: print '*** Top'
+ if self.curindex == 0:
+ print '*** Oldest frame'
else:
- self.stack.append(self.curframe)
- self.curframe = self.curframe.f_back
- self.lineno = None
- self.printwhere(self.curframe)
+ 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 not self.stack: print '*** Bottom'
+ if self.curindex + 1 == len(self.stack):
+ print '*** Newest frame'
else:
- self.curframe = self.stack[-1]
- self.lineno = None
- del self.stack[-1]
- self.printwhere(self.curframe)
+ 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):
@@ -445,7 +253,8 @@ class Pdb(Cmd):
do_c = do_cont = do_continue
def do_quit(self, arg):
- self.stopframe = self.botframe
+ self.quitting = 1
+ sys.trace = None; del sys.trace
raise PdbQuit
do_q = do_quit
@@ -516,36 +325,39 @@ class Pdb(Cmd):
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)
+ # 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 printwhere(self, 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
- lineno = frame.f_lineno
print filename + '(' + `lineno` + ')',
line = linecache.getline(filename, lineno)
- if line: print string.strip(line),
+ 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 = 0
+cheat = 2
cache = {}
def ack(x, y):
key = `(long(x), long(y))`
@@ -572,15 +384,21 @@ def foo(n):
print 'foo', n
x = bar(n*2)
print 'bar returned', x
- return
+ 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()
- Pdb().init().run('foo(12)\n')
+ runctx('from pdb import foo; foo(12)', {}, {})
+ runctx('from pdb import melt; melt(5)', {}, {})
# --------------------- main ---------------------
@@ -600,7 +418,4 @@ def main():
sys.path.insert(0, head)
run('import ' + tail[:-3])
else:
- run('')
-
-def run(statement):
- Pdb().init().run(statement)
+ run(raw_input('Python statement to debug: '))