mirror of
https://github.com/python/cpython.git
synced 2024-11-27 11:55:13 +08:00
4acc25bd39
1. Comments at the beginning of the module, before functions, and before classes have been turned into docstrings. 2. Tabs are normalized to four spaces. Also, removed the "remove" function from dircmp.py, which reimplements list.remove() (it must have been very old).
559 lines
18 KiB
Python
559 lines
18 KiB
Python
"""Debugger basics"""
|
|
|
|
import sys
|
|
import os
|
|
import types
|
|
|
|
BdbQuit = 'bdb.BdbQuit' # Exception to give up completely
|
|
|
|
|
|
class Bdb:
|
|
|
|
"""Generic Python debugger base class.
|
|
|
|
This class takes care of details of the trace facility;
|
|
a derived class should implement user interaction.
|
|
The standard debugger class (pdb.Pdb) is an example.
|
|
"""
|
|
|
|
def __init__(self):
|
|
self.breaks = {}
|
|
self.fncache = {}
|
|
|
|
def canonic(self, filename):
|
|
canonic = self.fncache.get(filename)
|
|
if not canonic:
|
|
canonic = os.path.abspath(filename)
|
|
self.fncache[filename] = canonic
|
|
return canonic
|
|
|
|
def reset(self):
|
|
import linecache
|
|
linecache.checkcache()
|
|
self.botframe = None
|
|
self.stopframe = None
|
|
self.returnframe = None
|
|
self.quitting = 0
|
|
|
|
def trace_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 'bdb.Bdb.dispatch: unknown debugging event:', `event`
|
|
return self.trace_dispatch
|
|
|
|
def dispatch_line(self, frame):
|
|
if self.stop_here(frame) or self.break_here(frame):
|
|
self.user_line(frame)
|
|
if self.quitting: raise BdbQuit
|
|
return self.trace_dispatch
|
|
|
|
def dispatch_call(self, frame, arg):
|
|
# XXX 'arg' is no longer used
|
|
if self.botframe is None:
|
|
# First call of dispatch since reset()
|
|
self.botframe = frame
|
|
return self.trace_dispatch
|
|
if not (self.stop_here(frame) or self.break_anywhere(frame)):
|
|
# No need to trace this function
|
|
return # None
|
|
self.user_call(frame, arg)
|
|
if self.quitting: raise BdbQuit
|
|
return self.trace_dispatch
|
|
|
|
def dispatch_return(self, frame, arg):
|
|
if self.stop_here(frame) or frame == self.returnframe:
|
|
self.user_return(frame, arg)
|
|
if self.quitting: raise BdbQuit
|
|
|
|
def dispatch_exception(self, frame, arg):
|
|
if self.stop_here(frame):
|
|
self.user_exception(frame, arg)
|
|
if self.quitting: raise BdbQuit
|
|
return self.trace_dispatch
|
|
|
|
# Normally derived classes don't override the following
|
|
# methods, but they may if they want to redefine the
|
|
# definition of stopping and breakpoints.
|
|
|
|
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):
|
|
filename = self.canonic(frame.f_code.co_filename)
|
|
if not self.breaks.has_key(filename):
|
|
return 0
|
|
lineno = frame.f_lineno
|
|
if not lineno in self.breaks[filename]:
|
|
return 0
|
|
# flag says ok to delete temp. bp
|
|
(bp, flag) = effective(filename, lineno, frame)
|
|
if bp:
|
|
self.currentbp = bp.number
|
|
if (flag and bp.temporary):
|
|
self.do_clear(str(bp.number))
|
|
return 1
|
|
else:
|
|
return 0
|
|
|
|
def break_anywhere(self, frame):
|
|
return self.breaks.has_key(
|
|
self.canonic(frame.f_code.co_filename))
|
|
|
|
# Derived classes should override the user_* methods
|
|
# to gain control.
|
|
|
|
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."""
|
|
pass
|
|
|
|
def user_line(self, frame):
|
|
"""This method is called when we stop or break at this line."""
|
|
pass
|
|
|
|
def user_return(self, frame, return_value):
|
|
"""This method is called when a return trap is set here."""
|
|
pass
|
|
|
|
def user_exception(self, frame, (exc_type, exc_value, exc_traceback)):
|
|
"""This method is called if an exception occurs,
|
|
but only if we are to stop at or just below this level."""
|
|
pass
|
|
|
|
# Derived classes and clients can call the following methods
|
|
# to affect the stepping state.
|
|
|
|
def set_step(self):
|
|
"""Stop after one line of code."""
|
|
self.stopframe = None
|
|
self.returnframe = None
|
|
self.quitting = 0
|
|
|
|
def set_next(self, frame):
|
|
"""Stop on the next line in or below the given frame."""
|
|
self.stopframe = frame
|
|
self.returnframe = None
|
|
self.quitting = 0
|
|
|
|
def set_return(self, frame):
|
|
"""Stop when returning from the given frame."""
|
|
self.stopframe = frame.f_back
|
|
self.returnframe = frame
|
|
self.quitting = 0
|
|
|
|
def set_trace(self):
|
|
"""Start debugging from here."""
|
|
try:
|
|
1 + ''
|
|
except:
|
|
frame = sys.exc_info()[2].tb_frame.f_back
|
|
self.reset()
|
|
while frame:
|
|
frame.f_trace = self.trace_dispatch
|
|
self.botframe = frame
|
|
frame = frame.f_back
|
|
self.set_step()
|
|
sys.settrace(self.trace_dispatch)
|
|
|
|
def set_continue(self):
|
|
# Don't stop except at breakpoints or when finished
|
|
self.stopframe = self.botframe
|
|
self.returnframe = None
|
|
self.quitting = 0
|
|
if not self.breaks:
|
|
# no breakpoints; run without debugger overhead
|
|
sys.settrace(None)
|
|
try:
|
|
1 + '' # raise an exception
|
|
except:
|
|
frame = sys.exc_info()[2].tb_frame.f_back
|
|
while frame and frame is not self.botframe:
|
|
del frame.f_trace
|
|
frame = frame.f_back
|
|
|
|
def set_quit(self):
|
|
self.stopframe = self.botframe
|
|
self.returnframe = None
|
|
self.quitting = 1
|
|
sys.settrace(None)
|
|
|
|
# Derived classes and clients can call the following methods
|
|
# to manipulate breakpoints. These methods return an
|
|
# error message is something went wrong, None if all is well.
|
|
# Set_break prints out the breakpoint line and file:lineno.
|
|
# Call self.get_*break*() to see the breakpoints or better
|
|
# for bp in Breakpoint.bpbynumber: if bp: bp.bpprint().
|
|
|
|
def set_break(self, filename, lineno, temporary=0, cond = None):
|
|
filename = self.canonic(filename)
|
|
import linecache # Import as late as possible
|
|
line = linecache.getline(filename, lineno)
|
|
if not line:
|
|
return 'Line %s:%d does not exist' % (filename,
|
|
lineno)
|
|
if not self.breaks.has_key(filename):
|
|
self.breaks[filename] = []
|
|
list = self.breaks[filename]
|
|
if not lineno in list:
|
|
list.append(lineno)
|
|
bp = Breakpoint(filename, lineno, temporary, cond)
|
|
|
|
def clear_break(self, filename, lineno):
|
|
filename = self.canonic(filename)
|
|
if not self.breaks.has_key(filename):
|
|
return 'There are no breakpoints in %s' % filename
|
|
if lineno not in self.breaks[filename]:
|
|
return 'There is no breakpoint at %s:%d' % (filename,
|
|
lineno)
|
|
# If there's only one bp in the list for that file,line
|
|
# pair, then remove the breaks entry
|
|
for bp in Breakpoint.bplist[filename, lineno][:]:
|
|
bp.deleteMe()
|
|
if not Breakpoint.bplist.has_key((filename, lineno)):
|
|
self.breaks[filename].remove(lineno)
|
|
if not self.breaks[filename]:
|
|
del self.breaks[filename]
|
|
|
|
def clear_bpbynumber(self, arg):
|
|
try:
|
|
number = int(arg)
|
|
except:
|
|
return 'Non-numeric breakpoint number (%s)' % arg
|
|
try:
|
|
bp = Breakpoint.bpbynumber[number]
|
|
except IndexError:
|
|
return 'Breakpoint number (%d) out of range' % number
|
|
if not bp:
|
|
return 'Breakpoint (%d) already deleted' % number
|
|
self.clear_break(bp.file, bp.line)
|
|
|
|
def clear_all_file_breaks(self, filename):
|
|
filename = self.canonic(filename)
|
|
if not self.breaks.has_key(filename):
|
|
return 'There are no breakpoints in %s' % filename
|
|
for line in self.breaks[filename]:
|
|
blist = Breakpoint.bplist[filename, line]
|
|
for bp in blist:
|
|
bp.deleteMe()
|
|
del self.breaks[filename]
|
|
|
|
def clear_all_breaks(self):
|
|
if not self.breaks:
|
|
return 'There are no breakpoints'
|
|
for bp in Breakpoint.bpbynumber:
|
|
if bp:
|
|
bp.deleteMe()
|
|
self.breaks = {}
|
|
|
|
def get_break(self, filename, lineno):
|
|
filename = self.canonic(filename)
|
|
return self.breaks.has_key(filename) and \
|
|
lineno in self.breaks[filename]
|
|
|
|
def get_breaks(self, filename, lineno):
|
|
filename = self.canonic(filename)
|
|
return self.breaks.has_key(filename) and \
|
|
lineno in self.breaks[filename] and \
|
|
Breakpoint.bplist[filename, lineno] or []
|
|
|
|
def get_file_breaks(self, filename):
|
|
filename = self.canonic(filename)
|
|
if self.breaks.has_key(filename):
|
|
return self.breaks[filename]
|
|
else:
|
|
return []
|
|
|
|
def get_all_breaks(self):
|
|
return self.breaks
|
|
|
|
# Derived classes and clients can call the following method
|
|
# to get a data structure representing a stack trace.
|
|
|
|
def get_stack(self, f, t):
|
|
stack = []
|
|
if t and t.tb_frame is f:
|
|
t = t.tb_next
|
|
while f is not None:
|
|
stack.append((f, f.f_lineno))
|
|
if f is self.botframe:
|
|
break
|
|
f = f.f_back
|
|
stack.reverse()
|
|
i = max(0, len(stack) - 1)
|
|
while t is not None:
|
|
stack.append((t.tb_frame, t.tb_lineno))
|
|
t = t.tb_next
|
|
return stack, i
|
|
|
|
#
|
|
|
|
def format_stack_entry(self, frame_lineno, lprefix=': '):
|
|
import linecache, repr, string
|
|
frame, lineno = frame_lineno
|
|
filename = self.canonic(frame.f_code.co_filename)
|
|
s = filename + '(' + `lineno` + ')'
|
|
if frame.f_code.co_name:
|
|
s = s + frame.f_code.co_name
|
|
else:
|
|
s = s + "<lambda>"
|
|
if frame.f_locals.has_key('__args__'):
|
|
args = frame.f_locals['__args__']
|
|
else:
|
|
args = None
|
|
if args:
|
|
s = s + repr.repr(args)
|
|
else:
|
|
s = s + '()'
|
|
if frame.f_locals.has_key('__return__'):
|
|
rv = frame.f_locals['__return__']
|
|
s = s + '->'
|
|
s = s + repr.repr(rv)
|
|
line = linecache.getline(filename, lineno)
|
|
if line: s = s + lprefix + string.strip(line)
|
|
return s
|
|
|
|
# The following two methods can be called by clients to use
|
|
# a debugger to debug a statement, given as a string.
|
|
|
|
def run(self, cmd, globals=None, locals=None):
|
|
if globals is None:
|
|
import __main__
|
|
globals = __main__.__dict__
|
|
if locals is None:
|
|
locals = globals
|
|
self.reset()
|
|
sys.settrace(self.trace_dispatch)
|
|
if not isinstance(cmd, types.CodeType):
|
|
cmd = cmd+'\n'
|
|
try:
|
|
try:
|
|
exec cmd in globals, locals
|
|
except BdbQuit:
|
|
pass
|
|
finally:
|
|
self.quitting = 1
|
|
sys.settrace(None)
|
|
|
|
def runeval(self, expr, globals=None, locals=None):
|
|
if globals is None:
|
|
import __main__
|
|
globals = __main__.__dict__
|
|
if locals is None:
|
|
locals = globals
|
|
self.reset()
|
|
sys.settrace(self.trace_dispatch)
|
|
if not isinstance(expr, types.CodeType):
|
|
expr = expr+'\n'
|
|
try:
|
|
try:
|
|
return eval(expr, globals, locals)
|
|
except BdbQuit:
|
|
pass
|
|
finally:
|
|
self.quitting = 1
|
|
sys.settrace(None)
|
|
|
|
def runctx(self, cmd, globals, locals):
|
|
# B/W compatibility
|
|
self.run(cmd, globals, locals)
|
|
|
|
# This method is more useful to debug a single function call.
|
|
|
|
def runcall(self, func, *args):
|
|
self.reset()
|
|
sys.settrace(self.trace_dispatch)
|
|
res = None
|
|
try:
|
|
try:
|
|
res = apply(func, args)
|
|
except BdbQuit:
|
|
pass
|
|
finally:
|
|
self.quitting = 1
|
|
sys.settrace(None)
|
|
return res
|
|
|
|
|
|
def set_trace():
|
|
Bdb().set_trace()
|
|
|
|
|
|
class Breakpoint:
|
|
|
|
"""Breakpoint class
|
|
|
|
Implements temporary breakpoints, ignore counts, disabling and
|
|
(re)-enabling, and conditionals.
|
|
|
|
Breakpoints are indexed by number through bpbynumber and by
|
|
the file,line tuple using bplist. The former points to a
|
|
single instance of class Breakpoint. The latter points to a
|
|
list of such instances since there may be more than one
|
|
breakpoint per line.
|
|
|
|
"""
|
|
|
|
# XXX Keeping state in the class is a mistake -- this means
|
|
# you cannot have more than one active Bdb instance.
|
|
|
|
next = 1 # Next bp to be assigned
|
|
bplist = {} # indexed by (file, lineno) tuple
|
|
bpbynumber = [None] # Each entry is None or an instance of Bpt
|
|
# index 0 is unused, except for marking an
|
|
# effective break .... see effective()
|
|
|
|
def __init__(self, file, line, temporary=0, cond = None):
|
|
self.file = file # This better be in canonical form!
|
|
self.line = line
|
|
self.temporary = temporary
|
|
self.cond = cond
|
|
self.enabled = 1
|
|
self.ignore = 0
|
|
self.hits = 0
|
|
self.number = Breakpoint.next
|
|
Breakpoint.next = Breakpoint.next + 1
|
|
# Build the two lists
|
|
self.bpbynumber.append(self)
|
|
if self.bplist.has_key((file, line)):
|
|
self.bplist[file, line].append(self)
|
|
else:
|
|
self.bplist[file, line] = [self]
|
|
|
|
|
|
def deleteMe(self):
|
|
index = (self.file, self.line)
|
|
self.bpbynumber[self.number] = None # No longer in list
|
|
self.bplist[index].remove(self)
|
|
if not self.bplist[index]:
|
|
# No more bp for this f:l combo
|
|
del self.bplist[index]
|
|
|
|
def enable(self):
|
|
self.enabled = 1
|
|
|
|
def disable(self):
|
|
self.enabled = 0
|
|
|
|
def bpprint(self):
|
|
if self.temporary:
|
|
disp = 'del '
|
|
else:
|
|
disp = 'keep '
|
|
if self.enabled:
|
|
disp = disp + 'yes'
|
|
else:
|
|
disp = disp + 'no '
|
|
print '%-4dbreakpoint %s at %s:%d' % (self.number, disp,
|
|
self.file, self.line)
|
|
if self.cond:
|
|
print '\tstop only if %s' % (self.cond,)
|
|
if self.ignore:
|
|
print '\tignore next %d hits' % (self.ignore)
|
|
if (self.hits):
|
|
if (self.hits > 1): ss = 's'
|
|
else: ss = ''
|
|
print ('\tbreakpoint already hit %d time%s' %
|
|
(self.hits, ss))
|
|
|
|
# -----------end of Breakpoint class----------
|
|
|
|
# Determines if there is an effective (active) breakpoint at this
|
|
# line of code. Returns breakpoint number or 0 if none
|
|
def effective(file, line, frame):
|
|
"""Determine which breakpoint for this file:line is to be acted upon.
|
|
|
|
Called only if we know there is a bpt at this
|
|
location. Returns breakpoint that was triggered and a flag
|
|
that indicates if it is ok to delete a temporary bp.
|
|
|
|
"""
|
|
possibles = Breakpoint.bplist[file,line]
|
|
for i in range(0, len(possibles)):
|
|
b = possibles[i]
|
|
if b.enabled == 0:
|
|
continue
|
|
# Count every hit when bp is enabled
|
|
b.hits = b.hits + 1
|
|
if not b.cond:
|
|
# If unconditional, and ignoring,
|
|
# go on to next, else break
|
|
if b.ignore > 0:
|
|
b.ignore = b.ignore -1
|
|
continue
|
|
else:
|
|
# breakpoint and marker that's ok
|
|
# to delete if temporary
|
|
return (b,1)
|
|
else:
|
|
# Conditional bp.
|
|
# Ignore count applies only to those bpt hits where the
|
|
# condition evaluates to true.
|
|
try:
|
|
val = eval(b.cond, frame.f_globals,
|
|
frame.f_locals)
|
|
if val:
|
|
if b.ignore > 0:
|
|
b.ignore = b.ignore -1
|
|
# continue
|
|
else:
|
|
return (b,1)
|
|
# else:
|
|
# continue
|
|
except:
|
|
# if eval fails, most conservative
|
|
# thing is to stop on breakpoint
|
|
# regardless of ignore count.
|
|
# Don't delete temporary,
|
|
# as another hint to user.
|
|
return (b,0)
|
|
return (None, None)
|
|
|
|
# -------------------- testing --------------------
|
|
|
|
class Tdb(Bdb):
|
|
def user_call(self, frame, args):
|
|
name = frame.f_code.co_name
|
|
if not name: name = '???'
|
|
print '+++ call', name, args
|
|
def user_line(self, frame):
|
|
import linecache, string
|
|
name = frame.f_code.co_name
|
|
if not name: name = '???'
|
|
fn = self.canonic(frame.f_code.co_filename)
|
|
line = linecache.getline(fn, frame.f_lineno)
|
|
print '+++', fn, frame.f_lineno, name, ':', string.strip(line)
|
|
def user_return(self, frame, retval):
|
|
print '+++ return', retval
|
|
def user_exception(self, frame, exc_stuff):
|
|
print '+++ exception', exc_stuff
|
|
self.set_continue()
|
|
|
|
def foo(n):
|
|
print 'foo(', n, ')'
|
|
x = bar(n*10)
|
|
print 'bar returned', x
|
|
|
|
def bar(a):
|
|
print 'bar(', a, ')'
|
|
return a/2
|
|
|
|
def test():
|
|
t = Tdb()
|
|
t.run('import bdb; bdb.foo(10)')
|