/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt Creator. ** ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #include "eventcallback.h" #include "extensioncontext.h" #include "gdbmihelpers.h" #include "outputcallback.h" #include "stringutils.h" #include "symbolgroup.h" #include "symbolgroupvalue.h" #ifdef WITH_PYTHON #include #include "pystdoutredirect.h" #include "pycdbextmodule.h" #endif #include #include #include #include /*! \group qtcreatorcdbext \title Qt Creator CDB Extensions Qt Creator CDB extensions are loaded into CDB.exe (see cdbengine.cpp). They provide the following features: \list \li Notification about the state of the debugging session: \list \li idle: (hooked with .idle_cmd) debuggee stopped \li accessible: Debuggee stopped, cdb.exe accepts commands \li inaccessible: Debuggee runs, no way to post commands \li session active/inactive: Lost debuggee, terminating. \endlist \li Hook up with output/event callbacks and produce formatted output \li Provide some extension commands that produce output in a standardized (GDBMI) format that ends up in handleExtensionMessage(). \endlist */ // Data struct and helpers for formatting help struct CommandDescription { const char *name; const char *description; const char *usage; }; // Single line of usage: For reporting usage errors back as a single line static std::string singleLineUsage(const CommandDescription &d) { std::string rc = "Usage: "; const char *endOfLine = strchr(d.usage, '\n'); rc += endOfLine ? std::string(d.usage, endOfLine - d.usage) : std::string(d.usage); return rc; } // Format description of a command std::ostream &operator<<(std::ostream &str, const CommandDescription &d) { str << "Command '" << d.name << "': " << d.description << '\n'; if (d.usage[0]) str << "Usage: " << d.name << ' ' << d.usage << '\n'; str << '\n'; return str; } enum Command { CmdPid, CmdExpandlocals, CmdLocals, CmdWatches, CmdDumplocal, CmdTypecast, CmdAddsymbol, CmdAssign, CmdThreads, CmdRegisters, CmdModules, CmdIdle, CmdHelp, CmdMemory, CmdExpression, CmdStack, CmdQmlStack, CmdShutdownex, CmdAddWatch, CmdWidgetAt, CmdBreakPoints, CmdTest, CmdSetParameter, CmdScript }; static const CommandDescription commandDescriptions[] = { {"pid", "Prints inferior process id and hooks up output callbacks.", "[-t token]"}, {"expandlocals", "Expands local variables by iname in symbol group.", "[-t token] [-c] \n" "iname1-list: Comma-separated list of inames\n" "-c complex dumpers"}, {"locals", "Prints local variables of symbol group in GDBMI or debug format", "[-t token] [-v] [T formats] [-I formats] [-f debugfilter] [-a] [-c] [-h] [-d]\n[-e expand-list] [-u uninitialized-list]\n" "[-W] [-w watch-iname watch-expression] [iname]\n" "-h human-readable output\n" "-v increase verboseness of dumping\n" "-d debug output\n" "-f debug_filter\n" "-c complex dumpers\n" "-a sort alphabetically\n" "-e expand-list Comma-separated list of inames to be expanded beforehand\n" "-u uninitialized-list Comma-separated list of uninitialized inames\n" "-I formatmap map of 'hex-encoded-iname=typecode'\n" "-T formatmap map of 'hex-encoded-type-name=typecode'\n" "-D Discard existing symbol group\n" "-W Synchronize watch items (-w)\n" "-w iname expression Watch item"}, {"watches", "Prints watches variables of symbol group in GDBMI or debug format", "[-t token] [-v] [T formats] [-I formats] [-f debugfilter] [-c] [-h] [-d] \n" "-h human-readable output\n" "-v increase verboseness of dumping\n" "-d debug output\n" "-f debug_filter\n" "-c complex dumpers\n" "-I formatmap map of 'hex-encoded-iname=typecode'\n" "-T formatmap map of 'hex-encoded-type-name=typecode'"}, {"dumplocal", "Dumps local variable using simple dumpers (testing command).", "[-t token] "}, {"typecast","Performs a type cast on an unexpanded iname of symbol group.", "[-t token] "}, {"addsymbol","Adds a symbol to symbol group (testing command).", "[-t token] [optional-iname]"}, {"assign","Assigns a value to a variable in current symbol group.", "[-t token] [-h] \n" "-h Data are hex-encoded, binary data\n" "-u Data are hex-encoded, UTF16 data" }, {"threads","Lists threads in GDBMI format.","[-t token]"}, {"registers","Lists registers in GDBMI format","[-t token]"}, {"modules","Lists modules in GDBMI format.","[-t token]"}, {"idle", "Reports stop reason in GDBMI format.\n" "Intended to be used with .idle_cmd to obtain proper stop notification.",""}, {"help","Prints help.",""}, {"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 maxArraySize=value maxStackDepth=value stateNotification=1,0"}, {"script", "Run Python command", "[-t token]"} }; typedef std::vector StringVector; typedef std::list StringList; static inline bool isOption(const std::string &opt) { return opt.size() == 2 && opt.at(0) == '-' && opt != "--"; } // Helper for commandTokens() below: // Simple splitting of command lines allowing for '"'-quoted tokens // 'typecast local.i "class QString *"' -> ('typecast','local.i','class QString *') template static inline void splitCommand(PCSTR args, Inserter it) { enum State { WhiteSpace, WithinToken, WithinQuoted }; State state = WhiteSpace; std::string current; for (PCSTR p = args; *p; p++) { char c = *p; switch (state) { case WhiteSpace: switch (c) { case ' ': break; case '"': state = WithinQuoted; current.clear(); break; default: state = WithinToken; current.clear(); current.push_back(c); break; } break; case WithinToken: switch (c) { case ' ': state = WhiteSpace; *it = current; ++it; break; default: current.push_back(c); break; } break; case WithinQuoted: switch (c) { case '"': state = WhiteSpace; *it = current; ++it; break; default: current.push_back(c); break; } break; } } if (state == WithinToken) { *it = current; ++it; } } // 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 // so that the calling CDB engine in Qt Creator can match the callback. template static inline StringContainer commandTokens(PCSTR args, int *token = 0) { typedef typename StringContainer::iterator ContainerIterator; 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); } return tokens; } // Extension command 'pid': // Report back PID so that Qt Creator engine knows whom to DebugBreakProcess. extern "C" HRESULT CALLBACK pid(CIDebugClient *client, PCSTR args) { ExtensionContext::instance().hookCallbacks(client); int token; commandTokens(args, &token); dprintf("Qt Creator CDB extension version 4.3 %d bit.\n", sizeof(void *) * 8); if (const ULONG pid = currentProcessId(client)) ExtensionContext::instance().report('R', token, 0, "pid", "%u", pid); else ExtensionContext::instance().report('N', token, 0, "pid", "0"); return S_OK; } // Extension command 'expandlocals': // Expand a comma-separated iname-list of local variables. extern "C" HRESULT CALLBACK expandlocals(CIDebugClient *client, PCSTR args) { ExtensionCommandContext exc(client); unsigned frame = 0; SymbolGroup *symGroup = 0; std::string errorMessage; int token; StringList tokens = commandTokens(args, &token); StringVector inames; bool runComplexDumpers = false; do { if (!tokens.empty() && tokens.front() == "-c") { runComplexDumpers = true; tokens.pop_front(); } if (tokens.empty() || !integerFromString(tokens.front(), &frame)) { errorMessage = singleLineUsage(commandDescriptions[CmdExpandlocals]); break; } tokens.pop_front(); if (tokens.empty()) { errorMessage = singleLineUsage(commandDescriptions[CmdExpandlocals]); break; } split(tokens.front(), ',', std::back_inserter(inames)); } while (false); if (errorMessage.empty()) symGroup = ExtensionContext::instance().symbolGroup(exc.symbols(), exc.threadId(), frame, &errorMessage); if (!symGroup) { ExtensionContext::instance().report('N', token, 0, "expandlocals", errorMessage.c_str()); return S_OK; } const unsigned succeeded = runComplexDumpers ? symGroup->expandListRunComplexDumpers(inames, SymbolGroupValueContext(exc.dataSpaces(), exc.symbols()), &errorMessage) : symGroup->expandList(inames, &errorMessage); ExtensionContext::instance().report('R', token, 0, "expandlocals", "%u/%u %s", succeeded, unsigned(inames.size()), errorMessage.c_str()); return S_OK; } // Parameters to be shared between watch/locals dump commands struct DumpCommandParameters { DumpCommandParameters() : debugOutput(0), verbose(0) {} // Check option off front and remove if parsed enum ParseOptionResult { Ok, Error, OtherOption, EndOfOptions }; ParseOptionResult parseOption(StringList *options); unsigned debugOutput; std::string debugFilter; DumpParameters dumpParameters; unsigned verbose; }; DumpCommandParameters::ParseOptionResult DumpCommandParameters::parseOption(StringList *options) { // Parse all options and erase valid ones from the list if (options->empty()) return DumpCommandParameters::EndOfOptions; const std::string &opt = options->front(); if (!isOption(opt)) return DumpCommandParameters::EndOfOptions; const char option = opt.at(1); bool knownOption = true; switch (option) { case 'd': debugOutput++; break; case 'h': dumpParameters.dumpFlags |= DumpParameters::DumpHumanReadable; break; case 'c': dumpParameters.dumpFlags |= DumpParameters::DumpComplexDumpers; break; case 'a': dumpParameters.dumpFlags |= DumpParameters::DumpAlphabeticallySorted; break; case 'f': if (options->size() < 2) return Error; options->pop_front(); debugFilter = options->front(); break; case 'v': verbose++; break; case 'T': // typeformats: 'hex'ed name = formatnumber,...' if (options->size() < 2) return Error; options->pop_front(); if (options->front().empty()) return Error; dumpParameters.typeFormats = DumpParameters::decodeFormatArgument(options->front(), true); break; case 'I': // individual formats: iname= formatnumber,...' if (options->size() < 2) return Error; options->pop_front(); dumpParameters.individualFormats = DumpParameters::decodeFormatArgument(options->front(), false); break; default: knownOption = false; break; } // case option if (knownOption) options->pop_front(); return knownOption ? Ok : OtherOption; } // Extension command 'locals': // Display local variables of symbol group in GDBMI or debug output form. // Takes an optional iname which is expanded before displaying (for updateWatchData) static std::string commandLocals(ExtensionCommandContext &commandExtCtx,PCSTR args, int *token, std::string *errorMessage) { typedef WatchesSymbolGroup::InameExpressionMap InameExpressionMap; typedef InameExpressionMap::value_type InameExpressionMapEntry; // Parse the command ExtensionContext &extCtx = ExtensionContext::instance(); DumpCommandParameters parameters; std::string iname; StringList tokens = commandTokens(args, token); StringVector expandedInames; StringVector uninitializedInames; InameExpressionMap watcherInameExpressionMap; bool watchSynchronization = false; bool discardSymbolGroup = false; // Parse away options for (bool optionLeft = true; optionLeft && !tokens.empty(); ) { switch (parameters.parseOption(&tokens)) { case DumpCommandParameters::Ok: break; case DumpCommandParameters::Error: *errorMessage = singleLineUsage(commandDescriptions[CmdLocals]); return std::string(); case DumpCommandParameters::OtherOption: { const char option = tokens.front().at(1); tokens.pop_front(); switch (option) { case 'u': if (tokens.empty()) { *errorMessage = singleLineUsage(commandDescriptions[CmdLocals]); return std::string(); } split(tokens.front(), ',', std::back_inserter(uninitializedInames)); tokens.pop_front(); break; case 'e': if (tokens.empty()) { *errorMessage = singleLineUsage(commandDescriptions[CmdLocals]); return std::string(); } split(tokens.front(), ',', std::back_inserter(expandedInames)); tokens.pop_front(); break; case 'w': { // Watcher iname exp if (tokens.size() < 2) { *errorMessage = singleLineUsage(commandDescriptions[CmdLocals]); return std::string(); } const std::string iname = tokens.front(); tokens.pop_front(); const std::string expression = tokens.front(); tokens.pop_front(); watcherInameExpressionMap.insert(InameExpressionMapEntry(iname, expression)); } break; case 'W': watchSynchronization = true; break; case 'D': discardSymbolGroup = true; break; } // case option } break; case DumpCommandParameters::EndOfOptions: optionLeft = false; break; } } // Frame and iname unsigned frame; if (tokens.empty() || !integerFromString(tokens.front(), &frame)) { *errorMessage = singleLineUsage(commandDescriptions[CmdLocals]); return std::string(); } tokens.pop_front(); if (!tokens.empty()) iname = tokens.front(); const SymbolGroupValueContext dumpContext(commandExtCtx.dataSpaces(), commandExtCtx.symbols()); if (discardSymbolGroup) extCtx.discardSymbolGroup(); SymbolGroup * const symGroup = extCtx.symbolGroup(commandExtCtx.symbols(), commandExtCtx.threadId(), frame, errorMessage); if (!symGroup) return std::string(); if (!uninitializedInames.empty()) symGroup->markUninitialized(uninitializedInames); SymbolGroupValue::verbose = parameters.verbose; if (SymbolGroupValue::verbose) DebugPrint() << parameters.dumpParameters; // Synchronize watches if desired. WatchesSymbolGroup *watchesSymbolGroup = extCtx.watchesSymbolGroup(); if (watchSynchronization) { watchesSymbolGroup = 0; extCtx.discardWatchesSymbolGroup(); } if (watchesSymbolGroup == 0 && (!watcherInameExpressionMap.empty() || WatchesSymbolGroup::isWatchIname(iname))) { // Force group into existence watchesSymbolGroup = extCtx.watchesSymbolGroup(commandExtCtx.symbols(), errorMessage); if (!watchesSymbolGroup || !watchesSymbolGroup->synchronize(commandExtCtx.symbols(), watcherInameExpressionMap, errorMessage)) { return std::string(); } } // Pre-expand. if (!expandedInames.empty()) { if (parameters.dumpParameters.dumpFlags & DumpParameters::DumpComplexDumpers) { symGroup->expandListRunComplexDumpers(expandedInames, dumpContext, errorMessage); if (watchesSymbolGroup) watchesSymbolGroup->expandListRunComplexDumpers(expandedInames, dumpContext, errorMessage); } else { symGroup->expandList(expandedInames, errorMessage); if (watchesSymbolGroup) watchesSymbolGroup->expandList(expandedInames, errorMessage); } } // Debug output: Join the 2 symbol groups if no iname is given. if (parameters.debugOutput) { std::string debugRc = symGroup->debug(iname, parameters.debugFilter, parameters.debugOutput - 1); if (iname.empty() && watchesSymbolGroup) { debugRc.push_back('\n'); debugRc += watchesSymbolGroup->debug(iname, parameters.debugFilter, parameters.debugOutput - 1); } return debugRc; } // Dump all: Join the 2 symbol groups '[local.x][watch.0]'->'[local.x,watch.0]' if (iname.empty()) { std::string dumpRc = symGroup->dump(dumpContext, parameters.dumpParameters); if (!dumpRc.empty() && watchesSymbolGroup) { std::string watchesDump = watchesSymbolGroup->dump(dumpContext, parameters.dumpParameters); if (!watchesDump.empty()) { dumpRc.erase(dumpRc.size() - 1, 1); // Strip ']' watchesDump[0] = ','; // '[' -> '.' dumpRc += watchesDump; } } return dumpRc; } if (WatchesSymbolGroup::isWatchIname(iname)) return watchesSymbolGroup->dump(iname, dumpContext, parameters.dumpParameters, errorMessage); else return symGroup->dump(iname, dumpContext, parameters.dumpParameters, errorMessage); } extern "C" HRESULT CALLBACK script(CIDebugClient *client, PCSTR argsIn) { ExtensionCommandContext exc(client); int token; #ifdef WITH_PYTHON std::stringstream command; for (std::string arg : commandTokens(argsIn, &token)) command << arg << ' '; PyObject *ptype = NULL; PyObject *pvalue = NULL; PyObject *ptraceback = NULL; PyErr_Fetch(&ptype, &pvalue, &ptraceback); startCapturePyStdout(); const char result = (PyRun_SimpleString(command.str().c_str()) == 0) ? 'R' : 'N'; if (PyErr_Occurred()) PyErr_Print(); ExtensionContext::instance().reportLong(result, token, "script", collectOutput().c_str()); endCapturePyStdout(); PyErr_Restore(ptype, pvalue, ptraceback); #else commandTokens(argsIn, &token); 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 " "pointing to a Python 3.5 installation."); #endif return S_OK; } extern "C" HRESULT CALLBACK locals(CIDebugClient *client, PCSTR args) { ExtensionCommandContext exc(client); std::string errorMessage; int token; const std::string output = commandLocals(exc, args, &token, &errorMessage); SymbolGroupValue::verbose = 0; if (output.empty()) ExtensionContext::instance().report('N', token, 0, "locals", errorMessage.c_str()); else ExtensionContext::instance().reportLong('R', token, "locals", output); return S_OK; } // Extension command 'locals': // Display local variables of symbol group in GDBMI or debug output form. // Takes an optional iname which is expanded before displaying (for updateWatchData) static std::string commmandWatches(ExtensionCommandContext &exc, PCSTR args, int *token, std::string *errorMessage) { // Parse the command DumpCommandParameters parameters; StringList tokens = commandTokens(args, token); // Parse away options for (bool optionLeft = true; optionLeft && !tokens.empty(); ) { switch (parameters.parseOption(&tokens)) { case DumpCommandParameters::Ok: break; case DumpCommandParameters::Error: case DumpCommandParameters::OtherOption: *errorMessage = singleLineUsage(commandDescriptions[CmdWatches]); return std::string(); case DumpCommandParameters::EndOfOptions: optionLeft = false; break; } } const std::string iname = tokens.empty() ? std::string() : tokens.front(); if (iname.empty() && !parameters.debugOutput) { *errorMessage = singleLineUsage(commandDescriptions[CmdWatches]); return std::string(); } const SymbolGroupValueContext dumpContext(exc.dataSpaces(), exc.symbols()); SymbolGroup * const symGroup = ExtensionContext::instance().watchesSymbolGroup(exc.symbols(), errorMessage); if (!symGroup) return std::string(); SymbolGroupValue::verbose = parameters.verbose; if (parameters.debugOutput) return symGroup->debug(iname, parameters.debugFilter, parameters.debugOutput - 1); return symGroup->dump(iname, dumpContext, parameters.dumpParameters, errorMessage); } extern "C" HRESULT CALLBACK watches(CIDebugClient *client, PCSTR args) { ExtensionCommandContext exc(client); std::string errorMessage = "e"; int token = 0; const std::string output = commmandWatches(exc, args, &token, &errorMessage); SymbolGroupValue::verbose = 0; if (output.empty()) ExtensionContext::instance().report('N', token, 0, "locals", errorMessage.c_str()); else ExtensionContext::instance().reportLong('R', token, "locals", output); return S_OK; } // Extension command 'dumplocal': // Dump a local variable using dumpers (testing command). static std::string dumplocalHelper(ExtensionCommandContext &exc,PCSTR args, int *token, std::string *errorMessage) { // Parse the command StringList tokens = commandTokens(args, token); // Frame and iname unsigned frame; if (tokens.empty() || integerFromString(tokens.front(), &frame)) { *errorMessage = singleLineUsage(commandDescriptions[CmdDumplocal]); return std::string(); } tokens.pop_front(); if (tokens.empty()) { *errorMessage = singleLineUsage(commandDescriptions[CmdDumplocal]); return std::string(); } const std::string iname = tokens.front(); SymbolGroup * const symGroup = ExtensionContext::instance().symbolGroup(exc.symbols(), exc.threadId(), frame, errorMessage); if (!symGroup) return std::string(); AbstractSymbolGroupNode *n = symGroup->find(iname); if (!n || !n->asSymbolGroupNode()) { *errorMessage = "No such iname " + iname; return std::string(); } std::wstring value; if (!dumpSimpleType(n->asSymbolGroupNode(), SymbolGroupValueContext(exc.dataSpaces(), exc.symbols()), &value, &std::string())) { *errorMessage = "Cannot dump " + iname; return std::string(); } return wStringToString(value); } extern "C" HRESULT CALLBACK dumplocal(CIDebugClient *client, PCSTR argsIn) { ExtensionCommandContext exc(client); std::string errorMessage; int token = 0; const std::string value = dumplocalHelper(exc,argsIn, &token, &errorMessage); if (value.empty()) ExtensionContext::instance().report('N', token, 0, "dumplocal", errorMessage.c_str()); else ExtensionContext::instance().reportLong('R', token, "dumplocal", value); return S_OK; } // Extension command 'typecast': // Change the type of a symbol group entry (testing purposes) extern "C" HRESULT CALLBACK typecast(CIDebugClient *client, PCSTR args) { ExtensionCommandContext exc(client); unsigned frame = 0; SymbolGroup *symGroup = 0; std::string errorMessage; int token; const StringVector tokens = commandTokens(args, &token); std::string iname; std::string desiredType; if (tokens.size() == 3u && integerFromString(tokens.front(), &frame)) { symGroup = ExtensionContext::instance().symbolGroup(exc.symbols(), exc.threadId(), frame, &errorMessage); iname = tokens.at(1); desiredType = tokens.at(2); } else { errorMessage = singleLineUsage(commandDescriptions[CmdTypecast]); } if (symGroup != 0 && symGroup->typeCast(iname, desiredType, &errorMessage)) ExtensionContext::instance().report('R', token, 0, "typecast", "OK"); else ExtensionContext::instance().report('N', token, 0, "typecast", errorMessage.c_str()); return S_OK; } // Extension command 'addsymbol': // Adds a symbol to a symbol group by name (testing purposes) extern "C" HRESULT CALLBACK addsymbol(CIDebugClient *client, PCSTR args) { ExtensionCommandContext exc(client); unsigned frame = 0; SymbolGroup *symGroup = 0; std::string errorMessage; int token; const StringVector tokens = commandTokens(args, &token); std::string name; std::string iname; if (tokens.size() >= 2u && integerFromString(tokens.front(), &frame)) { symGroup = ExtensionContext::instance().symbolGroup(exc.symbols(), exc.threadId(), frame, &errorMessage); name = tokens.at(1); if (tokens.size() >= 3) iname = tokens.at(2); } else { errorMessage = singleLineUsage(commandDescriptions[CmdAddsymbol]); } if (symGroup != 0 && symGroup->addSymbol(std::string(), name, iname, &errorMessage)) ExtensionContext::instance().report('R', token, 0, "addsymbol", "OK"); else ExtensionContext::instance().report('N', token, 0, "addsymbol", errorMessage.c_str()); return S_OK; } extern "C" HRESULT CALLBACK addwatch(CIDebugClient *client, PCSTR argsIn) { ExtensionCommandContext exc(client); std::string errorMessage; std::string watchExpression; std::string iname; int token = 0; bool success = false; do { StringList tokens = commandTokens(argsIn, &token); if (tokens.size() != 2) { errorMessage = singleLineUsage(commandDescriptions[CmdAddWatch]); break; } iname = tokens.front(); tokens.pop_front(); watchExpression = tokens.front(); WatchesSymbolGroup *watchesSymGroup = ExtensionContext::instance().watchesSymbolGroup(exc.symbols(), &errorMessage); if (!watchesSymGroup) break; success = watchesSymGroup->addWatch(exc.symbols(), iname, watchExpression, &errorMessage); } while (false); if (success) ExtensionContext::instance().report('R', token, 0, "addwatch", "Ok"); else ExtensionContext::instance().report('N', token, 0, "addwatch", errorMessage.c_str()); return S_OK; } // Extension command 'assign': // Assign locals by iname: 'assign locals.x=5' extern "C" HRESULT CALLBACK assign(CIDebugClient *client, PCSTR argsIn) { ExtensionCommandContext exc(client); std::string errorMessage; bool success = false; AssignEncoding enc = AssignPlainValue; int token = 0; do { StringList tokens = commandTokens(argsIn, &token); if (tokens.empty()) { errorMessage = singleLineUsage(commandDescriptions[CmdAssign]); break; } if (tokens.front() == "-h") { enc = AssignHexEncoded; tokens.pop_front(); } else if (tokens.front() == "-u") { enc = AssignHexEncodedUtf16; tokens.pop_front(); } if (tokens.empty()) { errorMessage = singleLineUsage(commandDescriptions[CmdAssign]); break; } // Parse 'assign locals.x=5' const std::string::size_type equalsPos = tokens.front().find('='); if (equalsPos == std::string::npos) { errorMessage = singleLineUsage(commandDescriptions[CmdAssign]); break; } const std::string iname = tokens.front().substr(0, equalsPos); const std::string value = tokens.front().substr(equalsPos + 1, tokens.front().size() - equalsPos - 1); // get the symbolgroup int currentFrame = ExtensionContext::instance().symbolGroupFrame(); if (currentFrame < 0) { CIDebugControl *control = ExtensionCommandContext::instance()->control(); DEBUG_STACK_FRAME frame; if (FAILED(control->GetStackTrace(0, 0, 0, &frame, 1, NULL))) { errorMessage = "No current frame."; break; } currentFrame = frame.FrameNumber; } SymbolGroup *symGroup = ExtensionContext::instance().symbolGroup(exc.symbols(), exc.threadId(), currentFrame, &errorMessage); if (!symGroup) break; success = symGroup->assign(iname, enc, value, SymbolGroupValueContext(exc.dataSpaces(), exc.symbols()), &errorMessage); } while (false); if (success) ExtensionContext::instance().report('R', token, 0, "assign", "Ok"); else ExtensionContext::instance().report('N', token, 0, "assign", errorMessage.c_str()); return S_OK; } // Extension command 'threads': // List all thread info in GDBMI syntax extern "C" HRESULT CALLBACK threads(CIDebugClient *client, PCSTR argsIn) { ExtensionCommandContext exc(client); std::string errorMessage; int token; commandTokens(argsIn, &token); const std::string gdbmi = gdbmiThreadList(exc.systemObjects(), exc.symbols(), exc.control(), exc.advanced(), &errorMessage); if (gdbmi.empty()) ExtensionContext::instance().report('N', token, 0, "threads", errorMessage.c_str()); else ExtensionContext::instance().reportLong('R', token, "threads", gdbmi); return S_OK; } // Extension command 'registers': // List all registers in GDBMI syntax extern "C" HRESULT CALLBACK registers(CIDebugClient *Client, PCSTR argsIn) { ExtensionCommandContext exc(Client); std::string errorMessage; int token; const StringList tokens = commandTokens(argsIn, &token); const bool humanReadable = !tokens.empty() && tokens.front() == "-h"; const std::string regs = gdbmiRegisters(exc.registers(), exc.control(), humanReadable, IncludePseudoRegisters, &errorMessage); if (regs.empty()) ExtensionContext::instance().report('N', token, 0, "registers", errorMessage.c_str()); else ExtensionContext::instance().reportLong('R', token, "registers", regs); return S_OK; } // Extension command 'modules': // List all modules in GDBMI syntax extern "C" HRESULT CALLBACK modules(CIDebugClient *Client, PCSTR argsIn) { ExtensionCommandContext exc(Client); std::string errorMessage; int token; const StringList tokens = commandTokens(argsIn, &token); const bool humanReadable = !tokens.empty() && tokens.front() == "-h"; const std::string modules = gdbmiModules(exc.symbols(), humanReadable, &errorMessage); if (modules.empty()) ExtensionContext::instance().report('N', token, 0, "modules", errorMessage.c_str()); else ExtensionContext::instance().reportLong('R', token, "modules", modules); return S_OK; } // Report stop of debuggee to Creator. This is hooked up as .idle_cmd command // by the Creator engine to reliably get notified about stops. extern "C" HRESULT CALLBACK idle(CIDebugClient *client, PCSTR) { ExtensionContext::instance().notifyIdleCommand(client); return S_OK; } // Extension command 'setparameter': // Parse a list of parameters: 'key=value' extern "C" HRESULT CALLBACK setparameter(CIDebugClient *, PCSTR args) { int token; StringVector tokens = commandTokens(args, &token); const size_t count = tokens.size(); size_t success = 0; for (size_t i = 0; i < count; ++i) { const std::string &token = tokens.at(i); const std::string::size_type equalsPos = token.find('='); if (equalsPos != std::string::npos) { const std::string value = token.substr(equalsPos + 1, token.size() - 1 - equalsPos); if (!token.compare(0, equalsPos, "maxStringLength")) { if (integerFromString(value, &ExtensionContext::instance().parameters().maxStringLength)) ++success; } else if (!token.compare(0, equalsPos, "maxArraySize")) { if (integerFromString(value, &ExtensionContext::instance().parameters().maxArraySize)) ++success; } 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; } } } } if (success != count) DebugPrint() << "Errors parsing setparameters command '" << args << '\''; return S_OK; } // Extension command 'help': // Display version extern "C" HRESULT CALLBACK help(CIDebugClient *, PCSTR) { std::ostringstream str; str << "### Qt Creator CDB extension" << "\n\n"; const size_t commandCount = sizeof(commandDescriptions)/sizeof(CommandDescription); std::copy(commandDescriptions, commandDescriptions + commandCount, std::ostream_iterator(str)); dprintf("%s\n", str.str().c_str()); return S_OK; } // Extension command 'memory': // Display memory as hex extern "C" HRESULT CALLBACK memory(CIDebugClient *Client, PCSTR argsIn) { ExtensionCommandContext exc(Client); std::string errorMessage; std::string memory; int token; ULONG64 address = 0; ULONG length = 0; const StringVector tokens = commandTokens(argsIn, &token); if (tokens.size() == 2 && integerFromString(tokens.front(), &address) && integerFromString(tokens.at(1), &length)) { memory = memoryToHex(exc.dataSpaces(), address, length, &errorMessage); } else { errorMessage = singleLineUsage(commandDescriptions[CmdMemory]); } if (memory.empty()) { ExtensionContext::instance().report('N', token, 0, "memory", errorMessage.c_str()); } else { ExtensionContext::instance().reportLong('R', token, "memory", memory); if (!errorMessage.empty()) ExtensionContext::instance().report('W', token, 0, "memory", errorMessage.c_str()); } return S_OK; } extern "C" HRESULT CALLBACK expression(CIDebugClient *Client, PCSTR argsIn) { ExtensionCommandContext exc(Client); std::string errorMessage; LONG64 value = 0; int token = 0; do { const StringVector tokens = commandTokens(argsIn, &token); if (tokens.size() != 1) { errorMessage = singleLineUsage(commandDescriptions[CmdExpression]); break; } if (!evaluateInt64Expression(exc.control(), tokens.front(), &value, &errorMessage)) break; } while (false); if (errorMessage.empty()) ExtensionContext::instance().reportLong('R', token, "expression", toString(value)); else ExtensionContext::instance().report('N', token, 0, "expression", errorMessage.c_str()); return S_OK; } // Extension command 'stack' // Report stack correctly as 'k' does not list instruction pointer // correctly. extern "C" HRESULT CALLBACK stack(CIDebugClient *Client, PCSTR argsIn) { ExtensionCommandContext exc(Client); std::string errorMessage; int token; bool humanReadable = false; unsigned maxFrames = ExtensionContext::instance().parameters().maxStackDepth; StringList tokens = commandTokens(argsIn, &token); if (!tokens.empty() && tokens.front() == "-h") { humanReadable = true; tokens.pop_front(); } if (!tokens.empty()) { if (tokens.front() == "unlimited") maxFrames = 1000; else integerFromString(tokens.front(), &maxFrames); } const std::string stack = gdbmiStack(exc.control(), exc.symbols(), maxFrames, humanReadable, &errorMessage); if (stack.empty()) ExtensionContext::instance().report('N', token, 0, "stack", errorMessage.c_str()); else ExtensionContext::instance().reportLong('R', token, "stack", stack); 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 jsExecutionEngine = 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(), &jsExecutionEngine)) { errorMessage = "Invalid address " + tokens.front(); break; } tokens.pop_front(); } ExtensionCommandContext exc(client); if (!jsExecutionEngine) { // Try to find execution engine unless it was given. jsExecutionEngine = ExtensionContext::instance().jsExecutionEngine(exc, &errorMessage); if (!jsExecutionEngine) 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 << jsExecutionEngine << 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. extern "C" HRESULT CALLBACK shutdownex(CIDebugClient *, PCSTR) { ExtensionContext::instance().unhookCallbacks(); return S_OK; } extern "C" HRESULT CALLBACK widgetat(CIDebugClient *client, PCSTR argsIn) { ExtensionCommandContext exc(client); int token = 0; std::string widgetAddress; std::string errorMessage; do { int x = -1; int y = -1; const StringVector tokens = commandTokens(argsIn, &token); if (tokens.size() != 2) { errorMessage = singleLineUsage(commandDescriptions[CmdWidgetAt]); break; } if (!integerFromString(tokens.front(), &x) || !integerFromString(tokens.at(1), &y)) { errorMessage = singleLineUsage(commandDescriptions[CmdWidgetAt]); break; } widgetAddress = widgetAt(SymbolGroupValueContext(exc.dataSpaces(), exc.symbols()), x, y, &errorMessage); } while (false); if (widgetAddress.empty()) ExtensionContext::instance().report('N', token, 0, "widgetat", errorMessage.c_str()); else ExtensionContext::instance().reportLong('R', token, "widgetat", widgetAddress); return S_OK; } extern "C" HRESULT CALLBACK breakpoints(CIDebugClient *client, PCSTR argsIn) { ExtensionCommandContext exc(client); int token; std::string errorMessage; bool humanReadable = false; unsigned verbose = 0; StringList tokens = commandTokens(argsIn, &token); while (!tokens.empty() && tokens.front().size() == 2 && tokens.front().at(0) == '-') { switch (tokens.front().at(1)) { case 'h': humanReadable = true; break; case 'v': verbose++; break; } tokens.pop_front(); } const std::string bp = gdbmiBreakpoints(exc.control(), exc.symbols(), exc.dataSpaces(), humanReadable, verbose, &errorMessage); if (bp.empty()) ExtensionContext::instance().report('N', token, 0, "breakpoints", errorMessage.c_str()); else ExtensionContext::instance().reportLong('R', token, "breakpoints", bp); return S_OK; } extern "C" HRESULT CALLBACK test(CIDebugClient *client, PCSTR argsIn) { enum Mode { Invalid, TestType, TestFixWatchExpression }; ExtensionCommandContext exc(client); std::string testType; Mode mode = Invalid; int token = 0; StringList tokens = commandTokens(argsIn, &token); // Parse away options while (!tokens.empty() && tokens.front().size() == 2 && tokens.front().at(0) == '-') { const char option = tokens.front().at(1); tokens.pop_front(); switch (option) { case 'T': mode = TestType; if (!tokens.empty()) { testType = tokens.front(); tokens.pop_front(); } break; case 'w': mode = TestFixWatchExpression; if (!tokens.empty()) { testType = tokens.front(); tokens.pop_front(); } break; } // case option } // for options // Frame and iname if (mode == Invalid || testType.empty()) { ExtensionContext::instance().report('N', token, 0, "test", singleLineUsage(commandDescriptions[CmdTest]).c_str()); } else { std::ostringstream str; switch (mode) { case Invalid: break; case TestType: { const KnownType kt = knownType(testType, 0); const std::string fixed = SymbolGroupValue::stripConst(testType); const unsigned size = SymbolGroupValue::sizeOf(fixed.c_str()); str << '"' << testType << "\" (" << fixed << ") " << kt << " ["; formatKnownTypeFlags(str, kt); str << "] size=" << size; } break; case TestFixWatchExpression: str << testType << " -> " << WatchesSymbolGroup::fixWatchExpression(exc.symbols(), testType); break; } ExtensionContext::instance().reportLong('R', token, "test", str.str()); } return S_OK; } // Hook for dumping Known Structs. // Shows up in 'dv' (appended) as well as IDebugSymbolGroup::GetValueText. extern "C" HRESULT CALLBACK KnownStructOutput(ULONG Flag, ULONG64 Address, PSTR StructName, PSTR Buffer, PULONG BufferSize) { static const char knownTypesC[] = "HWND__\0"; // implicitly adds terminating 0 if (Flag == DEBUG_KNOWN_STRUCT_GET_NAMES) { memcpy(Buffer, knownTypesC, sizeof(knownTypesC)); return S_OK; } // Usually 260 chars buf if (!strcmp(StructName, "HWND__")) { // Dump a HWND. This is usually passed around as 'HWND*' with an (opaque) pointer value. // CDB dereferences it and passes it on here, which is why we get it as address. RECT rectangle; enum { BufSize = 1000 }; char buf[BufSize]; std::ostringstream str; HWND hwnd; memset(&rectangle, 0, sizeof(RECT)); memset(&hwnd, 0, sizeof(HWND)); // Coerce the address into a HWND (little endian) memcpy(&hwnd, &Address, SymbolGroupValue::pointerSize()); // Dump geometry and class. if (GetWindowRect(hwnd, &rectangle) && GetClassNameA(hwnd, buf, BufSize)) str << ' ' << rectangle.left << '+' << rectangle.top << '+' << (rectangle.right - rectangle.left) << 'x' << (rectangle.bottom - rectangle.top) << " '" << buf << '\''; // Check size for string + 1; const std::string rc = str.str(); const ULONG requiredBufferLength = static_cast(rc.size()) + 1; if (requiredBufferLength >= *BufferSize) { *BufferSize = requiredBufferLength; return S_FALSE; } strcpy(Buffer, rc.c_str()); } return S_OK; }