Files
qt-creator/share/qtcreator/debugger/dumper.py

4059 lines
159 KiB
Python
Raw Normal View History

############################################################################
#
# Copyright (C) 2016 The Qt Company Ltd.
# Contact: https://www.qt.io/licensing/
#
# This file is part of Qt Creator.
#
# Commercial License Usage
# Licensees holding valid commercial Qt licenses may use this file in
# accordance with the commercial license agreement provided with the
# Software or, alternatively, in accordance with the terms contained in
# a written agreement between you and The Qt Company. For licensing terms
# and conditions see https://www.qt.io/terms-conditions. For further
# information use the contact form at https://www.qt.io/contact-us.
#
# GNU General Public License Usage
# Alternatively, this file may be used under the terms of the GNU
# General Public License version 3 as published by the Free Software
# Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
# included in the packaging of this file. Please review the following
# information to ensure the GNU General Public License requirements will
# be met: https://www.gnu.org/licenses/gpl-3.0.html.
#
############################################################################
import os
import codecs
import collections
import glob
import struct
import sys
import base64
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
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
Debugger: Make dumpers somewhat work in command line GDB With python sys.path.insert(1, '/data/dev/creator/share/qtcreator/debugger/') python from gdbbridge import * in .gdbinit there's a new "GDB command", called "pp". With code like int main(int argc, char *argv[]) { QString ss = "Hello"; QApplication app(argc, argv); app.setObjectName(ss); // break here } the 'pp' command can be used as follows: (gdb) pp app app = [ <Myns::QGuiApplication> = {"Hello"} staticMetaObject = <Myns::QMetaObject> = {""} [parent] = <Myns::QObject *> = {"0x0"} [children] = <Myns::QObjectList> = {"<3 items>"} [properties] = "<>0 items>" [methods] = "<6 items>" [signals] = "<1 items>" ],<Myns::QApplication> = {"Hello"} (gdb) pp app [properties],[children] app = [ <Myns::QGuiApplication> = {"Hello"} staticMetaObject = <Myns::QMetaObject> = {""} [parent] = <Myns::QObject *> = {"0x0"} [children] = [ <Myns::QObject> = {""} <Myns::QObject> = {""} <Myns::QObject> = {"fusion"} ],<Myns::QObjectList> = {"<3 items>"} [properties] = [ windowIcon = <Myns::QVariant (QIcon)> = {""} cursorFlashTime = <Myns::QVariant (int)> = {"1000"} doubleClickInterval = <Myns::QVariant (int)> = {"400"} keyboardInputInterval = <Myns::QVariant (int)> = {"400"} wheelScrollLines = <Myns::QVariant (int)> = {"3"} globalStrut = <Myns::QVariant (QSize)> = {"(0, 0)"} startDragTime = <Myns::QVariant (int)> = {"500"} startDragDistance = <Myns::QVariant (int)> = {"10"} styleSheet = <Myns::QVariant (QString)> = {""} autoSipEnabled = <Myns::QVariant (bool)> = {"true"} ],"<10 items>" [methods] = "<6 items>" [signals] = "<1 items>" ],<Myns::QApplication> = {"Hello"} (gdb) pp ss ss = <Myns::QString> = {"Hello"} Change-Id: I6e4714a5cfe34c38917500d114ad9a70d20cff39 Reviewed-by: Christian Stenger <christian.stenger@digia.com> Reviewed-by: hjk <hjk121@nokiamail.com>
2014-06-13 17:45:34 +02:00
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
Debugger: Make dumpers somewhat work in command line GDB With python sys.path.insert(1, '/data/dev/creator/share/qtcreator/debugger/') python from gdbbridge import * in .gdbinit there's a new "GDB command", called "pp". With code like int main(int argc, char *argv[]) { QString ss = "Hello"; QApplication app(argc, argv); app.setObjectName(ss); // break here } the 'pp' command can be used as follows: (gdb) pp app app = [ <Myns::QGuiApplication> = {"Hello"} staticMetaObject = <Myns::QMetaObject> = {""} [parent] = <Myns::QObject *> = {"0x0"} [children] = <Myns::QObjectList> = {"<3 items>"} [properties] = "<>0 items>" [methods] = "<6 items>" [signals] = "<1 items>" ],<Myns::QApplication> = {"Hello"} (gdb) pp app [properties],[children] app = [ <Myns::QGuiApplication> = {"Hello"} staticMetaObject = <Myns::QMetaObject> = {""} [parent] = <Myns::QObject *> = {"0x0"} [children] = [ <Myns::QObject> = {""} <Myns::QObject> = {""} <Myns::QObject> = {"fusion"} ],<Myns::QObjectList> = {"<3 items>"} [properties] = [ windowIcon = <Myns::QVariant (QIcon)> = {""} cursorFlashTime = <Myns::QVariant (int)> = {"1000"} doubleClickInterval = <Myns::QVariant (int)> = {"400"} keyboardInputInterval = <Myns::QVariant (int)> = {"400"} wheelScrollLines = <Myns::QVariant (int)> = {"3"} globalStrut = <Myns::QVariant (QSize)> = {"(0, 0)"} startDragTime = <Myns::QVariant (int)> = {"500"} startDragDistance = <Myns::QVariant (int)> = {"10"} styleSheet = <Myns::QVariant (QString)> = {""} autoSipEnabled = <Myns::QVariant (bool)> = {"true"} ],"<10 items>" [methods] = "<6 items>" [signals] = "<1 items>" ],<Myns::QApplication> = {"Hello"} (gdb) pp ss ss = <Myns::QString> = {"Hello"} Change-Id: I6e4714a5cfe34c38917500d114ad9a70d20cff39 Reviewed-by: Christian Stenger <christian.stenger@digia.com> Reviewed-by: hjk <hjk121@nokiamail.com>
2014-06-13 17:45:34 +02:00
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="<incomplete>",value="",type="",numchild="0"},')
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.output += '\n' + ' ' * self.d.indent
Debugger: Make dumpers somewhat work in command line GDB With python sys.path.insert(1, '/data/dev/creator/share/qtcreator/debugger/') python from gdbbridge import * in .gdbinit there's a new "GDB command", called "pp". With code like int main(int argc, char *argv[]) { QString ss = "Hello"; QApplication app(argc, argv); app.setObjectName(ss); // break here } the 'pp' command can be used as follows: (gdb) pp app app = [ <Myns::QGuiApplication> = {"Hello"} staticMetaObject = <Myns::QMetaObject> = {""} [parent] = <Myns::QObject *> = {"0x0"} [children] = <Myns::QObjectList> = {"<3 items>"} [properties] = "<>0 items>" [methods] = "<6 items>" [signals] = "<1 items>" ],<Myns::QApplication> = {"Hello"} (gdb) pp app [properties],[children] app = [ <Myns::QGuiApplication> = {"Hello"} staticMetaObject = <Myns::QMetaObject> = {""} [parent] = <Myns::QObject *> = {"0x0"} [children] = [ <Myns::QObject> = {""} <Myns::QObject> = {""} <Myns::QObject> = {"fusion"} ],<Myns::QObjectList> = {"<3 items>"} [properties] = [ windowIcon = <Myns::QVariant (QIcon)> = {""} cursorFlashTime = <Myns::QVariant (int)> = {"1000"} doubleClickInterval = <Myns::QVariant (int)> = {"400"} keyboardInputInterval = <Myns::QVariant (int)> = {"400"} wheelScrollLines = <Myns::QVariant (int)> = {"3"} globalStrut = <Myns::QVariant (QSize)> = {"(0, 0)"} startDragTime = <Myns::QVariant (int)> = {"500"} startDragDistance = <Myns::QVariant (int)> = {"10"} styleSheet = <Myns::QVariant (QString)> = {""} autoSipEnabled = <Myns::QVariant (bool)> = {"true"} ],"<10 items>" [methods] = "<6 items>" [signals] = "<1 items>" ],<Myns::QApplication> = {"Hello"} (gdb) pp ss ss = <Myns::QString> = {"Hello"} Change-Id: I6e4714a5cfe34c38917500d114ad9a70d20cff39 Reviewed-by: Christian Stenger <christian.stenger@digia.com> Reviewed-by: hjk <hjk121@nokiamail.com>
2014-06-13 17:45:34 +02:00
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
Debugger: Make dumpers somewhat work in command line GDB With python sys.path.insert(1, '/data/dev/creator/share/qtcreator/debugger/') python from gdbbridge import * in .gdbinit there's a new "GDB command", called "pp". With code like int main(int argc, char *argv[]) { QString ss = "Hello"; QApplication app(argc, argv); app.setObjectName(ss); // break here } the 'pp' command can be used as follows: (gdb) pp app app = [ <Myns::QGuiApplication> = {"Hello"} staticMetaObject = <Myns::QMetaObject> = {""} [parent] = <Myns::QObject *> = {"0x0"} [children] = <Myns::QObjectList> = {"<3 items>"} [properties] = "<>0 items>" [methods] = "<6 items>" [signals] = "<1 items>" ],<Myns::QApplication> = {"Hello"} (gdb) pp app [properties],[children] app = [ <Myns::QGuiApplication> = {"Hello"} staticMetaObject = <Myns::QMetaObject> = {""} [parent] = <Myns::QObject *> = {"0x0"} [children] = [ <Myns::QObject> = {""} <Myns::QObject> = {""} <Myns::QObject> = {"fusion"} ],<Myns::QObjectList> = {"<3 items>"} [properties] = [ windowIcon = <Myns::QVariant (QIcon)> = {""} cursorFlashTime = <Myns::QVariant (int)> = {"1000"} doubleClickInterval = <Myns::QVariant (int)> = {"400"} keyboardInputInterval = <Myns::QVariant (int)> = {"400"} wheelScrollLines = <Myns::QVariant (int)> = {"3"} globalStrut = <Myns::QVariant (QSize)> = {"(0, 0)"} startDragTime = <Myns::QVariant (int)> = {"500"} startDragDistance = <Myns::QVariant (int)> = {"10"} styleSheet = <Myns::QVariant (QString)> = {""} autoSipEnabled = <Myns::QVariant (bool)> = {"true"} ],"<10 items>" [methods] = "<6 items>" [signals] = "<1 items>" ],<Myns::QApplication> = {"Hello"} (gdb) pp ss ss = <Myns::QString> = {"Hello"} Change-Id: I6e4714a5cfe34c38917500d114ad9a70d20cff39 Reviewed-by: Christian Stenger <christian.stenger@digia.com> Reviewed-by: hjk <hjk121@nokiamail.com>
2014-06-13 17:45:34 +02:00
self.isCli = False
# Later set, or not set:
self.stringCutOff = 10000
self.displayStringLimit = 100
self.useTimeStamps = False
self.typesReported = {}
self.typesToReport = {}
self.qtNamespaceToReport = None
self.qtCustomEventFunc = 0
self.qtCustomEventPltFunc = 0
self.qtPropertyFunc = 0
self.passExceptions = False
self.isTesting = False
self.typeData = {}
self.isBigEndian = False
self.packCode = '<'
self.resetCaches()
self.resetStats()
self.childrenPrefix = 'children=['
self.childrenSuffix = '],'
self.dumpermodules = [
os.path.splitext(os.path.basename(p))[0] for p in
glob.glob(os.path.join(os.path.dirname(__file__), '*types.py'))
]
# 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 = set(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'))
self.fallbackQtVersion = 0x50200
#DumperBase.warn('NAMESPACE: "%s"' % self.qtNamespace())
#DumperBase.warn('EXPANDED INAMES: %s' % self.expandedINames)
#DumperBase.warn('WATCHERS: %s' % self.watchers)
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 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.output += '\n' + ' ' * self.indent
if isinstance(item.name, str):
self.output += 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('<not accessible>')
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):
typeId = 'unsigned short'
tdata = self.TypeData(self)
tdata.name = typeId
tdata.typeId = typeId
tdata.lbitsize = 16
tdata.code = TypeCode.Integral
self.registerType(typeId, tdata)
typeId = 'QChar'
tdata = self.TypeData(self)
tdata.name = typeId
tdata.typeId = typeId
tdata.lbitsize = 16
tdata.code = TypeCode.Struct
tdata.lfields = [self.Field(dumper=self, name='ucs',
type='unsigned short', bitsize=16, bitpos=0)]
tdata.lalignment = 2
tdata.templateArguments = []
self.registerType(typeId, tdata)
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<main(int, char**)::SomeStruct>
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.
def hexdecode(self, s):
if sys.version_info[0] == 2:
return s.decode('hex')
return bytes.fromhex(s).decode('utf8')
# Hex encoding operating on str or bytes, return str.
def hexencode(self, s):
if s is None:
s = ''
if sys.version_info[0] == 2:
if isinstance(s, buffer):
return bytes(s).encode('hex')
return s.encode('hex')
if isinstance(s, str):
s = s.encode('utf8')
return base64.b16encode(s).decode('utf8')
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<main(int, char**)::SomeStruct>
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)
Debugger: Make dumpers somewhat work in command line GDB With python sys.path.insert(1, '/data/dev/creator/share/qtcreator/debugger/') python from gdbbridge import * in .gdbinit there's a new "GDB command", called "pp". With code like int main(int argc, char *argv[]) { QString ss = "Hello"; QApplication app(argc, argv); app.setObjectName(ss); // break here } the 'pp' command can be used as follows: (gdb) pp app app = [ <Myns::QGuiApplication> = {"Hello"} staticMetaObject = <Myns::QMetaObject> = {""} [parent] = <Myns::QObject *> = {"0x0"} [children] = <Myns::QObjectList> = {"<3 items>"} [properties] = "<>0 items>" [methods] = "<6 items>" [signals] = "<1 items>" ],<Myns::QApplication> = {"Hello"} (gdb) pp app [properties],[children] app = [ <Myns::QGuiApplication> = {"Hello"} staticMetaObject = <Myns::QMetaObject> = {""} [parent] = <Myns::QObject *> = {"0x0"} [children] = [ <Myns::QObject> = {""} <Myns::QObject> = {""} <Myns::QObject> = {"fusion"} ],<Myns::QObjectList> = {"<3 items>"} [properties] = [ windowIcon = <Myns::QVariant (QIcon)> = {""} cursorFlashTime = <Myns::QVariant (int)> = {"1000"} doubleClickInterval = <Myns::QVariant (int)> = {"400"} keyboardInputInterval = <Myns::QVariant (int)> = {"400"} wheelScrollLines = <Myns::QVariant (int)> = {"3"} globalStrut = <Myns::QVariant (QSize)> = {"(0, 0)"} startDragTime = <Myns::QVariant (int)> = {"500"} startDragDistance = <Myns::QVariant (int)> = {"10"} styleSheet = <Myns::QVariant (QString)> = {""} autoSipEnabled = <Myns::QVariant (bool)> = {"true"} ],"<10 items>" [methods] = "<6 items>" [signals] = "<1 items>" ],<Myns::QApplication> = {"Hello"} (gdb) pp ss ss = <Myns::QString> = {"Hello"} Change-Id: I6e4714a5cfe34c38917500d114ad9a70d20cff39 Reviewed-by: Christian Stenger <christian.stenger@digia.com> Reviewed-by: hjk <hjk121@nokiamail.com>
2014-06-13 17:45:34 +02:00
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 += stuff
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 list(value.items())]) + ']'
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',
'wchar_t',
'unsigned char',
'signed char',
'CHAR',
'WCHAR'
):
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 '<no address>'
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', '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 = '*'
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.Array1000:
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',
'unsigned char',
'wchar_t',
'CHAR',
'WCHAR'
):
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)
Debugger: Make dumpers somewhat work in command line GDB With python sys.path.insert(1, '/data/dev/creator/share/qtcreator/debugger/') python from gdbbridge import * in .gdbinit there's a new "GDB command", called "pp". With code like int main(int argc, char *argv[]) { QString ss = "Hello"; QApplication app(argc, argv); app.setObjectName(ss); // break here } the 'pp' command can be used as follows: (gdb) pp app app = [ <Myns::QGuiApplication> = {"Hello"} staticMetaObject = <Myns::QMetaObject> = {""} [parent] = <Myns::QObject *> = {"0x0"} [children] = <Myns::QObjectList> = {"<3 items>"} [properties] = "<>0 items>" [methods] = "<6 items>" [signals] = "<1 items>" ],<Myns::QApplication> = {"Hello"} (gdb) pp app [properties],[children] app = [ <Myns::QGuiApplication> = {"Hello"} staticMetaObject = <Myns::QMetaObject> = {""} [parent] = <Myns::QObject *> = {"0x0"} [children] = [ <Myns::QObject> = {""} <Myns::QObject> = {""} <Myns::QObject> = {"fusion"} ],<Myns::QObjectList> = {"<3 items>"} [properties] = [ windowIcon = <Myns::QVariant (QIcon)> = {""} cursorFlashTime = <Myns::QVariant (int)> = {"1000"} doubleClickInterval = <Myns::QVariant (int)> = {"400"} keyboardInputInterval = <Myns::QVariant (int)> = {"400"} wheelScrollLines = <Myns::QVariant (int)> = {"3"} globalStrut = <Myns::QVariant (QSize)> = {"(0, 0)"} startDragTime = <Myns::QVariant (int)> = {"500"} startDragDistance = <Myns::QVariant (int)> = {"10"} styleSheet = <Myns::QVariant (QString)> = {""} autoSipEnabled = <Myns::QVariant (bool)> = {"true"} ],"<10 items>" [methods] = "<6 items>" [signals] = "<1 items>" ],<Myns::QApplication> = {"Hello"} (gdb) pp ss ss = <Myns::QString> = {"Hello"} Change-Id: I6e4714a5cfe34c38917500d114ad9a70d20cff39 Reviewed-by: Christian Stenger <christian.stenger@digia.com> Reviewed-by: hjk <hjk121@nokiamail.com>
2014-06-13 17:45:34 +02:00
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: 7 pointer + 2 int
# - vtable
# - QObject *q_ptr;
# - QObject *parent;
# - QObjectList children;
# - uint isWidget : 1; etc...
# - int postedEvents;
# - QDynamicMetaObjectData *metaObject;
extra = self.extractPointer(dd + 7 * ptrSize + 2 * intSize)
if extra == 0:
return False
# Offset of objectName in ExtraData: 12 pointer
# - QList<QByteArray> propertyNames;
# - QList<QVariant> propertyValues;
# - QVector<int> runningTimers;
# - QList<QPointer<QObject> > 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<QObjectUserData *> userData; only #ifndef QT_NO_USERDATA
# - QList<QByteArray> propertyNames;
# - QList<QVariant> propertyValues;
# - QVector<int> runningTimers;
# - QList<QPointer<QObject> > 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()
stringdata = self.extractPointer(toInteger(metaObjectPtr) + ptrSize)
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 '<not available>'
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() >= 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<QObject *>}', 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 = '<unknown>'
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, maxNumChild=10000):
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()
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)
if n > maxNumChild:
self.put('childrenelided="%s",' % n) # FIXME: Act on that in frontend
n = maxNumChild
self.put('arraydata="')
self.put(self.readMemory(addrBase, n * innerSize))
self.put('",')
else:
with Children(self, n, innerType, childNumChild, maxNumChild,
addrBase=addrBase, addrStep=innerSize):
for i in self.childRange():
self.putSubItem(i, self.createValue(addrBase + i * innerSize, innerType))
def putArrayItem(self, name, addr, n, typeName):
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, maxNumChild=maxNumChild)
def putSpecialArgv(self, value):
"""
Special handling for char** argv.
"""
n = 0
p = value
# p is 0 for "optimized out" cases. Or contains rubbish.
try:
if 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 = '<no such value>'
self.currentChildNumChild = -1
self.currentNumChild = 0
self.putNumChild(0)
def registerDumper(self, funcname, function):
try:
if funcname.startswith('qdump__'):
typename = funcname[7:]
spec = inspect.getargspec(function)
if len(spec.args) == 2:
self.qqDumpers[typename] = function
elif len(spec.args) == 3 and len(spec.defaults) == 1:
self.qqDumpersEx[spec.defaults[0]] = function
self.qqFormats[typename] = self.qqFormats.get(typename, [])
elif funcname.startswith('qform__'):
typename = funcname[7:]
try:
self.qqFormats[typename] = function()
except:
self.qqFormats[typename] = []
elif funcname.startswith('qedit__'):
typename = funcname[7:]
try:
self.qqEditable[typename] = function
except:
pass
except:
pass
def setupDumpers(self, _={}):
self.resetCaches()
for mod in self.dumpermodules:
m = __import__(mod)
dic = m.__dict__
for name in dic.keys():
item = dic[name]
self.registerDumper(name, item)
msg = 'dumpers=['
for key, value in self.qqFormats.items():
editable = ',editable="true"' if key in self.qqEditable else ''
formats = (',formats=\"%s\"' % str(value)[1:-1]) if len(value) else ''
msg += '{type="%s"%s%s},' % (key, editable, formats)
msg += '],'
v = 10000 * sys.version_info[0] + 100 * sys.version_info[1] + sys.version_info[2]
msg += 'python="%d"' % v
return msg
def reloadDumpers(self, args):
for mod in self.dumpermodules:
m = sys.modules[mod]
if sys.version_info[0] >= 3:
import importlib
importlib.reload(m)
else:
reload(m)
self.setupDumpers(args)
def 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 'servicename<space>msglen<space>msg' 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 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.typeData().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):
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.
Debugger: Retrieve and remember int from native GDB value When adding expressions for bitfield members in the debugger's expression view, their corresponding 'gdb.Value' does not expose the fact that those are actually bitfields, so e.g. an 'int : 3' is exposed like a "normal" 'int'. Previously, this would result in wrong values being retrieved in the 'DumperBase::Value::integer()' function, when trying to read the value from the corresponding memory address. To avoid this, retrieve the actual int representation for numeric values from the corresponding native 'gdb.Value', remember them and return that one, similar to how it is already done for known bitfield members (s. 'Dumper::memberFromNativeFieldAndValue'). The conversion from the 'gdb.Value' does not work for integers of a size larger than 64 bits (like '__int128' used in the "Int128" dumper test). Therefore, just ignore conversion failures and don't remember any value explicitly for those cases, so the same handling as previously used is applied. (At a quick glance, the reason seems to be that this is because GDB's corresponding functions use 'int64' as a return value of the relevant functions [1] [2], but I did not look closer into what GDB does internally.) Corresponding tests will be added in a separate commit. [1] https://sourceware.org/git/?p=binutils-gdb.git;a=blob;f=gdbsupport/common-types.h;h=f5b2f3d249177acea77231c21c5601f959c18d2f;hb=f3034e25fa98d44b775970f40c9ec85eeae096e6#l33 [2] https://sourceware.org/git/?p=binutils-gdb.git;a=blob;f=gdb/python/py-value.c;h=6e29284aad11ff344789152a4f601b3474d86bb5;hb=f3034e25fa98d44b775970f40c9ec85eeae096e6#l1706 Fixes: QTCREATORBUG-24693 Change-Id: Idfc3390115e8796f3c778070c23424c3dbdfeddd Reviewed-by: hjk <hjk@qt.io>
2020-09-24 12:02:06 +02:00
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
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
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.typeData().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 '<unknown data>'
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()
Debugger: Retrieve and remember int from native GDB value When adding expressions for bitfield members in the debugger's expression view, their corresponding 'gdb.Value' does not expose the fact that those are actually bitfields, so e.g. an 'int : 3' is exposed like a "normal" 'int'. Previously, this would result in wrong values being retrieved in the 'DumperBase::Value::integer()' function, when trying to read the value from the corresponding memory address. To avoid this, retrieve the actual int representation for numeric values from the corresponding native 'gdb.Value', remember them and return that one, similar to how it is already done for known bitfield members (s. 'Dumper::memberFromNativeFieldAndValue'). The conversion from the 'gdb.Value' does not work for integers of a size larger than 64 bits (like '__int128' used in the "Int128" dumper test). Therefore, just ignore conversion failures and don't remember any value explicitly for those cases, so the same handling as previously used is applied. (At a quick glance, the reason seems to be that this is because GDB's corresponding functions use 'int64' as a return value of the relevant functions [1] [2], but I did not look closer into what GDB does internally.) Corresponding tests will be added in a separate commit. [1] https://sourceware.org/git/?p=binutils-gdb.git;a=blob;f=gdbsupport/common-types.h;h=f5b2f3d249177acea77231c21c5601f959c18d2f;hb=f3034e25fa98d44b775970f40c9ec85eeae096e6#l33 [2] https://sourceware.org/git/?p=binutils-gdb.git;a=blob;f=gdb/python/py-value.c;h=6e29284aad11ff344789152a4f601b3474d86bb5;hb=f3034e25fa98d44b775970f40c9ec85eeae096e6#l1706 Fixes: QTCREATORBUG-24693 Change-Id: Idfc3390115e8796f3c778070c23424c3dbdfeddd Reviewed-by: hjk <hjk@qt.io>
2020-09-24 12:02:06 +02:00
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 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 sys.version_info[0] >= 3:
data += lbyte
else:
data += ord(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.typeData()
#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:
if self.nativeValue is None:
val.laddress = self.pointer()
val.type = self.type.dereference()
if self.dumper.useDynamicType:
val.type = self.dumper.nativeDynamicType(val.laddress, val.type)
else:
val = self.dumper.nativeValueDereferencePointer(self)
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 registerType(self, typeId, tdata):
#DumperBase.warn('REGISTER TYPE: %s' % typeId)
self.typeData[typeId] = tdata
#typeId = typeId.replace(' ', '')
#self.typeData[typeId] = tdata
#DumperBase.warn('REGISTERED: %s' % self.typeData)
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):
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 = []
self.code = None
self.name = None
self.typeId = None
self.enumDisplay = None
self.moduleName = None
def copy(self):
tdata = self.dumper.TypeData(self.dumper)
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
def __str__(self):
#return self.typeId
return self.stringify()
def typeData(self):
tdata = self.dumper.typeData.get(self.typeId, None)
if tdata is not None:
#DumperBase.warn('USING : %s' % self.typeId)
return tdata
typeId = self.typeId.replace(' ', '')
if tdata is not None:
#DumperBase.warn('USING FALLBACK : %s' % self.typeId)
return tdata
#DumperBase.warn('EXPANDING LAZILY: %s' % self.typeId)
self.dumper.lookupType(self.typeId)
return self.dumper.typeData.get(self.typeId)
@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.typeData().code
@property
def lbitsize(self):
return self.typeData().lbitsize
@property
def lbitpos(self):
return self.typeData().lbitpos
@property
def ltarget(self):
return self.typeData().ltarget
@property
def moduleName(self):
return self.typeData().moduleName
def stringify(self):
tdata = self.typeData()
if tdata is None:
return 'Type(id="%s")' % self.typeId
return 'Type(name="%s",bsize=%s,code=%s)' \
% (tdata.name, tdata.lbitsize, tdata.code)
def __getitem__(self, index):
if self.dumper.isInt(index):
return self.templateArgument(index)
raise RuntimeError('CANNOT INDEX TYPE')
def dynamicTypeName(self, address):
tdata = self.typeData()
if tdata is None:
return None
if 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):
tdata = self.typeData()
if tdata is None:
raise RuntimeError('TYPE WITHOUT DATA: %s ALL: %s' %
(self.typeId, self.dumper.typeData.keys()))
if 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):
tdata = self.typeData()
if tdata is None:
return self.dumper.listTemplateParameters(self.typeId)
return tdata.templateArguments
def templateArgument(self, position):
tdata = self.typeData()
#DumperBase.warn('TDATA: %s' % tdata)
#DumperBase.warn('ID: %s' % self.typeId)
if 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' % tdata.templateArguments)
return tdata.templateArguments[position]
def simpleEncoding(self):
res = {
'bool': 'int:1',
'char': 'int:1',
'signed char': 'int:1',
'unsigned char': 'uint:1',
'short': 'int:2',
'unsigned short': 'uint:2',
'int': 'int:4',
'unsigned int': 'uint:4',
'long long': 'int:8',
'unsigned long long': '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):
tdata = self.typeData()
if tdata.code == TypeCode.Typedef:
return tdata.ltarget.alignment()
if tdata.code in (TypeCode.Integral, TypeCode.Float, TypeCode.Enum):
if 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 tdata.code in (TypeCode.Pointer, TypeCode.Reference, TypeCode.RValueReference):
return self.dumper.ptrSize()
if tdata.lalignment is not None:
#if isinstance(tdata.lalignment, function): # Does not work that way.
if hasattr(tdata.lalignment, '__call__'):
return tdata.lalignment()
return tdata.lalignment
return 1
def pointer(self):
return self.dumper.createPointerType(self)
def target(self):
return self.typeData().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)
tdata.name = targetType.name + '*'
tdata.typeId = typeId
tdata.lbitsize = 8 * self.ptrSize()
tdata.code = TypeCode.Pointer
tdata.ltarget = targetType
self.registerType(typeId, tdata)
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)
tdata.name = targetType.name + ' &'
tdata.typeId = typeId
tdata.code = TypeCode.Reference
tdata.ltarget = targetType
tdata.lbitsize = 8 * self.ptrSize() # Needed for Gdb13393 test.
#tdata.lbitsize = None
self.registerType(typeId, tdata)
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)
tdata.name = targetType.name + ' &&'
tdata.typeId = typeId
tdata.code = TypeCode.RValueReference
tdata.ltarget = targetType
tdata.lbitsize = None
self.registerType(typeId, tdata)
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)
tdata.name = type_name
tdata.typeId = type_id
tdata.code = TypeCode.Array
tdata.ltarget = targetType
tdata.lbitsize = targetType.lbitsize * count
self.registerType(type_id, tdata)
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)
tdata.name = '%s : %d' % (targetType.typeId, bitsize)
tdata.typeId = typeId
tdata.code = TypeCode.Bitfield
tdata.ltarget = targetType
tdata.lbitsize = bitsize
self.registerType(typeId, tdata)
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)
tdata.name = typeName
tdata.typeId = typeId
tdata.code = TypeCode.Typedef
tdata.ltarget = targetType
tdata.lbitsize = targetType.lbitsize
#tdata.lfields = targetType.lfields
tdata.lbitsize = targetType.lbitsize
self.registerType(typeId, tdata)
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 == '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)
tdata.name = typish
tdata.typeId = typish
tdata.templateArguments = 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())
self.registerType(typish, tdata)
typeobj = self.Type(self, typish)
#DumperBase.warn('CREATE TYPE: %s' % typeobj.stringify())
typeobj.check()
return typeobj
raise RuntimeError('NEED TYPE, NOT %s' % type(typish))
def createValue(self, datish, typish):
val = self.Value(self)
val.type = self.createType(typish)
if self.isInt(datish): # Used as address.
#DumperBase.warn('CREATING %s AT 0x%x' % (val.type.name, datish))
val.laddress = datish
if self.useDynamicType:
val.type = val.type.dynamicType(datish)
return val
if isinstance(datish, bytes):
#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)
tdata.name = type_name
tdata.typeId = type_name
tdata.code = TypeCode.Struct
self.registerType(type_name, tdata)
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)