diff --git a/share/qtcreator/gdbmacros/gdbmacros.cpp b/share/qtcreator/gdbmacros/gdbmacros.cpp index df6004a735e23d0f5f740f5041b6c42067e03a19..7c766dff31ec64c8d9b1b834ddf26fa5f596fc6e 100644 --- a/share/qtcreator/gdbmacros/gdbmacros.cpp +++ b/share/qtcreator/gdbmacros/gdbmacros.cpp @@ -60,6 +60,10 @@ int qtGhVersion = QT_VERSION; # include <QtGui/QImage> #endif +#ifdef Q_OS_WIN +# include <windows.h> +#endif + #include <list> #include <map> #include <string> @@ -231,11 +235,17 @@ static QByteArray stripPointerType(QByteArray type) } // This is used to abort evaluation of custom data dumpers in a "coordinated" -// way. Abortion will happen anyway when we try to access a non-initialized +// way. Abortion will happen at the latest when we try to access a non-initialized // non-trivial object, so there is no way to prevent this from occuring at all -// conceptionally. Gdb will catch SIGSEGV and return to the calling frame. -// This is just fine provided we only _read_ memory in the custom handlers -// below. +// conceptionally. Ideally, if there is API to check memory access, it should +// be used to terminate nicely, especially with CDB. +// 1) Gdb will catch SIGSEGV and return to the calling frame. +// This is just fine provided we only _read_ memory in the custom handlers +// below. +// 2) For MSVC/CDB, exceptions must be handled in the dumper, which is +// achieved using __try/__except. The exception will be reported in the +// debugger, which will then execute a 'gN' command, passing handling back +// to the __except clause. volatile int qProvokeSegFaultHelper; @@ -269,11 +279,16 @@ static bool startsWith(const char *s, const char *t) return qstrncmp(s, t, qstrlen(t)) == 0; } -// provoke segfault when address is not readable -#define qCheckAccess(d) do { qProvokeSegFaultHelper = *(char*)d; } while (0) -#define qCheckPointer(d) do { if (d) qProvokeSegFaultHelper = *(char*)d; } while (0) -// provoke segfault unconditionally -#define qCheck(b) do { if (!(b)) qProvokeSegFaultHelper = *(char*)0; } while (0) +// Check memory for read access and provoke segfault if nothing else helps. +// On Windows, try to be less crash-prone by checking memory using WinAPI + +#ifdef Q_OS_WIN +# define qCheckAccess(d) if (IsBadReadPtr(d, 1)) return; do { qProvokeSegFaultHelper = *(char*)d; } while (0) +# define qCheckPointer(d) if (d && IsBadReadPtr(d, 1)) return; do { if (d) qProvokeSegFaultHelper = *(char*)d; } while (0) +#else +# define qCheckAccess(d) do { qProvokeSegFaultHelper = *(char*)d; } while (0) +# define qCheckPointer(d) do { if (d) qProvokeSegFaultHelper = *(char*)d; } while (0) +#endif const char *stripNamespace(const char *type) { @@ -692,11 +707,14 @@ void QDumper::putEllipsis() #define TT(type, value) \ "<tr><td>" << type << "</td><td> : </td><td>" << value << "</td></tr>" -static void qDumpUnknown(QDumper &d) +#define DUMPUNKNOWN_MESSAGE "<internal error>" +static void qDumpUnknown(QDumper &d, const char *why = 0) { P(d, "iname", d.iname); P(d, "addr", d.data); - P(d, "value", "<internal error>"); + if (!why) + why = DUMPUNKNOWN_MESSAGE; + P(d, "value", why); P(d, "type", d.outertype); P(d, "numchild", "0"); d.disarm(); @@ -1078,7 +1096,7 @@ static void qDumpQHash(QDumper &d) int n = h->size; if (n < 0) - qCheck(false); + return; if (n > 0) { qCheckPointer(h->fakeNext); qCheckPointer(*h->buckets); @@ -1181,19 +1199,21 @@ static void qDumpQList(QDumper &d) { // This uses the knowledge that QList<T> has only a single member // of type union { QListData p; QListData::Data *d; }; + const QListData &ldata = *reinterpret_cast<const QListData*>(d.data); const QListData::Data *pdata = *reinterpret_cast<const QListData::Data* const*>(d.data); + qCheckAccess(pdata); int nn = ldata.size(); if (nn < 0) - qCheck(false); + return; if (nn > 0) { qCheckAccess(ldata.d->array); //qCheckAccess(ldata.d->array[0]); //qCheckAccess(ldata.d->array[nn - 1]); #if QT_VERSION >= 0x040400 if (ldata.d->ref._q_value <= 0) - qCheck(false); + return; #endif } @@ -1262,7 +1282,7 @@ static void qDumpQLinkedList(QDumper &d) reinterpret_cast<const QLinkedListData*>(deref(d.data)); int nn = ldata->size; if (nn < 0) - qCheck(false); + return; int n = nn; P(d, "value", "<" << n << " items>"); @@ -1386,7 +1406,7 @@ static void qDumpQMap(QDumper &d) int n = h->size; if (n < 0) - qCheck(false); + return; if (n > 0) { qCheckAccess(h->backward); qCheckAccess(h->forward[0]); @@ -1899,7 +1919,7 @@ static void qDumpQSet(QDumper &d) int n = hd->size; if (n < 0) - qCheck(false); + return; if (n > 0) { qCheckAccess(node); qCheckPointer(node->next); @@ -1952,8 +1972,23 @@ static void qDumpQSharedPointer(QDumper &d) P(d, "name", "data"); qDumpInnerValue(d, d.innertype, ptr.data()); d.endHash(); - I(d, "strongref", 44); - I(d, "weakref", 45); + const int v = sizeof(void *); + d.beginHash(); + const void *weak = addOffset(deref(addOffset(d.data, v)), v); + P(d, "name", "weakref"); + P(d, "value", *static_cast<const int *>(weak)); + P(d, "type", "int"); + P(d, "addr", weak); + P(d, "numchild", "0"); + d.endHash(); + d.beginHash(); + const void *strong = addOffset(weak, sizeof(int)); + P(d, "name", "strongref"); + P(d, "value", *static_cast<const int *>(strong)); + P(d, "type", "int"); + P(d, "addr", strong); + P(d, "numchild", "0"); + d.endHash(); d << "]"; } d.disarm(); @@ -1982,7 +2017,7 @@ static void qDumpQStringList(QDumper &d) const QStringList &list = *reinterpret_cast<const QStringList *>(d.data); int n = list.size(); if (n < 0) - qCheck(false); + return; if (n > 0) { qCheckAccess(&list.front()); qCheckAccess(&list.back()); @@ -2117,7 +2152,7 @@ static void qDumpQVector(QDumper &d) // from asking for unavailable child details int nn = v->size; if (nn < 0) - qCheck(false); + return; if (nn > 0) { //qCheckAccess(&vec.front()); //qCheckAccess(&vec.back()); @@ -2209,7 +2244,8 @@ static void qDumpStdMap(QDumper &d) p = deref(p); int nn = map.size(); - qCheck(nn >= 0); + if (nn < 0) + return; DummyType::const_iterator it = map.begin(); for (int i = 0; i < nn && i < 10 && it != map.end(); ++i, ++it) qCheckAccess(it.operator->()); @@ -2274,7 +2310,8 @@ static void qDumpStdSet(QDumper &d) p = deref(p); int nn = set.size(); - qCheck(nn >= 0); + if (nn < 0) + return; DummyType::const_iterator it = set.begin(); for (int i = 0; i < nn && i < 10 && it != set.end(); ++i, ++it) qCheckAccess(it.operator->()); @@ -2361,7 +2398,7 @@ static void qDumpStdVector(QDumper &d) // from asking for unavailable child details int nn = (v->finish - v->start) / d.extraInt[0]; if (nn < 0) - qCheck(false); + return; if (nn > 0) { qCheckAccess(v->start); qCheckAccess(v->finish); @@ -2402,10 +2439,14 @@ static void qDumpStdVectorBool(QDumper &d) static void handleProtocolVersion2and3(QDumper & d) { + if (!d.outertype[0]) { qDumpUnknown(d); return; } +#ifdef Q_CC_MSVC // Catch exceptions with MSVC/CDB + __try { +#endif d.setupTemplateParameters(); P(d, "iname", d.iname); @@ -2551,6 +2592,12 @@ static void handleProtocolVersion2and3(QDumper & d) if (!d.success) qDumpUnknown(d); +#ifdef Q_CC_MSVC // Catch exceptions with MSVC/CDB + } __except(EXCEPTION_EXECUTE_HANDLER) { + qDumpUnknown(d, DUMPUNKNOWN_MESSAGE" <exception>"); + } +#endif + } } // anonymous namespace diff --git a/src/plugins/coreplugin/outputpane.cpp b/src/plugins/coreplugin/outputpane.cpp index c1cb59462ccb6948733221504149d5bd3690a2a6..e141473162d12ad1ae0d9b88e2594617e5239ed4 100644 --- a/src/plugins/coreplugin/outputpane.cpp +++ b/src/plugins/coreplugin/outputpane.cpp @@ -384,7 +384,7 @@ void OutputPaneManager::ensurePageVisible(int idx) } } - +// Slot connected to showPage signal of each page void OutputPaneManager::showPage(bool focus) { int idx = findIndexForPage(qobject_cast<IOutputPane*>(sender())); diff --git a/src/plugins/debugger/cdb/cdb.pri b/src/plugins/debugger/cdb/cdb.pri index c5680e705cc88c09879a5119ec6cc3bac225dc24..b602ef639a3619f6c45fde00a9d799aae8bf190b 100644 --- a/src/plugins/debugger/cdb/cdb.pri +++ b/src/plugins/debugger/cdb/cdb.pri @@ -34,6 +34,7 @@ HEADERS += \ $$PWD/cdbdebugoutput.h \ $$PWD/cdbsymbolgroupcontext.h \ $$PWD/cdbstacktracecontext.h \ + $$PWD/cdbstackframecontext.h \ $$PWD/cdbbreakpoint.h \ $$PWD/cdbmodules.h \ $$PWD/cdbassembler.h \ @@ -46,6 +47,7 @@ SOURCES += \ $$PWD/cdbdebugeventcallback.cpp \ $$PWD/cdbdebugoutput.cpp \ $$PWD/cdbsymbolgroupcontext.cpp \ + $$PWD/cdbstackframecontext.cpp \ $$PWD/cdbstacktracecontext.cpp \ $$PWD/cdbbreakpoint.cpp \ $$PWD/cdbmodules.cpp \ diff --git a/src/plugins/debugger/cdb/cdbdebugengine.cpp b/src/plugins/debugger/cdb/cdbdebugengine.cpp index a60057071b638cfc9aa6986b9027e8b4bcaa6532..e79d9dfdb5e952b78f74ee83eef32f53e593675e 100644 --- a/src/plugins/debugger/cdb/cdbdebugengine.cpp +++ b/src/plugins/debugger/cdb/cdbdebugengine.cpp @@ -29,8 +29,9 @@ #include "cdbdebugengine.h" #include "cdbdebugengine_p.h" -#include "cdbsymbolgroupcontext.h" #include "cdbstacktracecontext.h" +#include "cdbstackframecontext.h" +#include "cdbsymbolgroupcontext.h" #include "cdbbreakpoint.h" #include "cdbmodules.h" #include "cdbassembler.h" @@ -274,7 +275,7 @@ CdbDebugEnginePrivate::CdbDebugEnginePrivate(DebuggerManager *parent, m_hDebuggeeProcess(0), m_hDebuggeeThread(0), m_breakEventMode(BreakEventHandle), - m_dumper(&m_cif), + m_dumper(new CdbDumperHelper(parent, &m_cif)), m_watchTimer(-1), m_debugEventCallBack(engine), m_engine(engine), @@ -464,7 +465,7 @@ bool CdbDebugEngine::startDebugger() dumperEnabled = false; } } - m_d->m_dumper.reset(dumperLibName, dumperEnabled); + m_d->m_dumper->reset(dumperLibName, dumperEnabled); m_d->m_debuggerManager->showStatusMessage("Starting Debugger", -1); QString errorMessage; bool rc = false; @@ -590,19 +591,20 @@ void CdbDebugEnginePrivate::processCreatedAttached(ULONG64 processHandle, ULONG6 } else { m_currentThreadId = 0; } - // Set initial breakpoints + // Clear any saved breakpoints and set initial breakpoints + m_engine->executeDebuggerCommand(QLatin1String("bc")); 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 (m_dumper->state() == CdbDumperHelper::NotLoaded) { if (!hasBreakPointAtMain(m_debuggerManagerAccess->breakHandler())) { + QString errorMessage; 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); } @@ -675,13 +677,13 @@ void CdbDebugEngine::exitDebugger() killWatchTimer(); } -CdbSymbolGroupContext *CdbDebugEnginePrivate::getStackFrameSymbolGroupContext(int frameIndex, QString *errorMessage) const +CdbStackFrameContext *CdbDebugEnginePrivate::getStackFrameContext(int frameIndex, QString *errorMessage) const { if (!m_currentStackTrace) { *errorMessage = QLatin1String(msgNoStackTraceC); return 0; } - if (CdbSymbolGroupContext *sg = m_currentStackTrace->symbolGroupContextAt(frameIndex, errorMessage)) + if (CdbStackFrameContext *sg = m_currentStackTrace->frameContextAt(frameIndex, errorMessage)) return sg; return 0; } @@ -718,8 +720,8 @@ bool CdbDebugEnginePrivate::updateLocals(int frameIndex, } bool success = false; - if (CdbSymbolGroupContext *sgc = getStackFrameSymbolGroupContext(frameIndex, errorMessage)) - success = CdbSymbolGroupContext::populateModelInitially(sgc, wh, errorMessage); + if (CdbStackFrameContext *sgc = getStackFrameContext(frameIndex, errorMessage)) + success = sgc->populateModelInitially(wh, errorMessage); wh->rebuildModel(); return success; @@ -800,8 +802,8 @@ void CdbDebugEngine::updateWatchModel() filterEvaluateWatchers(&incomplete, watchHandler); // Do locals. We might get called while running when someone enters watchers if (!incomplete.empty()) { - CdbSymbolGroupContext *sg = m_d->m_currentStackTrace->symbolGroupContextAt(frameIndex, &errorMessage); - if (!sg || !CdbSymbolGroupContext::completeModel(sg, incomplete, watchHandler, &errorMessage)) + CdbStackFrameContext *sg = m_d->m_currentStackTrace->frameContextAt(frameIndex, &errorMessage); + if (!sg || !sg->completeModel(incomplete, watchHandler, &errorMessage)) break; } watchHandler->rebuildModel(); @@ -1016,7 +1018,7 @@ void CdbDebugEngine::assignValueInDebugger(const QString &expr, const QString &v bool success = false; do { QString newValue; - CdbSymbolGroupContext *sg = m_d->getStackFrameSymbolGroupContext(frameIndex, &errorMessage); + CdbStackFrameContext *sg = m_d->getStackFrameContext(frameIndex, &errorMessage); if (!sg) break; if (!sg->assignValue(expr, value, &newValue, &errorMessage)) @@ -1061,17 +1063,30 @@ bool CdbDebugEngine::evaluateExpression(const QString &expression, QString *value, QString *type, QString *errorMessage) +{ + DEBUG_VALUE debugValue; + if (!m_d->evaluateExpression(m_d->m_cif.debugControl, expression, &debugValue, errorMessage)) + return false; + *value = CdbSymbolGroupContext::debugValueToString(debugValue, m_d->m_cif.debugControl, type); + return true; +} + +bool CdbDebugEnginePrivate::evaluateExpression(CIDebugControl *ctrl, + const QString &expression, + DEBUG_VALUE *debugValue, + QString *errorMessage) { if (debugCDB > 1) qDebug() << Q_FUNC_INFO << expression; - DEBUG_VALUE debugValue; - memset(&debugValue, 0, sizeof(DEBUG_VALUE)); + + memset(debugValue, 0, sizeof(DEBUG_VALUE)); // Original syntax must be restored, else setting breakpoints will fail. - SyntaxSetter syntaxSetter(m_d->m_cif.debugControl, DEBUG_EXPR_CPLUSPLUS); + SyntaxSetter syntaxSetter(ctrl, DEBUG_EXPR_CPLUSPLUS); ULONG errorPosition = 0; - const HRESULT hr = m_d->m_cif.debugControl->EvaluateWide(expression.utf16(), - DEBUG_VALUE_INVALID, &debugValue, - &errorPosition); if (FAILED(hr)) { + const HRESULT hr = ctrl->EvaluateWide(expression.utf16(), + DEBUG_VALUE_INVALID, debugValue, + &errorPosition); + if (FAILED(hr)) { if (HRESULT_CODE(hr) == 517) { *errorMessage = QString::fromLatin1("Unable to evaluate '%1': Expression out of scope."). arg(expression); @@ -1081,7 +1096,6 @@ bool CdbDebugEngine::evaluateExpression(const QString &expression, } return false; } - *value = CdbSymbolGroupContext::debugValueToString(debugValue, m_d->m_cif.debugControl, type); return true; } @@ -1355,14 +1369,14 @@ void CdbDebugEnginePrivate::handleDebugEvent() case BreakEventHandle: case BreakEventMain: if (mode == BreakEventMain) - m_dumper.load(m_debuggerManager, m_debuggerManagerAccess); + m_dumper->load(m_debuggerManager); m_debuggerManagerAccess->notifyInferiorStopped(); updateThreadList(); updateStackTrace(); break; case BreakEventMainLoadDumpers: // Temp stop to load dumpers - m_dumper.load(m_debuggerManager, m_debuggerManagerAccess); + m_dumper->load(m_debuggerManager); m_engine->startWatchTimer(); continueInferiorProcess(); break; @@ -1436,7 +1450,7 @@ void CdbDebugEnginePrivate::updateStackTrace() QString errorMessage; m_engine->reloadRegisters(); m_currentStackTrace = - CdbStackTraceContext::create(&m_cif, m_currentThreadId, &errorMessage); + CdbStackTraceContext::create(m_dumper, m_currentThreadId, &errorMessage); if (!m_currentStackTrace) { qWarning("%s: failed to create trace context: %s", Q_FUNC_INFO, qPrintable(errorMessage)); return; diff --git a/src/plugins/debugger/cdb/cdbdebugengine_p.h b/src/plugins/debugger/cdb/cdbdebugengine_p.h index 61c7216a286be487df558a3705d326a7547794b1..04636a72167dc0e92123fac421fd14676014e7ba 100644 --- a/src/plugins/debugger/cdb/cdbdebugengine_p.h +++ b/src/plugins/debugger/cdb/cdbdebugengine_p.h @@ -46,7 +46,7 @@ namespace Internal { class DebuggerManager; class IDebuggerManagerAccessForEngines; class WatchHandler; -class CdbSymbolGroupContext; +class CdbStackFrameContext; class CdbStackTraceContext; // Thin wrapper around the 'DBEng' debugger engine shared library @@ -125,7 +125,7 @@ struct CdbDebugEnginePrivate void cleanStackTrace(); void clearForRun(); void handleModuleLoad(const QString &); - CdbSymbolGroupContext *getStackFrameSymbolGroupContext(int frameIndex, QString *errorMessage) const; + CdbStackFrameContext *getStackFrameContext(int frameIndex, QString *errorMessage) const; void clearDisplay(); bool interruptInterferiorProcess(QString *errorMessage); @@ -136,6 +136,7 @@ struct CdbDebugEnginePrivate bool attemptBreakpointSynchronization(QString *errorMessage); static bool executeDebuggerCommand(CIDebugControl *ctrl, const QString &command, QString *errorMessage); + static bool evaluateExpression(CIDebugControl *ctrl, const QString &expression, DEBUG_VALUE *v, QString *errorMessage); const QSharedPointer<CdbOptions> m_options; HANDLE m_hDebuggeeProcess; @@ -147,7 +148,7 @@ struct CdbDebugEnginePrivate CdbComInterfaces m_cif; CdbDebugEventCallback m_debugEventCallBack; CdbDebugOutput m_debugOutputCallBack; - CdbDumperHelper m_dumper; + QSharedPointer<CdbDumperHelper> m_dumper; CdbDebugEngine* m_engine; DebuggerManager *m_debuggerManager; diff --git a/src/plugins/debugger/cdb/cdbdebugeventcallback.cpp b/src/plugins/debugger/cdb/cdbdebugeventcallback.cpp index 372cda234c7a3bdcdba02a049471b1967dca766d..07eab3215e017a6a253b49cef4167e9b1b4ee6d0 100644 --- a/src/plugins/debugger/cdb/cdbdebugeventcallback.cpp +++ b/src/plugins/debugger/cdb/cdbdebugeventcallback.cpp @@ -318,14 +318,16 @@ void formatException(const EXCEPTION_RECORD64 *e, QTextStream &str) } // Format exception with stacktrace in case of C++ exception -void formatException(const EXCEPTION_RECORD64 *e, CdbComInterfaces &cif, QTextStream &str) +void formatException(const EXCEPTION_RECORD64 *e, + const QSharedPointer<CdbDumperHelper> &dumper, + QTextStream &str) { formatException(e, str); if (e->ExceptionCode == cppExceptionCode) { QString errorMessage; ULONG currentThreadId = 0; - cif.debugSystemObjects->GetCurrentThreadId(¤tThreadId); - if (CdbStackTraceContext *stc = CdbStackTraceContext::create(&cif, currentThreadId, &errorMessage)) { + dumper->comInterfaces()->debugSystemObjects->GetCurrentThreadId(¤tThreadId); + if (CdbStackTraceContext *stc = CdbStackTraceContext::create(dumper, currentThreadId, &errorMessage)) { str << "at:\n"; stc->format(str); str <<'\n'; @@ -343,7 +345,7 @@ STDMETHODIMP CdbDebugEventCallback::Exception( QString msg; { QTextStream str(&msg); - formatException(Exception, m_pEngine->m_d->m_cif, str); + formatException(Exception, m_pEngine->m_d->m_dumper, str); } if (debugCDB) qDebug() << Q_FUNC_INFO << '\n' << msg; @@ -469,6 +471,37 @@ STDMETHODIMP CdbDebugEventCallback::SystemError( return S_OK; } +// -----------ExceptionLoggerEventCallback +CdbExceptionLoggerEventCallback::CdbExceptionLoggerEventCallback(const QString &logPrefix, + IDebuggerManagerAccessForEngines *access) : + m_logPrefix(logPrefix), + m_access(access) +{ +} + +STDMETHODIMP CdbExceptionLoggerEventCallback::GetInterestMask(THIS_ __out PULONG mask) +{ + *mask = DEBUG_EVENT_EXCEPTION; + return S_OK; +} + +STDMETHODIMP CdbExceptionLoggerEventCallback::Exception( + THIS_ + __in PEXCEPTION_RECORD64 Exception, + __in ULONG /* FirstChance */ + ) +{ + m_exceptionMessages.push_back(QString()); + { + QTextStream str(&m_exceptionMessages.back()); + formatException(Exception, str); + } + if (debugCDB) + qDebug() << Q_FUNC_INFO << '\n' << m_exceptionMessages.back(); + m_access->showDebuggerOutput(m_logPrefix, m_exceptionMessages.back()); + return S_OK; +} + // -----------IgnoreDebugEventCallback IgnoreDebugEventCallback::IgnoreDebugEventCallback() { diff --git a/src/plugins/debugger/cdb/cdbdebugeventcallback.h b/src/plugins/debugger/cdb/cdbdebugeventcallback.h index d7fa7f8829f456e22df1c346253dfca9cef8bfc5..2ea3993c130ffe78f809c9f7f1c9f51a908ba0a6 100644 --- a/src/plugins/debugger/cdb/cdbdebugeventcallback.h +++ b/src/plugins/debugger/cdb/cdbdebugeventcallback.h @@ -32,12 +32,13 @@ #include "cdbcom.h" -#include <QtCore/QtGlobal> +#include <QtCore/QStringList> namespace Debugger { namespace Internal { class CdbDebugEngine; +class IDebuggerManagerAccessForEngines; // Base class for event callbacks that takes care // Active X magic. Provides base implementations with @@ -235,6 +236,34 @@ private: CdbDebugEngine *m_pEngine; }; +// Event handler logs exceptions to the debugger window +// and ignores the rest. To be used for running dumper calls. +class CdbExceptionLoggerEventCallback : public CdbDebugEventCallbackBase +{ +public: + explicit CdbExceptionLoggerEventCallback(const QString &logPrefix, + IDebuggerManagerAccessForEngines *access); + + STDMETHOD(GetInterestMask)( + THIS_ + __out PULONG mask + ); + + STDMETHOD(Exception)( + THIS_ + __in PEXCEPTION_RECORD64 Exception, + __in ULONG FirstChance + ); + + int exceptionCount() const { return m_exceptionMessages.size(); } + QStringList exceptionMessages() const { return m_exceptionMessages; } + +private: + const QString m_logPrefix; + IDebuggerManagerAccessForEngines *m_access; + QStringList m_exceptionMessages; +}; + // Event handler that ignores everything class IgnoreDebugEventCallback : public CdbDebugEventCallbackBase { diff --git a/src/plugins/debugger/cdb/cdbdebugoutput.cpp b/src/plugins/debugger/cdb/cdbdebugoutput.cpp index bb89a2f7eca0881ed392f10126fbe478ecd5a5d2..020812b7e0b851c390eaf20314d7dee9f9b1ea73 100644 --- a/src/plugins/debugger/cdb/cdbdebugoutput.cpp +++ b/src/plugins/debugger/cdb/cdbdebugoutput.cpp @@ -80,7 +80,8 @@ STDMETHODIMP CdbDebugOutputBase::Output( IN PCWSTR text ) { - output(mask, QString::fromUtf16(text)); + const QString msg = QString::fromUtf16(text); + output(mask, msg.trimmed()); return S_OK; } diff --git a/src/plugins/debugger/cdb/cdbdumperhelper.cpp b/src/plugins/debugger/cdb/cdbdumperhelper.cpp index a0aa5142d15740bd3d7ee5c9e6aed14c69bb48fb..1357eb99d6f6abeba672e0ac3780f243b3932779 100644 --- a/src/plugins/debugger/cdb/cdbdumperhelper.cpp +++ b/src/plugins/debugger/cdb/cdbdumperhelper.cpp @@ -32,7 +32,8 @@ #include "cdbdebugengine_p.h" #include "cdbdebugoutput.h" #include "cdbdebugeventcallback.h" -#include "watchutils.h" +#include "cdbsymbolgroupcontext.h" +#include "watchhandler.h" #include <QtCore/QRegExp> #include <QtCore/QCoreApplication> @@ -43,21 +44,20 @@ enum { loadDebug = 0 }; static const char *dumperModuleNameC = "gdbmacros"; static const char *qtCoreModuleNameC = "QtCore"; static const ULONG waitTimeOutMS = 30000; - +static const char *dumperPrefixC = "dumper:"; namespace Debugger { namespace Internal { // Alloc memory in debuggee using the ".dvalloc" command as // there seems to be no API for it. -static bool allocDebuggeeMemory(CIDebugControl *ctl, - CIDebugClient *client, +static bool allocDebuggeeMemory(CdbComInterfaces *cif, int size, ULONG64 *address, QString *errorMessage) { *address = 0; const QString allocCmd = QLatin1String(".dvalloc ") + QString::number(size); StringOutputHandler stringHandler; - OutputRedirector redir(client, &stringHandler); - if (!CdbDebugEnginePrivate::executeDebuggerCommand(ctl, allocCmd, errorMessage)) + OutputRedirector redir(cif->debugClient, &stringHandler); + if (!CdbDebugEnginePrivate::executeDebuggerCommand(cif->debugControl, allocCmd, errorMessage)) return false; // "Allocated 1000 bytes starting at 003a0000" .. hopefully never localized bool ok = false; @@ -68,7 +68,7 @@ static bool allocDebuggeeMemory(CIDebugControl *ctl, if (ok) *address = addri; } - if (loadDebug) + if (loadDebug > 1) qDebug() << Q_FUNC_INFO << '\n' << output << *address << ok; if (!ok) { *errorMessage = QString::fromLatin1("Failed to parse output '%1'").arg(output); @@ -78,18 +78,16 @@ static bool allocDebuggeeMemory(CIDebugControl *ctl, } // Alloc an AscII string in debuggee -static bool createDebuggeeAscIIString(CIDebugControl *ctl, - CIDebugClient *client, - CIDebugDataSpaces *data, - const QString &s, - ULONG64 *address, - QString *errorMessage) +static bool createDebuggeeAscIIString(CdbComInterfaces *cif, + const QString &s, + ULONG64 *address, + QString *errorMessage) { QByteArray sAsciiData = s.toLocal8Bit(); sAsciiData += '\0'; - if (!allocDebuggeeMemory(ctl, client, sAsciiData.size(), address, errorMessage)) + if (!allocDebuggeeMemory(cif, sAsciiData.size(), address, errorMessage)) return false; - const HRESULT hr = data->WriteVirtual(*address, sAsciiData.data(), sAsciiData.size(), 0); + const HRESULT hr = cif->debugDataSpaces->WriteVirtual(*address, sAsciiData.data(), sAsciiData.size(), 0); if (FAILED(hr)) { *errorMessage= msgComFailed("WriteVirtual", hr); return false; @@ -101,21 +99,20 @@ static bool createDebuggeeAscIIString(CIDebugControl *ctl, // the QtCored4.pdb file to be present as we need "qstrdup" // as dummy symbol. This is ok ATM since dumpers only // make sense for Qt apps. -static bool debuggeeLoadLibrary(CIDebugControl *ctl, - CIDebugClient *client, - CIDebugSymbols *syms, - CIDebugDataSpaces *data, - const QString &moduleName, QString *errorMessage) +static bool debuggeeLoadLibrary(IDebuggerManagerAccessForEngines *access, + CdbComInterfaces *cif, + const QString &moduleName, + QString *errorMessage) { - if (loadDebug) + if (loadDebug > 1) qDebug() << Q_FUNC_INFO << moduleName; // Try to ignore the breakpoints - IgnoreDebugEventCallback devNull; - EventCallbackRedirector eventRedir(client, &devNull); + CdbExceptionLoggerEventCallback exLogger(QLatin1String(dumperPrefixC), access); + EventCallbackRedirector eventRedir(cif->debugClient, &exLogger); // Make a call to LoadLibraryA. First, reserve memory in debugger // and copy name over. ULONG64 nameAddress; - if (!createDebuggeeAscIIString(ctl, client, data, moduleName, &nameAddress, errorMessage)) + if (!createDebuggeeAscIIString(cif, moduleName, &nameAddress, errorMessage)) return false; // We want to call "HMODULE LoadLibraryA(LPCTSTR lpFileName)" // (void* LoadLibraryA(char*)). However, despite providing a symbol @@ -125,21 +122,21 @@ static bool debuggeeLoadLibrary(CIDebugControl *ctl, // 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) + if (resolveSymbol(cif->debugSymbols, QLatin1String("QtCore[d]*4!"), &dummyFunc, errorMessage) != ResolveSymbolOk) return false; QString callCmd = QLatin1String(".call "); callCmd += dummyFunc; callCmd += QLatin1String("(0x"); callCmd += QString::number(nameAddress, 16); callCmd += QLatin1Char(')'); - if (!CdbDebugEnginePrivate::executeDebuggerCommand(ctl, callCmd, errorMessage)) + if (!CdbDebugEnginePrivate::executeDebuggerCommand(cif->debugControl, callCmd, errorMessage)) return false; - if (!CdbDebugEnginePrivate::executeDebuggerCommand(ctl, QLatin1String("r eip=Kernel32!LoadLibraryA"), errorMessage)) + if (!CdbDebugEnginePrivate::executeDebuggerCommand(cif->debugControl, QLatin1String("r eip=Kernel32!LoadLibraryA"), errorMessage)) return false; // This will hit a breakpoint. - if (!CdbDebugEnginePrivate::executeDebuggerCommand(ctl, QString(QLatin1Char('g')), errorMessage)) + if (!CdbDebugEnginePrivate::executeDebuggerCommand(cif->debugControl, QString(QLatin1Char('g')), errorMessage)) return false; - const HRESULT hr = ctl->WaitForEvent(0, waitTimeOutMS); + const HRESULT hr = cif->debugControl->WaitForEvent(0, waitTimeOutMS); if (FAILED(hr)) { *errorMessage = msgComFailed("WaitForEvent", hr); return false; @@ -147,66 +144,13 @@ static bool debuggeeLoadLibrary(CIDebugControl *ctl, return true; } -// ------------------- 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) : +CdbDumperHelper::CdbDumperHelper(IDebuggerManagerAccessForEngines *access, + CdbComInterfaces *cif) : + m_messagePrefix(QLatin1String(dumperPrefixC)), m_state(NotLoaded), + m_access(access), m_cif(cif), m_inBufferAddress(0), m_inBufferSize(0), @@ -234,24 +178,23 @@ void CdbDumperHelper::reset(const QString &library, bool enabled) m_library = library; m_state = enabled ? NotLoaded : Disabled; m_dumpObjectSymbol = QLatin1String("qDumpObjectData440"); - m_knownTypes.clear(); - m_qtVersion.clear(); - m_qtNamespace.clear(); + m_helper.clear(); m_inBufferAddress = m_outBufferAddress = 0; m_inBufferSize = m_outBufferSize = 0; + m_typeSizeCache.clear(); + m_failedTypes.clear(); clearBuffer(); } // Attempt to load and initialize dumpers, give feedback // to user. -void CdbDumperHelper::load(DebuggerManager *manager, IDebuggerManagerAccessForEngines *access) +void CdbDumperHelper::load(DebuggerManager *manager) { 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 { @@ -266,12 +209,11 @@ void CdbDumperHelper::load(DebuggerManager *manager, IDebuggerManagerAccessForEn // 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)) { + if (!debuggeeLoadLibrary(m_access, m_cif, m_library, &message)) { break; } } else { - access->showDebuggerOutput(messagePrefix, QCoreApplication::translate("CdbDumperHelper", "The dumper module appears to be already loaded.")); + m_access->showDebuggerOutput(m_messagePrefix, QCoreApplication::translate("CdbDumperHelper", "The dumper module appears to be already loaded.")); } // Resolve symbols and do call to get types if (!resolveSymbols(&message)) @@ -285,17 +227,17 @@ void CdbDumperHelper::load(DebuggerManager *manager, IDebuggerManagerAccessForEn 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_access->showDebuggerOutput(m_messagePrefix, message); + m_access->showQtDumperLibraryWarning(message); m_state = Disabled; break; case Succeeded: - access->showDebuggerOutput(messagePrefix, message); - access->showDebuggerOutput(messagePrefix, statusMessage()); + m_access->showDebuggerOutput(m_messagePrefix, message); + m_access->showDebuggerOutput(m_messagePrefix, m_helper.toString()); m_state = Loaded; break; case NoQtApp: - access->showDebuggerOutput(messagePrefix, message); + m_access->showDebuggerOutput(m_messagePrefix, message); m_state = Disabled; break; } @@ -303,15 +245,6 @@ void CdbDumperHelper::load(DebuggerManager *manager, IDebuggerManagerAccessForEn 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, @@ -373,41 +306,82 @@ bool CdbDumperHelper::resolveSymbols(QString *errorMessage) bool CdbDumperHelper::getKnownTypes(QString *errorMessage) { QByteArray output; - if (!callDumper(DumperInputParameters(1), &output, errorMessage)) { + QString callCmd; + QTextStream(&callCmd) << ".call " << m_dumpObjectSymbol << "(1,0,0,0,0,0,0,0)"; + const char *outData; + if (!callDumper(callCmd, QByteArray(), &outData, errorMessage)) { return false; } - if (!parseQueryDumperOutput(output, &m_knownTypes, &m_qtVersion, &m_qtNamespace)) { + if (!m_helper.parseQuery(outData, QtDumperHelper::CdbDebugger)) { *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; + qDebug() << Q_FUNC_INFO << m_helper.toString(true); return true; } -bool CdbDumperHelper::callDumper(const DumperInputParameters &p, QByteArray *output, QString *errorMessage) +// Write to debuggee memory in chunks +bool CdbDumperHelper::writeToDebuggee(CIDebugDataSpaces *ds, const QByteArray &buffer, quint64 address, 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. + char *ptr = const_cast<char*>(buffer.data()); + ULONG bytesToWrite = buffer.size(); + while (bytesToWrite > 0) { + ULONG bytesWritten = 0; + const HRESULT hr = ds->WriteVirtual(address, ptr, bytesToWrite, &bytesWritten); + if (FAILED(hr)) { + *errorMessage = msgComFailed("WriteVirtual", hr); + return false; + } + bytesToWrite -= bytesWritten; + ptr += bytesWritten; + } + return true; +} + +bool CdbDumperHelper::callDumper(const QString &callCmd, const QByteArray &inBuffer, const char **outDataPtr, QString *errorMessage) +{ + *outDataPtr = 0; + CdbExceptionLoggerEventCallback exLogger(m_messagePrefix, m_access); + EventCallbackRedirector eventRedir(m_cif->debugClient, &exLogger); + // write input buffer + if (!inBuffer.isEmpty()) { + if (!writeToDebuggee(m_cif->debugDataSpaces, inBuffer, m_inBufferAddress, errorMessage)) + return false; + } 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); + // Set up call and a temporary breakpoint after it. + // Try to skip debuggee crash exceptions and dumper exceptions + // by using 'gh' (go handled) + for (int i = 0; i < 10; i++) { + const int oldExceptionCount = exLogger.exceptionCount(); + // Go. If an exception occurs in loop 2, let the dumper handle it. + const QString goCmd = i ? QString(QLatin1String("gN")) : QString(QLatin1Char('g')); + if (!CdbDebugEnginePrivate::executeDebuggerCommand(m_cif->debugControl, goCmd, errorMessage)) + return false; + HRESULT hr = m_cif->debugControl->WaitForEvent(0, waitTimeOutMS); + if (FAILED(hr)) { + *errorMessage = msgComFailed("WaitForEvent", hr); + return false; + } + const int newExceptionCount = exLogger.exceptionCount(); + // no new exceptions? -> break + if (oldExceptionCount == newExceptionCount) + break; + } + if (exLogger.exceptionCount()) { + const QString exMsgs = exLogger.exceptionMessages().join(QString(QLatin1Char(','))); + *errorMessage = QString::fromLatin1("Exceptions occurred during the dumper call: %1").arg(exMsgs); return false; } // Read output - hr = m_cif->debugDataSpaces->ReadVirtual(m_outBufferAddress, m_buffer, m_outBufferSize, 0); + const HRESULT 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]; + const char result = m_buffer[0]; switch (result) { case 't': break; @@ -421,7 +395,144 @@ bool CdbDumperHelper::callDumper(const DumperInputParameters &p, QByteArray *out *errorMessage = QString::fromLatin1("Dumper call '%1' failed ('%2').").arg(callCmd).arg(QLatin1Char(result)); return false; } - *output = QByteArray(m_buffer + 1); + *outDataPtr = m_buffer + 1; + return true; +} + +CdbDumperHelper::DumpResult CdbDumperHelper::dumpType(const WatchData &wd, bool dumpChildren, int source, + QList<WatchData> *result, QString *errorMessage) +{ + // Check failure cache and supported types + if (m_failedTypes.contains(wd.type)) + return DumpNotHandled; + const QtDumperHelper::TypeData td = m_helper.typeData(wd.type); + if (td.type == QtDumperHelper::UnknownType) + return DumpNotHandled; + + // Now evaluate + const QString message = QCoreApplication::translate("CdbDumperHelper", + "Querying dumpers for '%1'/'%2' (%3)"). + arg(wd.name, wd.exp, wd.type); + m_access->showDebuggerOutput(m_messagePrefix, message); + const DumpExecuteResult der = executeDump(wd, td, dumpChildren, source, result, errorMessage); + if (der == DumpExecuteOk) + return DumpOk; + // Cache types that fail due to complicated template size expressions. + // Exceptions OTOH might occur when accessing variables that are not + // yet initialized in a particular breakpoint. That should be ignored + if (der == DumpExecuteSizeFailed) + m_failedTypes.push_back(wd.type); + // log error + *errorMessage = QString::fromLatin1("Unable to dump '%1' (%2): %3").arg(wd.name, wd.type, *errorMessage); + m_access->showDebuggerOutput(m_messagePrefix, *errorMessage); + return DumpError; +} + +CdbDumperHelper::DumpExecuteResult + CdbDumperHelper::executeDump(const WatchData &wd, + const QtDumperHelper::TypeData& td, bool dumpChildren, int source, + QList<WatchData> *result, QString *errorMessage) +{ + QByteArray inBuffer; + QStringList extraParameters; + // Build parameter list. + m_helper.evaluationParameters(wd, td, QtDumperHelper::CdbDebugger, &inBuffer, &extraParameters); + // If the parameter list contains sizeof-expressions, execute them separately + // and replace them by the resulting numbers + const QString sizeOfExpr = QLatin1String("sizeof"); + const QStringList::iterator eend = extraParameters.end(); + for (QStringList::iterator it = extraParameters.begin() ; it != eend; ++it) { + // Strip 'sizeof(X)' to 'X' and query size + QString &ep = *it; + if (ep.startsWith(sizeOfExpr)) { + int size; + ep.truncate(ep.lastIndexOf(QLatin1Char(')'))); + ep.remove(0, ep.indexOf(QLatin1Char('(')) + 1); + if (!getTypeSize(ep, &size, errorMessage)) + return DumpExecuteSizeFailed; + if (loadDebug) + qDebug() << "Size" << size << ep; + ep = QString::number(size); + } + } + // Execute call + QString callCmd; + QTextStream(&callCmd) << ".call " << m_dumpObjectSymbol + << "(2,0," << wd.addr << ',' + << (dumpChildren ? 1 : 0) << ',' << extraParameters.join(QString(QLatin1Char(','))) << ')'; + if (loadDebug) + qDebug() << "Query: " << wd.toString() << "\nwith: " << callCmd; + const char *outputData; + if (!callDumper(callCmd, inBuffer, &outputData, errorMessage)) + return DumpExecuteCallFailed; + QtDumperResult dumpResult; + if (!QtDumperHelper::parseValue(outputData, &dumpResult)) { + *errorMessage = QLatin1String("Parsing of value query output failed."); + return DumpExecuteCallFailed; + } + *result = dumpResult.toWatchData(source); + return DumpExecuteOk; +} + +// Simplify some types for sizeof expressions +static inline void simplifySizeExpression(QString *typeName) +{ + typeName->replace(QLatin1String("std::basic_string<char,std::char_traits<char>,std::allocator<char>>"), + QLatin1String("std::string")); +} + +bool CdbDumperHelper::getTypeSize(const QString &typeNameIn, int *size, QString *errorMessage) +{ + if (loadDebug > 1) + qDebug() << Q_FUNC_INFO << typeNameIn; + // Look up cache + const TypeSizeCache::const_iterator it = m_typeSizeCache.constFind(typeNameIn); + if (it != m_typeSizeCache.constEnd()) { + *size = it.value(); + return true; + } + QString typeName = typeNameIn; + simplifySizeExpression(&typeName); + // "std::" types sometimes only work without namespace. + // If it fails, try again with stripped namespace + *size = 0; + bool success = false; + do { + if (runTypeSizeQuery(typeName, size, errorMessage)) { + success = true; + break; + } + const QString stdNameSpace = QLatin1String("std::"); + if (!typeName.contains(stdNameSpace)) + break; + typeName.remove(stdNameSpace); + errorMessage->clear(); + if (!runTypeSizeQuery(typeName, size, errorMessage)) + break; + success = true; + } while (false); + if (success) + m_typeSizeCache.insert(typeName, *size); + return success; +} + +bool CdbDumperHelper::runTypeSizeQuery(const QString &typeName, int *size, QString *errorMessage) +{ + // Retrieve by C++ expression. If we knew the module, we could make use + // of the TypeId query mechanism provided by the IDebugSymbolGroup. + DEBUG_VALUE sizeValue; + QString expression = QLatin1String("sizeof("); + expression += typeName; + expression += QLatin1Char(')'); + if (!CdbDebugEnginePrivate::evaluateExpression(m_cif->debugControl, + expression, &sizeValue, errorMessage)) + return false; + qint64 size64; + if (!CdbSymbolGroupContext::debugValueToInteger(sizeValue, &size64)) { + *errorMessage = QLatin1String("Expression result is not an integer"); + return false; + } + *size = static_cast<int>(size64); return true; } diff --git a/src/plugins/debugger/cdb/cdbdumperhelper.h b/src/plugins/debugger/cdb/cdbdumperhelper.h index c5be52e22f45eef7dee26d24e78371984279eed9..2f576b196ef978ce5447b81db71e71c0db162bd8 100644 --- a/src/plugins/debugger/cdb/cdbdumperhelper.h +++ b/src/plugins/debugger/cdb/cdbdumperhelper.h @@ -30,7 +30,10 @@ #ifndef CDBDUMPERHELPER_H #define CDBDUMPERHELPER_H +#include "watchutils.h" +#include "cdbcom.h" #include <QtCore/QStringList> +#include <QtCore/QMap> namespace Debugger { namespace Internal { @@ -66,7 +69,8 @@ public: Failed }; - explicit CdbDumperHelper(CdbComInterfaces *cif); + explicit CdbDumperHelper(IDebuggerManagerAccessForEngines *access, + CdbComInterfaces *cif); ~CdbDumperHelper(); State state() const { return m_state; } @@ -76,31 +80,50 @@ public: void reset(const QString &library, bool enabled); // Call in a temporary breakpoint state to actually load. - void load(DebuggerManager *manager, IDebuggerManagerAccessForEngines *access); + void load(DebuggerManager *manager); -private: - struct DumperInputParameters; + enum DumpResult { DumpNotHandled, DumpOk, DumpError }; + DumpResult dumpType(const WatchData &d, bool dumpChildren, int source, + QList<WatchData> *result, QString *errorMessage); + + // bool handlesType(const QString &typeName) const; + inline CdbComInterfaces *comInterfaces() const { return m_cif; } + +private: void clearBuffer(); bool resolveSymbols(QString *errorMessage); bool getKnownTypes(QString *errorMessage); - bool callDumper(const DumperInputParameters &p, QByteArray *output, QString *errorMessage); - inline QString statusMessage() const; + bool getTypeSize(const QString &typeName, int *size, QString *errorMessage); + bool runTypeSizeQuery(const QString &typeName, int *size, QString *errorMessage); + bool callDumper(const QString &call, const QByteArray &inBuffer, const char **outputPtr, QString *errorMessage); + enum DumpExecuteResult { DumpExecuteOk, DumpExecuteSizeFailed, DumpExecuteCallFailed }; + DumpExecuteResult executeDump(const WatchData &wd, + const QtDumperHelper::TypeData& td, bool dumpChildren, int source, + QList<WatchData> *result, QString *errorMessage); + + static bool writeToDebuggee(CIDebugDataSpaces *ds, const QByteArray &buffer, quint64 address, QString *errorMessage); + + const QString m_messagePrefix; State m_state; + IDebuggerManagerAccessForEngines *m_access; CdbComInterfaces *m_cif; QString m_library; QString m_dumpObjectSymbol; - 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; + + typedef QMap<QString, int> TypeSizeCache; + TypeSizeCache m_typeSizeCache; + QStringList m_failedTypes; + + QtDumperHelper m_helper; }; } // namespace Internal diff --git a/src/plugins/debugger/cdb/cdbstackframecontext.cpp b/src/plugins/debugger/cdb/cdbstackframecontext.cpp new file mode 100644 index 0000000000000000000000000000000000000000..be04856ccc08eae66127acfbb3d1b54d318c2606 --- /dev/null +++ b/src/plugins/debugger/cdb/cdbstackframecontext.cpp @@ -0,0 +1,189 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** Commercial Usage +** +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** GNU Lesser General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** +**************************************************************************/ + +#include "cdbstackframecontext.h" +#include "cdbsymbolgroupcontext.h" +#include "cdbdumperhelper.h" +#include "debuggeractions.h" +#include "watchhandler.h" + +#include <QtCore/QDebug> + +enum { debug = 0 }; + +namespace Debugger { +namespace Internal { + +enum { OwnerNewItem, OwnerSymbolGroup, OwnerDumper }; + +typedef QSharedPointer<CdbDumperHelper> SharedPointerCdbDumperHelper; +typedef QList<WatchData> WatchDataList; + +// Put a sequence of WatchData into the model +class WatchHandlerModelInserter { +public: + explicit WatchHandlerModelInserter(WatchHandler *wh) : m_wh(wh) {} + + inline WatchHandlerModelInserter & operator*() { return *this; } + inline WatchHandlerModelInserter &operator=(const WatchData &wd) { + m_wh->insertData(wd); + return *this; + } + inline WatchHandlerModelInserter &operator++() { return *this; } + +private: + WatchHandler *m_wh; +}; + +// Helper to sort apart a sequence of WatchData using the Dumpers. +// Puts the stuff for which there is no dumper in the model +// as is and sets ownership to symbol group. The rest goes +// to the dumpers. +class WatchHandlerSorterInserter { +public: + explicit WatchHandlerSorterInserter(WatchHandler *wh, + const SharedPointerCdbDumperHelper &dumper); + + inline WatchHandlerSorterInserter & operator*() { return *this; } + inline WatchHandlerSorterInserter &operator=(WatchData wd); + inline WatchHandlerSorterInserter &operator++() { return *this; } + +private: + WatchHandler *m_wh; + const SharedPointerCdbDumperHelper m_dumper; + QList<WatchData> m_dumperResult; + QStringList m_dumperINames; +}; + +WatchHandlerSorterInserter::WatchHandlerSorterInserter(WatchHandler *wh, + const SharedPointerCdbDumperHelper &dumper) : + m_wh(wh), + m_dumper(dumper) +{ +} + +WatchHandlerSorterInserter &WatchHandlerSorterInserter::operator=(WatchData wd) +{ + // Is this a child belonging to some item handled by dumpers, + // such as d-elements of QStrings -> just ignore it. + if (const int dumperINamesCount = m_dumperINames.size()) { + for (int i = 0; i < dumperINamesCount; i++) + if (wd.iname.startsWith(m_dumperINames.at(i))) + return *this; + } + QString errorMessage; + switch (m_dumper->dumpType(wd, true, OwnerDumper, &m_dumperResult, &errorMessage)) { + case CdbDumperHelper::DumpOk: + // Discard the original item and insert the dumper results + m_dumperINames.push_back(wd.iname + QLatin1Char('.')); + foreach(const WatchData &dwd, m_dumperResult) + m_wh->insertData(dwd); + break; + case CdbDumperHelper::DumpNotHandled: + case CdbDumperHelper::DumpError: + wd.source = OwnerSymbolGroup; + m_wh->insertData(wd); + break; + } + return *this; +} + +// -----------CdbStackFrameContext +CdbStackFrameContext::CdbStackFrameContext(const QSharedPointer<CdbDumperHelper> &dumper, + CdbSymbolGroupContext *symbolContext) : + m_useDumpers(dumper->state() == CdbDumperHelper::Loaded + && theDebuggerBoolSetting(UseDebuggingHelpers)), + m_dumper(dumper), + m_symbolContext(symbolContext) +{ +} + +bool CdbStackFrameContext::assignValue(const QString &iname, const QString &value, + QString *newValue /* = 0 */, QString *errorMessage) +{ + return m_symbolContext->assignValue(iname, value, newValue, errorMessage); +} + +bool CdbStackFrameContext::populateModelInitially(WatchHandler *wh, QString *errorMessage) +{ + if (debug) + qDebug() << "populateModelInitially"; + const bool rc = m_useDumpers ? + CdbSymbolGroupContext::populateModelInitially(m_symbolContext, + WatchHandlerSorterInserter(wh, m_dumper), + errorMessage) : + CdbSymbolGroupContext::populateModelInitially(m_symbolContext, + WatchHandlerModelInserter(wh), + errorMessage); + return rc; +} + +bool CdbStackFrameContext::completeModel(const QList<WatchData> &incompleteLocals, + WatchHandler *wh, + QString *errorMessage) +{ + if (debug) { + QDebug nsp = qDebug().nospace(); + nsp << ">completeModel "; + foreach (const WatchData &wd, incompleteLocals) + nsp << wd.iname << ' '; + nsp << '\n'; + } + + if (!m_useDumpers) { + return CdbSymbolGroupContext::completeModel(m_symbolContext, incompleteLocals, + WatchHandlerModelInserter(wh), + errorMessage); + } + + // Expand dumper items + int handledDumpers = 0; + foreach (const WatchData &cwd, incompleteLocals) { + if (cwd.source == OwnerDumper) { // You already know that. + WatchData wd = cwd; + wd.setAllUnneeded(); + wh->insertData(wd); + handledDumpers++; + } + } + if (handledDumpers == incompleteLocals.size()) + return true; + // Expand symbol group items + return CdbSymbolGroupContext::completeModel(m_symbolContext, incompleteLocals, + WatchHandlerSorterInserter(wh, m_dumper), + errorMessage); +} + +CdbStackFrameContext::~CdbStackFrameContext() +{ + delete m_symbolContext; +} + +} // namespace Internal +} // namespace Debugger diff --git a/src/plugins/debugger/cdb/cdbstackframecontext.h b/src/plugins/debugger/cdb/cdbstackframecontext.h new file mode 100644 index 0000000000000000000000000000000000000000..caf84dde97bfc5e5e8ce06ceab20ac2323bb89c8 --- /dev/null +++ b/src/plugins/debugger/cdb/cdbstackframecontext.h @@ -0,0 +1,74 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** Commercial Usage +** +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** GNU Lesser General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** +**************************************************************************/ + +#ifndef CDBSTACKFRAMECONTEXT_H +#define CDBSTACKFRAMECONTEXT_H + +#include <QtCore/QList> +#include <QtCore/QSharedPointer> + +namespace Debugger { +namespace Internal { + +class WatchData; +class WatchHandler; +class CdbSymbolGroupContext; +class CdbDumperHelper; + +/* CdbStackFrameContext manages a symbol group context and + * a dumper context. It dispatches calls between the local items + * that are handled by the symbol group and those that are handled by the dumpers. */ + +class CdbStackFrameContext +{ + Q_DISABLE_COPY(CdbStackFrameContext) +public: + explicit CdbStackFrameContext(const QSharedPointer<CdbDumperHelper> &dumper, + CdbSymbolGroupContext *symbolContext); + ~CdbStackFrameContext(); + + bool assignValue(const QString &iname, const QString &value, + QString *newValue /* = 0 */, QString *errorMessage); + + bool populateModelInitially(WatchHandler *wh, QString *errorMessage); + + bool completeModel(const QList<WatchData> &incompleteLocals, + WatchHandler *wh, + QString *errorMessage); + +private: + const bool m_useDumpers; + const QSharedPointer<CdbDumperHelper> m_dumper; + CdbSymbolGroupContext *m_symbolContext; +}; + +} // namespace Internal +} // namespace Debugger + +#endif // CDBSTACKFRAMECONTEXT_H diff --git a/src/plugins/debugger/cdb/cdbstacktracecontext.cpp b/src/plugins/debugger/cdb/cdbstacktracecontext.cpp index d769817c7e5bf111e148a7a6af7a017be53fa372..3b3997635581d409848c25e57b67bd76859462c7 100644 --- a/src/plugins/debugger/cdb/cdbstacktracecontext.cpp +++ b/src/plugins/debugger/cdb/cdbstacktracecontext.cpp @@ -28,9 +28,11 @@ **************************************************************************/ #include "cdbstacktracecontext.h" +#include "cdbstackframecontext.h" #include "cdbbreakpoint.h" #include "cdbsymbolgroupcontext.h" #include "cdbdebugengine_p.h" +#include "cdbdumperhelper.h" #include <QtCore/QDir> #include <QtCore/QTextStream> @@ -38,18 +40,20 @@ namespace Debugger { namespace Internal { -CdbStackTraceContext::CdbStackTraceContext(CdbComInterfaces *cif) : - m_cif(cif), +CdbStackTraceContext::CdbStackTraceContext(const QSharedPointer<CdbDumperHelper> &dumper) : + m_dumper(dumper), + m_cif(dumper->comInterfaces()), m_instructionOffset(0) { } -CdbStackTraceContext *CdbStackTraceContext::create(CdbComInterfaces *cif, +CdbStackTraceContext *CdbStackTraceContext::create(const QSharedPointer<CdbDumperHelper> &dumper, unsigned long threadId, QString *errorMessage) { if (debugCDB) qDebug() << Q_FUNC_INFO << threadId; + CdbComInterfaces *cif = dumper->comInterfaces(); HRESULT hr = cif->debugSystemObjects->SetCurrentThreadId(threadId); if (FAILED(hr)) { *errorMessage = QString::fromLatin1("%1: SetCurrentThreadId %2 failed: %3"). @@ -60,7 +64,7 @@ CdbStackTraceContext *CdbStackTraceContext::create(CdbComInterfaces *cif, } // fill the DEBUG_STACK_FRAME array ULONG frameCount; - CdbStackTraceContext *ctx = new CdbStackTraceContext(cif); + CdbStackTraceContext *ctx = new CdbStackTraceContext(dumper); hr = cif->debugControl->GetStackTrace(0, 0, 0, ctx->m_cdbFrames, CdbStackTraceContext::maxFrames, &frameCount); if (FAILED(hr)) { delete ctx; @@ -77,7 +81,7 @@ CdbStackTraceContext *CdbStackTraceContext::create(CdbComInterfaces *cif, CdbStackTraceContext::~CdbStackTraceContext() { - qDeleteAll(m_symbolContexts); + qDeleteAll(m_frameContexts); } bool CdbStackTraceContext::init(unsigned long frameCount, QString * /*errorMessage*/) @@ -85,8 +89,8 @@ bool CdbStackTraceContext::init(unsigned long frameCount, QString * /*errorMessa if (debugCDB) qDebug() << Q_FUNC_INFO << frameCount; - m_symbolContexts.resize(frameCount); - qFill(m_symbolContexts, static_cast<CdbSymbolGroupContext*>(0)); + m_frameContexts.resize(frameCount); + qFill(m_frameContexts, static_cast<CdbStackFrameContext*>(0)); // Convert the DEBUG_STACK_FRAMEs to our StackFrame structure and populate the frames WCHAR wszBuf[MAX_PATH]; @@ -114,28 +118,28 @@ bool CdbStackTraceContext::init(unsigned long frameCount, QString * /*errorMessa return true; } -CdbSymbolGroupContext *CdbStackTraceContext::symbolGroupContextAt(int index, QString *errorMessage) +CdbStackFrameContext *CdbStackTraceContext::frameContextAt(int index, QString *errorMessage) { - // Create a symbol group on demand + // Create a frame on demand if (debugCDB) qDebug() << Q_FUNC_INFO << index; - if (index < 0 || index >= m_symbolContexts.size()) { + if (index < 0 || index >= m_frameContexts.size()) { *errorMessage = QString::fromLatin1("%1: Index %2 out of range %3."). - arg(QLatin1String(Q_FUNC_INFO)).arg(index).arg(m_symbolContexts.size()); + arg(QLatin1String(Q_FUNC_INFO)).arg(index).arg(m_frameContexts.size()); return 0; } - - if (m_symbolContexts.at(index)) - return m_symbolContexts.at(index); + if (m_frameContexts.at(index)) + return m_frameContexts.at(index); CIDebugSymbolGroup *sg = createSymbolGroup(index, errorMessage); if (!sg) return 0; CdbSymbolGroupContext *sc = CdbSymbolGroupContext::create(QLatin1String("local"), sg, errorMessage); if (!sc) - return 0; \ - m_symbolContexts[index] = sc; - return sc; + return 0; + CdbStackFrameContext *fr = new CdbStackFrameContext(m_dumper, sc); + m_frameContexts[index] = fr; + return fr; } CIDebugSymbolGroup *CdbStackTraceContext::createSymbolGroup(int index, QString *errorMessage) diff --git a/src/plugins/debugger/cdb/cdbstacktracecontext.h b/src/plugins/debugger/cdb/cdbstacktracecontext.h index 56389481c62f43865f8dd9a267609864c66354a6..98e509ad982e99c500342f7ee9332b8add8936ac 100644 --- a/src/plugins/debugger/cdb/cdbstacktracecontext.h +++ b/src/plugins/debugger/cdb/cdbstacktracecontext.h @@ -36,6 +36,7 @@ #include <QtCore/QString> #include <QtCore/QVector> +#include <QtCore/QSharedPointer> QT_BEGIN_NAMESPACE class QTextStream; @@ -46,21 +47,23 @@ namespace Internal { struct CdbComInterfaces; class CdbSymbolGroupContext; +class CdbStackFrameContext; +class CdbDumperHelper; /* Context representing a break point stack consisting of several frames. - * Maintains an on-demand constructed list of CdbSymbolGroupContext + * Maintains an on-demand constructed list of CdbStackFrameContext * containining the local variables of the stack. */ class CdbStackTraceContext { Q_DISABLE_COPY(CdbStackTraceContext) - explicit CdbStackTraceContext(CdbComInterfaces *cif); + explicit CdbStackTraceContext(const QSharedPointer<CdbDumperHelper> &dumper); public: enum { maxFrames = 100 }; ~CdbStackTraceContext(); - static CdbStackTraceContext *create(CdbComInterfaces *cif, + static CdbStackTraceContext *create(const QSharedPointer<CdbDumperHelper> &dumper, unsigned long threadid, QString *errorMessage); @@ -70,7 +73,7 @@ public: // Top-Level instruction offset for disassembler ULONG64 instructionOffset() const { return m_instructionOffset; } - CdbSymbolGroupContext *symbolGroupContextAt(int index, QString *errorMessage); + CdbStackFrameContext*frameContextAt(int index, QString *errorMessage); // Format for logging void format(QTextStream &str) const; @@ -80,10 +83,11 @@ private: bool init(unsigned long frameCount, QString *errorMessage); CIDebugSymbolGroup *createSymbolGroup(int index, QString *errorMessage); + const QSharedPointer<CdbDumperHelper> m_dumper; CdbComInterfaces *m_cif; DEBUG_STACK_FRAME m_cdbFrames[maxFrames]; - QVector <CdbSymbolGroupContext*> m_symbolContexts; + QVector <CdbStackFrameContext*> m_frameContexts; QList<StackFrame> m_frames; ULONG64 m_instructionOffset; }; diff --git a/src/plugins/debugger/cdb/cdbsymbolgroupcontext.cpp b/src/plugins/debugger/cdb/cdbsymbolgroupcontext.cpp index 857a80912f14bfbaa9762ca1ea21c36ce8b3bc54..7daba56a587ca91624b298569e391fb6ba727360 100644 --- a/src/plugins/debugger/cdb/cdbsymbolgroupcontext.cpp +++ b/src/plugins/debugger/cdb/cdbsymbolgroupcontext.cpp @@ -200,16 +200,6 @@ QString CdbSymbolGroupContext::toString(bool verbose) const return rc; } -bool CdbSymbolGroupContext::isSymbolDisplayable(const DEBUG_SYMBOL_PARAMETERS &p) -{ - if (p.Flags & (DEBUG_SYMBOL_IS_LOCAL|DEBUG_SYMBOL_IS_ARGUMENT)) - return true; - // Do not display static members. - if (p.Flags & DEBUG_SYMBOL_READ_ONLY) - return false; - return true; -} - CdbSymbolGroupContext::SymbolState CdbSymbolGroupContext::symbolState(unsigned long index) const { return getSymbolState(m_symbolParameters.at(index)); @@ -499,144 +489,26 @@ QString CdbSymbolGroupContext::debugValueToString(const DEBUG_VALUE &dv, CIDebug return formatArrayHelper(dv.RawBytes, 24, integerBase); } -// - Watch model functions -class WatchDataBackInserter { -public: - explicit WatchDataBackInserter(QList<WatchData> &wh) : m_wh(wh) {} - - WatchDataBackInserter & operator*() { return *this; } - WatchDataBackInserter &operator=(const WatchData &wd) { - m_wh.push_back(wd); - return *this; - } - WatchDataBackInserter &operator++() { return *this; } - -private: - QList<WatchData> &m_wh; -}; - -static bool insertChildrenRecursion(const QString &iname, - CdbSymbolGroupContext *sg, - WatchHandler *watchHandler, - int maxRecursionLevel, - int level, - QString *errorMessage, - int *childCount = 0); - -// Insert a symbol (and its first level children depending on forceRecursion) -static bool insertSymbolRecursion(WatchData wd, - CdbSymbolGroupContext *sg, - WatchHandler *watchHandler, - int maxRecursionLevel, - int level, - QString *errorMessage) -{ - // Find out whether to recurse (has children or at least knows it has children) - // Open next level if specified by recursion depth or child is already expanded - // (Sometimes, some root children are already expanded after creating the context). - const bool hasChildren = wd.childCount > 0 || wd.isChildrenNeeded(); - const bool recurse = hasChildren && (level < maxRecursionLevel || sg->isExpanded(wd.iname)); - if (debug) - qDebug() << Q_FUNC_INFO << '\n' << wd.iname << "level=" << level << "recurse=" << recurse; - bool rc = true; - if (recurse) { // Determine number of children and indicate in model - int childCount; - rc = insertChildrenRecursion(wd.iname, sg, watchHandler, maxRecursionLevel, level, errorMessage, &childCount); - if (rc) { - wd.setChildCount(childCount); - wd.setChildrenUnneeded(); - } - } else { - // No further recursion at this level, pretend entry is complete - if (wd.isChildrenNeeded()) { - wd.setChildCount(1); - wd.setChildrenUnneeded(); - } - } - if (debug) - qDebug() << " INSERTING: at " << level << wd.toString(); - watchHandler->insertData(wd); - return rc; -} - -// Insert the children of prefix. -static bool insertChildrenRecursion(const QString &iname, - CdbSymbolGroupContext *sg, - WatchHandler *watchHandler, - int maxRecursionLevel, - int level, - QString *errorMessage, - int *childCountPtr) +bool CdbSymbolGroupContext::debugValueToInteger(const DEBUG_VALUE &dv, qint64 *value) { - if (debug > 1) - qDebug() << Q_FUNC_INFO << '\n' << iname << level; - - QList<WatchData> watchList; - // This implicitly enforces expansion - if (!sg->getChildSymbols(iname, WatchDataBackInserter(watchList), errorMessage)) - return false; - - const int childCount = watchList.size(); - if (childCountPtr) - *childCountPtr = childCount; - int succeededChildCount = 0; - for (int c = 0; c < childCount; c++) { - const WatchData &wd = watchList.at(c); - if (wd.isValid()) { // We sometimes get empty names for deeply nested data - if (!insertSymbolRecursion(wd, sg, watchHandler, maxRecursionLevel, level + 1, errorMessage)) - return false; - succeededChildCount++; - } else { - const QString msg = QString::fromLatin1("WARNING: Skipping invalid child symbol #%2 (type %3) of '%4'."). - arg(QLatin1String(Q_FUNC_INFO)).arg(c).arg(wd.type, iname); - qWarning("%s\n", qPrintable(msg)); - } - } - if (childCountPtr) - *childCountPtr = succeededChildCount; - return true; -} - -bool CdbSymbolGroupContext::populateModelInitially(CdbSymbolGroupContext *sg, - WatchHandler *watchHandler, - QString *errorMessage) -{ - if (debugCDB) - qDebug() << "###" << Q_FUNC_INFO; - - // Insert root items - QList<WatchData> watchList; - if (!sg->getChildSymbols(sg->prefix(), WatchDataBackInserter(watchList), errorMessage)) - return false; - - foreach(const WatchData &wd, watchList) - if (!insertSymbolRecursion(wd, sg, watchHandler, 0, 0, errorMessage)) - return false; - return true; -} - -bool CdbSymbolGroupContext::completeModel(CdbSymbolGroupContext *sg, - const QList<WatchData> &incompleteLocals, - WatchHandler *watchHandler, - QString *errorMessage) -{ - if (debugCDB) - qDebug().nospace() << "###>" << Q_FUNC_INFO << ' ' << incompleteLocals.size() << '\n'; - // The view reinserts any node being expanded with flag 'ChildrenNeeded'. - // Recurse down one level in context unless this is already the case. - foreach(WatchData wd, incompleteLocals) { - const bool contextExpanded = sg->isExpanded(wd.iname); - if (debug) - qDebug() << " " << wd.iname << "CE=" << contextExpanded; - if (contextExpanded) { // You know that already. - wd.setChildrenUnneeded(); - watchHandler->insertData(wd); - } else { - if (!insertSymbolRecursion(wd, sg, watchHandler, 1, 0, errorMessage)) - return false; - } + *value = 0; + switch (dv.Type) { + case DEBUG_VALUE_INT8: + *value = dv.I8; + return true; + case DEBUG_VALUE_INT16: + *value = static_cast<short>(dv.I16); + return true; + case DEBUG_VALUE_INT32: + *value = static_cast<long>(dv.I32); + return true; + case DEBUG_VALUE_INT64: + *value = static_cast<long long>(dv.I64); + return true; + default: + break; } - return true; + return false; } } // namespace Internal diff --git a/src/plugins/debugger/cdb/cdbsymbolgroupcontext.h b/src/plugins/debugger/cdb/cdbsymbolgroupcontext.h index 31beb1d8d71fb31a4900c804df500de40a3cea78..033252e387c989ac561d01073c95a1a38575d26c 100644 --- a/src/plugins/debugger/cdb/cdbsymbolgroupcontext.h +++ b/src/plugins/debugger/cdb/cdbsymbolgroupcontext.h @@ -31,6 +31,7 @@ #define CDBSYMBOLGROUPCONTEXT_H #include "cdbcom.h" +#include "watchhandler.h" #include <QtCore/QString> #include <QtCore/QVector> @@ -72,11 +73,13 @@ public: bool assignValue(const QString &iname, const QString &value, QString *newValue /* = 0 */, QString *errorMessage); - static bool populateModelInitially(CdbSymbolGroupContext *sg, WatchHandler *wh, QString *errorMessage); + template <class OutputIterator> + static bool populateModelInitially(CdbSymbolGroupContext *sg, OutputIterator it, QString *errorMessage); + template <class OutputIterator> static bool completeModel(CdbSymbolGroupContext *sg, const QList<WatchData> &incompleteLocals, - WatchHandler *wh, + OutputIterator it, QString *errorMessage); // Retrieve child symbols of prefix as a sequence of WatchData. @@ -92,6 +95,7 @@ public: // Helper to convert a DEBUG_VALUE structure to a string representation static QString debugValueToString(const DEBUG_VALUE &dv, CIDebugControl *ctl, QString *type = 0, int integerBase = 10); + static bool debugValueToInteger(const DEBUG_VALUE &dv, qint64 *value); // format an array of unsigned longs as "0x323, 0x2322, ..." static QString hexFormatArray(const unsigned short *array, int size); @@ -125,26 +129,25 @@ private: QVector<DEBUG_SYMBOL_PARAMETERS> m_symbolParameters; }; -template <class OutputIterator> -bool CdbSymbolGroupContext::getChildSymbols(const QString &prefix, OutputIterator it, QString *errorMessage) -{ - unsigned long start; - unsigned long parentId; - if (!getChildSymbolsPosition(prefix, &start, &parentId, errorMessage)) - return false; - // Skip over expanded children - const unsigned long end = m_symbolParameters.size(); - for (unsigned long s = start; s < end; ++s) { - const DEBUG_SYMBOL_PARAMETERS &p = m_symbolParameters.at(s); - if (p.ParentSymbol == parentId && isSymbolDisplayable(p)) { - *it = symbolAt(s); - ++it; - } +// Helper to a sequence of WatchData into a list. +class WatchDataBackInserter { +public: + explicit WatchDataBackInserter(QList<WatchData> &wh) : m_wh(wh) {} + + inline WatchDataBackInserter & operator*() { return *this; } + inline WatchDataBackInserter &operator=(const WatchData &wd) { + m_wh.push_back(wd); + return *this; } - return true; -} + inline WatchDataBackInserter &operator++() { return *this; } + +private: + QList<WatchData> &m_wh; +}; } // namespace Internal } // namespace Debugger +#include "cdbsymbolgroupcontext_tpl.h" + #endif // CDBSYMBOLGROUPCONTEXT_H diff --git a/src/plugins/debugger/cdb/cdbsymbolgroupcontext_tpl.h b/src/plugins/debugger/cdb/cdbsymbolgroupcontext_tpl.h new file mode 100644 index 0000000000000000000000000000000000000000..9a2f992bf47b46ba5a7695d560126ffac88db04f --- /dev/null +++ b/src/plugins/debugger/cdb/cdbsymbolgroupcontext_tpl.h @@ -0,0 +1,203 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** Commercial Usage +** +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** GNU Lesser General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** +**************************************************************************/ + +#ifndef CDBSYMBOLGROUPCONTEXT_TPL_H +#define CDBSYMBOLGROUPCONTEXT_TPL_H + +#include <QtCore/QDebug> + +namespace Debugger { +namespace Internal { + +enum { debugSgRecursion = 0 }; + +/* inline static */ bool CdbSymbolGroupContext::isSymbolDisplayable(const DEBUG_SYMBOL_PARAMETERS &p) +{ + if (p.Flags & (DEBUG_SYMBOL_IS_LOCAL|DEBUG_SYMBOL_IS_ARGUMENT)) + return true; + // Do not display static members. + if (p.Flags & DEBUG_SYMBOL_READ_ONLY) + return false; + return true; +} + +template <class OutputIterator> +bool CdbSymbolGroupContext::getChildSymbols(const QString &prefix, OutputIterator it, QString *errorMessage) +{ + unsigned long start; + unsigned long parentId; + if (!getChildSymbolsPosition(prefix, &start, &parentId, errorMessage)) + return false; + // Skip over expanded children + const unsigned long end = m_symbolParameters.size(); + for (unsigned long s = start; s < end; ++s) { + const DEBUG_SYMBOL_PARAMETERS &p = m_symbolParameters.at(s); + if (p.ParentSymbol == parentId && isSymbolDisplayable(p)) { + *it = symbolAt(s); + ++it; + } + } + return true; +} + +template <class OutputIterator> + bool insertChildrenRecursion(const QString &iname, + CdbSymbolGroupContext *sg, + OutputIterator it, + int maxRecursionLevel, + int level, + QString *errorMessage, + int *childCount = 0); + +// Insert a symbol (and its first level children depending on forceRecursion) +template <class OutputIterator> +bool insertSymbolRecursion(WatchData wd, + CdbSymbolGroupContext *sg, + OutputIterator it, + int maxRecursionLevel, + int level, + QString *errorMessage) +{ + // Find out whether to recurse (has children or at least knows it has children) + // Open next level if specified by recursion depth or child is already expanded + // (Sometimes, some root children are already expanded after creating the context). + const bool hasChildren = wd.childCount > 0 || wd.isChildrenNeeded(); + const bool recurse = hasChildren && (level < maxRecursionLevel || sg->isExpanded(wd.iname)); + if (debugSgRecursion) + qDebug() << Q_FUNC_INFO << '\n' << wd.iname << "level=" << level << "recurse=" << recurse; + bool rc = true; + if (recurse) { // Determine number of children and indicate in model + int childCount; + rc = insertChildrenRecursion(wd.iname, sg, it, maxRecursionLevel, level, errorMessage, &childCount); + if (rc) { + wd.setChildCount(childCount); + wd.setChildrenUnneeded(); + } + } else { + // No further recursion at this level, pretend entry is complete + if (wd.isChildrenNeeded()) { + wd.setChildCount(1); + wd.setChildrenUnneeded(); + } + } + if (debugSgRecursion) + qDebug() << " INSERTING: at " << level << wd.toString(); + *it = wd; + ++it; + return rc; +} + +// Insert the children of prefix. +template <class OutputIterator> + bool insertChildrenRecursion(const QString &iname, + CdbSymbolGroupContext *sg, + OutputIterator it, + int maxRecursionLevel, + int level, + QString *errorMessage, + int *childCountPtr) +{ + if (debugSgRecursion > 1) + qDebug() << Q_FUNC_INFO << '\n' << iname << level; + + QList<WatchData> watchList; + // This implicitly enforces expansion + if (!sg->getChildSymbols(iname, WatchDataBackInserter(watchList), errorMessage)) + return false; + + const int childCount = watchList.size(); + if (childCountPtr) + *childCountPtr = childCount; + int succeededChildCount = 0; + for (int c = 0; c < childCount; c++) { + const WatchData &wd = watchList.at(c); + if (wd.isValid()) { // We sometimes get empty names for deeply nested data + if (!insertSymbolRecursion(wd, sg, it, maxRecursionLevel, level + 1, errorMessage)) + return false; + succeededChildCount++; + } else { + const QString msg = QString::fromLatin1("WARNING: Skipping invalid child symbol #%2 (type %3) of '%4'."). + arg(QLatin1String(Q_FUNC_INFO)).arg(c).arg(wd.type, iname); + qWarning("%s\n", qPrintable(msg)); + } + } + if (childCountPtr) + *childCountPtr = succeededChildCount; + return true; +} + +template <class OutputIterator> +bool CdbSymbolGroupContext::populateModelInitially(CdbSymbolGroupContext *sg, + OutputIterator it, + QString *errorMessage) +{ + if (debugSgRecursion) + qDebug() << "###" << Q_FUNC_INFO; + + // Insert root items + QList<WatchData> watchList; + if (!sg->getChildSymbols(sg->prefix(), WatchDataBackInserter(watchList), errorMessage)) + return false; + + foreach(const WatchData &wd, watchList) + if (!insertSymbolRecursion(wd, sg, it, 0, 0, errorMessage)) + return false; + return true; +} + +template <class OutputIterator> +bool CdbSymbolGroupContext::completeModel(CdbSymbolGroupContext *sg, + const QList<WatchData> &incompleteLocals, + OutputIterator it, + QString *errorMessage) +{ + if (debugSgRecursion) + qDebug().nospace() << "###>" << Q_FUNC_INFO << ' ' << incompleteLocals.size() << '\n'; + // The view reinserts any node being expanded with flag 'ChildrenNeeded'. + // Recurse down one level in context unless this is already the case. + foreach(WatchData wd, incompleteLocals) { + const bool contextExpanded = sg->isExpanded(wd.iname); + if (debugSgRecursion) + qDebug() << " " << wd.iname << "CE=" << contextExpanded; + if (contextExpanded) { // You know that already. + wd.setChildrenUnneeded(); + *it = wd; + ++it; + } else { + if (!insertSymbolRecursion(wd, sg, it, 1, 0, errorMessage)) + return false; + } + } + return true; +} + +} // namespace Internal +} // namespace Debugger + +#endif // CDBSYMBOLGROUPCONTEXT_TPL_H diff --git a/src/plugins/debugger/debuggermanager.h b/src/plugins/debugger/debuggermanager.h index 6ef682829d7a61ac73a5ad282a5b17f5e2a25f39..67b3e988372f8a17d05d7afac56bebaf7cbdc07c 100644 --- a/src/plugins/debugger/debuggermanager.h +++ b/src/plugins/debugger/debuggermanager.h @@ -146,6 +146,7 @@ private: friend class ScriptEngine; friend struct CdbDebugEnginePrivate; friend class CdbDumperHelper; + friend class CdbExceptionLoggerEventCallback; // called from the engines after successful startup virtual void notifyInferiorStopRequested() = 0; diff --git a/src/plugins/debugger/gdbengine.cpp b/src/plugins/debugger/gdbengine.cpp index e163b99802f24ef73f9d681cb9072fe0fa0f85ae..da1fead265e2435cbd72cd65d9e1d2be6c436fc0 100644 --- a/src/plugins/debugger/gdbengine.cpp +++ b/src/plugins/debugger/gdbengine.cpp @@ -55,6 +55,7 @@ #include <QtCore/QFileInfo> #include <QtCore/QTime> #include <QtCore/QTimer> +#include <QtCore/QTextStream> #include <QtGui/QAction> #include <QtGui/QApplication> @@ -2913,44 +2914,6 @@ void GdbEngine::setToolTipExpression(const QPoint &pos, const QString &exp0) static const QString strNotInScope = QLatin1String("<not in scope>"); -static QString quoteUnprintableLatin1(const QByteArray &ba) -{ - QString res; - char buf[10]; - for (int i = 0, n = ba.size(); i != n; ++i) { - unsigned char c = ba.at(i); - if (isprint(c)) { - res += c; - } else { - qsnprintf(buf, sizeof(buf) - 1, "\\%x", int(c)); - res += buf; - } - } - return res; -} - -static QString decodeData(QByteArray ba, int encoding) -{ - switch (encoding) { - case 0: // unencoded 8 bit data - return quoteUnprintableLatin1(ba); - case 1: // base64 encoded 8 bit data, used for QByteArray - ba = QByteArray::fromBase64(ba); - return '"' + quoteUnprintableLatin1(ba) + '"'; - case 2: // base64 encoded 16 bit data, used for QString - ba = QByteArray::fromBase64(ba); - return '"' + QString::fromUtf16((ushort *)ba.data(), ba.size() / 2) + '"'; - case 3: // base64 encoded 32 bit data - ba = QByteArray::fromBase64(ba); - return '"' + QString::fromUcs4((uint *)ba.data(), ba.size() / 4) + '"'; - break; - case 4: // base64 encoded 16 bit data, without quotes (see 2) - ba = QByteArray::fromBase64(ba); - return QString::fromUtf16((ushort *)ba.data(), ba.size() / 2); - } - return "<Encoding error>"; -} - static void setWatchDataValue(WatchData &data, const GdbMi &mi, int encoding = 0) { @@ -3041,15 +3004,7 @@ bool GdbEngine::hasDebuggingHelperForType(const QString &type) const return false; // simple types - if (m_availableSimpleDebuggingHelpers.contains(type)) - return true; - - // templates - QString tmplate; - QString inner; - if (!extractTemplate(type, &tmplate, &inner)) - return false; - return m_availableSimpleDebuggingHelpers.contains(tmplate); + return m_dumperHelper.type(type) != QtDumperHelper::UnknownType; } void GdbEngine::runDirectDebuggingHelper(const WatchData &data, bool dumpChildren) @@ -3080,145 +3035,33 @@ void GdbEngine::runDebuggingHelper(const WatchData &data0, bool dumpChildren) } WatchData data = data0; QTC_ASSERT(!data.exp.isEmpty(), return); - QString tmplate; - QString inner; - bool isTemplate = extractTemplate(data.type, &tmplate, &inner); - QStringList inners = inner.split('@'); - if (inners.at(0).isEmpty()) - inners.clear(); - for (int i = 0; i != inners.size(); ++i) - inners[i] = inners[i].simplified(); - - QString outertype = isTemplate ? tmplate : data.type; - // adjust the data extract - if (outertype == m_namespace + "QWidget") - outertype = m_namespace + "QObject"; - - QString extraArgs[4]; - extraArgs[0] = "0"; - extraArgs[1] = "0"; - extraArgs[2] = "0"; - extraArgs[3] = "0"; - - int extraArgCount = 0; - - // "generic" template dumpers: passing sizeof(argument) - // gives already most information the dumpers need - foreach (const QString &arg, inners) - extraArgs[extraArgCount++] = sizeofTypeExpression(arg); - - // in rare cases we need more or less: - if (outertype == m_namespace + "QObject") { - extraArgs[0] = "(char*)&((('" - + m_namespace + "QObjectPrivate'*)&" - + data.exp + ")->children)-(char*)&" + data.exp; - } else if (outertype == m_namespace + "QVector") { - extraArgs[1] = "(char*)&((" - + data.exp + ").d->array)-(char*)" + data.exp + ".d"; - } else if (outertype == m_namespace + "QObjectSlot" - || outertype == m_namespace + "QObjectSignal") { - // we need the number out of something like - // iname="local.ob.slots.2" // ".deleteLater()"? - int pos = data.iname.lastIndexOf('.'); - QString slotNumber = data.iname.mid(pos + 1); - QTC_ASSERT(slotNumber.toInt() != -1, /**/); - extraArgs[0] = slotNumber; - } else if (outertype == m_namespace + "QMap" || outertype == m_namespace + "QMultiMap") { - QString nodetype; - if (m_qtVersion >= (4 << 16) + (5 << 8) + 0) { - nodetype = m_namespace + "QMapNode"; - nodetype += data.type.mid(outertype.size()); - } else { - // FIXME: doesn't work for QMultiMap - nodetype = data.type + "::Node"; - } - //qDebug() << "OUTERTYPE: " << outertype << " NODETYPE: " << nodetype - // << "QT VERSION" << m_qtVersion << ((4 << 16) + (5 << 8) + 0); - extraArgs[2] = sizeofTypeExpression(nodetype); - extraArgs[3] = "(size_t)&(('" + nodetype + "'*)0)->value"; - } else if (outertype == m_namespace + "QMapNode") { - extraArgs[2] = sizeofTypeExpression(data.type); - extraArgs[3] = "(size_t)&(('" + data.type + "'*)0)->value"; - } else if (outertype == "std::vector" || outertype == "vector") { - //qDebug() << "EXTRACT TEMPLATE: " << outertype << inners; - if (inners.at(0) == "bool") { - outertype = "std::vector::bool"; - } else { - //extraArgs[extraArgCount++] = sizeofTypeExpression(data.type); - //extraArgs[extraArgCount++] = "(size_t)&(('" + data.type + "'*)0)->value"; - } - } else if (outertype == "std::deque" || outertype == "deque") { - // remove 'std::allocator<...>': - extraArgs[1] = "0"; - } else if (outertype == "std::stack" || outertype == "stack") { - // remove 'std::allocator<...>': - extraArgs[1] = "0"; - } else if (outertype == "std::set" || outertype == "set") { - // remove 'std::less<...>': - extraArgs[1] = "0"; - // remove 'std::allocator<...>': - extraArgs[2] = "0"; - } else if (outertype == "std::map" || outertype == "map") { - // We don't want the comparator and the allocator confuse gdb. - // But we need the offset of the second item in the value pair. - // We read the type of the pair from the allocator argument because - // that gets the constness "right" (in the sense that gdb can - // read it back; - QString pairType = inners.at(3); - // remove 'std::allocator<...>': - pairType = pairType.mid(15, pairType.size() - 15 - 2); - extraArgs[2] = "(size_t)&(('" + pairType + "'*)0)->second"; - extraArgs[3] = "0"; - } else if (outertype == "std::basic_string" || outertype == "basic_string") { - //qDebug() << "EXTRACT TEMPLATE: " << outertype << inners; - if (inners.at(0) == "char") { - outertype = "std::string"; - } else if (inners.at(0) == "wchar_t") { - outertype = "std::wstring"; - } - extraArgs[0] = "0"; - extraArgs[1] = "0"; - extraArgs[2] = "0"; - extraArgs[3] = "0"; - } + + QByteArray params; + QStringList extraArgs; + const QtDumperHelper::TypeData td = m_dumperHelper.typeData(data0.type); + m_dumperHelper.evaluationParameters(data, td, QtDumperHelper::GdbDebugger, ¶ms, &extraArgs); //int protocol = (data.iname.startsWith("watch") && data.type == "QImage") ? 3 : 2; //int protocol = data.iname.startsWith("watch") ? 3 : 2; - int protocol = 2; + const int protocol = 2; //int protocol = isDisplayedIName(data.iname) ? 3 : 2; QString addr; - if (data.addr.startsWith("0x")) - addr = "(void*)" + data.addr; - else - addr = "&(" + data.exp + ")"; - - QByteArray params; - params.append(outertype.toUtf8()); - params.append('\0'); - params.append(data.iname.toUtf8()); - params.append('\0'); - params.append(data.exp.toUtf8()); - params.append('\0'); - params.append(inner.toUtf8()); - params.append('\0'); - params.append(data.iname.toUtf8()); - params.append('\0'); + if (data.addr.startsWith(QLatin1String("0x"))) { + addr = QLatin1String("(void*)") + data.addr; + } else { + addr = QLatin1String("&(") + data.exp + QLatin1Char(')'); + } sendWatchParameters(params); - QString cmd ="call " - + QString("(void*)qDumpObjectData440(") - + QString::number(protocol) - + ',' + "%1+1" // placeholder for token - + ',' + addr - + ',' + (dumpChildren ? "1" : "0") - + ',' + extraArgs[0] - + ',' + extraArgs[1] - + ',' + extraArgs[2] - + ',' + extraArgs[3] + ')'; + QString cmd; + QTextStream(&cmd) << "call " << "(void*)qDumpObjectData440(" << + protocol << ',' << "%1+1" // placeholder for token + <<',' << addr << ',' << (dumpChildren ? "1" : "0") + << ',' << extraArgs.join(QString(QLatin1Char(','))) << ')'; - //qDebug() << "CMD: " << cmd; + qDebug() << "CMD: " << cmd; QVariant var; var.setValue(data); @@ -3451,6 +3294,7 @@ void GdbEngine::updateWatchModel2() void GdbEngine::handleQueryDebuggingHelper(const GdbResultRecord &record) { + m_dumperHelper.clear(); //qDebug() << "DATA DUMPER TRIAL:" << record.toString(); GdbMi output = record.data.findChild("consolestreamoutput"); QByteArray out = output.data(); @@ -3464,23 +3308,27 @@ void GdbEngine::handleQueryDebuggingHelper(const GdbResultRecord &record) GdbMi contents; contents.fromString(out); GdbMi simple = contents.findChild("dumpers"); - m_namespace = contents.findChild("namespace").data(); + + m_dumperHelper.setQtNamespace(contents.findChild("namespace").data()); GdbMi qtversion = contents.findChild("qtversion"); + int qtv = 0; if (qtversion.children().size() == 3) { - m_qtVersion = (qtversion.childAt(0).data().toInt() << 16) + qtv = (qtversion.childAt(0).data().toInt() << 16) + (qtversion.childAt(1).data().toInt() << 8) + qtversion.childAt(2).data().toInt(); //qDebug() << "FOUND QT VERSION: " << qtversion.toString() << m_qtVersion; - } else { - m_qtVersion = 0; } + m_dumperHelper.setQtVersion(qtv); //qDebug() << "CONTENTS: " << contents.toString(); //qDebug() << "SIMPLE DUMPERS: " << simple.toString(); - m_availableSimpleDebuggingHelpers.clear(); + + QStringList availableSimpleDebuggingHelpers; foreach (const GdbMi &item, simple.children()) - m_availableSimpleDebuggingHelpers.append(item.data()); - if (m_availableSimpleDebuggingHelpers.isEmpty()) { + availableSimpleDebuggingHelpers.append(item.data()); + m_dumperHelper.parseQueryTypes(availableSimpleDebuggingHelpers, QtDumperHelper::GdbDebugger); + + if (availableSimpleDebuggingHelpers.isEmpty()) { m_debuggingHelperState = DebuggingHelperUnavailable; q->showStatusMessage(tr("Debugging helpers not found.")); //QMessageBox::warning(q->mainWindow(), @@ -3494,8 +3342,9 @@ void GdbEngine::handleQueryDebuggingHelper(const GdbResultRecord &record) } else { m_debuggingHelperState = DebuggingHelperAvailable; q->showStatusMessage(tr("%1 custom dumpers found.") - .arg(m_availableSimpleDebuggingHelpers.size())); + .arg(m_dumperHelper.typeCount())); } + qDebug() << m_dumperHelper.toString(true); //qDebug() << "DATA DUMPERS AVAILABLE" << m_availableSimpleDebuggingHelpers; } diff --git a/src/plugins/debugger/gdbengine.h b/src/plugins/debugger/gdbengine.h index 67a7c9a24249154bb8824811cd0467bb764a33c5..5b12c8a7392d932ca8135d99222bf45391ade67d 100644 --- a/src/plugins/debugger/gdbengine.h +++ b/src/plugins/debugger/gdbengine.h @@ -33,6 +33,7 @@ #include "idebuggerengine.h" #include "gdbmi.h" #include "outputcollector.h" +#include "watchutils.h" #include <consoleprocess.h> @@ -342,9 +343,7 @@ private: QString m_editedData; int m_pendingRequests; - QStringList m_availableSimpleDebuggingHelpers; - QString m_namespace; // namespace used in "namespaced Qt"; - int m_qtVersion; // Qt version used in the debugged program + QtDumperHelper m_dumperHelper; DebuggingHelperState m_debuggingHelperState; QList<GdbMi> m_currentFunctionArgs; diff --git a/src/plugins/debugger/watchhandler.cpp b/src/plugins/debugger/watchhandler.cpp index 105f8384faec5e07b74c956091f9a37ddaa23f11..144f85f4f8dd9af3e40aed65a8ffb2845f8c7dcc 100644 --- a/src/plugins/debugger/watchhandler.cpp +++ b/src/plugins/debugger/watchhandler.cpp @@ -78,6 +78,7 @@ static int watcherCounter = 0; WatchData::WatchData() : childCount(-1), valuedisabled(false), + source(0), state(InitialState), parentIndex(-1), row(-1), @@ -807,6 +808,8 @@ void WatchHandler::cleanup() m_completeSet = initialSet(); m_displaySet = m_completeSet; + rebuildModel(); // to get the dummy entries back + #if 0 for (EditWindows::ConstIterator it = m_editWindows.begin(); it != m_editWindows.end(); ++it) { diff --git a/src/plugins/debugger/watchhandler.h b/src/plugins/debugger/watchhandler.h index 13f37dabf49c2a1cb1d6a206bdb708c78a1fa3dd..ed565044b29f847507bdf5305dd822b08727020c 100644 --- a/src/plugins/debugger/watchhandler.h +++ b/src/plugins/debugger/watchhandler.h @@ -124,6 +124,7 @@ public: private: public: + int source; // Used by some debuggers (CDB) to tell where it originates from (dumper or symbol evaluation) int state; // Model diff --git a/src/plugins/debugger/watchutils.cpp b/src/plugins/debugger/watchutils.cpp index cc1a9784870a3ee57ecdad660e03d6708a0295ca..c1150c2ad8f15598a3180500263bf8307d0b1066 100644 --- a/src/plugins/debugger/watchutils.cpp +++ b/src/plugins/debugger/watchutils.cpp @@ -28,10 +28,19 @@ **************************************************************************/ #include "watchutils.h" +#include "watchhandler.h" +#include <utils/qtcassert.h> #include <QtCore/QDebug> #include <QtCore/QTime> #include <QtCore/QStringList> +#include <QtCore/QCoreApplication> +#include <QtCore/QTextStream> + +#include <string.h> +#include <ctype.h> + +enum { debug = 0 }; namespace Debugger { namespace Internal { @@ -299,58 +308,957 @@ QString sizeofTypeExpression(const QString &type) return QLatin1String("sizeof(") + gdbQuoteTypes(type) + QLatin1Char(')'); } +// Utilities to decode string data returned by the dumper helpers. + +static QString quoteUnprintableLatin1(const QByteArray &ba) +{ + QString res; + char buf[10]; + for (int i = 0, n = ba.size(); i != n; ++i) { + const unsigned char c = ba.at(i); + if (isprint(c)) { + res += c; + } else { + qsnprintf(buf, sizeof(buf) - 1, "\\%x", int(c)); + res += buf; + } + } + return res; +} + +QString decodeData(const QByteArray &baIn, int encoding) +{ + switch (encoding) { + case 0: // unencoded 8 bit data + return quoteUnprintableLatin1(baIn); + case 1: { // base64 encoded 8 bit data, used for QByteArray + const QChar doubleQuote(QLatin1Char('"')); + QString rc = doubleQuote; + rc += quoteUnprintableLatin1(QByteArray::fromBase64(baIn)); + rc += doubleQuote; + return rc; + } + case 2: { // base64 encoded 16 bit data, used for QString + const QChar doubleQuote(QLatin1Char('"')); + const QByteArray ba = QByteArray::fromBase64(baIn); + QString rc = doubleQuote; + rc += QString::fromUtf16(reinterpret_cast<const ushort *>(ba.data()), ba.size() / 2); + rc += doubleQuote; + return rc; + } + case 3: { // base64 encoded 32 bit data + const QByteArray ba = QByteArray::fromBase64(baIn); + const QChar doubleQuote(QLatin1Char('"')); + QString rc = doubleQuote; + rc += QString::fromUcs4(reinterpret_cast<const uint *>(ba.data()), ba.size() / 4); + rc += doubleQuote; + return rc; + } + case 4: { // base64 encoded 16 bit data, without quotes (see 2) + const QByteArray ba = QByteArray::fromBase64(baIn); + return QString::fromUtf16(reinterpret_cast<const ushort *>(ba.data()), ba.size() / 2); + } + } + return QCoreApplication::translate("Debugger", "<Encoding error>"); +} + +// --------------- QtDumperResult + +QtDumperResult::Child::Child() : + valueEncoded(0) +{ +} + +QtDumperResult::QtDumperResult() : + valueEncoded(0), + valuedisabled(false), + childCount(0), + internal(false) + +{ +} + +void QtDumperResult::clear() +{ + iname.clear(); + value.clear(); + address.clear(); + type.clear(); + valueEncoded = 0; + valuedisabled = false; + childCount = 0; + internal = false; + childType.clear(); + children.clear(); +} + +QList<WatchData> QtDumperResult::toWatchData(int source) const +{ + QList<WatchData> rc; + rc.push_back(WatchData()); + WatchData &root = rc.front(); + root.iname = iname; + const QChar dot = QLatin1Char('.'); + const int lastDotIndex = root.iname.lastIndexOf(dot); + root.exp = root.name = lastDotIndex == -1 ? iname : iname.mid(lastDotIndex + 1); + root.setValue(decodeData(value, valueEncoded)); + root.setType(type); + root.valuedisabled = valuedisabled; + root.setAddress(address); + root.source = source; + root.setChildCount(childCount); + // Children + if (childCount > 0) { + if (children.size() == childCount) { + for (int c = 0; c < childCount; c++) { + const Child &dchild = children.at(c); + rc.push_back(WatchData()); + WatchData &wchild = rc.back(); + wchild.source = source; + wchild.iname = iname; + wchild.iname += dot; + wchild.iname += dchild.name; + wchild.exp = wchild.name = dchild.name; + wchild.setType(childType); + wchild.setAddress(dchild.address); + wchild.setValue(decodeData(dchild.value, dchild.valueEncoded)); + wchild.setChildCount(0); + } + root.setChildrenUnneeded(); + } else { + root.setChildrenNeeded(); + } + } + return rc; +} + +QDebug operator<<(QDebug in, const QtDumperResult &d) +{ + QDebug nospace = in.nospace(); + nospace << " iname=" << d.iname << " type=" << d.type << " address=" << d.address + << " value=" << d.value + << " disabled=" << d.valuedisabled + << " encoded=" << d.valueEncoded << " internal=" << d.internal; + if (d.childCount) { + nospace << " childCount=" << d.childCount + << " childType=" << d.childType << '\n'; + const int childCount = d.children.size(); + for (int i = 0; i < childCount; i++) { + const QtDumperResult::Child &c = d.children.at(i); + nospace << " #" << i << " addr=" << c.address + << " name=" << c.name << " encoded=" << c.valueEncoded + << " value=" << c.value << '\n'; + } + } + return in; +} + +// ----------------- QtDumperHelper::TypeData +QtDumperHelper::TypeData::TypeData() : + type(UnknownType), + isTemplate(false) +{ +} + +void QtDumperHelper::TypeData::clear() +{ + isTemplate = false; + type = UnknownType; + tmplate.clear(); + inner.clear(); +} + +// ----------------- QtDumperHelper +QtDumperHelper::QtDumperHelper() : + m_qtVersion(0) +{ +} + +void QtDumperHelper::clear() +{ + m_nameTypeMap.clear(); + m_qtVersion = 0; + m_qtNamespace.clear(); +} + +static inline void formatQtVersion(int v, QTextStream &str) +{ + str << ((v >> 16) & 0xFF) << '.' << ((v >> 8) & 0xFF) << '.' << (v & 0xFF); +} + +QString QtDumperHelper::toString(bool debug) const +{ + if (debug) { + QString rc; + QTextStream str(&rc); + str << "version="; + formatQtVersion(m_qtVersion, str); + str << " namespace='" << m_qtNamespace << "'," << m_nameTypeMap.size() << " known types: "; + const NameTypeMap::const_iterator cend = m_nameTypeMap.constEnd(); + for (NameTypeMap::const_iterator it = m_nameTypeMap.constBegin(); it != cend; ++it) { + str <<",[" << it.key() << ',' << it.value() << ']'; + } + return rc; + } + const QString nameSpace = m_qtNamespace.isEmpty() ? QCoreApplication::translate("QtDumperHelper", "<none>") : m_qtNamespace; + return QCoreApplication::translate("QtDumperHelper", + "%n known types, Qt version: %1, Qt namespace: %2", + 0, QCoreApplication::CodecForTr, + m_nameTypeMap.size()).arg(qtVersionString(), nameSpace); +} + +QtDumperHelper::Type QtDumperHelper::simpleType(const QString &simpleType) const +{ + return m_nameTypeMap.value(simpleType, UnknownType); +} + +int QtDumperHelper::qtVersion() const +{ + return m_qtVersion; +} + +QString QtDumperHelper::qtNamespace() const +{ + return m_qtNamespace; +} + +void QtDumperHelper::setQtNamespace(const QString &qtNamespace) +{ + m_qtNamespace = qtNamespace; +} + +int QtDumperHelper::typeCount() const +{ + return m_nameTypeMap.size(); +} + +// Look up unnamespaced 'std' types. +static inline QtDumperHelper::Type stdType(const QString &s) +{ + if (s == QLatin1String("vector")) + return QtDumperHelper::StdVectorType; + if (s == QLatin1String("deque")) + return QtDumperHelper::StdDequeType; + if (s == QLatin1String("set")) + return QtDumperHelper::StdSetType; + if (s == QLatin1String("stack")) + return QtDumperHelper::StdStackType; + if (s == QLatin1String("map")) + return QtDumperHelper::StdMapType; + if (s == QLatin1String("basic_string")) + return QtDumperHelper::StdStringType; + return QtDumperHelper::UnknownType; +} + +QtDumperHelper::Type QtDumperHelper::specialType(QString s) +{ + // Std classes. + if (s.startsWith(QLatin1String("std::"))) + return stdType(s.mid(5)); + // Strip namespace + const int namespaceIndex = s.lastIndexOf(QLatin1String("::")); + if (namespaceIndex == -1) { + // None ... check for std.. + const Type sType = stdType(s); + if (sType != UnknownType) + return sType; + } else { + s.remove(namespaceIndex + 2); + } + if (s == QLatin1String("QObject")) + return QObjectType; + if (s == QLatin1String("QWidget")) + return QWidgetType; + if (s == QLatin1String("QObjectSlot")) + return QObjectSlotType; + if (s == QLatin1String("QObjectSignal")) + return QObjectSignalType; + if (s == QLatin1String("QVector")) + return QVectorType; + if (s == QLatin1String("QMap")) + return QMapType; + if (s == QLatin1String("QMultiMap")) + return QMultiMapType; + if (s == QLatin1String("QMapNode")) + return QMapNodeType; + return UnknownType; +} + + +bool QtDumperHelper::needsExpressionSyntax(Type t) +{ + switch (t) { + case QObjectType: + case QWidgetType: + case QObjectSlotType: + case QObjectSignalType: + case QMapType: + case QVectorType: + case QMultiMapType: + case QMapNodeType: + case StdMapType: + return true; + default: + break; + } + return false; +} + +QString QtDumperHelper::qtVersionString() const +{ + QString rc; + QTextStream str(&rc); + formatQtVersion(m_qtVersion, str); + return rc; +} + +void QtDumperHelper::setQtVersion(int v) +{ + m_qtVersion = v; +} + +void QtDumperHelper::setQtVersion(const QString &v) +{ + m_qtVersion = 0; + const QStringList vl = v.split(QLatin1Char('.')); + if (vl.size() == 3) { + const int major = vl.at(0).toInt(); + const int minor = vl.at(1).toInt(); + const int patch = vl.at(2).toInt(); + m_qtVersion = (major << 16) | (minor << 8) | patch; + } +} + +// Parse a list of types. +void QtDumperHelper::parseQueryTypes(const QStringList &l, Debugger debugger) +{ + m_nameTypeMap.clear(); + const int count = l.count(); + for (int i = 0; i < count; i++) { + const Type t = specialType(l.at(i)); + if (t != UnknownType) { + // Exclude types that require expression syntax for CDB + if (debugger == GdbDebugger || !needsExpressionSyntax(t)) { + m_nameTypeMap.insert(l.at(i), t); + } + } else { + m_nameTypeMap.insert(l.at(i), SupportedType); + } + } +} + +/* A parse for dumper output: + * "iname="local.sl",addr="0x0012BA84",value="<3 items>",valuedisabled="true", + * numchild="3",childtype="QString",childnumchild="0",children=[{name="0",value="<binhex>", + * valueencoded="2"},{name="1",value="dAB3AG8A",valueencoded="2"},{name="2", + * value="dABoAHIAZQBlAA==",valueencoded="2"}]" + * Default implementation can be used for debugging purposes. */ + +class DumperParser { +public: + explicit DumperParser(const char *s) : m_s(s) {} + bool run(); + +protected: + // handle 'key="value"' + virtual bool handleKeyword(const char *k, int size); + virtual bool handleListStart(); + virtual bool handleListEnd(); + virtual bool handleHashStart(); + virtual bool handleHashEnd(); + virtual bool handleValue(const char *k, int size); + +private: + bool parseHash(int level, const char *&pos); + bool parseValue(int level, const char *&pos); + bool parseStringValue(const char *&ptr, int &size, const char *&pos) const; + + const char *m_s; +}; + +// get a string value with pos at the opening double quote +bool DumperParser::parseStringValue(const char *&ptr, int &size, const char *&pos) const +{ + pos++; + const char *endValuePtr = strchr(pos, '"'); + if (!endValuePtr) + return false; + size = endValuePtr - pos; + ptr = pos; + pos = endValuePtr + 1; + return true; +} + +bool DumperParser::run() +{ + const char *ptr = m_s; + const bool rc = parseHash(0, ptr); + if (debug) + qDebug() << Q_FUNC_INFO << '\n' << m_s << rc; + return rc; +} + +// Parse a non-empty hash with pos at the first keyword. +// Curly braces are present at level 0 only. +// '{a="X", b="X"}' +bool DumperParser::parseHash(int level, const char *&pos) +{ + while (true) { + switch (*pos) { + case '\0': // EOS is acceptable at level 0 only + return level == 0; + case '}': + pos++; + return true; + default: + break; + } + const char *equalsPtr = strchr(pos, '='); + if (!equalsPtr) + return false; + const int keywordLen = equalsPtr - pos; + if (!handleKeyword(pos, keywordLen)) + return false; + pos = equalsPtr + 1; + if (!*pos) + return false; + if (!parseValue(level + 1, pos)) + return false; + if (*pos == ',') + pos++; + } + return false; +} + +bool DumperParser::parseValue(int level, const char *&pos) +{ + // Simple string literal + switch (*pos) { + case '"': { + const char *valuePtr; + int valueSize; + return parseStringValue(valuePtr, valueSize, pos) && handleValue(valuePtr, valueSize); + } + // A List. Note that it has a trailing comma '["a",]' + case '[': { + if (!handleListStart()) + return false; + pos++; + while (true) { + switch (*pos) { + case ']': + pos++; + return handleListEnd(); + case '\0': + return false; + default: + break; + } + if (!parseValue(level + 1, pos)) + return false; + if (*pos == ',') + pos++; + } + } + return false; + // A hash '{a="b",b="c"}' + case '{': { + if (!handleHashStart()) + return false; + pos++; + if (!parseHash(level + 1, pos)) + return false; + return handleHashEnd(); + } + return false; + } + return false; +} + +bool DumperParser::handleKeyword(const char *k, int size) +{ + if (debug) + qDebug() << Q_FUNC_INFO << '\n' << QByteArray(k, size); + return true; +} + +bool DumperParser::handleListStart() +{ + if (debug) + qDebug() << Q_FUNC_INFO; + return true; +} + +bool DumperParser::handleListEnd() +{ + if (debug) + qDebug() << Q_FUNC_INFO; + return true; +} + +bool DumperParser::handleHashStart() +{ + if (debug) + qDebug() << Q_FUNC_INFO; + return true; +} + +bool DumperParser::handleHashEnd() +{ + if (debug) + qDebug() << Q_FUNC_INFO; + + return true; +} + +bool DumperParser::handleValue(const char *k, int size) +{ + if (debug) + qDebug() << Q_FUNC_INFO << '\n' << QByteArray(k, size); + return true; +} + /* 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('.')); +class QueryDumperParser : public DumperParser { +public: + explicit QueryDumperParser(const char *s); + + struct Data { + Data() : qtVersion(0) {} + QString qtNameSpace; + QString qtVersion; + QStringList types; + }; + + inline Data data() const { return m_data; } + +protected: + virtual bool handleKeyword(const char *k, int size); + virtual bool handleListStart(); + virtual bool handleListEnd(); + virtual bool handleValue(const char *k, int size); + +private: + enum Mode { None, ExpectingDumpers, ExpectingVersion, ExpectingNameSpace }; + Mode m_mode; + Data m_data; +}; + +QueryDumperParser::QueryDumperParser(const char *s) : + DumperParser(s), + m_mode(None) +{ +} + +bool QueryDumperParser::handleKeyword(const char *k, int size) +{ + if (!qstrncmp(k, "dumpers", size)) { + m_mode = ExpectingDumpers; + return true; + } + if (!qstrncmp(k, "qtversion", size)) { + m_mode = ExpectingVersion; + return true; + } + if (!qstrncmp(k, "namespace", size)) { + m_mode = ExpectingNameSpace; + return true; + } + qWarning("%s Unexpected keyword %s.\n", Q_FUNC_INFO, QByteArray(k, size).constData()); + return false; +} + +bool QueryDumperParser::handleListStart() +{ + return m_mode == ExpectingDumpers || m_mode == ExpectingVersion; +} + +bool QueryDumperParser::handleListEnd() +{ + m_mode = None; + return true; +} + +bool QueryDumperParser::handleValue(const char *k, int size) +{ + switch (m_mode) { + case None: + return false; + case ExpectingDumpers: + m_data.types.push_back(QString::fromLatin1(k, size)); + break; + case ExpectingNameSpace: + m_data.qtNameSpace = QString::fromLatin1(k, size); + break; + case ExpectingVersion: // ["4","1","5"] + if (!m_data.qtVersion.isEmpty()) + m_data.qtVersion += QLatin1Char('.'); + m_data.qtVersion += QString::fromLatin1(k, size); + break; + } + return true; +} + +// parse a query +bool QtDumperHelper::parseQuery(const char *data, Debugger debugger) +{ + + QueryDumperParser parser(data); + if (!parser.run()) + return false; + clear(); + m_qtNamespace = parser.data().qtNameSpace; + setQtVersion(parser.data().qtVersion); + parseQueryTypes(parser.data().types, debugger); + return true; +} + +QtDumperHelper::Type QtDumperHelper::type(const QString &typeName) const +{ + const QtDumperHelper::TypeData td = typeData(typeName); + return td.type; +} + +QtDumperHelper::TypeData QtDumperHelper::typeData(const QString &typeName) const +{ + TypeData td; + td.type = UnknownType; + const Type st = simpleType(typeName); + if (st != UnknownType) { + td.isTemplate = false; + td.type =st; + return td; + } + // Try template + td.isTemplate = extractTemplate(typeName, &td.tmplate, &td.inner); + if (!td.isTemplate) + return td; + // Check the template type QMap<X,Y> -> 'QMap' + td.type = simpleType(td.tmplate); + return td; +} + +void QtDumperHelper::evaluationParameters(const WatchData &data, + const TypeData &td, + Debugger /* debugger */, + QByteArray *inBuffer, + QStringList *extraArgsIn) const +{ + enum { maxExtraArgCount = 4 }; + + QStringList &extraArgs = *extraArgsIn; + + // See extractTemplate for parameters + QStringList inners = td.inner.split(QLatin1Char('@')); + if (inners.at(0).isEmpty()) + inners.clear(); + for (int i = 0; i != inners.size(); ++i) + inners[i] = inners[i].simplified(); + + QString outertype = td.isTemplate ? td.tmplate : data.type; + // adjust the data extract + if (outertype == m_qtNamespace + QLatin1String("QWidget")) + outertype = m_qtNamespace + QLatin1String("QObject"); + + extraArgs.clear(); + + if (!inners.empty()) { + // "generic" template dumpers: passing sizeof(argument) + // gives already most information the dumpers need + const int count = qMin(int(maxExtraArgCount), inners.size()); + for (int i = 0; i < count; i++) + extraArgs.push_back(sizeofTypeExpression(inners.at(i))); + } + int extraArgCount = extraArgs.size(); + // Pad with zeros + const QString zero = QString(QLatin1Char('0')); + const int extraPad = maxExtraArgCount - extraArgCount; + for (int i = 0; i < extraPad; i++) + extraArgs.push_back(zero); + + // in rare cases we need more or less: + switch (td.type) { + case QObjectType: + case QWidgetType: + extraArgs[0] = QLatin1String("(char*)&((('"); + extraArgs[0] += m_qtNamespace; + extraArgs[0] += QLatin1String("QObjectPrivate'*)&"); + extraArgs[0] += data.exp; + extraArgs[0] += QLatin1String(")->children)-(char*)&"); + extraArgs[0] += data.exp; + break; + case QVectorType: + extraArgs[1] = QLatin1String("(char*)&(("); + extraArgs[1] += data.exp; + extraArgs[1] += QLatin1String(").d->array)-(char*)"); + extraArgs[1] += data.exp; + extraArgs[1] += QLatin1String(".d"); + break; + case QObjectSlotType: + case QObjectSignalType: { + // we need the number out of something like + // iname="local.ob.slots.2" // ".deleteLater()"? + const int pos = data.iname.lastIndexOf('.'); + const QString slotNumber = data.iname.mid(pos + 1); + QTC_ASSERT(slotNumber.toInt() != -1, /**/); + extraArgs[0] = slotNumber; + } + break; + case QMapType: + case QMultiMapType: { + QString nodetype; + if (m_qtVersion >= (4 << 16) + (5 << 8) + 0) { + nodetype = m_qtNamespace + QLatin1String("QMapNode"); + nodetype += data.type.mid(outertype.size()); } else { - if (keyword == dumperKey) { - *types = value.remove(doubleQuote).split(QLatin1Char(',')); - } + // FIXME: doesn't work for QMultiMap + nodetype = data.type + QLatin1String("::Node"); } + //qDebug() << "OUTERTYPE: " << outertype << " NODETYPE: " << nodetype + // << "QT VERSION" << m_qtVersion << ((4 << 16) + (5 << 8) + 0); + extraArgs[2] = sizeofTypeExpression(nodetype); + extraArgs[3] = QLatin1String("(size_t)&(('"); + extraArgs[3] += nodetype; + extraArgs[3] += QLatin1String("'*)0)->value"); } - // find next keyword - while (pos < size && !s.at(pos).isLetterOrNumber()) - pos++; + break; + case QMapNodeType: + extraArgs[2] = sizeofTypeExpression(data.type); + extraArgs[3] = QLatin1String("(size_t)&(('"); + extraArgs[3] += data.type; + extraArgs[3] += QLatin1String("'*)0)->value"); + break; + case StdVectorType: + //qDebug() << "EXTRACT TEMPLATE: " << outertype << inners; + if (inners.at(0) == QLatin1String("bool")) { + outertype = QLatin1String("std::vector::bool"); + } else { + //extraArgs[extraArgCount++] = sizeofTypeExpression(data.type); + //extraArgs[extraArgCount++] = "(size_t)&(('" + data.type + "'*)0)->value"; + } + break; + case StdDequeType: + extraArgs[1] = zero; + case StdStackType: + // remove 'std::allocator<...>': + extraArgs[1] = zero; + break; + case StdSetType: + // remove 'std::less<...>': + extraArgs[1] = zero; + // remove 'std::allocator<...>': + extraArgs[2] = zero; + break; + case StdMapType: { + // We don't want the comparator and the allocator confuse gdb. + // But we need the offset of the second item in the value pair. + // We read the type of the pair from the allocator argument because + // that gets the constness "right" (in the sense that gdb can + // read it back; + QString pairType = inners.at(3); + // remove 'std::allocator<...>': + pairType = pairType.mid(15, pairType.size() - 15 - 2); + extraArgs[2] = QLatin1String("(size_t)&(('"); + extraArgs[2] += pairType; + extraArgs[2] += QLatin1String("'*)0)->second"); + extraArgs[3] = zero; + } + break; + case StdStringType: + //qDebug() << "EXTRACT TEMPLATE: " << outertype << inners; + if (inners.at(0) == QLatin1String("char")) { + outertype = QLatin1String("std::string"); + } else if (inners.at(0) == QLatin1String("wchar_t")) { + outertype = QLatin1String("std::wstring"); + } + qFill(extraArgs, zero); + break; + case UnknownType: + qWarning("Unknown type encountered in %s.\n", Q_FUNC_INFO); + break; + case SupportedType: + break; + } + + inBuffer->clear(); + inBuffer->append(outertype.toUtf8()); + inBuffer->append('\0'); + inBuffer->append(data.iname.toUtf8()); + inBuffer->append('\0'); + inBuffer->append(data.exp.toUtf8()); + inBuffer->append('\0'); + inBuffer->append(td.inner.toUtf8()); + inBuffer->append('\0'); + inBuffer->append(data.iname.toUtf8()); + inBuffer->append('\0'); + + if (debug) + qDebug() << '\n' << Q_FUNC_INFO << '\n' << data.toString() << "\n-->" << outertype << td.type << extraArgs; +} + +/* Parse value: + * "iname="local.sl",addr="0x0012BA84",value="<3 items>",valuedisabled="true", + * numchild="3",childtype="QString",childnumchild="0",children=[{name="0",value="<binhex>", + * valueencoded="2"},{name="1",value="dAB3AG8A",valueencoded="2"},{name="2", + * value="dABoAHIAZQBlAA==",valueencoded="2"}]" */ + +class ValueDumperParser : public DumperParser { +public: + explicit ValueDumperParser(const char *s); + + inline QtDumperResult result() const { return m_result; } + +protected: + virtual bool handleKeyword(const char *k, int size); + virtual bool handleHashStart(); + virtual bool handleValue(const char *k, int size); + +private: + enum Mode { None, ExpectingIName, ExpectingAddress, ExpectingValue, + ExpectingType, ExpectingInternal, + ExpectingValueDisabled, ExpectingValueEncoded, + ExpectingChildType, ExpectingChildCount, + IgnoreNext, + ChildModeStart, + ExpectingChildren,ExpectingChildName, ExpectingChildAddress, + ExpectingChildValue, ExpectingChildValueEncoded }; + + static inline Mode nextMode(Mode in, const char *keyword, int size); + + Mode m_mode; + QtDumperResult m_result; +}; + +ValueDumperParser::ValueDumperParser(const char *s) : + DumperParser(s), + m_mode(None) +{ +} + +// Check key words +ValueDumperParser::Mode ValueDumperParser::nextMode(Mode in, const char *keyword, int size) +{ + // Careful with same prefix + switch (size) { + case 4: + if (!qstrncmp(keyword, "addr", size)) + return in > ChildModeStart ? ExpectingChildAddress : ExpectingAddress; + if (!qstrncmp(keyword, "type", size)) + return ExpectingType; + if (!qstrncmp(keyword, "name", size)) + return ExpectingChildName; + break; + case 5: + if (!qstrncmp(keyword, "iname", size)) + return ExpectingIName; + if (!qstrncmp(keyword, "value", size)) + return in > ChildModeStart ? ExpectingChildValue : ExpectingValue; + break; + case 8: + if (!qstrncmp(keyword, "children", size)) + return ExpectingChildren; + if (!qstrncmp(keyword, "numchild", size)) + return ExpectingChildCount; + if (!qstrncmp(keyword, "internal", size)) + return ExpectingInternal; + break; + case 9: + if (!qstrncmp(keyword, "childtype", size)) + return ExpectingChildType; + break; + case 12: + if (!qstrncmp(keyword, "valueencoded", size)) + return in > ChildModeStart ? ExpectingChildValueEncoded : ExpectingValueEncoded; + break; + case 13: + if (!qstrncmp(keyword, "valuedisabled", size)) + return ExpectingValueDisabled; + if (!qstrncmp(keyword, "childnumchild", size)) + return IgnoreNext; + break; + } + return IgnoreNext; +} + +bool ValueDumperParser::handleKeyword(const char *k, int size) +{ + const Mode newMode = nextMode(m_mode, k, size); + if (debug && newMode == IgnoreNext) + qWarning("%s Unexpected keyword %s.\n", Q_FUNC_INFO, QByteArray(k, size).constData()); + m_mode = newMode; + return true; +} + +bool ValueDumperParser::handleHashStart() +{ + m_result.children.push_back(QtDumperResult::Child()); + return true; +} + +bool ValueDumperParser::handleValue(const char *k, int size) +{ + const QByteArray valueBA(k, size); + switch (m_mode) { + case None: + case ChildModeStart: + return false; + case ExpectingIName: + m_result.iname = QString::fromLatin1(valueBA); + break; + case ExpectingAddress: + m_result.address = QString::fromLatin1(valueBA); + break; + case ExpectingValue: + m_result.value = valueBA; + break; + case ExpectingValueDisabled: + m_result.valuedisabled = valueBA == "true"; + break; + case ExpectingValueEncoded: + m_result.valueEncoded = QString::fromLatin1(valueBA).toInt(); + break; + case ExpectingType: + m_result.type = QString::fromLatin1(valueBA); + break; + case ExpectingInternal: + m_result.internal = valueBA == "true"; + break; + case ExpectingChildType: + m_result.childType = QString::fromLatin1(valueBA); + break; + case ExpectingChildCount: + m_result.childCount = QString::fromLatin1(valueBA).toInt(); + break; + case ExpectingChildren: + case IgnoreNext: + break; + case ExpectingChildName: + m_result.children.back().name = QString::fromLatin1(valueBA); + break; + case ExpectingChildAddress: + m_result.children.back().address = QString::fromLatin1(valueBA); + break; + case ExpectingChildValue: + m_result.children.back().value = valueBA; + break; + case ExpectingChildValueEncoded: + m_result.children.back().valueEncoded = QString::fromLatin1(valueBA).toInt(); + break; } return true; } +bool QtDumperHelper::parseValue(const char *data, QtDumperResult *r) +{ + ValueDumperParser parser(data); + if (!parser.run()) + return false; + *r = parser.result(); + return true; +} + } } diff --git a/src/plugins/debugger/watchutils.h b/src/plugins/debugger/watchutils.h index 392f0e0c3fdb89880c7f98ec1d7e4166f3f7f883..2fd9a802465d7f26349f450cce223e66200da23f 100644 --- a/src/plugins/debugger/watchutils.h +++ b/src/plugins/debugger/watchutils.h @@ -31,15 +31,17 @@ #define WATCHUTILS_H #include <QtCore/QString> +#include <QtCore/QMap> QT_BEGIN_NAMESPACE -class QString; -class QByteArray; +class QDebug; QT_END_NAMESPACE namespace Debugger { namespace Internal { +class WatchData; + QString dotEscape(QString str); QString currentTime(); bool isSkippableFunction(const QString &funcName, const QString &fileName); @@ -64,8 +66,131 @@ 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); +// Decode string data as returned by the dumper helpers. +QString decodeData(const QByteArray &baIn, int encoding); + +// Result of a dumper call. +struct QtDumperResult +{ + struct Child { + Child(); + + int valueEncoded; + QString name; + QString address; + QByteArray value; + }; + + QtDumperResult(); + void clear(); + QList<WatchData> toWatchData(int source = 0) const; + + QString iname; + QString address; + QString type; + QByteArray value; + int valueEncoded; + bool valuedisabled; + int childCount; + bool internal; + QString childType; + QList <Child> children; +}; + +QDebug operator<<(QDebug in, const QtDumperResult &d); + +/* Attempt to put common code of the dumper handling into a helper + * class. + * "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. + * It provides a function that takes input from an input buffer + * and some parameters and writes output into an output buffer. + * Parameter 1 is the protocol: + * 1) Query. Fills output buffer with known types, Qt version and namespace. + * This information is parsed and stored by this class (special type + * enumeration). + * 2) Evaluate symbol, taking address and some additional parameters + * depending on type. */ + +class QtDumperHelper { +public: + enum Debugger { + GdbDebugger, // Can evalulate expressions in function calls + CdbDebugger // Can only handle scalar, simple types in function calls + }; + + enum Type { + UnknownType, + SupportedType, // A type that requires no special handling by the dumper + // Below types require special handling + QObjectType, QWidgetType, QObjectSlotType, QObjectSignalType, + QVectorType, QMapType, QMultiMapType, QMapNodeType, + StdVectorType, StdDequeType, StdSetType,StdMapType, StdStackType, + StdStringType + }; + + // Type/Parameter struct required for building a value query + struct TypeData { + TypeData(); + void clear(); + + Type type; + bool isTemplate; + QString tmplate; + QString inner; + }; + + QtDumperHelper(); + void clear(); + + int typeCount() const; + // Look up a simple, non-template type + Type simpleType(const QString &simpleType) const; + // Look up a (potentially) template type and fill parameter struct + TypeData typeData(const QString &typeName) const; + Type type(const QString &typeName) const; + + int qtVersion() const; + QString qtVersionString() const; + void setQtVersion(int v); + void setQtVersion(const QString &v); + + QString qtNamespace() const; + void setQtNamespace(const QString &qtNamespace); + + // Complete parse of "query" (protocol 1) response from debuggee buffer. + // 'data' excludes the leading indicator character. + bool parseQuery(const char *data, Debugger debugger); + // Set up from pre-parsed type list + void parseQueryTypes(const QStringList &l, Debugger debugger); + + // Determine the parameters required for an "evaluate" (protocol 2) call + void evaluationParameters(const WatchData &data, + const TypeData &td, + Debugger debugger, + QByteArray *inBuffer, + QStringList *extraParameters) const; + + // Parse the value response (protocol 2) from debuggee buffer. + // 'data' excludes the leading indicator character. + static bool parseValue(const char *data, QtDumperResult *r); + + static bool needsExpressionSyntax(Type t); + + QString toString(bool debug = false) const; + +private: + typedef QMap<QString, Type> NameTypeMap; + + // Look up a simple (namespace) type + static Type specialType(QString s); + + NameTypeMap m_nameTypeMap; + int m_qtVersion; + QString m_qtNamespace; +}; } // namespace Internal } // namespace Debugger diff --git a/src/plugins/debugger/watchwindow.cpp b/src/plugins/debugger/watchwindow.cpp index 71ac3bb1e6ca47b297da0b22fe59c1ec1ec08d02..7484b29e34543e03c5b91b66958aa5f23a904927 100644 --- a/src/plugins/debugger/watchwindow.cpp +++ b/src/plugins/debugger/watchwindow.cpp @@ -122,6 +122,9 @@ WatchWindow::WatchWindow(Type type, QWidget *parent) setIndentation(indentation() * 9/10); setUniformRowHeights(true); setItemDelegate(new WatchDelegate(this)); + setDragEnabled(true); + setAcceptDrops(true); + setDropIndicatorShown(true); connect(this, SIGNAL(expanded(QModelIndex)), this, SLOT(expandNode(QModelIndex))); @@ -161,6 +164,35 @@ void WatchWindow::keyPressEvent(QKeyEvent *ev) QTreeView::keyPressEvent(ev); } +void WatchWindow::dragEnterEvent(QDragEnterEvent *ev) +{ + //QTreeView::dragEnterEvent(ev); + if (ev->mimeData()->hasFormat("text/plain")) { + ev->setDropAction(Qt::CopyAction); + ev->accept(); + } +} + +void WatchWindow::dragMoveEvent(QDragMoveEvent *ev) +{ + //QTreeView::dragMoveEvent(ev); + if (ev->mimeData()->hasFormat("text/plain")) { + ev->setDropAction(Qt::CopyAction); + ev->accept(); + } +} + +void WatchWindow::dropEvent(QDropEvent *ev) +{ + if (ev->mimeData()->hasFormat("text/plain")) { + theDebuggerAction(WatchExpression)->trigger(ev->mimeData()->text()); + //ev->acceptProposedAction(); + ev->setDropAction(Qt::CopyAction); + ev->accept(); + } + //QTreeView::dropEvent(ev); +} + void WatchWindow::contextMenuEvent(QContextMenuEvent *ev) { QMenu menu; diff --git a/src/plugins/debugger/watchwindow.h b/src/plugins/debugger/watchwindow.h index 715507f8b16df9cef441cebd960581a11e02d9e4..adf4e06347e99268b29355a8822c527a2bd2f9e3 100644 --- a/src/plugins/debugger/watchwindow.h +++ b/src/plugins/debugger/watchwindow.h @@ -64,6 +64,10 @@ private: void keyPressEvent(QKeyEvent *ev); void contextMenuEvent(QContextMenuEvent *ev); + void dragEnterEvent(QDragEnterEvent *ev); + void dropEvent(QDropEvent *ev); + void dragMoveEvent(QDragMoveEvent *ev); + void editItem(const QModelIndex &idx); void reset(); /* reimpl */ diff --git a/src/plugins/fakevim/fakevimhandler.cpp b/src/plugins/fakevim/fakevimhandler.cpp index 2550f88eb80aac33d001987f37d4ac60c3c5b7ac..035fdb6792846d9cbbec5558aae1836fbde3e6a5 100644 --- a/src/plugins/fakevim/fakevimhandler.cpp +++ b/src/plugins/fakevim/fakevimhandler.cpp @@ -877,10 +877,12 @@ EventResult FakeVimHandler::Private::handleCommandMode(int key, int unmodified, finishMovement(); } else if (m_submode == DeleteSubMode && key == 'd') { // tested moveToStartOfLine(); + setTargetColumn(); setAnchor(); moveDown(count()); m_moveType = MoveLineWise; - finishMovement("d"); + setDotCommand("%1dd", count()); + finishMovement(); } else if (m_submode == YankSubMode && key == 'y') { moveToStartOfLine(); setAnchor(); diff --git a/src/plugins/projectexplorer/compileoutputwindow.cpp b/src/plugins/projectexplorer/compileoutputwindow.cpp index 56c294a2e06204ad5695935b07c8b62199a20a5f..493e6c492dcf6d66b4b44ca418dfb35f23c7f87b 100644 --- a/src/plugins/projectexplorer/compileoutputwindow.cpp +++ b/src/plugins/projectexplorer/compileoutputwindow.cpp @@ -36,6 +36,7 @@ #include <QtGui/QKeyEvent> #include <QtGui/QIcon> #include <QtGui/QTextEdit> +#include <QtGui/QScrollBar> using namespace ProjectExplorer; using namespace ProjectExplorer::Internal; @@ -82,9 +83,10 @@ void CompileOutputWindow::clearContents() m_textEdit->clear(); } -void CompileOutputWindow::visibilityChanged(bool /* b */) +void CompileOutputWindow::visibilityChanged(bool b) { - + if (b) + m_textEdit->verticalScrollBar()->setValue(m_textEdit->verticalScrollBar()->maximum()); } int CompileOutputWindow::priorityInStatusBar() const diff --git a/src/plugins/qt4projectmanager/projectloadwizard.cpp b/src/plugins/qt4projectmanager/projectloadwizard.cpp index 3887e4f9487e2702562accdf0c0296ce210f9d8e..ab27fb981ced6976102c0116e40138c46bcc415f 100644 --- a/src/plugins/qt4projectmanager/projectloadwizard.cpp +++ b/src/plugins/qt4projectmanager/projectloadwizard.cpp @@ -190,7 +190,7 @@ void ProjectLoadWizard::setupImportPage(QtVersion *version, QtVersion::QmakeBuil QVBoxLayout *importLayout = new QVBoxLayout(importPage); importLabel = new QLabel(importPage); - QString versionString = version->name() + " (" + version->path() + ")"; + QString versionString = version->name() + " (" + QDir::toNativeSeparators(version->path()) + ")"; QString buildConfigString = (buildConfig & QtVersion::BuildAll) ? QLatin1String("debug_and_release ") : QLatin1String(""); buildConfigString.append((buildConfig & QtVersion::DebugBuild) ? QLatin1String("debug") : QLatin1String("release")); importLabel->setTextFormat(Qt::RichText); @@ -211,7 +211,7 @@ void ProjectLoadWizard::setupImportPage(QtVersion *version, QtVersion::QmakeBuil import2Label->setTextFormat(Qt::RichText); if (m_temporaryVersion) import2Label->setText(tr("<b>Note:</b> Importing the settings will automatically add the Qt Version from:<br><b>%1</b> to the list of qt versions.") - .arg(m_importVersion->path())); + .arg(QDir::toNativeSeparators(m_importVersion->path()))); importLayout->addWidget(import2Label); addPage(importPage); } diff --git a/tests/auto/fakevim/main.cpp b/tests/auto/fakevim/main.cpp index fd5b06f0931b6c168e60a07cff90df3796e260f8..9e931526f92acfaa31d1cd33277d84d8dcc05a75 100644 --- a/tests/auto/fakevim/main.cpp +++ b/tests/auto/fakevim/main.cpp @@ -265,6 +265,10 @@ void tst_FakeVim::command_dd() check("dd", l[0] + "\n@" + lmid(2)); check(".", l[0] + "\n@" + lmid(3)); check("3dd", l[0] + "\n@" + lmid(6)); + check("8l", l[0] + "\n QApp@lication app(argc, argv);\n" + lmid(7)); + check("dd", l[0] + "\n@" + lmid(7)); + check(".", l[0] + "\n@" + lmid(8)); + check("dd", l[0] + "\n@" + lmid(9)); } void tst_FakeVim::command_dollar()