############################################################################ # # Copyright (C) 2016 The Qt Company Ltd. # Contact: https://www.qt.io/licensing/ # # 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 The Qt Company. For licensing terms # and conditions see https://www.qt.io/terms-conditions. For further # information use the contact form at https://www.qt.io/contact-us. # # GNU General Public License Usage # Alternatively, this file may be used under the terms of the GNU # General Public License version 3 as published by the Free Software # Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT # included in the packaging of this file. Please review the following # information to ensure the GNU General Public License requirements will # be met: https://www.gnu.org/licenses/gpl-3.0.html. # ############################################################################ import os import struct import sys import base64 import re import time import json import inspect if sys.version_info[0] >= 3: xrange = range toInteger = int else: toInteger = long verbosity = 0 verbosity = 1 # Debugger start modes. Keep in sync with DebuggerStartMode in debuggerconstants.h NoStartMode, \ StartInternal, \ StartExternal, \ AttachExternal, \ AttachCrashedExternal, \ AttachCore, \ AttachToRemoteServer, \ AttachToRemoteProcess, \ StartRemoteProcess, \ = range(0, 9) # Known special formats. Keep in sync with DisplayFormat in debuggerprotocol.h AutomaticFormat, \ RawFormat, \ SimpleFormat, \ EnhancedFormat, \ SeparateFormat, \ Latin1StringFormat, \ SeparateLatin1StringFormat, \ Utf8StringFormat, \ SeparateUtf8StringFormat, \ Local8BitStringFormat, \ Utf16StringFormat, \ Ucs4StringFormat, \ Array10Format, \ Array100Format, \ Array1000Format, \ Array10000Format, \ ArrayPlotFormat, \ CompactMapFormat, \ DirectQListStorageFormat, \ IndirectQListStorageFormat, \ = range(0, 20) # Breakpoints. Keep synchronized with BreakpointType in breakpoint.h UnknownType, \ BreakpointByFileAndLine, \ BreakpointByFunction, \ BreakpointByAddress, \ BreakpointAtThrow, \ BreakpointAtCatch, \ BreakpointAtMain, \ BreakpointAtFork, \ BreakpointAtExec, \ BreakpointAtSysCall, \ WatchpointAtAddress, \ WatchpointAtExpression, \ BreakpointOnQmlSignalEmit, \ BreakpointAtJavaScriptThrow, \ = range(0, 14) def arrayForms(): return [ArrayPlotFormat] def mapForms(): return [CompactMapFormat] class ReportItem: """ Helper structure to keep temporary "best" information about a value or a type scheduled to be reported. This might get overridden be subsequent better guesses during a putItem() run. """ def __init__(self, value = None, encoding = None, priority = -100, elided = None): self.value = value self.priority = priority self.encoding = encoding self.elided = elided def __str__(self): return "Item(value: %s, encoding: %s, priority: %s, elided: %s)" \ % (self.value, self.encoding, self.priority, self.elided) class Blob(object): """ Helper structure to keep a blob of bytes, possibly in the inferior. """ def __init__(self, data, isComplete = True): self.data = data self.size = len(data) self.isComplete = isComplete def size(self): return self.size def toBytes(self): """Retrieves "lazy" contents from memoryviews.""" data = self.data major = sys.version_info[0] if major == 3 or (major == 2 and sys.version_info[1] >= 7): if isinstance(data, memoryview): data = data.tobytes() if major == 2 and isinstance(data, buffer): data = ''.join([c for c in data]) return data def toString(self): data = self.toBytes() return data if sys.version_info[0] == 2 else data.decode("utf8") def extractByte(self, offset = 0): return struct.unpack_from("b", self.data, offset)[0] def extractShort(self, offset = 0): return struct.unpack_from("h", self.data, offset)[0] def extractUShort(self, offset = 0): return struct.unpack_from("H", self.data, offset)[0] def extractInt(self, offset = 0): return struct.unpack_from("i", self.data, offset)[0] def extractUInt(self, offset = 0): return struct.unpack_from("I", self.data, offset)[0] def extractLong(self, offset = 0): return struct.unpack_from("l", self.data, offset)[0] # FIXME: Note these should take target architecture into account. def extractULong(self, offset = 0): return struct.unpack_from("L", self.data, offset)[0] def extractInt64(self, offset = 0): return struct.unpack_from("q", self.data, offset)[0] def extractUInt64(self, offset = 0): return struct.unpack_from("Q", self.data, offset)[0] def extractDouble(self, offset = 0): return struct.unpack_from("d", self.data, offset)[0] def extractFloat(self, offset = 0): return struct.unpack_from("f", self.data, offset)[0] def warn(message): print('bridgemessage={msg="%s"},' % message.replace('"', '$').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 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 = d.stripClassTag(str(childType)) if not self.d.isCli: 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 self.printsAddress = not self.d.putAddressRange(addrBase, addrStep) 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(self.d.childrenPrefix) 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.putSpecialValue("notaccessible") 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.putNewline() self.d.put(self.d.childrenSuffix) return True class PairedChildrenData: def __init__(self, d, pairType, keyType, valueType, useKeyAndValue): self.useKeyAndValue = useKeyAndValue self.pairType = pairType self.keyType = keyType self.valueType = valueType self.isCompact = d.isMapCompact(self.keyType, self.valueType) self.childType = valueType if self.isCompact else pairType ns = d.qtNamespace() keyTypeName = d.stripClassTag(str(self.keyType)) self.keyIsQString = keyTypeName == ns + "QString" self.keyIsQByteArray = keyTypeName == ns + "QByteArray" self.keyIsStdString = keyTypeName == "std::string" \ or keyTypeName.startswith("std::basic_string= 48 and ord(c) <= 57: continue stripped += c self.cachedFormats[typeName] = stripped return stripped # Hex decoding operating on str, return str. def hexdecode(self, s): if sys.version_info[0] == 2: return s.decode("hex") return bytes.fromhex(s).decode("utf8") # Hex encoding operating on str or bytes, return str. def hexencode(self, s): if s is None: s = '' if sys.version_info[0] == 2: return s.encode("hex") if isinstance(s, str): s = s.encode("utf8") return base64.b16encode(s).decode("utf8") #def toBlob(self, value): # """Abstract""" def is32bit(self): return self.ptrSize() == 4 def is64bit(self): return self.ptrSize() == 8 def isQt3Support(self): # assume no Qt 3 support by default return False def lookupQtType(self, typeName): return self.lookupType(self.qtNamespace() + typeName) # Clamps size to limit. def computeLimit(self, size, limit): if limit == 0: limit = self.displayStringLimit if limit is None or size <= limit: return 0, size return size, limit def vectorDataHelper(self, addr): if self.qtVersion() >= 0x050000: size = self.extractInt(addr + 4) alloc = self.extractInt(addr + 8) & 0x7ffffff data = addr + self.extractPointer(addr + 8 + self.ptrSize()) else: alloc = self.extractInt(addr + 4) size = self.extractInt(addr + 8) data = addr + 16 return data, size, alloc 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.extractPointer(addr + 8 + self.ptrSize()) if self.ptrSize() == 4: data = data & 0xffffffff else: data = data & 0xffffffffffffffff elif self.qtVersion() >= 0x040000: # Data: # - QBasicAtomicInt ref; # - int alloc, size; # - [padding] # - char *data; alloc = self.extractInt(addr + 4) size = self.extractInt(addr + 8) data = self.extractPointer(addr + 8 + self.ptrSize()) else: # Data: # - QShared count; # - QChar *unicode # - char *ascii # - uint len: 30 size = self.extractInt(addr + 3 * self.ptrSize()) & 0x3ffffff alloc = size # pretend. data = self.extractPointer(addr + self.ptrSize()) return data, size, alloc # addr is the begin of a QByteArrayData structure def encodeStringHelper(self, addr, limit): # Should not happen, but we get it with LLDB as result # of inferior calls if addr == 0: return 0, "" data, size, alloc = self.byteArrayDataHelper(addr) if alloc != 0: self.check(0 <= size and size <= alloc and alloc <= 100*1000*1000) elided, shown = self.computeLimit(size, limit) return elided, self.readMemory(data, 2 * shown) def encodeByteArrayHelper(self, addr, limit): data, size, alloc = self.byteArrayDataHelper(addr) if alloc != 0: self.check(0 <= size and size <= alloc and alloc <= 100*1000*1000) elided, shown = self.computeLimit(size, limit) return elided, self.readMemory(data, shown) def putCharArrayHelper(self, data, size, charSize, displayFormat = AutomaticFormat, makeExpandable = True): bytelen = size * charSize elided, shown = self.computeLimit(bytelen, self.displayStringLimit) mem = self.readMemory(data, shown) if charSize == 1: if displayFormat == Latin1StringFormat \ or displayFormat == SeparateLatin1StringFormat: encodingType = "latin1" else: encodingType = "utf8" childType = "char" elif charSize == 2: encodingType = "utf16" childType = "short" else: encodingType = "ucs4" childType = "int" self.putValue(mem, encodingType, elided=elided) if displayFormat == SeparateLatin1StringFormat \ or displayFormat == SeparateUtf8StringFormat \ or displayFormat == SeparateFormat: elided, shown = self.computeLimit(bytelen, 100000) self.putDisplay(encodingType + ':separate', self.readMemory(data, shown)) if makeExpandable: self.putNumChild(size) if self.isExpanded(): with Children(self): for i in range(size): self.putSubItem(size, data[i]) def readMemory(self, addr, size): data = self.extractBlob(addr, size).toBytes() return self.hexencode(data) def encodeByteArray(self, value, limit = 0): elided, data = self.encodeByteArrayHelper(self.extractPointer(value), limit) return data def byteArrayData(self, value): return self.byteArrayDataHelper(self.extractPointer(value)) def putByteArrayValue(self, value): elided, data = self.encodeByteArrayHelper(self.extractPointer(value), self.displayStringLimit) self.putValue(data, "latin1", elided=elided) def encodeString(self, value, limit = 0): elided, data = self.encodeStringHelper(self.extractPointer(value), limit) return data def encodedUtf16ToUtf8(self, s): return ''.join([chr(int(s[i:i+2], 16)) for i in range(0, len(s), 4)]) def encodeStringUtf8(self, value, limit = 0): return self.encodedUtf16ToUtf8(self.encodeString(value, limit)) def stringData(self, value): return self.byteArrayDataHelper(self.extractPointer(value)) def encodeStdString(self, value, limit = 0): data = value["_M_dataplus"]["_M_p"] sizePtr = data.cast(self.sizetType().pointer()) size = int(sizePtr[-3]) alloc = int(sizePtr[-2]) self.check(0 <= size and size <= alloc and alloc <= 100*1000*1000) elided, shown = self.computeLimit(size, limit) return self.readMemory(data, shown) def extractTemplateArgument(self, typename, position): level = 0 skipSpace = False inner = '' for c in typename[typename.find('<') + 1 : -1]: if c == '<': inner += c level += 1 elif c == '>': level -= 1 inner += c elif c == ',': if level == 0: if position == 0: return inner.strip() position -= 1 inner = '' else: inner += c skipSpace = True else: if skipSpace and c == ' ': pass else: inner += c skipSpace = False # Handle local struct definitions like QList inner = inner.strip() p = inner.find(')::') if p > -1: inner = inner[p+3:] return inner def putStringValueByAddress(self, addr): elided, data = self.encodeStringHelper(addr, self.displayStringLimit) self.putValue(data, "utf16", elided=elided) def putStringValue(self, value): elided, data = self.encodeStringHelper( self.extractPointer(value), self.displayStringLimit) self.putValue(data, "utf16", elided=elided) def putAddressItem(self, name, value, type = ""): with SubItem(self, name): self.putValue("0x%x" % value) self.putType(type) self.putNumChild(0) def putIntItem(self, name, value): with SubItem(self, name): self.putValue(value) self.putType("int") self.putNumChild(0) def putBoolItem(self, name, value): with SubItem(self, name): self.putValue(value) self.putType("bool") self.putNumChild(0) def putGenericItem(self, name, type, value, encoding = None): with SubItem(self, name): self.putValue(value, encoding) self.putType(type) self.putNumChild(0) def putCallItem(self, name, value, func, *args): try: result = self.callHelper(value, func, args) with SubItem(self, name): self.putItem(result) except: with SubItem(self, name): self.putSpecialValue("notcallable"); self.putNumChild(0) def call(self, value, func, *args): return self.callHelper(value, func, args) def putAddressRange(self, base, step): try: if not addrBase is None and not step is None: self.put('addrbase="0x%x",' % toInteger(base)) self.put('addrstep="0x%x",' % toInteger(step)) return True except: #warn("ADDRBASE: %s" % base) #warn("ADDRSTEP: %s" % step) pass return False def putMapName(self, value, index = None): ns = self.qtNamespace() typeName = self.stripClassTag(str(value.type)) if typeName == ns + "QString": self.put('keyencoded="utf16:2:0",key="%s",' % self.encodeString(value)) elif typeName == ns + "QByteArray": self.put('keyencoded="latin1:1:0",key="%s",' % self.encodeByteArray(value)) elif typeName == "std::string": self.put('keyencoded="latin1:1:0",key="%s",' % self.encodeStdString(value)) else: val = str(value.GetValue()) if self.isLldb else str(value) if index is None: key = '%s' % val else: key = '[%s] %s' % (index, val) self.put('keyencoded="utf8:1:0",key="%s",' % self.hexencode(key)) def putPair(self, pair, index = None): if self.pairData.useKeyAndValue: key = pair["key"] value = pair["value"] else: key = pair["first"] value = pair["second"] if self.pairData.isCompact: if self.pairData.keyIsQString: self.put('keyencoded="utf16",key="%s",' % self.encodeString(key)) elif self.pairData.keyIsQByteArray: self.put('keyencoded="latin1",key="%s",' % self.encodeByteArray(key)) elif self.pairData.keyIsStdString: self.put('keyencoded="latin1",key="%s",' % self.encodeStdString(key)) else: name = str(key.GetValue()) if self.isLldb else str(key) if index == -1: self.put('name="%s",' % name) else: self.put('key="[%s] %s",' % (index, name)) self.putItem(value) else: self.putEmptyValue() self.putNumChild(2) self.putField("iname", self.currentIName) if self.isExpanded(): with Children(self): if self.pairData.useKeyAndValue: self.putSubItem("key", key) self.putSubItem("value", value) else: self.putSubItem("first", key) self.putSubItem("second", value) def putPlainChildren(self, value, dumpBase = True): self.putEmptyValue(-99) self.putNumChild(1) if self.isExpanded(): with Children(self): self.putFields(value, dumpBase) def putMembersItem(self, value, sortorder = 10): with SubItem(self, "[members]"): self.put('sortgroup="%s"' % sortorder) self.putPlainChildren(value) def isMapCompact(self, keyType, valueType): if self.currentItemFormat() == CompactMapFormat: return True return self.isSimpleType(keyType) and self.isSimpleType(valueType) def check(self, exp): if not exp: raise RuntimeError("Check failed") def checkRef(self, ref): count = self.extractInt(ref.address) # Assume there aren't a million references to any object. self.check(count >= -1) self.check(count < 1000000) def readToFirstZero(self, p, tsize, maximum): code = (None, "b", "H", None, "I")[tsize] base = toInteger(p) blob = self.extractBlob(base, maximum).toBytes() for i in xrange(0, maximum, tsize): t = struct.unpack_from(code, blob, i)[0] if t == 0: return 0, i, self.hexencode(blob[:i]) # Real end is unknown. return -1, maximum, self.hexencode(blob[:maximum]) def encodeCArray(self, p, tsize, limit): elided, shown, blob = self.readToFirstZero(p, tsize, limit) return elided, blob def putItemCount(self, count, maximum = 1000000000): # This needs to override the default value, so don't use 'put' directly. if count > maximum: self.putSpecialValue("minimumitemcount", maximum) else: self.putSpecialValue("itemcount", count) self.putNumChild(count) def resultToMi(self, value): if type(value) is bool: return '"%d"' % int(value) if type(value) is dict: return '{' + ','.join(['%s=%s' % (k, self.resultToMi(v)) for (k, v) in list(value.items())]) + '}' if type(value) is list: return '[' + ','.join([self.resultToMi(k) for k in list(value.items())]) + ']' return '"%s"' % value def variablesToMi(self, value, prefix): if type(value) is bool: return '"%d"' % int(value) if type(value) is dict: pairs = [] for (k, v) in list(value.items()): if k == 'iname': if v.startswith('.'): v = '"%s%s"' % (prefix, v) else: v = '"%s"' % v else: v = self.variablesToMi(v, prefix) pairs.append('%s=%s' % (k, v)) return '{' + ','.join(pairs) + '}' if type(value) is list: index = 0 pairs = [] for item in value: if item.get('type', '') == 'function': continue name = item.get('name', '') if len(name) == 0: name = str(index) index += 1 pairs.append((name, self.variablesToMi(item, prefix))) pairs.sort(key = lambda pair: pair[0]) return '[' + ','.join([pair[1] for pair in pairs]) + ']' return '"%s"' % value def filterPrefix(self, prefix, items): return [i[len(prefix):] for i in items if i.startswith(prefix)] def tryFetchInterpreterVariables(self, args): if not int(args.get('nativemixed', 0)): return (False, '') context = args.get('context', '') if not len(context): return (False, '') expanded = args.get('expanded') args['expanded'] = self.filterPrefix('local', expanded) res = self.sendInterpreterRequest('variables', args) if not res: return (False, '') reslist = [] for item in res.get('variables', {}): if not 'iname' in item: item['iname'] = '.' + item.get('name') reslist.append(self.variablesToMi(item, 'local')) watchers = args.get('watchers', None) if watchers: toevaluate = [] name2expr = {} seq = 0 for watcher in watchers: expr = self.hexdecode(watcher.get('exp')) name = str(seq) toevaluate.append({'name': name, 'expression': expr}) name2expr[name] = expr seq += 1 args['expressions'] = toevaluate args['expanded'] = self.filterPrefix('watch', expanded) del args['watchers'] res = self.sendInterpreterRequest('expressions', args) if res: for item in res.get('expressions', {}): name = item.get('name') iname = 'watch.' + name expr = name2expr.get(name) item['iname'] = iname item['wname'] = self.hexencode(expr) item['exp'] = expr reslist.append(self.variablesToMi(item, 'watch')) return (True, 'data=[%s]' % ','.join(reslist)) def putField(self, name, value): self.put('%s="%s",' % (name, value)) def putType(self, type, priority = 0): # Higher priority values override lower ones. if priority >= self.currentType.priority: self.currentType.value = str(type) self.currentType.priority = priority def putValue(self, value, encoding = None, priority = 0, elided = None): # Higher priority values override lower ones. # elided = 0 indicates all data is available in value, # otherwise it's the true length. if priority >= self.currentValue.priority: self.currentValue = ReportItem(value, encoding, priority, elided) def putSpecialValue(self, encoding, value = ""): self.putValue(value, encoding) def putEmptyValue(self, priority = -10): if priority >= self.currentValue.priority: self.currentValue = ReportItem("", None, priority, None) def putName(self, name): self.put('name="%s",' % name) def putBetterType(self, type): if isinstance(type, ReportItem): self.currentType.value = str(type.value) else: self.currentType.value = str(type) self.currentType.priority += 1 def putNoType(self): # FIXME: replace with something that does not need special handling # in SubItem.__exit__(). self.putBetterType(" ") def putInaccessible(self): #self.putBetterType(" ") self.putNumChild(0) self.currentValue.value = None def putNamedSubItem(self, component, value, name): with SubItem(self, component): self.putName(name) self.putItem(value) def isExpanded(self): #warn("IS EXPANDED: %s in %s: %s" % (self.currentIName, # self.expandedINames, self.currentIName in self.expandedINames)) return self.currentIName in self.expandedINames def putCStyleArray(self, value): arrayType = value.type.unqualified() innerType = value[0].type innerTypeName = str(innerType.unqualified()) ts = innerType.sizeof try: self.putValue("@0x%x" % self.addressOf(value), priority = -1) except: self.putEmptyValue() self.putType(arrayType) try: p = self.addressOf(value) except: p = None displayFormat = self.currentItemFormat() arrayByteSize = arrayType.sizeof if arrayByteSize == 0: # This should not happen. But it does, see QTCREATORBUG-14755. # GDB/GCC produce sizeof == 0 for QProcess arr[3] s = str(value.type) itemCount = s[s.find('[')+1:s.find(']')] if not itemCount: itemCount = '100' arrayByteSize = int(itemCount) * ts; n = int(arrayByteSize / ts) if displayFormat != RawFormat and p: if innerTypeName == "char" or innerTypeName == "wchar_t": self.putCharArrayHelper(p, n, ts, self.currentItemFormat(), makeExpandable = False) else: self.tryPutSimpleFormattedPointer(p, arrayType, innerTypeName, displayFormat, arrayByteSize) self.putNumChild(n) if self.isExpanded(): self.putArrayData(p, n, innerType) self.putPlotDataHelper(p, n, innerType) def cleanAddress(self, 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()) try: return "0x%x" % toInteger(hex(addr), 16) except: warn("CANNOT CONVERT TYPE: %s" % type(addr)) try: warn("ADDR: %s" % addr) except: pass try: warn("TYPE: %s" % addr.type) except: pass return str(addr) def tryPutPrettyItem(self, typeName, value): if self.useFancy and self.currentItemFormat() != RawFormat: self.putType(typeName) nsStrippedType = self.stripNamespaceFromType(typeName)\ .replace("::", "__") # The following block is only needed for D. if nsStrippedType.startswith("_A"): # DMD v2.058 encodes string[] as _Array_uns long long. # With spaces. if nsStrippedType.startswith("_Array_"): qdump_Array(self, value) return True if nsStrippedType.startswith("_AArray_"): qdump_AArray(self, value) return True dumper = self.qqDumpers.get(nsStrippedType) if not dumper is None: dumper(self, value) return True for pattern in self.qqDumpersEx.keys(): dumper = self.qqDumpersEx[pattern] if re.match(pattern, nsStrippedType): dumper(self, value) return True return False def putSimpleCharArray(self, base, size = None): if size is None: elided, shown, data = self.readToFirstZero(base, 1, self.displayStringLimit) else: elided, shown = self.computeLimit(int(size), self.displayStringLimit) data = self.readMemory(base, shown) self.putValue(data, "latin1", elided=elided) def putDisplay(self, editFormat, value): self.put('editformat="%s",' % editFormat) self.put('editvalue="%s",' % value) # This is shared by pointer and array formatting. def tryPutSimpleFormattedPointer(self, value, typeName, innerTypeName, displayFormat, limit): if displayFormat == AutomaticFormat: if innerTypeName == "char": # Use UTF-8 as default for char *. self.putType(typeName) (elided, data) = self.encodeCArray(value, 1, limit) self.putValue(data, "utf8", elided=elided) return True if innerTypeName == "wchar_t": self.putType(typeName) charSize = self.lookupType('wchar_t').sizeof (elided, data) = self.encodeCArray(value, charSize, limit) if charSize == 2: self.putValue(data, "utf16", elided=elided) else: self.putValue(data, "ucs4", elided=elided) return True if displayFormat == Latin1StringFormat: self.putType(typeName) (elided, data) = self.encodeCArray(value, 1, limit) self.putValue(data, "latin1", elided=elided) return True if displayFormat == SeparateLatin1StringFormat: self.putType(typeName) (elided, data) = self.encodeCArray(value, 1, limit) self.putValue(data, "latin1", elided=elided) self.putDisplay("latin1:separate", data) return True if displayFormat == Utf8StringFormat: self.putType(typeName) (elided, data) = self.encodeCArray(value, 1, limit) self.putValue(data, "utf8", elided=elided) return True if displayFormat == SeparateUtf8StringFormat: self.putType(typeName) (elided, data) = self.encodeCArray(value, 1, limit) self.putValue(data, "utf8", elided=elided) self.putDisplay("utf8:separate", data) return True if displayFormat == Local8BitStringFormat: self.putType(typeName) (elided, data) = self.encodeCArray(value, 1, limit) self.putValue(data, "local8bit", elided=elided) return True if displayFormat == Utf16StringFormat: self.putType(typeName) (elided, data) = self.encodeCArray(value, 2, limit) self.putValue(data, "utf16", elided=elided) return True if displayFormat == Ucs4StringFormat: self.putType(typeName) (elided, data) = self.encodeCArray(value, 4, limit) self.putValue(data, "ucs4", elided=elided) return True return False def putFormattedPointer(self, value): #warn("POINTER: %s" % value) if self.isNull(value): #warn("NULL POINTER") self.putType(value.type) self.putValue("0x0") self.putNumChild(0) return typeName = str(value.type) (dereferencable, pointerValue) = self.pointerInfo(value) self.putAddress(pointerValue) self.putOriginalAddress(value) if not dereferencable: # Failure to dereference a pointer should at least # show the value of a pointer. self.putValue(self.cleanAddress(pointerValue)) self.putType(typeName) self.putNumChild(0) return displayFormat = self.currentItemFormat(value.type) innerType = value.type.target().unqualified() innerTypeName = str(innerType) if innerTypeName == "void": #warn("VOID POINTER: %s" % displayFormat) self.putType(typeName) self.putValue(str(value)) self.putNumChild(0) return if displayFormat == RawFormat: # Explicitly requested bald pointer. self.putType(typeName) self.putValue(self.hexencode(str(value)), "utf8:1:0") self.putNumChild(1) if self.currentIName in self.expandedINames: with Children(self): with SubItem(self, '*'): self.putItem(value.dereference()) return limit = self.displayStringLimit if displayFormat == SeparateLatin1StringFormat \ or displayFormat == SeparateUtf8StringFormat: limit = 1000000 if self.tryPutSimpleFormattedPointer(value, typeName, innerTypeName, displayFormat, limit): self.putNumChild(0) return if Array10Format <= displayFormat and displayFormat <= Array1000Format: n = (10, 100, 1000, 10000)[displayFormat - Array10Format] self.putType(typeName) self.putItemCount(n) self.putArrayData(value, n, innerType) return if self.isFunctionType(innerType): # A function pointer. val = str(value) pos = val.find(" = ") # LLDB only, but... if pos > 0: val = val[pos + 3:] self.putValue(val) self.putType(innerTypeName) self.putNumChild(0) return #warn("AUTODEREF: %s" % self.autoDerefPointers) #warn("INAME: %s" % self.currentIName) if self.autoDerefPointers or self.currentIName.endswith('.this'): # Generic pointer type with AutomaticFormat. # Never dereference char types. if innerTypeName != "char" \ and innerTypeName != "signed char" \ and innerTypeName != "unsigned char" \ and innerTypeName != "wchar_t": self.putType(innerTypeName) savedCurrentChildType = self.currentChildType self.currentChildType = self.stripClassTag(innerTypeName) self.putItem(value.dereference()) self.currentChildType = savedCurrentChildType self.putOriginalAddress(value) return #warn("GENERIC PLAIN POINTER: %s" % value.type) #warn("ADDR PLAIN POINTER: 0x%x" % value.address) self.putType(typeName) self.putValue("0x%x" % self.pointerValue(value)) self.putNumChild(1) if self.currentIName in self.expandedINames: with Children(self): with SubItem(self, "*"): self.putItem(value.dereference()) def putOriginalAddress(self, value): if not value.address is None: self.put('origaddr="0x%x",' % toInteger(value.address)) def putQObjectNameValue(self, value): try: intSize = self.intSize() ptrSize = self.ptrSize() # dd = value["d_ptr"]["d"] is just behind the vtable. dd = self.extractPointer(value, offset=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.extractPointer(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.extractPointer(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.extractPointer(extra + 5 * ptrSize) data, size, alloc = self.byteArrayDataHelper(objectName) # Object names are short, and GDB can crash on to big chunks. # Since this here is a convenience feature only, limit it. if size <= 0 or size > 80: return False raw = self.readMemory(data, 2 * size) self.putValue(raw, "utf16", 1) return True except: # warn("NO QOBJECT: %s" % value.type) pass def extractMetaObjectPtr(self, objectPtr, typeobj): """ objectPtr - address of *potential* instance of QObject derived class typeobj - type of *objectPtr if known, None otherwise. """ def canBePointer(p): if ptrSize == 4: return p > 100000 and (p & 0x3 == 0) else: return p > 100000 and (p & 0x7 == 0) and (p < 0x7fffffffffff) def couldBeQObject(): try: (vtablePtr, dd) \ = self.extractStruct('PP', objectPtr, 2 * ptrSize) except: self.bump("nostruct-1") return False if not canBePointer(vtablePtr): self.bump("vtable") return False if not canBePointer(dd): self.bump("d_d_ptr") return False try: (dvtablePtr, qptr, parentPtr, childrenDPtr, flags) \ = self.extractStruct('PPPPI', dd, 4 * ptrSize + 4) except: self.bump("nostruct-2") return False #warn("STRUCT DD: %s %s" % (self.currentIName, x)) if not canBePointer(dvtablePtr): self.bump("dvtable") #warn("DVT: 0x%x" % dvtablePtr) return False # Check d_ptr.d.q_ptr == objectPtr if qptr != objectPtr: #warn("QPTR: 0x%x 0x%x" % (qptr, objectPtr)) self.bump("q_ptr") return False if parentPtr and not canBePointer(parentPtr): #warn("PAREN") self.bump("parent") return False if not canBePointer(childrenDPtr): #warn("CHILD") self.bump("children") return False #if flags >= 0x80: # Only 7 flags are defined # warn("FLAGS: 0x%x %s" % (flags, self.currentIName)) # self.bump("flags") # return False #warn("OK") #if dynMetaObjectPtr and not canBePointer(dynMetaObjectPtr): # self.bump("dynmo") # return False self.bump("couldBeQObject") return True def extractMetaObjectPtrFromAddress(): return 0 # FIXME: Calling "works" but seems to impact memory contents(!) # in relevant places. One symptom is that object name # contents "vanishes" as the reported size of the string # gets zeroed out(?). # Try vtable, metaObject() is the first entry. vtablePtr = self.extractPointer(objectPtr) metaObjectFunc = self.extractPointer(vtablePtr) cmd = "((void*(*)(void *))0x%x)(0x%x)" % (metaObjectFunc, objectPtr) try: #warn("MO CMD: %s" % cmd) res = self.parseAndEvaluate(cmd) #warn("MO RES: %s" % res) self.bump("successfulMetaObjectCall") return toInteger(res) except: self.bump("failedMetaObjectCall") #warn("COULD NOT EXECUTE: %s" % cmd) return 0 def extractStaticMetaObjectFromTypeHelper(typeobj): if self.isSimpleType(typeobj): return 0 typeName = str(typeobj) isQObjectProper = typeName == self.qtNamespace() + "QObject" if not isQObjectProper: if self.directBaseClass(typeobj, 0) is None: return 0 # No templates for now. if typeName.find('<') >= 0: return 0 result = self.findStaticMetaObject(typeName) # We need to distinguish Q_OBJECT from Q_GADGET: # a Q_OBJECT SMO has a non-null superdata (unless it's QObject itself), # a Q_GADGET SMO has a null superdata (hopefully) if result and not isQObjectProper: superdata = self.extractPointer(result) if superdata == 0: # This looks like a Q_GADGET return 0 return result def extractStaticMetaObjectPtrFromType(someTypeObj): if someTypeObj is None: return 0 someTypeName = str(someTypeObj) self.bump('metaObjectFromType') known = self.knownStaticMetaObjects.get(someTypeName, None) if known is not None: # Is 0 or the static metaobject. return known result = 0 #try: result = extractStaticMetaObjectFromTypeHelper(someTypeObj) #except RuntimeError as error: # warn("METAOBJECT EXTRACTION FAILED: %s" % error) #except: # warn("METAOBJECT EXTRACTION FAILED FOR UNKNOWN REASON") if not result: base = self.directBaseClass(someTypeObj, 0) if base != someTypeObj: # sanity check result = extractStaticMetaObjectPtrFromType(base) self.knownStaticMetaObjects[someTypeName] = result return result if not self.useFancy: return 0 ptrSize = self.ptrSize() typeName = str(typeobj) result = self.knownStaticMetaObjects.get(typeName, None) if result is not None: # Is 0 or the static metaobject. self.bump("typecached") #warn("CACHED RESULT: %s %s 0x%x" % (self.currentIName, typeName, result)) return result if not couldBeQObject(): self.bump('cannotBeQObject') #warn("DOES NOT LOOK LIKE A QOBJECT: %s" % self.currentIName) return 0 metaObjectPtr = 0 if not metaObjectPtr: # measured: 3 ms (example had one level of inheritance) self.preping("metaObjectType-" + self.currentIName) metaObjectPtr = extractStaticMetaObjectPtrFromType(typeobj) self.ping("metaObjectType-" + self.currentIName) if not metaObjectPtr: # measured: 200 ms (example had one level of inheritance) self.preping("metaObjectCall-" + self.currentIName) metaObjectPtr = extractMetaObjectPtrFromAddress() self.ping("metaObjectCall-" + self.currentIName) #if metaObjectPtr: # self.bump('foundMetaObject') # self.knownStaticMetaObjects[typeName] = metaObjectPtr return metaObjectPtr def extractStruct(self, pattern, base, size): #warn("PATTERN: '%s'" % pattern) pointerReplacement = 'Q' if self.ptrSize() == 8 else 'I' pattern = pattern.replace('P', pointerReplacement) mem = self.readRawMemory(base, size) #warn("OUT: '%s'" % str(struct.unpack_from(pattern, mem))) return struct.unpack_from(pattern, mem) def extractCString(self, addr): result = bytearray() while True: d = self.extractByte(addr) if d == 0: break result.append(d) addr += 1 return result def listChildrenGenerator(self, addr, innerType): base = self.extractPointer(addr) begin = self.extractInt(base + 8) end = self.extractInt(base + 12) array = base + 16 if self.qtVersion() < 0x50000: array += self.ptrSize() size = end - begin innerSize = innerType.sizeof stepSize = self.ptrSize() addr = array + begin * stepSize isInternal = innerSize <= stepSize and self.isMovableType(innerType) for i in range(size): if isInternal: yield self.createValue(addr + i * stepSize, innerType) else: p = self.extractPointer(addr + i * stepSize) yield self.createValue(p, innerType) def vectorChildrenGenerator(self, addr, innerType): base = self.extractPointer(addr) size = self.extractInt(base + 4) data = base + self.extractPointer(base + 8 + self.ptrSize()) innerSize = innerType.sizeof for i in range(size): yield self.createValue(data + i * innerSize, innerType) def putTypedPointer(self, name, addr, typeName): """ Prints a typed pointer, expandable if the type can be resolved, and without children otherwise """ with SubItem(self, name): self.putAddress(addr) self.putValue("@0x%x" % addr) typeObj = self.lookupType(typeName) if typeObj: self.putType(typeObj) self.putNumChild(1) if self.isExpanded(): with Children(self): self.putFields(self.createValue(addr, typeObj)) else: self.putType(typeName) self.putNumChild(0) def putStructGuts(self, value): self.putEmptyValue() #metaObjectPtr = self.extractMetaObjectPtr(self.addressOf(value), value.type) if self.showQObjectNames: self.preping(self.currentIName) metaObjectPtr = self.extractMetaObjectPtr(self.addressOf(value), value.type) self.ping(self.currentIName) if metaObjectPtr: self.context = value self.putQObjectNameValue(value) #warn("STRUCT GUTS: %s MO: 0x%x " % (self.currentIName, metaObjectPtr)) if self.isExpanded(): self.put('sortable="1"') with Children(self, 1, childType=None): self.putFields(value) if not self.showQObjectNames: metaObjectPtr = self.extractMetaObjectPtr(self.addressOf(value), value.type) if metaObjectPtr: self.putQObjectGuts(value, metaObjectPtr) # This is called is when a QObject derived class is expanded def putQObjectGuts(self, qobject, metaObjectPtr): self.putQObjectGutsHelper(qobject, self.addressOf(qobject), -1, metaObjectPtr, "QObject") def metaString(self, metaObjectPtr, index, revision = 7): #stringData = self.extractPointer(metaObjectPtr["d"]["stringdata"]) ptrSize = self.ptrSize() stringdata = self.extractPointer(toInteger(metaObjectPtr) + ptrSize) if revision >= 7: # Qt 5. #byteArrayDataType = self.lookupQtType("QByteArrayData") #byteArrayDataSize = byteArrayDataType.sizeof byteArrayDataSize = 24 if ptrSize == 8 else 16 literal = stringdata + toInteger(index) * byteArrayDataSize ldata, lsize, lalloc = self.byteArrayDataHelper(literal) try: return self.extractBlob(ldata, lsize).toString() except: return "" else: # Qt 4. ldata = stringdata + index return self.extractCString(ldata).decode("utf8") def putQMetaStuff(self, value, origType): metaObjectPtr = value["mobj"] if toInteger(metaObjectPtr): handle = toInteger(value["handle"]) index = toInteger(metaObjectPtr["d"]["data"][handle]) name = self.metaString(metaObjectPtr, index) self.putValue(name) self.putNumChild(1) if self.isExpanded(): with Children(self): self.putFields(value) self.putQObjectGutsHelper(0, 0, handle, toInteger(metaObjectPtr), origType) else: self.putEmptyValue() if self.isExpanded(): with Children(self): self.putFields(value) # basically all meta things go through this here. # qobject and qobjectPtr are non-null if coming from a real structure display # qobject == 0, qobjectPtr != 0 is possible for builds without QObject debug info # if qobject == 0, properties and d-ptr cannot be shown. # handle is what's store in QMetaMethod etc, pass -1 for QObject/QMetaObject itself # metaObjectPtr needs to point to a valid QMetaObject. def putQObjectGutsHelper(self, qobject, qobjectPtr, handle, metaObjectPtr, origType): intSize = self.intSize() ptrSize = self.ptrSize() def putt(name, value, typeName = ' '): with SubItem(self, name): self.putValue(value) self.putType(typeName) self.putNumChild(0) def extractSuperDataPtr(someMetaObjectPtr): #return someMetaObjectPtr['d']['superdata'] return self.extractPointer(someMetaObjectPtr) def extractDataPtr(someMetaObjectPtr): # dataPtr = metaObjectPtr["d"]["data"] return self.extractPointer(someMetaObjectPtr + 2 * ptrSize) isQMetaObject = origType == "QMetaObject" isQObject = origType == "QObject" #warn("OBJECT GUTS: %s 0x%x " % (self.currentIName, metaObjectPtr)) dataPtr = extractDataPtr(metaObjectPtr) #warn("DATA PTRS: %s 0x%x " % (self.currentIName, dataPtr)) (revision, classname, classinfo, classinfo2, methodCount, methods, propertyCount, properties, enumCount, enums, constructorCount, constructors, flags, signalCount) = self.extractStruct('IIIIIIIIIIIIII', dataPtr, 56) largestStringIndex = -1 for i in range(methodCount): t = self.extractStruct('IIIII', dataPtr + 56 + i * 20, 20) if largestStringIndex < t[0]: largestStringIndex = t[0] extraData = 0 if qobjectPtr: #isQt5 = self.qtVersion() >= 0x50000 #extraDataOffset = 5 * ptrSize + 8 if isQt5 else 6 * ptrSize + 8 # (QObjectData/QObjectPrivate) dd = value["d_ptr"]["d"] is just behind the vtable. dd = self.extractPointer(qobjectPtr + ptrSize) if self.qtVersion() >= 0x50000: (dvtablePtr, qptr, parentPtr, childrenDPtr, flags, postedEvents, dynMetaObjectPtr, extraData, threadDataPtr, connectionListsPtr, sendersPtr, currentSenderPtr) \ = self.extractStruct('PPPPIIPPPPPP', dd, 10 * ptrSize + 8) else: (dvtablePtr, qptr, parentPtr, childrenDPtr, flags, postedEvents, dynMetaObjectPtr, extraData, threadDataPtr, connectionListsPtr, sendersPtr, currentSenderPtr) \ = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) if qobjectPtr: qobjectType = self.lookupQtType("QObject") badType = qobjectType is None with SubItem(self, "[parent]"): self.put('sortgroup="9"') parentPtrType = self.voidPtrType() if badType else qobjectType.pointer() self.putItem(self.createValue(dd + 2 * ptrSize, parentPtrType)) with SubItem(self, "[children]"): self.put('sortgroup="8"') base = self.extractPointer(dd + 3 * ptrSize) # It's a QList begin = self.extractInt(base + 8) end = self.extractInt(base + 12) array = base + 16 if self.qtVersion() < 0x50000: array += ptrSize self.check(begin >= 0 and end >= 0 and end <= 1000 * 1000 * 1000) size = end - begin self.check(size >= 0) self.putItemCount(size) if self.isExpanded(): addrBase = array + begin * ptrSize with Children(self, size): for i in self.childRange(): with SubItem(self, i): childPtr = self.extractPointer(addrBase + i * ptrSize) if badType: # release build childMetaObjectPtr = self.extractMetaObjectPtr(childPtr, None) if childMetaObjectPtr: # release build + live process self.putNumChild(1) self.putAddress(childPtr) if self.isExpanded(): with Children(self): self.putQObjectGutsHelper(0, childPtr, -1, childMetaObjectPtr, "QObject") else: # release build + core dump self.putItem(self.createValue(addrBase + i * ptrSize, self.voidPtrType())) else: # debug info self.putItem(self.createValue(childPtr, qobjectType)) if isQMetaObject: with SubItem(self, "[strings]"): self.put('sortgroup="2"') if largestStringIndex > 0: self.putSpecialValue("minimumitemcount", largestStringIndex) self.putNumChild(1) if self.isExpanded(): with Children(self, largestStringIndex + 1): for i in self.childRange(): with SubItem(self, i): self.putValue(self.hexencode(self.metaString(metaObjectPtr, i)), "latin1") self.putNumChild(0) else: self.putValue(" ") self.putNumChild(0) if isQMetaObject: with SubItem(self, "[raw]"): self.put('sortgroup="1"') self.putEmptyValue() self.putNumChild(1) if self.isExpanded(): with Children(self): putt("revision", revision) putt("classname", classname) putt("classinfo", classinfo) putt("methods", "%d %d" % (methodCount, methods)) putt("properties", "%d %d" % (propertyCount, properties)) putt("enums/sets", "%d %d" % (enumCount, enums)) putt("constructors", "%d %d" % (constructorCount, constructors)) putt("flags", flags) putt("signalCount", signalCount) for i in range(methodCount): t = self.extractStruct('IIIII', dataPtr + 56 + i * 20, 20) putt("method %d" % i, "%s %s %s %s %s" % t) if isQObject: with SubItem(self, "[extra]"): self.put('sortgroup="1"') self.putEmptyValue() self.putNumChild(1) if self.isExpanded(): with Children(self): if extraData: self.putTypedPointer("[extraData]", extraData, self.qtNamespace() + "QObjectPrivate::ExtraData") if connectionListsPtr: self.putTypedPointer("[connectionLists]", connectionListsPtr, self.qtNamespace() + "QObjectConnectionListVector") with SubItem(self, "[metaObject]"): self.putAddress(metaObjectPtr) self.putNumChild(1) if self.isExpanded(): with Children(self): self.putQObjectGutsHelper(0, 0, -1, metaObjectPtr, "QMetaObject") if isQMetaObject or isQObject: with SubItem(self, "[properties]"): self.put('sortgroup="5"') if self.isExpanded(): usesVector = self.qtVersion() >= 0x50700 dynamicPropertyCount = 0 with Children(self): # Static properties. for i in range(propertyCount): t = self.extractStruct("III", dataPtr + properties * 4 + 12 * i, 12) name = self.metaString(metaObjectPtr, t[0]) if qobject: self.putCallItem(name, qobject, "property", '"' + name + '"') else: putt(name, ' ') # Dynamic properties. if extraData: byteArrayType = self.lookupQtType("QByteArray") variantType = self.lookupQtType("QVariant") names = self.listChildrenGenerator(extraData + ptrSize, byteArrayType) if usesVector: values = self.vectorChildrenGenerator(extraData + 2 * ptrSize, variantType) else: values = self.listChildrenGenerator(extraData + 2 * ptrSize, variantType) for (k, v) in zip(names, values): with SubItem(self, propertyCount + dynamicPropertyCount): self.put('key="%s",' % self.encodeByteArray(k)) self.put('keyencoded="latin1",') self.putItem(v) dynamicPropertyCount += 1 self.putItemCount(propertyCount + dynamicPropertyCount) else: # We need a handle to [x] for the user to expand the item # before we know whether there are actual children. Counting # them is too expensive. self.putSpecialValue("minimumitemcount", propertyCount) self.putNumChild(1) superDataPtr = extractSuperDataPtr(metaObjectPtr) globalOffset = 0 superDataIterator = superDataPtr while superDataIterator: sdata = extractDataPtr(superDataIterator) globalOffset += self.extractInt(sdata + 16) # methodCount member superDataIterator = extractSuperDataPtr(superDataIterator) if isQMetaObject or isQObject: with SubItem(self, "[methods]"): self.put('sortgroup="3"') self.putItemCount(methodCount) if self.isExpanded(): with Children(self): for i in range(methodCount): t = self.extractStruct("IIIII", dataPtr + 56 + 20 * i, 20) name = self.metaString(metaObjectPtr, t[0]) with SubItem(self, i): self.putValue(name) self.putType(" ") self.putNumChild(1) isSignal = False flags = t[4] if flags == 0x06: typ = "signal" isSignal = True elif flags == 0x0a: typ = "slot" elif flags == 0x0a: typ = "invokable" else: typ = "" with Children(self): putt("[nameindex]", t[0]) putt("[type]", typ) putt("[argc]", t[1]) putt("[parameter]", t[2]) putt("[tag]", t[3]) putt("[flags]", t[4]) putt("[localindex]", str(i)) putt("[globalindex]", str(globalOffset + i)) if isQObject: with SubItem(self, "[d]"): self.put('sortgroup="15"') try: qobjectPrivateType = self.lookupQtType("QObjectPrivate") self.putItem(qobject["d_ptr"]["d"].dereference().cast(qobjectPrivateType)) except: try: self.putItem(qobject["d_ptr"]["d"]) except: self.putNumChild(0) self.putType(self.qtNamespace() + "QObjectData *") self.putSpecialValue("notaccessible") if isQMetaObject: with SubItem(self, "[superdata]"): self.put('sortgroup="12"') if superDataPtr: self.putType(self.qtNamespace() + "QMetaObject") self.putAddress(superDataPtr) self.putNumChild(1) if self.isExpanded(): with Children(self): self.putQObjectGutsHelper(0, 0, -1, superDataPtr, "QMetaObject") else: self.putType(self.qtNamespace() + "QMetaObject *") self.putValue("0x0") self.putNumChild(0) if handle >= 0: localIndex = int((handle - methods) / 5) with SubItem(self, "[localindex]"): self.put('sortgroup="12"') self.putValue(localIndex) with SubItem(self, "[globalindex]"): self.put('sortgroup="11"') self.putValue(globalOffset + localIndex) #with SubItem(self, "[signals]"): # self.putItemCount(signalCount) # signalNames = metaData(52, -14, 5) # warn("NAMES: %s" % signalNames) # if self.isExpanded(): # with Children(self): # putt("A", "b") # for i in range(signalCount): # k = signalNames[i] # with SubItem(self, k): # self.putEmptyValue() # if dd: # self.putQObjectConnections(dd) def putQObjectConnections(self, dd): with SubItem(self, "[connections]"): ptrSize = self.ptrSize() self.putNoType() ns = self.qtNamespace() privateTypeName = ns + "QObjectPrivate" privateType = self.lookupType(privateTypeName) d_ptr = dd.cast(privateType.pointer()).dereference() connections = d_ptr["connectionLists"] if self.isNull(connections): self.putItemCount(0) else: connections = connections.dereference() connections = connections.cast(self.directBaseClass(connections.type)) self.putSpecialValue("minimumitemcount", 0) self.putNumChild(1) if self.isExpanded(): pp = 0 with Children(self): innerType = self.templateArgument(connections.type, 0) # Should check: innerType == ns::QObjectPrivate::ConnectionList base = self.extractPointer(connections) data, size, alloc = self.vectorDataHelper(base) connectionType = self.lookupType(ns + "QObjectPrivate::Connection") for i in xrange(size): first = self.extractPointer(data + i * 2 * ptrSize) while first: self.putSubItem("%s" % pp, self.createPointerValue(first, connectionType)) first = self.extractPointer(first + 3 * ptrSize) # We need to enforce some upper limit. pp += 1 if pp > 1000: break def isKnownMovableType(self, typeName): if typeName in ( "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" ): return True return typeName == "QStringList" and self.qtVersion() >= 0x050000 def currentItemFormat(self, type = None): displayFormat = self.formats.get(self.currentIName, AutomaticFormat) if displayFormat == AutomaticFormat: if type is None: type = self.currentType.value needle = self.stripForFormat(str(type)) displayFormat = self.typeformats.get(needle, AutomaticFormat) return displayFormat def putArrayData(self, base, n, innerType, childNumChild = None, maxNumChild = 10000): addrBase = toInteger(base) innerSize = innerType.sizeof enc = self.simpleEncoding(innerType) if enc: self.put('childtype="%s",' % innerType) self.put('addrbase="0x%x",' % addrBase) self.put('addrstep="0x%x",' % innerSize) self.put('arrayencoding="%s",' % enc) if n > maxNumChild: self.put('childrenelided="%s",' % n) # FIXME: Act on that in frontend n = maxNumChild self.put('arraydata="') self.put(self.readMemory(addrBase, n * innerSize)) self.put('",') else: with Children(self, n, innerType, childNumChild, maxNumChild, addrBase=addrBase, addrStep=innerSize): for i in self.childRange(): self.putSubItem(i, self.createValue(addrBase + i * innerSize, innerType)) def putArrayItem(self, name, addr, n, typeName): with SubItem(self, name): self.putEmptyValue() self.putType("%s [%d]" % (typeName, n)) self.putArrayData(addr, n, self.lookupType(typeName)) self.putAddress(addr) def putPlotDataHelper(self, base, n, innerType, maxNumChild = 1000*1000): if n > maxNumChild: self.put('plotelided="%s",' % n) # FIXME: Act on that in frontend n = maxNumChild if self.currentItemFormat() == ArrayPlotFormat and self.isSimpleType(innerType): enc = self.simpleEncoding(innerType) if enc: self.putField("editencoding", enc) self.putDisplay("plotdata:separate", self.readMemory(base, n * innerType.sizeof)) def putPlotData(self, base, n, innerType, maxNumChild = 1000*1000): self.putPlotDataHelper(base, n, innerType, maxNumChild=maxNumChild) if self.isExpanded(): self.putArrayData(base, n, innerType, maxNumChild=maxNumChild) def putSpecialArgv(self, value): """ Special handling for char** argv. """ n = 0 p = value # p is 0 for "optimized out" cases. Or contains rubbish. try: if not self.isNull(p): while not self.isNull(p.dereference()) and n <= 100: p += 1 n += 1 except: pass with TopLevelItem(self, 'local.argv'): self.put('iname="local.argv",name="argv",') self.putItemCount(n, 100) self.putType('char **') if self.currentIName in self.expandedINames: p = value with Children(self, n): for i in xrange(n): self.putSubItem(i, p.dereference()) p += 1 def extractPointer(self, thing, offset = 0): if isinstance(thing, int): rawBytes = self.extractBlob(thing, self.ptrSize()).toBytes() elif sys.version_info[0] == 2 and isinstance(thing, long): rawBytes = self.extractBlob(thing, self.ptrSize()).toBytes() elif isinstance(thing, Blob): rawBytes = thing.toBytes() else: # Assume it's a (backend specific) Value. rawBytes = self.toBlob(thing).toBytes() code = "I" if self.ptrSize() == 4 else "Q" return struct.unpack_from(code, rawBytes, offset)[0] # Parses a..b and a.(s).b def parseRange(self, exp): # Search for the first unbalanced delimiter in s def searchUnbalanced(s, upwards): paran = 0 bracket = 0 if upwards: open_p, close_p, open_b, close_b = '(', ')', '[', ']' else: open_p, close_p, open_b, close_b = ')', '(', ']', '[' for i in range(len(s)): c = s[i] if c == open_p: paran += 1 elif c == open_b: bracket += 1 elif c == close_p: paran -= 1 if paran < 0: return i elif c == close_b: bracket -= 1 if bracket < 0: return i return len(s) match = re.search("(\.)(\(.+?\))?(\.)", exp) if match: s = match.group(2) left_e = match.start(1) left_s = 1 + left_e - searchUnbalanced(exp[left_e::-1], False) right_s = match.end(3) right_e = right_s + searchUnbalanced(exp[right_s:], True) template = exp[:left_s] + '%s' + exp[right_e:] a = exp[left_s:left_e] b = exp[right_s:right_e] try: # Allow integral expressions. ss = toInteger(self.parseAndEvaluate(s[1:len(s)-1]) if s else 1) aa = toInteger(self.parseAndEvaluate(a)) bb = toInteger(self.parseAndEvaluate(b)) if aa < bb and ss > 0: return True, aa, ss, bb + 1, template except: pass return False, 0, 1, 1, exp def putNumChild(self, numchild): if numchild != self.currentChildNumChild: self.put('numchild="%s",' % numchild) def handleWatches(self, args): for watcher in args.get("watchers", []): iname = watcher['iname'] exp = self.hexdecode(watcher['exp']) self.handleWatch(exp, exp, iname) def handleWatch(self, origexp, exp, iname): exp = str(exp).strip() escapedExp = self.hexencode(exp) #warn("HANDLING WATCH %s -> %s, INAME: '%s'" % (origexp, exp, iname)) # Grouped items separated by semicolon if exp.find(";") >= 0: exps = exp.split(';') n = len(exps) with TopLevelItem(self, iname): self.put('iname="%s",' % iname) #self.put('wname="%s",' % escapedExp) self.put('name="%s",' % exp) self.put('exp="%s",' % exp) self.putItemCount(n) self.putNoType() for i in xrange(n): self.handleWatch(exps[i], exps[i], "%s.%d" % (iname, i)) return # Special array index: e.g a[1..199] or a[1.(3).199] for stride 3. isRange, begin, step, end, template = self.parseRange(exp) if isRange: #warn("RANGE: %s %s %s in %s" % (begin, step, end, template)) r = range(begin, end, step) n = len(r) with TopLevelItem(self, iname): self.put('iname="%s",' % iname) #self.put('wname="%s",' % escapedExp) self.put('name="%s",' % exp) self.put('exp="%s",' % exp) self.putItemCount(n) self.putNoType() with Children(self, n): for i in r: e = template % i self.handleWatch(e, e, "%s.%s" % (iname, i)) return # Fall back to less special syntax #return self.handleWatch(origexp, exp, iname) with TopLevelItem(self, iname): self.put('iname="%s",' % iname) self.put('wname="%s",' % escapedExp) try: value = self.parseAndEvaluate(exp) self.putItem(value) except RuntimeError: self.currentType.value = " " self.currentValue.value = "" self.currentChildNumChild = -1 self.currentNumChild = 0 self.putNumChild(0) def registerDumper(self, funcname, function): try: if funcname.startswith("qdump__"): typename = funcname[7:] spec = inspect.getargspec(function) if len(spec.args) == 2: self.qqDumpers[typename] = function elif len(spec.args) == 3 and len(spec.defaults) == 1: self.qqDumpersEx[spec.defaults[0]] = function self.qqFormats[typename] = self.qqFormats.get(typename, []) elif funcname.startswith("qform__"): typename = funcname[7:] try: self.qqFormats[typename] = function() except: self.qqFormats[typename] = [] elif funcname.startswith("qedit__"): typename = funcname[7:] try: self.qqEditable[typename] = function except: pass except: pass def setupDumpers(self, _ = {}): self.resetCaches() for mod in self.dumpermodules: m = __import__(mod) dic = m.__dict__ for name in dic.keys(): item = dic[name] self.registerDumper(name, item) msg = "dumpers=[" for key, value in self.qqFormats.items(): editable = ',editable="true"' if key in self.qqEditable else '' formats = (',formats=\"%s\"' % str(value)[1:-1]) if len(value) else '' msg += '{type="%s"%s%s},' % (key, editable, formats) msg += '],' v = 10000 * sys.version_info[0] + 100 * sys.version_info[1] + sys.version_info[2] msg += 'python="%d"' % v return msg def reloadDumpers(self, args): for mod in self.dumpermodules: m = sys.modules[mod] if sys.version_info[0] >= 3: import importlib importlib.reload(m) else: reload(m) self.setupDumpers(args) def addDumperModule(self, args): path = args['path'] (head, tail) = os.path.split(path) sys.path.insert(1, head) self.dumpermodules.append(os.path.splitext(tail)[0]) def extractQStringFromQDataStream(self, buf, offset): """ Read a QString from the stream """ size = struct.unpack_from("!I", buf, offset)[0] offset += 4 string = buf[offset:offset + size].decode('utf-16be') return (string, offset + size) def extractQByteArrayFromQDataStream(self, buf, offset): """ Read a QByteArray from the stream """ size = struct.unpack_from("!I", buf, offset)[0] offset += 4 string = buf[offset:offset + size].decode('latin1') return (string, offset + size) def extractIntFromQDataStream(self, buf, offset): """ Read an int from the stream """ value = struct.unpack_from("!I", buf, offset)[0] return (value, offset + 4) def handleInterpreterMessage(self): """ Return True if inferior stopped """ resdict = self.fetchInterpreterResult() return resdict.get('event') == 'break' def reportInterpreterResult(self, resdict, args): print('interpreterresult=%s,token="%s"' % (self.resultToMi(resdict), args.get('token', -1))) def reportInterpreterAsync(self, resdict, asyncclass): print('interpreterasync=%s,asyncclass="%s"' % (self.resultToMi(resdict), asyncclass)) def removeInterpreterBreakpoint(self, args): res = self.sendInterpreterRequest('removebreakpoint', { 'id' : args['id'] }) return res def insertInterpreterBreakpoint(self, args): args['condition'] = self.hexdecode(args.get('condition', '')) # Will fail if the service is not yet up and running. response = self.sendInterpreterRequest('setbreakpoint', args) resdict = args.copy() bp = None if response is None else response.get("breakpoint", None) if bp: resdict['number'] = bp resdict['pending'] = 0 else: self.createResolvePendingBreakpointsHookBreakpoint(args) resdict['number'] = -1 resdict['pending'] = 1 resdict['warning'] = 'Direct interpreter breakpoint insertion failed.' self.reportInterpreterResult(resdict, args) def resolvePendingInterpreterBreakpoint(self, args): self.parseAndEvaluate('qt_qmlDebugEnableService("NativeQmlDebugger")') response = self.sendInterpreterRequest('setbreakpoint', args) bp = None if response is None else response.get("breakpoint", None) resdict = args.copy() if bp: resdict['number'] = bp resdict['pending'] = 0 else: resdict['number'] = -1 resdict['pending'] = 0 resdict['error'] = 'Pending interpreter breakpoint insertion failed.' self.reportInterpreterAsync(resdict, 'breakpointmodified') def fetchInterpreterResult(self): buf = self.parseAndEvaluate("qt_qmlDebugMessageBuffer") size = self.parseAndEvaluate("qt_qmlDebugMessageLength") msg = self.hexdecode(self.readMemory(buf, size)) # msg is a sequence of 'servicenamemsglenmsg' items. resdict = {} # Native payload. while len(msg): pos0 = msg.index(' ') # End of service name pos1 = msg.index(' ', pos0 + 1) # End of message length service = msg[0:pos0] msglen = int(msg[pos0+1:pos1]) msgend = pos1+1+msglen payload = msg[pos1+1:msgend] msg = msg[msgend:] if service == 'NativeQmlDebugger': try: resdict = json.loads(payload) continue except: warn("Cannot parse native payload: %s" % payload) else: print('interpreteralien=%s' % {'service': service, 'payload': self.hexencode(payload)}) try: expr = 'qt_qmlDebugClearBuffer()' res = self.parseAndEvaluate(expr) except RuntimeError as error: warn("Cleaning buffer failed: %s: %s" % (expr, error)) return resdict def sendInterpreterRequest(self, command, args = {}): encoded = json.dumps({ 'command': command, 'arguments': args }) hexdata = self.hexencode(encoded) expr = 'qt_qmlDebugSendDataToService("NativeQmlDebugger","%s")' % hexdata try: res = self.parseAndEvaluate(expr) except RuntimeError as error: warn("Interpreter command failed: %s: %s" % (encoded, error)) return {} except AttributeError as error: # Happens with LLDB and 'None' current thread. warn("Interpreter command failed: %s: %s" % (encoded, error)) return {} if not res: warn("Interpreter command failed: %s " % encoded) return {} return self.fetchInterpreterResult() def executeStep(self, args): if self.nativeMixed: response = self.sendInterpreterRequest('stepin', args) self.doContinue() def executeStepOut(self, args): if self.nativeMixed: response = self.sendInterpreterRequest('stepout', args) self.doContinue() def executeNext(self, args): if self.nativeMixed: response = self.sendInterpreterRequest('stepover', args) self.doContinue() def executeContinue(self, args): if self.nativeMixed: response = self.sendInterpreterRequest('continue', args) self.doContinue() def doInsertInterpreterBreakpoint(self, args, wasPending): #warn("DO INSERT INTERPRETER BREAKPOINT, WAS PENDING: %s" % wasPending) # Will fail if the service is not yet up and running. response = self.sendInterpreterRequest('setbreakpoint', args) bp = None if response is None else response.get("breakpoint", None) if wasPending: if not bp: self.reportInterpreterResult({'bpnr': -1, 'pending': 1, 'error': 'Pending interpreter breakpoint insertion failed.'}, args) return else: if not bp: self.reportInterpreterResult({'bpnr': -1, 'pending': 1, 'warning': 'Direct interpreter breakpoint insertion failed.'}, args) self.createResolvePendingBreakpointsHookBreakpoint(args) return self.reportInterpreterResult({'bpnr': bp, 'pending': 0}, args) def isInternalInterpreterFrame(self, functionName): if functionName is None: return False if functionName.startswith("qt_v4"): return True return functionName.startswith(self.qtNamespace() + "QV4::") # Hack to avoid QDate* dumper timeouts with GDB 7.4 on 32 bit # due to misaligned %ebx in SSE calls (qstring.cpp:findChar) def canCallLocale(self): return True def isReportableInterpreterFrame(self, functionName): return functionName and functionName.find("QV4::Moth::VME::exec") >= 0 def extractQmlData(self, value): if value.type.code == PointerCode: value = value.dereference() data = value["data"] return data.cast(self.lookupType(str(value.type).replace("QV4::", "QV4::Heap::"))) # Contains iname, name, and value. class LocalItem: pass def extractInterpreterStack(self): return self.sendInterpreterRequest('backtrace', {'limit': 10 })