From 52915776cdb4810dce51043d8078768e1b3b47ac Mon Sep 17 00:00:00 2001 From: Friedemann Kleint <Friedemann.Kleint@nokia.com> Date: Wed, 22 Apr 2009 17:28:26 +0200 Subject: [PATCH] Make CDB load custom dumpers. Load in a 'well-defined' (temporary) breakpoint at main(). --- share/qtcreator/gdbmacros/gdbmacros.cpp | 4 + src/plugins/debugger/cdb/cdbbreakpoint.cpp | 24 +- src/plugins/debugger/cdb/cdbbreakpoint.h | 1 + src/plugins/debugger/cdb/cdbdebugengine.cpp | 104 ++++-- src/plugins/debugger/cdb/cdbdebugengine_p.h | 7 +- .../debugger/cdb/cdbdebugeventcallback.cpp | 20 +- src/plugins/debugger/cdb/cdbdumperhelper.cpp | 342 ++++++++++++++---- src/plugins/debugger/cdb/cdbdumperhelper.h | 60 ++- src/plugins/debugger/cdb/cdbmodules.cpp | 101 +++++- src/plugins/debugger/cdb/cdbmodules.h | 10 +- src/plugins/debugger/debuggermanager.h | 14 +- src/plugins/debugger/gdbengine.cpp | 6 +- src/plugins/debugger/watchutils.cpp | 53 +++ src/plugins/debugger/watchutils.h | 3 + 14 files changed, 593 insertions(+), 156 deletions(-) diff --git a/share/qtcreator/gdbmacros/gdbmacros.cpp b/share/qtcreator/gdbmacros/gdbmacros.cpp index 06c1e1ae1cf..639a0d6500b 100644 --- a/share/qtcreator/gdbmacros/gdbmacros.cpp +++ b/share/qtcreator/gdbmacros/gdbmacros.cpp @@ -2529,7 +2529,11 @@ void *qDumpObjectData440( int protocolVersion, int token, void *data, +#ifdef Q_CC_MSVC // CDB cannot handle boolean parameters + int dumpChildren, +#else bool dumpChildren, +#endif int extraInt0, int extraInt1, int extraInt2, diff --git a/src/plugins/debugger/cdb/cdbbreakpoint.cpp b/src/plugins/debugger/cdb/cdbbreakpoint.cpp index 6cb8ebdd910..2824a57e5c4 100644 --- a/src/plugins/debugger/cdb/cdbbreakpoint.cpp +++ b/src/plugins/debugger/cdb/cdbbreakpoint.cpp @@ -48,7 +48,8 @@ static const char sourceFileQuoteC = '`'; CDBBreakPoint::CDBBreakPoint() : ignoreCount(0), - lineNumber(-1) + lineNumber(-1), + oneShot(false) { } @@ -57,7 +58,8 @@ CDBBreakPoint::CDBBreakPoint(const BreakpointData &bpd) : condition(bpd.condition), ignoreCount(0), funcName(bpd.funcName), - lineNumber(-1) + lineNumber(-1), + oneShot(false) { if (!bpd.ignoreCount.isEmpty()) ignoreCount = bpd.ignoreCount.toInt(); @@ -75,6 +77,10 @@ int CDBBreakPoint::compare(const CDBBreakPoint& rhs) const return 1; if (lineNumber < rhs.lineNumber) return -1; + if (oneShot && !rhs.oneShot) + return 1; + if (!oneShot && rhs.oneShot) + return -1; if (const int fileCmp = fileName.compare(rhs.fileName)) return fileCmp; if (const int funcCmp = funcName.compare(rhs.funcName)) @@ -87,7 +93,8 @@ int CDBBreakPoint::compare(const CDBBreakPoint& rhs) const void CDBBreakPoint::clear() { ignoreCount = 0; - clearExpressionData(); + oneShot = false; + clearExpressionData(); } void CDBBreakPoint::clearExpressionData() @@ -110,6 +117,8 @@ QDebug operator<<(QDebug dbg, const CDBBreakPoint &bp) nsp << " condition='" << bp.condition << '\''; if (bp.ignoreCount) nsp << " ignoreCount=" << bp.ignoreCount; + if (bp.oneShot) + nsp << " oneShot"; return dbg; } @@ -144,7 +153,10 @@ bool CDBBreakPoint::apply(CIDebugBreakpoint *ibp, QString *errorMessage) const } // Pass Count is ignoreCount + 1 ibp->SetPassCount(ignoreCount + 1u); - ibp->AddFlags(DEBUG_BREAKPOINT_ENABLED); + ULONG flags = DEBUG_BREAKPOINT_ENABLED; + if (oneShot) + flags |= DEBUG_BREAKPOINT_ONE_SHOT; + ibp->AddFlags(flags); return true; } @@ -190,6 +202,10 @@ bool CDBBreakPoint::retrieve(CIDebugBreakpoint *ibp, QString *errorMessage) ibp->GetPassCount(&ignoreCount); if (ignoreCount) ignoreCount--; + ULONG flags = 0; + ibp->GetFlags(&flags); + if (flags & DEBUG_BREAKPOINT_ONE_SHOT) + oneShot = true; const QString expr = QString::fromUtf16(wszBuf); if (!parseExpression(expr)) { *errorMessage = QString::fromLatin1("Parsing of '%1' failed.").arg(expr); diff --git a/src/plugins/debugger/cdb/cdbbreakpoint.h b/src/plugins/debugger/cdb/cdbbreakpoint.h index f1a775fe6e8..9ee7ef8d860 100644 --- a/src/plugins/debugger/cdb/cdbbreakpoint.h +++ b/src/plugins/debugger/cdb/cdbbreakpoint.h @@ -83,6 +83,7 @@ struct CDBBreakPoint unsigned long ignoreCount; // ignore count associated with breakpoint int lineNumber; // line in source file QString funcName; // name of containing function + bool oneShot; }; QDebug operator<<(QDebug, const CDBBreakPoint &bp); diff --git a/src/plugins/debugger/cdb/cdbdebugengine.cpp b/src/plugins/debugger/cdb/cdbdebugengine.cpp index 2aec4e327c3..a60057071b6 100644 --- a/src/plugins/debugger/cdb/cdbdebugengine.cpp +++ b/src/plugins/debugger/cdb/cdbdebugengine.cpp @@ -53,6 +53,7 @@ #include <utils/consoleprocess.h> #include <QtCore/QDebug> +#include <QtCore/QTimer> #include <QtCore/QTimerEvent> #include <QtCore/QFileInfo> #include <QtCore/QDir> @@ -96,13 +97,13 @@ QString msgDebugEngineComResult(HRESULT hr) case E_UNEXPECTED: return QLatin1String("E_UNEXPECTED"); case E_NOTIMPL: - return QLatin1String("E_NOTIMPL"); + return QLatin1String("E_NOTIMPL"); } if (hr == HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED)) return QLatin1String("ERROR_ACCESS_DENIED");; if (hr == HRESULT_FROM_NT(STATUS_CONTROL_C_EXIT)) return QLatin1String("STATUS_CONTROL_C_EXIT"); - return Core::Utils::winErrorMessage(HRESULT_CODE(hr)); + return QLatin1String("E_FAIL ") + Core::Utils::winErrorMessage(HRESULT_CODE(hr)); } static QString msgStackIndexOutOfRange(int idx, int size) @@ -450,14 +451,16 @@ void CdbDebugEnginePrivate::clearDisplay() bool CdbDebugEngine::startDebugger() { m_d->clearDisplay(); + const DebuggerStartMode mode = m_d->m_debuggerManager->startMode(); // Figure out dumper. @TODO: same in gdb... - bool dumperEnabled = false && m_d->m_debuggerManager->qtDumperLibraryEnabled(); - const QString dumperLibName = QDir::toNativeSeparators(m_d->m_debuggerManager->qtDumperLibraryName()); + const QString dumperLibName = QDir::toNativeSeparators(m_d->m_debuggerManagerAccess->qtDumperLibraryName()); + bool dumperEnabled = mode != AttachCore && !dumperLibName.isEmpty() + && m_d->m_debuggerManagerAccess->qtDumperLibraryEnabled(); if (dumperEnabled) { const QFileInfo fi(dumperLibName); if (!fi.isFile()) { const QString msg = tr("The dumper library '%1' does not exist.").arg(dumperLibName); - m_d->m_debuggerManager->showQtDumperLibraryWarning(msg); + m_d->m_debuggerManagerAccess->showQtDumperLibraryWarning(msg); dumperEnabled = false; } } @@ -466,7 +469,6 @@ bool CdbDebugEngine::startDebugger() QString errorMessage; bool rc = false; m_d->clearForRun(); - const DebuggerStartMode mode = m_d->m_debuggerManager->startMode(); switch (mode) { case AttachExternal: rc = startAttachDebugger(m_d->m_debuggerManager->m_attachedPID, &errorMessage); @@ -561,6 +563,52 @@ bool CdbDebugEngine::startDebuggerWithExecutable(DebuggerStartMode sm, QString * return true; } +// check for a breakpoint at 'main()' +static inline bool hasBreakPointAtMain(const BreakHandler *bp) +{ + if (const int count = bp->size()) { + // check all variations, resolved or not + const QString main = QLatin1String("main"); + const QString qMain = QLatin1String("qMain"); + const QString moduleMainPattern = QLatin1String("!main"); + for (int i = 0; i < count ; i++) { + const QString &function = bp->at(i)->funcName; + if (function == main || function == qMain || function.endsWith(moduleMainPattern)) + return true; + } + } + return false; +} + +void CdbDebugEnginePrivate::processCreatedAttached(ULONG64 processHandle, ULONG64 initialThreadHandle) +{ + setDebuggeeHandles(reinterpret_cast<HANDLE>(processHandle), reinterpret_cast<HANDLE>(initialThreadHandle)); + m_debuggerManagerAccess->notifyInferiorRunning(); + ULONG currentThreadId; + if (SUCCEEDED(m_cif.debugSystemObjects->GetThreadIdByHandle(initialThreadHandle, ¤tThreadId))) { + m_currentThreadId = currentThreadId; + } else { + m_currentThreadId = 0; + } + // Set initial breakpoints + if (m_debuggerManagerAccess->breakHandler()->hasPendingBreakpoints()) + m_engine->attemptBreakpointSynchronization(); + // At any event, we want a temporary breakpoint at main() to load + // the dumpers. + if (m_dumper.state() == CdbDumperHelper::NotLoaded) { + if (!hasBreakPointAtMain(m_debuggerManagerAccess->breakHandler())) { + CDBBreakPoint mainBP; + // Do not resolve at this point in the rare event someone + // has main in a module + mainBP.funcName = QLatin1String("main"); + mainBP.oneShot = true; + QString errorMessage; + if (!mainBP.add(m_cif.debugControl, &errorMessage)) + m_debuggerManagerAccess->showQtDumperLibraryWarning(errorMessage); + } + } +} + void CdbDebugEngine::processTerminated(unsigned long exitCode) { if (debugCDB) @@ -859,13 +907,18 @@ void CdbDebugEngine::continueInferior() } // Continue process without notifications -bool CdbDebugEnginePrivate::continueInferiorProcess(QString *errorMessage) +bool CdbDebugEnginePrivate::continueInferiorProcess(QString *errorMessagePtr /* = 0 */) { if (debugCDB) qDebug() << Q_FUNC_INFO; const HRESULT hr = m_cif.debugControl->SetExecutionStatus(DEBUG_STATUS_GO); if (FAILED(hr)) { - *errorMessage = msgComFailed("SetExecutionStatus", hr); + const QString errorMessage = msgComFailed("SetExecutionStatus", hr); + if (errorMessagePtr) { + *errorMessagePtr = errorMessage; + } else { + qWarning("continueInferiorProcess: %s\n", qPrintable(errorMessage)); + } return false; } return true; @@ -992,10 +1045,10 @@ void CdbDebugEngine::executeDebuggerCommand(const QString &command) bool CdbDebugEnginePrivate::executeDebuggerCommand(CIDebugControl *ctrl, const QString &command, QString *errorMessage) { - if (debugCDB) - qDebug() << Q_FUNC_INFO << command; // output to all clients, else we do not see anything const HRESULT hr = ctrl->ExecuteWide(DEBUG_OUTCTL_ALL_CLIENTS, command.utf16(), 0); + if (debugCDB) + qDebug() << "executeDebuggerCommand" << command << SUCCEEDED(hr); if (FAILED(hr)) { *errorMessage = QString::fromLatin1("Unable to execute '%1': %2"). arg(command, msgDebugEngineComResult(hr)); @@ -1300,10 +1353,19 @@ void CdbDebugEnginePrivate::handleDebugEvent() switch (mode) { case BreakEventHandle: + case BreakEventMain: + if (mode == BreakEventMain) + m_dumper.load(m_debuggerManager, m_debuggerManagerAccess); m_debuggerManagerAccess->notifyInferiorStopped(); updateThreadList(); updateStackTrace(); break; + case BreakEventMainLoadDumpers: + // Temp stop to load dumpers + m_dumper.load(m_debuggerManager, m_debuggerManagerAccess); + m_engine->startWatchTimer(); + continueInferiorProcess(); + break; case BreakEventIgnoreOnce: m_engine->startWatchTimer(); break; @@ -1417,17 +1479,6 @@ void CdbDebugEnginePrivate::handleModuleLoad(const QString &name) if (debugCDB>2) qDebug() << Q_FUNC_INFO << "\n " << name; updateModules(); - // Call the dumper helper hook and notify about progress. - bool ignoreNextBreakPoint; - if (m_dumper.moduleLoadHook(name, &ignoreNextBreakPoint)) { - if (m_dumper.state() == CdbDumperHelper::Loaded) - m_debuggerManagerAccess->showDebuggerOutput(QLatin1String(dumperPrefixC), QString::fromLatin1("Dumpers loaded: %1").arg(m_dumper.library())); - } else { - m_debuggerManager->showQtDumperLibraryWarning(m_dumper.errorMessage()); - m_debuggerManagerAccess->showDebuggerOutput(QLatin1String(dumperPrefixC), QString::fromLatin1("Unable to load dumpers: %1").arg(m_dumper.errorMessage())); - } - if (ignoreNextBreakPoint) - m_breakEventMode = BreakEventIgnoreOnce; } void CdbDebugEnginePrivate::handleBreakpointEvent(PDEBUG_BREAKPOINT2 pBP) @@ -1435,6 +1486,17 @@ void CdbDebugEnginePrivate::handleBreakpointEvent(PDEBUG_BREAKPOINT2 pBP) Q_UNUSED(pBP) if (debugCDB) qDebug() << Q_FUNC_INFO; + // Did we hit main() and did the user want that or is that just + // our internal BP to load the dumpers? + QString errorMessage; + CDBBreakPoint bp; + if (bp.retrieve(pBP, &errorMessage) && !bp.funcName.isEmpty()) { + if (bp.funcName == QLatin1String("main") || bp.funcName.endsWith(QLatin1String("!main"))) { + m_breakEventMode = bp.oneShot ? BreakEventMainLoadDumpers : BreakEventMain; + } + if (debugCDB) + qDebug() << bp << " b-mode=" << m_breakEventMode; + } } void CdbDebugEngine::reloadSourceFiles() diff --git a/src/plugins/debugger/cdb/cdbdebugengine_p.h b/src/plugins/debugger/cdb/cdbdebugengine_p.h index 95cdc759f9a..86ea197f0a2 100644 --- a/src/plugins/debugger/cdb/cdbdebugengine_p.h +++ b/src/plugins/debugger/cdb/cdbdebugengine_p.h @@ -98,6 +98,10 @@ struct CdbDebugEnginePrivate enum HandleBreakEventMode { // Special modes for break event handler. BreakEventHandle, BreakEventIgnoreOnce, + // We hit main (and the user intended it) + BreakEventMain, + // We hit main (and the user did not intend it, just load dumpers) + BreakEventMainLoadDumpers, BreakEventSyncBreakPoints, }; @@ -107,6 +111,7 @@ struct CdbDebugEnginePrivate bool init(QString *errorMessage); ~CdbDebugEnginePrivate(); + void processCreatedAttached(ULONG64 processHandle, ULONG64 initialThreadHandle); void setDebuggeeHandles(HANDLE hDebuggeeProcess, HANDLE hDebuggeeThread); bool isDebuggeeRunning() const { return m_watchTimer != -1; } @@ -125,7 +130,7 @@ struct CdbDebugEnginePrivate bool interruptInterferiorProcess(QString *errorMessage); - bool continueInferiorProcess(QString *errorMessage); + bool continueInferiorProcess(QString *errorMessage = 0); bool continueInferior(QString *errorMessage); bool attemptBreakpointSynchronization(QString *errorMessage); diff --git a/src/plugins/debugger/cdb/cdbdebugeventcallback.cpp b/src/plugins/debugger/cdb/cdbdebugeventcallback.cpp index 05bbb897bfe..372cda234c7 100644 --- a/src/plugins/debugger/cdb/cdbdebugeventcallback.cpp +++ b/src/plugins/debugger/cdb/cdbdebugeventcallback.cpp @@ -247,7 +247,8 @@ void formatException(const EXCEPTION_RECORD64 *e, QTextStream &str) break; case EXCEPTION_ACCESS_VIOLATION: { const bool writeOperation = e->ExceptionInformation[0]; - str << (writeOperation ? "write access violation" : "read access violation"); + str << (writeOperation ? "write" : "read") + << " access violation at: 0x" << e->ExceptionInformation[1]; } break; case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: @@ -342,8 +343,10 @@ STDMETHODIMP CdbDebugEventCallback::Exception( QString msg; { QTextStream str(&msg); - formatException(Exception, m_pEngine->m_d->m_cif, str); + formatException(Exception, m_pEngine->m_d->m_cif, str); } + if (debugCDB) + qDebug() << Q_FUNC_INFO << '\n' << msg; m_pEngine->m_d->m_debuggerManagerAccess->showApplicationOutput(msg); return S_OK; } @@ -402,18 +405,7 @@ STDMETHODIMP CdbDebugEventCallback::CreateProcess( Q_UNUSED(StartOffset) if (debugCDB) qDebug() << Q_FUNC_INFO << ModuleName; - - m_pEngine->m_d->setDebuggeeHandles(reinterpret_cast<HANDLE>(Handle), reinterpret_cast<HANDLE>(InitialThreadHandle)); - m_pEngine->m_d->m_debuggerManagerAccess->notifyInferiorRunning(); - - ULONG currentThreadId; - if (SUCCEEDED(m_pEngine->m_d->m_cif.debugSystemObjects->GetThreadIdByHandle(InitialThreadHandle, ¤tThreadId))) - m_pEngine->m_d->m_currentThreadId = currentThreadId; - else - m_pEngine->m_d->m_currentThreadId = 0; - // Set initial breakpoints - if (m_pEngine->m_d->m_debuggerManagerAccess->breakHandler()->hasPendingBreakpoints()) - m_pEngine->attemptBreakpointSynchronization(); + m_pEngine->m_d->processCreatedAttached(Handle, InitialThreadHandle); return S_OK; } diff --git a/src/plugins/debugger/cdb/cdbdumperhelper.cpp b/src/plugins/debugger/cdb/cdbdumperhelper.cpp index 16c734164e0..a0aa5142d15 100644 --- a/src/plugins/debugger/cdb/cdbdumperhelper.cpp +++ b/src/plugins/debugger/cdb/cdbdumperhelper.cpp @@ -32,11 +32,18 @@ #include "cdbdebugengine_p.h" #include "cdbdebugoutput.h" #include "cdbdebugeventcallback.h" +#include "watchutils.h" #include <QtCore/QRegExp> +#include <QtCore/QCoreApplication> +#include <QtCore/QTextStream> enum { loadDebug = 0 }; +static const char *dumperModuleNameC = "gdbmacros"; +static const char *qtCoreModuleNameC = "QtCore"; +static const ULONG waitTimeOutMS = 30000; + namespace Debugger { namespace Internal { @@ -52,7 +59,7 @@ static bool allocDebuggeeMemory(CIDebugControl *ctl, OutputRedirector redir(client, &stringHandler); if (!CdbDebugEnginePrivate::executeDebuggerCommand(ctl, allocCmd, errorMessage)) return false; - // "Allocated 1000 bytes starting at 003a0000" .. hopefully never localized + // "Allocated 1000 bytes starting at 003a0000" .. hopefully never localized bool ok = false; const QString output = stringHandler.result(); const int lastBlank = output.lastIndexOf(QLatin1Char(' ')); @@ -61,6 +68,8 @@ static bool allocDebuggeeMemory(CIDebugControl *ctl, if (ok) *address = addri; } + if (loadDebug) + qDebug() << Q_FUNC_INFO << '\n' << output << *address << ok; if (!ok) { *errorMessage = QString::fromLatin1("Failed to parse output '%1'").arg(output); return false; @@ -88,25 +97,6 @@ static bool createDebuggeeAscIIString(CIDebugControl *ctl, return true; } -// Locate 'qstrdup' in the (potentially namespaced) corelib. For some -// reason, the symbol is present in QtGui as well without type information. -static inline QString resolveStrdup(CIDebugSymbols *syms, QString *errorMessage) -{ - QStringList matches; - const QString pattern = QLatin1String("*qstrdup"); - const QRegExp corelibPattern(QLatin1String("QtCore[d]*4!")); - Q_ASSERT(corelibPattern.isValid()); - if (!searchSymbols(syms, pattern, &matches, errorMessage)) - return QString(); - QStringList corelibStrdup = matches.filter(corelibPattern); - if (corelibStrdup.isEmpty()) { - *errorMessage = QString::fromLatin1("Unable to locate '%1' in '%2' (%3)"). - arg(pattern, corelibPattern.pattern(), matches.join(QString(QLatin1Char(',')))); - return QString(); - } - return corelibStrdup.front(); -} - // Load a library into the debuggee. Currently requires // the QtCored4.pdb file to be present as we need "qstrdup" // as dummy symbol. This is ok ATM since dumpers only @@ -132,9 +122,10 @@ static bool debuggeeLoadLibrary(CIDebugControl *ctl, // server, the debugger refuses to recognize it as a function. // Set up the call stack with a function of same signature (qstrdup) // and change the call register to LoadLibraryA() before executing "g". - // Prepare call. - const QString dummyFunc = resolveStrdup(syms, errorMessage); - if (dummyFunc.isEmpty()) + // Prepare call: Locate 'qstrdup' in the (potentially namespaced) corelib. For some + // reason, the symbol is present in QtGui as well without type information. + QString dummyFunc = QLatin1String("*qstrdup"); + if (resolveSymbol(syms, QLatin1String("QtCore[d]*4!"), &dummyFunc, errorMessage) != ResolveSymbolOk) return false; QString callCmd = QLatin1String(".call "); callCmd += dummyFunc; @@ -145,80 +136,293 @@ static bool debuggeeLoadLibrary(CIDebugControl *ctl, return false; if (!CdbDebugEnginePrivate::executeDebuggerCommand(ctl, QLatin1String("r eip=Kernel32!LoadLibraryA"), errorMessage)) return false; - // This will hit a breakpoint - if (loadDebug) - qDebug() << " executing 'g'"; + // This will hit a breakpoint. if (!CdbDebugEnginePrivate::executeDebuggerCommand(ctl, QString(QLatin1Char('g')), errorMessage)) + return false; + const HRESULT hr = ctl->WaitForEvent(0, waitTimeOutMS); + if (FAILED(hr)) { + *errorMessage = msgComFailed("WaitForEvent", hr); return false; - // @Todo: We cannot evaluate output here as it is asynchronous + } return true; } -CdbDumperHelper::CdbDumperHelper(CdbComInterfaces *cif) : +// ------------------- CdbDumperHelper::DumperInputParameters +struct CdbDumperHelper::DumperInputParameters { + DumperInputParameters(int protocolVersion_ = 1, + int token_ = 1, + quint64 Address_ = 0, + bool dumpChildren_ = false, + int extraInt0_ = 0, + int extraInt1_ = 0, + int extraInt2_ = 0, + int extraInt3_ = 0); + + QString command(const QString &dumpFunction) const; + + int protocolVersion; + int token; + quint64 address; + bool dumpChildren; + int extraInt0; + int extraInt1; + int extraInt2; + int extraInt3; +}; + +CdbDumperHelper::DumperInputParameters::DumperInputParameters(int protocolVersion_, + int token_, + quint64 address_, + bool dumpChildren_, + int extraInt0_, + int extraInt1_, + int extraInt2_, + int extraInt3_) : + protocolVersion(protocolVersion_), + token(token_), + address(address_), + dumpChildren(dumpChildren_), + extraInt0(extraInt0_), + extraInt1(extraInt1_), + extraInt2(extraInt2_), + extraInt3(extraInt3_) +{ +} + +QString CdbDumperHelper::DumperInputParameters::command(const QString &dumpFunction) const +{ + QString rc; + QTextStream str(&rc); + str.setIntegerBase(16); + str << ".call " << dumpFunction << '(' << protocolVersion << ',' << token << ','; + str.setIntegerBase(16); + str << "0x" << address; + str.setIntegerBase(10); + str << ',' << (dumpChildren ? 1 : 0) << ',' << extraInt0 << ',' << extraInt1 + << ',' << extraInt2 << ',' << extraInt3 << ')'; + return rc; +} + +// ------------------- CdbDumperHelper + +CdbDumperHelper::CdbDumperHelper(CdbComInterfaces *cif) : m_state(NotLoaded), - m_cif(cif) + m_cif(cif), + m_inBufferAddress(0), + m_inBufferSize(0), + m_outBufferAddress(0), + m_outBufferSize(0), + m_buffer(0) { } +CdbDumperHelper::~CdbDumperHelper() +{ + clearBuffer(); +} + +void CdbDumperHelper::clearBuffer() +{ + if (m_buffer) { + delete [] m_buffer; + m_buffer = 0; + } +} + void CdbDumperHelper::reset(const QString &library, bool enabled) { m_library = library; m_state = enabled ? NotLoaded : Disabled; m_dumpObjectSymbol = QLatin1String("qDumpObjectData440"); - m_errorMessage.clear(); + m_knownTypes.clear(); + m_qtVersion.clear(); + m_qtNamespace.clear(); + m_inBufferAddress = m_outBufferAddress = 0; + m_inBufferSize = m_outBufferSize = 0; + clearBuffer(); } -bool CdbDumperHelper::moduleLoadHook(const QString &name, bool *ignoreNextBreakPoint) +// Attempt to load and initialize dumpers, give feedback +// to user. +void CdbDumperHelper::load(DebuggerManager *manager, IDebuggerManagerAccessForEngines *access) { - *ignoreNextBreakPoint = false; - bool ok = true; // report failure only once - switch (m_state) { - case Disabled: - break; - case NotLoaded: - // Load once QtCore is there. - if (name.contains(QLatin1String("QtCore"))) { - if (loadDebug) - qDebug() << Q_FUNC_INFO << '\n' << name << m_state << executionStatusString(m_cif->debugControl); - ok = debuggeeLoadLibrary(m_cif->debugControl, m_cif->debugClient, m_cif->debugSymbols, m_cif->debugDataSpaces, - m_library, &m_errorMessage); - if (ok) { - m_state = Loading; - *ignoreNextBreakPoint = true; - } else { - m_state = Failed; - } - if (loadDebug) - qDebug() << m_state << executionStatusString(m_cif->debugControl); + enum Result { Failed, Succeeded, NoQtApp }; + + if (m_state != NotLoaded) + return; + manager->showStatusMessage(QCoreApplication::translate("CdbDumperHelper", "Loading dumpers..."), 10000); + const QString messagePrefix = QLatin1String("dumper:"); + QString message; + Result result = Failed; + do { + // Do we have Qt and are we already loaded by accident? + QStringList modules; + if (!getModuleNameList(m_cif->debugSymbols, &modules, &message)) + break; + if (modules.filter(QLatin1String(qtCoreModuleNameC)).isEmpty()) { + message = QCoreApplication::translate("CdbDumperHelper", "The debugger does not appear to be Qt application."); + result = NoQtApp; } - break; - case Loading: - // Hurray, loaded. Now resolve the symbols we need - if (name.contains(QLatin1String("gdbmacros"))) { - ok = resolveSymbols(&m_errorMessage); - if (ok) { - m_state = Loaded; - } else { - m_state = Failed; + // Make sure the dumper lib is loaded. + if (modules.filter(QLatin1String(dumperModuleNameC), Qt::CaseInsensitive).isEmpty()) { + // Try to load + if (!debuggeeLoadLibrary(m_cif->debugControl, m_cif->debugClient, m_cif->debugSymbols, m_cif->debugDataSpaces, + m_library, &message)) { + break; } + } else { + access->showDebuggerOutput(messagePrefix, QCoreApplication::translate("CdbDumperHelper", "The dumper module appears to be already loaded.")); } + // Resolve symbols and do call to get types + if (!resolveSymbols(&message)) + break; + if (!getKnownTypes(&message)) + break; + message = QCoreApplication::translate("CdbDumperHelper", "Dumper library '%1' loaded.").arg(m_library); + result = Succeeded; + } while (false); + // eval state and notify user + switch (result) { + case Failed: + message = QCoreApplication::translate("CdbDumperHelper", "The dumper library '%1' could not be loaded:\n%2").arg(m_library, message); + access->showDebuggerOutput(messagePrefix, message); + access->showQtDumperLibraryWarning(message); + m_state = Disabled; break; - case Loaded: + case Succeeded: + access->showDebuggerOutput(messagePrefix, message); + access->showDebuggerOutput(messagePrefix, statusMessage()); + m_state = Loaded; break; - case Failed: + case NoQtApp: + access->showDebuggerOutput(messagePrefix, message); + m_state = Disabled; break; - }; - return ok; + } + if (loadDebug) + qDebug() << Q_FUNC_INFO << "\n<" << result << '>' << m_state << message; +} + +QString CdbDumperHelper::statusMessage() const +{ + const QString nameSpace = m_qtNamespace.isEmpty() ? QCoreApplication::translate("CdbDumperHelper", "<none>") : m_qtNamespace; + return QCoreApplication::translate("CdbDumperHelper", + "%n known types, Qt version: %1, Qt namespace: %2", + 0, QCoreApplication::CodecForTr, + m_knownTypes.size()).arg(m_qtVersion, nameSpace); +} + +// Retrieve address and optionally size of a symbol. +static inline bool getSymbolAddress(CIDebugSymbols *sg, + const QString &name, + quint64 *address, + ULONG *size /* = 0*/, + QString *errorMessage) +{ + // Get address + HRESULT hr = sg->GetOffsetByNameWide(name.utf16(), address); + if (FAILED(hr)) { + *errorMessage = msgComFailed("GetOffsetByNameWide", hr); + return false; + } + // Get size. Even works for arrays + if (size) { + ULONG64 moduleAddress; + ULONG type; + hr = sg->GetOffsetTypeId(*address, &type, &moduleAddress); + if (FAILED(hr)) { + *errorMessage = msgComFailed("GetOffsetTypeId", hr); + return false; + } + hr = sg->GetTypeSize(moduleAddress, type, size); + if (FAILED(hr)) { + *errorMessage = msgComFailed("GetTypeSize", hr); + return false; + } + } // size desired + return true; } bool CdbDumperHelper::resolveSymbols(QString *errorMessage) { - // Resolve the symbols we need - m_dumpObjectSymbol = QLatin1String("qDumpObjectData440"); - const bool rc = resolveSymbol(m_cif->debugSymbols, &m_dumpObjectSymbol, errorMessage) == ResolveSymbolOk; + // Resolve the symbols we need (potentially namespaced). + // There is a 'qDumpInBuffer' in QtCore as well. + m_dumpObjectSymbol = QLatin1String("*qDumpObjectData440"); + QString inBufferSymbol = QLatin1String("*qDumpInBuffer"); + QString outBufferSymbol = QLatin1String("*qDumpOutBuffer"); + const QString dumperModuleName = QLatin1String(dumperModuleNameC); + bool rc = resolveSymbol(m_cif->debugSymbols, &m_dumpObjectSymbol, errorMessage) == ResolveSymbolOk + && resolveSymbol(m_cif->debugSymbols, dumperModuleName, &inBufferSymbol, errorMessage) == ResolveSymbolOk + && resolveSymbol(m_cif->debugSymbols, dumperModuleName, &outBufferSymbol, errorMessage) == ResolveSymbolOk; + if (!rc) + return false; + // Determine buffer addresses, sizes and alloc buffer + rc = getSymbolAddress(m_cif->debugSymbols, inBufferSymbol, &m_inBufferAddress, &m_inBufferSize, errorMessage) + && getSymbolAddress(m_cif->debugSymbols, outBufferSymbol, &m_outBufferAddress, &m_outBufferSize, errorMessage); + if (!rc) + return false; + m_buffer = new char[qMax(m_inBufferSize, m_outBufferSize)]; if (loadDebug) - qDebug() << Q_FUNC_INFO << '\n' << rc << m_dumpObjectSymbol; - return rc; + qDebug().nospace() << Q_FUNC_INFO << '\n' << rc << m_dumpObjectSymbol + << " buffers at 0x" << QString::number(m_inBufferAddress, 16) << ',' + << m_inBufferSize << "; 0x" + << QString::number(m_outBufferAddress, 16) << ',' << m_outBufferSize << '\n'; + return true; +} + +bool CdbDumperHelper::getKnownTypes(QString *errorMessage) +{ + QByteArray output; + if (!callDumper(DumperInputParameters(1), &output, errorMessage)) { + return false; + } + if (!parseQueryDumperOutput(output, &m_knownTypes, &m_qtVersion, &m_qtNamespace)) { + *errorMessage = QString::fromLatin1("Unable to parse the dumper output: '%1'").arg(QString::fromAscii(output)); + } + if (loadDebug) + qDebug() << Q_FUNC_INFO << m_knownTypes << m_qtVersion << m_qtNamespace; + return true; +} + +bool CdbDumperHelper::callDumper(const DumperInputParameters &p, QByteArray *output, QString *errorMessage) +{ + IgnoreDebugEventCallback devNull; + EventCallbackRedirector eventRedir(m_cif->debugClient, &devNull); + const QString callCmd = p.command(m_dumpObjectSymbol); + // Set up call and a temporary breakpoint after it. + if (!CdbDebugEnginePrivate::executeDebuggerCommand(m_cif->debugControl, callCmd, errorMessage)) + return false; + // Go and filter away break event + if (!CdbDebugEnginePrivate::executeDebuggerCommand(m_cif->debugControl, QString(QLatin1Char('g')), errorMessage)) + return false; + HRESULT hr = m_cif->debugControl->WaitForEvent(0, waitTimeOutMS); + if (FAILED(hr)) { + *errorMessage = msgComFailed("WaitForEvent", hr); + return false; + } + // Read output + hr = m_cif->debugDataSpaces->ReadVirtual(m_outBufferAddress, m_buffer, m_outBufferSize, 0); + if (FAILED(hr)) { + *errorMessage = msgComFailed("ReadVirtual", hr); + return false; + } + // see QDumper implementation + const char result = m_buffer[0]; + switch (result) { + case 't': + break; + case '+': + *errorMessage = QString::fromLatin1("Dumper call '%1' resulted in output overflow.").arg(callCmd); + return false; + case 'f': + *errorMessage = QString::fromLatin1("Dumper call '%1' failed.").arg(callCmd); + return false; + default: + *errorMessage = QString::fromLatin1("Dumper call '%1' failed ('%2').").arg(callCmd).arg(QLatin1Char(result)); + return false; + } + *output = QByteArray(m_buffer + 1); + return true; } } // namespace Internal diff --git a/src/plugins/debugger/cdb/cdbdumperhelper.h b/src/plugins/debugger/cdb/cdbdumperhelper.h index 8947acd134c..c5be52e22f4 100644 --- a/src/plugins/debugger/cdb/cdbdumperhelper.h +++ b/src/plugins/debugger/cdb/cdbdumperhelper.h @@ -30,55 +30,77 @@ #ifndef CDBDUMPERHELPER_H #define CDBDUMPERHELPER_H -#include <QtCore/QString> +#include <QtCore/QStringList> namespace Debugger { namespace Internal { struct CdbComInterfaces; - -// For code clarity, all the stuff related to custom dumpers -// goes here. -// "Custom dumper" is a library compiled against the current -// Qt containing functions to evaluate values of Qt classes -// (such as QString, taking pointers to their addresses). -// The library must be loaded into the debuggee. +class IDebuggerManagerAccessForEngines; +class DebuggerManager; + +/* For code clarity, all the stuff related to custom dumpers + * goes here. + * "Custom dumper" is a library compiled against the current + * Qt containing functions to evaluate values of Qt classes + * (such as QString, taking pointers to their addresses). + * The library must be loaded into the debuggee. + * Loading the dumpers requires making the debuggee call functions + * (LoadLibrary() and the dumper functions). This only works if the + * debuggee is in a 'well-defined' breakpoint state (such as at 'main()'). + * Calling the load functions from an IDebugEvent callback causes + * WaitForEvent() to fail with unknown errors. Calling the load functions from an + * non 'well-defined' (arbitrary) breakpoint state will cause LoadLibrary + * to trigger an access violations. + * Currently, we call the load function when stopping at 'main()' for which + * we set a temporary break point if the user does not want to stop there. */ class CdbDumperHelper { + Q_DISABLE_COPY(CdbDumperHelper) public: enum State { Disabled, NotLoaded, - Loading, Loaded, Failed }; explicit CdbDumperHelper(CdbComInterfaces *cif); + ~CdbDumperHelper(); + + State state() const { return m_state; } + operator bool() const { return m_state == Loaded; } // Call before starting the debugger void reset(const QString &library, bool enabled); - // Call from the module loaded event handler. - // It will load the dumper library and resolve the required symbols - // when appropriate. - bool moduleLoadHook(const QString &name, bool *ignoreNextBreakPoint); - - State state() const { return m_state; } - - QString errorMessage() const { return m_errorMessage; } - QString library() const { return m_library; } + // Call in a temporary breakpoint state to actually load. + void load(DebuggerManager *manager, IDebuggerManagerAccessForEngines *access); private: + struct DumperInputParameters; + + void clearBuffer(); bool resolveSymbols(QString *errorMessage); + bool getKnownTypes(QString *errorMessage); + bool callDumper(const DumperInputParameters &p, QByteArray *output, QString *errorMessage); + inline QString statusMessage() const; State m_state; CdbComInterfaces *m_cif; QString m_library; QString m_dumpObjectSymbol; - QString m_errorMessage; + QStringList m_knownTypes; + QString m_qtVersion; + QString m_qtNamespace; + + quint64 m_inBufferAddress; + unsigned long m_inBufferSize; + quint64 m_outBufferAddress; + unsigned long m_outBufferSize; + char *m_buffer; }; } // namespace Internal diff --git a/src/plugins/debugger/cdb/cdbmodules.cpp b/src/plugins/debugger/cdb/cdbmodules.cpp index d3e4250e5c8..8aef06ae03f 100644 --- a/src/plugins/debugger/cdb/cdbmodules.cpp +++ b/src/plugins/debugger/cdb/cdbmodules.cpp @@ -32,25 +32,47 @@ #include "cdbdebugengine_p.h" #include <QtCore/QFileInfo> +#include <QtCore/QRegExp> namespace Debugger { namespace Internal { -bool getModuleList(CIDebugSymbols *syms, QList<Module> *modules, QString *errorMessage) -{ - modules->clear(); +static inline bool getModuleCount(CIDebugSymbols *syms, ULONG *count, QString *errorMessage) +{ + *count = 0; ULONG loadedCount, unloadedCount; - HRESULT hr = syms->GetNumberModules(&loadedCount, &unloadedCount); + const HRESULT hr = syms->GetNumberModules(&loadedCount, &unloadedCount); if (FAILED(hr)) { *errorMessage= msgComFailed("GetNumberModules", hr); return false; } - // retrieve array of parameters - const ULONG count = loadedCount + unloadedCount; + *count = loadedCount + unloadedCount; + return true; +} + +bool getModuleNameList(CIDebugSymbols *syms, QStringList *modules, QString *errorMessage) +{ + ULONG count; + modules->clear(); + if (!getModuleCount(syms, &count, errorMessage)) + return false; + WCHAR wszBuf[MAX_PATH]; + for (ULONG m = 0; m < count; m++) + if (SUCCEEDED(syms->GetModuleNameStringWide(DEBUG_MODNAME_IMAGE, m, 0, wszBuf, MAX_PATH - 1, 0))) + modules->push_back(QString::fromUtf16(wszBuf)); + return true; +} + +bool getModuleList(CIDebugSymbols *syms, QList<Module> *modules, QString *errorMessage) +{ + ULONG count; + modules->clear(); + if (!getModuleCount(syms, &count, errorMessage)) + return false; QVector<DEBUG_MODULE_PARAMETERS> parameters(count); DEBUG_MODULE_PARAMETERS *parmPtr = &(*parameters.begin()); memset(parmPtr, 0, sizeof(DEBUG_MODULE_PARAMETERS) * count); - hr = syms->GetModuleParameters(count, 0, 0u, parmPtr); + HRESULT hr = syms->GetModuleParameters(count, 0, 0u, parmPtr); // E_INVALIDARG indicates 'Partial results' according to docu if (FAILED(hr) && hr != E_INVALIDARG) { *errorMessage= msgComFailed("GetModuleParameters", hr); @@ -67,7 +89,7 @@ bool getModuleList(CIDebugSymbols *syms, QList<Module> *modules, QString *errorM && (p.SymbolType != DEBUG_SYMTYPE_NONE); module.startAddress = hexPrefix + QString::number(p.Base, 16); module.endAddress = hexPrefix + QString::number((p.Base + p.Size), 16); - hr = syms ->GetModuleNameStringWide(DEBUG_MODNAME_IMAGE, m, 0, wszBuf, MAX_PATH - 1, 0); + hr = syms->GetModuleNameStringWide(DEBUG_MODNAME_IMAGE, m, 0, wszBuf, MAX_PATH - 1, 0); if (FAILED(hr) && hr != E_INVALIDARG) { *errorMessage= msgComFailed("GetModuleNameStringWide", hr); return false; @@ -111,10 +133,10 @@ bool searchSymbols(CIDebugSymbols *syms, const QString &pattern, return true; } -// Add missing the module specifier: "main" -> "project!main" - -ResolveSymbolResult resolveSymbol(CIDebugSymbols *syms, QString *symbol, - QString *errorMessage) +// Helper for the resolveSymbol overloads. +static ResolveSymbolResult resolveSymbol(CIDebugSymbols *syms, QString *symbol, + QStringList *matches, + QString *errorMessage) { // Is it an incomplete symbol? if (symbol->contains(QLatin1Char('!'))) @@ -123,20 +145,63 @@ ResolveSymbolResult resolveSymbol(CIDebugSymbols *syms, QString *symbol, if (*symbol == QLatin1String("qMain")) *symbol = QLatin1String("main"); // resolve - QStringList matches; - if (!searchSymbols(syms, *symbol, &matches, errorMessage)) + if (!searchSymbols(syms, *symbol, matches, errorMessage)) return ResolveSymbolError; - if (matches.empty()) + if (matches->empty()) return ResolveSymbolNotFound; - *symbol = matches.front(); - if (matches.size() > 1) { + *symbol = matches->front(); + if (matches->size() > 1) { *errorMessage = QString::fromLatin1("Ambiguous symbol '%1': %2"). - arg(*symbol, matches.join(QString(QLatin1Char(' ')))); + arg(*symbol, matches->join(QString(QLatin1Char(' ')))); return ResolveSymbolAmbiguous; } return ResolveSymbolOk; } +// Add missing the module specifier: "main" -> "project!main" +ResolveSymbolResult resolveSymbol(CIDebugSymbols *syms, QString *symbol, + QString *errorMessage) +{ + QStringList matches; + return resolveSymbol(syms, symbol, &matches, errorMessage); +} + +ResolveSymbolResult resolveSymbol(CIDebugSymbols *syms, const QString &pattern, QString *symbol, QString *errorMessage) +{ + QStringList matches; + const ResolveSymbolResult r1 = resolveSymbol(syms, symbol, &matches, errorMessage); + switch (r1) { + case ResolveSymbolOk: + case ResolveSymbolNotFound: + case ResolveSymbolError: + return r1; + case ResolveSymbolAmbiguous: + break; + } + // Filter out + errorMessage->clear(); + const QRegExp re(pattern); + if (!re.isValid()) { + *errorMessage = QString::fromLatin1("Internal error: Invalid pattern '%1'.").arg(pattern); + return ResolveSymbolError; + } + const QStringList filteredMatches = matches.filter(re); + if (filteredMatches.size() == 1) { + *symbol = filteredMatches.front(); + return ResolveSymbolOk; + } + // something went wrong + const QString matchesString = matches.join(QString(QLatin1Char(','))); + if (filteredMatches.empty()) { + *errorMessage = QString::fromLatin1("None of symbols '%1' found for '%2' matches '%3'."). + arg(matchesString, *symbol, pattern); + return ResolveSymbolNotFound; + } + *errorMessage = QString::fromLatin1("Ambiguous match of symbols '%1' found for '%2' (%3)"). + arg(matchesString, *symbol, pattern); + return ResolveSymbolAmbiguous; +} + // List symbols of a module bool getModuleSymbols(CIDebugSymbols *syms, const QString &moduleName, QList<Symbol> *symbols, QString *errorMessage) diff --git a/src/plugins/debugger/cdb/cdbmodules.h b/src/plugins/debugger/cdb/cdbmodules.h index 9df7dbf28b8..79030aea243 100644 --- a/src/plugins/debugger/cdb/cdbmodules.h +++ b/src/plugins/debugger/cdb/cdbmodules.h @@ -30,8 +30,7 @@ #ifndef CDBMODULES_H #define CDBMODULES_H -#include <QtCore/QList> -#include <QtCore/QString> +#include <QtCore/QStringList> #include "cdbcom.h" @@ -42,7 +41,8 @@ class Module; class Symbol; bool getModuleList(CIDebugSymbols *syms, QList<Module> *modules, QString *errorMessage); -// Search symbols matching a pattern +bool getModuleNameList(CIDebugSymbols *syms, QStringList *modules, QString *errorMessage); +// Search symbols matching a pattern. Does not filter on module names. bool searchSymbols(CIDebugSymbols *syms, const QString &pattern, QStringList *matches, QString *errorMessage); @@ -52,8 +52,12 @@ bool searchSymbols(CIDebugSymbols *syms, const QString &pattern, enum ResolveSymbolResult { ResolveSymbolOk, ResolveSymbolAmbiguous, ResolveSymbolNotFound, ResolveSymbolError }; +// Resolve a symbol that is unique to all modules ResolveSymbolResult resolveSymbol(CIDebugSymbols *syms, QString *symbol, QString *errorMessage); +// Resolve symbol overload with an additional regexp pattern to filter on modules. +ResolveSymbolResult resolveSymbol(CIDebugSymbols *syms, const QString &pattern, QString *symbol, QString *errorMessage); + // List symbols of a module bool getModuleSymbols(CIDebugSymbols *syms, const QString &moduleName, QList<Symbol> *symbols, QString *errorMessage); diff --git a/src/plugins/debugger/debuggermanager.h b/src/plugins/debugger/debuggermanager.h index e5117845a95..821c697fb27 100644 --- a/src/plugins/debugger/debuggermanager.h +++ b/src/plugins/debugger/debuggermanager.h @@ -145,6 +145,7 @@ private: friend class CdbDebugEventCallback; friend class ScriptEngine; friend struct CdbDebugEnginePrivate; + friend class CdbDumperHelper; // called from the engines after successful startup virtual void notifyInferiorStopRequested() = 0; @@ -171,6 +172,11 @@ private: virtual void reloadModules() = 0; virtual void reloadSourceFiles() = 0; virtual void reloadRegisters() = 0; + + virtual bool qtDumperLibraryEnabled() const = 0; + virtual QString qtDumperLibraryName() const = 0; + virtual void showQtDumperLibraryWarning(const QString &details = QString()) = 0; + }; @@ -252,10 +258,6 @@ public slots: void showStatusMessage(const QString &msg, int timeout = -1); // -1 forever - bool qtDumperLibraryEnabled() const; - QString qtDumperLibraryName() const; - void showQtDumperLibraryWarning(const QString &details); - private slots: void showDebuggerOutput(const QString &prefix, const QString &msg); void showDebuggerInput(const QString &prefix, const QString &msg); @@ -307,6 +309,10 @@ private: QList<QDockWidget*> dockWidgets() const { return m_dockWidgets; } void createDockWidgets(); + virtual bool qtDumperLibraryEnabled() const; + virtual QString qtDumperLibraryName() const; + virtual void showQtDumperLibraryWarning(const QString &details = QString()); + // // internal implementation // diff --git a/src/plugins/debugger/gdbengine.cpp b/src/plugins/debugger/gdbengine.cpp index 5b30a331c30..b9b35ce4d76 100644 --- a/src/plugins/debugger/gdbengine.cpp +++ b/src/plugins/debugger/gdbengine.cpp @@ -4135,16 +4135,16 @@ void GdbEngine::tryLoadDebuggingHelpers() PENDING_DEBUG("TRY LOAD CUSTOM DUMPERS"); m_debuggingHelperState = DebuggingHelperUnavailable; - if (!q->qtDumperLibraryEnabled()) + if (!qq->qtDumperLibraryEnabled()) return; - const QString lib = q->qtDumperLibraryName(); + const QString lib = qq->qtDumperLibraryName(); //qDebug() << "DUMPERLIB: " << lib; // @TODO: same in CDB engine... const QFileInfo fi(lib); if (!fi.exists()) { const QString msg = tr("The dumper library '%1' does not exist.").arg(lib); debugMessage(msg); - q->showQtDumperLibraryWarning(msg); + qq->showQtDumperLibraryWarning(msg); return; } diff --git a/src/plugins/debugger/watchutils.cpp b/src/plugins/debugger/watchutils.cpp index 993bd1a914a..cc1a9784870 100644 --- a/src/plugins/debugger/watchutils.cpp +++ b/src/plugins/debugger/watchutils.cpp @@ -299,5 +299,58 @@ QString sizeofTypeExpression(const QString &type) return QLatin1String("sizeof(") + gdbQuoteTypes(type) + QLatin1Char(')'); } +/* Parse 'query' (1) protocol response of the custom dumpers: + * "'dumpers=["QByteArray","QDateTime",..."std::basic_string",], + * qtversion=["4","5","1"],namespace="""' */ + +bool parseQueryDumperOutput(const QByteArray &a, QStringList *types, QString *qtVersion, QString *qtNamespace) +{ + types->clear(); + qtVersion->clear(); + qtNamespace->clear(); + const QChar equals = QLatin1Char('='); + const QChar doubleQuote = QLatin1Char('"'); + const QChar openingBracket = QLatin1Char('['); + const QChar closingBracket = QLatin1Char(']'); + const QString dumperKey = QLatin1String("dumpers"); + const QString versionKey = QLatin1String("qtversion"); + const QString namespaceKey = QLatin1String("namespace"); + + const QString s = QString::fromLatin1(a); + const int size = a.size(); + for (int pos = 0; pos < size; ) { + // split into keyword / value pairs + const int equalsPos = s.indexOf(equals, pos); + if (equalsPos == -1) + break; + const QStringRef keyword = s.midRef(pos, equalsPos - pos); + // Array or flat value. Cut out value without delimiters + int valuePos = equalsPos + 1; + const QChar endChar = s.at(valuePos) == doubleQuote ? doubleQuote : closingBracket; + valuePos++; + int endValuePos = s.indexOf(endChar, valuePos); + if (endValuePos == -1) + return false; + QString value = s.mid(valuePos, endValuePos - valuePos); + pos = endValuePos; + // Evaluate + if (keyword == namespaceKey) { + *qtNamespace = value; + } else { + if (keyword == versionKey) { + *qtVersion = value.remove(doubleQuote).replace(QLatin1Char(','), QLatin1Char('.')); + } else { + if (keyword == dumperKey) { + *types = value.remove(doubleQuote).split(QLatin1Char(',')); + } + } + } + // find next keyword + while (pos < size && !s.at(pos).isLetterOrNumber()) + pos++; + } + return true; +} + } } diff --git a/src/plugins/debugger/watchutils.h b/src/plugins/debugger/watchutils.h index 6c7dbb9143a..392f0e0c3fd 100644 --- a/src/plugins/debugger/watchutils.h +++ b/src/plugins/debugger/watchutils.h @@ -34,6 +34,7 @@ QT_BEGIN_NAMESPACE class QString; +class QByteArray; QT_END_NAMESPACE namespace Debugger { @@ -63,6 +64,8 @@ QString extractTypeFromPTypeOutput(const QString &str); bool isIntOrFloatType(const QString &type); QString sizeofTypeExpression(const QString &type); +// Parse 'query' (1) protocol response of the custom dumpers +bool parseQueryDumperOutput(const QByteArray &a, QStringList *types, QString *qtVersion, QString *qtNamespace); } // namespace Internal } // namespace Debugger -- GitLab