forked from qt-creator/qt-creator
		
	Change-Id: Ieba28bab3c603d06caf9abb287189bf87f6dabb0 Reviewed-by: Christian Stenger <christian.stenger@qt.io>
		
			
				
	
	
		
			466 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			466 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
############################################################################
 | 
						|
#
 | 
						|
# Copyright (C) 2016 The Qt Company Ltd.
 | 
						|
# Contact: https://www.qt.io/licensing/
 | 
						|
#
 | 
						|
# This file is part of Qt Creator.
 | 
						|
#
 | 
						|
# Commercial License Usage
 | 
						|
# Licensees holding valid commercial Qt licenses may use this file in
 | 
						|
# accordance with the commercial license agreement provided with the
 | 
						|
# Software or, alternatively, in accordance with the terms contained in
 | 
						|
# a written agreement between you and The Qt Company. For licensing terms
 | 
						|
# and conditions see https://www.qt.io/terms-conditions. For further
 | 
						|
# information use the contact form at https://www.qt.io/contact-us.
 | 
						|
#
 | 
						|
# GNU General Public License Usage
 | 
						|
# Alternatively, this file may be used under the terms of the GNU
 | 
						|
# General Public License version 3 as published by the Free Software
 | 
						|
# Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
 | 
						|
# included in the packaging of this file. Please review the following
 | 
						|
# information to ensure the GNU General Public License requirements will
 | 
						|
# be met: https://www.gnu.org/licenses/gpl-3.0.html.
 | 
						|
#
 | 
						|
############################################################################
 | 
						|
 | 
						|
import inspect
 | 
						|
import os
 | 
						|
import sys
 | 
						|
import cdbext
 | 
						|
import re
 | 
						|
 | 
						|
sys.path.insert(1, os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))))
 | 
						|
 | 
						|
from dumper import *
 | 
						|
 | 
						|
class FakeVoidType(cdbext.Type):
 | 
						|
    def __init__(self, name , dumper):
 | 
						|
        cdbext.Type.__init__(self)
 | 
						|
        self.typeName = name.strip()
 | 
						|
        self.dumper = dumper
 | 
						|
 | 
						|
    def name(self):
 | 
						|
        return self.typeName
 | 
						|
 | 
						|
    def bitsize(self):
 | 
						|
        return self.dumper.ptrSize() * 8
 | 
						|
 | 
						|
    def code(self):
 | 
						|
        if self.typeName.endswith('*'):
 | 
						|
            return TypeCodePointer
 | 
						|
        if self.typeName.endswith(']'):
 | 
						|
            return TypeCodeArray
 | 
						|
        return TypeCodeVoid
 | 
						|
 | 
						|
    def unqualified(self):
 | 
						|
        return self
 | 
						|
 | 
						|
    def target(self):
 | 
						|
        code = self.code()
 | 
						|
        if code == TypeCodePointer:
 | 
						|
            return FakeVoidType(self.typeName[:-1], self.dumper)
 | 
						|
        if code == TypeCodeVoid:
 | 
						|
            return self
 | 
						|
        try:
 | 
						|
            return FakeVoidType(self.typeName[:self.typeName.rindex('[')], self.dumper)
 | 
						|
        except:
 | 
						|
            return FakeVoidType('void', self.dumper)
 | 
						|
 | 
						|
    def stripTypedef(self):
 | 
						|
        return self
 | 
						|
 | 
						|
    def fields(self):
 | 
						|
        return []
 | 
						|
 | 
						|
    def templateArgument(self, pos, numeric):
 | 
						|
        return None
 | 
						|
 | 
						|
    def templateArguments(self):
 | 
						|
        return []
 | 
						|
 | 
						|
