Debugger: Split long cdbextension commands

Fixes: QTCREATORBUG-22922
Change-Id: I5def321f0f97717728bc5cdcd5309b458a8ecfa1
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
This commit is contained in:
David Schulz
2020-03-31 14:09:32 +02:00
parent 41a122641c
commit 9cfd1b5d6c
3 changed files with 177 additions and 27 deletions

View File

@@ -191,6 +191,12 @@ static inline bool isOption(const std::string &opt)
return opt.size() == 2 && opt.at(0) == '-' && opt != "--";
}
struct CommandToken
{
int majorPart = 0;
int minorPart = 0;
};
// Helper for commandTokens() below:
// Simple splitting of command lines allowing for '"'-quoted tokens
// 'typecast local.i "class QString *"' -> ('typecast','local.i','class QString *')
@@ -251,6 +257,38 @@ static inline void splitCommand(PCSTR args, Inserter it)
}
}
// check whether arguments start with a "-t 23.1" or "-t 3" token identifier
CommandToken extractToken(PCSTR args, PCSTR *afterToken)
{
CommandToken token;
while (*args == ' ') // skip whitespace
++args;
if (*args != '-')
return {};
if (*(++args) != 't')
return {};
++args;
while (*args == ' ') // skip whitespace
++args;
PSTR end = nullptr;
token.majorPart = strtol(args, &end, 10);
if (args >= end)
return {};
args = end;
if (*args == '.') {
++args;
end = nullptr;
token.minorPart = strtol(args, &end, 10);
if (args >= end)
return {};
args = end;
}
while (*args == ' ') // skip whitespace
++args;
*afterToken = args;
return token;
}
// Split & Parse the arguments of a command and extract the
// optional first integer token argument ('command -t <number> remaining arguments')
// Each command takes the 'token' argument and includes it in its output
@@ -259,18 +297,25 @@ static inline void splitCommand(PCSTR args, Inserter it)
template<class StringContainer>
static inline StringContainer commandTokens(PCSTR args, int *token = 0)
{
typedef typename StringContainer::iterator ContainerIterator;
static std::map<int, std::string> s_commandBuffer;
if (token)
*token = -1; // Handled as 'display' in engine, so that user can type commands
StringContainer tokens;
splitCommand(args, std::back_inserter(tokens));
// Check for token
ContainerIterator it = tokens.begin();
if (it != tokens.end() && *it == "-t" && ++it != tokens.end()) {
CommandToken commandToken = extractToken(args, &args);
if (commandToken.majorPart != 0) {
s_commandBuffer[commandToken.majorPart].append(args);
if (commandToken.minorPart == 0) {
DebugPrint() << commandToken.majorPart << ':' << s_commandBuffer[commandToken.majorPart];
splitCommand(s_commandBuffer[commandToken.majorPart].data(), std::back_inserter(tokens));
if (token)
std::istringstream(*it) >> *token;
tokens.erase(tokens.begin(), ++it);
*token = commandToken.majorPart;
} else if (token) {
*token = 0;
}
} else {
splitCommand(args, std::back_inserter(tokens));
}
return tokens;
}
@@ -283,6 +328,8 @@ extern "C" HRESULT CALLBACK pid(CIDebugClient *client, PCSTR args)
int token;
commandTokens<StringList>(args, &token);
if (token == 0) // partial message
return S_OK;
dprintf("Qt Creator CDB extension version 4.3 %d bit.\n",
sizeof(void *) * 8);
if (const ULONG pid = currentProcessId(client))
@@ -304,6 +351,9 @@ extern "C" HRESULT CALLBACK expandlocals(CIDebugClient *client, PCSTR args)
int token;
StringList tokens = commandTokens<StringList>(args, &token);
if (token == 0) // partial message
return S_OK;
StringVector inames;
bool runComplexDumpers = false;
do {
@@ -423,10 +473,12 @@ static std::string commandLocals(ExtensionCommandContext &commandExtCtx,PCSTR ar
typedef InameExpressionMap::value_type InameExpressionMapEntry;
// Parse the command
StringList tokens = commandTokens<StringList>(args, token);
if (token == 0) // partial arguments
return {};
ExtensionContext &extCtx = ExtensionContext::instance();
DumpCommandParameters parameters;
std::string iname;
StringList tokens = commandTokens<StringList>(args, token);
StringVector expandedInames;
StringVector uninitializedInames;
InameExpressionMap watcherInameExpressionMap;
@@ -577,8 +629,11 @@ extern "C" HRESULT CALLBACK script(CIDebugClient *client, PCSTR argsIn)
ExtensionCommandContext exc(client);
int token;
#ifdef WITH_PYTHON
const StringList args = commandTokens<StringList>(argsIn, &token);
if (token == 0) // partial arguments
return {};
std::stringstream command;
for (std::string arg : commandTokens<StringList>(argsIn, &token))
for (std::string arg : args)
command << arg << ' ';
PyObject *ptype = NULL;
@@ -594,6 +649,8 @@ extern "C" HRESULT CALLBACK script(CIDebugClient *client, PCSTR argsIn)
PyErr_Restore(ptype, pvalue, ptraceback);
#else
commandTokens<StringList>(argsIn, &token);
if (token == 0) // partial arguments
return {};
ExtensionContext::instance().report('N', token, 0, "script",
"Python is not supported in this CDB extension.\n"
"You need to define PYTHON_INSTALL_DIR in your creator build environment "
@@ -608,6 +665,8 @@ extern "C" HRESULT CALLBACK locals(CIDebugClient *client, PCSTR args)
std::string errorMessage;
int token;
const std::string output = commandLocals(exc, args, &token, &errorMessage);
if (token == 0) // partial message
return S_OK;
SymbolGroupValue::verbose = 0;
if (output.empty())
ExtensionContext::instance().report('N', token, 0, "locals", errorMessage.c_str());
@@ -625,6 +684,9 @@ static std::string commmandWatches(ExtensionCommandContext &exc, PCSTR args, int
// Parse the command
DumpCommandParameters parameters;
StringList tokens = commandTokens<StringList>(args, token);
if (token == 0) // partial message
return {};
// Parse away options
for (bool optionLeft = true; optionLeft && !tokens.empty(); ) {
switch (parameters.parseOption(&tokens)) {
@@ -662,6 +724,9 @@ extern "C" HRESULT CALLBACK watches(CIDebugClient *client, PCSTR args)
std::string errorMessage = "e";
int token = 0;
const std::string output = commmandWatches(exc, args, &token, &errorMessage);
if (token == 0) // partial message
return S_OK;
SymbolGroupValue::verbose = 0;
if (output.empty())
ExtensionContext::instance().report('N', token, 0, "locals", errorMessage.c_str());
@@ -677,6 +742,9 @@ static std::string dumplocalHelper(ExtensionCommandContext &exc,PCSTR args, int
{
// Parse the command
StringList tokens = commandTokens<StringList>(args, token);
if (token == 0) // partial message
return {};
// Frame and iname
unsigned frame;
if (tokens.empty() || integerFromString(tokens.front(), &frame)) {
@@ -714,6 +782,9 @@ extern "C" HRESULT CALLBACK dumplocal(CIDebugClient *client, PCSTR argsIn)
std::string errorMessage;
int token = 0;
const std::string value = dumplocalHelper(exc,argsIn, &token, &errorMessage);
if (token == 0) // partial message
return S_OK;
if (value.empty())
ExtensionContext::instance().report('N', token, 0, "dumplocal", errorMessage.c_str());
else
@@ -733,6 +804,9 @@ extern "C" HRESULT CALLBACK typecast(CIDebugClient *client, PCSTR args)
int token;
const StringVector tokens = commandTokens<StringVector>(args, &token);
if (token == 0) // partial message
return S_OK;
std::string iname;
std::string desiredType;
if (tokens.size() == 3u && integerFromString(tokens.front(), &frame)) {
@@ -761,6 +835,9 @@ extern "C" HRESULT CALLBACK addsymbol(CIDebugClient *client, PCSTR args)
int token;
const StringVector tokens = commandTokens<StringVector>(args, &token);
if (token == 0) // partial message
return S_OK;
std::string name;
std::string iname;
if (tokens.size() >= 2u && integerFromString(tokens.front(), &frame)) {
@@ -790,6 +867,9 @@ extern "C" HRESULT CALLBACK addwatch(CIDebugClient *client, PCSTR argsIn)
bool success = false;
do {
StringList tokens = commandTokens<StringList>(argsIn, &token);
if (token == 0) // partial message
return S_OK;
if (tokens.size() != 2) {
errorMessage = singleLineUsage(commandDescriptions[CmdAddWatch]);
break;
@@ -824,6 +904,9 @@ extern "C" HRESULT CALLBACK assign(CIDebugClient *client, PCSTR argsIn)
int token = 0;
do {
StringList tokens = commandTokens<StringList>(argsIn, &token);
if (token == 0) // partial message
return S_OK;
if (tokens.empty()) {
errorMessage = singleLineUsage(commandDescriptions[CmdAssign]);
break;
@@ -883,6 +966,8 @@ extern "C" HRESULT CALLBACK threads(CIDebugClient *client, PCSTR argsIn)
int token;
commandTokens<StringList>(argsIn, &token);
if (token == 0) // partial message
return S_OK;
const std::string gdbmi = gdbmiThreadList(exc.systemObjects(),
exc.symbols(),
@@ -906,6 +991,9 @@ extern "C" HRESULT CALLBACK registers(CIDebugClient *Client, PCSTR argsIn)
int token;
const StringList tokens = commandTokens<StringList>(argsIn, &token);
if (token == 0) // partial message
return S_OK;
const bool humanReadable = !tokens.empty() && tokens.front() == "-h";
const std::string regs = gdbmiRegisters(exc.registers(), exc.control(), humanReadable, IncludePseudoRegisters, &errorMessage);
if (regs.empty())
@@ -925,6 +1013,9 @@ extern "C" HRESULT CALLBACK modules(CIDebugClient *Client, PCSTR argsIn)
int token;
const StringList tokens = commandTokens<StringList>(argsIn, &token);
if (token == 0) // partial message
return S_OK;
const bool humanReadable = !tokens.empty() && tokens.front() == "-h";
const std::string modules = gdbmiModules(exc.symbols(), humanReadable, &errorMessage);
if (modules.empty())
@@ -949,6 +1040,9 @@ extern "C" HRESULT CALLBACK setparameter(CIDebugClient *, PCSTR args)
{
int token;
StringVector tokens = commandTokens<StringVector>(args, &token);
if (token == 0) // partial message
return S_OK;
const size_t count = tokens.size();
size_t success = 0;
for (size_t i = 0; i < count; ++i) {
@@ -1014,6 +1108,9 @@ extern "C" HRESULT CALLBACK memory(CIDebugClient *Client, PCSTR argsIn)
ULONG length = 0;
const StringVector tokens = commandTokens<StringVector>(argsIn, &token);
if (token == 0) // partial message
return S_OK;
if (tokens.size() == 2
&& integerFromString(tokens.front(), &address)
&& integerFromString(tokens.at(1), &length)) {
@@ -1041,6 +1138,9 @@ extern "C" HRESULT CALLBACK expression(CIDebugClient *Client, PCSTR argsIn)
do {
const StringVector tokens = commandTokens<StringVector>(argsIn, &token);
if (token == 0) // partial message
return S_OK;
if (tokens.size() != 1) {
errorMessage = singleLineUsage(commandDescriptions[CmdExpression]);
@@ -1070,6 +1170,9 @@ extern "C" HRESULT CALLBACK stack(CIDebugClient *Client, PCSTR argsIn)
unsigned maxFrames = ExtensionContext::instance().parameters().maxStackDepth;
StringList tokens = commandTokens<StringList>(argsIn, &token);
if (token == 0) // partial message
return S_OK;
if (!tokens.empty() && tokens.front() == "-h") {
humanReadable = true;
tokens.pop_front();
@@ -1106,6 +1209,9 @@ extern "C" HRESULT CALLBACK qmlstack(CIDebugClient *client, PCSTR argsIn)
do {
StringList tokens = commandTokens<StringList>(argsIn, &token);
if (token == 0) // partial message
return S_OK;
if (!tokens.empty() && tokens.front() == "-h") {
humanReadable = true;
tokens.pop_front();
@@ -1170,6 +1276,9 @@ extern "C" HRESULT CALLBACK widgetat(CIDebugClient *client, PCSTR argsIn)
int y = -1;
const StringVector tokens = commandTokens<StringVector>(argsIn, &token);
if (token == 0) // partial message
return S_OK;
if (tokens.size() != 2) {
errorMessage = singleLineUsage(commandDescriptions[CmdWidgetAt]);
break;
@@ -1197,6 +1306,9 @@ extern "C" HRESULT CALLBACK breakpoints(CIDebugClient *client, PCSTR argsIn)
bool humanReadable = false;
unsigned verbose = 0;
StringList tokens = commandTokens<StringList>(argsIn, &token);
if (token == 0) // partial message
return S_OK;
while (!tokens.empty() && tokens.front().size() == 2 && tokens.front().at(0) == '-') {
switch (tokens.front().at(1)) {
case 'h':
@@ -1226,6 +1338,9 @@ extern "C" HRESULT CALLBACK test(CIDebugClient *client, PCSTR argsIn)
Mode mode = Invalid;
int token = 0;
StringList tokens = commandTokens<StringList>(argsIn, &token);
if (token == 0) // partial message
return S_OK;
// Parse away options
while (!tokens.empty() && tokens.front().size() == 2 && tokens.front().at(0) == '-') {
const char option = tokens.front().at(1);

View File

@@ -959,6 +959,13 @@ void CdbEngine::executeDebuggerCommand(const QString &command)
// Post command to the cdb process
void CdbEngine::runCommand(const DebuggerCommand &dbgCmd)
{
constexpr int maxCommandLength = 4096;
constexpr int maxTokenLength = 4 /*" -t "*/
+ 5 /* 99999 tokens should be enough for a single qc run time*/
+ 1 /* token part splitter '.' */
+ 3 /* 1000 parts should also be more than enough */
+ 1 /* final space */;
QString cmd = dbgCmd.function + dbgCmd.argsToString();
if (!m_accessible) {
doInterruptInferior([this, dbgCmd](){
@@ -970,11 +977,26 @@ void CdbEngine::runCommand(const DebuggerCommand &dbgCmd)
return;
}
if (dbgCmd.flags == ScriptCommand) {
// repack script command into an extension command
DebuggerCommand newCmd("script", ExtensionCommand, dbgCmd.callback);
if (!dbgCmd.args.isNull())
newCmd.args = QString{dbgCmd.function + '(' + dbgCmd.argsToPython() + ')'};
else
newCmd.args = dbgCmd.function;
runCommand(newCmd);
return;
}
QString fullCmd;
if (dbgCmd.flags == NoFlags) {
fullCmd = cmd;
fullCmd = cmd + '\n';
if (fullCmd.length() > maxCommandLength) {
showMessage("Command is longer than 4096 characters execution will likely fail.",
LogWarning);
}
} else {
const int token = m_nextCommandToken++;
const int token = ++m_nextCommandToken;
StringInputStream str(fullCmd);
if (dbgCmd.flags == BuiltinCommand) {
// Post a built-in-command producing free-format output with a callback.
@@ -982,23 +1004,35 @@ void CdbEngine::runCommand(const DebuggerCommand &dbgCmd)
// printing a specially formatted token to be identifiable in the output.
str << ".echo \"" << m_tokenPrefix << token << "<\"\n"
<< cmd << "\n"
<< ".echo \"" << m_tokenPrefix << token << ">\"";
<< ".echo \"" << m_tokenPrefix << token << ">\"" << '\n';
if (fullCmd.length() > maxCommandLength) {
showMessage("Command is longer than 4096 characters execution will likely fail.",
LogWarning);
}
} else if (dbgCmd.flags == ExtensionCommand) {
// Post an extension command producing one-line output with a callback,
// pass along token for identification in hash.
str << m_extensionCommandPrefix << dbgCmd.function << "%1%2";
if (dbgCmd.args.isString())
str << ' ' << dbgCmd.argsToString();
cmd = fullCmd.arg("", "");
fullCmd = fullCmd.arg(" -t ").arg(token);
} else if (dbgCmd.flags == ScriptCommand) {
// Add extension prefix and quotes the script command
// pass along token for identification in hash.
str << m_extensionCommandPrefix + "script %1%2 " << dbgCmd.function;
if (!dbgCmd.args.isNull())
str << '(' << dbgCmd.argsToPython() << ')';
cmd = fullCmd.arg("", "");
fullCmd = fullCmd.arg(" -t ").arg(token);
const QString prefix = m_extensionCommandPrefix + dbgCmd.function;
QList<QStringRef> splittedArguments;
if (dbgCmd.args.isString()) {
const QString &arguments = dbgCmd.argsToString();
cmd = prefix + arguments;
int argumentSplitPos = 0;
QList<QStringRef> splittedArguments;
int maxArgumentSize = maxCommandLength - prefix.length() - maxTokenLength;
while (argumentSplitPos < arguments.size()) {
splittedArguments << arguments.midRef(argumentSplitPos, maxArgumentSize);
argumentSplitPos += splittedArguments.last().length();
}
QTC_CHECK(argumentSplitPos == arguments.size());
int tokenPart = splittedArguments.size();
for (const QStringRef part : qAsConst(splittedArguments))
str << prefix << " -t " << token << '.' << --tokenPart << ' ' << part << '\n';
} else {
cmd = prefix;
str << prefix << " -t " << token << '.' << 0 << '\n';
}
}
m_commandForToken.insert(token, dbgCmd);
}
@@ -1011,7 +1045,7 @@ void CdbEngine::runCommand(const DebuggerCommand &dbgCmd)
qDebug("CdbEngine::postCommand: resulting command '%s'\n", qPrintable(fullCmd));
}
showMessage(cmd, LogInput);
m_process.write(fullCmd.toLocal8Bit() + '\n');
m_process.write(fullCmd.toLocal8Bit());
}
void CdbEngine::activateFrame(int index)

View File

@@ -42,6 +42,7 @@ public:
StringInputStream &operator<<(char a) { m_target.append(a); return *this; }
StringInputStream &operator<<(const char *a) { m_target.append(QString::fromUtf8(a)); return *this; }
StringInputStream &operator<<(const QString &a) { m_target.append(a); return *this; }
StringInputStream &operator<<(const QStringRef &a) { m_target.append(a); return *this; }
StringInputStream &operator<<(int i) { appendInt(i); return *this; }
StringInputStream &operator<<(unsigned i) { appendInt(i); return *this; }