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 "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 ¶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; }
|
||||
|
||||
|
||||
@@ -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) {}
|
||||
|
||||
Reference in New Issue
Block a user