class Dumper(DumperBase):
 | 
						|
    def __init__(self):
 | 
						|
        DumperBase.__init__(self)
 | 
						|
        self.outputLock = threading.Lock()
 | 
						|
        self.isCdb = True
 | 
						|
 | 
						|
    def fromNativeValue(self, nativeValue):
 | 
						|
        self.check(isinstance(nativeValue, cdbext.Value))
 | 
						|
        val = self.Value(self)
 | 
						|
        val.name = nativeValue.name()
 | 
						|
        val.type = self.fromNativeType(nativeValue.type())
 | 
						|
        # There is no cdb api for the size of bitfields.
 | 
						|
        # Workaround this issue by parsing the native debugger text for integral types.
 | 
						|
        if val.type.code == TypeCodeIntegral:
 | 
						|
            integerString = nativeValue.nativeDebuggerValue()
 | 
						|
            if integerString == 'true':
 | 
						|
                val.ldata = int(1).to_bytes(1, byteorder='little')
 | 
						|
            elif integerString == 'false':
 | 
						|
                val.ldata = int(0).to_bytes(1, byteorder='little')
 | 
						|
            else:
 | 
						|
                integerString = integerString.replace('`','')
 | 
						|
                integerString = integerString.split(' ')[0]
 | 
						|
                if integerString.startswith('0n'):
 | 
						|
                    integerString = integerString[2:]
 | 
						|
                    base = 10
 | 
						|
                elif integerString.startswith('0x'):
 | 
						|
                    base = 16
 | 
						|
                else:
 | 
						|
                    base = 10
 | 
						|
                signed = not val.type.name.startswith('unsigned')
 | 
						|
                val.ldata = int(integerString, base).to_bytes(val.type.size(), \
 | 
						|
                        byteorder='little', signed=signed)
 | 
						|
        val.isBaseClass = val.name == val.type.name
 | 
						|
        val.lIsInScope = True
 | 
						|
        val.laddress = nativeValue.address()
 | 
						|
        return val
 | 
						|
 | 
						|
    def nativeTypeId(self, nativeType):
 | 
						|
        self.check(isinstance(nativeType, cdbext.Type))
 | 
						|
        name = nativeType.name()
 | 
						|
        if name is None or len(name) == 0:
 | 
						|
            c = '0'
 | 
						|
        elif name == 'struct {...}':
 | 
						|
            c = 's'
 | 
						|
        elif name == 'union {...}':
 | 
						|
            c = 'u'
 | 
						|
        else:
 | 
						|
            return name
 | 
						|
        typeId = c + ''.join(['{%s:%s}' % (f.name(), self.nativeTypeId(f.type())) for f in nativeType.fields()])
 | 
						|
        return typeId
 | 
						|
 | 
						|
    def fromNativeType(self, nativeType):
 | 
						|
        self.check(isinstance(nativeType, cdbext.Type))
 | 
						|
        code = nativeType.code()
 | 
						|
 | 
						|
        if nativeType.name().startswith('void'):
 | 
						|
            nativeType = FakeVoidType(nativeType.name(), self)
 | 
						|
 | 
						|
        if code == TypeCodePointer:
 | 
						|
            return self.createPointerType(self.fromNativeType(nativeType.target()))
 | 
						|
 | 
						|
        if code == TypeCodeArray:
 | 
						|
            targetType = self.fromNativeType(nativeType.target())
 | 
						|
            return self.createArrayType(targetType, nativeType.arrayElements())
 | 
						|
 | 
						|
        typeId = self.nativeTypeId(nativeType)
 | 
						|
        if self.typeData.get(typeId, None) is None:
 | 
						|
            tdata = self.TypeData(self)
 | 
						|
            tdata.name = nativeType.name()
 | 
						|
            tdata.typeId = typeId
 | 
						|
            tdata.lbitsize = nativeType.bitsize()
 | 
						|
            tdata.code = code
 | 
						|
            self.registerType(typeId, tdata) # Prevent recursion in fields.
 | 
						|
            if  code == TypeCodeStruct:
 | 
						|
                tdata.lfields = lambda value : \
 | 
						|
                    self.listFields(nativeType, value)
 | 
						|
                tdata.lalignment = lambda : \
 | 
						|
                    self.nativeStructAlignment(nativeType)
 | 
						|
            tdata.templateArguments = self.listTemplateParameters(nativeType)
 | 
						|
            self.registerType(typeId, tdata) # Fix up fields and template args
 | 
						|
        return self.Type(self, typeId)
 | 
						|
 | 
						|
    def listFields(self, nativeType, value):
 | 
						|
        if value.address() is None or value.address() == 0:
 | 
						|
            raise Exception("")
 | 
						|
        nativeValue = cdbext.createValue(value.address(), nativeType)
 | 
						|
        index = 0
 | 
						|
        nativeMember = nativeValue.childFromIndex(index)
 | 
						|
        while nativeMember is not None:
 | 
						|
            yield self.fromNativeValue(nativeMember)
 | 
						|
            index += 1
 | 
						|
            nativeMember = nativeValue.childFromIndex(index)
 | 
						|
 | 
						|
    def nativeStructAlignment(self, nativeType):
 | 
						|
        #warn("NATIVE ALIGN FOR %s" % nativeType.name)
 | 
						|
        def handleItem(nativeFieldType, align):
 | 
						|
            a = self.fromNativeType(nativeFieldType).alignment()
 | 
						|
            return a if a > align else align
 | 
						|
        align = 1
 | 
						|
        for f in nativeType.fields():
 | 
						|
            align = handleItem(f.type(), align)
 | 
						|
        return align
 | 
						|
 | 
						|
    def listTemplateParameters(self, nativeType):
 | 
						|
        targs = []
 | 
						|
        for targ in nativeType.templateArguments():
 | 
						|
            if isinstance(targ, str):
 | 
						|
                if self.typeData.get(targ, None) is None:
 | 
						|
                    targs.append(self.lookupType(targ))
 | 
						|
                else:
 | 
						|
                    targs.append(self.Type(self, targ))
 | 
						|
            elif isinstance(targ, int):
 | 
						|
                targs.append(targ)
 | 
						|
            else:
 | 
						|
                error('CDBCRAP %s' % type(targ))
 | 
						|
        return targs
 | 
						|
 | 
						|
    def nativeTypeEnumDisplay(self, nativeType, intval):
 | 
						|
        # TODO: generate fake value
 | 
						|
        return None
 | 
						|
 | 
						|
    def enumExpression(self, enumType, enumValue):
 | 
						|
        ns = self.qtNamespace()
 | 
						|
        return ns + "Qt::" + enumType + "(" \
 | 
						|
            + ns + "Qt::" + enumType + "::" + enumValue + ")"
 | 
						|
 | 
						|
    def pokeValue(self, typeName, *args):
 | 
						|
        return None
 | 
						|
 | 
						|
    def parseAndEvaluate(self, exp):
 | 
						|
        val = cdbext.parseAndEvaluate(exp)
 | 
						|
        if val is None:
 | 
						|
            return None
 | 
						|
        value = self.Value(self)
 | 
						|
        value.type = self.lookupType('void *')
 | 
						|
        value.ldata = val.to_bytes(8, sys.byteorder)
 | 
						|
        return value
 | 
						|
 | 
						|
    def isWindowsTarget(self):
 | 
						|
        return True
 | 
						|
 | 
						|
    def isQnxTarget(self):
 | 
						|
        return False
 | 
						|
 | 
						|
    def isArmArchitecture(self):
 | 
						|
        return False
 | 
						|
 | 
						|
    def isMsvcTarget(self):
 | 
						|
        return True
 | 
						|
 | 
						|
    def qtCoreModuleName(self):
 | 
						|
        modules = cdbext.listOfModules()
 | 
						|
        # first check for an exact module name match
 | 
						|
        for coreName in ['Qt5Cored', 'Qt5Core', 'QtCored4', 'QtCore4']:
 | 
						|
            if coreName in modules:
 | 
						|
                self.qtCoreModuleName = lambda: coreName
 | 
						|
                return coreName
 | 
						|
        # maybe we have a libinfix build.
 | 
						|
        for pattern in ['Qt5Core.*', 'QtCore.*']:
 | 
						|
            matches = [module for module in modules if re.match(pattern, module)]
 | 
						|
            if matches:
 | 
						|
                coreName = matches[0]
 | 
						|
                self.qtCoreModuleName = lambda: coreName
 | 
						|
                return coreName
 | 
						|
        return None
 | 
						|
 | 
						|
    def qtDeclarativeModuleName(self):
 | 
						|
        modules = cdbext.listOfModules()
 | 
						|
        for declarativeModuleName in ['Qt5Qmld', 'Qt5Qml']:
 | 
						|
            if declarativeModuleName in modules:
 | 
						|
                self.qtDeclarativeModuleName = lambda: declarativeModuleName
 | 
						|
                return declarativeModuleName
 | 
						|
        matches = [module for module in modules if re.match('Qt5Qml.*', module)]
 | 
						|
        if matches:
 | 
						|
            declarativeModuleName = matches[0]
 | 
						|
            self.qtDeclarativeModuleName = lambda: declarativeModuleName
 | 
						|
            return declarativeModuleName
 | 
						|
        return None
 | 
						|
 | 
						|
    def qtHookDataSymbolName(self):
 | 
						|
        hookSymbolName = 'qtHookData'
 | 
						|
        coreModuleName = self.qtCoreModuleName()
 | 
						|
        if coreModuleName is not None:
 | 
						|
            hookSymbolName = '%s!%s%s' % (coreModuleName, self.qtNamespace(), hookSymbolName)
 | 
						|
        else:
 | 
						|
            resolved = cdbext.resolveSymbol('*' + hookSymbolName)
 | 
						|
            if resolved:
 | 
						|
                hookSymbolName = resolved[0]
 | 
						|
            else:
 | 
						|
                hookSymbolName = '*%s' % hookSymbolName
 | 
						|
        self.qtHookDataSymbolName = lambda: hookSymbolName
 | 
						|
        return hookSymbolName
 | 
						|
 | 
						|
    def qtDeclarativeHookDataSymbolName(self):
 | 
						|
        hookSymbolName = 'qtDeclarativeHookData'
 | 
						|
        declarativeModuleName = self.qtDeclarativeModuleName()
 | 
						|
        if declarativeModuleName is not None:
 | 
						|
            hookSymbolName = '%s!%s%s' % (declarativeModuleName, self.qtNamespace(), hookSymbolName)
 | 
						|
        else:
 | 
						|
            resolved = cdbext.resolveSymbol('*' + hookSymbolName)
 | 
						|
            if resolved:
 | 
						|
                hookSymbolName = resolved[0]
 | 
						|
            else:
 | 
						|
                hookSymbolName = '*%s' % hookSymbolName
 | 
						|
 | 
						|
        self.qtDeclarativeHookDataSymbolName = lambda: hookSymbolName
 | 
						|
        return hookSymbolName
 | 
						|
 | 
						|
    def qtNamespace(self):
 | 
						|
        namespace = ''
 | 
						|
        qstrdupSymbolName = '*qstrdup'
 | 
						|
        coreModuleName = self.qtCoreModuleName()
 | 
						|
        if coreModuleName is not None:
 | 
						|
            qstrdupSymbolName = '%s!%s' % (coreModuleName, qstrdupSymbolName)
 | 
						|
        resolved = cdbext.resolveSymbol(qstrdupSymbolName)
 | 
						|
        if resolved:
 | 
						|
            name = resolved[0].split('!')[1]
 | 
						|
            namespaceIndex = name.find('::')
 | 
						|
            if namespaceIndex > 0:
 | 
						|
                namespace = name[:namespaceIndex + 2]
 | 
						|
        self.qtNamespace = lambda: namespace
 | 
						|
        self.qtCustomEventFunc = cdbext.parseAndEvaluate('%s!%sQObject::customEvent'
 | 
						|
                                                         % (self.qtCoreModuleName(), namespace))
 | 
						|
        return namespace
 | 
						|
 | 
						|
    def couldBeQObjectVTable(self, vtablePtr):
 | 
						|
        try:
 | 
						|
            customEventFunc = self.extractPointer(vtablePtr + 8 * self.ptrSize())
 | 
						|
        except:
 | 
						|
            self.bump('nostruct-3')
 | 
						|
            return False
 | 
						|
 | 
						|
        if customEventFunc in (self.qtCustomEventFunc, self.qtCustomEventPltFunc):
 | 
						|
            return True
 | 
						|
        try:
 | 
						|
            delta = int.from_bytes(self.readRawMemory(customEventFunc + 1, 4), byteorder='little')
 | 
						|
            if (customEventFunc + 5 + delta) in (self.qtCustomEventFunc, self.qtCustomEventPltFunc):
 | 
						|
                return True
 | 
						|
        except:
 | 
						|
            pass
 | 
						|
 | 
						|
        try:
 | 
						|
            return 'QObject::customEvent' in cdbext.getNameByAddress(customEventFunc)
 | 
						|
        except:
 | 
						|
            return False
 | 
						|
 | 
						|
 | 
						|
    def qtVersion(self):
 | 
						|
        qtVersion = self.findValueByExpression('((void**)&%s)[2]' % self.qtHookDataSymbolName())
 | 
						|
        if qtVersion is None and self.qtCoreModuleName() is not None:
 | 
						|
            try:
 | 
						|
                versionValue = cdbext.call(self.qtCoreModuleName() + '!qVersion()')
 | 
						|
                version = self.extractCString(self.fromNativeValue(versionValue).address())
 | 
						|
                (major, minor, patch) = version.decode('latin1').split('.')
 | 
						|
                qtVersion = 0x10000 * int(major) + 0x100 * int(minor) + int(patch)
 | 
						|
            except:
 | 
						|
                pass
 | 
						|
        if qtVersion is None:
 | 
						|
            qtVersion = self.fallbackQtVersion
 | 
						|
        self.qtVersion = lambda: qtVersion
 | 
						|
        return qtVersion
 | 
						|
 | 
						|
    def putVtableItem(self, address):
 | 
						|
        funcName = cdbext.getNameByAddress(address)
 | 
						|
        if funcName is None:
 | 
						|
            self.putItem(self.createPointerValue(address, 'void'))
 | 
						|
        else:
 | 
						|
            self.putValue(funcName)
 | 
						|
            self.putType('void*')
 | 
						|
            self.putAddress(address)
 | 
						|
 | 
						|
    def putVTableChildren(self, item, itemCount):
 | 
						|
        p = item.address()
 | 
						|
        for i in xrange(itemCount):
 | 
						|
            deref = self.extractPointer(p)
 | 
						|
            if deref == 0:
 | 
						|
                n = i
 | 
						|
                break
 | 
						|
            with SubItem(self, i):
 | 
						|
                self.putVtableItem(deref)
 | 
						|
                p += self.ptrSize()
 | 
						|
        return itemCount
 | 
						|
 | 
						|
    def ptrSize(self):
 | 
						|
        size = cdbext.pointerSize()
 | 
						|
        self.ptrSize = lambda: size
 | 
						|
        return size
 | 
						|
 | 
						|
    def put(self, stuff):
 | 
						|
        self.output += stuff
 | 
						|
 | 
						|
    def stripQintTypedefs(self, typeName):
 | 
						|
        if typeName.startswith('qint'):
 | 
						|
            prefix = ''
 | 
						|
            size = typeName[4:]
 | 
						|
        elif typeName.startswith('quint'):
 | 
						|
            prefix = 'unsigned '
 | 
						|
            size = typeName[5:]
 | 
						|
        else:
 | 
						|
            return typeName
 | 
						|
        if size == '8':
 | 
						|
            return '%schar' % prefix
 | 
						|
        elif size == '16':
 | 
						|
            return '%sshort' % prefix
 | 
						|
        elif size == '32':
 | 
						|
            return '%sint' % prefix
 | 
						|
        elif size == '64':
 | 
						|
            return '%sint64' % prefix
 | 
						|
        else:
 | 
						|
            return typeName
 | 
						|
 | 
						|
    def lookupType(self, typeNameIn):
 | 
						|
        if len(typeNameIn) == 0:
 | 
						|
            return None
 | 
						|
        typeName = self.stripQintTypedefs(typeNameIn)
 | 
						|
        if self.typeData.get(typeName, None) is None:
 | 
						|
            nativeType = self.lookupNativeType(typeName)
 | 
						|
            return None if nativeType is None else self.fromNativeType(nativeType)
 | 
						|
        return self.Type(self, typeName)
 | 
						|
 | 
						|
    def lookupNativeType(self, name):
 | 
						|
        if name.startswith('void'):
 | 
						|
            return FakeVoidType(name, self)
 | 
						|
        return cdbext.lookupType(name)
 | 
						|
 | 
						|
    def reportResult(self, result, args):
 | 
						|
        self.report('result={%s}' % (result))
 | 
						|
 | 
						|
    def readRawMemory(self, address, size):
 | 
						|
        mem = cdbext.readRawMemory(address, size)
 | 
						|
        if len(mem) != size:
 | 
						|
            raise Exception("Invalid memory request")
 | 
						|
        return mem
 | 
						|
 | 
						|
    def findStaticMetaObject(self, typeName):
 | 
						|
        ptr = self.findValueByExpression('&' + typeName + '::staticMetaObject')
 | 
						|
        return ptr
 | 
						|
 | 
						|
    def warn(self, msg):
 | 
						|
        self.put('{name="%s",value="",type="",numchild="0"},' % msg)
 | 
						|
 | 
						|
    def fetchVariables(self, args):
 | 
						|
        self.resetStats()
 | 
						|
        (ok, res) = self.tryFetchInterpreterVariables(args)
 | 
						|
        if ok:
 | 
						|
            self.reportResult(res, args)
 | 
						|
            return
 | 
						|
 | 
						|
        self.setVariableFetchingOptions(args)
 | 
						|
 | 
						|
        self.output = ''
 | 
						|
 | 
						|
        self.currentIName = 'local'
 | 
						|
        self.put('data=[')
 | 
						|
        self.anonNumber = 0
 | 
						|
 | 
						|
        variables = []
 | 
						|
        for val in cdbext.listOfLocals(self.partialVariable):
 | 
						|
            variables.append(self.fromNativeValue(val))
 | 
						|
 | 
						|
        self.handleLocals(variables)
 | 
						|
        self.handleWatches(args)
 | 
						|
 | 
						|
        self.put('],partial="%d"' % (len(self.partialVariable) > 0))
 | 
						|
        self.put(',timings=%s' % self.timings)
 | 
						|
        self.reportResult(self.output, args)
 | 
						|
 | 
						|
    def report(self, stuff):
 | 
						|
        sys.stdout.write(stuff + "\n")
 | 
						|
 | 
						|
    def loadDumpers(self, args):
 | 
						|
        msg = self.setupDumpers()
 | 
						|
        self.reportResult(msg, args)
 | 
						|
 | 
						|
    def findValueByExpression(self, exp):
 | 
						|
        return cdbext.parseAndEvaluate(exp)
 | 
						|
 | 
						|
    def nativeDynamicTypeName(self, address, baseType):
 | 
						|
        return None # FIXME: Seems sufficient, no idea why.
 | 
						|
 | 
						|
    def callHelper(self, rettype, value, function, args):
 | 
						|
        raise Exception("cdb does not support calling functions")
 | 
						|
 | 
						|
    def putCallItem(self, name, rettype, value, func, *args):
 | 
						|
        return
 |