From 9cfd1b5d6ccdd2c70dee3b80c74a7ff4ecc8bc5c Mon Sep 17 00:00:00 2001 From: David Schulz Date: Tue, 31 Mar 2020 14:09:32 +0200 Subject: [PATCH] Debugger: Split long cdbextension commands Fixes: QTCREATORBUG-22922 Change-Id: I5def321f0f97717728bc5cdcd5309b458a8ecfa1 Reviewed-by: Christian Stenger --- .../qtcreatorcdbext/qtcreatorcdbextension.cpp | 135 ++++++++++++++++-- src/plugins/debugger/cdb/cdbengine.cpp | 68 ++++++--- src/plugins/debugger/cdb/stringinputstream.h | 1 + 3 files changed, 177 insertions(+), 27 deletions(-) diff --git a/src/libs/qtcreatorcdbext/qtcreatorcdbextension.cpp b/src/libs/qtcreatorcdbext/qtcreatorcdbextension.cpp index 6bbd71367c0..bcadbbf2941 100644 --- a/src/libs/qtcreatorcdbext/qtcreatorcdbextension.cpp +++ b/src/libs/qtcreatorcdbext/qtcreatorcdbextension.cpp @@ -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 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 static inline StringContainer commandTokens(PCSTR args, int *token = 0) { - typedef typename StringContainer::iterator ContainerIterator; + static std::map 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()) { - if (token) - std::istringstream(*it) >> *token; - tokens.erase(tokens.begin(), ++it); + 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) + *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(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(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(args, token); + if (token == 0) // partial arguments + return {}; ExtensionContext &extCtx = ExtensionContext::instance(); DumpCommandParameters parameters; std::string iname; - StringList tokens = commandTokens(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(argsIn, &token); + if (token == 0) // partial arguments + return {}; std::stringstream command; - for (std::string arg : commandTokens(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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); diff --git a/src/plugins/debugger/cdb/cdbengine.cpp b/src/plugins/debugger/cdb/cdbengine.cpp index 45e3cb36416..963dd3ffab1 100644 --- a/src/plugins/debugger/cdb/cdbengine.cpp +++ b/src/plugins/debugger/cdb/cdbengine.cpp @@ -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 splittedArguments; + if (dbgCmd.args.isString()) { + const QString &arguments = dbgCmd.argsToString(); + cmd = prefix + arguments; + int argumentSplitPos = 0; + QList 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) diff --git a/src/plugins/debugger/cdb/stringinputstream.h b/src/plugins/debugger/cdb/stringinputstream.h index 61c78c7a0a3..21f4ccd14e4 100644 --- a/src/plugins/debugger/cdb/stringinputstream.h +++ b/src/plugins/debugger/cdb/stringinputstream.h @@ -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; }