dotfiles/.config/gdb/qt5printers/core.py

954 lines
30 KiB
Python

#############################################################################
##
## 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().
"""