diff --git a/share/qtcreator/debugger/python2/README.txt b/share/qtcreator/debugger/python2/README.txt new file mode 100644 index 00000000000..70e03d52ca9 --- /dev/null +++ b/share/qtcreator/debugger/python2/README.txt @@ -0,0 +1,8 @@ + +This is a copy of a previous version of share/qtcreator/debugger/*.py +which supported Python 2 and Python 3 based debugger backends (gdb, +lldb) at the same time. + +The code there is now Python-3-only. This copy here is not meant to be +used in general but could perhaps be used to replace the main code +in situations that cannot use Python 3 yet. diff --git a/share/qtcreator/debugger/python2/android_stdtypes.py b/share/qtcreator/debugger/python2/android_stdtypes.py new file mode 100644 index 00000000000..8b25d7bb358 --- /dev/null +++ b/share/qtcreator/debugger/python2/android_stdtypes.py @@ -0,0 +1,133 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + + +from dumper import DumperBase +from utils import DisplayFormat + +import stdtypes +import libcpp_stdtypes + + +def qform__std____ndk1__array(): + return [DisplayFormat.ArrayPlot] + + +def qdump__std____ndk1__array(d, value): + stdtypes.qdump__std__array(d, value) + + +def qdump__std____ndk1__complex(d, value): + stdtypes.qdump__std__complex(d, value) + + +def qdump__std____ndk1__deque(d, value): + stdtypes.qdumpHelper__std__deque__libcxx(d, value) + + +def qdump__std____ndk1__list(d, value): + return libcpp_stdtypes.qdump__std____1__list(d, value) + + +def qdump__std____ndk1__set(d, value): + return libcpp_stdtypes.qdump__std____1__set(d, value) + + +def qdump__std____ndk1__multiset(d, value): + qdump__std____ndk1__set(d, value) + + +def qform__std____ndk1__map(): + return [DisplayFormat.CompactMap] + + +def qdump__std____ndk1__map(d, value): + return libcpp_stdtypes.qdump__std____1__map(d, value) + + +def qform__std____ndk1__multimap(): + return [DisplayFormat.CompactMap] + + +def qdump__std____ndk1__multimap(d, value): + qdump__std____ndk1__map(d, value) + + +def qdump__std____ndk1__map__iterator(d, value): + return libcpp_stdtypes.qdump__std____1__map__iterator(d, value) + + +def qdump__std____ndk1__map__const_iterator(d, value): + qdump__std____ndk1__map__iterator(d, value) + + +def qdump__std____ndk1__set__iterator(d, value): + return libcpp_stdtypes.qdump__std____1__set__iterator(d, value) + + +def qdump__std____ndk1__set_const_iterator(d, value): + qdump__std____ndk1__set__iterator(d, value) + + +def qdump__std____ndk1__stack(d, value): + return libcpp_stdtypes.qdump__std____1__stack(d, value) + + +def qdump__std____ndk1__string(d, value): + return libcpp_stdtypes.qdump__std____1__string(d, value) + + +def qdump__std____ndk1__wstring(d, value): + return libcpp_stdtypes.qdump__std____1__wstring(d, value) + + +def qdump__std____ndk1__basic_string(d, value): + return libcpp_stdtypes.qdump__std____1__basic_string(d, value) + + +def qdump__std____ndk1__shared_ptr(d, value): + return libcpp_stdtypes.qdump__std____1__shared_ptr(d, value) + + +def qdump__std____ndk1__weak_ptr(d, value): + return qdump__std____ndk1__shared_ptr(d, value) + + +def qdump__std____ndk1__unique_ptr(d, value): + stdtypes.qdump__std__unique_ptr(d, value) + + +def qform__std____ndk1__unordered_map(): + return [DisplayFormat.CompactMap] + + +def qdump__std____ndk1__unordered_map(d, value): + libcpp_stdtypes.qdump__std____1__unordered_map(d, value) + + +def qdump__std____ndk1__unordered_set(d, value): + return libcpp_stdtypes.qdump__std____1__unordered_set(d, value) + + +def qdump__std____ndk1__unordered_multiset(d, value): + qdump__std____ndk1__unordered_set(d, value) + + +def qform__std____ndk1__valarray(): + return [DisplayFormat.ArrayPlot] + + +def qdump__std____ndk1__valarray(d, value): + return libcpp_stdtypes.qdump__std____1__valarray(d, value) + + +def qform__std____ndk1__vector(): + return [DisplayFormat.ArrayPlot] + + +def qdump__std____ndk1__vector(d, value): + stdtypes.qdumpHelper__std__vector__libcxx(d, value) + + +def qdump__std____ndk1__once_flag(d, value): + stdtypes.qdump__std__once_flag(d, value) diff --git a/share/qtcreator/debugger/python2/boosttypes.py b/share/qtcreator/debugger/python2/boosttypes.py new file mode 100644 index 00000000000..5eb0c1a1be9 --- /dev/null +++ b/share/qtcreator/debugger/python2/boosttypes.py @@ -0,0 +1,184 @@ +# Copyright (C) 2016 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +from utils import DisplayFormat +from dumper import Children + + +def qdump__boost__bimaps__bimap(d, value): + #leftType = value.type[0] + #rightType = value.type[1] + size = value["core"]["node_count"].integer() + d.putItemCount(size) + if d.isExpanded(): + d.putPlainChildren(value) + + +def qdump__boost__optional(d, value): + innerType = value.type[0] + (initialized, pad, payload) = d.split('b@{%s}' % innerType.name, value) + if initialized: + d.putItem(payload) + d.putBetterType(value.type) + else: + d.putSpecialValue("uninitialized") + + +def qdump__boost__shared_ptr(d, value): + # s boost::shared_ptr + # px 0x0 int * + # pn boost::detail::shared_count + # pi_ 0x0 boost::detail::sp_counted_base * + (px, pi) = value.split("pp") + if pi == 0: + d.putValue("(null)") + return + + if px == 0: + d.putValue("(null)") + return + + (vptr, usecount, weakcount) = d.split('pii', pi) + d.check(weakcount >= 0) + d.check(weakcount <= usecount) + d.check(usecount <= 10 * 1000 * 1000) + d.putItem(d.createValue(px, value.type[0])) + d.putBetterType(value.type) + + +def qdump__boost__container__list(d, value): + try: + m_icont = value["m_icont"] + except: + m_icont = value["members_"]["m_icont"] + r = m_icont["data_"]["root_plus_size_"] + n = r["size_"].integer() + d.putItemCount(n) + if d.isExpanded(): + innerType = value.type[0] + offset = 2 * d.ptrSize() + with Children(d, n): + try: + root = r["root_"] + except: + root = r["m_header"] + p = root["next_"].extractPointer() + for i in d.childRange(): + d.putSubItem(i, d.createValue(p + offset, innerType)) + p = d.extractPointer(p) + + +def qform__boost__container__vector(): + return [DisplayFormat.ArrayPlot] + + +def qdump__boost__container__vector(d, value): + holder = value["m_holder"] + size = holder["m_size"].integer() + d.putItemCount(size) + + if d.isExpanded(): + T = value.type[0] + try: + start = holder["m_start"].pointer() + except: + start = holder["storage"].address() + d.putPlotData(start, size, T) + + +def qform__boost__container__static_vector(): + return [DisplayFormat.ArrayPlot] + + +def qdump__boost__container__static_vector(d, value): + qdump__boost__container__vector(d, value) + + +def qform__boost__container__small_vector(): + return [DisplayFormat.ArrayPlot] + + +def qdump__boost__container__small_vector(d, value): + qdump__boost__container__vector(d, value) + + +def qdump__boost__gregorian__date(d, value): + d.putValue(value.integer(), "juliandate") + + +def qdump__boost__posix_time__ptime(d, value): + ms = int(value.integer() / 1000) + d.putValue("%s/%s" % divmod(ms, 86400000), "juliandateandmillisecondssincemidnight") + + +def qdump__boost__posix_time__time_duration(d, value): + d.putValue(int(value.integer() / 1000), "millisecondssincemidnight") + + +def qdump__boost__unordered__unordered_set(d, value): + innerType = value.type[0] + if value.type.size() == 7 * d.ptrSize(): # 56 for boost 1.79+ + bases, bucketCount, bcountLog2, size, mlf, maxload, buckets = value.split('ttttttp') + forward = True + elif value.type.size() == 6 * d.ptrSize(): # 48 for boost 1.55+ + # boost 1.58 or 1.55 + # bases are 3? bytes, and mlf is actually a float, but since + # its followed by size_t maxload, it's # effectively padded to a size_t + bases, bucketCount, size, mlf, maxload, buckets = value.split('tttttp') + # Distinguish 1.58 and 1.55. 1.58 used one template argument, 1.55 two. + try: + ittype = d.lookupType(value.type.name + '::iterator').target() + forward = len(ittype.templateArguments()) == 1 + except: + forward = True + elif value.type.size() == 5 * d.ptrSize(): # 40 for boost 1.48 + # boost 1.48 + # Values are stored before the next pointers. Determine the offset. + buckets, bucketCount, size, mlf, maxload = value.split('ptttt') + forward = False + else: + raise Exception("Unknown boost::unordered_set layout") + + if forward: + # boost >= 1.58 + code = 'pp{%s}' % innerType.name + + def children(p): + while True: + p, dummy, val = d.split(code, p) + yield val + else: + # boost 1.48 or 1.55 + code = '{%s}@p' % innerType.name + (pp, ssize, fields) = d.describeStruct(code) + offset = fields[2].offset() + + def children(p): + while True: + val, pad, p = d.split(code, p - offset) + yield val + + p = d.extractPointer(buckets + bucketCount * d.ptrSize()) + d.putItems(size, children(p), maxNumChild=10000) + + +def qdump__boost__variant(d, value): + allTypes = value.type.templateArguments() + realType = allTypes[value.split('i')[0]] + alignment = max([t.alignment() for t in allTypes]) + dummy, val = value.split('%is{%s}' % (max(4, alignment), realType.name)) + d.putItem(val) + d.putBetterType(value.type) + + +def qdump__boost__container__devector(d, value): + inner_type = value.type[0] + buffer = value["m_"]["buffer"].pointer() + front_idx = value["m_"]["front_idx"].integer() + back_idx = value["m_"]["back_idx"].integer() + start = buffer + (front_idx * inner_type.size()) + size = int(back_idx - front_idx) + if size > 0: + d.checkPointer(start) + d.putItemCount(size) + d.putPlotData(start, size, inner_type) diff --git a/share/qtcreator/debugger/python2/cdbbridge.py b/share/qtcreator/debugger/python2/cdbbridge.py new file mode 100644 index 00000000000..a9d86e24ee1 --- /dev/null +++ b/share/qtcreator/debugger/python2/cdbbridge.py @@ -0,0 +1,514 @@ +# Copyright (C) 2016 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import inspect +import os +import sys +import cdbext +import re +import threading +from utils import TypeCode + +sys.path.insert(1, os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))) + +from dumper import DumperBase, SubItem + + +class FakeVoidType(cdbext.Type): + def __init__(self, name, dumper): + cdbext.Type.__init__(self) + self.typeName = name.strip() + self.dumper = dumper + + def name(self): + return self.typeName + + def bitsize(self): + return self.dumper.ptrSize() * 8 + + def code(self): + if self.typeName.endswith('*'): + return TypeCode.Pointer + if self.typeName.endswith(']'): + return TypeCode.Array + return TypeCode.Void + + def unqualified(self): + return self + + def target(self): + code = self.code() + if code == TypeCode.Pointer: + return FakeVoidType(self.typeName[:-1], self.dumper) + if code == TypeCode.Void: + return self + try: + return FakeVoidType(self.typeName[:self.typeName.rindex('[')], self.dumper) + except: + return FakeVoidType('void', self.dumper) + + def targetName(self): + return self.target().name() + + def arrayElements(self): + try: + return int(self.typeName[self.typeName.rindex('[') + 1:self.typeName.rindex(']')]) + except: + return 0 + + def stripTypedef(self): + return self + + def fields(self): + return [] + + def templateArgument(self, pos, numeric): + return None + + def templateArguments(self): + return [] + + +class Dumper(DumperBase): + def __init__(self): + DumperBase.__init__(self) + self.outputLock = threading.Lock() + self.isCdb = True + + def enumValue(self, nativeValue): + val = nativeValue.nativeDebuggerValue() + # remove '0n' decimal prefix of the native cdb value output + return val.replace('(0n', '(') + + def fromNativeValue(self, nativeValue): + self.check(isinstance(nativeValue, cdbext.Value)) + val = self.Value(self) + val.name = nativeValue.name() + val._type = self.fromNativeType(nativeValue.type()) + # There is no cdb api for the size of bitfields. + # Workaround this issue by parsing the native debugger text for integral types. + if val._type.code == TypeCode.Integral: + try: + integerString = nativeValue.nativeDebuggerValue() + except UnicodeDecodeError: + integerString = '' # cannot decode - read raw + if integerString == 'true': + val.ldata = int(1).to_bytes(1, byteorder='little') + elif integerString == 'false': + val.ldata = int(0).to_bytes(1, byteorder='little') + else: + integerString = integerString.replace('`', '') + integerString = integerString.split(' ')[0] + if integerString.startswith('0n'): + integerString = integerString[2:] + base = 10 + elif integerString.startswith('0x'): + base = 16 + else: + base = 10 + signed = not val._type.name.startswith('unsigned') + try: + val.ldata = int(integerString, base).to_bytes(val._type.size(), + byteorder='little', signed=signed) + except: + # read raw memory in case the integerString can not be interpreted + pass + if val._type.code == TypeCode.Enum: + val.ldisplay = self.enumValue(nativeValue) + val.isBaseClass = val.name == val._type.name + val.nativeValue = nativeValue + val.laddress = nativeValue.address() + val.lbitsize = nativeValue.bitsize() + return val + + def nativeTypeId(self, nativeType): + self.check(isinstance(nativeType, cdbext.Type)) + name = nativeType.name() + if name is None or len(name) == 0: + c = '0' + elif name == 'struct {...}': + c = 's' + elif name == 'union {...}': + c = 'u' + else: + return name + typeId = c + ''.join(['{%s:%s}' % (f.name(), self.nativeTypeId(f.type())) + for f in nativeType.fields()]) + return typeId + + def fromNativeType(self, nativeType): + self.check(isinstance(nativeType, cdbext.Type)) + typeId = self.nativeTypeId(nativeType) + if self.typeData.get(typeId, None) is not None: + return self.Type(self, typeId) + + if nativeType.name().startswith('void'): + nativeType = FakeVoidType(nativeType.name(), self) + + code = nativeType.code() + if code == TypeCode.Pointer: + if nativeType.name().startswith(''): + code = TypeCode.Function + elif nativeType.targetName() != nativeType.name(): + targetType = self.lookupType(nativeType.targetName(), nativeType.moduleId()) + if targetType is not None and targetType is not nativeType: + return self.createPointerType(targetType) + + if code == TypeCode.Array: + # cdb reports virtual function tables as arrays those ar handled separetly by + # the DumperBase. Declare those types as structs prevents a lookup to a + # none existing type + if not nativeType.name().startswith('__fptr()') and not nativeType.name().startswith(' align else align + align = 1 + for f in nativeType.fields(): + align = handleItem(f.type(), align) + return align + + def nativeTypeEnumDisplay(self, nativeType, intval, form): + value = self.nativeParseAndEvaluate('(%s)%d' % (nativeType.name(), intval)) + if value is None: + return '' + return self.enumValue(value) + + def enumExpression(self, enumType, enumValue): + ns = self.qtNamespace() + return ns + "Qt::" + enumType + "(" \ + + ns + "Qt::" + enumType + "::" + enumValue + ")" + + def pokeValue(self, typeName, *args): + return None + + def parseAndEvaluate(self, exp): + return self.fromNativeValue(self.nativeParseAndEvaluate(exp)) + + def nativeParseAndEvaluate(self, exp): + return cdbext.parseAndEvaluate(exp) + + def isWindowsTarget(self): + return True + + def isQnxTarget(self): + return False + + def isArmArchitecture(self): + return False + + def isMsvcTarget(self): + return True + + def qtCoreModuleName(self): + modules = cdbext.listOfModules() + # first check for an exact module name match + for coreName in ['Qt6Core', 'Qt6Cored', 'Qt5Cored', 'Qt5Core', 'QtCored4', 'QtCore4']: + if coreName in modules: + self.qtCoreModuleName = lambda: coreName + return coreName + # maybe we have a libinfix build. + for pattern in ['Qt6Core.*', 'Qt5Core.*', 'QtCore.*']: + matches = [module for module in modules if re.match(pattern, module)] + if matches: + coreName = matches[0] + self.qtCoreModuleName = lambda: coreName + return coreName + return None + + def qtDeclarativeModuleName(self): + modules = cdbext.listOfModules() + for declarativeModuleName in ['Qt6Qmld', 'Qt6Qml', 'Qt5Qmld', 'Qt5Qml']: + if declarativeModuleName in modules: + self.qtDeclarativeModuleName = lambda: declarativeModuleName + return declarativeModuleName + matches = [module for module in modules if re.match('Qt[56]Qml.*', module)] + if matches: + declarativeModuleName = matches[0] + self.qtDeclarativeModuleName = lambda: declarativeModuleName + return declarativeModuleName + return None + + def qtHookDataSymbolName(self): + hookSymbolName = 'qtHookData' + coreModuleName = self.qtCoreModuleName() + if coreModuleName is not None: + hookSymbolName = '%s!%s%s' % (coreModuleName, self.qtNamespace(), hookSymbolName) + else: + resolved = cdbext.resolveSymbol('*' + hookSymbolName) + if resolved: + hookSymbolName = resolved[0] + else: + hookSymbolName = '*%s' % hookSymbolName + self.qtHookDataSymbolName = lambda: hookSymbolName + return hookSymbolName + + def qtDeclarativeHookDataSymbolName(self): + hookSymbolName = 'qtDeclarativeHookData' + declarativeModuleName = self.qtDeclarativeModuleName() + if declarativeModuleName is not None: + hookSymbolName = '%s!%s%s' % (declarativeModuleName, self.qtNamespace(), hookSymbolName) + else: + resolved = cdbext.resolveSymbol('*' + hookSymbolName) + if resolved: + hookSymbolName = resolved[0] + else: + hookSymbolName = '*%s' % hookSymbolName + + self.qtDeclarativeHookDataSymbolName = lambda: hookSymbolName + return hookSymbolName + + def qtNamespace(self): + namespace = '' + qstrdupSymbolName = '*qstrdup' + coreModuleName = self.qtCoreModuleName() + if coreModuleName is not None: + qstrdupSymbolName = '%s!%s' % (coreModuleName, qstrdupSymbolName) + resolved = cdbext.resolveSymbol(qstrdupSymbolName) + if resolved: + name = resolved[0].split('!')[1] + namespaceIndex = name.find('::') + if namespaceIndex > 0: + namespace = name[:namespaceIndex + 2] + self.qtCustomEventFunc = self.parseAndEvaluate( + '%s!%sQObject::customEvent' % + (self.qtCoreModuleName(), namespace)).address() + self.qtNamespace = lambda: namespace + return namespace + + def qtVersion(self): + qtVersion = None + try: + qtVersion = self.parseAndEvaluate( + '((void**)&%s)[2]' % self.qtHookDataSymbolName()).integer() + except: + if self.qtCoreModuleName() is not None: + try: + versionValue = cdbext.call(self.qtCoreModuleName() + '!qVersion()') + version = self.extractCString(self.fromNativeValue(versionValue).address()) + (major, minor, patch) = version.decode('latin1').split('.') + qtVersion = 0x10000 * int(major) + 0x100 * int(minor) + int(patch) + except: + pass + if qtVersion is None: + qtVersion = self.fallbackQtVersion + self.qtVersion = lambda: qtVersion + return qtVersion + + def putVtableItem(self, address): + funcName = cdbext.getNameByAddress(address) + if funcName is None: + self.putItem(self.createPointerValue(address, 'void')) + else: + self.putValue(funcName) + self.putType('void*') + self.putAddress(address) + + def putVTableChildren(self, item, itemCount): + p = item.address() + for i in range(itemCount): + deref = self.extractPointer(p) + if deref == 0: + n = i + break + with SubItem(self, i): + self.putVtableItem(deref) + p += self.ptrSize() + return itemCount + + def ptrSize(self): + size = cdbext.pointerSize() + self.ptrSize = lambda: size + return size + + def stripQintTypedefs(self, typeName): + if typeName.startswith('qint'): + prefix = '' + size = typeName[4:] + elif typeName.startswith('quint'): + prefix = 'unsigned ' + size = typeName[5:] + else: + return typeName + if size == '8': + return '%schar' % prefix + elif size == '16': + return '%sshort' % prefix + elif size == '32': + return '%sint' % prefix + elif size == '64': + return '%sint64' % prefix + else: + return typeName + + def lookupType(self, typeNameIn, module=0): + if len(typeNameIn) == 0: + return None + typeName = self.stripQintTypedefs(typeNameIn) + if self.typeData.get(typeName, None) is None: + nativeType = self.lookupNativeType(typeName, module) + if nativeType is None: + return None + _type = self.fromNativeType(nativeType) + if _type.typeId != typeName: + self.registerTypeAlias(_type.typeId, typeName) + return _type + return self.Type(self, typeName) + + def lookupNativeType(self, name, module=0): + if name.startswith('void'): + return FakeVoidType(name, self) + return cdbext.lookupType(name, module) + + def reportResult(self, result, args): + cdbext.reportResult('result={%s}' % result) + + def readRawMemory(self, address, size): + mem = cdbext.readRawMemory(address, size) + if len(mem) != size: + raise Exception("Invalid memory request: %d bytes from 0x%x" % (size, address)) + return mem + + def findStaticMetaObject(self, type): + typeName = type.name + if type.moduleName is not None: + typeName = type.moduleName + '!' + typeName + ptr = cdbext.getAddressByName(typeName + '::staticMetaObject') + return ptr + + def warn(self, msg): + self.put('{name="%s",value="",type="",numchild="0"},' % msg) + + def fetchVariables(self, args): + self.resetStats() + (ok, res) = self.tryFetchInterpreterVariables(args) + if ok: + self.reportResult(res, args) + return + + self.setVariableFetchingOptions(args) + + self.output = [] + + self.currentIName = 'local' + self.put('data=[') + self.anonNumber = 0 + + variables = [] + for val in cdbext.listOfLocals(self.partialVariable): + dumperVal = self.fromNativeValue(val) + dumperVal.lIsInScope = dumperVal.name not in self.uninitialized + variables.append(dumperVal) + + self.handleLocals(variables) + self.handleWatches(args) + + self.put('],partial="%d"' % (len(self.partialVariable) > 0)) + self.put(',timings=%s' % self.timings) + + if self.forceQtNamespace: + self.qtNamespaceToReport = self.qtNamespace() + + if self.qtNamespaceToReport: + self.put(',qtnamespace="%s"' % self.qtNamespaceToReport) + self.qtNamespaceToReport = None + + self.reportResult(''.join(self.output), args) + self.output = [] + + def report(self, stuff): + sys.stdout.write(stuff + "\n") + + def findValueByExpression(self, exp): + return cdbext.parseAndEvaluate(exp) + + def nativeDynamicTypeName(self, address, baseType): + return None # Does not work with cdb + + def nativeValueDereferenceReference(self, value): + return self.nativeValueDereferencePointer(value) + + def nativeValueDereferencePointer(self, value): + def nativeVtCastValue(nativeValue): + # If we have a pointer to a derived instance of the pointer type cdb adds a + # synthetic '__vtcast_' member as the first child + if nativeValue.hasChildren(): + vtcastCandidate = nativeValue.childFromIndex(0) + vtcastCandidateName = vtcastCandidate.name() + if vtcastCandidateName.startswith('__vtcast_'): + # found a __vtcast member + # make sure that it is not an actual field + for field in nativeValue.type().fields(): + if field.name() == vtcastCandidateName: + return None + return vtcastCandidate + return None + + nativeValue = value.nativeValue + if nativeValue is None: + if not self.isExpanded(): + raise Exception("Casting not expanded values is to expensive") + nativeValue = self.nativeParseAndEvaluate('(%s)0x%x' % (value.type.name, value.pointer())) + castVal = nativeVtCastValue(nativeValue) + if castVal is not None: + val = self.fromNativeValue(castVal) + else: + val = self.Value(self) + val.laddress = value.pointer() + val._type = value.type.dereference() + val.nativeValue = value.nativeValue + + return val + + def callHelper(self, rettype, value, function, args): + raise Exception("cdb does not support calling functions") + + def nameForCoreId(self, id): + for dll in ['Utilsd', 'Utils']: + idName = cdbext.call('%s!Utils::nameForId(%d)' % (dll, id)) + if idName is not None: + break + return self.fromNativeValue(idName) + + def putCallItem(self, name, rettype, value, func, *args): + return + + def symbolAddress(self, symbolName): + res = self.nativeParseAndEvaluate(symbolName) + return None if res is None else res.address() diff --git a/share/qtcreator/debugger/python2/creatortypes.py b/share/qtcreator/debugger/python2/creatortypes.py new file mode 100644 index 00000000000..24c16ce0094 --- /dev/null +++ b/share/qtcreator/debugger/python2/creatortypes.py @@ -0,0 +1,371 @@ +# Copyright (C) 2016 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +from dumper import Children + + +def typeTarget(type): + target = type.target() + if target: + return target + return type + + +def stripTypeName(value): + return typeTarget(value.type).unqualified().name + + +def extractPointerType(d, value): + postfix = "" + while stripTypeName(value) == "CPlusPlus::PointerType": + postfix += "*" + value = value["_elementType"]["_type"] + try: + return readLiteral(d, value["_name"]) + postfix + except: + typeName = typeTarget(value.type.unqualified()).name + if typeName == "CPlusPlus::IntegerType": + return "int" + postfix + elif typeName == "CPlusPlus::VoidType": + return "void" + postfix + return "" + + +def readTemplateName(d, value): + name = readLiteral(d, value["_identifier"]) + "<" + args = value["_templateArguments"] + impl = args["_M_impl"] + start = impl["_M_start"] + size = impl["_M_finish"] - start + try: + d.check(0 <= size and size <= 100) + d.checkPointer(start) + for i in range(int(size)): + if i > 0: + name += ", " + name += extractPointerType(d, start[i]["_type"]) + except: + return "" + name += ">" + return name + + +def readLiteral(d, value): + if not value.integer(): + return "" + type = typeTarget(value.type.unqualified()) + if type and (type.name == "CPlusPlus::TemplateNameId"): + return readTemplateName(d, value) + elif type and (type.name == "CPlusPlus::QualifiedNameId"): + return readLiteral(d, value["_base"]) + "::" + readLiteral(d, value["_name"]) + try: + return bytes(d.readRawMemory(value["_chars"], value["_size"])).decode('latin1') + except: + return "" + + +def dumpLiteral(d, value): + d.putValue(d.hexencode(readLiteral(d, value)), "latin1") + + +def qdump__Utils__Id(d, value): + val = value.extractPointer() + if True: + if d.isMsvcTarget(): + name = d.nameForCoreId(val).address() + else: + name = d.parseAndEvaluate("Utils::nameForId(0x%x)" % val).pointer() + d.putSimpleCharArray(name) + else: + d.putValue(val) + d.putPlainChildren(value) + + +def qdump__Utils__Key(d, value): + d.putByteArrayValue(value["data"]) + d.putBetterType(value.type) + + +def qdump__Debugger__Internal__GdbMi(d, value): + val = d.encodeString(value["m_name"]) + "3a002000" \ + + d.encodeString(value["m_data"]) + d.putValue(val, "utf16") + d.putPlainChildren(value) + + +def qdump__Debugger__Internal__DisassemblerLine(d, value): + d.putByteArrayValue(value["m_data"]) + d.putPlainChildren(value) + + +def qdump__Debugger__Internal__WatchData(d, value): + d.putStringValue(value["iname"]) + d.putPlainChildren(value) + + +def qdump__Debugger__Internal__WatchItem(d, value): + d.putStringValue(value["iname"]) + d.putPlainChildren(value) + + +def qdump__Debugger__Internal__BreakpointModelId(d, value): + d.putValue("%s.%s" % (value["m_majorPart"].integer(), value["m_minorPart"].integer())) + d.putPlainChildren(value) + + +def qdump__Debugger__Internal__ThreadId(d, value): + d.putValue("%s" % value["m_id"]) + d.putPlainChildren(value) + + +def qdump__CPlusPlus__ByteArrayRef(d, value): + d.putSimpleCharArray(value["m_start"], value["m_length"]) + d.putPlainChildren(value) + + +def qdump__CPlusPlus__Identifier(d, value): + try: + d.putSimpleCharArray(value["_chars"], value["_size"]) + except: + pass + d.putPlainChildren(value) + + +def qdump__CPlusPlus__Symbol(d, value): + dumpLiteral(d, value["_name"]) + d.putBetterType(value.type) + d.putPlainChildren(value) + + +def qdump__CPlusPlus__Class(d, value): + qdump__CPlusPlus__Symbol(d, value) + + +def kindName(d, value): + e = value.integer() + if e: + kindType = d.lookupType("CPlusPlus::Kind") + return kindType.tdata.enumDisplay(e, value.address(), '%d')[11:] + else: + return '' + + +def qdump__CPlusPlus__IntegerType(d, value): + d.putValue(kindName(d, value["_kind"])) + d.putPlainChildren(value) + + +def qdump__CPlusPlus__FullySpecifiedType(d, value): + type = value["_type"] + typeName = stripTypeName(type) + if typeName == "CPlusPlus::NamedType": + dumpLiteral(d, type["_name"]) + elif typeName == "CPlusPlus::PointerType": + d.putValue(d.hexencode(extractPointerType(d, type)), "latin1") + d.putPlainChildren(value) + + +def qdump__CPlusPlus__NamedType(d, value): + dumpLiteral(d, value["_name"]) + d.putBetterType(value.type) + d.putPlainChildren(value) + + +def qdump__CPlusPlus__PointerType(d, value): + d.putValue(d.hexencode(extractPointerType(d, value)), "latin1") + d.putPlainChildren(value) + + +def qdump__CPlusPlus__TemplateNameId(d, value): + dumpLiteral(d, value) + d.putBetterType(value.type) + d.putPlainChildren(value) + + +def qdump__CPlusPlus__QualifiedNameId(d, value): + dumpLiteral(d, value) + d.putPlainChildren(value) + + +def qdump__CPlusPlus__Literal(d, value): + dumpLiteral(d, value) + d.putPlainChildren(value) + + +def qdump__CPlusPlus__StringLiteral(d, value): + d.putSimpleCharArray(value["_chars"], value["_size"]) + d.putPlainChildren(value) + + +def qdump__CPlusPlus__Internal__Value(d, value): + d.putValue(value["l"]) + d.putPlainChildren(value) + + +def qdump__Utils__FilePath(d, value): + data, path_len, scheme_len, host_len = d.split("{@QString}IHH", value) + elided, enc = d.encodeStringHelper(data, d.displayStringLimit) + # enc is concatenated path + scheme + host + if scheme_len: + scheme_pos = path_len * 4 + host_pos = scheme_pos + scheme_len * 4 + path_enc = enc[0 : path_len * 4] + scheme_enc = enc[scheme_pos : scheme_pos + scheme_len * 4] + host_enc = enc[host_pos : host_pos + host_len * 4] + slash = "2F00" + dot = "2E00" + colon = "3A00" + val = scheme_enc + colon + slash + slash + host_enc + if not path_enc.startswith(slash): + val += slash + dot + slash + val += path_enc + else: + val = enc + d.putValue(val, "utf16", elided=elided) + d.putPlainChildren(value) + + +def qdump__Utils__FileName(d, value): + qdump__Utils__FilePath(d, value) + + +def qdump__Utils__ElfSection(d, value): + d.putByteArrayValue(value["name"]) + d.putPlainChildren(value) + + +def qdump__Utils__Port(d, value): + d.putValue(d.extractInt(value)) + d.putPlainChildren(value) + + + +def x_qdump__Utils__Environment(d, value): + qdump__Utils__NameValueDictionary(d, value) + + +def qdump__Utils__DictKey(d, value): + d.putStringValue(value["name"]) + + +def x_qdump__Utils__NameValueDictionary(d, value): + dptr = d.extractPointer(value) + if d.qtVersion() >= 0x60000: + if dptr == 0: + d.putItemCount(0) + return + m = value['d']['d']['m'] + d.putItem(m) + d.putBetterType('Utils::NameValueDictionary') + else: # Qt5 + (ref, n) = d.split('ii', dptr) + d.check(0 <= n and n <= 100 * 1000 * 1000) + d.check(-1 <= ref and ref < 100000) + + d.putItemCount(n) + if d.isExpanded(): + if n > 10000: + n = 10000 + + typeCode = 'ppp@{%s}@{%s}' % ("Utils::DictKey", "@QPair<@QString,bool>") + + def helper(node): + (p, left, right, padding1, key, padding2, value) = d.split(typeCode, node) + if left: + for res in helper(left): + yield res + yield (key["name"], value) + if right: + for res in helper(right): + yield res + + with Children(d, n): + for (pair, i) in zip(helper(dptr + 8), range(n)): + d.putPairItem(i, pair, 'key', 'value') + + +def qdump__Utf8String(d, value): + d.putByteArrayValue(value['byteArray']) + d.putPlainChildren(value) + + +def qdump__CPlusPlus__Token(d, value): + k = value["f"]["kind"] + e = k.lvalue + type = kindName(d, k) + try: + if e == 6: + type = readLiteral(d, value["identifier"]) + " (%s)" % type + elif e >= 7 and e <= 23: + type = readLiteral(d, value["literal"]) + " (%s)" % type + except: + pass + d.putValue(type) + d.putPlainChildren(value) + + +def qdump__CPlusPlus__Internal__PPToken(d, value): + data, size, alloc = d.qArrayData(value["m_src"]) + length = value["f"]["utf16chars"].integer() + offset = value["utf16charOffset"].integer() + #DumperBase.warn("size: %s, alloc: %s, offset: %s, length: %s, data: %s" + # % (size, alloc, offset, length, data)) + d.putValue(d.readMemory(data + offset, min(100, length)), "latin1") + d.putPlainChildren(value) + + +def qdump__ProString(d, value): + try: + s = value["m_string"] + data, size, alloc = d.stringData(s) + data += 2 * value["m_offset"].integer() + size = value["m_length"].integer() + s = d.readMemory(data, 2 * size) + d.putValue(s, "utf16") + except: + d.putEmptyValue() + d.putPlainChildren(value) + + +def qdump__ProKey(d, value): + qdump__ProString(d, value) + d.putBetterType(value.type) + + +def qdump__Core__GeneratedFile(d, value): + d.putStringValue(value["m_d"]["d"]["path"]) + d.putPlainChildren(value) + +#def qdump__ProjectExplorer__Node(d, value): +# d.putStringValue(value["m_filePath"]) +# d.putPlainChildren(value) +# +#def qdump__ProjectExplorer__FolderNode(d, value): +# d.putStringValue(value["m_displayName"]) +# d.putPlainChildren(value) + +# Broke when moving to unique_ptr +#def qdump__ProjectExplorer__ToolChain(d, value): +# d.putStringValue(value["d"]["m_displayName"]) +# d.putPlainChildren(value) + +# Broke when moving to unique_ptr +#def qdump__ProjectExplorer__Kit(d, value): +# d.putStringValue(value["d"]["m_unexpandedDisplayName"]) +# d.putPlainChildren(value) + + +def qdump__ProjectExplorer__ProjectNode(d, value): + qdump__ProjectExplorer__FolderNode(d, value) + + +def qdump__CMakeProjectManager__Internal__CMakeProjectNode(d, value): + qdump__ProjectExplorer__FolderNode(d, value) + + +def qdump__QmakeProjectManager__QmakePriFileNode(d, value): + qdump__ProjectExplorer__FolderNode(d, value) + + +def qdump__QmakeProjectManager__QmakeProFileNode(d, value): + qdump__ProjectExplorer__FolderNode(d, value) diff --git a/share/qtcreator/debugger/python2/dumper.py b/share/qtcreator/debugger/python2/dumper.py new file mode 100644 index 00000000000..1fd42848d4e --- /dev/null +++ b/share/qtcreator/debugger/python2/dumper.py @@ -0,0 +1,4094 @@ +# Copyright (C) 2016 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import os +import codecs +import collections +import glob +import struct +import sys +import re +import time +import inspect +from utils import DisplayFormat, TypeCode + +try: + # That's only used in native combined debugging right now, so + # we do not need to hard fail in cases of partial python installation + # that will never use this. + import json +except: + print("Python module json not found. " + "Native combined debugging might not work.") + pass + +try: + # That fails on some QNX via Windows installations + import base64 + def hexencode_(s): + return base64.b16encode(s).decode('utf8') +except: + def hexencode_(s): + return ''.join(["%x" % c for c in s]) + +if sys.version_info[0] >= 3: + toInteger = int +else: + toInteger = long + + +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 Timer(): + def __init__(self, d, desc): + self.d = d + self.desc = desc + '-' + d.currentIName + + def __enter__(self): + self.starttime = time.time() + + def __exit__(self, exType, exValue, exTraceBack): + elapsed = int(1000 * (time.time() - self.starttime)) + self.d.timings.append([self.desc, elapsed]) + + +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 + if childType is None: + self.childType = None + else: + self.childType = childType.name + if not self.d.isCli: + self.d.putField('childtype', self.childType) + if childNumChild is not None: + self.d.putField('childnumchild', childNumChild) + self.childNumChild = childNumChild + if addrBase is not None and addrStep is not None: + self.d.put('addrbase="0x%x",addrstep="%d",' % (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.d.currentChildType = self.childType + self.d.currentChildNumChild = self.childNumChild + self.d.currentNumChild = self.numChild + self.d.currentMaxNumChild = self.maxNumChild + self.d.put(self.d.childrenPrefix) + + def __exit__(self, exType, exValue, exTraceBack): + if exType is not None: + if self.d.passExceptions: + self.d.showException('CHILDREN', exType, exValue, exTraceBack) + self.d.putSpecialValue('notaccessible') + self.d.putNumChild(0) + if self.d.currentMaxNumChild is not None: + if self.d.currentMaxNumChild < self.d.currentNumChild: + self.d.put('{name="",value="",type="",numchild="1"},') + self.d.currentChildType = self.savedChildType + self.d.currentChildNumChild = self.savedChildNumChild + self.d.currentNumChild = self.savedNumChild + self.d.currentMaxNumChild = self.savedMaxNumChild + if self.d.isCli: + self.d.put('\n' + ' ' * self.d.indent) + self.d.put(self.d.childrenSuffix) + 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 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 + + +class DumperBase(): + @staticmethod + def warn(message): + print('bridgemessage={msg="%s"},' % message.replace('"', '$').encode('latin1')) + + @staticmethod + def showException(msg, exType, exValue, exTraceback): + DumperBase.warn('**** CAUGHT EXCEPTION: %s ****' % msg) + try: + import traceback + for line in traceback.format_exception(exType, exValue, exTraceback): + DumperBase.warn('%s' % line) + except: + pass + + def timer(self, desc): + return Timer(self, desc) + + def __init__(self): + self.isCdb = False + self.isGdb = False + self.isLldb = False + self.isCli = False + self.isDebugBuild = None + + # Later set, or not set: + self.stringCutOff = 10000 + self.displayStringLimit = 100 + self.useTimeStamps = False + + self.output = [] + self.typesReported = {} + self.typesToReport = {} + self.qtNamespaceToReport = None + self.qtCustomEventFunc = 0 + self.qtCustomEventPltFunc = 0 + self.qtPropertyFunc = 0 + self.fallbackQtVersion = 0x60200 + self.passExceptions = False + self.isTesting = False + + self.typeData = {} + self.isBigEndian = False + self.packCode = '<' + + self.resetCaches() + self.resetStats() + + self.childrenPrefix = 'children=[' + self.childrenSuffix = '],' + + self.dumpermodules = [] + + try: + # Fails in the piping case + self.dumpermodules = [ + os.path.splitext(os.path.basename(p))[0] for p in + glob.glob(os.path.join(os.path.dirname(__file__), '*types.py')) + ] + except: + pass + + # These values are never used, but the variables need to have + # some value base for the swapping logic in Children.__enter__() + # and Children.__exit__(). + self.currentIName = None + self.currentValue = None + self.currentType = None + self.currentNumChild = None + self.currentMaxNumChild = None + self.currentPrintsAddress = True + self.currentChildType = None + self.currentChildNumChild = None + self.registerKnownTypes() + + def setVariableFetchingOptions(self, args): + self.resultVarName = args.get('resultvarname', '') + self.expandedINames = args.get('expanded', {}) + self.stringCutOff = int(args.get('stringcutoff', 10000)) + self.displayStringLimit = int(args.get('displaystringlimit', 100)) + self.typeformats = args.get('typeformats', {}) + self.formats = args.get('formats', {}) + self.watchers = args.get('watchers', {}) + self.useDynamicType = int(args.get('dyntype', '0')) + self.useFancy = int(args.get('fancy', '0')) + self.forceQtNamespace = int(args.get('forcens', '0')) + self.passExceptions = int(args.get('passexceptions', '0')) + self.isTesting = int(args.get('testing', '0')) + self.showQObjectNames = int(args.get('qobjectnames', '1')) + self.nativeMixed = int(args.get('nativemixed', '0')) + self.autoDerefPointers = int(args.get('autoderef', '0')) + self.useTimeStamps = int(args.get('timestamps', '0')) + self.partialVariable = args.get('partialvar', '') + self.uninitialized = args.get('uninitialized', []) + self.uninitialized = list(map(lambda x: self.hexdecode(x), self.uninitialized)) + self.partialUpdate = int(args.get('partial', '0')) + #DumperBase.warn('NAMESPACE: "%s"' % self.qtNamespace()) + #DumperBase.warn('EXPANDED INAMES: %s' % self.expandedINames) + #DumperBase.warn('WATCHERS: %s' % self.watchers) + + def setFallbackQtVersion(self, args): + version = int(args.get('version', self.fallbackQtVersion)) + self.fallbackQtVersion = version + + def resetPerStepCaches(self): + self.perStepCache = {} + pass + + def resetCaches(self): + # This is a cache mapping from 'type name' to 'display alternatives'. + self.qqFormats = {'QVariant (QVariantMap)': [DisplayFormat.CompactMap]} + + # This is a cache of all known dumpers. + self.qqDumpers = {} # Direct type match + self.qqDumpersEx = {} # Using regexp + + # This is a cache of all dumpers that support writing. + self.qqEditable = {} + + # This keeps canonical forms of the typenames, without array indices etc. + self.cachedFormats = {} + + # Maps type names to static metaobjects. If a type is known + # to not be QObject derived, it contains a 0 value. + self.knownStaticMetaObjects = {} + + # A dictionary to serve as a per debugging step cache. + # Cleared on each step over / into / continue. + self.perStepCache = {} + + # A dictionary to serve as a general cache throughout the whole + # debug session. + self.generalCache = {} + + self.counts = {} + self.structPatternCache = {} + self.timings = [] + self.expandableINames = set({}) + + def resetStats(self): + # Timing collection + self.timings = [] + pass + + def dumpStats(self): + msg = [self.counts, self.timings] + self.resetStats() + return msg + + def bump(self, key): + if key in self.counts: + self.counts[key] += 1 + else: + self.counts[key] = 1 + + def childRange(self): + if self.currentMaxNumChild is None: + return range(0, self.currentNumChild) + return range(min(self.currentMaxNumChild, self.currentNumChild)) + + def maxArrayCount(self): + if self.currentIName in self.expandedINames: + return self.expandedINames[self.currentIName] + return 100 + + def enterSubItem(self, item): + if self.useTimeStamps: + item.startTime = time.time() + if not item.iname: + item.iname = '%s.%s' % (self.currentIName, item.name) + if not self.isCli: + self.put('{') + if isinstance(item.name, str): + self.putField('name', item.name) + else: + self.indent += 1 + self.put('\n' + ' ' * self.indent) + if isinstance(item.name, str): + self.put(item.name + ' = ') + item.savedIName = self.currentIName + item.savedValue = self.currentValue + item.savedType = self.currentType + self.currentIName = item.iname + self.currentValue = ReportItem() + self.currentType = ReportItem() + + def exitSubItem(self, item, exType, exValue, exTraceBack): + #DumperBase.warn('CURRENT VALUE: %s: %s %s' % + # (self.currentIName, self.currentValue, self.currentType)) + if exType is not None: + if self.passExceptions: + self.showException('SUBITEM', exType, exValue, exTraceBack) + self.putSpecialValue('notaccessible') + self.putNumChild(0) + if not self.isCli: + try: + if self.currentType.value: + typeName = self.currentType.value + if len(typeName) > 0 and typeName != self.currentChildType: + self.putField('type', typeName) + if self.currentValue.value is None: + self.put('value="",encoding="notaccessible",numchild="0",') + else: + if self.currentValue.encoding is not None: + self.put('valueencoded="%s",' % self.currentValue.encoding) + if self.currentValue.elided: + self.put('valueelided="%s",' % self.currentValue.elided) + self.put('value="%s",' % self.currentValue.value) + except: + pass + if self.useTimeStamps: + self.put('time="%s",' % (time.time() - item.startTime)) + self.put('},') + else: + self.indent -= 1 + try: + if self.currentType.value: + typeName = self.currentType.value + self.put('<%s> = {' % typeName) + + if self.currentValue.value is None: + self.put('') + else: + value = self.currentValue.value + if self.currentValue.encoding == 'latin1': + value = self.hexdecode(value) + elif self.currentValue.encoding == 'utf8': + value = self.hexdecode(value) + elif self.currentValue.encoding == 'utf16': + b = bytes(bytearray.fromhex(value)) + value = codecs.decode(b, 'utf-16') + self.put('"%s"' % value) + if self.currentValue.elided: + self.put('...') + + if self.currentType.value: + self.put('}') + except: + pass + self.currentIName = item.savedIName + self.currentValue = item.savedValue + self.currentType = item.savedType + return True + + def stripForFormat(self, typeName): + if not isinstance(typeName, str): + raise RuntimeError('Expected string in stripForFormat(), got %s' % type(typeName)) + if typeName in self.cachedFormats: + return self.cachedFormats[typeName] + stripped = '' + inArray = 0 + for c in 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 + self.cachedFormats[typeName] = stripped + return stripped + + def templateArgument(self, typeobj, position): + return typeobj.templateArgument(position) + + def intType(self): + result = self.lookupType('int') + self.intType = lambda: result + return result + + def charType(self): + result = self.lookupType('char') + self.intType = lambda: result + return result + + def ptrSize(self): + result = self.lookupType('void*').size() + self.ptrSize = lambda: result + return result + + def lookupType(self, typeName): + nativeType = self.lookupNativeType(typeName) + return None if nativeType is None else self.fromNativeType(nativeType) + + def registerKnownTypes(self): + tdata = self.TypeData(self, 'unsigned short') + tdata.lbitsize = 16 + tdata.lalignment = 2 + tdata.code = TypeCode.Integral + + tdata = self.TypeData(self, 'QChar') + tdata.lbitsize = 16 + tdata.lalignment = 2 + tdata.code = TypeCode.Struct + tdata.lfields = [self.Field(dumper=self, name='ucs', + type='unsigned short', bitsize=16, bitpos=0)] + tdata.templateArguments = lambda: [] + + def nativeDynamicType(self, address, baseType): + return baseType # Override in backends. + + def listTemplateParameters(self, typename): + return self.listTemplateParametersManually(typename) + + def listTemplateParametersManually(self, typename): + targs = [] + if not typename.endswith('>'): + return targs + + def push(inner): + # Handle local struct definitions like QList + inner = inner.strip()[::-1] + p = inner.find(')::') + if p > -1: + inner = inner[p + 3:].strip() + if inner.startswith('const '): + inner = inner[6:].strip() + if inner.endswith(' const'): + inner = inner[:-6].strip() + #DumperBase.warn("FOUND: %s" % inner) + targs.append(inner) + + #DumperBase.warn("SPLITTING %s" % typename) + level = 0 + inner = '' + for c in typename[::-1]: # Reversed... + #DumperBase.warn("C: %s" % c) + if c == '>': + if level > 0: + inner += c + level += 1 + elif c == '<': + level -= 1 + if level > 0: + inner += c + else: + push(inner) + inner = '' + break + elif c == ',': + #DumperBase.warn('c: %s level: %s' % (c, level)) + if level == 1: + push(inner) + inner = '' + else: + inner += c + else: + inner += c + + #DumperBase.warn("TARGS: %s %s" % (typename, targs)) + res = [] + for item in targs[::-1]: + if len(item) == 0: + continue + c = ord(item[0]) + if c in (45, 46) or (c >= 48 and c < 58): # '-', '.' or digit. + if item.find('.') > -1: + res.append(float(item)) + else: + if item.endswith('l'): + item = item[:-1] + if item.endswith('u'): + item = item[:-1] + val = toInteger(item) + if val > 0x80000000: + val -= 0x100000000 + res.append(val) + else: + res.append(self.Type(self, item)) + #DumperBase.warn("RES: %s %s" % (typename, [(None if t is None else t.name) for t in res])) + return res + + # Hex decoding operating on str, return str. + @staticmethod + def hexdecode(s, encoding='utf8'): + if sys.version_info[0] == 2: + # For python2 we need an extra str() call to return str instead of unicode + return str(s.decode('hex').decode(encoding)) + return bytes.fromhex(s).decode(encoding) + + # Hex encoding operating on str or bytes, return str. + @staticmethod + def hexencode(s): + if s is None: + s = '' + if sys.version_info[0] == 2: + if isinstance(s, buffer): + return bytes(s).encode('hex') + return s.encode('hex') + if isinstance(s, str): + s = s.encode('utf8') + return hexencode_(s) + + def isQt3Support(self): + # assume no Qt 3 support by default + return False + + # 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 vectorData(self, value): + if self.qtVersion() >= 0x060000: + data, size, alloc = self.qArrayData(value) + elif self.qtVersion() >= 0x050000: + vector_data_ptr = self.extractPointer(value) + if self.ptrSize() == 4: + (ref, size, alloc, offset) = self.split('IIIp', vector_data_ptr) + else: + (ref, size, alloc, pad, offset) = self.split('IIIIp', vector_data_ptr) + alloc = alloc & 0x7ffffff + data = vector_data_ptr + offset + else: + vector_data_ptr = self.extractPointer(value) + (ref, alloc, size) = self.split('III', vector_data_ptr) + data = vector_data_ptr + 16 + self.check(0 <= size and size <= alloc and alloc <= 1000 * 1000 * 1000) + return data, size + + def qArrayData(self, value): + if self.qtVersion() >= 0x60000: + dd, data, size = self.split('ppp', value) + if dd: + _, _, alloc = self.split('iip', dd) + else: # fromRawData + alloc = size + return data, size, alloc + return self.qArrayDataHelper(self.extractPointer(value)) + + def qArrayDataHelper(self, array_data_ptr): + # array_data_ptr is what is e.g. stored in a QByteArray's d_ptr. + if self.qtVersion() >= 0x050000: + # QTypedArray: + # - QtPrivate::RefCount ref + # - int size + # - uint alloc : 31, capacityReserved : 1 + # - qptrdiff offset + (ref, size, alloc, offset) = self.split('IIpp', array_data_ptr) + alloc = alloc & 0x7ffffff + data = array_data_ptr + offset + if self.ptrSize() == 4: + data = data & 0xffffffff + else: + data = data & 0xffffffffffffffff + elif self.qtVersion() >= 0x040000: + # Data: + # - QBasicAtomicInt ref; + # - int alloc, size; + # - [padding] + # - char *data; + if self.ptrSize() == 4: + (ref, alloc, size, data) = self.split('IIIp', array_data_ptr) + else: + (ref, alloc, size, pad, data) = self.split('IIIIp', array_data_ptr) + else: + # Data: + # - QShared count; + # - QChar *unicode + # - char *ascii + # - uint len: 30 + (dummy, dummy, dummy, size) = self.split('IIIp', array_data_ptr) + size = self.extractInt(array_data_ptr + 3 * self.ptrSize()) & 0x3ffffff + alloc = size # pretend. + data = self.extractPointer(array_data_ptr + self.ptrSize()) + return data, size, alloc + + def encodeStringHelper(self, value, limit): + data, size, alloc = self.qArrayData(value) + if alloc != 0: + self.check(0 <= size and size <= alloc and alloc <= 100 * 1000 * 1000) + elided, shown = self.computeLimit(2 * size, 2 * limit) + return elided, self.readMemory(data, shown) + + def encodeByteArrayHelper(self, value, limit): + data, size, alloc = self.qArrayData(value) + 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 putCharArrayValue(self, data, size, charSize, + displayFormat=DisplayFormat.Automatic): + bytelen = size * charSize + elided, shown = self.computeLimit(bytelen, self.displayStringLimit) + mem = self.readMemory(data, shown) + if charSize == 1: + if displayFormat in (DisplayFormat.Latin1String, DisplayFormat.SeparateLatin1String): + 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 in ( + DisplayFormat.SeparateLatin1String, + DisplayFormat.SeparateUtf8String, + DisplayFormat.Separate): + elided, shown = self.computeLimit(bytelen, 100000) + self.putDisplay(encodingType + ':separate', self.readMemory(data, shown)) + + def putCharArrayHelper(self, data, size, charType, + displayFormat=DisplayFormat.Automatic, + makeExpandable=True): + charSize = charType.size() + self.putCharArrayValue(data, size, charSize, displayFormat=displayFormat) + + if makeExpandable: + self.putNumChild(size) + if self.isExpanded(): + with Children(self): + for i in range(size): + self.putSubItem(size, self.createValue(data + i * charSize, charType)) + + def readMemory(self, addr, size): + return self.hexencode(bytes(self.readRawMemory(addr, size))) + + def encodeByteArray(self, value, limit=0): + elided, data = self.encodeByteArrayHelper(value, limit) + return data + + def putByteArrayValue(self, value): + elided, data = self.encodeByteArrayHelper(value, self.displayStringLimit) + self.putValue(data, 'latin1', elided=elided) + + def encodeString(self, value, limit=0): + elided, data = self.encodeStringHelper(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): # -> (data, size, alloc) + return self.qArrayData(value) + + 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 putStringValue(self, value): + elided, data = self.encodeStringHelper(value, self.displayStringLimit) + self.putValue(data, 'utf16', elided=elided) + + def putPtrItem(self, name, value): + with SubItem(self, name): + self.putValue('0x%x' % value) + self.putType('void*') + + def putIntItem(self, name, value): + with SubItem(self, name): + if isinstance(value, self.Value): + self.putValue(value.display()) + else: + self.putValue(value) + self.putType('int') + + def putEnumItem(self, name, ival, typish): + buf = bytearray(struct.pack('i', ival)) + val = self.Value(self) + val.ldata = bytes(buf) + val._type = self.createType(typish) + with SubItem(self, name): + self.putItem(val) + + def putBoolItem(self, name, value): + with SubItem(self, name): + self.putValue(value) + self.putType('bool') + + def putPairItem(self, index, pair, keyName='first', valueName='second'): + with SubItem(self, index): + self.putPairContents(index, pair, keyName, valueName) + + def putPairContents(self, index, pair, kname, vname): + with Children(self): + first, second = pair if isinstance(pair, tuple) else pair.members(False) + key = self.putSubItem(kname, first) + value = self.putSubItem(vname, second) + if self.isCli: + self.putEmptyValue() + else: + if index is not None: + self.putField('keyprefix', '[%s] ' % index) + self.putField('key', key.value) + if key.encoding is not None: + self.putField('keyencoded', key.encoding) + self.putValue(value.value, value.encoding) + + def putEnumValue(self, ival, vals): + nice = vals.get(ival, None) + display = ('%d' % ival) if nice is None else ('%s (%d)' % (nice, ival)) + self.putValue(display) + + def putCallItem(self, name, rettype, value, func, *args): + with SubItem(self, name): + try: + result = self.callHelper(rettype, value, func, args) + except Exception as error: + if self.passExceptions: + raise error + children = [('error', error)] + self.putSpecialValue("notcallable", children=children) + else: + self.putItem(result) + + def call(self, rettype, value, func, *args): + return self.callHelper(rettype, value, func, args) + + def putAddress(self, address): + if address is not None and not self.isCli: + self.put('address="0x%x",' % address) + + def putPlainChildren(self, value, dumpBase=True): + self.putExpandable() + if self.isExpanded(): + self.putEmptyValue(-99) + with Children(self): + self.putFields(value, dumpBase) + + def putNamedChildren(self, values, names): + self.putEmptyValue(-99) + self.putExpandable() + if self.isExpanded(): + with Children(self): + for n, v in zip(names, values): + self.putSubItem(n, v) + + def prettySymbolByAddress(self, address): + return '0x%x' % address + + def putSymbolValue(self, address): + self.putValue(self.prettySymbolByAddress(address)) + + def putVTableChildren(self, item, itemCount): + p = item.pointer() + for i in range(itemCount): + deref = self.extractPointer(p) + if deref == 0: + itemCount = i + break + with SubItem(self, i): + self.putItem(self.createPointerValue(deref, 'void')) + p += self.ptrSize() + return itemCount + + def putFields(self, value, dumpBase=True): + baseIndex = 0 + for item in value.members(True): + if item.name is not None: + if item.name.startswith('_vptr.') or item.name.startswith('__vfptr'): + with SubItem(self, '[vptr]'): + # int (**)(void) + self.putType(' ') + self.putSortGroup(20) + self.putValue(item.name) + n = 100 + if self.isExpanded(): + with Children(self): + n = self.putVTableChildren(item, n) + self.putNumChild(n) + continue + + if item.isBaseClass and dumpBase: + baseIndex += 1 + # We cannot use nativeField.name as part of the iname as + # it might contain spaces and other strange characters. + with UnnamedSubItem(self, "@%d" % baseIndex): + self.putField('iname', self.currentIName) + self.putField('name', '[%s]' % item.name) + if not self.isCli: + self.putSortGroup(1000 - baseIndex) + self.putAddress(item.address()) + self.putItem(item) + continue + + with SubItem(self, item.name): + self.putItem(item) + + def putExpandable(self): + self.putNumChild(1) + self.expandableINames.add(self.currentIName) + if self.isCli: + self.putValue('{...}', -99) + + def putMembersItem(self, value, sortorder=10): + with SubItem(self, '[members]'): + self.putSortGroup(sortorder) + self.putPlainChildren(value) + + def put(self, stuff): + self.output.append(stuff) + + def takeOutput(self): + res = ''.join(self.output) + self.output = [] + return res + + def check(self, exp): + if not exp: + raise RuntimeError('Check failed: %s' % exp) + + def checkRef(self, ref): + # Assume there aren't a million references to any object. + self.check(ref >= -1) + self.check(ref < 1000000) + + def checkIntType(self, thing): + if not self.isInt(thing): + raise RuntimeError('Expected an integral value, got %s' % type(thing)) + + def readToFirstZero(self, base, tsize, maximum): + self.checkIntType(base) + self.checkIntType(tsize) + self.checkIntType(maximum) + + code = self.packCode + (None, 'b', 'H', None, 'I')[tsize] + #blob = self.readRawMemory(base, 1) + blob = bytes() + while maximum > 1: + try: + blob = self.readRawMemory(base, maximum) + break + except: + maximum = int(maximum / 2) + self.warn('REDUCING READING MAXIMUM TO %s' % maximum) + + #DumperBase.warn('BASE: 0x%x TSIZE: %s MAX: %s' % (base, tsize, maximum)) + for i in range(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 isinstance(value, bool): + return '"%d"' % int(value) + if isinstance(value, dict): + return '{' + ','.join(['%s=%s' % (k, self.resultToMi(v)) + for (k, v) in list(value.items())]) + '}' + if isinstance(value, list): + return '[' + ','.join([self.resultToMi(k) + for k in value]) + ']' + return '"%s"' % value + + def variablesToMi(self, value, prefix): + if isinstance(value, bool): + return '"%d"' % int(value) + if isinstance(value, 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 isinstance(value, 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 'iname' not 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, typish, priority=0): + # Higher priority values override lower ones. + if priority >= self.currentType.priority: + types = (str) if sys.version_info[0] >= 3 else (str, unicode) + if isinstance(typish, types): + self.currentType.value = typish + else: + self.currentType.value = typish.name + 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='', children=None): + self.putValue(value, encoding) + if children is not None: + self.putExpandable() + if self.isExpanded(): + with Children(self): + for name, value in children: + with SubItem(self, name): + self.putValue(str(value).replace('"', '$')) + + def putEmptyValue(self, priority=-10): + if priority >= self.currentValue.priority: + self.currentValue = ReportItem('', None, priority, None) + + def putName(self, name): + self.putField('name', name) + + def putBetterType(self, typish): + if isinstance(typish, ReportItem): + self.currentType.value = typish.value + elif isinstance(typish, str): + self.currentType.value = typish.replace('@', self.qtNamespace()) + else: + self.currentType.value = typish.name + 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): + #DumperBase.warn('IS EXPANDED: %s in %s: %s' % (self.currentIName, + # self.expandedINames, self.currentIName in self.expandedINames)) + return self.currentIName in self.expandedINames + + def mangleName(self, typeName): + return '_ZN%sE' % ''.join(map(lambda x: '%d%s' % (len(x), x), + typeName.split('::'))) + + def arrayItemCountFromTypeName(self, typeName, fallbackMax=1): + itemCount = typeName[typeName.find('[') + 1:typeName.find(']')] + return int(itemCount) if itemCount else fallbackMax + + def putCStyleArray(self, value): + arrayType = value.type.unqualified() + innerType = arrayType.ltarget + if innerType is None: + innerType = value.type.target().unqualified() + address = value.address() + if address: + self.putValue('@0x%x' % address, priority=-1) + else: + self.putEmptyValue() + self.putType(arrayType) + + displayFormat = self.currentItemFormat() + arrayByteSize = arrayType.size() + if arrayByteSize == 0: + # This should not happen. But it does, see QTCREATORBUG-14755. + # GDB/GCC produce sizeof == 0 for QProcess arr[3] + # And in the Nim string dumper. + itemCount = self.arrayItemCountFromTypeName(value.type.name, 100) + arrayByteSize = int(itemCount) * innerType.size() + + n = arrayByteSize // innerType.size() + p = value.address() + if displayFormat != DisplayFormat.Raw and p: + if innerType.name in ( + 'char', + 'int8_t', + 'qint8', + 'wchar_t', + 'unsigned char', + 'uint8_t', + 'quint8', + 'signed char', + 'CHAR', + 'WCHAR', + 'char8_t', + 'char16_t', + 'char32_t' + ): + self.putCharArrayHelper(p, n, innerType, self.currentItemFormat(), + makeExpandable=False) + else: + self.tryPutSimpleFormattedPointer(p, arrayType, innerType, + 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 '' + return '0x%x' % toInteger(hex(addr), 16) + + def stripNamespaceFromType(self, typeName): + ns = self.qtNamespace() + if len(ns) > 0 and typeName.startswith(ns): + typeName = typeName[len(ns):] + # DumperBase.warn( 'stripping %s' % typeName ) + lvl = 0 + pos = None + stripChunks = [] + sz = len(typeName) + for index in range(0, sz): + s = typeName[index] + if s == '<': + lvl += 1 + if lvl == 1: + pos = index + continue + elif s == '>': + lvl -= 1 + if lvl < 0: + raise RuntimeError("Unbalanced '<' in type, @index %d" % index) + if lvl == 0: + stripChunks.append((pos, index + 1)) + if lvl != 0: + raise RuntimeError("unbalanced at end of type name") + for (f, l) in reversed(stripChunks): + typeName = typeName[:f] + typeName[l:] + return typeName + + def tryPutPrettyItem(self, typeName, value): + value.check() + if self.useFancy and self.currentItemFormat() != DisplayFormat.Raw: + self.putType(typeName) + + nsStrippedType = self.stripNamespaceFromType(typeName)\ + .replace('::', '__') + + # Strip leading 'struct' for C structs + if nsStrippedType.startswith('struct '): + nsStrippedType = nsStrippedType[7:] + + #DumperBase.warn('STRIPPED: %s' % nsStrippedType) + # 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) + #DumperBase.warn('DUMPER: %s' % dumper) + if dumper is not 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.putField('editformat', editFormat) + self.putField('editvalue', value) + + # This is shared by pointer and array formatting. + def tryPutSimpleFormattedPointer(self, ptr, typeName, innerType, displayFormat, limit): + if displayFormat == DisplayFormat.Automatic: + targetType = innerType + if innerType.code == TypeCode.Typedef: + targetType = innerType.ltarget + + if targetType.name in ('char', 'signed char', 'unsigned char', 'uint8_t', 'CHAR'): + # Use UTF-8 as default for char *. + self.putType(typeName) + (elided, shown, data) = self.readToFirstZero(ptr, 1, limit) + self.putValue(data, 'utf8', elided=elided) + if self.isExpanded(): + self.putArrayData(ptr, shown, innerType) + return True + + if targetType.name in ('wchar_t', 'WCHAR'): + self.putType(typeName) + charSize = self.lookupType('wchar_t').size() + (elided, data) = self.encodeCArray(ptr, charSize, limit) + if charSize == 2: + self.putValue(data, 'utf16', elided=elided) + else: + self.putValue(data, 'ucs4', elided=elided) + return True + + if displayFormat == DisplayFormat.Latin1String: + self.putType(typeName) + (elided, data) = self.encodeCArray(ptr, 1, limit) + self.putValue(data, 'latin1', elided=elided) + return True + + if displayFormat == DisplayFormat.SeparateLatin1String: + self.putType(typeName) + (elided, data) = self.encodeCArray(ptr, 1, limit) + self.putValue(data, 'latin1', elided=elided) + self.putDisplay('latin1:separate', data) + return True + + if displayFormat == DisplayFormat.Utf8String: + self.putType(typeName) + (elided, data) = self.encodeCArray(ptr, 1, limit) + self.putValue(data, 'utf8', elided=elided) + return True + + if displayFormat == DisplayFormat.SeparateUtf8String: + self.putType(typeName) + (elided, data) = self.encodeCArray(ptr, 1, limit) + self.putValue(data, 'utf8', elided=elided) + self.putDisplay('utf8:separate', data) + return True + + if displayFormat == DisplayFormat.Local8BitString: + self.putType(typeName) + (elided, data) = self.encodeCArray(ptr, 1, limit) + self.putValue(data, 'local8bit', elided=elided) + return True + + if displayFormat == DisplayFormat.Utf16String: + self.putType(typeName) + (elided, data) = self.encodeCArray(ptr, 2, limit) + self.putValue(data, 'utf16', elided=elided) + return True + + if displayFormat == DisplayFormat.Ucs4String: + self.putType(typeName) + (elided, data) = self.encodeCArray(ptr, 4, limit) + self.putValue(data, 'ucs4', elided=elided) + return True + + return False + + def putFormattedPointer(self, value): + #with self.timer('formattedPointer'): + self.putFormattedPointerX(value) + + def putDerefedPointer(self, value): + derefValue = value.dereference() + innerType = value.type.target() # .unqualified() + self.putType(innerType) + savedCurrentChildType = self.currentChildType + self.currentChildType = innerType.name + derefValue.name = '*' + derefValue.autoDerefCount = value.autoDerefCount + 1 + + if derefValue.type.code != TypeCode.Pointer: + self.putField('autoderefcount', '{}'.format(derefValue.autoDerefCount)) + + self.putItem(derefValue) + self.currentChildType = savedCurrentChildType + + def putFormattedPointerX(self, value): + self.putOriginalAddress(value.address()) + #DumperBase.warn("PUT FORMATTED: %s" % value) + pointer = value.pointer() + self.putAddress(pointer) + #DumperBase.warn('POINTER: 0x%x' % pointer) + if pointer == 0: + #DumperBase.warn('NULL POINTER') + self.putType(value.type) + self.putValue('0x0') + return + + typeName = value.type.name + + try: + self.readRawMemory(pointer, 1) + except: + # Failure to dereference a pointer should at least + # show the value of a pointer. + #DumperBase.warn('BAD POINTER: %s' % value) + self.putValue('0x%x' % pointer) + self.putType(typeName) + return + + if self.currentIName.endswith('.this'): + self.putDerefedPointer(value) + return + + displayFormat = self.currentItemFormat(value.type.name) + innerType = value.type.target() # .unqualified() + + if innerType.name == 'void': + #DumperBase.warn('VOID POINTER: %s' % displayFormat) + self.putType(typeName) + self.putSymbolValue(pointer) + return + + if displayFormat == DisplayFormat.Raw: + # Explicitly requested bald pointer. + #DumperBase.warn('RAW') + self.putType(typeName) + self.putValue('0x%x' % pointer) + self.putExpandable() + if self.currentIName in self.expandedINames: + with Children(self): + with SubItem(self, '*'): + self.putItem(value.dereference()) + return + + limit = self.displayStringLimit + if displayFormat in (DisplayFormat.SeparateLatin1String, DisplayFormat.SeparateUtf8String): + limit = 1000000 + if self.tryPutSimpleFormattedPointer(pointer, typeName, + innerType, displayFormat, limit): + self.putExpandable() + return + + if DisplayFormat.Array10 <= displayFormat and displayFormat <= DisplayFormat.Array10000: + n = (10, 100, 1000, 10000)[displayFormat - DisplayFormat.Array10] + self.putType(typeName) + self.putItemCount(n) + self.putArrayData(value.pointer(), n, innerType) + return + + if innerType.code == TypeCode.Function: + # A function pointer. + self.putSymbolValue(pointer) + self.putType(typeName) + return + + #DumperBase.warn('AUTODEREF: %s' % self.autoDerefPointers) + #DumperBase.warn('INAME: %s' % self.currentIName) + #DumperBase.warn('INNER: %s' % innerType.name) + if self.autoDerefPointers: + # Generic pointer type with AutomaticFormat, but never dereference char types: + if innerType.name not in ( + 'char', + 'signed char', + 'int8_t', + 'qint8', + 'unsigned char', + 'uint8_t', + 'quint8', + 'wchar_t', + 'CHAR', + 'WCHAR', + 'char8_t', + 'char16_t', + 'char32_t' + ): + self.putDerefedPointer(value) + return + + #DumperBase.warn('GENERIC PLAIN POINTER: %s' % value.type) + #DumperBase.warn('ADDR PLAIN POINTER: 0x%x' % value.laddress) + self.putType(typeName) + self.putSymbolValue(pointer) + self.putExpandable() + if self.currentIName in self.expandedINames: + with Children(self): + with SubItem(self, '*'): + self.putItem(value.dereference()) + + def putOriginalAddress(self, address): + if address is not None: + self.put('origaddr="0x%x",' % address) + + def putQObjectNameValue(self, value): + try: + # dd = value['d_ptr']['d'] is just behind the vtable. + (vtable, dd) = self.split('pp', value) + if not self.couldBeQObjectVTable(vtable): + return False + + intSize = 4 + ptrSize = self.ptrSize() + if self.qtVersion() >= 0x060000: + # Size of QObjectData: 9 pointer + 2 int + # - vtable + # - QObject *q_ptr; + # - QObject *parent; + # - QObjectList children; + # - uint isWidget : 1; etc... + # - int postedEvents; + # - QDynamicMetaObjectData *metaObject; + # - QBindingStorage bindingStorage; + extra = self.extractPointer(dd + 9 * ptrSize + 2 * intSize) + if extra == 0: + return False + + # Offset of objectName in ExtraData: 12 pointer + # - QList propertyNames; + # - QList propertyValues; + # - QVector runningTimers; + # - QList > eventFilters; + # - QString objectName + objectNameAddress = extra + 12 * ptrSize + elif self.qtVersion() >= 0x050000: + # 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 + objectNameAddress = extra + 5 * ptrSize + else: + # 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 + objectNameAddress = dd + 5 * ptrSize + 2 * intSize + + + data, size, alloc = self.qArrayData(objectNameAddress) + + # 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) + return False + + def couldBePointer(self, p): + if self.ptrSize() == 4: + return p > 100000 and (p & 0x3 == 0) + else: + return p > 100000 and (p & 0x7 == 0) and (p < 0x7fffffffffff) + + def couldBeVTableEntry(self, p): + if self.ptrSize() == 4: + return p > 100000 and (p & 0x1 == 0) + else: + return p > 100000 and (p & 0x1 == 0) and (p < 0x7fffffffffff) + + def couldBeQObjectPointer(self, objectPtr): + try: + vtablePtr, dd = self.split('pp', objectPtr) + except: + self.bump('nostruct-1') + return False + + try: + dvtablePtr, qptr, parentPtr = self.split('ppp', dd) + except: + self.bump('nostruct-2') + return False + # Check d_ptr.d.q_ptr == objectPtr + if qptr != objectPtr: + self.bump('q_ptr') + return False + + return self.couldBeQObjectVTable(vtablePtr) + + def couldBeQObjectVTable(self, vtablePtr): + def getJumpAddress_x86(dumper, address): + relativeJumpCode = 0xe9 + jumpCode = 0xff + try: + data = dumper.readRawMemory(address, 6) + except: + return 0 + primaryOpcode = data[0] + if primaryOpcode == relativeJumpCode: + # relative jump on 32 and 64 bit with a 32bit offset + offset = int.from_bytes(data[1:5], byteorder='little') + return address + 5 + offset + if primaryOpcode == jumpCode: + if data[1] != 0x25: # check for known extended opcode + return 0 + # 0xff25 is a relative jump on 64bit and an absolute jump on 32 bit + if self.ptrSize() == 8: + offset = int.from_bytes(data[2:6], byteorder='little') + return address + 6 + offset + else: + return int.from_bytes(data[2:6], byteorder='little') + return 0 + + # Do not try to extract a function pointer if there are no values to compare with + if self.qtCustomEventFunc == 0 and self.qtCustomEventPltFunc == 0: + return False + + try: + customEventOffset = 8 if self.isMsvcTarget() else 9 + customEventFunc = self.extractPointer(vtablePtr + customEventOffset * self.ptrSize()) + except: + self.bump('nostruct-3') + return False + + if self.isWindowsTarget(): + if customEventFunc in (self.qtCustomEventFunc, self.qtCustomEventPltFunc): + return True + # The vtable may point to a function that is just calling the customEvent function + customEventFunc = getJumpAddress_x86(self, customEventFunc) + if customEventFunc in (self.qtCustomEventFunc, self.qtCustomEventPltFunc): + return True + customEventFunc = self.extractPointer(customEventFunc) + if customEventFunc in (self.qtCustomEventFunc, self.qtCustomEventPltFunc): + return True + # If the object is defined in another module there may be another level of indirection + customEventFunc = getJumpAddress_x86(self, customEventFunc) + + return customEventFunc in (self.qtCustomEventFunc, self.qtCustomEventPltFunc) + +# def extractQObjectProperty(objectPtr): +# vtablePtr = self.extractPointer(objectPtr) +# metaObjectFunc = self.extractPointer(vtablePtr) +# cmd = '((void*(*)(void*))0x%x)((void*)0x%x)' % (metaObjectFunc, objectPtr) +# try: +# #DumperBase.warn('MO CMD: %s' % cmd) +# res = self.parseAndEvaluate(cmd) +# #DumperBase.warn('MO RES: %s' % res) +# self.bump('successfulMetaObjectCall') +# return res.pointer() +# except: +# self.bump('failedMetaObjectCall') +# #DumperBase.warn('COULD NOT EXECUTE: %s' % cmd) +# return 0 + + def extractMetaObjectPtr(self, objectPtr, typeobj): + """ objectPtr - address of *potential* instance of QObject derived class + typeobj - type of *objectPtr if known, None otherwise. """ + + if objectPtr is not None: + self.checkIntType(objectPtr) + + 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)((void*)0x%x)' % (metaObjectFunc, objectPtr) + try: + #DumperBase.warn('MO CMD: %s' % cmd) + res = self.parseAndEvaluate(cmd) + #DumperBase.warn('MO RES: %s' % res) + self.bump('successfulMetaObjectCall') + return res.pointer() + except: + self.bump('failedMetaObjectCall') + #DumperBase.warn('COULD NOT EXECUTE: %s' % cmd) + return 0 + + def extractStaticMetaObjectFromTypeHelper(someTypeObj): + if someTypeObj.isSimpleType(): + return 0 + + typeName = someTypeObj.name + isQObjectProper = typeName == self.qtNamespace() + 'QObject' + + # No templates for now. + if typeName.find('<') >= 0: + return 0 + + result = self.findStaticMetaObject(someTypeObj) + + # 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: + if self.qtVersion() >= 0x60000 and self.isWindowsTarget(): + (direct, indirect) = self.split('pp', result) + # since Qt 6 there is an additional indirect super data getter on windows + if direct == 0 and indirect == 0: + # This looks like a Q_GADGET + return 0 + else: + if self.extractPointer(result) == 0: + # This looks like a Q_GADGET + return 0 + + return result + + def extractStaticMetaObjectPtrFromType(someTypeObj): + if someTypeObj is None: + return 0 + someTypeName = someTypeObj.name + 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 = someTypeObj.firstBase() + # if base is not None and base != someTypeObj: # sanity check + # result = extractStaticMetaObjectPtrFromType(base) + + if result: + self.knownStaticMetaObjects[someTypeName] = result + return result + + if not self.useFancy: + return 0 + + ptrSize = self.ptrSize() + + typeName = typeobj.name + result = self.knownStaticMetaObjects.get(typeName, None) + if result is not None: # Is 0 or the static metaobject. + self.bump('typecached') + #DumperBase.warn('CACHED RESULT: %s %s 0x%x' % (self.currentIName, typeName, result)) + return result + + if not self.couldBeQObjectPointer(objectPtr): + self.bump('cannotBeQObject') + #DumperBase.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) + #with self.timer('metaObjectType-' + self.currentIName): + metaObjectPtr = extractStaticMetaObjectPtrFromType(typeobj) + + if not metaObjectPtr and not self.isWindowsTarget(): + # measured: 200 ms (example had one level of inheritance) + #with self.timer('metaObjectCall-' + self.currentIName): + metaObjectPtr = extractMetaObjectPtrFromAddress() + + #if metaObjectPtr: + # self.bump('foundMetaObject') + # self.knownStaticMetaObjects[typeName] = metaObjectPtr + + return metaObjectPtr + + def split(self, pattern, value): + if isinstance(value, self.Value): + return value.split(pattern) + if self.isInt(value): + val = self.Value(self) + val.laddress = value + return val.split(pattern) + raise RuntimeError('CANNOT EXTRACT STRUCT FROM %s' % type(value)) + + def extractCString(self, addr): + result = bytearray() + while True: + d = self.extractByte(addr) + if d == 0: + break + result.append(d) + addr += 1 + return result + + def listData(self, value, check=True): + if self.qtVersion() >= 0x60000: + dd, data, size = self.split('ppi', value) + return data, size + + base = self.extractPointer(value) + (ref, alloc, begin, end) = self.split('IIII', base) + array = base + 16 + if self.qtVersion() < 0x50000: + array += self.ptrSize() + size = end - begin + + if check: + self.check(begin >= 0 and end >= 0 and end <= 1000 * 1000 * 1000) + size = end - begin + self.check(size >= 0) + + stepSize = self.ptrSize() + data = array + begin * stepSize + return data, size + + 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.putExpandable() + if self.isExpanded(): + with Children(self): + self.putFields(self.createValue(addr, typeObj)) + else: + self.putType(typeName) + + # This is called is when a QObject derived class is expanded + def tryPutQObjectGuts(self, value): + metaObjectPtr = self.extractMetaObjectPtr(value.address(), value.type) + if metaObjectPtr: + self.putQObjectGutsHelper(value, value.address(), + -1, metaObjectPtr, 'QObject') + + def metaString(self, metaObjectPtr, index, revision): + ptrSize = self.ptrSize() + stringdataOffset = ptrSize + if self.isWindowsTarget() and self.qtVersion() >= 0x060000: + stringdataOffset += ptrSize # indirect super data member + stringdata = self.extractPointer(toInteger(metaObjectPtr) + stringdataOffset) + + def unpackString(base, size): + try: + s = struct.unpack_from('%ds' % size, self.readRawMemory(base, size))[0] + return s if sys.version_info[0] == 2 else s.decode('utf8') + except: + return '' + + if revision >= 9: # Qt 6. + pos, size = self.split('II', stringdata + 8 * index) + return unpackString(stringdata + pos, size) + + if revision >= 7: # Qt 5. + byteArrayDataSize = 24 if ptrSize == 8 else 16 + literal = stringdata + toInteger(index) * byteArrayDataSize + base, size, _ = self.qArrayDataHelper(literal) + return unpackString(base, size) + + ldata = stringdata + index + return self.extractCString(ldata).decode('utf8') + + def putSortGroup(self, sortorder): + if not self.isCli: + self.putField('sortgroup', sortorder) + + def putQMetaStuff(self, value, origType): + if self.qtVersion() >= 0x060000: + metaObjectPtr, handle = value.split('pp') + else: + metaObjectPtr, handle = value.split('pI') + if metaObjectPtr != 0: + if self.qtVersion() >= 0x060000: + if handle == 0: + self.putEmptyValue() + return + revision = 9 + name, alias, flags, keyCount, data = self.split('IIIII', handle) + index = name + elif self.qtVersion() >= 0x050000: + revision = 7 + dataPtr = self.extractPointer(metaObjectPtr + 2 * self.ptrSize()) + index = self.extractInt(dataPtr + 4 * handle) + else: + revision = 6 + dataPtr = self.extractPointer(metaObjectPtr + 2 * self.ptrSize()) + index = self.extractInt(dataPtr + 4 * handle) + #self.putValue("index: %s rev: %s" % (index, revision)) + name = self.metaString(metaObjectPtr, index, revision) + self.putValue(name) + self.putExpandable() + if self.isExpanded(): + with Children(self): + self.putFields(value) + self.putQObjectGutsHelper(0, 0, handle, 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): + ptrSize = self.ptrSize() + + def putt(name, value, typeName=' '): + with SubItem(self, name): + self.putValue(value) + self.putType(typeName) + + def extractSuperDataPtr(someMetaObjectPtr): + #return someMetaObjectPtr['d']['superdata'] + return self.extractPointer(someMetaObjectPtr) + + def extractDataPtr(someMetaObjectPtr): + # dataPtr = metaObjectPtr['d']['data'] + if self.qtVersion() >= 0x60000 and self.isWindowsTarget(): + offset = 3 + else: + offset = 2 + return self.extractPointer(someMetaObjectPtr + offset * ptrSize) + + isQMetaObject = origType == 'QMetaObject' + isQObject = origType == 'QObject' + + #DumperBase.warn('OBJECT GUTS: %s 0x%x ' % (self.currentIName, metaObjectPtr)) + dataPtr = extractDataPtr(metaObjectPtr) + #DumperBase.warn('DATA PTRS: %s 0x%x ' % (self.currentIName, dataPtr)) + (revision, classname, + classinfo, classinfo2, + methodCount, methods, + propertyCount, properties, + enumCount, enums, + constructorCount, constructors, + flags, signalCount) = self.split('I' * 14, dataPtr) + + largestStringIndex = -1 + for i in range(methodCount): + t = self.split('IIIII', dataPtr + 56 + i * 20) + if largestStringIndex < t[0]: + largestStringIndex = t[0] + + ns = self.qtNamespace() + extraData = 0 + if qobjectPtr: + dd = self.extractPointer(qobjectPtr + ptrSize) + if self.qtVersion() >= 0x60000: + (dvtablePtr, qptr, parent, children, bindingStorageData, bindingStatus, + flags, postedEvents, dynMetaObjectPtr, # Up to here QObjectData. + extraData, threadDataPtr, connectionListsPtr, + sendersPtr, currentSenderPtr) \ + = self.split('pp{@QObject*}{@QList<@QObject *>}ppIIp' + 'ppppp', dd) + elif self.qtVersion() >= 0x50000: + (dvtablePtr, qptr, parent, children, flags, postedEvents, + dynMetaObjectPtr, # Up to here QObjectData. + extraData, threadDataPtr, connectionListsPtr, + sendersPtr, currentSenderPtr) \ + = self.split('pp{@QObject*}{@QList<@QObject *>}IIp' + 'ppppp', dd) + else: + (dvtablePtr, qptr, parent, children, flags, postedEvents, + dynMetaObjectPtr, # Up to here QObjectData + objectName, extraData, threadDataPtr, connectionListsPtr, + sendersPtr, currentSenderPtr) \ + = self.split('pp{@QObject*}{@QList<@QObject *>}IIp' + 'pppppp', dd) + + with SubItem(self, '[parent]'): + if not self.isCli: + self.putSortGroup(9) + self.putItem(parent) + + with SubItem(self, '[children]'): + if not self.isCli: + self.putSortGroup(8) + + dvtablePtr, qptr, parentPtr, children = self.split('ppp{QList}', dd) + self.putItem(children) + + if isQMetaObject: + with SubItem(self, '[strings]'): + if not self.isCli: + self.putSortGroup(2) + if largestStringIndex > 0: + self.putSpecialValue('minimumitemcount', largestStringIndex) + self.putExpandable() + if self.isExpanded(): + with Children(self, largestStringIndex + 1): + for i in self.childRange(): + with SubItem(self, i): + s = self.metaString(metaObjectPtr, i, revision) + self.putValue(self.hexencode(s), 'latin1') + else: + self.putValue(' ') + + if isQMetaObject: + with SubItem(self, '[raw]'): + self.putSortGroup(1) + self.putEmptyValue() + self.putExpandable() + 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.split('IIIII', dataPtr + 56 + i * 20) + putt('method %d' % i, '%s %s %s %s %s' % t) + + if isQObject: + with SubItem(self, '[extra]'): + self.putSortGroup(1) + self.putEmptyValue() + self.putExpandable() + if self.isExpanded(): + with Children(self): + if extraData: + self.putTypedPointer('[extraData]', extraData, + ns + 'QObjectPrivate::ExtraData') + + with SubItem(self, '[metaObject]'): + self.putAddress(metaObjectPtr) + self.putExpandable() + if self.isExpanded(): + with Children(self): + self.putQObjectGutsHelper( + 0, 0, -1, metaObjectPtr, 'QMetaObject') + + if False: + with SubItem(self, '[connections]'): + if connectionListsPtr: + typeName = '@QObjectConnectionListVector' + self.putItem(self.createValue(connectionListsPtr, typeName)) + else: + self.putItemCount(0) + + if False: + with SubItem(self, '[signals]'): + self.putItemCount(signalCount) + if self.isExpanded(): + with Children(self): + j = -1 + for i in range(signalCount): + t = self.split('IIIII', dataPtr + 56 + 20 * i) + flags = t[4] + if flags != 0x06: + continue + j += 1 + with SubItem(self, j): + name = self.metaString( + metaObjectPtr, t[0], revision) + self.putType(' ') + self.putValue(name) + self.putExpandable() + with Children(self): + putt('[nameindex]', t[0]) + #putt('[type]', 'signal') + 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)) + #self.putQObjectConnections(dd) + + if isQMetaObject or isQObject: + with SubItem(self, '[properties]'): + self.putSortGroup(5) + if self.isExpanded(): + dynamicPropertyCount = 0 + with Children(self): + # Static properties. + for i in range(propertyCount): + if self.qtVersion() >= 0x60000: + t = self.split('IIIII', dataPtr + properties * 4 + 20 * i) + else: + t = self.split('III', dataPtr + properties * 4 + 12 * i) + name = self.metaString(metaObjectPtr, t[0], revision) + if qobject and self.qtPropertyFunc: + # LLDB doesn't like calling it on a derived class, possibly + # due to type information living in a different shared object. + #base = self.createValue(qobjectPtr, '@QObject') + #DumperBase.warn("CALL FUNC: 0x%x" % self.qtPropertyFunc) + cmd = '((QVariant(*)(void*,char*))0x%x)((void*)0x%x,"%s")' \ + % (self.qtPropertyFunc, qobjectPtr, name) + try: + #DumperBase.warn('PROP CMD: %s' % cmd) + res = self.parseAndEvaluate(cmd) + #DumperBase.warn('PROP RES: %s' % res) + except: + self.bump('failedMetaObjectCall') + putt(name, ' ') + continue + #DumperBase.warn('COULD NOT EXECUTE: %s' % cmd) + #self.putCallItem(name, '@QVariant', base, 'property', '"' + name + '"') + if res is None: + self.bump('failedMetaObjectCall2') + putt(name, ' ') + continue + self.putSubItem(name, res) + else: + putt(name, ' ') + + # Dynamic properties. + if extraData: + def list6Generator(addr, innerType): + data, size = self.listData(addr) + for i in range(size): + yield self.createValue(data + i * innerType.size(), innerType) + + def list5Generator(addr, innerType): + data, size = self.listData(addr) + for i in range(size): + yield self.createValue(data + i * ptrSize, innerType) + + def vectorGenerator(addr, innerType): + data, size = self.vectorData(addr) + for i in range(size): + yield self.createValue(data + i * innerType.size(), innerType) + + byteArrayType = self.createType('@QByteArray') + variantType = self.createType('@QVariant') + if self.qtVersion() >= 0x60000: + values = vectorGenerator(extraData + 3 * ptrSize, variantType) + elif self.qtVersion() >= 0x50600: + values = vectorGenerator(extraData + 2 * ptrSize, variantType) + elif self.qtVersion() >= 0x50000: + values = list5Generator(extraData + 2 * ptrSize, variantType) + else: + values = list5Generator(extraData + 2 * ptrSize, + variantType.pointer()) + + if self.qtVersion() >= 0x60000: + names = list6Generator(extraData, byteArrayType) + else: + names = list5Generator(extraData + ptrSize, byteArrayType) + + for (k, v) in zip(names, values): + with SubItem(self, propertyCount + dynamicPropertyCount): + if not self.isCli: + self.putField('key', self.encodeByteArray(k)) + self.putField('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.putExpandable() + + 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.putSortGroup(3) + self.putItemCount(methodCount) + if self.isExpanded(): + with Children(self): + for i in range(methodCount): + t = self.split('IIIII', dataPtr + 56 + 20 * i) + name = self.metaString(metaObjectPtr, t[0], revision) + 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.putItem(self.createValue(dd, '@QObjectPrivate')) + self.putSortGroup(15) + + if isQMetaObject: + with SubItem(self, '[superdata]'): + self.putSortGroup(12) + if superDataPtr: + self.putType('@QMetaObject') + self.putAddress(superDataPtr) + self.putExpandable() + if self.isExpanded(): + with Children(self): + self.putQObjectGutsHelper(0, 0, -1, superDataPtr, 'QMetaObject') + else: + self.putType('@QMetaObject *') + self.putValue('0x0') + + if handle >= 0: + localIndex = int((handle - methods) / 5) + with SubItem(self, '[localindex]'): + self.putSortGroup(12) + self.putValue(localIndex) + with SubItem(self, '[globalindex]'): + self.putSortGroup(11) + self.putValue(globalOffset + localIndex) + + def putQObjectConnections(self, dd): + with SubItem(self, '[connections]'): + ptrSize = self.ptrSize() + self.putNoType() + privateType = self.createType('@QObjectPrivate') + d_ptr = dd.cast(privateType.pointer()).dereference() + connections = d_ptr['connectionLists'] + if self.connections.integer() == 0: + self.putItemCount(0) + else: + connections = connections.dereference() + #connections = connections.cast(connections.type.firstBase()) + self.putSpecialValue('minimumitemcount', 0) + self.putExpandable() + if self.isExpanded(): + pp = 0 + with Children(self): + innerType = connections.type[0] + # Should check: innerType == ns::QObjectPrivate::ConnectionList + data, size = self.vectorData(connections) + connectionType = self.createType('@QObjectPrivate::Connection') + for i in range(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 currentItemFormat(self, typeName=None): + displayFormat = self.formats.get(self.currentIName, DisplayFormat.Automatic) + if displayFormat == DisplayFormat.Automatic: + if typeName is None: + typeName = self.currentType.value + needle = None if typeName is None else self.stripForFormat(typeName) + displayFormat = self.typeformats.get(needle, DisplayFormat.Automatic) + return displayFormat + + def putSubItem(self, component, value): # -> ReportItem + if not isinstance(value, self.Value): + raise RuntimeError('WRONG VALUE TYPE IN putSubItem: %s' % type(value)) + if not isinstance(value.type, self.Type): + raise RuntimeError('WRONG TYPE TYPE IN putSubItem: %s' % type(value.type)) + res = None + with SubItem(self, component): + self.putItem(value) + res = self.currentValue + return res # The 'short' display. + + def putArrayData(self, base, n, innerType, childNumChild=None): + self.checkIntType(base) + self.checkIntType(n) + addrBase = base + innerSize = innerType.size() + self.putNumChild(n) + #DumperBase.warn('ADDRESS: 0x%x INNERSIZE: %s INNERTYPE: %s' % (addrBase, innerSize, innerType)) + enc = innerType.simpleEncoding() + maxNumChild = self.maxArrayCount() + if enc: + self.put('childtype="%s",' % innerType.name) + self.put('addrbase="0x%x",' % addrBase) + self.put('addrstep="0x%x",' % innerSize) + self.put('arrayencoding="%s",' % enc) + self.put('endian="%s",' % self.packCode) + if n > maxNumChild: + self.put('childrenelided="%s",' % n) + 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): + self.checkIntType(addr) + self.checkIntType(n) + 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.putField('plotelided', n) # FIXME: Act on that in frontend + n = maxNumChild + if self.currentItemFormat() == DisplayFormat.ArrayPlot and innerType.isSimpleType(): + enc = innerType.simpleEncoding() + if enc: + self.putField('editencoding', enc) + self.putDisplay('plotdata:separate', + self.readMemory(base, n * innerType.size())) + + def putPlotData(self, base, n, innerType, maxNumChild=1000 * 1000): + self.putPlotDataHelper(base, n, innerType, maxNumChild=maxNumChild) + if self.isExpanded(): + self.putArrayData(base, n, innerType) + + 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 value.integer(): + while p.dereference().integer() 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 range(n): + self.putSubItem(i, p.dereference()) + p += 1 + + def extractPointer(self, value): + try: + if value.type.code == TypeCode.Array: + return value.address() + except: + pass + code = 'I' if self.ptrSize() == 4 else 'Q' + return self.extractSomething(value, code, 8 * self.ptrSize()) + + def extractInt64(self, value): + return self.extractSomething(value, 'q', 64) + + def extractUInt64(self, value): + return self.extractSomething(value, 'Q', 64) + + def extractInt(self, value): + return self.extractSomething(value, 'i', 32) + + def extractUInt(self, value): + return self.extractSomething(value, 'I', 32) + + def extractShort(self, value): + return self.extractSomething(value, 'h', 16) + + def extractUShort(self, value): + return self.extractSomething(value, 'H', 16) + + def extractByte(self, value): + return self.extractSomething(value, 'b', 8) + + def extractSomething(self, value, pattern, bitsize): + if self.isInt(value): + val = self.Value(self) + val.laddress = value + return val.extractSomething(pattern, bitsize) + if isinstance(value, self.Value): + return value.extractSomething(pattern, bitsize) + raise RuntimeError('CANT EXTRACT FROM %s' % type(value)) + + # 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(r'(\.)(\(.+?\))?(\.)', 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 = self.parseAndEvaluate(s[1:len(s) - 1]).integer() if s else 1 + aa = self.parseAndEvaluate(a).integer() + bb = self.parseAndEvaluate(b).integer() + 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.putField('numchild', numchild) + + def handleLocals(self, variables): + #DumperBase.warn('VARIABLES: %s' % variables) + #with self.timer('locals'): + shadowed = {} + for value in variables: + if value.name == 'argv': + if value.type.code == TypeCode.Pointer: + if value.type.ltarget.code == TypeCode.Pointer: + if value.type.ltarget.ltarget.name == 'char': + self.putSpecialArgv(value) + continue + + name = value.name + if name in shadowed: + level = shadowed[name] + shadowed[name] = level + 1 + name += '@%d' % level + else: + shadowed[name] = 1 + # A 'normal' local variable or parameter. + iname = value.iname if hasattr(value, 'iname') else 'local.' + name + with TopLevelItem(self, iname): + #with self.timer('all-' + iname): + self.putField('iname', iname) + self.putField('name', name) + self.putItem(value) + + def handleWatches(self, args): + #with self.timer('watches'): + 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) + #DumperBase.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.putField('iname', iname) + #self.putField('wname', escapedExp) + self.putField('name', exp) + self.putField('exp', exp) + self.putItemCount(n) + self.putNoType() + for i in range(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: + #DumperBase.warn('RANGE: %s %s %s in %s' % (begin, step, end, template)) + r = range(begin, end, step) + n = len(r) + with TopLevelItem(self, iname): + self.putField('iname', iname) + #self.putField('wname', escapedExp) + self.putField('name', exp) + self.putField('exp', 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.putField('iname', iname) + self.putField('wname', escapedExp) + try: + value = self.parseAndEvaluate(exp) + self.putItem(value) + except Exception: + 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:] + if sys.version_info > (3,): + spec = inspect.getfullargspec(function) + else: + 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: + try: + m = __import__(mod) + dic = m.__dict__ + for name in dic.keys(): + item = dic[name] + self.registerDumper(name, item) + except Exception as e: + print('Failed to load dumper module: %s (%s)' % (mod, e)) + + 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 loadDumpers(self, args): + msg = self.setupDumpers() + self.reportResult(msg, 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: + self.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: + self.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: + self.warn('Interpreter command failed: %s: %s' % (encoded, error)) + return {} + except AttributeError as error: + # Happens with LLDB and 'None' current thread. + self.warn('Interpreter command failed: %s: %s' % (encoded, error)) + return {} + if not res: + self.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): + #DumperBase.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 extractInterpreterStack(self): + return self.sendInterpreterRequest('backtrace', {'limit': 10}) + + def isInt(self, thing): + if isinstance(thing, int): + return True + if sys.version_info[0] == 2: + if isinstance(thing, long): + return True + return False + + def putItems(self, count, generator, maxNumChild=10000): + self.putItemCount(count) + if self.isExpanded(): + with Children(self, count, maxNumChild=maxNumChild): + for i, val in zip(self.childRange(), generator): + self.putSubItem(i, val) + + def putItem(self, value): + #with self.timer('putItem'): + self.putItemX(value) + + def putItemX(self, value): + #DumperBase.warn('PUT ITEM: %s' % value.stringify()) + + typeobj = value.type # unqualified() + typeName = typeobj.name + + self.addToCache(typeobj) # Fill type cache + + if not value.lIsInScope: + self.putSpecialValue('optimizedout') + #self.putType(typeobj) + #self.putSpecialValue('outofscope') + self.putNumChild(0) + return + + if not isinstance(value, self.Value): + raise RuntimeError('WRONG TYPE IN putItem: %s' % type(self.Value)) + + # Try on possibly typedefed type first. + if self.tryPutPrettyItem(typeName, value): + if typeobj.code == TypeCode.Pointer: + self.putOriginalAddress(value.address()) + else: + self.putAddress(value.address()) + return + + if typeobj.code == TypeCode.Typedef: + #DumperBase.warn('TYPEDEF VALUE: %s' % value.stringify()) + self.putItem(value.detypedef()) + self.putBetterType(typeName) + return + + if typeobj.code == TypeCode.Pointer: + self.putFormattedPointer(value) + if value.summary and self.useFancy: + self.putValue(self.hexencode(value.summary), 'utf8:1:0') + return + + self.putAddress(value.address()) + if value.lbitsize is not None: + self.putField('size', value.lbitsize // 8) + + if typeobj.code == TypeCode.Function: + #DumperBase.warn('FUNCTION VALUE: %s' % value) + self.putType(typeobj) + self.putSymbolValue(value.pointer()) + self.putNumChild(0) + return + + if typeobj.code == TypeCode.Enum: + #DumperBase.warn('ENUM VALUE: %s' % value.stringify()) + self.putType(typeobj.name) + self.putValue(value.display()) + self.putNumChild(0) + return + + if typeobj.code == TypeCode.Array: + #DumperBase.warn('ARRAY VALUE: %s' % value) + self.putCStyleArray(value) + return + + if typeobj.code == TypeCode.Bitfield: + #DumperBase.warn('BITFIELD VALUE: %s %d %s' % (value.name, value.lvalue, typeName)) + self.putNumChild(0) + dd = typeobj.ltarget.tdata.enumDisplay + self.putValue(str(value.lvalue) if dd is None else dd( + value.lvalue, value.laddress, '%d')) + self.putType(typeName) + return + + if typeobj.code == TypeCode.Integral: + #DumperBase.warn('INTEGER: %s %s' % (value.name, value)) + val = value.value() + self.putNumChild(0) + self.putValue(val) + self.putType(typeName) + return + + if typeobj.code == TypeCode.Float: + #DumperBase.warn('FLOAT VALUE: %s' % value) + self.putValue(value.value()) + self.putNumChild(0) + self.putType(typeobj.name) + return + + if typeobj.code in (TypeCode.Reference, TypeCode.RValueReference): + #DumperBase.warn('REFERENCE VALUE: %s' % value) + val = value.dereference() + if val.laddress != 0: + self.putItem(val) + else: + self.putSpecialValue('nullreference') + self.putBetterType(typeName) + return + + if typeobj.code == TypeCode.Complex: + self.putType(typeobj) + self.putValue(value.display()) + self.putNumChild(0) + return + + if typeobj.code == TypeCode.FortranString: + self.putValue(self.hexencode(value.data()), 'latin1') + self.putNumChild(0) + self.putType(typeobj) + + if typeName.endswith('[]'): + # D arrays, gdc compiled. + n = value['length'] + base = value['ptr'] + self.putType(typeName) + self.putItemCount(n) + if self.isExpanded(): + self.putArrayData(base.pointer(), n, base.type.target()) + return + + #DumperBase.warn('SOME VALUE: %s' % value) + #DumperBase.warn('GENERIC STRUCT: %s' % typeobj) + #DumperBase.warn('INAME: %s ' % self.currentIName) + #DumperBase.warn('INAMES: %s ' % self.expandedINames) + #DumperBase.warn('EXPANDED: %s ' % (self.currentIName in self.expandedINames)) + self.putType(typeName) + + if value.summary is not None and self.useFancy: + self.putValue(self.hexencode(value.summary), 'utf8:1:0') + self.putNumChild(0) + return + + self.putExpandable() + self.putEmptyValue() + #DumperBase.warn('STRUCT GUTS: %s ADDRESS: 0x%x ' % (value.name, value.address())) + if self.showQObjectNames: + #with self.timer(self.currentIName): + self.putQObjectNameValue(value) + if self.isExpanded(): + if not self.isCli: + self.putField('sortable', 1) + with Children(self, 1, childType=None): + self.putFields(value) + if self.showQObjectNames: + self.tryPutQObjectGuts(value) + + def symbolAddress(self, symbolName): + res = self.parseAndEvaluate('(size_t)&' + symbolName) + return None if res is None else res.pointer() + + def qtHookDataSymbolName(self): + return 'qtHookData' + + def qtTypeInfoVersion(self): + addr = self.symbolAddress(self.qtHookDataSymbolName()) + if addr: + # Only available with Qt 5.3+ + (hookVersion, x, x, x, x, x, tiVersion) = self.split('ppppppp', addr) + #DumperBase.warn('HOOK: %s TI: %s' % (hookVersion, tiVersion)) + if hookVersion >= 3: + self.qtTypeInfoVersion = lambda: tiVersion + return tiVersion + return None + + def qtDeclarativeHookDataSymbolName(self): + return 'qtDeclarativeHookData' + + def qtDeclarativeTypeInfoVersion(self): + addr = self.symbolAddress(self.qtDeclarativeHookDataSymbolName()) + if addr: + # Only available with Qt 5.6+ + (hookVersion, x, tiVersion) = self.split('ppp', addr) + if hookVersion >= 1: + self.qtTypeInfoVersion = lambda: tiVersion + return tiVersion + return None + + def addToCache(self, typeobj): + typename = typeobj.name + if typename in self.typesReported: + return + self.typesReported[typename] = True + self.typesToReport[typename] = typeobj + + class Value(): + def __init__(self, dumper): + # This can be helpful to track down from where a Value was created + #self._stack = inspect.stack() + self.dumper = dumper + self.name = None + self._type = None + self.ldata = None # Target address in case of references and pointers. + self.laddress = None # Own address. + self.lvalue = None + self.lIsInScope = True + self.ldisplay = None + self.summary = None # Always hexencoded UTF-8. + self.lbitpos = None + self.lbitsize = None + self.targetValue = None # For references. + self.isBaseClass = None + self.nativeValue = None + self.autoDerefCount = 0 + + def copy(self): + val = self.dumper.Value(self.dumper) + val.dumper = self.dumper + val.name = self.name + val._type = self._type + val.ldata = self.ldata + val.laddress = self.laddress + val.lIsInScope = self.lIsInScope + val.ldisplay = self.ldisplay + val.summary = self.summary + val.lbitpos = self.lbitpos + val.lbitsize = self.lbitsize + val.targetValue = self.targetValue + val.nativeValue = self.nativeValue + return val + + @property + def type(self): + if self._type is None and self.nativeValue is not None: + self._type = self.dumper.nativeValueType(self.nativeValue) + return self._type + + def check(self): + if self.laddress is not None and not self.dumper.isInt(self.laddress): + raise RuntimeError('INCONSISTENT ADDRESS: %s' % type(self.laddress)) + if self.type is not None and not isinstance(self.type, self.dumper.Type): + raise RuntimeError('INCONSISTENT TYPE: %s' % type(self.type)) + + def __str__(self): + #raise RuntimeError('Not implemented') + return self.stringify() + + def __int__(self): + return self.integer() + + def stringify(self): + addr = 'None' if self.laddress is None else ('0x%x' % self.laddress) + return "Value(name='%s',type=%s,bsize=%s,bpos=%s,data=%s,address=%s)" \ + % (self.name, self.type.name, self.lbitsize, self.lbitpos, + self.dumper.hexencode(self.ldata), addr) + + def displayEnum(self, form='%d', bitsize=None): + intval = self.integer(bitsize) + dd = self.type.tdata.enumDisplay + if dd is None: + return str(intval) + return dd(intval, self.laddress, form) + + def display(self): + if self.ldisplay is not None: + return self.ldisplay + simple = self.value() + if simple is not None: + return str(simple) + #if self.ldata is not None: + # if sys.version_info[0] == 2 and isinstance(self.ldata, buffer): + # return bytes(self.ldata).encode('hex') + # return self.ldata.encode('hex') + if self.laddress is not None: + return 'value of type %s at address 0x%x' % (self.type.name, self.laddress) + return '' + + def pointer(self): + if self.type.code == TypeCode.Typedef: + return self.detypedef().pointer() + return self.extractInteger(self.dumper.ptrSize() * 8, True) + + def integer(self, bitsize=None): + if self.type.code == TypeCode.Typedef: + return self.detypedef().integer() + elif isinstance(self.lvalue, int): + return self.lvalue + # Could be something like 'short unsigned int' + unsigned = self.type.name == 'unsigned' \ + or self.type.name.startswith('unsigned ') \ + or self.type.name.find(' unsigned ') != -1 + if bitsize is None: + bitsize = self.type.bitsize() + return self.extractInteger(bitsize, unsigned) + + def floatingPoint(self): + if self.nativeValue is not None and not self.dumper.isCdb: + return str(self.nativeValue) + if self.type.code == TypeCode.Typedef: + return self.detypedef().floatingPoint() + if self.type.size() == 8: + return self.extractSomething('d', 64) + if self.type.size() == 4: + return self.extractSomething('f', 32) + # Fall back in case we don't have a nativeValue at hand. + # FIXME: This assumes Intel's 80bit extended floats. Which might + # be wrong. + l, h = self.split('QQ') + if True: # 80 bit floats + sign = (h >> 15) & 1 + exp = (h & 0x7fff) + fraction = l + bit63 = (l >> 63) & 1 + #DumperBase.warn("SIGN: %s EXP: %s H: 0x%x L: 0x%x" % (sign, exp, h, l)) + if exp == 0: + if bit63 == 0: + if l == 0: + res = '-0' if sign else '0' + else: + res = (-1)**sign * l * 2**(-16382) # subnormal + else: + res = 'pseudodenormal' + elif exp == 0x7fff: + res = 'special' + else: + res = (-1)**sign * l * 2**(exp - 16383 - 63) + else: # 128 bits + sign = h >> 63 + exp = (h >> 48) & 0x7fff + fraction = h & (2**48 - 1) + #DumperBase.warn("SIGN: %s EXP: %s FRAC: %s H: 0x%x L: 0x%x" % (sign, exp, fraction, h, l)) + if exp == 0: + if fraction == 0: + res = -0.0 if sign else 0.0 + else: + res = (-1)**sign * fraction / 2**48 * 2**(-62) # subnormal + elif exp == 0x7fff: + res = ('-inf' if sign else 'inf') if fraction == 0 else 'nan' + else: + res = (-1)**sign * (1 + fraction / 2**48) * 2**(exp - 63) + return res + + def value(self): + if self.type is not None: + if self.type.code == TypeCode.Enum: + return self.displayEnum() + if self.type.code == TypeCode.Typedef: + return self.detypedef().value() + if self.type.code == TypeCode.Integral: + return self.integer() + if self.type.code == TypeCode.Bitfield: + return self.integer() + if self.type.code == TypeCode.Float: + return self.floatingPoint() + if self.type.code == TypeCode.Pointer: + return self.pointer() + return None + + def extractPointer(self): + return self.split('p')[0] + + def hasMember(self, name): + return self.findMemberByName(name) is not None + + def findMemberByName(self, name): + self.check() + if self.type.code == TypeCode.Typedef: + return self.findMemberByName(self.detypedef()) + if self.type.code in ( + TypeCode.Pointer, + TypeCode.Reference, + TypeCode.RValueReference): + res = self.dereference().findMemberByName(name) + if res is not None: + return res + if self.type.code == TypeCode.Struct: + #DumperBase.warn('SEARCHING FOR MEMBER: %s IN %s' % (name, self.type.name)) + members = self.members(True) + #DumperBase.warn('MEMBERS: %s' % members) + for member in members: + #DumperBase.warn('CHECKING FIELD %s' % member.name) + if member.type.code == TypeCode.Typedef: + member = member.detypedef() + if member.name == name: + return member + for member in members: + if member.type.code == TypeCode.Typedef: + member = member.detypedef() + if member.name == name: # Could be base class. + return member + if member.type.code == TypeCode.Struct: + res = member.findMemberByName(name) + if res is not None: + return res + return None + + def __getitem__(self, index): + #DumperBase.warn('GET ITEM %s %s' % (self, index)) + self.check() + if self.type.code == TypeCode.Typedef: + #DumperBase.warn('GET ITEM STRIP TYPEDEFS TO %s' % self.type.ltarget) + return self.cast(self.type.ltarget).__getitem__(index) + if isinstance(index, str): + if self.type.code == TypeCode.Pointer: + #DumperBase.warn('GET ITEM %s DEREFERENCE TO %s' % (self, self.dereference())) + return self.dereference().__getitem__(index) + res = self.findMemberByName(index) + if res is None: + raise RuntimeError('No member named %s in type %s' + % (index, self.type.name)) + return res + elif isinstance(index, self.dumper.Field): + field = index + elif self.dumper.isInt(index): + if self.type.code == TypeCode.Array: + addr = self.laddress + int(index) * self.type.ltarget.size() + return self.dumper.createValue(addr, self.type.ltarget) + if self.type.code == TypeCode.Pointer: + addr = self.pointer() + int(index) * self.type.ltarget.size() + return self.dumper.createValue(addr, self.type.ltarget) + return self.members(False)[index] + else: + raise RuntimeError('BAD INDEX TYPE %s' % type(index)) + field.check() + + #DumperBase.warn('EXTRACT FIELD: %s, BASE 0x%x' % (field, self.address())) + if self.type.code == TypeCode.Pointer: + #DumperBase.warn('IS TYPEDEFED POINTER!') + res = self.dereference() + #DumperBase.warn('WAS POINTER: %s' % res) + + return field.extract(self) + + def extractField(self, field): + if not isinstance(field, self.dumper.Field): + raise RuntimeError('BAD INDEX TYPE %s' % type(field)) + + if field.extractor is not None: + val = field.extractor(self) + if val is not None: + #DumperBase.warn('EXTRACTOR SUCCEEDED: %s ' % val) + return val + + if self.type.code == TypeCode.Typedef: + return self.cast(self.type.ltarget).extractField(field) + if self.type.code in (TypeCode.Reference, TypeCode.RValueReference): + return self.dereference().extractField(field) + #DumperBase.warn('FIELD: %s ' % (field,)) + val = self.dumper.Value(self.dumper) + val.name = field.name + val.isBaseClass = field.isBase + val._type = field.fieldType() + + if field.isArtificial: + if self.laddress is not None: + val.laddress = self.laddress + if self.ldata is not None: + val.ldata = self.ldata + return val + + fieldBitsize = field.bitsize + fieldSize = (fieldBitsize + 7) // 8 + fieldBitpos = field.bitpos + fieldOffset = fieldBitpos // 8 + fieldType = field.fieldType() + + if fieldType.code == TypeCode.Bitfield: + fieldBitpos -= fieldOffset * 8 + ldata = self.data() + data = 0 + for i in range(fieldSize): + data = data << 8 + if self.dumper.isBigEndian: + lbyte = ldata[i] + else: + lbyte = ldata[fieldOffset + fieldSize - 1 - i] + if isinstance(lbyte, (str, bytes)): + data += ord(lbyte) + else: + data += lbyte + data = data >> fieldBitpos + data = data & ((1 << fieldBitsize) - 1) + val.lvalue = data + val.laddress = None + return val + + if self.laddress is not None: + val.laddress = self.laddress + fieldOffset + elif self.ldata is not None: + val.ldata = self.ldata[fieldOffset:fieldOffset + fieldSize] + else: + self.dumper.check(False) + + if fieldType.code in (TypeCode.Reference, TypeCode.RValueReference): + if val.laddress is not None: + val = self.dumper.createReferenceValue(val.laddress, fieldType.ltarget) + val.name = field.name + + #DumperBase.warn('GOT VAL %s FOR FIELD %s' % (val, field)) + val.lbitsize = fieldBitsize + val.check() + return val + + # This is the generic version for synthetic values. + # The native backends replace it in their fromNativeValue() + # implementations. + def members(self, includeBases): + #DumperBase.warn("LISTING MEMBERS OF %s" % self) + if self.type.code == TypeCode.Typedef: + return self.detypedef().members(includeBases) + + tdata = self.type.tdata + #if isinstance(tdata.lfields, list): + # return tdata.lfields + + fields = [] + if tdata.lfields is not None: + if isinstance(tdata.lfields, list): + fields = tdata.lfields + else: + fields = list(tdata.lfields(self)) + + #DumperBase.warn("FIELDS: %s" % fields) + res = [] + for field in fields: + if isinstance(field, self.dumper.Value): + #DumperBase.warn("USING VALUE DIRECTLY %s" % field.name) + res.append(field) + continue + if field.isBase and not includeBases: + #DumperBase.warn("DROPPING BASE %s" % field.name) + continue + res.append(self.extractField(field)) + #DumperBase.warn("GOT MEMBERS: %s" % res) + return res + + def __add__(self, other): + self.check() + if self.dumper.isInt(other): + stripped = self.type.stripTypedefs() + if stripped.code == TypeCode.Pointer: + address = self.pointer() + stripped.dereference().size() * other + val = self.dumper.Value(self.dumper) + val.laddress = None + val.ldata = bytes(struct.pack(self.dumper.packCode + 'Q', address)) + val._type = self._type + return val + raise RuntimeError('BAD DATA TO ADD TO: %s %s' % (self.type, other)) + + def __sub__(self, other): + self.check() + if self.type.name == other.type.name: + stripped = self.type.stripTypedefs() + if stripped.code == TypeCode.Pointer: + return (self.pointer() - other.pointer()) // stripped.dereference().size() + raise RuntimeError('BAD DATA TO SUB TO: %s %s' % (self.type, other)) + + def dereference(self): + self.check() + if self.type.code == TypeCode.Typedef: + return self.detypedef().dereference() + val = self.dumper.Value(self.dumper) + if self.type.code in (TypeCode.Reference, TypeCode.RValueReference): + val.summary = self.summary + if self.nativeValue is None: + val.laddress = self.pointer() + if val.laddress is None and self.laddress is not None: + val.laddress = self.laddress + val._type = self.type.dereference() + if self.dumper.useDynamicType: + val._type = self.dumper.nativeDynamicType(val.laddress, val.type) + else: + val = self.dumper.nativeValueDereferenceReference(self) + elif self.type.code == TypeCode.Pointer: + try: + val = self.dumper.nativeValueDereferencePointer(self) + except: + val.laddress = self.pointer() + val._type = self.type.dereference() + if self.dumper.useDynamicType: + val._type = self.dumper.nativeDynamicType(val.laddress, val.type) + else: + raise RuntimeError("WRONG: %s" % self.type.code) + #DumperBase.warn("DEREFERENCING FROM: %s" % self) + #DumperBase.warn("DEREFERENCING TO: %s" % val) + #dynTypeName = val.type.dynamicTypeName(val.laddress) + #if dynTypeName is not None: + # val._type = self.dumper.createType(dynTypeName) + return val + + def detypedef(self): + self.check() + if self.type.code != TypeCode.Typedef: + raise RuntimeError("WRONG") + val = self.copy() + val._type = self.type.ltarget + #DumperBase.warn("DETYPEDEF FROM: %s" % self) + #DumperBase.warn("DETYPEDEF TO: %s" % val) + return val + + def extend(self, size): + if self.type.size() < size: + val = self.dumper.Value(self.dumper) + val.laddress = None + val.ldata = self.zeroExtend(self.ldata) + return val + if self.type.size() == size: + return self + raise RuntimeError('NOT IMPLEMENTED') + + def zeroExtend(self, data, size): + ext = '\0' * (size - len(data)) + if sys.version_info[0] == 3: + pad = bytes(ext, encoding='latin1') + else: + pad = bytes(ext) + return pad + data if self.dumper.isBigEndian else data + pad + + def cast(self, typish): + self.check() + val = self.dumper.Value(self.dumper) + val.laddress = self.laddress + val.lbitsize = self.lbitsize + val.ldata = self.ldata + val._type = self.dumper.createType(typish) + return val + + def address(self): + self.check() + return self.laddress + + def data(self, size=None): + self.check() + if self.ldata is not None: + if len(self.ldata) > 0: + if size is None: + return self.ldata + if size == len(self.ldata): + return self.ldata + if size < len(self.ldata): + return self.ldata[:size] + #raise RuntimeError('ZERO-EXTENDING DATA TO %s BYTES: %s' % (size, self)) + return self.zeroExtend(self.ldata, size) + if self.laddress is not None: + if size is None: + size = self.type.size() + res = self.dumper.readRawMemory(self.laddress, size) + if len(res) > 0: + return res + raise RuntimeError('CANNOT CONVERT ADDRESS TO BYTES: %s' % self) + raise RuntimeError('CANNOT CONVERT TO BYTES: %s' % self) + + def extractInteger(self, bitsize, unsigned): + #with self.dumper.timer('extractInt'): + self.check() + if bitsize > 32: + size = 8 + code = 'Q' if unsigned else 'q' + elif bitsize > 16: + size = 4 + code = 'I' if unsigned else 'i' + elif bitsize > 8: + size = 2 + code = 'H' if unsigned else 'h' + else: + size = 1 + code = 'B' if unsigned else 'b' + rawBytes = self.data(size) + res = struct.unpack_from(self.dumper.packCode + code, rawBytes, 0)[0] + #DumperBase.warn('Extract: Code: %s Bytes: %s Bitsize: %s Size: %s' + # % (self.dumper.packCode + code, self.dumper.hexencode(rawBytes), bitsize, size)) + return res + + def extractSomething(self, code, bitsize): + #with self.dumper.timer('extractSomething'): + self.check() + size = (bitsize + 7) >> 3 + rawBytes = self.data(size) + res = struct.unpack_from(self.dumper.packCode + code, rawBytes, 0)[0] + return res + + def to(self, pattern): + return self.split(pattern)[0] + + def split(self, pattern): + #with self.dumper.timer('split'): + #DumperBase.warn('EXTRACT STRUCT FROM: %s' % self.type) + (pp, size, fields) = self.dumper.describeStruct(pattern) + #DumperBase.warn('SIZE: %s ' % size) + result = struct.unpack_from(self.dumper.packCode + pp, self.data(size)) + + def structFixer(field, thing): + #DumperBase.warn('STRUCT MEMBER: %s' % type(thing)) + if field.isStruct: + #if field.type != field.fieldType(): + # raise RuntimeError('DO NOT SIMPLIFY') + #DumperBase.warn('FIELD POS: %s' % field.type.stringify()) + #DumperBase.warn('FIELD TYE: %s' % field.fieldType().stringify()) + res = self.dumper.createValue(thing, field.fieldType()) + #DumperBase.warn('RES TYPE: %s' % res.type) + if self.laddress is not None: + res.laddress = self.laddress + field.offset() + return res + return thing + if len(fields) != len(result): + raise RuntimeError('STRUCT ERROR: %s %s' % (fields, result)) + return tuple(map(structFixer, fields, result)) + + def checkPointer(self, p, align=1): + ptr = p if self.isInt(p) else p.pointer() + self.readRawMemory(ptr, 1) + + def type(self, typeId): + return self.typeData.get(typeId) + + def splitArrayType(self, type_name): + # "foo[2][3][4]" -> ("foo", "[3][4]", 2) + pos1 = len(type_name) + # In case there are more dimensions we need the inner one. + while True: + pos1 = type_name.rfind('[', 0, pos1 - 1) + pos2 = type_name.find(']', pos1) + if type_name[pos1 - 1] != ']': + break + + item_count = type_name[pos1 + 1:pos2] + return (type_name[0:pos1].strip(), type_name[pos2 + 1:].strip(), int(item_count)) + + def registerTypeAlias(self, existingTypeId, aliasId): + #DumperBase.warn('REGISTER ALIAS %s FOR %s' % (aliasId, existingTypeId)) + self.typeData[aliasId] = self.typeData[existingTypeId] + + class TypeData(): + def __init__(self, dumper, type_id): + self.dumper = dumper + self.lfields = None # None or Value -> list of member Values + self.lalignment = None # Function returning alignment of this struct + self.lbitsize = None + self.ltarget = None # Inner type for arrays + self.templateArguments = None + self.code = None + self.name = type_id + self.typeId = type_id + self.enumDisplay = None + self.moduleName = None + #DumperBase.warn('REGISTER TYPE: %s' % type_id) + dumper.typeData[type_id] = self + + def copy(self): + tdata = self.dumper.TypeData(self.dumper, self.typeId) + tdata.dumper = self.dumper + tdata.lfields = self.lfields + tdata.lalignment = self.lalignment + tdata.lbitsize = self.lbitsize + tdata.ltarget = self.ltarget + tdata.templateArguments = self.templateArguments + tdata.code = self.code + tdata.name = self.name + tdata.typeId = self.typeId + tdata.enumDisplay = self.enumDisplay + tdata.moduleName = self.moduleName + return tdata + + class Type(): + def __init__(self, dumper, typeId): + self.typeId = typeId + self.dumper = dumper + self.tdata = dumper.typeData.get(typeId, None) + if self.tdata is None: + #DumperBase.warn('USING : %s' % self.typeId) + self.dumper.lookupType(self.typeId) + self.tdata = self.dumper.typeData.get(self.typeId) + + def __str__(self): + #return self.typeId + return self.stringify() + + @property + def name(self): + tdata = self.dumper.typeData.get(self.typeId) + if tdata is None: + return self.typeId + return tdata.name + + @property + def code(self): + return self.tdata.code + + @property + def lbitsize(self): + return self.tdata.lbitsize + + @property + def lbitpos(self): + return self.tdata.lbitpos + + @property + def ltarget(self): + return self.tdata.ltarget + + @property + def moduleName(self): + return self.tdata.moduleName + + def stringify(self): + return 'Type(name="%s",bsize=%s,code=%s)' \ + % (self.tdata.name, self.tdata.lbitsize, self.tdata.code) + + def __getitem__(self, index): + if self.dumper.isInt(index): + return self.templateArgument(index) + raise RuntimeError('CANNOT INDEX TYPE') + + def dynamicTypeName(self, address): + if self.tdata.code != TypeCode.Struct: + return None + try: + vtbl = self.dumper.extractPointer(address) + except: + return None + #DumperBase.warn('VTBL: 0x%x' % vtbl) + if not self.dumper.couldBePointer(vtbl): + return None + return self.dumper.nativeDynamicTypeName(address, self) + + def dynamicType(self, address): + # FIXME: That buys some performance at the cost of a fail + # of Gdb13393 dumper test. + #return self + #with self.dumper.timer('dynamicType %s 0x%s' % (self.name, address)): + dynTypeName = self.dynamicTypeName(address) + if dynTypeName is not None: + return self.dumper.createType(dynTypeName) + return self + + def check(self): + if self.tdata.name is None: + raise RuntimeError('TYPE WITHOUT NAME: %s' % self.typeId) + + def dereference(self): + if self.code == TypeCode.Typedef: + return self.ltarget.dereference() + self.check() + return self.ltarget + + def unqualified(self): + return self + + def templateArguments(self): + if self.tdata is None: + return self.dumper.listTemplateParameters(self.typeId) + return self.tdata.templateArguments() + + def templateArgument(self, position): + #DumperBase.warn('TDATA: %s' % self.tdata) + #DumperBase.warn('ID: %s' % self.typeId) + if self.tdata is None: + # Native lookups didn't help. Happens for 'wrong' placement of 'const' + # etc. with LLDB. But not all is lost: + ta = self.dumper.listTemplateParameters(self.typeId) + #DumperBase.warn('MANUAL: %s' % ta) + res = ta[position] + #DumperBase.warn('RES: %s' % res.typeId) + return res + #DumperBase.warn('TA: %s %s' % (position, self.typeId)) + #DumperBase.warn('ARGS: %s' % self.tdata.templateArguments()) + return self.tdata.templateArguments()[position] + + def simpleEncoding(self): + res = { + 'bool': 'int:1', + 'char': 'int:1', + 'int8_t': 'int:1', + 'qint8': 'int:1', + 'signed char': 'int:1', + 'char8_t': 'uint:1', + 'unsigned char': 'uint:1', + 'uint8_t': 'uint:1', + 'quint8': 'uint:1', + 'short': 'int:2', + 'int16_t': 'int:2', + 'qint16': 'int:2', + 'unsigned short': 'uint:2', + 'char16_t': 'uint:2', + 'uint16_t': 'uint:2', + 'quint16': 'uint:2', + 'int': 'int:4', + 'int32_t': 'int:4', + 'qint32': 'int:4', + 'unsigned int': 'uint:4', + 'char32_t': 'uint:4', + 'uint32_t': 'uint:4', + 'quint32': 'uint:4', + 'long long': 'int:8', + 'int64_t': 'int:8', + 'qint64': 'int:8', + 'unsigned long long': 'uint:8', + 'uint64_t': 'uint:8', + 'quint64': 'uint:8', + 'float': 'float:4', + 'double': 'float:8', + 'QChar': 'uint:2' + }.get(self.name, None) + return res + + def isSimpleType(self): + return self.code in (TypeCode.Integral, TypeCode.Float, TypeCode.Enum) + + def alignment(self): + if self.tdata.code == TypeCode.Typedef: + return self.tdata.ltarget.alignment() + if self.tdata.code in (TypeCode.Integral, TypeCode.Float, TypeCode.Enum): + if self.tdata.name in ('double', 'long long', 'unsigned long long'): + # Crude approximation. + return 8 if self.dumper.isWindowsTarget() else self.dumper.ptrSize() + return self.size() + if self.tdata.code in (TypeCode.Pointer, TypeCode.Reference, TypeCode.RValueReference): + return self.dumper.ptrSize() + if self.tdata.lalignment is not None: + #if isinstance(self.tdata.lalignment, function): # Does not work that way. + if hasattr(self.tdata.lalignment, '__call__'): + return self.tdata.lalignment() + return self.tdata.lalignment + return 1 + + def pointer(self): + return self.dumper.createPointerType(self) + + def target(self): + return self.tdata.ltarget + + def stripTypedefs(self): + if isinstance(self, self.dumper.Type) and self.code != TypeCode.Typedef: + #DumperBase.warn('NO TYPEDEF: %s' % self) + return self + return self.ltarget + + def size(self): + bs = self.bitsize() + if bs % 8 != 0: + DumperBase.warn('ODD SIZE: %s' % self) + return (7 + bs) >> 3 + + def bitsize(self): + if self.lbitsize is not None: + return self.lbitsize + raise RuntimeError('DONT KNOW SIZE: %s' % self) + + def isMovableType(self): + if self.code in (TypeCode.Pointer, TypeCode.Integral, TypeCode.Float): + return True + strippedName = self.dumper.stripNamespaceFromType(self.name) + if strippedName 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 + if strippedName == 'QStringList': + return self.dumper.qtVersion() >= 0x050000 + if strippedName == 'QList': + return self.dumper.qtVersion() >= 0x050600 + return False + + class Field(collections.namedtuple('Field', + ['dumper', 'name', 'type', 'bitsize', 'bitpos', + 'extractor', 'isBase', 'isStruct', 'isArtificial'])): + + def __new__(cls, dumper, name=None, type=None, bitsize=None, bitpos=None, + extractor=None, isBase=False, isStruct=False, isArtificial=False): + return super(DumperBase.Field, cls).__new__( + cls, dumper, name, type, bitsize, bitpos, + extractor, isBase, isStruct, isArtificial) + + __slots__ = () + + def __str__(self): + return self.stringify() + + def stringify(self): + #return 'Field(name="%s")' % self.name + typename = None if self.type is None else self.type.stringify() + return 'Field(name="%s",type=%s,bitpos=%s,bitsize=%s)' \ + % (self.name, typename, self.bitpos, self.bitsize) + + def check(self): + pass + + def size(self): + return self.bitsize() // 8 + + def offset(self): + return self.bitpos // 8 + + def fieldType(self): + if self.type is not None: + return self.type + raise RuntimeError('CANT GET FIELD TYPE FOR %s' % self) + return None + + def ptrCode(self): + return 'I' if self.ptrSize() == 4 else 'Q' + + def toPointerData(self, address): + if not self.isInt(address): + raise RuntimeError('wrong') + return bytes(struct.pack(self.packCode + self.ptrCode(), address)) + + def fromPointerData(self, bytes_value): + return struct.unpack(self.packCode + self.ptrCode(), bytes_value) + + def createPointerValue(self, targetAddress, targetTypish): + if not isinstance(targetTypish, self.Type) and not isinstance(targetTypish, str): + raise RuntimeError('Expected type in createPointerValue(), got %s' + % type(targetTypish)) + if not self.isInt(targetAddress): + raise RuntimeError('Expected integral address value in createPointerValue(), got %s' + % type(targetTypish)) + val = self.Value(self) + val.ldata = self.toPointerData(targetAddress) + targetType = self.createType(targetTypish) + if self.useDynamicType: + targetType = targetType.dynamicType(targetAddress) + val._type = self.createPointerType(targetType) + return val + + def createReferenceValue(self, targetAddress, targetType): + if not isinstance(targetType, self.Type): + raise RuntimeError('Expected type in createReferenceValue(), got %s' + % type(targetType)) + if not self.isInt(targetAddress): + raise RuntimeError('Expected integral address value in createReferenceValue(), got %s' + % type(targetType)) + val = self.Value(self) + val.ldata = self.toPointerData(targetAddress) + if self.useDynamicType: + targetType = targetType.dynamicType(targetAddress) + val._type = self.createReferenceType(targetType) + return val + + def createPointerType(self, targetType): + if not isinstance(targetType, self.Type): + raise RuntimeError('Expected type in createPointerType(), got %s' + % type(targetType)) + typeId = targetType.typeId + ' *' + tdata = self.TypeData(self, typeId) + tdata.name = targetType.name + '*' + tdata.lbitsize = 8 * self.ptrSize() + tdata.code = TypeCode.Pointer + tdata.ltarget = targetType + return self.Type(self, typeId) + + def createReferenceType(self, targetType): + if not isinstance(targetType, self.Type): + raise RuntimeError('Expected type in createReferenceType(), got %s' + % type(targetType)) + typeId = targetType.typeId + ' &' + tdata = self.TypeData(self, typeId) + tdata.name = targetType.name + ' &' + tdata.code = TypeCode.Reference + tdata.ltarget = targetType + tdata.lbitsize = 8 * self.ptrSize() # Needed for Gdb13393 test. + #tdata.lbitsize = None + return self.Type(self, typeId) + + def createRValueReferenceType(self, targetType): + if not isinstance(targetType, self.Type): + raise RuntimeError('Expected type in createRValueReferenceType(), got %s' + % type(targetType)) + typeId = targetType.typeId + ' &&' + tdata = self.TypeData(self, typeId) + tdata.name = targetType.name + ' &&' + tdata.code = TypeCode.RValueReference + tdata.ltarget = targetType + tdata.lbitsize = None + return self.Type(self, typeId) + + def createArrayType(self, targetType, count): + if not isinstance(targetType, self.Type): + raise RuntimeError('Expected type in createArrayType(), got %s' + % type(targetType)) + targetTypeId = targetType.typeId + + if targetTypeId.endswith(']'): + (prefix, suffix, inner_count) = self.splitArrayType(targetTypeId) + type_id = '%s[%d][%d]%s' % (prefix, count, inner_count, suffix) + type_name = type_id + else: + type_id = '%s[%d]' % (targetTypeId, count) + type_name = '%s[%d]' % (targetType.name, count) + + tdata = self.TypeData(self, type_id) + tdata.name = type_name + tdata.code = TypeCode.Array + tdata.ltarget = targetType + tdata.lbitsize = targetType.lbitsize * count + return self.Type(self, type_id) + + def createBitfieldType(self, targetType, bitsize): + if not isinstance(targetType, self.Type): + raise RuntimeError('Expected type in createBitfieldType(), got %s' + % type(targetType)) + typeId = '%s:%d' % (targetType.typeId, bitsize) + tdata = self.TypeData(self, typeId) + tdata.name = '%s : %d' % (targetType.typeId, bitsize) + tdata.code = TypeCode.Bitfield + tdata.ltarget = targetType + tdata.lbitsize = bitsize + return self.Type(self, typeId) + + def createTypedefedType(self, targetType, typeName, typeId=None): + if typeId is None: + typeId = typeName + if not isinstance(targetType, self.Type): + raise RuntimeError('Expected type in createTypedefType(), got %s' + % type(targetType)) + # Happens for C-style struct in GDB: typedef { int x; } struct S1; + if targetType.typeId == typeId: + return targetType + tdata = self.TypeData(self, typeId) + tdata.name = typeName + tdata.code = TypeCode.Typedef + tdata.ltarget = targetType + tdata.lbitsize = targetType.lbitsize + #tdata.lfields = targetType.lfields + tdata.lbitsize = targetType.lbitsize + return self.Type(self, typeId) + + def knownArrayTypeSize(self): + return 3 * self.ptrSize() if self.qtVersion() >= 0x060000 else self.ptrSize() + + def knownTypeSize(self, typish): + if typish[0] == 'Q': + if typish.startswith('QList<') or typish.startswith('QVector<'): + return self.knownArrayTypeSize() + if typish == 'QObject': + return 2 * self.ptrSize() + if typish == 'QStandardItemData': + return 4 * self.ptrSize() if self.qtVersion() >= 0x060000 else 2 * self.ptrSize() + if typish == 'Qt::ItemDataRole': + return 4 + if typish == 'QChar': + return 2 + if typish in ('quint32', 'qint32'): + return 4 + return None + + def createType(self, typish, size=None): + if isinstance(typish, self.Type): + #typish.check() + if hasattr(typish, 'lbitsize') and typish.lbitsize is not None and typish.lbitsize > 0: + return typish + # Size 0 is sometimes reported by GDB but doesn't help at all. + # Force using the fallback: + typish = typish.name + + if isinstance(typish, str): + ns = self.qtNamespace() + typish = typish.replace('@', ns) + if typish.startswith(ns): + if size is None: + size = self.knownTypeSize(typish[len(ns):]) + else: + if size is None: + size = self.knownTypeSize(typish) + if size is not None: + typish = ns + typish + + tdata = self.typeData.get(typish, None) + if tdata is not None: + if tdata.lbitsize is not None: + if tdata.lbitsize > 0: + return self.Type(self, typish) + + knownType = self.lookupType(typish) + #DumperBase.warn('KNOWN: %s' % knownType) + if knownType is not None: + #DumperBase.warn('USE FROM NATIVE') + return knownType + + #DumperBase.warn('FAKING: %s SIZE: %s' % (typish, size)) + tdata = self.TypeData(self, typish) + tdata.templateArguments = lambda: self.listTemplateParameters(typish) + if size is not None: + tdata.lbitsize = 8 * size + if typish.endswith('*'): + tdata.code = TypeCode.Pointer + tdata.lbitsize = 8 * self.ptrSize() + tdata.ltarget = self.createType(typish[:-1].strip()) + + typeobj = self.Type(self, tdata.typeId) + #DumperBase.warn('CREATE TYPE: %s' % typeobj.stringify()) + typeobj.check() + return typeobj + raise RuntimeError('NEED TYPE, NOT %s' % type(typish)) + + def createValueFromAddressAndType(self, address, typish): + val = self.Value(self) + val._type = self.createType(typish) + #DumperBase.warn('CREATING %s AT 0x%x' % (val.type.name, datish)) + val.laddress = address + if self.useDynamicType: + val._type = val.type.dynamicType(address) + return val + + def createValue(self, datish, typish): + if self.isInt(datish): # Used as address. + return self.createValueFromAddressAndType(datish, typish) + if isinstance(datish, bytes): + val = self.Value(self) + val._type = self.createType(typish) + #DumperBase.warn('CREATING %s WITH DATA %s' % (val.type.name, self.hexencode(datish))) + val.ldata = datish + val.check() + return val + raise RuntimeError('EXPECTING ADDRESS OR BYTES, GOT %s' % type(datish)) + + def createProxyValue(self, proxy_data, type_name): + tdata = self.TypeData(self, type_name) + tdata.code = TypeCode.Struct + val = self.Value(self) + val._type = self.Type(self, type_name) + val.ldata = proxy_data + return val + + class StructBuilder(): + def __init__(self, dumper): + self.dumper = dumper + self.pattern = '' + self.currentBitsize = 0 + self.fields = [] + self.autoPadNext = False + self.maxAlign = 1 + + def addField(self, fieldSize, fieldCode=None, fieldIsStruct=False, + fieldName=None, fieldType=None, fieldAlign=1): + + if fieldType is not None: + fieldType = self.dumper.createType(fieldType) + if fieldSize is None and fieldType is not None: + fieldSize = fieldType.size() + if fieldCode is None: + fieldCode = '%ss' % fieldSize + + if self.autoPadNext: + self.currentBitsize = 8 * ((self.currentBitsize + 7) >> 3) # Fill up byte. + padding = (fieldAlign - (self.currentBitsize >> 3)) % fieldAlign + #DumperBase.warn('AUTO PADDING AT %s BITS BY %s BYTES' % (self.currentBitsize, padding)) + field = self.dumper.Field(self.dumper, bitpos=self.currentBitsize, + bitsize=padding * 8) + self.pattern += '%ds' % padding + self.currentBitsize += padding * 8 + self.fields.append(field) + self.autoPadNext = False + + if fieldAlign > self.maxAlign: + self.maxAlign = fieldAlign + #DumperBase.warn("MAX ALIGN: %s" % self.maxAlign) + + field = self.dumper.Field(dumper=self.dumper, name=fieldName, type=fieldType, + isStruct=fieldIsStruct, bitpos=self.currentBitsize, + bitsize=fieldSize * 8) + + self.pattern += fieldCode + self.currentBitsize += fieldSize * 8 + self.fields.append(field) + + def describeStruct(self, pattern): + if pattern in self.structPatternCache: + return self.structPatternCache[pattern] + ptrSize = self.ptrSize() + builder = self.StructBuilder(self) + n = None + typeName = '' + readingTypeName = False + for c in pattern: + if readingTypeName: + if c == '}': + readingTypeName = False + fieldType = self.createType(typeName) + fieldAlign = fieldType.alignment() + builder.addField(n, fieldIsStruct=True, + fieldType=fieldType, fieldAlign=fieldAlign) + typeName = None + n = None + else: + typeName += c + elif c == 't': # size_t + builder.addField(ptrSize, self.ptrCode(), fieldAlign=ptrSize) + elif c == 'p': # Pointer as int + builder.addField(ptrSize, self.ptrCode(), fieldAlign=ptrSize) + elif c == 'P': # Pointer as Value + builder.addField(ptrSize, '%ss' % ptrSize, fieldAlign=ptrSize) + elif c in ('d'): + builder.addField(8, c, fieldAlign=ptrSize) # fieldType = 'double' ? + elif c in ('q', 'Q'): + builder.addField(8, c, fieldAlign=ptrSize) + elif c in ('i', 'I', 'f'): + builder.addField(4, c, fieldAlign=4) + elif c in ('h', 'H'): + builder.addField(2, c, fieldAlign=2) + elif c in ('b', 'B', 'c'): + builder.addField(1, c, fieldAlign=1) + elif c >= '0' and c <= '9': + if n is None: + n = '' + n += c + elif c == 's': + builder.addField(int(n), fieldAlign=1) + n = None + elif c == '{': + readingTypeName = True + typeName = '' + elif c == '@': + if n is None: + # Automatic padding depending on next item + builder.autoPadNext = True + else: + # Explicit padding. + builder.currentBitsize = 8 * ((builder.currentBitsize + 7) >> 3) + padding = (int(n) - (builder.currentBitsize >> 3)) % int(n) + field = self.Field(self) + builder.pattern += '%ds' % padding + builder.currentBitsize += padding * 8 + builder.fields.append(field) + n = None + else: + raise RuntimeError('UNKNOWN STRUCT CODE: %s' % c) + pp = builder.pattern + size = (builder.currentBitsize + 7) >> 3 + fields = builder.fields + tailPad = (builder.maxAlign - size) % builder.maxAlign + size += tailPad + self.structPatternCache[pattern] = (pp, size, fields) + return (pp, size, fields) diff --git a/share/qtcreator/debugger/python2/gdbbridge.py b/share/qtcreator/debugger/python2/gdbbridge.py new file mode 100644 index 00000000000..d61160eed3b --- /dev/null +++ b/share/qtcreator/debugger/python2/gdbbridge.py @@ -0,0 +1,1624 @@ +# Copyright (C) 2016 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +try: + import __builtin__ +except: + import builtins + +import gdb +import os +import os.path +import re +import sys +import struct +import tempfile + +from dumper import DumperBase, Children, toInteger, TopLevelItem +from utils import TypeCode +from gdbtracepoint import * + + +####################################################################### +# +# Infrastructure +# +####################################################################### + + +def safePrint(output): + try: + print(output) + except: + out = '' + for c in output: + cc = ord(c) + if cc > 127: + out += '\\\\%d' % cc + elif cc < 0: + out += '\\\\%d' % (cc + 256) + else: + out += c + print(out) + + +def registerCommand(name, func): + + class Command(gdb.Command): + def __init__(self): + super(Command, self).__init__(name, gdb.COMMAND_OBSCURE) + + def invoke(self, args, from_tty): + safePrint(func(args)) + + Command() + + +####################################################################### +# +# Convenience +# +####################################################################### + +# For CLI dumper use, see README.txt +class PPCommand(gdb.Command): + def __init__(self): + super(PPCommand, self).__init__('pp', gdb.COMMAND_OBSCURE) + + def invoke(self, args, from_tty): + print(theCliDumper.fetchVariable(args)) + + +PPCommand() + +# Just convenience for 'python print gdb.parse_and_eval(...)' + + +class PPPCommand(gdb.Command): + def __init__(self): + super(PPPCommand, self).__init__('ppp', gdb.COMMAND_OBSCURE) + + def invoke(self, args, from_tty): + print(gdb.parse_and_eval(args)) + + +PPPCommand() + + +def scanStack(p, n): + p = int(p) + r = [] + for i in range(n): + f = gdb.parse_and_eval('{void*}%s' % p) + m = gdb.execute('info symbol %s' % f, to_string=True) + if not m.startswith('No symbol matches'): + r.append(m) + p += f.type.sizeof + return r + + +class ScanStackCommand(gdb.Command): + def __init__(self): + super(ScanStackCommand, self).__init__('scanStack', gdb.COMMAND_OBSCURE) + + def invoke(self, args, from_tty): + if len(args) == 0: + args = 20 + safePrint(scanStack(gdb.parse_and_eval('$sp'), int(args))) + + +ScanStackCommand() + + +####################################################################### +# +# Import plain gdb pretty printers +# +####################################################################### + +class PlainDumper(): + def __init__(self, printer): + self.printer = printer + self.typeCache = {} + + def __call__(self, d, value): + if value.nativeValue is None: + # warn('PlainDumper(gdb): value.nativeValue is missing (%s)'%value) + nativeType = theDumper.lookupNativeType(value.type.name) + nativeTypePointer = nativeType.pointer() + nativePointer = gdb.Value(value.laddress) + value.nativeValue = nativePointer.cast(nativeTypePointer).dereference() + try: + printer = self.printer.gen_printer(value.nativeValue) + except: + printer = self.printer.invoke(value.nativeValue) + d.putType(value.nativeValue.type.name) + val = printer.to_string() + if isinstance(val, str): + # encode and avoid extra quotes ('"') at beginning and end + d.putValue(d.hexencode(val), 'utf8:1:0') + elif sys.version_info[0] <= 2 and isinstance(val, unicode): + d.putValue(val) + elif val is not None: # Assuming LazyString + d.putCharArrayValue(val.address, val.length, + val.type.target().sizeof) + + lister = getattr(printer, 'children', None) + if lister is None: + d.putNumChild(0) + else: + if d.isExpanded(): + children = lister() + with Children(d): + i = 0 + for (name, child) in children: + d.putSubItem(name, d.fromNativeValue(child)) + i += 1 + if i > 1000: + break + d.putNumChild(1) + + +def importPlainDumpers(args): + if args == 'off': + theDumper.usePlainDumpers = False + try: + gdb.execute('disable pretty-printer .* .*') + except: + # Might occur in non-ASCII directories + DumperBase.warn('COULD NOT DISABLE PRETTY PRINTERS') + else: + theDumper.usePlainDumpers = True + theDumper.importPlainDumpers() + + +registerCommand('importPlainDumpers', importPlainDumpers) + + +####################################################################### +# +# The Dumper Class +# +####################################################################### + + +class Dumper(DumperBase): + + def __init__(self): + DumperBase.__init__(self) + + # whether to load plain dumpers for objfiles + self.usePlainDumpers = False + + # These values will be kept between calls to 'fetchVariables'. + self.isGdb = True + self.typeCache = {} + self.interpreterBreakpointResolvers = [] + + def prepare(self, args): + self.output = [] + self.setVariableFetchingOptions(args) + + def fromFrameValue(self, nativeValue): + #DumperBase.warn('FROM FRAME VALUE: %s' % nativeValue.address) + val = nativeValue + if self.useDynamicType: + try: + val = nativeValue.cast(nativeValue.dynamic_type) + except: + pass + return self.fromNativeValue(val) + + def nativeValueType(self, nativeValue): + return self.fromNativeType(nativeValue.type) + + def fromNativeValue(self, nativeValue): + #DumperBase.warn('FROM NATIVE VALUE: %s' % nativeValue) + self.check(isinstance(nativeValue, gdb.Value)) + nativeType = nativeValue.type + code = nativeType.code + if code == gdb.TYPE_CODE_REF: + targetType = self.fromNativeType(nativeType.target().unqualified()) + val = self.createReferenceValue(toInteger(nativeValue.address), targetType) + val.nativeValue = nativeValue + #DumperBase.warn('CREATED REF: %s' % val) + return val + if code == gdb.TYPE_CODE_PTR: + try: + nativeTargetValue = nativeValue.dereference() + except: + nativeTargetValue = None + targetType = self.fromNativeType(nativeType.target().unqualified()) + val = self.createPointerValue(toInteger(nativeValue), targetType) + # The nativeValue is needed in case of multiple inheritance, see + # QTCREATORBUG-17823. Using it triggers nativeValueDereferencePointer() + # later which + # is surprisingly expensive. + val.nativeValue = nativeValue + #DumperBase.warn('CREATED PTR 1: %s' % val) + if nativeValue.address is not None: + val.laddress = toInteger(nativeValue.address) + #DumperBase.warn('CREATED PTR 2: %s' % val) + return val + if code == gdb.TYPE_CODE_TYPEDEF: + targetType = nativeType.strip_typedefs().unqualified() + #DumperBase.warn('TARGET TYPE: %s' % targetType) + if targetType.code == gdb.TYPE_CODE_ARRAY: + val = self.Value(self) + else: + try: + # Cast may fail for arrays, for typedefs to __uint128_t with + # gdb.error: That operation is not available on integers + # of more than 8 bytes. + # See test for Bug5799, QTCREATORBUG-18450. + val = self.fromNativeValue(nativeValue.cast(targetType)) + except: + val = self.Value(self) + #DumperBase.warn('CREATED TYPEDEF: %s' % val) + else: + val = self.Value(self) + + val.nativeValue = nativeValue + if nativeValue.address is not None: + val.laddress = toInteger(nativeValue.address) + else: + size = nativeType.sizeof + chars = self.lookupNativeType('unsigned char') + y = nativeValue.cast(chars.array(0, int(nativeType.sizeof - 1))) + buf = bytearray(struct.pack('x' * size)) + for i in range(size): + try: + buf[i] = int(y[i]) + except: + pass + val.ldata = bytes(buf) + + val._type = self.fromNativeType(nativeType) + val.lIsInScope = not nativeValue.is_optimized_out + code = nativeType.code + if code == gdb.TYPE_CODE_ENUM: + val.ldisplay = str(nativeValue) + intval = int(nativeValue) + if val.ldisplay != intval: + val.ldisplay += ' (%s)' % intval + elif code == gdb.TYPE_CODE_COMPLEX: + val.ldisplay = str(nativeValue) + elif code in [gdb.TYPE_CODE_BOOL, gdb.TYPE_CODE_INT]: + try: + # extract int presentation from native value and remember it + val.lvalue = int(nativeValue) + except: + # GDB only support converting integers of max. 64 bits to Python int as of now + pass + #elif code == gdb.TYPE_CODE_ARRAY: + # val.type.ltarget = nativeValue[0].type.unqualified() + return val + + def ptrSize(self): + result = gdb.lookup_type('void').pointer().sizeof + self.ptrSize = lambda: result + return result + + def fromNativeType(self, nativeType): + self.check(isinstance(nativeType, gdb.Type)) + code = nativeType.code + #DumperBase.warn('FROM NATIVE TYPE: %s' % nativeType) + nativeType = nativeType.unqualified() + + if code == gdb.TYPE_CODE_PTR: + #DumperBase.warn('PTR') + targetType = self.fromNativeType(nativeType.target().unqualified()) + return self.createPointerType(targetType) + + if code == gdb.TYPE_CODE_REF: + #DumperBase.warn('REF') + targetType = self.fromNativeType(nativeType.target().unqualified()) + return self.createReferenceType(targetType) + + if hasattr(gdb, "TYPE_CODE_RVALUE_REF"): + if code == gdb.TYPE_CODE_RVALUE_REF: + #DumperBase.warn('RVALUEREF') + targetType = self.fromNativeType(nativeType.target()) + return self.createRValueReferenceType(targetType) + + if code == gdb.TYPE_CODE_ARRAY: + #DumperBase.warn('ARRAY') + nativeTargetType = nativeType.target().unqualified() + targetType = self.fromNativeType(nativeTargetType) + if nativeType.sizeof == 0: + # QTCREATORBUG-23998, note that nativeType.name == None here, + # whereas str(nativeType) returns sth like 'QObject [5]' + count = self.arrayItemCountFromTypeName(str(nativeType), 1) + else: + count = nativeType.sizeof // nativeTargetType.sizeof + return self.createArrayType(targetType, count) + + if code == gdb.TYPE_CODE_TYPEDEF: + #DumperBase.warn('TYPEDEF') + nativeTargetType = nativeType.unqualified() + while nativeTargetType.code == gdb.TYPE_CODE_TYPEDEF: + nativeTargetType = nativeTargetType.strip_typedefs().unqualified() + targetType = self.fromNativeType(nativeTargetType) + return self.createTypedefedType(targetType, str(nativeType), + self.nativeTypeId(nativeType)) + + if code == gdb.TYPE_CODE_ERROR: + self.warn('Type error: %s' % nativeType) + return self.Type(self, '') + + typeId = self.nativeTypeId(nativeType) + res = self.typeData.get(typeId, None) + if res is None: + tdata = self.TypeData(self, typeId) + tdata.name = str(nativeType) + tdata.lbitsize = nativeType.sizeof * 8 + tdata.code = { + #gdb.TYPE_CODE_TYPEDEF : TypeCode.Typedef, # Handled above. + gdb.TYPE_CODE_METHOD: TypeCode.Function, + gdb.TYPE_CODE_VOID: TypeCode.Void, + gdb.TYPE_CODE_FUNC: TypeCode.Function, + gdb.TYPE_CODE_METHODPTR: TypeCode.Function, + gdb.TYPE_CODE_MEMBERPTR: TypeCode.Function, + #gdb.TYPE_CODE_PTR : TypeCode.Pointer, # Handled above. + #gdb.TYPE_CODE_REF : TypeCode.Reference, # Handled above. + gdb.TYPE_CODE_BOOL: TypeCode.Integral, + gdb.TYPE_CODE_CHAR: TypeCode.Integral, + gdb.TYPE_CODE_INT: TypeCode.Integral, + gdb.TYPE_CODE_FLT: TypeCode.Float, + gdb.TYPE_CODE_ENUM: TypeCode.Enum, + #gdb.TYPE_CODE_ARRAY : TypeCode.Array, + gdb.TYPE_CODE_STRUCT: TypeCode.Struct, + gdb.TYPE_CODE_UNION: TypeCode.Struct, + gdb.TYPE_CODE_COMPLEX: TypeCode.Complex, + gdb.TYPE_CODE_STRING: TypeCode.FortranString, + }[code] + if tdata.code == TypeCode.Enum: + tdata.enumDisplay = lambda intval, addr, form: \ + self.nativeTypeEnumDisplay(nativeType, intval, form) + if tdata.code == TypeCode.Struct: + tdata.lalignment = lambda: \ + self.nativeStructAlignment(nativeType) + tdata.lfields = lambda value: \ + self.listMembers(value, nativeType) + tdata.templateArguments = lambda: \ + self.listTemplateParameters(nativeType) + # warn('CREATE TYPE: %s' % typeId) + #else: + # warn('REUSE TYPE: %s' % typeId) + return self.Type(self, typeId) + + def listTemplateParameters(self, nativeType): + targs = [] + pos = 0 + while True: + try: + targ = nativeType.template_argument(pos) + except: + break + if isinstance(targ, gdb.Type): + targs.append(self.fromNativeType(targ.unqualified())) + elif isinstance(targ, gdb.Value): + targs.append(self.fromNativeValue(targ).value()) + else: + raise RuntimeError('UNKNOWN TEMPLATE PARAMETER') + pos += 1 + targs2 = self.listTemplateParametersManually(str(nativeType)) + return targs if len(targs) >= len(targs2) else targs2 + + def nativeTypeEnumDisplay(self, nativeType, intval, form): + try: + enumerators = [] + for field in nativeType.fields(): + # If we found an exact match, return it immediately + if field.enumval == intval: + return field.name + ' (' + (form % intval) + ')' + enumerators.append((field.name, field.enumval)) + + # No match was found, try to return as flags + enumerators.sort(key=lambda x: x[1]) + flags = [] + v = intval + found = False + for (name, value) in enumerators: + if v & value != 0: + flags.append(name) + v = v & ~value + found = True + if not found or v != 0: + # Leftover value + flags.append('unknown: %d' % v) + return '(' + " | ".join(flags) + ') (' + (form % intval) + ')' + except: + pass + return form % intval + + def nativeTypeId(self, nativeType): + if nativeType and (nativeType.code == gdb.TYPE_CODE_TYPEDEF): + return '%s{%s}' % (nativeType, nativeType.strip_typedefs()) + name = str(nativeType) + if len(name) == 0: + c = '0' + elif name == 'union {...}': + c = 'u' + elif name.endswith('{...}'): + c = 's' + else: + return name + typeId = c + ''.join(['{%s:%s}' % (f.name, self.nativeTypeId(f.type)) + for f in nativeType.fields()]) + return typeId + + def nativeStructAlignment(self, nativeType): + #DumperBase.warn('NATIVE ALIGN FOR %s' % nativeType.name) + def handleItem(nativeFieldType, align): + a = self.fromNativeType(nativeFieldType).alignment() + return a if a > align else align + align = 1 + for f in nativeType.fields(): + align = handleItem(f.type, align) + return align + + #except: + # # Happens in the BoostList dumper for a 'const bool' + # # item named 'constant_time_size'. There isn't anything we can do + # # in this case. + # pass + + #yield value.extractField(field) + + def memberFromNativeFieldAndValue(self, nativeField, nativeValue, fieldName, value): + nativeMember = self.nativeMemberFromField(nativeValue, nativeField) + if nativeMember is None: + val = self.Value(self) + val.name = fieldName + val._type = self.fromNativeType(nativeField.type) + val.lIsInScope = False + return val + val = self.fromNativeValue(nativeMember) + nativeFieldType = nativeField.type.unqualified() + if nativeField.bitsize: + val.lvalue = int(nativeMember) + val.laddress = None + fieldType = self.fromNativeType(nativeFieldType) + val._type = self.createBitfieldType(fieldType, nativeField.bitsize) + val.isBaseClass = nativeField.is_base_class + val.name = fieldName + return val + + def nativeMemberFromField(self, nativeValue, nativeField): + if nativeField.is_base_class: + return nativeValue.cast(nativeField.type) + try: + return nativeValue[nativeField] + except: + pass + try: + return nativeValue[nativeField.name] + except: + pass + return None + + def listMembers(self, value, nativeType): + nativeValue = value.nativeValue + + anonNumber = 0 + + #DumperBase.warn('LISTING FIELDS FOR %s' % nativeType) + for nativeField in nativeType.fields(): + fieldName = nativeField.name + # Something without a name. + # Anonymous union? We need a dummy name to distinguish + # multiple anonymous unions in the struct. + # Since GDB commit b5b08fb4 anonymous structs get also reported + # with a 'None' name. + if fieldName is None or len(fieldName) == 0: + # Something without a name. + # Anonymous union? We need a dummy name to distinguish + # multiple anonymous unions in the struct. + anonNumber += 1 + fieldName = '#%s' % anonNumber + #DumperBase.warn('FIELD: %s' % fieldName) + # hasattr(nativeField, 'bitpos') == False indicates a static field, + # but if we have access to a nativeValue .fromNativeField will + # also succeed. We essentially skip only static members from + # artificial values, like array members constructed from address. + if hasattr(nativeField, 'bitpos') or nativeValue is not None: + yield self.fromNativeField(nativeField, nativeValue, fieldName) + + def fromNativeField(self, nativeField, nativeValue, fieldName): + nativeFieldType = nativeField.type.unqualified() + #DumperBase.warn(' TYPE: %s' % nativeFieldType) + #DumperBase.warn(' TYPEID: %s' % self.nativeTypeId(nativeFieldType)) + + if hasattr(nativeField, 'bitpos'): + bitpos = nativeField.bitpos + else: + bitpos = 0 + + if hasattr(nativeField, 'bitsize') and nativeField.bitsize != 0: + bitsize = nativeField.bitsize + else: + bitsize = 8 * nativeFieldType.sizeof + + fieldType = self.fromNativeType(nativeFieldType) + if bitsize != nativeFieldType.sizeof * 8: + fieldType = self.createBitfieldType(fieldType, bitsize) + else: + fieldType = fieldType + + if nativeValue is None: + extractor = None + else: + extractor = lambda value, \ + capturedNativeField = nativeField, \ + capturedNativeValue = nativeValue, \ + capturedFieldName = fieldName: \ + self.memberFromNativeFieldAndValue(capturedNativeField, + capturedNativeValue, + capturedFieldName, + value) + + #DumperBase.warn("FOUND NATIVE FIELD: %s bitpos: %s" % (fieldName, bitpos)) + return self.Field(dumper=self, name=fieldName, isBase=nativeField.is_base_class, + bitsize=bitsize, bitpos=bitpos, type=fieldType, + extractor=extractor) + + def listLocals(self, partialVar): + frame = gdb.selected_frame() + + try: + block = frame.block() + #DumperBase.warn('BLOCK: %s ' % block) + except RuntimeError as error: + #DumperBase.warn('BLOCK IN FRAME NOT ACCESSIBLE: %s' % error) + return [] + except: + self.warn('BLOCK NOT ACCESSIBLE FOR UNKNOWN REASONS') + return [] + + items = [] + shadowed = {} + while True: + if block is None: + self.warn("UNEXPECTED 'None' BLOCK") + break + for symbol in block: + + # Filter out labels etc. + if symbol.is_variable or symbol.is_argument: + name = symbol.print_name + + if name in ('__in_chrg', '__PRETTY_FUNCTION__'): + continue + + if partialVar is not None and partialVar != name: + continue + + # 'NotImplementedError: Symbol type not yet supported in + # Python scripts.' + #DumperBase.warn('SYMBOL %s (%s, %s)): ' % (symbol, name, symbol.name)) + if self.passExceptions and not self.isTesting: + nativeValue = frame.read_var(name, block) + value = self.fromFrameValue(nativeValue) + value.name = name + #DumperBase.warn('READ 0: %s' % value.stringify()) + items.append(value) + continue + + try: + # Same as above, but for production. + nativeValue = frame.read_var(name, block) + value = self.fromFrameValue(nativeValue) + value.name = name + #DumperBase.warn('READ 1: %s' % value.stringify()) + items.append(value) + continue + except: + pass + + try: + #DumperBase.warn('READ 2: %s' % item.value) + value = self.fromFrameValue(frame.read_var(name)) + value.name = name + items.append(value) + continue + except: + # RuntimeError: happens for + # void foo() { std::string s; std::wstring w; } + # ValueError: happens for (as of 2010/11/4) + # a local struct as found e.g. in + # gcc sources in gcc.c, int execute() + pass + + try: + #DumperBase.warn('READ 3: %s %s' % (name, item.value)) + #DumperBase.warn('ITEM 3: %s' % item.value) + value = self.fromFrameValue(gdb.parse_and_eval(name)) + value.name = name + items.append(value) + except: + # Can happen in inlined code (see last line of + # RowPainter::paintChars(): 'RuntimeError: + # No symbol '__val' in current context.\n' + pass + + # The outermost block in a function has the function member + # FIXME: check whether this is guaranteed. + if block.function is not None: + break + + block = block.superblock + + return items + + def reportToken(self, args): + pass + + # Hack to avoid QDate* dumper timeouts with GDB 7.4 on 32 bit + # due to misaligned %ebx in SSE calls (qstring.cpp:findChar) + # This seems to be fixed in 7.9 (or earlier) + def canCallLocale(self): + return self.ptrSize() == 8 + + def fetchVariables(self, args): + self.resetStats() + self.prepare(args) + + self.isBigEndian = gdb.execute('show endian', to_string=True).find('big endian') > 0 + self.packCode = '>' if self.isBigEndian else '<' + + (ok, res) = self.tryFetchInterpreterVariables(args) + if ok: + safePrint(res) + return + + self.put('data=[') + + partialVar = args.get('partialvar', '') + isPartial = len(partialVar) > 0 + partialName = partialVar.split('.')[1].split('@')[0] if isPartial else None + + variables = self.listLocals(partialName) + #DumperBase.warn('VARIABLES: %s' % variables) + + # Take care of the return value of the last function call. + if len(self.resultVarName) > 0: + try: + value = self.parseAndEvaluate(self.resultVarName) + value.name = self.resultVarName + value.iname = 'return.' + self.resultVarName + variables.append(value) + except: + # Don't bother. It's only supplementary information anyway. + pass + + self.handleLocals(variables) + self.handleWatches(args) + + self.put('],typeinfo=[') + for name in self.typesToReport.keys(): + typeobj = self.typesToReport[name] + # Happens e.g. for '(anonymous namespace)::InsertDefOperation' + #if not typeobj is None: + # self.put('{name="%s",size="%s"}' % (self.hexencode(name), typeobj.sizeof)) + self.put(']') + self.typesToReport = {} + + if self.forceQtNamespace: + self.qtNamespaceToReport = self.qtNamespace() + + if self.qtNamespaceToReport: + self.put(',qtnamespace="%s"' % self.qtNamespaceToReport) + self.qtNamespaceToReport = None + + self.put(',partial="%d"' % isPartial) + self.put(',counts=%s' % self.counts) + self.put(',timings=%s' % self.timings) + self.reportResult(''.join(self.output), args) + + def parseAndEvaluate(self, exp): + val = self.nativeParseAndEvaluate(exp) + return None if val is None else self.fromNativeValue(val) + + def nativeParseAndEvaluate(self, exp): + #DumperBase.warn('EVALUATE "%s"' % exp) + try: + val = gdb.parse_and_eval(exp) + return val + except RuntimeError as error: + if self.passExceptions: + self.warn("Cannot evaluate '%s': %s" % (exp, error)) + return None + + def callHelper(self, rettype, value, function, args): + if self.isWindowsTarget(): + raise Exception("gdb crashes when calling functions on Windows") + # args is a tuple. + arg = '' + for i in range(len(args)): + if i: + arg += ',' + a = args[i] + if (':' in a) and not ("'" in a): + arg = "'%s'" % a + else: + arg += a + + #DumperBase.warn('CALL: %s -> %s(%s)' % (value, function, arg)) + typeName = value.type.name + if typeName.find(':') >= 0: + typeName = "'" + typeName + "'" + # 'class' is needed, see http://sourceware.org/bugzilla/show_bug.cgi?id=11912 + #exp = '((class %s*)%s)->%s(%s)' % (typeName, value.laddress, function, arg) + addr = value.address() + if addr is None: + addr = self.pokeValue(value) + #DumperBase.warn('PTR: %s -> %s(%s)' % (value, function, addr)) + exp = '((%s*)0x%x)->%s(%s)' % (typeName, addr, function, arg) + #DumperBase.warn('CALL: %s' % exp) + result = gdb.parse_and_eval(exp) + #DumperBase.warn(' -> %s' % result) + res = self.fromNativeValue(result) + if value.address() is None: + self.releaseValue(addr) + return res + + def makeExpression(self, value): + typename = '::' + value.type.name + #DumperBase.warn(' TYPE: %s' % typename) + exp = '(*(%s*)(0x%x))' % (typename, value.address()) + #DumperBase.warn(' EXP: %s' % exp) + return exp + + def makeStdString(init): + # Works only for small allocators, but they are usually empty. + gdb.execute('set $d=(std::string*)calloc(sizeof(std::string), 2)') + gdb.execute('call($d->basic_string("' + init + + '",*(std::allocator*)(1+$d)))') + value = gdb.parse_and_eval('$d').dereference() + return value + + def pokeValue(self, value): + # Allocates inferior memory and copies the contents of value. + # Returns a pointer to the copy. + # Avoid malloc symbol clash with QVector + size = value.type.size() + data = value.data() + h = self.hexencode(data) + #DumperBase.warn('DATA: %s' % h) + string = ''.join('\\x' + h[2 * i:2 * i + 2] for i in range(size)) + exp = '(%s*)memcpy(calloc(%d, 1), "%s", %d)' \ + % (value.type.name, size, string, size) + #DumperBase.warn('EXP: %s' % exp) + res = gdb.parse_and_eval(exp) + #DumperBase.warn('RES: %s' % res) + return toInteger(res) + + def releaseValue(self, address): + gdb.parse_and_eval('free(0x%x)' % address) + + def setValue(self, address, typename, value): + cmd = 'set {%s}%s=%s' % (typename, address, value) + gdb.execute(cmd) + + def setValues(self, address, typename, values): + cmd = 'set {%s[%s]}%s={%s}' \ + % (typename, len(values), address, ','.join(map(str, values))) + gdb.execute(cmd) + + def selectedInferior(self): + try: + # gdb.Inferior is new in gdb 7.2 + self.cachedInferior = gdb.selected_inferior() + except: + # Pre gdb 7.4. Right now we don't have more than one inferior anyway. + self.cachedInferior = gdb.inferiors()[0] + + # Memoize result. + self.selectedInferior = lambda: self.cachedInferior + return self.cachedInferior + + def readRawMemory(self, address, size): + #DumperBase.warn('READ: %s FROM 0x%x' % (size, address)) + if address == 0 or size == 0: + return bytes() + res = self.selectedInferior().read_memory(address, size) + return res + + def findStaticMetaObject(self, type): + symbolName = type.name + '::staticMetaObject' + symbol = gdb.lookup_global_symbol(symbolName, gdb.SYMBOL_VAR_DOMAIN) + if not symbol: + return 0 + try: + # Older GDB ~7.4 don't have gdb.Symbol.value() + return toInteger(symbol.value().address) + except: + pass + + address = gdb.parse_and_eval("&'%s'" % symbolName) + return toInteger(address) + + def isArmArchitecture(self): + return 'arm' in gdb.TARGET_CONFIG.lower() + + def isQnxTarget(self): + return 'qnx' in gdb.TARGET_CONFIG.lower() + + def isWindowsTarget(self): + # We get i686-w64-mingw32 + return 'mingw' in gdb.TARGET_CONFIG.lower() + + def isMsvcTarget(self): + return False + + def prettySymbolByAddress(self, address): + try: + return str(gdb.parse_and_eval('(void(*))0x%x' % address)) + except: + return '0x%x' % address + + def qtVersionString(self): + try: + return str(gdb.lookup_symbol('qVersion')[0].value()()) + except: + pass + try: + ns = self.qtNamespace() + return str(gdb.parse_and_eval("((const char*(*)())'%sqVersion')()" % ns)) + except: + pass + return None + + def qtVersion(self): + try: + # Only available with Qt 5.3+ + qtversion = int(str(gdb.parse_and_eval('((void**)&qtHookData)[2]')), 16) + self.qtVersion = lambda: qtversion + return qtversion + except: + pass + + try: + version = self.qtVersionString() + (major, minor, patch) = version[version.find('"') + 1:version.rfind('"')].split('.') + qtversion = 0x10000 * int(major) + 0x100 * int(minor) + int(patch) + self.qtVersion = lambda: qtversion + return qtversion + except: + # Use fallback until we have a better answer. + return self.fallbackQtVersion + + def createSpecialBreakpoints(self, args): + self.specialBreakpoints = [] + + def newSpecial(spec): + # GDB < 8.1 does not have the 'qualified' parameter here, + # GDB >= 8.1 applies some generous pattern matching, hitting + # e.g. also Foo::abort() when asking for '::abort' + class Pre81SpecialBreakpoint(gdb.Breakpoint): + def __init__(self, spec): + super(Pre81SpecialBreakpoint, self).__init__(spec, + gdb.BP_BREAKPOINT, internal=True) + self.spec = spec + + def stop(self): + print("Breakpoint on '%s' hit." % self.spec) + return True + + class SpecialBreakpoint(gdb.Breakpoint): + def __init__(self, spec): + super(SpecialBreakpoint, self).__init__(spec, + gdb.BP_BREAKPOINT, + internal=True, + qualified=True) + self.spec = spec + + def stop(self): + print("Breakpoint on '%s' hit." % self.spec) + return True + + try: + return SpecialBreakpoint(spec) + except: + return Pre81SpecialBreakpoint(spec) + + # FIXME: ns is accessed too early. gdb.Breakpoint() has no + # 'rbreak' replacement, and breakpoints created with + # 'gdb.execute('rbreak...') cannot be made invisible. + # So let's ignore the existing of namespaced builds for this + # fringe feature here for now. + ns = self.qtNamespace() + if args.get('breakonabort', 0): + self.specialBreakpoints.append(newSpecial('abort')) + + if args.get('breakonwarning', 0): + self.specialBreakpoints.append(newSpecial(ns + 'qWarning')) + self.specialBreakpoints.append(newSpecial(ns + 'QMessageLogger::warning')) + + if args.get('breakonfatal', 0): + self.specialBreakpoints.append(newSpecial(ns + 'qFatal')) + self.specialBreakpoints.append(newSpecial(ns + 'QMessageLogger::fatal')) + + #def threadname(self, maximalStackDepth, objectPrivateType): + # e = gdb.selected_frame() + # out = '' + # ns = self.qtNamespace() + # while True: + # maximalStackDepth -= 1 + # if maximalStackDepth < 0: + # break + # e = e.older() + # if e == None or e.name() == None: + # break + # if e.name() in (ns + 'QThreadPrivate::start', '_ZN14QThreadPrivate5startEPv@4'): + # try: + # thrptr = e.read_var('thr').dereference() + # d_ptr = thrptr['d_ptr']['d'].cast(objectPrivateType).dereference() + # try: + # objectName = d_ptr['objectName'] + # except: # Qt 5 + # p = d_ptr['extraData'] + # if not self.isNull(p): + # objectName = p.dereference()['objectName'] + # if not objectName is None: + # (data, size, alloc) = self.stringData(objectName) + # if size > 0: + # s = self.readMemory(data, 2 * size) + # + # thread = gdb.selected_thread() + # inner = '{valueencoded="uf16:2:0",id="' + # inner += str(thread.num) + '",value="' + # inner += s + # #inner += self.encodeString(objectName) + # inner += '"},' + # + # out += inner + # except: + # pass + # return out + + def threadnames(self, maximalStackDepth): + # FIXME: This needs a proper implementation for MinGW, and only there. + # Linux, Mac and QNX mirror the objectName() to the underlying threads, + # so we get the names already as part of the -thread-info output. + return '[]' + #out = '[' + #oldthread = gdb.selected_thread() + #if oldthread: + # try: + # objectPrivateType = gdb.lookup_type(ns + 'QObjectPrivate').pointer() + # inferior = self.selectedInferior() + # for thread in inferior.threads(): + # thread.switch() + # out += self.threadname(maximalStackDepth, objectPrivateType) + # except: + # pass + # oldthread.switch() + #return out + ']' + + def importPlainDumper(self, printer): + name = printer.name.replace('::', '__') + self.qqDumpers[name] = PlainDumper(printer) + self.qqFormats[name] = '' + + def importPlainDumpersForObj(self, obj): + for printers in obj.pretty_printers + gdb.pretty_printers: + if hasattr(printers, "subprinters"): + for printer in printers.subprinters: + self.importPlainDumper(printer) + else: + self.warn('Loading a printer without the subprinters attribute not supported.') + + def importPlainDumpers(self): + for obj in gdb.objfiles(): + self.importPlainDumpersForObj(obj) + + def qtNamespace(self): + # This function is replaced by handleQtCoreLoaded() + return '' + + def findSymbol(self, symbolName): + try: + return toInteger(gdb.parse_and_eval("(size_t)&'%s'" % symbolName)) + except: + return 0 + + def handleNewObjectFile(self, objfile): + name = objfile.filename + if self.isWindowsTarget(): + qtCoreMatch = re.match(r'.*Qt[56]?Core[^/.]*d?\.dll', name) + else: + qtCoreMatch = re.match(r'.*/libQt[56]?Core[^/.]*\.so', name) + + if qtCoreMatch is not None: + self.addDebugLibs(objfile) + self.handleQtCoreLoaded(objfile) + + if self.usePlainDumpers: + self.importPlainDumpersForObj(objfile) + + def addDebugLibs(self, objfile): + # The directory where separate debug symbols are searched for + # is "/usr/lib/debug". + try: + cooked = gdb.execute('show debug-file-directory', to_string=True) + clean = cooked.split('"')[1] + newdir = '/'.join(objfile.filename.split('/')[:-1]) + gdb.execute('set debug-file-directory %s:%s' % (clean, newdir)) + except: + pass + + def handleQtCoreLoaded(self, objfile): + fd, tmppath = tempfile.mkstemp() + os.close(fd) + try: + cmd = 'maint print msymbols -objfile "%s" -- %s' % (objfile.filename, tmppath) + symbols = gdb.execute(cmd, to_string=True) + except: + try: + # command syntax depends on gdb version - below is gdb < 8 + cmd = 'maint print msymbols %s "%s"' % (tmppath, objfile.filename) + symbols = gdb.execute(cmd, to_string=True) + except: + pass + ns = '' + with open(tmppath) as f: + for line in f: + if line.find('msgHandlerGrabbed ') >= 0: + # [11] b 0x7ffff683c000 _ZN4MynsL17msgHandlerGrabbedE + # section .tbss Myns::msgHandlerGrabbed qlogging.cpp + ns = re.split(r'_ZN?(\d*)(\w*)L17msgHandlerGrabbedE? ', line)[2] + if len(ns): + ns += '::' + break + if line.find('currentThreadData ') >= 0: + # [ 0] b 0x7ffff67d3000 _ZN2UUL17currentThreadDataE + # section .tbss UU::currentThreadData qthread_unix.cpp\\n + ns = re.split(r'_ZN?(\d*)(\w*)L17currentThreadDataE? ', line)[2] + if len(ns): + ns += '::' + break + os.remove(tmppath) + + lenns = len(ns) + strns = ('%d%s' % (lenns - 2, ns[:lenns - 2])) if lenns else '' + + if lenns: + # This might be wrong, but we can't do better: We found + # a libQt5Core and could not extract a namespace. + # The best guess is that there isn't any. + self.qtNamespaceToReport = ns + self.qtNamespace = lambda: ns + + sym = '_ZN%s7QObject11customEventEPNS_6QEventE' % strns + else: + sym = '_ZN7QObject11customEventEP6QEvent' + self.qtCustomEventFunc = self.findSymbol(sym) + + sym += '@plt' + self.qtCustomEventPltFunc = self.findSymbol(sym) + + sym = '_ZNK%s7QObject8propertyEPKc' % strns + if not self.isWindowsTarget(): # prevent calling the property function on windows + self.qtPropertyFunc = self.findSymbol(sym) + + def assignValue(self, args): + typeName = self.hexdecode(args['type']) + expr = self.hexdecode(args['expr']) + value = self.hexdecode(args['value']) + simpleType = int(args['simpleType']) + ns = self.qtNamespace() + if typeName.startswith(ns): + typeName = typeName[len(ns):] + typeName = typeName.replace('::', '__') + pos = typeName.find('<') + if pos != -1: + typeName = typeName[0:pos] + if typeName in self.qqEditable and not simpleType: + #self.qqEditable[typeName](self, expr, value) + expr = self.parseAndEvaluate(expr) + self.qqEditable[typeName](self, expr, value) + else: + cmd = 'set variable (%s)=%s' % (expr, value) + gdb.execute(cmd) + + def appendSolibSearchPath(self, args): + new = list(map(self.hexdecode, args['path'])) + old = [gdb.parameter('solib-search-path')] + joined = os.pathsep.join([item for item in old + new if item != '']) + gdb.execute('set solib-search-path %s' % joined) + + def watchPoint(self, args): + self.reportToken(args) + ns = self.qtNamespace() + lenns = len(ns) + strns = ('%d%s' % (lenns - 2, ns[:lenns - 2])) if lenns else '' + sym = '_ZN%s12QApplication8widgetAtEii' % strns + expr = '%s(%s,%s)' % (sym, args['x'], args['y']) + res = self.parseAndEvaluate(expr) + p = 0 if res is None else res.pointer() + n = ("'%sQWidget'" % ns) if lenns else 'QWidget' + self.reportResult('selected="0x%x",expr="(%s*)0x%x"' % (p, n, p), args) + + def nativeValueDereferencePointer(self, value): + # This is actually pretty expensive, up to 100ms. + deref = value.nativeValue.dereference() + if self.useDynamicType: + deref = deref.cast(deref.dynamic_type) + return self.fromNativeValue(deref) + + def nativeValueDereferenceReference(self, value): + nativeValue = value.nativeValue + return self.fromNativeValue(nativeValue.cast(nativeValue.type.target())) + + def nativeDynamicTypeName(self, address, baseType): + # Needed for Gdb13393 test. + nativeType = self.lookupNativeType(baseType.name) + if nativeType is None: + return None + nativeTypePointer = nativeType.pointer() + nativeValue = gdb.Value(address).cast(nativeTypePointer).dereference() + val = nativeValue.cast(nativeValue.dynamic_type) + return str(val.type) + #try: + # vtbl = gdb.execute('info symbol {%s*}0x%x' % (baseType.name, address), to_string = True) + #except: + # return None + #pos1 = vtbl.find('vtable ') + #if pos1 == -1: + # return None + #pos1 += 11 + #pos2 = vtbl.find(' +', pos1) + #if pos2 == -1: + # return None + #return vtbl[pos1 : pos2] + + def nativeDynamicType(self, address, baseType): + # Needed for Gdb13393 test. + nativeType = self.lookupNativeType(baseType.name) + if nativeType is None: + return baseType + nativeTypePointer = nativeType.pointer() + nativeValue = gdb.Value(address).cast(nativeTypePointer).dereference() + return self.fromNativeType(nativeValue.dynamic_type) + + def enumExpression(self, enumType, enumValue): + return self.qtNamespace() + 'Qt::' + enumValue + + def lookupNativeType(self, typeName): + nativeType = self.lookupNativeTypeHelper(typeName) + if nativeType is not None: + self.check(isinstance(nativeType, gdb.Type)) + return nativeType + + def lookupNativeTypeHelper(self, typeName): + typeobj = self.typeCache.get(typeName) + #DumperBase.warn('LOOKUP 1: %s -> %s' % (typeName, typeobj)) + if typeobj is not None: + return typeobj + + if typeName == 'void': + typeobj = gdb.lookup_type(typeName) + self.typeCache[typeName] = typeobj + self.typesToReport[typeName] = typeobj + return typeobj + + #try: + # typeobj = gdb.parse_and_eval('{%s}&main' % typeName).typeobj + # if not typeobj is None: + # self.typeCache[typeName] = typeobj + # self.typesToReport[typeName] = typeobj + # return typeobj + #except: + # pass + + # See http://sourceware.org/bugzilla/show_bug.cgi?id=13269 + # gcc produces '{anonymous}', gdb '(anonymous namespace)' + # '' has been seen too. The only thing gdb + # understands when reading things back is '(anonymous namespace)' + if typeName.find('{anonymous}') != -1: + ts = typeName + ts = ts.replace('{anonymous}', '(anonymous namespace)') + typeobj = self.lookupNativeType(ts) + if typeobj is not None: + self.typeCache[typeName] = typeobj + self.typesToReport[typeName] = typeobj + return typeobj + + #DumperBase.warn(" RESULT FOR 7.2: '%s': %s" % (typeName, typeobj)) + + # This part should only trigger for + # gdb 7.1 for types with namespace separators. + # And anonymous namespaces. + + ts = typeName + while True: + if ts.startswith('class '): + ts = ts[6:] + elif ts.startswith('struct '): + ts = ts[7:] + elif ts.startswith('const '): + ts = ts[6:] + elif ts.startswith('volatile '): + ts = ts[9:] + elif ts.startswith('enum '): + ts = ts[5:] + elif ts.endswith(' const'): + ts = ts[:-6] + elif ts.endswith(' volatile'): + ts = ts[:-9] + elif ts.endswith('*const'): + ts = ts[:-5] + elif ts.endswith('*volatile'): + ts = ts[:-8] + else: + break + + if ts.endswith('*'): + typeobj = self.lookupNativeType(ts[0:-1]) + if typeobj is not None: + typeobj = typeobj.pointer() + self.typeCache[typeName] = typeobj + self.typesToReport[typeName] = typeobj + return typeobj + + try: + #DumperBase.warn("LOOKING UP 1 '%s'" % ts) + typeobj = gdb.lookup_type(ts) + except RuntimeError as error: + #DumperBase.warn("LOOKING UP 2 '%s' ERROR %s" % (ts, error)) + # See http://sourceware.org/bugzilla/show_bug.cgi?id=11912 + exp = "(class '%s'*)0" % ts + try: + typeobj = self.parse_and_eval(exp).type.target() + #DumperBase.warn("LOOKING UP 3 '%s'" % typeobj) + except: + # Can throw 'RuntimeError: No type named class Foo.' + pass + except: + #DumperBase.warn("LOOKING UP '%s' FAILED" % ts) + pass + + if typeobj is not None: + #DumperBase.warn('CACHING: %s' % typeobj) + self.typeCache[typeName] = typeobj + self.typesToReport[typeName] = typeobj + + # This could still be None as gdb.lookup_type('char[3]') generates + # 'RuntimeError: No type named char[3]' + #self.typeCache[typeName] = typeobj + #self.typesToReport[typeName] = typeobj + return typeobj + + def doContinue(self): + gdb.execute('continue') + + def fetchStack(self, args): + + def fromNativePath(string): + return string.replace('\\', '/') + + extraQml = int(args.get('extraqml', '0')) + limit = int(args['limit']) + if limit <= 0: + limit = 10000 + + self.prepare(args) + self.output = [] + + i = 0 + if extraQml: + frame = gdb.newest_frame() + ns = self.qtNamespace() + needle = self.qtNamespace() + 'QV4::ExecutionEngine' + pats = [ + '{0}qt_v4StackTraceForEngine((void*)0x{1:x})', + '{0}qt_v4StackTrace((({0}QV4::ExecutionEngine *)0x{1:x})->currentContext())', + '{0}qt_v4StackTrace((({0}QV4::ExecutionEngine *)0x{1:x})->currentContext)', + ] + done = False + while i < limit and frame and not done: + block = None + try: + block = frame.block() + except: + pass + if block is not None: + for symbol in block: + if symbol.is_variable or symbol.is_argument: + value = symbol.value(frame) + typeobj = value.type + if typeobj.code == gdb.TYPE_CODE_PTR: + dereftype = typeobj.target().unqualified() + if dereftype.name == needle: + addr = toInteger(value) + res = None + for pat in pats: + try: + expr = pat.format(ns, addr) + res = str(gdb.parse_and_eval(expr)) + break + except: + continue + + if res is None: + done = True + break + + pos = res.find('"stack=[') + if pos != -1: + res = res[pos + 8:-2] + res = res.replace('\\\"', '\"') + res = res.replace('func=', 'function=') + self.put(res) + done = True + break + frame = frame.older() + i += 1 + + frame = gdb.newest_frame() + self.currentCallContext = None + self.output = [] + self.put('stack={frames=[') + while i < limit and frame: + name = frame.name() + functionName = '??' if name is None else name + fileName = '' + objfile = '' + symtab = '' + pc = frame.pc() + sal = frame.find_sal() + line = -1 + if sal: + line = sal.line + symtab = sal.symtab + if symtab is not None: + objfile = fromNativePath(symtab.objfile.filename) + fullname = symtab.fullname() + if fullname is None: + fileName = '' + else: + fileName = fromNativePath(fullname) + + if self.nativeMixed and functionName == 'qt_qmlDebugMessageAvailable': + interpreterStack = self.extractInterpreterStack() + #print('EXTRACTED INTEPRETER STACK: %s' % interpreterStack) + for interpreterFrame in interpreterStack.get('frames', []): + function = interpreterFrame.get('function', '') + fileName = interpreterFrame.get('file', '') + language = interpreterFrame.get('language', '') + lineNumber = interpreterFrame.get('line', 0) + context = interpreterFrame.get('context', 0) + + self.put(('frame={function="%s",file="%s",' + 'line="%s",language="%s",context="%s"}') + % (function, self.hexencode(fileName), lineNumber, language, context)) + + if False and self.isInternalInterpreterFrame(functionName): + frame = frame.older() + self.put(('frame={address="0x%x",function="%s",' + 'file="%s",line="%s",' + 'module="%s",language="c",usable="0"}') % + (pc, functionName, fileName, line, objfile)) + i += 1 + frame = frame.older() + continue + + self.put(('frame={level="%s",address="0x%x",function="%s",' + 'file="%s",line="%s",module="%s",language="c"}') % + (i, pc, functionName, fileName, line, objfile)) + + try: + # This may fail with something like + # gdb.error: DW_FORM_addr_index used without .debug_addr section + #[in module /data/dev/qt-6/qtbase/lib/libQt6Widgets.so.6] + frame = frame.older() + except: + break + i += 1 + self.put(']}') + self.reportResult(self.takeOutput(), args) + + def createResolvePendingBreakpointsHookBreakpoint(self, args): + class Resolver(gdb.Breakpoint): + def __init__(self, dumper, args): + self.dumper = dumper + self.args = args + spec = 'qt_qmlDebugConnectorOpen' + super(Resolver, self).\ + __init__(spec, gdb.BP_BREAKPOINT, internal=True, temporary=False) + + def stop(self): + self.dumper.resolvePendingInterpreterBreakpoint(args) + self.enabled = False + return False + + self.interpreterBreakpointResolvers.append(Resolver(self, args)) + + def exitGdb(self, _): + gdb.execute('quit') + + def reportResult(self, result, args): + print('result={token="%s",%s}' % (args.get("token", 0), result)) + + def profile1(self, args): + '''Internal profiling''' + import cProfile + tempDir = tempfile.gettempdir() + '/bbprof' + cProfile.run('theDumper.fetchVariables(%s)' % args, tempDir) + import pstats + pstats.Stats(tempDir).sort_stats('time').print_stats() + + def profile2(self, args): + import timeit + print(timeit.repeat('theDumper.fetchVariables(%s)' % args, + 'from __main__ import theDumper', number=10)) + + def tracepointModified(self, tp): + self.tpExpressions = {} + self.tpExpressionWarnings = [] + s = self.resultToMi(tp.dicts()) + def handler(): + print("tracepointmodified=%s" % s) + gdb.post_event(handler) + + def tracepointHit(self, tp, result): + expressions = '{' + ','.join(["%s=%s" % (k,v) for k,v in self.tpExpressions.items()]) + '}' + warnings = [] + if 'warning' in result.keys(): + warnings.append(result.pop('warning')) + warnings += self.tpExpressionWarnings + r = self.resultToMi(result) + w = self.resultToMi(warnings) + def handler(): + print("tracepointhit={result=%s,expressions=%s,warnings=%s}" % (r, expressions, w)) + gdb.post_event(handler) + + def tracepointExpression(self, tp, expression, value, args): + key = "x" + str(len(self.tpExpressions)) + if (isinstance(value, gdb.Value)): + try: + val = self.fromNativeValue(value) + self.prepare(args) + with TopLevelItem(self, expression): + self.putItem(val) + self.tpExpressions[key] = self.output + except Exception as e: + self.tpExpressions[key] = '""' + self.tpExpressionWarnings.append(str(e)) + elif (isinstance(value, Exception)): + self.tpExpressions[key] = '""' + self.tpExpressionWarnings.append(str(value)) + else: + self.tpExpressions[key] = '""' + self.tpExpressionWarnings.append('Unknown expression value type') + return key + + def createTracepoint(self, args): + """ + Creates a tracepoint + """ + tp = GDBTracepoint.create(args, + onModified=self.tracepointModified, + onHit=self.tracepointHit, + onExpression=lambda tp, expr, val: self.tracepointExpression(tp, expr, val, args)) + self.reportResult("tracepoint=%s" % self.resultToMi(tp.dicts()), args) + +class CliDumper(Dumper): + def __init__(self): + Dumper.__init__(self) + self.childrenPrefix = '[' + self.chidrenSuffix = '] ' + self.indent = 0 + self.isCli = True + self.setupDumpers({}) + + def put(self, line): + if self.output: + if self.output[-1].endswith('\n'): + self.output[-1] = self.output[-1][0:-1] + self.output.append(line) + + def putNumChild(self, numchild): + pass + + def putOriginalAddress(self, address): + pass + + def fetchVariable(self, line): + # HACK: Currently, the response to the QtCore loading is completely + # eaten by theDumper, so copy the results here. Better would be + # some shared component. + self.qtCustomEventFunc = theDumper.qtCustomEventFunc + self.qtCustomEventPltFunc = theDumper.qtCustomEventPltFunc + self.qtPropertyFunc = theDumper.qtPropertyFunc + + names = line.split(' ') + name = names[0] + + toExpand = set() + for n in names: + while n: + toExpand.add(n) + try: + n = n[0:n.rindex('.')] + except ValueError: + break + + args = {} + args['fancy'] = 1 + args['passexceptions'] = 1 + args['autoderef'] = 1 + args['qobjectnames'] = 1 + args['varlist'] = name + args['expanded'] = toExpand + self.expandableINames = set() + self.prepare(args) + + self.output = [] + self.put(name + ' = ') + value = self.parseAndEvaluate(name) + with TopLevelItem(self, name): + self.putItem(value) + + if not self.expandableINames: + self.put('\n\nNo drill down available.\n') + return self.takeOutput() + + pattern = ' pp ' + name + ' ' + '%s' + return (self.takeOutput() + + '\n\nDrill down:\n ' + + '\n '.join(pattern % x for x in self.expandableINames) + + '\n') + + +# Global instances. +theDumper = Dumper() +theCliDumper = CliDumper() + + +###################################################################### +# +# ThreadNames Command +# +####################################################################### + +def threadnames(arg): + return theDumper.threadnames(int(arg)) + + +registerCommand('threadnames', threadnames) + +####################################################################### +# +# Native Mixed +# +####################################################################### + + +class InterpreterMessageBreakpoint(gdb.Breakpoint): + def __init__(self): + spec = 'qt_qmlDebugMessageAvailable' + super(InterpreterMessageBreakpoint, self).\ + __init__(spec, gdb.BP_BREAKPOINT, internal=True) + + def stop(self): + print('Interpreter event received.') + return theDumper.handleInterpreterMessage() + + +####################################################################### +# +# Shared objects +# +####################################################################### + +def new_objfile_handler(event): + return theDumper.handleNewObjectFile(event.new_objfile) + + +gdb.events.new_objfile.connect(new_objfile_handler) + + +#InterpreterMessageBreakpoint() diff --git a/share/qtcreator/debugger/python2/gdbtracepoint.py b/share/qtcreator/debugger/python2/gdbtracepoint.py new file mode 100644 index 00000000000..871132ac48d --- /dev/null +++ b/share/qtcreator/debugger/python2/gdbtracepoint.py @@ -0,0 +1,377 @@ +# Copyright (C) 2021 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import gdb +import sys +import time + +# for ProcessName capture +try: + import psutil +except: + psutil = None + +# Caps types +Address, \ +Caller, \ +Callstack, \ +FilePos, \ +Function, \ +Pid, \ +ProcessName, \ +Tick, \ +Tid, \ +ThreadName, \ +Expression, \ + = range(0, 11) + +class GDBTracepoint(gdb.Breakpoint): + """ + Python Breakpoint extension for "tracepoints", breakpoints that do not stop the inferior + """ + + @staticmethod + def create(args, onModified, onHit, onExpression): + """ + Static creator function + """ + tp_kwargs = {} + if 'temporary' in args.keys(): + tp_kwargs['temporary'] = args['temporary'] + spec = args['spec'] + tp = GDBTracepoint(spec, **tp_kwargs) + tp.onModified = onModified + tp.onHit = onHit + tp.onExpression = onExpression + if 'ignore_count' in args.keys(): + tp.ignore_count = args['ignore_count'] + if 'enabled' in args.keys(): + tp.enabled = args['enabled'] + if 'thread' in args.keys(): + tp.thread = args['thread'] + if 'condition' in args.keys(): + tp.condition = args['condition'] + if 'caps' in args.keys(): + for ce in args['caps']: + tp.addCaps(ce[0], ce[1]) + return tp + + def __init__(self, spec, **kwargs): + """ + Constructor + """ + kwargs['internal'] = True + super(GDBTracepoint, self).__init__(spec, **kwargs) + self.caps = [] + + _hexSize = 8 if sys.maxsize > 2**32 else 4 + _hasMonotonicTime = False if sys.version_info[0] <= 2 or (sys.version_info[0] == 3 and sys.version_info[1] < 3) else True + + def dicts(self): + """ + Returns dictionareis for mi representation + """ + results = [] + result = {} + result['number'] = str(self.number) + result['enabled'] = 'y' if self.enabled else 'n' + result['type'] = 'pseudo_tracepoint' + result['disp'] = 'del' if self.temporary else 'keep' + result['times'] = str(self.hit_count) + result['original-location'] = self.location + try: + d = gdb.decode_line(self.location) + if d[1] is None: + result['addr'] = '' + result['pending'] = self.location + results.append(result) + else: + if len(d[1]) > 1: + result['addr'] = '' + results.append(result) + for i, sl in enumerate(d[1]): + result_ = {} + result_['number'] = result['number'] + "." + str(i + 1) + result_['enabled'] = 'y' if self.enabled else 'n' + if sl.pc is None: + result_['addr'] = '' + else: + result_['addr'] = '{0:#0{1}x}'.format(sl.pc, self._hexSize + 2) + if sl.symtab and sl.symtab.is_valid(): + func = self._getFunctionFromAddr(sl.pc) + if func: + result_['func'] = func.print_name + result_['file'] = sl.symtab.filename + result_['fullname'] = sl.symtab.fullname() + result_['line'] = sl.line + results.append(result_) + else: + sl = d[1][0] + if sl.pc is None: + result['addr'] = '' + else: + result['addr'] = '{0:#0{1}x}'.format(sl.pc, self._hexSize + 2) + if sl.symtab and sl.symtab.is_valid(): + func = self._getFunctionFromAddr(sl.pc) + if func: + result['func'] = func.print_name + result['file'] = sl.symtab.filename + result['fullname'] = sl.symtab.fullname() + result['line'] = sl.line + results.append(result) + except Exception as e: + import traceback + traceback.print_exc() + result['addr'] = '' + result['pending'] = self.location + results.append(result) + return results + + def addCaps(self, capsType, expression=None): + """ + Adds capture expressions for a tracepoint + + :param caps_type: Type of capture + :param expression: Expression for Expression caps type + """ + if capsType != Expression: + expression = None + else: + if expression is None: + expression = '' + self.caps.append((self.capsMap[capsType], expression)) + + def stop(self): + """ + Overridden stop function, this evaluates conditions and captures data from the inferior + + :return: Always False + """ + try: + self.onModified(self) + result = {} + result['number'] = self.number + try: + if self.condition: + try: + result = gdb.parse_and_eval(self.condition) + if result.type.code == gdb.TYPE_CODE_BOOL and str(result) == 'false': + return False + except: + pass + if self.ignore_count > 0: + return False + if self.thread and gdb.selected_thread().global_num != self.thread: + return False + except Exception as e: + result['warning'] = str(e) + self.onHit(self, result) + return False + if len(self.caps) > 0: + caps = [] + try: + for func, expr in self.caps: + if expr is None: + caps.append(func(self)) + else: + caps.append(func(self, expr)) + except Exception as e: + result['warning'] = str(e) + self.onHit(self, result) + return False + result['caps'] = caps + self.onHit(self, result) + return False + except: + # Always return false, regardless... + return False + + def _getFunctionFromAddr(self, addr): + try: + block = gdb.block_for_pc(addr) + while block and not block.function: + block = block.superblock + if block is None: + return None + return block.function + except: + return None + + def _getAddress(self): + """ + Capture function for Address + """ + try: + frame = gdb.selected_frame() + if not (frame is None) and (frame.is_valid()): + return '{0:#0{1}x}'.format(frame.pc(), self._hexSize + 2) + except Exception as e: + return str(e) + return '' + + def _getCaller(self): + """ + Capture function for Caller + """ + try: + frame = gdb.selected_frame() + if not (frame is None) and (frame.is_valid()): + frame = frame.older() + if not (frame is None) and (frame.is_valid()): + name = frame.name() + if name is None: + return '' + return name + except Exception as e: + return str(e) + return '' + + def _getCallstack(self): + """ + Capture function for Callstack + """ + try: + frames = [] + frame = gdb.selected_frame() + if (frame is None) or (not frame.is_valid()): + frames.append('') + return str(frames) + while not (frame is None): + func = frame.function() + if func is None: + frames.append('{0:#0{1}x}'.format(frame.pc(), self._hexSize + 2)) + else: + sl = frame.find_sal() + if sl is None: + frames.append(func.symtab.filename) + else: + frames.append(func.symtab.filename + ':' + str(sl.line)) + frame = frame.older() + return frames + except Exception as e: + return str(e) + + def _getFilePos(self): + """ + Capture function for FilePos + """ + try: + frame = gdb.selected_frame() + if (frame is None) or (not frame.is_valid()): + return '' + sl = frame.find_sal() + if sl is None: + return '' + return sl.symtab.filename + ':' + str(sl.line) + except Exception as e: + return str(e) + + def _getFunction(self): + """ + Capture function for Function + """ + try: + frame = gdb.selected_frame() + if not (frame is None): + return str(frame.name()) + except Exception as e: + return str(e) + return '' + + def _getPid(self): + """ + Capture function for Pid + """ + try: + thread = gdb.selected_thread() + if not (thread is None): + (pid, lwpid, tid) = thread.ptid + return str(pid) + except Exception as e: + return str(e) + return '' + + def _getProcessName(slef): + """ + Capture for ProcessName + """ + # gdb does not expose process name, neither does (standard) python + # You can use for example psutil, but it might not be present. + # Default to name of thread with ID 1 + inf = gdb.selected_inferior() + if psutil is None: + try: + if inf is None: + return '' + threads = filter(lambda t: t.num == 1, list(inf.threads())) + if len(threads) < 1: + return '' + thread = threads[0] + # use thread name + return thread.name + except Exception as e: + return str(e) + else: + return psutil.Process(inf.pid).name() + + def _getTick(self): + """ + Capture function for Tick + """ + if self._hasMonotonicTime: + return str(int(time.monotonic() * 1000)) + else: + return '' + + def _getTid(self): + """ + Capture function for Tid + """ + try: + thread = gdb.selected_thread() + if not (thread is None): + (pid, lwpid, tid) = thread.ptid + if tid == 0: + return str(lwpid) + else: + return str(tid) + except Exception as e: + return str(e) + return '' + + def _getThreadName(self): + """ + Capture function for ThreadName + """ + try: + thread = gdb.selected_thread() + if not (thread is None): + return str(thread.name) + except Exception as e: + return str(e) + return '' + + def _getExpression(self, expression): + """ + Capture function for Expression + + :param expr: The expression to evaluate + """ + try: + value = gdb.parse_and_eval(expression) + if value: + return self.onExpression(self, expression, value) + except Exception as e: + return self.onExpression(self, expression, e) + + capsMap = {Address: _getAddress, + Caller: _getCaller, + Callstack: _getCallstack, + FilePos: _getFilePos, + Function: _getFunction, + Pid: _getPid, + ProcessName: _getProcessName, + Tid: _getTid, + Tick: _getTick, + ThreadName: _getThreadName, + Expression: _getExpression} diff --git a/share/qtcreator/debugger/python2/libcpp_stdtypes.py b/share/qtcreator/debugger/python2/libcpp_stdtypes.py new file mode 100644 index 00000000000..44b3a235ffd --- /dev/null +++ b/share/qtcreator/debugger/python2/libcpp_stdtypes.py @@ -0,0 +1,527 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +from stdtypes import qdump__std__array, qdump__std__complex, qdump__std__once_flag, qdump__std__unique_ptr, qdumpHelper__std__deque__libcxx, qdumpHelper__std__vector__libcxx +from utils import DisplayFormat +from dumper import Children, DumperBase + + +def qform__std____1__array(): + return [DisplayFormat.ArrayPlot] + + +def qdump__std____1__array(d, value): + qdump__std__array(d, value) + + +def qdump__std____1__complex(d, value): + qdump__std__complex(d, value) + + +def qdump__std____1__deque(d, value): + qdumpHelper__std__deque__libcxx(d, value) + + +def qdump__std____1__list(d, value): + if value.type.size() == 3 * d.ptrSize(): + # C++11 only. + (dummy1, dummy2, size) = value.split("ppp") + d.putItemCount(size) + else: + # Need to count manually. + p = d.extractPointer(value) + head = value.address() + size = 0 + while head != p and size < 1001: + size += 1 + p = d.extractPointer(p) + d.putItemCount(size, 1000) + + if d.isExpanded(): + (prev, p) = value.split("pp") + innerType = value.type[0] + typeCode = "pp{%s}" % innerType.name + with Children(d, size, maxNumChild=1000, childType=innerType): + for i in d.childRange(): + (prev, p, val) = d.split(typeCode, p) + d.putSubItem(i, val) + + +def qdump__std____1__set(d, value): + (proxy, head, size) = value.split("ppp") + + d.check(0 <= size and size <= 100 * 1000 * 1000) + d.putItemCount(size) + + if d.isExpanded(): + valueType = value.type[0] + + def in_order_traversal(node): + (left, right, parent, color, pad, data) = d.split("pppB@{%s}" % (valueType.name), node) + + if left: + for res in in_order_traversal(left): + yield res + + yield data + + if right: + for res in in_order_traversal(right): + yield res + + with Children(d, size, maxNumChild=1000): + for (i, data) in zip(d.childRange(), in_order_traversal(head)): + d.putSubItem(i, data) + + +def qdump__std____1__multiset(d, value): + qdump__std____1__set(d, value) + + +def qform__std____1__map(): + return [DisplayFormat.CompactMap] + + +def qdump__std____1__map(d, value): + try: + (proxy, head, size) = value.split("ppp") + d.check(0 <= size and size <= 100 * 1000 * 1000) + + # Sometimes there is extra data at the front. Don't know why at the moment. + except RuntimeError: + (junk, proxy, head, size) = value.split("pppp") + d.check(0 <= size and size <= 100 * 1000 * 1000) + + d.putItemCount(size) + + if d.isExpanded(): + keyType = value.type[0] + valueType = value.type[1] + pairType = value.type[3][0] + + def in_order_traversal(node): + (left, right, parent, color, pad, pair) = d.split("pppB@{%s}" % (pairType.name), node) + + if left: + for res in in_order_traversal(left): + yield res + + yield pair.split("{%s}@{%s}" % (keyType.name, valueType.name))[::2] + + if right: + for res in in_order_traversal(right): + yield res + + with Children(d, size, maxNumChild=1000): + for (i, pair) in zip(d.childRange(), in_order_traversal(head)): + d.putPairItem(i, pair) + + +def qform__std____1__multimap(): + return [DisplayFormat.CompactMap] + + +def qdump__std____1__multimap(d, value): + qdump__std____1__map(d, value) + + +def qdump__std____1__map__iterator(d, value): + d.putEmptyValue() + if d.isExpanded(): + with Children(d): + node = value['__i_']['__ptr_'].dereference()['__value_']['__cc'] + d.putSubItem('first', node['first']) + d.putSubItem('second', node['second']) + + +def qdump__std____1__map__const_iterator(d, value): + qdump__std____1__map__iterator(d, value) + + +def qdump__std____1__set__iterator(d, value): + d.putEmptyValue() + d.putExpandable() + if value.type.name.endswith("::iterator"): + treeTypeName = value.type.name[:-len("::iterator")] + elif value.type.name.endswith("::const_iterator"): + treeTypeName = value.type.name[:-len("::const_iterator")] + treeType = d.lookupType(treeTypeName) + keyType = treeType[0] + if d.isExpanded(): + with Children(d): + node = value['__ptr_'].dereference()['__value_'] + node = node.cast(keyType) + d.putSubItem('value', node) + + +def qdump__std____1__set_const_iterator(d, value): + qdump__std____1__set__iterator(d, value) + + +def qdump__std____1__stack(d, value): + d.putItem(value["c"]) + d.putBetterType(value.type) + + +def GetChildMemberWithName(value, name): + members = value.members(True) + + for member in members: + if member.name == name: + return member + return None + + +def GetIndexOfChildWithName(value, name): + members = value.members(True) + + for i, member in enumerate(members): + if member.name == name: + return i + return None + + +class StringLayout: + CSD = 0 + DSC = 1 + + +def std_1_string_dumper_v2(d, value): + charType = value['__l']['__data_'].dereference().type + + R = GetChildMemberWithName(value, "__r_") + if not R: + raise Exception("Could not find __r_") + + # __r_ is a compressed_pair of the actual data and the allocator. The data we + # want is in the first base class. + R_Base_SP = R[0] + + if not R_Base_SP: + raise Exception("Could not find R_Base_SP") + + Rep_Sp = GetChildMemberWithName(R_Base_SP, "__value_") + + if not Rep_Sp: + raise Exception("Could not find __value_") + + # Our layout seems a little different + Rep_Sp = Rep_Sp[0] + + if not Rep_Sp: + raise Exception("Could not find Rep_Sp") + + L = GetChildMemberWithName(Rep_Sp, "__l") + + if not L: + raise Exception("Could not find __l") + + layout = StringLayout.CSD + if GetIndexOfChildWithName(L, "__data_") == 0: + layout = StringLayout.DSC + + short_mode = False + using_bitmasks = True + size = 0 + size_mode_value = 0 + + Short_Sp = GetChildMemberWithName(Rep_Sp, "__s") + if not Short_Sp: + raise Exception("Could not find __s") + + Is_Long = GetChildMemberWithName(Short_Sp, "__is_long_") + Size_Sp = GetChildMemberWithName(Short_Sp, "__size_") + if not Size_Sp: + raise Exception("Could not find __size_") + + if Is_Long: + using_bitmasks = False + short_mode = Is_Long.integer() == 0 + size = Size_Sp.integer() + else: + size_mode_value = Size_Sp.integer() + mode_mask = 1 + if layout == StringLayout.DSC: + mode_mask = 0x80 + short_mode = (size_mode_value & mode_mask) == 0 + + if short_mode: + Location_Sp = GetChildMemberWithName(Short_Sp, "__data_") + + if using_bitmasks: + size = ((size_mode_value >> 1) % 256) + if layout == StringLayout.DSC: + size = size_mode_value + + # The string is most likely not initialized yet + if size > 100 or not Location_Sp: + raise Exception("Probably not initialized yet") + + d.putCharArrayHelper(d.extractPointer(Location_Sp), size, + charType, d.currentItemFormat()) + return + + Location_Sp = GetChildMemberWithName(L, "__data_") + Size_Vo = GetChildMemberWithName(L, "__size_") + Capacity_Vo = GetChildMemberWithName(L, "__cap_") + + if not Location_Sp or not Size_Vo or not Capacity_Vo: + raise Exception("Could not find Location_Sp, Size_Vo or Capacity_Vo") + + size = Size_Vo.integer() + capacity = Capacity_Vo.integer() + if not using_bitmasks and layout == StringLayout.CSD: + capacity *= 2 + if capacity < size: + raise Exception("Capacity is less than size") + + d.putCharArrayHelper(d.extractPointer(Location_Sp), size, + charType, d.currentItemFormat()) + + +def std_1_string_dumper_v1(d, value): + charType = value['__l']['__data_'].dereference().type + D = None + + if d.isLldb: + D = value[0][0][0][0] + elif d.isGdb: + D = value["__r_"].members(True)[0][0][0] + else: + raise Exception("Unknown debugger (neither gdb nor lldb)") + + layoutDecider = D[0][0] + if not layoutDecider: + raise Exception("Could not find layoutDecider") + + size = 0 + size_mode_value = 0 + short_mode = False + libcxx_version = 14 + + layoutModeIsDSC = layoutDecider.name == '__data_' + if (layoutModeIsDSC): + size_mode = D[1][1][0] + if not size_mode: + raise Exception("Could not find size_mode") + if not size_mode.name == '__size_': + size_mode = D[1][1][1] + if not size_mode: + raise Exception("Could not find size_mode") + + size_mode_value = size_mode.integer() + short_mode = ((size_mode_value & 0x80) == 0) + else: + size_mode = D[1][0][0] + if not size_mode: + raise Exception("Could not find size_mode") + + if size_mode.name == '__is_long_': + libcxx_version = 15 + short_mode = (size_mode.integer() == 0) + + size_mode = D[1][0][1] + size_mode_value = size_mode.integer() + else: + size_mode_value = size_mode.integer() + short_mode = ((size_mode_value & 1) == 0) + + if short_mode: + s = D[1] + + if not s: + raise Exception("Could not find s") + + if libcxx_version == 14: + location_sp = s[0] if layoutModeIsDSC else s[1] + size = size_mode_value if layoutModeIsDSC else ((size_mode_value >> 1) % 256) + elif libcxx_version == 15: + location_sp = s[0] if layoutModeIsDSC else s[2] + size = size_mode_value + + else: + l = D[0] + if not l: + raise Exception("Could not find l") + + # we can use the layout_decider object as the data pointer + location_sp = layoutDecider if layoutModeIsDSC else l[2] + size_vo = l[1] + if not size_vo or not location_sp: + raise Exception("Could not find size_vo or location_sp") + size = size_vo.integer() + + if short_mode and location_sp: + d.putCharArrayHelper(d.extractPointer(location_sp), size, + charType, d.currentItemFormat()) + else: + d.putCharArrayHelper(location_sp.integer(), + size, charType, d.currentItemFormat()) + + return + +def qdump__std____1__string(d, value): + try: + std_1_string_dumper_v2(d, value) + except Exception as eV2: + try: + std_1_string_dumper_v1(d, value) + except Exception as eV1: + d.putValue("Could not parse: %s, %s" % (eV1, eV2)) + + +def qdump__std____1__wstring(d, value): + try: + std_1_string_dumper_v2(d, value) + except Exception as eV2: + try: + std_1_string_dumper_v1(d, value) + except Exception as eV1: + d.putValue("Could not parse: %s, %s" % (eV1, eV2)) + + +def qdump__std____1__basic_string(d, value): + innerType = value.type[0].name + if innerType in ("char", "char8_t", "char16_t"): + qdump__std____1__string(d, value) + elif innerType in ("wchar_t", "char32_t"): + qdump__std____1__wstring(d, value) + else: + d.warn("UNKNOWN INNER TYPE %s" % innerType) + + +def qdump__std____1__shared_ptr(d, value): + i = value["__ptr_"] + if i.pointer() == 0: + d.putValue("(null)") + else: + d.putItem(i.dereference()) + d.putBetterType(value.type) + + +def qdump__std____1__weak_ptr(d, value): + return qdump__std____1__shared_ptr(d, value) + + +def qdump__std____1__unique_ptr(d, value): + if value.type.size() == d.ptrSize(): + p = d.extractPointer(value) + else: + _, p = value.split("pp"); # For custom deleters. + if p == 0: + d.putValue("(null)") + else: + try: + d.putItem(value["__value_"]) + d.putValue(d.currentValue.value, d.currentValue.encoding) + except: + d.putItem(d.createValue(p, value.type[0])) + d.putBetterType(value.type) + + +def qform__std____1__unordered_map(): + return [DisplayFormat.CompactMap] + + +def qdump__std____1__unordered_map(d, value): + (size, _) = value["__table_"]["__p2_"].split("pp") + d.putItemCount(size) + + keyType = value.type[0] + valueType = value.type[1] + pairType = value.type[4][0] + + if d.isExpanded(): + curr = value["__table_"]["__p1_"].split("pp")[0] + + def traverse_list(node): + while node: + (next_, _, pad, pair) = d.split("pp@{%s}" % (pairType.name), node) + yield pair.split("{%s}@{%s}" % (keyType.name, valueType.name))[::2] + node = next_ + + with Children(d, size, childType=value.type[0], maxNumChild=1000): + for (i, value) in zip(d.childRange(), traverse_list(curr)): + d.putPairItem(i, value, 'key', 'value') + + +def qdump__std____1__unordered_set(d, value): + (size, _) = value["__table_"]["__p2_"].split("pp") + d.putItemCount(size) + + valueType = value.type[0] + + if d.isExpanded(): + curr = value["__table_"]["__p1_"].split("pp")[0] + + def traverse_list(node): + while node: + (next_, _, pad, val) = d.split("pp@{%s}" % (valueType.name), node) + yield val + node = next_ + + with Children(d, size, childType=value.type[0], maxNumChild=1000): + for (i, value) in zip(d.childRange(), traverse_list(curr)): + d.putSubItem(i, value) + + +def qdump__std____1__unordered_multiset(d, value): + qdump__std____1__unordered_set(d, value) + + +def qform__std____1__valarray(): + return [DisplayFormat.ArrayPlot] + + +def qdump__std____1__valarray(d, value): + innerType = value.type[0] + (begin, end) = value.split('pp') + size = int((end - begin) / innerType.size()) + d.putItemCount(size) + d.putPlotData(begin, size, innerType) + + +def qform__std____1__vector(): + return [DisplayFormat.ArrayPlot] + + +def qdump__std____1__vector(d, value): + qdumpHelper__std__vector__libcxx(d, value) + + +def qdump__std____1__once_flag(d, value): + qdump__std__once_flag(d, value) + + +def qdump__std____1__variant(d, value): + index = value['__impl']['__index'] + index_num = int(index) + value_type = d.templateArgument(value.type, index_num) + d.putValue("<%s:%s>" % (index_num, value_type.name)) + + d.putNumChild(2) + if d.isExpanded(): + with Children(d): + d.putSubItem("index", index) + d.putSubItem("value", value.cast(value_type)) + + +def qdump__std____1__optional(d, value): + if value['__engaged_'].integer() == 0: + d.putSpecialValue("empty") + else: + d.putItem(value['#1']['__val_']) + + +def qdump__std____1__tuple(d, value): + values = [] + for member in value['__base_'].members(False): + values.append(member['__value_']) + d.putItemCount(len(values)) + d.putNumChild(len(values)) + if d.isExpanded(): + with Children(d): + count = 0 + for internal_value in values: + d.putSubItem("[%i]" % count, internal_value) + count += 1 diff --git a/share/qtcreator/debugger/python2/lldbbridge.py b/share/qtcreator/debugger/python2/lldbbridge.py new file mode 100644 index 00000000000..4d9b74b2ea2 --- /dev/null +++ b/share/qtcreator/debugger/python2/lldbbridge.py @@ -0,0 +1,2505 @@ +# Copyright (C) 2016 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import inspect +import os +import platform +import re +import sys +import threading +import time +import lldb +import utils +from utils import DebuggerStartMode, BreakpointType, TypeCode, LogChannel + +from contextlib import contextmanager + +sys.path.insert(1, os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))) + +# Simplify development of this module by reloading deps +if 'dumper' in sys.modules: + if sys.version_info[0] >= 3: + if sys.version_info[1] > 3: + from importlib import reload + else: + def reload(m): print('Unsupported Python version - not reloading %s' % str(m)) + reload(sys.modules['dumper']) + +from dumper import DumperBase, SubItem, Children, TopLevelItem + +####################################################################### +# +# Helpers +# +####################################################################### + +qqWatchpointOffset = 10000 +_c_str_trans = None + +if sys.version_info[0] >= 3: + _c_str_trans = str.maketrans({"\n": "\\n", '"':'\\"', "\\":"\\\\"}) + +def toCString(s): + if _c_str_trans is not None: + return str(s).translate(_c_str_trans) + else: + return str(s).replace('\\', '\\\\').replace('\n', '\\n').replace('"', '\\"') + +def fileNameAsString(file): + return toCString(file) if file.IsValid() else '' + + +def check(exp): + if not exp: + raise RuntimeError('Check failed') + + +class Dumper(DumperBase): + def __init__(self, debugger=None): + DumperBase.__init__(self) + lldb.theDumper = self + + self.isLldb = True + self.typeCache = {} + + self.outputLock = threading.Lock() + + if debugger: + # Re-use existing debugger + self.debugger = debugger + else: + self.debugger = lldb.SBDebugger.Create() + #self.debugger.SetLoggingCallback(loggingCallback) + #def loggingCallback(args): + # s = args.strip() + # s = s.replace('"', "'") + # sys.stdout.write('log="%s"@\n' % s) + #Same as: self.debugger.HandleCommand('log enable lldb dyld step') + #self.debugger.EnableLog('lldb', ['dyld', 'step', 'process', 'state', + # 'thread', 'events', + # 'communication', 'unwind', 'commands']) + #self.debugger.EnableLog('lldb', ['all']) + self.debugger.Initialize() + self.debugger.SetAsync(True) + self.debugger.HandleCommand('settings set auto-confirm on') + + # FIXME: warn('DISABLING DEFAULT FORMATTERS') + # It doesn't work at all with 179.5 and we have some bad + # interaction in 300 + # if not hasattr(lldb.SBType, 'GetCanonicalType'): # 'Test' for 179.5 + #self.debugger.HandleCommand('type category delete gnu-libstdc++') + #self.debugger.HandleCommand('type category delete libcxx') + #self.debugger.HandleCommand('type category delete default') + self.debugger.DeleteCategory('gnu-libstdc++') + self.debugger.DeleteCategory('libcxx') + self.debugger.DeleteCategory('default') + self.debugger.DeleteCategory('cplusplus') + #for i in range(self.debugger.GetNumCategories()): + # self.debugger.GetCategoryAtIndex(i).SetEnabled(False) + + self.process = None + self.target = None + self.fakeAddress_ = None + self.fakeLAddress_ = None + self.eventState = lldb.eStateInvalid + + self.executable_ = None + self.symbolFile_ = None + self.startMode_ = None + self.processArgs_ = None + self.attachPid_ = None + self.dyldImageSuffix = None + self.dyldLibraryPath = None + self.dyldFrameworkPath = None + + self.isShuttingDown_ = False + self.isInterrupting_ = False + self.interpreterBreakpointResolvers = [] + + DumperBase.warn = Dumper.warn_impl + self.report('lldbversion=\"%s\"' % lldb.SBDebugger.GetVersionString()) + + @staticmethod + def warn_impl(message): + if message[-1:] == '\n': + message += '\n' + print('@\nbridgemessage={msg="%s",channel="%s"}\n@' + % (message.replace('"', '$'), LogChannel.AppError)) + + def fromNativeFrameValue(self, nativeValue): + return self.fromNativeValue(nativeValue) + + def fromNativeValue(self, nativeValue): + self.check(isinstance(nativeValue, lldb.SBValue)) + nativeType = nativeValue.GetType() + typeName = nativeType.GetName() + code = nativeType.GetTypeClass() + + # Display the result of GetSummary() for Core Foundation string + # and string-like types. + summary = None + if self.useFancy: + if (typeName.startswith('CF') + or typeName.startswith('__CF') + or typeName.startswith('NS') + or typeName.startswith('__NSCF')): + if code == lldb.eTypeClassPointer: + summary = nativeValue.Dereference().GetSummary() + elif code == lldb.eTypeClassReference: + summary = nativeValue.Dereference().GetSummary() + else: + summary = nativeValue.GetSummary() + + nativeValue.SetPreferSyntheticValue(False) + + if code == lldb.eTypeClassReference: + nativeTargetType = nativeType.GetDereferencedType() + if not nativeTargetType.IsPointerType(): + nativeTargetType = nativeTargetType.GetUnqualifiedType() + targetType = self.fromNativeType(nativeTargetType) + val = self.createReferenceValue(nativeValue.GetValueAsUnsigned(), targetType) + val.laddress = nativeValue.AddressOf().GetValueAsUnsigned() + #DumperBase.warn('CREATED REF: %s' % val) + elif code == lldb.eTypeClassPointer: + nativeTargetType = nativeType.GetPointeeType() + if not nativeTargetType.IsPointerType(): + nativeTargetType = nativeTargetType.GetUnqualifiedType() + targetType = self.fromNativeType(nativeTargetType) + val = self.createPointerValue(nativeValue.GetValueAsUnsigned(), targetType) + #DumperBase.warn('CREATED PTR 1: %s' % val) + val.laddress = nativeValue.AddressOf().GetValueAsUnsigned() + #DumperBase.warn('CREATED PTR 2: %s' % val) + elif code == lldb.eTypeClassTypedef: + nativeTargetType = nativeType.GetUnqualifiedType() + if hasattr(nativeTargetType, 'GetCanonicalType'): + nativeTargetType = nativeTargetType.GetCanonicalType() + val = self.fromNativeValue(nativeValue.Cast(nativeTargetType)) + val._type = self.fromNativeType(nativeType) + #DumperBase.warn('CREATED TYPEDEF: %s' % val) + else: + val = self.Value(self) + address = nativeValue.GetLoadAddress() + if address is not None: + val.laddress = address + if True: + data = nativeValue.GetData() + error = lldb.SBError() + size = nativeValue.GetType().GetByteSize() + if size > 1: + # 0 happens regularly e.g. for cross-shared-object types. + # 1 happens on Linux e.g. for QObject uses outside of QtCore. + try: + val.ldata = data.ReadRawData(error, 0, size) + except: + pass + + val._type = self.fromNativeType(nativeType) + + if code == lldb.eTypeClassEnumeration: + intval = nativeValue.GetValueAsSigned() + display = str(nativeValue).split(' = ') + if len(display) == 2: + verbose = display[1] + if '|' in verbose and not verbose.startswith('('): + verbose = '(' + verbose + ')' + else: + verbose = intval + val.ldisplay = '%s (%d)' % (verbose, intval) + elif code in (lldb.eTypeClassComplexInteger, lldb.eTypeClassComplexFloat): + val.ldisplay = str(nativeValue.GetValue()) + #elif code == lldb.eTypeClassArray: + # if hasattr(nativeType, 'GetArrayElementType'): # New in 3.8(?) / 350.x + # val.type.ltarget = self.fromNativeType(nativeType.GetArrayElementType()) + # else: + # fields = nativeType.get_fields_array() + # if len(fields): + # val.type.ltarget = self.fromNativeType(fields[0]) + #elif code == lldb.eTypeClassVector: + # val.type.ltarget = self.fromNativeType(nativeType.GetVectorElementType()) + + val.summary = summary + val.lIsInScope = nativeValue.IsInScope() + val.name = nativeValue.GetName() + return val + + def nativeStructAlignment(self, nativeType): + def handleItem(nativeFieldType, align): + a = self.fromNativeType(nativeFieldType).alignment() + return a if a > align else align + align = 1 + for i in range(nativeType.GetNumberOfDirectBaseClasses()): + base = nativeType.GetDirectBaseClassAtIndex(i) + align = handleItem(base.GetType(), align) + for i in range(nativeType.GetNumberOfFields()): + child = nativeType.GetFieldAtIndex(i) + align = handleItem(child.GetType(), align) + return align + + def listMembers(self, value, nativeType): + #DumperBase.warn("ADDR: 0x%x" % self.fakeAddress_) + if value.laddress: + fakeAddress = lldb.SBAddress(value.laddress, self.target) + fakeLAddress = value.laddress + else: + fakeAddress = self.fakeAddress_ + fakeLAddress = self.fakeLAddress_ + + fakeValue = self.target.CreateValueFromAddress('x', fakeAddress, nativeType) + fakeValue.SetPreferSyntheticValue(False) + + baseNames = {} + for i in range(nativeType.GetNumberOfDirectBaseClasses()): + base = nativeType.GetDirectBaseClassAtIndex(i) + baseNames[base.GetName()] = i + + fieldBits = {} + for f in nativeType.get_fields_array(): + bitsize = f.GetBitfieldSizeInBits() + if bitsize == 0: + bitsize = f.GetType().GetByteSize() * 8 + bitpos = f.GetOffsetInBits() + fieldBits[f.name] = (bitsize, bitpos, f.IsBitfield()) + + # Normal members and non-empty base classes. + anonNumber = 0 + for i in range(fakeValue.GetNumChildren()): + nativeField = fakeValue.GetChildAtIndex(i) + nativeField.SetPreferSyntheticValue(False) + + fieldName = nativeField.GetName() + nativeFieldType = nativeField.GetType() + + if fieldName in fieldBits: + (fieldBitsize, fieldBitpos, isBitfield) = fieldBits[fieldName] + else: + fieldBitsize = nativeFieldType.GetByteSize() * 8 + fieldBitpos = None + isBitfield = False + + if isBitfield: # Bit fields + fieldType = self.createBitfieldType( + self.createType(self.typeName(nativeFieldType)), fieldBitsize) + yield self.Field(self, name=fieldName, type=fieldType, + bitsize=fieldBitsize, bitpos=fieldBitpos) + + elif fieldName is None: # Anon members + anonNumber += 1 + fieldName = '#%s' % anonNumber + fakeMember = fakeValue.GetChildAtIndex(i) + fakeMemberAddress = fakeMember.GetLoadAddress() + offset = fakeMemberAddress - fakeLAddress + yield self.Field(self, name=fieldName, type=self.fromNativeType(nativeFieldType), + bitsize=fieldBitsize, bitpos=8 * offset) + + elif fieldName in baseNames: # Simple bases + member = self.fromNativeValue(fakeValue.GetChildAtIndex(i)) + member.isBaseClass = True + yield member + + else: # Normal named members + member = self.fromNativeValue(fakeValue.GetChildAtIndex(i)) + member.name = nativeField.GetName() + yield member + + # Empty bases are not covered above. + for i in range(nativeType.GetNumberOfDirectBaseClasses()): + fieldObj = nativeType.GetDirectBaseClassAtIndex(i) + fieldType = fieldObj.GetType() + if fieldType.GetNumberOfFields() == 0: + if fieldType.GetNumberOfDirectBaseClasses() == 0: + member = self.Value(self) + fieldName = fieldObj.GetName() + member._type = self.fromNativeType(fieldType) + member.name = fieldName + member.fields = [] + if False: + # This would be correct if we came here only for + # truly empty base classes. Alas, we don't, see below. + member.ldata = bytes() + member.lbitsize = fieldType.GetByteSize() * 8 + else: + # This is a hack. LLDB 3.8 reports declared but not defined + # types as having no fields and(!) size == 1. At least + # for the common case of a single base class we can + # fake the contents by using the whole derived object's + # data as base class data. + data = fakeValue.GetData() + size = nativeType.GetByteSize() + member.lbitsize = size * 8 + error = lldb.SBError() + member.laddress = value.laddress + member.ldata = data.ReadRawData(error, 0, size) + member.isBaseClass = True + member.ltype = self.fromNativeType(fieldType) + member.name = fieldName + yield member + + def ptrSize(self): + result = self.target.GetAddressByteSize() + self.ptrSize = lambda: result + return result + + def fromNativeType(self, nativeType): + self.check(isinstance(nativeType, lldb.SBType)) + code = nativeType.GetTypeClass() + + # eTypeClassInvalid = (0u), + # eTypeClassArray = (1u << 0), + # eTypeClassBlockPointer = (1u << 1), + # eTypeClassBuiltin = (1u << 2), + # eTypeClassClass = (1u << 3), + # eTypeClassComplexFloat = (1u << 4), + # eTypeClassComplexInteger = (1u << 5), + # eTypeClassEnumeration = (1u << 6), + # eTypeClassFunction = (1u << 7), + # eTypeClassMemberPointer = (1u << 8), + # eTypeClassObjCObject = (1u << 9), + # eTypeClassObjCInterface = (1u << 10), + # eTypeClassObjCObjectPointer = (1u << 11), + # eTypeClassPointer = (1u << 12), + # eTypeClassReference = (1u << 13), + # eTypeClassStruct = (1u << 14), + # eTypeClassTypedef = (1u << 15), + # eTypeClassUnion = (1u << 16), + # eTypeClassVector = (1u << 17), + # // Define the last type class as the MSBit of a 32 bit value + # eTypeClassOther = (1u << 31), + # // Define a mask that can be used for any type when finding types + # eTypeClassAny = (0xffffffffu) + + #DumperBase.warn('CURRENT: %s' % self.typeData.keys()) + #DumperBase.warn('FROM NATIVE TYPE: %s' % nativeType.GetName()) + if code == lldb.eTypeClassInvalid: + return None + + if code == lldb.eTypeClassBuiltin: + nativeType = nativeType.GetUnqualifiedType() + + if code == lldb.eTypeClassPointer: + #DumperBase.warn('PTR') + nativeTargetType = nativeType.GetPointeeType() + if not nativeTargetType.IsPointerType(): + nativeTargetType = nativeTargetType.GetUnqualifiedType() + #DumperBase.warn('PTR: %s' % nativeTargetType.name) + return self.createPointerType(self.fromNativeType(nativeTargetType)) + + if code == lldb.eTypeClassReference: + #DumperBase.warn('REF') + nativeTargetType = nativeType.GetDereferencedType() + if not nativeTargetType.IsPointerType(): + nativeTargetType = nativeTargetType.GetUnqualifiedType() + #DumperBase.warn('REF: %s' % nativeTargetType.name) + return self.createReferenceType(self.fromNativeType(nativeTargetType)) + + if code == lldb.eTypeClassTypedef: + #DumperBase.warn('TYPEDEF') + nativeTargetType = nativeType.GetUnqualifiedType() + if hasattr(nativeTargetType, 'GetCanonicalType'): + nativeTargetType = nativeTargetType.GetCanonicalType() + targetType = self.fromNativeType(nativeTargetType) + return self.createTypedefedType(targetType, nativeType.GetName(), + self.nativeTypeId(nativeType)) + + nativeType = nativeType.GetUnqualifiedType() + typeName = self.typeName(nativeType) + + if code in (lldb.eTypeClassArray, lldb.eTypeClassVector): + #DumperBase.warn('ARRAY: %s' % nativeType.GetName()) + if hasattr(nativeType, 'GetArrayElementType'): # New in 3.8(?) / 350.x + nativeTargetType = nativeType.GetArrayElementType() + if not nativeTargetType.IsValid(): + if hasattr(nativeType, 'GetVectorElementType'): # New in 3.8(?) / 350.x + #DumperBase.warn('BAD: %s ' % nativeTargetType.get_fields_array()) + nativeTargetType = nativeType.GetVectorElementType() + count = nativeType.GetByteSize() // nativeTargetType.GetByteSize() + targetTypeName = nativeTargetType.GetName() + if targetTypeName.startswith('(anon'): + typeName = nativeType.GetName() + pos1 = typeName.rfind('[') + targetTypeName = typeName[0:pos1].strip() + #DumperBase.warn("TARGET TYPENAME: %s" % targetTypeName) + targetType = self.fromNativeType(nativeTargetType) + targetType.tdata = targetType.tdata.copy() + targetType.tdata.name = targetTypeName + return self.createArrayType(targetType, count) + if hasattr(nativeType, 'GetVectorElementType'): # New in 3.8(?) / 350.x + nativeTargetType = nativeType.GetVectorElementType() + count = nativeType.GetByteSize() // nativeTargetType.GetByteSize() + targetType = self.fromNativeType(nativeTargetType) + return self.createArrayType(targetType, count) + return self.createType(nativeType.GetName()) + + typeId = self.nativeTypeId(nativeType) + res = self.typeData.get(typeId, None) + if res is None: + # # This strips typedefs for pointers. We don't want that. + # typeobj.nativeType = nativeType.GetUnqualifiedType() + tdata = self.TypeData(self, typeId) + tdata.name = typeName + tdata.lbitsize = nativeType.GetByteSize() * 8 + if code == lldb.eTypeClassBuiltin: + if utils.isFloatingPointTypeName(typeName): + tdata.code = TypeCode.Float + elif utils.isIntegralTypeName(typeName): + tdata.code = TypeCode.Integral + elif typeName in ('__int128', 'unsigned __int128'): + tdata.code = TypeCode.Integral + elif typeName == 'void': + tdata.code = TypeCode.Void + elif typeName == 'wchar_t': + tdata.code = TypeCode.Integral + elif typeName in ("char16_t", "char32_t", "char8_t"): + tdata.code = TypeCode.Integral + else: + self.warn('UNKNOWN TYPE KEY: %s: %s' % (typeName, code)) + elif code == lldb.eTypeClassEnumeration: + tdata.code = TypeCode.Enum + tdata.enumDisplay = lambda intval, addr, form: \ + self.nativeTypeEnumDisplay(nativeType, intval, form) + elif code in (lldb.eTypeClassComplexInteger, lldb.eTypeClassComplexFloat): + tdata.code = TypeCode.Complex + elif code in (lldb.eTypeClassClass, lldb.eTypeClassStruct, lldb.eTypeClassUnion): + tdata.code = TypeCode.Struct + tdata.lalignment = lambda: \ + self.nativeStructAlignment(nativeType) + tdata.lfields = lambda value: \ + self.listMembers(value, nativeType) + tdata.templateArguments = lambda: \ + self.listTemplateParametersHelper(nativeType) + elif code == lldb.eTypeClassFunction: + tdata.code = TypeCode.Function + elif code == lldb.eTypeClassMemberPointer: + tdata.code = TypeCode.MemberPointer + # warn('CREATE TYPE: %s' % typeId) + #else: + # warn('REUSE TYPE: %s' % typeId) + return self.Type(self, typeId) + + def listTemplateParametersHelper(self, nativeType): + stringArgs = self.listTemplateParameters(nativeType.GetName()) + n = nativeType.GetNumberOfTemplateArguments() + if n != len(stringArgs): + # Something wrong in the debug info. + # Should work in theory, doesn't work in practice. + # Items like std::allocator report 0 + # for nativeType.GetNumberOfTemplateArguments() with LLDB 3.8 + return stringArgs + + targs = [] + for i in range(nativeType.GetNumberOfTemplateArguments()): + kind = nativeType.GetTemplateArgumentKind(i) + # eTemplateArgumentKindNull = 0, + # eTemplateArgumentKindType, + # eTemplateArgumentKindDeclaration, + # eTemplateArgumentKindIntegral, + # eTemplateArgumentKindTemplate, + # eTemplateArgumentKindTemplateExpansion, + # eTemplateArgumentKindExpression, + # eTemplateArgumentKindPack + if kind == lldb.eTemplateArgumentKindType: + innerType = nativeType.GetTemplateArgumentType( + i).GetUnqualifiedType().GetCanonicalType() + targs.append(self.fromNativeType(innerType)) + #elif kind == lldb.eTemplateArgumentKindIntegral: + # innerType = nativeType.GetTemplateArgumentType(i).GetUnqualifiedType().GetCanonicalType() + # #DumperBase.warn('INNER TYP: %s' % innerType) + # basicType = innerType.GetBasicType() + # #DumperBase.warn('IBASIC TYP: %s' % basicType) + # inner = self.extractTemplateArgument(nativeType.GetName(), i) + # exp = '(%s)%s' % (innerType.GetName(), inner) + # #DumperBase.warn('EXP : %s' % exp) + # val = self.nativeParseAndEvaluate('(%s)%s' % (innerType.GetName(), inner)) + # # Clang writes 'int' and '0xfffffff' into the debug info + # # LLDB manages to read a value of 0xfffffff... + # #if basicType == lldb.eBasicTypeInt: + # value = val.GetValueAsUnsigned() + # if value >= 0x8000000: + # value -= 0x100000000 + # #DumperBase.warn('KIND: %s' % kind) + # targs.append(value) + else: + #DumperBase.warn('UNHANDLED TEMPLATE TYPE : %s' % kind) + targs.append(stringArgs[i]) # Best we can do. + #DumperBase.warn('TARGS: %s %s' % (nativeType.GetName(), [str(x) for x in targs])) + return targs + + def typeName(self, nativeType): + # Don't use GetDisplayTypeName since LLDB removed the inline namespace __1 + # https://reviews.llvm.org/D74478 + return nativeType.GetName() + + def nativeTypeId(self, nativeType): + if nativeType and (nativeType.GetTypeClass() == lldb.eTypeClassTypedef): + nativeTargetType = nativeType.GetUnqualifiedType() + if hasattr(nativeTargetType, 'GetCanonicalType'): + nativeTargetType = nativeTargetType.GetCanonicalType() + return '%s{%s}' % (nativeType.name, nativeTargetType.name) + name = self.typeName(nativeType) + if name is None or len(name) == 0: + c = '0' + elif name == '(anonymous struct)' and nativeType.GetTypeClass() == lldb.eTypeClassStruct: + c = 's' + elif name == '(anonymous struct)' and nativeType.GetTypeClass() == lldb.eTypeClassUnion: + c = 'u' + else: + return name + fields = nativeType.get_fields_array() + typeId = c + ''.join(['{%s:%s}' % (f.name, self.nativeTypeId(f.GetType())) for f in fields]) + #DumperBase.warn('NATIVE TYPE ID FOR %s IS %s' % (name, typeId)) + return typeId + + def nativeTypeEnumDisplay(self, nativeType, intval, form): + if hasattr(nativeType, 'get_enum_members_array'): + enumerators = [] + flags = [] + found = False + for enumMember in nativeType.get_enum_members_array(): + # Even when asking for signed we get unsigned with LLDB 3.8. + value = enumMember.GetValueAsSigned() + name = nativeType.GetName().split('::') + name[-1] = enumMember.GetName() + if value == intval: + return '::'.join(name) + ' (' + (form % intval) + ')' + enumerators.append(('::'.join(name), value)) + + given = intval + for (name, value) in enumerators: + if value & given != 0: + flags.append(name) + given = given & ~value + found = True + + if not found or given != 0: + flags.append('unknown: %d' % given) + + return '(' + ' | '.join(flags) + ') (' + (form % intval) + ')' + return form % intval + + def nativeDynamicTypeName(self, address, baseType): + return None # FIXME: Seems sufficient, no idea why. + addr = self.target.ResolveLoadAddress(address) + ctx = self.target.ResolveSymbolContextForAddress(addr, 0) + sym = ctx.GetSymbol() + return sym.GetName() + + def stateName(self, s): + try: + # See db.StateType + return ( + 'invalid', + 'unloaded', # Process is object is valid, but not currently loaded + 'connected', # Process is connected to remote debug services, + # but not launched or attached to anything yet + 'attaching', # Process is currently trying to attach + 'launching', # Process is in the process of launching + 'stopped', # Process or thread is stopped and can be examined. + 'running', # Process or thread is running and can't be examined. + 'stepping', # Process or thread is in the process of stepping + # and can not be examined. + 'crashed', # Process or thread has crashed and can be examined. + 'detached', # Process has been detached and can't be examined. + 'exited', # Process has exited and can't be examined. + 'suspended' # Process or thread is in a suspended state as far + )[s] + except: + return 'unknown(%s)' % s + + def stopReason(self, s): + try: + return ( + 'invalid', + 'none', + 'trace', + 'breakpoint', + 'watchpoint', + 'signal', + 'exception', + 'exec', + 'plancomplete', + 'threadexiting', + 'instrumentation', + )[s] + except: + return 'unknown(%s)' % s + + def enumExpression(self, enumType, enumValue): + ns = self.qtNamespace() + return ns + 'Qt::' + enumType + '(' \ + + ns + 'Qt::' + enumType + '::' + enumValue + ')' + + def callHelper(self, rettype, value, func, args): + # args is a tuple. + arg = ','.join(args) + #DumperBase.warn('PRECALL: %s -> %s(%s)' % (value.address(), func, arg)) + typename = value.type.name + exp = '((%s*)0x%x)->%s(%s)' % (typename, value.address(), func, arg) + #DumperBase.warn('CALL: %s' % exp) + result = self.currentContextValue.CreateValueFromExpression('', exp) + #DumperBase.warn(' -> %s' % result) + return self.fromNativeValue(result) + + def pokeValue(self, typeName, *args): + thread = self.currentThread() + frame = thread.GetFrameAtIndex(0) + inner = ','.join(args) + value = frame.EvaluateExpression(typeName + '{' + inner + '}') + #DumperBase.warn(' TYPE: %s' % value.type) + #DumperBase.warn(' ADDR: 0x%x' % value.address) + #DumperBase.warn(' VALUE: %s' % value) + return value + + def nativeParseAndEvaluate(self, exp): + thread = self.currentThread() + frame = thread.GetFrameAtIndex(0) + val = frame.EvaluateExpression(exp) + #options = lldb.SBExpressionOptions() + #val = self.target.EvaluateExpression(exp, options) + err = val.GetError() + if err.Fail(): + #DumperBase.warn('FAILING TO EVAL: %s' % exp) + return None + #DumperBase.warn('NO ERROR.') + #DumperBase.warn('EVAL: %s -> %s' % (exp, val.IsValid())) + return val + + def parseAndEvaluate(self, exp): + val = self.nativeParseAndEvaluate(exp) + return None if val is None else self.fromNativeValue(val) + + def isWindowsTarget(self): + return 'windows' in self.target.triple + + def isQnxTarget(self): + return False + + def isArmArchitecture(self): + return 'arm' in self.target.triple + + def isMsvcTarget(self): + return 'msvc' in self.target.triple + + def prettySymbolByAddress(self, address): + try: + result = lldb.SBCommandReturnObject() + # Cast the address to a function pointer to get the name and location of the function. + expression = 'po (void (*)()){}' + self.debugger.GetCommandInterpreter().HandleCommand(expression.format(address), result) + output = '' + if result.Succeeded(): + output = result.GetOutput().strip() + if output: + return output + except: + pass + return '0x%x' % address + + def fetchInternalFunctions(self): + funcs = self.target.FindFunctions('QObject::customEvent') + if len(funcs): + symbol = funcs[0].GetSymbol() + self.qtCustomEventFunc = symbol.GetStartAddress().GetLoadAddress(self.target) + + funcs = self.target.FindFunctions('QObject::property') + if len(funcs): + symbol = funcs[0].GetSymbol() + self.qtPropertyFunc = symbol.GetStartAddress().GetLoadAddress(self.target) + + def fetchQtVersionAndNamespace(self): + for func in self.target.FindFunctions('qVersion'): + name = func.GetSymbol().GetName() + if name.endswith('()'): + name = name[:-2] + if name.count(':') > 2: + continue + + qtNamespace = name[:name.find('qVersion')] + self.qtNamespace = lambda: qtNamespace + + options = lldb.SBExpressionOptions() + res = self.target.EvaluateExpression(name + '()', options) + + if not res.IsValid() or not res.GetType().IsPointerType(): + exp = '((const char*())%s)()' % name + res = self.target.EvaluateExpression(exp, options) + + if not res.IsValid() or not res.GetType().IsPointerType(): + exp = '((const char*())_Z8qVersionv)()' + res = self.target.EvaluateExpression(exp, options) + + if not res.IsValid() or not res.GetType().IsPointerType(): + continue + + version = str(res) + if version.count('.') != 2: + continue + + version.replace("'", '"') # Both seem possible + version = version[version.find('"') + 1:version.rfind('"')] + + (major, minor, patch) = version.split('.') + qtVersion = 0x10000 * int(major) + 0x100 * int(minor) + int(patch) + self.qtVersion = lambda: qtVersion + + return (qtNamespace, qtVersion) + + try: + versionValue = self.target.EvaluateExpression('qtHookData[2]') + if versionValue.IsValid(): + return ('', versionValue.unsigned) + except: + pass + + return ('', self.fallbackQtVersion) + + def qtVersionAndNamespace(self): + qtVersionAndNamespace = None + try: + qtVersionAndNamespace = self.fetchQtVersionAndNamespace() + self.report("Detected Qt Version: 0x%0x (namespace='%s')" % + (qtVersionAndNamespace[1], qtVersionAndNamespace[0] or "no namespace")) + except Exception as e: + DumperBase.warn('[lldb] Error detecting Qt version: %s' % e) + + try: + self.fetchInternalFunctions() + self.report('Found function QObject::property: 0x%0x' % self.qtPropertyFunc) + self.report('Found function QObject::customEvent: 0x%0x' % self.qtCustomEventFunc) + except Exception as e: + DumperBase.warn('[lldb] Error fetching internal Qt functions: %s' % e) + + # Cache version information by overriding this function. + self.qtVersionAndNamespace = lambda: qtVersionAndNamespace + return qtVersionAndNamespace + + def qtNamespace(self): + return self.qtVersionAndNamespace()[0] + + def qtVersion(self): + return self.qtVersionAndNamespace()[1] + + def handleCommand(self, command): + result = lldb.SBCommandReturnObject() + self.debugger.GetCommandInterpreter().HandleCommand(command, result) + success = result.Succeeded() + if success: + self.report('output="%s"' % toCString(result.GetOutput())) + else: + self.report('error="%s"' % toCString(result.GetError())) + + def canonicalTypeName(self, name): + return re.sub('\\bconst\\b', '', name).replace(' ', '') + + def removeTypePrefix(self, name): + return re.sub('^(struct|class|union|enum|typedef) ', '', name) + + def lookupNativeType(self, name): + #DumperBase.warn('LOOKUP TYPE NAME: %s' % name) + + typeobj = self.typeCache.get(name) + if typeobj is not None: + #DumperBase.warn('CACHED: %s' % name) + return typeobj + typeobj = self.target.FindFirstType(name) + if typeobj.IsValid(): + #DumperBase.warn('VALID FIRST : %s' % typeobj) + self.typeCache[name] = typeobj + return typeobj + + # FindFirstType has a bug (in lldb) that if there are two types with the same base name + # but different scope name (e.g. inside different classes) and the searched for type name + # would be returned as the second result in a call to FindTypes, FindFirstType would return + # an empty result. + # Therefore an additional call to FindTypes is done as a fallback. + # Note that specifying a prefix like enum or typedef or class will make the call fail to + # find the type, thus the prefix is stripped. + nonPrefixedName = self.canonicalTypeName(self.removeTypePrefix(name)) + if re.match(r'^.+\(.*\)', nonPrefixedName) is not None: + return lldb.SBType() + + typeobjlist = self.target.FindTypes(nonPrefixedName) + if typeobjlist.IsValid(): + for typeobj in typeobjlist: + n = self.canonicalTypeName(self.removeTypePrefix(typeobj.GetName())) + if n == nonPrefixedName: + #DumperBase.warn('FOUND TYPE USING FindTypes : %s' % typeobj) + self.typeCache[name] = typeobj + return typeobj + if name.endswith('*'): + #DumperBase.warn('RECURSE PTR') + typeobj = self.lookupNativeType(name[:-1].strip()) + if typeobj is not None: + #DumperBase.warn('RECURSE RESULT X: %s' % typeobj) + self.fromNativeType(typeobj.GetPointerType()) + #DumperBase.warn('RECURSE RESULT: %s' % typeobj.GetPointerType()) + return typeobj.GetPointerType() + + #typeobj = self.target.FindFirstType(name[:-1].strip()) + #if typeobj.IsValid(): + # self.typeCache[name] = typeobj.GetPointerType() + # return typeobj.GetPointerType() + + if name.endswith(' const'): + #DumperBase.warn('LOOKUP END CONST') + typeobj = self.lookupNativeType(name[:-6]) + if typeobj is not None: + return typeobj + + if name.startswith('const '): + #DumperBase.warn('LOOKUP START CONST') + typeobj = self.lookupNativeType(name[6:]) + if typeobj is not None: + return typeobj + + # For QMetaType based typenames we have to re-format the type name. + # Converts "T>"" to "T >" since FindFirstType + # expects it that way. + name = name.replace(',', ', ').replace('>>', '> >') + typeobj = self.target.FindFirstType(name) + if typeobj.IsValid(): + self.typeCache[name] = typeobj + return typeobj + + return lldb.SBType() + + def setupInferior(self, args): + """ Set up SBTarget instance """ + + error = lldb.SBError() + + self.executable_ = args['executable'] + self.startMode_ = args.get('startmode', 1) + self.breakOnMain_ = args.get('breakonmain', 0) + self.useTerminal_ = args.get('useterminal', 0) + self.firstStop_ = True + pargs = self.hexdecode(args.get('processargs', '')) + self.processArgs_ = pargs.split('\0') if len(pargs) else [] + self.environment_ = args.get('environment', []) + self.environment_ = list(map(lambda x: self.hexdecode(x), self.environment_)) + self.attachPid_ = args.get('attachpid', 0) + self.sysRoot_ = args.get('sysroot', '') + self.remoteChannel_ = args.get('remotechannel', '') + self.platform_ = args.get('platform', '') + self.nativeMixed = int(args.get('nativemixed', 0)) + self.symbolFile_ = args['symbolfile']; + self.workingDirectory_ = args.get('workingdirectory', '') + if self.workingDirectory_ == '': + try: + self.workingDirectory_ = os.getcwd() + except: # Could have been deleted in the mean time. + pass + + if self.platform_: + self.debugger.SetCurrentPlatform(self.platform_) + # sysroot has to be set *after* the platform + if self.sysRoot_: + self.debugger.SetCurrentPlatformSDKRoot(self.sysRoot_) + + # There seems to be some kind of unexpected behavior, or bug in LLDB + # such that target.Attach(attachInfo, error) below does not create + # a valid process if this symbolFile here is valid. + if self.startMode_ == DebuggerStartMode.AttachExternal: + self.symbolFile_ = '' + + self.target = self.debugger.CreateTarget( + self.symbolFile_, None, self.platform_, True, error) + + if not error.Success(): + self.report(self.describeError(error)) + self.reportState('enginerunfailed') + return + + broadcaster = self.target.GetBroadcaster() + listener = self.debugger.GetListener() + broadcaster.AddListener(listener, lldb.SBProcess.eBroadcastBitStateChanged) + listener.StartListeningForEvents(broadcaster, lldb.SBProcess.eBroadcastBitStateChanged) + broadcaster.AddListener(listener, lldb.SBTarget.eBroadcastBitBreakpointChanged) + listener.StartListeningForEvents( + broadcaster, lldb.SBTarget.eBroadcastBitBreakpointChanged) + + if self.nativeMixed: + self.interpreterEventBreakpoint = \ + self.target.BreakpointCreateByName('qt_qmlDebugMessageAvailable') + + state = 1 if self.target.IsValid() else 0 + self.reportResult('success="%s",msg="%s",exe="%s"' + % (state, toCString(error), toCString(self.executable_)), args) + + def runEngine(self, args): + """ Set up SBProcess instance """ + + error = lldb.SBError() + + if self.startMode_ == DebuggerStartMode.AttachExternal: + attach_info = lldb.SBAttachInfo(self.attachPid_) + if self.breakOnMain_: + self.createBreakpointAtMain() + self.process = self.target.Attach(attach_info, error) + if not error.Success(): + self.reportState('enginerunfailed') + else: + self.report('pid="%s"' % self.process.GetProcessID()) + self.reportState('enginerunandinferiorstopok') + + elif (self.startMode_ == DebuggerStartMode.AttachToRemoteServer + and self.platform_ == 'remote-android'): + + connect_options = lldb.SBPlatformConnectOptions(self.remoteChannel_) + res = self.target.GetPlatform().ConnectRemote(connect_options) + + DumperBase.warn("CONNECT: %s %s platform: %s %s" % (res, + self.remoteChannel_, + self.target.GetPlatform().GetName(), + self.target.GetPlatform().IsConnected())) + if not res.Success(): + self.report(self.describeError(res)) + self.reportState('enginerunfailed') + return + + attach_info = lldb.SBAttachInfo(self.attachPid_) + self.process = self.target.Attach(attach_info, error) + if not error.Success(): + self.report(self.describeError(error)) + self.reportState('enginerunfailed') + else: + self.report('pid="%s"' % self.process.GetProcessID()) + self.reportState('enginerunandinferiorstopok') + + elif (self.startMode_ == DebuggerStartMode.AttachToRemoteServer + or self.startMode_ == DebuggerStartMode.AttachToRemoteProcess): + if self.platform_ == 'remote-ios': + self.process = self.target.ConnectRemote( + self.debugger.GetListener(), + self.remoteChannel_, None, error) + else: + if self.platform_ == "remote-macosx": + self.report("Connecting to remote target: connect://%s" % self.remoteChannel_) + self.process = self.target.ConnectRemote( + self.debugger.GetListener(), + "connect://" + self.remoteChannel_, None, error) + + if not error.Success(): + self.report("Failed to connect to remote target: %s" % error.GetCString()) + self.reportState('enginerunfailed') + return + + if self.breakOnMain_: + self.createBreakpointAtMain() + + DumperBase.warn("PROCESS: %s (%s)" % (self.process, error.Success() and "Success" or error.GetCString())) + elif self.platform_ == "remote-linux": + self.report("Connecting to remote target: connect://%s" % self.remoteChannel_) + + platform = self.target.GetPlatform() + url = "connect://" + self.remoteChannel_ + conOptions = lldb.SBPlatformConnectOptions(url) + error = platform.ConnectRemote(conOptions) + + if not error.Success(): + self.report("Failed to connect to remote target (%s): %s" % (url, error.GetCString())) + self.reportState('enginerunfailed') + return + + f = lldb.SBFileSpec() + f.SetFilename(self.executable_) + launchInfo = lldb.SBLaunchInfo(self.processArgs_) + launchInfo.SetWorkingDirectory(self.workingDirectory_) + launchInfo.SetWorkingDirectory('/tmp') + launchInfo.SetEnvironmentEntries(self.environment_, False) + launchInfo.SetExecutableFile(f, True) + self.process = self.target.Launch(launchInfo, error) + + if not error.Success(): + self.report("Failed to launch remote target: %s" % (error.GetCString())) + self.reportState('enginerunfailed') + return + else: + self.report("Process has launched.") + + if self.breakOnMain_: + self.createBreakpointAtMain() + + else: + self.report("Unsupported platform: %s" % self.platform_) + self.reportState('enginerunfailed') + return + + if not error.Success(): + self.report(self.describeError(error)) + self.reportState('enginerunfailed') + return + + # Even if it stops it seems that LLDB assumes it is running + # and later detects that it did stop after all, so it is be + # better to mirror that and wait for the spontaneous stop. + self.reportState('enginerunandinferiorrunok') + + elif self.startMode_ == DebuggerStartMode.AttachCore: + coreFile = args.get('coreFile', '') + self.process = self.target.LoadCore(coreFile) + if self.process.IsValid(): + self.reportState('enginerunokandinferiorunrunnable') + else: + self.reportState('enginerunfailed') + else: + launchInfo = lldb.SBLaunchInfo(self.processArgs_) + launchInfo.SetWorkingDirectory(self.workingDirectory_) + launchInfo.SetEnvironmentEntries(self.environment_, False) + if self.breakOnMain_: + self.createBreakpointAtMain() + self.process = self.target.Launch(launchInfo, error) + if not error.Success(): + self.report(self.describeError(error)) + self.reportState('enginerunfailed') + return + self.report('pid="%s"' % self.process.GetProcessID()) + self.reportState('enginerunandinferiorrunok') + + s = threading.Thread(target=self.loop, args=[]) + s.start() + + def loop(self): + event = lldb.SBEvent() + #broadcaster = self.target.GetBroadcaster() + listener = self.debugger.GetListener() + + while True: + while listener.GetNextEvent(event): + self.handleEvent(event) + time.sleep(0.25) + + #if listener.WaitForEventForBroadcaster(0, broadcaster, event): + # self.handleEvent(event) + + def describeError(self, error): + desc = lldb.SBStream() + error.GetDescription(desc) + result = 'success="%d",' % int(error.Success()) + result += 'error={type="%s"' % error.GetType() + if error.GetType(): + result += ',status="%s"' % error.GetCString() + result += ',code="%s"' % error.GetError() + result += ',desc="%s"}' % toCString(desc.GetData()) + return result + + def describeStatus(self, status): + return 'status="%s",' % toCString(status) + + def describeLocation(self, frame): + if int(frame.pc) == 0xffffffffffffffff: + return '' + fileName = fileNameAsString(frame.line_entry.file) + function = frame.GetFunctionName() + line = frame.line_entry.line + return 'location={file="%s",line="%s",address="%s",function="%s"}' \ + % (fileName, line, frame.pc, function) + + def currentThread(self): + return None if self.process is None else self.process.GetSelectedThread() + + def currentFrame(self): + thread = self.currentThread() + return None if thread is None else thread.GetSelectedFrame() + + def firstStoppedThread(self): + for i in range(0, self.process.GetNumThreads()): + thread = self.process.GetThreadAtIndex(i) + reason = thread.GetStopReason() + if (reason == lldb.eStopReasonBreakpoint or + reason == lldb.eStopReasonException or + reason == lldb.eStopReasonPlanComplete or + reason == lldb.eStopReasonSignal or + reason == lldb.eStopReasonWatchpoint): + return thread + return None + + def fetchThreads(self, args): + result = 'threads=[' + for i in range(0, self.process.GetNumThreads()): + thread = self.process.GetThreadAtIndex(i) + if thread.is_stopped: + state = 'stopped' + elif thread.is_suspended: + state = 'suspended' + else: + state = 'unknown' + reason = thread.GetStopReason() + result += '{id="%d"' % thread.GetThreadID() + result += ',index="%s"' % i + result += ',details="%s"' % toCString(thread.GetQueueName()) + result += ',stop-reason="%s"' % self.stopReason(thread.GetStopReason()) + result += ',state="%s"' % state + result += ',name="%s"' % toCString(thread.GetName()) + result += ',frame={' + frame = thread.GetFrameAtIndex(0) + result += 'pc="0x%x"' % frame.pc + result += ',addr="0x%x"' % frame.pc + result += ',fp="0x%x"' % frame.fp + result += ',func="%s"' % frame.GetFunctionName() + result += ',line="%s"' % frame.line_entry.line + result += ',fullname="%s"' % fileNameAsString(frame.line_entry.file) + result += ',file="%s"' % fileNameAsString(frame.line_entry.file) + result += '}},' + + result += '],current-thread-id="%s"' % self.currentThread().id + self.reportResult(result, args) + + def firstUsableFrame(self, thread): + for i in range(10): + frame = thread.GetFrameAtIndex(i) + lineEntry = frame.GetLineEntry() + line = lineEntry.GetLine() + if line != 0: + return i + return None + + def fetchStack(self, args): + if not self.process: + self.reportResult('msg="No process"', args) + return + thread = self.currentThread() + if not thread: + self.reportResult('msg="No thread"', args) + return + + isNativeMixed = int(args.get('nativemixed', 0)) + extraQml = int(args.get('extraqml', '0')) + + limit = args.get('stacklimit', -1) + (n, isLimited) = (limit, True) if limit > 0 else (thread.GetNumFrames(), False) + self.currentCallContext = None + result = 'stack={current-thread="%d"' % thread.GetThreadID() + result += ',frames=[' + + ii = 0 + if extraQml: + ns = self.qtNamespace() + needle = self.qtNamespace() + 'QV4::ExecutionEngine' + pats = [ + '{0}qt_v4StackTraceForEngine((void*)0x{1:x})', + '{0}qt_v4StackTrace((({0}QV4::ExecutionEngine *)0x{1:x})->currentContext())', + '{0}qt_v4StackTrace((({0}QV4::ExecutionEngine *)0x{1:x})->currentContext)', + ] + done = False + while ii < n and not done: + res = None + frame = thread.GetFrameAtIndex(ii) + if not frame.IsValid(): + break + for variable in frame.GetVariables(True, True, False, True): + if not variable.GetType().IsPointerType(): + continue + derefvar = variable.Dereference() + if derefvar.GetType().GetName() != needle: + continue + addr = derefvar.GetLoadAddress() + for pat in pats: + exp = pat.format(ns, addr) + val = frame.EvaluateExpression(exp) + err = val.GetError() + res = str(val) + if err.Fail(): + continue + pos = res.find('"stack=[') + if pos == -1: + continue + res = res[pos + 8:-2] + res = res.replace('\\\"', '\"') + res = res.replace('func=', 'function=') + result += res + done = True + break + ii += 1 + # if we have not found a qml stack do not omit original stack + if not done: + DumperBase.warn("Failed to fetch qml stack - you need Qt debug information") + ii = 0 + + for i in range(n - ii): + frame = thread.GetFrameAtIndex(i) + if not frame.IsValid(): + isLimited = False + break + + lineEntry = frame.GetLineEntry() + lineNumber = lineEntry.GetLine() + + pc = frame.GetPC() + level = frame.idx + addr = frame.GetPCAddress().GetLoadAddress(self.target) + + functionName = frame.GetFunctionName() + module = frame.GetModule() + + if isNativeMixed and functionName == '::qt_qmlDebugMessageAvailable()': + interpreterStack = self.extractInterpreterStack() + for interpreterFrame in interpreterStack.get('frames', []): + function = interpreterFrame.get('function', '') + fileName = toCString(interpreterFrame.get('file', '')) + language = interpreterFrame.get('language', '') + lineNumber = interpreterFrame.get('line', 0) + context = interpreterFrame.get('context', 0) + result += ('frame={function="%s",file="%s",' + 'line="%s",language="%s",context="%s"}' + % (function, fileName, lineNumber, language, context)) + + fileName = fileNameAsString(lineEntry.file) + result += '{pc="0x%x"' % pc + result += ',level="%d"' % level + result += ',address="0x%x"' % addr + result += ',function="%s"' % functionName + result += ',line="%d"' % lineNumber + result += ',module="%s"' % toCString(module) + result += ',file="%s"},' % fileName + result += ']' + result += ',hasmore="%d"' % isLimited + result += ',limit="%d"' % limit + result += '}' + self.reportResult(result, args) + + def reportResult(self, result, args): + self.report('result={token="%s",%s}' % (args.get("token", 0), result)) + + def reportToken(self, args): + if "token" in args: + # Unusual syntax intended, to support the double-click in left + # logview pane feature. + self.report('token(\"%s\")' % args["token"]) + + def reportBreakpointUpdate(self, bp): + self.report('breakpointmodified={%s}' % self.describeBreakpoint(bp)) + + def readRawMemory(self, address, size): + if size == 0: + return bytes() + error = lldb.SBError() + #DumperBase.warn("READ: %s %s" % (address, size)) + res = self.process.ReadMemory(address, size, error) + if res is None or len(res) != size: + # Using code in e.g. readToFirstZero relies on exceptions. + raise RuntimeError("Unreadable %s bytes at 0x%x" % (size, address)) + return res + + def findStaticMetaObject(self, type): + symbolName = self.mangleName(type.name + '::staticMetaObject') + symbol = self.target.FindFirstGlobalVariable(symbolName) + return symbol.AddressOf().GetValueAsUnsigned() if symbol.IsValid() else 0 + + def findSymbol(self, symbolName): + return self.target.FindFirstGlobalVariable(symbolName) + + def warn(self, msg): + self.put('{name="%s",value="",type="",numchild="0"},' % toCString(msg)) + + def fetchVariables(self, args): + (ok, res) = self.tryFetchInterpreterVariables(args) + if ok: + self.reportResult(res, args) + return + + self.setVariableFetchingOptions(args) + + # Reset certain caches whenever a step over / into / continue + # happens. + # FIXME: Caches are currently also cleared if currently + # selected frame is changed, that shouldn't happen. + if not self.partialVariable: + self.resetPerStepCaches() + + frame = self.currentFrame() + if frame is None: + self.reportResult('error="No frame"', args) + return + + self.output = [] + isPartial = len(self.partialVariable) > 0 + + self.currentIName = 'local' + self.put('data=[') + + with SubItem(self, '[statics]'): + self.put('iname="%s",' % self.currentIName) + self.putEmptyValue() + self.putExpandable() + if self.isExpanded(): + with Children(self): + statics = frame.GetVariables(False, False, True, False) + if len(statics): + for i in range(len(statics)): + staticVar = statics[i] + staticVar.SetPreferSyntheticValue(False) + typename = staticVar.GetType().GetName() + name = staticVar.GetName() + with SubItem(self, i): + self.put('name="%s",' % name) + self.put('iname="%s",' % self.currentIName) + self.putItem(self.fromNativeValue(staticVar)) + else: + with SubItem(self, "None"): + self.putEmptyValue() + + # FIXME: Implement shortcut for partial updates. + #if isPartial: + # values = [frame.FindVariable(partialVariable)] + #else: + if True: + values = list(frame.GetVariables(True, True, False, True)) + values.reverse() # To get shadowed vars numbered backwards. + + variables = [] + for val in values: + val.SetPreferSyntheticValue(False) + if not val.IsValid(): + continue + self.currentContextValue = val + name = val.GetName() + if name is None: + # This can happen for unnamed function parameters with + # default values: void foo(int = 0) + continue + value = self.fromNativeFrameValue(val) + variables.append(value) + + self.handleLocals(variables) + self.handleWatches(args) + + self.put('],partial="%d"' % isPartial) + self.reportResult(self.takeOutput(), args) + + + def fetchRegisters(self, args=None): + if not self.process: + self.reportResult('process="none",registers=[]', args) + return + + frame = self.currentFrame() + if not frame or not frame.IsValid(): + self.reportResult('frame="none",registers=[]', args) + return + + result = 'registers=[' + for group in frame.GetRegisters(): + for reg in group: + data = reg.GetData() + if data.GetByteOrder() == lldb.eByteOrderLittle: + value = ''.join(["%02x" % x for x in reversed(data.uint8s)]) + else: + value = ''.join(["%02x" % x for x in data.uint8s]) + result += '{name="%s"' % reg.GetName() + result += ',value="0x%s"' % value + result += ',size="%s"' % reg.GetByteSize() + result += ',type="%s"},' % reg.GetType() + result += ']' + self.reportResult(result, args) + + + def setRegister(self, args): + name = args["name"] + value = args["value"] + result = lldb.SBCommandReturnObject() + interp = self.debugger.GetCommandInterpreter() + interp.HandleCommand("register write %s %s" % (name, value), result) + success = result.Succeeded() + if success: + self.reportResult('output="%s"' % toCString(result.GetOutput()), args) + return + # Try again with register write xmm0 "{0x00 ... 0x02}" syntax: + vec = ' '.join(["0x" + value[i:i + 2] for i in range(2, len(value), 2)]) + success = interp.HandleCommand('register write %s "{%s}"' % (name, vec), result) + if success: + self.reportResult('output="%s"' % toCString(result.GetOutput()), args) + else: + self.reportResult('error="%s"' % toCString(result.GetError()), args) + + def report(self, stuff): + with self.outputLock: + sys.stdout.write("@\n" + stuff + "@\n") + sys.stdout.flush() + + def reportState(self, state): + self.report('state="%s"' % state) + + def interruptInferior(self, args): + if self.process is None: + self.reportResult('status="No process to interrupt",success="0"', args) + else: + self.isInterrupting_ = True + error = self.process.Stop() + self.reportResult(self.describeError(error), args) + + def detachInferior(self, args): + if self.process is None: + self.reportResult('status="No process to detach from."', args) + else: + error = self.process.Detach() + self.reportResult(self.describeError(error), args) + + def continueInferior(self, args): + if self.process is None: + self.reportResult('status="No process to continue."', args) + else: + # Can fail when attaching to GDBserver. + error = self.process.Continue() + self.reportResult(self.describeError(error), args) + + def quitDebugger(self, args): + self.reportState("inferiorshutdownrequested") + self.process.Kill() + self.reportResult('', args) + + def handleBreakpointEvent(self, event): + eventType = lldb.SBBreakpoint.GetBreakpointEventTypeFromEvent(event) + # handle only the resolved locations for now.. + if eventType & lldb.eBreakpointEventTypeLocationsResolved: + bp = lldb.SBBreakpoint.GetBreakpointFromEvent(event) + if bp is not None: + self.reportBreakpointUpdate(bp) + + def handleEvent(self, event): + if lldb.SBBreakpoint.EventIsBreakpointEvent(event): + self.handleBreakpointEvent(event) + return + if not lldb.SBProcess.EventIsProcessEvent(event): + self.warn("UNEXPECTED event (%s)" % event.GetType()) + return + + out = lldb.SBStream() + event.GetDescription(out) + #DumperBase.warn("EVENT: %s" % event) + eventType = event.GetType() + msg = lldb.SBEvent.GetCStringFromEvent(event) + flavor = event.GetDataFlavor() + state = lldb.SBProcess.GetStateFromEvent(event) + bp = lldb.SBBreakpoint.GetBreakpointFromEvent(event) + skipEventReporting = eventType in ( + lldb.SBProcess.eBroadcastBitSTDOUT, lldb.SBProcess.eBroadcastBitSTDERR) + self.report('event={type="%s",data="%s",msg="%s",flavor="%s",state="%s",bp="%s"}' + % (eventType, toCString(out.GetData()), + toCString(msg), flavor, self.stateName(state), bp)) + + if state == lldb.eStateExited: + self.eventState = state + if not self.isShuttingDown_: + self.reportState("inferiorexited") + self.report('exited={status="%d",desc="%s"}' + % (self.process.GetExitStatus(), + toCString(self.process.GetExitDescription()))) + elif state != self.eventState and not skipEventReporting: + self.eventState = state + if state == lldb.eStateStopped: + stoppedThread = self.firstStoppedThread() + if stoppedThread: + #self.report("STOPPED THREAD: %s" % stoppedThread) + frame = stoppedThread.GetFrameAtIndex(0) + #self.report("FRAME: %s" % frame) + function = frame.GetFunction() + functionName = function.GetName() + if functionName == "::qt_qmlDebugConnectorOpen()": + self.report("RESOLVER HIT") + for resolver in self.interpreterBreakpointResolvers: + resolver() + self.report("AUTO-CONTINUE AFTER RESOLVING") + self.reportState("inferiorstopok") + self.process.Continue() + return + if functionName == "::qt_qmlDebugMessageAvailable()": + self.report("ASYNC MESSAGE FROM SERVICE") + res = self.handleInterpreterMessage() + if not res: + self.report("EVENT NEEDS NO STOP") + self.reportState("stopped") + self.process.Continue() + return + if self.isInterrupting_: + self.isInterrupting_ = False + self.reportState("inferiorstopok") + else: + self.reportState("stopped") + if self.firstStop_: + self.firstStop_ = False + if self.useTerminal_ or self.platform_ == "remote-macosx": + # When using a terminal or remote debugging macosx apps, + # the process will be interrupted on startup. + # We therefore need to continue it here. + self.process.Continue() + else: + self.reportState(self.stateName(state)) + + if eventType == lldb.SBProcess.eBroadcastBitStateChanged: # 1 + state = self.process.GetState() + if state == lldb.eStateStopped: + stoppedThread = self.firstStoppedThread() + if stoppedThread: + self.process.SetSelectedThread(stoppedThread) + elif eventType == lldb.SBProcess.eBroadcastBitInterrupt: # 2 + pass + elif eventType == lldb.SBProcess.eBroadcastBitSTDOUT: + self.handleInferiorOutput(self.process.GetSTDOUT, "stdout") + elif eventType == lldb.SBProcess.eBroadcastBitSTDERR: + self.handleInferiorOutput(self.process.GetSTDERR, "stderr") + elif eventType == lldb.SBProcess.eBroadcastBitProfileData: + pass + + def handleInferiorOutput(self, proc, channel): + while True: + msg = proc(1024) + if msg == None or len(msg) == 0: + break + self.report('output={channel="%s",data="%s"}' % (channel, self.hexencode(msg))) + + def describeBreakpoint(self, bp): + isWatch = isinstance(bp, lldb.SBWatchpoint) + if isWatch: + result = 'lldbid="%s"' % (qqWatchpointOffset + bp.GetID()) + else: + result = 'lldbid="%s"' % bp.GetID() + result += ',valid="%d"' % (1 if bp.IsValid() else 0) + result += ',hitcount="%d"' % bp.GetHitCount() + if bp.IsValid(): + if isinstance(bp, lldb.SBBreakpoint): + result += ',threadid="%d"' % bp.GetThreadID() + result += ',oneshot="%d"' % (1 if bp.IsOneShot() else 0) + cond = bp.GetCondition() + result += ',condition="%s"' % self.hexencode("" if cond is None else cond) + result += ',enabled="%d"' % (1 if bp.IsEnabled() else 0) + result += ',valid="%d"' % (1 if bp.IsValid() else 0) + result += ',ignorecount="%d"' % bp.GetIgnoreCount() + if bp.IsValid() and isinstance(bp, lldb.SBBreakpoint): + result += ',locations=[' + lineEntry = None + for i in range(bp.GetNumLocations()): + loc = bp.GetLocationAtIndex(i) + addr = loc.GetAddress() + lineEntry = addr.GetLineEntry() + result += '{locid="%d"' % loc.GetID() + result += ',function="%s"' % addr.GetFunction().GetName() + result += ',enabled="%d"' % (1 if loc.IsEnabled() else 0) + result += ',resolved="%d"' % (1 if loc.IsResolved() else 0) + result += ',valid="%d"' % (1 if loc.IsValid() else 0) + result += ',ignorecount="%d"' % loc.GetIgnoreCount() + result += ',file="%s"' % toCString(lineEntry.GetFileSpec()) + result += ',line="%d"' % lineEntry.GetLine() + result += ',addr="%s"},' % addr.GetFileAddress() + result += ']' + if lineEntry is not None: + result += ',file="%s"' % toCString(lineEntry.GetFileSpec()) + result += ',line="%d"' % lineEntry.GetLine() + return result + + def createBreakpointAtMain(self): + return self.target.BreakpointCreateByName( + 'main', self.target.GetExecutable().GetFilename()) + + def insertBreakpoint(self, args): + bpType = args['type'] + if bpType == BreakpointType.BreakpointByFileAndLine: + fileName = args['file'] + if fileName.endswith('.js') or fileName.endswith('.qml'): + self.insertInterpreterBreakpoint(args) + return + + extra = '' + more = True + if bpType == BreakpointType.BreakpointByFileAndLine: + bp = self.target.BreakpointCreateByLocation( + str(args['file']), int(args['line'])) + elif bpType == BreakpointType.BreakpointByFunction: + bp = self.target.BreakpointCreateByName(args['function']) + elif bpType == BreakpointType.BreakpointByAddress: + bp = self.target.BreakpointCreateByAddress(args['address']) + elif bpType == BreakpointType.BreakpointAtMain: + bp = self.createBreakpointAtMain() + elif bpType == BreakpointType.BreakpointAtThrow: + bp = self.target.BreakpointCreateForException( + lldb.eLanguageTypeC_plus_plus, False, True) + elif bpType == BreakpointType.BreakpointAtCatch: + bp = self.target.BreakpointCreateForException( + lldb.eLanguageTypeC_plus_plus, True, False) + elif bpType == BreakpointType.WatchpointAtAddress: + error = lldb.SBError() + # This might yield bp.IsValid() == False and + # error.desc == 'process is not alive'. + bp = self.target.WatchAddress(args['address'], 4, False, True, error) + extra = self.describeError(error) + elif bpType == BreakpointType.WatchpointAtExpression: + # FIXME: Top level-only for now. + try: + frame = self.currentFrame() + value = frame.FindVariable(args['expression']) + error = lldb.SBError() + bp = self.target.WatchAddress(value.GetLoadAddress(), + value.GetByteSize(), False, True, error) + except: + bp = self.target.BreakpointCreateByName(None) + else: + # This leaves the unhandled breakpoint in a (harmless) + # 'pending' state. + bp = self.target.BreakpointCreateByName(None) + more = False + + if more and bp.IsValid(): + bp.SetIgnoreCount(int(args['ignorecount'])) + bp.SetCondition(self.hexdecode(args['condition'])) + bp.SetEnabled(bool(args['enabled'])) + bp.SetScriptCallbackBody('\n'.join([ + 'def foo(frame = frame, bp_loc = bp_loc, dict = internal_dict):', + ' ' + self.hexdecode(args['command']).replace('\n', '\n '), + 'from cStringIO import StringIO', + 'origout = sys.stdout', + 'sys.stdout = StringIO()', + 'result = foo()', + 'd = lldb.theDumper', + 'output = d.hexencode(sys.stdout.getvalue())', + 'sys.stdout = origout', + 'd.report("output={channel=\"stderr\",data=\" + output + \"}")', + 'sys.stdout.flush()', + 'if result is False:', + ' d.reportState("continueafternextstop")', + 'return True' + ])) + if isinstance(bp, lldb.SBBreakpoint): + bp.SetOneShot(bool(args['oneshot'])) + self.reportResult(self.describeBreakpoint(bp) + extra, args) + + def changeBreakpoint(self, args): + lldbId = int(args['lldbid']) + if lldbId > qqWatchpointOffset: + bp = self.target.FindWatchpointByID(lldbId) + else: + bp = self.target.FindBreakpointByID(lldbId) + if bp.IsValid(): + bp.SetIgnoreCount(int(args['ignorecount'])) + bp.SetCondition(self.hexdecode(args['condition'])) + bp.SetEnabled(bool(args['enabled'])) + if isinstance(bp, lldb.SBBreakpoint): + bp.SetOneShot(bool(args['oneshot'])) + self.reportResult(self.describeBreakpoint(bp), args) + + def enableSubbreakpoint(self, args): + lldbId = int(args['lldbid']) + locId = int(args['locid']) + bp = self.target.FindBreakpointByID(lldbId) + res = False + enabled = False + if bp.IsValid(): + loc = bp.FindLocationByID(locId) + if loc.IsValid(): + loc.SetEnabled(bool(args['enabled'])) + enabled = loc.IsEnabled() + res = True + self.reportResult('success="%d",enabled="%d",locid="%d"' + % (int(res), int(enabled), locId), args) + + def removeBreakpoint(self, args): + lldbId = int(args['lldbid']) + if lldbId > qqWatchpointOffset: + res = self.target.DeleteWatchpoint(lldbId - qqWatchpointOffset) + res = self.target.BreakpointDelete(lldbId) + self.reportResult('success="%d"' % int(res), args) + + def fetchModules(self, args): + result = 'modules=[' + for i in range(self.target.GetNumModules()): + module = self.target.GetModuleAtIndex(i) + result += '{file="%s"' % toCString(module.file.fullpath) + result += ',name="%s"' % toCString(module.file.basename) + result += ',addrsize="%d"' % module.addr_size + result += ',triple="%s"' % module.triple + #result += ',sections={' + #for section in module.sections: + # result += '[name="%s"' % section.name + # result += ',addr="%s"' % section.addr + # result += ',size="%d"],' % section.size + #result += '}' + result += '},' + result += ']' + self.reportResult(result, args) + + def fetchSymbols(self, args): + moduleName = args['module'] + #file = lldb.SBFileSpec(moduleName) + #module = self.target.FindModule(file) + for i in range(self.target.GetNumModules()): + module = self.target.GetModuleAtIndex(i) + if module.file.fullpath == moduleName: + break + result = 'symbols={valid="%s"' % module.IsValid() + result += ',sections="%s"' % module.GetNumSections() + result += ',symbols=[' + for symbol in module.symbols: + startAddress = symbol.GetStartAddress().GetLoadAddress(self.target) + endAddress = symbol.GetEndAddress().GetLoadAddress(self.target) + result += '{type="%s"' % symbol.GetType() + result += ',name="%s"' % symbol.GetName() + result += ',address="0x%x"' % startAddress + result += ',demangled="%s"' % symbol.GetMangledName() + result += ',size="%d"' % (endAddress - startAddress) + result += '},' + result += ']}' + self.reportResult(result, args) + + def executeNext(self, args): + self.currentThread().StepOver() + self.reportResult('', args) + + def executeNextI(self, args): + self.currentThread().StepInstruction(True) + self.reportResult('', args) + + def executeStep(self, args): + self.currentThread().StepInto() + self.reportResult('', args) + + def shutdownInferior(self, args): + self.isShuttingDown_ = True + if self.process is not None: + state = self.process.GetState() + if state == lldb.eStateStopped: + self.process.Kill() + self.reportState('inferiorshutdownfinished') + self.reportResult('', args) + + def quit(self, args): + self.reportState('engineshutdownfinished') + self.process.Kill() + self.reportResult('', args) + + def executeStepI(self, args): + self.currentThread().StepInstruction(False) + self.reportResult('', args) + + def executeStepOut(self, args={}): + self.currentThread().StepOut() + self.reportResult('', args) + + def executeRunToLocation(self, args): + self.reportToken(args) + addr = args.get('address', 0) + if addr: + # Does not seem to hit anything on Linux: + # self.currentThread().RunToAddress(addr) + bp = self.target.BreakpointCreateByAddress(addr) + if bp.GetNumLocations() == 0: + self.target.BreakpointDelete(bp.GetID()) + self.reportResult(self.describeStatus('No target location found.') + + self.describeLocation(frame), args) + return + bp.SetOneShot(True) + self.reportResult('', args) + self.process.Continue() + else: + frame = self.currentFrame() + file = args['file'] + line = int(args['line']) + error = self.currentThread().StepOverUntil(frame, lldb.SBFileSpec(file), line) + self.reportResult(self.describeError(error), args) + self.reportState('running') + self.reportState('stopped') + + def executeJumpToLocation(self, args): + self.reportToken(args) + frame = self.currentFrame() + if not frame: + self.reportResult(self.describeStatus('No frame available.'), args) + return + addr = args.get('address', 0) + if addr: + bp = self.target.BreakpointCreateByAddress(addr) + else: + bp = self.target.BreakpointCreateByLocation( + str(args['file']), int(args['line'])) + if bp.GetNumLocations() == 0: + self.target.BreakpointDelete(bp.GetID()) + status = 'No target location found.' + else: + loc = bp.GetLocationAtIndex(0) + self.target.BreakpointDelete(bp.GetID()) + res = frame.SetPC(loc.GetLoadAddress()) + status = 'Jumped.' if res else 'Cannot jump.' + self.report(self.describeLocation(frame)) + self.reportResult(self.describeStatus(status), args) + + def breakList(self): + result = lldb.SBCommandReturnObject() + self.debugger.GetCommandInterpreter().HandleCommand('break list', result) + self.report('success="%d",output="%s",error="%s"' + % (result.Succeeded(), toCString(result.GetOutput()), + toCString(result.GetError()))) + + def activateFrame(self, args): + self.reportToken(args) + frame = max(0, int(args['index'])) # Can be -1 in all-asm stacks + self.currentThread().SetSelectedFrame(frame) + self.reportResult('', args) + + def selectThread(self, args): + self.reportToken(args) + self.process.SetSelectedThreadByID(int(args['id'])) + self.reportResult('', args) + + def fetchFullBacktrace(self, args): + command = 'thread backtrace all' + result = lldb.SBCommandReturnObject() + self.debugger.GetCommandInterpreter().HandleCommand(command, result) + self.reportResult('fulltrace="%s"' % self.hexencode(result.GetOutput()), args) + + def executeDebuggerCommand(self, args): + self.reportToken(args) + result = lldb.SBCommandReturnObject() + command = args['command'] + self.debugger.GetCommandInterpreter().HandleCommand(command, result) + success = result.Succeeded() + output = toCString(result.GetOutput()) + error = toCString(str(result.GetError())) + self.report('success="%d",output="%s",error="%s"' % (success, output, error)) + + def executeRoundtrip(self, args): + self.reportResult('', args) + + def fetchDisassembler(self, args): + functionName = args.get('function', '') + flavor = args.get('flavor', '') + function = None + if len(functionName): + functions = self.target.FindFunctions(functionName).functions + if len(functions): + function = functions[0] + if function: + base = function.GetStartAddress().GetLoadAddress(self.target) + instructions = function.GetInstructions(self.target) + else: + base = args.get('address', 0) + if int(base) == 0xffffffffffffffff: + self.warn('INVALID DISASSEMBLER BASE') + return + addr = lldb.SBAddress(base, self.target) + instructions = self.target.ReadInstructions(addr, 100) + + currentFile = None + currentLine = None + hunks = dict() + sources = dict() + result = 'lines=[' + for insn in instructions: + comment = insn.GetComment(self.target) + addr = insn.GetAddress() + loadAddr = addr.GetLoadAddress(self.target) + lineEntry = addr.GetLineEntry() + if lineEntry: + lineNumber = lineEntry.GetLine() + fileName = str(lineEntry.GetFileSpec()) + if lineNumber != currentLine or fileName != currentFile: + currentLine = lineNumber + currentFile = fileName + key = '%s:%s' % (fileName, lineNumber) + hunk = hunks.get(key, 0) + 1 + hunks[key] = hunk + source = sources.get(fileName, None) + if source is None: + try: + with open(fileName, 'r') as f: + source = f.read().splitlines() + sources[fileName] = source + except IOError as error: + # With lldb-3.8 files like /data/dev/creator-3.6/tests/ + # auto/debugger/qt_tst_dumpers_StdVector_bfNWZa/main.cpp + # with non-existent directories appear. + self.warn('FILE: %s ERROR: %s' % (fileName, error)) + source = '' + result += '{line="%d"' % lineNumber + result += ',file="%s"' % toCString(fileName) + if 0 < lineNumber and lineNumber <= len(source): + result += ',hexdata="%s"' % self.hexencode(source[lineNumber - 1]) + result += ',hunk="%s"}' % hunk + result += '{address="%s"' % loadAddr + result += ',data="%s %s"' % (insn.GetMnemonic(self.target), + insn.GetOperands(self.target)) + result += ',function="%s"' % functionName + rawData = insn.GetData(self.target).uint8s + result += ',rawdata="%s"' % ' '.join(["%02x" % x for x in rawData]) + if comment: + result += ',comment="%s"' % self.hexencode(comment) + result += ',offset="%s"}' % (loadAddr - base) + self.reportResult(result + ']', args) + + def fetchMemory(self, args): + address = args['address'] + length = args['length'] + error = lldb.SBError() + contents = self.process.ReadMemory(address, length, error) + result = 'address="%s",' % address + result += self.describeError(error) + result += ',contents="%s"' % self.hexencode(contents) + self.reportResult(result, args) + + def findValueByExpression(self, exp): + # FIXME: Top level-only for now. + frame = self.currentFrame() + value = frame.FindVariable(exp) + return value + + def setValue(self, address, typename, value): + sbtype = self.lookupNativeType(typename) + error = lldb.SBError() + sbaddr = lldb.SBAddress(address, self.target) + sbvalue = self.target.CreateValueFromAddress('x', sbaddr, sbtype) + sbvalue.SetValueFromCString(str(value), error) + + def setValues(self, address, typename, values): + sbtype = self.lookupNativeType(typename) + sizeof = sbtype.GetByteSize() + error = lldb.SBError() + for i in range(len(values)): + sbaddr = lldb.SBAddress(address + i * sizeof, self.target) + sbvalue = self.target.CreateValueFromAddress('x', sbaddr, sbtype) + sbvalue.SetValueFromCString(str(values[i]), error) + + def assignValue(self, args): + self.reportToken(args) + error = lldb.SBError() + expr = self.hexdecode(args['expr']) + value = self.hexdecode(args['value']) + simpleType = int(args['simpleType']) + lhs = self.findValueByExpression(expr) + typeName = lhs.GetType().GetName() + typeName = typeName.replace('::', '__') + pos = typeName.find('<') + if pos != -1: + typeName = typeName[0:pos] + if typeName in self.qqEditable and not simpleType: + expr = self.parseAndEvaluate(expr) + self.qqEditable[typeName](self, expr, value) + else: + self.parseAndEvaluate(expr + '=' + value) + self.reportResult(self.describeError(error), args) + + def watchPoint(self, args): + self.reportToken(args) + ns = self.qtNamespace() + lenns = len(ns) + funcs = self.target.FindGlobalFunctions('.*QApplication::widgetAt', 2, 1) + func = funcs[1] + addr = func.GetFunction().GetStartAddress().GetLoadAddress(self.target) + expr = '((void*(*)(int,int))0x%x)' % addr + #expr = '%sQApplication::widgetAt(%s,%s)' % (ns, args['x'], args['y']) + res = self.parseAndEvaluate(expr) + p = 0 if res is None else res.pointer() + n = ns + 'QWidget' + self.reportResult('selected="0x%x",expr="(%s*)0x%x"' % (p, n, p), args) + + def createResolvePendingBreakpointsHookBreakpoint(self, args): + bp = self.target.BreakpointCreateByName('qt_qmlDebugConnectorOpen') + bp.SetOneShot(True) + self.interpreterBreakpointResolvers.append( + lambda: self.resolvePendingInterpreterBreakpoint(args)) + + +# Used in dumper auto test. +class Tester(Dumper): + def __init__(self, binary, args): + Dumper.__init__(self) + lldb.theDumper = self + self.loadDumpers({'token': 1}) + error = lldb.SBError() + self.target = self.debugger.CreateTarget(binary, None, None, True, error) + + if error.GetType(): + self.warn('ERROR: %s' % error) + return + + s = threading.Thread(target=self.testLoop, args=(args,)) + s.start() + s.join(30) + + def testLoop(self, args): + # Disable intermediate reporting. + savedReport = self.report + self.report = lambda stuff: 0 + + error = lldb.SBError() + launchInfo = lldb.SBLaunchInfo([]) + launchInfo.SetWorkingDirectory(os.getcwd()) + environmentList = [key + '=' + value for key, value in os.environ.items()] + launchInfo.SetEnvironmentEntries(environmentList, False) + + self.process = self.target.Launch(launchInfo, error) + if error.GetType(): + self.warn('ERROR: %s' % error) + + event = lldb.SBEvent() + listener = self.debugger.GetListener() + while True: + state = self.process.GetState() + if listener.WaitForEvent(100, event): + #DumperBase.warn('EVENT: %s' % event) + state = lldb.SBProcess.GetStateFromEvent(event) + if state == lldb.eStateExited: # 10 + break + if state == lldb.eStateStopped: # 5 + stoppedThread = None + for i in range(0, self.process.GetNumThreads()): + thread = self.process.GetThreadAtIndex(i) + reason = thread.GetStopReason() + #DumperBase.warn('THREAD: %s REASON: %s' % (thread, reason)) + if (reason == lldb.eStopReasonBreakpoint or + reason == lldb.eStopReasonException or + reason == lldb.eStopReasonSignal): + stoppedThread = thread + + if stoppedThread: + # This seems highly fragile and depending on the 'No-ops' in the + # event handling above. + frame = stoppedThread.GetFrameAtIndex(0) + line = frame.line_entry.line + if line != 0: + self.report = savedReport + self.process.SetSelectedThread(stoppedThread) + self.fakeAddress_ = frame.GetPC() + self.fakeLAddress_ = frame.GetPCAddress() + self.fetchVariables(args) + #self.describeLocation(frame) + self.report('@NS@%s@' % self.qtNamespace()) + #self.report('ENV=%s' % os.environ.items()) + #self.report('DUMPER=%s' % self.qqDumpers) + break + + else: + self.warn('TIMEOUT') + self.warn('Cannot determined stopped thread') + + lldb.SBDebugger.Destroy(self.debugger) + +if 'QT_CREATOR_LLDB_PROCESS' in os.environ: + # Initialize Qt Creator dumper + try: + theDumper = Dumper() + except Exception as error: + print('@\nstate="enginesetupfailed",error="{}"@\n'.format(error)) + +# ------------------------------ For use in LLDB ------------------------------ + +debug = print if 'QT_LLDB_SUMMARY_PROVIDER_DEBUG' in os.environ \ + else lambda *a, **k: None + +debug(f"Loading lldbbridge.py from {__file__}") + +class LogMixin(): + @staticmethod + def log(message='', log_caller=False, frame=1, args=''): + if log_caller: + message = ": " + message if len(message) else '' + # FIXME: Compute based on first frame not in this class? + frame = sys._getframe(frame) + fn = frame.f_code.co_name + localz = frame.f_locals + instance = str(localz["self"]) + "." if 'self' in localz else '' + message = "%s%s(%s)%s" % (instance, fn, args, message) + debug(message) + + @staticmethod + def log_fn(arg_str=''): + LogMixin.log(log_caller=True, frame=2, args=arg_str) + + +class DummyDebugger(object): + def __getattr__(self, attr): + raise AttributeError("Debugger should not be needed to create summaries") + + +class SummaryDumper(Dumper, LogMixin): + _instance = None + _lock = threading.RLock() + _type_caches = {} + + @staticmethod + def initialize(): + SummaryDumper._instance = SummaryDumper() + return SummaryDumper._instance + + @staticmethod + @contextmanager + def shared(valobj): + SummaryDumper._lock.acquire() + dumper = SummaryDumper._instance + dumper.target = valobj.target + dumper.process = valobj.process + debugger_id = dumper.target.debugger.GetID() + dumper.typeCache = SummaryDumper._type_caches.get(debugger_id, {}) + yield dumper + SummaryDumper._type_caches[debugger_id] = dumper.typeCache + SummaryDumper._lock.release() + + @staticmethod + def warn(message): + print("Qt summary warning: %s" % message) + + @staticmethod + def showException(message, exType, exValue, exTraceback): + # FIXME: Store for later and report back in summary + SummaryDumper.log("Exception during dumping: %s" % exValue) + + def __init__(self): + DumperBase.warn = staticmethod(SummaryDumper.warn) + DumperBase.showException = staticmethod(SummaryDumper.showException) + + Dumper.__init__(self, DummyDebugger()) + + self.setVariableFetchingOptions({ + 'fancy': True, + 'qobjectnames': True, + 'passexceptions': True + }) + + self.dumpermodules = ['qttypes'] + self.loadDumpers({}) + self.output = [] + + def report(self, stuff): + return # Don't mess up lldb output + + def dump_summary(self, valobj, expanded=False): + from pygdbmi import gdbmiparser + + value = self.fromNativeValue(valobj) + + # Expand variable if we need synthetic children + oldExpanded = self.expandedINames + self.expandedINames = {value.name: 100} if expanded else {} + + savedOutput = self.output + self.output = [] + with TopLevelItem(self, value.name): + self.putItem(value) + + # FIXME: Hook into putField, etc to build up object instead of parsing MI + response = gdbmiparser.parse_response("^ok,summary=%s" % self.takeOutput()) + + self.output = savedOutput + self.expandedINames = oldExpanded + + summary = response["payload"]["summary"] + + #print value, " --> ", + #pprint(summary) + + return summary + + +class SummaryProvider(LogMixin): + + DEFAULT_SUMMARY = '' + VOID_PTR_TYPE = None + + @staticmethod + def provide_summary(valobj, internal_dict, options=None): + if __name__ not in internal_dict: + # When disabling the import of the Qt summary providers, the + # summary functions are still registered with LLDB, and we will + # get callbacks to provide summaries, but at this point the child + # providers are not active, so instead of providing half-baked and + # confusing summaries we opt to unload all the summaries. + SummaryDumper.log("Module '%s' was unloaded, removing Qt category" % __name__) + lldb.debugger.HandleCommand('type category delete Qt') + return SummaryProvider.DEFAULT_SUMMARY + + # FIXME: It would be nice to cache the providers, so that if a + # synthetic child provider has already been created for a valobj, + # we can re-use that when providing summary for the synthetic child + # parent, but we lack a consistent cache key for that to work. + + provider = SummaryProvider(valobj) + provider.update() + + return provider.get_summary(options) + + def __init__(self, valobj, expand=False): + # Prevent recursive evaluation during logging, etc + valobj.SetPreferSyntheticValue(False) + + self.log_fn(valobj.path) + + self.valobj = valobj + self.expand = expand + + if not SummaryProvider.VOID_PTR_TYPE: + with SummaryDumper.shared(self.valobj) as dumper: + SummaryProvider.VOID_PTR_TYPE = dumper.lookupNativeType('void*') + + def __repr__(self): + return "%s(%s)" % (self.__class__.__name__, repr(self.key())) + + def key(self): + if not hasattr(self, 'valobj'): + return None + return self.valobj.path + + def update(self): + self.log_fn() + with SummaryDumper.shared(self.valobj) as dumper: + self.summary = dumper.dump_summary(self.valobj, self.expand) + + def get_summary(self, options): + self.log_fn() + + summary = self.summary + if not summary: + return '' + + encoding = summary.get("valueencoded") + summaryValue = summary["value"] + + # FIXME: Share between Creator and LLDB in the python code + + if encoding: + special_encodings = { + "empty": "", + "minimumitemcount": "" % summaryValue, + "undefined": "", + "null": "", + "itemcount": "<%s items>" % summaryValue, + "notaccessible": "", + "optimizedout": "", + "nullreference": "", + "emptystructure": "", + "uninitialized": "", + "invalid": "", + "notcallable": "", + "outofscope": "" + } + if encoding in special_encodings: + return special_encodings[encoding] + + text_encodings = [ + 'latin1', + # 'local8bit', + 'utf8', + 'utf16', + # 'ucs4' + ] + + if encoding in text_encodings: + try: + decodedValue = Dumper.hexdecode(summaryValue, encoding) + # LLDB expects UTF-8 for python 2 + if sys.version_info[0] < 3: + return "\"%s\"" % (decodedValue.encode('utf8')) + return '"' + decodedValue + '"' + except: + return "" % (summaryValue, encoding, sys.exc_info()[1]) + + # FIXME: Support these + other_encodings = [ + 'int', + 'uint', + 'float', + 'juliandate', + 'juliandateandmillisecondssincemidnight', + 'millisecondssincemidnight', + 'ipv6addressandhexscopeid', + 'datetimeinternal'] + + summaryValue += " " % encoding + + if self.valobj.value: + # If we've resolved a pointer that matches what LLDB natively chose, + # then use that instead of printing the values twice. FIXME, ideally + # we'd have the same pointer format as LLDB uses natively. + if re.sub('^0x0*', '', self.valobj.value) == re.sub('^0x0*', '', summaryValue): + return SummaryProvider.DEFAULT_SUMMARY + + return summaryValue.strip() + + +class SyntheticChildrenProvider(SummaryProvider): + def __init__(self, valobj, dict): + SummaryProvider.__init__(self, valobj, expand=True) + self.summary = None + self.synthetic_children = [] + + def num_native_children(self): + return self.valobj.GetNumChildren() + + def num_children(self): + self.log("native: %d synthetic: %d" % + (self.num_native_children(), len(self.synthetic_children)), True) + return self._num_children() + + def _num_children(self): + return self.num_native_children() + len(self.synthetic_children) + + def has_children(self): + return self._num_children() > 0 + + def get_child_index(self, name): + # FIXME: Do we ever need this to return something? + self.log_fn(name) + return None + + def get_child_at_index(self, index): + self.log_fn(index) + + if index < 0 or index > self._num_children(): + return None + if not self.valobj.IsValid() or not self.summary: + return None + + if index < self.num_native_children(): + # Built-in children + value = self.valobj.GetChildAtIndex(index) + else: + # Synthetic children + index -= self.num_native_children() + child = self.synthetic_children[index] + name = child.get('name', "[%s]" % index) + value = self.create_value(child, name) + + return value + + def create_value(self, child, name=''): + child_type = child.get('type', self.summary.get('childtype')) + + value = None + if child_type: + if 'address' in child: + value_type = None + with SummaryDumper.shared(self.valobj) as dumper: + value_type = dumper.lookupNativeType(child_type) + if not value_type or not value_type.IsValid(): + return None + address = int(child['address'], 16) + + # Create as void* so that the value is fully initialized before + # we trigger our own summary/child providers recursively. + value = self.valobj.synthetic_child_from_address( + name, address, SummaryProvider.VOID_PTR_TYPE).Cast(value_type) + else: + self.log("Don't know how to create value for child %s" % child) + # FIXME: Figure out if we ever will hit this case, and deal with it + #value = self.valobj.synthetic_child_from_expression(name, + # "(%s)(%s)" % (child_type, child['value'])) + else: + # FIXME: Find a good way to deal with synthetic values + self.log("Don't know how to create value for child %s" % child) + + # FIXME: Handle value type or value errors consistently + return value + + def update(self): + SummaryProvider.update(self) + + self.synthetic_children = [] + if 'children' not in self.summary: + return + + dereference_child = None + for child in self.summary['children']: + if not child or not isinstance(child, dict): + continue + + # Skip base classes, they are built-in children + # FIXME: Is there a better check than 'iname'? + if 'iname' in child: + continue + + name = child.get('name') + if name: + if name == '*': + # No use for this unless the built in children failed to resolve + dereference_child = child + continue + + if name.startswith('['): + # Skip pure synthetic children until we can deal with them + continue + + if name.startswith('#'): + # Skip anonymous unions, lookupNativeType doesn't handle them (yet), + # so it triggers the slow lookup path, and the union is provided as + # a native child anyways. + continue + + # Skip native children + if self.valobj.GetChildMemberWithName(name): + continue + + self.synthetic_children.append(child) + + # Xcode will sometimes fail to show the children of pointers in + # the debugger UI, even if dereferencing the pointer works fine. + # We fall back to the special child reported by the Qt dumper. + if not self.valobj.GetNumChildren() and dereference_child: + self.valobj = self.create_value(dereference_child) + self.update() + +def ensure_gdbmiparser(): + try: + from pygdbmi import gdbmiparser + return True + except ImportError: + try: + if not 'QT_LLDB_SUMMARY_PROVIDER_NO_AUTO_INSTALL' in os.environ: + print("Required module 'pygdbmi' not installed. Installing automatically...") + import subprocess + python3 = os.path.join(sys.exec_prefix, 'bin', 'python3') + process = subprocess.run([python3, '-m', 'pip', + '--disable-pip-version-check', + 'install', '--user', 'pygdbmi' ], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + print(process.stdout.decode('utf-8').strip()) + process.check_returncode() + from importlib import invalidate_caches + invalidate_caches() + from pygdbmi import gdbmiparser + return True + except Exception as e: + print(e) + + print("Qt summary provider requires the pygdbmi module. Please install\n" \ + "manually using '/usr/bin/pip3 install pygdbmi', and restart Xcode.") + return False + + +def __lldb_init_module(debugger, internal_dict): + # Module is being imported in an LLDB session + if 'QT_CREATOR_LLDB_PROCESS' in os.environ: + # Let Qt Creator take care of its own dumper + return + + debug("Initializing module with", debugger) + + if not ensure_gdbmiparser(): + return + + if not __name__ == 'qt': + # Make available under global 'qt' name for consistency, + # and so we can refer to SyntheticChildrenProvider below. + internal_dict['qt'] = internal_dict[__name__] + + dumper = SummaryDumper.initialize() + + type_category = 'Qt' + + # Concrete types + summary_function = "%s.%s.%s" % (__name__, 'SummaryProvider', 'provide_summary') + types = map(lambda x: x.replace('__', '::'), dumper.qqDumpers) + debugger.HandleCommand("type summary add -F %s -w %s %s" + % (summary_function, type_category, ' '.join(types))) + + regex_types = list(map(lambda x: "'" + x + "'", dumper.qqDumpersEx.keys())) + if regex_types: + debugger.HandleCommand("type summary add -x -F %s -w %s %s" + % (summary_function, type_category, ' '.join(regex_types))) + + # Global catch-all for Qt classes + debugger.HandleCommand("type summary add -x '^Q.*$' -F %s -w %s" + % (summary_function, type_category)) + + # Named summary ('frame variable foo --summary Qt') + debugger.HandleCommand("type summary add --name Qt -F %s -w %s" + % (summary_function, type_category)) + + # Synthetic children + debugger.HandleCommand("type synthetic add -x '^Q.*$' -l %s -w %s" + % ("qt.SyntheticChildrenProvider", type_category)) + + debugger.HandleCommand('type category enable %s' % type_category) diff --git a/share/qtcreator/debugger/python2/misctypes.py b/share/qtcreator/debugger/python2/misctypes.py new file mode 100644 index 00000000000..9099f19a2f7 --- /dev/null +++ b/share/qtcreator/debugger/python2/misctypes.py @@ -0,0 +1,588 @@ +# Copyright (C) 2016 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +from dumper import Children, SubItem +from utils import TypeCode, DisplayFormat +import re + +####################################################################### +# +# SSE +# +####################################################################### + + +def qdump____m128(d, value): + d.putEmptyValue() + d.putExpandable() + if d.isExpanded(): + d.putArrayData(value.address(), 4, d.lookupType('float')) + + +def qdump____m256(d, value): + d.putEmptyValue() + d.putExpandable() + if d.isExpanded(): + d.putArrayData(value.address(), 8, d.lookupType('float')) + + +def qdump____m512(d, value): + d.putEmptyValue() + d.putExpandable() + if d.isExpanded(): + d.putArrayData(value.address(), 16, d.lookupType('float')) + + +def qdump____m128d(d, value): + d.putEmptyValue() + d.putExpandable() + if d.isExpanded(): + d.putArrayData(value.address(), 2, d.lookupType('double')) + + +def qdump____m256d(d, value): + d.putEmptyValue() + d.putExpandable() + if d.isExpanded(): + d.putArrayData(value.address(), 4, d.lookupType('double')) + + +def qdump____m512d(d, value): + d.putEmptyValue() + d.putExpandable() + if d.isExpanded(): + d.putArrayData(value.address(), 8, d.lookupType('double')) + + +def qdump____m128i(d, value): + data = d.hexencode(value.data(16)) + d.putValue(':'.join('%04x' % int(data[i:i + 4], 16) for i in range(0, 32, 4))) + d.putExpandable() + if d.isExpanded(): + with Children(d): + addr = value.address() + d.putArrayItem('uint8x16', addr, 16, 'unsigned char') + d.putArrayItem('uint16x8', addr, 8, 'unsigned short') + d.putArrayItem('uint32x4', addr, 4, 'unsigned int') + d.putArrayItem('uint64x2', addr, 2, 'unsigned long long') + + +def qdump____m256i(d, value): + data = d.hexencode(value.data(32)) + d.putValue(':'.join('%04x' % int(data[i:i + 4], 16) for i in range(0, 64, 4))) + d.putExpandable() + if d.isExpanded(): + with Children(d): + addr = value.address() + d.putArrayItem('uint8x32', addr, 32, 'unsigned char') + d.putArrayItem('uint16x16', addr, 16, 'unsigned short') + d.putArrayItem('uint32x8', addr, 8, 'unsigned int') + d.putArrayItem('uint64x4', addr, 4, 'unsigned long long') + + +def qdump____m512i(d, value): + data = d.hexencode(value.data(64)) + d.putValue(':'.join('%04x' % int(data[i:i + 4], 16) for i in range(0, 64, 4)) + + ', ' + ':'.join('%04x' % int(data[i:i + 4], 16) for i in range(64, 128, 4))) + d.putExpandable() + if d.isExpanded(): + with Children(d): + d.putArrayItem('uint32x16', value.address(), 16, 'unsigned int') + d.putArrayItem('uint64x8', value.address(), 8, 'unsigned long long') + +####################################################################### +# +# GSL +# +####################################################################### + + +def qform__std__array(): + return [DisplayFormat.ArrayPlot] + + +def qdump__gsl__span(d, value): + size, pointer = value.split('pp') + d.putItemCount(size) + if d.isExpanded(): + d.putPlotData(pointer, size, value.type[0]) + + +def qdump__gsl__byte(d, value): + d.putValue(value.integer()) + +####################################################################### +# +# Eigen +# +####################################################################### + +#def qform__Eigen__Matrix(): +# return 'Transposed' + + +def qdump__Eigen__Matrix(d, value): + innerType = value.type[0] + argRow = value.type[1] + argCol = value.type[2] + options = value.type[3] + rowMajor = (int(options) & 0x1) + # The magic dimension value is -1 in Eigen3, but 10000 in Eigen2. + # 10000 x 10000 matrices are rare, vectors of dim 10000 less so. + # So 'fix' only the matrix case: + if argCol == 10000 and argRow == 10000: + argCol = -1 + argRow = -1 + if argCol != -1 and argRow != -1: + nrows = argRow + ncols = argCol + p = value.address() + else: + storage = value['m_storage'] + nrows = storage['m_rows'].integer() if argRow == -1 else argRow + ncols = storage['m_cols'].integer() if argCol == -1 else argCol + p = storage['m_data'].pointer() + innerSize = innerType.size() + d.putValue('(%s x %s), %s' % (nrows, ncols, ['ColumnMajor', 'RowMajor'][rowMajor])) + d.putField('keeporder', '1') + d.putNumChild(nrows * ncols) + + limit = 10000 + nncols = min(ncols, limit) + nnrows = min(nrows, limit * limit / nncols) + if d.isExpanded(): + #format = d.currentItemFormat() # format == 1 is 'Transposed' + with Children(d, nrows * ncols, childType=innerType): + if ncols == 1 or nrows == 1: + for i in range(0, min(nrows * ncols, 10000)): + d.putSubItem(i, d.createValue(p + i * innerSize, innerType)) + elif rowMajor == 1: + s = 0 + for i in range(0, nnrows): + for j in range(0, nncols): + v = d.createValue(p + (i * ncols + j) * innerSize, innerType) + d.putNamedSubItem(s, v, '[%d,%d]' % (i, j)) + s = s + 1 + else: + s = 0 + for j in range(0, nncols): + for i in range(0, nnrows): + v = d.createValue(p + (i + j * nrows) * innerSize, innerType) + d.putNamedSubItem(s, v, '[%d,%d]' % (i, j)) + s = s + 1 + + +####################################################################### +# +# Nim +# +####################################################################### + +def qdump__NimStringDesc(d, value): + size, reserved = value.split('pp') + data = value.address() + 2 * d.ptrSize() + d.putBetterType('string') + d.putCharArrayHelper(data, size, d.createType('char'), 'utf8') + + +def qdump__NimGenericSequence__(d, value, regex=r'^TY[\d]+$'): + code = value.type.stripTypedefs().code + if code == TypeCode.Struct: + size, reserved = d.split('pp', value) + data = value.address() + 2 * d.ptrSize() + typeobj = value['data'].type.dereference() + d.putItemCount(size) + d.putArrayData(data, size, typeobj) + d.putBetterType('%s (%s[%s])' % (value.type.name, typeobj.name, size)) + else: + d.putEmptyValue() + d.putPlainChildren(value) + + +def qdump__TNimNode(d, value): + name = value['name'].pointer() + d.putSimpleCharArray(name) if name != 0 else d.putEmptyValue() + if d.isExpanded(): + with Children(d): + sons = value['sons'].pointer() + size = value['len'].integer() + for i in range(size): + val = d.createValue(d.extractPointer(sons + i * d.ptrSize()), value.type) + with SubItem(d, '[%d]' % i): + d.putItem(val) + with SubItem(d, '[raw]'): + d.putPlainChildren(value) + + +####################################################################### +# +# D +# +####################################################################### + +def cleanDType(type): + return str(type).replace('uns long long', 'string') + + +def qdump_Array(d, value): + n = value['length'] + p = value['ptr'] + t = cleanDType(value.type)[7:] + d.putType('%s[%d]' % (t, n)) + if t == 'char': + d.putValue(encodeCharArray(p, 100), 'local8bit') + else: + d.putEmptyValue() + d.putNumChild(n) + innerType = p.type + if d.isExpanded(): + with Children(d, n, childType=innerType): + for i in range(0, n): + d.putSubItem(i, p.dereference()) + p = p + 1 + + +def qdump_AArray(d, value): + #n = value['length'] + # This ends up as _AArray__ with a single .ptr + # member of type void *. Not much that can be done here. + p = value['ptr'] + t = cleanDType(value.type)[8:] + d.putType('%s]' % t.replace('_', '[')) + d.putEmptyValue() + if d.isExpanded(): + with Children(d, 1): + d.putSubItem('ptr', p) + + +####################################################################### +# +# MPI +# +####################################################################### + +if False: + + def qdump__tree_entry(d, value): + d.putValue('len: %s, offset: %s, type: %s' % + (value['blocklength'], value['offset'], value['type'])) + + def qdump__tree(d, value): + count = value['count'] + entries = value['entries'] + base = value['base'].pointer() + d.putItemCount(count) + if d.isExpanded(): + with Children(d): + with SubItem(d, 'tree'): + d.putEmptyValue() + d.putNoType() + if d.isExpanded(): + with Children(d): + for i in range(count): + d.putSubItem(Item(entries[i], iname)) + with SubItem(d, 'data'): + d.putEmptyValue() + d.putNoType() + if d.isExpanded(): + with Children(d): + for i in range(count): + with SubItem(d, i): + entry = entries[i] + mpitype = str(entry['type']) + d.putType(mpitype) + length = int(entry['blocklength']) + offset = int(entry['offset']) + d.putValue('%s items at %s' % (length, offset)) + if mpitype == 'MPI_INT': + innerType = 'int' + elif mpitype == 'MPI_CHAR': + innerType = 'char' + elif mpitype == 'MPI_DOUBLE': + innerType = 'double' + else: + length = 0 + d.putNumChild(length) + if d.isExpanded(): + with Children(d): + t = d.lookupType(innerType).pointer() + p = (base + offset).cast(t) + for j in range(length): + d.putSubItem(j, p.dereference()) + + +####################################################################### +# +# KDSoap +# +####################################################################### + +def qdump__KDSoapValue1(d, value): + inner = value['d']['d'].dereference() + d.putStringValue(inner['m_name']) + d.putPlainChildren(inner) + + +def qdump__KDSoapValue(d, value): + p = (value.cast(d.lookupType('char*')) + 4).dereference().cast(d.lookupType('QString')) + d.putStringValue(p) + d.putPlainChildren(value['d']['d'].dereference()) + + +####################################################################### +# +# Webkit +# +####################################################################### + +def qdump__WTF__String(d, value): + # WTF::String -> WTF::RefPtr -> WTF::StringImpl* + data = value['m_impl']['m_ptr'] + d.checkPointer(data) + + stringLength = int(data['m_length']) + d.check(0 <= stringLength and stringLength <= 100000000) + + # WTF::StringImpl* -> WTF::StringImpl -> sizeof(WTF::StringImpl) + offsetToData = data.type.target().size() + bufferPtr = data.pointer() + offsetToData + + is8Bit = data['m_is8Bit'] + charSize = 1 + if not is8Bit: + charSize = 2 + + d.putCharArrayHelper(bufferPtr, stringLength, charSize) + + +####################################################################### +# +# Python +# +####################################################################### + +def get_python_interpreter_major_version(d): + key = 'python_interpreter_major_version' + if key in d.generalCache: + return d.generalCache[key] + + e = "(char*)Py_GetVersion()" + result = d.nativeParseAndEvaluate(e) + result_str = str(result) + matches = re.search(r'(\d+?)\.(\d+?)\.(\d+?)', result_str) + if matches: + result_str = matches.group(1) + d.generalCache[key] = result_str + return result_str + + +def is_python_3(d): + return get_python_interpreter_major_version(d) == '3' + + +def repr_cache_decorator(namespace): + def real_decorator(func_to_decorate): + def wrapper(d, address): + if namespace in d.perStepCache and address in d.perStepCache[namespace]: + return d.perStepCache[namespace][address] + + if namespace not in d.perStepCache: + d.perStepCache[namespace] = {} + + if address == 0: + result_str = d.perStepCache[namespace][address] = "" + return result_str + + result = func_to_decorate(d, address) + d.perStepCache[namespace][address] = result + return result + return wrapper + return real_decorator + + +@repr_cache_decorator('py_object') +def get_py_object_repr_helper(d, address): + # The code below is a long way to evaluate: + # ((PyBytesObject *)PyUnicode_AsEncodedString(PyObject_Repr( + # (PyObject*){}), \"utf-8\", \"backslashreplace\"))->ob_sval" + # But with proper object cleanup. + + e_decref = "Py_DecRef((PyObject *){})" + + e = "PyObject_Repr((PyObject*){})" + repr_object_value = d.parseAndEvaluate(e.format(address)) + repr_object_address = d.fromPointerData(repr_object_value.ldata)[0] + + if is_python_3(d): + # Try to get a UTF-8 encoded string from the repr object. + e = "PyUnicode_AsEncodedString((PyObject*){}, \"utf-8\", \"backslashreplace\")" + string_object_value = d.parseAndEvaluate(e.format(repr_object_address)) + string_object_address = d.fromPointerData(string_object_value.ldata)[0] + + e = "(char*)(((PyBytesObject *){})->ob_sval)" + result = d.nativeParseAndEvaluate(e.format(string_object_address)) + + # It's important to stringify the result before any other evaluations happen. + result_str = str(result) + + # Clean up. + d.nativeParseAndEvaluate(e_decref.format(string_object_address)) + + else: + # Retrieve non-unicode string. + e = "(char*)(PyString_AsString((PyObject*){}))" + result = d.nativeParseAndEvaluate(e.format(repr_object_address)) + + # It's important to stringify the result before any other evaluations happen. + result_str = str(result) + + # Do some string stripping. + # FIXME when using cdb engine. + matches = re.search(r'.+?"(.+)"$', result_str) + if matches: + result_str = matches.group(1) + + # Clean up. + d.nativeParseAndEvaluate(e_decref.format(repr_object_address)) + + return result_str + + +@repr_cache_decorator('py_object_type') +def get_py_object_type(d, object_address): + e = "((PyObject *){})->ob_type" + type_value = d.parseAndEvaluate(e.format(object_address)) + type_address = d.fromPointerData(type_value.ldata)[0] + type_repr = get_py_object_repr_helper(d, type_address) + return type_repr + + +@repr_cache_decorator('py_object_meta_type') +def get_py_object_meta_type(d, object_address): + # The python3 object layout has a few more indirections. + if is_python_3(d): + e = "((PyObject *){})->ob_type->ob_base->ob_base->ob_type" + else: + e = "((PyObject *){})->ob_type->ob_type" + type_value = d.parseAndEvaluate(e.format(object_address)) + type_address = d.fromPointerData(type_value.ldata)[0] + type_repr = get_py_object_repr_helper(d, type_address) + return type_repr + + +@repr_cache_decorator('py_object_base_class') +def get_py_object_base_class(d, object_address): + e = "((PyObject *){})->ob_type->tp_base" + base_value = d.parseAndEvaluate(e.format(object_address)) + base_address = d.fromPointerData(base_value.ldata)[0] + base_repr = get_py_object_repr_helper(d, base_address) + return base_repr + + +def get_py_object_repr(d, value): + address = value.address() + repr_available = False + try: + result = get_py_object_repr_helper(d, address) + d.putValue(d.hexencode(result), encoding='utf8') + repr_available = True + except: + d.putEmptyValue() + + def sub_item(name, functor, address): + with SubItem(d, '[{}]'.format(name)): + sub_value = functor(d, address) + d.putValue(d.hexencode(sub_value), encoding='utf8') + + d.putExpandable() + if d.isExpanded(): + with Children(d): + if repr_available: + sub_item('class', get_py_object_type, address) + sub_item('super class', get_py_object_base_class, address) + sub_item('meta type', get_py_object_meta_type, address) + d.putFields(value) + + +def qdump__PyTypeObject(d, value): + get_py_object_repr(d, value) + + +def qdump___typeobject(d, value): + get_py_object_repr(d, value) + + +def qdump__PyObject(d, value): + get_py_object_repr(d, value) + + +def qdump__PyVarObject(d, value): + get_py_object_repr(d, value) + + +####################################################################### +# +# Internal test +# +####################################################################### + +def qdump__QtcDumperTest_FieldAccessByIndex(d, value): + d.putValue(value["d"][2].integer()) + + +def qdump__QtcDumperTest_PointerArray(d, value): + foos = value["foos"] + d.putItemCount(10) + if d.isExpanded(): + with Children(d, 10): + for i in d.childRange(): + d.putSubItem(i, foos[i]) + + +def qdump__QtcDumperTest_BufArray(d, value): + maxItems = 1000 + buffer = value['buffer'] + count = int(value['count']) + objsize = int(value['objSize']) + valueType = value.type.templateArgument(0) + d.putItemCount(count, maxItems) + d.putNumChild(count) + if d.isExpanded(): + with Children(d, count, maxNumChild=maxItems, childType=valueType): + for i in d.childRange(): + d.putSubItem(i, (buffer + (i * objsize)).dereference().cast(valueType)) + + +def qdump__QtcDumperTest_List__NodeX(d, value): + typename = value.type.unqualified().name + pos0 = typename.find('<') + pos1 = typename.find('>') + tName = typename[pos0 + 1:pos1] + d.putBetterType('QtcDumperTest_List<' + tName + '>::Node') + d.putExpandable() + if d.isExpanded(): + obj_type = d.lookupType(tName) + with Children(d): + d.putSubItem("this", value.cast(obj_type)) + d.putFields(value) + #d.putPlainChildren(value) + + +def qdump__QtcDumperTest_List(d, value): + innerType = value.type[0] + d.putExpandable() + p = value['root'] + if d.isExpanded(): + with Children(d): + d.putSubItem("[p]", p) + d.putSubItem("[root]", value["root"].cast(innerType)) + d.putFields(value) + #d.putPlainChildren(value) + + +def qdump__QtcDumperTest_String(d, value): + with Children(d): + first = d.hexdecode(d.putSubItem('first', value['first']).value) + second = d.hexdecode(d.putSubItem('second', value['second']).value) + third = d.hexdecode(d.putSubItem('third', value['third']).value)[:-1] + d.putValue(first + ', ' + second + ', ' + third) diff --git a/share/qtcreator/debugger/python2/opencvtypes.py b/share/qtcreator/debugger/python2/opencvtypes.py new file mode 100644 index 00000000000..5cfd60c5d25 --- /dev/null +++ b/share/qtcreator/debugger/python2/opencvtypes.py @@ -0,0 +1,44 @@ +# Copyright (C) 2016 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +from dumper import Children, SubItem +from utils import TypeCode, DisplayFormat + + +def qdump__cv__Size_(d, value): + d.putValue('(%s, %s)' % (value[0].display(), value[1].display())) + d.putPlainChildren(value) + + +def qform__cv__Mat(): + return [DisplayFormat.Separate] + + +def qdump__cv__Mat(d, value): + (flag, dims, rows, cols, data, refcount, datastart, dataend, + datalimit, allocator, size, stepp) \ + = value.split('iiiipppppppp') + steps = d.split('p' * dims, stepp) + innerSize = 0 if dims == 0 else steps[dims - 1] + if dims != 2: + d.putEmptyValue() + d.putPlainChildren(value) + return + + if d.currentItemFormat() == DisplayFormat.Separate: + rs = steps[0] * innerSize + cs = cols * innerSize + dform = 'arraydata:separate:int:%d::2:%d:%d' % (innerSize, cols, rows) + out = ''.join(d.readMemory(data + i * rs, cs) for i in range(rows)) + d.putDisplay(dform, out) + + d.putValue('(%s x %s)' % (rows, cols)) + if d.isExpanded(): + with Children(d): + innerType = d.createType(TypeCode.Integral, innerSize) + for i in range(rows): + for j in range(cols): + with SubItem(d, None): + d.putName('[%d,%d]' % (i, j)) + addr = data + (i * steps[0] + j) * innerSize + d.putItem(d.createValue(addr, innerType)) diff --git a/share/qtcreator/debugger/python2/pdbbridge.py b/share/qtcreator/debugger/python2/pdbbridge.py new file mode 100644 index 00000000000..228f4c8c1fc --- /dev/null +++ b/share/qtcreator/debugger/python2/pdbbridge.py @@ -0,0 +1,1726 @@ +# Copyright (C) 2016 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import os +import re +import sys +import code +import base64 +import linecache +import signal +import string +import inspect +import traceback +import fnmatch +import platform + + +class QuitException(Exception): + pass + + +class QtcInternalBreakpoint(): + """Breakpoint class. + Breakpoints are indexed by number through bpbynumber and by + the file,line tuple using bplist. The former points to a + single instance of class Breakpoint. The latter points to a + list of such instances since there may be more than one + breakpoint per line. + """ + + next = 1 # Next bp to be assigned + bplist = {} # indexed by (file, lineno) tuple + bpbynumber = [None] # Each entry is None or an instance of Bpt + # index 0 is unused, except for marking an + # effective break .... see effective() + + def __init__(self, filepath, line, temporary=False, cond=None, funcname=None): + self.funcname = funcname + # Needed if funcname is not None. + self.func_first_executable_line = None + self.file = filepath # This better be in canonical form! + self.line = line + self.temporary = temporary + self.cond = cond + self.enabled = True + self.ignore = 0 + self.hits = 0 + self.number = QtcInternalBreakpoint.next + QtcInternalBreakpoint.next += 1 + # Build the two lists + self.bpbynumber.append(self) + if (filepath, line) in self.bplist: + self.bplist[filepath, line].append(self) + else: + self.bplist[filepath, line] = [self] + + def deleteMe(self): + index = (self.file, self.line) + self.bpbynumber[self.number] = None # No longer in list + self.bplist[index].remove(self) + if not self.bplist[index]: + # No more bp for this f:l combo + del self.bplist[index] + + def enable(self): + self.enabled = True + + def disable(self): + self.enabled = False + + def __str__(self): + return 'breakpoint %s at %s:%s' % (self.number, self.file, self.line) + + +def checkfuncname(b, frame): + """Check whether we should break here because of `b.funcname`.""" + if not b.funcname: + # Breakpoint was set via line number. + if b.line != frame.f_lineno: + # Breakpoint was set at a line with a def statement and the function + # defined is called: don't break. + return False + return True + + # Breakpoint set via function name. + + if frame.f_code.co_name != b.funcname: + # It's not a function call, but rather execution of def statement. + return False + + # We are in the right frame. + if not b.func_first_executable_line: + # The function is entered for the 1st time. + b.func_first_executable_line = frame.f_lineno + + if b.func_first_executable_line != frame.f_lineno: + # But we are not at the first line number: don't break. + return False + return True + + +# Determines if there is an effective (active) breakpoint at this +# line of code. Returns breakpoint number or 0 if none +def effective(filename, line, frame): + """Determine which breakpoint for this file:line is to be acted upon. + + Called only if we know there is a bpt at this + location. Returns breakpoint that was triggered and a flag + that indicates if it is ok to delete a temporary bp. + + """ + possibles = QtcInternalBreakpoint.bplist[filename, line] + for b in possibles: + if not b.enabled: + continue + if not checkfuncname(b, frame): + continue + # Count every hit when bp is enabled + b.hits += 1 + if not b.cond: + # If unconditional, and ignoring go on to next, else break + if b.ignore > 0: + b.ignore -= 1 + continue + else: + # breakpoint and marker that it's ok to delete if temporary + return (b, True) + else: + # Conditional bp. + # Ignore count applies only to those bpt hits where the + # condition evaluates to true. + try: + val = eval(b.cond, frame.f_globals, frame.f_locals) + if val: + if b.ignore > 0: + b.ignore -= 1 + # continue + else: + return (b, True) + # else: + # continue + except: + # if eval fails, most conservative thing is to stop on + # breakpoint regardless of ignore count. Don't delete + # temporary, as another hint to user. + return (b, False) + return (None, None) + + +# __all__ = ['QtcInternalDumper'] + + +def find_function(funcname, filename): + cre = re.compile(r'def\s+%s\s*[(]' % re.escape(funcname)) + try: + fp = open(filename) + except OSError: + return None + # consumer of this info expects the first line to be 1 + with fp: + for lineno, line in enumerate(fp, start=1): + if cre.match(line): + return funcname, filename, lineno + return None + + +class _rstr(str): + """String that doesn't quote its repr.""" + + def __repr__(self): + return self + + +class QtcInternalDumper(): + identchars = string.ascii_letters + string.digits + '_' + lastcmd = '' + use_rawinput = 1 + + def __init__(self, stdin=None, stdout=None): + self.skip = [] + self.breaks = {} + self.fncache = {} + self.frame_returning = None + + nosigint = False + + if stdin is not None: + self.stdin = stdin + else: + self.stdin = sys.stdin + if stdout is not None: + self.stdout = stdout + else: + self.stdout = sys.stdout + self.cmdqueue = [] + + if stdout: + self.use_rawinput = 0 + self.aliases = {} + self.mainpyfile = '' + self._wait_for_mainpyfile = False + # Try to load readline if it exists + try: + import readline + # remove some common file name delimiters + readline.set_completer_delims(' \t\n`@#$%^&*()=+[{]}\\|;:\'",<>?') + except ImportError: + pass + self.allow_kbdint = False + self.nosigint = nosigint + + self.commands = {} # associates a command list to breakpoint numbers + self.botframe = None + self.currentbp = -1 + self.stopframe = None + self.returnframe = None + self.quitting = False + self.stoplineno = 0 + self.mainpyfile = '' + self._user_requested_quit = False + self.stack = [] + self.curindex = 0 + self.curframe = None + self.curframe_locals = {} + self.typeformats = {} + self.formats = {} + self.output = '' + self.expandedINames = [] + + # Hex decoding operating on str, return str. + @staticmethod + def hexdecode(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. + @staticmethod + def hexencode(s): + if sys.version_info[0] == 2: + return s.encode('hex') + if isinstance(s, __builtins__.str): + s = s.encode('utf8') + return base64.b16encode(s).decode('utf8') + + @staticmethod + def cleanAddress(addr): + if addr is None: + return '' + h = hex(addr) + return '0x%x' % (int(h, 16) if sys.version_info[0] >= 3 else long(h, 16)) + + def canonic(self, filename): + if filename == '<' + filename[1:-1] + '>': + return filename + canonic = self.fncache.get(filename) + if not canonic: + canonic = os.path.abspath(filename) + canonic = os.path.normcase(canonic) + if platform.system() in ('Microsoft', 'Windows'): + canonic = canonic.replace('\\', '/') + self.fncache[filename] = canonic + return canonic + + def reset(self): + linecache.checkcache() + self.botframe = None + self._set_stopinfo(None, None) + self.forget() + + def trace_dispatch(self, frame, event, arg): + if self.quitting: + return None + if event == 'line': + return self.dispatch_line(frame) + if event == 'call': + return self.dispatch_call(frame) + if event == 'return': + return self.dispatch_return(frame, arg) + if event == 'exception': + return self.dispatch_exception(frame, arg) + if event == 'c_call': + return self.trace_dispatch + if event == 'c_exception': + return self.trace_dispatch + if event == 'c_return': + return self.trace_dispatch + print('Bdb.dispatch: unknown debugging event:', repr(event)) + return self.trace_dispatch + + def dispatch_line(self, frame): + if self.stop_here(frame) or self.break_here(frame): + self.user_line(frame) + if self.quitting: + raise QuitException + return self.trace_dispatch + + def dispatch_call(self, frame): + if self.botframe is None: + # First call of dispatch since reset() + self.botframe = frame.f_back # (CT) Note that this may also be None! + return self.trace_dispatch + if not (self.stop_here(frame) or self.break_anywhere(frame)): + # No need to trace this function + return None + # Ignore call events in generator except when stepping. + if self.stopframe and frame.f_code.co_flags & inspect.CO_GENERATOR: + return self.trace_dispatch + self.user_call(frame) + if self.quitting: + raise QuitException + return self.trace_dispatch + + def dispatch_return(self, frame, arg): + if self.stop_here(frame) or frame == self.returnframe: + # Ignore return events in generator except when stepping. + if self.stopframe and frame.f_code.co_flags & inspect.CO_GENERATOR: + return self.trace_dispatch + try: + self.frame_returning = frame + self.user_return(frame, arg) + finally: + self.frame_returning = None + if self.quitting: + raise QuitException + # The user issued a 'next' or 'until' command. + if self.stopframe is frame and self.stoplineno != -1: + self._set_stopinfo(None, None) + return self.trace_dispatch + + def dispatch_exception(self, frame, arg): + if self.stop_here(frame): + # When stepping with next/until/return in a generator frame, skip + # the internal StopIteration exception (with no traceback) + # triggered by a subiterator run with the 'yield from' statement. + if not (frame.f_code.co_flags & inspect.CO_GENERATOR + and arg[0] is StopIteration and arg[2] is None): + self.user_exception(frame, arg) + if self.quitting: + raise QuitException + # Stop at the StopIteration or GeneratorExit exception when the user + # has set stopframe in a generator by issuing a return command, or a + # next/until command at the last statement in the generator before the + # exception. + elif (self.stopframe and frame is not self.stopframe + and self.stopframe.f_code.co_flags & inspect.CO_GENERATOR + and arg[0] in (StopIteration, GeneratorExit)): + self.user_exception(frame, arg) + if self.quitting: + raise QuitException + + return self.trace_dispatch + + # Normally derived classes don't override the following + # methods, but they may if they want to redefine the + # definition of stopping and breakpoints. + + def is_skipped_module(self, module_name): + for pattern in self.skip: + if fnmatch.fnmatch(module_name, pattern): + return True + return False + + def stop_here(self, frame): + # (CT) stopframe may now also be None, see dispatch_call. + # (CT) the former test for None is therefore removed from here. + if self.skip and \ + self.is_skipped_module(frame.f_globals.get('__name__')): + return False + if frame is self.stopframe: + if self.stoplineno == -1: + return False + return frame.f_lineno >= self.stoplineno + if not self.stopframe: + return True + return False + + def break_here(self, frame): + filename = self.canonic(frame.f_code.co_filename) + if filename not in self.breaks: + return False + lineno = frame.f_lineno + if lineno not in self.breaks[filename]: + # The line itself has no breakpoint, but maybe the line is the + # first line of a function with breakpoint set by function name. + lineno = frame.f_code.co_firstlineno + if lineno not in self.breaks[filename]: + return False + + # flag says ok to delete temp. bp + (bp, flag) = effective(filename, lineno, frame) + if bp: + self.currentbp = bp.number + if (flag and bp.temporary): + self.do_clear(__builtins__.str(bp.number)) + return True + else: + return False + + def break_anywhere(self, frame): + return self.canonic(frame.f_code.co_filename) in self.breaks + + def _set_stopinfo(self, stopframe, returnframe, stoplineno=0): + self.stopframe = stopframe + self.returnframe = returnframe + self.quitting = False + # stoplineno >= 0 means: stop at line >= the stoplineno + # stoplineno -1 means: don't stop at all + self.stoplineno = stoplineno + + # Derived classes and clients can call the following methods + # to affect the stepping state. + + def set_step(self): + """Stop after one line of code.""" + # Issue #13183: pdb skips frames after hitting a breakpoint and running + # step commands. + # Restore the trace function in the caller (that may not have been set + # for performance reasons) when returning from the current frame. + if self.frame_returning: + caller_frame = self.frame_returning.f_back + if caller_frame and not caller_frame.f_trace: + caller_frame.f_trace = self.trace_dispatch + self._set_stopinfo(None, None) + + def set_trace(self, frame=None): + """Start debugging from `frame`. + + If frame is not specified, debugging starts from caller's frame. + """ + if frame is None: + frame = sys._getframe().f_back + self.reset() + while frame: + frame.f_trace = self.trace_dispatch + self.botframe = frame + frame = frame.f_back + self.set_step() + sys.settrace(self.trace_dispatch) + + def set_continue(self): + # Don't stop except at breakpoints or when finished + self._set_stopinfo(self.botframe, None, -1) + if not self.breaks: + # no breakpoints; run without debugger overhead + sys.settrace(None) + frame = sys._getframe().f_back + while frame and frame is not self.botframe: + del frame.f_trace + frame = frame.f_back + + def set_quit(self): + self.stopframe = self.botframe + self.returnframe = None + self.quitting = True + sys.settrace(None) + + # Derived classes and clients can call the following methods + # to manipulate breakpoints. These methods return an + # error message if something went wrong, None if all is well. + # Set_break prints out the breakpoint line and file:lineno. + # Call self.get_*break*() to see the breakpoints or better + # for bp in Breakpoint.bpbynumber: if bp: bp.bpprint(). + + def set_break(self, filename, lineno, temporary=False, cond=None, + funcname=None): + filename = self.canonic(filename) + line = linecache.getline(filename, lineno) + if not line: + return 'Line %s:%d does not exist' % (filename, lineno) + lines = self.breaks.setdefault(filename, []) + if lineno not in lines: + lines.append(lineno) + QtcInternalBreakpoint(filename, lineno, temporary, cond, funcname) + return None + + def _prune_breaks(self, filename, lineno): + if (filename, lineno) not in QtcInternalBreakpoint.bplist: + self.breaks[filename].remove(lineno) + if not self.breaks[filename]: + del self.breaks[filename] + + def clear_break(self, filename, lineno): + filename = self.canonic(filename) + if filename not in self.breaks: + return 'There are no breakpoints in %s' % filename + if lineno not in self.breaks[filename]: + return 'There is no breakpoint at %s:%d' % (filename, lineno) + # If there's only one bp in the list for that file,line + # pair, then remove the breaks entry + for bp in QtcInternalBreakpoint.bplist[filename, lineno][:]: + bp.deleteMe() + self._prune_breaks(filename, lineno) + return None + + def clear_bpbynumber(self, arg): + try: + bp = self.get_bpbynumber(arg) + except ValueError as err: + return __builtins__.str(err) + bp.deleteMe() + self._prune_breaks(bp.file, bp.line) + return None + + def clear_all_file_breaks(self, filename): + filename = self.canonic(filename) + if filename not in self.breaks: + return + for line in self.breaks[filename]: + blist = QtcInternalBreakpoint.bplist[filename, line] + for bp in blist: + bp.deleteMe() + del self.breaks[filename] + + def clear_all_breaks(self): + if not self.breaks: + return + for bp in QtcInternalBreakpoint.bpbynumber: + if bp: + bp.deleteMe() + self.breaks = {} + + @staticmethod + def get_bpbynumber(arg): + if not arg: + raise ValueError('Breakpoint number expected') + try: + number = int(arg) + except ValueError: + raise ValueError('Non-numeric breakpoint number %s' % arg) + try: + bp = QtcInternalBreakpoint.bpbynumber[number] + except IndexError: + raise ValueError('Breakpoint number %d out of range' % number) + if bp is None: + raise ValueError('Breakpoint %d already deleted' % number) + return bp + + def get_break(self, filename, lineno): + filename = self.canonic(filename) + return filename in self.breaks and \ + lineno in self.breaks[filename] + + def get_breaks(self, filename, lineno): + filename = self.canonic(filename) + return QtcInternalBreakpoint.bplist.get((filename, lineno), []) + + def get_file_breaks(self, filename): + filename = self.canonic(filename) + return self.breaks.get(filename, []) + + def get_all_breaks(self): + return self.breaks + + # Derived classes and clients can call the following method + # to get a data structure representing a stack trace. + + def get_stack(self, frame, tb): + stack = [] + if tb and tb.tb_frame is frame: + tb = tb.tb_next + while frame is not None: + stack.append((frame, frame.f_lineno)) + if frame is self.botframe: + break + frame = frame.f_back + stack.reverse() + i = max(0, __builtins__.len(stack) - 1) + while tb is not None: + stack.append((tb.tb_frame, tb.tb_lineno)) + tb = tb.tb_next + if frame is None: + i = max(0, __builtins__.len(stack) - 1) + return stack, i + + # The following methods can be called by clients to use + # a debugger to debug a statement or an expression. + # Both can be given as a string, or a code object. + + def run(self, cmd, pyGlobals=None, pyLocals=None): + if pyGlobals is None: + import __main__ + pyGlobals = __main__.__dict__ + if pyLocals is None: + pyLocals = pyGlobals + self.reset() + if isinstance(cmd, __builtins__.str): + cmd = compile(cmd, '', 'exec') + sys.settrace(self.trace_dispatch) + try: + exec(cmd, pyGlobals, pyLocals) + except QuitException: + pass + finally: + self.quitting = True + sys.settrace(None) + + def runeval(self, expr, pyGlobals=None, pyLocals=None): + if pyGlobals is None: + import __main__ + pyGlobals = __main__.__dict__ + if pyLocals is None: + pyLocals = pyGlobals + self.reset() + sys.settrace(self.trace_dispatch) + try: + return eval(expr, pyGlobals, pyLocals) + except QuitException: + pass + finally: + self.quitting = True + sys.settrace(None) + + # This method is more useful to debug a single function call. + def runcall(self, func, *args, **kwds): + self.reset() + sys.settrace(self.trace_dispatch) + res = None + try: + res = func(*args, **kwds) + except QuitException: + pass + finally: + self.quitting = True + sys.settrace(None) + return res + + def cmdloop(self): + """Repeatedly accept input, parse an initial prefix + off the received input, and dispatch to action methods, passing them + the remainder of the line as argument. + """ + try: + stop = None + while not stop: + if self.cmdqueue: + line = self.cmdqueue.pop(0) + else: + if self.use_rawinput: + try: + if sys.version_info[0] == 2: + line = raw_input('') + else: + line = input('') + except EOFError: + line = 'EOF' + else: + self.stdout.write('') + self.stdout.flush() + line = self.stdin.readline() + if not line: + line = 'EOF' + else: + line = line.rstrip('\r\n') + print('LINE: %s' % line) + stop = self.onecmd(line) + finally: + pass + + def parseline(self, line): + """Parse the line into a command name and a string containing + the arguments. Returns a tuple containing (command, args, line). + 'command' and 'args' may be None if the line couldn't be parsed. + """ + line = line.strip() + if not line: + return None, None, line + elif line[0] == '?': + line = 'help ' + line[1:] + elif line[0] == '!': + if hasattr(self, 'do_shell'): + line = 'shell ' + line[1:] + else: + return None, None, line + i, length = 0, __builtins__.len(line) + while i < length and line[i] in self.identchars: + i = i + 1 + cmd, arg = line[:i], line[i:].strip() + return cmd, arg, line + + def onecmd(self, line): + """Interpret the argument as though it had been typed in response + to the prompt. + + The return value is a flag indicating whether interpretation of + commands by the interpreter should stop. + """ + line = __builtins__.str(line) + print('LINE 0: %s' % line) + cmd, arg, line = self.parseline(line) + print('LINE 1: %s' % line) + if cmd is None: + return self.default(line) + self.lastcmd = line + if line == 'EOF': + self.lastcmd = '' + if cmd == '': + return self.default(line) + else: + func = getattr(self, 'do_' + cmd, None) + if func: + return func(arg) + return self.default(line) + + def runit(self): + print('DIR: %s' % dir()) + if sys.argv[0] == '-c': + sys.argv = sys.argv[2:] + else: + sys.argv = sys.argv[1:] + print('ARGV: %s' % sys.argv) + mainpyfile = sys.argv[0] # Get script filename + sys.path.append(os.path.dirname(mainpyfile)) + print('MAIN: %s' % mainpyfile) + + while True: + try: + # The script has to run in __main__ namespace (or imports from + # __main__ will break). + # + # So we clear up the __main__ and set several special variables + # (this gets rid of pdb's globals and cleans old variables on restarts). + + import __main__ + # __main__.__dict__.clear() + __main__.__dict__.update({'__name__': '__main__', + '__file__': mainpyfile, + #'__builtins__': __builtins__, + }) + + # When bdb sets tracing, a number of call and line events happens + # BEFORE debugger even reaches user's code (and the exact sequence of + # events depends on python version). So we take special measures to + # avoid stopping before we reach the main script (see user_line and + # user_call for details). + self._wait_for_mainpyfile = True + self.mainpyfile = self.canonic(mainpyfile) + self._user_requested_quit = False + with open(mainpyfile, 'rb') as fp: + statement = "exec(compile(%r, %r, 'exec'))" % \ + (fp.read(), self.mainpyfile) + self.run(statement) + + if self._user_requested_quit: + break + print('The program finished') + sys.exit(0) + except SystemExit: + # In most cases SystemExit does not warrant a post-mortem session. + print('The program exited via sys.exit(). Exit status:') + print(sys.exc_info()[1]) + t = sys.exc_info()[2] + print('Post-mortem debugging is finished - ending debug session.') + sys.exit(0) + + except: + traceback.print_exc() + print('Uncaught exception. Entering post mortem debugging') + t = sys.exc_info()[2] + self.curframe_locals['__exception__'] = t + print('Post mortem debugger finished - ending debug session.') + sys.exit(0) + + def sigint_handler(self, signum, frame): + if self.allow_kbdint: + raise KeyboardInterrupt + self.report('state="stopped"') + self.set_step() + self.set_trace(frame) + # restore previous signal handler + signal.signal(signal.SIGINT, self._previous_sigint_handler) + + def forget(self): + self.stack = [] + self.curindex = 0 + self.curframe = None + + def setup(self, frame, tb): + self.forget() + self.stack, self.curindex = self.get_stack(frame, tb) + self.curframe = self.stack[self.curindex][0] + # The f_locals dictionary is updated from the actual frame + # locals whenever the .f_locals accessor is called, so we + # cache it here to ensure that modifications are not overwritten. + self.curframe_locals = self.curframe.f_locals + + def user_call(self, frame): + """This method is called when there is the remote possibility + that we ever need to stop in this function.""" + if self._wait_for_mainpyfile: + return + if self.stop_here(frame): + self.message('--Call--') + self.interaction(frame, None) + + def user_line(self, frame): + """This function is called when we stop or break at this line.""" + if self._wait_for_mainpyfile: + if (self.mainpyfile != self.canonic(frame.f_code.co_filename) + or frame.f_lineno <= 0): + return + self._wait_for_mainpyfile = False + if self.bp_commands(frame): + self.interaction(frame, None) + + def bp_commands(self, frame): + """Call every command that was set for the current active breakpoint + (if there is one). + + Returns True if the normal interaction function must be called, + False otherwise.""" + # self.currentbp is set in break_here if a breakpoint was hit + if getattr(self, 'currentbp', False) and self.currentbp in self.commands: + currentbp = self.currentbp + self.currentbp = 0 + lastcmd_back = self.lastcmd + self.setup(frame, None) + for line in self.commands[currentbp]: + self.onecmd(line) + self.lastcmd = lastcmd_back + self.forget() + return False + return True + + def user_return(self, frame, return_value): + """This function is called when a return trap is set here.""" + if self._wait_for_mainpyfile: + return + frame.f_locals['__return__'] = return_value + self.message('--Return--') + self.interaction(frame, None) + + def user_exception(self, frame, exc_info): + """This function is called if an exception occurs, + but only if we are to stop at or just below this level.""" + if self._wait_for_mainpyfile: + return + exc_type, exc_value, exc_traceback = exc_info + frame.f_locals['__exception__'] = exc_type, exc_value + + # An 'Internal StopIteration' exception is an exception debug event + # issued by the interpreter when handling a subgenerator run with + # 'yield from' or a generator controled by a for loop. No exception has + # actually occurred in this case. The debugger uses this debug event to + # stop when the debuggee is returning from such generators. + prefix = 'Internal ' if (not exc_traceback + and exc_type is StopIteration) else '' + self.message('%s%s' % (prefix, + traceback.format_exception_only(exc_type, exc_value)[-1].strip())) + self.interaction(frame, exc_traceback) + + def interaction(self, frame, tb): + if self.setup(frame, tb): + # no interaction desired at this time (happens if .pdbrc contains + # a command like 'continue') + self.forget() + return + + frame, lineNumber = self.stack[self.curindex] + fileName = self.canonic(frame.f_code.co_filename) + self.report('location={file="%s",line="%s"}' % (fileName, lineNumber)) + + while True: + try: + # keyboard interrupts allow for an easy way to cancel + # the current command, so allow them during interactive input + self.allow_kbdint = True + self.cmdloop() + self.allow_kbdint = False + break + except KeyboardInterrupt: + self.message('--KeyboardInterrupt--') + self.forget() + + def displayhook(self, obj): + """Custom displayhook for the exec in default(), which prevents + assignment of the _ variable in the builtins. + """ + # reproduce the behavior of the standard displayhook, not printing None + if obj is not None: + self.message(repr(obj)) + + def default(self, line): + if line[:1] == '!': + line = line[1:] + pyLocals = self.curframe_locals + pyGlobals = self.curframe.f_globals + try: + code = compile(line + '\n', '', 'single') + save_stdout = sys.stdout + save_stdin = sys.stdin + save_displayhook = sys.displayhook + try: + sys.stdin = self.stdin + sys.stdout = self.stdout + sys.displayhook = self.displayhook + exec(code, pyGlobals, pyLocals) + finally: + sys.stdout = save_stdout + sys.stdin = save_stdin + sys.displayhook = save_displayhook + except: + exc_info = sys.exc_info()[:2] + self.error(traceback.format_exception_only(*exc_info)[-1].strip()) + + @staticmethod + def message(msg): + print(msg) + + @staticmethod + def error(msg): + # print('***'+ msg) + pass + + def do_break(self, arg, temporary=0): + """b(reak) [ ([filename:]lineno | function) [, condition] ] + Without argument, list all breaks. + + With a line number argument, set a break at this line in the + current file. With a function name, set a break at the first + executable line of that function. If a second argument is + present, it is a string specifying an expression which must + evaluate to true before the breakpoint is honored. + + The line number may be prefixed with a filename and a colon, + to specify a breakpoint in another file (probably one that + hasn't been loaded yet). The file is searched for on + sys.path; the .py suffix may be omitted. + """ + if not arg: + if self.breaks: # There's at least one + self.message('Num Type Disp Enb Where') + for bp in QtcInternalBreakpoint.bpbynumber: + if bp: + self.message(bp.bpformat()) + return + # parse arguments; comma has lowest precedence + # and cannot occur in filename + filename = None + lineno = None + cond = None + comma = arg.find(',') + if comma > 0: + # parse stuff after comma: 'condition' + cond = arg[comma + 1:].lstrip() + arg = arg[:comma].rstrip() + # parse stuff before comma: [filename:]lineno | function + colon = arg.rfind(':') + funcname = None + if colon >= 0: + filename = arg[:colon].rstrip() + f = self.lookupmodule(filename) + if not f: + self.error('%r not found from sys.path' % filename) + return + else: + filename = f + arg = arg[colon + 1:].lstrip() + try: + lineno = int(arg) + except ValueError: + self.error('Bad lineno: %s' % arg) + return + else: + # no colon; can be lineno or function + try: + lineno = int(arg) + except ValueError: + try: + func = eval(arg, + self.curframe.f_globals, + self.curframe_locals) + except: + func = arg + try: + if hasattr(func, '__func__'): + func = func.__func__ + code = func.__code__ + # use co_name to identify the bkpt (function names + # could be aliased, but co_name is invariant) + funcname = code.co_name + lineno = code.co_firstlineno + filename = code.co_filename + except: + # last thing to try + (ok, filename, ln) = self.lineinfo(arg) + if not ok: + self.error('The specified object %r is not a function ' + 'or was not found along sys.path.' % arg) + return + funcname = ok # ok contains a function name + lineno = int(ln) + if not filename: + filename = self.defaultFile() + # Check for reasonable breakpoint + line = self.checkline(filename, lineno) + if line: + # now set the break point + err = self.set_break(filename, line, temporary, cond, funcname) + if err: + self.error(err) + else: + bp = self.get_breaks(filename, line)[-1] + self.message('Breakpoint %d at %s:%d' % + (bp.number, bp.file, bp.line)) + + # To be overridden in derived debuggers + def defaultFile(self): + """Produce a reasonable default.""" + filename = self.curframe.f_code.co_filename + if filename == '' and self.mainpyfile: + filename = self.mainpyfile + return filename + + def do_tbreak(self, arg): + """tbreak [ ([filename:]lineno | function) [, condition] ] + Same arguments as break, but sets a temporary breakpoint: it + is automatically deleted when first hit. + """ + self.do_break(arg, 1) + + def lineinfo(self, identifier): + failed = (None, None, None) + # Input is identifier, may be in single quotes + idstring = identifier.split("'") + if __builtins__.len(idstring) == 1: + # not in single quotes + tmp_id = idstring[0].strip() + elif __builtins__.len(idstring) == 3: + # quoted + tmp_id = idstring[1].strip() + else: + return failed + if tmp_id == '': + return failed + parts = tmp_id.split('.') + # Protection for derived debuggers + if parts[0] == 'self': + del parts[0] + if not parts: + return failed + # Best first guess at file to look at + fname = self.defaultFile() + if __builtins__.len(parts) == 1: + item = parts[0] + else: + # More than one part. + # First is module, second is method/class + f = self.lookupmodule(parts[0]) + if f: + fname = f + item = parts[1] + answer = find_function(item, fname) + return answer or failed + + def checkline(self, filename, lineno): + """Check whether specified line seems to be executable. + + Return `lineno` if it is, 0 if not (e.g. a docstring, comment, blank + line or EOF). Warning: testing is not comprehensive. + """ + # this method should be callable before starting debugging, so default + # to "no globals" if there is no current frame + globs = self.curframe.f_globals if hasattr(self, 'curframe') else None + line = linecache.getline(filename, lineno, globs) + if not line: + self.message('End of file') + return 0 + line = line.strip() + # Don't allow setting breakpoint at a blank line + if (not line or (line[0] == '#') or + (line[:3] == '"""') or line[:3] == "'''"): + self.error('Blank or comment') + return 0 + return lineno + + def do_enable(self, arg): + """enable bpnumber [bpnumber ...] + Enables the breakpoints given as a space separated list of + breakpoint numbers. + """ + args = arg.split() + for i in args: + try: + bp = self.get_bpbynumber(i) + except ValueError as err: + self.error(err) + else: + bp.enable() + self.message('Enabled %s' % bp) + + def do_disable(self, arg): + """disable bpnumber [bpnumber ...] + Disables the breakpoints given as a space separated list of + breakpoint numbers. Disabling a breakpoint means it cannot + cause the program to stop execution, but unlike clearing a + breakpoint, it remains in the list of breakpoints and can be + (re-)enabled. + """ + args = arg.split() + for i in args: + try: + bp = self.get_bpbynumber(i) + except ValueError as err: + self.error(err) + else: + bp.disable() + self.message('Disabled %s' % bp) + + def do_condition(self, arg): + """condition bpnumber [condition] + Set a new condition for the breakpoint, an expression which + must evaluate to true before the breakpoint is honored. If + condition is absent, any existing condition is removed; i.e., + the breakpoint is made unconditional. + """ + args = arg.split(' ', 1) + try: + cond = args[1] + except IndexError: + cond = None + try: + bp = self.get_bpbynumber(args[0].strip()) + except IndexError: + self.error('Breakpoint number expected') + except ValueError as err: + self.error(err) + else: + bp.cond = cond + if not cond: + self.message('Breakpoint %d is now unconditional.' % bp.number) + else: + self.message('New condition set for breakpoint %d.' % bp.number) + + def do_ignore(self, arg): + """ignore bpnumber [count] + Set the ignore count for the given breakpoint number. If + count is omitted, the ignore count is set to 0. A breakpoint + becomes active when the ignore count is zero. When non-zero, + the count is decremented each time the breakpoint is reached + and the breakpoint is not disabled and any associated + condition evaluates to true. + """ + args = arg.split() + try: + count = int(args[1].strip()) + except: + count = 0 + try: + bp = self.get_bpbynumber(args[0].strip()) + except IndexError: + self.error('Breakpoint number expected') + except ValueError as err: + self.error(err) + else: + bp.ignore = count + if count > 0: + if count > 1: + countstr = '%d crossings' % count + else: + countstr = '1 crossing' + self.message('Will ignore next %s of breakpoint %d.' % + (countstr, bp.number)) + else: + self.message('Will stop next time breakpoint %d is reached.' + % bp.number) + + def do_clear(self, arg): + """cl(ear) filename:lineno\ncl(ear) [bpnumber [bpnumber...]] + With a space separated list of breakpoint numbers, clear + those breakpoints. Without argument, clear all breaks (but + first ask confirmation). With a filename:lineno argument, + clear all breaks at that line in that file. + """ + if not arg: + try: + reply = input('Clear all breaks? ') + except EOFError: + reply = 'no' + reply = reply.strip().lower() + if reply in ('y', 'yes'): + bplist = [bp for bp in QtcInternalBreakpoint.bpbynumber if bp] + self.clear_all_breaks() + for bp in bplist: + self.message('Deleted %s' % bp) + return + if ':' in arg: + # Make sure it works for 'clear C:\foo\bar.py:12' + i = arg.rfind(':') + filename = arg[:i] + arg = arg[i + 1:] + try: + lineno = int(arg) + except ValueError: + err = 'Invalid line number (%s)' % arg + else: + bplist = self.get_breaks(filename, lineno) + err = self.clear_break(filename, lineno) + if err: + self.error(err) + else: + for bp in bplist: + self.message('Deleted %s' % bp) + return + numberlist = arg.split() + for i in numberlist: + try: + bp = self.get_bpbynumber(i) + except ValueError as err: + self.error(err) + else: + self.clear_bpbynumber(i) + self.message('Deleted %s' % bp) + + def do_until(self, arg): + """until [lineno] + Without argument, continue execution until the line with a + number greater than the current one is reached. With a line + number, continue execution until a line with a number greater + or equal to that is reached. In both cases, also stop when + the current frame returns. + """ + if arg: + try: + lineno = int(arg) + except ValueError: + self.error('Error in argument: %r' % arg) + return 0 + if lineno <= self.curframe.f_lineno: + self.error('"until" line number is smaller than current ' + 'line number') + return 0 + else: + lineno = None + lineno = self.curframe.f_lineno + 1 + self._set_stopinfo(self.curframe, self.curframe, lineno) + + return 1 + + def do_step(self, arg): + self.set_step() + return 1 + + def do_next(self, arg): + self._set_stopinfo(self.curframe, None) + return 1 + + def do_return(self, arg): + if self.curframe.f_code.co_flags & inspect.CO_GENERATOR: + self._set_stopinfo(self.curframe, None, -1) + else: + self._set_stopinfo(self.curframe.f_back, self.curframe) + return 1 + + def do_continue(self, arg): + """continue + Continue execution, only stop when a breakpoint is encountered. + """ + if not self.nosigint: + try: + self._previous_sigint_handler = \ + signal.signal(signal.SIGINT, self.sigint_handler) + except ValueError: + # ValueError happens when do_continue() is invoked from + # a non-main thread in which case we just continue without + # SIGINT set. Would printing a message here (once) make + # sense? + pass + self.set_continue() + return 1 + + def do_jump(self, arg): + """jump lineno + Set the next line that will be executed. Only available in + the bottom-most frame. This lets you jump back and execute + code again, or jump forward to skip code that you don't want + to run. + + It should be noted that not all jumps are allowed -- for + instance it is not possible to jump into the middle of a + for loop or out of a finally clause. + """ + if self.curindex + 1 != __builtins__.len(self.stack): + self.error('You can only jump within the bottom frame') + return + try: + arg = int(arg) + except ValueError: + self.error("The 'jump' command requires a line number") + else: + try: + # Do the jump, fix up our copy of the stack, and display the + # new position + self.curframe.f_lineno = arg + self.stack[self.curindex] = self.stack[self.curindex][0], arg + except ValueError as e: + self.error('Jump failed: %s' % e) + + def do_debug(self, arg): + """debug code + Enter a recursive debugger that steps through the code + argument (which is an arbitrary expression or statement to be + executed in the current environment). + """ + sys.settrace(None) + pyGlobals = self.curframe.f_globals + pyLocals = self.curframe_locals + p = QtcInternalDumper(self.stdin, self.stdout) + self.message('ENTERING RECURSIVE DEBUGGER') + sys.call_tracing(p.run, (arg, pyGlobals, pyLocals)) + self.message('LEAVING RECURSIVE DEBUGGER') + sys.settrace(self.trace_dispatch) + self.lastcmd = p.lastcmd + + def do_quit(self, arg): + """q(uit)\nexit + Quit from the debugger. The program being executed is aborted. + """ + self._user_requested_quit = True + self.set_quit() + return 1 + + def do_EOF(self, arg): + """EOF + Handles the receipt of EOF as a command. + """ + self.message('') + self._user_requested_quit = True + self.set_quit() + return 1 + + def do_args(self, arg): + """a(rgs) + Print the argument list of the current function. + """ + co = self.curframe.f_code + loc = self.curframe_locals + n = co.co_argcount + if co.co_flags & 4: + n = n + 1 + if co.co_flags & 8: + n = n + 1 + for i in range(n): + name = co.co_varnames[i] + if name in loc: + self.message('%s = %r' % (name, loc[name])) + else: + self.message('%s = *** undefined ***' % (name,)) + + def do_retval(self, arg): + """retval + Print the return value for the last return of a function. + """ + if '__return__' in self.curframe_locals: + self.message(repr(self.curframe_locals['__return__'])) + else: + self.error('Not yet returned!') + + def _getval(self, arg): + try: + return eval(arg, self.curframe.f_globals, self.curframe_locals) + except: + exc_info = sys.exc_info()[:2] + self.error(traceback.format_exception_only(*exc_info)[-1].strip()) + raise + + def _getval_except(self, arg, frame=None): + try: + if frame is None: + return eval(arg, self.curframe.f_globals, self.curframe_locals) + return eval(arg, frame.f_globals, frame.f_locals) + except: + exc_info = sys.exc_info()[:2] + err = traceback.format_exception_only(*exc_info)[-1].strip() + return _rstr('** raised %s **' % err) + + def do_whatis(self, arg): + """whatis arg + Print the type of the argument. + """ + try: + value = self._getval(arg) + except: + # _getval() already printed the error + return + code = None + # Is it a function? + try: + code = value.__code__ + except Exception: + pass + if code: + self.message('Function %s' % code.co_name) + return + # Is it an instance method? + try: + code = value.__func__.__code__ + except Exception: + pass + if code: + self.message('Method %s' % code.co_name) + return + # Is it a class? + if value.__class__ is type: + self.message('Class %s.%s' % (value.__module__, value.__name__)) + return + # None of the above... + self.message(__builtins__.type(value)) + + def do_interact(self, arg): + """interact + + Start an interactive interpreter whose global namespace + contains all the (global and local) names found in the current scope. + """ + ns = self.curframe.f_globals.copy() + ns.update(self.curframe_locals) + code.interact('*interactive*', local=ns) + + def lookupmodule(self, filename): + """Helper function for break/clear parsing -- may be overridden. + + lookupmodule() translates (possibly incomplete) file or module name + into an absolute file name. + """ + if os.path.isabs(filename) and os.path.exists(filename): + return filename + f = os.path.join(sys.path[0], filename) + if os.path.exists(f) and self.canonic(f) == self.mainpyfile: + return f + ext = os.path.splitext(filename)[1] + if ext == '': + filename = filename + '.py' + if os.path.isabs(filename): + return filename + for dirname in sys.path: + while os.path.islink(dirname): + dirname = os.readlink(dirname) + fullname = os.path.join(dirname, filename) + if os.path.exists(fullname): + return fullname + return None + + def evaluateTooltip(self, args): + self.updateData(args) + + def updateData(self, args): + self.expandedINames = args.get('expanded', {}) + self.typeformats = args.get('typeformats', {}) + self.formats = args.get('formats', {}) + self.output = '' + + frameNr = args.get('frame', 0) + if frameNr == -1: + frameNr = 0 + + frame_lineno = self.stack[-1 - frameNr] + frame = frame_lineno[0] + + self.output += 'data={' + for var in frame.f_locals.keys(): + if var in ('__file__', '__name__', '__package__', '__spec__', + '__doc__', '__loader__', '__cached__', '__the_dumper__', + '__annotations__', 'QtcInternalBreakpoint', 'QtcInternalDumper'): + continue + value = frame.f_locals[var] + # this applies only for anonymous arguments + # e.g. def dummy(var, (width, height), var2) would create an anonymous local var + # named '.1' for (width, height) as this is the second argument + if var.startswith('.'): + var = '@arg' + var[1:] + self.dumpValue(value, var, 'local.%s' % var) + + for watcher in args.get('watchers', []): + iname = watcher['iname'] + exp = self.hexdecode(watcher['exp']) + exp = __builtins__.str(exp).strip() + escapedExp = self.hexencode(exp) + self.put('{') + self.putField('iname', iname) + self.putField('wname', escapedExp) + try: + res = eval(exp, {}, frame.f_locals) + self.putValue(res) + except: + self.putValue('') + self.put('}') + # self.dumpValue(eval(value), escapedExp, iname) + + self.output += '}' + self.output += '{frame="%s"}' % frameNr + self.flushOutput() + + def flushOutput(self): + sys.stdout.write('@\n' + self.output + '@\n') + sys.stdout.flush() + self.output = '' + + def put(self, value): + # sys.stdout.write(value) + self.output += value + + def putField(self, name, value): + self.put('%s="%s",' % (name, value)) + + def putItemCount(self, count): + self.put('value="<%s items>",numchild="%s",' % (count, count)) + + @staticmethod + def cleanType(typename): + t = __builtins__.str(typename) + if t.startswith(""): + t = t[7:-2] + if t.startswith(""): + t = t[8:-2] + return t + + def putType(self, typeName, priority=0): + self.putField('type', QtcInternalDumper.cleanType(typeName)) + + def putNumChild(self, numchild): + self.put('numchild="%s",' % numchild) + + def putValue(self, value, encoding=None, priority=0): + self.putField('value', value) + + def putName(self, name): + self.put('name="%s",' % name) + + def isExpanded(self, iname): + # DumperBase.warn('IS EXPANDED: %s in %s' % (iname, self.expandedINames)) + if iname.startswith('None'): + raise "Illegal iname '%s'" % iname + # DumperBase.warn(' --> %s' % (iname in self.expandedINames)) + return iname in self.expandedINames + + def isExpandedIName(self, iname): + return iname in self.expandedINames + + def itemFormat(self, item): + form = self.formats.get(__builtins__.str(QtcInternalDumper.cleanAddress(item.value.address))) + if form is None: + form = self.typeformats.get(__builtins__.str(item.value.type)) + return form + + def dumpValue(self, value, name, iname): + t = __builtins__.type(value) + tt = QtcInternalDumper.cleanType(t) + valueStr = __builtins__.str(value) + if tt == 'module' or tt == 'function': + return + if valueStr.startswith("= 3: + vals = value.items() + else: + vals = value.iteritems() + for (k, v) in vals: + self.put('{') + self.putType(' ') + self.putValue('%s: %s' % (k, v)) + if self.isExpanded(iname): + self.put('children=[') + self.dumpValue(k, 'key', '%s.%d.k' % (iname, i)) + self.dumpValue(v, 'value', '%s.%d.v' % (iname, i)) + self.put(']') + self.put('},') + i += 1 + self.put(']') + elif tt == 'class': + pass + elif tt == 'module': + pass + elif tt == 'function': + pass + elif valueStr.startswith(' 1: + self.putValue('@' + v[p + 11:-1]) + self.putType(v[1:p]) + else: + p = v.find(' instance at ') + if p > 1: + self.putValue('@' + v[p + 13:-1]) + self.putType(v[1:p]) + else: + self.putType(tt) + self.putValue(v) + if self.isExpanded(iname): + self.put('children=[') + for child in dir(value): + if child in ('__dict__', '__doc__', '__module__'): + continue + attr = getattr(value, child) + if callable(attr): + continue + try: + self.dumpValue(attr, child, '%s.%s' % (iname, child)) + except: + pass + self.put('],') + self.put('},') + + def warn(self, msg): + self.putField('warning', msg) + + def listModules(self, args): + self.put('modules=[') + for name in sys.modules: + self.put('{') + self.putName(name) + self.putValue(sys.modules[name]) + self.put('},') + self.put(']') + self.flushOutput() + + def listSymbols(self, args): + moduleName = args['module'] + module = sys.modules.get(moduleName, None) + self.put("symbols={module='%s',symbols='%s'}" + % (module, dir(module) if module else [])) + self.flushOutput() + + def assignValue(self, args): + exp = args['expression'] + value = args['value'] + cmd = '%s=%s' % (exp, value) + eval(cmd, {}) + self.put('CMD: "%s"' % cmd) + self.flushOutput() + + def stackListFrames(self, args): + # result = 'stack={current-thread="%s"' % thread.GetThreadID() + result = 'stack={current-thread="%s"' % 1 + + result += ',frames=[' + try: + level = 0 + frames = __builtins__.list(reversed(self.stack)) + frames = frames[:-2] # Drop "pdbbridge" and "" levels + for frame_lineno in frames: + frame, lineno = frame_lineno + filename = self.canonic(frame.f_code.co_filename) + level += 1 + result += '{' + result += 'file="%s",' % filename + result += 'line="%s",' % lineno + result += 'level="%s",' % level + result += '}' + except KeyboardInterrupt: + pass + result += ']' + + # result += ',hasmore="%d"' % isLimited + # result += ',limit="%d"' % limit + result += '}' + self.report(result) + + @staticmethod + def report(stuff): + sys.stdout.write('@\n' + stuff + '@\n') + sys.stdout.flush() + + +def qdebug(cmd, args): + global __the_dumper__ + method = getattr(__the_dumper__, cmd) + method(args) + + +__the_dumper__ = QtcInternalDumper() +__the_dumper__.runit() diff --git a/share/qtcreator/debugger/python2/personaltypes.py b/share/qtcreator/debugger/python2/personaltypes.py new file mode 100644 index 00000000000..291922b8fa2 --- /dev/null +++ b/share/qtcreator/debugger/python2/personaltypes.py @@ -0,0 +1,40 @@ +# Copyright (C) 2016 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +# This is a place to add your own dumpers for testing purposes. +# Any contents here will be picked up by GDB, LLDB, and CDB based +# debugging in Qt Creator automatically. + +# NOTE: This file will get overwritten when updating Qt Creator. +# +# To add dumpers that don't get overwritten, copy this file here +# to a safe location outside the Qt Creator installation and +# make this location known to Qt Creator using the Debugger > +# Locals & Expressions > Extra Debugging Helpers setting. + +# Example to display a simple type +# template struct MapNode +# { +# U key; +# V data; +# } +# +# def qdump__MapNode(d, value): +# d.putValue("This is the value column contents") +# d.putExpandable() +# if d.isExpanded(): +# with Children(d): +# # Compact simple case. +# d.putSubItem("key", value["key"]) +# # Same effect, with more customization possibilities. +# with SubItem(d, "data") +# d.putItem("data", value["data"]) + +# Check http://doc.qt.io/qtcreator/creator-debugging-helpers.html +# for more details or look at qttypes.py, stdtypes.py, boosttypes.py +# for more complex examples. + +from dumper import Children, SubItem, UnnamedSubItem, DumperBase +from utils import DisplayFormat, TypeCode + +######################## Your code below ####################### diff --git a/share/qtcreator/debugger/python2/qttypes.py b/share/qtcreator/debugger/python2/qttypes.py new file mode 100644 index 00000000000..b08f60a9bb3 --- /dev/null +++ b/share/qtcreator/debugger/python2/qttypes.py @@ -0,0 +1,3674 @@ +# Copyright (C) 2016 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import platform +import struct +import re +from dumper import Children, SubItem, UnnamedSubItem, toInteger, DumperBase +from utils import DisplayFormat, TypeCode + + +def qdump__QAtomicInt(d, value): + d.putValue(value.integer()) + + +def qdump__QBasicAtomicInt(d, value): + d.putValue(value.integer()) + + +def qdump__QAtomicPointer(d, value): + d.putItem(value.cast(value.type[0].pointer())) + d.putBetterType(value.type) + + +def qform__QByteArray(): + return [DisplayFormat.Latin1String, DisplayFormat.SeparateLatin1String, + DisplayFormat.Utf8String, DisplayFormat.SeparateUtf8String] + + +def qedit__QByteArray(d, value, data): + d.call('void', value, 'resize', str(len(data))) + (base, size, alloc) = d.stringData(value) + d.setValues(base, 'char', [ord(c) for c in data]) + + +def qdump__QByteArray(d, value): + if d.qtVersion() >= 0x60000: + dd, data, size = value.split('ppi') + if dd: + _, _, alloc = d.split('iii', dd) + else: # fromRawData + alloc = size + else: + data, size, alloc = d.qArrayData(value) + + d.check(alloc == 0 or (0 <= size and size <= alloc and alloc <= 100000000)) + if size > 0: + d.putExpandable() + + elided, shown = d.computeLimit(size, d.displayStringLimit) + p = d.readMemory(data, shown) + + displayFormat = d.currentItemFormat() + if displayFormat == DisplayFormat.Automatic or displayFormat == DisplayFormat.Latin1String: + d.putValue(p, 'latin1', elided=elided) + elif displayFormat == DisplayFormat.SeparateLatin1String: + d.putValue(p, 'latin1', elided=elided) + d.putDisplay('latin1:separate', d.encodeByteArray(value, limit=100000)) + elif displayFormat == DisplayFormat.Utf8String: + d.putValue(p, 'utf8', elided=elided) + elif displayFormat == DisplayFormat.SeparateUtf8String: + d.putValue(p, 'utf8', elided=elided) + d.putDisplay('utf8:separate', d.encodeByteArray(value, limit=100000)) + if d.isExpanded(): + d.putArrayData(data, size, d.charType()) + + +#def qdump__QArrayData(d, value): +# data, size, alloc = d.qArrayDataHelper(value.address()) +# d.check(alloc == 0 or (0 <= size and size <= alloc and alloc <= 100000000)) +# d.putValue(d.readMemory(data, size), 'latin1') +# d.putPlainChildren(value) + + +#def qdump__QByteArrayData(d, value): +# qdump__QArrayData(d, value) + + +def qdump__QBitArray(d, value): + if d.qtVersion() >= 0x60000: + _, data, basize = value.split('ppi') + else: + data, basize, _ = d.qArrayData(value['d']) + unused = d.extractByte(data) if data else 0 + size = basize * 8 - unused + d.putItemCount(size) + if d.isExpanded(): + with Children(d, size, maxNumChild=10000): + for i in d.childRange(): + q = data + 1 + int(i / 8) + with SubItem(d, i): + d.putValue((int(d.extractPointer(q)) >> (i % 8)) & 1) + d.putType('bool') + + +def qdump__QChar(d, value): + d.putValue(d.extractUShort(value)) + + +def qform_X_QAbstractItemModel(): + return [DisplayFormat.Simple, DisplayFormat.Enhanced] + + +def qdump_X_QAbstractItemModel(d, value): + displayFormat = d.currentItemFormat() + if displayFormat == DisplayFormat.Simple: + d.putPlainChildren(value) + return + #displayFormat == Enhanced: + # Create a default-constructed QModelIndex on the stack. + try: + ri = d.pokeValue(d.qtNamespace() + 'QModelIndex', '-1, -1, 0, 0') + this_ = d.makeExpression(value) + ri_ = d.makeExpression(ri) + rowCount = int(d.parseAndEvaluate('%s.rowCount(%s)' % (this_, ri_))) + columnCount = int(d.parseAndEvaluate('%s.columnCount(%s)' % (this_, ri_))) + except: + d.putPlainChildren(value) + return + d.putValue('%d x %d' % (rowCount, columnCount)) + d.putNumChild(rowCount * columnCount) + if d.isExpanded(): + with Children(d, numChild=rowCount * columnCount, childType=ri.type): + i = 0 + for row in range(rowCount): + for column in range(columnCount): + with SubItem(d, i): + d.putName('[%s, %s]' % (row, column)) + mi = d.parseAndEvaluate('%s.index(%d,%d,%s)' + % (this_, row, column, ri_)) + d.putItem(mi) + i = i + 1 + #gdb.execute('call free($ri)') + + +def qform_X_QModelIndex(): + return [DisplayFormat.Simple, DisplayFormat.Enhanced] + + +def qdump_X_QModelIndex(d, value): + displayFormat = d.currentItemFormat() + if displayFormat == DisplayFormat.Simple: + d.putPlainChildren(value) + return + r = value['r'] + c = value['c'] + try: + p = value['p'] + except: + p = value['i'] + m = value['m'] + if m.pointer() == 0 or r < 0 or c < 0: + d.putValue('(invalid)') + d.putPlainChildren(value) + return + + mm = m.dereference() + mm = mm.cast(mm.type.unqualified()) + ns = d.qtNamespace() + try: + mi = d.pokeValue(ns + 'QModelIndex', '%s,%s,%s,%s' % (r, c, p, m)) + mm_ = d.makeExpression(mm) + mi_ = d.makeExpression(mi) + rowCount = int(d.parseAndEvaluate('%s.rowCount(%s)' % (mm_, mi_))) + columnCount = int(d.parseAndEvaluate('%s.columnCount(%s)' % (mm_, mi_))) + except: + d.putPlainChildren(value) + return + + try: + # Access DisplayRole as value + val = d.parseAndEvaluate('%s.data(%s, 0)' % (mm_, mi_)) + v = val['d']['data']['ptr'] + d.putStringValue(d.pokeValue(ns + 'QString', v)) + except: + d.putValue('') + + d.putExpandable() + if d.isExpanded(): + with Children(d): + d.putFields(value, False) + i = 0 + for row in range(rowCount): + for column in range(columnCount): + with UnnamedSubItem(d, i): + d.putName('[%s, %s]' % (row, column)) + mi2 = d.parseAndEvaluate('%s.index(%d,%d,%s)' + % (mm_, row, column, mi_)) + d.putItem(mi2) + i = i + 1 + d.putCallItem('parent', '@QModelIndex', value, 'parent') + #gdb.execute('call free($mi)') + + +def qdump__Qt__ItemDataRole(d, value): + d.putEnumValue(value.integer(), { + 0: "Qt::DisplayRole", + 1: "Qt::DecorationRole", + 2: "Qt::EditRole", + 3: "Qt::ToolTipRole", + 4: "Qt::StatusTipRole", + 5: "Qt::WhatsThisRole", + 6: "Qt::FontRole", + 7: "Qt::TextAlignmentRole", + # obsolete: 8 : "Qt::BackgroundColorRole", + 8: "Qt::BackgroundRole", + # obsolete: 9 : "Qt::TextColorRole", + 9: "Qt::ForegroundRole", + 10: "Qt::CheckStateRole", + 11: "Qt::AccessibleTextRole", + 12: "Qt::AccessibleDescriptionRole", + 13: "Qt::SizeHintRole", + 14: "Qt::InitialSortOrderRole", + # 27-31 Qt4 ItemDataRoles + 27: "Qt::DisplayPropertyRole", + 28: "Qt::DecorationPropertyRole", + 29: "Qt::ToolTipPropertyRole", + 30: "Qt::StatusTipPropertyRole", + 31: "Qt::WhatsThisPropertyRole", + 0x100: "Qt::UserRole" + }) + + +def qdump__QStandardItemData(d, value): + d.createType('@Qt::ItemDataRole') # warm up cache + role, pad, val = value.split('{@Qt::ItemDataRole}@{@QVariant}') + d.putPairContents(role.value(), (role, val), 'role', 'value') + + +def qdump__QStandardItem(d, value): + d.createType('@QStandardItemData') # warm up cache + d.createType('@QStandardItem') + d.createType('@QStandardItem*') + + vtable, dptr = value.split('pp') + if d.qtVersion() >= 0x060000: + model, parent, values, children, rows, cols, item = \ + d.split('pp{@QList<@QStandardItemData>}{@QList<@QStandardItem *>}IIp', dptr) + else: + # There used to be a virtual destructor that got removed in + # 88b6abcebf29b455438 on Apr 18 17:01:22 2017 + if d.qtVersion() < 0x050900 and not d.isMsvcTarget(): + dptr += d.ptrSize(); + model, parent, values, children, rows, cols, item = \ + d.split('pp{@QVector<@QStandardItemData>}{@QVector<@QStandardItem *>}IIp', dptr) + + d.putEmptyValue() + d.putExpandable() + if d.isExpanded(): + with Children(d): + d.putSubItem('[model]', d.createValue(model, '@QStandardItemModel')) + d.putSubItem('[values]', values) + d.putSubItem('[children]', children) + + +def qdump__QDate(d, value): + jd = value.pointer() + if not jd: + d.putValue('(invalid)') + return + d.putValue(jd, 'juliandate') + d.putExpandable() + if d.isExpanded(): + with Children(d): + if d.canCallLocale(): + d.putCallItem('toString', '@QString', value, 'toString', + d.enumExpression('DateFormat', 'TextDate')) + d.putCallItem('(ISO)', '@QString', value, 'toString', + d.enumExpression('DateFormat', 'ISODate')) + if d.qtVersion() < 0x060000: + d.putCallItem('(SystemLocale)', '@QString', value, 'toString', + d.enumExpression('DateFormat', 'SystemLocaleDate')) + d.putCallItem('(Locale)', '@QString', value, 'toString', + d.enumExpression('DateFormat', 'LocaleDate')) + d.putFields(value) + + +def qdump__QTime(d, value): + mds = value.split('i')[0] + if mds == -1: + d.putValue('(invalid)') + return + d.putValue(mds, 'millisecondssincemidnight') + if d.isExpanded(): + with Children(d): + d.putCallItem('toString', '@QString', value, 'toString', + d.enumExpression('DateFormat', 'TextDate')) + d.putCallItem('(ISO)', '@QString', value, 'toString', + d.enumExpression('DateFormat', 'ISODate')) + if d.canCallLocale() and d.qtVersion() < 0x060000: + d.putCallItem('(SystemLocale)', '@QString', value, 'toString', + d.enumExpression('DateFormat', 'SystemLocaleDate')) + d.putCallItem('(Locale)', '@QString', value, 'toString', + d.enumExpression('DateFormat', 'LocaleDate')) + d.putFields(value) + + +def qdump__QTimeZone(d, value): + base = d.extractPointer(value) + if base == 0: + d.putValue('(null)') + return + idAddr = base + 2 * d.ptrSize() # [QSharedData] + [vptr] + d.putByteArrayValue(idAddr) + d.putPlainChildren(value['d']) + + +def qdump__QDateTime(d, value): + qtVersion = d.qtVersion() + isValid = False + # This relies on the Qt4/Qt5 internal structure layout: + # {sharedref(4), ... + base = d.extractPointer(value) + is32bit = d.ptrSize() == 4 + if qtVersion >= 0x050200: + tiVersion = d.qtTypeInfoVersion() + #DumperBase.warn('TI VERSION: %s' % tiVersion) + if tiVersion is None: + tiVersion = 4 + if tiVersion > 10: + status = d.extractByte(value) + #DumperBase.warn('STATUS: %s' % status) + if status & 0x01: + # Short data + msecs = d.extractUInt64(value) >> 8 + spec = (status & 0x30) >> 4 + offsetFromUtc = 0 + timeZone = 0 + isValid = status & 0x08 + else: + dptr = d.extractPointer(value) + (_, status, msecs, offsetFromUtc, _, timeZone) = d.split('iIqII{QTimeZone}', dptr) + spec = (status & 0x30) >> 4 + isValid = True + tzD = d.extractPointer(timeZone) + if tzD == 0: + timeZone = 'UTC' + else: + idAddr = tzD + 2 * d.ptrSize() + tzBa = d.encodeByteArray(idAddr, limit=100) + timeZone = tzBa + d.putValue( + '%s/%s/%s/%s/%s/%s' % + (msecs, + spec, + offsetFromUtc, + timeZone, + status, + tiVersion), + 'datetimeinternal') + else: + if d.isWindowsTarget(): + msecsOffset = 8 + specOffset = 16 + offsetFromUtcOffset = 20 + timeZoneOffset = 24 + statusOffset = 28 if is32bit else 32 + else: + msecsOffset = 4 if is32bit else 8 + specOffset = 12 if is32bit else 16 + offsetFromUtcOffset = 16 if is32bit else 20 + timeZoneOffset = 20 if is32bit else 24 + statusOffset = 24 if is32bit else 32 + status = d.extractInt(base + statusOffset) + if int(status & 0x0c == 0x0c): # ValidDate and ValidTime + isValid = True + msecs = d.extractInt64(base + msecsOffset) + spec = d.extractInt(base + specOffset) + offset = d.extractInt(base + offsetFromUtcOffset) + tzp = d.extractPointer(base + timeZoneOffset) + if tzp == 0: + tz = '' + else: + idBase = tzp + 2 * d.ptrSize() # [QSharedData] + [vptr] + tz = d.encodeByteArray(idBase, limit=100) + d.putValue('%s/%s/%s/%s/%s/%s' % (msecs, spec, offset, tz, status, 0), + 'datetimeinternal') + else: + # This relies on the Qt4/Qt5 internal structure layout: + # {sharedref(4), date(8), time(4+x)} + # QDateTimePrivate: + # - QAtomicInt ref; (padded on 64 bit) + # - [QDate date;] + # - - uint jd in Qt 4, qint64 in Qt 5.0 and Qt 5.1; padded on 64 bit + # - [QTime time;] + # - - uint mds; + # - Spec spec; + dateSize = 8 if qtVersion >= 0x050000 else 4 # Qt5: qint64, Qt4 uint + # 4 byte padding after 4 byte QAtomicInt if we are on 64 bit and QDate is 64 bit + refPlusPadding = 8 if qtVersion >= 0x050000 and d.ptrSize() == 8 else 4 + dateBase = base + refPlusPadding + timeBase = dateBase + dateSize + mds = d.extractInt(timeBase) + isValid = mds > 0 + if isValid: + jd = d.extractInt(dateBase) + d.putValue('%s/%s' % (jd, mds), 'juliandateandmillisecondssincemidnight') + + if not isValid: + d.putValue('(invalid)') + return + + d.putExpandable() + if d.isExpanded(): + with Children(d): + d.putCallItem('toTime_t', 'unsigned int', value, 'toTime_t') + if d.canCallLocale(): + d.putCallItem('toString', '@QString', value, 'toString', + d.enumExpression('DateFormat', 'TextDate')) + d.putCallItem('(ISO)', '@QString', value, 'toString', + d.enumExpression('DateFormat', 'ISODate')) + d.putCallItem('toUTC', '@QDateTime', value, 'toTimeSpec', + d.enumExpression('TimeSpec', 'UTC')) + if d.qtVersion() < 0x060000: + d.putCallItem('(SystemLocale)', '@QString', value, 'toString', + d.enumExpression('DateFormat', 'SystemLocaleDate')) + d.putCallItem('(Locale)', '@QString', value, 'toString', + d.enumExpression('DateFormat', 'LocaleDate')) + d.putCallItem('toLocalTime', '@QDateTime', value, 'toTimeSpec', + d.enumExpression('TimeSpec', 'LocalTime')) + d.putFields(value) + + +def qdump__QDir(d, value): + d.putExpandable() + privAddress = d.extractPointer(value) + bit32 = d.ptrSize() == 4 + + # change fc3942114da adds FileCache + # QStringList nameFilters; + # QDir::SortFlags sort; + # QDir::Filters filters; + # std::unique_ptr fileEngine; + # QFileSystemEntry dirEntry; + # struct FileCache + # { + # QMutex mutex; + # QStringList files; + # QFileInfoList fileInfos; + # std::atomic fileListsInitialized = false; + # QFileSystemEntry absoluteDirEntry; + # QFileSystemMetaData metaData; + # }; + # mutable FileCache fileCache; + + + # Change 9fc0965 reorders members again. + # bool fileListsInitialized + # QStringList files + # QFileInfoList fileInfos + # QStringList nameFilters + # QDir::SortFlags sort + # QDir::Filters filters + + # Before 9fc0965: + # QDirPrivate: + # QAtomicInt ref + # QStringList nameFilters; + # QDir::SortFlags sort; + # QDir::Filters filters; + # // qt3support: + # QChar filterSepChar; + # bool matchAllDirs; + # // end qt3support + # QScopedPointer fileEngine; + # bool fileListsInitialized; + # QStringList files; + # QFileInfoList fileInfos; + # QFileSystemEntry dirEntry; + # QFileSystemEntry absoluteDirEntry; + + # QFileSystemEntry: + # QString m_filePath + # QByteArray m_nativeFilePath + # qint16 m_lastSeparator + # qint16 m_firstDotInFileName + # qint16 m_lastDotInFileName + # + 2 byte padding + fileSystemEntrySize = 2 * d.ptrSize() + 8 + + if d.qtVersion() >= 0x060600: + case = 3 + elif d.qtVersion() >= 0x060000: + case = 2 + elif d.qtVersion() >= 0x050300: + case = 1 + elif d.qtVersion() < 0x050200: + case = 0 + else: + # Try to distinguish bool vs QStringList at the first item + # after the (padded) refcount. If it looks like a bool assume + # this is after 9fc0965. This is not safe. + firstValue = d.extractInt(privAddress + d.ptrSize()) + case = 1 if firstValue == 0 or firstValue == 1 else 0 + + if case == 3: + if bit32: + dirEntryOffset = 24 + fileCacheOffset = 52 + filesOffset = fileCacheOffset + 4 + fileInfosOffset = fileCacheOffset + 16 + absoluteDirEntryOffset = fileCacheOffset + 32 + else: + dirEntryOffset = 48 + fileCacheOffset = 104 + filesOffset = fileCacheOffset + 8 + fileInfosOffset = fileCacheOffset + 32 + absoluteDirEntryOffset = fileCacheOffset + 64 + elif case == 2: + if bit32: + filesOffset = 4 + fileInfosOffset = 16 + dirEntryOffset = 40 + absoluteDirEntryOffset = 72 + else: + filesOffset = 8 + fileInfosOffset = 32 + dirEntryOffset = 96 + absoluteDirEntryOffset = 152 + elif case == 1: + if bit32: + filesOffset = 4 + fileInfosOffset = 8 + dirEntryOffset = 32 + absoluteDirEntryOffset = 48 + else: + filesOffset = 8 + fileInfosOffset = 16 + dirEntryOffset = 48 + absoluteDirEntryOffset = 72 + else: + # Assume this is before 9fc0965. + qt3support = d.isQt3Support() + qt3SupportAddition = d.ptrSize() if qt3support else 0 + filesOffset = (24 if bit32 else 40) + qt3SupportAddition + fileInfosOffset = filesOffset + d.ptrSize() + dirEntryOffset = fileInfosOffset + d.ptrSize() + absoluteDirEntryOffset = dirEntryOffset + fileSystemEntrySize + + d.putStringValue(privAddress + dirEntryOffset) + if d.isExpanded(): + with Children(d): + if not d.isMsvcTarget(): + ns = d.qtNamespace() + try: + d.call('int', value, 'count') # Fill cache. + except: + pass + + #d.putCallItem('absolutePath', '@QString', value, 'absolutePath') + #d.putCallItem('canonicalPath', '@QString', value, 'canonicalPath') + with SubItem(d, 'absolutePath'): + typ = d.lookupType(ns + 'QString') + d.putItem(d.createValue(privAddress + absoluteDirEntryOffset, typ)) + with SubItem(d, 'entryInfoList'): + typ = d.lookupType(ns + 'QFileInfo') + qdumpHelper_QList(d, privAddress + fileInfosOffset, typ) + with SubItem(d, 'entryList'): + typ = d.lookupType(ns + 'QStringList') + d.putItem(d.createValue(privAddress + filesOffset, typ)) + d.putFields(value) + + +def qdump__QEvent(d, value): + d.putExpandable() + if d.isExpanded(): + with Children(d): + # Add a sub-item with the event type. + with SubItem(d, '[type]'): + (vtable, privateD, t, flags) = value.split("pp{short}{short}") + event_type_name = d.qtNamespace() + "QEvent::Type" + type_value = t.cast(event_type_name) + d.putValue(type_value.displayEnum('0x%04x', bitsize=16)) + d.putType(event_type_name) + + # Show the rest of the class fields as usual. + d.putFields(value) + + +def qdump__QKeyEvent(d, value): + # QEvent fields + # virtual table pointer + # QEventPrivate *d; + # ushort t; + # ushort posted : 1; + # ushort spont : 1; + # ushort m_accept : 1; + # ushort reserved : 13; + # QInputEvent fields + # Qt::KeyboardModifiers modState; + # ulong ts; + # QKeyEvent fields + # QString txt; + # int k; + # quint32 nScanCode; + # quint32 nVirtualKey; + # quint32 nModifiers; <- nativeModifiers + # ushort c; + # ushort autor:1; + # ushort reserved:15; + (vtable, privateD, t, flags, modState, ts, txt, k, scanCode, + virtualKey, modifiers, + c, autor) = value.split("ppHHiQ{@QString}{int}IIIHH") + + #d.putStringValue(txt) + #data = d.encodeString(txt) + key_txt_utf8 = d.encodeStringUtf8(txt) + + k_type_name = d.qtNamespace() + "Qt::Key" + k_cast_to_enum_value = k.cast(k_type_name) + k_name = k_cast_to_enum_value.displayEnum(bitsize=32) + matches = re.search(r'Key_(\w+)', k_name) + if matches: + k_name = matches.group(1) + + if t == 6: + key_event_type = "Pressed" + elif t == 7: + key_event_type = "Released" + else: + key_event_type = "" + + data = "" + + if key_event_type: + data += "{} ".format(key_event_type) + + # Try to use the name of the enum value, otherwise the value + # of txt in QKeyEvent. + if k_name: + data += "'{}'".format(k_name) + elif key_txt_utf8: + data += "'{}'".format(key_txt_utf8) + else: + data += "" + + k_int = k.integer() + data += " (key:{} vKey:{}".format(k_int, virtualKey) + + modifier_list = [] + modifier_list.append(("Shift", 0x02000000)) + modifier_list.append(("Control", 0x04000000)) + modifier_list.append(("Alt", 0x08000000)) + modifier_list.append(("Meta", 0x10000000)) + # modifier_map.append(("KeyPad", 0x20000000)) Is this useful? + modifier_list.append(("Grp", 0x40000000)) + + modifiers = [] + for modifier_name, mask in modifier_list: + if modState & mask: + modifiers.append(modifier_name) + + if modifiers: + data += " mods:" + "+".join(modifiers) + + data += ")" + + d.putValue(d.hexencode(data), 'utf8') + + d.putExpandable() + if d.isExpanded(): + with Children(d): + # Add a sub-item with the enum name and value. + with SubItem(d, '[{}]'.format(k_type_name)): + k_cast_to_enum_value = k.cast(k_type_name) + d.putValue(k_cast_to_enum_value.displayEnum('0x%04x', bitsize=32)) + d.putType(k_type_name) + + # Show the rest of the class fields as usual. + d.putFields(value, dumpBase=True) + + +def qdump__QKeySequence(d, value): + dd = d.extractPointer(value) + _, k0, k1, k2, k3 = d.split('iiiii', dd) + d.putValue("(0x%x, 0x%x, 0x%x, 0x%x)" % (k0, k1, k2, k3)); + d.putPlainChildren(value) + + +def qdump__QFile(d, value): + # 9fc0965 and a373ffcd change the layout of the private structure + qtVersion = d.qtVersion() + is32bit = d.ptrSize() == 4 + # FIXME: values 0 are wrong. As the file name is the + # only direct member of QFilePrivate, the offsets are + # equal to sizeof(QFileDevicePrivate), the base class. + if qtVersion >= 0x060300 and d.qtTypeInfoVersion() >= 22: + if d.isWindowsTarget(): + if d.isMsvcTarget(): + offset = 0 if is32bit else 424 + else: + offset = 0 if is32bit else 424 + else: + offset = 300 if is32bit else 424 + elif qtVersion >= 0x060000 and d.qtTypeInfoVersion() >= 20: + if d.isWindowsTarget(): + if d.isMsvcTarget(): + offset = 0 if is32bit else 304 + else: + offset = 0 if is32bit else 304 + else: + offset = 196 if is32bit else 304 + elif qtVersion >= 0x050600 and d.qtTypeInfoVersion() >= 17: + # Some QRingBuffer member got removed in 8f92baf5c9 + if d.isWindowsTarget(): + if d.isMsvcTarget(): + offset = 164 if is32bit else 224 + else: + offset = 160 if is32bit else 224 + else: + offset = 156 if is32bit else 224 + elif qtVersion >= 0x050700: + if d.isWindowsTarget(): + if d.isMsvcTarget(): + offset = 176 if is32bit else 248 + else: + offset = 172 if is32bit else 248 + else: + offset = 168 if is32bit else 248 + elif qtVersion >= 0x050600: + if d.isWindowsTarget(): + if d.isMsvcTarget(): + offset = 184 if is32bit else 248 + else: + offset = 180 if is32bit else 248 + else: + offset = 168 if is32bit else 248 + elif qtVersion >= 0x050500: + if d.isWindowsTarget(): + offset = 164 if is32bit else 248 + else: + offset = 164 if is32bit else 248 + elif qtVersion >= 0x050400: + if d.isWindowsTarget(): + offset = 188 if is32bit else 272 + else: + offset = 180 if is32bit else 272 + elif qtVersion > 0x050200: + if d.isWindowsTarget(): + offset = 180 if is32bit else 272 + else: + offset = 176 if is32bit else 272 + elif qtVersion >= 0x050000: + offset = 176 if is32bit else 280 + else: + if d.isWindowsTarget(): + offset = 144 if is32bit else 232 + else: + offset = 140 if is32bit else 232 + vtable, privAddress = value.split('pp') + fileNameAddress = privAddress + offset + d.putStringValue(fileNameAddress) + d.putExpandable() + if d.isExpanded(): + with Children(d): + d.putCallItem('exists', 'bool', value, 'exists') + d.putFields(value) + + +def qdump__QFileInfo(d, value): + privAddress = d.extractPointer(value) + #bit32 = d.ptrSize() == 4 + #qt5 = d.qtVersion() >= 0x050000 + #try: + # d.putStringValue(value['d_ptr']['d'].dereference()['fileNames'][3]) + #except: + # d.putPlainChildren(value) + # return + filePathAddress = privAddress + d.ptrSize() + d.putStringValue(filePathAddress) + d.putExpandable() + if d.isExpanded(): + ns = d.qtNamespace() + with Children(d): + stype = '@QString' + d.putCallItem('absolutePath', stype, value, 'absolutePath') + d.putCallItem('absoluteFilePath', stype, value, 'absoluteFilePath') + d.putCallItem('canonicalPath', stype, value, 'canonicalPath') + d.putCallItem('canonicalFilePath', stype, value, 'canonicalFilePath') + d.putCallItem('completeBaseName', stype, value, 'completeBaseName') + d.putCallItem('completeSuffix', stype, value, 'completeSuffix') + d.putCallItem('baseName', stype, value, 'baseName') + if platform.system() == 'Darwin': + d.putCallItem('isBundle', stype, value, 'isBundle') + d.putCallItem('bundleName', stype, value, 'bundleName') + d.putCallItem('fileName', stype, value, 'fileName') + d.putCallItem('filePath', stype, value, 'filePath') + # Crashes gdb (archer-tromey-python, at dad6b53fe) + #d.putCallItem('group', value, 'group') + #d.putCallItem('owner', value, 'owner') + d.putCallItem('path', stype, value, 'path') + + d.putCallItem('groupid', 'unsigned int', value, 'groupId') + d.putCallItem('ownerid', 'unsigned int', value, 'ownerId') + + #QFile::Permissions permissions () const + try: + perms = d.call('int', value, 'permissions') + except: + perms = None + + if perms is None: + with SubItem(d, 'permissions'): + d.putSpecialValue('notcallable') + d.putType(ns + 'QFile::Permissions') + else: + with SubItem(d, 'permissions'): + d.putEmptyValue() + d.putType(ns + 'QFile::Permissions') + d.putExpandable() + if d.isExpanded(): + with Children(d, 10): + perms = perms['i'] + d.putBoolItem('ReadOwner', perms & 0x4000) + d.putBoolItem('WriteOwner', perms & 0x2000) + d.putBoolItem('ExeOwner', perms & 0x1000) + d.putBoolItem('ReadUser', perms & 0x0400) + d.putBoolItem('WriteUser', perms & 0x0200) + d.putBoolItem('ExeUser', perms & 0x0100) + d.putBoolItem('ReadGroup', perms & 0x0040) + d.putBoolItem('WriteGroup', perms & 0x0020) + d.putBoolItem('ExeGroup', perms & 0x0010) + d.putBoolItem('ReadOther', perms & 0x0004) + d.putBoolItem('WriteOther', perms & 0x0002) + d.putBoolItem('ExeOther', perms & 0x0001) + + #QDir absoluteDir () const + #QDir dir () const + d.putCallItem('caching', 'bool', value, 'caching') + d.putCallItem('exists', 'bool', value, 'exists') + d.putCallItem('isAbsolute', 'bool', value, 'isAbsolute') + d.putCallItem('isDir', 'bool', value, 'isDir') + d.putCallItem('isExecutable', 'bool', value, 'isExecutable') + d.putCallItem('isFile', 'bool', value, 'isFile') + d.putCallItem('isHidden', 'bool', value, 'isHidden') + d.putCallItem('isReadable', 'bool', value, 'isReadable') + d.putCallItem('isRelative', 'bool', value, 'isRelative') + d.putCallItem('isRoot', 'bool', value, 'isRoot') + d.putCallItem('isSymLink', 'bool', value, 'isSymLink') + d.putCallItem('isWritable', 'bool', value, 'isWritable') + d.putCallItem('created', 'bool', value, 'created') + d.putCallItem('lastModified', 'bool', value, 'lastModified') + d.putCallItem('lastRead', 'bool', value, 'lastRead') + d.putFields(value) + + +def qdump__QFixed(d, value): + v = value.split('i')[0] + d.putValue('%s/64 = %s' % (v, v / 64.0)) + + +def qform__QFiniteStack(): + return [DisplayFormat.ArrayPlot] + + +def qdump__QFiniteStack(d, value): + array, alloc, size = value.split('pii') + d.check(0 <= size and size <= alloc and alloc <= 1000 * 1000 * 1000) + d.putItemCount(size) + d.putPlotData(array, size, value.type[0]) + + +def qdump__QFlags(d, value): + i = value.split('{int}')[0] + enumType = value.type[0] + v = i.cast(enumType.name) + d.putValue(v.displayEnum('0x%04x', bitsize=32)) + + +def qform__QHash(): + return [DisplayFormat.CompactMap] + + +def qdump__QHash(d, value): + qdumpHelper_QHash(d, value, value.type[0], value.type[1]) + + +def qdump__QVariantHash(d, value): + qdumpHelper_QHash(d, value, d.createType('@QString'), d.createType('@QVariant')) + + +def qdumpHelper_QHash(d, value, keyType, valueType): + if d.qtVersion() >= 0x60000: + qdumpHelper_QHash_6(d, value, keyType, valueType) + else: + qdumpHelper_QHash_5(d, value, keyType, valueType) + +def qdumpHelper_QHash_5(d, value, keyType, valueType): + def hashDataFirstNode(): + b = buckets + n = numBuckets + while n: + n -= 1 + bb = d.extractPointer(b) + if bb != dptr: + return bb + b += ptrSize + return dptr + + def hashDataNextNode(node): + (nextp, h) = d.split('pI', node) + if d.extractPointer(nextp): + return nextp + start = (h % numBuckets) + 1 + b = buckets + start * ptrSize + n = numBuckets - start + while n: + n -= 1 + bb = d.extractPointer(b) + if bb != nextp: + return bb + b += ptrSize + return nextp + + ptrSize = d.ptrSize() + dptr = d.extractPointer(value) + (fakeNext, buckets, ref, size, nodeSize, userNumBits, numBits, numBuckets) = \ + d.split('ppiiihhi', dptr) + + d.check(0 <= size and size <= 100 * 1000 * 1000) + d.check(-1 <= ref and ref < 100000) + + d.putItemCount(size) + if d.isExpanded(): + isShort = d.qtVersion() < 0x050000 and keyType.name == 'int' + with Children(d, size): + node = hashDataFirstNode() + for i in d.childRange(): + if isShort: + typeCode = 'P{%s}@{%s}' % (keyType.name, valueType.name) + (pnext, key, padding2, val) = d.split(typeCode, node) + else: + typeCode = 'Pi@{%s}@{%s}' % (keyType.name, valueType.name) + (pnext, hashval, padding1, key, padding2, val) = d.split(typeCode, node) + d.putPairItem(i, (key, val), 'key', 'value') + node = hashDataNextNode(node) + + +def qdumpHelper_QHash_6(d, value, keyType, valueType): + dptr = d.extractPointer(value) + if dptr == 0: + d.putItemCount(0) + return + + ref, _, size, buckets, seed, spans = d.split('i@qqqp', dptr) + + d.check(0 <= size and size <= 100 * 1000 * 1000) + d.check(-1 <= ref and ref < 100000) + #d.putValue("%d 0x%x 0x%x 0x%x" % (ref, size, buckets, seed)); + d.putItemCount(size) + + if d.isExpanded(): + type_code = '{%s}@{%s}' % (keyType.name, valueType.name) + _, entry_size, _ = d.describeStruct(type_code) + with Children(d, size): + span_size = 128 + 2 * d.ptrSize() # Including tail padding. + nspans = int((buckets + 127) / 128) + count = 0 + for b in range(nspans): + span = spans + b * span_size + offsets, entries, allocated, next_free = d.split('128spbb', span) + #with SubItem(d, 'span %d' % b): + # d.putValue('span: 0x%x %s alloc: %s next: %s' + # % (span, d.hexencode(offsets), allocated, next_free)) + entry_pos = 0 + for i in range(128): + offset = offsets[i] + #with SubItem(d, 'offset %i' % i): + # d.putValue('i: %s off: %s' % (i, offset)) + if offset != 255: # Entry is used + entry = entries + offset * entry_size + key, _, val = d.split(type_code, entry) + #with SubItem(d, 'count %d entry %d' % (count, i)): + # d.putValue('i: %s entry: 0x%x' % (i, entry)) + d.putPairItem(count, (key, val), 'key', 'value') + count += 1 + entry_pos += 1 + #with SubItem(d, 'total'): + # d.putValue('total: %s item size: %s' % (count, entry_size)) + + +def qform__QHashNode(): + return [DisplayFormat.CompactMap] + + +def qdump__QHashNode(d, value): + d.putPairItem(None, value) + + +def qHashIteratorHelper(d, value): + typeName = value.type.name + hashTypeName = typeName[0:typeName.rfind('::')] + hashType = d.lookupType(hashTypeName) + keyType = hashType[0] + valueType = hashType[1] + d.putExpandable() + d.putEmptyValue() + if d.isExpanded(): + with Children(d): + node = d.extractPointer(value) + isShort = d.qtVersion() < 0x050000 and keyType.name == 'int' + if isShort: + typeCode = 'P{%s}@{%s}' % (keyType.name, valueType.name) + (pnext, key, padding2, val) = d.split(typeCode, node) + else: + typeCode = 'Pi@{%s}@{%s}' % (keyType.name, valueType.name) + (pnext, hashval, padding1, key, padding2, val) = d.split(typeCode, node) + d.putSubItem('key', key) + d.putSubItem('value', val) + + +def qdump__QHash__const_iterator(d, value): + qHashIteratorHelper(d, value) + + +def qdump__QHash__iterator(d, value): + qHashIteratorHelper(d, value) + + +def qdump__QHostAddress(d, value): + dd = d.extractPointer(value) + qtVersion = d.qtVersion() + tiVersion = d.qtTypeInfoVersion() + #DumperBase.warn('QT: %x, TI: %s' % (qtVersion, tiVersion)) + mayNeedParse = True + if tiVersion is not None: + if tiVersion >= 16: + # After a6cdfacf + p, scopeId, a6, a4, protocol = d.split('p{@QString}16s{@quint32}B', dd) + mayNeedParse = False + elif tiVersion >= 5: + # Branch 5.8.0 at f70b4a13 TI: 15 + # Branch 5.7.0 at b6cf0418 TI: 5 + (ipString, scopeId, a6, a4, protocol, isParsed) \ + = d.split('{@QString}{@QString}16s{@quint32}B{bool}', dd) + else: + (ipString, scopeId, a4, pad, a6, protocol, isParsed) \ + = d.split('{@QString}{@QString}{@quint32}I16sI{bool}', dd) + elif qtVersion >= 0x050600: # 5.6.0 at f3aabb42 + if d.ptrSize() == 8 or d.isWindowsTarget(): + (ipString, scopeId, a4, pad, a6, protocol, isParsed) \ + = d.split('{@QString}{@QString}{@quint32}I16sI{bool}', dd) + else: + (ipString, scopeId, a4, a6, protocol, isParsed) \ + = d.split('{@QString}{@QString}{@quint32}16sI{bool}', dd) + elif qtVersion >= 0x050000: # 5.2.0 at 62feb088 + (ipString, scopeId, a4, a6, protocol, isParsed) \ + = d.split('{@QString}{@QString}{@quint32}16sI{bool}', dd) + else: # 4.8.7 at b05d05f + (a4, a6, protocol, pad, ipString, isParsed, pad, scopeId) \ + = d.split('{@quint32}16sB@{@QString}{bool}@{@QString}', dd) + + if mayNeedParse: + ipStringData, ipStringSize, ipStringAlloc = d.stringData(ipString) + if mayNeedParse and isParsed.integer() and ipStringSize > 0: + d.putStringValue(ipString) + else: + # value.d.d->protocol: + # QAbstractSocket::IPv4Protocol = 0 + # QAbstractSocket::IPv6Protocol = 1 + if protocol == 1: + # value.d.d->a6 + data = d.hexencode(a6) + address = ':'.join('%x' % int(data[i:i + 4], 16) for i in range(0, 32, 4)) + d.putValue(address) + elif protocol == 0: + # value.d.d->a + a = a4.integer() + a, n4 = divmod(a, 256) + a, n3 = divmod(a, 256) + a, n2 = divmod(a, 256) + a, n1 = divmod(a, 256) + d.putValue('%d.%d.%d.%d' % (n1, n2, n3, n4)) + else: + d.putValue('' % protocol) + + d.putExpandable() + if d.isExpanded(): + with Children(d): + if mayNeedParse: + d.putSubItem('ipString', ipString) + d.putSubItem('isParsed', isParsed) + d.putSubItem('scopeId', scopeId) + d.putSubItem('a', a4) + + +def qdump__QIPv6Address(d, value): + raw = d.split('16s', value)[0] + data = d.hexencode(raw) + d.putValue(':'.join('%x' % int(data[i:i + 4], 16) for i in range(0, 32, 4))) + d.putArrayData(value.address(), 16, d.lookupType('unsigned char')) + + +def qform__QList(): + return [DisplayFormat.DirectQListStorage, DisplayFormat.IndirectQListStorage] + + +def qdump__QList(d, value): + return qdumpHelper_QList(d, value, d.createType(value.type[0])) + + +def qdump__QVariantList(d, value): + qdumpHelper_QList(d, value, d.createType('@QVariant')) + + +def qdumpHelper_QList(d, value, innerType): + data, size = d.listData(value, check=True) + d.putItemCount(size) + + if d.qtVersion() >= 0x60000: + d.putPlotData(data, size, innerType) + return + + if d.isExpanded(): + innerSize = innerType.size() + stepSize = d.ptrSize() + # The exact condition here is: + # QTypeInfo::isLarge || QTypeInfo::isStatic + # but this data is available neither in the compiled binary nor + # in the frontend. + # So as first approximation only do the 'isLarge' check: + displayFormat = d.currentItemFormat() + if displayFormat == DisplayFormat.DirectQListStorage: + isInternal = True + elif displayFormat == DisplayFormat.IndirectQListStorage: + isInternal = False + else: + isInternal = innerSize <= stepSize and innerType.isMovableType() + if isInternal: + if innerSize == stepSize: + d.putArrayData(data, size, innerType) + else: + with Children(d, size, childType=innerType): + for i in d.childRange(): + p = d.createValue(data + i * stepSize, innerType) + d.putSubItem(i, p) + else: + # about 0.5s / 1000 items + with Children(d, size, maxNumChild=2000, childType=innerType): + for i in d.childRange(): + p = d.extractPointer(data + i * stepSize) + x = d.createValue(p, innerType) + d.putSubItem(i, x) + + +def qform__QImage(): + return [DisplayFormat.Simple, DisplayFormat.Separate] + + +def qdump__QImage(d, value): + if d.qtVersion() >= 0x060000: + vtbl, painters, image_data = value.split('ppp') + elif d.qtVersion() >= 0x050000: + vtbl, painters, reserved, image_data = value.split('pppp') + else: + vtbl, painters, image_data = value.split('ppp') + + if image_data == 0: + d.putValue('(invalid)') + return + + ref, width, height = d.split('iii', image_data) + d.putValue('(%dx%d)' % (width, height)) + + d.putExpandable() + if d.isExpanded(): + if d.qtVersion() < 0x060000: + (ref, width, height, depth, nbytes, pad, devicePixelRatio, colorTable, + bits, iformat) = d.split('iiiii@dppi', image_data) + else: + (ref, width, height, depth, nbytes, pad, devicePixelRatio, _, _, _, + bits, iformat) = d.split('iiiii@dppppi', image_data) + with Children(d): + d.putIntItem('width', width) + d.putIntItem('height', height) + d.putIntItem('nbytes', nbytes) + d.putIntItem('format', iformat) + with SubItem(d, 'data'): + d.putValue('0x%x' % bits) + d.putType('void *') + + displayFormat = d.currentItemFormat() + if displayFormat == DisplayFormat.Separate: + d.putDisplay('imagedata:separate', '%08x%08x%08x%08x' % (width, height, nbytes, iformat) + + d.readMemory(bits, nbytes)) + + +def qdump__QLinkedList(d, value): + dd = d.extractPointer(value) + ptrSize = d.ptrSize() + n = d.extractInt(dd + 4 + 2 * ptrSize) + ref = d.extractInt(dd + 2 * ptrSize) + d.check(0 <= n and n <= 100 * 1000 * 1000) + d.check(-1 <= ref and ref <= 1000) + d.putItemCount(n) + if d.isExpanded(): + innerType = value.type[0] + with Children(d, n, maxNumChild=1000, childType=innerType): + pp = d.extractPointer(dd) + for i in d.childRange(): + d.putSubItem(i, d.createValue(pp + 2 * ptrSize, innerType)) + pp = d.extractPointer(pp) + + +qqLocalesCount = None + + +def qdump__QLocale(d, value): + # Check for uninitialized 'index' variable. Retrieve size of + # QLocale data array from variable in qlocale.cpp. + # Default is 368 in Qt 4.8, 438 in Qt 5.0.1, the last one + # being 'System'. + #global qqLocalesCount + #if qqLocalesCount is None: + # #try: + # qqLocalesCount = int(value(ns + 'locale_data_size')) + # #except: + # qqLocalesCount = 438 + #try: + # index = int(value['p']['index']) + #except: + # try: + # index = int(value['d']['d']['m_index']) + # except: + # index = int(value['d']['d']['m_data']...) + #d.check(index >= 0) + #d.check(index <= qqLocalesCount) + if d.qtVersion() < 0x50000: + d.putStringValue(d.call('const char *', value, 'name')) + d.putPlainChildren(value) + return + + ns = d.qtNamespace() + dd = value.extractPointer() + (data, ref, numberOptions) = d.split('pi4s', dd) + (languageId, scriptId, countryId, + decimal, group, listt, percent, zero, + minus, plus, exponential) \ + = d.split('2s{short}2s' + + '{@QChar}{@QChar}{short}{@QChar}{@QChar}' + + '{@QChar}{@QChar}{@QChar}', data) + try: + d.putStringValue(d.call('const char *', value, 'name')) + except: + pass + d.putExpandable() + if d.isExpanded(): + with Children(d): + prefix = ns + 'QLocale::' + d.putSubItem('country', d.createValue(countryId, prefix + 'Country')) + d.putSubItem('language', d.createValue(languageId, prefix + 'Language')) + d.putSubItem('numberOptions', d.createValue(numberOptions, prefix + 'NumberOptions')) + d.putSubItem('decimalPoint', decimal) + d.putSubItem('exponential', exponential) + d.putSubItem('percent', percent) + d.putSubItem('zeroDigit', zero) + d.putSubItem('groupSeparator', group) + d.putSubItem('negativeSign', minus) + d.putSubItem('positiveSign', plus) + d.putCallItem('measurementSystem', '@QLocale::MeasurementSystem', + value, 'measurementSystem') + d.putCallItem('timeFormat_(short)', '@QString', + value, 'timeFormat', ns + 'QLocale::ShortFormat') + d.putCallItem('timeFormat_(long)', '@QString', + value, 'timeFormat', ns + 'QLocale::LongFormat') + d.putFields(value) + + +def qdump__QMapNode(d, value): + d.putEmptyValue() + d.putExpandable() + if d.isExpanded(): + with Children(d): + d.putSubItem('key', value['key']) + d.putSubItem('value', value['value']) + + +def qdumpHelper_Qt4_QMap(d, value, keyType, valueType): + dd = value.extractPointer() + (dummy, it, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, + ref, toplevel, n) = d.split('p' * 13 + 'iii', dd) + d.check(0 <= n and n <= 100 * 1000 * 1000) + d.checkRef(ref) + d.putItemCount(n) + if d.isExpanded(): + if n > 10000: + n = 10000 + + typeCode = '{%s}@{%s}' % (keyType.name, valueType.name) + pp, payloadSize, fields = d.describeStruct(typeCode) + + with Children(d, n): + for i in d.childRange(): + key, pad, value = d.split(typeCode, it - payloadSize) + d.putPairItem(i, (key, value), 'key', 'value') + dummy, it = d.split('Pp', it) + + +def qdumpHelper_Qt5_QMap(d, value, keyType, valueType): + dptr = d.extractPointer(value) + (ref, n) = d.split('ii', dptr) + d.check(0 <= n and n <= 100 * 1000 * 1000) + d.check(-1 <= ref and ref < 100000) + + d.putItemCount(n) + if d.isExpanded(): + if n > 10000: + n = 10000 + + typeCode = 'ppp@{%s}@{%s}' % (keyType.name, valueType.name) + + def helper(node): + (p, left, right, padding1, key, padding2, value) = d.split(typeCode, node) + if left: + for res in helper(left): + yield res + yield (key, value) + if right: + for res in helper(right): + yield res + + with Children(d, n): + for (pair, i) in zip(helper(dptr + 8), range(n)): + d.putPairItem(i, pair, 'key', 'value') + + +def qdumpHelper_Qt6_QMap(d, value, keyType, valueType): + d_ptr = d.extractPointer(value) + if d_ptr == 0: + d.putItemCount(0) + return + m = value['d']['d']['m'] + d.putItem(m) + d.putBetterType('@QMap<%s, %s>' % (keyType.name, valueType.name)) + + +def qform__QMap(): + return [DisplayFormat.CompactMap] + + +def qdump__QMap(d, value): + qdumpHelper_QMap(d, value, value.type[0], value.type[1]) + + +def qdumpHelper_QMap(d, value, keyType, valueType): + if d.qtVersion() >= 0x60000: + qdumpHelper_Qt6_QMap(d, value, keyType, valueType) + elif d.qtVersion() >= 0x50000: + qdumpHelper_Qt5_QMap(d, value, keyType, valueType) + else: + qdumpHelper_Qt4_QMap(d, value, keyType, valueType) + + +def qform__QMultiMap(): + return [DisplayFormat.CompactMap] + + +def qdumpHelper_Qt6_QMultiMap(d, value, keyType, valueType): + d_ptr = d.extractPointer(value) + if d_ptr == 0: + d.putItemCount(0) + return + m = value['d']['d']['m'] + d.putItem(m) + d.putBetterType('@QMultiMap<%s, %s>' % (keyType.name, valueType.name)) + +def qdump__QMultiMap(d, value): + if d.qtVersion() >= 0x60000: + qdumpHelper_Qt6_QMultiMap(d, value, value.type[0], value.type[1]) + else: + qdump__QMap(d, value) + + +def qform__QVariantMap(): + return [DisplayFormat.CompactMap] + + +def qdump__QVariantMap(d, value): + qdumpHelper_QMap(d, value, d.createType('@QString'), d.createType('@QVariant')) + d.putBetterType('@QVariantMap') + + +def qdump__QMetaMethod(d, value): + d.putQMetaStuff(value, 'QMetaMethod') + + +def qdump__QMetaEnum(d, value): + d.putQMetaStuff(value, 'QMetaEnum') + + +def qdump__QMetaProperty(d, value): + d.putQMetaStuff(value, 'QMetaProperty') + + +def qdump__QMetaClassInfo(d, value): + d.putQMetaStuff(value, 'QMetaClassInfo') + + +def qdump__QMetaObject(d, value): + d.putEmptyValue() + d.putExpandable() + if d.isExpanded(): + with Children(d): + d.putQObjectGutsHelper(0, 0, -1, value.address(), 'QMetaObject') + d.putMembersItem(value) + + +if False: + def qdump__QObjectPrivate__ConnectionList(d, value): + d.putExpandable() + if d.isExpanded(): + i = 0 + with Children(d): + first, last = value.split('pp') + currentConnection = first + connectionType = d.createType('@QObjectPrivate::Connection') + while currentConnection and currentConnection != last: + sender, receiver, slotObj, nextConnectionList, nextp, prev = \ + d.split('pppppp', currentConnection) + d.putSubItem(i, d.createValue(currentConnection, connectionType)) + currentConnection = nextp + i += 1 + d.putFields(value) + d.putItemCount(i) + else: + d.putSpecialValue('minimumitemcount', 0) + + +def qdump__QPair(d, value): + typeCode = '{%s}@{%s}' % (value.type[0].name, value.type[1].name) + first, pad, second = value.split(typeCode) + with Children(d): + key = d.putSubItem('first', first) + value = d.putSubItem('second', second) + key = key.value if key.encoding is None else "..." + value = value.value if value.encoding is None else "..." + d.putValue('(%s, %s)' % (key, value)) + + +def qdump__QProcEnvKey(d, value): + d.putByteArrayValue(value) + d.putPlainChildren(value) + + +def qdump__QPixmap(d, value): + if d.qtVersion() >= 0x060000: + vtbl, painters, data = value.split('ppp') + elif d.qtVersion() >= 0x050000: + vtbl, painters, reserved, data = s = d.split('pppp', value) + else: + vtbl, painters, data = value.split('ppp') + + if data == 0: + d.putValue('(invalid)') + else: + _, width, height = d.split('pii', data) + d.putValue('(%dx%d)' % (width, height)) + + d.putPlainChildren(value) + + +def qdump__QMargins(d, value): + d.putValue('left:%s, top:%s, right:%s, bottom:%s' % (value.split('iiii'))) + d.putPlainChildren(value) + + +def qdump__QPoint(d, value): + d.putValue('(%s, %s)' % (value.split('ii'))) + d.putPlainChildren(value) + + +def qdump__QPointF(d, value): + d.putValue('(%s, %s)' % (value.split('dd'))) + d.putPlainChildren(value) + + +def qdump__QRect(d, value): + def pp(l): return ('+' if l >= 0 else '') + str(l) + (x1, y1, x2, y2) = d.split('iiii', value) + d.putValue('%sx%s%s%s' % (x2 - x1 + 1, y2 - y1 + 1, pp(x1), pp(y1))) + d.putPlainChildren(value) + + +def qdump__QRectF(d, value): + def pp(l): return ('+' if l >= 0 else '') + str(l) + (x, y, w, h) = value.split('dddd') + d.putValue('%sx%s%s%s' % (w, h, pp(x), pp(y))) + d.putPlainChildren(value) + + +def qdump__QRegExp(d, value): + # value.priv.engineKey.pattern + privAddress = d.extractPointer(value) + (eng, pattern) = d.split('p{@QString}', privAddress) + d.putStringValue(pattern) + d.putExpandable() + if d.isExpanded(): + with Children(d): + try: + d.call('void', value, 'capturedTexts') # Warm up internal cache. + except: + # Might fail (LLDB, Core files, ...), still cache might be warm. + pass + (patternSyntax, caseSensitive, minimal, pad, t, captures) \ + = d.split('{int}{int}B@{@QString}{@QStringList}', privAddress + 2 * d.ptrSize()) + d.putSubItem('syntax', patternSyntax.cast(d.qtNamespace() + 'QRegExp::PatternSyntax')) + d.putSubItem('captures', captures) + + +def qdump__QRegion(d, value): + d_ptr = d.extractPointer(value) + if d_ptr == 0: + d.putSpecialValue('empty') + else: + if d.qtVersion() >= 0x060000: + ref, _, rgn = d.split('i@p', d_ptr) + numRects, innerArea, rects, extents, innerRect = \ + d.split('ii{@QList<@QRect>}{@QRect}{@QRect}', rgn) + elif d.qtVersion() >= 0x050400: # Padding removed in ee324e4ed + ref, _, rgn = d.split('i@p', d_ptr) + numRects, innerArea, rects, extents, innerRect = \ + d.split('ii{@QVector<@QRect>}{@QRect}{@QRect}', rgn) + elif d.qtVersion() >= 0x050000: + ref, _, rgn = d.split('i@p', d_ptr) + numRects, _, rects, extents, innerRect, innerArea = \ + d.split('i@{@QVector<@QRect>}{@QRect}{@QRect}i', rgn) + else: + if d.isWindowsTarget(): + ref, _, rgn = d.split('i@p', d_ptr) + else: + ref, _, xrgn, xrectangles, rgn = d.split('i@ppp', d_ptr) + if rgn == 0: + numRects = 0 + else: + numRects, _, rects, extents, innerRect, innerArea = \ + d.split('i@{@QVector<@QRect>}{@QRect}{@QRect}i', rgn) + + d.putItemCount(numRects) + if d.isExpanded(): + with Children(d): + d.putIntItem('numRects', numRects) + d.putIntItem('innerArea', innerArea) + d.putSubItem('extents', extents) + d.putSubItem('innerRect', innerRect) + d.putSubItem('rects', rects) + + +def qdump__QScopedPointer(d, value): + if value.pointer() == 0: + d.putValue('(null)') + else: + d.putItem(value['d']) + d.putValue(d.currentValue.value, d.currentValue.encoding) + typeName = value.type.name + if value.type[1].name == d.qtNamespace() + 'QScopedPointerDeleter<%s>' % value.type[0].name: + typeName = '@QScopedPointer<%s>' % value.type[0].name + d.putBetterType(typeName) + + +def qdump__QSet(d, value): + if d.qtVersion() >= 0x060000: + qdumpHelper_QSet6(d, value) + else: + qdumpHelper_QSet45(d, value) + +def qdumpHelper_QSet6(d, value): + dptr = d.extractPointer(value) + if dptr == 0: + d.putItemCount(0) + return + + ref, _, size, buckets, seed, spans = d.split('i@qqqp', dptr) + + d.check(0 <= size and size <= 100 * 1000 * 1000) + d.check(-1 <= ref and ref < 100000) + d.putItemCount(size) + + if d.isExpanded(): + value_type = value.type[0] + value_size = value_type.size() + with Children(d, size): + span_size = 128 + 2 * d.ptrSize() # Including tail padding. + nspans = int((buckets + 127) / 128) + count = 0 + for b in range(nspans): + span = spans + b * span_size + offsets, entries, allocated, next_free = d.split('128spbb', span) + for i in range(128): + offset = offsets[i] + if offset != 255: # Entry is used + entry = entries + offset * value_size + d.putSubItem(count, d.createValue(entry, value_type)) + count += 1 + + +def qdumpHelper_QSet45(d, value): + def hashDataFirstNode(): + b = buckets + n = numBuckets + while n: + n -= 1 + bb = d.extractPointer(b) + if bb != dptr: + return bb + b += ptrSize + return dptr + + def hashDataNextNode(node): + (nextp, h) = d.split('pI', node) + if d.extractPointer(nextp): + return nextp + start = (h % numBuckets) + 1 + b = buckets + start * ptrSize + n = numBuckets - start + while n: + n -= 1 + bb = d.extractPointer(b) + if bb != nextp: + return bb + b += ptrSize + return nextp + + ptrSize = d.ptrSize() + dptr = d.extractPointer(value) + (fakeNext, buckets, ref, size, nodeSize, userNumBits, numBits, numBuckets) = \ + d.split('ppiiihhi', dptr) + + d.check(0 <= size and size <= 100 * 1000 * 1000) + d.check(-1 <= ref and ref < 100000) + + d.putItemCount(size) + if d.isExpanded(): + keyType = value.type[0] + isShort = d.qtVersion() < 0x050000 and keyType.name == 'int' + with Children(d, size, childType=keyType): + node = hashDataFirstNode() + for i in d.childRange(): + if isShort: + typeCode = 'P{%s}' % keyType.name + (pnext, key) = d.split(typeCode, node) + else: + typeCode = 'Pi@{%s}' % keyType.name + (pnext, hashval, padding1, key) = d.split(typeCode, node) + with SubItem(d, i): + d.putItem(key) + node = hashDataNextNode(node) + + +def qdump__QSharedData(d, value): + d.putValue('ref: %s' % value.to('i')) + + +def qdump__QSharedDataPointer(d, value): + d_ptr = value['d'] + if d_ptr.pointer() == 0: + d.putValue('(null)') + else: + # This replaces the pointer by the pointee, making the + # pointer transparent. + try: + innerType = value.type[0] + except: + d.putValue(d_ptr) + d.putPlainChildren(value) + return + d.putBetterType(d.currentType) + d.putItem(d_ptr.dereference()) + + +def qdump__QSize(d, value): + d.putValue('(%s, %s)' % value.split('ii')) + d.putPlainChildren(value) + + +def qdump__QSizeF(d, value): + d.putValue('(%s, %s)' % value.split('dd')) + d.putPlainChildren(value) + + +def qdump__QSizePolicy__Policy(d, value): + d.putEnumValue(value.integer(), { + 0: 'QSizePolicy::Fixed', + 1: 'QSizePolicy::GrowFlag', + 2: 'QSizePolicy::ExpandFlag', + 3: 'QSizePolicy::MinimumExpanding (GrowFlag|ExpandFlag)', + 4: 'QSizePolicy::ShrinkFlag', + 5: 'QSizePolicy::Preferred (GrowFlag|ShrinkFlag)', + 7: 'QSizePolicy::Expanding (GrowFlag|ShrinkFlag|ExpandFlag)', + 8: 'QSizePolicy::IgnoreFlag', + 13: 'QSizePolicy::Ignored (ShrinkFlag|GrowFlag|IgnoreFlag)', + }) + + +def qdump__QSizePolicy(d, value): + bits = value.integer() + d.putEmptyValue(-99) + d.putExpandable() + if d.isExpanded(): + with Children(d): + d.putIntItem('horStretch', (bits >> 0) & 0xff) + d.putIntItem('verStretch', (bits >> 8) & 0xff) + d.putEnumItem('horPolicy', (bits >> 16) & 0xf, "@QSizePolicy::Policy") + d.putEnumItem('verPolicy', (bits >> 20) & 0xf, "@QSizePolicy::Policy") + + +def qform__QStack(): + return [DisplayFormat.ArrayPlot] + + +def qdump__QStack(d, value): + if d.qtVersion() >= 0x60000: + qdump__QList(d, value) + else: + qdump__QVector(d, value) + + +def qdump__QPolygonF(d, value): + data, size = d.vectorData(value) + d.putItemCount(size) + d.putPlotData(data, size, d.createType('@QPointF')) + + +def qdump__QPolygon(d, value): + data, size = d.vectorData(value) + d.putItemCount(size) + d.putPlotData(data, size, d.createType('@QPoint')) + + +def qdump__QGraphicsPolygonItem(d, value): + (vtbl, dptr) = value.split('pp') + if d.qtVersion() >= 0x060000: + if d.ptrSize() == 8: + offset = 424 # sizeof(QGraphicsPolygonItemPrivate), the base + else: + # Chicken out. Not worth maintaining. + d.putPlainChildren(value) + return + else: + if d.ptrSize() == 8: + offset = 384 + elif d.isWindowsTarget(): + offset = 328 if d.isMsvcTarget() else 320 + else: + offset = 308 + data, size = d.vectorData(dptr + offset) + d.putItemCount(size) + d.putPlotData(data, size, d.createType('@QPointF')) + + +def qedit__QString(d, value, data): + d.call('void', value, 'resize', str(len(data))) + (base, size, alloc) = d.stringData(value) + d.setValues(base, 'short', [ord(c) for c in data]) + + +def qform__QString(): + return [DisplayFormat.Simple, DisplayFormat.Separate] + + +def qdump__QString(d, value): + d.putStringValue(value) + data, size, _ = d.stringData(value) + displayFormat = d.currentItemFormat() + if displayFormat == DisplayFormat.Separate: + d.putDisplay('utf16:separate', d.encodeString(value, limit=100000)) + if (size > 0): + d.putExpandable() + if d.isExpanded(): + d.putArrayData(data, size, d.createType('@QChar')) + + +def qdump__QSettingsKey(d, value): + qdump__QString(d, value) + d.putBetterType(value.type) + + +def qdump__QStaticStringData(d, value): + size = value.type[0] + (ref, size, alloc, pad, offset, data) = value.split('iii@p%ss' % (2 * size)) + d.putValue(d.hexencode(data), 'utf16') + d.putPlainChildren(value) + + +def qdump__QTypedArrayData(d, value): + if value.type[0].name == 'unsigned short': + qdump__QStringData(d, value) +# else: +# qdump__QArrayData(d, value) + + +def qdump__QStringData(d, value): + (ref, size, alloc, pad, offset) = value.split('III@p') + elided, shown = d.computeLimit(size, d.displayStringLimit) + data = d.readMemory(value.address() + offset, shown * 2) + d.putValue(data, 'utf16', elided=elided) + d.putPlainChildren(value) + + +def qdump__QAnyStringView(d, value): + data, size = value.split('pp') + bits = d.ptrSize() * 8 - 2 + tag = size >> bits + size = size & (2**bits - 1) + elided, shown = d.computeLimit(size, d.displayStringLimit) + if tag == 0: + mem = d.readMemory(data, shown) + d.putValue(mem, 'utf8', elided=elided) + elif tag == 1: + mem = d.readMemory(data, shown) + d.putValue(mem, 'latin1', elided=elided) + elif tag == 2: + mem = d.readMemory(data, shown * 2) + d.putValue(mem, 'utf16', elided=elided) + else: + d.putSpecialValue('empty') + d.putPlainChildren(value) + + +def qform__QStringView(): + return [DisplayFormat.Simple, DisplayFormat.Separate] + + +def qdump__QStringView(d, value): + data = value['m_data'] + idata = data.integer() + if idata == 0: + d.putValue('(null)') + return + size = value['m_size'] + isize = size.integer() + elided, shown = d.computeLimit(isize, d.displayStringLimit) + mem = d.readMemory(idata, shown * 2) + d.putValue(mem, 'utf16', elided=elided) + if d.currentItemFormat() == DisplayFormat.Separate: + d.putDisplay('utf16:separate', mem) + d.putExpandable() + if d.isExpanded(): + d.putArrayData(idata, isize, d.createType('char16_t')) + + +def qdump__QHashedString(d, value): + qdump__QString(d, value) + d.putBetterType(value.type) + + +def qdump__QQmlRefCount(d, value): + d.putItem(value['refCount']) + d.putBetterType(value.type) + + +def qdump__QStringRef(d, value): + (stringptr, pos, size) = value.split('pii') + if stringptr == 0: + d.putValue('(null)') + return + data, ssize, alloc = d.stringData(d.createValue(stringptr, '@QString')) + d.putValue(d.readMemory(data + 2 * pos, 2 * size), 'utf16') + d.putPlainChildren(value) + + +def qdump__QStringList(d, value): + qdumpHelper_QList(d, value, d.createType('@QString')) + d.putBetterType(value.type) + + +def qdump__QTemporaryFile(d, value): + qdump__QFile(d, value) + + +def qdump__QTextCodec(d, value): + name = d.call('const char *', value, 'name') + d.putValue(d.encodeByteArray(name, limit=100), 6) + d.putExpandable() + if d.isExpanded(): + with Children(d): + d.putCallItem('name', '@QByteArray', value, 'name') + d.putCallItem('mibEnum', 'int', value, 'mibEnum') + d.putFields(value) + + +def qdump__QTextCursor(d, value): + privAddress = d.extractPointer(value) + if privAddress == 0: + d.putValue('(invalid)') + else: + positionAddress = privAddress + 2 * d.ptrSize() + 8 + d.putValue(d.extractInt(positionAddress)) + d.putExpandable() + if d.isExpanded(): + with Children(d): + positionAddress = privAddress + 2 * d.ptrSize() + 8 + d.putIntItem('position', d.extractInt(positionAddress)) + d.putIntItem('anchor', d.extractInt(positionAddress + 4)) + d.putCallItem('selected', '@QString', value, 'selectedText') + d.putFields(value) + + +def qdump__QTextDocument(d, value): + d.putEmptyValue() + d.putExpandable() + if d.isExpanded(): + with Children(d): + d.putCallItem('blockCount', 'int', value, 'blockCount') + d.putCallItem('characterCount', 'int', value, 'characterCount') + d.putCallItem('lineCount', 'int', value, 'lineCount') + d.putCallItem('revision', 'int', value, 'revision') + d.putCallItem('toPlainText', '@QString', value, 'toPlainText') + d.putFields(value) + + +def qform__QUrl(): + return [DisplayFormat.Simple, DisplayFormat.Separate] + + +def qdump__QUrl(d, value): + privAddress = d.extractPointer(value) + if not privAddress: + # d == 0 if QUrl was constructed with default constructor + d.putValue('') + return + + if d.qtVersion() < 0x050000: + d.call('void', value, 'port') # Warm up internal cache. + d.call('void', value, 'path') + st = '{@QString}' + ba = '{@QByteArray}' + (ref, dummy, + scheme, userName, password, host, path, # QString + query, # QByteArray + fragment, # QString + encodedOriginal, encodedUserName, encodedPassword, + encodedPath, encodedFragment, # QByteArray + port) \ + = d.split('i@' + st * 5 + ba + st + ba * 5 + 'i', privAddress) + else: + (ref, port, scheme, userName, password, host, path, query, fragment) \ + = d.split('ii' + '{@QString}' * 7, privAddress) + + userNameEnc = d.encodeString(userName) + hostEnc = d.encodeString(host) + elided, pathEnc = d.encodeStringHelper(path, d.displayStringLimit) + url = d.encodeString(scheme) + url += '3a002f002f00' # '://' + if len(userNameEnc): + url += userNameEnc + '4000' # '@' + url += hostEnc + if port >= 0: + url += '3a00' + ''.join(['%02x00' % ord(c) for c in str(port)]) + url += pathEnc + d.putValue(url, 'utf16', elided=elided) + + displayFormat = d.currentItemFormat() + if displayFormat == DisplayFormat.Separate: + d.putDisplay('utf16:separate', url) + + d.putExpandable() + if d.isExpanded(): + with Children(d): + d.putIntItem('port', port) + d.putSubItem('scheme', scheme) + d.putSubItem('userName', userName) + d.putSubItem('password', password) + d.putSubItem('host', host) + d.putSubItem('path', path) + d.putSubItem('query', query) + d.putSubItem('fragment', fragment) + d.putFields(value) + + +def qdump__QUuid(d, value): + r = value.split('IHHBBBBBBBB') + d.putValue('{%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}' % r) + d.putPlainChildren(value) + + +def qdumpHelper_QVariant_0(d, value): + # QVariant::Invalid + d.putBetterType('@QVariant (invalid)') + d.putValue('(invalid)') + + +def qdumpHelper_QVariant_1(d, value): + # QVariant::Bool + d.putBetterType('@QVariant (bool)') + d.putValue('true' if value.to('b') else 'false') + + +def qdumpHelper_QVariant_2(d, value): + # QVariant::Int + d.putBetterType('@QVariant (int)') + d.putValue(value.to('i')) + + +def qdumpHelper_QVariant_3(d, value): + # uint + d.putBetterType('@QVariant (uint)') + d.putValue(value.to('I')) + + +def qdumpHelper_QVariant_4(d, value): + # qlonglong + d.putBetterType('@QVariant (qlonglong)') + d.putValue(value.to('q')) + + +def qdumpHelper_QVariant_5(d, value): + # qulonglong + d.putBetterType('@QVariant (qulonglong)') + d.putValue(value.to('Q')) + + +def qdumpHelper_QVariant_6(d, value): + # QVariant::Double + d.putBetterType('@QVariant (double)') + d.putValue(value.to('d')) + + +qdumpHelper_QVariants_A = [ + qdumpHelper_QVariant_0, + qdumpHelper_QVariant_1, + qdumpHelper_QVariant_2, + qdumpHelper_QVariant_3, + qdumpHelper_QVariant_4, + qdumpHelper_QVariant_5, + qdumpHelper_QVariant_6 +] + + +qdumpHelper_QVariants_B = [ + 'QChar', # 7 + 'QVariantMap', # 8 + 'QVariantList', # 9 + 'QString', # 10 + 'QStringList', # 11 + 'QByteArray', # 12 + 'QBitArray', # 13 + 'QDate', # 14 + 'QTime', # 15 + 'QDateTime', # 16 + 'QUrl', # 17 + 'QLocale', # 18 + 'QRect', # 19 + 'QRectF', # 20 + 'QSize', # 21 + 'QSizeF', # 22 + 'QLine', # 23 + 'QLineF', # 24 + 'QPoint', # 25 + 'QPointF', # 26 + 'QRegExp', # 27 + 'QVariantHash', # 28 +] + + +def qdumpHelper_QVariant_31(d, value): + # QVariant::VoidStar + d.putBetterType('@QVariant (void *)') + d.putValue('0x%x' % d.extractPointer(value)) + + +def qdumpHelper_QVariant_32(d, value): + # QVariant::Long + d.putBetterType('@QVariant (long)') + if d.ptrSize() == 4: + d.putValue('%s' % d.extractInt(value)) + else: + d.putValue('%s' % d.extractInt64(value)) # sic! + + +def qdumpHelper_QVariant_33(d, value): + # QVariant::Short + d.putBetterType('@QVariant (short)') + d.putValue('%s' % d.extractShort(value)) + + +def qdumpHelper_QVariant_34(d, value): + # QVariant::Char + d.putBetterType('@QVariant (char)') + d.putValue('%s' % d.extractByte(value)) + + +def qdumpHelper_QVariant_35(d, value): + # QVariant::ULong + d.putBetterType('@QVariant (unsigned long)') + if d.ptrSize() == 4: + d.putValue('%s' % d.extractUInt(value)) + else: + d.putValue('%s' % d.extractUInt64(value)) # sic! + + +def qdumpHelper_QVariant_36(d, value): + # QVariant::UShort + d.putBetterType('@QVariant (unsigned short)') + d.putValue('%s' % d.extractUShort(value)) + + +def qdumpHelper_QVariant_37(d, value): + # QVariant::UChar + d.putBetterType('@QVariant (unsigned char)') + d.putValue('%s' % d.extractByte(value)) + + +def qdumpHelper_QVariant_38(d, value): + # QVariant::Float + d.putBetterType('@QVariant (float)') + d.putValue(value.to('f')) + + +qdumpHelper_QVariants_D = [ + qdumpHelper_QVariant_31, + qdumpHelper_QVariant_32, + qdumpHelper_QVariant_33, + qdumpHelper_QVariant_34, + qdumpHelper_QVariant_35, + qdumpHelper_QVariant_36, + qdumpHelper_QVariant_37, + qdumpHelper_QVariant_38 +] + +qdumpHelper_QVariants_E = [ + 'QFont', # 64 + 'QPixmap', # 65 + 'QBrush', # 66 + 'QColor', # 67 + 'QPalette', # 68 + 'QIcon', # 69 + 'QImage', # 70 + 'QPolygon', # 71 + 'QRegion', # 72 + 'QBitmap', # 73 + 'QCursor', # 74 +] + +qdumpHelper_QVariants_F = [ + # Qt 5. In Qt 4 add one. + 'QKeySequence', # 75 + 'QPen', # 76 + 'QTextLength', # 77 + 'QTextFormat', # 78 + 'X', + 'QTransform', # 80 + 'QMatrix4x4', # 81 + 'QVector2D', # 82 + 'QVector3D', # 83 + 'QVector4D', # 84 + 'QQuaternion', # 85 + 'QPolygonF' # 86 +] + + +def qdump__QVariant(d, value): + if d.qtVersion() >= 0x060000: + qdumpHelper__QVariant6(d, value) + else: + qdumpHelper__QVariant45(d, value) + +def qdumpHelper__QVariant6(d, value): + data, typeStuff = d.split('24sp', value) + packedType = typeStuff >> 2 + metaTypeInterface = typeStuff - (typeStuff & 3) + + if metaTypeInterface == 0: + qdumpHelper_QVariant_0(d, value) + return + + revision, alignment, size, flags, variantType, metaObjectPtr, name = \ + d.split('HHIIIpp', metaTypeInterface) + + # Well-known simple type. + if variantType >= 1 and variantType <= 6: + qdumpHelper_QVariants_A[variantType](d, value) + return None + + # Extended Core type (Qt 5+) + if variantType >= 31 and variantType <= 38: + qdumpHelper_QVariants_D[variantType - 31](d, value) + return None + + typeName = d.extractCString(name).decode('utf8') + isShared = bool(typeStuff & 0x1) + + if isShared: + # indirectly stored items (QRectF, ...) + ptr = d.extractPointer(value) + _, data = d.split('8s{%s}' % typeName, ptr) + d.putItem(data) + else: + val = d.createValue(data, typeName) + val.laddress = value.laddress + d.putItem(val) + + d.putBetterType('@QVariant (%s)' % typeName) + + +def qdumpHelper__QVariant45(d, value): + data, typeStuff = d.split('8sI', value) + variantType = typeStuff & 0x3fffffff + isShared = bool(typeStuff & 0x40000000) + + # Well-known simple type. + if variantType <= 6: + qdumpHelper_QVariants_A[variantType](d, value) + return None + + # Extended Core type (Qt 5) + if variantType >= 31 and variantType <= 38 and d.qtVersion() >= 0x050000: + qdumpHelper_QVariants_D[variantType - 31](d, value) + return None + + # Extended Core type (Qt 4) + if variantType >= 128 and variantType <= 135 and d.qtVersion() < 0x050000: + if variantType == 128: + d.putBetterType('@QVariant (void *)') + d.putValue('0x%x' % value.extractPointer()) + else: + if variantType == 135: # Float + blob = value + else: + p = d.extractPointer(value) + blob = d.extractUInt64(p) + qdumpHelper_QVariants_D[variantType - 128](d, blob) + return None + + #DumperBase.warn('TYPE: %s' % variantType) + + if variantType <= 86: + # Known Core or Gui type. + if variantType <= 28: + innert = qdumpHelper_QVariants_B[variantType - 7] + elif variantType <= 74: + innert = qdumpHelper_QVariants_E[variantType - 64] + elif d.qtVersion() < 0x050000: + innert = qdumpHelper_QVariants_F[variantType - 76] + else: + innert = qdumpHelper_QVariants_F[variantType - 75] + + #data = value['d']['data'] + innerType = d.qtNamespace() + innert + + #DumperBase.warn('SHARED: %s' % isShared) + if isShared: + base1 = d.extractPointer(value) + #DumperBase.warn('BASE 1: %s %s' % (base1, innert)) + base = d.extractPointer(base1) + #DumperBase.warn('SIZE 1: %s' % size) + val = d.createValue(base, innerType) + else: + #DumperBase.warn('DIRECT ITEM 1: %s' % innerType) + val = d.createValue(data, innerType) + val.laddress = value.laddress + + d.putEmptyValue(-99) + d.putItem(val) + d.putBetterType('@QVariant (%s)' % innert) + + return innert + + # User types. + ns = d.qtNamespace() + d.putEmptyValue(-99) + d.putType('%sQVariant (%s)' % (ns, variantType)) + d.putExpandable() + if d.isExpanded(): + innerType = None + with Children(d): + try: + p = d.call('const char *', value, 'typeName') + except: + d.putSpecialValue('notcallable') + return None + ptr = p.pointer() + (elided, blob) = d.encodeCArray(ptr, 1, 100) + innerType = d.hexdecode(blob) + + # Prefer namespaced version. + if len(ns) > 0: + if not d.lookupNativeType(ns + innerType) is None: + innerType = ns + innerType + + if isShared: + base1 = d.extractPointer(value) + base = d.extractPointer(base1) + val = d.createValue(base, innerType) + else: + val = d.createValue(data, innerType) + val.laddress = value.laddress + d.putSubItem('data', val) + + if innerType is not None: + d.putBetterType('%sQVariant (%s)' % (ns, innerType)) + return None + + +def qedit__QVector(d, value, data): + values = data.split(',') + d.call('void', value, 'resize', str(len(values))) + base, vsize = d.vectorData(value) + d.setValues(base, value.type[0].name, values) + + +def qform__QVector(): + return [DisplayFormat.ArrayPlot] + + +def qdump__QVector(d, value): + if d.qtVersion() >= 0x060000: + data, size = d.listData(value) + d.putItemCount(size) + d.putPlotData(data, size, d.createType(value.type.ltarget[0])) + # g++ 9.3 does not add the template parameter list to the debug info. + # Fake it for the common case: + if value.type.name == d.qtNamespace() + "QVector": + d.putBetterType(value.type.name + '<' + value.type.ltarget[0].name + '>') + else: + data, size = d.vectorData(value) + d.putItemCount(size) + d.putPlotData(data, size, d.createType(value.type[0])) + + +if False: + def qdump__QObjectConnectionList(d, value): + data, size = d.vectorData(value) + d.putItemCount(size) + d.putPlotData(data, size, d.createType('@QObjectPrivate::ConnectionList')) + + +def qdump__QVarLengthArray(d, value): + if d.qtVersion() >= 0x060000: + cap, size, data = value.split('QQp') + else: + cap, size, data = value.split('iip') + d.check(0 <= size) + d.putItemCount(size) + d.putPlotData(data, size, value.type[0]) + + +def qdump__QSharedPointer(d, value): + qdump_QWeakPointerHelper(d, value, False) + + +def qdump__QWeakPointer(d, value): + qdump_QWeakPointerHelper(d, value, True) + + +def qdump__QPointer(d, value): + # actually, we'd use value['wp'] instead of value, but since we + # only split() on the result and the (sub-)object address is the + # same it does not matter but saves some cycles. + qdump_QWeakPointerHelper(d, value, True, value.type[0]) + + +def qdump_QWeakPointerHelper(d, value, isWeak, innerType=None): + if isWeak: + (d_ptr, val) = value.split('pp') + else: + (val, d_ptr) = value.split('pp') + if d_ptr == 0 and val == 0: + d.putValue('(null)') + return + if d_ptr == 0 or val == 0: + d.putValue('') + return + + if d.qtVersion() >= 0x050000: + (weakref, strongref) = d.split('ii', d_ptr) + else: + (vptr, weakref, strongref) = d.split('pii', d_ptr) + d.check(strongref >= -1) + d.check(strongref <= weakref) + d.check(weakref <= 10 * 1000 * 1000) + + if innerType is None: + innerType = value.type[0] + with Children(d): + dataAddress = value.laddress + if isWeak: + dataAddress = dataAddress + d.ptrSize() + short = d.putSubItem('data', d.createValue(dataAddress, d.createPointerType(innerType))) + d.putIntItem('weakref', weakref) + d.putIntItem('strongref', strongref) + d.putValue(short.value, short.encoding) + + +def qdump__QXmlAttributes__Attribute(d, value): + d.putEmptyValue() + d.putExpandable() + if d.isExpanded(): + with Children(d): + (qname, uri, localname, val) = value.split('{@QString}' * 4) + d.putSubItem('qname', qname) + d.putSubItem('uri', uri) + d.putSubItem('localname', localname) + d.putSubItem('value', val) + + +def qdump__QXmlAttributes(d, value): + vptr, atts = value.split('p{@QList<@QXmlAttributes::Attribute>}') + _, att_size, _ = d.describeStruct('{@QString}' * 4) + innerType = d.createType('@QXmlAttributes::Attribute', att_size) + qdumpHelper_QList(d, atts, innerType) + + +def qdump__QXmlStreamStringRef(d, value): + s = value['m_string'] + (data, size, alloc) = d.stringData(s) + data += 2 * int(value['m_position']) + size = int(value['m_size']) + s = d.readMemory(data, 2 * size) + d.putValue(s, 'utf16') + d.putPlainChildren(value) + + +def qdump__QXmlStreamAttribute(d, value): + s = value['m_name']['m_string'] + (data, size, alloc) = d.stringData(s) + data += 2 * int(value['m_name']['m_position']) + size = int(value['m_name']['m_size']) + s = d.readMemory(data, 2 * size) + d.putValue(s, 'utf16') + d.putPlainChildren(value) + + +####################################################################### +# +# V4 +# +####################################################################### + +def extractQmlData(d, value): + #if value.type.code == TypeCode.Pointer: + # value = value.dereference() + base = value.split('p')[0] + #mmdata = d.split('Q', base)[0] + #PointerMask = 0xfffffffffffffffd + #vtable = mmdata & PointerMask + #DumperBase.warn('QML DATA: %s' % value.stringify()) + #data = value['data'] + #return #data.cast(d.lookupType(value.type.name.replace('QV4::', 'QV4::Heap::'))) + typeName = value.type.name.replace('QV4::', 'QV4::Heap::') + #DumperBase.warn('TYOE DATA: %s' % typeName) + return d.createValue(base, typeName) + + +def qdump__QV4__Heap__Base(d, value): + mm_data = value.extractPointer() + d.putValue('[%s]' % mm_data) + if d.isExpanded(): + with Children(d): + with SubItem(d, 'vtable'): + d.putItem(d.createValue(mm_data & (~3), d.qtNamespace() + 'QV4::VTable')) + d.putBoolItem('isMarked', mm_data & 1) + d.putBoolItem('inUse', (mm_data & 2) == 0) + with SubItem(d, 'nextFree'): + d.putItem(d.createValue(mm_data & (~3), value.type)) + + +def qdump__QV4__Heap__String(d, value): + # Note: There's also the 'Identifier' case. And the largestSubLength != 0 case. + (baseClass, textOrLeft, idOrRight, subtype, stringHash, largestSub, length, mm) \ + = value.split('QppIIIIp') + textPtr = d.split('{@QStringDataPtr}', textOrLeft)[0] + qdump__QStringData(d, d.createValue(textOrLeft, d.qtNamespace() + 'QStringData')) + if d.isExpanded(): + with Children(d): + d.putFields(value) + + +def qmlPutHeapChildren(d, value): + d.putItem(extractQmlData(d, value)) + + +def qdump__QV4__Object(d, value): + qmlPutHeapChildren(d, value) + + +def qdump__QV4__FunctionObject(d, value): + #qmlPutHeapChildren(d, value) + d.putEmptyValue() + if d.isExpanded(): + with Children(d): + d.putFields(value) + d.putSubItem('heap', extractQmlData(d, value)) + d.putCallItem('sourceLocation', '@QQmlSourceLocation', + value, 'sourceLocation') + + +def qdump__QV4__CompilationUnit(d, value): + qmlPutHeapChildren(d, value) + + +def qdump__QV4__CallContext(d, value): + qmlPutHeapChildren(d, value) + + +def qdump__QV4__ScriptFunction(d, value): + qmlPutHeapChildren(d, value) + + +def qdump__QV4__SimpleScriptFunction(d, value): + qdump__QV4__FunctionObject(d, value) + + +def qdump__QV4__ExecutionContext(d, value): + qmlPutHeapChildren(d, value) + + +def qdump__QQmlSourceLocation(d, value): + (sourceFile, line, col) = value.split('pHH') + (data, size, alloc) = d.stringData(value) + d.putValue(d.readMemory(data, 2 * size), 'utf16') + d.putField('valuesuffix', ':%s:%s' % (line, col)) + d.putPlainChildren(value) + + +#def qdump__QV4__CallData(d, value): +# argc = value['argc'].integer() +# d.putItemCount(argc) +# if d.isExpanded(): +# with Children(d): +# d.putSubItem('[this]', value['thisObject']) +# for i in range(0, argc): +# d.putSubItem(i, value['args'][i]) +# + +def qdump__QV4__String(d, value): + qmlPutHeapChildren(d, value) + + +def qdump__QV4__Identifier(d, value): + d.putStringValue(value) + d.putPlainChildren(value) + + +def qdump__QV4__PropertyHash(d, value): + data = value.extractPointer() + (ref, alloc, size, numBits, entries) = d.split('iiiip', data) + n = 0 + with Children(d): + for i in range(alloc): + (identifier, index) = d.split('pI', entries + i * 2 * d.ptrSize()) + if identifier != 0: + n += 1 + with SubItem(d): + d.putItem(d, d.createValue(identifier, '@QV4::Identifier')) + d.put('keysuffix', ' %d' % index) + d.putItemCount(n) + d.putPlainChildren(value) + + +def qdump__QV4__InternalClass__Transition(d, value): + identifier = d.createValue(value.extractPointer(), '@QV4::Identifier') + d.putStringValue(identifier) + d.putPlainChildren(value) + + +def qdump__QV4__InternalClassTransition(d, value): + qdump__QV4__InternalClass__Transition(d, value) + + +def qdump__QV4__SharedInternalClassData(d, value): + (ref, alloc, size, pad, data) = value.split('iIIip') + val = d.createValue(data, value.type[0]) + with Children(d): + with SubItem(d, 'data'): + d.putItem(val) + short = d.currentValue + d.putIntItem('size', size) + d.putIntItem('alloc', alloc) + d.putIntItem('refcount', ref) + d.putValue(short.value, short.encoding) + + +def qdump__QV4__IdentifierTable(d, value): + (engine, alloc, size, numBits, pad, entries) = value.split('piiiip') + n = 0 + with Children(d): + for i in range(alloc): + identifierPtr = d.extractPointer(entries + i * d.ptrSize()) + if identifierPtr != 0: + n += 1 + with SubItem(d, None): + d.putItem(d.createValue(identifierPtr, '@QV4::Heap::String')) + d.putItemCount(n) + d.putPlainChildren(value) + + +if False: + # 32 bit. + QV4_Masks_SilentNaNBit = 0x00040000 + QV4_Masks_NaN_Mask = 0x7ff80000 + QV4_Masks_NotDouble_Mask = 0x7ffa0000 + QV4_Masks_Type_Mask = 0xffffc000 + QV4_Masks_Immediate_Mask = QV4_Masks_NotDouble_Mask | 0x00004000 | QV4_Masks_SilentNaNBit + QV4_Masks_IsNullOrUndefined_Mask = QV4_Masks_Immediate_Mask | 0x08000 + QV4_Masks_Tag_Shift = 32 + + QV4_ValueType_Undefined_Type = QV4_Masks_Immediate_Mask | 0x00000 + QV4_ValueType_Null_Type = QV4_Masks_Immediate_Mask | 0x10000 + QV4_ValueType_Boolean_Type = QV4_Masks_Immediate_Mask | 0x08000 + QV4_ValueType_Integer_Type = QV4_Masks_Immediate_Mask | 0x18000 + QV4_ValueType_Managed_Type = QV4_Masks_NotDouble_Mask | 0x00000 | QV4_Masks_SilentNaNBit + QV4_ValueType_Empty_Type = QV4_Masks_NotDouble_Mask | 0x18000 | QV4_Masks_SilentNaNBit + + QV4_ConvertibleToInt = QV4_Masks_Immediate_Mask | 0x1 + + QV4_ValueTypeInternal_Null_Type_Internal = QV4_ValueType_Null_Type | QV4_ConvertibleToInt + QV4_ValueTypeInternal_Boolean_Type_Internal = QV4_ValueType_Boolean_Type | QV4_ConvertibleToInt + QV4_ValueTypeInternal_Integer_Type_Internal = QV4_ValueType_Integer_Type | QV4_ConvertibleToInt + + +def QV4_getValue(d, jsval): # (Dumper, QJSValue *jsval) -> QV4::Value * + dd = d.split('Q', jsval)[0] + if dd & 3: + return 0 + return dd + + +def QV4_getVariant(d, jsval): # (Dumper, QJSValue *jsval) -> QVariant * + dd = d.split('Q', jsval)[0] + if dd & 1: + return dd & ~3 + return 0 + + +def QV4_valueForData(d, jsval): # (Dumper, QJSValue *jsval) -> QV4::Value * + v = QV4_getValue(d, jsval) + if v: + return v + d.warn('Not implemented: VARIANT') + return 0 + + +def QV4_putObjectValue(d, objectPtr): + ns = d.qtNamespace() + base = d.extractPointer(objectPtr) + (inlineMemberOffset, inlineMemberSize, internalClass, prototype, + memberData, arrayData) = d.split('IIpppp', base) + d.putValue('PTR: 0x%x' % objectPtr) + if d.isExpanded(): + with Children(d): + with SubItem(d, '[raw]'): + d.putValue('[0x%x]' % objectPtr) + d.putType(' ') + d.putIntItem('inlineMemberOffset', inlineMemberOffset) + d.putIntItem('inlineMemberSize', inlineMemberSize) + d.putIntItem('internalClass', internalClass) + d.putIntItem('prototype', prototype) + d.putPtrItem('memberData', memberData) + d.putPtrItem('arrayData', arrayData) + d.putSubItem('OBJ', d.createValue(objectPtr, ns + 'QV4::Object')) + #d.putFields(value) + + +def qdump__QV4_Object(d, value): + ns = d.qtNamespace() + d.putEmptyValue() + if d.isExpanded(): + with Children(d): + with SubItem(d, '[raw]'): + base = d.extractPointer(objectPtr) + (inlineMemberOffset, inlineMemberSize, internalClass, prototype, + memberData, arrayData) = d.split('IIpppp', base) + d.putValue('PTR: 0x%x' % objectPtr) + + +def qdump__QV4__Value(d, value): + if d.ptrSize() == 4: + qdump_32__QV4__Value(d, value) + else: + qdump_64__QV4__Value(d, value) + + +def qdump_32__QV4__Value(d, value): + # QV4_Masks_SilentNaNBit = 0x00040000 + # QV4_Masks_NaN_Mask = 0x7ff80000 + # QV4_Masks_NotDouble_Mask = 0x7ffa0000 + # QV4_Masks_Type_Mask = 0xffffc000 + ns = d.qtNamespace() + v = value.split('Q')[0] + tag = v >> 32 + val = v & 0xffffffff + if (tag & 0x7fff2000) == 0x7fff2000: # Int + d.putValue(val) + d.putBetterType('@QV4::Value (int32)') + elif (tag & 0x7fff4000) == 0x7fff4000: # Bool + d.putValue(val) + d.putBetterType('@QV4::Value (bool)') + elif (tag & 0x7fff0000) == 0x7fff0000: # Null + d.putValue(val) + d.putBetterType('@QV4::Value (null)') + elif (tag & 0x7ffa0000) != 0x7ffa0000: # Double + d.putValue(value.split('d')[0]) + d.putBetterType('@QV4::Value (double)') + elif tag == 0x7ffa0000: + if val == 0: + d.putValue('(undefined)') + d.putBetterType('@QV4::Value (undefined)') + else: + managed = d.createValue(val, ns + 'QV4::Heap::Base') + qdump__QV4__Heap__Base(d, managed) + #d.putValue('[0x%x]' % v) + #d.putPlainChildren(value) + if d.isExpanded(): + with Children(d): + with SubItem(d, '[raw]'): + d.putValue('[0x%x]' % v) + d.putType(' ') + with SubItem(d, '[val]'): + d.putValue('[0x%x]' % val) + d.putType(' ') + with SubItem(d, '[tag]'): + d.putValue('[0x%x]' % tag) + d.putType(' ') + #with SubItem(d, '[vtable]'): + # d.putItem(d.createValue(vtable, ns + 'QV4::VTable')) + # d.putType(' '); + d.putFields(value) + + +def qdump_64__QV4__Value(d, value): + dti = d.qtDeclarativeTypeInfoVersion() + new = dti is not None and dti >= 2 + if new: + QV4_NaNEncodeMask = 0xfffc000000000000 + QV4_Masks_Immediate_Mask = 0x00020000 # bit 49 + + QV4_ValueTypeInternal_Empty_Type_Internal = QV4_Masks_Immediate_Mask | 0 + QV4_ConvertibleToInt = QV4_Masks_Immediate_Mask | 0x10000 # bit 48 + QV4_ValueTypeInternal_Null_Type_Internal = QV4_ConvertibleToInt | 0x08000 + QV4_ValueTypeInternal_Boolean_Type_Internal = QV4_ConvertibleToInt | 0x04000 + QV4_ValueTypeInternal_Integer_Type_Internal = QV4_ConvertibleToInt | 0x02000 + + QV4_ValueType_Undefined_Type = 0 # Dummy to make generic code below pass. + + else: + QV4_NaNEncodeMask = 0xffff800000000000 + QV4_Masks_Immediate_Mask = 0x00018000 + + QV4_IsInt32Mask = 0x0002000000000000 + QV4_IsDoubleMask = 0xfffc000000000000 + QV4_IsNumberMask = QV4_IsInt32Mask | QV4_IsDoubleMask + QV4_IsNullOrUndefinedMask = 0x0000800000000000 + QV4_IsNullOrBooleanMask = 0x0001000000000000 + + QV4_Masks_NaN_Mask = 0x7ff80000 + QV4_Masks_Type_Mask = 0xffff8000 + QV4_Masks_IsDouble_Mask = 0xfffc0000 + QV4_Masks_IsNullOrUndefined_Mask = 0x00008000 + QV4_Masks_IsNullOrBoolean_Mask = 0x00010000 + + QV4_ValueType_Undefined_Type = QV4_Masks_IsNullOrUndefined_Mask + QV4_ValueType_Null_Type = QV4_Masks_IsNullOrUndefined_Mask \ + | QV4_Masks_IsNullOrBoolean_Mask + QV4_ValueType_Boolean_Type = QV4_Masks_IsNullOrBoolean_Mask + QV4_ValueType_Integer_Type = 0x20000 | QV4_Masks_IsNullOrBoolean_Mask + QV4_ValueType_Managed_Type = 0 + QV4_ValueType_Empty_Type = QV4_ValueType_Undefined_Type | 0x4000 + + QV4_ValueTypeInternal_Null_Type_Internal = QV4_ValueType_Null_Type + QV4_ValueTypeInternal_Boolean_Type_Internal = QV4_ValueType_Boolean_Type + QV4_ValueTypeInternal_Integer_Type_Internal = QV4_ValueType_Integer_Type + + QV4_PointerMask = 0xfffffffffffffffd + + QV4_Masks_Tag_Shift = 32 + QV4_IsDouble_Shift = 64 - 14 + QV4_IsNumber_Shift = 64 - 15 + QV4_IsConvertibleToInt_Shift = 64 - 16 + QV4_IsManaged_Shift = 64 - 17 + + v = value.split('Q')[0] + tag = v >> QV4_Masks_Tag_Shift + vtable = v & QV4_PointerMask + ns = d.qtNamespace() + if (v >> QV4_IsNumber_Shift) == 1: + d.putBetterType('@QV4::Value (int32)') + vv = v & 0xffffffff + vv = vv if vv < 0x80000000 else -(0x100000000 - vv) + d.putBetterType('@QV4::Value (int32)') + d.putValue('%d' % vv) + elif (v >> QV4_IsDouble_Shift): + d.putBetterType('@QV4::Value (double)') + d.putValue('%0.16x' % (v ^ QV4_NaNEncodeMask), 'float:8') + elif tag == QV4_ValueType_Undefined_Type and not new: + d.putBetterType('@QV4::Value (undefined)') + d.putValue('(undefined)') + elif tag == QV4_ValueTypeInternal_Null_Type_Internal: + d.putBetterType('@QV4::Value (null?)') + d.putValue('(null?)') + elif v == 0: + if new: + d.putBetterType('@QV4::Value (undefined)') + d.putValue('(undefined)') + else: + d.putBetterType('@QV4::Value (null)') + d.putValue('(null)') + #elif ((v >> QV4_IsManaged_Shift) & ~1) == 1: + # d.putBetterType('@QV4::Value (null/undef)') + # d.putValue('(null/undef)') + #elif v & QV4_IsNullOrBooleanMask: + # d.putBetterType('@QV4::Value (null/bool)') + # d.putValue('(null/bool)') + # d.putValue(v & 1) + else: + (parentv, flags, pad, className) = d.split('pIIp', vtable) + #vtable = value['m']['vtable'] + if flags & 2: # isString' + d.putBetterType('@QV4::Value (string)') + qdump__QV4__Heap__String(d, d.createValue(v, ns + 'QV4::Heap::String')) + #d.putStringValue(d.extractPointer(value) + 2 * d.ptrSize()) + #d.putValue('ptr: 0x%x' % d.extractPointer(value)) + return + elif flags & 4: # isObject + d.putBetterType('@QV4::Value (object)') + #QV4_putObjectValue(d, d.extractPointer(value) + 2 * d.ptrSize()) + arrayVTable = d.symbolAddress(ns + 'QV4::ArrayObject::static_vtbl') + #DumperBase.warn('ARRAY VTABLE: 0x%x' % arrayVTable) + d.putExpandable() + d.putItem(d.createValue(d.extractPointer(value) + 2 * d.ptrSize(), ns + 'QV4::Object')) + return + elif flags & 8: # isFunction + d.putBetterType('@QV4::Value (function)') + d.putEmptyValue() + else: + d.putBetterType('@QV4::Value (unknown)') + #d.putValue('[0x%x]' % v) + d.putValue('[0x%x : flag 0x%x : tag 0x%x]' % (v, flags, tag)) + if d.isExpanded(): + with Children(d): + with SubItem(d, '[raw]'): + d.putValue('[0x%x]' % v) + d.putType(' ') + with SubItem(d, '[vtable]'): + d.putItem(d.createValue(vtable, ns + 'QV4::VTable')) + d.putType(' ') + d.putFields(value) + + +def qdump__QV__PropertyHashData(d, value): + (ref, alloc, size, numBits, entries) = value.split('IIIIp') + d.putItemCount(size) + if d.isExpanded(): + with Children(d): + d.putFields(value) + + +def qdump__QV__PropertyHash(d, value): + qdump__QV__PropertyHashData(d, d.createValue(d.extractPointer(), value.type.name + 'Data')) + + +def qdump__QV4__Scoped(d, value): + innerType = value.type[0] + d.putItem(d.createValue(value.extractPointer(), innerType)) + #d.putEmptyValue() + #if d.isExpanded(): + # with Children(d): + # d.putSubItem('[]', d.createValue(value.extractPointer(), innerType)) + # d.putFields(value) + + +def qdump__QV4__ScopedString(d, value): + innerType = value.type[0] + qdump__QV4__String(d, d.createValue(value.extractPointer(), innerType)) + + +def qdump__QJSValue(d, value): + if d.ptrSize() == 4: + qdump_32__QJSValue(d, value) + else: + if d.qtVersion() >= 0x60000: + qdump_64__QJSValue_6(d, value) + else: + qdump_64__QJSValue_5(d, value) + + +def qdump_32__QJSValue(d, value): + ns = d.qtNamespace() + dd = value.split('I')[0] + d.putValue('[0x%x]' % dd) + if dd == 0: + d.putValue('(null)') + d.putType(value.type.name + ' (null)') + elif dd & 1: + variant = d.createValue(dd & ~3, ns + 'QVariant') + qdump__QVariant(d, variant) + d.putBetterType(d.currentType.value.replace('QVariant', 'QJSValue', 1)) + elif dd & 3 == 0: + v4value = d.createValue(dd, ns + 'QV4::Value') + qdump_32__QV4__Value(d, v4value) + d.putBetterType(d.currentType.value.replace('QV4::Value', 'QJSValue', 1)) + return + if d.isExpanded(): + with Children(d): + with SubItem(d, '[raw]'): + d.putValue('[0x%x]' % dd) + d.putType(' ') + d.putFields(value) + +def qdump_64__QJSValue_6(d, value): + dd = value.split('Q')[0] + if dd == 0: + d.putValue('(undefined)') + d.putType(value.type.name + ' (undefined)') + if d.qtVersion() < 0x60500: + typ = dd >> 47 + if typ == 5: + d.putValue('(null)') + d.putType(value.type.name + ' (null)') + elif typ == 6: + d.putValue('true' if dd & 1 else 'false') + d.putType(value.type.name + ' (bool)') + elif typ == 7: + d.putValue(dd & 0xfffffffff) + d.putType(value.type.name + ' (int)') + elif typ > 7: + val = d.Value(d) + val.ldata = struct.pack('q', dd ^ 0xfffc000000000000) + val._type = d.createType('double') + d.putItem(val) + d.putType(value.type.name + ' (double)') + elif typ <= 3: # Heap + if dd & 1: # String + val = d.Value(d) + val.ldata = struct.pack('q', dd & ~1) + val._type = d.createType('@QString*') + d.putItem(val) + d.putType(value.type.name + ' (QString)') + else: + # FIXME: Arrays, Objects missing. + val = d.split('{@QV4::Managed*}', value)[0] + d.putItem(val) + d.putItemCount(1) + else: + d.putEmptyValue() + d.putItemCount(1) + d.putPlainChildren(value) + return + + else: + typ = dd & 7 + isPointer = typ & 1 + if typ == 0: + d.putValue('(undefined)') + d.putType(value.type.name + ' (undefined)') + elif typ == 2: + d.putValue('(null)') + d.putType(value.type.name + ' (null)') + elif typ == 4: + d.putValue(dd >> 32) + d.putType(value.type.name + ' (int)') + elif typ == 6: + d.putValue('true' if dd >> 32 & 1 else 'false') + d.putType(value.type.name + ' (bool)') + elif isPointer: + pointer = dd >> 3 + pointer = pointer << 3 + val = d.Value(d) + val.ldata = struct.pack('q', pointer) + if typ == 1: + val._type = d.createType('double*') + d.putItem(val) + d.putType(value.type.name + ' (double)') + elif typ == 3: + val._type = d.createType('@QV4::Value*') + d.putItem(val) + d.putType(value.type.name + ' (QV4::Value)') + elif typ == 5: + val._type = d.createType('@QString*') + d.putItem(val) + d.putType(value.type.name + ' (QString)') + + else: + d.putEmptyValue() + d.putItemCount(1) + d.putPlainChildren(value) + return + + if d.isExpanded(): + with Children(d): + with SubItem(d, '[raw]'): + d.putValue('[0x%x]' % dd) + d.putType(' ') + d.putFields(value) + + +def qdump_64__QJSValue_5(d, value): + ns = d.qtNamespace() + dd = value.split('Q')[0] + if dd == 0: + d.putValue('(null)') + d.putType(value.type.name + ' (null)') + elif dd & 1: + variant = d.createValue(dd & ~3, ns + 'QVariant') + qdump__QVariant(d, variant) + d.putBetterType(d.currentType.value.replace('QVariant', 'QJSValue', 1)) + else: + d.putEmptyValue() + #qdump__QV4__Value(d, d.createValue(dd, ns + 'QV4::Value')) + #return + if d.isExpanded(): + with Children(d): + with SubItem(d, '[raw]'): + d.putValue('[0x%x]' % dd) + d.putType(' ') + d.putFields(value) + + +def qdump__QQmlBinding(d, value): + d.putEmptyValue() + if d.isExpanded(): + with Children(d): + d.putCallItem('expressionIdentifier', '@QString', + value, 'expressionIdentifier') + d.putFields(value) + + +####################################################################### +# +# Webkit +# +####################################################################### + + +def jstagAsString(tag): + # enum { Int32Tag = 0xffffffff }; + # enum { CellTag = 0xfffffffe }; + # enum { TrueTag = 0xfffffffd }; + # enum { FalseTag = 0xfffffffc }; + # enum { NullTag = 0xfffffffb }; + # enum { UndefinedTag = 0xfffffffa }; + # enum { EmptyValueTag = 0xfffffff9 }; + # enum { DeletedValueTag = 0xfffffff8 }; + if tag == -1: + return 'Int32' + if tag == -2: + return 'Cell' + if tag == -3: + return 'True' + if tag == -4: + return 'Null' + if tag == -5: + return 'Undefined' + if tag == -6: + return 'Empty' + if tag == -7: + return 'Deleted' + return 'Unknown' + + +def qdump__QTJSC__JSValue(d, value): + d.putEmptyValue() + d.putExpandable() + if d.isExpanded(): + with Children(d): + tag = value['u']['asBits']['tag'] + payload = value['u']['asBits']['payload'] + #d.putIntItem('tag', tag) + with SubItem(d, 'tag'): + d.putValue(jstagAsString(int(tag))) + d.putNoType() + + d.putIntItem('payload', int(payload)) + d.putFields(value['u']) + + if tag == -2: + cellType = d.lookupType('QTJSC::JSCell').pointer() + d.putSubItem('cell', payload.cast(cellType)) + + try: + # FIXME: This might not always be a variant. + delegateType = d.lookupType(d.qtNamespace() + 'QScript::QVariantDelegate').pointer() + delegate = scriptObject['d']['delegate'].cast(delegateType) + #d.putSubItem('delegate', delegate) + variant = delegate['m_value'] + d.putSubItem('variant', variant) + except: + pass + + +def qdump__QScriptValue(d, value): + # structure: + # engine QScriptEnginePrivate + # jscValue QTJSC::JSValue + # next QScriptValuePrivate * + # numberValue 5.5987310416280426e-270 myns::qsreal + # prev QScriptValuePrivate * + # ref QBasicAtomicInt + # stringValue QString + # type QScriptValuePrivate::Type: { JavaScriptCore, Number, String } + #d.putEmptyValue() + dd = value['d_ptr']['d'] + ns = d.qtNamespace() + if dd.pointer() == 0: + d.putValue('(invalid)') + return + if int(dd['type']) == 1: # Number + d.putValue(dd['numberValue']) + d.putType('%sQScriptValue (Number)' % ns) + return + if int(dd['type']) == 2: # String + d.putStringValue(dd['stringValue']) + d.putType('%sQScriptValue (String)' % ns) + return + + d.putType('%sQScriptValue (JSCoreValue)' % ns) + x = dd['jscValue']['u'] + tag = x['asBits']['tag'] + payload = x['asBits']['payload'] + #isValid = int(x['asBits']['tag']) != -6 # Empty + #isCell = int(x['asBits']['tag']) == -2 + #DumperBase.warn('IS CELL: %s ' % isCell) + #isObject = False + #className = 'UNKNOWN NAME' + #if isCell: + # # isCell() && asCell()->isObject(); + # # in cell: m_structure->typeInfo().type() == ObjectType; + # cellType = d.lookupType('QTJSC::JSCell').pointer() + # cell = payload.cast(cellType).dereference() + # dtype = 'NO DYNAMIC TYPE' + # try: + # dtype = cell.dynamic_type + # except: + # pass + # warn('DYNAMIC TYPE: %s' % dtype) + # warn('STATUC %s' % cell.type) + # type = cell['m_structure']['m_typeInfo']['m_type'] + # isObject = int(type) == 7 # ObjectType; + # className = 'UNKNOWN NAME' + #DumperBase.warn('IS OBJECT: %s ' % isObject) + + #inline bool JSCell::inherits(const ClassInfo* info) const + #for (const ClassInfo* ci = classInfo(); ci; ci = ci->parentClass) { + # if (ci == info) + # return true; + #return false; + + try: + # This might already fail for 'native' payloads. + scriptObjectType = d.lookupType(ns + 'QScriptObject').pointer() + scriptObject = payload.cast(scriptObjectType) + + # FIXME: This might not always be a variant. + delegateType = d.lookupType(ns + 'QScript::QVariantDelegate').pointer() + delegate = scriptObject['d']['delegate'].cast(delegateType) + #d.putSubItem('delegate', delegate) + + variant = delegate['m_value'] + #d.putSubItem('variant', variant) + t = qdump__QVariant(d, variant) + # Override the 'QVariant (foo)' output + d.putBetterType('@QScriptValue (%s)' % t) + if t != 'JSCoreValue': + return + except: + pass + + # This is a 'native' JSCore type for e.g. QDateTime. + d.putValue('') + d.putExpandable() + if d.isExpanded(): + with Children(d): + d.putSubItem('jscValue', dd['jscValue']) + + +def qdump__QQmlAccessorProperties__Properties(d, value): + size = int(value['count']) + d.putItemCount(size) + if d.isExpanded(): + d.putArrayData(value['properties'], size) + + +# +# QJson +# + +def qdumpHelper_qle_cutBits(value, offset, length): + return (value >> offset) & ((1 << length) - 1) + + +def qdump__QJsonPrivate__qle_bitfield(d, value): + offset = value.type[0] + length = value.type[1] + val = value['val'].integer() + d.putValue('%s' % qdumpHelper_qle_cutBits(val, offset, length)) + + +def qdumpHelper_qle_signedbitfield_value(d, value): + offset = value.type[0] + length = value.type[1] + val = value['val'].integer() + val = (val >> offset) & ((1 << length) - 1) + if val >= (1 << (length - 1)): + val -= (1 << (length - 1)) + return val + + +def qdump__QJsonPrivate__qle_signedbitfield(d, value): + d.putValue('%s' % qdumpHelper_qle_signedbitfield_value(d, value)) + + +def qdump__QJsonPrivate__q_littleendian(d, value): + d.putValue('%s' % value['val'].integer()) + + +def qdumpHelper_QJsonValue(d, data, base, pv): + """ + Parameters are the parameters to the + QJsonValue(QJsonPrivate::Data *data, QJsonPrivate::Base *base, + const QJsonPrivate::Value& pv) + constructor. We 'inline' the construction here. + + data is passed as pointer integer + base is passed as pointer integer + pv is passed as 32 bit integer. + """ + d.checkIntType(data) + d.checkIntType(base) + d.checkIntType(pv) + + t = qdumpHelper_qle_cutBits(pv, 0, 3) + v = qdumpHelper_qle_cutBits(pv, 5, 27) + latinOrIntValue = qdumpHelper_qle_cutBits(pv, 3, 1) + + if t == 0: + d.putType('QJsonValue (Null)') + d.putValue('Null') + return + if t == 1: + d.putType('QJsonValue (Bool)') + d.putValue('true' if v else 'false') + return + if t == 2: + d.putType('QJsonValue (Number)') + if latinOrIntValue: + w = toInteger(v) + if w >= 0x4000000: + w -= 0x8000000 + d.putValue(w) + else: + data = base + v + f = d.split('d', data)[0] + d.putValue(str(f)) + return + if t == 3: + d.putType('QJsonValue (String)') + data = base + v + if latinOrIntValue: + length = d.extractUShort(data) + d.putValue(d.readMemory(data + 2, length), 'latin1') + else: + length = d.extractUInt(data) + d.putValue(d.readMemory(data + 4, length * 2), 'utf16') + return + if t == 4: + d.putType('QJsonValue (Array)') + qdumpHelper_QJsonArray(d, data, base + v) + return + if t == 5: + d.putType('QJsonValue (Object)') + qdumpHelper_QJsonObject(d, data, base + v) + + +def qdumpHelper_QJsonArray(d, data, array): + """ + Parameters are the parameters to the + QJsonArray(QJsonPrivate::Data *data, QJsonPrivate::Array *array) + constructor. We 'inline' the construction here. + + array is passed as integer pointer to the QJsonPrivate::Base object. + """ + + if data: + # The 'length' part of the _dummy member: + n = qdumpHelper_qle_cutBits(d.extractUInt(array + 4), 1, 31) + else: + n = 0 + + d.putItemCount(n) + if d.isExpanded(): + with Children(d, maxNumChild=1000): + table = array + d.extractUInt(array + 8) + for i in range(n): + with SubItem(d, i): + qdumpHelper_QJsonValue(d, data, array, d.extractUInt(table + 4 * i)) + + +def qdumpHelper_QJsonObject(d, data, obj): + """ + Parameters are the parameters to the + QJsonObject(QJsonPrivate::Data *data, QJsonPrivate::Object *object); + constructor. We "inline" the construction here. + + obj is passed as integer pointer to the QJsonPrivate::Base object. + """ + + if data: + # The 'length' part of the _dummy member: + n = qdumpHelper_qle_cutBits(d.extractUInt(obj + 4), 1, 31) + else: + n = 0 + + d.putItemCount(n) + if d.isExpanded(): + with Children(d, maxNumChild=1000): + table = obj + d.extractUInt(obj + 8) + for i in range(n): + with SubItem(d, i): + entryPtr = table + 4 * i # entryAt(i) + entryStart = obj + d.extractUInt(entryPtr) # Entry::value + keyStart = entryStart + 4 # sizeof(QJsonPrivate::Entry) == 4 + val = d.extractInt(entryStart) + key = d.extractInt(keyStart) + isLatinKey = qdumpHelper_qle_cutBits(val, 4, 1) + if isLatinKey: + keyLength = d.extractUShort(keyStart) + d.putField('key', d.readMemory(keyStart + 2, keyLength)) + d.putField('keyencoded', 'latin1') + else: + keyLength = d.extractUInt(keyStart) + d.putField('key', d.readMemory(keyStart + 4, keyLength)) + d.putField('keyencoded', 'utf16') + + qdumpHelper_QJsonValue(d, data, obj, val) + + +def qdump__QJsonValue(d, value): + (data, dd, t) = value.split('QpI') + + if d.qtVersion() >= 0x050f00: + value = d.createProxyValue((data, dd, t, False), 'QCborValue_proxy') + d.putItem(value) + return + + if t == 0: + d.putType('QJsonValue (Null)') + d.putValue('Null') + return + if t == 1: + d.putType('QJsonValue (Bool)') + v = value.split('b') + d.putValue('true' if v else 'false') + return + if t == 2: + d.putType('QJsonValue (Number)') + d.putValue(value.split('d')) + return + if t == 3: + d.putType('QJsonValue (String)') + string = value.split('{@QString}')[0] + elided, base = d.encodeString(string, d.displayStringLimit) + d.putValue(base, 'utf16', elided=elided) + return + if t == 4: + d.putType('QJsonValue (Array)') + qdumpHelper_QJsonArray(d, dd, data) + return + if t == 5: + d.putType('QJsonValue (Object)') + qdumpHelper_QJsonObject(d, dd, data) + return + d.putType('QJsonValue (Undefined)') + d.putEmptyValue() + + +def qdump__QJsonArray(d, value): + if d.qtVersion() >= 0x060000: + dptr = d.extractPointer(value) + if not dptr: + d.putItemCount(0) + else: + qdumpHelper_QCbor_array(d, dptr, False) + elif d.qtVersion() >= 0x050f00: + _, container_ptr = value.split('pp') + qdumpHelper_QCbor_array(d, container_ptr, False) + else: + qdumpHelper_QJsonArray(d, value['d'].pointer(), value['a'].pointer()) + + +def qdump__QJsonObject(d, value): + if d.qtVersion() >= 0x060000: + dptr = d.extractPointer(value) + if not dptr: + d.putItemCount(0) + else: + qdumpHelper_QCbor_map(d, dptr, False) + elif d.qtVersion() >= 0x050f00: + _, container_ptr = value.split('pp') + qdumpHelper_QCbor_map(d, container_ptr, False) + else: + qdumpHelper_QJsonObject(d, value['d'].pointer(), value['o'].pointer()) + + +def qdump__QSqlResultPrivate(d, value): + # QSqlResult *q_ptr; + # QPointer sqldriver; + # int idx; + # QString sql; + # bool active; + # bool isSel; + # QSqlError error; + # bool forwardOnly; + # QSql::NumericalPrecisionPolicy precisionPolicy; + # int bindCount; + # QSqlResult::BindingSyntax binds; + # QString executedQuery; + # QHash types; + # QVector values; + # QHash > indexes; + # QVector holders + vptr, qptr, sqldriver1, sqldriver2, idx, pad, sql, active, isSel, pad, \ + error1, error2, error3, \ + forwardOnly, pad, precisionPolicy, bindCount, \ + binds, executedQuery, types, values, indexes, holders = \ + value.split('ppppi@{@QString}bb@pppb@iiii{@QString}ppp') + + d.putStringValue(sql) + d.putPlainChildren(value) + + +def qdump__QSqlField(d, value): + val, dptr = value.split('{@QVariant}p') + qdump__QVariant(d, val) + d.putBetterType(d.currentType.value.replace('QVariant', 'QSqlField')) + d.putPlainChildren(value) + + +def qdump__QLazilyAllocated(d, value): + p = value.extractPointer() + if p == 0: + d.putValue("(null)") + else: + d.putItem(d.createValue(p, value.type[0])) + d.putBetterType(value.type) + + +def qdump__qfloat16(d, value): + h = value.split('H')[0] + # Stole^H^H^HHeavily inspired by J.F. Sebastian at + # http://math.stackexchange.com/questions/1128204/how-to-convert- + # from-floating-point-binary-to-decimal-in-half-precision16-bits + sign = h >> 15 + exp = (h >> 10) & 0b011111 + fraction = h & (2**10 - 1) + if exp == 0: + if fraction == 0: + res = -0.0 if sign else 0.0 + else: + res = (-1)**sign * fraction / 2**10 * 2**(-14) # subnormal + elif exp == 0b11111: + res = ('-inf' if sign else 'inf') if fraction == 0 else 'nan' + else: + res = (-1)**sign * (1 + 1. * fraction / 2**10) * 2**(exp - 15) + d.putValue(res) + d.putPlainChildren(value) + + + +def qdumpHelper_QCbor_string(d, container_ptr, element_index, is_bytes): + # d.split('i@{@QByteArray::size_type}pp', container_ptr) doesn't work with CDB, + # so be explicit: + data_pos = container_ptr + (2 * d.ptrSize() if d.qtVersion() >= 0x060000 else 8) + elements_pos = data_pos + (3 * d.ptrSize() if d.qtVersion() >= 0x060000 else d.ptrSize()) + elements_data_ptr, elements_size = d.vectorData(elements_pos) + element_at_n_addr = elements_data_ptr + element_index * 16 # sizeof(QtCbor::Element) == 16 + element_value, _, element_flags = d.split('qII', element_at_n_addr) + enc = 'latin1' if is_bytes or (element_flags & 8) else 'utf16' + bytedata, _, _ = d.qArrayData(data_pos) + bytedata += element_value + if d.qtVersion() >= 0x060000: + bytedata_len = d.extractInt64(bytedata) + bytedata_data = bytedata + 8 + else: + bytedata_len = d.extractInt(bytedata) + bytedata_data = bytedata + 4 # sizeof(QtCbor::ByteData) header part + + elided, shown = d.computeLimit(bytedata_len, d.displayStringLimit) + res = d.readMemory(bytedata_data, shown) + d.putValue(res, enc, elided=elided) + + +def qdumpHelper_QCborArray_valueAt(d, container_ptr, elements_data_ptr, idx, bytedata, is_cbor): + element_at_n_addr = elements_data_ptr + idx * 16 # sizeof(QtCbor::Element) == 15 + element_value, element_type, element_flags = d.split('qII', element_at_n_addr) + element_container, _, _ = d.split('pII', element_at_n_addr) + if element_flags & 1: # QtCbor::Element::IsContainer + return d.createProxyValue((-1, element_container, element_type, is_cbor), 'QCborValue_proxy') + if element_flags & 2: # QtCbor::Element::HasByteData + return d.createProxyValue((idx, container_ptr, element_type, is_cbor), 'QCborValue_proxy') + return d.createProxyValue((element_value, 0, element_type, is_cbor), 'QCborValue_proxy') + + +def qdumpHelper_QCbor_array(d, container_ptr, is_cbor): + if not container_ptr: + d.putItemCount(0) + return + # d.split('i@{@QByteArray::size_type}pp', container_ptr) doesn't work with CDB, + # so be explicit: + data_pos = container_ptr + (2 * d.ptrSize() if d.qtVersion() >= 0x060000 else 8) + elements_pos = data_pos + (3 * d.ptrSize() if d.qtVersion() >= 0x060000 else d.ptrSize()) + elements_data_ptr, elements_size = d.vectorData(elements_pos) + d.putItemCount(elements_size) + if d.isExpanded(): + bytedata, _, _ = d.qArrayData(data_pos) + with Children(d, maxNumChild=1000): + for i in range(elements_size): + d.putSubItem(i, qdumpHelper_QCborArray_valueAt(d, container_ptr, elements_data_ptr, i, bytedata, is_cbor)) + + +def qdump__QCborArray(d, value): + container_ptr = value.extractPointer() + qdumpHelper_QCbor_array(d, container_ptr, True) + + +def qdumpHelper_QCbor_map(d, container_ptr, is_cbor): + if not container_ptr: + d.putItemCount(0) + return + # d.split('i@{@QByteArray::size_type}pp', container_ptr) doesn't work with CDB, + # so be explicit: + data_pos = container_ptr + (2 * d.ptrSize() if d.qtVersion() >= 0x060000 else 8) + elements_pos = data_pos + (3 * d.ptrSize() if d.qtVersion() >= 0x060000 else d.ptrSize()) + elements_data_ptr, elements_size = d.vectorData(elements_pos) + elements_size = int(elements_size / 2) + d.putItemCount(elements_size) + if d.isExpanded(): + bytedata, _, _ = d.qArrayDataHelper(data_pos) + with Children(d, maxNumChild=1000): + for i in range(elements_size): + key = qdumpHelper_QCborArray_valueAt(d, container_ptr, elements_data_ptr, 2 * i, bytedata, is_cbor) + val = qdumpHelper_QCborArray_valueAt(d, container_ptr, elements_data_ptr, 2 * i + 1, bytedata, is_cbor) + d.putPairItem(i, (key, val), 'key', 'value') + + +def qdump__QCborMap(d, value): + container_ptr = value.extractPointer() + qdumpHelper_QCbor_map(d, container_ptr, True) + + +def qdump__QCborValue(d, value): + item_data, container_ptr, item_type = value.split('qpi') + d.putItem(d.createProxyValue((item_data, container_ptr, item_type, True), 'QCborValue_proxy')) + d.putPlainChildren(value) + +def qdump__QCborValue_proxy(d, value): + item_data, container_ptr, item_type, is_cbor = value.data() + + def typename(key, is_cbor): + if is_cbor: + return { + 'invalid' : 'QCborValue (Invalid)', + 'int' : 'QCborValue (Integer)', + 'false' : 'QCborValue (False)', + 'true' : 'QCborValue (True)', + 'null' : 'QCborValue (Null)', + 'undefined' : 'QCborValue (Undefined)', + 'double' : 'QCborValue (Double)', + 'bytes' : 'QCborValue (ByteArray)', + 'string' : 'QCborValue (String)', + 'map' : 'QCborValue (Map)', + 'array' : 'QCborValue (Array)' + }.get(key, 'QCborValue (Unknown)') + else: + return { + 'invalid' : 'QJsonValue (Invalid)', + 'int' : 'QJsonValue (Number)', + 'false' : 'QJsonValue (Bool)', + 'true' : 'QJsonValue (Bool)', + 'null' : 'QJsonValue (Null)', + 'undefined' : 'QJsonValue (Undefined)', + 'double' : 'QJsonValue (Number)', + 'bytes' : 'QJsonValue (ByteArray)', + 'string' : 'QJsonValue (String)', + 'map' : 'QJsonValue (Object)', + 'array' : 'QJsonValue (Array)' + }.get(key, 'QJsonValue (Unknown)') + + + if item_type == 0xffffffffffffffff: + d.putType(typename('invalid', is_cbor)) + d.putValue('Invalid') + + elif item_type == 0x00: + d.putType(typename('int', is_cbor)) + d.putValue(item_data) + + elif item_type == 0x100 + 20: + d.putType(typename('false', is_cbor)) + d.putValue('False') + + elif item_type == 0x100 + 21: + d.putType(typename('true', is_cbor)) + d.putValue('True') + + elif item_type == 0x100 + 22: + d.putType(typename('null', is_cbor)) + d.putValue('Null') + + elif item_type == 0x100 + 23: + d.putType(typename('undefined', is_cbor)) + d.putValue('Undefined') + + elif item_type == 0x202: + val = struct.unpack('d', struct.pack('q', item_data)) + d.putType(typename('double', is_cbor)) + d.putValue('%f' % val) + + elif item_type == 0x40: + d.putType(typename('bytes', is_cbor)) + qdumpHelper_QCbor_string(d, container_ptr, item_data, True) + + elif item_type == 0x60: + d.putType(typename('string', is_cbor)) + qdumpHelper_QCbor_string(d, container_ptr, item_data, False) + + elif item_type == 0x80: + d.putType(typename('array', is_cbor)) + qdumpHelper_QCbor_array(d, container_ptr, is_cbor) + + elif item_type == 0xa0: + d.putType(typename('map', is_cbor)) + qdumpHelper_QCbor_map(d, container_ptr, is_cbor) + + elif item_type == 0x10000: + d.putType('QCborValue (DateTime)') + d.putValue('DateTime') + + elif item_type == 0x10020: + d.putType('QCborValue (Url)') + d.putValue('Url') + + elif item_type == 0x10023: + d.putType('QCborValue (RegularExpression)') + d.putValue('RegularExpression') + + elif item_type == 0x10025: + d.putType('QCborValue (Uuid)') + d.putValue('Uuid') + + else: + d.putType('QCborValue (Unknown)') + d.putValue(item_data) + + +def qendian_helper(d, value, code, endian): + data = value.data() + size = len(data) + v = struct.unpack_from(endian + code, data) + d.putValue('%d (%s)' % (v[0], 'LE' if endian == '<' else 'BE')) + d.putNumChild(size) + if d.isExpanded(): + s = struct.unpack_from(endian + 'b' * size, data) + with Children(d): + for i in range(size): + with SubItem(d, '[%d]' % i): + d.putValue(s[i]) + d.putType('byte') + + +def qdump__quint16_le(d, value): + qendian_helper(d, value, 'H', '<') + +def qdump__quint32_le(d, value): + qendian_helper(d, value, 'I', '<') + +def qdump__quint64_le(d, value): + qendian_helper(d, value, 'Q', '<') + +def qdump__quint16_be(d, value): + qendian_helper(d, value, 'H', '>') + +def qdump__quint32_be(d, value): + qendian_helper(d, value, 'I', '>') + +def qdump__quint64_be(d, value): + qendian_helper(d, value, 'Q', '>') diff --git a/share/qtcreator/debugger/python2/stdtypes.py b/share/qtcreator/debugger/python2/stdtypes.py new file mode 100644 index 00000000000..c14812dee1d --- /dev/null +++ b/share/qtcreator/debugger/python2/stdtypes.py @@ -0,0 +1,1115 @@ +# Copyright (C) 2016 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +from utils import DisplayFormat +from dumper import Children, SubItem, DumperBase + + +def qform__std__array(): + return [DisplayFormat.ArrayPlot] + + +def qdump__std__array(d, value): + size = value.type[1] + d.putItemCount(size) + if d.isExpanded(): + d.putPlotData(value.address(), size, value.type[0]) + + +def qdump__std__function(d, value): + (ptr, dummy1, manager, invoker) = value.split('pppp') + if manager: + if ptr > 2: + d.putSymbolValue(ptr) + else: + d.putEmptyValue() + d.putBetterType(value.type) + else: + d.putValue('(null)') + d.putPlainChildren(value) + + +def qdump__std__complex(d, value): + innerType = value.type[0] + (real, imag) = value.split('{%s}{%s}' % (innerType.name, innerType.name)) + d.putValue("(%s, %s)" % (real.display(), imag.display())) + d.putExpandable() + if d.isExpanded(): + with Children(d, 2, childType=innerType): + d.putSubItem("real", real) + d.putSubItem("imag", imag) + + +def qdump__std__deque(d, value): + if d.isQnxTarget(): + qdumpHelper__std__deque__qnx(d, value) + elif d.isMsvcTarget(): + qdumpHelper__std__deque__msvc(d, value) + elif value.hasMember("_M_impl"): + qdumpHelper__std__deque__libstdcxx(d, value) + elif value.hasMember("__start_"): + qdumpHelper__std__deque__libcxx(d, value) + elif value.type.size() == 10 * d.ptrSize(): + qdumpHelper__std__deque__libstdcxx(d, value) + elif value.type.size() == 6 * d.ptrSize(): + qdumpHelper__std__deque__libcxx(d, value) + else: + qdumpHelper__std__deque__libstdcxx(d, value) + + +def qdumpHelper__std__deque__libstdcxx(d, value): + innerType = value.type[0] + innerSize = innerType.size() + bufsize = 1 + if innerSize < 512: + bufsize = 512 // innerSize + + (mapptr, mapsize, startCur, startFirst, startLast, startNode, + finishCur, finishFirst, finishLast, finishNode) = value.split("pppppppppp") + + size = bufsize * ((finishNode - startNode) // d.ptrSize() - 1) + size += (finishCur - finishFirst) // innerSize + size += (startLast - startCur) // innerSize + + d.check(0 <= size and size <= 1000 * 1000 * 1000) + d.putItemCount(size) + if d.isExpanded(): + with Children(d, size, maxNumChild=2000, childType=innerType): + pcur = startCur + plast = startLast + pnode = startNode + for i in d.childRange(): + d.putSubItem(i, d.createValue(pcur, innerType)) + pcur += innerSize + if pcur == plast: + newnode = pnode + d.ptrSize() + pfirst = d.extractPointer(newnode) + plast = pfirst + bufsize * d.ptrSize() + pcur = pfirst + pnode = newnode + + +def qdumpHelper__std__deque__libcxx(d, value): + mptr, mfirst, mbegin, mend, start, size = value.split("pppptt") + d.check(0 <= size and size <= 1000 * 1000 * 1000) + d.putItemCount(size) + if d.isExpanded(): + innerType = value.type[0] + innerSize = innerType.size() + ptrSize = d.ptrSize() + bufsize = (4096 // innerSize) if innerSize < 256 else 16 + with Children(d, size, maxNumChild=2000, childType=innerType): + for i in d.childRange(): + k, j = divmod(start + i, bufsize) + base = d.extractPointer(mfirst + k * ptrSize) + d.putSubItem(i, d.createValue(base + j * innerSize, innerType)) + + +def qdumpHelper__std__deque__qnx(d, value): + innerType = value.type[0] + innerSize = innerType.size() + if innerSize <= 1: + bufsize = 16 + elif innerSize <= 2: + bufsize = 8 + elif innerSize <= 4: + bufsize = 4 + elif innerSize <= 8: + bufsize = 2 + else: + bufsize = 1 + + try: + val = value['_Mypair']['_Myval2'] + except: + val = value + + myoff = val['_Myoff'].integer() + mysize = val['_Mysize'].integer() + mapsize = val['_Mapsize'].integer() + + d.check(0 <= mapsize and mapsize <= 1000 * 1000 * 1000) + d.putItemCount(mysize) + if d.isExpanded(): + with Children(d, mysize, maxNumChild=2000, childType=innerType): + map = val['_Map'] + for i in d.childRange(): + block = myoff / bufsize + offset = myoff - (block * bufsize) + if mapsize <= block: + block -= mapsize + d.putSubItem(i, map[block][offset]) + myoff += 1 + + +def qdumpHelper__std__deque__msvc(d, value): + innerType = value.type[0] + innerSize = innerType.size() + if innerSize <= 1: + bufsize = 16 + elif innerSize <= 2: + bufsize = 8 + elif innerSize <= 4: + bufsize = 4 + elif innerSize <= 8: + bufsize = 2 + else: + bufsize = 1 + + (proxy, map, mapsize, myoff, mysize) = value.split("ppppp") + + d.check(0 <= mapsize and mapsize <= 1000 * 1000 * 1000) + d.putItemCount(mysize) + if d.isExpanded(): + with Children(d, mysize, maxNumChild=2000, childType=innerType): + for i in d.childRange(): + if myoff >= bufsize * mapsize: + myoff = 0 + buf = map + ((myoff // bufsize) * d.ptrSize()) + address = d.extractPointer(buf) + ((myoff % bufsize) * innerSize) + d.putSubItem(i, d.createValue(address, innerType)) + myoff += 1 + + +def qdump__std____debug__deque(d, value): + qdump__std__deque(d, value) + + +def qdump__std__list(d, value): + if d.isQnxTarget() or d.isMsvcTarget(): + qdump__std__list__QNX(d, value) + return + + if value.type.size() == 3 * d.ptrSize(): + # C++11 only. + (dummy1, dummy2, size) = value.split("ppp") + d.putItemCount(size) + else: + # Need to count manually. + p = d.extractPointer(value) + head = value.address() + size = 0 + while head != p and size < 1001: + size += 1 + p = d.extractPointer(p) + d.putItemCount(size, 1000) + + if d.isExpanded(): + p = d.extractPointer(value) + innerType = value.type[0] + with Children(d, size, maxNumChild=1000, childType=innerType): + for i in d.childRange(): + d.putSubItem(i, d.createValue(p + 2 * d.ptrSize(), innerType)) + p = d.extractPointer(p) + + +def qdump__std__list__QNX(d, value): + if d.isDebugBuild is None: + try: + _ = value["_Mypair"]["_Myval2"]["_Myproxy"] + d.isDebugBuild = True + except Exception: + d.isDebugBuild = False + if d.isDebugBuild: + (proxy, head, size) = value.split("ppp") + else: + (head, size) = value.split("pp") + + d.putItemCount(size, 1000) + + if d.isExpanded(): + p = d.extractPointer(head) + innerType = value.type[0] + with Children(d, size, maxNumChild=1000, childType=innerType): + for i in d.childRange(): + d.putSubItem(i, d.createValue(p + 2 * d.ptrSize(), innerType)) + p = d.extractPointer(p) + + +def qdump__std____debug__list(d, value): + qdump__std__list(d, value) + + +def qdump__std____cxx11__list(d, value): + qdump__std__list(d, value) + + +def qform__std__map(): + return [DisplayFormat.CompactMap] + + +def qdump__std__map(d, value): + if d.isQnxTarget() or d.isMsvcTarget(): + qdump_std__map__helper(d, value) + return + + # stuff is actually (color, pad) with 'I@', but we can save cycles/ + (compare, stuff, parent, left, right) = value.split('ppppp') + size = value["_M_t"]["_M_impl"]["_M_node_count"].integer() + d.check(0 <= size and size <= 100 * 1000 * 1000) + d.putItemCount(size) + + if d.isExpanded(): + keyType = value.type[0] + valueType = value.type[1] + with Children(d, size, maxNumChild=1000): + node = value["_M_t"]["_M_impl"]["_M_header"]["_M_left"] + nodeSize = node.dereference().type.size() + typeCode = "@{%s}@{%s}" % (keyType.name, valueType.name) + for i in d.childRange(): + (pad1, key, pad2, value) = d.split(typeCode, node.pointer() + nodeSize) + d.putPairItem(i, (key, value)) + if node["_M_right"].pointer() == 0: + parent = node["_M_parent"] + while True: + if node.pointer() != parent["_M_right"].pointer(): + break + node = parent + parent = parent["_M_parent"] + if node["_M_right"] != parent: + node = parent + else: + node = node["_M_right"] + while True: + if node["_M_left"].pointer() == 0: + break + node = node["_M_left"] + + +def qdump_std__map__helper(d, value): + if d.isDebugBuild is None: + try: + _ = value["_Mypair"]["_Myval2"]["_Myval2"]["_Myproxy"] + d.isDebugBuild = True + except Exception: + d.isDebugBuild = False + if d.isDebugBuild: + (proxy, head, size) = value.split("ppp") + else: + (head, size) = value.split("pp") + d.check(0 <= size and size <= 100 * 1000 * 1000) + d.putItemCount(size) + if d.isExpanded(): + keyType = value.type[0] + valueType = value.type[1] + pairType = value.type[3][0] + + def helper(node): + (left, parent, right, color, isnil, pad, pair) = d.split( + "pppcc@{%s}" % (pairType.name), node) + if left != head: + for res in helper(left): + yield res + yield pair.split("{%s}@{%s}" % (keyType.name, valueType.name))[::2] + if right != head: + for res in helper(right): + yield res + + (smallest, root) = d.split("pp", head) + with Children(d, size, maxNumChild=1000): + for (pair, i) in zip(helper(root), d.childRange()): + d.putPairItem(i, pair) + + +def qdump__std____debug__map(d, value): + qdump__std__map(d, value) + + +def qdump__std____debug__set(d, value): + qdump__std__set(d, value) + + +def qdump__std__multiset(d, value): + qdump__std__set(d, value) + + +def qdump__std____cxx1998__map(d, value): + qdump__std__map(d, value) + + +def qform__std__multimap(): + return [DisplayFormat.CompactMap] + + +def qdump__std__multimap(d, value): + return qdump__std__map(d, value) + + +def qdumpHelper__std__tree__iterator(d, value, isSet=False): + treeTypeName = None + if value.type.name.endswith("::iterator"): + treeTypeName = value.type.name[:-len("::iterator")] + elif value.type.name.endswith("::const_iterator"): + treeTypeName = value.type.name[:-len("::const_iterator")] + treeType = d.lookupType(treeTypeName) if treeTypeName else value.type[0] + keyType = treeType[0] + valueType = treeType[1] + node = value["_M_node"].dereference() # std::_Rb_tree_node_base + d.putExpandable() + d.putEmptyValue() + if d.isExpanded(): + with Children(d): + if isSet: + typecode = 'pppp@{%s}' % keyType.name + (color, parent, left, right, pad1, key) = d.split(typecode, node) + d.putSubItem("value", key) + else: + typecode = 'pppp@{%s}@{%s}' % (keyType.name, valueType.name) + (color, parent, left, right, pad1, key, pad2, value) = d.split(typecode, node) + d.putSubItem("first", key) + d.putSubItem("second", value) + with SubItem(d, "[node]"): + d.putExpandable() + d.putEmptyValue() + d.putType(" ") + if d.isExpanded(): + with Children(d): + #d.putSubItem("color", color) + nodeType = node.type.pointer() + d.putSubItem("left", d.createValue(left, nodeType)) + d.putSubItem("right", d.createValue(right, nodeType)) + d.putSubItem("parent", d.createValue(parent, nodeType)) + + +def qdump__std___Rb_tree_iterator(d, value): + qdumpHelper__std__tree__iterator(d, value) + + +def qdump__std___Rb_tree_const_iterator(d, value): + qdumpHelper__std__tree__iterator(d, value) + + +def qdump__std__map__iterator(d, value): + qdumpHelper__std__tree__iterator(d, value) + + +def qdump____gnu_debug___Safe_iterator(d, value): + d.putItem(value["_M_current"]) + + +def qdump__std__map__const_iterator(d, value): + qdumpHelper__std__tree__iterator(d, value) + + +def qdump__std__set__iterator(d, value): + qdumpHelper__std__tree__iterator(d, value, True) + + +def qdump__std__set__const_iterator(d, value): + qdumpHelper__std__tree__iterator(d, value, True) + + +def qdump__std____cxx1998__set(d, value): + qdump__std__set(d, value) + + +def qdumpHelper__std__tree__iterator_MSVC(d, value): + d.putExpandable() + d.putEmptyValue() + if d.isExpanded(): + with Children(d): + childType = value.type[0][0][0] + (proxy, nextIter, node) = value.split("ppp") + (left, parent, right, color, isnil, pad, child) = \ + d.split("pppcc@{%s}" % (childType.name), node) + if (childType.name.startswith("std::pair")): + # workaround that values created via split have no members + keyType = childType[0].name + valueType = childType[1].name + d.putPairItem(None, child.split("{%s}@{%s}" % (keyType, valueType))[::2]) + else: + d.putSubItem("value", child) + + +def qdump__std___Tree_const_iterator(d, value): + qdumpHelper__std__tree__iterator_MSVC(d, value) + + +def qdump__std___Tree_iterator(d, value): + qdumpHelper__std__tree__iterator_MSVC(d, value) + + +def qdump__std__set(d, value): + if d.isQnxTarget() or d.isMsvcTarget(): + qdump__std__set__QNX(d, value) + return + + impl = value["_M_t"]["_M_impl"] + size = impl["_M_node_count"].integer() + d.check(0 <= size and size <= 100 * 1000 * 1000) + d.putItemCount(size) + if d.isExpanded(): + valueType = value.type[0] + node = impl["_M_header"]["_M_left"] + nodeSize = node.dereference().type.size() + typeCode = "@{%s}" % valueType.name + with Children(d, size, maxNumChild=1000, childType=valueType): + for i in d.childRange(): + (pad, val) = d.split(typeCode, node.pointer() + nodeSize) + d.putSubItem(i, val) + if node["_M_right"].pointer() == 0: + parent = node["_M_parent"] + while node.pointer() == parent["_M_right"].pointer(): + node = parent + parent = parent["_M_parent"] + if node["_M_right"] != parent: + node = parent + else: + node = node["_M_right"] + while node["_M_left"].pointer() != 0: + node = node["_M_left"] + + +def qdump__std__set__QNX(d, value): + if d.isDebugBuild is None: + try: + _ = value["_Mypair"]["_Myval2"]["_Myval2"]["_Myproxy"] + d.isDebugBuild = True + except Exception: + d.isDebugBuild = False + if d.isDebugBuild: + (proxy, head, size) = value.split("ppp") + else: + (head, size) = value.split("pp") + d.check(0 <= size and size <= 100 * 1000 * 1000) + d.putItemCount(size) + if d.isExpanded(): + childType = value.type[0] + + def helper(node): + (left, parent, right, color, isnil, pad, value) = d.split( + "pppcc@{%s}" % childType.name, node) + if left != head: + for res in helper(left): + yield res + yield value + if right != head: + for res in helper(right): + yield res + + (smallest, root) = d.split("pp", head) + with Children(d, size, maxNumChild=1000): + for (item, i) in zip(helper(root), d.childRange()): + d.putSubItem(i, item) + + +def std1TreeMin(d, node): + #_NodePtr __tree_min(_NodePtr __x): + # while (__x->__left_ != nullptr) + # __x = __x->__left_; + # return __x; + # + left = node['__left_'] + if left.pointer(): + node = left + return node + + +def std1TreeIsLeftChild(d, node): + # bool __tree_is_left_child(_NodePtr __x): + # return __x == __x->__parent_->__left_; + # + other = node['__parent_']['__left_'] + return node.pointer() == other.pointer() + + +def std1TreeNext(d, node): + #_NodePtr __tree_next(_NodePtr __x): + # if (__x->__right_ != nullptr) + # return __tree_min(__x->__right_); + # while (!__tree_is_left_child(__x)) + # __x = __x->__parent_; + # return __x->__parent_; + # + right = node['__right_'] + if right.pointer(): + return std1TreeMin(d, right) + while not std1TreeIsLeftChild(d, node): + node = node['__parent_'] + return node['__parent_'] + + +def qdump__std__stack(d, value): + d.putItem(value["c"]) + d.putBetterType(value.type) + + +def qdump__std____debug__stack(d, value): + qdump__std__stack(d, value) + + +def qform__std__string(): + return [DisplayFormat.Latin1String, DisplayFormat.SeparateLatin1String, + DisplayFormat.Utf8String, DisplayFormat.SeparateUtf8String] + + +def qdump__std__string(d, value): + qdumpHelper_std__string(d, value, d.createType("char"), d.currentItemFormat()) + + +def qdumpHelper_std__string(d, value, charType, format): + if d.isQnxTarget(): + qdumpHelper__std__string__QNX(d, value, charType, format) + return + if d.isMsvcTarget(): + qdumpHelper__std__string__MSVC(d, value, charType, format) + return + + # GCC 9, QTCREATORBUG-22753 + try: + data = value["_M_dataplus"]["_M_p"].pointer() + except: + data = value.extractPointer() + try: + size = int(value["_M_string_length"]) + d.putCharArrayHelper(data, size, charType, format) + return + except: + pass + + # We can't lookup the std::string::_Rep type without crashing LLDB, + # so hard-code assumption on member position + # struct { size_type _M_length, size_type _M_capacity, int _M_refcount; } + (size, alloc, refcount) = d.split("ppi", data - 3 * d.ptrSize()) + d.check(refcount >= -1) # Can be -1 according to docs. + d.check(0 <= size and size <= alloc and alloc <= 100 * 1000 * 1000) + d.putCharArrayHelper(data, size, charType, format) + + +def qdumpHelper__std__string__QNX(d, value, charType, format): + size = value['_Mysize'] + alloc = value['_Myres'] + _BUF_SIZE = int(16 / charType.size()) + if _BUF_SIZE <= alloc: # (_BUF_SIZE <= _Myres ? _Bx._Ptr : _Bx._Buf); + data = value['_Bx']['_Ptr'] + else: + data = value['_Bx']['_Buf'] + sizePtr = data.cast(d.charType().pointer()) + refcount = int(sizePtr[-1]) + d.check(refcount >= -1) # Can be -1 accoring to docs. + d.check(0 <= size and size <= alloc and alloc <= 100 * 1000 * 1000) + d.putCharArrayHelper(sizePtr, size, charType, format) + + +def qdumpHelper__std__string__MSVC(d, value, charType, format): + if d.isDebugBuild is None: + try: + _ = value["_Mypair"]["_Myval2"]["_Myproxy"] + d.isDebugBuild = True + except Exception: + d.isDebugBuild = False + if d.isDebugBuild: + (_, buffer, size, alloc) = value.split("p16spp") + else: + (buffer, size, alloc) = value.split("16spp") + d.check(0 <= size and size <= alloc and alloc <= 100 * 1000 * 1000) + _BUF_SIZE = int(16 / charType.size()) + if _BUF_SIZE <= alloc: + if d.isDebugBuild: + (proxy, data) = value.split("pp") + else: + data = value.extractPointer() + else: + if d.isDebugBuild: + data = value.address() + d.ptrSize() + else: + data = value.address() + d.putCharArrayHelper(data, size, charType, format) + + +def qdump__std____weak_ptr(d, value): + return qdump__std__shared_ptr(d, value) + + +def qdump__std__weak_ptr(d, value): + return qdump__std__shared_ptr(d, value) + + +def qdump__std__shared_ptr(d, value): + if d.isMsvcTarget(): + i = value["_Ptr"] + else: + i = value["_M_ptr"] + + if i.pointer() == 0: + d.putValue("(null)") + else: + d.putItem(i.dereference()) + d.putBetterType(value.type) + + +def qdump__std__unique_ptr(d, value): + if value.type.size() == d.ptrSize(): + p = d.extractPointer(value) + else: + _, p = value.split("pp"); # For custom deleters. + if p == 0: + d.putValue("(null)") + else: + if d.isMsvcTarget(): + try: + d.putItem(value["_Mypair"]["_Myval2"]) + d.putValue(d.currentValue.value, d.currentValue.encoding) + except: + d.putItem(d.createValue(p, value.type[0])) + else: + d.putItem(d.createValue(p, value.type[0])) + d.putBetterType(value.type) + + +def qdump__std__pair(d, value): + typeCode = '{%s}@{%s}' % (value.type[0].name, value.type[1].name) + first, pad, second = value.split(typeCode) + with Children(d): + key = d.putSubItem('first', first) + value = d.putSubItem('second', second) + key = key.value if key.encoding is None else "..." + value = value.value if value.encoding is None else "..." + d.putValue('(%s, %s)' % (key, value)) + + +def qform__std__unordered_map(): + return [DisplayFormat.CompactMap] + + +def qform__std____debug__unordered_map(): + return [DisplayFormat.CompactMap] + + +def qdump__std__unordered_map(d, value): + if d.isQnxTarget(): + qdump__std__list__QNX(d, value["_List"]) + return + + if d.isMsvcTarget(): + _list = value["_List"] + if d.isDebugBuild is None: + try: + _ = _list["_Mypair"]["_Myval2"]["_Myproxy"] + d.isDebugBuild = True + except Exception: + d.isDebugBuild = False + if d.isDebugBuild: + (_, start, size) = _list.split("ppp") + else: + (start, size) = _list.split("pp") + else: + try: + # gcc ~= 4.7 + size = value["_M_element_count"].integer() + start = value["_M_before_begin"]["_M_nxt"] + except: + try: + # libc++ (Mac) + size = value["_M_h"]["_M_element_count"].integer() + start = value["_M_h"]["_M_bbegin"]["_M_node"]["_M_nxt"] + except: + try: + # gcc 4.9.1 + size = value["_M_h"]["_M_element_count"].integer() + start = value["_M_h"]["_M_before_begin"]["_M_nxt"] + except: + # gcc 4.6.2 + size = value["_M_element_count"].integer() + start = value["_M_buckets"].dereference() + # FIXME: Pointer-aligned? + d.putItemCount(size) + # We don't know where the data is + d.putNumChild(0) + return + + d.putItemCount(size) + if d.isExpanded(): + keyType = value.type[0] + valueType = value.type[1] + if d.isMsvcTarget(): + typeCode = 'pp@{%s}@{%s}' % (keyType.name, valueType.name) + p = d.extractPointer(start) + else: + typeCode = 'p@{%s}@{%s}' % (keyType.name, valueType.name) + p = start.pointer() + with Children(d, size): + for i in d.childRange(): + if d.isMsvcTarget(): + p, _, _, key, _, val = d.split(typeCode, p) + else: + p, _, key, _, val = d.split(typeCode, p) + d.putPairItem(i, (key, val)) + + +def qdump__std____debug__unordered_map(d, value): + qdump__std__unordered_map(d, value) + + +def qform__std__unordered_multimap(): + return qform__std__unordered_map() + + +def qform__std____debug__unordered_multimap(): + return qform__std____debug__unordered_map() + + +def qdump__std__unordered_multimap(d, value): + qdump__std__unordered_map(d, value) + + +def qdump__std____debug__unordered_multimap(d, value): + qdump__std__unordered_multimap(d, value) + + +def qdump__std__unordered_set(d, value): + if d.isQnxTarget() or d.isMsvcTarget(): + qdump__std__list__QNX(d, value["_List"]) + return + + try: + # gcc ~= 4.7 + size = value["_M_element_count"].integer() + start = value["_M_before_begin"]["_M_nxt"] + offset = 0 + except: + try: + # libc++ (Mac) + size = value["_M_h"]["_M_element_count"].integer() + start = value["_M_h"]["_M_bbegin"]["_M_node"]["_M_nxt"] + offset = 0 + except: + try: + # gcc 4.6.2 + size = value["_M_element_count"].integer() + start = value["_M_buckets"].dereference() + offset = d.ptrSize() + except: + # gcc 4.9.1 + size = value["_M_h"]["_M_element_count"].integer() + start = value["_M_h"]["_M_before_begin"]["_M_nxt"] + offset = 0 + + d.putItemCount(size) + if d.isExpanded(): + p = start.pointer() + valueType = value.type[0] + with Children(d, size, childType=valueType): + ptrSize = d.ptrSize() + for i in d.childRange(): + d.putSubItem(i, d.createValue(p + ptrSize - offset, valueType)) + p = d.extractPointer(p + offset) + + +def qdump__std____debug__unordered_set(d, value): + qdump__std__unordered_set(d, value) + + +def qdump__std__unordered_multiset(d, value): + qdump__std__unordered_set(d, value) + + +def qdump__std____debug__unordered_multiset(d, value): + qdump__std__unordered_multiset(d, value) + + +def qform__std__valarray(): + return [DisplayFormat.ArrayPlot] + + +def qdump__std__valarray(d, value): + if d.isMsvcTarget(): + (data, size) = value.split('pp') + else: + (size, data) = value.split('pp') + d.putItemCount(size) + d.putPlotData(data, size, value.type[0]) + + +def qdump__std__variant(d, value): + if d.isMsvcTarget(): + which = int(value["_Which"]) + else: + which = int(value["_M_index"]) + type = d.templateArgument(value.type, which) + + if d.isMsvcTarget(): + storage = value + while which > 0: + storage = storage["_Tail"] + which = which - 1 + storage = storage["_Head"] + else: + storage = value["_M_u"]["_M_first"]["_M_storage"] + storage = storage.cast(type) + d.putItem(storage) + d.putBetterType(type) + + +def qform__std__vector(): + return [DisplayFormat.ArrayPlot] + + +def qedit__std__vector(d, value, data): + import gdb + values = data.split(',') + n = len(values) + innerType = value.type[0].name + cmd = "set $d = (%s*)calloc(sizeof(%s)*%s,1)" % (innerType, innerType, n) + gdb.execute(cmd) + cmd = "set {void*[3]}%s = {$d, $d+%s, $d+%s}" % (value.address(), n, n) + gdb.execute(cmd) + cmd = "set (%s[%d])*$d={%s}" % (innerType, n, data) + gdb.execute(cmd) + + +def qdump__std__vector(d, value): + if d.isQnxTarget() or d.isMsvcTarget(): + qdumpHelper__std__vector__msvc(d, value) + elif value.hasMember("_M_impl"): + qdumpHelper__std__vector__libstdcxx(d, value) + elif value.hasMember("__begin_"): + qdumpHelper__std__vector__libcxx(d, value) + else: + qdumpHelper__std__vector__libstdcxx(d, value) + + +def qdumpHelper__std__vector__nonbool(d, start, finish, alloc, inner_type): + size = int((finish - start) / inner_type.size()) + d.check(finish <= alloc) + if size > 0: + d.checkPointer(start) + d.checkPointer(finish) + d.checkPointer(alloc) + d.check(0 <= size and size <= 1000 * 1000 * 1000) + d.putItemCount(size) + d.putPlotData(start, size, inner_type) + + +def qdumpHelper__std__vector__bool(d, start, size, inner_type): + d.check(0 <= size and size <= 1000 * 1000 * 1000) + d.putItemCount(size) + if d.isExpanded(): + with Children(d, size, maxNumChild=10000, childType=inner_type): + for i in d.childRange(): + q = start + int(i / 8) + with SubItem(d, i): + d.putValue((int(d.extractPointer(q)) >> (i % 8)) & 1) + d.putType("bool") + + +def qdumpHelper__std__vector__libstdcxx(d, value): + inner_type = value.type[0] + if inner_type.name == "bool": + start = value["_M_start"]["_M_p"].pointer() + soffset = value["_M_start"]["_M_offset"].integer() + finish = value["_M_finish"]["_M_p"].pointer() + foffset = value["_M_finish"]["_M_offset"].integer() + alloc = value["_M_end_of_storage"].pointer() + size = (finish - start) * 8 + foffset - soffset # 8 is CHAR_BIT. + qdumpHelper__std__vector__bool(d, start, size, inner_type) + else: + start = value["_M_start"].pointer() + finish = value["_M_finish"].pointer() + alloc = value["_M_end_of_storage"].pointer() + qdumpHelper__std__vector__nonbool(d, start, finish, alloc, inner_type) + + +def qdumpHelper__std__vector__libcxx(d, value): + inner_type = value.type[0] + if inner_type.name == "bool": + start = value["__begin_"].pointer() + size = value["__size_"].integer() + qdumpHelper__std__vector__bool(d, start, size, inner_type) + else: + start = value["__begin_"].pointer() + finish = value["__end_"].pointer() + alloc = value["__end_cap_"].pointer() + qdumpHelper__std__vector__nonbool(d, start, finish, alloc, inner_type) + + +def qdumpHelper__std__vector__msvc(d, value): + inner_type = value.type[0] + if inner_type.name == "bool": + if d.isDebugBuild is None: + try: + _ = value["_Myproxy"] + d.isDebugBuild = True + except RuntimeError: + d.isDebugBuild = False + if d.isDebugBuild: + proxy1, proxy2, start, finish, alloc, size = value.split("pppppi") + else: + start, finish, alloc, size = value.split("pppi") + d.check(0 <= size and size <= 1000 * 1000 * 1000) + qdumpHelper__std__vector__bool(d, start, size, inner_type) + else: + if d.isDebugBuild is None: + try: + _ = value["_Mypair"]["_Myval2"]["_Myproxy"] + d.isDebugBuild = True + except RuntimeError: + d.isDebugBuild = False + if d.isDebugBuild: + proxy, start, finish, alloc = value.split("pppp") + else: + start, finish, alloc = value.split("ppp") + size = (finish - start) // inner_type.size() + d.check(0 <= size and size <= 1000 * 1000 * 1000) + qdumpHelper__std__vector__nonbool(d, start, finish, alloc, inner_type) + + +def qform__std____debug__vector(): + return [DisplayFormat.ArrayPlot] + + +def qdump__std____debug__vector(d, value): + qdump__std__vector(d, value) + + +def qdump__std__initializer_list(d, value): + innerType = value.type[0] + if d.isMsvcTarget(): + start = value["_First"].pointer() + end = value["_Last"].pointer() + size = int((end - start) / innerType.size()) + else: + try: + start = value["_M_array"].pointer() + size = value["_M_len"].integer() + except: + start = value["__begin_"].pointer() + size = value["__size_"].integer() + + d.putItemCount(size) + if d.isExpanded(): + d.putPlotData(start, size, innerType) + + +def qedit__std__string(d, value, data): + d.call('void', value, 'assign', '"%s"' % data.replace('"', '\\"')) + + +def qedit__string(d, expr, value): + qedit__std__string(d, expr, value) + + +def qedit__std____cxx11__string(d, expr, value): + qedit__std__string(d, expr, value) + + +def qedit__std__wstring(d, value, data): + d.call('void', value, 'assign', 'L"%s"' % data.replace('"', '\\"')) + + +def qedit__wstring(d, expr, value): + qedit__std__wstring(d, expr, value) + + +def qedit__std____cxx11__wstring(d, expr, value): + qedit__std__wstring(d, expr, value) + + +def qdump__string(d, value): + qdump__std__string(d, value) + + +def qform__std__wstring(): + return [DisplayFormat.Simple, DisplayFormat.Separate] + + +def qdump__std__wstring(d, value): + qdumpHelper_std__string(d, value, d.createType('wchar_t'), d.currentItemFormat()) + + +def qdump__std__basic_string(d, value): + innerType = value.type[0] + qdumpHelper_std__string(d, value, innerType, d.currentItemFormat()) + + +def qdump__std____cxx11__basic_string(d, value): + innerType = value.type[0] + try: + data = value["_M_dataplus"]["_M_p"].pointer() + size = int(value["_M_string_length"]) + except: + d.putEmptyValue() + d.putPlainChildren(value) + return + d.check(0 <= size) # and size <= alloc and alloc <= 100*1000*1000) + d.putCharArrayHelper(data, size, innerType, d.currentItemFormat()) + + +def qform__std____cxx11__string(d, value): + qform__std__string(d, value) + + +def qdump__std____cxx11__string(d, value): + (data, size) = value.split("pI") + d.check(0 <= size) # and size <= alloc and alloc <= 100*1000*1000) + d.putCharArrayHelper(data, size, d.charType(), d.currentItemFormat()) + +# Needed only to trigger the form report above. + + +def qform__std____cxx11__string(): + return qform__std__string() + + +def qform__std____cxx11__wstring(): + return qform__std__wstring() + + +def qdump__wstring(d, value): + qdump__std__wstring(d, value) + + +def qdump__std__once_flag(d, value): + d.putValue(value.split("i")[0]) + d.putBetterType(value.type) + d.putPlainChildren(value) + + +def qdump____gnu_cxx__hash_set(d, value): + ht = value["_M_ht"] + size = ht["_M_num_elements"].integer() + d.check(0 <= size and size <= 1000 * 1000 * 1000) + d.putItemCount(size) + innerType = value.type[0] + d.putType("__gnu__cxx::hash_set<%s>" % innerType.name) + if d.isExpanded(): + with Children(d, size, maxNumChild=1000, childType=innerType): + buckets = ht["_M_buckets"]["_M_impl"] + bucketStart = buckets["_M_start"] + bucketFinish = buckets["_M_finish"] + p = bucketStart + itemCount = 0 + for i in range((bucketFinish.pointer() - bucketStart.pointer()) // d.ptrSize()): + if p.dereference().pointer(): + cur = p.dereference() + while cur.pointer(): + d.putSubItem(itemCount, cur["_M_val"]) + cur = cur["_M_next"] + itemCount += 1 + p = p + 1 + + +def qdump__uint8_t(d, value): + d.putValue(value.integer()) + + +def qdump__int8_t(d, value): + d.putValue(value.integer()) + + +def qdump__std__byte(d, value): + d.putValue(value.integer()) + + +def qdump__std__optional(d, value): + innerType = value.type[0] + (payload, pad, initialized) = d.split('{%s}@b' % innerType.name, value) + if initialized: + d.putItem(payload) + d.putBetterType(innerType) + else: + d.putSpecialValue("empty") + + +def qdump__std__experimental__optional(d, value): + qdump__std__optional(d, value) diff --git a/share/qtcreator/debugger/python2/utils.py b/share/qtcreator/debugger/python2/utils.py new file mode 100644 index 00000000000..8019d1e530a --- /dev/null +++ b/share/qtcreator/debugger/python2/utils.py @@ -0,0 +1,129 @@ +# Copyright (C) 2016 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +# Debugger start modes. Keep in sync with DebuggerStartMode in debuggerconstants.h + + +# MT: Why does this not match (anymore?) to debuggerconstants.h : DebuggerStartMode ? +class DebuggerStartMode(): + ( + NoStartMode, + StartInternal, + StartExternal, + AttachExternal, + AttachCrashedExternal, + AttachCore, + AttachToRemoteServer, + AttachToRemoteProcess, + StartRemoteProcess, + ) = range(0, 9) + + +# Known special formats. Keep in sync with DisplayFormat in debuggerprotocol.h +class DisplayFormat(): + ( + Automatic, + Raw, + Simple, + Enhanced, + Separate, + Latin1String, + SeparateLatin1String, + Utf8String, + SeparateUtf8String, + Local8BitString, + Utf16String, + Ucs4String, + Array10, + Array100, + Array1000, + Array10000, + ArrayPlot, + CompactMap, + DirectQListStorage, + IndirectQListStorage, + ) = range(0, 20) + + +# Breakpoints. Keep synchronized with BreakpointType in breakpoint.h +class BreakpointType(): + ( + UnknownType, + BreakpointByFileAndLine, + BreakpointByFunction, + BreakpointByAddress, + BreakpointAtThrow, + BreakpointAtCatch, + BreakpointAtMain, + BreakpointAtFork, + BreakpointAtExec, + BreakpointAtSysCall, + WatchpointAtAddress, + WatchpointAtExpression, + BreakpointOnQmlSignalEmit, + BreakpointAtJavaScriptThrow, + ) = range(0, 14) + + +# Internal codes for types. Keep in sync with cdbextensions pytype.cpp +class TypeCode(): + ( + Typedef, + Struct, + Void, + Integral, + Float, + Enum, + Pointer, + Array, + Complex, + Reference, + Function, + MemberPointer, + FortranString, + Unresolvable, + Bitfield, + RValueReference, + ) = range(0, 16) + + +# Internal codes for logging channels. Keep in sync woth debuggerconstants.h +class LogChannel(): + ( + LogInput, # Used for user input + LogMiscInput, # Used for misc stuff in the input pane + LogOutput, + LogWarning, + LogError, + LogStatus, # Used for status changed messages + LogTime, # Used for time stamp messages + LogDebug, + LogMisc, + AppOutput, # stdout + AppError, # stderr + AppStuff, # (possibly) windows debug channel + StatusBar, # LogStatus and also put to the status bar + ConsoleOutput # Used to output to console + ) = range(0, 14) + + +def isIntegralTypeName(name): + return name in ( + "int", + "unsigned int", + "signed int", + "short", + "unsigned short", + "long", + "unsigned long", + "long long", + "unsigned long long", + "char", + "signed char", + "unsigned char", + "bool", + ) + + +def isFloatingPointTypeName(name): + return name in ("float", "double", "long double")