From 525c33f9991766342b41a0518b534836dc60ed69 Mon Sep 17 00:00:00 2001 From: hjk Date: Thu, 8 Oct 2015 16:19:57 +0200 Subject: [PATCH] Debugger: Infrastructure for reworked native mixed debugging - Remove old experimental native mixed approach. - Move some common stack parsing to Stackhandler. - Mark gdbbridge.py debug output explicitly to remove it from actual reponse handling New native mixed needs QtDeclarative changes and QTC_DEBUGGER_NATIVE_MIXED=1 for now. Change-Id: I09eed1da51cea878636d36756015b7bfaed34203 Reviewed-by: Christian Stenger --- share/qtcreator/debugger/dumper.py | 291 ++++++++---------- share/qtcreator/debugger/gdbbridge.py | 178 +++++------ share/qtcreator/debugger/lldbbridge.py | 47 ++- .../qmldebug/qmldebugcommandlinearguments.h | 10 +- src/plugins/debugger/breakhandler.cpp | 5 +- src/plugins/debugger/cdb/cdbengine.cpp | 54 ++-- src/plugins/debugger/debugger.qbs | 1 + .../debugger/debugger_dependencies.pri | 1 + src/plugins/debugger/debuggeractions.cpp | 12 - src/plugins/debugger/debuggeractions.h | 1 - src/plugins/debugger/debuggerconstants.h | 1 - src/plugins/debugger/debuggercore.h | 2 - src/plugins/debugger/debuggerengine.cpp | 31 +- src/plugins/debugger/debuggerengine.h | 5 + src/plugins/debugger/debuggerplugin.cpp | 24 -- src/plugins/debugger/debuggerprotocol.cpp | 25 +- src/plugins/debugger/debuggerprotocol.h | 6 +- src/plugins/debugger/debuggerruncontrol.cpp | 39 ++- src/plugins/debugger/gdb/gdbengine.cpp | 180 +++++------ src/plugins/debugger/gdb/gdbengine.h | 4 +- src/plugins/debugger/lldb/lldbengine.cpp | 40 +-- src/plugins/debugger/pdb/pdbengine.cpp | 8 +- src/plugins/debugger/qml/qmlengine.cpp | 11 +- src/plugins/debugger/stackframe.cpp | 92 ++++-- src/plugins/debugger/stackframe.h | 16 +- src/plugins/debugger/stackhandler.cpp | 16 +- src/plugins/debugger/stackhandler.h | 12 +- src/plugins/debugger/stackwindow.cpp | 2 +- src/plugins/debugger/watchdata.cpp | 4 +- tests/auto/debugger/tst_dumpers.cpp | 8 +- 30 files changed, 568 insertions(+), 558 deletions(-) diff --git a/share/qtcreator/debugger/dumper.py b/share/qtcreator/debugger/dumper.py index 3d82e743cdb..efc5249e4e3 100644 --- a/share/qtcreator/debugger/dumper.py +++ b/share/qtcreator/debugger/dumper.py @@ -34,6 +34,7 @@ import sys import base64 import re import time +import json if sys.version_info[0] >= 3: xrange = range @@ -241,7 +242,7 @@ class Blob(object): return struct.unpack_from("f", self.data, offset)[0] def warn(message): - print("XXX: %s\n" % message.encode("latin1")) + print('bridgemessage={msg="%s"},' % message.replace('"', '$').encode("latin1")) def showException(msg, exType, exValue, exTraceback): @@ -413,6 +414,8 @@ class DumperBase: "personaltypes", ] + self.interpreterSeq = 0 + def resetCaches(self): # This is a cache mapping from 'type name' to 'display alternatives'. @@ -598,6 +601,9 @@ class DumperBase: data = self.extractBlob(addr, size).toBytes() return self.hexencode(data) + def readJsonFromMemory(self, addr, size): + return json.loads(self.hexdecode(self.readMemory(addr, size))) + def encodeByteArray(self, value, limit = 0): elided, data = self.encodeByteArrayHelper(self.extractPointer(value), limit) return data @@ -718,7 +724,6 @@ class DumperBase: pass return False - #warn("CHILDREN: %s %s %s" % (numChild, childType, childNumChild)) def putMapName(self, value, index = None): ns = self.qtNamespace() typeName = self.stripClassTag(str(value.type)) @@ -829,6 +834,16 @@ class DumperBase: self.putSpecialValue(SpecialItemCountValue, count) self.putNumChild(count) + def dictToMi(self, value): + if type(value) is bool: + return '"%d"' % int(value) + if type(value) is dict: + return '{' + ','.join(['%s=%s' % (k, self.dictToMi(v)) + for (k, v) in list(value.items())]) + '}' + if type(value) is list: + return '[' + ','.join([self.dictToMi(v) for v in value]) + ']' + return '"%s"' % value + def putField(self, name, value): self.put('%s="%s",' % (name, value)) @@ -1719,69 +1734,120 @@ class DumperBase: sys.path.insert(1, head) self.dumpermodules.append(os.path.splitext(tail)[0]) - def sendQmlCommand(self, command, data = ""): - data += '"version":"1","command":"%s"' % command - data = data.replace('"', '\\"') - expr = 'qt_v4DebuggerHook("{%s}")' % data - try: - res = self.parseAndEvaluate(expr) - print("QML command ok, RES: %s, CMD: %s" % (res, expr)) - except RuntimeError as error: - #print("QML command failed: %s: %s" % (expr, error)) - res = None - except AttributeError as error: - # Happens with LLDB and 'None' current thread. - #print("QML command failed: %s: %s" % (expr, error)) - res = None + def extractQStringFromQDataStream(self, buf, offset): + """ Read a QString from the stream """ + size = struct.unpack_from("!I", buf, offset)[0] + offset += 4 + string = buf[offset:offset + size].decode('utf-16be') + return (string, offset + size) + + def extractQByteArrayFromQDataStream(self, buf, offset): + """ Read a QByteArray from the stream """ + size = struct.unpack_from("!I", buf, offset)[0] + offset += 4 + string = buf[offset:offset + size].decode('latin1') + return (string, offset + size) + + def extractIntFromQDataStream(self, buf, offset): + """ Read an int from the stream """ + value = struct.unpack_from("!I", buf, offset)[0] + return (value, offset + 4) + + def readInterpreterOutput(self): + buf = self.parseAndEvaluate("qt_qmlDebugOutputBuffer") + size = self.parseAndEvaluate("qt_qmlDebugOutputLength") + return self.readJsonFromMemory(buf, size) + + def handleInterpreterEvent(self): + """ Return True if inferior stopped """ + buf = self.parseAndEvaluate("qt_qmlDebugEventBuffer") + size = self.parseAndEvaluate("qt_qmlDebugEventLength") + resdict = self.readJsonFromMemory(buf, size) + warn("RES DICT : %s" % resdict) + return resdict.get('event') == 'break' + + def removeInterpreterBreakpoint(self, args): + res = self.sendInterpreterRequest('removebreakpoint', { 'id' : args['id'] }) return res - def prepareQmlStep(self, _): - self.sendQmlCommand('prepareStep') - - def removeQmlBreakpoint(self, args): - fullName = args['fileName'] - lineNumber = args['lineNumber'] - #print("Remove QML breakpoint %s:%s" % (fullName, lineNumber)) - bp = self.sendQmlCommand('removeBreakpoint', - '"fullName":"%s","lineNumber":"%s",' - % (fullName, lineNumber)) - if bp is None: - #print("Direct QML breakpoint removal failed: %s.") - return 0 - #print("Removing QML breakpoint: %s" % bp) - return int(bp) - - def insertQmlBreakpoint(self, args): - print("Insert QML breakpoint %s" % self.describeBreakpointData(args)) - bp = self.doInsertQmlBreakpoint(args) - res = self.sendQmlCommand('prepareStep') - #if res is None: - # print("Resetting stepping failed.") + def insertInterpreterBreakpoint(self, args): + args['condition'] = self.hexdecode(args.get('condition', '')) + warn("Insert interpreter breakpoint %s:%s (%s)" + % (args['file'], args['line'], args['condition'])) + bp = self.doInsertInterpreterBreakpoint(args, False) return str(bp) - def doInsertQmlBreakpoint(self, args): - fullName = args['fileName'] - lineNumber = args['lineNumber'] - pos = fullName.rfind('/') - engineName = "qrc:/" + fullName[pos+1:] - bp = self.sendQmlCommand('insertBreakpoint', - '"fullName":"%s","lineNumber":"%s","engineName":"%s",' - % (fullName, lineNumber, engineName)) - if bp is None: - #print("Direct QML breakpoint insertion failed.") - #print("Make pending.") - self.createResolvePendingBreakpointsHookBreakpoint(args) - return 0 + def sendInterpreterRequest(self, command, args = {}): + self.interpreterSeq += 1 + cmd = { 'seq': self.interpreterSeq, 'type': 'request', 'command': command, 'arguments': args } + encoded = json.dumps(cmd) + hexdata = self.hexencode(encoded) + expr = 'qt_qmlDebugSendDataToService("NativeQmlDebugger","%s")' % hexdata + try: + res = self.parseAndEvaluate(expr) + except RuntimeError as error: + warn("Interpreter command failed: %s: %s" % (encoded, error)) + return {} + except AttributeError as error: + # Happens with LLDB and 'None' current thread. + warn("Interpreter command failed: %s: %s" % (encoded, error)) + return {} - print("Resolving QML breakpoint: %s" % bp) + if not res: + warn("Interpreter command failed: %s " % encoded) + return {} + resdict = self.readInterpreterOutput() + warn("Interpreter command output: '%s'" % resdict) + service = resdict.get("service") + if service == "NativeQmlDebugger": + messages = resdict.get("messages", []) + if len(messages) == 1: + return messages[0] + warn("Unexpected multiple interpreter messages: %s" % messages) + else: + warn("Interpreter result from alien service: %s" % service) + return {'messages': messages } + + def executeStep(self, args): + if self.nativeMixed: + response = self.sendInterpreterRequest('stepin', args) + self.doContinue() + + def executeStepOut(self, args): + if self.nativeMixed: + response = self.sendInterpreterRequest('stepout', args) + self.doContinue() + + def executeNext(self, args): + if self.nativeMixed: + response = self.sendInterpreterRequest('stepover', args) + self.doContinue() + + def executeContinue(self, args): + if self.nativeMixed: + response = self.sendInterpreterRequest('continue', args) + self.doContinue() + + def doInsertInterpreterBreakpoint(self, args, wasPending): + warn("DO INSERT INTERPRETER BREAKPOINT, WAS PENDING: %s" % wasPending) + # Will fail if the service is not yet up and running. + response = self.sendInterpreterRequest('setbreakpoint', args) + bp = None if response is None else response.get("breakpoint", None) + if wasPending: + if not bp: + warn("ERROR: Pending interpreter breakpoint insertion failed.") + return -1 + else: + if not bp: + warn("Direct interpreter breakpoint insertion failed.") + warn("Make pending.") + self.createResolvePendingBreakpointsHookBreakpoint(args) + return -1 + + warn("Resolved interpreter breakpoint: BP: %s" % bp) return int(bp) - def describeBreakpointData(self, args): - fullName = args['fileName'] - lineNumber = args['lineNumber'] - return "%s:%s" % (fullName, lineNumber) - - def isInternalQmlFrame(self, functionName): + def isInternalInterpreterFrame(self, functionName): if functionName is None: return False if functionName.startswith("qt_v4"): @@ -1793,7 +1859,7 @@ class DumperBase: def canCallLocale(self): return True - def isReportableQmlFrame(self, functionName): + def isReportableInterpreterFrame(self, functionName): return functionName and functionName.find("QV4::Moth::VME::exec") >= 0 def extractQmlData(self, value): @@ -1802,112 +1868,13 @@ class DumperBase: data = value["data"] return data.cast(self.lookupType(str(value.type).replace("QV4::", "QV4::Heap::"))) - def extractQmlRuntimeString(self, compilationUnitPtr, index): - # This mimics compilationUnit->runtimeStrings[index] - # typeof runtimeStrings = QV4.StringValue ** - runtimeStrings = compilationUnitPtr.dereference()["runtimeStrings"] - entry = runtimeStrings[index] - text = self.extractPointer(entry.dereference(), self.ptrSize()) - (elided, fn) = self.encodeStringHelper(text, 100) - return self.encodedUtf16ToUtf8(fn) - - def extractQmlLocation(self, engine): - if self.currentCallContext is None: - context = engine["current"] # QV4.ExecutionContext * or derived - self.currentCallContext = context - else: - context = self.currentCallContext["parent"] - ctxCode = int(context["type"]) - compilationUnit = context["compilationUnit"] - functionName = "Unknown JS"; - ns = self.qtNamespace() - - # QV4.ExecutionContext.Type_SimpleCallContext - 4 - # QV4.ExecutionContext.Type_CallContext - 5 - if ctxCode == 4 or ctxCode == 5: - callContextDataType = self.lookupQtType("QV4::Heap::CallContext") - callContext = context.cast(callContextDataType.pointer()) - functionObject = callContext["function"] - function = functionObject["function"] - # QV4.CompiledData.Function - compiledFunction = function["compiledFunction"].dereference() - index = int(compiledFunction["nameIndex"]) - functionName = "JS: " + self.extractQmlRuntimeString(compilationUnit, index) - - string = self.parseAndEvaluate("((%s)0x%x)->fileName()" - % (compilationUnit.type, compilationUnit)) - fileName = self.encodeStringUtf8(string) - - return {'functionName': functionName, - 'lineNumber': int(context["lineNumber"]), - 'fileName': fileName, - 'context': context } - # Contains iname, name, and value. class LocalItem: pass - def extractQmlVariables(self, qmlcontext): - items = [] - - contextType = self.lookupQtType("QV4::Heap::CallContext") - context = self.createPointerValue(self.qmlcontext, contextType) - - contextItem = self.LocalItem() - contextItem.iname = "local.@context" - contextItem.name = "[context]" - contextItem.value = context.dereference() - items.append(contextItem) - - argsItem = self.LocalItem() - argsItem.iname = "local.@args" - argsItem.name = "[args]" - argsItem.value = context["callData"] - items.append(argsItem) - - functionObject = context["function"].dereference() - functionPtr = functionObject["function"] - if not self.isNull(functionPtr): - compilationUnit = context["compilationUnit"] - compiledFunction = functionPtr["compiledFunction"] - base = int(compiledFunction) - - formalsOffset = int(compiledFunction["formalsOffset"]) - formalsCount = int(compiledFunction["nFormals"]) - for index in range(formalsCount): - stringIndex = self.extractInt(base + formalsOffset + 4 * index) - name = self.extractQmlRuntimeString(compilationUnit, stringIndex) - item = self.LocalItem() - item.iname = "local." + name - item.name = name - item.value = argsItem.value["args"][index] - items.append(item) - - localsOffset = int(compiledFunction["localsOffset"]) - localsCount = int(compiledFunction["nLocals"]) - for index in range(localsCount): - stringIndex = self.extractInt(base + localsOffset + 4 * index) - name = self.extractQmlRuntimeString(compilationUnit, stringIndex) - item = self.LocalItem() - item.iname = "local." + name - item.name = name - item.value = context["locals"][index] - items.append(item) - - for engine in self.qmlEngines: - engineItem = self.LocalItem() - engineItem.iname = "local.@qmlengine" - engineItem.name = "[engine]" - engineItem.value = engine - items.append(engineItem) - - rootContext = self.LocalItem() - rootContext.iname = "local.@rootContext" - rootContext.name = "[rootContext]" - rootContext.value = engine["d_ptr"] - items.append(rootContext) - break - - return items + def extractInterpreterStack(self): + return self.sendInterpreterRequest('backtrace', {'limit': 10 }) + def extractInterpreterVariables(self, args): + return self.sendInterpreterRequest('variables', args) diff --git a/share/qtcreator/debugger/gdbbridge.py b/share/qtcreator/debugger/gdbbridge.py index 2ada3ae02a8..526e0359faf 100644 --- a/share/qtcreator/debugger/gdbbridge.py +++ b/share/qtcreator/debugger/gdbbridge.py @@ -14,9 +14,6 @@ import sys import struct import types -def warn(message): - print("XXX: %s\n" % message.encode("latin1")) - from dumper import * @@ -221,15 +218,14 @@ class Dumper(DumperBase): def __init__(self): DumperBase.__init__(self) - # These values will be kept between calls to 'showData'. + # These values will be kept between calls to 'fetchVariables'. self.isGdb = True self.childEventAddress = None self.typeCache = {} self.typesReported = {} self.typesToReport = {} self.qtNamespaceToReport = None - self.qmlEngines = [] - self.qmlBreakpoints = [] + self.interpreterBreakpoints = [] def prepare(self, args): self.output = [] @@ -243,7 +239,7 @@ class Dumper(DumperBase): self.currentType = ReportItem() self.currentAddress = None - # The guess does not need to be updated during a showData() + # The guess does not need to be updated during a fetchVariables() # as the result is fixed during that time (ignoring "active" # dumpers causing loading of shared objects etc). self.currentQtNamespaceGuess = None @@ -255,11 +251,10 @@ class Dumper(DumperBase): self.typeformats = args.get("typeformats", {}) self.formats = args.get("formats", {}) self.watchers = args.get("watchers", {}) - self.qmlcontext = int(args.get("qmlcontext", "0"), 0) self.useDynamicType = int(args.get("dyntype", "0")) self.useFancy = int(args.get("fancy", "0")) self.forceQtNamespace = int(args.get("forcens", "0")) - self.passExceptions = int(args.get("passExceptions", "0")) + self.passExceptions = int(args.get("passexeptions", "0")) self.nativeMixed = int(args.get("nativemixed", "0")) self.autoDerefPointers = int(args.get("autoderef", "0")) self.partialUpdate = int(args.get("partial", "0")) @@ -359,21 +354,25 @@ class Dumper(DumperBase): def canCallLocale(self): return False if self.is32bit() else True - def showData(self, args): + def fetchVariables(self, args): self.prepare(args) - partialVariable = args.get("partialVariable", "") isPartial = len(partialVariable) > 0 + if self.nativeMixed: + context = args.get('context', '') + if len(context): + res = self.extractInterpreterVariables(args) + if res: + safePrint('data=%s' % self.dictToMi(res.get('data', {}))) + return + # # Locals # self.output.append('data=[') - if self.qmlcontext: - locals = self.extractQmlVariables(self.qmlcontext) - - elif isPartial: + if isPartial: parts = partialVariable.split('.') name = parts[1] item = self.LocalItem() @@ -498,7 +497,7 @@ class Dumper(DumperBase): def parseAndEvaluate(self, exp): return gdb.parse_and_eval(exp) - def callHelper(self, value, func, args): + def callHelper(self, value, function, args): # args is a tuple. arg = "" for i in range(len(args)): @@ -510,14 +509,14 @@ class Dumper(DumperBase): else: arg += a - #warn("CALL: %s -> %s(%s)" % (value, func, arg)) + #warn("CALL: %s -> %s(%s)" % (value, function, arg)) typeName = self.stripClassTag(str(value.type)) if typeName.find(":") >= 0: typeName = "'" + typeName + "'" # 'class' is needed, see http://sourceware.org/bugzilla/show_bug.cgi?id=11912 - #exp = "((class %s*)%s)->%s(%s)" % (typeName, value.address, func, arg) + #exp = "((class %s*)%s)->%s(%s)" % (typeName, value.address, function, arg) ptr = value.address if value.address else self.pokeValue(value) - exp = "((%s*)%s)->%s(%s)" % (typeName, ptr, func, arg) + exp = "((%s*)%s)->%s(%s)" % (typeName, ptr, function, arg) #warn("CALL: %s" % exp) result = gdb.parse_and_eval(exp) #warn(" -> %s" % result) @@ -739,32 +738,32 @@ class Dumper(DumperBase): self.selectedInferior = lambda: self.cachedInferior return self.cachedInferior - def readRawMemory(self, addr, size): - mem = self.selectedInferior().read_memory(addr, size) + def readRawMemory(self, address, size): + mem = self.selectedInferior().read_memory(address, size) if sys.version_info[0] >= 3: mem.tobytes() return mem - def extractInt64(self, addr): - return struct.unpack("q", self.readRawMemory(addr, 8))[0] + def extractInt64(self, address): + return struct.unpack("q", self.readRawMemory(address, 8))[0] - def extractUInt64(self, addr): - return struct.unpack("Q", self.readRawMemory(addr, 8))[0] + def extractUInt64(self, address): + return struct.unpack("Q", self.readRawMemory(address, 8))[0] - def extractInt(self, addr): - return struct.unpack("i", self.readRawMemory(addr, 4))[0] + def extractInt(self, address): + return struct.unpack("i", self.readRawMemory(address, 4))[0] - def extractUInt(self, addr): - return struct.unpack("I", self.readRawMemory(addr, 4))[0] + def extractUInt(self, address): + return struct.unpack("I", self.readRawMemory(address, 4))[0] - def extractShort(self, addr): - return struct.unpack("h", self.readRawMemory(addr, 2))[0] + def extractShort(self, address): + return struct.unpack("h", self.readRawMemory(address, 2))[0] - def extractUShort(self, addr): - return struct.unpack("H", self.readRawMemory(addr, 2))[0] + def extractUShort(self, address): + return struct.unpack("H", self.readRawMemory(address, 2))[0] - def extractByte(self, addr): - return struct.unpack("b", self.readRawMemory(addr, 1))[0] + def extractByte(self, address): + return struct.unpack("b", self.readRawMemory(address, 1))[0] def findStaticMetaObject(self, typename): return self.findSymbol(typename + "::staticMetaObject") @@ -847,12 +846,12 @@ class Dumper(DumperBase): self.isQt3Support = lambda: self.cachedIsQt3Suport return self.cachedIsQt3Suport - def putAddress(self, addr): + def putAddress(self, address): if self.currentPrintsAddress and not self.isCli: try: - # addr can be "None", int(None) fails. - #self.put('addr="0x%x",' % int(addr)) - self.currentAddress = 'addr="0x%x",' % toInteger(addr) + # address can be "None", int(None) fails. + #self.put('address="0x%x",' % int(address)) + self.currentAddress = 'address="0x%x",' % toInteger(address) except: pass @@ -1574,6 +1573,9 @@ class Dumper(DumperBase): self.typesToReport[typestring] = typeobj return typeobj + def doContinue(self): + gdb.execute('continue') + def stackListFrames(self, args): def fromNativePath(str): return str.replace('\\', '/') @@ -1581,12 +1583,8 @@ class Dumper(DumperBase): limit = int(args['limit']) if limit <= 0: limit = 10000 - options = args['options'] - opts = {} - if options == "nativemixed": - opts["nativemixed"] = 1 - self.prepare(opts) + self.prepare(args) self.output = [] frame = gdb.newest_frame() @@ -1598,7 +1596,7 @@ class Dumper(DumperBase): functionName = "??" if name is None else name fileName = "" objfile = "" - fullName = "" + symtab = "" pc = frame.pc() sal = frame.find_sal() line = -1 @@ -1607,62 +1605,57 @@ class Dumper(DumperBase): symtab = sal.symtab if not symtab is None: objfile = fromNativePath(symtab.objfile.filename) - fileName = fromNativePath(symtab.filename) - fullName = symtab.fullname() - if fullName is None: - fullName = "" - else: - fullName = fromNativePath(fullName) + fileName = fromNativePath(symtab.fullname()) - if self.nativeMixed: - if self.isReportableQmlFrame(functionName): - engine = frame.read_var("engine") - h = self.extractQmlLocation(engine) - self.put(('frame={level="%s",func="%s",file="%s",' - 'fullname="%s",line="%s",language="js",addr="0x%x"}') - % (i, h['functionName'], h['fileName'], h['fileName'], - h['lineNumber'], h['context'])) + if self.nativeMixed and functionName == "qt_qmlDebugEventFromService": + interpreterStack = self.extractInterpreterStack() + #print("EXTRACTED INTEPRETER STACK: %s" % interpreterStack) + for interpreterFrame in interpreterStack.get('frames', []): + function = interpreterFrame.get('function', '') + fileName = interpreterFrame.get('file', '') + language = interpreterFrame.get('language', '') + lineNumber = interpreterFrame.get('line', 0) + context = interpreterFrame.get('context', 0) + self.put(('frame={function="%s",file="%s",' + 'line="%s",language="%s",context="%s"}') + % (function, fileName, lineNumber, language, context)) + + if False and self.isInternalInterpreterFrame(functionName): + frame = frame.older() + self.put(('frame={address="0x%x",function="%s",' + 'file="%s",line="%s",' + 'module="%s",language="c",usable="0"}') % + (pc, functionName, fileName, line, objfile)) i += 1 frame = frame.older() continue - if self.isInternalQmlFrame(functionName): - frame = frame.older() - self.put(('frame={level="%s",addr="0x%x",func="%s",' - 'file="%s",fullname="%s",line="%s",' - 'from="%s",language="c",usable="0"}') % - (i, pc, functionName, fileName, fullName, line, objfile)) - i += 1 - frame = frame.older() - continue - - self.put(('frame={level="%s",addr="0x%x",func="%s",' - 'file="%s",fullname="%s",line="%s",' - 'from="%s",language="c"}') % - (i, pc, functionName, fileName, fullName, line, objfile)) + self.put(('frame={level="%s",address="0x%x",function="%s",' + 'file="%s",line="%s",module="%s",language="c"}') % + (i, pc, functionName, fileName, line, objfile)) frame = frame.older() i += 1 - safePrint(''.join(self.output)) + safePrint('frames=[' + ','.join(self.output) + ']') def createResolvePendingBreakpointsHookBreakpoint(self, args): class Resolver(gdb.Breakpoint): def __init__(self, dumper, args): self.dumper = dumper self.args = args - spec = "qt_v4ResolvePendingBreakpointsHook" + spec = "qt_qmlDebugConnectorOpen" print("Preparing hook to resolve pending QML breakpoint at %s" % args) super(Resolver, self).\ __init__(spec, gdb.BP_BREAKPOINT, internal=True, temporary=False) def stop(self): - bp = self.dumper.doInsertQmlBreakpoint(args) + bp = self.dumper.doInsertInterpreterBreakpoint(args, True) print("Resolving QML breakpoint %s -> %s" % (args, bp)) self.enabled = False return False - self.qmlBreakpoints.append(Resolver(self, args)) + self.interpreterBreakpoints.append(Resolver(self, args)) def exitGdb(self, _): gdb.execute("quit") @@ -1675,13 +1668,13 @@ class Dumper(DumperBase): import tempfile import cProfile tempDir = tempfile.gettempdir() + "/bbprof" - cProfile.run('theDumper.showData(%s)' % args, tempDir) + cProfile.run('theDumper.fetchVariables(%s)' % args, tempDir) import pstats pstats.Stats(tempDir).sort_stats('time').print_stats() def profile2(self, args): import timeit - print(timeit.repeat('theDumper.showData(%s)' % args, + print(timeit.repeat('theDumper.fetchVariables(%s)' % args, 'from __main__ import theDumper', number=10)) @@ -1774,7 +1767,7 @@ class CliDumper(Dumper): def putAddressRange(self, base, step): return True - def showData(self, args): + def fetchVariables(self, args): args['fancy'] = 1 args['passException'] = 1 args['autoderef'] = 1 @@ -1811,20 +1804,6 @@ registerCommand("threadnames", threadnames) # ####################################################################### -#class QmlEngineCreationTracker(gdb.Breakpoint): -# def __init__(self): -# spec = "QQmlEnginePrivate::init" -# super(QmlEngineCreationTracker, self).\ -# __init__(spec, gdb.BP_BREAKPOINT, internal=True) -# -# def stop(self): -# engine = gdb.parse_and_eval("q_ptr") -# print("QML engine created: %s" % engine) -# theDumper.qmlEngines.append(engine) -# return False -# -#QmlEngineCreationTracker() - class TriggeredBreakpointHookBreakpoint(gdb.Breakpoint): def __init__(self): spec = "qt_v4TriggeredBreakpointHook" @@ -1837,3 +1816,14 @@ class TriggeredBreakpointHookBreakpoint(gdb.Breakpoint): TriggeredBreakpointHookBreakpoint() +class QmlEngineEventBreakpoint(gdb.Breakpoint): + def __init__(self): + spec = "qt_qmlDebugEventFromService" + super(QmlEngineEventBreakpoint, self).\ + __init__(spec, gdb.BP_BREAKPOINT, internal=True) + + def stop(self): + print("QML engine event received.") + return theDumper.handleInterpreterEvent() + +QmlEngineEventBreakpoint() diff --git a/share/qtcreator/debugger/lldbbridge.py b/share/qtcreator/debugger/lldbbridge.py index 8a4da00499b..ed4d3e4053e 100644 --- a/share/qtcreator/debugger/lldbbridge.py +++ b/share/qtcreator/debugger/lldbbridge.py @@ -57,7 +57,7 @@ def showException(msg, exType, exValue, exTraceback): lines = [line for line in traceback.format_exception(exType, exValue, exTraceback)] warn('\n'.join(lines)) -def fileName(file): +def fileNameAsString(file): return str(file) if file.IsValid() else '' @@ -775,7 +775,7 @@ class Dumper(DumperBase): def describeLocation(self, frame): if int(frame.pc) == 0xffffffffffffffff: return '' - file = fileName(frame.line_entry.file) + file = fileNameAsString(frame.line_entry.file) line = frame.line_entry.line return 'location={file="%s",line="%s",addr="%s"}' % (file, line, frame.pc) @@ -822,8 +822,8 @@ class Dumper(DumperBase): result += ',fp="0x%x"' % frame.fp result += ',func="%s"' % frame.GetFunctionName() result += ',line="%s"' % frame.line_entry.line - result += ',fullname="%s"' % fileName(frame.line_entry.file) - result += ',file="%s"' % fileName(frame.line_entry.file) + 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 @@ -849,7 +849,7 @@ class Dumper(DumperBase): self.report(self.describeLocation(thread.GetFrameAtIndex(0))) # FIXME - isNativeMixed = int(args.get('nativeMixed', 0)) + isNativeMixed = int(args.get('nativemixed', 0)) limit = args.get('stacklimit', -1) (n, isLimited) = (limit, True) if limit > 0 else (thread.GetNumFrames(), False) @@ -869,39 +869,38 @@ class Dumper(DumperBase): level = frame.idx addr = frame.GetPCAddress().GetLoadAddress(self.target) functionName = frame.GetFunctionName() - fullname = fileName(lineEntry.file) + fileName = fileNameAsString(lineEntry.file) usable = None language = None - if isNativeMixed: - if self.isReportableQmlFrame(functionName): + if False and isNativeMixed: + if self.isReportableInterpreterFrame(functionName): engine = frame.FindVariable("engine") self.context = engine h = self.extractQmlLocation(engine) pc = 0 - functionName = h['functionName'] - fullname = h['fileName'] - lineNumber = h['lineNumber'] + functionName = h['function'] + fileName = h['file'] + lineNumber = h['line'] addr = h['context'] language = 'js' - elif not functionName is None: - if functionName.startswith("qt_v4"): - usable = 0 - elif functionName.find("QV4::") >= 0: - usable = 0 + #elif not functionName is None: + # if functionName.startswith("qt_v4"): + # usable = 0 + # elif functionName.find("QV4::") >= 0: + # usable = 0 result += '{pc="0x%x"' % pc result += ',level="%d"' % level - result += ',addr="0x%x"' % addr + result += ',address="0x%x"' % addr if not usable is None: result += ',usable="%s"' % usable - result += ',func="%s"' % functionName + result += ',function="%s"' % functionName result += ',line="%d"' % lineNumber if not language is None: result += ',language="%s"' % language - result += ',fullname="%s"' % fullname - result += ',file="%s"},' % fullname + result += ',file="%s"},' % fileName result += ']' result += ',hasmore="%d"' % isLimited result += ',limit="%d"' % limit @@ -1400,7 +1399,7 @@ class Dumper(DumperBase): addr = loc.GetAddress() lineEntry = addr.GetLineEntry() result += '{locid="%s"' % loc.GetID() - result += ',func="%s"' % addr.GetFunction().GetName() + result += ',function="%s"' % addr.GetFunction().GetName() result += ',enabled="%s"' % (1 if loc.IsEnabled() else 0) result += ',resolved="%s"' % (1 if loc.IsResolved() else 0) result += ',valid="%s"' % (1 if loc.IsValid() else 0) @@ -1421,16 +1420,16 @@ class Dumper(DumperBase): def insertBreakpoint(self, args): bpType = args["type"] if bpType == BreakpointByFileAndLine: - fileName = args["fileName"] + fileName = args["file"] if fileName.endswith(".js") or fileName.endswith(".qml"): - self.insertQmlBreakpoint(args) + self.doInsertInterpreterBreakpoint(args, False) return extra = '' more = True if bpType == BreakpointByFileAndLine: bp = self.target.BreakpointCreateByLocation( - str(args["fileName"]), int(args["lineNumber"])) + str(args["file"]), int(args["line"])) elif bpType == BreakpointByFunction: bp = self.target.BreakpointCreateByName(args["function"]) elif bpType == BreakpointByAddress: diff --git a/src/libs/qmldebug/qmldebugcommandlinearguments.h b/src/libs/qmldebug/qmldebugcommandlinearguments.h index 3ef0bf77e18..cfa26d929d0 100644 --- a/src/libs/qmldebug/qmldebugcommandlinearguments.h +++ b/src/libs/qmldebug/qmldebugcommandlinearguments.h @@ -34,10 +34,12 @@ #include namespace QmlDebug { + enum QmlDebugServicesPreset { NoQmlDebugServices, QmlDebuggerServices, - QmlProfilerServices + QmlProfilerServices, + QmlNativeDebuggerServices }; static inline QString qmlDebugServices(QmlDebugServicesPreset preset) @@ -49,6 +51,8 @@ static inline QString qmlDebugServices(QmlDebugServicesPreset preset) return QStringLiteral("DebugMessages,QmlDebugger,V8Debugger,QmlInspector"); case QmlProfilerServices: return QStringLiteral("CanvasFrameRate,EngineControl"); + case QmlNativeDebuggerServices: + return QStringLiteral("NativeQmlDebugger"); default: Q_ASSERT(false); return QString(); @@ -61,6 +65,10 @@ static inline QString qmlDebugCommandLineArguments(QmlDebugServicesPreset servic if (services == NoQmlDebugServices) return QString(); + if (services == QmlNativeDebuggerServices) + return QString::fromLatin1("-qmljsdebugger=native,services:%1") + .arg(qmlDebugServices(services)); + return QString::fromLatin1("-qmljsdebugger=port:%1,block,services:%2") .arg(port ? QString::number(port) : QStringLiteral("%qml_port%")) .arg(qmlDebugServices(services)); diff --git a/src/plugins/debugger/breakhandler.cpp b/src/plugins/debugger/breakhandler.cpp index 44a0ad06f3b..b95d1965890 100644 --- a/src/plugins/debugger/breakhandler.cpp +++ b/src/plugins/debugger/breakhandler.cpp @@ -757,14 +757,15 @@ const BreakpointParameters &Breakpoint::parameters() const void Breakpoint::addToCommand(DebuggerCommand *cmd) const { cmd->arg("modelid", id().toByteArray()); + cmd->arg("id", int(response().id.majorPart())); cmd->arg("type", type()); cmd->arg("ignorecount", ignoreCount()); cmd->arg("condition", condition().toHex()); cmd->arg("function", functionName().toUtf8()); cmd->arg("oneshot", isOneShot()); cmd->arg("enabled", isEnabled()); - cmd->arg("fileName", fileName().toUtf8()); - cmd->arg("lineNumber", lineNumber()); + cmd->arg("file", fileName().toUtf8()); + cmd->arg("line", lineNumber()); cmd->arg("address", address()); cmd->arg("expression", expression()); } diff --git a/src/plugins/debugger/cdb/cdbengine.cpp b/src/plugins/debugger/cdb/cdbengine.cpp index 22ce1c2278a..1a69843726d 100644 --- a/src/plugins/debugger/cdb/cdbengine.cpp +++ b/src/plugins/debugger/cdb/cdbengine.cpp @@ -2539,28 +2539,29 @@ bool CdbEngine::stateAcceptsBreakpointChanges() const bool CdbEngine::acceptsBreakpoint(Breakpoint bp) const { - if (!bp.parameters().isCppBreakpoint()) - return false; - switch (bp.type()) { - case UnknownBreakpointType: - case LastBreakpointType: - case BreakpointAtFork: - case WatchpointAtExpression: - case BreakpointAtSysCall: - case BreakpointOnQmlSignalEmit: - case BreakpointAtJavaScriptThrow: - return false; - case WatchpointAtAddress: - case BreakpointByFileAndLine: - case BreakpointByFunction: - case BreakpointByAddress: - case BreakpointAtThrow: - case BreakpointAtCatch: - case BreakpointAtMain: - case BreakpointAtExec: - break; + if (bp.parameters().isCppBreakpoint()) { + switch (bp.type()) { + case UnknownBreakpointType: + case LastBreakpointType: + case BreakpointAtFork: + case WatchpointAtExpression: + case BreakpointAtSysCall: + case BreakpointOnQmlSignalEmit: + case BreakpointAtJavaScriptThrow: + return false; + case WatchpointAtAddress: + case BreakpointByFileAndLine: + case BreakpointByFunction: + case BreakpointByAddress: + case BreakpointAtThrow: + case BreakpointAtCatch: + case BreakpointAtMain: + case BreakpointAtExec: + break; + } + return true; } - return true; + return isNativeMixedEnabled(); } // Context for fixing file/line-type breakpoints, for delayed creation. @@ -2801,7 +2802,7 @@ static StackFrames parseFrames(const GdbMi &gdbmi, bool *incomplete = 0) break; } StackFrame frame; - frame.level = i; + frame.level = QByteArray::number(i); const GdbMi fullName = frameMi["fullname"]; if (fullName.isValid()) { frame.file = QFile::decodeName(fullName.data()); @@ -2811,9 +2812,10 @@ static StackFrames parseFrames(const GdbMi &gdbmi, bool *incomplete = 0) if (languageMi.isValid() && languageMi.data() == "js") frame.language = QmlLanguage; } - frame.function = QLatin1String(frameMi["func"].data()); - frame.from = QLatin1String(frameMi["from"].data()); - frame.address = frameMi["addr"].data().toULongLong(0, 16); + frame.function = QLatin1String(frameMi["function"].data()); + frame.module = QLatin1String(frameMi["from"].data()); + frame.context = frameMi["context"].data(); + frame.address = frameMi["address"].data().toULongLong(0, 16); rc.push_back(frame); } return rc; @@ -2895,7 +2897,7 @@ void CdbEngine::handleAdditionalQmlStack(const DebuggerResponse &response) break; } for (int i = 0; i < qmlFrameCount; ++i) - qmlFrames[i].fixQmlFrame(runParameters()); + qmlFrames[i].fixQrcFrame(runParameters()); stackHandler()->prependFrames(qmlFrames); } while (false); if (!errorMessage.isEmpty()) diff --git a/src/plugins/debugger/debugger.qbs b/src/plugins/debugger/debugger.qbs index e249c0c6b2b..c165487a9fd 100644 --- a/src/plugins/debugger/debugger.qbs +++ b/src/plugins/debugger/debugger.qbs @@ -15,6 +15,7 @@ QtcPlugin { Depends { name: "Core" } Depends { name: "CppTools" } Depends { name: "ProjectExplorer" } + Depends { name: "QtSupport" } Depends { name: "TextEditor" } cpp.includePaths: base.concat([project.sharedSourcesDir + "/registryaccess"]) diff --git a/src/plugins/debugger/debugger_dependencies.pri b/src/plugins/debugger/debugger_dependencies.pri index d337d67c3d4..259d30c6b1c 100644 --- a/src/plugins/debugger/debugger_dependencies.pri +++ b/src/plugins/debugger/debugger_dependencies.pri @@ -12,6 +12,7 @@ QTC_PLUGIN_DEPENDS += \ coreplugin \ cpptools \ projectexplorer \ + qtsupport \ texteditor QTC_PLUGIN_RECOMMENDS += \ cppeditor diff --git a/src/plugins/debugger/debuggeractions.cpp b/src/plugins/debugger/debuggeractions.cpp index bd025d77a2f..b1d7e193dcc 100644 --- a/src/plugins/debugger/debuggeractions.cpp +++ b/src/plugins/debugger/debuggeractions.cpp @@ -184,18 +184,6 @@ DebuggerSettings::DebuggerSettings() item->setIconVisibleInMenu(false); insertItem(OperateByInstruction, item); - item = new SavedAction(this); - item->setText(tr("Native Mixed Mode")); - item->setCheckable(true); - item->setDefaultValue(true); - item->setIcon(QIcon(QLatin1String(Core::Constants::ICON_LINK))); - item->setToolTip(tr("

