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 <christian.stenger@theqtcompany.com>
This commit is contained in:
hjk
2015-01-22 12:05:00 +01:00
parent fc3abe9984
commit 61c49a122f
4 changed files with 331 additions and 19 deletions

View File

@@ -285,6 +285,7 @@ class Dumper(DumperBase):
self.typesReported = {} self.typesReported = {}
self.typesToReport = {} self.typesToReport = {}
self.qtNamespaceToReport = None self.qtNamespaceToReport = None
self.qmlEngines = []
def prepare(self, args): def prepare(self, args):
self.output = [] self.output = []
@@ -301,6 +302,7 @@ class Dumper(DumperBase):
self.formats = {} self.formats = {}
self.useDynamicType = True self.useDynamicType = True
self.expandedINames = {} self.expandedINames = {}
self.qmlcontext = ""
# The guess does not need to be updated during a run() # The guess does not need to be updated during a run()
# as the result is fixed during that time (ignoring "active" # 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:]) self.formats[f[0:pos]] = int(f[pos+1:])
elif arg.startswith("watchers:"): elif arg.startswith("watchers:"):
self.watchers = self.hexdecode(arg[pos:]) self.watchers = self.hexdecode(arg[pos:])
elif arg.startswith("qmlcontext:"):
self.qmlcontext = int(arg[pos:], 0)
self.useDynamicType = "dyntype" in self.options self.useDynamicType = "dyntype" in self.options
self.useFancy = "fancy" in self.options self.useFancy = "fancy" in self.options
self.forceQtNamespace = "forcens" in self.options self.forceQtNamespace = "forcens" in self.options
self.passExceptions = "pe" 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.autoDerefPointers = "autoderef" in self.options
self.partialUpdate = "partial" in self.options self.partialUpdate = "partial" in self.options
self.tooltipOnly = "tooltiponly" in self.options self.tooltipOnly = "tooltiponly" in self.options
@@ -366,6 +370,68 @@ class Dumper(DumperBase):
def listOfLocals(self): def listOfLocals(self):
frame = gdb.selected_frame() 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: try:
block = frame.block() block = frame.block()
@@ -452,7 +518,8 @@ class Dumper(DumperBase):
# Locals # Locals
# #
self.output.append('data=[') 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) #warn("PARTIAL: %s" % self.varList)
parts = self.varList[0].split('.') parts = self.varList[0].split('.')
#warn("PARTIAL PARTS: %s" % parts) #warn("PARTIAL PARTS: %s" % parts)
@@ -1699,6 +1766,59 @@ class Dumper(DumperBase):
self.typesToReport[typestring] = typeobj self.typesToReport[typestring] = typeobj
return 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): def stackListFrames(self, n, options):
self.prepare("options:" + options + ",pe") self.prepare("options:" + options + ",pe")
@@ -1706,8 +1826,7 @@ class Dumper(DumperBase):
frame = gdb.newest_frame() frame = gdb.newest_frame()
i = 0 i = 0
t1 = 'frame={level="%s",addr="0x%x",func="%s",' self.currentCallContext = None
t1 += 'file="%s",fullname="%s",line="%s",from="%s"}'
while i < n and frame: while i < n and frame:
with OutputSafer(self): with OutputSafer(self):
name = frame.name() name = frame.name()
@@ -1725,7 +1844,28 @@ class Dumper(DumperBase):
objfile = symtab.objfile.filename objfile = symtab.objfile.filename
fileName = symtab.filename fileName = symtab.filename
fullName = symtab.fullname() 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() frame = frame.older()
i += 1 i += 1
@@ -1941,3 +2081,104 @@ def addExtraDumper(args):
return str((head, tail)) return str((head, tail))
registerCommand("addExtraDumper", addExtraDumper) 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)

View File

