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 a Qt free version of the debug client.
"""

import sys
import socket
import select
import codeop
import traceback
import bdb
import os
import types
import string

from DebugProtocol import *

import PyCoverage
import PyProfile

######################################################################
##
##  Copy of the definition found in Config.py
##
######################################################################

ConfigVarTypeStrings = ['__', 'NoneType', 'type',\
        'int', 'long', 'float', 'complex',\
        'str', 'unicode', 'tuple', 'list',\
        'dict', 'dict-proxy', 'file', 'xrange',\
        'slice', 'buffer', 'class', 'instance',\
        'instance method', 'property', 'generator',\
        'function', 'builtin_function_or_method', 'code', 'module',\
        'ellipsis', 'traceback', 'frame', 'other']


DebugClientInstance = None

def printerr(s):
    """
    Module function used for debugging the debug client.
    
    Arguments
    
        s -- the data to be printed
    """
    sys.__stderr__.write('%s\n' % str(s))
    sys.__stderr__.flush()

def DebugClientRawInput(prompt):
    """
    Replacement for the standard raw_input builtin.
    
    This function works with the split debugger.
    """
    if DebugClientInstance is None:
        return DebugClientOrigRawInput(prompt)

    return DebugClientInstance.raw_input(prompt)

# Use our own raw_input().
try:
    DebugClientOrigRawInput = __builtins__.__dict__['raw_input']
    __builtins__.__dict__['raw_input'] = DebugClientRawInput
except:
    import __main__
    DebugClientOrigRawInput = __main__.__builtins__.__dict__['raw_input']
    __main__.__builtins__.__dict__['raw_input'] = DebugClientRawInput
 

#########################################################################
##
## Modified versions of the ones found in AsyncIO.py
##
#########################################################################

def AsyncPendingWrite(file):
    """
    Module function to check for data to be written.
    
    Arguments
    
        file -- the file object to be checked (file)
        
    Returns
    
        flag indicating if there is data wating (int)
    """
    try:
        pending = file.pendingWrite()
    except:
        pending = 0

    return pending


class AsyncFile:
    """
    Class wrapping a socket object with a file interface.
    """
    def __init__(self,sock,mode,name):
        """
        Constructor
        
        Arguments
        
            sock -- the socket object being wrapped
            
            mode -- mode of this file (string)
            
            name -- name of this file (string)
        """
        # Initialise the attributes.
        self.closed = 0
        self.sock = sock
        self.mode = mode
        self.name = name
        self.softspace = 0

        self.wpending = ''

    def checkMode(self,mode):
        """
        Private method to check the mode.
        
        This method checks, if an operation is permitted according to
        the mode of the file. If it is not, an IOError is raised.
        
        Arguments
        
            mode -- the mode to be checked (string)
        """
        if mode != self.mode:
            raise IOError, '[Errno 9] Bad file descriptor'

    def nWrite(self,n):
        """
        Private method to write a specific number of pending bytes.
        
        Arguments
        
            n -- the number of bytes to be written (int)
        """
        if n:
            sent = self.sock.send(self.wpending[:n])
            self.wpending = self.wpending[sent:]

    def pendingWrite(self):
        """
        Public method that returns the number of bytes waiting to be written.
        
        Returns
        
            the number of bytes to be written (int)
        """
        return self.wpending.rfind('\n') + 1

    def close(self):
        """
        Public method to close the file.
        """
        if not self.closed:
            self.flush()
            self.sock.close()
            self.closed = 1

    def flush(self):
        """
        Public method to write all pending bytes.
        """
        self.nWrite(len(self.wpending))

    def isatty(self):
        """
        Public method to indicate whether a tty interface is supported.
        
        Returns
        
            always false
        """
        return 0

    def fileno(self):
        """
        Public method returning the file number.
        
        Returns
        
            file number (int)
        """
        return self.sock.fileno()

    def read(self,size=-1):
        """
        Public method to read bytes from this file.
        
        Arguments
        
            size -- maximum number of bytes to be read (int)
            
        Returns
        
            the bytes read (any)
        """
        self.checkMode('r')

        if size < 0:
            size = 20000

        return self.sock.recv(size)

    def readline(self,size=-1):
        """
        Public method to read a line from this file.
        
        Arguments
        
            size -- maximum number of bytes to be read (int)
            
        Returns
        
            one line of text up to size bytes (string)
            
        Note
        
            This method will not block and may return only a part of a
            line if that is all that is available.
        """
        self.checkMode('r')

        if size < 0:
            size = 20000

        # The integration of the debugger client event loop and the connection
        # to the debugger relies on the two lines of the debugger command being
        # delivered as two separate events.  Therefore we make sure we only
        # read a line at a time.
        line = self.sock.recv(size,socket.MSG_PEEK)

        eol = line.find('\n')

        if eol >= 0:
            size = eol + 1
        else:
            size = len(line)

        # Now we know how big the line is, read it for real.
        return self.sock.recv(size)

    def readlines(self,sizehint=-1):
        """
        Public method to read all lines from this file.
        
        Arguments
        
            sizehint -- hint of the numbers of bytes to be read (int)
            
        Returns
        
            list of lines read (list of strings)
        """
        lines = []
        room = sizehint

        line = self.readline(room)
        linelen = len(line)

        while linelen > 0:
            lines.append(line)

            if sizehint >= 0:
                room = room - linelen

                if room <= 0:
                    break

            line = self.readline(room)
            linelen = len(line)

        return lines

    def seek(self,offset,whence=0):
        """
        Public method to move the filepointer.
        
        This method is not supported and always raises an
        IOError.
        """
        raise IOError, '[Errno 29] Illegal seek'

    def tell(self):
        """
        Public method to get the filepointer position.
        
        This method is not supported and always raises an
        IOError.
        """
        raise IOError, '[Errno 29] Illegal seek'

    def truncate(self,size=-1):
        """
        Public method to truncate the file.
        
        This method is not supported and always raises an
        IOError.
        """
        raise IOError, '[Errno 29] Illegal seek'

    def write(self,str):
        """
        Public method to write a string to the file.
        
        Arguments
        
            str -- bytes to be written (string)
        """
        self.checkMode('w')
        self.wpending = self.wpending + str
        self.nWrite(self.pendingWrite())

    def writelines(self,list):
        """
        Public method to write a list of strings to the file.
        
        Arguments
        
            list -- the list to be written (list of string)
        """
        map(self.write,list)


class AsyncIO:
    """
    Class implementing asynchronous reading and writing.
    """
    def __init__(self,parent=None):
        """
        Constructor
        
        Arguments
        
            parent -- the optional parent of this object (QObject) (ignored)
        """
        # There is no connection yet.
        self.disconnect()

    def disconnect(self):
        """
        Public method to disconnect any current connection.
        """
        self.readfd = None
        self.writefd = None

    def setDescriptors(self,rfd,wfd):
        """
        Public method called to set the descriptors for the connection.
        
        Arguments
        
            rfd -- file descriptor of the input file (int)
            
            wfd -- file descriptor of the output file (int)
        """
        self.rbuf = ''
        self.readfd = rfd

        self.wbuf = ''
        self.writefd = wfd

    def sessionClose(self):
        """
        Method to be overwritten in subclasses to do something
        usefull. It is called, if a zero length string is received on
        self.readstream.
        """
        pass
        
    def handleLine(self, s):
        """
        Method to be overwritten in subclasses. It is called when
        a line of text is received on self.readstream.
        """
        pass
        
    def readReady(self,fd):
        """
        Protected method called when there is data ready to be read.
        
        Arguments
        
            fd -- file descriptor of the file that has data to be read (int)
        """
        try:
            got = self.readfd.readline()
        except:
            return

        if len(got) == 0:
            self.sessionClose()
            return

        self.rbuf = self.rbuf + got

        # Call handleLine for the line if it is complete.
        eol = self.rbuf.find('\n')

        while eol >= 0:
            s = self.rbuf[:eol + 1]
            self.rbuf = self.rbuf[eol + 1:]
            self.handleLine(s)
            eol = self.rbuf.find('\n')

    def writeReady(self,fd):
        """
        Protected method called when we are ready to write data.
        
        Arguments
        
            fd -- file descriptor of the file that has data to be written (int)
        """
        self.writefd.write(self.wbuf)
        self.writefd.flush()
        self.wbuf = ''

    def write(self,s):
        """
        Public method to write a string.
        
        Arguments
        
            s -- the data to be written (string)
        """
        self.wbuf = self.wbuf + s


class DebugClient(AsyncIO,bdb.Bdb):
    """
    Class implementing the client side of the debugger.

    It provides access to the Python interpeter from a debugger running in another
    process whether or not the Qt event loop is running.

    The protocol between the debugger and the client assumes that there will be
    a single source of debugger commands and a single source of Python
    statements.  Commands and statement are always exactly one line and may be
    interspersed.

    The protocol is as follows.  First the client opens a connection to the
    debugger and then sends a series of one line commands.  A command is either
    >Load<, >Step<, >StepInto<, ... or a Python statement. See DebugProtocol.py
    for a listing of valid protocol tokens.

    A Python statement consists of the statement to execute, followed (in a
    separate line) by >OK?<.  If the statement was incomplete then the response
    is >Continue<.  If there was an exception then the response is >Exception<.
    Otherwise the response is >OK<.  The reason for the >OK?< part is to
    provide a sentinal (ie. the responding >OK<) after any possible output as a
    result of executing the command.

    The client may send any other lines at any other time which should be
    interpreted as program output.

    If the debugger closes the session there is no response from the client.
    The client may close the session at any time as a result of the script
    being debugged closing or crashing.
    """
    def __init__(self):
        """
        Constructor
        """
        AsyncIO.__init__(self)
        bdb.Bdb.__init__(self)

        # The context to run the debugged program in.
        self.context = {'__name__' : '__main__'}

        # The list of complete lines to execute.
        self.buffer = ''

        self.pendingResponse = ResponseOK
        self.fncache = {}
        self.dircache = []
        self.inRawMode = 0
        self.mainProcStr = None     # used for the passive mode
        self.passive = 0            # used to indicate the passive mode
        self.running = None

        self.readstream = None
        self.writestream = None
        self.errorstream = None
        
        self.stepFrame = None
        
        # So much for portability.
        if sys.platform == 'win32':
            self.skipdir = sys.prefix
        else:
            self.skipdir = os.path.join(sys.prefix,'lib/python' + sys.version[0:3])

    def raw_input(self,prompt):
        """
        Public method to implement raw_input() using the event loop.
        
        Arguments
        
            prompt -- the prompt to be shown (string)
            
        Returns
            the entered string
        """
        self.write("%s%s\n" % (ResponseRaw, prompt))
        self.inRawMode = 1
        self.eventLoop()
        return self.rawLine

    def handleException(self):
        """
        Private method called in the case of an exception
        
        It ensures that the debug server is informed of the raised exception.
        """
        self.pendingResponse = ResponseException

    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.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
        
        # Don't stop except at breakpoints or when finished
        self.stopframe = self.botframe
        self.returnframe = None
        self.quitting = 0

    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 sessionClose(self):
        """
        Private method to close the session with the debugger and terminate.
        """
        try:
            self.set_quit()
        except:
            pass

        # clean up asyncio.
        self.disconnect()
        
        # make sure we close down our end of the socket
        # might be overkill as normally stdin, stdout and stderr
        # SHOULD be closed on exit, but it does not hurt to do it here
        self.readstream.close()
        self.writestream.close()
        self.errorstream.close()

        # Ok, go away.
        sys.exit()

    def handleLine(self,line):
        """
        Private method to handle the receipt of a complete line.

        It first looks for a valid protocol token at the start of the line. Thereafter
        it trys to execute the lines accumulated so far.
        
        Arguments
        
            line -- the received line
        """
        # Remove any newline.
        if line[-1] == '\n':
            line = line[:-1]

##~         printerr(line)          ##debug

        eoc = line.find('<')

        if eoc >= 0 and line[0] == '>':
            # Get the command part and any argument.
            cmd = line[:eoc + 1]
            arg = line[eoc + 1:]
            
            if cmd == RequestVariables:
                frmnr, scope, filter = eval(arg)
                self.dumpVariables(int(frmnr), int(scope), filter)
                return
                
            if cmd == RequestStep:
                self.stepFrame = self.currentFrame
                self.currentFrame = None
                self.set_step()
                self.eventExit = 1
                return

            if cmd == RequestStepOver:
                self.stepFrame = self.currentFrame
                self.set_next(self.currentFrame)
                self.eventExit = 1
                return
                
            if cmd == RequestStepOut:
                self.stepFrame = self.currentFrame
                self.set_return(self.currentFrame)
                self.eventExit = 1
                return
                
            if cmd == RequestStepQuit:
                if self.passive:
                    self.progTerminated(42)
                else:
                    self.currentFrame = None
                    self.set_quit()
                    self.eventExit = 1
                return

            if cmd == RequestContinue:
                self.currentFrame = None
                self.set_continue()
                self.eventExit = 1
                return

            if cmd == RequestOK:
                self.write(self.pendingResponse + '\n')
                self.pendingResponse = ResponseOK
                return

            if cmd == RequestLoad:
                self.fncache = {}
                self.dircache = []
                sys.argv = []
                wd, fn, args = arg.split('|')
                sys.argv.append(fn)
                sys.argv.extend(args.split())
                sys.path[0] = os.path.dirname(sys.argv[0])
                if wd == '':
                    os.chdir(sys.path[0])
                else:
                    os.chdir(wd)
                self.running = sys.argv[0]
                self.firstFrame = None
                self.currentFrame = None
                self.inRawMode = 0

                # set the system exception handling function to ensure, that
                # we report on all unhandled exceptions
                sys.excepthook = self.unhandled_exception
                
                # This will eventually enter a local event loop.
                # Note the use of backquotes to cause a repr of self.running. The
                # need for this is on Windows os where backslash is the path separator.
                # They will get inadvertantly stripped away during the eval causing IOErrors
                # if self.running is passed as a normal str.
                self.run('execfile(' + `self.running` + ')',self.context)
                return

            if cmd == RequestRun:
                sys.argv = []
                wd, fn, args = arg.split('|')
                sys.argv.append(fn)
                sys.argv.extend(args.split())
                sys.path[0] = os.path.dirname(sys.argv[0])
                if wd == '':
                    os.chdir(sys.path[0])
                else:
                    os.chdir(wd)

                # set the system exception handling function to ensure, that
                # we report on all unhandled exceptions
                sys.excepthook = self.unhandled_exception
                
                execfile(sys.argv[0], self.context)
                return

            if cmd == RequestCoverage:
                sys.argv = []
                wd, fn, args, erase = arg.split('|')
                sys.argv.append(fn)
                sys.argv.extend(args.split())
                sys.path[0] = os.path.dirname(sys.argv[0])
                if wd == '':
                    os.chdir(sys.path[0])
                else:
                    os.chdir(wd)

                # set the system exception handling function to ensure, that
                # we report on all unhandled exceptions
                sys.excepthook = self.unhandled_exception
                
                # generate a coverage object
                self.cover = PyCoverage.coverage(sys.argv[0])
                
                # register an exit handler to save the collected data
                try:
                    import atexit
                    atexit.register(self.cover.save)
                except ImportError:
                    sys.exitfunc = self.cover.save

                if int(erase):
                    self.cover.erase()
                self.cover.start()
                execfile(sys.argv[0], self.context)
                return

            if cmd == RequestProfile:
                sys.argv = []
                wd, fn, args, erase = arg.split('|')
                sys.argv.append(fn)
                sys.argv.extend(args.split())
                sys.path[0] = os.path.dirname(sys.argv[0])
                if wd == '':
                    os.chdir(sys.path[0])
                else:
                    os.chdir(wd)

                # set the system exception handling function to ensure, that
                # we report on all unhandled exceptions
                sys.excepthook = self.unhandled_exception
                
                # generate a profile object
                self.prof = PyProfile.PyProfile(sys.argv[0])
                
                if int(erase):
                    self.prof.erase()
                self.prof.run('execfile(' + `sys.argv[0]` + ',' + `self.context` + ')')
                return

            if cmd == RequestShutdown:
                self.sessionClose()
                return
                
            if cmd == RequestBreak:
                fn, line, set, cond = arg.split(',')
                line = int(line)
                set = int(set)

                if set:
                    if cond == 'None':
                        cond = None
                    self.set_break(fn,line, 0, cond)
                else:
                    self.clear_break(fn,line)

                return
                
            if cmd == RequestEval:
                try:
                    value = eval(arg, self.currentFrame.f_globals,
                                      self.currentFrame.f_locals)
                except:
                    # Report the exception and the traceback
                    try:
                        type, value, tb = sys.exc_info()
                        sys.last_type = type
                        sys.last_value = value
                        sys.last_traceback = tb
                        tblist = traceback.extract_tb(tb)
                        del tblist[:1]
                        list = traceback.format_list(tblist)
                        if list:
                            list.insert(0, "Traceback (innermost last):\n")
                            list[len(list):] = traceback.format_exception_only(type, value)
                    finally:
                        tblist = tb = None

                    map(self.write,list)

                    self.write(ResponseException + '\n')
                    
                else:
                    self.write(str(value) + '\n')
                    self.write(ResponseOK + '\n')
                    
                return
            
            if cmd == RequestExec:
                globals = self.currentFrame.f_globals
                locals = self.currentFrame.f_locals
                try:
                    code = compile(arg + '\n', '<stdin>', 'single')
                    exec code in globals, locals
                except:
                    # Report the exception and the traceback
                    try:
                        type, value, tb = sys.exc_info()
                        sys.last_type = type
                        sys.last_value = value
                        sys.last_traceback = tb
                        tblist = traceback.extract_tb(tb)
                        del tblist[:1]
                        list = traceback.format_list(tblist)
                        if list:
                            list.insert(0, "Traceback (innermost last):\n")
                            list[len(list):] = traceback.format_exception_only(type, value)
                    finally:
                        tblist = tb = None

                    map(self.write,list)

                    self.write(ResponseException + '\n')
                    
                return
            
            if cmd == RequestBanner:
                self.write('%s%s\n' % (ResponseBanner, 
                    str((sys.version, sys.platform, 'No Qt-Version'))))
                return
            
        # If we are handling raw mode input then reset the mode and break out
        # of the current event loop.
        if self.inRawMode:
            self.inRawMode = 0
            self.rawLine = line
            self.eventExit = 1
            return

        if self.buffer:
            self.buffer = self.buffer + '\n' + line
        else:
            self.buffer = line

        try:
            code = codeop.compile_command(self.buffer,self.readstream.name)
        except (OverflowError, SyntaxError, ValueError):
            # Report the exception
            sys.last_type, sys.last_value, sys.last_traceback = sys.exc_info()
            map(self.write,traceback.format_exception_only(sys.last_type,sys.last_value))
            self.buffer = ''

            self.handleException()
        else:
            if code is None:
                self.pendingResponse = ResponseContinue
            else: 
                self.buffer = ''

                try:
                    if self.running is None:
                        exec code in self.context
                    else:
                        globals = self.currentFrame.f_globals
                        locals = self.currentFrame.f_locals
                        exec code in globals, locals
                except SystemExit:
                    self.sessionClose()
                except:
                    # Report the exception and the traceback
                    try:
                        type, value, tb = sys.exc_info()
                        sys.last_type = type
                        sys.last_value = value
                        sys.last_traceback = tb
                        tblist = traceback.extract_tb(tb)
                        del tblist[:1]
                        list = traceback.format_list(tblist)
                        if list:
                            list.insert(0, "Traceback (innermost last):\n")
                            list[len(list):] = traceback.format_exception_only(type, value)
                    finally:
                        tblist = tb = None

                    map(self.write,list)

                    self.handleException()


    def write(self,s):
        """
        Private method to write data to the output stream.
        
        Arguments
        
            s -- data to be written (string)
        """
        self.writestream.write(s)
        self.writestream.flush()

    def interact(self):
        """
        Private method to Interact with  the debugger.
        
        Returns
        
            Never
        """
        global DebugClientInstance

        self.setDescriptors(self.readstream,self.writestream)
        DebugClientInstance = self

        if not self.passive:
            self.eventLoop()

    def eventLoop(self):
        """
        Private method implementing our event loop.
        """
        self.eventExit = None

        while self.eventExit is None:
            wrdy = []

            if AsyncPendingWrite(self.writestream):
                wrdy.append(self.writestream)

            if AsyncPendingWrite(self.errorstream):
                wrdy.append(self.errorstream)

            rrdy, wrdy, xrdy = select.select([self.readstream],wrdy,[])

            if self.readstream in rrdy:
                self.readReady(self.readstream.fileno())

            if self.writestream in wrdy:
                self.writeReady(self.writestream.fileno())

            if self.errorstream in wrdy:
                self.writeReady(self.errorstream.fileno())

        self.eventExit = None

    def connectDebugger(self,port,remoteAddress=None):
        """
        Public method to establish a session with the debugger. 
        
        It opens a network connection to the debugger, connects it to stdin, 
        stdout and stderr and saves these file objects in case the application
        being debugged redirects them itself.
        
        Arguments
        
            port -- the port number to connect to (int)
            
            remoteAddress -- the network address of the debug server host (string)
        """
        sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
        if remoteAddress is None:
            sock.connect((DebugAddress,port))
        else:
            sock.connect((remoteAddress,port))
        sock.setblocking(0)

        sys.stdin = AsyncFile(sock,sys.stdin.mode,sys.stdin.name)
        sys.stdout = AsyncFile(sock,sys.stdout.mode,sys.stdout.name)
        sys.stderr = AsyncFile(sock,sys.stderr.mode,sys.stderr.name)
        
        # save the stream in case a program redirects them
        self.readstream = sys.stdin
        self.writestream = sys.stdout
        self.errorstream = sys.stderr

    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.absPath(self.fix_frame_filename(frame))

        # See if we are skipping at the start of a newly loaded program.
        if self.firstFrame is None:
            if fn != self.running:
                return

            self.firstFrame = 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.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.firstFrame:
                fr = None
            else:
                fr = fr.f_back
        
        self.write('%s%s\n' % (ResponseLine,str(stack)))
        self.eventLoop()

    def unhandled_exception(self, exctype, excval, exctb):
        """
        Private method called to report an uncaught exception.
        
        Arguments
        
            exctype -- the type of the exception
            
            excval -- data about the exception
            
            exctb -- traceback for the exception
        """
        self.user_exception(None, (exctype,excval,exctb), 1)
        
    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.progTerminated(excval)
            
        elif exctype in [SyntaxError, IndentationError]:
            try:
                message, (filename, linenr, charnr, text) = excval
            except:
                exclist = []
            else:
                exclist = [message, (filename, linenr, charnr)]
            
            self.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.absPath(self.fix_frame_filename(fr))
                linenr = fr.f_lineno
                
                exclist.append((filename, linenr))
            
            self.write("%s%s\n" % (ResponseException, str(exclist)))
            
        self.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.firstFrame:
            self.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 ['DebugClientNoQt.py']:
            return 0

        # Eliminate anything that is part of the Python installation.
        #XXX - this should be a user option, or an extension of the meaning of
        # 'step into', or a separate type of step altogether, or have a
        #configurable set of ignored directories.
        afn = self.absPath(fn)

        if self.skipdir is not None and afn.find(self.skipdir) == 0:
            return 0

        return bdb.Bdb.stop_here(self,frame)

    def absPath(self,fn):
        """
        Private method to convert a filename to an absolute name.

        sys.path is used as a set of possible prefixes. The name stays 
        relative if a file could not be found.
        
        Arguments
        
            fn -- filename (string)
            
        Returns
        
            the converted filename (string)
        """
        if os.path.isabs(fn):
            return fn

        # Check the cache.
        if self.fncache.has_key(fn):
            return self.fncache[fn]

        # Search sys.path.
        for p in sys.path:
            afn = os.path.abspath(os.path.join(p,fn))

            if os.path.exists(afn):
                self.fncache[fn] = afn
                d = os.path.dirname(afn)
                if (d not in sys.path) and (d not in self.dircache):
                    self.dircache.append(d)
                return afn

        # Search the additional directory cache
        for p in self.dircache:
            afn = os.path.abspath(os.path.join(p,fn))
            
            if os.path.exists(afn):
                self.fncache[fn] = afn
                return afn
                
        # Nothing found.
        return fn

    def progTerminated(self,status):
        """
        Private method to tell the debugger that the program has terminated.
        
        Arguments
        
            status -- the return status
        """
        if status is None:
            status = 0
        else:
            try:
                int(status)
            except:
                status = 1

        self.set_quit()
        self.running = None
        self.write('%s%d\n' % (ResponseExit,status))

    def dumpVariables(self, frmnr, scope, filter):
        """
        Private method to return the variables of a frame to the debug server.
        
        Arguments
        
            frmnr -- distance of frame reported on. 0 is the current frame (int)
            
            scope -- 1 to report global variables, 0 for local variables (int)
            
            filter -- the indices of variable types to be filtered (list of int)
        """
        f = self.currentFrame
        
        while f is not None and frmnr > 0:
            f = f.f_back
            frmnr -= 1
        
        if f is None:
            return
        
        if scope:
            dict = f.f_globals
        else:
            dict = f.f_locals
            
            if f.f_globals is f.f_locals:
                scope = -1
                
        varlist = [scope]
        
        if scope != -1:
            keylist = dict.keys()
            
            (vlist, klist) = self.formatVariablesList(keylist, dict, filter)
            varlist = varlist + vlist
            
            if len(klist):
                for key in klist:
                    cdict = None
                    try:
                        cdict = dict[key].__dict__
                    except:
                        try:
                            slv = dict[key].__slots__
                            cdict = {}
                            for v in slv:
                                exec 'cdict[v] = dict[key].%s' % v
                        except:
                            pass
                    if cdict is not None:
                        keylist = cdict.keys()
                        (vlist, klist) = \
                            self.formatVariablesList(keylist, cdict, filter, 1, key)
                        varlist = varlist + vlist
                
        self.write('%s%s\n' % (ResponseVariables, str(varlist)))
    
    def formatVariablesList(self, keylist, dict, filter = [], classdict = 0, prefix = ''):
        """
        Private method to produce a formated variables list.
        
        The dictionary passed in to it is scanned. If classdict is false,
        it builds a list of all class instances in dict. If it is
        true, we are formatting a class dictionary. In this case
        we prepend prefix to the variable names. Variables are
        only added to the list, if their type is not contained 
        in the filter list. The formated variables list (a list of 
        tuples of 3 values) and the list of class instances is returned.
        
        Arguments
        
            keylist -- keys of the dictionary
            
            dict -- the dictionary to be scanned
            
            filter -- the indices of variable types to be filtered. Variables are
              only added to the list, if their type is not contained 
              in the filter list.
            
            classdict -- boolean indicating the formating of a class or
              module dictionary. If classdict is false,
              it builds a list of all class instances in dict. If it is
              true, we are formatting a class dictionary. In this case
              we prepend prefix to the variable names.
              
            prefix -- prefix to prepend to the variable names (string)
            
        Returns
        
            A tuple consisting of a list of formatted variables and a list of
            class instances. Each variable entry is a tuple of three elements,
            the variable name, its type and value.
        """
        varlist = []
        classes = []
        
        for key in keylist:
            # filter hidden attributes (filter #0)
            if 0 in filter and str(key)[:2] == '__':
                continue
            
            # special handling for '__builtins__' (it's way too big)
            if key == '__builtins__':
                rvalue = '<module __builtin__ (built-in)>'
                valtype = 'module'
            else:
                value = dict[key]
                valtypestr = str(type(value))[1:-1]
                if string.split(valtypestr,' ',1)[0] == 'class':
                    # handle new class type of python 2.2+
                    if ConfigVarTypeStrings.index('instance') in filter:
                        continue
                    valtype = valtypestr[7:-1]
                    classes.append(key)
                else:
                    valtype = valtypestr[6:-1]
                    try:
                        if ConfigVarTypeStrings.index(valtype) in filter:
                            continue
                        if valtype in ['instance', 'module']:
                            classes.append(key)
                    except ValueError:
                        if ConfigVarTypeStrings.index('other') in filter:
                            continue
                    
                try:
                    rvalue = repr(value)
                except:
                    rvalue = ''
                    
                if classdict:
                    key = prefix + '.' + key
                    
            varlist.append((key, valtype, rvalue))
        
        return (varlist, classes)

    def startDebugger(self, filename=None, host=None, port=None):
        """
        Method used to start the remote debugger.
        
        Arguments
        
            filename -- the program to be debugged (string)
            
            host -- hostname of the debug server (string)
            
            port -- portnumber of the debug server (int)
        """
        global debugClient
        if host is None:
            host = os.getenv('ERICHOST', 'localhost')
        if port is None:
            port = os.getenv('ERICPORT', 42424)
            
        self.connectDebugger(port, socket.gethostbyname(host))
        if filename is not None:
            self.running = os.path.abspath(filename)
        else:
            try:
                self.running = os.path.abspath(sys.argv[0])
            except:
                pass
        self.passive = 1
        self.write(PassiveStartup + '\n')
        self.interact()
        
        # setup the debugger variables
        self.fncache = {}
        self.dircache = []
        self.firstFrame = None
        self.currentFrame = None
        self.inRawMode = 0
        
        # set the system exception handling function to ensure, that
        # we report on all unhandled exceptions
        sys.excepthook = self.unhandled_exception
        
        # now start debugging
        self.set_trace()
    
# We are normally called by the debugger to execute directly.

if __name__ == '__main__':
    try:
        port = int(sys.argv[1])
    except:
        port = -1
    try:
        remoteAddress = sys.argv[2]
    except:
        remoteAddress = None
    sys.argv = ['']
    sys.path[0] = ''
    debugClient = DebugClient()
    if port >= 0:
        debugClient.connectDebugger(port, remoteAddress)
        debugClient.interact()

Table of Contents

This document was automatically generated by HappyDoc version 2.1