# -*- 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
|