@@ -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): def qdump__QV4__TypedValue(d, value):
d.putBetterType(d.currentType)
qdump__QV4__Value(d, d.directBaseObject(value)) qdump__QV4__Value(d, d.directBaseObject(value))
d.putBetterType(value.type)
def qdump__QV4__CallData(d, value): def qdump__QV4__CallData(d, value):
argc = toInteger(value["argc"]) argc = toInteger(value["argc"])
d.putValue("<%s args>" % argc) d.putItemCount(argc)
d.putNumChild(1)
if d.isExpanded(): if d.isExpanded():
with Children(d): 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.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): def qdump__QV4__Value(d, value):
v = toInteger(str(value["val"])) v = toInteger(str(value["val"]))
@@ -2295,7 +2325,7 @@ def qdump__QV4__Value(d, value):
d.putBetterType("%sQV4::Value (null/bool)" % ns) d.putBetterType("%sQV4::Value (null/bool)" % ns)
d.putValue("(null/bool)") d.putValue("(null/bool)")
else: else:
vtable = value["m"]["data"]["internalClass"]["vtable"] vtable = value["m"]["vtable"]
if toInteger(vtable["isString"]): if toInteger(vtable["isString"]):
d.putBetterType("%sQV4::Value (string)" % ns) d.putBetterType("%sQV4::Value (string)" % ns)
d.putStringValue(d.extractPointer(value) + 2 * d.ptrSize()) d.putStringValue(d.extractPointer(value) + 2 * d.ptrSize())

View File

