+#---------------------------------------------------------------------------
+# Name: PHPDebugger.py
+# Purpose: php dbg client and supporting code
+# Author: Matt Fryer, Kevin Wang
+# Created: 2/1/06
+# Copyright: (c) 2006 ActiveGrid, Inc.
+# License: wxWindows License
+#---------------------------------------------------------------------------
+
+
+import os
+import socket
+import sys
+import threading
+import traceback
+import wx
+import DebuggerService
+import activegrid.util.sysutils as sysutils
+
+
+DBGC_REPLY = 0x0 # reply to previous DBGA_REQUEST request
+DBGC_STARTUP = 0x0001 # script startup
+DBGC_END = 0x0002 # script done
+DBGC_BREAKPOINT = 0x0003 # user definded breakpoint occured
+DBGC_STEPINTO_DONE = 0x0004 # step to the next statement is completed
+DBGC_STEPOVER_DONE = 0x0005 # step to the next statement is completed
+DBGC_STEPOUT_DONE = 0x0006 # step to the next statement is completed
+DBGC_EMBEDDED_BREAK = 0x0007 # breakpoint caused by DebugBreak() function
+DBGC_ERROR = 0x0010 # error occured
+DBGC_LOG = 0x0011 # logging support
+DBGC_SID = 0x0012 # send SID
+DBGC_PAUSE = 0x0013 # pause current session as soon as possible
+DBGC_AG_SHUTDOWN_REQ = 0x0201 # special ActiveGrid UI shutdown listening thread command
+
+
+FRAME_STACK = 100000 # "call:stack" - e.g. backtrace
+FRAME_SOURCE = 100100 # source text
+FRAME_SRC_TREE = 100200 # tree of source files
+FRAME_RAWDATA = 100300 # raw data or string
+FRAME_ERROR = 100400 # error notification
+FRAME_EVAL = 100500 # evaluating/watching
+FRAME_BPS = 100600 # set/remove breakpoint
+FRAME_BPL = 100700 # breakpoint(s) request = get the list
+FRAME_VER = 100800 # version request
+FRAME_SID = 100900 # session id info
+FRAME_SRCLINESINFO = 101000 # source lines info
+FRAME_SRCCTXINFO = 101100 # source contexts info
+FRAME_LOG = 101200 # logging
+FRAME_PROF = 101300 # profiler
+FRAME_PROF_C = 101400 # profiler counter/accuracy
+FRAME_SET_OPT = 101500 # set/update options
+
+
+DBGF_STARTED = 0x0001 # debugger has been started
+DBGF_FINISHED = 0x0002 # DBGC_END notification has been sent
+DBGF_WAITACK = 0x0004 # awaiting replay|request
+DBGF_UNSYNC = 0x0008 # protocol has been unsynchronized
+DBGF_REQUESTPENDING = 0x0010 # Debug session request pending
+DBGF_REQUESTFOUND = 0x0020 # Debug session request found
+DBGF_REJECTIONFOUND = 0x0040 # DBGSESSID=-1 found - session rejection
+
+
+E_ERROR = 1 << 0
+E_WARNING = 1 << 1
+E_PARSE = 1 << 2
+E_NOTICE = 1 << 3
+E_CORE_ERROR = 1 << 4
+E_CORE_WARNING = 1 << 5
+E_COMPILE_ERROR = 1 << 6
+E_COMPILE_WARNING = 1 << 7
+E_USER_ERROR = 1 << 8
+E_USER_WARNING = 1 << 9
+E_USER_NOTICE = 1 << 10
+
+
+BPS_DELETED = 0
+BPS_DISABLED = 1
+BPS_ENABLED = 2
+BPS_UNRESOLVED = 0x100
+
+
+DBG_SYNC = 0x5953
+DBG_SYNC2_STR = chr(0) + chr(0) + chr(89) + chr(83)
+RESPONSE_HEADER_SIZE = 16
+
+
+_VERBOSE = False
+def myprint(format, vlist=None):
+ if _VERBOSE:
+ if vlist:
+ print format % vlist
+ else:
+ print format
+
+
+#
+# 4 Char's to an Integer
+#
+def C4ToInt(ch, startPos):
+ retval = 0
+ pos = startPos
+
+ retval = retval + (CharToInt(ch[pos]) << 24)
+ pos = pos + 1
+ retval = retval + (CharToInt(ch[pos]) << 16)
+ pos = pos + 1
+ retval = retval + (CharToInt(ch[pos]) << 8)
+ pos = pos + 1
+ retval = retval + (CharToInt(ch[pos]) << 0)
+
+ return retval
+
+
+def CharToInt(ch):
+ return int((ord(ch) & 0x00FF));
+
+
+#
+# An Integer to 4 Char's
+#
+def IntToC4(num):
+ retval = chr((num >> 24) & 0x00FF)
+ retval += chr((num >> 16) & 0x00FF)
+ retval += chr((num >> 8 ) & 0x00FF)
+ retval += chr((num >> 0 ) & 0x00FF)
+
+ return retval
+
+
+DBGA_CONTINUE = IntToC4(0x8001)
+DBGA_STOP = IntToC4(0x8002)
+DBGA_STEPINTO = IntToC4(0x8003)
+DBGA_STEPOVER = IntToC4(0x8004)
+DBGA_STEPOUT = IntToC4(0x8005)
+DBGA_IGNORE = IntToC4(0x8006)
+DBGA_REQUEST = IntToC4(0x8010)
+
+
+def getCommandString(code):
+ if code == DBGC_REPLY:
+ return "REPLY"
+ elif code == DBGC_STARTUP:
+ return "STARTUP"
+ elif code == DBGC_END:
+ return "END"
+ elif code == DBGC_BREAKPOINT:
+ return "BREAKPOINT"
+ elif code == DBGC_STEPINTO_DONE:
+ return "STEPINTO DONE"
+ elif code == DBGC_STEPOVER_DONE:
+ return "STEPOVER DONE"
+ elif code == DBGC_STEPOUT_DONE:
+ return "STEPOUT DONE"
+ elif code == DBGC_EMBEDDED_BREAK:
+ return "EMBEDDED BREAK"
+ elif code == DBGC_PAUSE:
+ return "PAUSE"
+ elif code == DBGC_ERROR:
+ return "ERROR"
+ elif code == DBGC_LOG:
+ return "LOG"
+ elif code == DBGC_SID:
+ return "SEND SID"
+ elif code == DBGC_AG_SHUTDOWN_REQ:
+ return "AG SHUTDOWN REQ"
+
+
+def reportFlags(flagsValue):
+ flagsRetVal = ""
+ if flagsValue & DBGF_STARTED: # debugger has been started
+ flagsRetVal += "started+"
+ if flagsValue & DBGF_FINISHED: # DBGC_END notification has been sent
+ flagsRetVal += "finished+"
+ if flagsValue & DBGF_WAITACK: # awaiting replay|request
+ flagsRetVal += "awaiting ack+"
+ if flagsValue & DBGF_UNSYNC: # protocol has been unsynchronized
+ flagsRetVal += "protocol unsynchronized+"
+ if flagsValue & DBGF_REQUESTPENDING: # Debug session request pending
+ flagsRetVal += "request pending+"
+ if flagsValue & DBGF_REQUESTFOUND: # Debug session request found
+ flagsRetVal += "request found+"
+ if flagsValue & DBGF_REJECTIONFOUND : # DBGSESSID=-1 found - session rejection
+ flagsRetVal += "session rejection+"
+ return flagsRetVal
+
+
+def getErrorTypeString(code):
+ if code == E_ERROR:
+ return "[Error]"
+ elif code == E_WARNING:
+ return "[Warning]"
+ elif code == E_PARSE:
+ return "[Parse Error]"
+ elif code == E_NOTICE:
+ return "[Notice]"
+ elif code == E_CORE_ERROR:
+ return "[Core Error]"
+ elif code == E_CORE_WARNING:
+ return "[Core Warning]"
+ elif code == E_COMPILE_ERROR:
+ return "[Compile Error]"
+ elif code == E_COMPILE_WARNING:
+ return "[Compile Warning]"
+ elif code == E_USER_ERROR:
+ return "[User Error]"
+ elif code == E_USER_WARNING:
+ return "[User Warning]"
+ elif code == E_USER_NOTICE:
+ return "[User Notice]"
+ else:
+ return "[Unexpected Error]"
+
+
+class ResponseHeader(object):
+ def __init__(self, conn, blocking = False):
+ self.isValid = False
+ receivedData = conn.recv(RESPONSE_HEADER_SIZE, blocking)
+
+ if not receivedData:
+ myprint("Tried to get %d bytes of PHP DBG header, got None\n" % RESPONSE_HEADER_SIZE)
+ return
+ elif len(receivedData) != RESPONSE_HEADER_SIZE:
+ myprint("Tried to get %d bytes of PHP DBG header, got %d\n" % (RESPONSE_HEADER_SIZE, len(receivedData)))
+ return
+
+ self.sync = C4ToInt(receivedData, 0)
+ self.command = C4ToInt(receivedData, 4)
+ self.flags = C4ToInt(receivedData, 8)
+ self.toRead = C4ToInt(receivedData, 12)
+
+ myprint("ResponseHeader: sync=%x, command=%s, flags=(%s), toRead=%s\n", (self.sync, getCommandString(self.command), reportFlags(self.flags), self.toRead))
+ if self.sync != DBG_SYNC:
+ myprint("Sync wrong for header! Expected %x, got %s\n" % (DBG_SYNC, self.sync))
+ return
+
+ self.isValid = True
+
+
+class ResponsePacketFrame(object):
+ def __init__(self, conn, size, data, blocking = False):
+ self.isValid = False
+ self.conn = conn
+ self.data = ''
+
+ if data:
+ self.data = data
+ newlyReceived = False
+ elif conn:
+ newlyReceived = True
+
+ sizeToReceive = size
+ while True:
+ oneChunk = conn.recv(sizeToReceive, blocking)
+ sizeReceived = len(oneChunk)
+ if sizeReceived > 0:
+ self.data = self.data + oneChunk
+
+ if sizeReceived < sizeToReceive:
+ sizeToReceive = sizeToReceive - sizeReceived
+ continue
+ else:
+ break
+
+ if len(self.data) != size:
+ myprint("Expected to get %d bytes of a PHP DBG packet, got %d\n" % (size, len(self.data)))
+ return
+ else:
+ return
+
+ self.frameName = C4ToInt(self.data, 0)
+ self.frameSize = C4ToInt(self.data, 4)
+ if newlyReceived:
+ myprint("Newly received ResponsePacketFrame: frameName=%d, frameSize=%d", (self.frameName, self.frameSize))
+ else:
+ myprint("Created from existing ResponsePacketFrame: frameName=%d, frameSize=%d", (self.frameName, self.frameSize))
+
+ if self.frameSize == 0:
+ return
+
+ self.currPos = 8
+ self.totalDataLen = len(self.data)
+ self.length = 8 + self.frameSize
+ self.isValid = True
+ myprint("new ResponsePacketFrame: currPos=%s, totalDataLen=%s, length=%s", (repr(self.currPos), repr(self.totalDataLen), repr(self.length)))
+ return
+
+ def getNextInt(self):
+ myprint("getNextInt(): currPos=%s, totalDataLen=%s, length=%s", (repr(self.currPos), repr(self.totalDataLen), repr(self.length)))
+ if self.isValid and self.currPos + 4 <= self.length:
+ val = C4ToInt(self.data, self.currPos)
+ self.currPos = self.currPos + 4
+ myprint("getNextInt(): got an integar: %s", repr(val))
+ return val
+ else:
+ return self._errorReturn("getNextInt(): no more integar available with current frame: ")
+
+ def getNextString(self, strLen):
+ endPos = self.currPos + strLen
+ if self.isValid and endPos <= self.length:
+ #
+ # Trim the ending '\0'. TODO: confirm this applies to all raw string data.
+ #
+ str = self.data[self.currPos:endPos - 1]
+ self.currPos = endPos
+ myprint("getNextString(): got a string: %s", str)
+ return str
+ else:
+ return self._errorReturn("getNextString(): no more string available with current frame: ")
+
+ def getNextFrame(self, useAbsolutePos = False):
+ if useAbsolutePos:
+ #
+ # Skip this frame's header (8 bytes for frameSize and frameSize) and frame data (frameSize).
+ #
+ self.currPos = self.length
+
+ if self.isValid and self.currPos < self.totalDataLen:
+ return ResponsePacketFrame(None, None, self.data[self.currPos:])
+ else:
+ return self._errorReturn("getNextFrame(): no more frame available with current frame: ")
+
+ def _errorReturn(self, preMsg = ''):
+ myprint(preMsg + "frameName=%s, frameSize=%s, totalDataLen=%s, length=%s, currPos:%s", (repr(self.frameName), repr(self.frameSize), repr(self.totalDataLen), repr(self.length), repr(self.currPos)))
+ self.isValid = False
+ return None
+
+
+class PHPDBGFrame(object):
+ FRAME_HEADER_SIZE = 8
+ def __init__(self, frameType):
+ self.frameType = IntToC4(frameType)
+ self.frameData = ""
+
+ def addInt(self, intVal):
+ self.frameData = self.frameData + IntToC4(intVal)
+
+ def addChar(self, charVal):
+ self.frameData = self.frameData + charVal
+
+ def addStr(self, string):
+ #
+ # Add the trailing '\0'.
+ #
+ self.frameData = self.frameData + string + '\0'
+
+ def getSize(self):
+ return len(self.frameData) + PHPDBGFrame.FRAME_HEADER_SIZE
+
+ def writeFrame(self, conn):
+ header = self.frameType + IntToC4(len(self.frameData))
+ conn.sendall(header)
+ conn.sendall(self.frameData)
+
+
+class PHPDBGPacket(object):
+ def __init__(self, packetType):
+ self.header = DBG_SYNC2_STR + packetType
+ self.frames = []
+ self.packetSize = 0
+
+ def addFrame(self, frame):
+ self.frames.append(frame)
+ self.packetSize += frame.getSize()
+
+ def sendPacket(self, conn, flags = 0):
+ self.header += IntToC4(flags)
+ self.header += IntToC4(self.packetSize)
+ conn.sendall(self.header)
+ for frame in self.frames:
+ frame.writeFrame(conn)
+
+
+class PHPDBGException(Exception):
+ def __init__(self, msg = None, cause = None):
+ if (msg == None):
+ Exception.__init__(self)
+ elif (cause == None):
+ Exception.__init__(self, msg)
+ else:
+ Exception.__init__(self, "PHPDBGException: message:%s\n, cause:%s" % (msg, cause))
+
+
+class PHPDBGConnException(PHPDBGException):
+ pass
+
+
+class PHPDebuggerCallback(object):
+ ACTION_NONE = 0
+ ACTION_STOP = 1
+ ACTION_LISTEN = 2
+
+ def __init__(self, ui, service, lsnrHosti, lsnrPorti):
+ self.ui = ui
+ self.service = service
+ self.lsnrHost = lsnrHosti
+ self.lsnrPort = lsnrPorti
+ self.lsnrThr = None
+ self.lsnrAction = PHPDebuggerCallback.ACTION_NONE
+ self.clearInternals()
+ self.initLsnrThr()
+
+
+ ############################################################################
+ # Public callback functions begin
+ #
+ def Start(self):
+ self.lsnrThr.start()
+
+ def ShutdownServer(self, stopLsnr = True):
+ #
+ # First to tell php debugger to stop execution of the current PHP
+ # program. Disconnect with php dbg module too.
+ #
+ self.stopPhpDbg()
+
+ #
+ # Stop debug listener.
+ #
+ if stopLsnr:
+ self.stopLsnr()
+
+ def BreakExecution(self):
+ reqPacket = PHPDBGPacket(IntToC4(DBGC_PAUSE))
+
+ try:
+ reqPacket.sendPacket(self.lsnrThr)
+ self.awaitAndHandleResponse()
+ except PHPDBGConnException:
+ self.currConnFinished()
+ return
+
+ self.ui.LoadPHPFramesList(self.stackList)
+ return
+
+ def SingleStep(self):
+ reqPacket = PHPDBGPacket(DBGA_STEPINTO)
+
+ try:
+ reqPacket.sendPacket(self.lsnrThr)
+ self.lastCommand = DBGA_STEPINTO
+ self.awaitAndHandleResponse(blocking = True)
+ except PHPDBGConnException:
+ self.currConnFinished()
+ return
+
+ self.ui.LoadPHPFramesList(self.stackList)
+ return
+
+ def Next(self):
+ reqPacket = PHPDBGPacket(DBGA_STEPOVER)
+
+ try:
+ reqPacket.sendPacket(self.lsnrThr)
+ self.lastCommand = DBGA_STEPOVER
+ self.awaitAndHandleResponse(blocking = True)
+ except PHPDBGConnException:
+ self.currConnFinished()
+ return
+
+ self.ui.LoadPHPFramesList(self.stackList)
+ return
+
+ def Continue(self):
+ reqPacket = PHPDBGPacket(DBGA_CONTINUE)
+
+ try:
+ reqPacket.sendPacket(self.lsnrThr)
+ self.lastCommand = DBGA_CONTINUE
+ self.awaitAndHandleResponse(blocking = True)
+ except PHPDBGConnException:
+ self.currConnFinished()
+ return
+
+ self.ui.LoadPHPFramesList(self.stackList)
+ return
+
+ def Return(self):
+ reqPacket = PHPDBGPacket(DBGA_STEPOUT)
+
+ try:
+ reqPacket.sendPacket(self.lsnrThr)
+ self.lastCommand = DBGA_STEPOUT
+ self.awaitAndHandleResponse(blocking = True)
+ except PHPDBGConnException:
+ self.currConnFinished()
+ return
+
+ self.ui.LoadPHPFramesList(self.stackList)
+ return
+
+ def PushBreakpoints(self, noRemove = False):
+ tmpList = []
+ bps = self.service.GetMasterBreakpointDict()
+ for fileName in bps.keys():
+ if fileName.endswith('.php'):
+ lines = bps[fileName]
+ if lines:
+ for lineNo in lines:
+ if lineNo:
+ #
+ # A tuple (fileName, lineNo) is an item which is
+ # used as a key in self.bpDict.
+ #
+ tmpList.append(self.createBpKey(fileName, lineNo))
+ myprint("PushBreakpoints(): global breakpoint \'%s:%i\'", (fileName, lineNo))
+
+ #
+ # Check to see if we have any new breakpoints added.
+ #
+ for oneKey in tmpList:
+ if not self.bpDict.has_key(oneKey):
+ #
+ # A new breakpoint.
+ #
+ newBp = BreakPoint(self, oneKey[0], oneKey[1])
+ newBp.addSelf()
+ self.bpDict[oneKey] = newBp
+ myprint("PushBreakpoints(): newly added global breakpoint \'%s:%i\'", (oneKey[0], oneKey[1]))
+
+ if noRemove:
+ return
+
+ #
+ # Check to see if any bp that is in our list, but not in the latest
+ # global list. If so, it must have been removed recently in the
+ # global one. Remove it from our list and tell php debugger to do
+ # so as well.
+ #
+ toRemoveList = []
+ for oneKey in self.bpDict.keys():
+ if tmpList.count(oneKey) == 0:
+ toRemoveList.append((oneKey, self.bpDict[oneKey]))
+ myprint("PushBreakpoints(): recently removed global breakpoint \'%s:%i\'", (oneKey[0], oneKey[1]))
+
+ for bp in toRemoveList:
+ bp[1].removeSelf()
+ del self.bpDict[bp[0]]
+ myprint("PushBreakpoints(): successfully removed breakpoint \'%s:%i\' from both our local list and php debugger", (bp[0][0], bp[0][1]))
+
+ return
+ #
+ # Public callback functions end
+ ############################################################################
+
+
+ def newConnEventHandler(self):
+ #
+ # Ok, we've got a connection from the php debugger, and some initial
+ # frame data from it. Everything is ready and let's make some initial
+ # actions.
+ #
+ self.clearInternals()
+
+ try:
+ self.awaitAndHandleResponse(self.lsnrThr.getConnHeader())
+ except PHPDBGConnException:
+ self.currConnFinished()
+ return
+
+ self.PushBreakpoints(True)
+ self.ui.LoadPHPFramesList(self.stackList)
+
+ #
+ # This could be called when this object is constructed or when self is
+ # re-initialized after getting a new dbg module connection as a new
+ # session.
+ #
+ def clearInternals(self):
+ self.stackList = []
+ self.errStackList = []
+ self.stackFrameIndex = 0
+ self.isErrStack = False
+ self.errStr = ''
+ self.modList = []
+ self.stopOnError = True
+ self.lastCommand = None
+ self.evalRet = ''
+ self.modDict = {}
+ self.bpDict = {}
+ self.rawDataDict = {}
+ self.sessID = 0
+ self.sessType = 0
+ self.sessEnded = False
+ self.frameCounter = 1000
+ self.variableList = []
+ self.verMajor = 0
+ self.verMinor = 0
+ self.verDesc = None
+
+ def initLsnrThr(self):
+ self.actionEvent = threading.Event()
+ self.lsnrThr = PHPDBGLsnrThr(self, self.lsnrHost, self.lsnrPort, self.actionEvent, self.ui)
+
+ def awaitAndHandleResponse(self, header = None, blocking = False, disable = True, stopping = False):
+ if disable:
+ self.ui.DisableWhileDebuggerRunning()
+
+ while self.readResponse(header, blocking) != 0:
+ myprint("Waiting for response")
+
+ if stopping:
+ self.ui.DisableAfterStop()
+ else:
+ self.ui.EnableWhileDebuggerStopped()
+
+ def requestDBGVersion(self):
+ #TODO: necessary?
+ pass
+
+ def getSourceTree(self):
+ #TODO: necessary?
+ pass
+
+ def addDBGModName(self):
+ #TODO: necessary?
+ pass
+
+ def getNextFrameCounter(self):
+ self.frameCounter = self.frameCounter + 1
+ return self.frameCounter
+
+ def getVariables(self, stack):
+ self.variableList = []
+
+ reqPacket = PHPDBGPacket(DBGA_REQUEST)
+ reqFrame = PHPDBGFrame(FRAME_EVAL)
+
+ reqFrame.addInt(0)
+ reqFrame.addInt(stack.getFrameScopeId())
+ reqPacket.addFrame(reqFrame)
+ myprint("PHPDebuggerCallback::getVariables(): about to send eval request")
+
+ try:
+ reqPacket.sendPacket(self.lsnrThr)
+ self.awaitAndHandleResponse(disable = False)
+ except PHPDBGConnException:
+ self.currConnFinished()
+ return self.variableList
+
+ myprint("PHPDebuggerCallback::getVariables(): evalRet=%s", self.evalRet)
+ evalStr = PHPDBGEvalString(stack, self.evalRet)
+ if evalStr:
+ self.variableList = evalStr.getVars()
+ myprint("PHPDebuggerCallback::getVariables(): about to return")
+
+ return self.variableList
+
+ def evalBlock(self, stack, evalStr):
+ reqPacket = PHPDBGPacket(DBGA_REQUEST)
+ reqFrame1 = PHPDBGFrame(FRAME_EVAL)
+ reqFrame2 = PHPDBGFrame(FRAME_RAWDATA)
+
+ frameID = self.getNextFrameCounter()
+ reqFrame1.addInt(frameID)
+ reqFrame1.addInt(1)
+
+ reqFrame2.addInt(frameID)
+ reqFrame2.addInt(len(evalStr) + 1)
+ reqFrame2.addStr(evalString)
+
+ reqPacket.addFrame(reqFrame2)
+ reqPacket.addFrame(reqFrame1)
+
+ try:
+ reqPacket.sendPacket(self.lsnrThr)
+ self.awaitAndHandleResponse(disable = False)
+ except PHPDBGConnException:
+ self.currConnFinished()
+ return None
+
+ evalStr = PHPDBGEvalString(stack, self.evalRet)
+
+ return evalStr.getVars()
+
+ def getBPUnderHit(self):
+ for bp in self.bpDict.values():
+ if bp.isUnderHit():
+ return bp
+
+ return None
+
+ def getRawFrameData(self, frameNo):
+ if self.rawDataDict.has_key(frameNo):
+ #
+ # Once the frameData is consumed, remove it from rawDataDict.
+ #
+ return self.rawDataDict.pop(frameNo)
+ else:
+ #
+ # TODO: do we need to handle the case when the raw frame data hasn't
+ # been received before?
+ #
+ return None
+
+ def getModByNum(self, modNum):
+ if self.modDict.has_key(modNum):
+ return self.modDict[modNum]
+ else:
+ return None
+
+ def getModByFileName(self, fileName):
+ for mn, fn in self.modDict.iteritems():
+ if fn == fileName:
+ return mn
+
+ return 0
+
+ def setMod(self, modNum, fileName):
+ if modNum != 0 and fileName:
+ self.modDict[modNum] = fileName
+
+ return
+
+ def readResponse(self, headeri = None, blockingi = False):
+ inHeader = headeri
+ header = None
+ cmdReceived = 0
+ isFirstPacket = True
+ blocking = blockingi
+ self.isErrStack = False
+ self.rawDataDict.clear()
+
+ while True:
+ #
+ # If we have already received the first packet, we can't block any
+ # more.
+ #
+ if not isFirstPacket:
+ blocking = False
+
+ #
+ # If this is the first loop and we have a non-empty header passed in, use it. Otherwise,
+ # read in a new header. For subsequent loops, inHeader is None so we always read a new
+ # header from the wire.
+ #
+ if inHeader:
+ header = inHeader
+ inHeader = None
+ else:
+ header = ResponseHeader(self.lsnrThr, blocking)
+
+ if not header.isValid:
+ return 0
+
+ cmdReceived = header.command
+ frame = ResponsePacketFrame(self.lsnrThr, header.toRead, None, blocking)
+ if not frame.isValid:
+ return 0
+
+ isFirstPacket = False
+ isFirstStackFrame = True
+ while frame and frame.isValid:
+ frameName = frame.frameName
+ if frameName == FRAME_STACK:
+ if self.isErrStack:
+ self.errStackList = self.handleRespFrameStack(self.errStackList, frame, isFirstStackFrame)
+ else:
+ self.stackList = self.handleRespFrameStack(self.stackList, frame, isFirstStackFrame)
+
+ if isFirstStackFrame:
+ isFirstStackFrame = False
+ elif frameName == FRAME_SOURCE:
+ self.handleRespFrameSource(frame)
+ elif frameName == FRAME_SRC_TREE:
+ self.handleRespFrameSrcTree(frame)
+ elif frameName == FRAME_RAWDATA:
+ self.handleRespFrameRawdata(frame)
+ elif frameName == FRAME_ERROR:
+ self.handleRespFrameError(frame)
+ elif frameName == FRAME_EVAL:
+ self.handleRespFrameEval(frame)
+ elif frameName == FRAME_BPS:
+ self.handleRespFrameBps(frame)
+ elif frameName == FRAME_BPL:
+ self.handleRespFrameBpl(frame)
+ elif frameName == FRAME_VER:
+ self.handleRespFrameVer(frame)
+ elif frameName == FRAME_SID:
+ self.handleRespFrameSid(frame)
+ elif frameName == FRAME_SRCLINESINFO:
+ self.handleRespFrameSrclinesinfo(frame)
+ elif frameName == FRAME_SRCCTXINFO:
+ self.handleRespFrameSrcctxinfo(frame)
+ elif frameName == FRAME_LOG:
+ self.handleRespFrameLog(frame)
+ elif frameName == FRAME_PROF:
+ self.handleRespFrameProf(frame)
+ elif frameName == FRAME_PROF_C:
+ self.handleRespFrameProfC(frame)
+ elif frameName == FRAME_SET_OPT:
+ self.handleRespFrameSetOpt(frame)
+ else:
+ self.handleRespFrameUnknown(frame)
+ return 0
+
+ #
+ # After handling of this frame, force frame to point to the
+ # next one based on current frame's absolute size.
+ #
+ frame = frame.getNextFrame(True)
+
+ if cmdReceived == DBGC_REPLY:
+ self.handleRespCmdReply()
+ elif cmdReceived == DBGC_STARTUP:
+ self.handleRespCmdStartup()
+ elif cmdReceived == DBGC_END:
+ self.handleRespCmdEnd()
+ elif cmdReceived == DBGC_BREAKPOINT:
+ self.handleRespCmdBreakpoint()
+ cmdReceived = 0
+ elif cmdReceived == DBGC_STEPINTO_DONE:
+ self.handleRespCmdStepintoDone()
+ elif cmdReceived == DBGC_STEPOVER_DONE:
+ self.handleRespCmdStepoverDone()
+ elif cmdReceived == DBGC_STEPOUT_DONE:
+ self.handleRespCmdStepoutDone()
+ elif cmdReceived == DBGC_EMBEDDED_BREAK:
+ self.handleRespCmdEmbeddedBreak()
+ elif cmdReceived == DBGC_PAUSE:
+ self.handleRespCmdPause()
+ elif cmdReceived == DBGC_ERROR:
+ self.handleRespCmdError()
+ elif cmdReceived == DBGC_LOG:
+ self.handleRespCmdLog()
+ elif cmdReceived == DBGC_SID:
+ self.handleRespCmdSid()
+ else:
+ self.handleRespCmdUnknown()
+
+ return cmdReceived
+
+ def handleRespFrameStack(self, stackList, frame, isFirst):
+ if isFirst:
+ stackList = []
+ self.stackFrameIndex = 0
+
+ lineNo = frame.getNextInt()
+ modNo = frame.getNextInt()
+ scopeId = frame.getNextInt()
+ frameId = frame.getNextInt()
+ if modNo != 0:
+ newStackFrame = PHPStackFrame(self, self.getModByNum(modNo), lineNo, self.stackFrameIndex, scopeId, self.getRawFrameData(frameId), modNo)
+ stackList.append(newStackFrame)
+ self.stackFrameIndex = self.stackFrameIndex + 1
+
+ return stackList
+
+ def handleRespFrameSource(self, frame):
+ modNo = frame.getNextInt()
+ fromFilePos = frame.getNextInt()
+ error = frame.getNextInt()
+ fullSize = frame.getNextInt()
+ fileNameFrameId = frame.getNextInt()
+ textFrameId = frame.getNextInt()
+
+ fileName = self.getModByNum(modNo)
+ if not fileName:
+ self.setFileMod(modNo, fileNameFrameId)
+
+ #
+ # TODO: fullSize string and textFrameId are not handled here.
+ #
+ return
+
+ def handleRespFrameSrcTree(self, frame):
+ parentModNo = frame.getNextInt()
+ parentLineNo = frame.getNextInt()
+ modNo = frame.getNextInt()
+ fileNameFrameId = frame.getNextInt()
+
+ fileName = self.getModByNum(modNo)
+ if not fileName:
+ self.setFileMod(modNo, fileNameFrameId)
+
+ return
+
+ def handleRespFrameRawdata(self, frame):
+ frameNo = frame.getNextInt()
+ if frameNo > 0:
+ toRead = frame.getNextInt()
+ if toRead > 0:
+ str = frame.getNextString(toRead)
+ self.rawDataDict[frameNo] = str
+ myprint("handleRespFrameRawdata(): added \'%d\'=\'%s\' to rawDataDict.", (frameNo, str))
+
+ return
+
+ def handleRespFrameError(self, frame):
+ self.isErrStack = True
+
+ #
+ # Type of the error.
+ #
+ errInt0 = frame.getNextInt()
+ #
+ # ID of error message.
+ #
+ errInt1 = frame.getNextInt()
+
+ if errInt0 == E_ERROR:
+ errIdStr = "[Error]"
+ elif errInt0 == E_WARNING:
+ errIdStr = "[Warning]"
+ elif errInt0 == E_PARSE:
+ errIdStr = "[Parse Error]"
+ elif errInt0 == E_NOTICE:
+ errIdStr = "[Notice]"
+ elif errInt0 == E_CORE_ERROR:
+ errIdStr = "[Core Error]"
+ elif errInt0 == E_CORE_WARNING:
+ errIdStr = "[Core Warning]"
+ elif errInt0 == E_COMPILE_ERROR:
+ errIdStr = "[Compile Error]"
+ elif errInt0 == E_COMPILE_WARNING:
+ errIdStr = "[Compile Warning]"
+ elif errInt0 == E_USER_ERROR:
+ errIdStr = "[User Error]"
+ elif errInt0 == E_USER_WARNING:
+ errIdStr = "[User Warning]"
+ elif errInt0 == E_USER_NOTICE:
+ errIdStr = "[User Notice]"
+ else:
+ errIdStr = "[Unexpected Error]"
+
+ errMsg = self.getRawFrameData(errInt1)
+ if errMsg and len(errMsg) > 0:
+ self.errStr = errIdStr + ": " + errMsg + "\n"
+ else:
+ self.errStr = errIdStr + ": <Invalid Error Message>\n"
+
+ if not self.stopOnError:
+ if self.lastCommand == DBGA_CONTINUE:
+ self.Continue()
+ elif self.lastCommand == DBGA_STEPINTO:
+ self.SingleStep()
+ elif self.lastCommand == DBGA_STEPOUT:
+ self.Return()
+ elif self.lastCommand == DBGA_STEPOVER:
+ self.Next()
+
+ return
+
+ def handleRespFrameEval(self, frame):
+ evalInt0 = frame.getNextInt()
+ evalInt1 = frame.getNextInt()
+ evalInt2 = frame.getNextInt()
+ self.evalRet = self.getRawFrameData(evalInt1)
+ #TODO: is the following necessary?
+ evalStr = self.getRawFrameData(evalInt0)
+
+ return
+
+ def handleRespFrameBps(self, frame):
+ return
+
+ def handleRespFrameBpl(self, frame):
+ #
+ # Get this breakpoint.
+ #
+ dbgBp = []
+ for i in range(10):
+ dbgBp.append(frame.getNextInt())
+
+ if dbgBp[2] != 0:
+ #
+ # If filename is sent, get it from the rawDataDict.
+ #
+ fileName = self.getRawFrameData(dbgBp[2])
+ if not fileName:
+ return
+
+ #
+ # If this filename comes with a mod number, store this
+ # modNum/fileName into this session's modDict. Notice it might
+ # overwrite previous value.
+ #
+ if dbgBp[0] != 0:
+ self.setMod(dbgBp[0], fileName)
+ elif dbgBp[0] != 0:
+ #
+ # Use modNum to get the fileName.
+ #
+ fileName = self.getModByNum(dbgBp[0])
+ if not fileName:
+ return
+ else:
+ #
+ # Couldn't get the filename; nothing we can do with.
+ #
+ return
+
+ bpKey = self.createBpKey(fileName, dbgBp[1])
+ if not self.bpDict.has_key(bpKey):
+ #
+ # Not in our bp list? Anyway, create one for it.
+ #
+ ourBp = BreakPoint(self, fileName, dbgBp[1], dbgBp[0], dbgBp[3], dbgBp[4], dbgBp[5], dbgBp[6], dbgBp[7], dbgBp[8], dbgBp[9])
+ self.bpDict[bpKey] = ourBp
+ newlyCreated = True
+ else:
+ ourBp = self.bpDict[bpKey]
+ newlyCreated = False
+
+ #
+ # Update with the latest bp information.
+ #
+ if not newlyCreated:
+ ourBp.update(dbgBp)
+
+ return
+
+ def handleRespFrameVer(self, frame):
+ self.verMajor = frame.getNextInt()
+ self.verMinor = frame.getNextInt()
+ verFrameNo = frame.getNextInt()
+ self.verDesc = self.getRawFrameData(verFrameNo)
+ myprint("respFrameVer: verMajor=%s, verMinor=%s, verDesc=%s", (repr(self.verMajor), repr(self.verMinor), repr(self.verDesc)))
+
+ return
+
+ def handleRespFrameSid(self, frame):
+ self.sessID = frame.getNextInt()
+ self.sessType = frame.getNextInt()
+ myprint("respFrameSid: sessID=%s, sessType=%s", (self.sessID, self.sessType))
+
+ return
+
+ def handleRespFrameSrclinesinfo(self, frame):
+ return
+
+ def handleRespFrameSrcctxinfo(self, frame):
+ return
+
+ def handleRespFrameLog(self, frame):
+ #
+ # TODO:
+ # Now we don't do much here besides following the protocol to retrieve
+ # the data.
+ #
+ logId = frame.getNextInt()
+ logType = frame.getNextInt()
+ modNo = frame.getNextInt()
+ lineNo = frame.getNextInt()
+ fileNameFrameId = frame.getNextInt()
+ extInfo = frame.getNextInt()
+
+ fileName = self.getModByNum(modNo)
+ if not fileName:
+ self.setFileMod(modNo, fileNameFrameId)
+
+ return
+
+ def handleRespFrameProf(self, frame):
+ return
+
+ def handleRespFrameProfC(self, frame):
+ return
+
+ def handleRespFrameSetOpt(self, frame):
+ return
+
+ def handleRespCmdReply(self):
+ return
+
+ def handleRespCmdStartup(self):
+ return
+
+ def handleRespCmdEnd(self):
+ self.sessEnded = True
+ return
+
+ def handleRespCmdBreakpoint(self):
+ return
+
+ def handleRespCmdStepintoDone(self):
+ return
+
+ def handleRespCmdStepoverDone(self):
+ return
+
+ def handleRespCmdStepoutDone(self):
+ return
+
+ def handleRespCmdEmbeddedBreak(self):
+ return
+
+ def handleRespCmdPause(self):
+ return
+
+ def handleRespCmdError(self):
+ self.stackList = []
+
+ if len(self.errStackList) > 0:
+ self.errStr = self.errStr + "Stack Trace:\n"
+
+ while len(self.errStackList) > 0:
+ oneStack = self.errStackList.pop()
+ self.errStr = self.errStr + "%s\n" % oneStack.getLongDisplayStr()
+
+ self.ui.showErrorDialog(self.errStr, "PHP Error")
+ myprint("Got PHP Error:\n%s", self.errStr)
+
+ return
+
+ def handleRespCmdLog(self):
+ return
+
+ def handleRespCmdSid(self):
+ return
+
+ def setFileMod(self, modNo, fileNameFrameId):
+ if fileNameFrameId != 0:
+ fileName = self.getRawFrameData(fileNameFrameId)
+ if fileName and modNo != 0:
+ self.setMod(modNo, fileName)
+
+ return
+
+ def createBpKey(self, fileName, lineNo):
+ #
+ # This is to work around a bug in dbg module where it changes the path
+ # names that we pass to it to lower cases.
+ #
+ if sysutils.isWindows():
+ fileName = fileName.lower()
+
+ return (fileName, lineNo)
+
+ def setLsnrAction(self, actioni):
+ self.lsnrAction = actioni
+ return
+
+ def getLsnrAction(self):
+ return self.lsnrAction
+
+ def currConnFinished(self):
+ self.clearInternals()
+ self.setLsnrAction(PHPDebuggerCallback.ACTION_LISTEN)
+ self.actionEvent.set()
+ self.ui.DisableAfterStop()
+
+ def stopPhpDbg(self):
+ #
+ # TODO: should send a request to stop the current running PHP program.
+ # should handle network blocking issue correctly, otherwise, we
+ # might hang here.
+ #
+ reqPacket = PHPDBGPacket(DBGA_STOP)
+
+ try:
+ reqPacket.sendPacket(self.lsnrThr)
+ self.awaitAndHandleResponse(stopping = True)
+ except PHPDBGConnException:
+ pass
+
+ self.currConnFinished()
+ return
+
+ def stopLsnr(self):
+ if not self.lsnrThr:
+ return
+
+ #
+ # Then we try to stop our listener thread.
+ #
+ if self.lsnrThr.hasBeenConnected():
+ #
+ # If the listener thread has already accepted a connection from a
+ # php debug module/client, it is sleeping now and wait for this
+ # condition object to be set so that it can exit.
+ #
+ self.setLsnrAction(PHPDebuggerCallback.ACTION_STOP)
+ self.actionEvent.set()
+ else:
+ #
+ # If the listener thread has never been connected from a php debug
+ # module/client, it is still blocking on a accept() call. We
+ # connect to it here and send a special shutdown command asking it
+ # to exit.
+ #
+ shutdownMessage = IntToC4(DBG_SYNC) + IntToC4(DBGC_AG_SHUTDOWN_REQ) + IntToC4(0) + IntToC4(0)
+ tempSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ try:
+ tempSocket.connect((self.lsnrHost, self.lsnrPort))
+ tempSocket.sendall(shutdownMessage)
+ except:
+ myprint("shutdown connection/send message got exception!")
+
+ tempSocket.close()
+
+ self.lsnrThr.join()
+ self.lsnrThr = None
+
+
+class PHPDBGLsnrThr(threading.Thread):
+ def __init__(self, interfacei, hosti, porti, actionEventi, uii):
+ threading.Thread.__init__(self)
+ self.interface = interfacei
+ self.svrHost = hosti
+ self.svrPort = porti
+ self.actionEvent = actionEventi
+ self.svrSocket = None
+ self.clntConn = None
+ self.clntAddr = None
+ self.nonBlockingTimeout = 1
+ self.connHeader = None
+ self.ui = uii
+
+ def initSvrSocket(self):
+ self.svrSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self.svrSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ self.svrSocket.bind((self.svrHost, self.svrPort))
+
+ def waitForClntConn(self):
+ self.svrSocket.listen(5)
+ self.clntConn, self.clntAddr = self.svrSocket.accept()
+ self.clntConn.settimeout(self.nonBlockingTimeout)
+
+ def run(self):
+ #
+ # Initialize this server socket.
+ #
+ self.initSvrSocket()
+
+ while True:
+ #
+ # Block until we get a new connection from a php debug client or our
+ # debugger ui (with a special shutting down header/command).
+ #
+ self.waitForClntConn()
+
+ #
+ # Ok, a new connection comes in ... Read the header to see where it
+ # comes from.
+ #
+ self.connHeader = ResponseHeader(self)
+ if self.connHeader.command == DBGC_AG_SHUTDOWN_REQ:
+ #
+ # This is a special command coming from our UI asking this
+ # thread to exit. This only happens if after this thread has
+ # been waiting for new connections from PHP debug module, no one
+ # connects, and UI is ready to shutdown this thread.
+ #
+ self.shutdown()
+ break
+ else:
+ #
+ # Tell the main gui thread to handle this new connection.
+ #
+ wx.CallAfter(self.interface.newConnEventHandler)
+
+ #
+ # From now on, PHPDebuggerCallback will communicate with the php
+ # debug module using this thread's clntConn socket. This thread
+ # itself will keep sleeping until get notified to make some
+ # actions.
+ #
+ self.actionEvent.wait()
+ self.actionEvent.clear()
+
+ action = self.interface.getLsnrAction()
+ if action == PHPDebuggerCallback.ACTION_STOP:
+ self.shutdown()
+ break
+ elif action == PHPDebuggerCallback.ACTION_LISTEN:
+ if self.clntConn:
+ self.clntConn.shutdown(socket.SHUT_RDWR)
+ self.clntConn.close()
+ self.clntConn = None
+
+ continue
+ else:
+ continue
+
+ def shutdown(self):
+ #
+ # Cleanup and ready to exit.
+ #
+ self.clntConn.shutdown(socket.SHUT_RDWR)
+ self.clntConn.close()
+ self.svrSocket.close()
+
+ def recv(self, size, blocking = False):
+ if self.clntConn:
+ myprint("recv: trying to receive %d bytes of data ...", size)
+ if blocking:
+ self.clntConn.settimeout(None)
+ else:
+ self.clntConn.settimeout(self.nonBlockingTimeout)
+
+ try:
+ rData = self.clntConn.recv(size)
+ except socket.timeout:
+ myprint("recv: got timed out")
+ rData = None
+ except:
+ myprint("recv: got an unexpected exception: %s", sys.exc_info()[0])
+ raise PHPDBGConnException
+
+ return rData
+ else:
+ raise PHPDBGConnException
+
+ def sendall(self, message):
+ if self.clntConn:
+ try:
+ self.clntConn.sendall(message)
+ except:
+ myprint("sendall: got an unexpected exception: %s", sys.exc_info()[0])
+ raise PHPDBGConnException
+ else:
+ raise PHPDBGConnException
+
+ def hasBeenConnected(self):
+ return self.clntConn != None
+
+ def getConnHeader(self):
+ return self.connHeader
+
+
+class PHPValue(object):
+ PEV_NAMES = ("undefined", "long", "double", "string", "array", "object", "boolean", "resource", "reference", "soft reference", "null")
+ PEVT_UNKNOWN = 0
+ PEVT_LONG = 1
+ PEVT_DOUBLE = 2
+ PEVT_STRING = 3
+ PEVT_ARRAY = 4
+ PEVT_OBJECT = 5
+ PEVT_BOOLEAN = 6
+ PEVT_RESOURCE = 7
+ PEVT_REF = 8
+ PEVT_SOFTREF = 9
+ PEVT_NULL = 10
+
+ NULL_VALUE_STR = "NULL"
+ TRUE_VALUE_STR = "True"
+ FALSE_VALUE_STR = "False"
+ OBJECT_VALUE_STR = "<%s> object"
+ STRING_VALUE_STR = "\"%s\""
+ REFERENCE_VALUE_STR = "<reference><%s>"
+ RESOURCE_VALUE_STR = "<%s><%s>"
+
+ def __init__(self, frame, type, valueList):
+ self.fStackFrame = frame
+ self.fValueType = type
+
+ if type == self.PEVT_OBJECT:
+ self.fValueString = self.OBJECT_VALUE_STR % valueList[0]
+ self.fVariables = valueList[1:]
+ elif type == self.PEVT_ARRAY:
+ self.fValueString = ''
+ self.fVariables = valueList
+ else:
+ self.fVariables = []
+ if type == self.PEVT_STRING:
+ self.fValueString = self.STRING_VALUE_STR % valueList[0]
+ elif type == self.PEVT_NULL:
+ self.fValueString = self.NULL_VALUE_STR
+ elif type == self.PEVT_BOOLEAN:
+ if valueList[0] == "0":
+ self.fValueString = self.FALSE_VALUE_STR
+ else:
+ self.fValueString = self.TRUE_VALUE_STR
+ elif type == self.PEVT_REF or type == self.PEVT_SOFTREF:
+ self.fValueString = self.REFERENCE_VALUE_STR % valueList[0]
+ elif type == self.PEVT_RESOURCE:
+ self.fValueString = self.RESOURCE_VALUE_STR % (valueList[0], valueList[1])
+ else:
+ self.fValueString = valueList[0]
+
+ def addVariable(self, item):
+ if item != None:
+ self.fVariables.append(item)
+
+ return self.fVariables
+
+ def setParent(self, parent):
+ if self.fVariables != None and len(self.fVariables) > 0:
+ for item in self.fVariables:
+ item.setParent(parent)
+
+ def getReferenceType(self):
+ return self.fValueType
+
+ def getReferenceTypeName(self):
+ return self.PEV_NAMES[self.fValueType]
+
+ def setReferenceType(self, type):
+ self.fValueType = type
+
+ def getValueString(self):
+ return self.fValueString
+
+ def getChildrenVariables(self):
+ return self.fVariables
+
+ def hasVariables(self):
+ return len(self.fVariables) > 0
+
+ def childrenIsSortable(self):
+ #
+ # TODO: if self.fValueType != self.PEVT_ARRAY:
+ #
+ return True
+
+
+class PHPVariable(object):
+ def __init__(self, frame, parent, valueType, name, valueList):
+ self.fStackFrame = frame
+ self.fName = None
+ self.fLongName = None
+ self.fPureName = None
+ self.fValue = PHPValue(frame, valueType, valueList)
+ self.fParent = parent
+ self.setName(name)
+ self.setChildrensParent(valueList)
+
+ def setName(self, name):
+ self.fPureName = name
+
+ type = self.getReferenceType()
+ if type == PHPValue.PEVT_ARRAY:
+ numItems = len(self.getChildrenVariables())
+ self.fName = name + "[" + str(numItems) + "]"
+ else:
+ self.fName = name
+
+ if not self.fParent or self.fParent.getName() == None:
+ self.fLongName = name
+ else:
+ self.setLongName()
+
+ return
+
+ def setLongName(self):
+ parentType = self.fParent.getReferenceType()
+ if parentType == PHPValue.PEVT_ARRAY:
+ self.fLongName = self.fParent.getLongName() + "['" + self.fPureName + "']"
+ elif parentType == PHPValue.PEVT_OBJECT:
+ self.fLongName = self.fParent.getLongName() + "." + self.fName
+ else:
+ self.fLongName = self.fName
+
+ return
+
+ def getValue(self):
+ return self.fValue
+
+ def getValueString(self):
+ return self.fValue.getValueString()
+
+ def getChildrenVariables(self):
+ return self.fValue.getChildrenVariables()
+
+ def getName(self):
+ return self.fName
+
+ def getParent(self):
+ return self.fParent
+
+ def setParent(self, parent):
+ self.fParent = parent
+ self.setLongName()
+ return
+
+ def setChildrensParent(self, childrenList):
+ if self.fValue.hasVariables():
+ for child in self.fValue.getChildrenVariables():
+ child.setParent(self)
+
+ def getLongName(self):
+ return self.fLongName
+
+ def getReferenceTypeName(self):
+ return self.fValue.getReferenceTypeName()
+
+ def getReferenceType(self):
+ return self.fValue.getReferenceType()
+
+ def setReferenceType(self, type):
+ tp = self.getValue.setReferenceType(type)
+ return tp
+
+ def setValue(self, expression):
+ if self.fValue.getReferenceType() == PHPValue.PEVT_STRING:
+ evalString = self.fLongName + "=\"" + expression + "\""
+ else:
+ evalString = self.fLongName + "=" + expression
+
+ vars = self.fStackFrame.getPHPDBGInterface().evalBlock(self.fStackFrame, evalString)
+ self.fValue = vars[0].fValue
+
+ def toString(self):
+ rtype = self.getReferenceType()
+ if rtype == PHPValue.PEVT_ARRAY:
+ elements = len(self.fValue.getChildrenVariables())
+ if elements == 0:
+ tmpStr = self.getName() + " [no elements]"
+ elif elements == 1:
+ tmpStr = self.getName() + " [1 element]"
+ else:
+ tmpStr = self.getName() + " [" + str(elements) + " elements]"
+ elif rtype == PHPValue.PEVT_OBJECT:
+ tmpStr = self.getName() + " [ class: " + self.fValue.getValueString() + "]"
+ elif rtype == PHPValue.PEVT_STRING:
+ tmpStr = self.getName() + " = \"" + self.fValue.getValueString() + "\""
+ else:
+ tmpStr = self.getName() + " = " + self.fValue.getValueString()
+
+ return tmpStr
+
+ def hasChildren(self):
+ return self.fValue.hasVariables()
+
+ def childrenIsSortable(self):
+ return self.fValue.childrenIsSortable()
+
+
+class PHPStackFrame(object):
+ def __init__(self, interface, file, line, frameIndex, scopeId, desc, modNum):
+ self.interface = interface
+ self.fileName = file
+ self.lineNo = line
+ self.frameIndex = frameIndex
+ self.scopeId = scopeId
+ self.desc = desc
+ self.modNum = modNum
+ self.variables = []
+ self.shortFileName = None
+ self.shortDesc = None
+ self.displayStr = None
+ self.longDisplayStr = None
+
+ self._getFileNamesAndShortDesc()
+
+ myprint("PHPStackFrame::__init__(): new PHPStackFrame: file=%s, lineNo=%s, frameIndex=%s, scopeId=%s, desc=%s, modNum=%s, shortFileName=%s, shortDesc=%s", (repr(file), repr(line), repr(frameIndex), repr(scopeId), repr(desc), repr(modNum), repr(self.shortFileName), repr(self.shortDesc)))
+
+ def _getFileNamesAndShortDesc(self):
+ tmp = []
+ if self.desc:
+ tmp = self.desc.split("::")
+
+ if self.fileName:
+ self.shortFileName = os.path.basename(self.fileName)
+ if len(tmp) == 2:
+ self.shortDesc = tmp[1]
+ elif len(tmp) == 1:
+ self.shortDesc = tmp[0]
+ else:
+ self.shortDesc = None
+
+ return
+
+ #
+ # The fileName is None, we will try our best efforts to get it.
+ #
+ if len(tmp) == 2:
+ #
+ # We retrieved long finename from the description. If we haven't
+ # stored the file mod before, set this one as the new one.
+ # Otherwise, we prefer to keep the stored one.
+ #
+ if self.modNum != 0:
+ storedFileName = self.interface.getModByNum(self.modNum)
+ if not storedFileName:
+ self.interface.setMod(self.modNum, tmp[0])
+
+ self.fileName = tmp[0]
+ self.shortFileName = os.path.basename(tmp[0])
+ self.shortDesc = tmp[1]
+ elif len(tmp) == 1:
+ self.fileName = None
+ self.shortFileName = None
+ self.shortDesc = tmp[0]
+ else:
+ self.shortFileName = None
+ self.shortDesc = None
+ myprint("PHPStackFrame::_getFileNamesAndShortDesc(): something wrong with desc: %s?", self.desc)
+
+ return
+
+ def getShortFileName(self):
+ return self.shortFileName
+
+ def setShortFileName(self, shortFileName):
+ self.shortFileName = shortFileName
+
+ def getShortDesc(self):
+ return self.shortDesc
+
+ def getLineNo(self):
+ return self.lineNo
+
+ def getInterface(self):
+ return self.interface
+
+ def getVariables(self):
+ if len(self.variables) == 0:
+ self.variables = self.interface.getVariables(self)
+
+ return self.variables
+
+ def findVariables(self, s):
+ if self.hasVariables():
+ name = "$" + s
+ for var in self.variables:
+ if var.getName() == name:
+ return var
+
+ return None
+
+ def hasVariables(self):
+ if len(self.variables) == 0:
+ return False
+ else:
+ return True
+
+ def getName(self):
+ if self.getDesc():
+ return self.getDesc() + " [line: " + str(self.getLineNo()) + "]"
+ else:
+ return self.getFileName() + " [line: " + str(self.getLineNo()) + "]"
+
+ def getFileName(self):
+ return self.fileName
+
+ def setFileName(self, fileName):
+ self.fileName = fileName
+
+ def setDesc(self, desc):
+ self.desc = desc
+
+ def getDesc(self):
+ return self.desc
+
+ def getFrameScopeId(self):
+ return self.scopeId
+
+ def getFrameIndex(self):
+ return self.frameIndex
+
+ def getDisplayStr(self, stackList = None):
+ if self.displayStr:
+ return self.displayStr
+
+ if not self.shortFileName:
+ if stackList:
+ i = stackList.index(self)
+ for j in range(i + 1, len(stackList)):
+ self.shortFileName = stackList[j].getShortFileName()
+ if self.shortFileName:
+ self.fileName = stackList[j].getFileName()
+
+ if self.shortFileName:
+ if self.shortDesc:
+ self.displayStr = "<%s> at %s:%d" % (self.shortDesc, self.shortFileName, self.lineNo)
+ else:
+ self.displayStr = "%s:%d" % (self.shortFileName, self.lineNo)
+ else:
+ if self.shortDesc:
+ self.displayStr = "<%s>" % self.shortDesc
+ else:
+ self.displayStr = "<internal stack error>"
+
+ return self.displayStr
+
+ def getLongDisplayStr(self):
+ if self.longDisplayStr:
+ return self.longDisplayStr
+
+ if self.fileName:
+ if self.shortDesc:
+ self.longDisplayStr = "<%s> at %s:%d" % (self.shortDesc, self.fileName, self.lineNo)
+ else:
+ self.longDisplayStr = "%s:%d" % (self.fileName, self.lineNo)
+ else:
+ if self.shortDesc:
+ self.longDisplayStr = "<%s>" % self.shortDesc
+ else:
+ self.longDisplayStr = "<internal stack error>"
+
+ return self.longDisplayStr
+
+class BreakPoint(object):
+ def __init__(self, interface, fileName, lineNo, modNum = 0, state = BPS_ENABLED + BPS_UNRESOLVED, isTemp = 0, hitCount = 0, skipHits = 0, condition = 0, bpId = 0, isUnderHit = 0):
+ self.interface = interface
+ self.fileName = fileName
+ self.lineNo = lineNo
+ self.bpID = bpId
+ self.state = state
+ self.isTemp = isTemp
+ self.hitCount = hitCount
+ self.skipHits = skipHits
+ self.condition = condition
+ self.isUnderHit = 0
+ if modNum == 0:
+ self.modNum = self.interface.getModByFileName(fileName)
+ else:
+ self.modNum = modNum
+
+ if self.modNum:
+ self.fCounterOrZero = 0
+ else:
+ self.fCounterOrZero = interface.getNextFrameCounter()
+
+ def sendSelf(self):
+ reqPacket = PHPDBGPacket(DBGA_REQUEST)
+ reqFrame1 = PHPDBGFrame(FRAME_BPS)
+
+ if self.modNum:
+ reqFrame1.addInt(self.modNum)
+ else:
+ #
+ # 0 in modNum to tell to use fileName instead.
+ #
+ reqFrame1.addInt(0)
+
+ reqFrame1.addInt(self.lineNo) # lineNo
+ reqFrame1.addInt(self.fCounterOrZero) # fileName frameCounter or 0
+ reqFrame1.addInt(self.state) # state
+ reqFrame1.addInt(self.isTemp) # isTemp
+ reqFrame1.addInt(self.hitCount) # hitCount
+ reqFrame1.addInt(self.skipHits) # skipHits
+ reqFrame1.addInt(self.condition) # condition
+ reqFrame1.addInt(self.bpID) # breakpoint sequence id
+ reqFrame1.addInt(self.isUnderHit) # isUnderHit
+
+ if not self.modNum:
+ reqFrame2 = PHPDBGFrame(FRAME_RAWDATA)
+ reqFrame2.addInt(self.fCounterOrZero)
+ reqFrame2.addInt(len(self.fileName) + 1)
+ reqFrame2.addStr(self.fileName)
+ reqPacket.addFrame(reqFrame2)
+
+ reqPacket.addFrame(reqFrame1)
+
+ try:
+ reqPacket.sendPacket(self.interface.lsnrThr)
+ self.interface.awaitAndHandleResponse()
+ except PHPDBGConnException:
+ self.interface.currConnFinished()
+
+ return
+
+ def addSelf(self):
+ self.sendSelf()
+
+ def removeSelf(self):
+ self.state = BPS_DISABLED
+ self.sendSelf()
+
+ def isUnderHit(self):
+ return self.isUnderHit == 1
+
+ def update(self, dbgBp):
+ self.modNum = dbgBp[0]
+ self.state = dbgBp[3]
+ self.isTemp = dbgBp[4]
+ self.hitCount = dbgBp[5]
+ self.skipHits = dbgBp[6]
+ self.condition = dbgBp[7]
+ self.bpID = dbgBp[8]
+ self.isUnderHit = dbgBp[9]
+
+
+class PHPDBGEvalString(object):
+ def __init__(self, stackFrame, dataStr):
+ self.stackFrame = stackFrame
+ self.dataStr = dataStr
+
+ #
+ # Get a list of variables under self.stackFrame.
+ #
+ def getVars(self):
+ return self.parseAVariable(isRealVar = False)
+
+ #
+ # if isRealVar:
+ # returnList[0] = The Variable
+ # else:
+ # returnList = list of variables.
+ #
+ def parseAVariable(self, isRealVar = True):
+ returnList = []
+
+ #
+ # Get the variable name first. Notice we ignore this entity's data
+ # type here.
+ #
+ if isRealVar:
+ nameEntity = self.parseAnEntity()
+ if not nameEntity or len(nameEntity) != 2 or type(nameEntity[1]) != str:
+ myprint("PHPDBGEvalStr::parseAVariable() got a wrong name entity")
+ return returnList
+ else:
+ varName = nameEntity[1]
+
+ #
+ # Get the variable's value.
+ #
+ valueEntity = self.parseAnEntity()
+ if not valueEntity or len(valueEntity) < 1:
+ myprint("PHPDBGEvalStr::parseAVariable(): couldn't get a variable's value entity.")
+ return returnList
+
+ #
+ # This variable's data type.
+ #
+ varType = valueEntity[0]
+
+ if isRealVar:
+ #
+ # If this is a real variable, return a list which contains only
+ # this variable item.
+ #
+ #valueEntity = valueEntity[1:]
+ variable = PHPVariable(self.stackFrame, None, varType, varName, valueEntity[1:])
+ #myprint("xxxxCreated variable varName=%s, valueEntity=%s", (repr(varName), repr(valueEntity[1])))
+ myprint("xxxxCreated variable: %s", repr(variable.toString()))
+ returnList.append(variable)
+ else:
+ #
+ # If this is a root variable container, returns a list of
+ # variables under the root. Do a sanity check here.
+ #
+ if valueEntity[0] != PHPValue.PEVT_ARRAY:
+ myprint("PHPDBGEvalStr::parseAVariable(): failed to parse the root variable container.")
+ else:
+ returnList = valueEntity[1:]
+
+ return returnList
+
+ #
+ # An entity could be a variable's name or its value.
+ #
+ # returnList[0] = variable data type
+ # returnList[1:] = the real list
+ #
+ def parseAnEntity(self):
+ if not self.dataStr or len(self.dataStr) < 2 or (self.dataStr[1] != ':' and self.dataStr[1] != ';'):
+ myprint("PHPDBGEvalStr::parseAnEntity(): failed to parse %s.", repr(self.dataStr))
+ return None
+
+ returnList = []
+ typeChar = self.dataStr[0]
+ self.dataStr = self.dataStr[2:]
+ if typeChar == 'i':
+ returnList.append(PHPValue.PEVT_LONG)
+ self.parseInt(returnList)
+ elif typeChar == 'a':
+ returnList.append(PHPValue.PEVT_ARRAY)
+ self.parseArray(returnList)
+ elif typeChar == 's':
+ returnList.append(PHPValue.PEVT_STRING)
+ self.parseString(returnList)
+ elif typeChar == 'O':
+ returnList.append(PHPValue.PEVT_OBJECT)
+ self.parseObject(returnList)
+ elif typeChar == 'r':
+ returnList.append(PHPValue.PEVT_SOFTREF)
+ self.parseReference(returnList, isSoftRef = True)
+ elif typeChar == 'R':
+ returnList.append(PHPValue.PEVT_REF)
+ self.parseReference(returnList, isSoftRef = False)
+ elif typeChar == 'b':
+ returnList.append(PHPValue.PEVT_BOOLEAN)
+ self.parseBoolean(returnList)
+ elif typeChar == 'd':
+ returnList.append(PHPValue.PEVT_DOUBLE)
+ self.parseDouble(returnList)
+ elif typeChar == 'z':
+ returnList.append(PHPValue.PEVT_RESOURCE)
+ self.parseResource(returnList)
+ elif typeChar == 'N':
+ returnList.append(PHPValue.PEVT_NULL)
+ self.parseNull(returnList)
+ else:
+ myprint("PHPDBGEvalStr::parseAnEntity(): unknown data type: %s", typeChar)
+
+ return returnList
+
+ def parseInt(self, returnList):
+ myprint("enter parseInt().")
+ returnList.append(self.getAnIntStr(';'))
+
+ return
+
+ def parseArray(self, returnList):
+ myprint("enter parseArray().")
+ #
+ # The shortest array is 'a:0:{}'.
+ #
+ if len(self.dataStr) < 4:
+ myprint("PHPDBGEvalStr::parseArray(): failed (1) to parse an array: %s.", repr(self.dataStr))
+ return
+
+ expectedNumItems = self.getAnInt(':')
+ if len(self.dataStr) < 2 or self.dataStr[0] != '{':
+ myprint("PHPDBGEvalStr::parseArray(): failed (3) to parse an array: %s.", repr(self.dataStr))
+ return
+
+ self.dataStr = self.dataStr[1:]
+ varList = []
+ while self.dataStr and len(self.dataStr) > 0 and self.dataStr[0] != '}':
+ tmpList = self.parseAVariable()
+ if not tmpList or len(tmpList) != 1 or not tmpList[0]:
+ myprint("PHPDBGEvalStr::parseArray(): failed (4) to parse an array. dataStr=%s.", repr(self.dataStr))
+ break
+ else:
+ varList.append(tmpList[0])
+
+ if expectedNumItems != len(varList):
+ myprint("PHPDBGEvalStr::parseArray(): failed (5) expected no. of items=%d, but got %d", (expectedNumItems, len(varList)))
+
+ #
+ # An array should end with a '}'.
+ #
+ if self.dataStr and len(self.dataStr) > 0 and self.dataStr[0] == '}':
+ self.dataStr = self.dataStr[1:]
+ returnList.extend(varList)
+ else:
+ myprint("PHPDBGEvalStr::parseArray(): failed (6) to parse an array. dataStr=%s.", repr(self.dataStr))
+
+ myprint("parseArray() ends.")
+ return
+
+ def parseString(self, returnList, endChar = ';'):
+ myprint("enter parseString().")
+ #
+ # The shortest string is 's:<str_len>:"<str>"<endChar>'.
+ #
+ if len(self.dataStr) < 5:
+ myprint("PHPDBGEvalStr::parseString(): failed (1) to parse a string. dataStr=%s.", repr(self.dataStr))
+ return
+
+ expectedStrLen = self.getAnInt(':')
+ if len(self.dataStr) < expectedStrLen + 3 or self.dataStr[0] != '"':
+ myprint("PHPDBGEvalStr::parseString(): failed (3) to parse a string. dataStr=%s.", repr(self.dataStr))
+ return
+
+ strValue = self.dataStr[1:expectedStrLen + 1]
+ if self.dataStr[expectedStrLen + 1:expectedStrLen + 3] != '"' + endChar:
+ myprint("PHPDBGEvalStr::parseString(): failed (4) to parse a string. dataStr=%s.", repr(self.dataStr))
+ return
+
+ #
+ # Skip the starting double quote, real string, ending double quote, and ending semicolon.
+ #
+ self.dataStr = self.dataStr[expectedStrLen + 3:]
+ returnList.append(strValue)
+
+ return
+
+ def parseObject(self, returnList):
+ #
+ # A simple sanity check. The shortest object is:
+ # 'O:<class_name_len>:"<class_name>":<num_of_items>:{<list_of_items>}'
+ #
+ if not self.dataStr or len(self.dataStr) < 10:
+ myprint("PHPDBGEvalStr::parseObject(): failed (1) to parse an object: %s.", repr(self.dataStr))
+
+ #
+ # Get the class name in classNameList[0].
+ #
+ classNameList = []
+ self.parseString(classNameList, ':')
+
+ expectedNumItems = self.getAnInt(':')
+ if len(self.dataStr) < 2 or self.dataStr[0] != '{':
+ myprint("PHPDBGEvalStr::parseObject(): failed (2) to parse an object: %s.", repr(self.dataStr))
+ return
+
+ self.dataStr = self.dataStr[1:]
+ varList = []
+ while self.dataStr and len(self.dataStr) > 0 and self.dataStr[0] != '}':
+ tmpList = self.parseAVariable()
+ if not tmpList or len(tmpList) != 1 or not tmpList[0]:
+ myprint("PHPDBGEvalStr::parseObject(): failed (3) to parse an object. dataStr=%s.", repr(self.dataStr))
+ break
+ else:
+ varList.append(tmpList[0])
+
+ if expectedNumItems != len(varList):
+ myprint("PHPDBGEvalStr::parseObject(): failed (4) expected no. of items=%d, but got %d", (expectedNumItems, len(varList)))
+
+ #
+ # An object should end with a '}'.
+ #
+ if self.dataStr and len(self.dataStr) > 0 and self.dataStr[0] == '}':
+ self.dataStr = self.dataStr[1:]
+ returnList.append(classNameList[0])
+ returnList.extend(varList)
+ else:
+ myprint("PHPDBGEvalStr::parseObject(): failed (6) to parse an object. dataStr=%s.", repr(self.dataStr))
+
+ myprint("parseObject() ends.")
+ return
+
+ def parseReference(self, returnList, isSoftRef):
+ myprint("enter parseReference().")
+ intStr = self.getAnIntStr(';')
+ if intStr:
+ returnList.append(intStr)
+
+ return
+
+ def parseBoolean(self, returnList):
+ tmpBooleanStr = self.getAnIntStr(';')
+ returnList.append(tmpBooleanStr)
+
+ return
+
+ def parseDouble(self, returnList):
+ tmpStr = self.getAStrTillEndChar(';')
+ if tmpStr:
+ returnList.append(tmpStr)
+
+ return
+
+ def parseResource(self, returnList):
+ tmpList = []
+ self.parseString(tmpList, ':')
+
+ if len(tmpList) == 1:
+ returnList.extend(tmpList)
+ else:
+ return
+
+ resourceId = self.getAnIntStr(';')
+ if resourceId:
+ returnList.append(resourceId)
+
+ return
+
+ def parseNull(self, returnList):
+ return
+
+ def getAStrTillEndChar(self, endChar):
+ if len(self.dataStr) < 1:
+ myprint("PHPDBGEvalStr::getAStrTillEndChar(): no more data string to work with.")
+ return
+
+ i = self.findNextChar(self.dataStr, endChar)
+ if i == -1:
+ myprint("PHPDBGEvalStr::getAStrTillEndChar(): no double/float string supplied.")
+ return
+
+ tmpStr = self.dataStr[:i]
+ self.dataStr = self.dataStr[i + 1:]
+
+ if self.isFloat(tmpStr):
+ return tmpStr
+ else:
+ myprint("PHPDBGEvalStr::getAStrTillEndChar(): parsing error. tried to get an float number, but get %s.", tmpStr)
+ return None
+
+ def getAnInt(self, endChar):
+ tmpStr = self.getAnIntStr(endChar)
+ if tmpStr:
+ return int(tmpStr)
+ else:
+ return 0
+
+ def getAnIntStr(self, endChar):
+ if len(self.dataStr) == 0:
+ myprint("PHPDBGEvalStr::getAnIntStr(): no more data string to work with.")
+ return
+
+ i = self.findNextChar(self.dataStr, endChar)
+ if i == -1:
+ tmpStr = self.dataStr
+ self.dataStr = ''
+ else:
+ tmpStr = self.dataStr[:i]
+ self.dataStr = self.dataStr[i + 1:]
+
+ if self.isInt(tmpStr):
+ return tmpStr
+ else:
+ myprint("PHPDBGEvalStr::getAnIntStr(): parsing error. tried to get an integer, but get %s.", tmpStr)
+ return None
+
+ def isInt(self, aStr):
+ try:
+ int(aStr)
+ except ValueError:
+ return False
+
+ return True
+
+ def isFloat(self, aStr):
+ try:
+ float(aStr)
+ except ValueError:
+ return False
+
+ return True
+
+ def findNextChar(self, aStr, aChar):
+ try:
+ index = aStr.index(aChar)
+ except ValueError:
+ index = -1
+
+ return index