forked from qt-creator/qt-creator
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 <david.schulz@digia.com>
This commit is contained in:
@@ -29,6 +29,7 @@
|
|||||||
|
|
||||||
#include "extensioncontext.h"
|
#include "extensioncontext.h"
|
||||||
#include "symbolgroup.h"
|
#include "symbolgroup.h"
|
||||||
|
#include "symbolgroupvalue.h"
|
||||||
#include "eventcallback.h"
|
#include "eventcallback.h"
|
||||||
#include "outputcallback.h"
|
#include "outputcallback.h"
|
||||||
#include "stringutils.h"
|
#include "stringutils.h"
|
||||||
@@ -189,6 +190,80 @@ ULONG ExtensionContext::executionStatus() const
|
|||||||
return (m_control && SUCCEEDED(m_control->GetExecutionStatus(&ex))) ? ex : ULONG(0);
|
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
|
// Complete stop parameters with common parameters and report
|
||||||
static inline ExtensionContext::StopReasonMap
|
static inline ExtensionContext::StopReasonMap
|
||||||
completeStopReasons(CIDebugClient *client, ExtensionContext::StopReasonMap stopReasons, ULONG ex)
|
completeStopReasons(CIDebugClient *client, ExtensionContext::StopReasonMap stopReasons, ULONG ex)
|
||||||
|
|||||||
@@ -39,6 +39,7 @@
|
|||||||
class LocalsSymbolGroup;
|
class LocalsSymbolGroup;
|
||||||
class WatchesSymbolGroup;
|
class WatchesSymbolGroup;
|
||||||
class OutputCallback;
|
class OutputCallback;
|
||||||
|
class ExtensionCommandContext;
|
||||||
|
|
||||||
// Global parameters
|
// Global parameters
|
||||||
class Parameters
|
class Parameters
|
||||||
@@ -121,6 +122,8 @@ public:
|
|||||||
const Parameters ¶meters() const { return m_parameters; }
|
const Parameters ¶meters() const { return m_parameters; }
|
||||||
Parameters ¶meters() { return m_parameters; }
|
Parameters ¶meters() { return m_parameters; }
|
||||||
|
|
||||||
|
ULONG64 jsExecutionContext(ExtensionCommandContext &exc, std::string *errorMessage);
|
||||||
|
|
||||||
bool stateNotification() const { return m_stateNotification; }
|
bool stateNotification() const { return m_stateNotification; }
|
||||||
void setStateNotification(bool s) { m_stateNotification = s; }
|
void setStateNotification(bool s) { m_stateNotification = s; }
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ expression
|
|||||||
shutdownex
|
shutdownex
|
||||||
test
|
test
|
||||||
stack
|
stack
|
||||||
|
qmlstack
|
||||||
addwatch
|
addwatch
|
||||||
widgetat
|
widgetat
|
||||||
breakpoints
|
breakpoints
|
||||||
|
|||||||
@@ -104,6 +104,7 @@ enum Command {
|
|||||||
CmdMemory,
|
CmdMemory,
|
||||||
CmdExpression,
|
CmdExpression,
|
||||||
CmdStack,
|
CmdStack,
|
||||||
|
CmdQmlStack,
|
||||||
CmdShutdownex,
|
CmdShutdownex,
|
||||||
CmdAddWatch,
|
CmdAddWatch,
|
||||||
CmdWidgetAt,
|
CmdWidgetAt,
|
||||||
@@ -167,12 +168,13 @@ static const CommandDescription commandDescriptions[] = {
|
|||||||
{"memory","Prints memory contents in Base64 encoding.","[-t token] <address> <length>"},
|
{"memory","Prints memory contents in Base64 encoding.","[-t token] <address> <length>"},
|
||||||
{"expression","Prints expression value.","[-t token] <expression>"},
|
{"expression","Prints expression value.","[-t token] <expression>"},
|
||||||
{"stack","Prints stack in GDBMI format.","[-t token] [<max-frames>|unlimited]"},
|
{"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.",""},
|
{"shutdownex","Unhooks output callbacks.\nNeeds to be called explicitly only in case of remote debugging.",""},
|
||||||
{"addwatch","Add watch expression","<iname> <expression>"},
|
{"addwatch","Add watch expression","<iname> <expression>"},
|
||||||
{"widgetat","Return address of widget at position","<x> <y>"},
|
{"widgetat","Return address of widget at position","<x> <y>"},
|
||||||
{"breakpoints","List breakpoints with modules","[-h] [-v]"},
|
{"breakpoints","List breakpoints with modules","[-h] [-v]"},
|
||||||
{"test","Testing command","-T type | -w watch-expression"},
|
{"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;
|
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")) {
|
} else if (!token.compare(0, equalsPos, "maxStackDepth")) {
|
||||||
if (integerFromString(value, &ExtensionContext::instance().parameters().maxStackDepth))
|
if (integerFromString(value, &ExtensionContext::instance().parameters().maxStackDepth))
|
||||||
++success;
|
++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;
|
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):
|
// Extension command 'shutdownex' (shutdown is reserved):
|
||||||
// Unhook the output callbacks. This is normally done by the session
|
// Unhook the output callbacks. This is normally done by the session
|
||||||
// inaccessible notification, however, this does not work for remote-controlled sessions.
|
// 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
|
// Must match the enumeration
|
||||||
static const char* modNames[] =
|
static const char* modNames[] =
|
||||||
{"Core", "Gui", "Widgets", "Network", "Script" };
|
{"Core", "Gui", "Widgets", "Network", "Script", "Qml" };
|
||||||
std::ostringstream result;
|
std::ostringstream result;
|
||||||
result << "Qt";
|
result << "Qt";
|
||||||
if (version >= 5)
|
if (version >= 5)
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ struct QtInfo
|
|||||||
{
|
{
|
||||||
enum Module
|
enum Module
|
||||||
{
|
{
|
||||||
Core, Gui, Widgets, Network, Script
|
Core, Gui, Widgets, Network, Script, Qml
|
||||||
};
|
};
|
||||||
|
|
||||||
QtInfo() : version(0) {}
|
QtInfo() : version(0) {}
|
||||||
|
|||||||
Reference in New Issue
Block a user