__all__
= ["BdbQuit","Bdb","Breakpoint"]
class BdbQuit(Exception):
"""Exception to give up completely"""
"""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 canonic(self
, filename
):
if filename
== "<" + filename
[1:-1] + ">":
canonic
= self
.fncache
.get(filename
)
canonic
= os
.path
.abspath(filename
)
canonic
= os
.path
.normcase(canonic
)
self
.fncache
[filename
] = canonic
def trace_dispatch(self
, frame
, event
, arg
):
return self
.dispatch_line(frame
)
return self
.dispatch_call(frame
, arg
)
return self
.dispatch_return(frame
, arg
)
return self
.dispatch_exception(frame
, arg
)
return self
.trace_dispatch
if event
== 'c_exception':
return self
.trace_dispatch
return self
.trace_dispatch
print 'bdb.Bdb.dispatch: unknown debugging event:', repr(event
)
return self
.trace_dispatch
def dispatch_line(self
, frame
):
if self
.stop_here(frame
) or self
.break_here(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
.f_back
# (CT) Note that this may also be None!
return self
.trace_dispatch
if not (self
.stop_here(frame
) or self
.break_anywhere(frame
)):
# No need to trace this function
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
return self
.trace_dispatch
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
):
# (CT) stopframe may now also be None, see dispatch_call.
# (CT) the former test for None is therefore removed from here.
if frame
is self
.stopframe
:
while frame
is not None and frame
is not self
.stopframe
:
if frame
is self
.botframe
:
def break_here(self
, frame
):
filename
= self
.canonic(frame
.f_code
.co_filename
)
if not filename
in self
.breaks
:
if not lineno
in self
.breaks
[filename
]:
# The line itself has no breakpoint, but maybe the line is the
# first line of a function with breakpoint set by function name.
lineno
= frame
.f_code
.co_firstlineno
if not lineno
in self
.breaks
[filename
]:
# flag says ok to delete temp. bp
(bp
, flag
) = effective(filename
, lineno
, frame
)
self
.currentbp
= bp
.number
if (flag
and bp
.temporary
):
self
.do_clear(str(bp
.number
))
raise NotImplementedError, "subclass of bdb must implement do_clear()"
def break_anywhere(self
, frame
):
return self
.breaks
.has_key(
self
.canonic(frame
.f_code
.co_filename
))
# Derived classes should override the user_* methods
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."""
def user_line(self
, frame
):
"""This method is called when we stop or break at this line."""
def user_return(self
, frame
, return_value
):
"""This method is called when a return trap is set here."""
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."""
# Derived classes and clients can call the following methods
# to affect the stepping state.
"""Stop after one line of code."""
def set_next(self
, frame
):
"""Stop on the next line in or below the given frame."""
def set_return(self
, frame
):
"""Stop when returning from the given frame."""
self
.stopframe
= frame
.f_back
def set_trace(self
, frame
=None):
"""Start debugging from `frame`.
If frame is not specified, debugging starts from caller's frame.
frame
= sys
._getframe
().f_back
frame
.f_trace
= self
.trace_dispatch
sys
.settrace(self
.trace_dispatch
)
# Don't stop except at breakpoints or when finished
self
.stopframe
= self
.botframe
# no breakpoints; run without debugger overhead
frame
= sys
._getframe
().f_back
while frame
and frame
is not self
.botframe
:
self
.stopframe
= self
.botframe
# 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
)
return 'Line %s:%d does not exist' % (filename
,
if not filename
in self
.breaks
:
self
.breaks
[filename
] = []
list = self
.breaks
[filename
]
bp
= Breakpoint(filename
, lineno
, temporary
, cond
, funcname
)
def clear_break(self
, filename
, lineno
):
filename
= self
.canonic(filename
)
if not filename
in self
.breaks
:
return 'There are no breakpoints in %s' % filename
if lineno
not in self
.breaks
[filename
]:
return 'There is no breakpoint at %s:%d' % (filename
,
# 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
][:]:
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
):
return 'Non-numeric breakpoint number (%s)' % arg
bp
= Breakpoint
.bpbynumber
[number
]
return 'Breakpoint number (%d) out of range' % number
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 filename
in self
.breaks
:
return 'There are no breakpoints in %s' % filename
for line
in self
.breaks
[filename
]:
blist
= Breakpoint
.bplist
[filename
, line
]
del self
.breaks
[filename
]
def clear_all_breaks(self
):
return 'There are no breakpoints'
for bp
in Breakpoint
.bpbynumber
:
def get_break(self
, filename
, lineno
):
filename
= self
.canonic(filename
)
return filename
in self
.breaks
and \
lineno
in self
.breaks
[filename
]
def get_breaks(self
, filename
, lineno
):
filename
= self
.canonic(filename
)
return filename
in self
.breaks
and \
lineno
in self
.breaks
[filename
] and \
Breakpoint
.bplist
[filename
, lineno
] or []
def get_file_breaks(self
, filename
):
filename
= self
.canonic(filename
)
if filename
in self
.breaks
:
return self
.breaks
[filename
]
def get_all_breaks(self
):
# Derived classes and clients can call the following method
# to get a data structure representing a stack trace.
def get_stack(self
, f
, t
):
if t
and t
.tb_frame
is f
:
stack
.append((f
, f
.f_lineno
))
i
= max(0, len(stack
) - 1)
stack
.append((t
.tb_frame
, t
.tb_lineno
))
def format_stack_entry(self
, frame_lineno
, lprefix
=': '):
frame
, lineno
= frame_lineno
filename
= self
.canonic(frame
.f_code
.co_filename
)
s
= '%s(%r)' % (filename
, lineno
)
s
= s
+ frame
.f_code
.co_name
if '__args__' in frame
.f_locals
:
args
= frame
.f_locals
['__args__']
if '__return__' in frame
.f_locals
:
rv
= frame
.f_locals
['__return__']
line
= linecache
.getline(filename
, lineno
)
if line
: s
= s
+ lprefix
+ line
.strip()
# 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):
globals = __main__
.__dict
__
sys
.settrace(self
.trace_dispatch
)
if not isinstance(cmd
, types
.CodeType
):
exec cmd
in globals, locals
def runeval(self
, expr
, globals=None, locals=None):
globals = __main__
.__dict
__
sys
.settrace(self
.trace_dispatch
)
if not isinstance(expr
, types
.CodeType
):
return eval(expr
, globals, locals)
def runctx(self
, cmd
, globals, locals):
self
.run(cmd
, globals, locals)
# This method is more useful to debug a single function call.
def runcall(self
, func
, *args
, **kwds
):
sys
.settrace(self
.trace_dispatch
)
res
= func(*args
, **kwds
)
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
# 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, funcname
=None):
# Needed if funcname is not None.
self
.func_first_executable_line
= None
self
.file = file # This better be in canonical form!
self
.temporary
= temporary
self
.number
= Breakpoint
.next
Breakpoint
.next
= Breakpoint
.next
+ 1
self
.bpbynumber
.append(self
)
if self
.bplist
.has_key((file, line
)):
self
.bplist
[file, line
].append(self
)
self
.bplist
[file, line
] = [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
print '%-4dbreakpoint %s at %s:%d' % (self
.number
, disp
,
print '\tstop only if %s' % (self
.cond
,)
print '\tignore next %d hits' % (self
.ignore
)
if (self
.hits
> 1): ss
= 's'
print ('\tbreakpoint already hit %d time%s' %
# -----------end of Breakpoint class----------
def checkfuncname(b
, frame
):
"""Check whether we should break here because of `b.funcname`."""
# Breakpoint was set via line number.
if b
.line
!= frame
.f_lineno
:
# Breakpoint was set at a line with a def statement and the function
# defined is called: don't break.
# Breakpoint set via function name.
if frame
.f_code
.co_name
!= b
.funcname
:
# It's not a function call, but rather execution of def statement.
# We are in the right frame.
if not b
.func_first_executable_line
:
# The function is entered for the 1st time.
b
.func_first_executable_line
= frame
.f_lineno
if b
.func_first_executable_line
!= frame
.f_lineno
:
# But we are not at the first line number: don't break.
# 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
)):
if not checkfuncname(b
, frame
):
# Count every hit when bp is enabled
# If unconditional, and ignoring,
# go on to next, else break
# breakpoint and marker that's ok
# Ignore count applies only to those bpt hits where the
# condition evaluates to true.
val
= eval(b
.cond
, frame
.f_globals
,
# if eval fails, most conservative
# thing is to stop on breakpoint
# regardless of ignore count.
# Don't delete temporary,
# as another hint to user.
# -------------------- testing --------------------
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
):
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
, ':', line
.strip()
def user_return(self
, frame
, retval
):
print '+++ return', retval
def user_exception(self
, frame
, exc_stuff
):
print '+++ exception', exc_stuff
t
.run('import bdb; bdb.foo(10)')