Richard Wolff's changes:

pdb.py  Uses the Breakpoint class so one can enable/disable breakpoints,
	set temporary ones, set ignore counts, and conditions.  The last
	can be set using the 'b' command
		b 243 , i>4		( b 243,i>4 if you are space adverse)
	or with the condition command so conditions can be changed
	for a particular breakpoint.

	Breakpoints are numbered from 1 on, and if a breakpoint is deleted,
	the number is not reused.  All the breakpoint handling commands
	refer to breakpoints by number.  To be consistent, the clear command
	does so as well, which is the one change from the original pdb that
	is not transparent.  Thus only the breakpoint command 'b' uses a
	line number or file:line or method.  You can also give
		b whrandom.random    and the method will be searched for along
	sys.path.  This is implemented with an 'egrep' command and so
	is not as portable as it might be.  [ see  lineinfo() and
	lineinfoCmd ]

	Breakpoints cannot be set at a line that is blank or a '#' comment
	or starts a triply quoted comment.  This is because I would like
	this behavior in my DDD interface and think it reasonable for
	pdb as well.  It can be removed readily, however as it is all
	incorporated in the routine checkline().  If one attempts to
	set a breakpoint at a 'def' line, the breakpoint is automatically
	moved to the first executable line after the 'def'.  This too is
	in checkline().

	do_EOF() returns zero so typing an end-of-file character as a command
	does nothing.  'quit' does the quitting.

	The routine defaultFile() is present so as to preserve the current
	pdb behavior and yet allow me to override it in pydb.

	There's some code in lineinfo() that is probably mainly useful only
	for pydb and if you prefer, much up to the comment "Best first guess"
	could be removed.

	Keith Davidson provided the code for handling $HOME/.pdbrc and
	./.pdbrc, and it has been incorporated.  He also provided the
	alias handling routine.  I modified it a bit so it could live
	nicely in precmd().  He and I have been in contact; he has the
	new pdb (and pydb) with his code incorporated.  He also asked
	about the possibility of allowing multiple commands on one
	line, such as step;step  or s;s  or with an alias such as
		alias ct tbreak %1 ; continue
	and since it was so easy, that's in place as well.  It's a simple
	'split the line at the first ";"' operation and puts the second
	half in the command queue (self.cmdqueue).  This has the unfortunate
	effect of destroying a line like   print "i: "+i+"; j: "+j
	but either there's a simple way to deal with this, or my attitude
	will remain that pdb is a debugger, not a compiler/parser/etc.
	An alias like   alias 4s  s;;s;
	will work because the adjacent and trailing ";" act like a <cr> which
	repeats the last command.  Of course, either s;s;s;s or s;;;  would be
	a bit more sensible.

	The help commands have been updated.
This commit is contained in:
Guido van Rossum 1998-09-11 22:50:09 +00:00
parent d93643fe4a
commit 2424f855f3
1 changed files with 350 additions and 58 deletions

View File