@@ -1379,8 +1379,10 @@ void GdbEngine::handleStopResponse(const GdbMi &data)
int lineNumber = 0; int lineNumber = 0;
QString fullName; QString fullName;
QByteArray function;
if (frame.isValid()) { if (frame.isValid()) {
const GdbMi lineNumberG = frame["line"]; const GdbMi lineNumberG = frame["line"];
function = frame["func"].data();
if (lineNumberG.isValid()) { if (lineNumberG.isValid()) {
lineNumber = lineNumberG.toInt(); lineNumber = lineNumberG.toInt();
fullName = cleanupFullName(QString::fromLocal8Bit(frame["fullname"].data())); fullName = cleanupFullName(QString::fromLocal8Bit(frame["fullname"].data()));
@@ -1411,7 +1413,8 @@ void GdbEngine::handleStopResponse(const GdbMi &data)
// Quickly set the location marker. // Quickly set the location marker.
if (lineNumber && !boolSetting(OperateByInstruction) if (lineNumber && !boolSetting(OperateByInstruction)
&& QFileInfo::exists(fullName) && QFileInfo::exists(fullName)
&& !isQFatalBreakpoint(rid)) && !isQFatalBreakpoint(rid)
&& function != "qt_v4TriggeredBreakpointHook")
gotoLocation(Location(fullName, lineNumber)); gotoLocation(Location(fullName, lineNumber));
if (!m_commandsToRunOnTemporaryBreak.isEmpty()) { if (!m_commandsToRunOnTemporaryBreak.isEmpty()) {
@@ -1453,7 +1456,6 @@ void GdbEngine::handleStopResponse(const GdbMi &data)
QTC_ASSERT(state() == InferiorStopOk, qDebug() << state()); QTC_ASSERT(state() == InferiorStopOk, qDebug() << state());
if (gotoHandleStop1) if (gotoHandleStop1)
handleStop1(data); handleStop1(data);
} }
@@ -2060,6 +2062,11 @@ void GdbEngine::executeStep()
setTokenBarrier(); setTokenBarrier();
notifyInferiorRunRequested(); notifyInferiorRunRequested();
showStatusMessage(tr("Step requested..."), 5000); showStatusMessage(tr("Step requested..."), 5000);
if (isNativeMixedActive()) {
postCommand("prepareQmlStep 0");
postCommand("-exec-continue", RunRequest, CB(handleExecuteContinue));
return;
}
if (isReverseDebugging()) if (isReverseDebugging())
postCommand("reverse-step", RunRequest, CB(handleExecuteStep)); postCommand("reverse-step", RunRequest, CB(handleExecuteStep));
else else
@@ -2132,6 +2139,11 @@ void GdbEngine::executeNext()
setTokenBarrier(); setTokenBarrier();
notifyInferiorRunRequested(); notifyInferiorRunRequested();
showStatusMessage(tr("Step next requested..."), 5000); showStatusMessage(tr("Step next requested..."), 5000);
if (isNativeMixedActive()) {
postCommand("prepareQmlStep 1");
postCommand("-exec-continue", RunRequest, CB(handleExecuteContinue));
return;
}
if (isReverseDebugging()) { if (isReverseDebugging()) {
postCommand("reverse-next", RunRequest, CB(handleExecuteNext)); postCommand("reverse-next", RunRequest, CB(handleExecuteNext));
} else { } else {
@@ -2887,6 +2899,15 @@ void GdbEngine::removeBreakpoint(Breakpoint bp)
{ {
QTC_CHECK(bp.state() == BreakpointRemoveRequested); QTC_CHECK(bp.state() == BreakpointRemoveRequested);
BreakpointResponse br = bp.response(); 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()) { if (br.id.isValid()) {
// We already have a fully inserted breakpoint. // We already have a fully inserted breakpoint.
bp.notifyBreakpointRemoveProceeding(); bp.notifyBreakpointRemoveProceeding();
@@ -3291,7 +3312,7 @@ QByteArray GdbEngine::stackCommand(int depth)
if (isNativeMixedEnabled()) { if (isNativeMixedEnabled()) {
cmd = "stackListFrames " + QByteArray::number(depth) + ' '; cmd = "stackListFrames " + QByteArray::number(depth) + ' ';
if (isNativeMixedActive()) if (isNativeMixedActive())
cmd += "mixed"; cmd += "nativemixed";
else else
cmd += "noopt"; cmd += "noopt";
} else { } else {
@@ -3325,8 +3346,15 @@ StackFrame GdbEngine::parseStackFrame(const GdbMi &frameMi, int level)
frame.from = _(frameMi["from"].data()); frame.from = _(frameMi["from"].data());
frame.line = frameMi["line"].toInt(); frame.line = frameMi["line"].toInt();
frame.address = frameMi["addr"].toAddress(); frame.address = frameMi["addr"].toAddress();
frame.usable = QFileInfo(frame.file).isReadable(); GdbMi usable = frameMi["usable"];
if (frameMi["language"].data() == "js") { 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.language = QmlLanguage;
frame.fixQmlFrame(startParameters()); frame.fixQmlFrame(startParameters());
} }
@@ -4847,6 +4875,8 @@ void GdbEngine::updateLocalsPython(const UpdateParameters &params)
options += "autoderef,"; options += "autoderef,";
if (boolSetting(UseDynamicType)) if (boolSetting(UseDynamicType))
options += "dyntype,"; options += "dyntype,";
if (isNativeMixedActive())
options += "nativemixed,";
if (options.isEmpty()) if (options.isEmpty())
options += "defaults,"; options += "defaults,";
if (params.tryPartial) if (params.tryPartial)
@@ -4855,16 +4885,23 @@ void GdbEngine::updateLocalsPython(const UpdateParameters &params)
options += "tooltiponly,"; options += "tooltiponly,";
options.chop(1); 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; QByteArray resultVar;
if (!m_resultVarName.isEmpty()) if (!m_resultVarName.isEmpty())
resultVar = "resultvarname:" + m_resultVarName + ' '; resultVar = "resultvarname:" + m_resultVarName + ' ';
m_lastDebuggableCommand = m_lastDebuggableCommand =
"bb options:pe," + options + " vars:" + params.varList + ' ' "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 + ' ' 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)); Discardable, CB(handleStackFramePython), QVariant(params.tryPartial));
} }

View File

@@ -31,6 +31,7 @@
#include "gdbprocess.h" #include "gdbprocess.h"
#include <debugger/debuggerconstants.h> #include <debugger/debuggerconstants.h>
#include <debugger/debuggercore.h>
#include <debugger/procinterrupt.h> #include <debugger/procinterrupt.h>
namespace Debugger { namespace Debugger {
@@ -115,8 +116,11 @@ void GdbProcess::setProcessEnvironment(const QProcessEnvironment &env)
m_gdbProc.setProcessEnvironment(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)); m_gdbProc.setEnvironment(Utils::Environment(env));
} }