# 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 json
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
# Does not work when the module is injected via stdio
# if 'dumper' in sys.modules:
#     from importlib import reload
#     reload(sys.modules['dumper'])

from dumper import DumperBase, SubItem, Children, TopLevelItem

#######################################################################
#
# Helpers
#
#######################################################################

qqWatchpointOffset = 10000
_c_str_trans = str.maketrans({"\n": "\\n", '"':'\\"', "\\":"\\\\"})

def toCString(s):
    return str(s).translate(_c_str_trans)

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.deviceUuid_ = None
        self.dyldImageSuffix = None
        self.dyldLibraryPath = None
        self.dyldFrameworkPath = None

        self.isShuttingDown_ = False
        self.isInterrupting_ = False
        self.interpreterBreakpointResolvers = []
        # Internal (not user-visible) breakpoint ids - see handleBreakpointEvent().
        self.internalBreakpointIds = set()

        self.report('lldbversion=\"%s\"' % lldb.SBDebugger.GetVersionString())

    def warn(self, msg):
        #self.put('{name="%s",value="",type="",numchild="0"},' % toCString(msg))
        if msg[-1:] == '\n':
            msg += '\n'
        print('@\nbridgemessage={msg="%s",channel="%s"}\n@'
                % (msg.replace('"', '$'), LogChannel.LogWarning))

    def fromNativeValue(self, nativeValue):
        self.check(isinstance(nativeValue, lldb.SBValue))
        nativeType = nativeValue.GetType()
        type_name = nativeType.GetName()
        code = nativeType.GetTypeClass()

        # Display the result of GetSummary() for Core Foundation string
        # and string-like types.
        summary = None
        if self.useFancy:
            if (type_name.startswith('CF')
                    or type_name.startswith('__CF')
                    or type_name.startswith('NS')
                    or type_name.startswith('__NSCF')):
                if code == lldb.eTypeClassPointer:
                    summary = nativeValue.Dereference().GetSummary()
                elif code == lldb.eTypeClassReference:
                    summary = nativeValue.Dereference().GetSummary()
                else:
                    summary = nativeValue.GetSummary()
                # GetSummary() uses LLDB's built-in ObjC formatters (Apple only).
                # Fall back to GetObjectDescription() for GNUstep and other runtimes.
                if not summary:
                    summary = nativeValue.GetObjectDescription()

        nativeValue.SetPreferSyntheticValue(False)

        if code == lldb.eTypeClassReference:
            nativeTargetType = nativeType.GetDereferencedType()
            if not nativeTargetType.IsPointerType():
                nativeTargetType = nativeTargetType.GetUnqualifiedType()
            target_typeid = self.from_native_type(nativeTargetType)
            target_address = nativeValue.GetValueAsUnsigned()
            val = self.Value(self)
            val.ldata = target_address.to_bytes(self.ptrSize(), self.byteorder)
            if self.useDynamicType:
                target_typeid = self.dynamic_typeid_at_address(target_typeid, target_address)
            val.typeid = self.create_reference_typeid(target_typeid)
            val.laddress = nativeValue.AddressOf().GetValueAsUnsigned()
            #self.warn('CREATED REF: %s' % val)

        elif code == lldb.eTypeClassPointer:
            nativeTargetType = nativeType.GetPointeeType()
            if not nativeTargetType.IsPointerType():
                nativeTargetType = nativeTargetType.GetUnqualifiedType()
            target_typeid = self.from_native_type(nativeTargetType)
            val = self.Value(self)
            val.ldata = nativeValue.GetValueAsUnsigned()
            val.typeid = self.create_pointer_typeid(target_typeid)
            val.laddress = nativeValue.AddressOf().GetValueAsUnsigned()

        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 Exception:
                        pass

            val.typeid = self.from_native_type(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.from_native_type(nativeType.GetArrayElementType())
            #    else:
            #        fields = nativeType.get_fields_array()
            #        if len(fields):
            #            val.type.ltarget = self.from_native_type(fields[0])
            #elif code == lldb.eTypeClassVector:
            #    val.type.ltarget = self.from_native_type(nativeType.GetVectorElementType())

        val.summary = summary
        val.lIsInScope = nativeValue.IsInScope()
        val.name = nativeValue.GetName()
        val.size = nativeType.GetByteSize() * 8
        return val

    def nativeListMembers(self, value, nativeType, include_base):
        #self.warn("ADDR: 0x%x" % self.fakeAddress_)
        nativeValue = value.nativeValue
        if nativeValue is None:
            if value.laddress:
                fakeAddress = lldb.SBAddress(value.laddress, self.target)
                fakeLAddress = value.laddress
            else:
                fakeAddress = self.fakeAddress_
                fakeLAddress = self.fakeLAddress_
            nativeValue = self.target.CreateValueFromAddress('x', fakeAddress, nativeType)

        nativeValue.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

        fields = []
        for i in range(nativeValue.GetNumChildren()):
            nativeField = nativeValue.GetChildAtIndex(i)
            nativeField.SetPreferSyntheticValue(False)

            fieldName = nativeField.GetName()
            nativeFieldType = nativeField.GetType()

            if fieldName in fieldBits:
                (bitsize, bitpos, isBitfield) = fieldBits[fieldName]
            else:
                bitsize = nativeFieldType.GetByteSize() * 8
                bitpos = None
                isBitfield = False

            if isBitfield:  # Bit fields
                field_typeid = self.create_bitfield_typeid(
                    self.create_typeid(nativeFieldType.GetName()), bitsize)
                val = self.Value(self)
                val.name = fieldName
                val.isBaseClass = False
                val.typeid = field_typeid
                val.ldata = self.value_extract_bits(value, bitpos, bitsize)
                val.laddress = None
                fields.append(val)

            elif not fieldName:  # Anon members (None in old LLDB, "" in LLDB 2100+)
                anonNumber += 1
                fieldName = '#%s' % anonNumber
                fakeMember = nativeValue.GetChildAtIndex(i)
                fakeMemberAddress = fakeMember.GetLoadAddress()
                val = self.Value(self)
                val.name = fieldName
                val.isBaseClass = False
                val.typeid = self.from_native_type(nativeFieldType)
                field_offset = fakeMemberAddress - fakeLAddress
                if value.laddress is not None:
                    val.laddress = value.laddress + field_offset
                if value.ldata is not None:
                    field_size = (bitsize + 7) // 8
                    val.ldata = value.ldata[field_offset:field_offset + field_size]
                fields.append(val)

            elif fieldName in baseNames:  # Simple bases
                member = self.fromNativeValue(nativeValue.GetChildAtIndex(i))
                member.isBaseClass = True
                fields.append(member)

            else:  # Normal named members
                member = self.fromNativeValue(nativeValue.GetChildAtIndex(i))
                member.name = nativeField.GetName()
                fields.append(member)


        if include_base:
            # 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.typeid = self.from_native_type(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()
                        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 = nativeValue.GetData()
                            size = nativeType.GetByteSize()
                            error = lldb.SBError()
                            member.laddress = value.laddress
                            member.ldata = data.ReadRawData(error, 0, size)
                        member.isBaseClass = True
                        fields.append(member)
        return fields

    def ptrSize(self):
        result = self.target.GetAddressByteSize()
        self.ptrSize = lambda: result
        return result

    def is_qobject_based(self, nativeType):
        if nativeType.GetName() == self.qtNamespace() + 'QObject':
            return True
        if nativeType.GetNumberOfDirectBaseClasses() > 0:
            return self.is_qobject_based(nativeType.GetDirectBaseClassAtIndex(0).GetType())
        if nativeType.GetNumberOfFields() > 0:
            return False
        return None  # No info, can't drill deeper

    def from_native_type(self, nativeType):
        self.check(isinstance(nativeType, lldb.SBType))

        # 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)

        #self.warn('CURRENT: %s' % self.typeData.keys())
        #self.warn('FROM NATIVE TYPE: %s' % nativeType.GetName())

        typeid_str = self.native_type_key(nativeType)
        known_typeid = self.typeid_from_typekey.get(typeid_str, None)
        if known_typeid is not None:
            return known_typeid

        code = nativeType.GetTypeClass()

        if code == lldb.eTypeClassInvalid:
            typeid = 0

        elif code == lldb.eTypeClassPointer:
            #self.warn('PTR: %s' % nativeTargetType.name)
            target_typeid  = self.from_native_type(nativeType.GetPointeeType())
            typeid = self.create_pointer_typeid(target_typeid)

        elif code == lldb.eTypeClassReference:
            #DumperBase.warn('REF')
            target_typeid = self.from_native_type(nativeType.GetDereferencedType())
            typeid = self.create_reference_typeid(target_typeid)

        elif code == lldb.eTypeClassTypedef:
            #DumperBase.warn('TYPEDEF')
            nativeTargetType = nativeType.GetUnqualifiedType()
            if hasattr(nativeTargetType, 'GetCanonicalType'):
                nativeTargetType = nativeTargetType.GetCanonicalType()
            target_typeid = self.from_native_type(nativeTargetType)
            typeid = self.create_typedefed_typeid(target_typeid, nativeType.GetName(),
                                                typeid_str)

        elif code in (lldb.eTypeClassArray, lldb.eTypeClassVector):
            nativeType = nativeType.GetUnqualifiedType()
            type_name = nativeType.GetName()
            #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()
                target_typename = nativeTargetType.GetName()
                if target_typename.startswith('(anon'):
                    type_name = nativeType.GetName()
                    pos1 = type_name.rfind('[')
                    target_typename = type_name[0:pos1].strip()
                #DumperBase.warn("TARGET TYPENAME: %s" % target_typename)
                target_typeid = self.from_native_type(nativeTargetType)
                #target_typeid.setTdata(target_typeid.tdata.copy())
                #target_typeid.tdata.name = target_typename
                typeid = self.create_array_typeid(target_typeid, count)
            elif hasattr(nativeType, 'GetVectorElementType'):  # New in 3.8(?) / 350.x
                nativeTargetType = nativeType.GetVectorElementType()
                count = nativeType.GetByteSize() // nativeTargetType.GetByteSize()
                target_typeid = self.from_native_type(nativeTargetType)
                typeid = self.create_array_typeid(target_typeid, count)
            else:
                typeid = self.create_type(nativeType.GetName())

        else:
            nativeType = nativeType.GetUnqualifiedType()
            type_name = nativeType.GetName()

            typeid = self.typeid_for_string(typeid_str)
            #if not typeid in self.typeid_cache:
            #  # This strips typedefs for pointers. We don't want that.
            #  typeobj.nativeType = nativeType.GetUnqualifiedType()
            self.type_name_cache[typeid] = type_name
            self.type_size_cache[typeid] = nativeType.GetByteSize()
            type_code = None
            if code == lldb.eTypeClassBuiltin:
                if utils.isFloatingPointTypeName(type_name):
                    type_code = TypeCode.Float
                elif utils.isIntegralTypeName(type_name):
                    type_code = TypeCode.Integral
                elif type_name in ('__int128', 'unsigned __int128'):
                    type_code = TypeCode.Integral
                elif type_name == 'void':
                    type_code = TypeCode.Void
                elif type_name == 'wchar_t':
                    type_code = TypeCode.Integral
                elif type_name in ("char16_t", "char32_t", "char8_t"):
                    type_code = TypeCode.Integral
                else:
                    self.warn('UNKNOWN TYPE KEY: %s: %s' % (type_name, code))
            elif code == lldb.eTypeClassEnumeration:
                type_code = TypeCode.Enum
                self.type_enum_display_cache[typeid] = lambda intval, addr, form: \
                    self.nativeTypeEnumDisplay(nativeType, intval, form)
            elif code in (lldb.eTypeClassComplexInteger, lldb.eTypeClassComplexFloat):
                type_code = TypeCode.Complex
            elif code in (lldb.eTypeClassClass, lldb.eTypeClassStruct,
                          lldb.eTypeClassUnion):
                type_code = TypeCode.Struct
            elif code == lldb.eTypeClassFunction:
                type_code = TypeCode.Function
            elif code == lldb.eTypeClassMemberPointer:
                type_code = TypeCode.MemberPointer

            if code is not None:
                self.type_code_cache[typeid] = type_code

        self.type_nativetype_cache[typeid] = nativeType
        self.typeid_from_typekey[typeid_str] = typeid

        # self.warn('REUSE TYPE: %s' % typeid)
        return typeid

    def nativeTemplateParameter(self, typeid, index, nativeType):
        #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

        kind = nativeType.GetTemplateArgumentKind(index)
        # eTemplateArgumentKindNull = 0,
        # eTemplateArgumentKindType,
        # eTemplateArgumentKindDeclaration,
        # eTemplateArgumentKindIntegral,
        # eTemplateArgumentKindTemplate,
        # eTemplateArgumentKindTemplateExpansion,
        # eTemplateArgumentKindExpression,
        # eTemplateArgumentKindPack
        if kind == lldb.eTemplateArgumentKindType:
            innerType = nativeType.GetTemplateArgumentType(index) \
                .GetUnqualifiedType().GetCanonicalType()
            return self.Type(self, self.from_native_type(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
        return None

    def native_type_key(self, nativeType):
        code = nativeType.GetTypeClass()
        if nativeType and code == lldb.eTypeClassTypedef:
            nativeTargetType = nativeType.GetUnqualifiedType()
            if hasattr(nativeTargetType, 'GetCanonicalType'):
                nativeTargetType = nativeTargetType.GetCanonicalType()
            return '%s{%s}' % (nativeType.name, nativeTargetType.name)
        # Don't use GetDisplayTypeName since LLDB removed the inline namespace __1
        # https://reviews.llvm.org/D74478
        name = nativeType.GetName()
        if not name:
            c = '0'
        elif name == '(anonymous struct)':
            c = 's' if code == lldb.eTypeClassStruct else 'u'
        else:
            return name
        fields = nativeType.get_fields_array()
        id_str = c + ''.join(['{%s:%s}' %
            (f.name, self.typeid_for_string(self.native_type_key(f.GetType())))
                for f in fields])
        return id_str

    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 nativeDynamicType(self, address, base_typeid):
        return self.nativeDynamicType_2(address, base_typeid)

    def nativeDynamicType_1(self, address, base_typeid):
        # Solutions 1: Breaks StdUniquePtr and QVariant1 test
        return base_typeid

    def nativeDynamicType_2(self, address, base_typeid):
        # Solution 2: ~10% slower in total than Solution 1
        typename = self.type_name(base_typeid)
        #self.warn("LOOKING FOR DYN TYPE: 0x%x %s" % (address, typename))
        #self.warn(" PRETTY: 0x%x %s" % (address, self.prettySymbolByAddress(address)))

        expr = '(void*)%s' % address
        value = self.target.EvaluateExpression(expr)

        #self.warn("VALUE: %s" % value)
        if value.GetType().GetName() == "void *":
            #self.warn("NO DYN TYPE: %s" % value)
            return base_typeid

        dvalue = value.Dereference()
        #self.warn("DVALUE: %s" % value)
        sbtype = dvalue.GetType()
        #self.warn("TYPE: %s" % sbtype)

        #self.warn("OUTPUT: %s" % output)
        #self.warn("DYNTYPE: %s" % dyn_typename)
        return self.from_native_type(sbtype)

    def nativeDynamicType_3(self, address, base_typeid):
        # Solution 3: Doesn't improve over 1
        typename = self.type_name(base_typeid)
        self.warn("LOOKING FOR DYN TYPE: 0x%x %s" % (address, typename))
        #self.warn(" PRETTY: 0x%x %s" % (address, self.prettySymbolByAddress(address)))
        nativeType = self.type_nativetype_cache.get(base_typeid, None)
        #self.warn(" NATIVE BASE %s" % nativeType)
        if nativeType is None:
            return base_typeid
        #versionValue = self.target.EvaluateExpression('qtHookData[2]').GetNonSyntheticValue()
        addr = lldb.SBAddress(address, self.target)
        value = self.target.CreateValueFromAddress('x', addr, nativeType)
        self.warn(" VALUE %s" % value)
        return base_typeid

    def nativeDynamicType_4(self, address, base_typeid):
        #self.warn("RESULT: %s" % result)
        #self.warn("ADDRESS: 0x%x" % address)

        #thread = self.currentThread()
        #frame = thread.GetFrameAtIndex(0)
        #expr = '(void*)%s' % address
        #value = self.target.EvaluateExpression(expr)
        #sbtype = self.lookupNativeType(typename)
        #addr = self.target.ResolveLoadAddress(address)
        #addr = lldb.SBAddress(address, self.target)
        #value = self.target.CreateValueFromAddress('x', addr, sbtype)
        #x = lldb::DynamicValueType()
        #lldb.eNoDynamicValues
        #lldb.eDynamicCanRunTarget
        #lldb.eDynamicDontRunTarget
        #dyn_value = value.GetDynamicValue(lldb.eDynamicDontRunTarget)
        #typ = dyn_value.GetType()
        self.warn("GOT DYN VALUE: %s" % dyn_value)
        #self.warn("GOT TYPE: %s FOR OBJECT AT 0x%x" % (typ, address))
        return self.from_native_type(typ)

        #result = lldb.SBCommandReturnObject()
        #cmd = 'p (void*)%s' % address
        #self.debugger.GetCommandInterpreter().HandleCommand(cmd, result)
        #if not result.Succeeded():
        #    return self.Type(self, typeid)
        #output = result.GetOutput().strip()
        #dyn_typename = output[1:output.find('$') - 4]
        #sbtype = self.lookupNativeType(dyn_typename)


    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 Exception:
            return 'unknown(%s)' % s

    def stopReason(self, s):
        try:
            return (
                'invalid',
                'none',
                'trace',
                'breakpoint',
                'watchpoint',
                'signal',
                'exception',
                'exec',
                'plancomplete',
                'threadexiting',
                'instrumentation',
            )[s]
        except Exception:
            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, type_name, *args):
        thread = self.currentThread()
        frame = thread.GetFrameAtIndex(0)
        inner = ','.join(args)
        value = frame.EvaluateExpression(type_name + '{' + 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 Exception:
            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)

        self.fetchInternalFunctions = lambda: None

    def extractQtVersion(self):
        for func in self.target.FindFunctions('qVersion'):
            name = func.GetSymbol().GetName()
            if name is None:
                continue
            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)
            return qtVersion

        try:
            versionValue = self.target.EvaluateExpression('qtHookData[2]').GetNonSyntheticValue()
            if versionValue.IsValid():
                return versionValue.unsigned
        except Exception:
            pass

        return None


    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: %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 nativeStructAlignment(self, nativeType):
        #DumperBase.warn("NATIVE ALIGN FOR %s" % nativeType.name)
        def handleItem(nativeFieldType, align):
            a = self.type_alignment(self.from_native_type(nativeFieldType))
            return a if a > align else align
        align = 1
        for f in nativeType.get_fields_array():
            align = handleItem(f.type, align)
        return align


    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.deviceUuid_ = args.get('deviceUuid', '')
        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 Exception:  # 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_ = ''

        if self.startMode_ == DebuggerStartMode.AttachToIosDevice:
            # The script code depends on a target from now on,
            # so we already need to attach with the special Apple lldb debugger commands
            self.runDebuggerCommand('device select ' + self.deviceUuid_)
            self.runDebuggerCommand('device process attach -p ' + str(self.attachPid_))
            self.target = self.debugger.GetSelectedTarget()
        else:
            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

        if not self.target:
            self.report('Debugger failed to create target.')
            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')
            self.internalBreakpointIds.add(self.interpreterEventBreakpoint.GetID())

        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_)
            target_platform = self.target.GetPlatform()

            res = target_platform.ConnectRemote(connect_options)

            is_connected = target_platform.IsConnected()

            DumperBase.warn("CONNECT: %s %s target platform: %s connected: %s"
                % (res, self.remoteChannel_, target_platform.GetName(), is_connected))

            if not res.Success():
                self.report(self.describeError(res))
                self.reportState('enginerunfailed')
                return

            if not is_connected:
                self.report('Could not connect to debug server')
                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) \
                and not self.platform_:
            # Plain "target remote"-equivalent: no platform/lldb-server
            # abstraction, just the raw gdb remote protocol gdbserver speaks.
            url = "connect://" + self.remoteChannel_
            self.process = self.target.ConnectRemote(
                self.debugger.GetListener(), url, "gdb-remote", error)
            if not error.Success():
                self.report(self.describeError(error))
                self.reportState('enginerunfailed')
                return
            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')
        elif self.startMode_ == DebuggerStartMode.AttachToIosDevice:
            # Already attached in setupInferior (to get a SBTarget),
            # just get the process from it
            self.process = self.target.GetProcess()
            self.reportState('enginerunandinferiorrunok')
        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 reportSignalStop(self, thread):
        # A spontaneous stop caused by a signal or exception (e.g. SIGSEGV on a
        # bad pointer access) is otherwise only visible as a bare "Stopped".
        # Surface the signal to the engine like GdbEngine does so the user gets
        # a status message and, optionally, a dialog. QTCREATORBUG-11049.
        reason = thread.GetStopReason()
        if reason != lldb.eStopReasonSignal and reason != lldb.eStopReasonException:
            return
        name = ''
        if reason == lldb.eStopReasonSignal and thread.GetStopReasonDataCount() > 0:
            signo = thread.GetStopReasonDataAtIndex(0)
            try:
                name = self.process.GetUnixSignals().GetSignalAsCString(signo) or ''
            except Exception:
                name = ''
        # Debugger-control signals used to interrupt/step the inferior are
        # expected and must not be reported as a crash.
        if name in ('SIGINT', 'SIGSTOP', 'SIGCONT', 'SIGTRAP'):
            return
        meaning = thread.GetStopDescription(1024) or ''
        # LLDB prefixes the description with "signal <NAME>: "; drop it so the
        # signal name is not repeated in the assembled status message.
        prefix = 'signal %s: ' % name
        if name and meaning.startswith(prefix):
            meaning = meaning[len(prefix):]
        self.report('signal-received={name="%s",meaning="%s"}'
                    % (toCString(name), toCString(meaning)))

    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

        # Pre-pass: find where the QML frames will be spliced and the
        # contiguous metacall machinery run leading down to it, so those
        # frames (e.g. qt_static_metacall, QMetaObject::metacall between a
        # C++ method and its QML caller) can be de-emphasized too.
        preSpliceMachineryPcs = set()
        if isNativeMixed:
            pending = []
            for k in range(n):
                scan = thread.GetFrameAtIndex(k)
                if not scan.IsValid():
                    break
                scanName = scan.GetFunctionName() or ''
                if (scanName == '::qt_qmlDebugMessageAvailable()'
                        or 'QV4::Moth::VME::' in scanName):
                    preSpliceMachineryPcs.update(pending)
                    break
                if self.isQmlCallMachineryFrame(scan):
                    pending.append(scan.GetPC())
                else:
                    pending = []  # only the run immediately above the splice

        inMachineryBlock = False
        splicedQml = False
        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()

            # Splice the QML frames into the C++ stack. At a QML breakpoint
            # this happens at the qt_qmlDebugMessageAvailable notification;
            # when stopped in a C++ method called from QML there is no such
            # frame, so splice at the QML interpreter frame instead, which
            # shows the QML caller chain. Do it once.
            if (isNativeMixed and not splicedQml
                    and (functionName == '::qt_qmlDebugMessageAvailable()'
                         or 'QV4::Moth::VME::' in (functionName or ''))):
                splicedQml = True
                inMachineryBlock = True
                interpreterStack = self.extractInterpreterStack()
                for interpreterFrame in interpreterStack.get('frames', []):
                    result += ('frame={function="%s",file="%s",'
                               'line="%s",language="%s",context="%s"}'
                               % (interpreterFrame.get('function', ''),
                                  toCString(interpreterFrame.get('file', '')),
                                  interpreterFrame.get('line', 0),
                                  interpreterFrame.get('language', ''),
                                  interpreterFrame.get('context', 0)))

            # Mark debugger machinery frames. The frontend de-emphasizes
            # or collapses them. Two runs are marked: the metacall chain
            # between a C++ method and its QML caller (preSpliceMachineryPcs),
            # and the contiguous block at and below the QML splice point.
            extra = ''
            if pc in preSpliceMachineryPcs:
                extra = ',usable="0",machinery="1"'
            elif inMachineryBlock:
                if self.isInterpreterMachineryFrame(functionName):
                    extra = ',usable="0",machinery="1"'
                else:
                    inMachineryBlock = False

            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 += extra
            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 reportInterpreterResult(self, resdict, args):
        # Overrides DumperBase's plain print() to use report()'s "@" framing.
        self.report('interpreterresult=%s,token="%s"'
                    % (self.resultToMi(resdict), args.get('token', -1)))

    def reportInterpreterAsync(self, resdict, asyncclass):
        self.report('interpreterasync=%s,asyncclass="%s"'
                    % (self.resultToMi(resdict), asyncclass))

    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 fetchVariables(self, args):
        start_time = time.perf_counter()
        (ok, res) = self.tryFetchInterpreterVariables(args)
        if ok:
            self.reportResult(res, args)
            return

        self.setVariableFetchingOptions(args)

        self.qtLoaded = True # FIXME: Do that elsewhere


        # 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.isArmMac = frame.module.triple.startswith('arm64-apple')
        self.isBigEndian = frame.module.byte_order == lldb.eByteOrderBig
        self.packCode = '>' if self.isBigEndian else '<'
        self.byteorder = 'big' if self.isBigEndian else 'little'

        self.output = []
        isPartial = bool(self.partialVariable)

        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, staticVar in enumerate(statics):
                            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.fromNativeValue(val)
            variables.append(value)

        self.handleLocals(variables)
        self.handleWatches(args)

        run_time = time.perf_counter() - start_time

        self.put('],partial="%d",runtime="%s"' % (isPartial, run_time))
        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 and bp.GetID() not in self.internalBreakpointIds:
                self.reportBreakpointUpdate(bp)

    def wantAutoContinue(self, frame):
        if self.platform_ != 'remote-android':
            return False
        funcname = frame.GetFunctionName()
        if funcname:
            if funcname.startswith('java.'):
                return True
            if funcname.startswith('android.'):
                return True
            if funcname.startswith('com.android.'):
                return True
            if funcname.startswith('jdk.'):
                return True
            if funcname.startswith('sun.'):
                return True
        module = frame.GetModule()
        filespec = module.GetPlatformFileSpec() # Not GetFileSpec
        filename = filespec.GetFilename()
        if filename and filename.endswith('libart.so'):
            return True
        if funcname is None and not frame.line_entry.file.IsValid() and filename is None:
            return True
        return False

    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:
            # A breakpoint whose condition evaluated to false (and other cases
            # where the target is auto-continued) still broadcasts a 'stopped'
            # event, but with the "restarted" flag set. Not honoring it made
            # every false-condition hit look like a real stop, e.g. stealing the
            # keyboard focus on each hit on macOS (QTCREATORBUG-34583). Skip such
            # events and leave eventState unchanged so the eventual real stop is
            # still reported.
            if state == lldb.eStateStopped and lldb.SBProcess.GetRestartedFromEvent(event):
                return
            self.eventState = state
            if state == lldb.eStateStopped:
                stoppedThread = self.firstStoppedThread()
                if stoppedThread:
                    frame = stoppedThread.GetFrameAtIndex(0)
                    if self.wantAutoContinue(frame):
                        #self.warn("AUTO CONTINUE")
                        error = self.process.Continue()
                        return

                    #self.report("FRAME: %s" % frame)
                    function = frame.GetFunction()
                    functionName = function.GetName()
                    if self.handleNativeMethodStepInto(stoppedThread, functionName):
                        return
                    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")
                        # The interpreter step won the race (possibly a
                        # C++-to-QML crossing); the armed interpreter step
                        # and the native call hook are now consumed.
                        self.interpreterStepArmed = False
                        self.disarmNativeCallStepIn()
                        res = self.handleInterpreterMessage()
                        if not res:
                            self.report("EVENT NEEDS NO STOP")
                            self.reportState("stopped")
                            self.process.Continue()
                            return
                # A native step (possibly a C++-to-QML crossing attempt)
                # stopped in C++ rather than re-entering QML: take back the
                # armed interpreter step so it does not fire on a later run.
                if self.interpreterStepArmed:
                    self.disarmInterpreterStep()
                if self.isInterrupting_:
                    self.isInterrupting_ = False
                    self.reportState("inferiorstopok")
                else:
                    if stoppedThread is not None:
                        self.reportSignalStop(stoppedThread)
                    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:
            try:
                msg = proc(1024)
                if not msg:
                    break
                self.report('output={channel="%s",data="%s"}' % (channel, self.hexencode(msg)))
            except SystemError as e:
                self.warn('Error during reading of process output: %s' % e)

    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.GetLoadAddress(self.target)
                result += ',faddr="%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):
        # On Android main() lives in a shared object rather than the executable,
        # so do not restrict the breakpoint to the executable's module.
        bp = self.target.BreakpointCreateByName('main')
        self.internalBreakpointIds.add(bp.GetID())
        return bp

    def breakpointCallback(self, frame, bp_loc, extra_args, internal_dict):
        command_str = extra_args.GetValueForKey('command').GetStringValue(65536)
        tracepoint = extra_args.GetValueForKey('tracepoint').GetBooleanValue()
        message = extra_args.GetValueForKey('message').GetStringValue(65536)

        from io import StringIO
        origout = sys.stdout
        sys.stdout = StringIO()
        result = True

        if command_str:
            local_ns = {'frame': frame, 'bp_loc': bp_loc, 'internal_dict': internal_dict}
            userCode = 'def foo(frame=frame, bp_loc=bp_loc, dict=internal_dict):\n  ' + command_str.replace('\n', '\n  ')
            exec(userCode, local_ns)
            result = local_ns['foo']()

        d = lldb.theDumper
        output = d.hexencode(sys.stdout.getvalue())
        sys.stdout = origout
        d.report(f'output={{channel="stderr",data="{output}"}}')
        sys.stdout.flush()
        if result is False:
            d.reportState("continueafternextstop")
        if tracepoint:
            d.reportBreakpointUpdate(bp_loc.GetBreakpoint())
            captures = d.tracepointCaptures(message, frame)
            d.report(f'tracepointhit={{message="{d.hexencode(message)}",'
                     f'expressions=[{captures}]}}')
            d.reportState("continueafternextstop")

        return True

    def tracepointCaptures(self, message, frame):
        # Report each "{expr}" as a raw value/encoding pair. The engine
        # side substitutes it into the message and decodes it via
        # decodeData() (DebuggerEncoding), the same path GdbEngine uses -
        # so there is a single source of truth for all the encodings.
        self.setVariableFetchingOptions({'fancy': 1, 'autoderef': 1})
        captures = []
        for expr in re.findall(r'\{([^}]+)\}', message):
            value, encoding = '', 'notaccessible'
            native = frame.EvaluateExpression(expr)
            if native.GetError().Success():
                try:
                    # Read currentValue inside the "with" block, before
                    # __exit__ resets it - takeOutput() would return the
                    # raw tuple text instead of just the value.
                    with TopLevelItem(self, expr):
                        self.putItem(self.fromNativeValue(native))
                        result = self.currentValue
                    self.takeOutput()  # discard the tuple text this generated
                    if result.value is not None:
                        value = str(result.value)
                        encoding = result.encoding if result.encoding else ''
                except Exception:
                    value, encoding = '', 'notaccessible'
            captures.append('{expr="%s",value="%s",valueencoded="%s"}'
                            % (self.hexencode(expr), value, encoding))
        return ','.join(captures)

    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.BreakpointAtFork:
            # No native "catch fork" in lldb - break on the libc entry point.
            bp = self.target.BreakpointCreateByName('fork')
        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 Exception:
                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']))
            if isinstance(bp, lldb.SBBreakpoint):
                # SBWatchpoint has neither of these.
                extra_args_dict = {
                    'tracepoint': bool(args['tracepoint']),
                    'message': self.hexdecode(args['message']),
                    'command': self.hexdecode(args['command'])
                }
                extra_args = lldb.SBStructuredData()
                extra_args.SetFromJSON(json.dumps(extra_args_dict))
                bp.SetScriptCallbackFunction('lldb.theDumper.breakpointCallback', extra_args)
                bp.SetOneShot(bool(args['oneshot']))
        # extra needs its own separating comma before it.
        self.reportResult(self.describeBreakpoint(bp) + (',' + extra if extra else ''), 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)
        else:
            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):
        if self.atQmlStop():
            self.sendInterpreterRequest('stepover', args)
            self.process.Continue()
        else:
            self.currentThread().StepOver()
        self.reportResult('', args)

    def executeNextI(self, args):
        self.currentThread().StepInstruction(True)
        self.reportResult('', args)

    def atQmlStop(self):
        # True if the inferior is stopped at the interpreter's stop hook,
        # i.e. the current frame is a QML frame.
        if not self.nativeMixed or self.process is None:
            return False
        frame = self.currentThread().GetFrameAtIndex(0)
        return (frame.GetFunctionName() or '') == '::qt_qmlDebugMessageAvailable()'

    def executeStep(self, args):
        if self.atQmlStop():
            # Stepping from QML: pause at the next JS statement, and arm
            # the native call hook so a C++ method call is stepped into.
            self.sendInterpreterRequest('stepin', args)
            self.armNativeCallStepIn()
            self.process.Continue()
        else:
            # Stepping from C++: if the step reaches the QML interpreter,
            # pause at the next JS statement. The native step and the
            # armed interpreter step race; disarmInterpreterStep() resolves
            # it when the native part stops first.
            if self.nativeMixed:
                self.setupMachinerySkips()
                self.sendInterpreterRequest('stepin', args)
                self.interpreterStepArmed = True
            self.currentThread().StepInto()
        self.reportResult('', args)

    def nativeCallHookAvailable(self):
        if not getattr(self, 'nativeCallHookChecked', False):
            self.nativeCallHookChecked = True
            sym = self.target.FindFirstGlobalVariable('qt_v4NativeCallHookEnabled')
            self.nativeCallHookOk = sym.IsValid()
        return self.nativeCallHookOk

    def armNativeCallStepIn(self):
        if not self.nativeCallHookAvailable():
            return
        if getattr(self, 'nativeCallHookBreakpoint', None) is None:
            self.nativeCallHookBreakpoint = \
                self.target.BreakpointCreateByName('qt_v4AboutToCallNativeMethodHook')
            self.internalBreakpointIds.add(self.nativeCallHookBreakpoint.GetID())
        self.parseAndEvaluate('qt_v4NativeCallHookEnabled = 1')

    def disarmNativeCallStepIn(self):
        if self.nativeCallHookAvailable():
            self.parseAndEvaluate('qt_v4NativeCallHookEnabled = 0')

    def setupMachinerySkips(self):
        # Stepping from C++ should pass through the V4 dispatch and the
        # generic signal plumbing rather than stop at their entries; while
        # passing through, an armed interpreter step pauses at the next JS
        # statement. LLDB takes a single step-avoid regexp, so combine the
        # machinery patterns (and keep the std default).
        if getattr(self, 'machinerySkipsDone', False):
            return
        self.machinerySkipsDone = True
        pattern = ('(^QV4::)|(^QQml)|(^QtPrivate::)|(^doActivate)|'
                   '(^QMetaObject::)|(^qt_qmlDebug)|(^debug_slowPath)|(^std::)')
        result = lldb.SBCommandReturnObject()
        self.debugger.GetCommandInterpreter().HandleCommand(
            'settings set target.process.thread.step-avoid-regexp ' + pattern,
            result)

    def isQmlCallMachineryFrame(self, frame):
        # A frame on the metacall path between a C++ method and the QML
        # interpreter: the generated qt_(static_)metacall trampolines, or
        # anything inside the Qt libraries (recognized by source path, or
        # by having no source at all in a release build).
        name = frame.GetFunctionName() or ''
        if 'qt_static_metacall' in name or 'qt_metacall' in name:
            return True
        fileName = ''
        lineEntry = frame.GetLineEntry()
        if lineEntry.IsValid():
            fileSpec = lineEntry.GetFileSpec()
            if fileSpec.IsValid():
                fileName = fileSpec.GetDirectory() or ''
        if not fileName:
            return True
        return '/qtbase/' in fileName or '/qtdeclarative/' in fileName

    def atNativeToQmlBoundary(self):
        # True if the current C++ frame was called straight from the QML
        # interpreter through the metacall machinery, with no intervening
        # user C++ frame. Stepping out then returns to the QML caller.
        thread = self.currentThread()
        if thread is None:
            return False
        limit = min(thread.GetNumFrames(), 25)
        for i in range(1, limit):
            frame = thread.GetFrameAtIndex(i)
            name = frame.GetFunctionName() or ''
            if 'QV4::Moth::VME::' in name or 'callInternal' in name:
                return True
            if not self.isQmlCallMachineryFrame(frame):
                return False
        return False

    def waitForNativeStop(self):
        # Synchronously pump the process's state-change events until it next
        # settles - stops, exits or dies - so the engine never sees the
        # intermediate run/stop cycles of a multi-step descent. This reuses
        # the debugger's listener on purpose: it runs on the event-pump thread
        # (inside handleEvent), so there is no contention, and the intermediate
        # events must be drained here rather than re-surfaced by handleEvent.
        # Time-bounded so a wedged or crashed inferior cannot block for long;
        # the normal case returns on the first settled event in milliseconds.
        settled = (lldb.eStateStopped, lldb.eStateExited,
                   lldb.eStateCrashed, lldb.eStateDetached)
        event = lldb.SBEvent()
        listener = self.debugger.GetListener()
        deadline = time.time() + 10
        while time.time() < deadline:
            if not listener.WaitForEvent(1, event):
                continue
            if not lldb.SBProcess.EventIsProcessEvent(event):
                continue
            state = lldb.SBProcess.GetStateFromEvent(event)
            self.eventState = state
            if state in settled:
                return state
        return lldb.eStateInvalid

    def handleNativeMethodStepInto(self, thread, functionName):
        # When stopped at the JS-to-C++ dispatch hook, drive the descent
        # into the C++ method synchronously (mirroring the GDB bridge's
        # handleNativeCallHook), so the engine only ever sees the final
        # landing in the user method. Returns False so handleEvent reports
        # that final stop normally.
        if 'qt_v4AboutToCallNativeMethodHook' not in (functionName or ''):
            return False
        # Leaving QML for C++: take back the racing interpreter step and
        # resolve the receiver's generated qt_static_metacall trampoline
        # via meta->d.static_metacall.
        self.disarmNativeCallStepIn()
        self.sendInterpreterRequest('continue', {})
        self.interpreterStepArmed = False
        frame = thread.GetFrameAtIndex(0)
        meta = frame.FindVariable('receiverMeta')
        smc = meta.GetChildMemberWithName('d').GetChildMemberWithName(
            'static_metacall')
        addr = smc.GetValueAsUnsigned()
        if addr == 0:
            return False
        bp = self.target.BreakpointCreateByAddress(addr)
        self.internalBreakpointIds.add(bp.GetID())
        bp.SetThreadID(thread.GetThreadID())
        self.process.Continue()
        landed = self.waitForNativeStop()
        self.target.BreakpointDelete(bp.GetID())
        if landed != lldb.eStateStopped:
            return False
        # Step out of the generated qt_static_metacall trampoline into the
        # actual method.
        thread = self.firstStoppedThread() or self.process.GetSelectedThread()
        for _ in range(32):
            fn = thread.GetFrameAtIndex(0).GetFunctionName() or ''
            if 'qt_static_metacall' not in fn:
                break
            thread.StepInto()
            if self.waitForNativeStop() != lldb.eStateStopped:
                break
            thread = self.firstStoppedThread() or self.process.GetSelectedThread()
        return False

    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={}):
        if self.atQmlStop():
            self.sendInterpreterRequest('stepout', args)
            self.process.Continue()
        elif self.nativeMixed and self.atNativeToQmlBoundary():
            # The C++ frame was called straight from the QML interpreter;
            # return to the QML caller by letting the frame finish and
            # pausing at the next JS statement.
            self.setupMachinerySkips()
            self.sendInterpreterRequest('stepin', args)
            self.interpreterStepArmed = True
            self.process.Continue()
        else:
            self.currentThread().StepOut()
        self.reportResult('', args)

    def executeRunToLocation(self, args):
        self.reportToken(args)
        frame = self.currentFrame()
        addr = args.get('address', 0)
        if addr:
            # Does not seem to hit anything on Linux:
            # self.currentThread().RunToAddress(addr)
            bp = self.target.BreakpointCreateByAddress(addr)
            self.internalBreakpointIds.add(bp.GetID())
            if bp.GetNumLocations() == 0:
                self.target.BreakpointDelete(bp.GetID())
                self.reportResult(self.describeStatus('No target location found.')
                                  + self.describeLocation(frame), args)
                self.reportState('inferiorrunfailed')
                return
            bp.SetOneShot(True)
            self.reportResult('', args)
            self.process.Continue()
        else:
            file = args['file']
            line = int(args['line'])
            error = self.currentThread().StepOverUntil(frame, lldb.SBFileSpec(file), line)
            if error.Success():
                self.reportResult('', args)
                self.reportState('running')
                self.reportState('stopped')
            else:
                # Target line is outside the current function - fall back
                # to a one-shot breakpoint, like the address branch above.
                bp = self.target.BreakpointCreateByLocation(str(file), line)
                self.internalBreakpointIds.add(bp.GetID())
                if bp.GetNumLocations() == 0:
                    self.target.BreakpointDelete(bp.GetID())
                    self.reportResult(self.describeStatus('No target location found.')
                                      + self.describeLocation(frame), args)
                    self.reportState('inferiorrunfailed')
                    return
                bp.SetOneShot(True)
                self.reportResult('', args)
                self.process.Continue()

    def executeRunToFunction(self, args):
        self.reportToken(args)
        frame = self.currentFrame()
        bp = self.target.BreakpointCreateByName(str(args['function']))
        self.internalBreakpointIds.add(bp.GetID())
        if bp.GetNumLocations() == 0:
            self.target.BreakpointDelete(bp.GetID())
            self.reportResult(self.describeStatus('No target location found.')
                              + self.describeLocation(frame), args)
            self.reportState('inferiorrunfailed')
            return
        bp.SetOneShot(True)
        self.reportResult('', args)
        self.process.Continue()

    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']))
        self.internalBreakpointIds.add(bp.GetID())
        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 runDebuggerCommand(self, command):
        self.report('Running debugger command "{}"'.format(command))
        result = lldb.SBCommandReturnObject()
        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 executeDebuggerCommand(self, args):
        self.reportToken(args)
        command = args['command']
        self.runDebuggerCommand(command)

    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 writeMemory(self, args):
        address = args['address']
        data = bytes.fromhex(args['data'])
        error = lldb.SBError()
        self.process.WriteMemory(address, data, error)
        result = 'address="%s",' % address
        result += self.describeError(error)
        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, value in enumerate(values):
            sbaddr = lldb.SBAddress(address + i * sizeof, self.target)
            sbvalue = self.target.CreateValueFromAddress('x', sbaddr, sbtype)
            sbvalue.SetValueFromCString(str(value), 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)
        type_name = lhs.GetType().GetName()
        type_name = type_name.replace('::', '__')
        pos = type_name.find('<')
        if pos != -1:
            type_name = type_name[0:pos]
        if type_name in self.qqEditable and not simpleType:
            expr = self.parseAndEvaluate(expr)
            self.qqEditable[type_name](self, expr, value)
        else:
            self.parseAndEvaluate(expr + '=' + value)
        self.reportResult(self.describeError(error), args)

    def watchPoint(self, args):
        self.reportToken(args)
        ns = self.qtNamespace()
        # No QApplication::widgetAt at all (e.g. a non-widgets target) - degrade
        # gracefully instead of indexing funcs[1] into an empty/short list.
        funcs = self.target.FindGlobalFunctions('.*QApplication::widgetAt', 2, 1)
        p = 0
        if len(funcs) > 1:
            func = funcs[1]
            addr = func.GetFunction().GetStartAddress().GetLoadAddress(self.target)
            expr = '((void*(*)(int,int))0x%x)' % addr
            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.internalBreakpointIds.add(bp.GetID())
        self.interpreterBreakpointResolvers.append(
            lambda: self.resolvePendingInterpreterBreakpoint(args))


# Used in dumper auto test.
class Tester(Dumper):
    def __init__(self, binary, frameLevel, 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, frameLevel])
        s.start()
        s.join(30)

    def testLoop(self, args, frameLevel):
        # 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(frameLevel)
                        line = frame.line_entry.line
                        if line != 0:
                            self.report = savedReport
                            stoppedThread.SetSelectedFrame(frameLevel)
                            self.process.SetSelectedThread(stoppedThread)
                            self.fakeAddress_ = frame.GetPC()
                            self.fakeLAddress_ = frame.GetPCAddress()
                            if 'memwrite' in args:
                                mw = args['memwrite']
                                address = frame.FindVariable(mw['exp']).GetLoadAddress()
                                self.writeMemory({'address': address, 'data': mw['data']})
                            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:
                            # Stopped at an intermediate location (e.g. runtime
                            # initialisation) with no source line. Resume.
                            self.process.Continue()

            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)
                    return '"' + decodedValue + '"'
                except Exception:
                    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:
        debug("Returning early, letting Qt Creator take care of its own dumper", debugger)
        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)
    debugger.HandleCommand("settings set target.process.prefer-dynamic-value no-dynamic-values")
