config/gdb: add qt printers

https://github.com/mixxxdj/mixxx/wiki/Debugging-Cpp-Code
This commit is contained in:
xeruf 2021-11-17 11:55:44 +01:00
parent a4b7d602ed
commit ba44dd2888
7 changed files with 2167 additions and 0 deletions

714
.config/gdb/.unused/qt.py Normal file
View File

@ -0,0 +1,714 @@
# -*- coding: iso-8859-1 -*-
# Pretty-printers for Qt 4 and Qt 5.
# Copyright (C) 2009 Niko Sams <niko.sams@gmail.com>
# 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 <http://www.gnu.org/licenses/>.
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<T>::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<int, int>']
#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<const QDateTime*>(%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<const QUrl*>(%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 ()

11
.config/gdb/init Normal file
View File

@ -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

View File

@ -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 <QTextStream>
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

View File

@ -0,0 +1,54 @@
#############################################################################
##
## Copyright (C) 2014 Alex Merry <alex.merry@kde.org>
## 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)

View File

@ -0,0 +1,953 @@
#############################################################################
##
## Copyright (C) 2014 Alex Merry <alex.merry@kde.org>
## 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 '<empty>'
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 '<invalid>'
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 '<invalid>'
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 '<invalid>'
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 '<invalid>'
elif not (status & self._validDateTime):
return '<invalid>'
# 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 '<empty>'
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 '<empty>'
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 '<empty>'
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 '<empty>'
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 '<empty>'
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 '<invalid>'
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 '<invalid type>'
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 '<empty>'
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 '<empty>'
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 '<empty>'
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 += ':<omitted>'
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().
"""

View File

@ -0,0 +1,375 @@
#############################################################################
##
## Copyright (C) 2014 Alex Merry <alex.merry@kde.org>
## 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<T>").
"""
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<T>").
"""
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<T>").
"""
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."""

1
.gdbinit Symbolic link
View File

@ -0,0 +1 @@
.config/gdb/init