From 61c49a122f83a2c064ea7d92861199e8cd4a35c8 Mon Sep 17 00:00:00 2001 From: hjk Date: Thu, 22 Jan 2015 12:05:00 +0100 Subject: [PATCH] Debugger: Initial shot at native Qml-and-C++ debugging Only for GDB now, requires a debug build of Qt, not feature complete. To enable it, run with QTC_DEBUGGER_NATIVE_MIXED=1 and press the 'oo' button in the debugger toolbar. Change-Id: I28aac9db13f7067e03cc364b89cc8046fa213dae Reviewed-by: Christian Stenger --- share/qtcreator/debugger/gdbbridge.py | 251 +++++++++++++++++++++++- share/qtcreator/debugger/qttypes.py | 42 +++- src/plugins/debugger/gdb/gdbengine.cpp | 51 ++++- src/plugins/debugger/gdb/gdbprocess.cpp | 6 +- 4 files changed, 331 insertions(+), 19 deletions(-) diff --git a/share/qtcreator/debugger/gdbbridge.py b/share/qtcreator/debugger/gdbbridge.py index e8966afea4e..666f6e3a135 100644 --- a/share/qtcreator/debugger/gdbbridge.py +++ b/share/qtcreator/debugger/gdbbridge.py @@ -285,6 +285,7 @@ class Dumper(DumperBase): self.typesReported = {} self.typesToReport = {} self.qtNamespaceToReport = None + self.qmlEngines = [] def prepare(self, args): self.output = [] @@ -301,6 +302,7 @@ class Dumper(DumperBase): self.formats = {} self.useDynamicType = True self.expandedINames = {} + self.qmlcontext = "" # The guess does not need to be updated during a run() # as the result is fixed during that time (ignoring "active" @@ -340,12 +342,14 @@ class Dumper(DumperBase): self.formats[f[0:pos]] = int(f[pos+1:]) elif arg.startswith("watchers:"): self.watchers = self.hexdecode(arg[pos:]) + elif arg.startswith("qmlcontext:"): + self.qmlcontext = int(arg[pos:], 0) self.useDynamicType = "dyntype" in self.options self.useFancy = "fancy" in self.options self.forceQtNamespace = "forcens" in self.options self.passExceptions = "pe" in self.options - #self.passExceptions = True + self.nativeMixed = "nativemixed" in self.options self.autoDerefPointers = "autoderef" in self.options self.partialUpdate = "partial" in self.options self.tooltipOnly = "tooltiponly" in self.options @@ -366,6 +370,68 @@ class Dumper(DumperBase): def listOfLocals(self): frame = gdb.selected_frame() + if self.qmlcontext: + items = [] + + contextType = self.lookupQtType("QV4::Heap::CallContext") + context = self.createPointerValue(self.qmlcontext, contextType) + + contextItem = LocalItem() + contextItem.iname = "local.@context" + contextItem.name = "[context]" + contextItem.value = context.dereference() + items.append(contextItem) + + argsItem = 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 = 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 = LocalItem() + item.iname = "local." + name + item.name = name + item.value = contextData["locals"][index] + items.append(item) + + for engine in self.qmlEngines: + engineItem = LocalItem() + engineItem.iname = "local.@qmlengine" + engineItem.name = "[engine]" + engineItem.value = engine + items.append(engineItem) + + rootContext = LocalItem() + rootContext.iname = "local.@rootContext" + rootContext.name = "[rootContext]" + rootContext.value = engine["d_ptr"] + items.append(rootContext) + break + + return items try: block = frame.block() @@ -452,7 +518,8 @@ class Dumper(DumperBase): # Locals # self.output.append('data=[') - if self.partialUpdate and len(self.varList) == 1: + if self.partialUpdate and len(self.varList) == 1 \ + and not self.qmlcontext: #warn("PARTIAL: %s" % self.varList) parts = self.varList[0].split('.') #warn("PARTIAL PARTS: %s" % parts) @@ -1699,6 +1766,59 @@ class Dumper(DumperBase): self.typesToReport[typestring] = typeobj return typeobj + def extractQmlData(self, value): + if value.type.code == PointerCode: + value = value.dereference() + 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] + runtimeStrings = compilationUnitPtr.dereference()["runtimeStrings"] # QV4.StringValue * + entry = (runtimeStrings + index) # QV4.StringValue * + text = entry["text"] + (elided, fn) = self.encodeStringHelper(toInteger(text), 100) + return self.encodedUtf16ToUtf8(fn) # string + + def putQmlLocation(self, level, frame, sal): + engine = frame.read_var("engine") # QV4.ExecutionEngine * + 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"] # QV4.CompiledData.CompilationUnit * + functionName = "### 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"] + compiledFunction = function["compiledFunction"].dereference() # QV4.CompiledData.Function + index = int(compiledFunction["nameIndex"]) + functionName = "### JS ### " + self.extractQmlRuntimeString(compilationUnit, index) + + string = gdb.parse_and_eval("((%s)0x%x)->fileName()" + % (compilationUnit.type, compilationUnit)) + fileName = self.encodeStringUtf8(string) + + lineNumber = int(context["lineNumber"]) + self.put(('frame={level="%s",func="%s",file="%s",' + 'fullname="%s",line="%s",language="js",addr="0x%x"}') + % (level, functionName, fileName, fileName, lineNumber, context)) + + def isInternalQmlFrame(self, functionName): + if functionName.startswith("qt_v4"): + return True + return functionName.startswith(self.qtNamespace() + "QV4::") + + def isReportableQmlFrame(self, functionName): + return functionName.find("QV4::Moth::VME::exec") >= 0 def stackListFrames(self, n, options): self.prepare("options:" + options + ",pe") @@ -1706,8 +1826,7 @@ class Dumper(DumperBase): frame = gdb.newest_frame() i = 0 - t1 = 'frame={level="%s",addr="0x%x",func="%s",' - t1 += 'file="%s",fullname="%s",line="%s",from="%s"}' + self.currentCallContext = None while i < n and frame: with OutputSafer(self): name = frame.name() @@ -1725,7 +1844,28 @@ class Dumper(DumperBase): objfile = symtab.objfile.filename fileName = symtab.filename fullName = symtab.fullname() - self.put(t1 % (i, pc, functionName, fileName, fullName, line, objfile)) + + if self.nativeMixed: + if self.isReportableQmlFrame(functionName): + self.putQmlLocation(i, frame, sal) + 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)) frame = frame.older() i += 1 @@ -1941,3 +2081,104 @@ def addExtraDumper(args): return str((head, tail)) registerCommand("addExtraDumper", addExtraDumper) + +####################################################################### +# +# Native Mixed +# +####################################################################### + +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" + super(TriggeredBreakpointHookBreakpoint, self).\ + __init__(spec, gdb.BP_BREAKPOINT, internal=True) + + def stop(self): + print("QML engine stopped.") + return True + +TriggeredBreakpointHookBreakpoint() + + +class ResolvePendingBreakpointsHookBreakpoint(gdb.Breakpoint): + def __init__(self, fullName, lineNumber): + self.fullName = fullName + self.lineNumber = lineNumber + spec = "qt_v4ResolvePendingBreakpointsHook" + print("Preparing hook to resolve pending QML breakpoint at %s:%s" + % (self.fullName, self.lineNumber)) + super(ResolvePendingBreakpointsHookBreakpoint, self).\ + __init__(spec, gdb.BP_BREAKPOINT, internal=True, temporary=False) + + def stop(self): + bp = doInsertQmlBreakPoint(self.fullName, self.lineNumber) + print("Resolving QML breakpoint %s:%s -> %s" + % (self.fullName, self.lineNumber, bp)) + self.enabled = False + return False + +def doInsertQmlBreakPoint(fullName, lineNumber): + pos = fullName.rfind('/') + engineName = "qrc:/" + fullName[pos+1:] + cmd = 'qt_v4InsertBreakpoint("%s",%s,"%s","")' \ + % (fullName, lineNumber, engineName) + try: + bp = gdb.parse_and_eval(cmd) + print("Resolving QML breakpoint: %s" % bp) + return int(bp) + except RuntimeError as error: + print("Direct QML breakpoint insertion failed: %s" % error) + print("Make pending.") + ResolvePendingBreakpointsHookBreakpoint(fullName, lineNumber) + return 0 + +def insertQmlBreakpoint(arg): + (fullName, lineNumber) = arg.split(' ') + print("Insert QML breakpoint %s:%s" % (fullName, lineNumber)) + bp = doInsertQmlBreakPoint(fullName, lineNumber) + try: + gdb.execute("set variable qt_v4IsStepping=0") + except RuntimeError as error: + print("Resetting stepping failed: %s" % error) + return str(bp) + +registerCommand("insertQmlBreakpoint", insertQmlBreakpoint) + +def removeQmlBreakpoint(arg): + (fullName, lineNumber) = arg.split(' ') + pos = fullName.rfind('/') + engineName = "qrc:/" + fullName[pos+1:] + fsName = fullName + cmd = 'qt_v4RemoveBreakpoint("%s",%s)' % (fullName, lineNumber) + print("Remove QML breakpoint %s:%s" % (fullName, lineNumber)) + try: + res = gdb.parse_and_eval(cmd) + print("Removing QML breakpoint: %s" % (cmd, res)) + return int(res) + except RuntimeError as error: + print("Direct QML breakpoint removal failed: %s." % error) + return 0 + return str(bp) + +registerCommand("removeQmlBreakpoint", removeQmlBreakpoint) + +def prepareQmlStep(arg): + gdb.execute("set variable qt_v4IsStepping=1") + return "" + +registerCommand("prepareQmlStep", prepareQmlStep) diff --git a/share/qtcreator/debugger/qttypes.py b/share/qtcreator/debugger/qttypes.py index 82618a3caf3..7596d601c9e 100644 --- a/share/qtcreator/debugger/qttypes.py +++ b/share/qtcreator/debugger/qttypes.py @@ -2255,19 +2255,49 @@ def qdump__QXmlStreamAttribute(d, value): # ####################################################################### +def qdump__QV4__Object(d, value): + d.putBetterType(d.currentType) + d.putItem(d.extractQmlData(value)) + +def qdump__QV4__FunctionObject(d, value): + d.putBetterType(d.currentType) + d.putItem(d.extractQmlData(value)) + +def qdump__QV4__CompilationUnit(d, value): + d.putBetterType(d.currentType) + d.putItem(d.extractQmlData(value)) + +def qdump__QV4__CallContext(d, value): + d.putBetterType(d.currentType) + d.putItem(d.extractQmlData(value)) + +def qdump__QV4__ScriptFunction(d, value): + d.putBetterType(d.currentType) + d.putItem(d.extractQmlData(value)) + +def qdump__QV4__SimpleScriptFunction(d, value): + d.putBetterType(d.currentType) + d.putItem(d.extractQmlData(value)) + +def qdump__QV4__ExecutionContext(d, value): + d.putBetterType(d.currentType) + d.putItem(d.extractQmlData(value)) + def qdump__QV4__TypedValue(d, value): + d.putBetterType(d.currentType) qdump__QV4__Value(d, d.directBaseObject(value)) - d.putBetterType(value.type) def qdump__QV4__CallData(d, value): argc = toInteger(value["argc"]) - d.putValue("<%s args>" % argc) - d.putNumChild(1) + d.putItemCount(argc) if d.isExpanded(): with Children(d): - for i in range(0, argc + 1): + d.putSubItem("[this]", value["thisObject"]) + for i in range(0, argc): d.putSubItem(i, value["args"][i]) - d.putFields(value) + +def qdump__QV4__String(d, value): + d.putStringValue(d.addressOf(value) + 2 * d.ptrSize()) def qdump__QV4__Value(d, value): v = toInteger(str(value["val"])) @@ -2295,7 +2325,7 @@ def qdump__QV4__Value(d, value): d.putBetterType("%sQV4::Value (null/bool)" % ns) d.putValue("(null/bool)") else: - vtable = value["m"]["data"]["internalClass"]["vtable"] + vtable = value["m"]["vtable"] if toInteger(vtable["isString"]): d.putBetterType("%sQV4::Value (string)" % ns) d.putStringValue(d.extractPointer(value) + 2 * d.ptrSize()) diff --git a/src/plugins/debugger/gdb/gdbengine.cpp b/src/plugins/debugger/gdb/gdbengine.cpp index aee9b572db5..1d339ee11d7 100644 --- a/src/plugins/debugger/gdb/gdbengine.cpp +++ b/src/plugins/debugger/gdb/gdbengine.cpp @@ -1379,8 +1379,10 @@ void GdbEngine::handleStopResponse(const GdbMi &data) int lineNumber = 0; QString fullName; + QByteArray function; if (frame.isValid()) { const GdbMi lineNumberG = frame["line"]; + function = frame["func"].data(); if (lineNumberG.isValid()) { lineNumber = lineNumberG.toInt(); fullName = cleanupFullName(QString::fromLocal8Bit(frame["fullname"].data())); @@ -1411,7 +1413,8 @@ void GdbEngine::handleStopResponse(const GdbMi &data) // Quickly set the location marker. if (lineNumber && !boolSetting(OperateByInstruction) && QFileInfo::exists(fullName) - && !isQFatalBreakpoint(rid)) + && !isQFatalBreakpoint(rid) + && function != "qt_v4TriggeredBreakpointHook") gotoLocation(Location(fullName, lineNumber)); if (!m_commandsToRunOnTemporaryBreak.isEmpty()) { @@ -1453,7 +1456,6 @@ void GdbEngine::handleStopResponse(const GdbMi &data) QTC_ASSERT(state() == InferiorStopOk, qDebug() << state()); - if (gotoHandleStop1) handleStop1(data); } @@ -2060,6 +2062,11 @@ void GdbEngine::executeStep() setTokenBarrier(); notifyInferiorRunRequested(); showStatusMessage(tr("Step requested..."), 5000); + if (isNativeMixedActive()) { + postCommand("prepareQmlStep 0"); + postCommand("-exec-continue", RunRequest, CB(handleExecuteContinue)); + return; + } if (isReverseDebugging()) postCommand("reverse-step", RunRequest, CB(handleExecuteStep)); else @@ -2132,6 +2139,11 @@ void GdbEngine::executeNext() setTokenBarrier(); notifyInferiorRunRequested(); showStatusMessage(tr("Step next requested..."), 5000); + if (isNativeMixedActive()) { + postCommand("prepareQmlStep 1"); + postCommand("-exec-continue", RunRequest, CB(handleExecuteContinue)); + return; + } if (isReverseDebugging()) { postCommand("reverse-next", RunRequest, CB(handleExecuteNext)); } else { @@ -2887,6 +2899,15 @@ void GdbEngine::removeBreakpoint(Breakpoint bp) { QTC_CHECK(bp.state() == BreakpointRemoveRequested); BreakpointResponse br = bp.response(); + + const BreakpointParameters &data = bp.parameters(); + if (!data.isCppBreakpoint()) { + postCommand("removeQmlBreakpoint " + data.fileName.toUtf8() + ' ' + + QByteArray::number(data.lineNumber)); + bp.notifyBreakpointRemoveOk(); + return; + } + if (br.id.isValid()) { // We already have a fully inserted breakpoint. bp.notifyBreakpointRemoveProceeding(); @@ -3291,7 +3312,7 @@ QByteArray GdbEngine::stackCommand(int depth) if (isNativeMixedEnabled()) { cmd = "stackListFrames " + QByteArray::number(depth) + ' '; if (isNativeMixedActive()) - cmd += "mixed"; + cmd += "nativemixed"; else cmd += "noopt"; } else { @@ -3325,8 +3346,15 @@ StackFrame GdbEngine::parseStackFrame(const GdbMi &frameMi, int level) frame.from = _(frameMi["from"].data()); frame.line = frameMi["line"].toInt(); frame.address = frameMi["addr"].toAddress(); - frame.usable = QFileInfo(frame.file).isReadable(); - if (frameMi["language"].data() == "js") { + 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(startParameters()); } @@ -4847,6 +4875,8 @@ void GdbEngine::updateLocalsPython(const UpdateParameters ¶ms) options += "autoderef,"; if (boolSetting(UseDynamicType)) options += "dyntype,"; + if (isNativeMixedActive()) + options += "nativemixed,"; if (options.isEmpty()) options += "defaults,"; if (params.tryPartial) @@ -4855,16 +4885,23 @@ void GdbEngine::updateLocalsPython(const UpdateParameters ¶ms) options += "tooltiponly,"; options.chop(1); + QByteArray context; + if (isNativeMixedActive()) { + StackFrame frame = stackHandler()->currentFrame(); + if (frame.language == QmlLanguage) + context += " qmlcontext:0x" + QByteArray::number(frame.address, 16); + } + QByteArray resultVar; if (!m_resultVarName.isEmpty()) resultVar = "resultvarname:" + m_resultVarName + ' '; m_lastDebuggableCommand = "bb options:pe," + options + " vars:" + params.varList + ' ' - + expanded + " watchers:" + watchers.toHex() + cutOff; + + expanded + " watchers:" + watchers.toHex() + cutOff + context; postCommand("bb options:" + options + " vars:" + params.varList + ' ' - + resultVar + expanded + " watchers:" + watchers.toHex() + cutOff, + + resultVar + expanded + " watchers:" + watchers.toHex() + cutOff + context, Discardable, CB(handleStackFramePython), QVariant(params.tryPartial)); } diff --git a/src/plugins/debugger/gdb/gdbprocess.cpp b/src/plugins/debugger/gdb/gdbprocess.cpp index 01e6b881ef5..f96d36824a3 100644 --- a/src/plugins/debugger/gdb/gdbprocess.cpp +++ b/src/plugins/debugger/gdb/gdbprocess.cpp @@ -31,6 +31,7 @@ #include "gdbprocess.h" #include +#include #include namespace Debugger { @@ -115,8 +116,11 @@ void GdbProcess::setProcessEnvironment(const QProcessEnvironment &env) m_gdbProc.setProcessEnvironment(env); } -void GdbProcess::setEnvironment(const QStringList &env) +void GdbProcess::setEnvironment(const QStringList &env_) { + QStringList env = env_; + if (isNativeMixedActive()) + env.append(QLatin1String("QV4_FORCE_INTERPRETER=1")); // FIXME: REMOVE! m_gdbProc.setEnvironment(Utils::Environment(env)); }