diff --git a/src/libs/qtcreatorcdbext/qtcreatorcdbextension.cpp b/src/libs/qtcreatorcdbext/qtcreatorcdbextension.cpp index eea8a86e047532ced34ab38000c88083fba35b53..c49b2d2691d42c915bb3c6d9acce896f38dbef5b 100644 --- a/src/libs/qtcreatorcdbext/qtcreatorcdbextension.cpp +++ b/src/libs/qtcreatorcdbext/qtcreatorcdbextension.cpp @@ -104,11 +104,14 @@ static const CommandDescription commandDescriptions[] = { "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" + "[-t token] [T formats] [-I formats] [-c] [-h] [-d] [-e expand-list] [-u uninitialized-list]\n<frame-number> [iname]\n" "-h human-readable ouput\n" "-d debug output\n" + "-c complex dumpers\n" "-e expand-list Comma-separated list of inames to be expanded beforehand\n" - "-u uninitialized-list Comma-separated list of uninitialized inames"}, + "-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'"}, {"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.", @@ -270,12 +273,11 @@ static std::string commmandLocals(ExtensionCommandContext &exc,PCSTR args, int * { // Parse the command unsigned debugOutput = 0; - bool humanReadableGdbmi = false; std::string iname; - StringList tokens = commandTokens<StringList>(args, token); StringVector expandedInames; StringVector uninitializedInames; + DumpParameters parameters; // Parse away options while (!tokens.empty() && tokens.front().size() == 2 && tokens.front().at(0) == '-') { const char option = tokens.front().at(1); @@ -285,7 +287,10 @@ static std::string commmandLocals(ExtensionCommandContext &exc,PCSTR args, int * debugOutput++; break; case 'h': - humanReadableGdbmi = true; + parameters.dumpFlags |= DumpParameters::DumpHumanReadable; + break; + case 'c': + parameters.dumpFlags |= DumpParameters::DumpComplexDumpers; break; case 'u': if (tokens.empty()) { @@ -303,8 +308,25 @@ static std::string commmandLocals(ExtensionCommandContext &exc,PCSTR args, int * split(tokens.front(), ',', std::back_inserter(expandedInames)); tokens.pop_front(); break; - } - } + case 'T': // typeformats: 'hex'ed name = formatnumber,...' + if (tokens.empty()) { + *errorMessage = singleLineUsage(commandDescriptions[CmdLocals]); + return std::string(); + } + parameters.typeFormats = DumpParameters::decodeFormatArgument(tokens.front()); + tokens.pop_front(); + break; + case 'I': // individual formats: 'hex'ed name = formatnumber,...' + if (tokens.empty()) { + *errorMessage = singleLineUsage(commandDescriptions[CmdLocals]); + return std::string(); + } + parameters.individualFormats = DumpParameters::decodeFormatArgument(tokens.front()); + tokens.pop_front(); + break; + } // case option + } // for options + // Frame and iname unsigned frame; if (tokens.empty() || !integerFromString(tokens.front(), &frame)) { @@ -329,8 +351,8 @@ static std::string commmandLocals(ExtensionCommandContext &exc,PCSTR args, int * const SymbolGroupValueContext dumpContext(exc.dataSpaces()); return iname.empty() ? - symGroup->dump(dumpContext, humanReadableGdbmi) : - symGroup->dump(iname, dumpContext, humanReadableGdbmi, errorMessage); + symGroup->dump(dumpContext, parameters) : + symGroup->dump(iname, dumpContext, parameters, errorMessage); } extern "C" HRESULT CALLBACK locals(CIDebugClient *client, PCSTR args) diff --git a/src/libs/qtcreatorcdbext/stringutils.cpp b/src/libs/qtcreatorcdbext/stringutils.cpp index b0f476d1b30edf4145129b88b7e736847532d863..dfc2d0ad08e315dfc8ae41e3e7f73eab76263486 100644 --- a/src/libs/qtcreatorcdbext/stringutils.cpp +++ b/src/libs/qtcreatorcdbext/stringutils.cpp @@ -185,6 +185,55 @@ std::wstring stringToWString(const std::string &w) return rc; } +// Convert an ASCII hex digit to its value 'A'->10 +inline unsigned hexDigit(char c) +{ + if (c <= '9') + return c - '0'; + if (c <= 'F') + return c - 'A' + 10; + return c - 'a' + 10; +} + +// Convert an ASCII hex digit to its value 'A'->10 +inline char toHexDigit(unsigned v) +{ + if (v < 10) + return char(v) + '0'; + return char(v - 10) + 'a'; +} + +// String from hex "414A" -> "AJ". +std::string stringFromHex(const char *p, const char *end) +{ + if (p == end) + return std::string(); + + std::string rc; + rc.reserve((end - p) / 2); + for ( ; p < end; p++) { + unsigned c = 16 * hexDigit(*p); + c += hexDigit(*++p); + rc.push_back(char(c)); + } + return rc; +} + +std::wstring dataToHexW(const unsigned char *p, const unsigned char *end) +{ + if (p == end) + return std::wstring(); + + std::wstring rc; + rc.reserve(2 * (end - p)); + for ( ; p < end ; p++) { + const unsigned c = *p; + rc.push_back(toHexDigit(c / 16)); + rc.push_back(toHexDigit(c &0xF)); + } + return rc; +} + // Format a map as a GDBMI hash {key="value",..} void formatGdbmiHash(std::ostream &os, const std::map<std::string, std::string> &m) { diff --git a/src/libs/qtcreatorcdbext/stringutils.h b/src/libs/qtcreatorcdbext/stringutils.h index 49e703c13ee3d152f62f4c3bb233202746621967..9a0bd40c435c9c1d6f08d550143860a3ec0e137f 100644 --- a/src/libs/qtcreatorcdbext/stringutils.h +++ b/src/libs/qtcreatorcdbext/stringutils.h @@ -86,6 +86,18 @@ bool integerFromString(const std::string &s, Integer *v) return !str.fail(); } +// Read an integer from a wstring as '10' or '0xA' +template <class Integer> +bool integerFromWString(const std::wstring &s, Integer *v) +{ + const bool isHex = s.compare(0, 2, L"0x") == 0; + std::wistringstream str(isHex ? s.substr(2, s.size() - 2) : s); + if (isHex) + str >> std::hex; + str >> *v; + return !str.fail(); +} + void replace(std::wstring &s, wchar_t before, wchar_t after); // Stream a string onto a char stream doing backslash & octal escaping @@ -128,6 +140,10 @@ std::string wStringToGdbmiString(const std::wstring &w); std::string wStringToString(const std::wstring &w); std::wstring stringToWString(const std::string &w); +// String from hex "414A" -> "AJ". +std::string stringFromHex(const char *begin, const char *end); +std::wstring dataToHexW(const unsigned char *begin, const unsigned char *end); + // Format a map as a GDBMI hash {key="value",..} void formatGdbmiHash(std::ostream &os, const std::map<std::string, std::string> &); diff --git a/src/libs/qtcreatorcdbext/symbolgroup.cpp b/src/libs/qtcreatorcdbext/symbolgroup.cpp index c8ec41ff8271ac7db1c3cf798561d74cca7b169d..39cfc60557a9d44eb6da6f716468bf450b4b3073 100644 --- a/src/libs/qtcreatorcdbext/symbolgroup.cpp +++ b/src/libs/qtcreatorcdbext/symbolgroup.cpp @@ -76,6 +76,128 @@ std::ostream &operator<<(std::ostream &str, const DEBUG_SYMBOL_PARAMETERS ¶m return str; } +// --------------- DumpParameters +DumpParameters::DumpParameters() : dumpFlags(0) +{ +} + +// typeformats: decode hex-encoded name, value pairs: +// '414A=2,...' -> map of "AB:2". +DumpParameters::FormatMap DumpParameters::decodeFormatArgument(const std::string &f) +{ + FormatMap rc; + const std::string::size_type size = f.size(); + // Split 'hexname=4,' + for (std::string::size_type pos = 0; pos < size ; ) { + // Cut out key + const std::string::size_type equalsPos = f.find('=', pos); + if (equalsPos == std::string::npos) + return rc; + const std::string name = stringFromHex(f.c_str() + pos, f.c_str() + equalsPos); + // Search for number + const std::string::size_type numberPos = equalsPos + 1; + std::string::size_type nextPos = f.find(',', numberPos); + if (nextPos == std::string::npos) + nextPos = size; + int format; + if (!integerFromString(f.substr(numberPos, nextPos - numberPos), &format)) + return rc; + rc.insert(FormatMap::value_type(name, format)); + pos = nextPos + 1; + } + return rc; +} + +int DumpParameters::format(const std::string &type, const std::string &iname) const +{ + if (!individualFormats.empty()) { + const FormatMap::const_iterator iit = individualFormats.find(iname); + if (iit != individualFormats.end()) + return iit->second; + } + if (!typeFormats.empty()) { + const FormatMap::const_iterator tit = typeFormats.find(type); + if (tit != typeFormats.end()) + return tit->second; + } + return -1; +} + +enum PointerFormats // Watch data pointer format requests +{ + FormatRawPointer = 0, + FormatLatin1String = 1, + FormatUtf8String = 2, + FormatUtf16String = 3, + FormatUcs4String = 4 +}; + +enum DumpEncoding // WatchData encoding of GDBMI values +{ + DumpEncodingAscii = 0, + DumpEncodingBase64 = 1, + DumpEncodingBase64_Utf16 = 2, + DumpEncodingBase64_Ucs4 = 3, + DumpEncodingHex_Latin1 = 6, + DumpEncodingHex_Utf161 = 7, + DumpEncodingHex_Ucs4_LittleEndian = 8, + DumpEncodingHex_Utf8_LittleEndian = 9, + DumpEncodingHex_Ucs4_BigEndian = 10, + DumpEncodingHex_Utf16_BigEndian = 11, + DumpEncodingHex_Utf16_LittleEndian = 12 +}; + +bool DumpParameters::recode(const std::string &type, + const std::string &iname, + const SymbolGroupValueContext &ctx, + std::wstring *value, int *encoding) const +{ + // We basically handle char formats for 'char *', '0x834478 "hallo.."' + // Determine address and length from the pointer value output, + // read the raw memory and recode if that is possible. + const int newFormat = format(type, iname); + if (newFormat < 2) + return false; + if (value->compare(0, 2, L"0x")) + return false; + const std::wstring::size_type quote1 = value->find(L'"', 2); + if (quote1 == std::wstring::npos) + return false; + const std::wstring::size_type quote2 = value->find(L'"', quote1 + 1); + if (quote2 == std::wstring::npos) + return false; + const std::wstring::size_type length = quote2 - quote1 - 1; + if (!length) + return false; + ULONG64 address = 0; + if (!integerFromWString(value->substr(0, quote1 - 1), &address) || !address) + return false; + // Allocate real length + 4 bytes ('\0') for largest format. + // '\0' is not listed in the CDB output. + const std::wstring::size_type allocLength = length + 4; + unsigned char *buffer = new unsigned char[allocLength]; + std::fill(buffer, buffer + allocLength, 0); + ULONG obtained = 0; + if (FAILED(ctx.dataspaces->ReadVirtual(address, buffer, ULONG(length), &obtained))) { + delete [] buffer; + return false; + } + // Recode raw memory + switch (newFormat) { + case FormatUtf8String: + *value = dataToHexW(buffer, buffer + length + 1); // UTF8 + 0 + *encoding = DumpEncodingHex_Utf8_LittleEndian; + break; + case FormatUtf16String: + break; + case FormatUcs4String: + break; + } + delete [] buffer; + return true; +} + +// ------------- SymbolGroup SymbolGroup::SymbolGroup(IDebugSymbolGroup2 *sg, const SymbolParameterVector &vec, ULONG threadId, @@ -579,7 +701,9 @@ static inline void indentStream(std::ostream &str, unsigned depth) str << " "; } -void SymbolGroupNode::dump(std::ostream &str, const SymbolGroupValueContext &ctx) +void SymbolGroupNode::dump(std::ostream &str, + const DumpParameters &p, + const SymbolGroupValueContext &ctx) { const std::string iname = fullIName(); const std::string t = type(); @@ -595,16 +719,22 @@ void SymbolGroupNode::dump(std::ostream &str, const SymbolGroupValueContext &ctx bool valueEditable = !uninitialized; bool valueEnabled = !uninitialized; - const std::wstring value = displayValue(ctx); - // ASCII or base64? - if (isSevenBitClean(value.c_str(), value.size())) { - str << ",valueencoded=\"0\",value=\"" << gdbmiWStringFormat(value) << '"'; - } else { - str << ",valueencoded=\"2\",value=\""; - base64Encode(str, reinterpret_cast<const unsigned char *>(value.c_str()), value.size() * sizeof(wchar_t)); - str << '"'; + // Shall it be recoded? + std::wstring value = displayValue(ctx); + int encoding = 0; + if (p.recode(t, iname, ctx, &value, &encoding)) { + str << ",valueencoded=\"" << encoding + << "\",value=\"" << gdbmiWStringFormat(value) <<'"'; + } else { // As is: ASCII or base64? + if (isSevenBitClean(value.c_str(), value.size())) { + str << ",valueencoded=\"" << DumpEncodingAscii << "\",value=\"" + << gdbmiWStringFormat(value) << '"'; + } else { + str << ",valueencoded=\"" << DumpEncodingBase64_Utf16 << "\",value=\""; + base64Encode(str, reinterpret_cast<const unsigned char *>(value.c_str()), value.size() * sizeof(wchar_t)); + str << '"'; + } } - // Children: Dump all known or subelements (guess). const VectorIndexType childCountGuess = uninitialized ? 0 : (m_children.empty() ? m_parameters.SubElements : m_children.size()); @@ -792,11 +922,12 @@ static inline std::string msgNotFound(const std::string &nodeName) return str.str(); } -std::string SymbolGroup::dump(const SymbolGroupValueContext &ctx, bool humanReadable) const +std::string SymbolGroup::dump(const SymbolGroupValueContext &ctx, + const DumpParameters &p) const { std::ostringstream str; - DumpSymbolGroupNodeVisitor visitor(str, ctx, humanReadable); - if (humanReadable) + DumpSymbolGroupNodeVisitor visitor(str, ctx, p); + if (p.humanReadable()) str << '\n'; str << '['; accept(visitor); @@ -805,7 +936,10 @@ std::string SymbolGroup::dump(const SymbolGroupValueContext &ctx, bool humanRead } // Dump a node, potentially expand -std::string SymbolGroup::dump(const std::string &iname, const SymbolGroupValueContext &ctx, bool humanReadable, std::string *errorMessage) +std::string SymbolGroup::dump(const std::string &iname, + const SymbolGroupValueContext &ctx, + const DumpParameters &p, + std::string *errorMessage) { SymbolGroupNode *const node = find(iname); if (node == 0) { @@ -819,9 +953,9 @@ std::string SymbolGroup::dump(const std::string &iname, const SymbolGroupValueCo return false; } std::ostringstream str; - if (humanReadable) + if (p.humanReadable()) str << '\n'; - DumpSymbolGroupNodeVisitor visitor(str, ctx, humanReadable); + DumpSymbolGroupNodeVisitor visitor(str, ctx, p); str << '['; node->accept(visitor, 0, 0); str << ']'; @@ -1056,8 +1190,9 @@ SymbolGroupNodeVisitor::VisitResult // --------------------- DumpSymbolGroupNodeVisitor DumpSymbolGroupNodeVisitor::DumpSymbolGroupNodeVisitor(std::ostream &os, const SymbolGroupValueContext &context, - bool humanReadable) : - m_os(os), m_humanReadable(humanReadable),m_context(context), m_visitChildren(false) + const DumpParameters ¶meters) : + m_os(os), m_context(context), m_parameters(parameters), + m_visitChildren(false) { } @@ -1073,18 +1208,18 @@ SymbolGroupNodeVisitor::VisitResult // Do not recurse into children unless the node was expanded by the watch model if (child) m_os << ','; // Separator in parents list - if (m_humanReadable) { + if (m_parameters.humanReadable()) { m_os << '\n'; indentStream(m_os, depth * 2); } m_os << '{'; - node->dump(m_os, m_context); + node->dump(m_os, m_parameters, m_context); if (m_visitChildren) { // open children array m_os << ",children=["; } else { // No children, close array. m_os << '}'; } - if (m_humanReadable) + if (m_parameters.humanReadable()) m_os << '\n'; return m_visitChildren ? VisitContinue : VisitSkipChildren; } @@ -1092,6 +1227,6 @@ SymbolGroupNodeVisitor::VisitResult void DumpSymbolGroupNodeVisitor::childrenVisited(const SymbolGroupNode *, unsigned) { m_os << "]}"; // Close children array and self - if (m_humanReadable) + if (m_parameters.humanReadable()) m_os << '\n'; } diff --git a/src/libs/qtcreatorcdbext/symbolgroup.h b/src/libs/qtcreatorcdbext/symbolgroup.h index 03ed8cbf08b810c0b8670fc4ef8f74e30c1e0a47..2f5dd73332d9a7890c3d7865df9de2e880e3b30c 100644 --- a/src/libs/qtcreatorcdbext/symbolgroup.h +++ b/src/libs/qtcreatorcdbext/symbolgroup.h @@ -34,6 +34,7 @@ #include <string> #include <vector> +#include <map> #include <iosfwd> std::ostream &operator<<(std::ostream &, const DEBUG_SYMBOL_PARAMETERS&p); @@ -42,6 +43,31 @@ class SymbolGroupNodeVisitor; class SymbolGroup; struct SymbolGroupValueContext; +// All parameters for dumping in one struct. +struct DumpParameters +{ + typedef std::map<std::string, int> FormatMap; // type or iname to format + enum DumpFlags + { + DumpHumanReadable = 0x1, + DumpComplexDumpers = 0x2 + }; + + DumpParameters(); + bool humanReadable() const { return dumpFlags & DumpHumanReadable; } + // Helper to decode format option arguments. + static FormatMap decodeFormatArgument(const std::string &f); + + bool recode(const std::string &type, const std::string &iname, + const SymbolGroupValueContext &ctx, + std::wstring *value, int *encoding) const; + int format(const std::string &type, const std::string &iname) const; + + unsigned dumpFlags; + FormatMap typeFormats; + FormatMap individualFormats; +}; + // Thin wrapper around a symbol group entry. Provides accessors for fixed-up // symbol group value and a dumping facility triggered by dump()/displayValue() // calling dumpSimpleType() based on SymbolGroupValue expressions. These values @@ -70,6 +96,7 @@ public: ExpandedByDumper = 0x10, AdditionalSymbol = 0x20 // Introduced by addSymbol, should not be visible }; + typedef std::vector<DEBUG_SYMBOL_PARAMETERS> SymbolParameterVector; typedef std::vector<SymbolGroupNode *> SymbolGroupNodePtrVector; typedef SymbolGroupNodePtrVector::iterator SymbolGroupNodePtrVectorIterator; @@ -102,7 +129,7 @@ public: SymbolGroup *symbolGroup() const { return m_symbolGroup; } // I/O: Gdbmi dump for Visitors - void dump(std::ostream &str, const SymbolGroupValueContext &ctx); + void dump(std::ostream &str, const DumpParameters &p, const SymbolGroupValueContext &ctx); // I/O: debug for Visitors void debug(std::ostream &os, unsigned verbosity, unsigned depth) const; @@ -202,10 +229,11 @@ public: ~SymbolGroup(); // Dump all - std::string dump(const SymbolGroupValueContext &ctx, bool humanReadable = false) const; + std::string dump(const SymbolGroupValueContext &ctx, + const DumpParameters &p = DumpParameters()) const; // Expand node and dump std::string dump(const std::string &iname, const SymbolGroupValueContext &ctx, - bool humanReadable, std::string *errorMessage); + const DumpParameters &p, std::string *errorMessage); std::string debug(const std::string &iname = std::string(), unsigned verbosity = 0) const; unsigned frame() const { return m_frame; } @@ -274,15 +302,15 @@ class DumpSymbolGroupNodeVisitor : public SymbolGroupNodeVisitor { public: explicit DumpSymbolGroupNodeVisitor(std::ostream &os, const SymbolGroupValueContext &context, - bool humanReadable); + const DumpParameters ¶meters = DumpParameters()); private: virtual VisitResult visit(SymbolGroupNode *node, unsigned child, unsigned depth); virtual void childrenVisited(const SymbolGroupNode * node, unsigned depth); std::ostream &m_os; - const bool m_humanReadable; const SymbolGroupValueContext &m_context; + const DumpParameters &m_parameters; bool m_visitChildren; }; diff --git a/src/plugins/debugger/cdb2/cdbengine2.cpp b/src/plugins/debugger/cdb2/cdbengine2.cpp index f6191cdee8849886448873537151978e9e866926..f62879d2092183c67b2413cf52da791b97f1fbdf 100644 --- a/src/plugins/debugger/cdb2/cdbengine2.cpp +++ b/src/plugins/debugger/cdb2/cdbengine2.cpp @@ -653,13 +653,26 @@ void CdbEngine::updateWatchData(const Debugger::Internal::WatchData &dataIn, updateLocalVariable(dataIn.iname); } +void CdbEngine::addLocalsOptions(ByteArrayInputStream &str) const +{ + if (debuggerCore()->boolSetting(UseDebuggingHelpers)) + str << blankSeparator << "-c"; + const QByteArray typeFormats = watchHandler()->typeFormatRequests(); + if (!typeFormats.isEmpty()) + str << blankSeparator << "-T " << typeFormats; + const QByteArray individualFormats = watchHandler()->individualFormatRequests(); + if (!individualFormats.isEmpty()) + str << blankSeparator << "-I " << individualFormats; +} + void CdbEngine::updateLocalVariable(const QByteArray &iname) { const int stackFrame = stackHandler()->currentIndex(); if (stackFrame >= 0) { QByteArray localsArguments; ByteArrayInputStream str(localsArguments); - str << stackFrame << ' ' << iname; + addLocalsOptions(str); + str << blankSeparator << stackFrame << ' ' << iname; postExtensionCommand("locals", localsArguments, 0, &CdbEngine::handleLocals); } else { qWarning("Internal error; no stack frame in updateLocalVariable"); @@ -973,6 +986,7 @@ void CdbEngine::activateFrame(int index) str << e; } } + addLocalsOptions(str); // Uninitialized variables if desired if (debuggerCore()->boolSetting(UseCodeModel)) { QStringList uninitializedVariables; @@ -1299,8 +1313,10 @@ void CdbEngine::handleSessionIdle(const QByteArray &message) notifyInferiorSpontaneousStop(); } // Start sequence to get all relevant data. Hack: Avoid module reload? - unsigned sequence = CommandListStack|CommandListRegisters|CommandListThreads; - if (modulesHandler()->modules().size() == 0) + unsigned sequence = CommandListStack; + if (debuggerCore()->isDockVisible(QLatin1String(Constants::DOCKWIDGET_REGISTER))) + sequence |= CommandListRegisters; + if (debuggerCore()->isDockVisible(QLatin1String(Constants::DOCKWIDGET_MODULES))) sequence |= CommandListModules; postCommandSequence(sequence); // Report stop reason (GDBMI) diff --git a/src/plugins/debugger/cdb2/cdbengine2.h b/src/plugins/debugger/cdb2/cdbengine2.h index fb18021005e432127e1c5bacd53676faac863150..9b428e078b2b07addc502a2b8dab1da2c404a6b4 100644 --- a/src/plugins/debugger/cdb2/cdbengine2.h +++ b/src/plugins/debugger/cdb2/cdbengine2.h @@ -45,6 +45,7 @@ class DisassemblerViewAgent; struct CdbBuiltinCommand; struct CdbExtensionCommand; struct CdbOptions; +class ByteArrayInputStream; class CdbEngine : public Debugger::DebuggerEngine { @@ -174,6 +175,7 @@ private: QString normalizeFileName(const QString &f); void updateLocalVariable(const QByteArray &iname); int elapsedLogTime() const; + void addLocalsOptions(ByteArrayInputStream &s) const; const QByteArray m_creatorExtPrefix; const QByteArray m_tokenPrefix;