forked from qt-creator/qt-creator
Previously lldb never actually attached to the process running in the terminal, but started its own copy. Since the process is interrupted by the terminal stub already, code was added to automatically continue the process. "Start and break on main" and "Run in Terminal" also did not work together and are now fixed. Change-Id: Iaeb6e7dd0f511f3bf195ab5d0008856b310615d9 Reviewed-by: hjk <hjk@qt.io> Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
2441 lines
101 KiB
Python
2441 lines
101 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 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 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()
|
|
DumperBase.warn("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()
|
|
DumperBase.warn('Found function QObject::property: 0x%0x' % self.qtPropertyFunc)
|
|
DumperBase.warn('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:
|
|
f = lldb.SBFileSpec()
|
|
f.SetFilename(self.executable_)
|
|
|
|
launchInfo = lldb.SBLaunchInfo(self.processArgs_)
|
|
#launchInfo.SetWorkingDirectory(self.workingDirectory_)
|
|
launchInfo.SetWorkingDirectory('/tmp')
|
|
if self.platform_ == 'remote-android':
|
|
launchInfo.SetWorkingDirectory('/data/local/tmp')
|
|
launchInfo.SetEnvironmentEntries(self.environment_, False)
|
|
launchInfo.SetExecutableFile(f, True)
|
|
|
|
DumperBase.warn("TARGET: %s" % self.target)
|
|
self.process = self.target.Launch(launchInfo, error)
|
|
DumperBase.warn("PROCESS: %s" % self.process)
|
|
|
|
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_:
|
|
# When using a terminal, 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, _=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 = 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)
|
|
|
|
# ------------------------------ 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 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: 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 __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))
|