############################################################################ # # Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). # Contact: http://www.qt-project.org/legal # # This file is part of Qt Creator. # # Commercial License Usage # Licensees holding valid commercial Qt licenses may use this file in # accordance with the commercial license agreement provided with the # Software or, alternatively, in accordance with the terms contained in # a written agreement between you and Digia. For licensing terms and # conditions see http://qt.digia.com/licensing. For further information # use the contact form at http://qt.digia.com/contact-us. # # GNU Lesser General Public License Usage # Alternatively, this file may be used under the terms of the GNU Lesser # General Public License version 2.1 as published by the Free Software # Foundation and appearing in the file LICENSE.LGPL included in the # packaging of this file. Please review the following information to # ensure the GNU Lesser General Public License version 2.1 requirements # will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. # # In addition, as a special exception, Digia gives you certain additional # rights. These rights are described in the Digia Qt LGPL Exception # version 1.1, included in the file LGPL_EXCEPTION.txt in this package. # ############################################################################ import os import sys import base64 if sys.version_info[0] >= 3: xrange = range toInteger = int else: toInteger = long verbosity = 0 verbosity = 1 qqStringCutOff = 10000 # This is a cache mapping from 'type name' to 'display alternatives'. qqFormats = {} # This is a cache of all known dumpers. qqDumpers = {} # This is a cache of all dumpers that support writing. qqEditable = {} # This is an approximation of the Qt Version found qqVersion = None # This keeps canonical forms of the typenames, without array indices etc. qqStripForFormat = {} def stripForFormat(typeName): global qqStripForFormat if typeName in qqStripForFormat: return qqStripForFormat[typeName] stripped = "" inArray = 0 for c in stripClassTag(typeName): if c == '<': break if c == ' ': continue if c == '[': inArray += 1 elif c == ']': inArray -= 1 if inArray and ord(c) >= 48 and ord(c) <= 57: continue stripped += c qqStripForFormat[typeName] = stripped return stripped def hasPlot(): fileName = "/usr/bin/gnuplot" return os.path.isfile(fileName) and os.access(fileName, os.X_OK) try: import subprocess def arrayForms(): if hasPlot(): return "Normal,Plot" return "Normal" except: def arrayForms(): return "Normal" def bytesToString(b): if sys.version[0] == 2: return b return b.decode("utf8") def stringToBytes(s): if sys.version[0] == 2: return s return s.encode("utf8") # Base 16 decoding operating on string->string def b16decode(s): return bytesToString(base64.b16decode(stringToBytes(s), True)) # Base 16 decoding operating on string->string def b16encode(s): return bytesToString(base64.b16encode(stringToBytes(s))) # Base 64 decoding operating on string->string def b64decode(s): return bytesToString(base64.b64decode(stringToBytes(s))) # Base 64 decoding operating on string->string def b64encode(s): return bytesToString(base64.b64encode(stringToBytes(s))) # # Gnuplot based display for array-like structures. # gnuplotPipe = {} gnuplotPid = {} def warn(message): print("XXX: %s\n" % message.encode("latin1")) def showException(msg, exType, exValue, exTraceback): warn("**** CAUGHT EXCEPTION: %s ****" % msg) try: import traceback for line in traceback.format_exception(exType, exValue, exTraceback): warn("%s" % line) except: pass def stripClassTag(typeName): if typeName.startswith("class "): return typeName[6:] if typeName.startswith("struct "): return typeName[7:] if typeName.startswith("const "): return typeName[6:] if typeName.startswith("volatile "): return typeName[9:] return typeName class Children: def __init__(self, d, numChild = 1, childType = None, childNumChild = None, maxNumChild = None, addrBase = None, addrStep = None): self.d = d self.numChild = numChild self.childNumChild = childNumChild self.maxNumChild = maxNumChild self.addrBase = addrBase self.addrStep = addrStep self.printsAddress = True if childType is None: self.childType = None else: self.childType = stripClassTag(str(childType)) self.d.put('childtype="%s",' % self.childType) if childNumChild is None: pass #if self.d.isSimpleType(childType): # self.d.put('childnumchild="0",') # self.childNumChild = 0 #elif childType.code == PointerCode: # self.d.put('childnumchild="1",') # self.childNumChild = 1 else: self.d.put('childnumchild="%s",' % childNumChild) self.childNumChild = childNumChild try: if not addrBase is None and not addrStep is None: self.d.put('addrbase="0x%x",' % long(addrBase)) self.d.put('addrstep="0x%x",' % long(addrStep)) self.printsAddress = False except: warn("ADDRBASE: %s" % addrBase) #warn("CHILDREN: %s %s %s" % (numChild, childType, childNumChild)) def __enter__(self): self.savedChildType = self.d.currentChildType self.savedChildNumChild = self.d.currentChildNumChild self.savedNumChild = self.d.currentNumChild self.savedMaxNumChild = self.d.currentMaxNumChild self.savedPrintsAddress = self.d.currentPrintsAddress self.d.currentChildType = self.childType self.d.currentChildNumChild = self.childNumChild self.d.currentNumChild = self.numChild self.d.currentMaxNumChild = self.maxNumChild self.d.currentPrintsAddress = self.printsAddress self.d.put("children=[") def __exit__(self, exType, exValue, exTraceBack): if not exType is None: if self.d.passExceptions: showException("CHILDREN", exType, exValue, exTraceBack) self.d.putNumChild(0) self.d.putValue("") if not self.d.currentMaxNumChild is None: if self.d.currentMaxNumChild < self.d.currentNumChild: self.d.put('{name="",value="",type="",numchild="0"},') self.d.currentChildType = self.savedChildType self.d.currentChildNumChild = self.savedChildNumChild self.d.currentNumChild = self.savedNumChild self.d.currentMaxNumChild = self.savedMaxNumChild self.d.currentPrintsAddress = self.savedPrintsAddress self.d.put('],') return True class SubItem: def __init__(self, d, component): self.d = d self.name = component self.iname = None def __enter__(self): self.d.enterSubItem(self) def __exit__(self, exType, exValue, exTraceBack): return self.d.exitSubItem(self, exType, exValue, exTraceBack) class NoAddress: def __init__(self, d): self.d = d def __enter__(self): self.savedPrintsAddress = self.d.currentPrintsAddress self.d.currentPrintsAddress = False def __exit__(self, exType, exValue, exTraceBack): self.d.currentPrintsAddress = self.savedPrintsAddress class TopLevelItem(SubItem): def __init__(self, d, iname): self.d = d self.iname = iname self.name = None class UnnamedSubItem(SubItem): def __init__(self, d, component): self.d = d self.iname = "%s.%s" % (self.d.currentIName, component) self.name = None movableTypes = set([ "QBrush", "QBitArray", "QByteArray", "QCustomTypeInfo", "QChar", "QDate", "QDateTime", "QFileInfo", "QFixed", "QFixedPoint", "QFixedSize", "QHashDummyValue", "QIcon", "QImage", "QLine", "QLineF", "QLatin1Char", "QLocale", "QMatrix", "QModelIndex", "QPoint", "QPointF", "QPen", "QPersistentModelIndex", "QResourceRoot", "QRect", "QRectF", "QRegExp", "QSize", "QSizeF", "QString", "QTime", "QTextBlock", "QUrl", "QVariant", "QXmlStreamAttribute", "QXmlStreamNamespaceDeclaration", "QXmlStreamNotationDeclaration", "QXmlStreamEntityDeclaration" ]) movableTypes5 = set([ "QStringList" ]) class DumperBase: def __init__(self): self.isCdb = False self.isGdb = False self.isLldb = False def computeLimit(self, size, limit): if limit is None: return size if limit == 0: return min(size, qqStringCutOff) return min(size, limit) def byteArrayDataHelper(self, addr): if self.qtVersion() >= 0x050000: # QTypedArray: # - QtPrivate::RefCount ref # - int size # - uint alloc : 31, capacityReserved : 1 # - qptrdiff offset size = self.extractInt(addr + 4) alloc = self.extractInt(addr + 8) & 0x7ffffff data = addr + self.dereference(addr + 8 + self.ptrSize()) else: # Data: # - QBasicAtomicInt ref; # - int alloc, size; # - [padding] # - char *data; alloc = self.extractInt(addr + 4) size = self.extractInt(addr + 8) data = self.dereference(addr + 8 + self.ptrSize()) return data, size, alloc # addr is the begin of a QByteArrayData structure def encodeStringHelper(self, addr, limit = 0): # Should not happen, but we get it with LLDB as result # of inferior calls if addr == 0: return "" data, size, alloc = self.byteArrayDataHelper(addr) if alloc != 0: self.check(0 <= size and size <= alloc and alloc <= 100*1000*1000) limit = self.computeLimit(size, limit) s = self.readMemory(data, 2 * limit) if limit < size: s += "2e002e002e00" return s def encodeByteArrayHelper(self, addr, limit = None): data, size, alloc = self.byteArrayDataHelper(addr) if alloc != 0: self.check(0 <= size and size <= alloc and alloc <= 100*1000*1000) limit = self.computeLimit(size, limit) s = self.readMemory(data, limit) if limit < size: s += "2e2e2e" return s def encodeByteArray(self, value): return self.encodeByteArrayHelper(self.dereferenceValue(value)) def byteArrayData(self, value): return self.byteArrayDataHelper(self.dereferenceValue(value)) def putByteArrayValue(self, value): return self.putValue(self.encodeByteArray(value), Hex2EncodedLatin1) def putStringValueByAddress(self, addr): self.putValue(self.encodeStringHelper(self.dereference(addr)), Hex4EncodedLittleEndian) def encodeString(self, value): return self.encodeStringHelper(self.dereferenceValue(value)) def stringData(self, value): return self.byteArrayDataHelper(self.dereferenceValue(value)) def putStringValue(self, value): return self.putValue(self.encodeString(value), Hex4EncodedLittleEndian) def putMapName(self, value): if str(value.type) == self.ns + "QString": self.put('key="%s",' % self.encodeString(value)) self.put('keyencoded="%s",' % Hex4EncodedLittleEndian) elif str(value.type) == self.ns + "QByteArray": self.put('key="%s",' % self.encodeByteArray(value)) self.put('keyencoded="%s",' % Hex2EncodedLatin1) else: if self.isLldb: self.put('name="%s",' % value.GetValue()) else: self.put('name="%s",' % value) def isMapCompact(self, keyType, valueType): format = self.currentItemFormat() if format == 2: return True # Compact. return self.isSimpleType(keyType) and self.isSimpleType(valueType) def check(self, exp): if not exp: raise RuntimeError("Check failed") def checkRef(self, ref): try: count = int(ref["atomic"]["_q_value"]) # Qt 5. minimum = -1 except: count = int(ref["_q_value"]) # Qt 4. minimum = 0 # Assume there aren't a million references to any object. self.check(count >= minimum) self.check(count < 1000000) def putQObjectNameValue(self, value): try: intSize = self.intSize() ptrSize = self.ptrSize() # dd = value["d_ptr"]["d"] is just behind the vtable. dd = self.dereference(self.addressOf(value) + ptrSize) if self.qtVersion() < 0x050000: # Size of QObjectData: 5 pointer + 2 int # - vtable # - QObject *q_ptr; # - QObject *parent; # - QObjectList children; # - uint isWidget : 1; etc.. # - int postedEvents; # - QMetaObject *metaObject; # Offset of objectName in QObjectPrivate: 5 pointer + 2 int # - [QObjectData base] # - QString objectName objectName = self.dereference(dd + 5 * ptrSize + 2 * intSize) else: # Size of QObjectData: 5 pointer + 2 int # - vtable # - QObject *q_ptr; # - QObject *parent; # - QObjectList children; # - uint isWidget : 1; etc... # - int postedEvents; # - QDynamicMetaObjectData *metaObject; extra = self.dereference(dd + 5 * ptrSize + 2 * intSize) if extra == 0: return False # Offset of objectName in ExtraData: 6 pointer # - QVector userData; only #ifndef QT_NO_USERDATA # - QList propertyNames; # - QList propertyValues; # - QVector runningTimers; # - QList > eventFilters; # - QString objectName objectName = self.dereference(extra + 5 * ptrSize) data, size, alloc = self.byteArrayDataHelper(objectName) if size == 0: return False str = self.readMemory(data, 2 * size) self.putValue(str, Hex4EncodedLittleEndian, 1) return True except: pass def cleanAddress(addr): if addr is None: return "" # We cannot use str(addr) as it yields rubbish for char pointers # that might trigger Unicode encoding errors. #return addr.cast(lookupType("void").pointer()) # We do not use "hex(...)" as it (sometimes?) adds a "L" suffix. return "0x%x" % toInteger(addr) # Some "Enums" # Encodings. Keep that synchronized with DebuggerEncoding in watchutils.h Unencoded8Bit, \ Base64Encoded8BitWithQuotes, \ Base64Encoded16BitWithQuotes, \ Base64Encoded32BitWithQuotes, \ Base64Encoded16Bit, \ Base64Encoded8Bit, \ Hex2EncodedLatin1, \ Hex4EncodedLittleEndian, \ Hex8EncodedLittleEndian, \ Hex2EncodedUtf8, \ Hex8EncodedBigEndian, \ Hex4EncodedBigEndian, \ Hex4EncodedLittleEndianWithoutQuotes, \ Hex2EncodedLocal8Bit, \ JulianDate, \ MillisecondsSinceMidnight, \ JulianDateAndMillisecondsSinceMidnight, \ Hex2EncodedInt1, \ Hex2EncodedInt2, \ Hex2EncodedInt4, \ Hex2EncodedInt8, \ Hex2EncodedUInt1, \ Hex2EncodedUInt2, \ Hex2EncodedUInt4, \ Hex2EncodedUInt8, \ Hex2EncodedFloat4, \ Hex2EncodedFloat8, \ IPv6AddressAndHexScopeId \ = range(28) # Display modes. Keep that synchronized with DebuggerDisplay in watchutils.h StopDisplay, \ DisplayImageData, \ DisplayUtf16String, \ DisplayImageFile, \ DisplayProcess, \ DisplayLatin1String, \ DisplayUtf8String \ = range(7) def mapForms(): return "Normal,Compact" def arrayForms(): if hasPlot(): return "Normal,Plot" return "Normal"