@ -26,12 +26,35 @@ class Pdb(bdb.Bdb, cmd.Cmd):
bdb.Bdb.__init__(self)
cmd.Cmd.__init__(self)
self.prompt = '(Pdb) '
self.lineinfoCmd = 'egrep -n "def *%s *[(:]" %s /dev/null'
self.aliases = {}
# Try to load readline if it exists
try:
import readline
except ImportError:
pass
# Read $HOME/.pdbrc and ./.pdbrc
self.rcLines = []
if os.environ.has_key('HOME'):
envHome = os.environ['HOME']
try:
rcFile = open (envHome + "/.pdbrc")
except IOError:
pass
else:
for line in rcFile.readlines():
self.rcLines.append (line)
rcFile.close()
try:
rcFile = open ("./.pdbrc")
except IOError:
pass
else:
for line in rcFile.readlines():
self.rcLines.append (line)
rcFile.close()
def reset(self):
bdb.Bdb.reset(self)
self.forget()
@ -46,6 +69,19 @@ class Pdb(bdb.Bdb, cmd.Cmd):
self.forget()
self.stack, self.curindex = self.get_stack(f, t)
self.curframe = self.stack[self.curindex][0]
self.execRcLines();
# Can be executed earlier than 'setup' if desired
def execRcLines(self):
if self.rcLines:
# Make local copy because of recursion
rcLines = self.rcLines
# executed only once
self.rcLines = []
for line in rcLines:
line = line[:-1]
if len (line) > 0 and line[0] != '#':
self.onecmd (line)
# Override Bdb methods (except user_call, for now)
@ -91,16 +127,49 @@ class Pdb(bdb.Bdb, cmd.Cmd):
else: exc_type_name = t.__name__
print '***', exc_type_name + ':', v
def precmd(self, line):
# Handle alias expansion and ';' separator
if not line:
return line
args = string.split(line)
while self.aliases.has_key(args[0]):
line = self.aliases[args[0]]
ii = 1
for tmpArg in args[1:]:
line = string.replace(line, "%" + str(ii),
tmpArg)
ii = ii + 1
line = string.replace (line, "%*",
string.join(args[1:], ' '))
args = string.split(line)
# split into ';' separated commands
# unless it's an alias command
if args[0] != 'alias':
semicolon = string.find(line, ';')
if semicolon >= 0:
# queue up everything after semicolon
next = string.lstrip(line[semicolon+1:])
self.cmdqueue.append(next)
line = string.rstrip(line[:semicolon])
return line
# Command definitions, called by cmdloop()
# The argument is the remaining string on the command line
# Return true to exit from the command loop
do_h = cmd.Cmd.do_help
def do_break(self, arg):
def do_EOF(self, arg):
return 0 # Don't die on EOF
def do_break(self, arg, temporary = 0):
# break [ ([filename:]lineno | function) [, "condition"] ]
if not arg:
print self.get_all_breaks() # XXX
if self.breaks: # There's at least one
print "Num Type Disp Enb Where"
for bp in bdb.Breakpoint.bpbynumber:
if bp:
bp.bpprint()
return
# parse arguments; comma has lowest precendence
# and cannot occur in filename
@ -112,19 +181,17 @@ class Pdb(bdb.Bdb, cmd.Cmd):
# parse stuff after comma: "condition"
cond = string.lstrip(arg[comma+1:])
arg = string.rstrip(arg[:comma])
try:
cond = eval(
cond,
self.curframe.f_globals,
self.curframe.f_locals)
except:
print '*** Could not eval condition:', cond
return
# parse stuff before comma: [filename:]lineno | function
colon = string.rfind(arg, ':')
if colon >= 0:
filename = string.rstrip(arg[:colon])
filename = self.lookupmodule(filename)
f = self.lookupmodule(filename)
if not f:
print '*** ', `filename`,
print 'not found from sys.path'
return
else:
filename = f
arg = string.lstrip(arg[colon+1:])
try:
lineno = int(arg)
@ -141,29 +208,180 @@ class Pdb(bdb.Bdb, cmd.Cmd):
self.curframe.f_globals,
self.curframe.f_locals)
except:
print '*** Could not eval argument:',
print arg
return
func = arg
try:
if hasattr(func, 'im_func'):
func = func.im_func
code = func.func_code
except:
print '*** The specified object',
print 'is not a function', arg
return
lineno = code.co_firstlineno
if not filename:
lineno = code.co_firstlineno
filename = code.co_filename
# supply default filename if necessary
except:
# last thing to try
(ok, filename, ln) = self.lineinfo(arg)
if not ok:
print '*** The specified object',
print `arg`,
print 'is not a function'
print ('or was not found '
'along sys.path.')
return
lineno = int(ln)
if not filename:
filename = self.curframe.f_code.co_filename
# now set the break point
err = self.set_break(filename, lineno, cond)
if err: print '***', err
filename = self.defaultFile()
# Check for reasonable breakpoint
line = self.checkline(filename, lineno)
if line:
# now set the break point
err = self.set_break(filename, line, temporary, cond)
if err: print '***', err
# To be overridden in derived debuggers
def defaultFile(self):
# Produce a reasonable default
filename = self.curframe.f_code.co_filename
if filename == '<string>' and mainpyfile:
filename = mainpyfile
return filename
do_b = do_break
def do_tbreak(self, arg):
self.do_break(arg, 1)
def lineinfo(self, identifier):
failed = (None, None, None)
# Input is identifier, may be in single quotes
idstring = string.split(identifier, "'")
if len(idstring) == 1:
# not in single quotes
id = string.strip(idstring[0])
elif len(idstring) == 3:
# quoted
id = string.strip(idstring[1])
else:
return failed
if id == '': return failed
parts = string.split(id, '.')
# Protection for derived debuggers
if parts[0] == 'self':
del parts[0]
if len(parts) == 0:
return failed
# Best first guess at file to look at
fname = self.defaultFile()
if len(parts) == 1:
item = parts[0]
else:
# More than one part.
# First is module, second is method/class
f = self.lookupmodule(parts[0])
if f:
fname = f
item = parts[1]
grepstring = self.lineinfoCmd % (item, fname)
answer = os.popen(grepstring, 'r').readline()
if answer:
f, line, junk = string.split(answer, ':', 2)
return(item, f,line)
else:
return failed
def checkline(self, filename, lineno):
"""Return line number of first line at or after input
argument such that if the input points to a 'def', the
returned line number is the first
non-blank/non-comment line to follow. If the input
points to a blank or comment line, return 0. At end
of file, also return 0."""
line = linecache.getline(filename, lineno)
if not line:
print 'End of file'
return 0
line = string.strip(line)
# Don't allow setting breakpoint at a blank line
if ( not line or (line[0] == '#') or
(line[:3] == '"""') or line[:3] == "'''" ):
print '*** Blank or comment'
return 0
# When a file is read in and a breakpoint is at
# the 'def' statement, the system stops there at
# code parse time. We don't want that, so all breakpoints
# set at 'def' statements are moved one line onward
if line[:3] == 'def':
incomment = ''
while 1:
lineno = lineno+1
line = linecache.getline(filename, lineno)
if not line:
print 'end of file'
return 0
line = string.strip(line)
if incomment:
if len(line) < 3: continue
if (line[-3:] == incomment):
incomment = ''
continue
if not line: continue # Blank line
if len(line) >= 3:
if (line[:3] == '"""'
or line[:3] == "'''"):
incomment = line[:3]
continue
if line[0] != '#': break
return lineno
def do_enable(self, arg):
args = string.split(arg)
for i in args:
bp = bdb.Breakpoint.bpbynumber[int(i)]
if bp:
bp.enable()
def do_disable(self, arg):
args = string.split(arg)
for i in args:
bp = bdb.Breakpoint.bpbynumber[int(i)]
if bp:
bp.disable()
def do_condition(self, arg):
# arg is breakpoint number and condition
args = string.split(arg, ' ', 1)
bpnum = int(string.strip(args[0]))
try:
cond = args[1]
except:
cond = None
bp = bdb.Breakpoint.bpbynumber[bpnum]
if bp:
bp.cond = cond
if not cond:
print 'Breakpoint', bpnum,
print 'is now unconditional.'
def do_ignore(self,arg):
# arg is bp number followed by ignore count
args = string.split(arg)
bpnum = int(string.strip(args[0]))
try:
count = int(string.strip(args[1]))
except:
count = 0
bp = bdb.Breakpoint.bpbynumber[bpnum]
if bp:
bp.ignore = count
if (count > 0):
reply = 'Will ignore next '
if (count > 1):
reply = reply + '%d crossings' % count
else:
reply = reply + '1 crossing'
print reply + ' of breakpoint %d.' % bpnum
else:
print 'Will stop next time breakpoint',
print bpnum, 'is reached.'
def do_clear(self, arg):
if not arg:
try:
@ -174,21 +392,13 @@ class Pdb(bdb.Bdb, cmd.Cmd):
if reply in ('y', 'yes'):
self.clear_all_breaks()
return
filename = None
colon = string.rfind(arg, ':')
if colon >= 0:
filename = string.rstrip(arg[:colon])
filename = self.lookupmodule(filename)
arg = string.lstrip(arg[colon+1:])
try:
lineno = int(arg)
except:
print '*** Bad lineno:', `arg`
return
if not filename:
filename = self.curframe.f_code.co_filename
err = self.clear_break(filename, lineno)
if err: print '***', err
numberlist = string.split(arg)
for i in numberlist:
err = self.clear_break(i)
if err:
print '***'+err
else:
print 'Deleted breakpoint %s ' % (i,)
do_cl = do_clear # 'c' is already an abbreviation for 'continue'
def do_where(self, arg):
@ -263,7 +473,7 @@ class Pdb(bdb.Bdb, cmd.Cmd):
def do_p(self, arg):
try:
value = eval(arg, self.curframe.f_globals, \
value = eval(arg, self.curframe.f_globals,
self.curframe.f_locals)
except:
t, v = sys.exc_info()[:2]
@ -322,7 +532,7 @@ class Pdb(bdb.Bdb, cmd.Cmd):
def do_whatis(self, arg):
try:
value = eval(arg, self.curframe.f_globals, \
value = eval(arg, self.curframe.f_globals,
self.curframe.f_locals)
except:
t, v = sys.exc_info()[:2]
@ -347,6 +557,25 @@ class Pdb(bdb.Bdb, cmd.Cmd):
# None of the above...
print type(value)
def do_alias(self, arg):
args = string.split (arg)
if len(args) == 0:
keys = self.aliases.keys()
keys.sort()
for alias in keys:
print "%s = %s" % (alias, self.aliases[alias])
return
if self.aliases.has_key(args[0]) and len (args) == 1:
print "%s = %s" % (args[0], self.aliases[args[0]])
else:
self.aliases[args[0]] = string.join(args[1:], ' ')
def do_unalias(self, arg):
args = string.split (arg)
if len(args) == 0: return
if self.aliases.has_key(args[0]):
del self.aliases[args[0]]
# Print a traceback starting at the top stack frame.
# The most recently entered frame is printed last;
# this is different from dbx and gdb, but consistent with
@ -414,28 +643,57 @@ class Pdb(bdb.Bdb, cmd.Cmd):
def help_b(self):
print """b(reak) ([file:]lineno | function) [, "condition"]
With a line number argument, set a break there in the current
file. With a function name, set a break at the entry of that
function. Without argument, list all breaks. If a second
file. With a function name, set a break at first executable line
of that function. Without argument, list all breaks. If a second
argument is present, it is a string specifying an expression
which must evaluate to true before the breakpoint is honored.
The line number may be prefixed with a filename and a colon,
to specify a breakpoint in another file (probably one that
hasn't been loaded yet). The file is searched on sys.path;
hasn't been loaded yet). The file is searched for on sys.path;
the .py suffix may be omitted."""
def help_clear(self):
self.help_cl()
def help_cl(self):
print """cl(ear) [file:][lineno]
With a line number argument, clear that break in the current file.
Without argument, clear all breaks (but first ask confirmation).
print """cl(ear) [bpnumber [bpnumber...]]
With a space separated list of breakpoint numbers, clear
those breakpoints. Without argument, clear all breaks (but
first ask confirmation).
The line number may be prefixed with a filename and a colon,
to specify a breakpoint in another file (probably one that
hasn't been loaded yet). The file is searched on sys.path;
the .py suffix may be omitted."""
Note that the argument is different from previous versions of
the debugger (in python distributions 1.5.1 and before) where
a linenumber was used instead of breakpoint numbers."""
def help_tbreak(self):
print """tbreak same arguments as break, but breakpoint is
removed when first hit."""
def help_enable(self):
print """enable bpnumber [bpnumber ...]
Enables the breakpoints given as a space separated list of
bp numbers."""
def help_disable(self):
print """disable bpnumber [bpnumber ...]
Disables the breakpoints given as a space separated list of
bp numbers."""
def help_ignore(self):
print """ignore bpnumber count
Sets the ignore count for the given breakpoint number. A breakpoint
becomes active when the ignore count is zero. When non-zero, the
count is decremented each time the breakpoint is reached and the
breakpoint is not disabled and any associated condition evaluates
to true."""
def help_condition(self):
print """condition bpnumber str_condition
str_condition is a string specifying an expression which
must evaluate to true before the breakpoint is honored.
If str_condition is absent, any existing condition is removed;
i.e., the breakpoint is made unconditional."""
def help_step(self):
self.help_s()
@ -511,26 +769,60 @@ class Pdb(bdb.Bdb, cmd.Cmd):
print """q(uit) Quit from the debugger.
The program being executed is aborted."""
def help_whatis(self):
print """whatis arg
Prints the type of the argument."""
def help_EOF(self):
print """EOF
Handles the receipt of EOF as a command."""
def help_alias(self):
print """alias [name [command [parameter parameter ...] ]]
Creates an alias called 'name' the executes 'command'. The command
must *not* be enclosed in quotes. Replaceable parameters are
indicated by %1, %2, and so on, while %* is replaced by all the
parameters. If no command is given, the current alias for name
is shown. If no name is given, all aliases are listed.
Aliases may be nested and can contain anything that can be
legally typed at the pdb prompt. Note! You *can* override
internal pdb commands with aliases! Those internal commands
are then hidden until the alias is removed. Aliasing is recursively
applied to the first word of the command line; all other words
in the line are left alone.
Some useful aliases (especially when placed in the .pdbrc file) are:
#Print instance variables (usage "pi classInst")
alias pi for k in %1.__dict__.keys(): print "%1.",k,"=",%1.__dict__[k]
#Print instance variables in self
alias ps pi self
"""
def help_unalias(self):
print """unalias name
Deletes the specified alias."""
def help_pdb(self):
help()
# Helper function for break/clear parsing -- may be overridden
def lookupmodule(self, filename):
if filename == mainmodule:
return mainpyfile
root, ext = os.path.splitext(filename)
if ext == '':
filename = filename + '.py'
if os.path.isabs(filename):
return filename
for dirname in sys.path:
while os.path.islink(dirname):
dirname = os.readlink(dirname)
fullname = os.path.join(dirname, filename)
if os.path.exists(fullname):
return fullname
print 'Warning:', `filename`, 'not found from sys.path'
return filename
return None
# Simplified interface