613 lines · 19.7 KB · Python
RawDownload
"""Debugger basics""" import sysimport osimport types __all__ = ["BdbQuit","Bdb","Breakpoint"] class BdbQuit(Exception):    """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):        if filename == "<" + filename[1:-1] + ">":            return filename        canonic = self.fncache.get(filename)        if not canonic:            canonic = os.path.abspath(filename)            canonic = os.path.normcase(canonic)            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)        if event == 'c_call':            return self.trace_dispatch        if event == 'c_exception':            return self.trace_dispatch        if event == 'c_return':            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):            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.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            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        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:            return True        while frame is not None and frame is not self.stopframe:            if frame is self.botframe:                return True            frame = frame.f_back        return False     def break_here(self, frame):        filename = self.canonic(frame.f_code.co_filename)        if not filename in self.breaks:            return False        lineno = frame.f_lineno        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]:                return False         # 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 True        else:            return False     def do_clear(self, arg):        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    # 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, frame=None):        """Start debugging from `frame`.         If frame is not specified, debugging starts from caller's frame.        """        if frame is None:            frame = sys._getframe().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)            frame = sys._getframe().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,                  funcname=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 filename in self.breaks:            self.breaks[filename] = []        list = self.breaks[filename]        if not lineno in list:            list.append(lineno)        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,                                    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 filename in self.breaks:            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 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]        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        frame, lineno = frame_lineno        filename = self.canonic(frame.f_code.co_filename)        s = '%s(%r)' % (filename, lineno)        if frame.f_code.co_name:            s = s + frame.f_code.co_name        else:            s = s + "<lambda>"        if '__args__' in frame.f_locals:            args = frame.f_locals['__args__']        else:            args = None        if args:            s = s + repr.repr(args)        else:            s = s + '()'        if '__return__' in frame.f_locals:            rv = frame.f_locals['__return__']            s = s + '->'            s = s + repr.repr(rv)        line = linecache.getline(filename, lineno)        if line: s = s + lprefix + line.strip()        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, **kwds):        self.reset()        sys.settrace(self.trace_dispatch)        res = None        try:            try:                res = func(*args, **kwds)            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, funcname=None):        self.funcname = funcname        # Needed if funcname is not None.        self.func_first_executable_line = 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, out=None):        if out is None:            out = sys.stdout        if self.temporary:            disp = 'del  '        else:            disp = 'keep '        if self.enabled:            disp = disp + 'yes  '        else:            disp = disp + 'no   '        print >>out, '%-4dbreakpoint   %s at %s:%d' % (self.number, disp,                                                       self.file, self.line)        if self.cond:            print >>out, '\tstop only if %s' % (self.cond,)        if self.ignore:            print >>out, '\tignore next %d hits' % (self.ignore)        if (self.hits):            if (self.hits > 1): ss = 's'            else: ss = ''            print >>out, ('\tbreakpoint already hit %d time%s' %                          (self.hits, ss)) # -----------end of Breakpoint class---------- 

Lines 1–500 of 613.