aca310e5cc
New Features: In Tab-View mode, Ctrl-number will take the user to the numbered tab view. Modified files now show an '*' astrisk in the view title. Debugger framework can now support PHP debugging. Not important for python development, but at least that means the debugger framework is more generalized. git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@38852 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
2106 lines
70 KiB
Python
2106 lines
70 KiB
Python
#---------------------------------------------------------------------------
|
|
# 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
|