+++ /dev/null
-"""Debugger basics"""\r
-\r
-import fnmatch\r
-import sys\r
-import os\r
-import types\r
-\r
-__all__ = ["BdbQuit","Bdb","Breakpoint"]\r
-\r
-class BdbQuit(Exception):\r
- """Exception to give up completely"""\r
-\r
-\r
-class Bdb:\r
-\r
- """Generic Python debugger base class.\r
-\r
- This class takes care of details of the trace facility;\r
- a derived class should implement user interaction.\r
- The standard debugger class (pdb.Pdb) is an example.\r
- """\r
-\r
- def __init__(self, skip=None):\r
- self.skip = set(skip) if skip else None\r
- self.breaks = {}\r
- self.fncache = {}\r
-\r
- def canonic(self, filename):\r
- if filename == "<" + filename[1:-1] + ">":\r
- return filename\r
- canonic = self.fncache.get(filename)\r
- if not canonic:\r
- canonic = os.path.abspath(filename)\r
- canonic = os.path.normcase(canonic)\r
- self.fncache[filename] = canonic\r
- return canonic\r
-\r
- def reset(self):\r
- import linecache\r
- linecache.checkcache()\r
- self.botframe = None\r
- self._set_stopinfo(None, None)\r
-\r
- def trace_dispatch(self, frame, event, arg):\r
- if self.quitting:\r
- return # None\r
- if event == 'line':\r
- return self.dispatch_line(frame)\r
- if event == 'call':\r
- return self.dispatch_call(frame, arg)\r
- if event == 'return':\r
- return self.dispatch_return(frame, arg)\r
- if event == 'exception':\r
- return self.dispatch_exception(frame, arg)\r
- if event == 'c_call':\r
- return self.trace_dispatch\r
- if event == 'c_exception':\r
- return self.trace_dispatch\r
- if event == 'c_return':\r
- return self.trace_dispatch\r
- print 'bdb.Bdb.dispatch: unknown debugging event:', repr(event)\r
- return self.trace_dispatch\r
-\r
- def dispatch_line(self, frame):\r
- if self.stop_here(frame) or self.break_here(frame):\r
- self.user_line(frame)\r
- if self.quitting: raise BdbQuit\r
- return self.trace_dispatch\r
-\r
- def dispatch_call(self, frame, arg):\r
- # XXX 'arg' is no longer used\r
- if self.botframe is None:\r
- # First call of dispatch since reset()\r
- self.botframe = frame.f_back # (CT) Note that this may also be None!\r
- return self.trace_dispatch\r
- if not (self.stop_here(frame) or self.break_anywhere(frame)):\r
- # No need to trace this function\r
- return # None\r
- self.user_call(frame, arg)\r
- if self.quitting: raise BdbQuit\r
- return self.trace_dispatch\r
-\r
- def dispatch_return(self, frame, arg):\r
- if self.stop_here(frame) or frame == self.returnframe:\r
- self.user_return(frame, arg)\r
- if self.quitting: raise BdbQuit\r
- return self.trace_dispatch\r
-\r
- def dispatch_exception(self, frame, arg):\r
- if self.stop_here(frame):\r
- self.user_exception(frame, arg)\r
- if self.quitting: raise BdbQuit\r
- return self.trace_dispatch\r
-\r
- # Normally derived classes don't override the following\r
- # methods, but they may if they want to redefine the\r
- # definition of stopping and breakpoints.\r
-\r
- def is_skipped_module(self, module_name):\r
- for pattern in self.skip:\r
- if fnmatch.fnmatch(module_name, pattern):\r
- return True\r
- return False\r
-\r
- def stop_here(self, frame):\r
- # (CT) stopframe may now also be None, see dispatch_call.\r
- # (CT) the former test for None is therefore removed from here.\r
- if self.skip and \\r
- self.is_skipped_module(frame.f_globals.get('__name__')):\r
- return False\r
- if frame is self.stopframe:\r
- if self.stoplineno == -1:\r
- return False\r
- return frame.f_lineno >= self.stoplineno\r
- while frame is not None and frame is not self.stopframe:\r
- if frame is self.botframe:\r
- return True\r
- frame = frame.f_back\r
- return False\r
-\r
- def break_here(self, frame):\r
- filename = self.canonic(frame.f_code.co_filename)\r
- if not filename in self.breaks:\r
- return False\r
- lineno = frame.f_lineno\r
- if not lineno in self.breaks[filename]:\r
- # The line itself has no breakpoint, but maybe the line is the\r
- # first line of a function with breakpoint set by function name.\r
- lineno = frame.f_code.co_firstlineno\r
- if not lineno in self.breaks[filename]:\r
- return False\r
-\r
- # flag says ok to delete temp. bp\r
- (bp, flag) = effective(filename, lineno, frame)\r
- if bp:\r
- self.currentbp = bp.number\r
- if (flag and bp.temporary):\r
- self.do_clear(str(bp.number))\r
- return True\r
- else:\r
- return False\r
-\r
- def do_clear(self, arg):\r
- raise NotImplementedError, "subclass of bdb must implement do_clear()"\r
-\r
- def break_anywhere(self, frame):\r
- return self.canonic(frame.f_code.co_filename) in self.breaks\r
-\r
- # Derived classes should override the user_* methods\r
- # to gain control.\r
-\r
- def user_call(self, frame, argument_list):\r
- """This method is called when there is the remote possibility\r
- that we ever need to stop in this function."""\r
- pass\r
-\r
- def user_line(self, frame):\r
- """This method is called when we stop or break at this line."""\r
- pass\r
-\r
- def user_return(self, frame, return_value):\r
- """This method is called when a return trap is set here."""\r
- pass\r
-\r
- def user_exception(self, frame, exc_info):\r
- exc_type, exc_value, exc_traceback = exc_info\r
- """This method is called if an exception occurs,\r
- but only if we are to stop at or just below this level."""\r
- pass\r
-\r
- def _set_stopinfo(self, stopframe, returnframe, stoplineno=0):\r
- self.stopframe = stopframe\r
- self.returnframe = returnframe\r
- self.quitting = 0\r
- # stoplineno >= 0 means: stop at line >= the stoplineno\r
- # stoplineno -1 means: don't stop at all\r
- self.stoplineno = stoplineno\r
-\r
- # Derived classes and clients can call the following methods\r
- # to affect the stepping state.\r
-\r
- def set_until(self, frame): #the name "until" is borrowed from gdb\r
- """Stop when the line with the line no greater than the current one is\r
- reached or when returning from current frame"""\r
- self._set_stopinfo(frame, frame, frame.f_lineno+1)\r
-\r
- def set_step(self):\r
- """Stop after one line of code."""\r
- self._set_stopinfo(None, None)\r
-\r
- def set_next(self, frame):\r
- """Stop on the next line in or below the given frame."""\r
- self._set_stopinfo(frame, None)\r
-\r
- def set_return(self, frame):\r
- """Stop when returning from the given frame."""\r
- self._set_stopinfo(frame.f_back, frame)\r
-\r
- def set_trace(self, frame=None):\r
- """Start debugging from `frame`.\r
-\r
- If frame is not specified, debugging starts from caller's frame.\r
- """\r
- if frame is None:\r
- frame = sys._getframe().f_back\r
- self.reset()\r
- while frame:\r
- frame.f_trace = self.trace_dispatch\r
- self.botframe = frame\r
- frame = frame.f_back\r
- self.set_step()\r
- sys.settrace(self.trace_dispatch)\r
-\r
- def set_continue(self):\r
- # Don't stop except at breakpoints or when finished\r
- self._set_stopinfo(self.botframe, None, -1)\r
- if not self.breaks:\r
- # no breakpoints; run without debugger overhead\r
- sys.settrace(None)\r
- frame = sys._getframe().f_back\r
- while frame and frame is not self.botframe:\r
- del frame.f_trace\r
- frame = frame.f_back\r
-\r
- def set_quit(self):\r
- self.stopframe = self.botframe\r
- self.returnframe = None\r
- self.quitting = 1\r
- sys.settrace(None)\r
-\r
- # Derived classes and clients can call the following methods\r
- # to manipulate breakpoints. These methods return an\r
- # error message is something went wrong, None if all is well.\r
- # Set_break prints out the breakpoint line and file:lineno.\r
- # Call self.get_*break*() to see the breakpoints or better\r
- # for bp in Breakpoint.bpbynumber: if bp: bp.bpprint().\r
-\r
- def set_break(self, filename, lineno, temporary=0, cond = None,\r
- funcname=None):\r
- filename = self.canonic(filename)\r
- import linecache # Import as late as possible\r
- line = linecache.getline(filename, lineno)\r
- if not line:\r
- return 'Line %s:%d does not exist' % (filename,\r
- lineno)\r
- if not filename in self.breaks:\r
- self.breaks[filename] = []\r
- list = self.breaks[filename]\r
- if not lineno in list:\r
- list.append(lineno)\r
- bp = Breakpoint(filename, lineno, temporary, cond, funcname)\r
-\r
- def _prune_breaks(self, filename, lineno):\r
- if (filename, lineno) not in Breakpoint.bplist:\r
- self.breaks[filename].remove(lineno)\r
- if not self.breaks[filename]:\r
- del self.breaks[filename]\r
-\r
- def clear_break(self, filename, lineno):\r
- filename = self.canonic(filename)\r
- if not filename in self.breaks:\r
- return 'There are no breakpoints in %s' % filename\r
- if lineno not in self.breaks[filename]:\r
- return 'There is no breakpoint at %s:%d' % (filename,\r
- lineno)\r
- # If there's only one bp in the list for that file,line\r
- # pair, then remove the breaks entry\r
- for bp in Breakpoint.bplist[filename, lineno][:]:\r
- bp.deleteMe()\r
- self._prune_breaks(filename, lineno)\r
-\r
- def clear_bpbynumber(self, arg):\r
- try:\r
- number = int(arg)\r
- except:\r
- return 'Non-numeric breakpoint number (%s)' % arg\r
- try:\r
- bp = Breakpoint.bpbynumber[number]\r
- except IndexError:\r
- return 'Breakpoint number (%d) out of range' % number\r
- if not bp:\r
- return 'Breakpoint (%d) already deleted' % number\r
- bp.deleteMe()\r
- self._prune_breaks(bp.file, bp.line)\r
-\r
- def clear_all_file_breaks(self, filename):\r
- filename = self.canonic(filename)\r
- if not filename in self.breaks:\r
- return 'There are no breakpoints in %s' % filename\r
- for line in self.breaks[filename]:\r
- blist = Breakpoint.bplist[filename, line]\r
- for bp in blist:\r
- bp.deleteMe()\r
- del self.breaks[filename]\r
-\r
- def clear_all_breaks(self):\r
- if not self.breaks:\r
- return 'There are no breakpoints'\r
- for bp in Breakpoint.bpbynumber:\r
- if bp:\r
- bp.deleteMe()\r
- self.breaks = {}\r
-\r
- def get_break(self, filename, lineno):\r
- filename = self.canonic(filename)\r
- return filename in self.breaks and \\r
- lineno in self.breaks[filename]\r
-\r
- def get_breaks(self, filename, lineno):\r
- filename = self.canonic(filename)\r
- return filename in self.breaks and \\r
- lineno in self.breaks[filename] and \\r
- Breakpoint.bplist[filename, lineno] or []\r
-\r
- def get_file_breaks(self, filename):\r
- filename = self.canonic(filename)\r
- if filename in self.breaks:\r
- return self.breaks[filename]\r
- else:\r
- return []\r
-\r
- def get_all_breaks(self):\r
- return self.breaks\r
-\r
- # Derived classes and clients can call the following method\r
- # to get a data structure representing a stack trace.\r
-\r
- def get_stack(self, f, t):\r
- stack = []\r
- if t and t.tb_frame is f:\r
- t = t.tb_next\r
- while f is not None:\r
- stack.append((f, f.f_lineno))\r
- if f is self.botframe:\r
- break\r
- f = f.f_back\r
- stack.reverse()\r
- i = max(0, len(stack) - 1)\r
- while t is not None:\r
- stack.append((t.tb_frame, t.tb_lineno))\r
- t = t.tb_next\r
- if f is None:\r
- i = max(0, len(stack) - 1)\r
- return stack, i\r
-\r
- #\r
-\r
- def format_stack_entry(self, frame_lineno, lprefix=': '):\r
- import linecache, repr\r
- frame, lineno = frame_lineno\r
- filename = self.canonic(frame.f_code.co_filename)\r
- s = '%s(%r)' % (filename, lineno)\r
- if frame.f_code.co_name:\r
- s = s + frame.f_code.co_name\r
- else:\r
- s = s + "<lambda>"\r
- if '__args__' in frame.f_locals:\r
- args = frame.f_locals['__args__']\r
- else:\r
- args = None\r
- if args:\r
- s = s + repr.repr(args)\r
- else:\r
- s = s + '()'\r
- if '__return__' in frame.f_locals:\r
- rv = frame.f_locals['__return__']\r
- s = s + '->'\r
- s = s + repr.repr(rv)\r
- line = linecache.getline(filename, lineno, frame.f_globals)\r
- if line: s = s + lprefix + line.strip()\r
- return s\r
-\r
- # The following two methods can be called by clients to use\r
- # a debugger to debug a statement, given as a string.\r
-\r
- def run(self, cmd, globals=None, locals=None):\r
- if globals is None:\r
- import __main__\r
- globals = __main__.__dict__\r
- if locals is None:\r
- locals = globals\r
- self.reset()\r
- sys.settrace(self.trace_dispatch)\r
- if not isinstance(cmd, types.CodeType):\r
- cmd = cmd+'\n'\r
- try:\r
- exec cmd in globals, locals\r
- except BdbQuit:\r
- pass\r
- finally:\r
- self.quitting = 1\r
- sys.settrace(None)\r
-\r
- def runeval(self, expr, globals=None, locals=None):\r
- if globals is None:\r
- import __main__\r
- globals = __main__.__dict__\r
- if locals is None:\r
- locals = globals\r
- self.reset()\r
- sys.settrace(self.trace_dispatch)\r
- if not isinstance(expr, types.CodeType):\r
- expr = expr+'\n'\r
- try:\r
- return eval(expr, globals, locals)\r
- except BdbQuit:\r
- pass\r
- finally:\r
- self.quitting = 1\r
- sys.settrace(None)\r
-\r
- def runctx(self, cmd, globals, locals):\r
- # B/W compatibility\r
- self.run(cmd, globals, locals)\r
-\r
- # This method is more useful to debug a single function call.\r
-\r
- def runcall(self, func, *args, **kwds):\r
- self.reset()\r
- sys.settrace(self.trace_dispatch)\r
- res = None\r
- try:\r
- res = func(*args, **kwds)\r
- except BdbQuit:\r
- pass\r
- finally:\r
- self.quitting = 1\r
- sys.settrace(None)\r
- return res\r
-\r
-\r
-def set_trace():\r
- Bdb().set_trace()\r
-\r
-\r
-class Breakpoint:\r
-\r
- """Breakpoint class\r
-\r
- Implements temporary breakpoints, ignore counts, disabling and\r
- (re)-enabling, and conditionals.\r
-\r
- Breakpoints are indexed by number through bpbynumber and by\r
- the file,line tuple using bplist. The former points to a\r
- single instance of class Breakpoint. The latter points to a\r
- list of such instances since there may be more than one\r
- breakpoint per line.\r
-\r
- """\r
-\r
- # XXX Keeping state in the class is a mistake -- this means\r
- # you cannot have more than one active Bdb instance.\r
-\r
- next = 1 # Next bp to be assigned\r
- bplist = {} # indexed by (file, lineno) tuple\r
- bpbynumber = [None] # Each entry is None or an instance of Bpt\r
- # index 0 is unused, except for marking an\r
- # effective break .... see effective()\r
-\r
- def __init__(self, file, line, temporary=0, cond=None, funcname=None):\r
- self.funcname = funcname\r
- # Needed if funcname is not None.\r
- self.func_first_executable_line = None\r
- self.file = file # This better be in canonical form!\r
- self.line = line\r
- self.temporary = temporary\r
- self.cond = cond\r
- self.enabled = 1\r
- self.ignore = 0\r
- self.hits = 0\r
- self.number = Breakpoint.next\r
- Breakpoint.next = Breakpoint.next + 1\r
- # Build the two lists\r
- self.bpbynumber.append(self)\r
- if (file, line) in self.bplist:\r
- self.bplist[file, line].append(self)\r
- else:\r
- self.bplist[file, line] = [self]\r
-\r
-\r
- def deleteMe(self):\r
- index = (self.file, self.line)\r
- self.bpbynumber[self.number] = None # No longer in list\r
- self.bplist[index].remove(self)\r
- if not self.bplist[index]:\r
- # No more bp for this f:l combo\r
- del self.bplist[index]\r
-\r
- def enable(self):\r
- self.enabled = 1\r
-\r
- def disable(self):\r
- self.enabled = 0\r
-\r
- def bpprint(self, out=None):\r
- if out is None:\r
- out = sys.stdout\r
- if self.temporary:\r
- disp = 'del '\r
- else:\r
- disp = 'keep '\r
- if self.enabled:\r
- disp = disp + 'yes '\r
- else:\r
- disp = disp + 'no '\r
- print >>out, '%-4dbreakpoint %s at %s:%d' % (self.number, disp,\r
- self.file, self.line)\r
- if self.cond:\r
- print >>out, '\tstop only if %s' % (self.cond,)\r
- if self.ignore:\r
- print >>out, '\tignore next %d hits' % (self.ignore)\r
- if (self.hits):\r
- if (self.hits > 1): ss = 's'\r
- else: ss = ''\r
- print >>out, ('\tbreakpoint already hit %d time%s' %\r
- (self.hits, ss))\r
-\r
-# -----------end of Breakpoint class----------\r
-\r
-def checkfuncname(b, frame):\r
- """Check whether we should break here because of `b.funcname`."""\r
- if not b.funcname:\r
- # Breakpoint was set via line number.\r
- if b.line != frame.f_lineno:\r
- # Breakpoint was set at a line with a def statement and the function\r
- # defined is called: don't break.\r
- return False\r
- return True\r
-\r
- # Breakpoint set via function name.\r
-\r
- if frame.f_code.co_name != b.funcname:\r
- # It's not a function call, but rather execution of def statement.\r
- return False\r
-\r
- # We are in the right frame.\r
- if not b.func_first_executable_line:\r
- # The function is entered for the 1st time.\r
- b.func_first_executable_line = frame.f_lineno\r
-\r
- if b.func_first_executable_line != frame.f_lineno:\r
- # But we are not at the first line number: don't break.\r
- return False\r
- return True\r
-\r
-# Determines if there is an effective (active) breakpoint at this\r
-# line of code. Returns breakpoint number or 0 if none\r
-def effective(file, line, frame):\r
- """Determine which breakpoint for this file:line is to be acted upon.\r
-\r
- Called only if we know there is a bpt at this\r
- location. Returns breakpoint that was triggered and a flag\r
- that indicates if it is ok to delete a temporary bp.\r
-\r
- """\r
- possibles = Breakpoint.bplist[file,line]\r
- for i in range(0, len(possibles)):\r
- b = possibles[i]\r
- if b.enabled == 0:\r
- continue\r
- if not checkfuncname(b, frame):\r
- continue\r
- # Count every hit when bp is enabled\r
- b.hits = b.hits + 1\r
- if not b.cond:\r
- # If unconditional, and ignoring,\r
- # go on to next, else break\r
- if b.ignore > 0:\r
- b.ignore = b.ignore -1\r
- continue\r
- else:\r
- # breakpoint and marker that's ok\r
- # to delete if temporary\r
- return (b,1)\r
- else:\r
- # Conditional bp.\r
- # Ignore count applies only to those bpt hits where the\r
- # condition evaluates to true.\r
- try:\r
- val = eval(b.cond, frame.f_globals,\r
- frame.f_locals)\r
- if val:\r
- if b.ignore > 0:\r
- b.ignore = b.ignore -1\r
- # continue\r
- else:\r
- return (b,1)\r
- # else:\r
- # continue\r
- except:\r
- # if eval fails, most conservative\r
- # thing is to stop on breakpoint\r
- # regardless of ignore count.\r
- # Don't delete temporary,\r
- # as another hint to user.\r
- return (b,0)\r
- return (None, None)\r
-\r
-# -------------------- testing --------------------\r
-\r
-class Tdb(Bdb):\r
- def user_call(self, frame, args):\r
- name = frame.f_code.co_name\r
- if not name: name = '???'\r
- print '+++ call', name, args\r
- def user_line(self, frame):\r
- import linecache\r
- name = frame.f_code.co_name\r
- if not name: name = '???'\r
- fn = self.canonic(frame.f_code.co_filename)\r
- line = linecache.getline(fn, frame.f_lineno, frame.f_globals)\r
- print '+++', fn, frame.f_lineno, name, ':', line.strip()\r
- def user_return(self, frame, retval):\r
- print '+++ return', retval\r
- def user_exception(self, frame, exc_stuff):\r
- print '+++ exception', exc_stuff\r
- self.set_continue()\r
-\r
-def foo(n):\r
- print 'foo(', n, ')'\r
- x = bar(n*10)\r
- print 'bar returned', x\r
-\r
-def bar(a):\r
- print 'bar(', a, ')'\r
- return a/2\r
-\r
-def test():\r
- t = Tdb()\r
- t.run('import bdb; bdb.foo(10)')\r
-\r
-# end\r