Commit 61c49a12 authored by hjk's avatar hjk
Browse files

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: default avatarChristian Stenger <christian.stenger@theqtcompany.com>
parent fc3abe99
......@@ -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)
......@@ -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())
......
......@@ -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 &params)
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 &params)
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));
}
......
......@@ -31,6 +31,7 @@
#include "gdbprocess.h"
#include <debugger/debuggerconstants.h>
#include <debugger/debuggercore.h>
#include <debugger/procinterrupt.h>
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));
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment