Commit 437e593a authored by Friedemann Kleint's avatar Friedemann Kleint

CDB extension: Add qmlstack command and helpers.

Add a command that dumps the QML stack. It tries to find
the address of the JS execution context in a complete stack
trace and calls the exported function in QML to create the
trace from it.

Task-number: QTCREATORBUG-11144

Change-Id: I8fef5df2b33b95748e78d837aba703945eaeead9
Reviewed-by: default avatarDavid Schulz <david.schulz@digia.com>
parent 845cef82
......@@ -29,6 +29,7 @@
#include "extensioncontext.h"
#include "symbolgroup.h"
#include "symbolgroupvalue.h"
#include "eventcallback.h"
#include "outputcallback.h"
#include "stringutils.h"
......@@ -189,6 +190,80 @@ ULONG ExtensionContext::executionStatus() const
return (m_control && SUCCEEDED(m_control->GetExecutionStatus(&ex))) ? ex : ULONG(0);
}
// Helpers for finding the address of the JS execution context in
// case of a QML crash: Find module
static std::string findModule(CIDebugSymbols *syms,
const std::string &name,
std::string *errorMessage)
{
const Modules mods = getModules(syms, errorMessage);
const size_t count = mods.size();
for (size_t m = 0; m < count; ++m)
if (!mods.at(m).name.compare(0, name.size(), name))
return mods.at(m).name;
return std::string();
}
// Try to find a JS execution context passed as parameter in a complete stack dump (kp)
static ULONG64 jsExecutionContextFromStackTrace(const std::wstring &stack)
{
// Search for "QV4::ExecutionContext * - varying variable names - 0x...[,)]"
const wchar_t needle[] = L"struct QV4::ExecutionContext * "; // .. varying variable names .. 0x...
const std::string::size_type varPos = stack.find(needle);
if (varPos == std::string::npos)
return 0;
const std::string::size_type varEnd = varPos + sizeof(needle) / sizeof(wchar_t) - 1;
std::string::size_type numPos = stack.find(L"0x", varEnd);
if (numPos == std::string::npos || numPos > (varEnd + 20))
return 0;
numPos += 2;
const std::string::size_type endPos = stack.find_first_of(L",)", numPos);
if (endPos == std::string::npos)
return 0;
// Fix hex values: (0x)000000f5`cecae5b0 -> (0x)000000f5cecae5b0
std::wstring address = stack.substr(numPos, endPos - numPos);
if (address.size() > 8 && address.at(8) == L'`')
address.erase(8, 1);
std::wistringstream str(address);
ULONG64 result;
str >> std::hex >> result;
return str.fail() ? 0 : result;
}
// Try to find address of jsExecutionContext by looking at the
// stack trace in case QML is loaded.
ULONG64 ExtensionContext::jsExecutionContext(ExtensionCommandContext &exc,
std::string *errorMessage)
{
const QtInfo &qtInfo = QtInfo::get(SymbolGroupValueContext(exc.dataSpaces(), exc.symbols()));
static const std::string qmlModule =
findModule(exc.symbols(), qtInfo.moduleName(QtInfo::Qml), errorMessage);
if (qmlModule.empty()) {
if (errorMessage->empty())
*errorMessage = "QML not loaded";
return 0;
}
// Retrieve full stack (costly) and try to find a JS execution context passed as parameter
startRecordingOutput();
StateNotificationBlocker blocker(this);
const HRESULT hr = m_control->Execute(DEBUG_OUTCTL_ALL_CLIENTS, "kp", DEBUG_EXECUTE_ECHO);
if (FAILED(hr)) {
stopRecordingOutput();
*errorMessage = msgDebugEngineComFailed("Execute", hr);
return 0;
}
const std::wstring fullStackTrace = stopRecordingOutput();
if (fullStackTrace.empty()) {
*errorMessage = "Unable to obtain stack (output redirection in place?)";
return 0;
}
const ULONG64 result = jsExecutionContextFromStackTrace(fullStackTrace);
if (!result)
*errorMessage = "JS ExecutionContext address not found in stack";
return result;
}
// Complete stop parameters with common parameters and report
static inline ExtensionContext::StopReasonMap
completeStopReasons(CIDebugClient *client, ExtensionContext::StopReasonMap stopReasons, ULONG ex)
......
......@@ -39,6 +39,7 @@
class LocalsSymbolGroup;
class WatchesSymbolGroup;
class OutputCallback;
class ExtensionCommandContext;
// Global parameters
class Parameters
......@@ -121,6 +122,8 @@ public:
const Parameters &parameters() const { return m_parameters; }
Parameters &parameters() { return m_parameters; }
ULONG64 jsExecutionContext(ExtensionCommandContext &exc, std::string *errorMessage);
bool stateNotification() const { return m_stateNotification; }
void setStateNotification(bool s) { m_stateNotification = s; }
......
......@@ -21,6 +21,7 @@ expression
shutdownex
test
stack
qmlstack
addwatch
widgetat
breakpoints
......
......@@ -104,6 +104,7 @@ enum Command {
CmdMemory,
CmdExpression,
CmdStack,
CmdQmlStack,
CmdShutdownex,
CmdAddWatch,
CmdWidgetAt,
......@@ -167,12 +168,13 @@ static const CommandDescription commandDescriptions[] = {
{"memory","Prints memory contents in Base64 encoding.","[-t token] <address> <length>"},
{"expression","Prints expression value.","[-t token] <expression>"},
{"stack","Prints stack in GDBMI format.","[-t token] [<max-frames>|unlimited]"},
{"qmlstack","Prints QML stack in GDBMI format.","[-t token] [js-executioncontext]"},
{"shutdownex","Unhooks output callbacks.\nNeeds to be called explicitly only in case of remote debugging.",""},
{"addwatch","Add watch expression","<iname> <expression>"},
{"widgetat","Return address of widget at position","<x> <y>"},
{"breakpoints","List breakpoints with modules","[-h] [-v]"},
{"test","Testing command","-T type | -w watch-expression"},
{"setparameter","Set parameter","maxStringLength=value maxStackDepth=value"}
{"setparameter","Set parameter","maxStringLength=value maxStackDepth=value stateNotification=1,0"}
};
typedef std::vector<std::string> StringVector;
......@@ -913,6 +915,12 @@ extern "C" HRESULT CALLBACK setparameter(CIDebugClient *, PCSTR args)
} else if (!token.compare(0, equalsPos, "maxStackDepth")) {
if (integerFromString(value, &ExtensionContext::instance().parameters().maxStackDepth))
++success;
} else if (!token.compare(0, equalsPos, "stateNotification")) {
int s; // Silence state notification (development purposes only)
if (integerFromString(value, &s)) {
ExtensionContext::instance().setStateNotification(s != 0);
++success;
}
}
}
}
......@@ -1027,6 +1035,64 @@ extern "C" HRESULT CALLBACK stack(CIDebugClient *Client, PCSTR argsIn)
return S_OK;
}
// Extension command 'qmlstack'
// Report stack correctly as 'k' does not list instruction pointer
// correctly.
extern "C" HRESULT CALLBACK qmlstack(CIDebugClient *client, PCSTR argsIn)
{
ExtensionCommandContext exc(client);
std::string errorMessage;
int token = 0;
bool humanReadable = false;
ULONG64 jsExecutionContext = 0;
std::string stackDump;
do {
StringList tokens = commandTokens<StringList>(argsIn, &token);
if (!tokens.empty() && tokens.front() == "-h") {
humanReadable = true;
tokens.pop_front();
}
if (!tokens.empty()) {
if (!integerFromString(tokens.front(), &jsExecutionContext)) {
errorMessage = "Invalid address " + tokens.front();
break;
}
tokens.pop_front();
}
ExtensionCommandContext exc(client);
if (!jsExecutionContext) { // Try to find execution context unless it was given.
jsExecutionContext = ExtensionContext::instance().jsExecutionContext(exc, &errorMessage);
if (!jsExecutionContext)
break;
}
// call function to get stack trace. Call with exceptions handled right from
// the start assuming this is invoked for crashed applications.
std::ostringstream callStr;
const QtInfo &qtInfo = QtInfo::get(SymbolGroupValueContext(exc.dataSpaces(), exc.symbols()));
callStr << qtInfo.prependQtModule("qt_v4StackTrace(", QtInfo::Qml) << std::showbase << std::hex
<< jsExecutionContext << std::dec << std::noshowbase << ')';
std::wstring wOutput;
if (!ExtensionContext::instance().call(callStr.str(), ExtensionContext::CallWithExceptionsHandled, &wOutput, &errorMessage))
break;
// extract GDBMI info from call
const std::string::size_type sPos = wOutput.find(L"stack=[");
const std::string::size_type sEndPos = wOutput.rfind(L']');
if (sPos == std::string::npos || sEndPos == std::string::npos || sEndPos < sPos) {
errorMessage = "No stack returned";
break;
}
stackDump = wStringToString(wOutput.substr(sPos, sEndPos - sPos + 1));
} while (false);
if (stackDump.empty())
ExtensionContext::instance().report('N', token, 0, "qmlstack", errorMessage.c_str());
else
ExtensionContext::instance().reportLong('R', token, "qmlstack", stackDump);
return S_OK;
}
// Extension command 'shutdownex' (shutdown is reserved):
// Unhook the output callbacks. This is normally done by the session
// inaccessible notification, however, this does not work for remote-controlled sessions.
......
......@@ -747,7 +747,7 @@ std::string QtInfo::moduleName(Module m) const
{
// Must match the enumeration
static const char* modNames[] =
{"Core", "Gui", "Widgets", "Network", "Script" };
{"Core", "Gui", "Widgets", "Network", "Script", "Qml" };
std::ostringstream result;
result << "Qt";
if (version >= 5)
......
......@@ -184,7 +184,7 @@ struct QtInfo
{
enum Module
{
Core, Gui, Widgets, Network, Script
Core, Gui, Widgets, Network, Script, Qml
};
QtInfo() : version(0) {}
......
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