Files
qt-creator/share/qtcreator/debugger/gdbbridge.py
hjk e2c45012d6 Debugger: Streamline type code handling
GDB and LLDB share some ideas but are not identical. Map them
both to what we need in the end. This also reduces the size
of the Type class interface as a step towards more type info
caching.

Change-Id: I43e60c4e5736ac15a5c776832ffb8e6939f45e45
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
2016-09-20 09:22:34 +00:00

1398 lines
49 KiB
Python

############################################################################
#
# Copyright (C) 2016 The Qt Company Ltd.
# Contact: https://www.qt.io/licensing/
#
# This file is part of Qt Creator.
#
# Commercial License Usage
# Licensees holding valid commercial Qt licenses may use this file in
# accordance with the commercial license agreement provided with the
# Software or, alternatively, in accordance with the terms contained in
# a written agreement between you and The Qt Company. For licensing terms
# and conditions see https://www.qt.io/terms-conditions. For further
# information use the contact form at https://www.qt.io/contact-us.
#
# GNU General Public License Usage
# Alternatively, this file may be used under the terms of the GNU
# General Public License version 3 as published by the Free Software
# Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
# included in the packaging of this file. Please review the following
# information to ensure the GNU General Public License requirements will
# be met: https://www.gnu.org/licenses/gpl-3.0.html.
#
############################################################################
try:
import __builtin__
except:
import builtins
try:
import gdb
except:
pass
import os
import os.path
import sys
import struct
import types
from dumper import *
#######################################################################
#
# Infrastructure
#
#######################################################################
def safePrint(output):
try:
print(output)
except:
out = ""
for c in output:
cc = ord(c)
if cc > 127:
out += "\\\\%d" % cc
elif cc < 0:
out += "\\\\%d" % (cc + 256)
else:
out += c
print(out)
def registerCommand(name, func):
class Command(gdb.Command):
def __init__(self):
super(Command, self).__init__(name, gdb.COMMAND_OBSCURE)
def invoke(self, args, from_tty):
safePrint(func(args))
Command()
#######################################################################
#
# Convenience
#
#######################################################################
# Just convienience for 'python print ...'
class PPCommand(gdb.Command):
def __init__(self):
super(PPCommand, self).__init__("pp", gdb.COMMAND_OBSCURE)
def invoke(self, args, from_tty):
print(eval(args))
PPCommand()
# Just convienience for 'python print gdb.parse_and_eval(...)'
class PPPCommand(gdb.Command):
def __init__(self):
super(PPPCommand, self).__init__("ppp", gdb.COMMAND_OBSCURE)
def invoke(self, args, from_tty):
print(gdb.parse_and_eval(args))
PPPCommand()
def scanStack(p, n):
p = int(p)
r = []
for i in xrange(n):
f = gdb.parse_and_eval("{void*}%s" % p)
m = gdb.execute("info symbol %s" % f, to_string=True)
if not m.startswith("No symbol matches"):
r.append(m)
p += f.type.sizeof
return r
class ScanStackCommand(gdb.Command):
def __init__(self):
super(ScanStackCommand, self).__init__("scanStack", gdb.COMMAND_OBSCURE)
def invoke(self, args, from_tty):
if len(args) == 0:
args = 20
safePrint(scanStack(gdb.parse_and_eval("$sp"), int(args)))
ScanStackCommand()
#######################################################################
#
# Import plain gdb pretty printers
#
#######################################################################
class PlainDumper:
def __init__(self, printer):
self.printer = printer
self.typeCache = {}
def __call__(self, d, value):
try:
printer = self.printer.gen_printer(value)
except:
printer = self.printer.invoke(value)
lister = getattr(printer, "children", None)
children = [] if lister is None else list(lister())
d.putType(self.printer.name)
val = printer.to_string()
if isinstance(val, str):
d.putValue(val)
elif sys.version_info[0] <= 2 and isinstance(val, unicode):
d.putValue(val)
else: # Assuming LazyString
d.putCharArrayHelper(val.address, val.length, val.type)
d.putNumChild(len(children))
if d.isExpanded():
with Children(d):
for child in children:
d.putSubItem(child[0], child[1])
def importPlainDumpers(args):
if args == "off":
try:
gdb.execute("disable pretty-printer .* .*")
except:
# Might occur in non-ASCII directories
warn("COULD NOT DISABLE PRETTY PRINTERS")
else:
theDumper.importPlainDumpers()
registerCommand("importPlainDumpers", importPlainDumpers)
class OutputSafer:
def __init__(self, d):
self.d = d
def __enter__(self):
self.savedOutput = self.d.output
self.d.output = []
def __exit__(self, exType, exValue, exTraceBack):
if self.d.passExceptions and not exType is None:
showException("OUTPUTSAFER", exType, exValue, exTraceBack)
self.d.output = self.savedOutput
else:
self.savedOutput.extend(self.d.output)
self.d.output = self.savedOutput
return False
#def couldBePointer(p, align):
# typeobj = lookupType("unsigned int")
# ptr = gdb.Value(p).cast(typeobj)
# d = int(str(ptr))
# warn("CHECKING : %s %d " % (p, ((d & 3) == 0 and (d > 1000 or d == 0))))
# return (d & (align - 1)) and (d > 1000 or d == 0)
Value = gdb.Value
#######################################################################
#
# The Dumper Class
#
#######################################################################
class Dumper(DumperBase):
def __init__(self):
DumperBase.__init__(self)
# These values will be kept between calls to 'fetchVariables'.
self.isGdb = True
self.typeCache = {}
self.interpreterBreakpointResolvers = []
def prepare(self, args):
self.output = []
self.currentIName = ""
self.currentPrintsAddress = True
self.currentChildType = ""
self.currentChildNumChild = -1
self.currentMaxNumChild = -1
self.currentNumChild = -1
self.currentValue = ReportItem()
self.currentType = ReportItem()
self.currentAddress = None
self.resultVarName = args.get("resultvarname", "")
self.expandedINames = set(args.get("expanded", []))
self.stringCutOff = int(args.get("stringcutoff", 10000))
self.displayStringLimit = int(args.get("displaystringlimit", 100))
self.typeformats = args.get("typeformats", {})
self.formats = args.get("formats", {})
self.watchers = args.get("watchers", {})
self.useDynamicType = int(args.get("dyntype", "0"))
self.useFancy = int(args.get("fancy", "0"))
self.forceQtNamespace = int(args.get("forcens", "0"))
self.passExceptions = int(args.get("passexceptions", "0"))
self.showQObjectNames = int(args.get("qobjectnames", "0"))
self.nativeMixed = int(args.get("nativemixed", "0"))
self.autoDerefPointers = int(args.get("autoderef", "0"))
self.partialUpdate = int(args.get("partial", "0"))
self.fallbackQtVersion = 0x50200
#warn("NAMESPACE: '%s'" % self.qtNamespace())
#warn("EXPANDED INAMES: %s" % self.expandedINames)
#warn("WATCHERS: %s" % self.watchers)
# The guess does not need to be updated during a fetchVariables()
# as the result is fixed during that time (ignoring "active"
# dumpers causing loading of shared objects etc).
self.currentQtNamespaceGuess = None
def fromNativeDowncastableValue(self, nativeValue):
if self.useDynamicType:
try:
return self.fromNativeValue(nativeValue.cast(nativeValue.dynamic_type))
except:
pass
return self.fromNativeValue(nativeValue)
def fromNativeValue(self, nativeValue):
#self.check(isinstance(nativeType, gdb.Value))
val = self.Value(self)
val.nativeValue = nativeValue
if not nativeValue.address is None:
val.laddress = toInteger(nativeValue.address)
val.type = self.fromNativeType(nativeValue.type)
val.lIsInScope = not nativeValue.is_optimized_out
return val
def fromNativeType(self, nativeType):
self.check(isinstance(nativeType, gdb.Type))
typeobj = self.Type(self)
typeobj.nativeType = nativeType.unqualified()
typeobj.name = str(typeobj.nativeType)
typeobj.lbitsize = nativeType.sizeof * 8
typeobj.code = {
gdb.TYPE_CODE_TYPEDEF : TypeCodeTypedef,
gdb.TYPE_CODE_METHOD : TypeCodeFunction,
gdb.TYPE_CODE_VOID : TypeCodeVoid,
gdb.TYPE_CODE_FUNC : TypeCodeFunction,
gdb.TYPE_CODE_METHODPTR : TypeCodeFunction,
gdb.TYPE_CODE_MEMBERPTR : TypeCodeFunction,
gdb.TYPE_CODE_PTR : TypeCodePointer,
gdb.TYPE_CODE_REF : TypeCodeReference,
gdb.TYPE_CODE_BOOL : TypeCodeIntegral,
gdb.TYPE_CODE_CHAR : TypeCodeIntegral,
gdb.TYPE_CODE_INT : TypeCodeIntegral,
gdb.TYPE_CODE_FLT : TypeCodeFloat,
gdb.TYPE_CODE_ENUM : TypeCodeEnum,
gdb.TYPE_CODE_ARRAY : TypeCodeArray,
gdb.TYPE_CODE_STRUCT : TypeCodeStruct,
gdb.TYPE_CODE_UNION : TypeCodeStruct,
gdb.TYPE_CODE_COMPLEX : TypeCodeComplex,
gdb.TYPE_CODE_STRING : TypeCodeFortranString,
}[nativeType.code]
return typeobj
def fromNativeField(self, nativeField):
self.check(isinstance(nativeField, gdb.Field))
field = self.Field(self)
field.ltype = self.fromNativeType(nativeField.type)
field.parentType = self.fromNativeType(nativeField.parent_type)
field.name = nativeField.name
field.isBaseClass = nativeField.is_base_class
if hasattr(nativeField, 'bitpos'):
field.lbitpos = nativeField.bitpos
if hasattr(nativeField, 'bitsize') and nativeField.bitsize != 0:
field.lbitsize = nativeField.bitsize
else:
field.lbitsize = 8 * nativeField.type.sizeof
return field
def nativeValueDereference(self, nativeValue):
return self.nativeValueDownCast(nativeValue.dereference())
def nativeValueCast(self, nativeValue, nativeType):
try:
return self.fromNativeValue(nativeValue.cast(nativeType))
except:
return None
def nativeValueAddressOf(self, nativeValue):
return toInteger(nativeValue.address)
def nativeTypeDereference(self, nativeType):
return self.fromNativeType(nativeType.strip_typedefs().target())
def nativeTypeUnqualified(self, nativeType):
#warn("NATIVE TYPE: %s" % nativeType)
return self.fromNativeType(nativeType.unqualified())
def nativeTypePointer(self, nativeType):
return self.fromNativeType(nativeType.pointer())
def nativeTypeTarget(self, nativeType):
while nativeType.code == gdb.TYPE_CODE_TYPEDEF:
nativeType = nativeType.strip_typedefs().unqualified()
return self.fromNativeType(nativeType.target())
def nativeValueHasChildren(self, nativeValue):
nativeType = nativeValue.type
if nativeType.code == gdb.TYPE_CODE_ARRAY:
return True
if nativeType.code not in (gdb.TYPE_CODE_STRUCT, gdb.TYPE_CODE_UNION):
return False
nativeFields = nativeType.fields()
return len(nativeFields) > 0
def nativeTypeFirstBase(self, nativeType):
nativeFields = nativeType.fields()
if len(nativeFields) and nativeFields[0].is_base_class:
return self.fromNativeType(nativeFields[0].type)
def nativeTypeEnumDisplay(self, nativeType, intval):
try:
val = gdb.parse_and_eval("(%s)%d" % (nativeType, intval))
return "%s (%d)" % (val, intval)
except:
return "%d" % intval
def nativeTypeFields(self, nativeType):
#warn("TYPE: %s" % nativeType)
fields = []
if nativeType.code == gdb.TYPE_CODE_ARRAY:
# An array.
typeobj = nativeType.strip_typedefs()
innerType = typeobj.target()
for i in xrange(int(typeobj.sizeof / innerType.sizeof)):
field = self.Field(self)
field.ltype = self.fromNativeType(innerType)
field.parentType = self.fromNativeType(nativeType)
field.isBaseClass = False
field.lbitsize = innerType.sizeof
field.lbitpos = i * innerType.sizeof * 8
fields.append(field)
return fields
if not nativeType.code in (gdb.TYPE_CODE_STRUCT, gdb.TYPE_CODE_UNION):
return fields
nativeIndex = 0
baseIndex = 0
nativeFields = nativeType.fields()
#warn("NATIVE FIELDS: %s" % nativeFields)
for nativeField in nativeFields:
#warn("FIELD: %s" % nativeField)
#warn(" DIR: %s" % dir(nativeField))
#warn(" BITSIZE: %s" % nativeField.bitsize)
#warn(" ARTIFICIAL: %s" % nativeField.artificial)
#warn("FIELD NAME: %s" % nativeField.name)
#warn("FIELD TYPE: %s" % nativeField.type)
#self.check(isinstance(nativeField, gdb.Field))
field = self.Field(self)
field.ltype = self.fromNativeType(nativeField.type)
field.parentType = self.fromNativeType(nativeType)
field.name = nativeField.name
field.isBaseClass = nativeField.is_base_class
if hasattr(nativeField, 'bitpos'):
field.lbitpos = nativeField.bitpos
if hasattr(nativeField, 'bitsize') and nativeField.bitsize != 0:
field.lbitsize = nativeField.bitsize
else:
field.lbitsize = 8 * nativeField.type.sizeof
if nativeField.is_base_class:
# Field is base type. We cannot use nativeField.name as part
# of the iname as it might contain spaces and other
# strange characters.
field.name = nativeField.name
field.baseIndex = baseIndex
baseIndex += 1
else:
# Since GDB commit b5b08fb4 anonymous structs get also reported
# with a 'None' name.
if nativeField.name is None or len(nativeField.name) == 0:
# Something without a name.
# Anonymous union? We need a dummy name to distinguish
# multiple anonymous unions in the struct.
self.anonNumber += 1
field.name = "#%s" % self.anonNumber
else:
# Normal named field.
field.name = nativeField.name
field.nativeIndex = nativeIndex
fields.append(field)
nativeIndex += 1
#warn("FIELDS: %s" % fields)
return fields
def nativeTypeStripTypedefs(self, typeobj):
typeobj = typeobj.unqualified()
while typeobj.code == gdb.TYPE_CODE_TYPEDEF:
typeobj = typeobj.strip_typedefs().unqualified()
return self.fromNativeType(typeobj)
def nativeValueChildFromField(self, nativeValue, field):
#warn("EXTRACTING: %s FROM %s" % (field, nativeValue))
if field.nativeIndex is not None:
nativeField = nativeValue.type.fields()[field.nativeIndex]
#warn("NATIVE FIELD: %s" % nativeField)
if nativeField.is_base_class:
return self.fromNativeValue(nativeValue.cast(nativeField.type))
try:
# Fails with GDB 7.5.
return self.nativeValueDownCast(nativeValue[nativeField])
except:
# The generic handling is almost good enough, but does not
# downcast the produced values.
return None
if field.name is not None:
return self.nativeValueDownCast(nativeValue[field.name])
error("FIELD EXTARCTION FAILED: %s" % field)
return None
def listOfLocals(self):
frame = gdb.selected_frame()
try:
block = frame.block()
#warn("BLOCK: %s " % block)
except RuntimeError as error:
#warn("BLOCK IN FRAME NOT ACCESSIBLE: %s" % error)
return []
except:
warn("BLOCK NOT ACCESSIBLE FOR UNKNOWN REASONS")
return []
items = []
shadowed = {}
while True:
if block is None:
warn("UNEXPECTED 'None' BLOCK")
break
for symbol in block:
# Filter out labels etc.
if symbol.is_variable or symbol.is_argument:
name = symbol.print_name
if name == "__in_chrg" or name == "__PRETTY_FUNCTION__":
continue
# "NotImplementedError: Symbol type not yet supported in
# Python scripts."
#warn("SYMBOL %s (%s): " % (symbol, name))
if name in shadowed:
level = shadowed[name]
name1 = "%s@%s" % (name, level)
shadowed[name] = level + 1
else:
name1 = name
shadowed[name] = 1
#warn("SYMBOL %s (%s, %s)): " % (symbol, name, symbol.name))
try:
value = self.fromNativeDowncastableValue(frame.read_var(name, block))
#warn("READ 1: %s" % value)
value.name = name1
value.iname = "local." + name1
items.append(value)
continue
except:
pass
try:
#warn("READ 2: %s" % item.value)
value = self.fromNativeDowncastableValue(frame.read_var(name))
value.name = name1
value.iname = "local." + name1
items.append(value)
continue
except:
# RuntimeError: happens for
# void foo() { std::string s; std::wstring w; }
# ValueError: happens for (as of 2010/11/4)
# a local struct as found e.g. in
# gcc sources in gcc.c, int execute()
pass
try:
#warn("READ 3: %s %s" % (name, item.value))
#warn("ITEM 3: %s" % item.value)
value = self.fromNativeDowncastableValue(gdb.parse_and_eval(name))
items.append(value)
except:
# Can happen in inlined code (see last line of
# RowPainter::paintChars(): "RuntimeError:
# No symbol \"__val\" in current context.\n"
pass
# The outermost block in a function has the function member
# FIXME: check whether this is guaranteed.
if not block.function is None:
break
block = block.superblock
return items
# Hack to avoid QDate* dumper timeouts with GDB 7.4 on 32 bit
# due to misaligned %ebx in SSE calls (qstring.cpp:findChar)
# This seems to be fixed in 7.9 (or earlier)
def canCallLocale(self):
return self.ptrSize() == 8
def fetchVariables(self, args):
self.resetStats()
self.preping("locals")
self.prepare(args)
partialVariable = args.get("partialvar", "")
isPartial = len(partialVariable) > 0
(ok, res) = self.tryFetchInterpreterVariables(args)
if ok:
safePrint(res)
return
#
# Locals
#
self.output.append('data=[')
if isPartial:
parts = partialVariable.split('.')
name = parts[1]
try:
if parts[0] == 'local':
frame = gdb.selected_frame()
value = self.fromNativeDowncastableValue(frame.read_var(name))
else:
name = self.hexdecode(name)
value = self.fromNativeValue(gdb.parse_and_eval(name))
value.iname = parts[0] + '.' + name
value.name = name
variables = [value]
except RuntimeError as error:
warn("ERROR: %s" % error)
variables = []
except:
warn("ERROR")
variables = []
else:
variables = self.listOfLocals()
#warn("VARIABLES: %s" % variables)
self.ping("locals")
# Take care of the return value of the last function call.
if len(self.resultVarName) > 0:
try:
value = self.parseAndEvaluate(self.resultVarName)
value.name = self.resultVarName
value.iname = "return." + self.resultVarName
variables.append(value)
except:
# Don't bother. It's only supplementary information anyway.
pass
for value in variables:
with OutputSafer(self):
self.anonNumber = 0
if value.iname == "local.argv" and value.type.name == "char **":
self.putSpecialArgv(value)
else:
# A "normal" local variable or parameter.
with TopLevelItem(self, value.iname):
self.preping("all-" + value.iname)
self.put('iname="%s",' % value.iname)
self.put('name="%s",' % value.name)
self.putItem(value)
self.ping("all-" + value.iname)
self.preping("watches")
with OutputSafer(self):
self.handleWatches(args)
self.ping("watches")
self.output.append('],typeinfo=[')
for name in self.typesToReport.keys():
typeobj = self.typesToReport[name]
# Happens e.g. for '(anonymous namespace)::InsertDefOperation'
#if not typeobj is None:
# self.output.append('{name="%s",size="%s"}'
# % (self.hexencode(name), typeobj.sizeof))
self.output.append(']')
self.typesToReport = {}
if self.forceQtNamespace:
self.qtNamepaceToReport = self.qtNamespace()
if self.qtNamespaceToReport:
self.output.append(',qtnamespace="%s"' % self.qtNamespaceToReport)
self.qtNamespaceToReport = None
self.output.append(',partial="%d"' % isPartial)
self.preping('safePrint')
safePrint(''.join(self.output))
self.ping('safePrint')
safePrint('"%s"' % str(self.dumpStats()))
def parseAndEvaluate(self, exp):
#warn("EVALUATE '%s'" % exp)
try:
val = gdb.parse_and_eval(exp)
except RuntimeError as error:
warn("Cannot evaluate '%s': %s" % (exp, error))
if self.passExceptions:
raise error
else:
return None
return self.fromNativeValue(val)
def nativeValueAsBytes(self, nativeValue, size):
# Assume it's a (backend specific) Value.
if nativeValue.address:
return self.readRawMemory(nativeValue.address, size)
# No address. Possibly the result of an inferior call.
# Note: Only a cast to unsigned char[sizeof(origtype)] succeeds
# in this situation in gdb.
chars = self.lookupNativeType("unsigned char")
y = nativeValue.cast(chars.array(0, int(nativeValue.type.sizeof - 1)))
buf = bytearray(struct.pack('x' * size))
for i in range(size):
buf[i] = int(y[i])
return bytes(buf)
def callHelper(self, rettype, value, function, args):
# args is a tuple.
arg = ""
for i in range(len(args)):
if i:
arg += ','
a = args[i]
if (':' in a) and not ("'" in a):
arg = "'%s'" % a
else:
arg += a
#warn("CALL: %s -> %s(%s)" % (value, function, arg))
typeName = self.stripClassTag(value.type.name)
if typeName.find(":") >= 0:
typeName = "'" + typeName + "'"
# 'class' is needed, see http://sourceware.org/bugzilla/show_bug.cgi?id=11912
#exp = "((class %s*)%s)->%s(%s)" % (typeName, value.address, function, arg)
addr = value.address()
if not addr:
addr = self.pokeValue(value)
#warn("PTR: %s -> %s(%s)" % (value, function, addr))
exp = "((%s*)0x%x)->%s(%s)" % (typeName, addr, function, arg)
#warn("CALL: %s" % exp)
result = gdb.parse_and_eval(exp)
#warn(" -> %s" % result)
if not value.address():
gdb.parse_and_eval("free((void*)0x%x)" % addr)
return self.fromNativeValue(result)
def makeExpression(self, value):
typename = "::" + self.stripClassTag(value.type.name)
#warn(" TYPE: %s" % typename)
exp = "(*(%s*)(0x%x))" % (typename, value.address())
#warn(" EXP: %s" % exp)
return exp
def makeStdString(init):
# Works only for small allocators, but they are usually empty.
gdb.execute("set $d=(std::string*)calloc(sizeof(std::string), 2)");
gdb.execute("call($d->basic_string(\"" + init +
"\",*(std::allocator<char>*)(1+$d)))")
value = gdb.parse_and_eval("$d").dereference()
#warn(" TYPE: %s" % value.type)
#warn(" ADDR: %s" % value.address)
#warn(" VALUE: %s" % value)
return value
def nativeTypeTemplateArgument(self, nativeType, position, numeric):
#warn("NATIVE TYPE: %s" % dir(nativeType))
arg = nativeType.template_argument(position)
if numeric:
return int(str(arg))
return self.fromNativeType(arg)
def intType(self):
self.cachedIntType = self.lookupType('int')
self.intType = lambda: self.cachedIntType
return self.cachedIntType
def charType(self):
return self.lookupType('char')
def sizetType(self):
return self.lookupType('size_t')
def charPtrType(self):
return self.lookupType('char*')
def voidPtrType(self):
return self.lookupType('void*')
def ptrSize(self):
self.cachedPtrSize = self.lookupNativeType('void*').sizeof
self.ptrSize = lambda: self.cachedPtrSize
return self.cachedPtrSize
def pokeValue(self, value):
# Allocates inferior memory and copies the contents of value.
# Returns a pointer to the copy.
# Avoid malloc symbol clash with QVector
size = value.type.size()
data = value.data()
h = self.hexencode(data)
#warn("DATA: %s" % h
string = ''.join("\\x" + h[2*i:2*i+2] for i in range(size))
exp = '(%s*)memcpy(calloc(%d, 1), "%s", %d)' % (value.type.name, size, string, size)
#warn("EXP: %s" % exp)
res = gdb.parse_and_eval(exp)
#warn("RES: %s" % res)
return toInteger(res)
def setValue(self, address, typename, value):
cmd = "set {%s}%s=%s" % (typename, address, value)
gdb.execute(cmd)
def setValues(self, address, typename, values):
cmd = "set {%s[%s]}%s={%s}" \
% (typename, len(values), address, ','.join(map(str, values)))
gdb.execute(cmd)
def selectedInferior(self):
try:
# gdb.Inferior is new in gdb 7.2
self.cachedInferior = gdb.selected_inferior()
except:
# Pre gdb 7.4. Right now we don't have more than one inferior anyway.
self.cachedInferior = gdb.inferiors()[0]
# Memoize result.
self.selectedInferior = lambda: self.cachedInferior
return self.cachedInferior
def readRawMemory(self, address, size):
return self.selectedInferior().read_memory(address, size)
def findStaticMetaObject(self, typename):
symbolName = typename + "::staticMetaObject"
symbol = gdb.lookup_global_symbol(symbolName, gdb.SYMBOL_VAR_DOMAIN)
if not symbol:
return 0
try:
# Older GDB ~7.4 don't have gdb.Symbol.value()
return toInteger(symbol.value().address)
except:
pass
address = gdb.parse_and_eval("&'%s'" % symbolName)
return toInteger(address)
def put(self, value):
self.output.append(value)
def childRange(self):
if self.currentMaxNumChild is None:
return xrange(0, toInteger(self.currentNumChild))
return xrange(min(toInteger(self.currentMaxNumChild), toInteger(self.currentNumChild)))
def isArmArchitecture(self):
return 'arm' in gdb.TARGET_CONFIG.lower()
def isQnxTarget(self):
return 'qnx' in gdb.TARGET_CONFIG.lower()
def isWindowsTarget(self):
# We get i686-w64-mingw32
return 'mingw' in gdb.TARGET_CONFIG.lower()
def qtVersionString(self):
try:
return str(gdb.lookup_symbol("qVersion")[0].value()())
except:
pass
try:
ns = self.qtNamespace()
return str(gdb.parse_and_eval("((const char*(*)())'%sqVersion')()" % ns))
except:
pass
return None
def qtVersion(self):
try:
# Only available with Qt 5.3+
qtversion = int(str(gdb.parse_and_eval("((void**)&qtHookData)[2]")), 16)
self.qtVersion = lambda: qtversion
return qtversion
except:
pass
try:
version = self.qtVersionString()
(major, minor, patch) = version[version.find('"')+1:version.rfind('"')].split('.')
qtversion = 0x10000 * int(major) + 0x100 * int(minor) + int(patch)
self.qtVersion = lambda: qtversion
return qtversion
except:
# Use fallback until we have a better answer.
return self.fallbackQtVersion
def qtTypeInfoVersion(self):
try:
# Only available with Qt 5.3+
hookVersion = int(str(gdb.parse_and_eval("((void**)&qtHookData)[0]")), 16)
tiVersion = int(str(gdb.parse_and_eval("((void**)&qtHookData)[6]")), 16)
#warn("HOOK: %s TI: %s" % (hookVersion, tiVersion))
if hookVersion >= 3:
self.qtTypeInfoVersion = lambda: tiVersion
return tiVersion
except:
pass
return None
def isQt3Support(self):
if self.qtVersion() >= 0x050000:
return False
else:
try:
# This will fail on Qt 4 without Qt 3 support
gdb.execute("ptype QChar::null", to_string=True)
self.cachedIsQt3Suport = True
except:
self.cachedIsQt3Suport = False
# Memoize good results.
self.isQt3Support = lambda: self.cachedIsQt3Suport
return self.cachedIsQt3Suport
def readCString(self, base):
inferior = self.selectedInferior()
mem = ""
while True:
char = inferior.read_memory(base, 1)[0]
if not char:
break
mem += char
base += 1
return mem
def createSpecialBreakpoints(self, args):
self.specialBreakpoints = []
def newSpecial(spec):
class SpecialBreakpoint(gdb.Breakpoint):
def __init__(self, spec):
super(SpecialBreakpoint, self).\
__init__(spec, gdb.BP_BREAKPOINT, internal=True)
self.spec = spec
def stop(self):
print("Breakpoint on '%s' hit." % self.spec)
return True
return SpecialBreakpoint(spec)
# FIXME: ns is accessed too early. gdb.Breakpoint() has no
# 'rbreak' replacement, and breakpoints created with
# 'gdb.execute("rbreak...") cannot be made invisible.
# So let's ignore the existing of namespaced builds for this
# fringe feature here for now.
ns = self.qtNamespace()
if args.get('breakonabort', 0):
self.specialBreakpoints.append(newSpecial("abort"))
if args.get('breakonwarning', 0):
self.specialBreakpoints.append(newSpecial(ns + "qWarning"))
self.specialBreakpoints.append(newSpecial(ns + "QMessageLogger::warning"))
if args.get('breakonfatal', 0):
self.specialBreakpoints.append(newSpecial(ns + "qFatal"))
self.specialBreakpoints.append(newSpecial(ns + "QMessageLogger::fatal"))
#def threadname(self, maximalStackDepth, objectPrivateType):
# e = gdb.selected_frame()
# out = ""
# ns = self.qtNamespace()
# while True:
# maximalStackDepth -= 1
# if maximalStackDepth < 0:
# break
# e = e.older()
# if e == None or e.name() == None:
# break
# if e.name() == ns + "QThreadPrivate::start" \
# or e.name() == "_ZN14QThreadPrivate5startEPv@4":
# try:
# thrptr = e.read_var("thr").dereference()
# d_ptr = thrptr["d_ptr"]["d"].cast(objectPrivateType).dereference()
# try:
# objectName = d_ptr["objectName"]
# except: # Qt 5
# p = d_ptr["extraData"]
# if not self.isNull(p):
# objectName = p.dereference()["objectName"]
# if not objectName is None:
# (data, size, alloc) = self.stringData(objectName)
# if size > 0:
# s = self.readMemory(data, 2 * size)
#
# thread = gdb.selected_thread()
# inner = '{valueencoded="uf16:2:0",id="'
# inner += str(thread.num) + '",value="'
# inner += s
# #inner += self.encodeString(objectName)
# inner += '"},'
#
# out += inner
# except:
# pass
# return out
def threadnames(self, maximalStackDepth):
# FIXME: This needs a proper implementation for MinGW, and only there.
# Linux, Mac and QNX mirror the objectName() to the underlying threads,
# so we get the names already as part of the -thread-info output.
return '[]'
#out = '['
#oldthread = gdb.selected_thread()
#if oldthread:
# try:
# objectPrivateType = gdb.lookup_type(ns + "QObjectPrivate").pointer()
# inferior = self.selectedInferior()
# for thread in inferior.threads():
# thread.switch()
# out += self.threadname(maximalStackDepth, objectPrivateType)
# except:
# pass
# oldthread.switch()
#return out + ']'
def importPlainDumper(self, printer):
name = printer.name.replace("::", "__")
self.qqDumpers[name] = PlainDumper(printer)
self.qqFormats[name] = ""
def importPlainDumpers(self):
for obj in gdb.objfiles():
for printers in obj.pretty_printers + gdb.pretty_printers:
for printer in printers.subprinters:
self.importPlainDumper(printer)
def qtNamespace(self):
if not self.currentQtNamespaceGuess is None:
return self.currentQtNamespaceGuess
# This only works when called from a valid frame.
try:
cand = "QArrayData::shared_null"
symbol = gdb.lookup_symbol(cand)[0]
if symbol:
ns = symbol.name[:-len(cand)]
self.qtNamespaceToReport = ns
self.qtNamespace = lambda: ns
return ns
except:
pass
try:
# This is Qt, but not 5.x.
cand = "QByteArray::shared_null"
symbol = gdb.lookup_symbol(cand)[0]
if symbol:
ns = symbol.name[:-len(cand)]
self.qtNamespaceToReport = ns
self.qtNamespace = lambda: ns
self.fallbackQtVersion = 0x40800
return ns
except:
pass
try:
# Last fall backs.
s = gdb.execute("ptype QByteArray", to_string=True)
if s.find("QMemArray") >= 0:
# Qt 3.
self.qtNamespaceToReport = ""
self.qtNamespace = lambda: ""
self.qtVersion = lambda: 0x30308
self.fallbackQtVersion = 0x30308
return ""
# Seemingly needed with Debian's GDB 7.4.1
pos1 = s.find("class")
pos2 = s.find("QByteArray")
if pos1 > -1 and pos2 > -1:
ns = s[s.find("class") + 6:s.find("QByteArray")]
self.qtNamespaceToReport = ns
self.qtNamespace = lambda: ns
return ns
except:
pass
self.currentQtNamespaceGuess = ""
return ""
def assignValue(self, args):
typeName = self.hexdecode(args['type'])
expr = self.hexdecode(args['expr'])
value = self.hexdecode(args['value'])
simpleType = int(args['simpleType'])
ns = self.qtNamespace()
if typeName.startswith(ns):
typeName = typeName[len(ns):]
typeName = typeName.replace("::", "__")
pos = typeName.find('<')
if pos != -1:
typeName = typeName[0:pos]
if typeName in self.qqEditable and not simpleType:
#self.qqEditable[typeName](self, expr, value)
expr = gdb.parse_and_eval(expr)
self.qqEditable[typeName](self, expr, value)
else:
cmd = "set variable (%s)=%s" % (expr, value)
gdb.execute(cmd)
def hasVTable(self, typeobj):
fields = typeobj.fields()
if len(fields) == 0:
return False
if fields[0].isBaseClass:
return hasVTable(fields[0].type)
return str(fields[0].type) == "int (**)(void)"
def dynamicTypeName(self, value):
if self.hasVTable(value.type):
#vtbl = str(gdb.parse_and_eval("{int(*)(int)}%s" % int(value.address)))
try:
# Fails on 7.1 due to the missing to_string.
vtbl = gdb.execute("info symbol {int*}%s" % int(value.address),
to_string = True)
pos1 = vtbl.find("vtable ")
if pos1 != -1:
pos1 += 11
pos2 = vtbl.find(" +", pos1)
if pos2 != -1:
return vtbl[pos1 : pos2]
except:
pass
return str(value.type)
def nativeValueDownCast(self, nativeValue):
try:
return self.fromNativeValue(nativeValue.cast(nativeValue.dynamic_type))
except:
return self.fromNativeValue(nativeValue)
def expensiveDowncast(self, value):
try:
return value.cast(value.dynamic_type)
except:
pass
try:
return value.cast(self.lookupType(self.dynamicTypeName(value)))
except:
pass
return value
def enumExpression(self, enumType, enumValue):
return self.qtNamespace() + "Qt::" + enumValue
def lookupType(self, typeName):
return self.fromNativeType(self.lookupNativeType(typeName))
def lookupNativeType(self, typeName):
nativeType = self.lookupNativeTypeHelper(typeName)
if not nativeType is None:
self.check(isinstance(nativeType, gdb.Type))
return nativeType
def lookupNativeTypeHelper(self, typeName):
typeobj = self.typeCache.get(typeName)
#warn("LOOKUP 1: %s -> %s" % (typeName, typeobj))
if not typeobj is None:
return typeobj
if typeName == "void":
typeobj = gdb.lookup_type(typeName)
self.typeCache[typeName] = typeobj
self.typesToReport[typeName] = typeobj
return typeobj
#try:
# typeobj = gdb.parse_and_eval("{%s}&main" % typeName).typeobj
# if not typeobj is None:
# self.typeCache[typeName] = typeobj
# self.typesToReport[typeName] = typeobj
# return typeobj
#except:
# pass
# See http://sourceware.org/bugzilla/show_bug.cgi?id=13269
# gcc produces "{anonymous}", gdb "(anonymous namespace)"
# "<unnamed>" has been seen too. The only thing gdb
# understands when reading things back is "(anonymous namespace)"
if typeName.find("{anonymous}") != -1:
ts = typeName
ts = ts.replace("{anonymous}", "(anonymous namespace)")
typeobj = self.lookupNativeType(ts)
if not typeobj is None:
self.typeCache[typeName] = typeobj
self.typesToReport[typeName] = typeobj
return typeobj
#warn(" RESULT FOR 7.2: '%s': %s" % (typeName, typeobj))
# This part should only trigger for
# gdb 7.1 for types with namespace separators.
# And anonymous namespaces.
ts = typeName
while True:
#warn("TS: '%s'" % ts)
if ts.startswith("class "):
ts = ts[6:]
elif ts.startswith("struct "):
ts = ts[7:]
elif ts.startswith("const "):
ts = ts[6:]
elif ts.startswith("volatile "):
ts = ts[9:]
elif ts.startswith("enum "):
ts = ts[5:]
elif ts.endswith(" const"):
ts = ts[:-6]
elif ts.endswith(" volatile"):
ts = ts[:-9]
elif ts.endswith("*const"):
ts = ts[:-5]
elif ts.endswith("*volatile"):
ts = ts[:-8]
else:
break
if ts.endswith('*'):
typeobj = self.lookupNativeType(ts[0:-1])
if not typeobj is None:
typeobj = typeobj.pointer()
self.typeCache[typeName] = typeobj
self.typesToReport[typeName] = typeobj
return typeobj
try:
#warn("LOOKING UP 1 '%s'" % ts)
typeobj = gdb.lookup_type(ts)
except RuntimeError as error:
#warn("LOOKING UP 2 '%s' ERROR %s" % (ts, error))
# See http://sourceware.org/bugzilla/show_bug.cgi?id=11912
exp = "(class '%s'*)0" % ts
try:
typeobj = self.parse_and_eval(exp).type.target()
#warn("LOOKING UP 3 '%s'" % typeobj)
except:
# Can throw "RuntimeError: No type named class Foo."
pass
except:
#warn("LOOKING UP '%s' FAILED" % ts)
pass
if not typeobj is None:
#warn("CACHING: %s" % typeobj)
self.typeCache[typeName] = typeobj
self.typesToReport[typeName] = typeobj
# This could still be None as gdb.lookup_type("char[3]") generates
# "RuntimeError: No type named char[3]"
#self.typeCache[typeName] = typeobj
#self.typesToReport[typeName] = typeobj
return typeobj
def doContinue(self):
gdb.execute('continue')
def fetchStack(self, args):
def fromNativePath(str):
return str.replace('\\', '/')
limit = int(args['limit'])
if limit <= 0:
limit = 10000
self.prepare(args)
self.output = []
frame = gdb.newest_frame()
i = 0
self.currentCallContext = None
while i < limit and frame:
with OutputSafer(self):
name = frame.name()
functionName = "??" if name is None else name
fileName = ""
objfile = ""
symtab = ""
pc = frame.pc()
sal = frame.find_sal()
line = -1
if sal:
line = sal.line
symtab = sal.symtab
if not symtab is None:
objfile = fromNativePath(symtab.objfile.filename)
fullname = symtab.fullname()
if fullname is None:
fileName = ""
else:
fileName = fromNativePath(fullname)
if self.nativeMixed and functionName == "qt_qmlDebugMessageAvailable":
interpreterStack = self.extractInterpreterStack()
#print("EXTRACTED INTEPRETER STACK: %s" % interpreterStack)
for interpreterFrame in interpreterStack.get('frames', []):
function = interpreterFrame.get('function', '')
fileName = interpreterFrame.get('file', '')
language = interpreterFrame.get('language', '')
lineNumber = interpreterFrame.get('line', 0)
context = interpreterFrame.get('context', 0)
self.put(('frame={function="%s",file="%s",'
'line="%s",language="%s",context="%s"}')
% (function, fileName, lineNumber, language, context))
if False and self.isInternalInterpreterFrame(functionName):
frame = frame.older()
self.put(('frame={address="0x%x",function="%s",'
'file="%s",line="%s",'
'module="%s",language="c",usable="0"}') %
(pc, functionName, fileName, line, objfile))
i += 1
frame = frame.older()
continue
self.put(('frame={level="%s",address="0x%x",function="%s",'
'file="%s",line="%s",module="%s",language="c"}') %
(i, pc, functionName, fileName, line, objfile))
frame = frame.older()
i += 1
safePrint('frames=[' + ','.join(self.output) + ']')
def createResolvePendingBreakpointsHookBreakpoint(self, args):
class Resolver(gdb.Breakpoint):
def __init__(self, dumper, args):
self.dumper = dumper
self.args = args
spec = "qt_qmlDebugConnectorOpen"
super(Resolver, self).\
__init__(spec, gdb.BP_BREAKPOINT, internal=True, temporary=False)
def stop(self):
self.dumper.resolvePendingInterpreterBreakpoint(args)
self.enabled = False
return False
self.interpreterBreakpointResolvers.append(Resolver(self, args))
def exitGdb(self, _):
gdb.execute("quit")
def loadDumpers(self, args):
print(self.setupDumpers())
def profile1(self, args):
"""Internal profiling"""
import tempfile
import cProfile
tempDir = tempfile.gettempdir() + "/bbprof"
cProfile.run('theDumper.fetchVariables(%s)' % args, tempDir)
import pstats
pstats.Stats(tempDir).sort_stats('time').print_stats()
def profile2(self, args):
import timeit
print(timeit.repeat('theDumper.fetchVariables(%s)' % args,
'from __main__ import theDumper', number=10))
class CliDumper(Dumper):
def __init__(self):
Dumper.__init__(self)
self.childrenPrefix = '['
self.chidrenSuffix = '] '
self.indent = 0
self.isCli = True
def reportDumpers(self, msg):
return msg
def put(self, line):
if self.output.endswith('\n'):
self.output = self.output[0:-1]
self.output += line
def putNumChild(self, numchild):
pass
def putOriginalAddress(self, value):
pass
def fetchVariables(self, args):
args['fancy'] = 1
args['passexception'] = 1
args['autoderef'] = 1
args['qobjectnames'] = 1
name = args['varlist']
self.prepare(args)
self.output = name + ' = '
frame = gdb.selected_frame()
value = frame.read_var(name)
with TopLevelItem(self, name):
self.putItem(value)
return self.output
# Global instance.
#if gdb.parameter('height') is None:
theDumper = Dumper()
#else:
# import codecs
# theDumper = CliDumper()
######################################################################
#
# ThreadNames Command
#
#######################################################################
def threadnames(arg):
return theDumper.threadnames(int(arg))
registerCommand("threadnames", threadnames)
#######################################################################
#
# Native Mixed
#
#######################################################################
class InterpreterMessageBreakpoint(gdb.Breakpoint):
def __init__(self):
spec = "qt_qmlDebugMessageAvailable"
super(InterpreterMessageBreakpoint, self).\
__init__(spec, gdb.BP_BREAKPOINT, internal=True)
def stop(self):
print("Interpreter event received.")
return theDumper.handleInterpreterMessage()
InterpreterMessageBreakpoint()