Files
qt-creator/share/qtcreator/debugger/lldbbridge.py
David Schulz e17c96cf11 Dumper: move global enums and functions to utils
Change-Id: I11f5f0d970cd23a5fd907d33daa3cb8a441d0c58
Reviewed-by: hjk <hjk@qt.io>
2020-02-24 07:47:40 +00:00

2301 lines
96 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.
#
############################################################################
import inspect
import os
import platform
import re
import sys
import threading
import time
import lldb
import utils
from utils import DebuggerStartMode, BreakpointType, TypeCode
from contextlib import contextmanager
sys.path.insert(1, os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))))
# Simplify development of this module by reloading deps
if 'dumper' in sys.modules:
reload(sys.modules['dumper'])
from dumper import *
#######################################################################
#
# Helpers
#
#######################################################################
qqWatchpointOffset = 10000
def fileNameAsString(file):
return str(file) if file.IsValid() else ''
def check(exp):
if not exp:
raise RuntimeError('Check failed')
class Dumper(DumperBase):
def __init__(self, debugger = None):
DumperBase.__init__(self)
lldb.theDumper = self
self.isLldb = True
self.typeCache = {}
self.outputLock = threading.Lock()
if debugger:
# Re-use existing debugger
self.debugger = debugger
else:
self.debugger = lldb.SBDebugger.Create()
#self.debugger.SetLoggingCallback(loggingCallback)
#def loggingCallback(args):
# s = args.strip()
# s = s.replace('"', "'")
# sys.stdout.write('log="%s"@\n' % s)
#Same as: self.debugger.HandleCommand('log enable lldb dyld step')
#self.debugger.EnableLog('lldb', ['dyld', 'step', 'process', 'state',
# 'thread', 'events',
# 'communication', 'unwind', 'commands'])
#self.debugger.EnableLog('lldb', ['all'])
self.debugger.Initialize()
self.debugger.SetAsync(True)
self.debugger.HandleCommand('settings set auto-confirm on')
# FIXME: warn('DISABLING DEFAULT FORMATTERS')
# It doesn't work at all with 179.5 and we have some bad
# interaction in 300
# if not hasattr(lldb.SBType, 'GetCanonicalType'): # 'Test' for 179.5
#self.debugger.HandleCommand('type category delete gnu-libstdc++')
#self.debugger.HandleCommand('type category delete libcxx')
#self.debugger.HandleCommand('type category delete default')
self.debugger.DeleteCategory('gnu-libstdc++')
self.debugger.DeleteCategory('libcxx')
self.debugger.DeleteCategory('default')
self.debugger.DeleteCategory('cplusplus')
#for i in range(self.debugger.GetNumCategories()):
# self.debugger.GetCategoryAtIndex(i).SetEnabled(False)
self.process = None
self.target = None
self.eventState = lldb.eStateInvalid
self.runEngineAttempted = False
self.executable_ = None
self.startMode_ = None
self.processArgs_ = None
self.attachPid_ = None
self.dyldImageSuffix = None
self.dyldLibraryPath = None
self.dyldFrameworkPath = None
self.isShuttingDown_ = False
self.isInterrupting_ = False
self.interpreterBreakpointResolvers = []
self.report('lldbversion=\"%s\"' % lldb.SBDebugger.GetVersionString())
self.reportState('enginesetupok')
def fromNativeFrameValue(self, nativeValue):
return self.fromNativeValue(nativeValue)
def fromNativeValue(self, nativeValue):
self.check(isinstance(nativeValue, lldb.SBValue))
nativeType = nativeValue.GetType()
typeName = nativeType.GetName()
code = nativeType.GetTypeClass()
# Display the result of GetSummary() for Core Foundation string
# and string-like types.
summary = None
if self.useFancy:
if (typeName.startswith('CF')
or typeName.startswith('__CF')
or typeName.startswith('NS')
or typeName.startswith('__NSCF')):
if code == lldb.eTypeClassPointer:
summary = nativeValue.Dereference().GetSummary()
elif code == lldb.eTypeClassReference:
summary = nativeValue.Dereference().GetSummary()
else:
summary = nativeValue.GetSummary()
nativeValue.SetPreferSyntheticValue(False)
if code == lldb.eTypeClassReference:
nativeTargetType = nativeType.GetDereferencedType()
if not nativeTargetType.IsPointerType():
nativeTargetType = nativeTargetType.GetUnqualifiedType()
targetType = self.fromNativeType(nativeTargetType)
val = self.createReferenceValue(nativeValue.GetValueAsUnsigned(), targetType)
val.laddress = nativeValue.AddressOf().GetValueAsUnsigned()
#DumperBase.warn('CREATED REF: %s' % val)
elif code == lldb.eTypeClassPointer:
nativeTargetType = nativeType.GetPointeeType()
if not nativeTargetType.IsPointerType():
nativeTargetType = nativeTargetType.GetUnqualifiedType()
targetType = self.fromNativeType(nativeTargetType)
val = self.createPointerValue(nativeValue.GetValueAsUnsigned(), targetType)
#DumperBase.warn('CREATED PTR 1: %s' % val)
val.laddress = nativeValue.AddressOf().GetValueAsUnsigned()
#DumperBase.warn('CREATED PTR 2: %s' % val)
elif code == lldb.eTypeClassTypedef:
nativeTargetType = nativeType.GetUnqualifiedType()
if hasattr(nativeTargetType, 'GetCanonicalType'):
nativeTargetType = nativeTargetType.GetCanonicalType()
val = self.fromNativeValue(nativeValue.Cast(nativeTargetType))
val.type = self.fromNativeType(nativeType)
#DumperBase.warn('CREATED TYPEDEF: %s' % val)
else:
val = self.Value(self)
address = nativeValue.GetLoadAddress()
if not address is None:
val.laddress = address
if True:
data = nativeValue.GetData()
error = lldb.SBError()
size = nativeValue.GetType().GetByteSize()
if size > 1:
# 0 happens regularly e.g. for cross-shared-object types.
# 1 happens on Linux e.g. for QObject uses outside of QtCore.
try:
val.ldata = data.ReadRawData(error, 0, size)
except:
pass
val.type = self.fromNativeType(nativeType)
if code == lldb.eTypeClassEnumeration:
intval = nativeValue.GetValueAsSigned()
if hasattr(nativeType, 'get_enum_members_array'):
for enumMember in nativeType.get_enum_members_array():
# Even when asking for signed we get unsigned with LLDB 3.8.
diff = enumMember.GetValueAsSigned() - intval
mask = (1 << nativeType.GetByteSize() * 8) - 1
if diff & mask == 0:
path = nativeType.GetName().split('::')
path[-1] = enumMember.GetName()
val.ldisplay = '%s (%d)' % ('::'.join(path), intval)
val.ldisplay = '%d' % intval
elif code in (lldb.eTypeClassComplexInteger, lldb.eTypeClassComplexFloat):
val.ldisplay = str(nativeValue.GetValue())
#elif code == lldb.eTypeClassArray:
# if hasattr(nativeType, 'GetArrayElementType'): # New in 3.8(?) / 350.x
# val.type.ltarget = self.fromNativeType(nativeType.GetArrayElementType())
# else:
# fields = nativeType.get_fields_array()
# if len(fields):
# val.type.ltarget = self.fromNativeType(fields[0])
#elif code == lldb.eTypeClassVector:
# val.type.ltarget = self.fromNativeType(nativeType.GetVectorElementType())
val.summary = summary
val.lIsInScope = nativeValue.IsInScope()
val.name = nativeValue.GetName()
return val
def nativeStructAlignment(self, nativeType):
def handleItem(nativeFieldType, align):
a = self.fromNativeType(nativeFieldType).alignment()
return a if a > align else align
align = 1
for i in range(nativeType.GetNumberOfDirectBaseClasses()):
base = nativeType.GetDirectBaseClassAtIndex(i)
align = handleItem(base.GetType(), align)
for i in range(nativeType.GetNumberOfFields()):
child = nativeType.GetFieldAtIndex(i)
align = handleItem(child.GetType(), align)
return align
def listMembers(self, value, nativeType):
#DumperBase.warn("ADDR: 0x%x" % self.fakeAddress)
fakeAddress = self.fakeAddress if value.laddress is None else value.laddress
sbaddr = lldb.SBAddress(fakeAddress, self.target)
fakeValue = self.target.CreateValueFromAddress('x', sbaddr, nativeType)
fakeValue.SetPreferSyntheticValue(False)
baseNames = {}
for i in range(nativeType.GetNumberOfDirectBaseClasses()):
base = nativeType.GetDirectBaseClassAtIndex(i)
baseNames[base.GetName()] = i
fieldBits = {}
for f in nativeType.get_fields_array():
bitsize = f.GetBitfieldSizeInBits()
if bitsize == 0:
bitsize = f.GetType().GetByteSize() * 8
bitpos = f.GetOffsetInBits()
fieldBits[f.name] = (bitsize, bitpos, f.IsBitfield())
# Normal members and non-empty base classes.
anonNumber = 0
for i in range(fakeValue.GetNumChildren()):
nativeField = fakeValue.GetChildAtIndex(i)
nativeField.SetPreferSyntheticValue(False)
fieldName = nativeField.GetName()
nativeFieldType = nativeField.GetType()
if fieldName in fieldBits:
(fieldBitsize, fieldBitpos, isBitfield) = fieldBits[fieldName]
else:
fieldBitsize = nativeFieldType.GetByteSize() * 8
fieldBitpos = None
isBitfield = False
if isBitfield: # Bit fields
fieldType = self.createBitfieldType(
self.createType(self.typeName(nativeFieldType)), fieldBitsize)
yield self.Field(self, name=fieldName, type=fieldType,
bitsize=fieldBitsize, bitpos=fieldBitpos)
elif fieldName is None: # Anon members
anonNumber += 1
fieldName = '#%s' % anonNumber
fakeMember = fakeValue.GetChildAtIndex(i)
fakeMemberAddress = fakeMember.GetLoadAddress()
offset = fakeMemberAddress - fakeAddress
yield self.Field(self, name=fieldName, type=self.fromNativeType(nativeFieldType),
bitsize=fieldBitsize, bitpos=8*offset)
elif fieldName in baseNames: # Simple bases
member = self.fromNativeValue(fakeValue.GetChildAtIndex(i))
member.isBaseClass = True
yield member
else: # Normal named members
member = self.fromNativeValue(fakeValue.GetChildAtIndex(i))
member.name = nativeField.GetName()
yield member
# Empty bases are not covered above.
for i in range(nativeType.GetNumberOfDirectBaseClasses()):
fieldObj = nativeType.GetDirectBaseClassAtIndex(i)
fieldType = fieldObj.GetType()
if fieldType.GetNumberOfFields() == 0:
if fieldType.GetNumberOfDirectBaseClasses() == 0:
member = self.Value(self)
fieldName = fieldObj.GetName()
member.type = self.fromNativeType(fieldType)
member.name = fieldName
member.fields = []
if False:
# This would be correct if we came here only for
# truly empty base classes. Alas, we don't, see below.
member.ldata = bytes()
member.lbitsize = fieldType.GetByteSize() * 8
else:
# This is a hack. LLDB 3.8 reports declared but not defined
# types as having no fields and(!) size == 1. At least
# for the common case of a single base class we can
# fake the contents by using the whole derived object's
# data as base class data.
data = fakeValue.GetData()
size = nativeType.GetByteSize()
member.lbitsize = size * 8
error = lldb.SBError()
member.laddress = value.laddress
member.ldata = data.ReadRawData(error, 0, size)
member.isBaseClass = True
member.ltype = self.fromNativeType(fieldType)
member.name = fieldName
yield member
def ptrSize(self):
result = self.target.GetAddressByteSize()
self.ptrSize = lambda: result
return result
def fromNativeType(self, nativeType):
self.check(isinstance(nativeType, lldb.SBType))
code = nativeType.GetTypeClass()
# eTypeClassInvalid = (0u),
# eTypeClassArray = (1u << 0),
# eTypeClassBlockPointer = (1u << 1),
# eTypeClassBuiltin = (1u << 2),
# eTypeClassClass = (1u << 3),
# eTypeClassComplexFloat = (1u << 4),
# eTypeClassComplexInteger = (1u << 5),
# eTypeClassEnumeration = (1u << 6),
# eTypeClassFunction = (1u << 7),
# eTypeClassMemberPointer = (1u << 8),
# eTypeClassObjCObject = (1u << 9),
# eTypeClassObjCInterface = (1u << 10),
# eTypeClassObjCObjectPointer = (1u << 11),
# eTypeClassPointer = (1u << 12),
# eTypeClassReference = (1u << 13),
# eTypeClassStruct = (1u << 14),
# eTypeClassTypedef = (1u << 15),
# eTypeClassUnion = (1u << 16),
# eTypeClassVector = (1u << 17),
# // Define the last type class as the MSBit of a 32 bit value
# eTypeClassOther = (1u << 31),
# // Define a mask that can be used for any type when finding types
# eTypeClassAny = (0xffffffffu)
#DumperBase.warn('CURRENT: %s' % self.typeData.keys())
#DumperBase.warn('FROM NATIVE TYPE: %s' % nativeType.GetName())
if code == lldb.eTypeClassInvalid:
return None
if code == lldb.eTypeClassBuiltin:
nativeType = nativeType.GetUnqualifiedType()
if code == lldb.eTypeClassPointer:
#DumperBase.warn('PTR')
nativeTargetType = nativeType.GetPointeeType()
if not nativeTargetType.IsPointerType():
nativeTargetType = nativeTargetType.GetUnqualifiedType()
#DumperBase.warn('PTR: %s' % nativeTargetType.name)
return self.createPointerType(self.fromNativeType(nativeTargetType))
if code == lldb.eTypeClassReference:
#DumperBase.warn('REF')
nativeTargetType = nativeType.GetDereferencedType()
if not nativeTargetType.IsPointerType():
nativeTargetType = nativeTargetType.GetUnqualifiedType()
#DumperBase.warn('REF: %s' % nativeTargetType.name)
return self.createReferenceType(self.fromNativeType(nativeTargetType))
if code == lldb.eTypeClassTypedef:
#DumperBase.warn('TYPEDEF')
nativeTargetType = nativeType.GetUnqualifiedType()
if hasattr(nativeTargetType, 'GetCanonicalType'):
nativeTargetType = nativeTargetType.GetCanonicalType()
targetType = self.fromNativeType(nativeTargetType)
return self.createTypedefedType(targetType, nativeType.GetName())
nativeType = nativeType.GetUnqualifiedType()
typeName = self.typeName(nativeType)
if code in (lldb.eTypeClassArray, lldb.eTypeClassVector):
#DumperBase.warn('ARRAY: %s' % nativeType.GetName())
if hasattr(nativeType, 'GetArrayElementType'): # New in 3.8(?) / 350.x
nativeTargetType = nativeType.GetArrayElementType()
if not nativeTargetType.IsValid():
if hasattr(nativeType, 'GetVectorElementType'): # New in 3.8(?) / 350.x
#DumperBase.warn('BAD: %s ' % nativeTargetType.get_fields_array())
nativeTargetType = nativeType.GetVectorElementType()
count = nativeType.GetByteSize() // nativeTargetType.GetByteSize()
targetTypeName = nativeTargetType.GetName()
if targetTypeName.startswith('(anon'):
typeName = nativeType.GetName()
pos1 = typeName.rfind('[')
targetTypeName = typeName[0:pos1].strip()
#DumperBase.warn("TARGET TYPENAME: %s" % targetTypeName)
targetType = self.fromNativeType(nativeTargetType)
tdata = targetType.typeData().copy()
tdata.name = targetTypeName
targetType.typeData = lambda : tdata
return self.createArrayType(targetType, count)
if hasattr(nativeType, 'GetVectorElementType'): # New in 3.8(?) / 350.x
nativeTargetType = nativeType.GetVectorElementType()
count = nativeType.GetByteSize() // nativeTargetType.GetByteSize()
targetType = self.fromNativeType(nativeTargetType)
return self.createArrayType(targetType, count)
return self.createType(nativeType.GetName())
typeId = self.nativeTypeId(nativeType)
res = self.typeData.get(typeId, None)
if res is None:
# # This strips typedefs for pointers. We don't want that.
# typeobj.nativeType = nativeType.GetUnqualifiedType()
tdata = self.TypeData(self)
tdata.typeId = typeId
tdata.name = typeName
tdata.lbitsize = nativeType.GetByteSize() * 8
if code == lldb.eTypeClassBuiltin:
if utils.isFloatingPointTypeName(typeName):
tdata.code = TypeCode.TypeCodeFloat
elif utils.isIntegralTypeName(typeName):
tdata.code = TypeCode.TypeCodeIntegral
elif typeName in ('__int128', 'unsigned __int128'):
tdata.code = TypeCode.TypeCodeIntegral
elif typeName == 'void':
tdata.code = TypeCode.TypeCodeVoid
else:
self.warn('UNKNOWN TYPE KEY: %s: %s' % (typeName, code))
elif code == lldb.eTypeClassEnumeration:
tdata.code = TypeCode.TypeCodeEnum
tdata.enumDisplay = lambda intval, addr, form : \
self.nativeTypeEnumDisplay(nativeType, intval, form)
elif code in (lldb.eTypeClassComplexInteger, lldb.eTypeClassComplexFloat):
tdata.code = TypeCode.TypeCodeComplex
elif code in (lldb.eTypeClassClass, lldb.eTypeClassStruct, lldb.eTypeClassUnion):
tdata.code = TypeCode.TypeCodeStruct
tdata.lalignment = lambda : \
self.nativeStructAlignment(nativeType)
tdata.lfields = lambda value : \
self.listMembers(value, nativeType)
tdata.templateArguments = self.listTemplateParametersHelper(nativeType)
elif code == lldb.eTypeClassFunction:
tdata.code = TypeCode.TypeCodeFunction
elif code == lldb.eTypeClassMemberPointer:
tdata.code = TypeCode.TypeCodeMemberPointer
self.registerType(typeId, tdata) # Fix up fields and template args
# warn('CREATE TYPE: %s' % typeId)
#else:
# warn('REUSE TYPE: %s' % typeId)
return self.Type(self, typeId)
def listTemplateParametersHelper(self, nativeType):
stringArgs = self.listTemplateParameters(nativeType.GetName())
n = nativeType.GetNumberOfTemplateArguments()
if n != len(stringArgs):
# Something wrong in the debug info.
# Should work in theory, doesn't work in practice.
# Items like std::allocator<std::pair<unsigned int const, float> report 0
# for nativeType.GetNumberOfTemplateArguments() with LLDB 3.8
return stringArgs
targs = []
for i in range(nativeType.GetNumberOfTemplateArguments()):
kind = nativeType.GetTemplateArgumentKind(i)
# eTemplateArgumentKindNull = 0,
# eTemplateArgumentKindType,
# eTemplateArgumentKindDeclaration,
# eTemplateArgumentKindIntegral,
# eTemplateArgumentKindTemplate,
# eTemplateArgumentKindTemplateExpansion,
# eTemplateArgumentKindExpression,
# eTemplateArgumentKindPack
if kind == lldb.eTemplateArgumentKindType:
innerType = nativeType.GetTemplateArgumentType(i).GetUnqualifiedType().GetCanonicalType()
targs.append(self.fromNativeType(innerType))
#elif kind == lldb.eTemplateArgumentKindIntegral:
# innerType = nativeType.GetTemplateArgumentType(i).GetUnqualifiedType().GetCanonicalType()
# #DumperBase.warn('INNER TYP: %s' % innerType)
# basicType = innerType.GetBasicType()
# #DumperBase.warn('IBASIC TYP: %s' % basicType)
# inner = self.extractTemplateArgument(nativeType.GetName(), i)
# exp = '(%s)%s' % (innerType.GetName(), inner)
# #DumperBase.warn('EXP : %s' % exp)
# val = self.nativeParseAndEvaluate('(%s)%s' % (innerType.GetName(), inner))
# # Clang writes 'int' and '0xfffffff' into the debug info
# # LLDB manages to read a value of 0xfffffff...
# #if basicType == lldb.eBasicTypeInt:
# value = val.GetValueAsUnsigned()
# if value >= 0x8000000:
# value -= 0x100000000
# #DumperBase.warn('KIND: %s' % kind)
# targs.append(value)
else:
#DumperBase.warn('UNHANDLED TEMPLATE TYPE : %s' % kind)
targs.append(stringArgs[i]) # Best we can do.
#DumperBase.warn('TARGS: %s %s' % (nativeType.GetName(), [str(x) for x in targs]))
return targs
def typeName(self, nativeType):
if hasattr(nativeType, 'GetDisplayTypeName'):
return nativeType.GetDisplayTypeName() # Xcode 6 (lldb-320)
return nativeType.GetName() # Xcode 5 (lldb-310)
def nativeTypeId(self, nativeType):
name = self.typeName(nativeType)
if name is None or len(name) == 0:
c = '0'
elif name == '(anonymous struct)' and nativeType.GetTypeClass() == lldb.eTypeClassStruct:
c = 's'
elif name == '(anonymous struct)' and nativeType.GetTypeClass() == lldb.eTypeClassUnion:
c = 'u'
else:
return name
fields = nativeType.get_fields_array()
typeId = c + ''.join(['{%s:%s}' % (f.name, self.nativeTypeId(f.GetType())) for f in fields])
#DumperBase.warn('NATIVE TYPE ID FOR %s IS %s' % (name, typeId))
return typeId
def nativeTypeEnumDisplay(self, nativeType, intval, form):
if hasattr(nativeType, 'get_enum_members_array'):
enumerators = []
flags = []
found = False
for enumMember in nativeType.get_enum_members_array():
# Even when asking for signed we get unsigned with LLDB 3.8.
value = enumMember.GetValueAsSigned()
name = nativeType.GetName().split('::')
name[-1] = enumMember.GetName()
if value == intval:
return '::'.join(name) + ' (' + (form % intval) + ')'
enumerators.append(('::'.join(name), value))
given = intval
for (name, value) in enumerators:
if value & given != 0:
flags.append(name)
given = given & ~value
found = True
if not found or given != 0:
flags.append('unknown: %d' % given)
return '(' + ' | '.join(flags) + ') (' + (form % intval) + ')'
return form % intval
def nativeDynamicTypeName(self, address, baseType):
return None # FIXME: Seems sufficient, no idea why.
addr = self.target.ResolveLoadAddress(address)
ctx = self.target.ResolveSymbolContextForAddress(addr, 0)
sym = ctx.GetSymbol()
return sym.GetName()
def stateName(self, s):
try:
# See db.StateType
return (
'invalid',
'unloaded', # Process is object is valid, but not currently loaded
'connected', # Process is connected to remote debug services,
# but not launched or attached to anything yet
'attaching', # Process is currently trying to attach
'launching', # Process is in the process of launching
'stopped', # Process or thread is stopped and can be examined.
'running', # Process or thread is running and can't be examined.
'stepping', # Process or thread is in the process of stepping
# and can not be examined.
'crashed', # Process or thread has crashed and can be examined.
'detached', # Process has been detached and can't be examined.
'exited', # Process has exited and can't be examined.
'suspended' # Process or thread is in a suspended state as far
)[s]
except:
return 'unknown(%s)' % s
def stopReason(self, s):
try:
return (
'invalid',
'none',
'trace',
'breakpoint',
'watchpoint',
'signal',
'exception',
'exec',
'plancomplete',
'threadexiting',
'instrumentation',
)[s]
except:
return 'unknown(%s)' % s
def enumExpression(self, enumType, enumValue):
ns = self.qtNamespace()
return ns + 'Qt::' + enumType + '(' \
+ ns + 'Qt::' + enumType + '::' + enumValue + ')'
def callHelper(self, rettype, value, func, args):
# args is a tuple.
arg = ','.join(args)
#DumperBase.warn('PRECALL: %s -> %s(%s)' % (value.address(), func, arg))
typename = value.type.name
exp = '((%s*)0x%x)->%s(%s)' % (typename, value.address(), func, arg)
#DumperBase.warn('CALL: %s' % exp)
result = self.currentContextValue.CreateValueFromExpression('', exp)
#DumperBase.warn(' -> %s' % result)
return self.fromNativeValue(result)
def pokeValue(self, typeName, *args):
thread = self.currentThread()
frame = thread.GetFrameAtIndex(0)
inner = ','.join(args)
value = frame.EvaluateExpression(typeName + '{' + inner + '}')
#DumperBase.warn(' TYPE: %s' % value.type)
#DumperBase.warn(' ADDR: 0x%x' % value.address)
#DumperBase.warn(' VALUE: %s' % value)
return value
def nativeParseAndEvaluate(self, exp):
thread = self.currentThread()
frame = thread.GetFrameAtIndex(0)
val = frame.EvaluateExpression(exp)
#options = lldb.SBExpressionOptions()
#val = self.target.EvaluateExpression(exp, options)
err = val.GetError()
if err.Fail():
#DumperBase.warn('FAILING TO EVAL: %s' % exp)
return None
#DumperBase.warn('NO ERROR.')
#DumperBase.warn('EVAL: %s -> %s' % (exp, val.IsValid()))
return val
def parseAndEvaluate(self, exp):
val = self.nativeParseAndEvaluate(exp)
return None if val is None else self.fromNativeValue(val)
def isWindowsTarget(self):
return False
def isQnxTarget(self):
return False
def isArmArchitecture(self):
return False
def isMsvcTarget(self):
return False
def prettySymbolByAddress(self, address):
try:
result = lldb.SBCommandReturnObject()
# Cast the address to a function pointer to get the name and location of the function.
expression = 'po (void (*)()){}'
self.debugger.GetCommandInterpreter().HandleCommand(expression.format(address), result)
output = ''
if result.Succeeded():
output = result.GetOutput().strip()
if output:
return output
except:
pass
return '0x%x' % address
def qtVersionAndNamespace(self):
for func in self.target.FindFunctions('qVersion'):
name = func.GetSymbol().GetName()
if name.endswith('()'):
name = name[:-2]
if name.count(':') > 2:
continue
qtNamespace = name[:name.find('qVersion')]
self.qtNamespace = lambda: qtNamespace
options = lldb.SBExpressionOptions()
res = self.target.EvaluateExpression(name + '()', options)
if not res.IsValid() or not res.GetType().IsPointerType():
exp = '((const char*())%s)()' % name
res = self.target.EvaluateExpression(exp, options)
if not res.IsValid() or not res.GetType().IsPointerType():
exp = '((const char*())_Z8qVersionv)()'
res = self.target.EvaluateExpression(exp, options)
if not res.IsValid() or not res.GetType().IsPointerType():
continue
version = str(res)
if version.count('.') != 2:
continue
version.replace("'", '"') # Both seem possible
version = version[version.find('"')+1:version.rfind('"')]
(major, minor, patch) = version.split('.')
qtVersion = 0x10000 * int(major) + 0x100 * int(minor) + int(patch)
self.qtVersion = lambda: qtVersion
funcs = self.target.FindFunctions('QObject::customEvent')
if len(funcs):
symbol = funcs[0].GetSymbol()
self.qtCustomEventFunc = symbol.GetStartAddress().GetLoadAddress(self.target)
funcs = self.target.FindFunctions('QObject::property')
if len(funcs):
symbol = funcs[0].GetSymbol()
self.qtPropertyFunc = symbol.GetStartAddress().GetLoadAddress(self.target)
return (qtNamespace, qtVersion)
return ('', 0x50200)
def qtNamespace(self):
return self.qtVersionAndNamespace()[0]
def qtVersion(self):
self.qtVersionAndNamespace()
return self.qtVersionAndNamespace()[1]
def handleCommand(self, command):
result = lldb.SBCommandReturnObject()
self.debugger.GetCommandInterpreter().HandleCommand(command, result)
success = result.Succeeded()
if success:
self.report('output="%s"' % result.GetOutput())
else:
self.report('error="%s"' % result.GetError())
def canonicalTypeName(self, name):
return re.sub('\\bconst\\b', '', name).replace(' ', '')
def removeTypePrefix(self, name):
return re.sub('^(struct|class|union|enum|typedef) ', '', name)
def lookupNativeType(self, name):
#DumperBase.warn('LOOKUP TYPE NAME: %s' % name)
typeobj = self.typeCache.get(name)
if not typeobj is None:
#DumperBase.warn('CACHED: %s' % name)
return typeobj
typeobj = self.target.FindFirstType(name)
if typeobj.IsValid():
#DumperBase.warn('VALID FIRST : %s' % typeobj)
self.typeCache[name] = typeobj
return typeobj
# FindFirstType has a bug (in lldb) that if there are two types with the same base name
# but different scope name (e.g. inside different classes) and the searched for type name
# would be returned as the second result in a call to FindTypes, FindFirstType would return
# an empty result.
# Therefore an additional call to FindTypes is done as a fallback.
# Note that specifying a prefix like enum or typedef or class will make the call fail to
# find the type, thus the prefix is stripped.
nonPrefixedName = self.canonicalTypeName(self.removeTypePrefix(name))
typeobjlist = self.target.FindTypes(nonPrefixedName)
if typeobjlist.IsValid():
for typeobj in typeobjlist:
n = self.canonicalTypeName(self.removeTypePrefix(typeobj.GetDisplayTypeName()))
if n == nonPrefixedName:
#DumperBase.warn('FOUND TYPE USING FindTypes : %s' % typeobj)
self.typeCache[name] = typeobj
return typeobj
if name.endswith('*'):
#DumperBase.warn('RECURSE PTR')
typeobj = self.lookupNativeType(name[:-1].strip())
if typeobj is not None:
#DumperBase.warn('RECURSE RESULT X: %s' % typeobj)
self.fromNativeType(typeobj.GetPointerType())
#DumperBase.warn('RECURSE RESULT: %s' % typeobj.GetPointerType())
return typeobj.GetPointerType()
#typeobj = self.target.FindFirstType(name[:-1].strip())
#if typeobj.IsValid():
# self.typeCache[name] = typeobj.GetPointerType()
# return typeobj.GetPointerType()
if name.endswith(' const'):
#DumperBase.warn('LOOKUP END CONST')
typeobj = self.lookupNativeType(name[:-6])
if typeobj is not None:
return typeobj
if name.startswith('const '):
#DumperBase.warn('LOOKUP START CONST')
typeobj = self.lookupNativeType(name[6:])
if typeobj is not None:
return typeobj
return self.lookupNativeTypeInAllModules(name)
def lookupNativeTypeInAllModules(self, name):
needle = self.canonicalTypeName(name)
#DumperBase.warn('NEEDLE: %s ' % needle)
self.warn('Searching for type %s across all target modules, this could be very slow' % name)
for i in xrange(self.target.GetNumModules()):
module = self.target.GetModuleAtIndex(i)
# SBModule.GetType is new somewhere after early 300.x
# So this may fail.
for t in module.GetTypes():
n = self.canonicalTypeName(t.GetName())
#DumperBase.warn('N: %s' % n)
if n == needle:
#DumperBase.warn('FOUND TYPE DIRECT 2: %s ' % t)
self.typeCache[name] = t
return t
if n == needle + '*':
res = t.GetPointeeType()
self.typeCache[name] = res
x = self.fromNativeType(res) # Register under both names
self.registerTypeAlias(x.typeId, name)
#DumperBase.warn('FOUND TYPE BY POINTER: %s ' % res.name)
return res
if n == needle + '&':
res = t.GetDereferencedType().GetUnqualifiedType()
self.typeCache[name] = res
x = self.fromNativeType(res) # Register under both names
self.registerTypeAlias(x.typeId, name)
#DumperBase.warn('FOUND TYPE BY REFERENCE: %s ' % res.name)
return res
#DumperBase.warn('NOT FOUND: %s ' % needle)
return None
def setupInferior(self, args):
error = lldb.SBError()
self.executable_ = args['executable']
self.startMode_ = args.get('startmode', 1)
self.breakOnMain_ = args.get('breakonmain', 0)
self.useTerminal_ = args.get('useterminal', 0)
pargs = self.hexdecode(args.get('processargs', ''))
self.processArgs_ = pargs.split('\0') if len(pargs) else []
self.environment_ = args.get('environment', [])
self.environment_ = list(map(lambda x: self.hexdecode(x), self.environment_))
self.attachPid_ = args.get('attachpid', 0)
self.sysRoot_ = args.get('sysroot', '')
self.remoteChannel_ = args.get('remotechannel', '')
self.platform_ = args.get('platform', '')
self.nativeMixed = int(args.get('nativemixed', 0))
self.workingDirectory_ = args.get('workingdirectory', '')
if self.workingDirectory_ == '':
try:
self.workingDirectory_ = os.getcwd()
except: # Could have been deleted in the mean time.
pass
self.ignoreStops = 0
if platform.system() == 'Linux':
if self.startMode_ == DebuggerStartMode.AttachCore:
pass
else:
if self.useTerminal_:
self.ignoreStops = 1
if self.platform_:
self.debugger.SetCurrentPlatform(self.platform_)
# sysroot has to be set *after* the platform
if self.sysRoot_:
self.debugger.SetCurrentPlatformSDKRoot(self.sysRoot_)
exefile = None if self.attachPid_ > 0 else self.executable_
self.target = self.debugger.CreateTarget(exefile, None, None, True, error)
if self.nativeMixed:
self.interpreterEventBreakpoint = \
self.target.BreakpointCreateByName('qt_qmlDebugMessageAvailable')
state = 1 if self.target.IsValid() else 0
self.reportResult('success="%s",msg="%s",exe="%s"'
% (state, error, self.executable_), args)
def runEngine(self, args):
if self.runEngineAttempted:
return
self.runEngineAttempted = True
self.prepare(args)
s = threading.Thread(target=self.loop, args=[])
s.start()
def prepare(self, args):
error = lldb.SBError()
if self.attachPid_ > 0:
attachInfo = lldb.SBAttachInfo(self.attachPid_)
self.process = self.target.Attach(attachInfo, error)
if not error.Success():
self.reportState('inferiorrunfailed')
return
self.report('pid="%s"' % self.process.GetProcessID())
# Even if it stops it seems that LLDB assumes it is running
# and later detects that it did stop after all, so it is be
# better to mirror that and wait for the spontaneous stop
if self.process and self.process.GetState() == lldb.eStateStopped:
# lldb stops the process after attaching. This happens before the
# eventloop starts. Relay the correct state back.
self.reportState('enginerunandinferiorstopok')
else:
self.reportState('enginerunandinferiorrunok')
elif (self.startMode_ == DebuggerStartMode.AttachToRemoteServer
or self.startMode_ == DebuggerStartMode.AttachToRemoteProcess):
self.process = self.target.ConnectRemote(
self.debugger.GetListener(),
self.remoteChannel_, None, error)
if not error.Success():
self.report(self.describeError(error))
self.reportState('enginerunfailed')
return
# Even if it stops it seems that LLDB assumes it is running
# and later detects that it did stop after all, so it is be
# better to mirror that and wait for the spontaneous stop.
self.reportState('enginerunandinferiorrunok')
elif self.startMode_ == DebuggerStartMode.AttachCore:
coreFile = args.get('coreFile', '');
self.process = self.target.LoadCore(coreFile)
if self.process.IsValid():
self.reportState('enginerunokandinferiorunrunnable')
else:
self.reportState('enginerunfailed')
else:
launchInfo = lldb.SBLaunchInfo(self.processArgs_)
launchInfo.SetWorkingDirectory(self.workingDirectory_)
launchInfo.SetEnvironmentEntries(self.environment_, False)
if self.breakOnMain_:
self.createBreakpointAtMain()
self.process = self.target.Launch(launchInfo, error)
if not error.Success():
self.report(self.describeError(error))
self.reportState('enginerunfailed')
return
self.report('pid="%s"' % self.process.GetProcessID())
self.reportState('enginerunandinferiorrunok')
if self.target is not None:
broadcaster = self.target.GetBroadcaster()
listener = self.debugger.GetListener()
broadcaster.AddListener(listener, lldb.SBProcess.eBroadcastBitStateChanged)
listener.StartListeningForEvents(broadcaster, lldb.SBProcess.eBroadcastBitStateChanged)
broadcaster.AddListener(listener, lldb.SBTarget.eBroadcastBitBreakpointChanged)
listener.StartListeningForEvents(broadcaster, lldb.SBTarget.eBroadcastBitBreakpointChanged)
def loop(self):
event = lldb.SBEvent()
#broadcaster = self.target.GetBroadcaster()
listener = self.debugger.GetListener()
while True:
while listener.GetNextEvent(event):
self.handleEvent(event)
time.sleep(0.25)
#if listener.WaitForEventForBroadcaster(0, broadcaster, event):
# self.handleEvent(event)
def describeError(self, error):
desc = lldb.SBStream()
error.GetDescription(desc)
result = 'success="%s",' % int(error.Success())
result += 'error={type="%s"' % error.GetType()
if error.GetType():
result += ',status="%s"' % error.GetCString()
result += ',code="%s"' % error.GetError()
result += ',desc="%s"}' % desc.GetData()
return result
def describeStatus(self, status):
return 'status="%s",' % status
def describeLocation(self, frame):
if int(frame.pc) == 0xffffffffffffffff:
return ''
fileName = fileNameAsString(frame.line_entry.file)
function = frame.GetFunctionName()
line = frame.line_entry.line
return 'location={file="%s",line="%s",address="%s",function="%s"}' \
% (fileName, line, frame.pc, function)
def currentThread(self):
return None if self.process is None else self.process.GetSelectedThread()
def currentFrame(self):
thread = self.currentThread()
return None if thread is None else thread.GetSelectedFrame()
def firstStoppedThread(self):
for i in xrange(0, self.process.GetNumThreads()):
thread = self.process.GetThreadAtIndex(i)
reason = thread.GetStopReason()
if (reason == lldb.eStopReasonBreakpoint or
reason == lldb.eStopReasonException or
reason == lldb.eStopReasonPlanComplete or
reason == lldb.eStopReasonSignal or
reason == lldb.eStopReasonWatchpoint):
return thread
return None
def fetchThreads(self, args):
result = 'threads=['
for i in xrange(0, self.process.GetNumThreads()):
thread = self.process.GetThreadAtIndex(i)
if thread.is_stopped:
state = 'stopped'
elif thread.is_suspended:
state = 'suspended'
else:
state = 'unknown'
reason = thread.GetStopReason()
result += '{id="%d"' % thread.GetThreadID()
result += ',index="%s"' % i
result += ',details="%s"' % thread.GetQueueName()
result += ',stop-reason="%s"' % self.stopReason(thread.GetStopReason())
result += ',state="%s"' % state
result += ',name="%s"' % thread.GetName()
result += ',frame={'
frame = thread.GetFrameAtIndex(0)
result += 'pc="0x%x"' % frame.pc
result += ',addr="0x%x"' % frame.pc
result += ',fp="0x%x"' % frame.fp
result += ',func="%s"' % frame.GetFunctionName()
result += ',line="%s"' % frame.line_entry.line
result += ',fullname="%s"' % fileNameAsString(frame.line_entry.file)
result += ',file="%s"' % fileNameAsString(frame.line_entry.file)
result += '}},'
result += '],current-thread-id="%s"' % self.currentThread().id
self.reportResult(result, args)
def firstUsableFrame(self, thread):
for i in xrange(10):
frame = thread.GetFrameAtIndex(i)
lineEntry = frame.GetLineEntry()
line = lineEntry.GetLine()
if line != 0:
return i
return None
def fetchStack(self, args):
if not self.process:
self.reportResult('msg="No process"', args)
return
thread = self.currentThread()
if not thread:
self.reportResult('msg="No thread"', args)
return
isNativeMixed = int(args.get('nativemixed', 0))
limit = args.get('stacklimit', -1)
(n, isLimited) = (limit, True) if limit > 0 else (thread.GetNumFrames(), False)
self.currentCallContext = None
result = 'stack={current-thread="%s"' % thread.GetThreadID()
result += ',frames=['
for i in xrange(n):
frame = thread.GetFrameAtIndex(i)
if not frame.IsValid():
isLimited = False
break
lineEntry = frame.GetLineEntry()
lineNumber = lineEntry.GetLine()
pc = frame.GetPC()
level = frame.idx
addr = frame.GetPCAddress().GetLoadAddress(self.target)
functionName = frame.GetFunctionName()
module = frame.GetModule()
if isNativeMixed and functionName == '::qt_qmlDebugMessageAvailable()':
interpreterStack = self.extractInterpreterStack()
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)
result += ('frame={function="%s",file="%s",'
'line="%s",language="%s",context="%s"}'
% (function, fileName, lineNumber, language, context))
fileName = fileNameAsString(lineEntry.file)
result += '{pc="0x%x"' % pc
result += ',level="%d"' % level
result += ',address="0x%x"' % addr
result += ',function="%s"' % functionName
result += ',line="%d"' % lineNumber
result += ',module="%s"' % module
result += ',file="%s"},' % fileName
result += ']'
result += ',hasmore="%d"' % isLimited
result += ',limit="%d"' % limit
result += '}'
self.reportResult(result, args)
def reportResult(self, result, args):
self.report('result={token="%s",%s}' % (args.get("token", 0), result))
def reportToken(self, args):
if "token" in args:
# Unusual syntax intended, to support the double-click in left
# logview pane feature.
self.report('token(\"%s\")' % args["token"])
def reportBreakpointUpdate(self, bp):
self.report('breakpointmodified={%s}' % self.describeBreakpoint(bp))
def readRawMemory(self, address, size):
if size == 0:
return bytes()
error = lldb.SBError()
#DumperBase.warn("READ: %s %s" % (address, size))
res = self.process.ReadMemory(address, size, error)
if res is None or len(res) != size:
# Using code in e.g. readToFirstZero relies on exceptions.
raise RuntimeError("Unreadable %s bytes at 0x%x" % (size, address))
return res
def findStaticMetaObject(self, type):
symbolName = self.mangleName(type.name + '::staticMetaObject')
symbol = self.target.FindFirstGlobalVariable(symbolName)
return symbol.AddressOf().GetValueAsUnsigned() if symbol.IsValid() else 0
def findSymbol(self, symbolName):
return self.target.FindFirstGlobalVariable(symbolName)
def warn(self, msg):
self.put('{name="%s",value="",type="",numchild="0"},' % msg)
def fetchVariables(self, args):
(ok, res) = self.tryFetchInterpreterVariables(args)
if ok:
self.reportResult(res, args)
return
self.setVariableFetchingOptions(args)
# Reset certain caches whenever a step over / into / continue
# happens.
# FIXME: Caches are currently also cleared if currently
# selected frame is changed, that shouldn't happen.
if not self.partialVariable:
self.resetPerStepCaches()
anyModule = self.target.GetModuleAtIndex(0)
anySymbol = anyModule.GetSymbolAtIndex(0)
self.fakeAddress = int(anySymbol.GetStartAddress())
frame = self.currentFrame()
if frame is None:
self.reportResult('error="No frame"', args)
return
self.output = ''
isPartial = len(self.partialVariable) > 0
self.currentIName = 'local'
self.put('data=[')
with SubItem(self, '[statics]'):
self.put('iname="%s",' % self.currentIName)
self.putEmptyValue()
self.putNumChild(1)
if self.isExpanded():
with Children(self):
statics = frame.GetVariables(False, False, True, False)
if len(statics):
for i in xrange(len(statics)):
staticVar = statics[i]
staticVar.SetPreferSyntheticValue(False)
typename = staticVar.GetType().GetName()
name = staticVar.GetName()
with SubItem(self, i):
self.put('name="%s",' % name)
self.put('iname="%s",' % self.currentIName)
self.putItem(self.fromNativeValue(staticVar))
else:
with SubItem(self, "None"):
self.putEmptyValue()
self.putNumChild(0)
# FIXME: Implement shortcut for partial updates.
#if isPartial:
# values = [frame.FindVariable(partialVariable)]
#else:
if True:
values = list(frame.GetVariables(True, True, False, True))
values.reverse() # To get shadowed vars numbered backwards.
variables = []
for val in values:
val.SetPreferSyntheticValue(False)
if not val.IsValid():
continue
self.currentContextValue = val
name = val.GetName()
if name is None:
# This can happen for unnamed function parameters with
# default values: void foo(int = 0)
continue
value = self.fromNativeFrameValue(val)
variables.append(value)
self.handleLocals(variables)
self.handleWatches(args)
self.put('],partial="%d"' % isPartial)
self.reportResult(self.output, args)
def fetchRegisters(self, args = None):
if self.process is None:
result = 'process="none"'
else:
frame = self.currentFrame()
if frame:
result = 'registers=['
for group in frame.GetRegisters():
for reg in group:
value = ''.join(["%02x" % x for x in reg.GetData().uint8s])
result += '{name="%s"' % reg.GetName()
result += ',value="0x%s"' % value
result += ',size="%s"' % reg.GetByteSize()
result += ',type="%s"},' % reg.GetType()
result += ']'
self.reportResult(result, args)
def setRegister(self, args):
name = args["name"]
value = args["value"]
result = lldb.SBCommandReturnObject()
interp = self.debugger.GetCommandInterpreter()
interp.HandleCommand("register write %s %s" % (name, value), result)
success = result.Succeeded()
if success:
self.reportResult('output="%s"' % result.GetOutput(), args)
return
# Try again with register write xmm0 "{0x00 ... 0x02}" syntax:
vec = ' '.join(["0x" + value[i:i+2] for i in range(2, len(value), 2)])
success = interp.HandleCommand('register write %s "{%s}"' % (name, vec), result)
if success:
self.reportResult('output="%s"' % result.GetOutput(), args)
else:
self.reportResult('error="%s"' % result.GetError(), args)
def report(self, stuff):
with self.outputLock:
sys.stdout.write("@\n" + stuff + "@\n")
def reportState(self, state):
self.report('state="%s"' % state)
def interruptInferior(self, args):
if self.process is None:
self.reportResult('status="No process to interrupt",success="0"', args)
else:
self.isInterrupting_ = True
error = self.process.Stop()
self.reportResult(self.describeError(error), args)
def detachInferior(self, args):
if self.process is None:
self.reportResult('status="No process to detach from."', args)
else:
error = self.process.Detach()
self.reportResult(self.describeError(error), args)
def continueInferior(self, args):
if self.process is None:
self.reportResult('status="No process to continue."', args)
else:
# Can fail when attaching to GDBserver.
error = self.process.Continue()
self.reportResult(self.describeError(error), args)
def quitDebugger(self, args):
self.reportState("inferiorshutdownrequested")
self.process.Kill()
self.reportResult('', args)
def handleBreakpointEvent(self, event):
eventType = lldb.SBBreakpoint.GetBreakpointEventTypeFromEvent(event)
# handle only the resolved locations for now..
if eventType & lldb.eBreakpointEventTypeLocationsResolved:
bp = lldb.SBBreakpoint.GetBreakpointFromEvent(event)
if bp is not None:
self.reportBreakpointUpdate(bp)
def handleEvent(self, event):
if lldb.SBBreakpoint.EventIsBreakpointEvent(event):
self.handleBreakpointEvent(event)
return
if not lldb.SBProcess.EventIsProcessEvent(event):
self.warn("UNEXPECTED event (%s)" % event.GetType())
return
out = lldb.SBStream()
event.GetDescription(out)
#DumperBase.warn("EVENT: %s" % event)
eventType = event.GetType()
msg = lldb.SBEvent.GetCStringFromEvent(event)
flavor = event.GetDataFlavor()
state = lldb.SBProcess.GetStateFromEvent(event)
bp = lldb.SBBreakpoint.GetBreakpointFromEvent(event)
skipEventReporting = eventType in (lldb.SBProcess.eBroadcastBitSTDOUT, lldb.SBProcess.eBroadcastBitSTDERR)
self.report('event={type="%s",data="%s",msg="%s",flavor="%s",state="%s",bp="%s"}'
% (eventType, out.GetData(), msg, flavor, self.stateName(state), bp))
if state == lldb.eStateExited:
self.eventState = state
if not self.isShuttingDown_:
self.reportState("inferiorexited")
self.report('exited={status="%s",desc="%s"}'
% (self.process.GetExitStatus(), self.process.GetExitDescription()))
elif state != self.eventState and not skipEventReporting:
self.eventState = state
if state == lldb.eStateStopped:
stoppedThread = self.firstStoppedThread()
if stoppedThread:
#self.report("STOPPED THREAD: %s" % stoppedThread)
frame = stoppedThread.GetFrameAtIndex(0)
#self.report("FRAME: %s" % frame)
function = frame.GetFunction()
functionName = function.GetName()
if functionName == "::qt_qmlDebugConnectorOpen()":
self.report("RESOLVER HIT")
for resolver in self.interpreterBreakpointResolvers:
resolver()
self.report("AUTO-CONTINUE AFTER RESOLVING")
self.reportState("inferiorstopok")
self.process.Continue();
return
if functionName == "::qt_qmlDebugMessageAvailable()":
self.report("ASYNC MESSAGE FROM SERVICE")
res = self.handleInterpreterMessage()
if not res:
self.report("EVENT NEEDS NO STOP")
self.reportState("stopped")
self.process.Continue();
return
if self.isInterrupting_:
self.isInterrupting_ = False
self.reportState("inferiorstopok")
elif self.ignoreStops > 0:
self.ignoreStops -= 1
self.process.Continue()
else:
self.reportState("stopped")
else:
self.reportState(self.stateName(state))
if eventType == lldb.SBProcess.eBroadcastBitStateChanged: # 1
state = self.process.GetState()
if state == lldb.eStateStopped:
stoppedThread = self.firstStoppedThread()
if stoppedThread:
self.process.SetSelectedThread(stoppedThread)
elif eventType == lldb.SBProcess.eBroadcastBitInterrupt: # 2
pass
elif eventType == lldb.SBProcess.eBroadcastBitSTDOUT:
# FIXME: Size?
msg = self.process.GetSTDOUT(1024)
if msg is not None:
self.report('output={channel="stdout",data="%s"}' % self.hexencode(msg))
elif eventType == lldb.SBProcess.eBroadcastBitSTDERR:
msg = self.process.GetSTDERR(1024)
if msg is not None:
self.report('output={channel="stderr",data="%s"}' % self.hexencode(msg))
elif eventType == lldb.SBProcess.eBroadcastBitProfileData:
pass
def describeBreakpoint(self, bp):
isWatch = isinstance(bp, lldb.SBWatchpoint)
if isWatch:
result = 'lldbid="%s"' % (qqWatchpointOffset + bp.GetID())
else:
result = 'lldbid="%s"' % bp.GetID()
result += ',valid="%s"' % (1 if bp.IsValid() else 0)
result += ',hitcount="%s"' % bp.GetHitCount()
if bp.IsValid():
if isinstance(bp, lldb.SBBreakpoint):
result += ',threadid="%s"' % bp.GetThreadID()
result += ',oneshot="%s"' % (1 if bp.IsOneShot() else 0)
cond = bp.GetCondition()
result += ',condition="%s"' % self.hexencode("" if cond is None else cond)
result += ',enabled="%s"' % (1 if bp.IsEnabled() else 0)
result += ',valid="%s"' % (1 if bp.IsValid() else 0)
result += ',ignorecount="%s"' % bp.GetIgnoreCount()
if bp.IsValid() and isinstance(bp, lldb.SBBreakpoint):
result += ',locations=['
lineEntry = None
for i in xrange(bp.GetNumLocations()):
loc = bp.GetLocationAtIndex(i)
addr = loc.GetAddress()
lineEntry = addr.GetLineEntry()
result += '{locid="%s"' % loc.GetID()
result += ',function="%s"' % addr.GetFunction().GetName()
result += ',enabled="%s"' % (1 if loc.IsEnabled() else 0)
result += ',resolved="%s"' % (1 if loc.IsResolved() else 0)
result += ',valid="%s"' % (1 if loc.IsValid() else 0)
result += ',ignorecount="%s"' % loc.GetIgnoreCount()
result += ',file="%s"' % lineEntry.GetFileSpec()
result += ',line="%s"' % lineEntry.GetLine()
result += ',addr="%s"},' % addr.GetFileAddress()
result += ']'
if lineEntry is not None:
result += ',file="%s"' % lineEntry.GetFileSpec()
result += ',line="%s"' % lineEntry.GetLine()
return result
def createBreakpointAtMain(self):
return self.target.BreakpointCreateByName(
'main', self.target.GetExecutable().GetFilename())
def insertBreakpoint(self, args):
bpType = args['type']
if bpType == BreakpointType.BreakpointByFileAndLine:
fileName = args['file']
if fileName.endswith('.js') or fileName.endswith('.qml'):
self.insertInterpreterBreakpoint(args)
return
extra = ''
more = True
if bpType == BreakpointType.BreakpointByFileAndLine:
bp = self.target.BreakpointCreateByLocation(
str(args['file']), int(args['line']))
elif bpType == BreakpointType.BreakpointByFunction:
bp = self.target.BreakpointCreateByName(args['function'])
elif bpType == BreakpointType.BreakpointByAddress:
bp = self.target.BreakpointCreateByAddress(args['address'])
elif bpType == BreakpointType.BreakpointAtMain:
bp = self.createBreakpointAtMain()
elif bpType == BreakpointType.BreakpointAtThrow:
bp = self.target.BreakpointCreateForException(
lldb.eLanguageTypeC_plus_plus, False, True)
elif bpType == BreakpointType.BreakpointAtCatch:
bp = self.target.BreakpointCreateForException(
lldb.eLanguageTypeC_plus_plus, True, False)
elif bpType == BreakpointType.WatchpointAtAddress:
error = lldb.SBError()
# This might yield bp.IsValid() == False and
# error.desc == 'process is not alive'.
bp = self.target.WatchAddress(args['address'], 4, False, True, error)
extra = self.describeError(error)
elif bpType == BreakpointType.WatchpointAtExpression:
# FIXME: Top level-only for now.
try:
frame = self.currentFrame()
value = frame.FindVariable(args['expression'])
error = lldb.SBError()
bp = self.target.WatchAddress(value.GetLoadAddress(),
value.GetByteSize(), False, True, error)
except:
bp = self.target.BreakpointCreateByName(None)
else:
# This leaves the unhandled breakpoint in a (harmless)
# 'pending' state.
bp = self.target.BreakpointCreateByName(None)
more = False
if more and bp.IsValid():
bp.SetIgnoreCount(int(args['ignorecount']))
bp.SetCondition(self.hexdecode(args['condition']))
bp.SetEnabled(bool(args['enabled']))
bp.SetScriptCallbackBody('\n'.join([
'def foo(frame = frame, bp_loc = bp_loc, dict = internal_dict):',
' ' + self.hexdecode(args['command']).replace('\n', '\n '),
'from cStringIO import StringIO',
'origout = sys.stdout',
'sys.stdout = StringIO()',
'result = foo()',
'd = lldb.theDumper',
'output = d.hexencode(sys.stdout.getvalue())',
'sys.stdout = origout',
'd.report("output={channel=\"stderr\",data=\" + output + \"}")',
'if result is False:',
' d.reportState("continueafternextstop")',
'return True'
]))
if isinstance(bp, lldb.SBBreakpoint):
bp.SetOneShot(bool(args['oneshot']))
self.reportResult(self.describeBreakpoint(bp) + extra, args)
def changeBreakpoint(self, args):
lldbId = int(args['lldbid'])
if lldbId > qqWatchpointOffset:
bp = self.target.FindWatchpointByID(lldbId)
else:
bp = self.target.FindBreakpointByID(lldbId)
if bp.IsValid():
bp.SetIgnoreCount(int(args['ignorecount']))
bp.SetCondition(self.hexdecode(args['condition']))
bp.SetEnabled(bool(args['enabled']))
if isinstance(bp, lldb.SBBreakpoint):
bp.SetOneShot(bool(args['oneshot']))
self.reportResult(self.describeBreakpoint(bp), args)
def enableSubbreakpoint(self, args):
lldbId = int(args['lldbid'])
locId = int(args['locid'])
bp = self.target.FindBreakpointByID(lldbId)
res = False
enabled = False
if bp.IsValid():
loc = bp.FindLocationByID(locId)
if loc.IsValid():
loc.SetEnabled(bool(args['enabled']))
enabled = loc.IsEnabled()
res = True
self.reportResult('success="%s",enabled="%s",locid="%s"'
% (int(res), int(enabled), locId), args)
def removeBreakpoint(self, args):
lldbId = int(args['lldbid'])
if lldbId > qqWatchpointOffset:
res = self.target.DeleteWatchpoint(lldbId - qqWatchpointOffset)
res = self.target.BreakpointDelete(lldbId)
self.reportResult('success="%s"' % int(res), args)
def fetchModules(self, args):
result = 'modules=['
for i in xrange(self.target.GetNumModules()):
module = self.target.GetModuleAtIndex(i)
result += '{file="%s"' % module.file.fullpath
result += ',name="%s"' % module.file.basename
result += ',addrsize="%s"' % module.addr_size
result += ',triple="%s"' % module.triple
#result += ',sections={'
#for section in module.sections:
# result += '[name="%s"' % section.name
# result += ',addr="%s"' % section.addr
# result += ',size="%s"],' % section.size
#result += '}'
result += '},'
result += ']'
self.reportResult(result, args)
def fetchSymbols(self, args):
moduleName = args['module']
#file = lldb.SBFileSpec(moduleName)
#module = self.target.FindModule(file)
for i in xrange(self.target.GetNumModules()):
module = self.target.GetModuleAtIndex(i)
if module.file.fullpath == moduleName:
break
result = 'symbols={valid="%s"' % module.IsValid()
result += ',sections="%s"' % module.GetNumSections()
result += ',symbols=['
for symbol in module.symbols:
startAddress = symbol.GetStartAddress().GetLoadAddress(self.target)
endAddress = symbol.GetEndAddress().GetLoadAddress(self.target)
result += '{type="%s"' % symbol.GetType()
result += ',name="%s"' % symbol.GetName()
result += ',address="0x%x"' % startAddress
result += ',demangled="%s"' % symbol.GetMangledName()
result += ',size="%s"' % (endAddress - startAddress)
result += '},'
result += ']}'
self.reportResult(result, args)
def executeNext(self, args):
self.currentThread().StepOver()
self.reportResult('', args)
def executeNextI(self, args):
self.currentThread().StepInstruction(True)
self.reportResult('', args)
def executeStep(self, args):
self.currentThread().StepInto()
self.reportResult('', args)
def shutdownInferior(self, args):
self.isShuttingDown_ = True
if self.process is not None:
state = self.process.GetState()
if state == lldb.eStateStopped:
self.process.Kill()
self.reportState('inferiorshutdownfinished')
self.reportResult('', args)
def quit(self, args):
self.reportState('engineshutdownfinished')
self.process.Kill()
self.reportResult('', args)
def executeStepI(self, args):
self.currentThread().StepInstruction(False)
self.reportResult('', args)
def executeStepOut(self, args = {}):
self.currentThread().StepOut()
self.reportResult('', args)
def executeRunToLocation(self, args):
self.reportToken(args)
addr = args.get('address', 0)
if addr:
# Does not seem to hit anything on Linux:
# self.currentThread().RunToAddress(addr)
bp = self.target.BreakpointCreateByAddress(addr)
if bp.GetNumLocations() == 0:
self.target.BreakpointDelete(bp.GetID())
self.reportResult(self.describeStatus('No target location found.')
+ self.describeLocation(frame), args)
return
bp.SetOneShot(True)
self.reportResult('', args)
self.process.Continue()
else:
frame = self.currentFrame()
file = args['file']
line = int(args['line'])
error = self.currentThread().StepOverUntil(frame, lldb.SBFileSpec(file), line)
self.reportResult(self.describeError(error), args)
self.reportState('running')
self.reportState('stopped')
def executeJumpToLocation(self, args):
self.reportToken(args)
frame = self.currentFrame()
if not frame:
self.reportResult(self.describeStatus('No frame available.'), args)
return
addr = args.get('address', 0)
if addr:
bp = self.target.BreakpointCreateByAddress(addr)
else:
bp = self.target.BreakpointCreateByLocation(
str(args['file']), int(args['line']))
if bp.GetNumLocations() == 0:
self.target.BreakpointDelete(bp.GetID())
status = 'No target location found.'
else:
loc = bp.GetLocationAtIndex(0)
self.target.BreakpointDelete(bp.GetID())
res = frame.SetPC(loc.GetLoadAddress())
status = 'Jumped.' if res else 'Cannot jump.'
self.report(self.describeLocation(frame))
self.reportResult(self.describeStatus(status), args)
def breakList(self):
result = lldb.SBCommandReturnObject()
self.debugger.GetCommandInterpreter().HandleCommand('break list', result)
self.report('success="%d",output="%s",error="%s"'
% (result.Succeeded(), result.GetOutput(), result.GetError()))
def activateFrame(self, args):
self.reportToken(args)
self.currentThread().SetSelectedFrame(args['index'])
self.reportResult('', args)
def selectThread(self, args):
self.reportToken(args)
self.process.SetSelectedThreadByID(int(args['id']))
self.reportResult('', args)
def fetchFullBacktrace(self, _ = None):
command = 'thread backtrace all'
result = lldb.SBCommandReturnObject()
self.debugger.GetCommandInterpreter().HandleCommand(command, result)
self.reportResult(self.hexencode(result.GetOutput()), {})
def executeDebuggerCommand(self, args):
self.reportToken(args)
result = lldb.SBCommandReturnObject()
command = args['command']
self.debugger.GetCommandInterpreter().HandleCommand(command, result)
success = result.Succeeded()
output = result.GetOutput()
error = str(result.GetError())
self.report('success="%d",output="%s",error="%s"' % (success, output, error))
def fetchDisassembler(self, args):
functionName = args.get('function', '')
flavor = args.get('flavor', '')
function = None
if len(functionName):
functions = self.target.FindFunctions(functionName).functions
if len(functions):
function = functions[0]
if function:
base = function.GetStartAddress().GetLoadAddress(self.target)
instructions = function.GetInstructions(self.target)
else:
base = args.get('address', 0)
if int(base) == 0xffffffffffffffff:
self.warn('INVALID DISASSEMBLER BASE')
return
addr = lldb.SBAddress(base, self.target)
instructions = self.target.ReadInstructions(addr, 100)
currentFile = None
currentLine = None
hunks = dict()
sources = dict()
result = 'lines=['
for insn in instructions:
comment = insn.GetComment(self.target)
addr = insn.GetAddress()
loadAddr = addr.GetLoadAddress(self.target)
lineEntry = addr.GetLineEntry()
if lineEntry:
lineNumber = lineEntry.GetLine()
fileName = str(lineEntry.GetFileSpec())
if lineNumber != currentLine or fileName != currentFile:
currentLine = lineNumber
currentFile = fileName
key = '%s:%s' % (fileName, lineNumber)
hunk = hunks.get(key, 0) + 1
hunks[key] = hunk
source = sources.get(fileName, None)
if source is None:
try:
with open(fileName, 'r') as f:
source = f.read().splitlines()
sources[fileName] = source
except IOError as error:
# With lldb-3.8 files like /data/dev/creator-3.6/tests/
# auto/debugger/qt_tst_dumpers_StdVector_bfNWZa/main.cpp
# with non-existent directories appear.
self.warn('FILE: %s ERROR: %s' % (fileName, error))
source = ''
result += '{line="%s"' % lineNumber
result += ',file="%s"' % fileName
if 0 < lineNumber and lineNumber <= len(source):
result += ',hexdata="%s"' % self.hexencode(source[lineNumber - 1])
result += ',hunk="%s"}' % hunk
result += '{address="%s"' % loadAddr
result += ',data="%s %s"' % (insn.GetMnemonic(self.target),
insn.GetOperands(self.target))
result += ',function="%s"' % functionName
rawData = insn.GetData(self.target).uint8s
result += ',rawdata="%s"' % ' '.join(["%02x" % x for x in rawData])
if comment:
result += ',comment="%s"' % self.hexencode(comment)
result += ',offset="%s"}' % (loadAddr - base)
self.reportResult(result + ']', args)
def fetchMemory(self, args):
address = args['address']
length = args['length']
error = lldb.SBError()
contents = self.process.ReadMemory(address, length, error)
result = 'address="%s",' % address
result += self.describeError(error)
result += ',contents="%s"' % self.hexencode(contents)
self.reportResult(result, args)
def findValueByExpression(self, exp):
# FIXME: Top level-only for now.
frame = self.currentFrame()
value = frame.FindVariable(exp)
return value
def setValue(self, address, typename, value):
sbtype = self.lookupNativeType(typename)
error = lldb.SBError()
sbaddr = lldb.SBAddress(address, self.target)
sbvalue = self.target.CreateValueFromAddress('x', sbaddr, sbtype)
sbvalue.SetValueFromCString(str(value), error)
def setValues(self, address, typename, values):
sbtype = self.lookupNativeType(typename)
sizeof = sbtype.GetByteSize()
error = lldb.SBError()
for i in range(len(values)):
sbaddr = lldb.SBAddress(address + i * sizeof, self.target)
sbvalue = self.target.CreateValueFromAddress('x', sbaddr, sbtype)
sbvalue.SetValueFromCString(str(values[i]), error)
def assignValue(self, args):
self.reportToken(args)
error = lldb.SBError()
expr = self.hexdecode(args['expr'])
value = self.hexdecode(args['value'])
simpleType = int(args['simpleType'])
lhs = self.findValueByExpression(expr)
typeName = lhs.GetType().GetName()
typeName = typeName.replace('::', '__')
pos = typeName.find('<')
if pos != -1:
typeName = typeName[0:pos]
if typeName in self.qqEditable and not simpleType:
expr = self.parseAndEvaluate(expr)
self.qqEditable[typeName](self, expr, value)
else:
self.parseAndEvaluate(expr + '=' + value)
self.reportResult(self.describeError(error), args)
def watchPoint(self, args):
self.reportToken(args)
ns = self.qtNamespace()
lenns = len(ns)
funcs = self.target.FindGlobalFunctions('.*QApplication::widgetAt', 2, 1)
func = funcs[1]
addr = func.GetFunction().GetStartAddress().GetLoadAddress(self.target)
expr = '((void*(*)(int,int))0x%x)' % addr
#expr = '%sQApplication::widgetAt(%s,%s)' % (ns, args['x'], args['y'])
res = self.parseAndEvaluate(expr)
p = 0 if res is None else res.pointer()
n = ns + 'QWidget'
self.reportResult('selected="0x%x",expr="(%s*)0x%x"' % (p, n, p), args)
def createResolvePendingBreakpointsHookBreakpoint(self, args):
bp = self.target.BreakpointCreateByName('qt_qmlDebugConnectorOpen')
bp.SetOneShot(True)
self.interpreterBreakpointResolvers.append(
lambda: self.resolvePendingInterpreterBreakpoint(args))
# Used in dumper auto test.
class Tester(Dumper):
def __init__(self, binary, args):
Dumper.__init__(self)
lldb.theDumper = self
self.loadDumpers({'token': 1})
error = lldb.SBError()
self.target = self.debugger.CreateTarget(binary, None, None, True, error)
if error.GetType():
self.warn('ERROR: %s' % error)
return
s = threading.Thread(target=self.testLoop, args=(args,))
s.start()
s.join(30)
def testLoop(self, args):
# Disable intermediate reporting.
savedReport = self.report
self.report = lambda stuff: 0
error = lldb.SBError()
launchInfo = lldb.SBLaunchInfo([])
launchInfo.SetWorkingDirectory(os.getcwd())
environmentList = [key + '=' + value for key,value in os.environ.items()]
launchInfo.SetEnvironmentEntries(environmentList, False)
self.process = self.target.Launch(launchInfo, error)
if error.GetType():
self.warn('ERROR: %s' % error)
event = lldb.SBEvent()
listener = self.debugger.GetListener()
while True:
state = self.process.GetState()
if listener.WaitForEvent(100, event):
#DumperBase.warn('EVENT: %s' % event)
state = lldb.SBProcess.GetStateFromEvent(event)
if state == lldb.eStateExited: # 10
break
if state == lldb.eStateStopped: # 5
stoppedThread = None
for i in xrange(0, self.process.GetNumThreads()):
thread = self.process.GetThreadAtIndex(i)
reason = thread.GetStopReason()
#DumperBase.warn('THREAD: %s REASON: %s' % (thread, reason))
if (reason == lldb.eStopReasonBreakpoint or
reason == lldb.eStopReasonException or
reason == lldb.eStopReasonSignal):
stoppedThread = thread
if stoppedThread:
# This seems highly fragile and depending on the 'No-ops' in the
# event handling above.
frame = stoppedThread.GetFrameAtIndex(0)
line = frame.line_entry.line
if line != 0:
self.report = savedReport
self.process.SetSelectedThread(stoppedThread)
self.fetchVariables(args)
#self.describeLocation(frame)
self.report('@NS@%s@' % self.qtNamespace())
#self.report('ENV=%s' % os.environ.items())
#self.report('DUMPER=%s' % self.qqDumpers)
break
else:
d.warn('TIMEOUT')
d.warn('Cannot determined stopped thread')
lldb.SBDebugger.Destroy(self.debugger)
# ------------------------------ For use in LLDB ------------------------------
from pprint import pprint
__module__ = sys.modules[__name__]
DEBUG = False if not hasattr(__module__, 'DEBUG') else DEBUG
class LogMixin:
@staticmethod
def log(message = '', log_caller = False, frame = 1, args = ''):
if not DEBUG:
return
if log_caller:
message = ": " + message if len(message) else ''
# FIXME: Compute based on first frame not in this class?
frame = sys._getframe(frame)
fn = frame.f_code.co_name
localz = frame.f_locals
instance = str(localz["self"]) + "." if 'self' in localz else ''
message = "%s%s(%s)%s" % (instance, fn, args, message)
print(message)
@staticmethod
def log_fn(arg_str = ''):
LogMixin.log(log_caller = True, frame = 2, args = arg_str)
class DummyDebugger(object):
def __getattr__(self, attr):
raise AttributeError("Debugger should not be needed to create summaries")
class SummaryDumper(Dumper, LogMixin):
_instance = None
_lock = threading.RLock()
_type_caches = {}
@staticmethod
def initialize():
SummaryDumper._instance = SummaryDumper()
return SummaryDumper._instance
@staticmethod
@contextmanager
def shared(valobj):
SummaryDumper._lock.acquire()
dumper = SummaryDumper._instance
dumper.target = valobj.target
dumper.process = valobj.process
debugger_id = dumper.target.debugger.GetID()
dumper.typeCache = SummaryDumper._type_caches.get(debugger_id, {})
yield dumper
SummaryDumper._type_caches[debugger_id] = dumper.typeCache
SummaryDumper._lock.release()
@staticmethod
def warn(message):
print("Qt summary warning: %s" % message)
@staticmethod
def showException(message, exType, exValue, exTraceback):
# FIXME: Store for later and report back in summary
SummaryDumper.log("Exception during dumping: %s" % exValue)
def __init__(self):
DumperBase.warn = staticmethod(SummaryDumper.warn)
DumperBase.showException = staticmethod(SummaryDumper.showException)
Dumper.__init__(self, DummyDebugger())
self.setVariableFetchingOptions({
'fancy' : True,
'qobjectnames' : True,
'passexceptions' : True
})
self.dumpermodules = ['qttypes']
self.loadDumpers({})
self.output = ''
def report(self, stuff):
return # Don't mess up lldb output
def lookupNativeTypeInAllModules(self, name):
d.warn('Failed to resolve type %s' % name)
return None
def dump_summary(self, valobj, expanded = False):
try:
from pygdbmi import gdbmiparser
except ImportError:
print("Qt summary provider requires the pygdbmi module, "
"please install using 'sudo /usr/bin/easy_install pygdbmi', "
"and then restart Xcode.")
lldb.debugger.HandleCommand('type category delete Qt')
return None
value = self.fromNativeValue(valobj)
# Expand variable if we need synthetic children
oldExpanded = self.expandedINames
self.expandedINames = [value.name] if expanded else []
savedOutput = self.output
self.output = ''
with TopLevelItem(self, value.name):
self.putItem(value)
# FIXME: Hook into putField, etc to build up object instead of parsing MI
response = gdbmiparser.parse_response("^ok,summary=%s" % self.output)
self.output = savedOutput
self.expandedINames = oldExpanded
summary = response["payload"]["summary"]
#print value, " --> ",
#pprint(summary)
return summary
class SummaryProvider(LogMixin):
DEFAULT_SUMMARY = ''
VOID_PTR_TYPE = None
@staticmethod
def provide_summary(valobj, internal_dict, options = None):
if not __name__ in internal_dict:
# When disabling the import of the Qt summary providers, the
# summary functions are still registered with LLDB, and we will
# get callbacks to provide summaries, but at this point the child
# providers are not active, so instead of providing half-baked and
# confusing summaries we opt to unload all the summaries.
SummaryDumper.log("Module '%s' was unloaded, removing Qt category" % __name__)
lldb.debugger.HandleCommand('type category delete Qt')
return SummaryProvider.DEFAULT_SUMMARY
# FIXME: It would be nice to cache the providers, so that if a
# synthetic child provider has already been created for a valobj,
# we can re-use that when providing summary for the synthetic child
# parent, but we lack a consistent cache key for that to work.
provider = SummaryProvider(valobj)
provider.update()
return provider.get_summary(options)
def __init__(self, valobj, expand = False):
# Prevent recursive evaluation during logging, etc
valobj.SetPreferSyntheticValue(False)
self.log_fn(valobj.path)
self.valobj = valobj
self.expand = expand
if not SummaryProvider.VOID_PTR_TYPE:
with SummaryDumper.shared(self.valobj) as dumper:
SummaryProvider.VOID_PTR_TYPE = dumper.lookupNativeType('void*')
def __repr__(self):
return "%s(%s)" % (self.__class__.__name__, repr(self.key()))
def key(self):
if not hasattr(self, 'valobj'):
return None
return self.valobj.path
def update(self):
self.log_fn()
with SummaryDumper.shared(self.valobj) as dumper:
self.summary = dumper.dump_summary(self.valobj, self.expand)
def get_summary(self, options):
self.log_fn()
summary = self.summary
if not summary:
return '<error: could not get summary from Qt provider>'
encoding = summary.get("valueencoded")
summaryValue = summary["value"]
# FIXME: Share between Creator and LLDB in the python code
if encoding:
special_encodings = {
"empty" : "<empty>",
"minimumitemcount" : "<at least %s items>" % summaryValue,
"undefined" : "<undefined>",
"null" : "<null>",
"itemcount" : "<%s items>" % summaryValue,
"notaccessible" : "<not accessible>",
"optimizedout" : "<optimized out>",
"nullreference" : "<null reference>",
"emptystructure" : "<empty structure>",
"uninitialized" : "<uninitialized>",
"invalid" : "<invalid>",
"notcallable" : "<not callable>",
"outofscope" : "<out of scope>"
}
if encoding in special_encodings:
return special_encodings[encoding]
text_encodings = [
'latin1',
# 'local8bit',
'utf8',
'utf16',
# 'ucs4'
]
if encoding in text_encodings:
try:
binaryvalue = summaryValue.decode('hex')
# LLDB expects UTF-8
return "\"%s\"" % (binaryvalue.decode(encoding).encode('utf8'))
except:
return "<failed to decode '%s' as '%s'>" % (summaryValue, encoding)
# FIXME: Support these
other_encodings = ['int', 'uint', 'float', 'juliandate', 'juliandateandmillisecondssincemidnight',
'millisecondssincemidnight', 'ipv6addressandhexscopeid', 'datetimeinternal']
summaryValue += " <encoding='%s'>" % encoding
if self.valobj.value:
# If we've resolved a pointer that matches what LLDB natively chose,
# then use that instead of printing the values twice. FIXME, ideally
# we'd have the same pointer format as LLDB uses natively.
if re.sub('^0x0*', '', self.valobj.value) == re.sub('^0x0*', '', summaryValue):
return SummaryProvider.DEFAULT_SUMMARY
return summaryValue.strip()
class SyntheticChildrenProvider(SummaryProvider):
def __init__(self, valobj, dict):
SummaryProvider.__init__(self, valobj, expand = True)
self.summary = None
self.synthetic_children = []
def num_native_children(self):
return self.valobj.GetNumChildren()
def num_children(self):
self.log("native: %d synthetic: %d" %
(self.num_native_children(), len(self.synthetic_children)), True)
return self._num_children()
def _num_children(self):
return self.num_native_children() + len(self.synthetic_children)
def has_children(self):
return self._num_children() > 0
def get_child_index(self, name):
# FIXME: Do we ever need this to return something?
self.log_fn(name)
return None
def get_child_at_index(self, index):
self.log_fn(index)
if index < 0 or index > self._num_children():
return None
if not self.valobj.IsValid() or not self.summary:
return None
if index < self.num_native_children():
# Built-in children
value = self.valobj.GetChildAtIndex(index)
else:
# Synthetic children
index -= self.num_native_children()
child = self.synthetic_children[index]
name = child.get('name', "[%s]" % index)
value = self.create_value(child, name)
return value
def create_value(self, child, name = ''):
child_type = child.get('type', self.summary.get('childtype'))
value = None
if child_type:
if 'address' in child:
value_type = None
with SummaryDumper.shared(self.valobj) as dumper:
value_type = dumper.lookupNativeType(child_type)
if not value_type or not value_type.IsValid():
return None
address = int(child['address'], 16)
# Create as void* so that the value is fully initialized before
# we trigger our own summary/child providers recursively.
value = self.valobj.synthetic_child_from_address(name, address,
SummaryProvider.VOID_PTR_TYPE).Cast(value_type)
else:
self.log("Don't know how to create value for child %s" % child)
# FIXME: Figure out if we ever will hit this case, and deal with it
#value = self.valobj.synthetic_child_from_expression(name,
# "(%s)(%s)" % (child_type, child['value']))
else:
# FIXME: Find a good way to deal with synthetic values
self.log("Don't know how to create value for child %s" % child)
# FIXME: Handle value type or value errors consistently
return value
def update(self):
SummaryProvider.update(self)
self.synthetic_children = []
if 'children' not in self.summary:
return
dereference_child = None
for child in self.summary['children']:
if not child or not type(child) is dict:
continue
# Skip base classes, they are built-in children
# FIXME: Is there a better check than 'iname'?
if 'iname' in child:
continue
name = child.get('name')
if name:
if name == '*':
# No use for this unless the built in children failed to resolve
dereference_child = child
continue
if name.startswith('['):
# Skip pure synthetic children until we can deal with them
continue
if name.startswith('#'):
# Skip anonymous unions, lookupNativeType doesn't handle them (yet),
# so it triggers the slow lookup path, and the union is provided as
# a native child anyways.
continue
# Skip native children
if self.valobj.GetChildMemberWithName(name):
continue
self.synthetic_children.append(child)
# Xcode will sometimes fail to show the children of pointers in
# the debugger UI, even if dereferencing the pointer works fine.
# We fall back to the special child reported by the Qt dumper.
if not self.valobj.GetNumChildren() and dereference_child:
self.valobj = self.create_value(dereference_child)
self.update()
def __lldb_init_module(debugger, internal_dict):
# Module is being imported in an LLDB session
dumper = SummaryDumper.initialize()
type_category = 'Qt'
# Concrete types
summary_function = "%s.%s.%s" % (__name__, 'SummaryProvider', 'provide_summary')
types = map(lambda x: x.replace('__', '::'), dumper.qqDumpers)
debugger.HandleCommand("type summary add -F %s -w %s %s" \
% (summary_function, type_category, ' '.join(types)))
regex_types = list(map(lambda x: "'" + x + "'", dumper.qqDumpersEx.keys()))
if regex_types:
debugger.HandleCommand("type summary add -x -F %s -w %s %s" \
% (summary_function, type_category, ' '.join(regex_types)))
# Global catch-all for Qt classes
debugger.HandleCommand("type summary add -x '^Q.*$' -F %s -w %s" \
% (summary_function, type_category))
# Named summary ('frame variable foo --summary Qt')
debugger.HandleCommand("type summary add --name Qt -F %s -w %s" \
% (summary_function, type_category))
# Synthetic children
debugger.HandleCommand("type synthetic add -x '^Q.*$' -l %s -w %s" \
% ("lldbbridge.SyntheticChildrenProvider", type_category))
debugger.HandleCommand('type category enable %s' % type_category)
if not __name__ == 'qt':
# Make available under global 'qt' name for consistency
internal_dict['qt'] = internal_dict[__name__]
if __name__ == "lldbbridge":
try:
theDumper = Dumper()
except Exception as error:
print('@\nstate="enginesetupfailed",error="{}"@\n'.format(error))