This switches the debugger to native-mixed " - "operation mode. In this mode, stepping and data display will " - "be handled by the native debugger backend (GDB, LLDB or CDB) " - "for C++, QML and JS sources.")); - item->setIconVisibleInMenu(false); - insertItem(OperateNativeMixed, item); - item = new SavedAction(this); item->setText(tr("Dereference Pointers Automatically")); item->setCheckable(true); diff --git a/src/plugins/debugger/debuggeractions.h b/src/plugins/debugger/debuggeractions.h index 843d0dfadb4..c7dabd7015c 100644 --- a/src/plugins/debugger/debuggeractions.h +++ b/src/plugins/debugger/debuggeractions.h @@ -99,7 +99,6 @@ enum DebuggerActionCode LogTimeStamps, VerboseLog, OperateByInstruction, - OperateNativeMixed, CloseSourceBuffersOnExit, CloseMemoryBuffersOnExit, SwitchModeOnExit, diff --git a/src/plugins/debugger/debuggerconstants.h b/src/plugins/debugger/debuggerconstants.h index 9484990362d..0fc33fefec8 100644 --- a/src/plugins/debugger/debuggerconstants.h +++ b/src/plugins/debugger/debuggerconstants.h @@ -61,7 +61,6 @@ const char NEXT[] = "Debugger.NextLine"; const char REVERSE[] = "Debugger.ReverseDirection"; const char RESET[] = "Debugger.Reset"; const char OPERATE_BY_INSTRUCTION[] = "Debugger.OperateByInstruction"; -const char OPERATE_NATIVE_MIXED[] = "Debugger.OperateNativeMixed"; const char QML_SHOW_APP_ON_TOP[] = "Debugger.QmlShowAppOnTop"; const char QML_SELECTTOOL[] = "Debugger.QmlSelectTool"; const char QML_ZOOMTOOL[] = "Debugger.QmlZoomTool"; diff --git a/src/plugins/debugger/debuggercore.h b/src/plugins/debugger/debuggercore.h index 7e091496d8b..7e983ca362d 100644 --- a/src/plugins/debugger/debuggercore.h +++ b/src/plugins/debugger/debuggercore.h @@ -110,8 +110,6 @@ DebuggerEngine *currentEngine(); QMessageBox *showMessageBox(int icon, const QString &title, const QString &text, int buttons = 0); -bool isNativeMixedActive(); -bool isNativeMixedEnabled(); bool isReverseDebuggingEnabled(); } // namespace Internal diff --git a/src/plugins/debugger/debuggerengine.cpp b/src/plugins/debugger/debuggerengine.cpp index 6b242de2a65..2d5f14c0ebb 100644 --- a/src/plugins/debugger/debuggerengine.cpp +++ b/src/plugins/debugger/debuggerengine.cpp @@ -78,6 +78,11 @@ #include #include +#include +#include +#include +#include + using namespace Core; using namespace Debugger::Internal; using namespace ProjectExplorer; @@ -126,7 +131,7 @@ Location::Location(const StackFrame &frame, bool marker) m_functionName = frame.function; m_hasDebugInfo = frame.isUsable(); m_address = frame.address; - m_from = frame.from; + m_from = frame.module; } @@ -192,7 +197,7 @@ public: m_modulesHandler(engine), m_registerHandler(engine), m_sourceFilesHandler(), - m_stackHandler(), + m_stackHandler(engine), m_threadsHandler(), m_watchHandler(engine), m_disassemblerAgent(engine), @@ -203,8 +208,6 @@ public: this, &DebuggerEnginePrivate::resetLocation); connect(action(IntelFlavor), &Utils::SavedAction::valueChanged, this, &DebuggerEnginePrivate::reloadDisassembly); - connect(action(OperateNativeMixed), &QAction::triggered, - engine, &DebuggerEngine::reloadFullStack); Utils::globalMacroExpander()->registerFileVariables(PrefixDebugExecutable, tr("Debugged executable"), @@ -2023,6 +2026,26 @@ void DebuggerEngine::checkState(DebuggerState state, const char *file, int line) qDebug("%s", qPrintable(msg)); } +bool DebuggerEngine::isNativeMixedEnabled() const +{ + return runParameters().nativeMixedEnabled && (runParameters().languages & QmlLanguage); +} + +bool DebuggerEngine::isNativeMixedActive() const +{ + return isNativeMixedEnabled(); //&& boolSetting(OperateNativeMixed); +} + +bool DebuggerEngine::isNativeMixedActiveFrame() const +{ + if (!isNativeMixedActive()) + return false; + if (stackHandler()->frames().isEmpty()) + return false; + StackFrame frame = stackHandler()->frameAt(0); + return frame.language == QmlLanguage; +} + } // namespace Internal } // namespace Debugger diff --git a/src/plugins/debugger/debuggerengine.h b/src/plugins/debugger/debuggerengine.h index 15357a7221d..b4edd5eeaa1 100644 --- a/src/plugins/debugger/debuggerengine.h +++ b/src/plugins/debugger/debuggerengine.h @@ -117,6 +117,8 @@ public: // Used by AttachCrashedExternal. QString crashParameter; + bool nativeMixedEnabled = false; + // For Debugger testing. int testCase = 0; }; @@ -443,6 +445,9 @@ protected: void updateLocalsView(const GdbMi &all); void checkState(DebuggerState state, const char *file, int line); + bool isNativeMixedEnabled() const; + bool isNativeMixedActive() const; + bool isNativeMixedActiveFrame() const; private: // Wrapper engine needs access to state of its subengines. diff --git a/src/plugins/debugger/debuggerplugin.cpp b/src/plugins/debugger/debuggerplugin.cpp index bd7716ee9d9..d644824e198 100644 --- a/src/plugins/debugger/debuggerplugin.cpp +++ b/src/plugins/debugger/debuggerplugin.cpp @@ -474,7 +474,6 @@ bool DummyEngine::hasCapability(unsigned cap) const return cap & (WatchpointByAddressCapability | BreakConditionCapability | TracePointCapability - | OperateNativeMixed | OperateByInstructionCapability); // This is a Qml or unknown engine. @@ -2321,17 +2320,6 @@ QMessageBox *showMessageBox(int icon, const QString &title, return mb; } -bool isNativeMixedEnabled() -{ - static bool enabled = qEnvironmentVariableIsSet("QTC_DEBUGGER_NATIVE_MIXED"); - return enabled; -} - -bool isNativeMixedActive() -{ - return isNativeMixedEnabled() && boolSetting(OperateNativeMixed); -} - bool isReverseDebuggingEnabled() { static bool enabled = qEnvironmentVariableIsSet("QTC_DEBUGGER_ENABLE_REVERSE"); @@ -2769,16 +2757,6 @@ void DebuggerPluginPrivate::extensionsInitialized() cmd->setAttribute(Command::CA_Hide); debugMenu->addAction(cmd); - if (isNativeMixedEnabled()) { - SavedAction *act = action(OperateNativeMixed); - act->setValue(true); - cmd = ActionManager::registerAction(act, Constants::OPERATE_NATIVE_MIXED); - cmd->setAttribute(Command::CA_Hide); - debugMenu->addAction(cmd); - connect(cmd->action(), &QAction::triggered, - [this] { currentEngine()->updateAll(); }); - } - cmd = ActionManager::registerAction(m_breakAction, "Debugger.ToggleBreak"); cmd->setDefaultKeySequence(QKeySequence(UseMacShortcuts ? tr("F8") : tr("F9"))); debugMenu->addAction(cmd); @@ -2912,8 +2890,6 @@ void DebuggerPluginPrivate::extensionsInitialized() hbox->addWidget(toolButton(Constants::STEPOUT)); hbox->addWidget(toolButton(Constants::RESET)); hbox->addWidget(toolButton(Constants::OPERATE_BY_INSTRUCTION)); - if (isNativeMixedEnabled()) - hbox->addWidget(toolButton(Constants::OPERATE_NATIVE_MIXED)); if (isReverseDebuggingEnabled()) { m_reverseToolButton = toolButton(Constants::REVERSE); diff --git a/src/plugins/debugger/debuggerprotocol.cpp b/src/plugins/debugger/debuggerprotocol.cpp index 651a8c1a2be..763c9477c62 100644 --- a/src/plugins/debugger/debuggerprotocol.cpp +++ b/src/plugins/debugger/debuggerprotocol.cpp @@ -80,7 +80,7 @@ void GdbMi::parseResultOrValue(const char *&from, const char *to) if (from == to || *from == '(') return; const char *ptr = from; - while (ptr < to && *ptr != '=') { + while (ptr < to && *ptr != '=' && *ptr != ':') { //qDebug() << "adding" << QChar(*ptr) << "to name"; ++ptr; } @@ -770,6 +770,12 @@ QString decodeData(const QByteArray &ba, DebuggerEncoding encoding) case SpecialEmptyStructureValue: { // 39 return QLatin1String("{...}"); } + case SpecialUndefinedValue: { // 40 + return QLatin1String("Undefined"); + } + case SpecialNullValue: { // 41 + return QLatin1String("Null"); + } } qDebug() << "ENCODING ERROR: " << encoding; return QCoreApplication::translate("Debugger", ""); @@ -857,5 +863,22 @@ QByteArray DebuggerCommand::argsToString() const return args.toString().toLatin1(); } +DebuggerEncoding debuggerEncoding(const QByteArray &data) +{ + if (data == "utf16") + return Hex4EncodedLittleEndianWithQuotes; + if (data == "empty") + return SpecialEmptyValue; + if (data == "minimumitemcount") + return SpecialMinimumItemCountValue; + if (data == "undefined") + return SpecialUndefinedValue; + if (data == "null") + return SpecialNullValue; + if (data == "itemcount") + return SpecialItemCountValue; + return DebuggerEncoding(data.toInt()); +} + } // namespace Internal } // namespace Debugger diff --git a/src/plugins/debugger/debuggerprotocol.h b/src/plugins/debugger/debuggerprotocol.h index c8dc3a307d5..a8006172ae5 100644 --- a/src/plugins/debugger/debuggerprotocol.h +++ b/src/plugins/debugger/debuggerprotocol.h @@ -252,9 +252,13 @@ enum DebuggerEncoding SpecialNotCallableValue = 36, SpecialNullReferenceValue = 37, SpecialOptimizedOutValue = 38, - SpecialEmptyStructureValue = 39 + SpecialEmptyStructureValue = 39, + SpecialUndefinedValue = 40, + SpecialNullValue = 41 }; +DebuggerEncoding debuggerEncoding(const QByteArray &data); + // Decode string data as returned by the dumper helpers. QString decodeData(const QByteArray &baIn, DebuggerEncoding encoding); diff --git a/src/plugins/debugger/debuggerruncontrol.cpp b/src/plugins/debugger/debuggerruncontrol.cpp index 9e4af8fd71d..2c323a3b1ea 100644 --- a/src/plugins/debugger/debuggerruncontrol.cpp +++ b/src/plugins/debugger/debuggerruncontrol.cpp @@ -59,6 +59,8 @@ #include #include +#include + #include using namespace Debugger::Internal; @@ -416,6 +418,19 @@ void DebuggerRunControlCreator::enrich(const RunConfiguration *runConfig, const if (m_project && m_rp.projectSourceFiles.isEmpty()) m_rp.projectSourceFiles = m_project->files(Project::ExcludeGeneratedFiles); + if (m_project && m_rp.projectSourceFiles.isEmpty()) + m_rp.projectSourceFiles = m_project->files(Project::ExcludeGeneratedFiles); + + if (false && m_project && m_kit) { + const QtSupport::BaseQtVersion *version = QtSupport::QtKitInformation::qtVersion(m_kit); + m_rp.nativeMixedEnabled = version && version->qtVersion() >= QtSupport::QtVersionNumber(5, 7, 0); + } + + bool ok = false; + int nativeMixedOverride = qgetenv("QTC_DEBUGGER_NATIVE_MIXED").toInt(&ok); + if (ok) + m_rp.nativeMixedEnabled = bool(nativeMixedOverride); + // validate debugger if C++ debugging is enabled if (m_rp.languages & CppLanguage) { const QList tasks = DebuggerKitInformation::validateDebugger(m_kit); @@ -482,9 +497,6 @@ void DebuggerRunControlCreator::enrich(const RunConfiguration *runConfig, const const QString optimizerKey = _("QML_DISABLE_OPTIMIZER"); if (!m_rp.environment.hasKey(optimizerKey)) m_rp.environment.set(optimizerKey, _("1")); - - QtcProcess::addArg(&m_rp.processArgs, QmlDebug::qmlDebugCommandLineArguments( - QmlDebug::QmlDebuggerServices, m_rp.qmlServerPort)); } } } @@ -502,14 +514,23 @@ void DebuggerRunControlCreator::enrich(const RunConfiguration *runConfig, const } if (m_rp.masterEngineType == NoEngineType && m_debuggerAspect) { - const bool useCppDebugger = m_debuggerAspect->useCppDebugger() && (m_rp.languages & CppLanguage); - const bool useQmlDebugger = m_debuggerAspect->useQmlDebugger() && (m_rp.languages & QmlLanguage); + const bool wantCppDebugger = m_debuggerAspect->useCppDebugger() && (m_rp.languages & CppLanguage); + const bool wantQmlDebugger = m_debuggerAspect->useQmlDebugger() && (m_rp.languages & QmlLanguage); - if (useQmlDebugger) { - if (useCppDebugger) - m_rp.masterEngineType = QmlCppEngineType; - else + if (wantQmlDebugger) { + QString qmlArgs; + if (wantCppDebugger) { + if (m_rp.nativeMixedEnabled) { + qmlArgs = QmlDebug::qmlDebugCommandLineArguments(QmlDebug::QmlNativeDebuggerServices); + } else { + m_rp.masterEngineType = QmlCppEngineType; + qmlArgs = QmlDebug::qmlDebugCommandLineArguments(QmlDebug::QmlDebuggerServices, m_rp.qmlServerPort); + } + } else { m_rp.masterEngineType = QmlEngineType; + qmlArgs = QmlDebug::qmlDebugCommandLineArguments(QmlDebug::QmlDebuggerServices, m_rp.qmlServerPort); + } + QtcProcess::addArg(&m_rp.processArgs, qmlArgs); } } diff --git a/src/plugins/debugger/gdb/gdbengine.cpp b/src/plugins/debugger/gdb/gdbengine.cpp index a3686221b21..5692a00245c 100644 --- a/src/plugins/debugger/gdb/gdbengine.cpp +++ b/src/plugins/debugger/gdb/gdbengine.cpp @@ -625,6 +625,18 @@ void GdbEngine::handleResponse(const QByteArray &buff) case '~': { QByteArray data = GdbMi::parseCString(from, to); + if (data.startsWith("bridgemessage={")) { + //showMessage(_(data), LogDebug); + break; + } + if (data.startsWith("bridgeresult={")) { + //showMessage(_(data), LogDebug); + DebuggerResponse response; + response.resultClass = ResultDone; + response.data.fromStringMultiple(data); + handleResultRecord(&response); + break; + } m_pendingConsoleStreamOutput += data; // Parse pid from noise. @@ -1369,9 +1381,13 @@ void GdbEngine::handleStopResponse(const GdbMi &data) int lineNumber = 0; QString fullName; QByteArray function; + QByteArray language; if (frame.isValid()) { const GdbMi lineNumberG = frame["line"]; - function = frame["func"].data(); + function = frame["function"].data(); // V4 protocol + if (function.isEmpty()) + function = frame["func"].data(); // GDB's *stopped messages + language = frame["language"].data(); if (lineNumberG.isValid()) { lineNumber = lineNumberG.toInt(); fullName = cleanupFullName(QString::fromLocal8Bit(frame["fullname"].data())); @@ -1384,9 +1400,6 @@ void GdbEngine::handleStopResponse(const GdbMi &data) if (rid.isValid() && frame.isValid() && !isQFatalBreakpoint(rid)) { // Use opportunity to update the breakpoint marker position. - //qDebug() << " PROBLEM: " << m_qmlBreakpointNumbers << rid - // << isQmlStepBreakpoint1(rid) - // << isQmlStepBreakpoint2(rid) Breakpoint bp = breakHandler()->findBreakpointByResponseId(rid); const BreakpointResponse &response = bp.response(); QString fileName = response.fileName; @@ -1403,7 +1416,9 @@ void GdbEngine::handleStopResponse(const GdbMi &data) if (lineNumber && !boolSetting(OperateByInstruction) && QFileInfo::exists(fullName) && !isQFatalBreakpoint(rid) - && function != "qt_v4TriggeredBreakpointHook") + && function != "qt_v4TriggeredBreakpointHook" + && function != "qt_qmlDebugEventFromService" + && language != "js") gotoLocation(Location(fullName, lineNumber)); if (state() == InferiorRunOk) { @@ -1479,7 +1494,7 @@ void GdbEngine::handleStop1(const GdbMi &data) if (boolSetting(SkipKnownFrames)) { if (reason == "end-stepping-range" || reason == "function-finished") { //showMessage(frame.toString()); - QString funcName = _(frame["func"].data()); + QString funcName = _(frame["function"].data()); QString fileName = QString::fromLocal8Bit(frame["file"].data()); if (isLeavableFunction(funcName, fileName)) { //showMessage(_("LEAVING ") + funcName); @@ -1978,9 +1993,15 @@ void GdbEngine::continueInferiorInternal() notifyInferiorRunRequested(); showStatusMessage(tr("Running requested..."), 5000); CHECK_STATE(InferiorRunRequested); - DebuggerCommand cmd("-exec-continue", RunRequest); - cmd.callback = CB(handleExecuteContinue); - runCommand(cmd); + if (isNativeMixedActiveFrame()) { + DebuggerCommand cmd("executeContinue", RunRequest|PythonCommand); + cmd.callback = CB(handleExecuteContinue); + runCommand(cmd); + } else { + DebuggerCommand cmd("-exec-continue", RunRequest); + cmd.callback = CB(handleExecuteContinue); + runCommand(cmd); + } } void GdbEngine::continueInferior() @@ -1996,10 +2017,10 @@ void GdbEngine::executeStep() setTokenBarrier(); notifyInferiorRunRequested(); showStatusMessage(tr("Step requested..."), 5000); - if (isNativeMixedActive()) { - DebuggerCommand cmd("prepareQmlStep", PythonCommand); + if (isNativeMixedActiveFrame()) { + DebuggerCommand cmd("executeStep", RunRequest|PythonCommand); + cmd.callback = CB(handleExecuteStep); runCommand(cmd); - continueInferiorInternal(); } else { DebuggerCommand cmd(isReverseDebugging() ? "reverse-step" : "-exec-step", RunRequest); cmd.callback = CB(handleExecuteStep); @@ -2061,12 +2082,17 @@ void GdbEngine::executeStepOut() setTokenBarrier(); notifyInferiorRunRequested(); showStatusMessage(tr("Finish function requested..."), 5000); - runCommand("-exec-finish", CB(handleExecuteContinue), RunRequest); - // -exec-finish in 'main' results (correctly) in - // 40^error,msg="\"finish\" not meaningful in the outermost frame." - // However, this message does not seem to get flushed before - // anything else happen - i.e. "never". Force some extra output. - runCommand("print 32"); + if (isNativeMixedActiveFrame()) { + DebuggerCommand cmd("executeStepOut", RunRequest|PythonCommand); + runCommand(cmd); + } else { + runCommand("-exec-finish", CB(handleExecuteContinue), RunRequest); + // -exec-finish in 'main' results (correctly) in + // 40^error,msg="\"finish\" not meaningful in the outermost frame." + // However, this message does not seem to get flushed before + // anything else happen - i.e. "never". Force some extra output. + runCommand("print 32"); + } } void GdbEngine::executeNext() @@ -2075,10 +2101,9 @@ void GdbEngine::executeNext() setTokenBarrier(); notifyInferiorRunRequested(); showStatusMessage(tr("Step next requested..."), 5000); - if (isNativeMixedActive()) { - DebuggerCommand cmd("prepareQmlStep", PythonCommand); + if (isNativeMixedActiveFrame()) { + DebuggerCommand cmd("executeNext", RunRequest|PythonCommand); runCommand(cmd); - continueInferiorInternal(); } else { DebuggerCommand cmd(isReverseDebugging() ? "reverse-next" : "-exec-next", RunRequest); cmd.callback = CB(handleExecuteNext); @@ -2656,10 +2681,9 @@ bool GdbEngine::acceptsBreakpoint(Breakpoint bp) const { if (runParameters().startMode == AttachCore) return false; - // We handle QML breakpoint unless specifically - if (isNativeMixedEnabled() && !(runParameters().languages & QmlLanguage)) + if (bp.parameters().isCppBreakpoint()) return true; - return bp.parameters().isCppBreakpoint(); + return isNativeMixedEnabled(); } void GdbEngine::insertBreakpoint(Breakpoint bp) @@ -2672,7 +2696,7 @@ void GdbEngine::insertBreakpoint(Breakpoint bp) const BreakpointParameters &data = bp.parameters(); if (!data.isCppBreakpoint()) { - DebuggerCommand cmd("insertQmlBreakpoint", PythonCommand); + DebuggerCommand cmd("insertInterpreterBreakpoint", PythonCommand); bp.addToCommand(&cmd); runCommand(cmd); bp.notifyBreakpointInsertOk(); @@ -2791,7 +2815,7 @@ void GdbEngine::removeBreakpoint(Breakpoint bp) const BreakpointParameters &data = bp.parameters(); if (!data.isCppBreakpoint()) { - DebuggerCommand cmd("removeQmlBreakpoint", PythonCommand); + DebuggerCommand cmd("removeInterpreterBreakpoint", PythonCommand); bp.addToCommand(&cmd); runCommand(cmd); bp.notifyBreakpointRemoveOk(); @@ -3140,13 +3164,11 @@ void GdbEngine::reloadFullStack() runCommand(cmd); } -void GdbEngine::loadAdditionalQmlStack() +static QString msgCannotLoadQmlStack(const QString &why) { - // Scan for QV4::ExecutionContext parameter in the parameter list of a V4 call. - runCommand("-stack-list-arguments --simple-values", CB(handleQmlStackFrameArguments), NeedsStop); + return _("Unable to load QML stack: ") + why; } -// Scan the arguments of a stack list for the address of a QV4::ExecutionContext. static quint64 findJsExecutionContextAddress(const GdbMi &stackArgsResponse, const QByteArray &qtNamespace) { const GdbMi frameList = stackArgsResponse.childAt(0); @@ -3169,29 +3191,30 @@ static quint64 findJsExecutionContextAddress(const GdbMi &stackArgsResponse, con return 0; } -static QString msgCannotLoadQmlStack(const QString &why) +void GdbEngine::loadAdditionalQmlStack() { - return _("Unable to load QML stack: ") + why; -} - -void GdbEngine::handleQmlStackFrameArguments(const DebuggerResponse &response) -{ - if (!response.data.isValid()) { - showMessage(msgCannotLoadQmlStack(_("No stack obtained.")), LogError); - return; - } - const quint64 contextAddress = findJsExecutionContextAddress(response.data, qtNamespace()); - if (!contextAddress) { - showMessage(msgCannotLoadQmlStack(_("The address of the JS execution context could not be found.")), LogError); - return; - } - // Call the debug function of QML with the context address to obtain the QML stack trace. - DebuggerCommand cmd = "-data-evaluate-expression \"qt_v4StackTrace((QV4::ExecutionContext *)0x" - + QByteArray::number(contextAddress, 16) + ")\""; - cmd.callback = CB(handleQmlStackTrace); + // Scan for QV4::ExecutionContext parameter in the parameter list of a V4 call. + DebuggerCommand cmd("-stack-list-arguments --simple-values", NeedsStop); + cmd.callback = [this](const DebuggerResponse &response) { + if (!response.data.isValid()) { + showMessage(msgCannotLoadQmlStack(_("No stack obtained.")), LogError); + return; + } + const quint64 contextAddress = findJsExecutionContextAddress(response.data, qtNamespace()); + if (!contextAddress) { + showMessage(msgCannotLoadQmlStack(_("The address of the JS execution context could not be found.")), LogError); + return; + } + // Call the debug function of QML with the context address to obtain the QML stack trace. + DebuggerCommand cmd = "-data-evaluate-expression \"qt_v4StackTrace((QV4::ExecutionContext *)0x" + + QByteArray::number(contextAddress, 16) + ")\""; + cmd.callback = CB(handleQmlStackTrace); + runCommand(cmd); + }; runCommand(cmd); } +// Scan the arguments of a stack list for the address of a QV4::ExecutionContext. void GdbEngine::handleQmlStackTrace(const DebuggerResponse &response) { if (!response.data.isValid()) { @@ -3216,11 +3239,8 @@ void GdbEngine::handleQmlStackTrace(const DebuggerResponse &response) } QList qmlFrames; qmlFrames.reserve(qmlFrameCount); - for (int i = 0; i < qmlFrameCount; ++i) { - StackFrame frame = parseStackFrame(stackMi.childAt(i), i); - frame.fixQmlFrame(runParameters()); - qmlFrames.append(frame); - } + for (int i = 0; i < qmlFrameCount; ++i) + qmlFrames.append(StackFrame::parseFrame(stackMi.childAt(i), runParameters())); stackHandler()->prependFrames(qmlFrames); } @@ -3228,7 +3248,7 @@ DebuggerCommand GdbEngine::stackCommand(int depth) { DebuggerCommand cmd("stackListFrames"); cmd.arg("limit", depth); - cmd.arg("options", isNativeMixedActive() ? "nativemixed" : ""); + cmd.arg("nativemixed", isNativeMixedActive()); return cmd; } @@ -3241,35 +3261,6 @@ void GdbEngine::reloadStack() runCommand(cmd); } -StackFrame GdbEngine::parseStackFrame(const GdbMi &frameMi, int level) -{ - //qDebug() << "HANDLING FRAME:" << frameMi.toString(); - StackFrame frame; - frame.level = level; - GdbMi fullName = frameMi["fullname"]; - if (fullName.isValid()) - frame.file = cleanupFullName(QFile::decodeName(fullName.data())); - else - frame.file = QFile::decodeName(frameMi["file"].data()); - frame.function = _(frameMi["func"].data()); - frame.from = _(frameMi["from"].data()); - frame.line = frameMi["line"].toInt(); - frame.address = frameMi["addr"].toAddress(); - GdbMi usable = frameMi["usable"]; - if (usable.isValid()) - frame.usable = usable.data().toInt(); - else - frame.usable = QFileInfo(frame.file).isReadable(); - if (frameMi["language"].data() == "js" - || frame.file.endsWith(QLatin1String(".js")) - || frame.file.endsWith(QLatin1String(".qml"))) { - frame.file = QFile::decodeName(frameMi["file"].data()); - frame.language = QmlLanguage; - frame.fixQmlFrame(runParameters()); - } - return frame; -} - void GdbEngine::handleStackListFrames(const DebuggerResponse &response, bool isFull) { if (response.resultClass != ResultDone) { @@ -3284,8 +3275,10 @@ void GdbEngine::handleStackListFrames(const DebuggerResponse &response, bool isF QList stackFrames; GdbMi stack = response.data["stack"]; // C++ - if (!stack.isValid() || stack.childCount() == 0) // Mixed. + if (!stack.isValid() || stack.childCount() == 0) { // Mixed. stack.fromStringMultiple(response.consoleStreamOutput); + stack = stack["frames"]; + } if (!stack.isValid()) { qDebug() << "FIXME: stack:" << stack.toString(); @@ -3296,7 +3289,7 @@ void GdbEngine::handleStackListFrames(const DebuggerResponse &response, bool isF int n = stack.childCount(); for (int i = 0; i != n; ++i) { - stackFrames.append(parseStackFrame(stack.childAt(i), i)); + stackFrames.append(StackFrame::parseFrame(stack.childAt(i), runParameters())); const StackFrame &frame = stackFrames.back(); // Initialize top frame to the first valid frame. @@ -3407,7 +3400,7 @@ void GdbEngine::handleThreadNames(const DebuggerResponse &response) ThreadData thread; thread.id = ThreadId(name["id"].toInt()); thread.name = decodeData(name["value"].data(), - DebuggerEncoding(name["valueencoded"].toInt())); + debuggerEncoding(name["valueencoded"].data())); handler->updateThread(thread); } updateViews(); @@ -4622,7 +4615,7 @@ void GdbEngine::doUpdateLocals(const UpdateParameters ¶ms) watchHandler()->notifyUpdateStarted(params.partialVariables()); - DebuggerCommand cmd("showData", Discardable | InUpdateLocals | PythonCommand); + DebuggerCommand cmd("fetchVariables", Discardable|InUpdateLocals|PythonCommand); watchHandler()->appendFormatRequests(&cmd); watchHandler()->appendWatchersAndTooltipRequests(&cmd); @@ -4637,23 +4630,20 @@ void GdbEngine::doUpdateLocals(const UpdateParameters ¶ms) cmd.arg("dyntype", boolSetting(UseDynamicType)); cmd.arg("nativemixed", isNativeMixedActive()); - if (isNativeMixedActive()) { - StackFrame frame = stackHandler()->currentFrame(); - if (frame.language == QmlLanguage) - cmd.arg("qmlcontext", "0x" + QByteArray::number(frame.address, 16)); - } + StackFrame frame = stackHandler()->currentFrame(); + cmd.arg("context", frame.context); cmd.arg("resultvarname", m_resultVarName); cmd.arg("partialVariable", params.partialVariable); cmd.arg("sortStructMembers", boolSetting(SortStructMembers)); - cmd.callback = CB(handleStackFrame); + cmd.callback = CB(handleFetchVariables); runCommand(cmd); cmd.arg("passExceptions", true); m_lastDebuggableCommand = cmd; } -void GdbEngine::handleStackFrame(const DebuggerResponse &response) +void GdbEngine::handleFetchVariables(const DebuggerResponse &response) { m_inUpdateLocals = false; diff --git a/src/plugins/debugger/gdb/gdbengine.h b/src/plugins/debugger/gdb/gdbengine.h index cc4bc0b86c8..70e2816404a 100644 --- a/src/plugins/debugger/gdb/gdbengine.h +++ b/src/plugins/debugger/gdb/gdbengine.h @@ -216,7 +216,6 @@ protected: void handleStop1(const GdbMi &data); void handleStop2(const GdbMi &data); Q_SLOT void handleStop2(); - StackFrame parseStackFrame(const GdbMi &mi, int level); void resetCommandQueue(); bool isSynchronous() const override { return true; } @@ -367,7 +366,6 @@ protected: Q_SLOT void reloadStack(); Q_SLOT virtual void reloadFullStack() override; virtual void loadAdditionalQmlStack() override; - void handleQmlStackFrameArguments(const DebuggerResponse &response); void handleQmlStackTrace(const DebuggerResponse &response); int currentFrame() const; @@ -399,7 +397,7 @@ protected: Q_SLOT void createFullBacktrace(); void doUpdateLocals(const UpdateParameters ¶meters) override; - void handleStackFrame(const DebuggerResponse &response); + void handleFetchVariables(const DebuggerResponse &response); void setLocals(const QList &locals); diff --git a/src/plugins/debugger/lldb/lldbengine.cpp b/src/plugins/debugger/lldb/lldbengine.cpp index 39a2936cc06..de99a107b34 100644 --- a/src/plugins/debugger/lldb/lldbengine.cpp +++ b/src/plugins/debugger/lldb/lldbengine.cpp @@ -537,7 +537,7 @@ void LldbEngine::selectThread(ThreadId threadId) cmd.arg("id", threadId.raw()); cmd.callback = [this](const DebuggerResponse &) { DebuggerCommand cmd("fetchStack"); - cmd.arg("nativeMixed", isNativeMixedActive()); + cmd.arg("nativemixed", isNativeMixedActive()); cmd.arg("stacklimit", action(MaximalStackDepth)->value().toInt()); runCommand(cmd); updateLocals(); @@ -563,10 +563,9 @@ bool LldbEngine::acceptsBreakpoint(Breakpoint bp) const { if (runParameters().startMode == AttachCore) return false; - // We handle QML breakpoint unless specifically disabled. - if (isNativeMixedEnabled() && !(runParameters().languages & QmlLanguage)) + if (bp.parameters().isCppBreakpoint()) return true; - return bp.parameters().isCppBreakpoint(); + return isNativeMixedEnabled(); } void LldbEngine::insertBreakpoint(Breakpoint bp) @@ -753,37 +752,12 @@ void LldbEngine::reloadFullStack() void LldbEngine::fetchStack(int limit) { DebuggerCommand cmd("fetchStack"); - cmd.arg("nativeMixed", isNativeMixedActive()); + cmd.arg("nativemixed", isNativeMixedActive()); cmd.arg("stacklimit", limit); + cmd.arg("context", stackHandler()->currentFrame().context); cmd.callback = [this](const DebuggerResponse &response) { const GdbMi &stack = response.data["stack"]; - StackHandler *handler = stackHandler(); - StackFrames frames; - foreach (const GdbMi &item, stack["frames"].children()) { - StackFrame frame; - frame.level = item["level"].toInt(); - frame.file = item["file"].toUtf8(); - frame.function = item["func"].toUtf8(); - frame.from = item["func"].toUtf8(); - frame.line = item["line"].toInt(); - frame.address = item["addr"].toAddress(); - GdbMi usable = item["usable"]; - if (usable.isValid()) - frame.usable = usable.data().toInt(); - else - frame.usable = QFileInfo(frame.file).isReadable(); - if (item["language"].data() == "js" - || frame.file.endsWith(QLatin1String(".js")) - || frame.file.endsWith(QLatin1String(".qml"))) { - frame.language = QmlLanguage; - frame.fixQmlFrame(runParameters()); - } - frames.append(frame); - } - bool canExpand = stack["hasmore"].toInt(); - action(ExpandStack)->setEnabled(canExpand); - handler->setFrames(frames, canExpand); - + stackHandler()->setAllFrames(stack["frames"], stack["hasmore"].toInt()); updateLocals(); }; runCommand(cmd); @@ -816,7 +790,7 @@ void LldbEngine::doUpdateLocals(const UpdateParameters ¶ms) watchHandler()->notifyUpdateStarted(params.partialVariables()); DebuggerCommand cmd("fetchLocals"); - cmd.arg("nativeMixed", isNativeMixedActive()); + cmd.arg("nativemixed", isNativeMixedActive()); watchHandler()->appendFormatRequests(&cmd); watchHandler()->appendWatchersAndTooltipRequests(&cmd); diff --git a/src/plugins/debugger/pdb/pdbengine.cpp b/src/plugins/debugger/pdb/pdbengine.cpp index 0adc23d0588..7fa38015676 100644 --- a/src/plugins/debugger/pdb/pdbengine.cpp +++ b/src/plugins/debugger/pdb/pdbengine.cpp @@ -532,12 +532,12 @@ void PdbEngine::refreshStack(const GdbMi &stack) StackFrames frames; foreach (const GdbMi &item, stack["frames"].children()) { StackFrame frame; - frame.level = item["level"].toInt(); + frame.level = item["level"].data(); frame.file = item["file"].toUtf8(); - frame.function = item["func"].toUtf8(); - frame.from = item["func"].toUtf8(); + frame.function = item["function"].toUtf8(); + frame.module = item["function"].toUtf8(); frame.line = item["line"].toInt(); - frame.address = item["addr"].toAddress(); + frame.address = item["address"].toAddress(); GdbMi usable = item["usable"]; if (usable.isValid()) frame.usable = usable.data().toInt(); diff --git a/src/plugins/debugger/qml/qmlengine.cpp b/src/plugins/debugger/qml/qmlengine.cpp index a91ae925be0..5031575d29b 100644 --- a/src/plugins/debugger/qml/qmlengine.cpp +++ b/src/plugins/debugger/qml/qmlengine.cpp @@ -2062,10 +2062,9 @@ void QmlEnginePrivate::handleBacktrace(const QVariantMap &response) stackIndexLookup.clear(); foreach (const QVariant &frame, frames) { StackFrame stackFrame = extractStackFrame(frame); - if (stackFrame.level < 0) + if (stackFrame.level.isEmpty()) continue; - stackIndexLookup.insert(i, stackFrame.level); - stackFrame.level = i; + stackIndexLookup.insert(i, stackFrame.level.toInt()); stackFrames << stackFrame; i++; } @@ -2111,10 +2110,10 @@ StackFrame QmlEnginePrivate::extractStackFrame(const QVariant &bodyVal) const QVariantMap body = bodyVal.toMap(); StackFrame stackFrame; - stackFrame.level = body.value(_("index")).toInt(); + stackFrame.level = body.value(_("index")).toByteArray(); //Do not insert the frame corresponding to the internal function if (body.value(QLatin1String("sourceLineText")) == QLatin1String(INTERNAL_FUNCTION)) { - stackFrame.level = -1; + stackFrame.level.clear(); return stackFrame; } @@ -2129,7 +2128,7 @@ StackFrame QmlEnginePrivate::extractStackFrame(const QVariant &bodyVal) stackFrame.usable = QFileInfo(stackFrame.file).isReadable(); objectData = extractData(body.value(_("receiver"))); - stackFrame.to = objectData.value.toString(); + stackFrame.receiver = objectData.value.toString(); stackFrame.line = body.value(_("line")).toInt() + 1; diff --git a/src/plugins/debugger/stackframe.cpp b/src/plugins/debugger/stackframe.cpp index cc07c4406e5..11fa1a99e51 100644 --- a/src/plugins/debugger/stackframe.cpp +++ b/src/plugins/debugger/stackframe.cpp @@ -31,6 +31,7 @@ #include "stackframe.h" #include "debuggerengine.h" +#include "debuggerprotocol.h" #include "watchutils.h" #include @@ -49,16 +50,16 @@ namespace Internal { //////////////////////////////////////////////////////////////////////// StackFrame::StackFrame() - : language(CppLanguage), level(-1), line(-1), address(0), usable(false) + : language(CppLanguage), line(-1), address(0), usable(false) {} void StackFrame::clear() { - line = level = -1; + line = -1; function.clear(); file.clear(); - from.clear(); - to.clear(); + module.clear(); + receiver.clear(); address = 0; } @@ -79,11 +80,44 @@ QString StackFrame::toString() const << tr("Function:") << ' ' << function << ' ' << tr("File:") << ' ' << file << ' ' << tr("Line:") << ' ' << line << ' ' - << tr("From:") << ' ' << from << ' ' - << tr("To:") << ' ' << to; + << tr("From:") << ' ' << module << ' ' + << tr("To:") << ' ' << receiver; return res; } +QList StackFrame::parseFrames(const GdbMi &data, const DebuggerRunParameters &rp) +{ + StackFrames frames; + frames.reserve(data.children().size()); + foreach (const GdbMi &item, data.children()) + frames.append(parseFrame(item, rp)); + return frames; +} + +StackFrame StackFrame::parseFrame(const GdbMi &frameMi, const DebuggerRunParameters &rp) +{ + StackFrame frame; + frame.level = frameMi["level"].data(); + frame.function = frameMi["function"].toUtf8(); + frame.module = frameMi["module"].toUtf8(); + frame.file = QFile::decodeName(frameMi["file"].data()); + frame.line = frameMi["line"].toInt(); + frame.address = frameMi["address"].toAddress(); + frame.context = frameMi["context"].data(); + if (frameMi["language"].data() == "js" + || frame.file.endsWith(QLatin1String(".js")) + || frame.file.endsWith(QLatin1String(".qml"))) { + frame.language = QmlLanguage; + frame.fixQrcFrame(rp); + } + GdbMi usable = frameMi["usable"]; + if (usable.isValid()) + frame.usable = usable.data().toInt(); + else + frame.usable = QFileInfo(frame.file).isReadable(); + return frame; +} + QString StackFrame::toToolTip() const { const QString filePath = QDir::toNativeSeparators(file); @@ -101,10 +135,10 @@ QString StackFrame::toToolTip() const str << "" << tr("File:") << "" << filePath << ""; if (line != -1) str << "" << tr("Line:") << "" << line << ""; - if (!from.isEmpty()) - str << "" << tr("From:") << "" << from << ""; - if (!to.isEmpty()) - str << "" << tr("To:") << "" << to << ""; + if (!module.isEmpty()) + str << "" << tr("Module:") << "" << module << ""; + if (!receiver.isEmpty()) + str << "" << tr("Receiver:") << "" << receiver << ""; str << ""; str <<"

" << tr("Note:") << " "; @@ -133,8 +167,8 @@ QString StackFrame::toToolTip() const return res; } -// Try to resolve files of a QML stack (resource files). -void StackFrame::fixQmlFrame(const DebuggerRunParameters &rp) +// Try to resolve files coming from resource files. +void StackFrame::fixQrcFrame(const DebuggerRunParameters &rp) { if (language != QmlLanguage) return; @@ -146,19 +180,19 @@ void StackFrame::fixQmlFrame(const DebuggerRunParameters &rp) if (!file.startsWith(QLatin1String("qrc:/"))) return; const QString relativeFile = file.right(file.size() - 5); - if (!rp.projectSourceDirectory.isEmpty()) { - const QFileInfo pFi(rp.projectSourceDirectory + QLatin1Char('/') + relativeFile); - if (pFi.isFile()) { - file = pFi.absoluteFilePath(); - usable = true; - return; - } - const QFileInfo cFi(QDir::currentPath() + QLatin1Char('/') + relativeFile); - if (cFi.isFile()) { - file = cFi.absoluteFilePath(); - usable = true; - return; - } + if (rp.projectSourceDirectory.isEmpty()) + return; + const QFileInfo pFi(rp.projectSourceDirectory + QLatin1Char('/') + relativeFile); + if (pFi.isFile()) { + file = pFi.absoluteFilePath(); + usable = true; + return; + } + const QFileInfo cFi(QDir::currentPath() + QLatin1Char('/') + relativeFile); + if (cFi.isFile()) { + file = cFi.absoluteFilePath(); + usable = true; + return; } } @@ -171,10 +205,10 @@ QDebug operator<<(QDebug d, const StackFrame &f) str << ' ' << f.function; if (!f.file.isEmpty()) str << ' ' << f.file << ':' << f.line; - if (!f.from.isEmpty()) - str << " from=" << f.from; - if (!f.to.isEmpty()) - str << " to=" << f.to; + if (!f.module.isEmpty()) + str << " from=" << f.module; + if (!f.receiver.isEmpty()) + str << " to=" << f.receiver; d.nospace() << res; return d; } diff --git a/src/plugins/debugger/stackframe.h b/src/plugins/debugger/stackframe.h index 8d29f3f004f..b9b23dfcede 100644 --- a/src/plugins/debugger/stackframe.h +++ b/src/plugins/debugger/stackframe.h @@ -44,6 +44,7 @@ namespace Debugger { namespace Internal { class DebuggerRunParameters; +class GdbMi; class StackFrame { @@ -53,24 +54,25 @@ public: bool isUsable() const; QString toToolTip() const; QString toString() const; - void fixQmlFrame(const DebuggerRunParameters &rp); + static StackFrame parseFrame(const GdbMi &data, const DebuggerRunParameters &rp); + static QList parseFrames(const GdbMi &data, const DebuggerRunParameters &rp); + void fixQrcFrame(const DebuggerRunParameters &rp); public: DebuggerLanguage language; - qint32 level; + QByteArray level; QString function; - QString file; // We try to put an absolute file name in there. - QString from; // Sometimes something like "/usr/lib/libstdc++.so.6" - QString to; // Used in ScriptEngine only. + QString file; // We try to put an absolute file name in there. + QString module; // Sometimes something like "/usr/lib/libstdc++.so.6" + QString receiver; // Used in ScriptEngine only. qint32 line; quint64 address; bool usable; + QByteArray context; // Opaque value produced and consumed by the native backends. Q_DECLARE_TR_FUNCTIONS(Debugger::Internal::StackHandler) }; -QDebug operator<<(QDebug d, const StackFrame &frame); - typedef QList StackFrames; } // namespace Internal diff --git a/src/plugins/debugger/stackhandler.cpp b/src/plugins/debugger/stackhandler.cpp index ec5fe966416..39bbae9a20a 100644 --- a/src/plugins/debugger/stackhandler.cpp +++ b/src/plugins/debugger/stackhandler.cpp @@ -32,6 +32,7 @@ #include "debuggeractions.h" #include "debuggercore.h" +#include "debuggerengine.h" #include "simplifytype.h" #include @@ -55,8 +56,9 @@ namespace Internal { QTreeView. */ -StackHandler::StackHandler() - : m_positionIcon(QIcon(QLatin1String(":/debugger/images/location_16.png"))), +StackHandler::StackHandler(DebuggerEngine *engine) + : m_engine(engine), + m_positionIcon(QIcon(QLatin1String(":/debugger/images/location_16.png"))), m_emptyIcon(QIcon(QLatin1String(":/debugger/images/debugger_empty_14.png"))) { setObjectName(QLatin1String("StackModel")); @@ -103,11 +105,11 @@ QVariant StackHandler::data(const QModelIndex &index, int role) const if (role == Qt::DisplayRole) { switch (index.column()) { case StackLevelColumn: - return QString::number(frame.level); + return QString::number(index.row() + 1); case StackFunctionNameColumn: return simplifyType(frame.function); case StackFileNameColumn: - return frame.file.isEmpty() ? frame.from : Utils::FileName::fromString(frame.file).fileName(); + return frame.file.isEmpty() ? frame.module : Utils::FileName::fromString(frame.file).fileName(); case StackLineNumberColumn: return frame.line > 0 ? QVariant(frame.line) : QVariant(); case StackAddressColumn: @@ -166,6 +168,12 @@ StackFrame StackHandler::currentFrame() const return m_stackFrames.at(m_currentIndex); } +void StackHandler::setAllFrames(const GdbMi &frames, bool canExpand) +{ + action(ExpandStack)->setEnabled(canExpand); + setFrames(StackFrame::parseFrames(frames, m_engine->runParameters()), canExpand); +} + void StackHandler::setCurrentIndex(int level) { if (level == -1 || level == m_currentIndex) diff --git a/src/plugins/debugger/stackhandler.h b/src/plugins/debugger/stackhandler.h index 0713042dc15..3607eb7d6c4 100644 --- a/src/plugins/debugger/stackhandler.h +++ b/src/plugins/debugger/stackhandler.h @@ -38,6 +38,8 @@ namespace Debugger { namespace Internal { +class DebuggerEngine; + enum StackColumns { StackLevelColumn, @@ -48,18 +50,12 @@ enum StackColumns StackColumnCount }; -//////////////////////////////////////////////////////////////////////// -// -// StackModel -// -//////////////////////////////////////////////////////////////////////// - class StackHandler : public QAbstractTableModel { Q_OBJECT public: - StackHandler(); + explicit StackHandler(DebuggerEngine *engine); ~StackHandler(); void setFrames(const StackFrames &frames, bool canExpand = false); @@ -72,6 +68,7 @@ public: const StackFrame &frameAt(int index) const { return m_stackFrames.at(index); } int stackSize() const { return m_stackFrames.size(); } quint64 topAddress() const { return m_stackFrames.at(0).address; } + void setAllFrames(const GdbMi &frames, bool canExpand); // Called from StackHandler after a new stack list has been received void removeAll(); @@ -93,6 +90,7 @@ private: Qt::ItemFlags flags(const QModelIndex &index) const; Q_SLOT void resetModel() { beginResetModel(); endResetModel(); } + DebuggerEngine *m_engine; StackFrames m_stackFrames; int m_currentIndex; const QVariant m_positionIcon; diff --git a/src/plugins/debugger/stackwindow.cpp b/src/plugins/debugger/stackwindow.cpp index cf07816ae5a..b6654c466b1 100644 --- a/src/plugins/debugger/stackwindow.cpp +++ b/src/plugins/debugger/stackwindow.cpp @@ -108,7 +108,7 @@ static inline StackFrame inputFunctionForDisassembly() return frame; const int bangPos = function.indexOf(QLatin1Char('!')); if (bangPos != -1) { - frame.from = function.left(bangPos); + frame.module = function.left(bangPos); frame.function = function.mid(bangPos + 1); } else { frame.function = function; diff --git a/src/plugins/debugger/watchdata.cpp b/src/plugins/debugger/watchdata.cpp index bab35465528..188fe5cd822 100644 --- a/src/plugins/debugger/watchdata.cpp +++ b/src/plugins/debugger/watchdata.cpp @@ -392,8 +392,8 @@ void WatchData::updateValue(const GdbMi &item) { GdbMi value = item["value"]; if (value.isValid()) { - int encoding = item["valueencoded"].toInt(); - setValue(decodeData(value.data(), DebuggerEncoding(encoding))); + DebuggerEncoding encoding = debuggerEncoding(item["valueencoded"].data()); + setValue(decodeData(value.data(), encoding)); } else { setValueNeeded(); } diff --git a/tests/auto/debugger/tst_dumpers.cpp b/tests/auto/debugger/tst_dumpers.cpp index 4e86a343db9..c04d79b1dee 100644 --- a/tests/auto/debugger/tst_dumpers.cpp +++ b/tests/auto/debugger/tst_dumpers.cpp @@ -1258,7 +1258,8 @@ void tst_Dumpers::dumper() if (m_debuggerEngine == GdbEngine) { const QFileInfo gdbBinaryFile(QString::fromLatin1(exe)); - const QByteArray uninstalledData = gdbBinaryFile.absolutePath().toLocal8Bit() + "/data-directory/python"; + const QByteArray uninstalledData = gdbBinaryFile.absolutePath().toLocal8Bit() + + "/data-directory/python"; args << QLatin1String("-i") << QLatin1String("mi") @@ -1280,8 +1281,9 @@ void tst_Dumpers::dumper() "python from gdbbridge import *\n" "python theDumper.setupDumpers()\n" "run " + nograb + "\n" - "python theDumper.showData({'fancy':1,'forcens':1,'autoderef':1," - "'dyntype':1,'passExceptions':1,'expanded':[" + expandedq + "]})\n"; + "python theDumper.fetchVariables({'fancy':1,'forcens':1," + "'autoderef':1,'dyntype':1,'passExceptions':1," + "'expanded':[" + expandedq + "]})\n"; cmds += "quit\n";