Commit e64fefdb authored by Friedemann Kleint's avatar Friedemann Kleint Committed by hjk

Add a stack window menu entry to display QML stack frame.

Add language field to stack frame.
Add virtual for loading QML stack invoked by stack window
context menu, implement for CDB, GDB.

Task-number: QTCREATORBUG-11144

Change-Id: Ic39be3978b40d96ed18cb69a8355296ec572ece7
Reviewed-by: default avatarhjk <hjk121@nokiamail.com>
parent 27ae8780
......@@ -1109,7 +1109,8 @@ bool CdbEngine::hasCapability(unsigned cap) const
|CreateFullBacktraceCapability
|OperateByInstructionCapability
|RunToLineCapability
|MemoryAddressCapability);
|MemoryAddressCapability
|AdditionalQmlStackCapability);
}
void CdbEngine::executeStep()
......@@ -1471,6 +1472,11 @@ void CdbEngine::activateFrame(int index)
}
const StackFrame frame = frames.at(index);
if (frame.language != CppLanguage) {
gotoLocation(frame);
return;
}
if (debug || debugLocals)
qDebug("activateFrame idx=%d '%s' %d", index,
qPrintable(frame.file), frame.line);
......@@ -2933,6 +2939,9 @@ static StackFrames parseFrames(const GdbMi &gdbmi, bool *incomplete = 0)
frame.file = QFile::decodeName(fullName.data());
frame.line = frameMi["line"].data().toInt();
frame.usable = false; // To be decided after source path mapping.
const GdbMi languageMi = frameMi["language"];
if (languageMi.isValid() && languageMi.data() == "js")
frame.language = QmlLanguage;
}
frame.function = QLatin1String(frameMi["func"].data());
frame.from = QLatin1String(frameMi["from"].data());
......@@ -2987,6 +2996,39 @@ unsigned CdbEngine::parseStackTrace(const GdbMi &data, bool sourceStepInto)
return 0;
}
void CdbEngine::loadAdditionalQmlStack()
{
postExtensionCommand("qmlstack", QByteArray(), 0, &CdbEngine::handleAdditionalQmlStack);
}
void CdbEngine::handleAdditionalQmlStack(const CdbExtensionCommandPtr &reply)
{
QString errorMessage;
do {
if (!reply->success) {
errorMessage = QLatin1String(reply->errorMessage);
break;
}
GdbMi stackGdbMi;
stackGdbMi.fromString(reply->reply);
if (!stackGdbMi.isValid()) {
errorMessage = QLatin1String("GDBMI parser error");
break;
}
StackFrames qmlFrames = parseFrames(stackGdbMi);
const int qmlFrameCount = qmlFrames.size();
if (!qmlFrameCount) {
errorMessage = QLatin1String("Empty stack");
break;
}
for (int i = 0; i < qmlFrameCount; ++i)
qmlFrames[i].fixQmlFrame(startParameters());
stackHandler()->prependFrames(qmlFrames);
} while (false);
if (!errorMessage.isEmpty())
showMessage(QLatin1String("Unable to obtain QML stack trace: ") + errorMessage, LogError);
}
void CdbEngine::mergeStartParametersSourcePathMap()
{
const DebuggerStartParameters &sp = startParameters();
......
......@@ -124,6 +124,7 @@ public:
virtual void reloadRegisters();
virtual void reloadSourceFiles();
virtual void reloadFullStack();
void loadAdditionalQmlStack();
static QString extensionLibraryName(bool is64Bit);
......@@ -241,6 +242,7 @@ private:
void handleWidgetAt(const CdbExtensionCommandPtr &);
void handleBreakPoints(const CdbExtensionCommandPtr &);
void handleBreakPoints(const GdbMi &value);
void handleAdditionalQmlStack(const CdbExtensionCommandPtr &);
NormalizedSourceFileName sourceMapNormalizeFileNameFromDebugger(const QString &f);
void updateLocalVariable(const QByteArray &iname);
void updateLocals(bool forNewStackFrame = false);
......
......@@ -161,7 +161,8 @@ enum DebuggerCapabilities
RunToLineCapability = 0x800000,
MemoryAddressCapability = 0x1000000,
ShowModuleSectionsCapability = 0x200000,
WatchComplexExpressionsCapability = 0x400000 // Used to filter out challenges for cdb.
WatchComplexExpressionsCapability = 0x400000, // Used to filter out challenges for cdb.
AdditionalQmlStackCapability = 0x800000 // C++ debugger engine is able to retrieve QML stack as well.
};
enum LogChannel
......
......@@ -1402,6 +1402,10 @@ void DebuggerEngine::reloadFullStack()
{
}
void DebuggerEngine::loadAdditionalQmlStack()
{
}
void DebuggerEngine::reloadDebuggingHelpers()
{
}
......
......@@ -184,6 +184,7 @@ public:
virtual void reloadRegisters();
virtual void reloadSourceFiles();
virtual void reloadFullStack();
virtual void loadAdditionalQmlStack();
virtual void reloadDebuggingHelpers();
virtual void setRegisterValue(int regnr, const QString &value);
......
......@@ -2020,7 +2020,8 @@ bool GdbEngine::hasCapability(unsigned cap) const
| OperateByInstructionCapability
| RunToLineCapability
| WatchComplexExpressionsCapability
| MemoryAddressCapability))
| MemoryAddressCapability
| AdditionalQmlStackCapability))
return true;
if (startParameters().startMode == AttachCore)
......@@ -3207,6 +3208,90 @@ void GdbEngine::reloadFullStack()
QVariant::fromValue<StackCookie>(StackCookie(true, true)));
}
void GdbEngine::loadAdditionalQmlStack()
{
// Scan for QV4::ExecutionContext parameter in the parameter list of a V4 call.
postCommand("-stack-list-arguments --simple-values", NeedsStop, CB(handleQmlStackFrameArguments));
}
// 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);
if (!frameList.childCount())
return 0;
QByteArray jsExecutionContextType = qtNamespace;
if (!jsExecutionContextType.isEmpty())
jsExecutionContextType.append("::");
jsExecutionContextType.append("QV4::ExecutionContext *");
foreach (const GdbMi &frameNode, frameList.children()) {
foreach (const GdbMi &argNode, frameNode["args"].children()) {
if (argNode["type"].data() == jsExecutionContextType) {
bool ok;
const quint64 address = argNode["value"].data().toULongLong(&ok, 16);
if (ok && address)
return address;
}
}
}
return 0;
}
static QString msgCannotLoadQmlStack(const QString &why)
{
return _("Unable to load QML stack: ") + why;
}
void GdbEngine::handleQmlStackFrameArguments(const GdbResponse &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.
QByteArray command = "-data-evaluate-expression \"qt_v4StackTrace((QV4::ExecutionContext *)0x";
command += QByteArray::number(contextAddress, 16);
command += ")\"";
postCommand(command, CB(handleQmlStackTrace));
}
void GdbEngine::handleQmlStackTrace(const GdbResponse &response)
{
if (!response.data.isValid()) {
showMessage(msgCannotLoadQmlStack(_("No result obtained.")), LogError);
return;
}
// Prepend QML stack frames to existing C++ stack frames.
QByteArray stackData = response.data["value"].data();
const int index = stackData.indexOf("stack=");
if (index == -1) {
showMessage(msgCannotLoadQmlStack(_("Malformed result.")), LogError);
return;
}
stackData.remove(0, index);
stackData.replace("\\\"", "\"");
GdbMi stackMi;
stackMi.fromString(stackData);
const int qmlFrameCount = stackMi.childCount();
if (!qmlFrameCount) {
showMessage(msgCannotLoadQmlStack(_("No stack frames obtained.")), LogError);
return;
}
QList<StackFrame> qmlFrames;
qmlFrames.reserve(qmlFrameCount);
for (int i = 0; i < qmlFrameCount; ++i) {
StackFrame frame = parseStackFrame(stackMi.childAt(i), i);
frame.fixQmlFrame(startParameters());
qmlFrames.append(frame);
}
stackHandler()->prependFrames(qmlFrames);
}
void GdbEngine::reloadStack(bool forceGotoLocation)
{
PENDING_DEBUG("RELOAD STACK");
......@@ -3233,6 +3318,8 @@ StackFrame GdbEngine::parseStackFrame(const GdbMi &frameMi, int level)
frame.line = frameMi["line"].toInt();
frame.address = frameMi["addr"].toAddress();
frame.usable = QFileInfo(frame.file).isReadable();
if (frameMi["language"].data() == "js")
frame.language = QmlLanguage;
return frame;
}
......@@ -3308,6 +3395,10 @@ void GdbEngine::activateFrame(int frameIndex)
QTC_ASSERT(frameIndex < handler->stackSize(), return);
if (handler->frameAt(frameIndex).language == QmlLanguage) {
gotoLocation(handler->frameAt(frameIndex));
return;
}
// Assuming the command always succeeds this saves a roundtrip.
// Otherwise the lines below would need to get triggered
// after a response to this -stack-select-frame here.
......
......@@ -409,6 +409,9 @@ protected:
void handleThreadNames(const GdbResponse &response);
Q_SLOT void reloadStack(bool forceGotoLocation);
Q_SLOT virtual void reloadFullStack();
virtual void loadAdditionalQmlStack();
void handleQmlStackFrameArguments(const GdbResponse &response);
void handleQmlStackTrace(const GdbResponse &response);
int currentFrame() const;
QList<GdbMi> m_currentFunctionArgs;
......
......@@ -28,10 +28,13 @@
****************************************************************************/
#include "stackframe.h"
#include "debuggerstartparameters.h"
#include "watchutils.h"
#include <QDebug>
#include <QDir>
#include <QFileInfo>
#include <utils/hostosinfo.h>
......@@ -45,7 +48,7 @@ namespace Internal {
////////////////////////////////////////////////////////////////////////
StackFrame::StackFrame()
: level(-1), line(-1), address(0), usable(false)
: language(CppLanguage), level(-1), line(-1), address(0), usable(false)
{}
void StackFrame::clear()
......@@ -90,7 +93,9 @@ QString StackFrame::toToolTip() const
str << "<tr><td>" << tr("Address:") << "</td><td>"
<< formatToolTipAddress(address) << "</td></tr>";
if (!function.isEmpty())
str << "<tr><td>" << tr("Function:") << "</td><td>" << function << "</td></tr>";
str << "<tr><td>"
<< (language == CppLanguage ? tr("Function:") : tr("JS-Function:"))
<< "</td><td>" << function << "</td></tr>";
if (!file.isEmpty())
str << "<tr><td>" << tr("File:") << "</td><td>" << filePath << "</td></tr>";
if (line != -1)
......@@ -127,6 +132,35 @@ QString StackFrame::toToolTip() const
return res;
}
// Try to resolve files of a QML stack (resource files).
void StackFrame::fixQmlFrame(const DebuggerStartParameters &sp)
{
if (language != QmlLanguage)
return;
QFileInfo aFi(file);
if (aFi.isAbsolute()) {
usable = aFi.isFile();
return;
}
if (!file.startsWith(QLatin1String("qrc:/")))
return;
const QString relativeFile = file.right(file.size() - 5);
if (!sp.projectSourceDirectory.isEmpty()) {
const QFileInfo pFi(sp.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;
}
}
}
QDebug operator<<(QDebug d, const StackFrame &f)
{
QString res;
......
......@@ -30,6 +30,8 @@
#ifndef DEBUGGER_STACKFRAME_H
#define DEBUGGER_STACKFRAME_H
#include "debuggerconstants.h"
#include <QCoreApplication>
#include <QMetaType>
......@@ -38,6 +40,9 @@ class QDebug;
QT_END_NAMESPACE
namespace Debugger {
class DebuggerStartParameters;
namespace Internal {
class StackFrame
......@@ -48,8 +53,10 @@ public:
bool isUsable() const;
QString toToolTip() const;
QString toString() const;
void fixQmlFrame(const DebuggerStartParameters &sp);
public:
DebuggerLanguage language;
qint32 level;
QString function;
QString file; // We try to put an absolute file name in there.
......
......@@ -205,6 +205,20 @@ void StackHandler::setFrames(const StackFrames &frames, bool canExpand)
emit stackChanged();
}
void StackHandler::prependFrames(const StackFrames &frames)
{
if (frames.isEmpty())
return;
const int count = frames.size();
beginInsertRows(QModelIndex(), 0, count - 1);
for (int i = count - 1; i >= 0; --i)
m_stackFrames.prepend(frames.at(i));
endInsertRows();
if (m_currentIndex >= 0)
setCurrentIndex(m_currentIndex + count);
emit stackChanged();
}
const StackFrames &StackHandler::frames() const
{
return m_stackFrames;
......
......@@ -67,6 +67,7 @@ public:
~StackHandler();
void setFrames(const StackFrames &frames, bool canExpand = false);
void prependFrames(const StackFrames &frames);
const StackFrames &frames() const;
void setCurrentIndex(int index);
int currentIndex() const { return m_currentIndex; }
......
......@@ -168,6 +168,10 @@ void StackTreeView::contextMenuEvent(QContextMenuEvent *ev)
if (engine->hasCapability(CreateFullBacktraceCapability))
menu.addAction(debuggerCore()->action(CreateFullBacktrace));
QAction *additionalQmlStackAction = 0;
if (engine->hasCapability(AdditionalQmlStackCapability))
additionalQmlStackAction = menu.addAction(tr("Load QML stack"));
QAction *actShowMemory = 0;
if (engine->hasCapability(ShowMemoryCapability)) {
actShowMemory = menu.addAction(QString());
......@@ -242,6 +246,8 @@ void StackTreeView::contextMenuEvent(QContextMenuEvent *ev)
engine->loadSymbolsForStack();
else if (act == actSaveTaskFile)
saveTaskFile(this, handler);
else if (act == additionalQmlStackAction)
engine->loadAdditionalQmlStack();
else
handleBaseContextAction(act);
}
......
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