Table of Contents

Eric3 Source Documentation: Debugger  
# -*- coding: utf-8 -*-

# Copyright (c) 2002, 2003 Detlev Offenbach <detlev@die-offenbachs.de>
# Copyright (c) 2000 Phil Thompson <phil@river-bank.demon.co.uk>
#

"""
Module implementing the debug thread.
"""
import bdb
import types
import os
import traceback
import sys

from DebugProtocol import *

def printerr(s):
    import sys
    """
    Module function used for debugging the debug threads.
    
    Arguments
    
        s -- the data to be printed
    """
    sys.__stderr__.write('%s\n' % str(s))
    sys.__stderr__.flush()
    
class DebugThread(bdb.Bdb):
    """
    Class implementing a debug thread.

    It represents a thread in the python interpreter that we are tracing.
    
    Provides simple wrapper methods around bdb for the 'owning' client to
    call to step etc.
    """
    def __init__(self, dbgClient, targ=None, args=None, kwargs=None, mainThread=0):
        """
        Constructor
        
        Arguments
        
            dbgClient -- the owning client
            
            targ -- the target method in the run thread
            
            args --  arguments to be passed to the thread
            
            kwargs -- arguments to be passed to the thread
            
            mainThread -- 0 if this thread is not the mainscripts thread
        """
        bdb.Bdb.__init__(self)
        
        self._dbgClient = dbgClient
        self._target = targ 
        self._args = args
        self._kwargs = kwargs
        self._dbgClient = dbgClient
        self._mainThread = mainThread
        # thread running tracks execution state of client code
        # it will always be 0 for main thread as that is tracked
        # by DebugClientThreads and Bdb...
        self._threadRunning = 0 

        
        self.currentFrame = None # current frame we are at
        self.ident = None # id of this thread.
        self.stepFrame = None # frame that we are stepping in, can be different than currentFrame
        
        #
        # point breakpoints list at class level dictionary so that
        # threads share breakpoints. If we ever want thread level 
        # breakpoints, then remove this and adjust DebugClient accordingly
        # when a breakpoint request is sent.
        # In addition, point the file name cache at the global client cache...
        #
        self.breaks = self._dbgClient.breakpoints
        self.fncache = self._dbgClient.fncache
        
    def set_ident(self, id):
        """
        Public method to set the id for this thread.
        
        Arguments
        
            id -- id for this thread (int)
        """
        self.ident = id
      
    def get_ident(self):
        """
        Public method to return the id for this thread.
        
        Returns
        
            the id of this thread (int)
        """
        return self.ident
    
    def getCurrentFrame(self):
        """
        Public method to return the current frame.
        
        Returns
        
            the current frame
        """
        return self.currentFrame        
            
    def step(self, traceMode):
        """
        Public method to perform a step operation in this thread.
        
        Arguments
        
            tracemode -- if it is non-zero, then the step is a step into,
              otherwise it is a step over.
        """
        self.stepFrame = self.currentFrame
        
        if traceMode:
            self.currentFrame = None
            self.set_step()
        else:
            self.set_next(self.currentFrame)
    
    def stepOut(self):
        """
        Public method to perform a step out of the current call.
        """
        self.stepFrame = self.currentFrame
        self.set_return(self.currentFrame)
    
    def set_quit(self):
        """
        Public method to quit thread. 
        
        It wraps call to bdb to clear the current frame properly.
        """
        self.currentFrame = None
        bdb.Bdb.set_quit(self)
        
    def go(self):
        """
        Public method to resume the thread.

        It resumes the thread stopping only at breakpoints or exceptions.
        """
        self.currentFrame = None
        self.set_continue()
        
    def traceThread(self):
        """
        Private method to setup tracing for this thread.
        """
        self.set_trace()
        if not self._mainThread:
            self.set_continue()
    
    def bootstrap(self):
        """
        Private method to bootstrap the thread.
        
        It wraps the call to the user function to enable tracing 
        before hand.
        """
        try:
            try:
                self._threadRunning = 1
                self.traceThread()
                apply(self._target, self._args, self._kwargs)
            except bdb.BdbQuit:
                pass
        finally:
            self._threadRunning = 0
            self.quitting = 1
            self._dbgClient.threadTerminated(self)
            sys.settrace(None)
            
    def trace_dispatch(self, frame, event, arg):
        """
        Private method wraping the trace_dispatch of bdb.py.
        
        It wraps the call to dispatch tracing into
        bdb to make sure we have locked the client to prevent multiple
        threads from entering the client event loop.
        """
        try:
            self._dbgClient.lockClient()
            # if this thread came out of a lock, and we are quitting 
            # and we are still running, then get rid of tracing for this thread 
            # and raise a quit to abort the thread. 
            if self.quitting and self._threadRunning:
                sys.settrace(None)
                raise bdb.BdbQuit
            retval = bdb.Bdb.trace_dispatch(self, frame, event, arg)
        finally:
            self._dbgClient.unlockClient()
            
        return retval

    def user_line(self,frame):
        """
        Reimplemented to handle the program about to execute a particular line.
        
        Arguments
        
            frame -- the frame object
        """
        line = frame.f_lineno
        
       
        # We never stop an line 0.
        if line == 0:
            return

        fn = self._dbgClient.absPath(self.fix_frame_filename(frame))

        # See if we are skipping at the start of a newly loaded program.
        if self._dbgClient.mainFrame is None:
            if fn != self._dbgClient.getRunning():
                return
            self._dbgClient.mainFrame = frame

        self.currentFrame = frame
        
        fr = frame
        stack = []
        while fr is not None:
            # Reset the trace function so we can be sure
            # to trace all functions up the stack... This gets around
            # problems where an exception/breakpoint has occurred
            # but we had disabled tracing along the way via a None
            # return from dispatch_call
            fr.f_trace = self.trace_dispatch
            fname = self._dbgClient.absPath(self.fix_frame_filename(fr))
            fline = fr.f_lineno
            ffunc = fr.f_code.co_name
            
            if ffunc == '?':
                ffunc = ''
            
            stack.append((fname, fline, ffunc))
            
            if fr == self._dbgClient.mainFrame:
                fr = None
            else:
                fr = fr.f_back
        
        self._dbgClient.write('%s%s\n' % (ResponseLine,str(stack)))
        self._dbgClient.eventLoop()
        
    def fix_frame_filename(self, frame):
        """
        Protected method used to fixup the filename for a given frame.
        
        The logic employed here is that if a module was loaded
        from a .pyc file, then the correct .py to operate with
        should be in the same path as the .pyc. The reason this
        logic is needed is that when a .pyc file is generated, the
        filename embedded and thus what is readable in the code object
        of the frame object is the fully qualified filepath when the
        pyc is generated. If files are moved from machine to machine
        this can break debugging as the .pyc will refer to the .py
        on the original machine. Another case might be sharing
        code over a network... This logic deals with that.
        
        Arguments
        
            frame -- the frame object
        """
        # get module name from __file__
        if frame.f_globals.has_key('__file__'):
            root, ext = os.path.splitext(frame.f_globals['__file__'])
            if ext == '.pyc' or ext == '.py':
                fixedName = root + '.py'
                if os.path.exists(fixedName):
                    return fixedName

        return frame.f_code.co_filename

    def break_here(self, frame):
        """
        Reimplemented from bdb.py to fix the filename from the frame. 
        
        See fix_frame_filename for more info.
        
        Arguments
        
            frame -- the frame object
            
        Returns
        
            flag indicating the break status (boolean)
        """
        filename = self.canonic(self.fix_frame_filename(frame))
        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) = bdb.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):
        """
        Reimplemented from bdb.py to fix the filename from the frame. 
        
        See fix_frame_filename for more info.
        
        Arguments
        
            frame -- the frame object
            
        Returns
        
            flag indicating the break status (boolean)
        """
        return self.breaks.has_key(
            self.canonic(self.fix_frame_filename(frame)))

    def dispatch_return(self, frame, arg):
        """
        Reimplemented from bdb.py to handle passive mode cleanly.
        """
        if self.stop_here(frame) or frame == self.returnframe:
            self.user_return(frame, arg)
            if self.quitting and not self._dbgClient.passive:
                raise bdb.BdbQuit
        return self.trace_dispatch

    def dispatch_exception(self, frame, arg):
        """
        Reimplemented from bdb.py to always call user_exception.
        """
        self.user_exception(frame, arg)
        if self.quitting: raise bdb.BdbQuit
        return self.trace_dispatch

    def set_continue(self):
        """
        Reimplemented from bdb.py to always get informed of exceptions.
        """
        # Modified version of the one found in bdb.py
        # Here we leave tracing switched on in order to get
        # informed of exceptions
        self.stopframe = self.botframe
        self.returnframe = None
        self.quitting = 0


    def user_exception(self,frame,(exctype,excval,exctb),unhandled=0):
        """
        Reimplemented to report an exception to the debug server.
        
        Arguments
        
            frame -- the frame object
            
            exctype -- the type of the exception
            
            excval -- data about the exception
            
            exctb -- traceback for the exception
            
            unhandled -- flag indicating an uncaught exception
        """
        if exctype == SystemExit:
            self._dbgClient.progTerminated(excval)
            
        elif exctype in [SyntaxError, IndentationError]:
            try:
                message, (filename, linenr, charnr, text) = excval
            except:
                exclist = []
            else:
                exclist = [message, (filename, linenr, charnr)]
            
            self._dbgClient.write("%s%s\n" % (ResponseSyntax, str(exclist)))
            
        else:
            self.currentFrame = frame

            if type(exctype) == types.ClassType:
                exctype = exctype.__name__
                
            if excval is None:
                excval = ''
                
            if unhandled:
                exclist = ["unhandled %s" % str(exctype), str(excval)]
            else:
                exclist = [str(exctype), str(excval)]
            
            frlist = self.extract_stack(exctb)
            frlist.reverse()
            
            for fr in frlist:
                filename = self._dbgClient.absPath(self.fix_frame_filename(fr))
                linenr = fr.f_lineno
                
                exclist.append((filename, linenr))
            
            self._dbgClient.write("%s%s\n" % (ResponseException, str(exclist)))
            
        self._dbgClient.eventLoop()

    def extract_stack(self, exctb):
        """
        Protected member to return a list of stack frames.
        
        Arguments
        
            exctb -- exception traceback
            
        Returns
        
            list of stack frames
        """
        tb = exctb
        stack = []
        while tb is not None:
            stack.append(tb.tb_frame)
            tb = tb.tb_next
        tb = None
        return stack
         
    def user_return(self,frame,retval):
        """
        Reimplemented to report program termination to the debug server.
        
        Arguments
        
            frame -- the frame object
            
            retval -- the return value of the program
        """
        # The program has finished if we have just left the first frame.
        if frame == self._dbgClient.mainFrame and \
            self._mainThread:
            self._dbgClient.progTerminated(retval)
        elif frame is not self.stepFrame:
            self.stepFrame = None
            self.user_line(frame)

    def stop_here(self,frame):
        """
        Reimplemented to filter out debugger files.
        
        Tracing is turned off for files that are part of the
        debugger that are called from the application being debugged.
        
        Arguments
        
            frame -- the frame object
            
        Returns
        
            flag indicating whether the debugger should stop here
        """
        fn = self.fix_frame_filename(frame)

        # Eliminate things like <string> and <stdin>.
        if fn[0] == '<':
            return 0

        #XXX - think of a better way to do this.  It's only a convience for
        #debugging the debugger - when the debugger code is in the current
        #directory.
        if os.path.basename(fn) in ['AsyncIO.py', 'DebugClientThreads.py', 'DebugThread.py']:
            return 0

        if self._dbgClient.shouldSkip(fn):
            return 0
        
        return bdb.Bdb.stop_here(self,frame)

Table of Contents

This document was automatically generated by HappyDoc version 2.1