Table of Contents

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

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

"""
Module implementing the debug server.
"""

import sys
import os
import socket
import time
import signal

from qt import SIGNAL, PYSIGNAL
from qtnetwork import QServerSocket, QSocket

from DebugProtocol import *
import Preferences


class DebugServer(QServerSocket):
    """
    Class implementing the debug server embedded within the IDE.
    
    Signals
    
        clientRawInputSent -- emitted after the data was sent to the debug client
        
        clientLine(filename, lineno) -- emitted after the debug client has executed a
            line of code
        
        clientStack(stack) -- emitted after the debug client has executed a
            line of code
            
        clientVariables(variables) -- emitted after a variables dump has been received
        
        clientStatement(boolean) -- emitted after an interactive command has
            been executed. The parameter is 0 to indicate that the command is
            complete and 1 if it needs more input.
            
        clientException(exception) -- emitted after an exception occured on the 
            client side
            
        clientSyntaxError(exception) -- emitted after a syntax error has been detected
            on the client side
            
        clientExit(exitcode) -- emitted after the client has exited
        
        clientRawInput(prompt) -- emitted after a raw input request was received
        
        clientBanner(banner) -- emitted after the client banner was received
        
        passiveDebuStarted -- emitted after the debug client has connected in
            passive debug mode
            
        clientGone -- emitted if the client went away (planned or unplanned)
    """
    def __init__(self):
        """
        Constructor
        """
        if Preferences.getDebugger("PassiveDbgEnabled"):
            socket = Preferences.getDebugger("PassiveDbgPort") # default: 42424
            QServerSocket.__init__(self,socket)
            self.passive = 1
        else:
            QServerSocket.__init__(self,0)
            self.passive = 0

        self.qsock = None
        self.progLoaded = 0
        self.debugging = 1
        self.queue = []
        self.breaks = {}    # key: filename, value: (lineno, condition)
        self.clientPID = 0

        if not self.passive:
            self.startRemote()

    def startRemote(self):
        """
        Private method to start a remote interpreter.
        """
        if Preferences.getDebugger("CustomPythonInterpreter"):
            interpreter = str(Preferences.getDebugger("PythonInterpreter"))
            if interpreter == "":
                interpreter = sys.executable
        else:
            interpreter = sys.executable
            
        debugClientType = Preferences.getDebugger("DebugClientType")
        if debugClientType == 1:
            debugClient = os.path.join(sys.path[0],'Debugger','DebugClient.py')
        elif debugClientType == 2:
            debugClient = os.path.join(sys.path[0],'Debugger','DebugClientThreads.py')
        elif debugClientType == 3:
            debugClient = os.path.join(sys.path[0],'Debugger','DebugClientNoQt.py')
        else:
            debugClient = str(Preferences.getDebugger("DebugClient"))
            if debugClient == "":
                debugClient = os.path.join(sys.path[0],'Debugger','DebugClient.py')
            
        if self.clientPID:
            try:
                # get rid of died child on Unix systems; if the child is
                # non cooperative, it will be killed
                status = os.waitpid(self.clientPID, os.WNOHANG)
                if status == (0, 0):
                    os.kill(self.clientPID, signal.SIGTERM)
                    time.sleep(1)
                    status = os.waitpid(self.clientPID, os.WNOHANG)
                    if status == (0, 0):
                        os.kill(self.clientPID, signal.SIGKILL)
            except:
                pass
            
        if Preferences.getDebugger("RemoteDbgEnabled"):
            ipaddr = socket.gethostbyname(socket.gethostname())
            rexec = str(Preferences.getDebugger("RemoteExecution"))
            rhost = str(Preferences.getDebugger("RemoteHost"))
            self.clientPID = os.spawnv(os.P_NOWAIT, rexec,
                [rexec, rhost, 
                 interpreter, os.path.abspath(debugClient), `self.port()`, ipaddr])
        else:
            self.clientPID = os.spawnv(os.P_NOWAIT,interpreter,
                [interpreter, debugClient, `self.port()`])

    def newConnection(self,sockfd):
        """
        Reimplemented to handle a new connection.
        
        Arguments
        
            sockfd -- the socket descriptor
        """
        sock = QSocket()
        sock.setSocket(sockfd)

        # If we already have a connection, refuse this one.  It will be closed
        # automatically.
        if self.qsock is not None:
            return

        self.connect(sock,SIGNAL('readyRead()'),self.handleLine)
        self.connect(sock,SIGNAL('connectionClosed()'),self.startClient)

        self.qsock = sock

        # Send commands that were waiting for the connection.
        for cmd in self.queue:
            self.qsock.writeBlock(cmd)

        self.queue = []
        
        if self.passive:
            self.progLoaded = 1

    def shutdownServer(self):
        """
        Public method to cleanly shut down.
        
        It closes our socket and shuts down
        the debug client. (Needed on Win OS)
        """
        if self.qsock is None:
            return
            
        # do not want any slots called during shutdown
        self.disconnect(self.qsock, SIGNAL('connectionClosed()'), self.startClient)
        self.disconnect(self.qsock, SIGNAL('readyRead()'), self.handleLine)
        
        # close down socket, and shut down client as well.
        self.sendCommand('%s\n' % RequestShutdown)
        self.qsock.flush()
        
        self.qsock.close()
        
        # reinitialize
        self.qsock = None
        self.progLoaded = 1  # fake server into starting new client later...
        self.queue = []

    def remoteLoad(self,fn,argv,wd):
        """
        Public method to load a new program to debug.
        
        Arguments
        
            fn -- the filename to debug (string)
            
            argv -- the commandline arguments to pass to the program (list of strings)
            
            wd -- the working directory for the program (string)
        """
        # Restart the client if there is already a program loaded.
        if self.progLoaded:
            self.startClient(0)
        
        self.sendCommand('%s%s|%s|%s\n' % \
            (RequestLoad, str(wd),
             str(os.path.abspath(str(fn))),str(argv)))
        self.progLoaded = 1
        self.debugging = 1
        self.restoreBreakpoints()

    def remoteRun(self,fn,argv,wd):
        """
        Public method to load a new program to run.
        
        Arguments
        
            fn -- the filename to run (string)
            
            argv -- the commandline arguments to pass to the program (list of strings)
            
            wd -- the working directory for the program (string)
        """
        # Restart the client if there is already a program loaded.
        if self.progLoaded:
            self.startClient(0)
        
        self.sendCommand('%s%s|%s|%s\n' % \
            (RequestRun, str(wd),
             str(os.path.abspath(str(fn))),str(argv)))
        self.progLoaded = 1
        self.debugging = 0

    def remoteCoverage(self,fn,argv,wd,erase):
        """
        Public method to load a new program to collect coverage data.
        
        Arguments
        
            fn -- the filename to run (string)
            
            argv -- the commandline arguments to pass to the program (list of strings)
            
            wd -- the working directory for the program (string)
            
            erase -- flag indicating that coverage info should be cleared first (boolean)
        """
        # Restart the client if there is already a program loaded.
        if self.progLoaded:
            self.startClient(0)
        
        self.sendCommand('%s%s|%s|%s|%d\n' % \
            (RequestCoverage, str(wd),
             str(os.path.abspath(str(fn))),str(argv),erase))
        self.progLoaded = 1
        self.debugging = 0

    def remoteProfile(self,fn,argv,wd,erase):
        """
        Public method to load a new program to collect profiling data.
        
        Arguments
        
            fn -- the filename to run (string)
            
            argv -- the commandline arguments to pass to the program (list of strings)
            
            wd -- the working directory for the program (string)
            
            erase -- flag indicating that timing info should be cleared first (boolean)
        """
        # Restart the client if there is already a program loaded.
        if self.progLoaded:
            self.startClient(0)
        
        self.sendCommand('%s%s|%s|%s|%d\n' % \
            (RequestProfile, str(wd),
             str(os.path.abspath(str(fn))),str(argv),erase))
        self.progLoaded = 1
        self.debugging = 0

    def remoteStatement(self,stmt):
        """
        Public method to execute a Python statement.  
        
        Arguments
        
            stmt -- the Python statement to execute (string). It
              should not have a trailing newline.
        """
        self.sendCommand('%s\n' % stmt)
        self.sendCommand(RequestOK + '\n')

    def remoteStep(self):
        """
        Public method to single step the debugged program.
        """
        self.sendCommand(RequestStep + '\n')

    def remoteStepOver(self):
        """
        Public method to step over the debugged program.
        """
        self.sendCommand(RequestStepOver + '\n')

    def remoteStepOut(self):
        """
        Public method to step out the debugged program.
        """
        self.sendCommand(RequestStepOut + '\n')

    def remoteStepQuit(self):
        """
        Public method to stop the debugged program.
        """
        self.sendCommand(RequestStepQuit + '\n')

    def remoteContinue(self):
        """
        Public method to continue the debugged program.
        """
        self.sendCommand(RequestContinue + '\n')

    def remoteBreakpoint(self,fn,line,set,cond):
        """
        Public method to set or clear a breakpoint.
        
        Arguments
        
            fn -- filename the breakpoint belongs to (string)
            
            line -- linenumber of the breakpoint (int)
            
            set -- flag indicating setting or resetting a breakpoint (boolean)
            
            cond -- condition of the breakpoint (string)
        """
        if set:
            try:
                self.breaks[str(fn)].append((line, cond))
            except KeyError:
                self.breaks[str(fn)] = [(line, cond)]
        else:
            try:
                self.breaks[str(fn)].remove((line, cond))
            except:
                pass
        self.sendCommand('%s%s,%d,%d,%s\n' % (RequestBreak,fn,line,set,cond))

    def remoteRawInput(self,s):
        """
        Public method to send the raw input to the debugged program.
        
        Arguments
        
            s -- the raw input (string)
        """
        self.sendCommand(s + '\n')
        self.emit(PYSIGNAL('clientRawInputSent'), ())
        
    def remoteClientVariables(self, scope, filter, framenr=0):
        """
        Public method to request the variables of the debugged program.
        
        Arguments
        
            scope -- the scope of the variables (0 = local, 1 = global)
            
            filter -- list of variable types to filter out (list of int)
            
            framenr -- framenumber of the variables to retrieve (int)
        """
        self.sendCommand('%s%d, %d, %s\n' % (RequestVariables,framenr,scope,str(filter)))
        
    def remoteEval(self, arg):
        """
        Public method to evaluate arg in the current context of the debugged program.
        
        Arguments
        
            arg -- the arguments to evaluate (string)
        """
        self.sendCommand('%s%s\n' % (RequestEval,arg))
        
    def remoteExec(self, stmt):
        """
        Public method to execute stmt in the current context of the debugged program.
        
        Arguments
        
            stmt -- statement to execute (string)
        """
        self.sendCommand('%s%s\n' % (RequestExec,stmt))
        
    def remoteBanner(self):
        """
        Public slot to get the banner info of the remote client.
        """
        self.sendCommand(RequestBanner + '\n')
        
    def handleLine(self):
        """
        Private method to handle data from the client.
        """
        while self.qsock.canReadLine():
            line = str(self.qsock.readLine())

