diff --git a/src/libs/qtcreatorcdbext/qtcreatorcdbext.def b/src/libs/qtcreatorcdbext/qtcreatorcdbext.def index 2f84541610a3a67142421dedf1ad11c66d18eb80..6bd14850ed66017efdb447ca395eb43e03d8b5ba 100644 --- a/src/libs/qtcreatorcdbext/qtcreatorcdbext.def +++ b/src/libs/qtcreatorcdbext/qtcreatorcdbext.def @@ -6,6 +6,8 @@ pid expandlocals locals dumplocal +typecast +addsymbol assign threads registers diff --git a/src/libs/qtcreatorcdbext/qtcreatorcdbextension.cpp b/src/libs/qtcreatorcdbext/qtcreatorcdbextension.cpp index 32ed01315b1e5765f90aa59fc8834ae2e37387d2..eea8a86e047532ced34ab38000c88083fba35b53 100644 --- a/src/libs/qtcreatorcdbext/qtcreatorcdbextension.cpp +++ b/src/libs/qtcreatorcdbext/qtcreatorcdbextension.cpp @@ -49,14 +49,149 @@ * - Hook up with output/event callbacks and produce formatted output * - Provide some extension commands that produce output in a standardized (GDBMI) * format that ends up in handleExtensionMessage(). - * + pid Return debuggee pid for interrupting. - * + locals Print locals from SymbolGroup - * + expandLocals Expand locals in symbol group - * + registers, modules, threads */ + */ + +// 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, + CmdDumplocal, + CmdTypecast, + CmdAddsymbol, + CmdAssign, + CmdThreads, + CmdRegisters, + CmdModules, + CmdIdle, + CmdHelp, + CmdMemory, + CmdStack, + CmdShutdownex +}; + +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] <frame-number> <iname1-list>\n" + "iname1-list: Comma-separated list of inames"}, +{"locals", + "Prints local variables of symbol group in GDBMI or debug format", + "[-t token] [-h] [-d] [-e expand-list] [-u uninitialized-list]\n<frame-number> [iname]\n" + "-h human-readable ouput\n" + "-d debug output\n" + "-e expand-list Comma-separated list of inames to be expanded beforehand\n" + "-u uninitialized-list Comma-separated list of uninitialized inames"}, +{"dumplocal", "Dumps local variable using simple dumpers (testing command).", + "[-t token] <frame-number> <iname>"}, +{"typecast","Performs a type cast on an unexpanded iname of symbol group.", + "[-t token] <frame-number> <iname> <desired-type>"}, +{"addsymbol","Adds a symbol to symbol group (testing command).", + "[-t token] <frame-number> <name-expression> [optional-iname]"}, +{"assign","Assigns a value to a variable in current symbol group.", + "[-t token] <iname=value>"}, +{"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] <address> <length>"}, +{"stack","Prints stack in GDBMI format.","[-t token] [max-frames]"}, +{"shutdownex","Unhooks output callbacks.\nNeeds to be called explicitly only in case of remote debugging.",""} +}; typedef std::vector<std::string> StringVector; typedef std::list<std::string> StringList; +// Helper for commandTokens() below: +// Simple splitting of command lines allowing for '"'-quoted tokens +// 'typecast local.i "class QString *"' -> ('typecast','local.i','class QString *') +template<class Inserter> +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 <number> remaining arguments') // Each command takes the 'token' argument and includes it in its output @@ -69,11 +204,8 @@ static inline StringContainer commandTokens(PCSTR args, int *token = 0) if (token) *token = -1; // Handled as 'display' in engine, so that user can type commands - std::string cmd(args); - simplify(cmd); StringContainer tokens; - split(cmd, ' ', std::back_inserter(tokens)); - + splitCommand(args, std::back_inserter(tokens)); // Check for token ContainerIterator it = tokens.begin(); if (it != tokens.end() && *it == "-t" && ++it != tokens.end()) { @@ -114,13 +246,11 @@ extern "C" HRESULT CALLBACK expandlocals(CIDebugClient *client, PCSTR args) int token; const StringVector tokens = commandTokens<StringVector>(args, &token); StringVector inames; - if (tokens.size() == 2u && sscanf(tokens.front().c_str(), "%u", &frame) == 1) { + if (tokens.size() == 2u && integerFromString(tokens.front(), &frame)) { symGroup = ExtensionContext::instance().symbolGroup(exc.symbols(), exc.threadId(), frame, &errorMessage); split(tokens.at(1), ',', std::back_inserter(inames)); } else { - std::ostringstream str; - str << "Invalid parameter: '" << args << "' (usage expand <frame> iname1,iname2..)."; - errorMessage = str.str(); + errorMessage = singleLineUsage(commandDescriptions[CmdExpandlocals]); } if (symGroup) { const unsigned succeeded = symGroup->expandList(inames, &errorMessage); @@ -132,17 +262,6 @@ extern "C" HRESULT CALLBACK expandlocals(CIDebugClient *client, PCSTR args) return S_OK; } -static inline std::string msgLocalsUsage(PCSTR args) -{ - std::ostringstream str; - str << "Invalid parameter: '" << args - << "'\nUsage: locals [-t token] [-h] [-d] [-e expandset] [-u uninitializedset] <frame> [iname]).\n" - "-h human-readable ouput\n" - "-d debug output\n-e expandset Comma-separated list of expanded inames\n" - "-u uninitializedset Comma-separated list of uninitialized inames\n"; - return str.str(); -} - // 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) @@ -170,7 +289,7 @@ static std::string commmandLocals(ExtensionCommandContext &exc,PCSTR args, int * break; case 'u': if (tokens.empty()) { - *errorMessage = msgLocalsUsage(args); + *errorMessage = singleLineUsage(commandDescriptions[CmdLocals]); return std::string(); } split(tokens.front(), ',', std::back_inserter(uninitializedInames)); @@ -178,7 +297,7 @@ static std::string commmandLocals(ExtensionCommandContext &exc,PCSTR args, int * break; case 'e': if (tokens.empty()) { - *errorMessage = msgLocalsUsage(args); + *errorMessage = singleLineUsage(commandDescriptions[CmdLocals]); return std::string(); } split(tokens.front(), ',', std::back_inserter(expandedInames)); @@ -188,8 +307,8 @@ static std::string commmandLocals(ExtensionCommandContext &exc,PCSTR args, int * } // Frame and iname unsigned frame; - if (tokens.empty() || sscanf(tokens.front().c_str(), "%u", &frame) != 1) { - *errorMessage = msgLocalsUsage(args); + if (tokens.empty() || !integerFromString(tokens.front(), &frame)) { + *errorMessage = singleLineUsage(commandDescriptions[CmdLocals]); return std::string(); } @@ -231,21 +350,19 @@ extern "C" HRESULT CALLBACK locals(CIDebugClient *client, PCSTR args) // Extension command 'dumplocal': // Dump a local variable using dumpers (testing command). -const char dumpLocalUsageC[] = "Usage: dumplocal <frame> <iname>"; - static std::string dumplocalHelper(ExtensionCommandContext &exc,PCSTR args, int *token, std::string *errorMessage) { // Parse the command StringList tokens = commandTokens<StringList>(args, token); // Frame and iname unsigned frame; - if (tokens.empty() || sscanf(tokens.front().c_str(), "%u", &frame) != 1) { - *errorMessage = dumpLocalUsageC; + if (tokens.empty() || integerFromString(tokens.front(), &frame)) { + *errorMessage = singleLineUsage(commandDescriptions[CmdDumplocal]); return std::string(); } tokens.pop_front(); if (tokens.empty()) { - *errorMessage = dumpLocalUsageC; + *errorMessage = singleLineUsage(commandDescriptions[CmdDumplocal]); return std::string(); } const std::string iname = tokens.front(); @@ -281,6 +398,65 @@ extern "C" HRESULT CALLBACK dumplocal(CIDebugClient *client, PCSTR argsIn) 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<StringVector>(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, "typecast", "OK"); + } else { + ExtensionContext::instance().report('N', token, "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<StringVector>(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(name, iname, &errorMessage)) { + ExtensionContext::instance().report('R', token, "addsymbol", "OK"); + } else { + ExtensionContext::instance().report('N', token, "addsymbol", errorMessage.c_str()); + } + return S_OK; +} + // Extension command 'assign': // Assign locals by iname: 'assign locals.x=5' @@ -297,7 +473,7 @@ extern "C" HRESULT CALLBACK assign(CIDebugClient *client, PCSTR argsIn) // Parse 'assign locals.x=5' const std::string::size_type equalsPos = tokens.size() == 1 ? tokens.front().find('=') : std::string::npos; if (equalsPos == std::string::npos) { - errorMessage = "Syntax error, expecting 'locals.x=5'."; + errorMessage = singleLineUsage(commandDescriptions[CmdAssign]); break; } const std::string iname = tokens.front().substr(0, equalsPos); @@ -399,7 +575,13 @@ extern "C" HRESULT CALLBACK idle(CIDebugClient *, PCSTR) extern "C" HRESULT CALLBACK help(CIDebugClient *, PCSTR) { - dprintf("Qt Creator CDB extension built %s\n", __DATE__); + std::ostringstream str; + str << "### Qt Creator CDB extension built " << __DATE__ << "\n\n"; + + const size_t commandCount = sizeof(commandDescriptions)/sizeof(CommandDescription); + std::copy(commandDescriptions, commandDescriptions + commandCount, + std::ostream_iterator<CommandDescription>(str)); + dprintf("%s\n", str.str().c_str()); return S_OK; } @@ -422,7 +604,7 @@ extern "C" HRESULT CALLBACK memory(CIDebugClient *Client, PCSTR argsIn) && integerFromString(tokens.at(1), &length)) { memory = memoryToBase64(exc.dataSpaces(), address, length, &errorMessage); } else { - errorMessage = "Invalid parameters to memory command."; + errorMessage = singleLineUsage(commandDescriptions[CmdMemory]); } if (memory.empty()) { diff --git a/src/libs/qtcreatorcdbext/symbolgroup.cpp b/src/libs/qtcreatorcdbext/symbolgroup.cpp index a212755b7e73271b52a40c1fafad95bacee4ce62..e63a2102d88a8bcbe9c3bcaac5a8680259d82530 100644 --- a/src/libs/qtcreatorcdbext/symbolgroup.cpp +++ b/src/libs/qtcreatorcdbext/symbolgroup.cpp @@ -468,7 +468,7 @@ static void fixValue(const std::string &type, std::wstring *value) return; } // Fix long class names on std containers 'class std::tree<...>' -> 'class std::tree<>' - if (value->compare(0, 6, L"class ") == 0) { + if (value->compare(0, 6, L"class ") == 0 || value->compare(0, 7, L"struct ") == 0) { const std::string::size_type openTemplate = value->find(L'<'); if (openTemplate != std::string::npos) { value->erase(openTemplate + 1, value->size() - openTemplate - 2); @@ -665,6 +665,17 @@ void SymbolGroupNode::debug(std::ostream &str, unsigned verbosity, unsigned dept str << '\n'; } +static inline std::string msgCannotCast(const std::string &nodeName, + const std::string &fromType, + const std::string &toType, + const std::string &why) +{ + std::ostringstream str; + str << "Cannot cast node '" << nodeName << "' from '" << fromType + << "' to '" << toType << "': " << why; + return str.str(); +} + // Expand! bool SymbolGroupNode::expand(std::string *errorMessage) { @@ -704,6 +715,65 @@ bool SymbolGroupNode::expand(std::string *errorMessage) return true; } +bool SymbolGroupNode::typeCast(const std::string &desiredType, std::string *errorMessage) +{ + const std::string fromType = type(); + if (fromType == desiredType) + return true; + if (isExpanded()) { + *errorMessage = msgCannotCast(fullIName(), fromType, desiredType, "Already expanded"); + return false; + } + HRESULT hr = m_symbolGroup->debugSymbolGroup()->OutputAsType(m_index, desiredType.c_str()); + if (FAILED(hr)) { + *errorMessage = msgCannotCast(fullIName(), fromType, desiredType, msgDebugEngineComFailed("OutputAsType", hr)); + return false; + } + hr = m_symbolGroup->debugSymbolGroup()->GetSymbolParameters(m_index, 1, &m_parameters); + if (FAILED(hr)) { // Should never fail + *errorMessage = msgCannotCast(fullIName(), fromType, desiredType, msgDebugEngineComFailed("GetSymbolParameters", hr)); + return false; + } + return true; +} + +static inline std::string msgCannotAddSymbol(const std::string &name, const std::string &why) +{ + std::ostringstream str; + str << "Cannot add symbol '" << name << "': " << why; + return str.str(); +} + +// For root nodes, only: Add a new symbol by name +bool SymbolGroupNode::addSymbolByName(const std::string &name, + const std::string &iname, + std::string *errorMessage) +{ + ULONG index = DEBUG_ANY_ID; // Append + HRESULT hr = m_symbolGroup->debugSymbolGroup()->AddSymbol(name.c_str(), &index); + if (FAILED(hr)) { + *errorMessage = msgCannotAddSymbol(name, msgDebugEngineComFailed("AddSymbol", hr)); + return false; + } + SymbolParameterVector parameters(1, DEBUG_SYMBOL_PARAMETERS()); + hr = m_symbolGroup->debugSymbolGroup()->GetSymbolParameters(index, 1, &(*parameters.begin())); + if (FAILED(hr)) { // Should never fail + *errorMessage = msgCannotAddSymbol(name, msgDebugEngineComFailed("GetSymbolParameters", hr)); + return false; + } + // Paranoia: Check for cuckoo's eggs (which should not happen) + if (parameters.front().ParentSymbol != m_index) { + *errorMessage = msgCannotAddSymbol(name, "Parent id mismatch"); + return false; + } + SymbolGroupNode *node = new SymbolGroupNode(m_symbolGroup, index, + name, iname.empty() ? name : iname, + this); + node->parseParameters(0, 0, parameters); + m_children.push_back(node); + return true; +} + static inline std::string msgNotFound(const std::string &nodeName) { std::ostringstream str; @@ -834,6 +904,26 @@ bool SymbolGroup::expand(const std::string &nodeName, std::string *errorMessage) return node->expand(errorMessage); } +// Cast an (unexpanded) node +bool SymbolGroup::typeCast(const std::string &iname, const std::string &desiredType, std::string *errorMessage) +{ + SymbolGroupNode *node = find(iname); + if (!node) { + *errorMessage = msgNotFound(iname); + return false; + } + if (node == m_root) { + *errorMessage = "Cannot cast root node"; + return false; + } + return node->typeCast(desiredType, errorMessage); +} + +bool SymbolGroup::addSymbol(const std::string &name, const std::string &iname, std::string *errorMessage) +{ + return m_root->addSymbolByName(name, iname, errorMessage); +} + // Mark uninitialized (top level only) void SymbolGroup::markUninitialized(const std::vector<std::string> &uniniNodes) { diff --git a/src/libs/qtcreatorcdbext/symbolgroup.h b/src/libs/qtcreatorcdbext/symbolgroup.h index 950066bca41559ef93816f255018ecd5723604a7..8fd886045fc3fe0d6f50ed3a928b2c2cd5fea82a 100644 --- a/src/libs/qtcreatorcdbext/symbolgroup.h +++ b/src/libs/qtcreatorcdbext/symbolgroup.h @@ -53,6 +53,13 @@ struct SymbolGroupValueContext; class SymbolGroupNode { SymbolGroupNode(const SymbolGroupNode&); SymbolGroupNode& operator=(const SymbolGroupNode&); + + explicit SymbolGroupNode(SymbolGroup *symbolGroup, + ULONG index, + const std::string &name, + const std::string &iname, + SymbolGroupNode *parent = 0); + public: enum Flags { Uninitialized = 0x1, @@ -67,12 +74,6 @@ public: typedef SymbolGroupNodePtrVector::iterator SymbolGroupNodePtrVectorIterator; typedef SymbolGroupNodePtrVector::const_iterator SymbolGroupNodePtrVectorConstIterator; - explicit SymbolGroupNode(SymbolGroup *symbolGroup, - ULONG index, - const std::string &name, - const std::string &iname, - SymbolGroupNode *parent = 0); - ~SymbolGroupNode() { removeChildren(); } void removeChildren(); @@ -81,6 +82,10 @@ public: const SymbolParameterVector &vec); static SymbolGroupNode *create(SymbolGroup *sg, const std::string &name, const SymbolParameterVector &vec); + // For root nodes, only: Add a new symbol by name + bool addSymbolByName(const std::string &name, // Expression like 'myarray[1]' + const std::string &iname, // Desired iname, defaults to name + std::string *errorMessage); const std::string &name() const { return m_name; } std::string fullIName() const; @@ -111,6 +116,8 @@ public: bool expand(std::string *errorMessage); bool isExpanded() const { return !m_children.empty(); } bool canExpand() const { return m_parameters.SubElements > 0; } + // Cast to a different type. Works only on unexpanded nodes + bool typeCast(const std::string &desiredType, std::string *errorMessage); ULONG subElements() const { return m_parameters.SubElements; } ULONG index() const { return m_index; } @@ -213,6 +220,12 @@ public: // Expand a single node "locals.A.B" requiring that "locals.A.B" is already visible // (think mkdir without -p). bool expand(const std::string &node, std::string *errorMessage); + // Cast an (unexpanded) node + bool typeCast(const std::string &iname, const std::string &desiredType, std::string *errorMessage); + // Add a symbol by name expression + bool addSymbol(const std::string &name, // Expression like 'myarray[1]' + const std::string &iname, // Desired iname, defaults to name + std::string *errorMessage); bool accept(SymbolGroupNodeVisitor &visitor) const;