forked from qt-creator/qt-creator
They get in the way when working in that area. Change-Id: Id59c83472a6ce093ca9b31ad9e94ec638b4a1df8 Reviewed-by: Christian Stenger <christian.stenger@qt.io>
2506 lines
104 KiB
Python
2506 lines
104 KiB
Python
# Copyright (C) 2016 The Qt Company Ltd.
|
|
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
|
|
|
import inspect
|
|
import os
|
|
import platform
|
|
import re
|
|
import sys
|
|
import threading
|
|
import time
|
|
import lldb
|
|
import utils
|
|
from utils import DebuggerStartMode, BreakpointType, TypeCode, LogChannel
|
|
|
|
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:
|
|
if sys.version_info[0] >= 3:
|
|
if sys.version_info[1] > 3:
|
|
from importlib import reload
|
|
else:
|
|
def reload(m): print('Unsupported Python version - not reloading %s' % str(m))
|
|
reload(sys.modules['dumper'])
|
|
|
|
from dumper import DumperBase, SubItem, Children, TopLevelItem
|
|
|
|
#######################################################################
|
|
#
|
|
# Helpers
|
|
#
|
|
#######################################################################
|
|
|
|
qqWatchpointOffset = 10000
|
|
_c_str_trans = None
|
|
|
|
if sys.version_info[0] >= 3:
|
|
_c_str_trans = str.maketrans({"\n": "\\n", '"':'\\"', "\\":"\\\\"})
|
|
|
|
def toCString(s):
|
|
if _c_str_trans is not None:
|
|
return str(s).translate(_c_str_trans)
|
|
else:
|
|
return str(s).replace('\\', '\\\\').replace('\n', '\\n').replace('"', '\\"')
|
|
|
|
def fileNameAsString(file):
|
|
return toCString(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.fakeAddress_ = None
|
|
self.fakeLAddress_ = None
|
|
self.eventState = lldb.eStateInvalid
|
|
|
|
self.executable_ = None
|
|
self.symbolFile_ = 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 = []
|
|
|
|
DumperBase.warn = Dumper.warn_impl
|
|
self.report('lldbversion=\"%s\"' % lldb.SBDebugger.GetVersionString())
|
|
|
|
@staticmethod
|
|
def warn_impl(message):
|
|
if message[-1:] == '\n':
|
|
message += '\n'
|
|
print('@\nbridgemessage={msg="%s",channel="%s"}\n@'
|
|
% (message.replace('"', '$'), LogChannel.AppError))
|
|
|
|
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 address is not 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()
|
|
display = str(nativeValue).split(' = ')
|
|
if len(display) == 2:
|
|
verbose = display[1]
|
|
if '|' in verbose and not verbose.startswith('('):
|
|
verbose = '(' + verbose + ')'
|
|
else:
|
|
verbose = intval
|
|
val.ldisplay = '%s (%d)' % (verbose, 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_)
|
|
if value.laddress:
|
|
fakeAddress = lldb.SBAddress(value.laddress, self.target)
|
|
fakeLAddress = value.laddress
|
|
else:
|
|
fakeAddress = self.fakeAddress_
|
|
fakeLAddress = self.fakeLAddress_
|
|
|
|
fakeValue = self.target.CreateValueFromAddress('x', fakeAddress, 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 - fakeLAddress
|
|
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(),
|
|
self.nativeTypeId(nativeType))
|
|
|
|
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)
|
|
targetType.tdata = targetType.tdata.copy()
|
|
targetType.tdata.name = targetTypeName
|
|
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, typeId)
|
|
tdata.name = typeName
|
|
tdata.lbitsize = nativeType.GetByteSize() * 8
|
|
if code == lldb.eTypeClassBuiltin:
|
|
if utils.isFloatingPointTypeName(typeName):
|
|
tdata.code = TypeCode.Float
|
|
elif utils.isIntegralTypeName(typeName):
|
|
tdata.code = TypeCode.Integral
|
|
elif typeName in ('__int128', 'unsigned __int128'):
|
|
tdata.code = TypeCode.Integral
|
|
elif typeName == 'void':
|
|
tdata.code = TypeCode.Void
|
|
elif typeName == 'wchar_t':
|
|
tdata.code = TypeCode.Integral
|
|
elif typeName in ("char16_t", "char32_t", "char8_t"):
|
|
tdata.code = TypeCode.Integral
|
|
else:
|
|
self.warn('UNKNOWN TYPE KEY: %s: %s' % (typeName, code))
|
|
elif code == lldb.eTypeClassEnumeration:
|
|
tdata.code = TypeCode.Enum
|
|
tdata.enumDisplay = lambda intval, addr, form: \
|
|
self.nativeTypeEnumDisplay(nativeType, intval, form)
|
|
elif code in (lldb.eTypeClassComplexInteger, lldb.eTypeClassComplexFloat):
|
|
tdata.code = TypeCode.Complex
|
|
elif code in (lldb.eTypeClassClass, lldb.eTypeClassStruct, lldb.eTypeClassUnion):
|
|
tdata.code = TypeCode.Struct
|
|
tdata.lalignment = lambda: \
|
|
self.nativeStructAlignment(nativeType)
|
|
tdata.lfields = lambda value: \
|
|
self.listMembers(value, nativeType)
|
|
tdata.templateArguments = lambda: \
|
|
self.listTemplateParametersHelper(nativeType)
|
|
elif code == lldb.eTypeClassFunction:
|
|
tdata.code = TypeCode.Function
|
|
elif code == lldb.eTypeClassMemberPointer:
|
|
tdata.code = TypeCode.MemberPointer
|
|
# 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):
|
|
# Don't use GetDisplayTypeName since LLDB removed the inline namespace __1
|
|
# https://reviews.llvm.org/D74478
|
|
return nativeType.GetName()
|
|
|
|
def nativeTypeId(self, nativeType):
|
|
if nativeType and (nativeType.GetTypeClass() == lldb.eTypeClassTypedef):
|
|
nativeTargetType = nativeType.GetUnqualifiedType()
|
|
if hasattr(nativeTargetType, 'GetCanonicalType'):
|
|
nativeTargetType = nativeTargetType.GetCanonicalType()
|
|
return '%s{%s}' % (nativeType.name, nativeTargetType.name)
|
|
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 'windows' in self.target.triple
|
|
|
|
def isQnxTarget(self):
|
|
return False
|
|
|
|
def isArmArchitecture(self):
|
|
return 'arm' in self.target.triple
|
|
|
|
def isMsvcTarget(self):
|
|
return 'msvc' in self.target.triple
|
|
|
|
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 fetchInternalFunctions(self):
|
|
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)
|
|
|
|
def fetchQtVersionAndNamespace(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
|
|
|
|
return (qtNamespace, qtVersion)
|
|
|
|
try:
|
|
versionValue = self.target.EvaluateExpression('qtHookData[2]')
|
|
if versionValue.IsValid():
|
|
return ('', versionValue.unsigned)
|
|
except:
|
|
pass
|
|
|
|
return ('', self.fallbackQtVersion)
|
|
|
|
def qtVersionAndNamespace(self):
|
|
qtVersionAndNamespace = None
|
|
try:
|
|
qtVersionAndNamespace = self.fetchQtVersionAndNamespace()
|
|
self.report("Detected Qt Version: 0x%0x (namespace='%s')" %
|
|
(qtVersionAndNamespace[1], qtVersionAndNamespace[0] or "no namespace"))
|
|
except Exception as e:
|
|
DumperBase.warn('[lldb] Error detecting Qt version: %s' % e)
|
|
|
|
try:
|
|
self.fetchInternalFunctions()
|
|
self.report('Found function QObject::property: 0x%0x' % self.qtPropertyFunc)
|
|
self.report('Found function QObject::customEvent: 0x%0x' % self.qtCustomEventFunc)
|
|
except Exception as e:
|
|
DumperBase.warn('[lldb] Error fetching internal Qt functions: %s' % e)
|
|
|
|
# Cache version information by overriding this function.
|
|
self.qtVersionAndNamespace = lambda: qtVersionAndNamespace
|
|
return qtVersionAndNamespace
|
|
|
|
def qtNamespace(self):
|
|
return self.qtVersionAndNamespace()[0]
|
|
|
|
def qtVersion(self):
|
|
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"' % toCString(result.GetOutput()))
|
|
else:
|
|
self.report('error="%s"' % toCString(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 typeobj is not 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))
|
|
if re.match(r'^.+\(.*\)', nonPrefixedName) is not None:
|
|
return lldb.SBType()
|
|
|
|
typeobjlist = self.target.FindTypes(nonPrefixedName)
|
|
if typeobjlist.IsValid():
|
|
for typeobj in typeobjlist:
|
|
n = self.canonicalTypeName(self.removeTypePrefix(typeobj.GetName()))
|
|
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
|
|
|
|
# For QMetaType based typenames we have to re-format the type name.
|
|
# Converts "T<A,B<C,D>>"" to "T<A, B<C, D> >" since FindFirstType
|
|
# expects it that way.
|
|
name = name.replace(',', ', ').replace('>>', '> >')
|
|
typeobj = self.target.FindFirstType(name)
|
|
if typeobj.IsValid():
|
|
self.typeCache[name] = typeobj
|
|
return typeobj
|
|
|
|
return lldb.SBType()
|
|
|
|
def setupInferior(self, args):
|
|
""" Set up SBTarget instance """
|
|
|
|
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)
|
|
self.firstStop_ = True
|
|
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.symbolFile_ = args['symbolfile'];
|
|
self.workingDirectory_ = args.get('workingdirectory', '')
|
|
if self.workingDirectory_ == '':
|
|
try:
|
|
self.workingDirectory_ = os.getcwd()
|
|
except: # Could have been deleted in the mean time.
|
|
pass
|
|
|
|
if self.platform_:
|
|
self.debugger.SetCurrentPlatform(self.platform_)
|
|
# sysroot has to be set *after* the platform
|
|
if self.sysRoot_:
|
|
self.debugger.SetCurrentPlatformSDKRoot(self.sysRoot_)
|
|
|
|
# There seems to be some kind of unexpected behavior, or bug in LLDB
|
|
# such that target.Attach(attachInfo, error) below does not create
|
|
# a valid process if this symbolFile here is valid.
|
|
if self.startMode_ == DebuggerStartMode.AttachExternal:
|
|
self.symbolFile_ = ''
|
|
|
|
self.target = self.debugger.CreateTarget(
|
|
self.symbolFile_, None, self.platform_, True, error)
|
|
|
|
if not error.Success():
|
|
self.report(self.describeError(error))
|
|
self.reportState('enginerunfailed')
|
|
return
|
|
|
|
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)
|
|
|
|
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, toCString(error), toCString(self.executable_)), args)
|
|
|
|
def runEngine(self, args):
|
|
""" Set up SBProcess instance """
|
|
|
|
error = lldb.SBError()
|
|
|
|
if self.startMode_ == DebuggerStartMode.AttachExternal:
|
|
attach_info = lldb.SBAttachInfo(self.attachPid_)
|
|
if self.breakOnMain_:
|
|
self.createBreakpointAtMain()
|
|
self.process = self.target.Attach(attach_info, error)
|
|
if not error.Success():
|
|
self.reportState('enginerunfailed')
|
|
else:
|
|
self.report('pid="%s"' % self.process.GetProcessID())
|
|
self.reportState('enginerunandinferiorstopok')
|
|
|
|
elif (self.startMode_ == DebuggerStartMode.AttachToRemoteServer
|
|
and self.platform_ == 'remote-android'):
|
|
|
|
connect_options = lldb.SBPlatformConnectOptions(self.remoteChannel_)
|
|
res = self.target.GetPlatform().ConnectRemote(connect_options)
|
|
|
|
DumperBase.warn("CONNECT: %s %s platform: %s %s" % (res,
|
|
self.remoteChannel_,
|
|
self.target.GetPlatform().GetName(),
|
|
self.target.GetPlatform().IsConnected()))
|
|
if not res.Success():
|
|
self.report(self.describeError(res))
|
|
self.reportState('enginerunfailed')
|
|
return
|
|
|
|
attach_info = lldb.SBAttachInfo(self.attachPid_)
|
|
self.process = self.target.Attach(attach_info, error)
|
|
if not error.Success():
|
|
self.report(self.describeError(error))
|
|
self.reportState('enginerunfailed')
|
|
else:
|
|
self.report('pid="%s"' % self.process.GetProcessID())
|
|
self.reportState('enginerunandinferiorstopok')
|
|
|
|
elif (self.startMode_ == DebuggerStartMode.AttachToRemoteServer
|
|
or self.startMode_ == DebuggerStartMode.AttachToRemoteProcess):
|
|
if self.platform_ == 'remote-ios':
|
|
self.process = self.target.ConnectRemote(
|
|
self.debugger.GetListener(),
|
|
self.remoteChannel_, None, error)
|
|
else:
|
|
if self.platform_ == "remote-macosx":
|
|
self.report("Connecting to remote target: connect://%s" % self.remoteChannel_)
|
|
self.process = self.target.ConnectRemote(
|
|
self.debugger.GetListener(),
|
|
"connect://" + self.remoteChannel_, None, error)
|
|
|
|
if not error.Success():
|
|
self.report("Failed to connect to remote target: %s" % error.GetCString())
|
|
self.reportState('enginerunfailed')
|
|
return
|
|
|
|
if self.breakOnMain_:
|
|
self.createBreakpointAtMain()
|
|
|
|
DumperBase.warn("PROCESS: %s (%s)" % (self.process, error.Success() and "Success" or error.GetCString()))
|
|
elif self.platform_ == "remote-linux":
|
|
self.report("Connecting to remote target: connect://%s" % self.remoteChannel_)
|
|
|
|
platform = self.target.GetPlatform()
|
|
url = "connect://" + self.remoteChannel_
|
|
conOptions = lldb.SBPlatformConnectOptions(url)
|
|
error = platform.ConnectRemote(conOptions)
|
|
|
|
if not error.Success():
|
|
self.report("Failed to connect to remote target (%s): %s" % (url, error.GetCString()))
|
|
self.reportState('enginerunfailed')
|
|
return
|
|
|
|
f = lldb.SBFileSpec()
|
|
f.SetFilename(self.executable_)
|
|
launchInfo = lldb.SBLaunchInfo(self.processArgs_)
|
|
launchInfo.SetWorkingDirectory(self.workingDirectory_)
|
|
launchInfo.SetWorkingDirectory('/tmp')
|
|
launchInfo.SetEnvironmentEntries(self.environment_, False)
|
|
launchInfo.SetExecutableFile(f, True)
|
|
self.process = self.target.Launch(launchInfo, error)
|
|
|
|
if not error.Success():
|
|
self.report("Failed to launch remote target: %s" % (error.GetCString()))
|
|
self.reportState('enginerunfailed')
|
|
return
|
|
else:
|
|
self.report("Process has launched.")
|
|
|
|
if self.breakOnMain_:
|
|
self.createBreakpointAtMain()
|
|
|
|
else:
|
|
self.report("Unsupported platform: %s" % self.platform_)
|
|
self.reportState('enginerunfailed')
|
|
return
|
|
|
|
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')
|
|
|
|
s = threading.Thread(target=self.loop, args=[])
|
|
s.start()
|
|
|
|
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="%d",' % 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"}' % toCString(desc.GetData())
|
|
return result
|
|
|
|
def describeStatus(self, status):
|
|
return 'status="%s",' % toCString(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 range(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 range(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"' % toCString(thread.GetQueueName())
|
|
result += ',stop-reason="%s"' % self.stopReason(thread.GetStopReason())
|
|
result += ',state="%s"' % state
|
|
result += ',name="%s"' % toCString(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 range(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))
|
|
extraQml = int(args.get('extraqml', '0'))
|
|
|
|
limit = args.get('stacklimit', -1)
|
|
(n, isLimited) = (limit, True) if limit > 0 else (thread.GetNumFrames(), False)
|
|
self.currentCallContext = None
|
|
result = 'stack={current-thread="%d"' % thread.GetThreadID()
|
|
result += ',frames=['
|
|
|
|
ii = 0
|
|
if extraQml:
|
|
ns = self.qtNamespace()
|
|
needle = self.qtNamespace() + 'QV4::ExecutionEngine'
|
|
pats = [
|
|
'{0}qt_v4StackTraceForEngine((void*)0x{1:x})',
|
|
'{0}qt_v4StackTrace((({0}QV4::ExecutionEngine *)0x{1:x})->currentContext())',
|
|
'{0}qt_v4StackTrace((({0}QV4::ExecutionEngine *)0x{1:x})->currentContext)',
|
|
]
|
|
done = False
|
|
while ii < n and not done:
|
|
res = None
|
|
frame = thread.GetFrameAtIndex(ii)
|
|
if not frame.IsValid():
|
|
break
|
|
for variable in frame.GetVariables(True, True, False, True):
|
|
if not variable.GetType().IsPointerType():
|
|
continue
|
|
derefvar = variable.Dereference()
|
|
if derefvar.GetType().GetName() != needle:
|
|
continue
|
|
addr = derefvar.GetLoadAddress()
|
|
for pat in pats:
|
|
exp = pat.format(ns, addr)
|
|
val = frame.EvaluateExpression(exp)
|
|
err = val.GetError()
|
|
res = str(val)
|
|
if err.Fail():
|
|
continue
|
|
pos = res.find('"stack=[')
|
|
if pos == -1:
|
|
continue
|
|
res = res[pos + 8:-2]
|
|
res = res.replace('\\\"', '\"')
|
|
res = res.replace('func=', 'function=')
|
|
result += res
|
|
done = True
|
|
break
|
|
ii += 1
|
|
# if we have not found a qml stack do not omit original stack
|
|
if not done:
|
|
DumperBase.warn("Failed to fetch qml stack - you need Qt debug information")
|
|
ii = 0
|
|
|
|
for i in range(n - ii):
|
|
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 = toCString(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"' % toCString(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"},' % toCString(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()
|
|
|
|
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.putExpandable()
|
|
if self.isExpanded():
|
|
with Children(self):
|
|
statics = frame.GetVariables(False, False, True, False)
|
|
if len(statics):
|
|
for i in range(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()
|
|
|
|
# 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.takeOutput(), args)
|
|
|
|
|
|
def fetchRegisters(self, args=None):
|
|
if not self.process:
|
|
self.reportResult('process="none",registers=[]', args)
|
|
return
|
|
|
|
frame = self.currentFrame()
|
|
if not frame or not frame.IsValid():
|
|
self.reportResult('frame="none",registers=[]', args)
|
|
return
|
|
|
|
result = 'registers=['
|
|
for group in frame.GetRegisters():
|
|
for reg in group:
|
|
data = reg.GetData()
|
|
if data.GetByteOrder() == lldb.eByteOrderLittle:
|
|
value = ''.join(["%02x" % x for x in reversed(data.uint8s)])
|
|
else:
|
|
value = ''.join(["%02x" % x for x in data.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"' % toCString(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"' % toCString(result.GetOutput()), args)
|
|
else:
|
|
self.reportResult('error="%s"' % toCString(result.GetError()), args)
|
|
|
|
def report(self, stuff):
|
|
with self.outputLock:
|
|
sys.stdout.write("@\n" + stuff + "@\n")
|
|
sys.stdout.flush()
|
|
|
|
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, toCString(out.GetData()),
|
|
toCString(msg), flavor, self.stateName(state), bp))
|
|
|
|
if state == lldb.eStateExited:
|
|
self.eventState = state
|
|
if not self.isShuttingDown_:
|
|
self.reportState("inferiorexited")
|
|
self.report('exited={status="%d",desc="%s"}'
|
|
% (self.process.GetExitStatus(),
|
|
toCString(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")
|
|
else:
|
|
self.reportState("stopped")
|
|
if self.firstStop_:
|
|
self.firstStop_ = False
|
|
if self.useTerminal_ or self.platform_ == "remote-macosx":
|
|
# When using a terminal or remote debugging macosx apps,
|
|
# the process will be interrupted on startup.
|
|
# We therefore need to continue it here.
|
|
self.process.Continue()
|
|
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:
|
|
self.handleInferiorOutput(self.process.GetSTDOUT, "stdout")
|
|
elif eventType == lldb.SBProcess.eBroadcastBitSTDERR:
|
|
self.handleInferiorOutput(self.process.GetSTDERR, "stderr")
|
|
elif eventType == lldb.SBProcess.eBroadcastBitProfileData:
|
|
pass
|
|
|
|
def handleInferiorOutput(self, proc, channel):
|
|
while True:
|
|
msg = proc(1024)
|
|
if msg == None or len(msg) == 0:
|
|
break
|
|
self.report('output={channel="%s",data="%s"}' % (channel, self.hexencode(msg)))
|
|
|
|
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="%d"' % (1 if bp.IsValid() else 0)
|
|
result += ',hitcount="%d"' % bp.GetHitCount()
|
|
if bp.IsValid():
|
|
if isinstance(bp, lldb.SBBreakpoint):
|
|
result += ',threadid="%d"' % bp.GetThreadID()
|
|
result += ',oneshot="%d"' % (1 if bp.IsOneShot() else 0)
|
|
cond = bp.GetCondition()
|
|
result += ',condition="%s"' % self.hexencode("" if cond is None else cond)
|
|
result += ',enabled="%d"' % (1 if bp.IsEnabled() else 0)
|
|
result += ',valid="%d"' % (1 if bp.IsValid() else 0)
|
|
result += ',ignorecount="%d"' % bp.GetIgnoreCount()
|
|
if bp.IsValid() and isinstance(bp, lldb.SBBreakpoint):
|
|
result += ',locations=['
|
|
lineEntry = None
|
|
for i in range(bp.GetNumLocations()):
|
|
loc = bp.GetLocationAtIndex(i)
|
|
addr = loc.GetAddress()
|
|
lineEntry = addr.GetLineEntry()
|
|
result += '{locid="%d"' % loc.GetID()
|
|
result += ',function="%s"' % addr.GetFunction().GetName()
|
|
result += ',enabled="%d"' % (1 if loc.IsEnabled() else 0)
|
|
result += ',resolved="%d"' % (1 if loc.IsResolved() else 0)
|
|
result += ',valid="%d"' % (1 if loc.IsValid() else 0)
|
|
result += ',ignorecount="%d"' % loc.GetIgnoreCount()
|
|
result += ',file="%s"' % toCString(lineEntry.GetFileSpec())
|
|
result += ',line="%d"' % lineEntry.GetLine()
|
|
result += ',addr="%s"},' % addr.GetFileAddress()
|
|
result += ']'
|
|
if lineEntry is not None:
|
|
result += ',file="%s"' % toCString(lineEntry.GetFileSpec())
|
|
result += ',line="%d"' % 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 + \"}")',
|
|
'sys.stdout.flush()',
|
|
'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="%d",enabled="%d",locid="%d"'
|
|
% (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="%d"' % int(res), args)
|
|
|
|
def fetchModules(self, args):
|
|
result = 'modules=['
|
|
for i in range(self.target.GetNumModules()):
|
|
module = self.target.GetModuleAtIndex(i)
|
|
result += '{file="%s"' % toCString(module.file.fullpath)
|
|
result += ',name="%s"' % toCString(module.file.basename)
|
|
result += ',addrsize="%d"' % 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="%d"],' % 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 range(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="%d"' % (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(), toCString(result.GetOutput()),
|
|
toCString(result.GetError())))
|
|
|
|
def activateFrame(self, args):
|
|
self.reportToken(args)
|
|
frame = max(0, int(args['index'])) # Can be -1 in all-asm stacks
|
|
self.currentThread().SetSelectedFrame(frame)
|
|
self.reportResult('', args)
|
|
|
|
def selectThread(self, args):
|
|
self.reportToken(args)
|
|
self.process.SetSelectedThreadByID(int(args['id']))
|
|
self.reportResult('', args)
|
|
|
|
def fetchFullBacktrace(self, args):
|
|
command = 'thread backtrace all'
|
|
result = lldb.SBCommandReturnObject()
|
|
self.debugger.GetCommandInterpreter().HandleCommand(command, result)
|
|
self.reportResult('fulltrace="%s"' % self.hexencode(result.GetOutput()), args)
|
|
|
|
def executeDebuggerCommand(self, args):
|
|
self.reportToken(args)
|
|
result = lldb.SBCommandReturnObject()
|
|
command = args['command']
|
|
self.debugger.GetCommandInterpreter().HandleCommand(command, result)
|
|
success = result.Succeeded()
|
|
output = toCString(result.GetOutput())
|
|
error = toCString(str(result.GetError()))
|
|
self.report('success="%d",output="%s",error="%s"' % (success, output, error))
|
|
|
|
def executeRoundtrip(self, args):
|
|
self.reportResult('', args)
|
|
|
|
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="%d"' % lineNumber
|
|
result += ',file="%s"' % toCString(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 range(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.fakeAddress_ = frame.GetPC()
|
|
self.fakeLAddress_ = frame.GetPCAddress()
|
|
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:
|
|
self.warn('TIMEOUT')
|
|
self.warn('Cannot determined stopped thread')
|
|
|
|
lldb.SBDebugger.Destroy(self.debugger)
|
|
|
|
if 'QT_CREATOR_LLDB_PROCESS' in os.environ:
|
|
# Initialize Qt Creator dumper
|
|
try:
|
|
theDumper = Dumper()
|
|
except Exception as error:
|
|
print('@\nstate="enginesetupfailed",error="{}"@\n'.format(error))
|
|
|
|
# ------------------------------ For use in LLDB ------------------------------
|
|
|
|
debug = print if 'QT_LLDB_SUMMARY_PROVIDER_DEBUG' in os.environ \
|
|
else lambda *a, **k: None
|
|
|
|
debug(f"Loading lldbbridge.py from {__file__}")
|
|
|
|
class LogMixin():
|
|
@staticmethod
|
|
def log(message='', log_caller=False, frame=1, args=''):
|
|
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)
|
|
debug(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 dump_summary(self, valobj, expanded=False):
|
|
from pygdbmi import gdbmiparser
|
|
|
|
value = self.fromNativeValue(valobj)
|
|
|
|
# Expand variable if we need synthetic children
|
|
oldExpanded = self.expandedINames
|
|
self.expandedINames = {value.name: 100} 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.takeOutput())
|
|
|
|
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 __name__ not 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:
|
|
decodedValue = Dumper.hexdecode(summaryValue, encoding)
|
|
# LLDB expects UTF-8 for python 2
|
|
if sys.version_info[0] < 3:
|
|
return "\"%s\"" % (decodedValue.encode('utf8'))
|
|
return '"' + decodedValue + '"'
|
|
except:
|
|
return "<failed to decode '%s' as '%s': %s>" % (summaryValue, encoding, sys.exc_info()[1])
|
|
|
|
# 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 isinstance(child, 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 ensure_gdbmiparser():
|
|
try:
|
|
from pygdbmi import gdbmiparser
|
|
return True
|
|
except ImportError:
|
|
try:
|
|
if not 'QT_LLDB_SUMMARY_PROVIDER_NO_AUTO_INSTALL' in os.environ:
|
|
print("Required module 'pygdbmi' not installed. Installing automatically...")
|
|
import subprocess
|
|
python3 = os.path.join(sys.exec_prefix, 'bin', 'python3')
|
|
process = subprocess.run([python3, '-m', 'pip',
|
|
'--disable-pip-version-check',
|
|
'install', '--user', 'pygdbmi' ],
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.STDOUT)
|
|
print(process.stdout.decode('utf-8').strip())
|
|
process.check_returncode()
|
|
from importlib import invalidate_caches
|
|
invalidate_caches()
|
|
from pygdbmi import gdbmiparser
|
|
return True
|
|
except Exception as e:
|
|
print(e)
|
|
|
|
print("Qt summary provider requires the pygdbmi module. Please install\n" \
|
|
"manually using '/usr/bin/pip3 install pygdbmi', and restart Xcode.")
|
|
return False
|
|
|
|
|
|
def __lldb_init_module(debugger, internal_dict):
|
|
# Module is being imported in an LLDB session
|
|
if 'QT_CREATOR_LLDB_PROCESS' in os.environ:
|
|
# Let Qt Creator take care of its own dumper
|
|
return
|
|
|
|
debug("Initializing module with", debugger)
|
|
|
|
if not ensure_gdbmiparser():
|
|
return
|
|
|
|
if not __name__ == 'qt':
|
|
# Make available under global 'qt' name for consistency,
|
|
# and so we can refer to SyntheticChildrenProvider below.
|
|
internal_dict['qt'] = internal_dict[__name__]
|
|
|
|
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"
|
|
% ("qt.SyntheticChildrenProvider", type_category))
|
|
|
|
debugger.HandleCommand('type category enable %s' % type_category)
|