From ba44dd2888c2e148e2f3195344f02891e90cc0b9 Mon Sep 17 00:00:00 2001 From: xeruf <27jf@pm.me> Date: Wed, 17 Nov 2021 11:55:44 +0100 Subject: [PATCH] config/gdb: add qt printers https://github.com/mixxxdj/mixxx/wiki/Debugging-Cpp-Code --- .config/gdb/.unused/qt.py | 714 +++++++++++++++++++++ .config/gdb/init | 11 + .config/gdb/qt5printers/README.md | 59 ++ .config/gdb/qt5printers/__init__.py | 54 ++ .config/gdb/qt5printers/core.py | 953 ++++++++++++++++++++++++++++ .config/gdb/qt5printers/typeinfo.py | 375 +++++++++++ .gdbinit | 1 + 7 files changed, 2167 insertions(+) create mode 100644 .config/gdb/.unused/qt.py create mode 100644 .config/gdb/init create mode 100644 .config/gdb/qt5printers/README.md create mode 100644 .config/gdb/qt5printers/__init__.py create mode 100644 .config/gdb/qt5printers/core.py create mode 100644 .config/gdb/qt5printers/typeinfo.py create mode 120000 .gdbinit diff --git a/.config/gdb/.unused/qt.py b/.config/gdb/.unused/qt.py new file mode 100644 index 0000000..a32bc7b --- /dev/null +++ b/.config/gdb/.unused/qt.py @@ -0,0 +1,714 @@ +# -*- coding: iso-8859-1 -*- +# Pretty-printers for Qt 4 and Qt 5. + +# Copyright (C) 2009 Niko Sams + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import gdb +import itertools +import re +import time + +from helper import * + +class QStringPrinter: + + def __init__(self, val): + self.val = val + + def to_string(self): + size = self.val['d']['size'] + ret = "" + + # The QString object might be not yet initialized. In this case size is a bogus value + # and the following 2 lines might throw memory access error. Hence the try/catch. + try: + isQt4 = has_field(self.val['d'], 'data') # Qt4 has d->data, Qt5 doesn't. + if isQt4: + dataAsCharPointer = self.val['d']['data'].cast(gdb.lookup_type("char").pointer()) + else: + dataAsCharPointer = (self.val['d'] + 1).cast(gdb.lookup_type("char").pointer()) + ret = dataAsCharPointer.string(encoding = 'UTF-16', length = size * 2) + except Exception: + # swallow the exception and return empty string + pass + return ret + + def display_hint (self): + return 'string' + +class QByteArrayPrinter: + + def __init__(self, val): + self.val = val + # Qt4 has 'data', Qt5 doesn't + self.isQt4 = has_field(self.val['d'], 'data') + + class _iterator(Iterator): + def __init__(self, data, size): + self.data = data + self.size = size + self.count = 0 + + def __iter__(self): + return self + + def __next__(self): + if self.count >= self.size: + raise StopIteration + count = self.count + self.count = self.count + 1 + return ('[%d]' % count, self.data[count]) + + def stringData(self): + if self.isQt4: + return self.val['d']['data'] + else: + return self.val['d'].cast(gdb.lookup_type("char").const().pointer()) + self.val['d']['offset'] + + def children(self): + return self._iterator(self.stringData(), self.val['d']['size']) + + def to_string(self): + #todo: handle charset correctly + return self.stringData() + + def display_hint (self): + return 'string' + +class QListPrinter: + "Print a QList" + + class _iterator(Iterator): + def __init__(self, nodetype, d): + self.nodetype = nodetype + self.d = d + self.count = 0 + + #from QTypeInfo::isLarge + isLarge = self.nodetype.sizeof > gdb.lookup_type('void').pointer().sizeof + + isPointer = self.nodetype.code == gdb.TYPE_CODE_PTR + + #unfortunately we can't use QTypeInfo::isStatic as it's all inlined, so use + #this list of types that use Q_DECLARE_TYPEINFO(T, Q_MOVABLE_TYPE) + #(obviously it won't work for custom types) + movableTypes = ['QRect', 'QRectF', 'QString', 'QMargins', 'QLocale', 'QChar', 'QDate', 'QTime', 'QDateTime', 'QVector', + 'QRegExpr', 'QPoint', 'QPointF', 'QByteArray', 'QSize', 'QSizeF', 'QBitArray', 'QLine', 'QLineF', 'QModelIndex', 'QPersitentModelIndex', + 'QVariant', 'QFileInfo', 'QUrl', 'QXmlStreamAttribute', 'QXmlStreamNamespaceDeclaration', 'QXmlStreamNotationDeclaration', + 'QXmlStreamEntityDeclaration', 'QPair'] + #this list of types that use Q_DECLARE_TYPEINFO(T, Q_PRIMITIVE_TYPE) (from qglobal.h) + primitiveTypes = ['bool', 'char', 'signed char', 'unsigned char', 'short', 'unsigned short', 'int', 'unsigned int', 'long', 'unsigned long', 'long long', 'unsigned long long', 'float', 'double'] + + if movableTypes.count(self.nodetype.tag) or primitiveTypes.count(str(self.nodetype)): + isStatic = False + else: + isStatic = not isPointer + + self.externalStorage = isLarge or isStatic #see QList::Node::t() + + + def __iter__(self): + return self + + def __next__(self): + if self.count >= self.d['end'] - self.d['begin']: + raise StopIteration + count = self.count + array = self.d['array'].address + self.d['begin'] + count + if self.externalStorage: + value = array.cast(gdb.lookup_type('QList<%s>::Node' % self.nodetype).pointer())['v'] + else: + value = array + self.count = self.count + 1 + return ('[%d]' % count, value.cast(self.nodetype.pointer()).dereference()) + + def __init__(self, val, container, itype): + self.d = val['d'] + self.container = container + self.size = self.d['end'] - self.d['begin'] + if itype == None: + self.itype = val.type.template_argument(0) + else: + self.itype = gdb.lookup_type(itype) + + def children(self): + return self._iterator(self.itype, self.d) + + def to_string(self): + return "%s<%s> (size = %s)" % ( self.container, self.itype, self.size ) + +class QVectorPrinter: + "Print a QVector" + + class _iterator(Iterator): + def __init__(self, nodetype, data, size): + self.nodetype = nodetype + self.data = data + self.size = size + self.count = 0 + + def __iter__(self): + return self + + def __next__(self): + if self.count >= self.size: + raise StopIteration + count = self.count + + self.count = self.count + 1 + return ('[%d]' % count, self.data[count]) + + def __init__(self, val, container): + self.val = val + self.container = container + self.itype = self.val.type.template_argument(0) + + def children(self): + isQt4 = has_field(self.val['d'], 'p') # Qt4 has 'p', Qt5 doesn't + if isQt4: + return self._iterator(self.itype, self.val['p']['array'], self.val['p']['size']) + else: + data = self.val['d'].cast(gdb.lookup_type("char").const().pointer()) + self.val['d']['offset'] + return self._iterator(self.itype, data.cast(self.itype.pointer()), self.val['d']['size']) + + def to_string(self): + size = self.val['d']['size'] + + return "%s<%s> (size = %s)" % ( self.container, self.itype, size ) + +class QLinkedListPrinter: + "Print a QLinkedList" + + class _iterator(Iterator): + def __init__(self, nodetype, begin, size): + self.nodetype = nodetype + self.it = begin + self.pos = 0 + self.size = size + + def __iter__(self): + return self + + def __next__(self): + if self.pos >= self.size: + raise StopIteration + + pos = self.pos + val = self.it['t'] + self.it = self.it['n'] + self.pos = self.pos + 1 + return ('[%d]' % pos, val) + + def __init__(self, val): + self.val = val + self.itype = self.val.type.template_argument(0) + + def children(self): + return self._iterator(self.itype, self.val['e']['n'], self.val['d']['size']) + + def to_string(self): + size = self.val['d']['size'] + + return "QLinkedList<%s> (size = %s)" % ( self.itype, size ) + +class QMapPrinter: + "Print a QMap" + + class _iteratorQt4(Iterator): + def __init__(self, val): + self.val = val + self.ktype = self.val.type.template_argument(0) + self.vtype = self.val.type.template_argument(1) + self.data_node = self.val['e']['forward'][0] + self.count = 0 + + def __iter__(self): + return self + + def payload (self): + if gdb.parse_and_eval: + ret = int(gdb.parse_and_eval('QMap<%s, %s>::payload()' % (self.ktype, self.vtype))) + if (ret): return ret; + + #if the inferior function call didn't work, let's try to calculate ourselves + + #we can't use QMapPayloadNode as it's inlined + #as a workaround take the sum of sizeof(members) + ret = self.ktype.sizeof + ret += self.vtype.sizeof + ret += gdb.lookup_type('void').pointer().sizeof + + #but because of data alignment the value can be higher + #so guess it's aliged by sizeof(void*) + #TODO: find a real solution for this problem + ret += ret % gdb.lookup_type('void').pointer().sizeof + + #for some reason booleans are different + if str(self.vtype) == 'bool': + ret += 2 + + ret -= gdb.lookup_type('void').pointer().sizeof + + return ret + + def concrete (self, data_node): + node_type = gdb.lookup_type('QMapNode<%s, %s>' % (self.ktype, self.vtype)).pointer() + return (data_node.cast(gdb.lookup_type('char').pointer()) - self.payload()).cast(node_type) + + def __next__(self): + if self.data_node == self.val['e']: + raise StopIteration + node = self.concrete(self.data_node).dereference() + if self.count % 2 == 0: + item = node['key'] + else: + item = node['value'] + self.data_node = node['forward'][0] + + result = ('[%d]' % self.count, item) + self.count = self.count + 1 + return result + + class _iteratorQt5: + def __init__(self, val): + realtype = val.type.strip_typedefs() + keytype = realtype.template_argument(0) + valtype = realtype.template_argument(1) + node_type = gdb.lookup_type('QMapData<' + keytype.name + ',' + valtype.name + '>::Node') + self.node_p_type = node_type.pointer() + self.root = val['d']['header'] + self.current = None + self.next_is_key = True + self.i = -1 + # we store the path here to avoid keeping re-fetching + # values from the inferior (also, skips the pointer + # arithmetic involved in using the parent pointer) + self.path = [] + + def __iter__(self): + return self + + def moveToNextNode(self): + if self.current is None: + # find the leftmost node + if not self.root['left']: + return False + self.current = self.root + while self.current['left']: + self.path.append(self.current) + self.current = self.current['left'] + elif self.current['right']: + self.path.append(self.current) + self.current = self.current['right'] + while self.current['left']: + self.path.append(self.current) + self.current = self.current['left'] + else: + last = self.current + self.current = self.path.pop() + while self.current['right'] == last: + last = self.current + self.current = self.path.pop() + # if there are no more parents, we are at the root + if len(self.path) == 0: + return False + return True + + def __next__(self): + if self.next_is_key: + if not self.moveToNextNode(): + raise StopIteration + self.current_typed = self.current.reinterpret_cast(self.node_p_type) + self.next_is_key = False + self.i += 1 + return ('key' + str(self.i), self.current_typed['key']) + else: + self.next_is_key = True + return ('value' + str(self.i), self.current_typed['value']) + + def next(self): + return self.__next__() + + def __init__(self, val, container): + self.val = val + self.container = container + + def children(self): + if self.val['d']['size'] == 0: + return [] + + isQt4 = has_field(self.val, 'e') # Qt4 has 'e', Qt5 doesn't + if isQt4: + return self._iteratorQt4(self.val) + else: + return self._iteratorQt5(self.val) + + def to_string(self): + size = self.val['d']['size'] + + return "%s<%s, %s> (size = %s)" % ( self.container, self.val.type.template_argument(0), self.val.type.template_argument(1), size ) + + def display_hint (self): + return 'map' + +class QHashPrinter: + "Print a QHash" + + class _iterator(Iterator): + def __init__(self, val): + self.val = val + self.d = self.val['d'] + self.ktype = self.val.type.template_argument(0) + self.vtype = self.val.type.template_argument(1) + self.end_node = self.d.cast(gdb.lookup_type('QHashData::Node').pointer()) + self.data_node = self.firstNode() + self.count = 0 + + def __iter__(self): + return self + + def hashNode (self): + "Casts the current QHashData::Node to a QHashNode and returns the result. See also QHash::concrete()" + return self.data_node.cast(gdb.lookup_type('QHashNode<%s, %s>' % (self.ktype, self.vtype)).pointer()) + + def firstNode (self): + "Get the first node, See QHashData::firstNode()." + e = self.d.cast(gdb.lookup_type('QHashData::Node').pointer()) + #print "QHashData::firstNode() e %s" % e + bucketNum = 0 + bucket = self.d['buckets'][bucketNum] + #print "QHashData::firstNode() *bucket %s" % bucket + n = self.d['numBuckets'] + #print "QHashData::firstNode() n %s" % n + while n: + #print "QHashData::firstNode() in while, n %s" % n; + if bucket != e: + #print "QHashData::firstNode() in while, return *bucket %s" % bucket + return bucket + bucketNum += 1 + bucket = self.d['buckets'][bucketNum] + #print "QHashData::firstNode() in while, new bucket %s" % bucket + n -= 1 + #print "QHashData::firstNode() return e %s" % e + return e + + + def nextNode (self, node): + "Get the nextNode after the current, see also QHashData::nextNode()." + #print "******************************** nextNode" + #print "nextNode: node %s" % node + next = node['next'].cast(gdb.lookup_type('QHashData::Node').pointer()) + e = next + + #print "nextNode: next %s" % next + if next['next']: + #print "nextNode: return next" + return next + + #print "nextNode: node->h %s" % node['h'] + #print "nextNode: numBuckets %s" % self.d['numBuckets'] + start = (node['h'] % self.d['numBuckets']) + 1 + bucketNum = start + #print "nextNode: start %s" % start + bucket = self.d['buckets'][start] + #print "nextNode: bucket %s" % bucket + n = self.d['numBuckets'] - start + #print "nextNode: n %s" % n + while n: + #print "nextNode: in while; n %s" % n + #print "nextNode: in while; e %s" % e + #print "nextNode: in while; *bucket %s" % bucket + if bucket != e: + #print "nextNode: in while; return bucket %s" % bucket + return bucket + bucketNum += 1 + bucket = self.d['buckets'][bucketNum] + n -= 1 + #print "nextNode: return e %s" % e + return e + + def __next__(self): + "GDB iteration, first call returns key, second value and then jumps to the next hash node." + if self.data_node == self.end_node: + raise StopIteration + + node = self.hashNode() + + if self.count % 2 == 0: + item = node['key'] + else: + item = node['value'] + self.data_node = self.nextNode(self.data_node) + + self.count = self.count + 1 + return ('[%d]' % self.count, item) + + def __init__(self, val, container): + self.val = val + self.container = container + + def children(self): + return self._iterator(self.val) + + def to_string(self): + size = self.val['d']['size'] + + return "%s<%s, %s> (size = %s)" % ( self.container, self.val.type.template_argument(0), self.val.type.template_argument(1), size ) + + def display_hint (self): + return 'map' + +class QDatePrinter: + + def __init__(self, val): + self.val = val + + def to_string(self): + julianDay = self.val['jd'] + + if julianDay == 0: + return "invalid QDate" + + # Copied from Qt sources + if julianDay >= 2299161: + # Gregorian calendar starting from October 15, 1582 + # This algorithm is from Henry F. Fliegel and Thomas C. Van Flandern + ell = julianDay + 68569; + n = (4 * ell) / 146097; + ell = ell - (146097 * n + 3) / 4; + i = (4000 * (ell + 1)) / 1461001; + ell = ell - (1461 * i) / 4 + 31; + j = (80 * ell) / 2447; + d = ell - (2447 * j) / 80; + ell = j / 11; + m = j + 2 - (12 * ell); + y = 100 * (n - 49) + i + ell; + else: + # Julian calendar until October 4, 1582 + # Algorithm from Frequently Asked Questions about Calendars by Claus Toendering + julianDay += 32082; + dd = (4 * julianDay + 3) / 1461; + ee = julianDay - (1461 * dd) / 4; + mm = ((5 * ee) + 2) / 153; + d = ee - (153 * mm + 2) / 5 + 1; + m = mm + 3 - 12 * (mm / 10); + y = dd - 4800 + (mm / 10); + if y <= 0: + --y; + return "%d-%02d-%02d" % (y, m, d) + +class QTimePrinter: + + def __init__(self, val): + self.val = val + + def to_string(self): + ds = self.val['mds'] + + if ds == -1: + return "invalid QTime" + + MSECS_PER_HOUR = 3600000 + SECS_PER_MIN = 60 + MSECS_PER_MIN = 60000 + + hour = ds / MSECS_PER_HOUR + minute = (ds % MSECS_PER_HOUR) / MSECS_PER_MIN + second = (ds / 1000)%SECS_PER_MIN + msec = ds % 1000 + return "%02d:%02d:%02d.%03d" % (hour, minute, second, msec) + +class QDateTimePrinter: + + def __init__(self, val): + self.val = val + + def to_string(self): + time_t = gdb.parse_and_eval("reinterpret_cast(%s)->toSecsSinceEpoch()" % self.val.address) + return time.ctime(int(time_t)) + +class QUrlPrinter: + + def __init__(self, val): + self.val = val + + def to_string(self): + # first try to access the Qt 5 data + try: + int_type = gdb.lookup_type('int') + string_type = gdb.lookup_type('QString') + string_pointer = string_type.pointer() + + addr = self.val['d'].cast(gdb.lookup_type('char').pointer()) + # skip QAtomicInt ref + addr += int_type.sizeof + # handle int port + port = addr.cast(int_type.pointer()).dereference() + addr += int_type.sizeof + # handle QString scheme + scheme = QStringPrinter(addr.cast(string_pointer).dereference()).to_string() + addr += string_type.sizeof + # handle QString username + username = QStringPrinter(addr.cast(string_pointer).dereference()).to_string() + addr += string_type.sizeof + # skip QString password + addr += string_type.sizeof + # handle QString host + host = QStringPrinter(addr.cast(string_pointer).dereference()).to_string() + addr += string_type.sizeof + # handle QString path + path = QStringPrinter(addr.cast(string_pointer).dereference()).to_string() + addr += string_type.sizeof + # handle QString query + query = QStringPrinter(addr.cast(string_pointer).dereference()).to_string() + addr += string_type.sizeof + # handle QString fragment + fragment = QStringPrinter(addr.cast(string_pointer).dereference()).to_string() + + url = "" + if len(scheme) > 0: + # TODO: always adding // is apparently not compliant in all cases + url += scheme + "://" + if len(host) > 0: + if len(username) > 0: + url += username + "@" + url += host + if port != -1: + url += ":" + str(port) + url += path + if len(query) > 0: + url += "?" + query + if len(fragment) > 0: + url += "#" + fragment + + return url + except: + pass + # then try to print directly, but that might lead to issues (see http://sourceware-org.1504.n7.nabble.com/help-Calling-malloc-from-a-Python-pretty-printer-td284031.html) + try: + return gdb.parse_and_eval("reinterpret_cast(%s)->toString((QUrl::FormattingOptions)QUrl::PrettyDecoded)" % self.val.address) + except: + pass + # if everything fails, maybe we deal with Qt 4 code + try: + return self.val['d']['encodedOriginal'] + except RuntimeError: + #if no debug information is available for Qt, try guessing the correct address for encodedOriginal + #problem with this is that if QUrlPrivate members get changed, this fails + offset = gdb.lookup_type('int').sizeof + offset += offset % gdb.lookup_type('void').pointer().sizeof #alignment + offset += gdb.lookup_type('QString').sizeof * 6 + offset += gdb.lookup_type('QByteArray').sizeof + encodedOriginal = self.val['d'].cast(gdb.lookup_type('char').pointer()); + encodedOriginal += offset + encodedOriginal = encodedOriginal.cast(gdb.lookup_type('QByteArray').pointer()).dereference(); + encodedOriginal = encodedOriginal['d']['data'].string() + return encodedOriginal + +class QSetPrinter: + "Print a QSet" + + def __init__(self, val): + self.val = val + + class _iterator(Iterator): + def __init__(self, hashIterator): + self.hashIterator = hashIterator + self.count = 0 + + def __iter__(self): + return self + + def __next__(self): + if self.hashIterator.data_node == self.hashIterator.end_node: + raise StopIteration + + node = self.hashIterator.hashNode() + + item = node['key'] + self.hashIterator.data_node = self.hashIterator.nextNode(self.hashIterator.data_node) + + self.count = self.count + 1 + return ('[%d]' % (self.count-1), item) + + def children(self): + hashPrinter = QHashPrinter(self.val['q_hash'], None) + hashIterator = hashPrinter._iterator(self.val['q_hash']) + return self._iterator(hashIterator) + + def to_string(self): + size = self.val['q_hash']['d']['size'] + + return "QSet<%s> (size = %s)" % ( self.val.type.template_argument(0), size ) + + +class QCharPrinter: + + def __init__(self, val): + self.val = val + + def to_string(self): + return unichr(self.val['ucs']) + + def display_hint (self): + return 'string' + +class QUuidPrinter: + + def __init__(self, val): + self.val = val + + def to_string(self): + return "QUuid({%x-%x-%x-%x%x-%x%x%x%x%x%x})" % (int(self.val['data1']), int(self.val['data2']), int(self.val['data3']), + int(self.val['data4'][0]), int(self.val['data4'][1]), + int(self.val['data4'][2]), int(self.val['data4'][3]), + int(self.val['data4'][4]), int(self.val['data4'][5]), + int(self.val['data4'][6]), int(self.val['data4'][7])) + + def display_hint (self): + return 'string' + +pretty_printers_dict = {} + +def register_qt_printers (obj): + if obj == None: + obj = gdb + + obj.pretty_printers.append(FunctionLookup(gdb, pretty_printers_dict)) + +def build_dictionary (): + pretty_printers_dict[re.compile('^QString$')] = lambda val: QStringPrinter(val) + pretty_printers_dict[re.compile('^QByteArray$')] = lambda val: QByteArrayPrinter(val) + pretty_printers_dict[re.compile('^QList<.*>$')] = lambda val: QListPrinter(val, 'QList', None) + pretty_printers_dict[re.compile('^QStringList$')] = lambda val: QListPrinter(val, 'QStringList', 'QString') + pretty_printers_dict[re.compile('^QQueue')] = lambda val: QListPrinter(val, 'QQueue', None) + pretty_printers_dict[re.compile('^QVector<.*>$')] = lambda val: QVectorPrinter(val, 'QVector') + pretty_printers_dict[re.compile('^QStack<.*>$')] = lambda val: QVectorPrinter(val, 'QStack') + pretty_printers_dict[re.compile('^QLinkedList<.*>$')] = lambda val: QLinkedListPrinter(val) + pretty_printers_dict[re.compile('^QMap<.*>$')] = lambda val: QMapPrinter(val, 'QMap') + pretty_printers_dict[re.compile('^QMultiMap<.*>$')] = lambda val: QMapPrinter(val, 'QMultiMap') + pretty_printers_dict[re.compile('^QHash<.*>$')] = lambda val: QHashPrinter(val, 'QHash') + pretty_printers_dict[re.compile('^QMultiHash<.*>$')] = lambda val: QHashPrinter(val, 'QMultiHash') + pretty_printers_dict[re.compile('^QDate$')] = lambda val: QDatePrinter(val) + pretty_printers_dict[re.compile('^QTime$')] = lambda val: QTimePrinter(val) + pretty_printers_dict[re.compile('^QDateTime$')] = lambda val: QDateTimePrinter(val) + pretty_printers_dict[re.compile('^QUrl$')] = lambda val: QUrlPrinter(val) + pretty_printers_dict[re.compile('^QSet<.*>$')] = lambda val: QSetPrinter(val) + pretty_printers_dict[re.compile('^QChar$')] = lambda val: QCharPrinter(val) + pretty_printers_dict[re.compile('^QUuid')] = lambda val: QUuidPrinter(val) + + +build_dictionary () diff --git a/.config/gdb/init b/.config/gdb/init new file mode 100644 index 0000000..2eb0ef1 --- /dev/null +++ b/.config/gdb/init @@ -0,0 +1,11 @@ +#set auto-load local-gdbinit on +#add-auto-load-safe-path / + +python +import sys, os.path +sys.path.insert(0, '/home/janek/.config/gdb') +import qt5printers +qt5printers.register_printers(gdb.current_objfile()) +end + +set print pretty on diff --git a/.config/gdb/qt5printers/README.md b/.config/gdb/qt5printers/README.md new file mode 100644 index 0000000..984f2ce --- /dev/null +++ b/.config/gdb/qt5printers/README.md @@ -0,0 +1,59 @@ +# qt5printers +The `core.py`, `typeinfo.py` and `__init__.py` files are taken from patchset 2 +at https://codereview.qt-project.org/87052 and provide a GDB pretty printer for +Qt5. These are authored by Alex Merry from the KDE project. + +## Usage +Copy the three Python files to `~/.gdb/qt5printers/` and add this to your +`~/.gdbinit` (or execute it from an existing `gdb` session): + + + python + import sys, os.path + sys.path.insert(0, os.path.expanduser('~/.gdb')) + import qt5printers + qt5printers.register_printers(gdb.current_objfile()) + end + +Now verify it with your favorite program. Below you can find a quick test +program. + +### Test program +Here is a test program (save it as `test.cpp`): + + #include + void test(const QByteArray & ba) { } + int main(void) { + test(QByteArray("abc")); + return 0; + } + +Compile it with: + + g++ test.cpp $(pkg-config --cflags --libs Qt5Core) -g + +If everything goes well you should see the expanded data: + + $ gdb -q -ex break\ test -ex r ./a.out + ... + Breakpoint 1, test (ba="abc" = {...}) at test.cpp:4 + 4 test(QByteArray("abc")); + +## Background +The Qt4 pretty printers from KDevelop[0] are not fully compatible with Qt5. For +instance, the latest version (from December 2014) does not properly handle +QByteArray. While these qt5printers are compatible with Qt5, it conflicts with +Qt4 (for example, QByteArray changed in Qt 5 from Qt 4 in this commit[1]). + +See also: + + - https://bugs.kde.org/show_bug.cgi?id=331044 + - https://techbase.kde.org/Development/Tutorials/Debugging/Debugging_with_GDB + + +## License +For the applicable licenses, see the headers of the files and refer to the Qt5 +sources at https://code.qt.io/cgit/qt/qtbase.git/tree/. + + [0]: https://projects.kde.org/projects/extragear/kdevelop/kdevelop/repository/revisions/master/show/debuggers/gdb/printers + [1]: https://code.qt.io/cgit/qt/qtbase.git/commit/src/corelib/tools/qbytearray.h?id=ad35a41739c8e1fb6db62ed37b764448b2e59ece diff --git a/.config/gdb/qt5printers/__init__.py b/.config/gdb/qt5printers/__init__.py new file mode 100644 index 0000000..62b7622 --- /dev/null +++ b/.config/gdb/qt5printers/__init__.py @@ -0,0 +1,54 @@ +############################################################################# +## +## Copyright (C) 2014 Alex Merry +## Contact: http://www.qt-project.org/legal +## +## This file is part of the GDB pretty printers for the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:LGPL$ +## Commercial License Usage +## Licensees holding valid commercial Qt licenses may use this file in +## accordance with the commercial license agreement provided with the +## Software or, alternatively, in accordance with the terms contained in +## a written agreement between you and Digia. For licensing terms and +## conditions see http://qt.digia.com/licensing. For further information +## use the contact form at http://qt.digia.com/contact-us. +## +## GNU Lesser General Public License Usage +## Alternatively, this file may be used under the terms of the GNU Lesser +## General Public License version 2.1 as published by the Free Software +## Foundation and appearing in the file LICENSE.LGPL included in the +## packaging of this file. Please review the following information to +## ensure the GNU Lesser General Public License version 2.1 requirements +## will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +## +## In addition, as a special exception, Digia gives you certain additional +## rights. These rights are described in the Digia Qt LGPL Exception +## version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +## +## GNU General Public License Usage +## Alternatively, this file may be used under the terms of the GNU +## General Public License version 3.0 as published by the Free Software +## Foundation and appearing in the file LICENSE.GPL included in the +## packaging of this file. Please review the following information to +## ensure the GNU General Public License version 3.0 requirements will be +## met: http://www.gnu.org/copyleft/gpl.html. +## +## +## $QT_END_LICENSE$ +## +############################################################################# + +import gdb.printing +from . import core + +"""Qt5 Pretty Printers for GDB. + +The printers are split into submodules, one for each Qt library. Each +submodule has a "printer" attribute that contains a pretty-printer for +that library. +""" + +def register_printers(obj): + """Registers all known Qt5 pretty-printers.""" + gdb.printing.register_pretty_printer(obj, core.printer) diff --git a/.config/gdb/qt5printers/core.py b/.config/gdb/qt5printers/core.py new file mode 100644 index 0000000..9a030fd --- /dev/null +++ b/.config/gdb/qt5printers/core.py @@ -0,0 +1,953 @@ +############################################################################# +## +## Copyright (C) 2014 Alex Merry +## Contact: http://www.qt-project.org/legal +## +## This file is part of the GDB pretty printers for the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:LGPL$ +## Commercial License Usage +## Licensees holding valid commercial Qt licenses may use this file in +## accordance with the commercial license agreement provided with the +## Software or, alternatively, in accordance with the terms contained in +## a written agreement between you and Digia. For licensing terms and +## conditions see http://qt.digia.com/licensing. For further information +## use the contact form at http://qt.digia.com/contact-us. +## +## GNU Lesser General Public License Usage +## Alternatively, this file may be used under the terms of the GNU Lesser +## General Public License version 2.1 as published by the Free Software +## Foundation and appearing in the file LICENSE.LGPL included in the +## packaging of this file. Please review the following information to +## ensure the GNU Lesser General Public License version 2.1 requirements +## will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +## +## In addition, as a special exception, Digia gives you certain additional +## rights. These rights are described in the Digia Qt LGPL Exception +## version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +## +## GNU General Public License Usage +## Alternatively, this file may be used under the terms of the GNU +## General Public License version 3.0 as published by the Free Software +## Foundation and appearing in the file LICENSE.GPL included in the +## packaging of this file. Please review the following information to +## ensure the GNU General Public License version 3.0 requirements will be +## met: http://www.gnu.org/copyleft/gpl.html. +## +## +## $QT_END_LICENSE$ +## +############################################################################# + +import gdb.printing +import itertools +from . import typeinfo +try: + import urlparse +except ImportError: + # Python 3 + import urllib.parse as urlparse + +"""Qt5Core pretty printer for GDB.""" + +# NB: no QPair printer: the default should be fine + +def _format_jd(jd): + """Format a Julian Day in YYYY-MM-DD format.""" + # maths from http://www.tondering.dk/claus/cal/julperiod.php + a = jd + 32044 + b = (4 * a + 3) // 146097 + c = a - ( (146097 * b) // 4 ) + d = (4 * c + 3) // 1461 + e = c - ( (1461 * d) // 4 ) + m = (5 * e + 2) // 153 + day = e - ( (153 * m + 2) // 5 ) + 1 + month = m + 3 - 12 * ( m // 10 ) + year = 100 * b + d - 4800 + ( m // 10 ) + return '{:0=4}-{:0=2}-{:0=2}'.format(year, month, day) + +def _jd_is_valid(jd): + """Return whether QDate would consider a given Julian Day valid.""" + return jd >= -784350574879 and jd <= 784354017364 + +def _format_time_ms(msecs): + """Format a number of milliseconds since midnight in HH:MM:SS.ssss format.""" + secs = msecs // 1000 + mins = secs // 60 + hours = mins // 60 + return '{:0=2}:{:0=2}:{:0=2}.{:0=3}'.format( + hours % 24, mins % 60, secs % 60, msecs % 1000) + +def _ms_is_valid(msecs): + """Return whether QTime would consider a ms since midnight valid.""" + return msecs >= 0 and msecs <= 86400000 + +class ArrayIter: + """Iterates over a fixed-size array.""" + def __init__(self, array, size): + self.array = array + self.i = -1 + self.size = size + + def __iter__(self): + return self + + def __next__(self): + if self.i + 1 >= self.size: + raise StopIteration + self.i += 1 + return ('[%d]' % self.i, self.array[self.i]) + + def next(self): + return self.__next__() + +class StructReader: + """Reads entries from a struct.""" + def __init__(self, data): + self.data = data.reinterpret_cast(gdb.lookup_type('char').pointer()) + self.ptr_t = gdb.lookup_type('void').pointer() + + def next_aligned_val(self, typ): + ptr_val = int(str(self.data.reinterpret_cast(self.ptr_t)), 16) + misalignment = ptr_val % self.ptr_t.sizeof + if misalignment > 0: + self.data += self.ptr_t.sizeof - misalignment + val = self.data.reinterpret_cast(typ.pointer()) + self.data += typ.sizeof + return val.referenced_value() + + def next_val(self, typ): + val = self.data.reinterpret_cast(typ.pointer()) + self.data += typ.sizeof + return val.referenced_value() + +class QBitArrayPrinter: + """Print a Qt5 QBitArray""" + + class Iter: + def __init__(self, data, size): + self.data = data + self.i = -1 + self.size = size + + def __iter__(self): + return self + + def __next__(self): + if self.i + 1 >= self.size: + raise StopIteration + self.i += 1 + if self.data[1 + (self.i >> 3)] & (1 << (self.i&7)): + return (str(self.i), 1) + else: + return (str(self.i), 0) + + def next(self): + return self.__next__() + + def __init__(self, val): + self.val = val + + def children(self): + d = self.val['d']['d'] + data = d.reinterpret_cast(gdb.lookup_type('char').pointer()) + d['offset'] + size = (int(d['size']) << 3) - int(data[0]) + + return self.Iter(data, size) + + def to_string(self): + d = self.val['d']['d'] + data = d.reinterpret_cast(gdb.lookup_type('char').pointer()) + d['offset'] + size = (int(d['size']) << 3) - int(data[0]) + if size == 0: + return '' + return None + + def display_hint(self): + return 'array' + +class QByteArrayPrinter: + """Print a Qt5 QByteArray""" + + def __init__(self, val): + self.val = val + + def children(self): + d = self.val['d'] + data = d.reinterpret_cast(gdb.lookup_type('char').pointer()) + d['offset'] + return ArrayIter(data, d['size']) + + def to_string(self): + d = self.val['d'] + data = d.reinterpret_cast(gdb.lookup_type('char').pointer()) + d['offset'] + return data.string('', 'replace', d['size']) + + def display_hint(self): + return 'string' + +class QCharPrinter: + """Print a Qt5 QChar""" + + def __init__(self, val): + self.val = val + + def to_string(self): + ucs = self.val['ucs'] + data = ucs.address.reinterpret_cast(gdb.lookup_type('char').pointer()) + unicode_str = data.string('utf-16', 'replace', 2) + uch = unicode_str[0] + if uch == unichr(0x27): + return "'\\''" + # this actually gives us Python escapes, but they should all be + # valid C escapes as well + return "'" + uch.encode('unicode_escape') + "'" + + def display_hint(self): + # this is not recognized by gdb, hence the manual escaping and quoting + # we do above + return 'char' + +class QDatePrinter: + """Print a Qt5 QDate""" + + def __init__(self, val): + self.val = val + + def to_string(self): + jd = int(self.val['jd']) + if not _jd_is_valid(jd): + return '' + return _format_jd(jd) + + def display_hint(self): + return 'date' + +class QDateTimePrinter: + """Print a Qt5 QDateTime""" + + def __init__(self, val): + self.val = val + + _unix_epoch_jd = 2440588 + _ms_per_day = 86400000 + + # status field + _validDate = 0x04 + _validTime = 0x08 + _validDateTime = 0x10 + _timeZoneCached = 0x20 + + # time spec + _localTime = 0 + _UTC = 1 + _offsetFromUTC = 2 + _timeZone = 3 + + def to_string(self): + d = self.val['d']['d'] + if not d: + return '' + + try: + qshareddata_t = gdb.lookup_type('QSharedData') + except gdb.error: + try: + # well, it only has a QAtomicInt in it + qshareddata_t = gdb.lookup_type('QAtomicInt') + except gdb.error: + # let's hope it's the same size as an int + qshareddata_t = gdb.lookup_type('int') + try: + timespec_t = gdb.lookup_type('Qt::TimeSpec') + except gdb.error: + # probably an int + timespec_t = gdb.lookup_type('int') + + reader = StructReader(d) + reader.next_val(qshareddata_t) + m_msecs = reader.next_aligned_val(gdb.lookup_type('qint64')) + spec = int(reader.next_val(timespec_t)) + m_offsetFromUtc = reader.next_val(gdb.lookup_type('int')) + m_timeZone = reader.next_val(gdb.lookup_type('QTimeZone')) + status = int(reader.next_val(gdb.lookup_type('int'))) + + if spec == self._timeZone: + timeZoneStr = QTimeZonePrinter(m_timeZone).to_string() + if timeZoneStr == '': + return '' + + if spec == self._localTime or (spec == self._timeZone and + not status & self._timeZoneCached): + # Because QDateTime delays timezone calculations as far as + # possible, the ValidDateTime flag may not be set even if + # it is a valid DateTime. + if not status & self._validDate or not status & self._validTime: + return '' + elif not (status & self._validDateTime): + return '' + + # actually fetch: + m_msecs = int(m_msecs) + + jd = self._unix_epoch_jd # UNIX epoch + jd += m_msecs // self._ms_per_day + msecs = m_msecs % self._ms_per_day + if msecs < 0: + # need to adjust back to the previous day + jd -= 1 + msecs += self._ms_per_day + + result = _format_jd(jd) + ' ' + _format_time_ms(msecs) + + if spec == self._localTime: + result += ' (Local)' + elif spec == self._UTC: + result += ' (UTC)' + elif spec == self._offsetFromUTC: + offset = int(m_offsetFromUtc) + if offset == 0: + diffstr = '' + else: + hours = abs(offset // 3600) + mins = abs((offset % 3600) // 60) + secs = abs(offset % 60) + sign = '+' if offset > 0 else '-' + diffstr = '{:}{:0=2d}:{:0=2d}'.format(sign, hours, mins) + if secs > 0: + diffstr += ':{:0=2d}'.format(secs) + result += ' (UTC{:})'.format(diffstr) + elif spec == self._timeZone: + result += ' ({:})'.format(timeZoneStr) + + return result + + def display_hint(self): + return 'datetime' + +class QHashPrinter: + """Print a Qt5 QHash""" + + class Iter: + def __init__(self, d, e): + self.buckets_left = d['numBuckets'] + self.node_type = e.type + # set us up at the end of a "dummy bucket" + self.current_bucket = d['buckets'] - 1 + self.current_node = None + self.i = -1 + self.waiting_for_value = False + + def __iter__(self): + return self + + def __next__(self): + if self.waiting_for_value: + self.waiting_for_value = False + node = self.current_node.reinterpret_cast(self.node_type) + return ('value' + str(self.i), node['value']) + + if self.current_node: + self.current_node = self.current_node['next'] + + # the dummy node that terminates a bucket is distinguishable + # by not having its 'next' value set + if not self.current_node or not self.current_node['next']: + while self.buckets_left: + self.current_bucket += 1 + self.buckets_left -= 1 + self.current_node = self.current_bucket.referenced_value() + if self.current_node['next']: + break + else: + raise StopIteration + + self.i += 1 + self.waiting_for_value = True + node = self.current_node.reinterpret_cast(self.node_type) + return ('key' + str(self.i), node['key']) + + def next(self): + return self.__next__() + + def __init__(self, val): + self.val = val + + def children(self): + d = self.val['d'] + + if d['size'] == 0: + return [] + + return self.Iter(d, self.val['e']) + + def to_string(self): + # if we return an empty list from children, gdb doesn't print anything + if self.val['d']['size'] == 0: + return '' + return None + + def display_hint(self): + return 'map' + +class QJsonObjectPrinter: + """Print a Qt5 QJsonObject""" + + def __init__(self, val): + # delegate everything to map + self.printer = QMapPrinter(gdb.parse_and_eval('((QJsonObject*){:})->toVariantMap()'.format(int(val.address)))) + + def children(self): + return self.printer.children() + + def to_string(self): + return self.printer.to_string() + + def display_hint(self): + return 'map' + +class QJsonArrayPrinter: + """Print a Qt5 QJsonArray""" + + def __init__(self, val): + # delegate everything to list + self.printer = QListPrinter(gdb.parse_and_eval('((QJsonArray*){:})->toVariantList()'.format(int(val.address)))) + + def children(self): + return self.printer.children() + + def to_string(self): + return self.printer.to_string() + + def display_hint(self): + return 'array' + +class QLatin1StringPrinter: + """Print a Qt5 QLatin1String""" + + def __init__(self, val): + self.val = val + + def to_string(self): + return self.val['m_data'].string('', 'replace', self.val['m_size']) + + def display_hint(self): + return 'string' + +class QLinkedListPrinter: + """Print a Qt5 QLinkedList""" + + class Iter: + def __init__(self, tail, size): + self.current = tail + self.i = -1 + self.size = size + + def __iter__(self): + return self + + def __next__(self): + if self.i + 1 >= self.size: + raise StopIteration + self.i += 1 + self.current = self.current['n'] + return (str(self.i), self.current['t']) + + def next(self): + return self.__next__() + + def __init__(self, val): + self.val = val + + def children(self): + size = int(self.val['d']['size']) + + if size == 0: + return [] + + return self.Iter(self.val['e'], size) + + def to_string(self): + # if we return an empty list from children, gdb doesn't print anything + if self.val['d']['size'] == 0: + return '' + return None + + def display_hint(self): + return 'array' + +class QListPrinter: + """Print a Qt5 QList""" + + class Iter: + def __init__(self, array, begin, end, typ): + self.array = array + self.end = end + self.begin = begin + self.offset = 0 + if typ.name == 'QStringList': + self.el_type = gdb.lookup_type('QString') + else: + self.el_type = typ.template_argument(0) + + if ((self.el_type.sizeof > gdb.lookup_type('void').pointer().sizeof) + or typeinfo.type_is_known_static(self.el_type)): + self.is_pointer = True + elif (typeinfo.type_is_known_movable(self.el_type) or + typeinfo.type_is_known_primitive(self.el_type)): + self.is_pointer = False + else: + raise ValueError("Could not determine whether QList stores " + + self.el_type.name + " directly or as a pointer: to fix " + + "this, add it to one of the variables in the "+ + "qt5printers.typeinfo module") + self.node_type = gdb.lookup_type(typ.name + '::Node').pointer() + + def __iter__(self): + return self + + def __next__(self): + if self.begin + self.offset >= self.end: + raise StopIteration + node = self.array[self.begin + self.offset].reinterpret_cast(self.node_type) + if self.is_pointer: + p = node['v'] + else: + p = node + self.offset += 1 + value = p.address.cast(self.el_type.pointer()).dereference() + return (str(self.offset), value) + + def next(self): + return self.__next__() + + def __init__(self, val): + self.val = val + + def children(self): + d = self.val['d'] + begin = int(d['begin']) + end = int(d['end']) + + if begin == end: + return [] + + return self.Iter(d['array'], begin, end, self.val.type.strip_typedefs()) + + def to_string(self): + # if we return an empty list from children, gdb doesn't print anything + if self.val['d']['begin'] == self.val['d']['end']: + return '' + return None + + def display_hint(self): + return 'array' + +class QMapPrinter: + """Print a Qt5 QMap""" + + class Iter: + def __init__(self, root, node_p_type): + self.root = root + self.current = None + self.node_p_type = node_p_type + self.next_is_key = True + self.i = -1 + # we store the path here to avoid keeping re-fetching + # values from the inferior (also, skips the pointer + # arithmetic involved in using the parent pointer) + self.path = [] + + def __iter__(self): + return self + + def moveToNextNode(self): + if self.current is None: + # find the leftmost node + if not self.root['left']: + return False + self.current = self.root + while self.current['left']: + self.path.append(self.current) + self.current = self.current['left'] + elif self.current['right']: + self.path.append(self.current) + self.current = self.current['right'] + while self.current['left']: + self.path.append(self.current) + self.current = self.current['left'] + else: + last = self.current + self.current = self.path.pop() + while self.current['right'] == last: + last = self.current + self.current = self.path.pop() + # if there are no more parents, we are at the root + if len(self.path) == 0: + return False + return True + + def __next__(self): + if self.next_is_key: + if not self.moveToNextNode(): + raise StopIteration + self.current_typed = self.current.reinterpret_cast(self.node_p_type) + self.next_is_key = False + self.i += 1 + return ('key' + str(self.i), self.current_typed['key']) + else: + self.next_is_key = True + return ('value' + str(self.i), self.current_typed['value']) + + def next(self): + return self.__next__() + + def __init__(self, val): + self.val = val + + def children(self): + d = self.val['d'] + size = int(d['size']) + + if size == 0: + return [] + + realtype = self.val.type.strip_typedefs() + keytype = realtype.template_argument(0) + valtype = realtype.template_argument(1) + node_type = gdb.lookup_type('QMapData<' + keytype.name + ',' + valtype.name + '>::Node') + + return self.Iter(d['header'], node_type.pointer()) + + def to_string(self): + # if we return an empty list from children, gdb doesn't print anything + if self.val['d']['size'] == 0: + return '' + return None + + def display_hint(self): + return 'map' + +class QSetPrinter: + """Print a Qt5 QSet""" + + def __init__(self, val): + self.val = val + + def children(self): + hashPrinter = QHashPrinter(self.val['q_hash']) + # the keys of the hash are the elements of the set, so select + # every other item (starting with the first) + return itertools.islice(hashPrinter.children(), 0, None, 2) + + def to_string(self): + # if we return an empty list from children, gdb doesn't print anything + if self.val['q_hash']['d']['size'] == 0: + return '' + return None + + def display_hint(self): + return 'array' + +class QStringPrinter: + """Print a Qt5 QString""" + + def __init__(self, val): + self.val = val + + def to_string(self): + d = self.val['d'] + data = d.reinterpret_cast(gdb.lookup_type('char').pointer()) + d['offset'] + data_len = d['size'] * gdb.lookup_type('unsigned short').sizeof + return data.string('utf-16', 'replace', data_len) + + def display_hint(self): + return 'string' + +class QTimePrinter: + """Print a Qt5 QTime""" + + def __init__(self, val): + self.val = val + + def to_string(self): + msecs = int(self.val['mds']) + if not _ms_is_valid(msecs): + return '' + return _format_time_ms(msecs) + + def display_hint(self): + return 'time' + +class QTimeZonePrinter: + """Print a Qt5 QTimeZone""" + + def __init__(self, val): + self.val = val + + def to_string(self): + d = self.val['d']['d'] + if not d: + return '' + + try: + # Accessing the private data is error-prone, + # so try just calling the id() method. + # This should be reasonably safe, as all it will + # do is create a QByteArray that references the + # same internal data as the stored one. However, + # it will only work with an attached process. + m_id = gdb.parse_and_eval('((QTimeZone*){:})->id()'.format(self.val.address)) + except: + ptr_size = gdb.lookup_type('void').pointer().sizeof + try: + qshareddata_t = gdb.lookup_type('QSharedData') + except gdb.error: + try: + # well, it only has a QAtomicInt in it + qshareddata_t = gdb.lookup_type('QAtomicInt') + except gdb.error: + # let's hope it's the same size as an int + qshareddata_t = gdb.lookup_type('int') + + reader = StructReader(d) + reader.next_val(gdb.lookup_type('void').pointer()) # vtable + reader.next_val(qshareddata_t) + m_id = reader.next_aligned_val(gdb.lookup_type('QByteArray')) + + return QByteArrayPrinter(m_id).to_string() + + def display_hint(self): + return 'string' + +class QVariantPrinter: + """Print a Qt5 QVariant""" + + _varmap = { + 'char': 'c', + 'uchar': 'uc', + 'short': 's', + 'signed char': 'sc', + 'ushort': 'us', + 'int': 'i', + 'uint': 'u', + 'long': 'l', + 'ulong': 'ul', + 'bool': 'b', + 'double': 'd', + 'float': 'f', + 'qreal': 'real', + 'qlonglong': 'll', + 'qulonglong': 'ull', + 'QObject*': 'o', + 'void*': 'ptr' + } + + def __init__(self, val): + self.val = val + + def to_string(self): + d = self.val['d'] + typ = int(d['type']) + if typ == typeinfo.meta_type_unknown: + return '' + + data = d['data'] + + if typ in typeinfo.meta_type_names: + typename = typeinfo.meta_type_names[typ] + if typename in self._varmap: + field = self._varmap[typename] + return data[field] + + try: + if typename.endswith('*'): + gdb_type = gdb.lookup_type(typename[0:-1]).pointer() + else: + gdb_type = gdb.lookup_type(typename) + except gdb.error: + # couldn't find any type information + return data + + if gdb_type.sizeof > data.type.sizeof: + is_pointer = True + elif (typeinfo.type_is_known_movable(gdb_type) or + typeinfo.type_is_known_primitive(gdb_type)): + is_pointer = False + elif gdb_type.tag == 'enum': + is_pointer = False + else: + # couldn't figure out how the type is stored + return data['o'].cast(gdb_type) + + if is_pointer: + value = data['shared']['ptr'].reinterpret_cast(gdb_type.pointer()) + else: + void_star = gdb.lookup_type('void').pointer() + data_void = data['c'].address.reinterpret_cast(void_star) + value = data_void.reinterpret_cast(gdb_type.pointer()) + + return value.referenced_value() + else: + # custom type? + return data + +class QVarLengthArrayPrinter: + """Print a Qt5 QVarLengthArray""" + + def __init__(self, val): + self.val = val + + def children(self): + size = int(self.val['s']) + + if size == 0: + return [] + + return ArrayIter(self.val['ptr'], size) + + def to_string(self): + # if we return an empty list from children, gdb doesn't print anything + if self.val['s'] == 0: + return '' + return None + + def display_hint(self): + return 'array' + +class QVectorPrinter: + """Print a Qt5 QVector""" + + def __init__(self, val): + self.val = val + + def children(self): + d = self.val['d'] + el_type = self.val.type.template_argument(0) + data_len = int(d['size']) + + if data_len == 0: + return [] + + data_char = d.reinterpret_cast(gdb.lookup_type('char').pointer()) + d['offset'] + data = data_char.reinterpret_cast(el_type.pointer()) + + return ArrayIter(data, data_len) + + def to_string(self): + # if we return an empty list from children, gdb doesn't print anything + if self.val['d']['size'] == 0: + return '' + return None + + def display_hint(self): + return 'array' + +class QUrlPrinter: + """Print a Qt5 QUrl""" + + def __init__(self, val): + self.val = val + + def to_string(self): + d = self.val['d'] + if not d: + return '' + + int_t = gdb.lookup_type('int') + try: + atomicint_t = gdb.lookup_type('QAtomicInt') + except gdb.error: + # let's hope it's the same size as an int + atomicint_t = int_t + qstring_t = gdb.lookup_type('QString') + uchar_t = gdb.lookup_type('uchar') + + reader = StructReader(d) + + # These fields (including order) are unstable, and + # may change between even patch-level Qt releases + reader.next_val(atomicint_t) + port = int(reader.next_val(int_t)) + scheme = reader.next_val(qstring_t) + userName = reader.next_val(qstring_t) + password = reader.next_val(qstring_t) + host = reader.next_val(qstring_t) + path = reader.next_val(qstring_t) + query = reader.next_val(qstring_t) + fragment = reader.next_val(qstring_t) + reader.next_val(gdb.lookup_type('void').pointer()) + sections = int(reader.next_val(uchar_t)) + flags = int(reader.next_val(uchar_t)) + + # isLocalFile and no query and no fragment + if flags & 0x01 and not (sections & 0x40) and not (sections & 0x80): + # local file + return path + + def qs_to_s(qstring): + return QStringPrinter(qstring).to_string() + + # QUrl::toString() is way more complicated than what we do here, + # but this is good enough for debugging + result = '' + if sections & 0x01: + result += qs_to_s(scheme) + ':' + if sections & (0x02 | 0x04 | 0x08 | 0x10) or flags & 0x01: + result += '//' + if sections & 0x02 or sections & 0x04: + result += qs_to_s(userName) + if sections & 0x04: + # this may appear in backtraces that will be sent to other + # people + result += ':' + result += '@' + if sections & 0x08: + result += qs_to_s(host) + if port != -1: + result += ':' + str(port) + result += qs_to_s(path) + if sections & 0x40: + result += '?' + qs_to_s(query) + if sections & 0x80: + result += '#' + qs_to_s(fragment) + return result + + def display_hint(self): + return 'string' + + +def build_pretty_printer(): + """Builds the pretty printer for Qt5Core.""" + pp = gdb.printing.RegexpCollectionPrettyPrinter("Qt5Core") + pp.add_printer('QBitArray', '^QBitArray$', QBitArrayPrinter) + pp.add_printer('QByteArray', '^QByteArray$', QByteArrayPrinter) + pp.add_printer('QChar', '^QChar$', QCharPrinter) + pp.add_printer('QDate', '^QDate$', QDatePrinter) + pp.add_printer('QDateTime', '^QDateTime$', QDateTimePrinter) + pp.add_printer('QJsonArray', '^QJsonArray', QJsonArrayPrinter) + pp.add_printer('QJsonObject', '^QJsonObject$', QJsonObjectPrinter) + pp.add_printer('QLatin1String', '^QLatin1String$', QLatin1StringPrinter) + pp.add_printer('QLinkedList', '^QLinkedList<.*>$', QLinkedListPrinter) + pp.add_printer('QList', '^QList<.*>$', QListPrinter) + pp.add_printer('QMap', '^QMap<.*>$', QMapPrinter) + pp.add_printer('QHash', '^QHash<.*>$', QHashPrinter) + pp.add_printer('QQueue', '^QQueue<.*>$', QListPrinter) + pp.add_printer('QSet', '^QSet<.*>$', QSetPrinter) + pp.add_printer('QStack', '^QStack<.*>$', QVectorPrinter) + pp.add_printer('QString', '^QString$', QStringPrinter) + pp.add_printer('QStringList', '^QStringList$', QListPrinter) + pp.add_printer('QTime', '^QTime$', QTimePrinter) + pp.add_printer('QTimeZone', '^QTimeZone$', QTimeZonePrinter) + pp.add_printer('QVariant', '^QVariant$', QVariantPrinter) + pp.add_printer('QVariantList', '^QVariantList$', QListPrinter) + pp.add_printer('QVariantMap', '^QVariantMap$', QMapPrinter) + pp.add_printer('QVector', '^QVector<.*>$', QVectorPrinter) + pp.add_printer('QVarLengthArray', '^QVarLengthArray<.*>$', QVarLengthArrayPrinter) + pp.add_printer('QUrl', '^QUrl$', QUrlPrinter) + return pp + +printer = build_pretty_printer() +"""The pretty printer for Qt5Core. + +This can be registered using gdb.printing.register_pretty_printer(). +""" diff --git a/.config/gdb/qt5printers/typeinfo.py b/.config/gdb/qt5printers/typeinfo.py new file mode 100644 index 0000000..ad44640 --- /dev/null +++ b/.config/gdb/qt5printers/typeinfo.py @@ -0,0 +1,375 @@ +############################################################################# +## +## Copyright (C) 2014 Alex Merry +## Contact: http://www.qt-project.org/legal +## +## This file is part of the GDB pretty printers for the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:LGPL$ +## Commercial License Usage +## Licensees holding valid commercial Qt licenses may use this file in +## accordance with the commercial license agreement provided with the +## Software or, alternatively, in accordance with the terms contained in +## a written agreement between you and Digia. For licensing terms and +## conditions see http://qt.digia.com/licensing. For further information +## use the contact form at http://qt.digia.com/contact-us. +## +## GNU Lesser General Public License Usage +## Alternatively, this file may be used under the terms of the GNU Lesser +## General Public License version 2.1 as published by the Free Software +## Foundation and appearing in the file LICENSE.LGPL included in the +## packaging of this file. Please review the following information to +## ensure the GNU Lesser General Public License version 2.1 requirements +## will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +## +## In addition, as a special exception, Digia gives you certain additional +## rights. These rights are described in the Digia Qt LGPL Exception +## version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +## +## GNU General Public License Usage +## Alternatively, this file may be used under the terms of the GNU +## General Public License version 3.0 as published by the Free Software +## Foundation and appearing in the file LICENSE.GPL included in the +## packaging of this file. Please review the following information to +## ensure the GNU General Public License version 3.0 requirements will be +## met: http://www.gnu.org/copyleft/gpl.html. +## +## +## $QT_END_LICENSE$ +## +############################################################################# + +import gdb.printing + +"""Qt5 Type Information + +Since the QTypeInfo information is not necessarily available at debug time, this +module contains useful type information about standard and Qt types (such as +whether a type is movable) that is necessary for the operation of the printers. +This information allows the QList printer, for example, to determine how the +elements are stored in the list. +""" + +primitive_types = set([ + 'HB_FixedPoint', + 'HB_GlyphAttributes', + 'QCharAttributes', + 'QFlag', + 'QIncompatibleFlag', + 'QRegExpAnchorAlternation', + 'QRegExpAtom', + 'QRegExpCharClassRange', + 'QStaticPlugin', + 'QStringRef', + 'QTzType', + 'QUuid' + ]) +"""Primitive (non-template) types. + +This does not need to include compiler-primitive types (like int). + +If you use the Q_DECLARE_TYPEINFO macro with Q_PRIMITIVE_TYPE flag, you +should add the type to this set. This is particularly important for +types that are the same size as a pointer or smaller. +""" + +primitive_tpl_types = set(['QFlags']) +"""Primitive template types. + +If you use the Q_DECLARE_TYPEINFO_BODY macro with Q_PRIMITIVE_TYPE flag +on a type with template parameters, you should add the type to this +set. This is particularly important for types that are the same size as +a pointer or smaller. + +Entries should just be the base typename, without any template +parameters (eg: "QFlags", rather than "QFlags"). +""" + +movable_types = set([ + 'QBasicTimer', + 'QBitArray', + 'QByteArray', + 'QChar', + 'QCharRef', + 'QCustomTypeInfo', + 'QDate', + 'QDateTime', + 'QFileInfo', + 'QEasingCurve', + 'QFileSystemWatcherPathKey', + 'QHashDummyValue', + 'QItemSelectionRange', + 'QLatin1String', + 'QLine', + 'QLineF', + 'QLocale', + 'QLoggingRule', + 'QMargins', + 'QMarginsF', + 'QMetaClassInfo', + 'QMetaEnum', + 'QMetaMethod', + 'QMimeMagicRule', + 'QModelIndex', + 'QPersistentModelIndex', + 'QObjectPrivate::Connection', + 'QObjectPrivate::Sender', + 'QPoint', + 'QPointF', + 'QPostEvent', + 'QProcEnvKey', + 'QProcEnvValue', + 'QRect', + 'QRectF', + 'QRegExp', + 'QRegExpAutomatonState', + 'QRegExpCharClass', + 'QResourceRoot', + 'QSize', + 'QSizeF', + 'QString', + 'QStringList', + 'QTime', + 'QTimeZone::OffsetData', + 'QUrl', + 'QVariant', + 'QXmlStreamAttribute', + 'QXmlStreamEntityDeclaration', + 'QXmlStreamNamespaceDeclaration', + 'QXmlStreamNotationDeclaration' + ]) +"""Movable (non-template) types. + +If you use the Q_DECLARE_TYPEINFO macro with Q_MOVABLE_TYPE flag, you +should add the type to this set. This is particularly important for +types that are the same size as a pointer or smaller. +""" + +movable_tpl_types = set([ + 'QExplicitlySharedDataPointer', + 'QLinkedList', + 'QList', + 'QPointer', + 'QQueue', + 'QSet', + 'QSharedDataPointer', + 'QSharedPointer', + 'QStack', + 'QVector', + 'QWeakPointer' + ]) +"""Movable template types. + +If you use the Q_DECLARE_TYPEINFO_BODY macro with Q_MOVABLE_TYPE flag +on a type with template parameters, you should add the type to this +set. This is particularly important for types that are the same size as +a pointer or smaller. + +Entries should just be the base typename, without any template +parameters (eg: "QFlags", rather than "QFlags"). +""" + +static_types = set() +"""Static (non-template) types. + +If you define a custom type that is neither primitive nor movable, you +can add the type to this set to indicate this. This is particularly +important for types that are the same size as a pointer or smaller. +""" + +static_tpl_types = set() +"""Static template types. + +If you define a custom type with template parameters that is neither +primitive nor movable, you can add the type to this set to indicate +this. This is particularly important for types that are the same size as +a pointer or smaller. + +Entries should just be the base typename, without any template +parameters (eg: "QFlags", rather than "QFlags"). +""" + +def type_is_known_primitive(typ): + """Returns True if the given gdb type is known to be primitive.""" + if typ.code == gdb.TYPE_CODE_PTR or typ.code == gdb.TYPE_CODE_INT or typ.code == gdb.TYPE_CODE_FLT or typ.code == gdb.TYPE_CODE_CHAR or typ.code == gdb.TYPE_CODE_BOOL: + return True + pos = typ.name.find('<') + if pos > 0: + return typ.name[0:pos] in primitive_tpl_types + else: + return typ.name in primitive_types + +def type_is_known_movable(typ): + """Returns True if the given gdb type is known to be movable.""" + if not typ.name: + return False + pos = typ.name.find('<') + if pos > 0: + return typ.name[0:pos] in movable_tpl_types + else: + return typ.name in movable_types + +def type_is_known_static(typ): + """Returns True if the given gdb type is known to be neither primitive nor movable.""" + if not typ.name: + return False + pos = typ.name.find('<') + if pos > 0: + return typ.name[0:pos] in static_tpl_types + else: + return typ.name in static_types + +meta_type_unknown = 0 +"""The unknown/invalid meta type ID.""" +meta_type_user = 1024 +"""The starting value for custom type IDs.""" +meta_type_ids = { + 'bool': 1, + 'int': 2, + 'uint': 3, + 'qlonglong': 4, + 'qulonglong': 5, + 'double': 6, + 'QChar': 7, + 'QVariantMap': 8, + 'QVariantList': 9, + 'QString': 10, + 'QStringList': 11, + 'QByteArray': 12, + 'QBitArray': 13, + 'QDate': 14, + 'QTime': 15, + 'QDateTime': 16, + 'QUrl': 17, + 'QLocale': 18, + 'QRect': 19, + 'QRectF': 20, + 'QSize': 21, + 'QSizeF': 22, + 'QLine': 23, + 'QLineF': 24, + 'QPoint': 25, + 'QPointF': 26, + 'QRegExp': 27, + 'QVariantHash': 28, + 'QEasingCurve': 29, + 'QUuid': 30, + 'void*': 31, + 'long': 32, + 'short': 33, + 'char': 34, + 'ulong': 35, + 'ushort': 36, + 'uchar': 37, + 'float': 38, + 'QObject*': 39, + 'signed char': 40, + 'QVariant': 41, + 'QModelIndex': 42, + 'void': 43, + 'QRegularExpression': 44, + 'QJsonValue': 45, + 'QJsonObject': 46, + 'QJsonArray': 47, + 'QJsonDocument': 48, + 'QFont': 64, + 'QPixmap': 65, + 'QBrush': 66, + 'QColor': 67, + 'QPalette': 68, + 'QIcon': 69, + 'QImage': 70, + 'QPolygon': 71, + 'QRegion': 72, + 'QBitmap': 73, + 'QCursor': 74, + 'QKeySequence': 75, + 'QPen': 76, + 'QTextLength': 77, + 'QTextFormat': 78, + 'QMatrix': 79, + 'QTransform': 80, + 'QMatrix4x4': 81, + 'QVector2D': 82, + 'QVector3D': 83, + 'QVector4D': 84, + 'QQuaternion': 85, + 'QPolygonF': 86, + 'QSizePolicy': 121 +} +"""Map from type names to meta type IDs.""" +meta_type_names = { + 1: 'bool', + 2: 'int', + 3: 'uint', + 4: 'qlonglong', + 5: 'qulonglong', + 6: 'double', + 7: 'QChar', + 8: 'QVariantMap', + 9: 'QVariantList', + 10: 'QString', + 11: 'QStringList', + 12: 'QByteArray', + 13: 'QBitArray', + 14: 'QDate', + 15: 'QTime', + 16: 'QDateTime', + 17: 'QUrl', + 18: 'QLocale', + 19: 'QRect', + 20: 'QRectF', + 21: 'QSize', + 22: 'QSizeF', + 23: 'QLine', + 24: 'QLineF', + 25: 'QPoint', + 26: 'QPointF', + 27: 'QRegExp', + 28: 'QVariantHash', + 29: 'QEasingCurve', + 30: 'QUuid', + 31: 'void*', + 32: 'long', + 33: 'short', + 34: 'char', + 35: 'ulong', + 36: 'ushort', + 37: 'uchar', + 38: 'float', + 39: 'QObject*', + 40: 'signed char', + 41: 'QVariant', + 42: 'QModelIndex', + 43: 'void', + 44: 'QRegularExpression', + 45: 'QJsonValue', + 46: 'QJsonObject', + 47: 'QJsonArray', + 48: 'QJsonDocument', + 64: 'QFont', + 65: 'QPixmap', + 66: 'QBrush', + 67: 'QColor', + 68: 'QPalette', + 69: 'QIcon', + 70: 'QImage', + 71: 'QPolygon', + 72: 'QRegion', + 73: 'QBitmap', + 74: 'QCursor', + 75: 'QKeySequence', + 76: 'QPen', + 77: 'QTextLength', + 78: 'QTextFormat', + 79: 'QMatrix', + 80: 'QTransform', + 81: 'QMatrix4x4', + 82: 'QVector2D', + 83: 'QVector3D', + 84: 'QVector4D', + 85: 'QQuaternion', + 86: 'QPolygonF', + 121: 'QSizePolicy' +} +"""Map from meta type IDs to type names.""" diff --git a/.gdbinit b/.gdbinit new file mode 120000 index 0000000..bd7ca27 --- /dev/null +++ b/.gdbinit @@ -0,0 +1 @@ +.config/gdb/init \ No newline at end of file