Improvements when running pdb as a script.

Bug fixes:

* Use fresh copy of globals/locals so the script being debugged can't access
  the pdb namespace (e.g.: p line_prefix will no longer work).

* Remove pdb.py's path from sys.path. Having it in there is normally not a
  problem, but it could prove irritating when messing with PYTHONPATH or
  invoking pdb via /usr/bin/pdf.

* You can now set a breakpoint on the script being debugged, even if the script
  doesn't end with a '.py' extension. Also, setting breakpoints with absolute
  paths now works reliably.

Enhancements:

* Go directly to the first line of the script.

* Enter post-mortem debugging if the script being debugged doesn't catch an
  exception.

* Restart the script being debugged and preserve debugger state when the script
  being debugged exits.

Cleanup:

* Moved the __main__ method into a main() function.

* Kill the (undocumented, not in __all__) mainmodule/mainpyfile globals, add a
  mainpyfile attribute to pdb.

Thanks Ilya Sandler for the patch!
This commit is contained in:
Johannes Gijsbers 2004-10-12 18:12:09 +00:00
parent 51ee66e611
commit 25b38c8969
1 changed files with 81 additions and 16 deletions

View File

@ -12,7 +12,7 @@ from repr import Repr
import os
import re
import pprint
import traceback
# Create a custom safe Repr instance and increase its maxstring.
# The default of 30 truncates error messages too easily.
_repr = Repr()
@ -57,6 +57,8 @@ class Pdb(bdb.Bdb, cmd.Cmd):
cmd.Cmd.__init__(self)
self.prompt = '(Pdb) '
self.aliases = {}
self.mainpyfile = ''
self._wait_for_mainpyfile = 0
# Try to load readline if it exists
try:
import readline
@ -117,12 +119,19 @@ class Pdb(bdb.Bdb, cmd.Cmd):
def user_call(self, frame, argument_list):
"""This method is called when there is the remote possibility
that we ever need to stop in this function."""
if self._wait_for_mainpyfile:
return
if self.stop_here(frame):
print '--Call--'
self.interaction(frame, None)
def user_line(self, frame):
"""This function is called when we stop or break at this line."""
if self._wait_for_mainpyfile:
if (self.mainpyfile != self.canonic(frame.f_code.co_filename)
or frame.f_lineno<= 0):
return
self._wait_for_mainpyfile = 0
self.interaction(frame, None)
def user_return(self, frame, return_value):
@ -281,8 +290,8 @@ class Pdb(bdb.Bdb, cmd.Cmd):
def defaultFile(self):
"""Produce a reasonable default."""
filename = self.curframe.f_code.co_filename
if filename == '<string>' and mainpyfile:
filename = mainpyfile
if filename == '<string>' and self.mainpyfile:
filename = self.mainpyfile
return filename
do_b = do_break
@ -525,13 +534,16 @@ class Pdb(bdb.Bdb, cmd.Cmd):
self.lastcmd = p.lastcmd
def do_quit(self, arg):
self._user_requested_quit = 1
self.set_quit()
return 1
do_q = do_quit
do_exit = do_quit
def do_EOF(self, arg):
print
self._user_requested_quit = 1
self.set_quit()
return 1
@ -928,7 +940,16 @@ Deletes the specified alias."""
help()
def lookupmodule(self, filename):
"""Helper function for break/clear parsing -- may be overridden."""
"""Helper function for break/clear parsing -- may be overridden.
lookupmodule() translates (possibly incomplete) file or module name
into an absolute file name.
"""
if os.path.isabs(filename) and os.path.exists(filename):
return filename
f = os.path.join(sys.path[0], filename)
if os.path.exists(f) and self.canonic(f) == self.mainpyfile:
return f
root, ext = os.path.splitext(filename)
if ext == '':
filename = filename + '.py'
@ -942,6 +963,24 @@ Deletes the specified alias."""
return fullname
return None
def _runscript(self, filename):
# Start with fresh empty copy of globals and locals and tell the script
# that it's being run as __main__ to avoid scripts being able to access
# the pdb.py namespace.
globals_ = {"__name__" : "__main__"}
locals_ = globals_
# When bdb sets tracing, a number of call and line events happens
# BEFORE debugger even reaches user's code (and the exact sequence of
# events depends on python version). So we take special measures to
# avoid stopping before we reach the main script (see user_line and
# user_call for details).
self._wait_for_mainpyfile = 1
self.mainpyfile = self.canonic(filename)
self._user_requested_quit = 0
statement = 'execfile( "%s")' % filename
self.run(statement, globals=globals_, locals=locals_)
# Simplified interface
def run(statement, globals=None, locals=None):
@ -992,23 +1031,49 @@ def help():
print 'Sorry, can\'t find the help file "pdb.doc"',
print 'along the Python search path'
mainmodule = ''
mainpyfile = ''
# When invoked as main program, invoke the debugger on a script
if __name__=='__main__':
def main():
if not sys.argv[1:]:
print "usage: pdb.py scriptfile [arg] ..."
sys.exit(2)
mainpyfile = filename = sys.argv[1] # Get script filename
if not os.path.exists(filename):
print 'Error:', repr(filename), 'does not exist'
mainpyfile = sys.argv[1] # Get script filename
if not os.path.exists(mainpyfile):
print 'Error:', mainpyfile, 'does not exist'
sys.exit(1)
mainmodule = os.path.basename(filename)
del sys.argv[0] # Hide "pdb.py" from argument list
# Insert script directory in front of module search path
sys.path.insert(0, os.path.dirname(filename))
# Replace pdb's dir with script's dir in front of module search path.
sys.path[0] = os.path.dirname(mainpyfile)
run('execfile(%r)' % (filename,))
# Note on saving/restoring sys.argv: it's a good idea when sys.argv was
# modified by the script being debugged. It's a bad idea when it was
# changed by the user from the command line. The best approach would be to
# have a "restart" command which would allow explicit specification of
# command line arguments.
pdb = Pdb()
while 1:
try:
pdb._runscript(mainpyfile)
if pdb._user_requested_quit:
break
print "The program finished and will be restarted"
except SystemExit:
# In most cases SystemExit does not warrant a post-mortem session.
print "The program exited via sys.exit(). Exit status: ",
print sys.exc_info()[1]
except:
traceback.print_exc()
print "Uncaught exception. Entering post mortem debugging"
print "Running 'cont' or 'step' will restart the program"
t = sys.exc_info()[2]
while t.tb_next is not None:
t = t.tb_next
pdb.interaction(t.tb_frame,t)
print "Post mortem debugger finished. The "+mainpyfile+" will be restarted"
# When invoked as main program, invoke the debugger on a script
if __name__=='__main__':
main()