##~             print line          ##debug
            
            eoc = line.find('<') + 1
            
            # Deal with case where user has written directly to stdout
            # or stderr, but not line terminated and we stepped over the
            # write call, in that case the >line< will not be the first
            # string read from the socket...
            boc = line.find('>')
            if boc > 0:
                self.emit(PYSIGNAL('clientOutput'),(line[:boc],))
                
                    
            if boc >= 0 and eoc > boc:
                # Emit the signal corresponding to the response.
                resp = line[boc:eoc]

                if resp == ResponseLine:
                    stack = eval(line[eoc:-1])
                    cf = stack[0]
                    self.emit(PYSIGNAL('clientLine'),(cf[0],int(cf[1])))
                    self.emit(PYSIGNAL('clientStack'),(stack,))
                    return
                    
                if resp == ResponseVariables:
                    self.emit(PYSIGNAL('clientVariables'),(line[eoc:-1],))
                    return

                if resp == ResponseOK:
                    self.emit(PYSIGNAL('clientStatement'),(0,))
                    return

                if resp == ResponseContinue:
                    self.emit(PYSIGNAL('clientStatement'),(1,))
                    return

                if resp == ResponseException:
                    self.emit(PYSIGNAL('clientException'),(line[eoc:-1],))
                    return

                if resp == ResponseSyntax:
                    self.emit(PYSIGNAL('clientSyntaxError'),(line[eoc:-1],))
                    return
                    
                if resp == ResponseExit:
                    self.emit(PYSIGNAL('clientExit'),(line[eoc:-1],))
                    if self.passive:
                        self.passiveShutDown()
                    return
                
                if resp == ResponseRaw:
                    self.emit(PYSIGNAL('clientRawInput'),(line[eoc:-1],))
                    return
                    
                if resp == ResponseBanner:
                    bi = eval(line[eoc:-1])
                    self.emit(PYSIGNAL('clientBanner'),bi)
                    return
                    
                if resp == PassiveStartup:
                    self.passiveStartUp()
                    return

            self.emit(PYSIGNAL('clientOutput'),(line,))

    def passiveStartUp(self):
        """
        Private method to handle a passive debug connection.
        """
        print str(self.trUtf8("Passive debug connection received"))
        self.passiveClientExited = 0
        self.restoreBreakpoints()
        self.emit(PYSIGNAL('passiveDebugStarted'), ())
        
    def passiveShutDown(self):
        """
        Private method to shut down a passive debug connection.
        """
        self.passiveClientExited = 1
        self.shutdownServer()
        print str(self.trUtf8("Passive debug connection closed"))
        
    def startClient(self,unplanned=1):
        """
        Private method to start a debug client.
        
        Arguments
        
            unplanned -- flag indicating that the client has died
        """
        if not self.passive or not self.passiveClientExited: 
            if self.qsock is not None:
                self.shutdownServer()
                self.emit(PYSIGNAL('clientGone'),(unplanned & self.debugging,))
            
        if not self.passive:
            self.startRemote()

    def sendCommand(self,cmd):
        """
        Private method to send a single line command to the client.
        
        Arguments
        
            cmd -- command to send to the debug client (string)
        """
        if self.qsock is not None:
            self.qsock.writeBlock(cmd)
        else:
            self.queue.append(cmd)

    def restoreBreakpoints(self):
        """
        Private method to restore the break points after a restart.
        """
        for fn, bps in self.breaks.items():
            for line, cond in self.breaks[fn]:
                self.sendCommand('%s%s,%d,%d,%s\n' % (RequestBreak,fn,line,1,cond))
    
    def clearAllBreakpoints(self):
        """
        Public method (slot) to clear the local list of breakpoints.
        """
        self.breaks = {}
    
    def clearFileBreakpoints(self, fn):
        """
        Public method to clear the local list of breakpoints for a file.
        
        Arguments
        
            fn -- filename of the breakpoints to be cleared (string)
        """
        try:
            del self.breaks[str(fn)]
        except:
            pass
    
    def getAllBreakpoints(self):
        """
        Public method to get all breakpoints set in the server.
        
        Returns
        
            list of all breakpoints
        """
        return self.breaks
        
    def getFileBreakpoints(self, filename):
        """
        Public method to get all breakpoints of a specific file.
        
        Arguments
        
            filename -- the filename of the breakpoints to retrieve (string)
            
        Returns
        
            list of all breakpoints belonging to filename
        """
        try:
            return self.breaks[str(filename)]
        except:
            return []
        
    def getProjectBreakpoints(self, filelist):
        """
        Public method to get all breakpoints of the current project.
        
        Arguments
        
            filelist -- list of filenames belonging to the current project
            
        Returns
        
            list of all breakpoint belonging to the current project
        """
        bpDict = {}
        if filelist is not None:
            for fn in filelist:
                try:
                    bpDict[fn] = self.breaks[str(fn)]
                except:
                    pass
        return bpDict

Table of Contents

This document was automatically generated by HappyDoc version 2.1