From 437e593a8724db8d117c867f94a51f05f37f4b71 Mon Sep 17 00:00:00 2001 From: Friedemann Kleint Date: Mon, 24 Feb 2014 10:17:41 +0100 Subject: [PATCH] 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: David Schulz --- src/libs/qtcreatorcdbext/extensioncontext.cpp | 75 +++++++++++++++++++ src/libs/qtcreatorcdbext/extensioncontext.h | 3 + src/libs/qtcreatorcdbext/qtcreatorcdbext.def | 1 + .../qtcreatorcdbext/qtcreatorcdbextension.cpp | 68 ++++++++++++++++- src/libs/qtcreatorcdbext/symbolgroupvalue.cpp | 2 +- src/libs/qtcreatorcdbext/symbolgroupvalue.h | 2 +- 6 files changed, 148 insertions(+), 3 deletions(-) diff --git a/src/libs/qtcreatorcdbext/extensioncontext.cpp b/src/libs/qtcreatorcdbext/extensioncontext.cpp index 2419f7cebd4..2afe0c3bcb0 100644 --- a/src/libs/qtcreatorcdbext/extensioncontext.cpp +++ b/src/libs/qtcreatorcdbext/extensioncontext.cpp @@ -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) diff --git a/src/libs/qtcreatorcdbext/extensioncontext.h b/src/libs/qtcreatorcdbext/extensioncontext.h index f4f0cac43ea..8b61c34908c 100644 --- a/src/libs/qtcreatorcdbext/extensioncontext.h +++ b/src/libs/qtcreatorcdbext/extensioncontext.h @@ -39,6 +39,7 @@ class LocalsSymbolGroup; class WatchesSymbolGroup; class OutputCallback; +class ExtensionCommandContext; // Global parameters class Parameters @@ -121,6 +122,8 @@ public: const Parameters ¶meters() const { return m_parameters; } Parameters ¶meters() { return m_parameters; } + ULONG64 jsExecutionContext(ExtensionCommandContext &exc, std::string *errorMessage); + bool stateNotification() const { return m_stateNotification; } void setStateNotification(bool s) { m_stateNotification = s; } diff --git a/src/libs/qtcreatorcdbext/qtcreatorcdbext.def b/src/libs/qtcreatorcdbext/qtcreatorcdbext.def index db92e4750f4..c9e9fe28d80 100644 --- a/src/libs/qtcreatorcdbext/qtcreatorcdbext.def +++ b/src/libs/qtcreatorcdbext/qtcreatorcdbext.def @@ -21,6 +21,7 @@ expression shutdownex test stack +qmlstack addwatch widgetat breakpoints diff --git a/src/libs/qtcreatorcdbext/qtcreatorcdbextension.cpp b/src/libs/qtcreatorcdbext/qtcreatorcdbextension.cpp index 3889b2777f2..eced0c95040 100644 --- a/src/libs/qtcreatorcdbext/qtcreatorcdbextension.cpp +++ b/src/libs/qtcreatorcdbext/qtcreatorcdbextension.cpp @@ -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]
"}, {"expression","Prints expression value.","[-t token] "}, {"stack","Prints stack in GDBMI format.","[-t token] [|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"," "}, {"widgetat","Return address of widget at position"," "}, {"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 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(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. diff --git a/src/libs/qtcreatorcdbext/symbolgroupvalue.cpp b/src/libs/qtcreatorcdbext/symbolgroupvalue.cpp index 61f116acdf7..d7ccf2f84ee 100644 --- a/src/libs/qtcreatorcdbext/symbolgroupvalue.cpp +++ b/src/libs/qtcreatorcdbext/symbolgroupvalue.cpp @@ -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) diff --git a/src/libs/qtcreatorcdbext/symbolgroupvalue.h b/src/libs/qtcreatorcdbext/symbolgroupvalue.h index 5811da9e91e..2e53a15abea 100644 --- a/src/libs/qtcreatorcdbext/symbolgroupvalue.h +++ b/src/libs/qtcreatorcdbext/symbolgroupvalue.h @@ -184,7 +184,7 @@ struct QtInfo { enum Module { - Core, Gui, Widgets, Network, Script + Core, Gui, Widgets, Network, Script, Qml }; QtInfo() : version(0) {}