diff --git a/doc/qtcreator.qdoc b/doc/qtcreator.qdoc index 126fd98c63da27f2dbd8f72057ebb9f8e8e01b3b..d5e3582218aaf306e040d19d00a102e04236fd41 100644 --- a/doc/qtcreator.qdoc +++ b/doc/qtcreator.qdoc @@ -1080,7 +1080,7 @@ \l{http://www.microsoft.com/whdc/devtools/debugging/installx86.Mspx}{32-bit} or \l{http://www.microsoft.com/whdc/devtools/debugging/install64bit.Mspx}{64-bit} - package (Version 6.10 for the 32-bit or the 64-bit version of Qt Creator, respectively), + package (Version 6.11.1.404 for the 32-bit or the 64-bit version of Qt Creator, respectively), which is freely available for download from the \l{http://msdn.microsoft.com/en-us/default.aspx} {Microsoft Developer Network}. diff --git a/src/plugins/debugger/cdb/cdbdebugengine.cpp b/src/plugins/debugger/cdb/cdbdebugengine.cpp index 11f37d15f1928cf6f919f03e4aecfb5dc8f01bf6..c7f04055708f0b3bd1baf0b28fc99fa57d98c92c 100644 --- a/src/plugins/debugger/cdb/cdbdebugengine.cpp +++ b/src/plugins/debugger/cdb/cdbdebugengine.cpp @@ -302,6 +302,7 @@ CdbDebugEnginePrivate::CdbDebugEnginePrivate(DebuggerManager *manager, m_dumper(new CdbDumperHelper(manager, &m_cif)), m_currentThreadId(-1), m_eventThreadId(-1), + m_interrupted(false), m_watchTimer(-1), m_debugEventCallBack(engine), m_engine(engine), @@ -445,6 +446,7 @@ void CdbDebugEnginePrivate::clearForRun() m_breakEventMode = BreakEventHandle; m_eventThreadId = -1; + m_interrupted = false; cleanStackTrace(); } @@ -1026,6 +1028,9 @@ bool CdbDebugEngine::step(unsigned long executionStatus) warning(msgStepFailed(executionStatus, m_d->m_currentThreadId, msgDebuggerCommandFailed(command, hr))); } if (success) { + // Oddity: Step into will first break at the calling function. Ignore + if (executionStatus == DEBUG_STATUS_STEP_INTO || executionStatus == DEBUG_STATUS_REVERSE_STEP_INTO) + m_d->m_breakEventMode = CdbDebugEnginePrivate::BreakEventIgnoreOnce; startWatchTimer(); setState(InferiorRunning, Q_FUNC_INFO, __LINE__); } else { @@ -1169,7 +1174,9 @@ bool CdbDebugEnginePrivate::interruptInterferiorProcess(QString *errorMessage) qDebug() << Q_FUNC_INFO << "\n ex=" << executionStatus; } - if (!DebugBreakProcess(m_hDebuggeeProcess)) { + if (DebugBreakProcess(m_hDebuggeeProcess)) { + m_interrupted = true; + } else { *errorMessage = QString::fromLatin1("DebugBreakProcess failed: %1").arg(Utils::winErrorMessage(GetLastError())); return false; } @@ -1704,12 +1711,34 @@ ULONG CdbDebugEnginePrivate::updateThreadList() QList<ThreadData> threads; ULONG currentThreadId; QString errorMessage; + // When interrupting, an artifical thread with a breakpoint is created. if (!CdbStackTraceContext::getThreads(m_cif, true, &threads, ¤tThreadId, &errorMessage)) m_engine->warning(errorMessage); manager()->threadsHandler()->setThreads(threads); return currentThreadId; } +// Figure out the thread to run the dumpers in (see notes on. +// CdbDumperHelper). Avoid the artifical threads created by interrupt +// and threads that are in waitFor(). +// A stricter version could only use the thread if it is the event +// thread of a step or breakpoint hit (see CdbDebugEnginePrivate::m_interrupted). + +static inline unsigned long dumperThreadId(const QList<StackFrame> &frames, + unsigned long currentThread) +{ + if (frames.empty()) + return CdbDumperHelper::InvalidDumperCallThread; + if (frames.at(0).function == QLatin1String(CdbStackTraceContext::winFuncDebugBreakPoint)) + return CdbDumperHelper::InvalidDumperCallThread; + const int waitCheckDepth = qMin(frames.size(), 5); + static const QString waitForPrefix = QLatin1String(CdbStackTraceContext::winFuncWaitForPrefix); + for (int f = 0; f < waitCheckDepth; f++) + if (frames.at(f).function.startsWith(waitForPrefix)) + return CdbDumperHelper::InvalidDumperCallThread; + return currentThread; +} + void CdbDebugEnginePrivate::updateStackTrace() { if (debugCDB) @@ -1750,11 +1779,19 @@ void CdbDebugEnginePrivate::updateStackTrace() CdbDebugEngine::tr("Thread %1: No debug information available (%2).").arg(m_currentThreadId).arg(topFunction); m_engine->warning(msg); } - + // Set up dumper with a thread (or invalid) + const unsigned long dumperThread = dumperThreadId(stackFrames, m_currentThreadId); + if (debugCDB) + qDebug() << "updateStackTrace() current: " << m_currentThreadId << " dumper=" << dumperThread; + m_dumper->setDumperCallThread(dumperThread); + // Display frames manager()->stackHandler()->setFrames(stackFrames); m_firstActivatedFrame = true; if (current >= 0) { manager()->stackHandler()->setCurrentIndex(current); + // First time : repaint + if (m_dumper->isEnabled() && m_dumper->state() != CdbDumperHelper::Initialized) + QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); m_engine->activateFrame(current); } manager()->watchHandler()->updateWatchers(); diff --git a/src/plugins/debugger/cdb/cdbdebugengine_p.h b/src/plugins/debugger/cdb/cdbdebugengine_p.h index 72a154906ccaa127ebb9bb3076e339646ac63b2f..73fb7d41fa0a85dc300a5bf3eddd45cc01760f83 100644 --- a/src/plugins/debugger/cdb/cdbdebugengine_p.h +++ b/src/plugins/debugger/cdb/cdbdebugengine_p.h @@ -156,6 +156,7 @@ struct CdbDebugEnginePrivate const QSharedPointer<CdbOptions> m_options; HANDLE m_hDebuggeeProcess; HANDLE m_hDebuggeeThread; + bool m_interrupted; int m_currentThreadId; int m_eventThreadId; HandleBreakEventMode m_breakEventMode; diff --git a/src/plugins/debugger/cdb/cdbdebugeventcallback.cpp b/src/plugins/debugger/cdb/cdbdebugeventcallback.cpp index fcebdee77904917e88658fb7406812f4e2233137..21f867ede22f7678add22ed44b961ebb8113f867 100644 --- a/src/plugins/debugger/cdb/cdbdebugeventcallback.cpp +++ b/src/plugins/debugger/cdb/cdbdebugeventcallback.cpp @@ -248,6 +248,7 @@ STDMETHODIMP CdbDebugEventCallback::Exception( if (debugCDB) qDebug() << Q_FUNC_INFO << "\nex=" << Exception->ExceptionCode << " fatal=" << fatal << msg; m_pEngine->manager()->showApplicationOutput(msg); + m_pEngine->manager()->showDebuggerOutput(LogMisc, msg); if (fatal) m_pEngine->m_d->notifyCrashed(); return S_OK; diff --git a/src/plugins/debugger/cdb/cdbdumperhelper.cpp b/src/plugins/debugger/cdb/cdbdumperhelper.cpp index b0757784e003a7b72c18a8d5d3c873611597f5bc..9d1d8c9713ef28ff8a8f7595f710d221475ce033 100644 --- a/src/plugins/debugger/cdb/cdbdumperhelper.cpp +++ b/src/plugins/debugger/cdb/cdbdumperhelper.cpp @@ -41,8 +41,10 @@ #include <QtCore/QRegExp> #include <QtCore/QCoreApplication> #include <QtCore/QTextStream> +#include <QtCore/QTime> enum { loadDebug = 0 }; +enum { dumpDebug = 0 }; static const char *dumperModuleNameC = "gdbmacros"; static const char *qtCoreModuleNameC = "QtCore"; @@ -158,24 +160,25 @@ static bool debuggeeLoadLibrary(DebuggerManager *manager, // We want to call "HMODULE LoadLibraryA(LPCTSTR lpFileName)" // (void* LoadLibraryA(char*)). However, despite providing a symbol // server, the debugger refuses to recognize it as a function. - // Set up the call stack with a function of same signature (qstrdup) - // and change the call register to LoadLibraryA() before executing "g". + // Call with a prototype of 'qstrdup', as it is the same // 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(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(')'); + + QString callCmd; { + QTextStream str(&callCmd); + str.setIntegerBase(16); + str << ".call /s " << dummyFunc << " Kernel32!LoadLibraryA(0x" << nameAddress << ')'; + } + if (loadDebug) + qDebug() << "Calling" << callCmd; + if (!CdbDebugEnginePrivate::executeDebuggerCommand(cif->debugControl, callCmd, errorMessage)) return false; - if (!CdbDebugEnginePrivate::executeDebuggerCommand(cif->debugControl, QLatin1String("r eip=Kernel32!LoadLibraryA"), errorMessage)) - return false; - // This will hit a breakpoint. - if (!CdbDebugEnginePrivate::executeDebuggerCommand(cif->debugControl, QString(QLatin1Char('g')), errorMessage)) + // Execute current thread. This will hit a breakpoint. + if (!CdbDebugEnginePrivate::executeDebuggerCommand(cif->debugControl, QLatin1String("~. g"), errorMessage)) return false; const HRESULT hr = cif->debugControl->WaitForEvent(0, waitTimeOutMS); if (FAILED(hr)) { @@ -185,6 +188,14 @@ static bool debuggeeLoadLibrary(DebuggerManager *manager, return true; } +// Format a "go" in a thread +static inline QString goCommand(unsigned long threadId) +{ + QString rc; + QTextStream(&rc) << '~' << threadId << " g"; + return rc; +} + // ---- Load messages static inline QString msgMethod(bool injectOrCall) { @@ -226,7 +237,9 @@ CdbDumperHelper::CdbDumperHelper(DebuggerManager *manager, m_inBufferSize(0), m_outBufferAddress(0), m_outBufferSize(0), - m_buffer(0) + m_buffer(0), + m_dumperCallThread(0), + m_goCommand(goCommand(m_dumperCallThread)) { } @@ -324,7 +337,7 @@ CdbDumperHelper::CallLoadResult CdbDumperHelper::initCallLoad(QString *errorMess bool CdbDumperHelper::ensureInitialized(QString *errorMessage) { if (loadDebug) - qDebug() << Q_FUNC_INFO << '\n' << m_state; + qDebug() << "ensureInitialized thread: " << m_dumperCallThread << " state: " << m_state; switch (m_state) { case Disabled: @@ -372,6 +385,9 @@ bool CdbDumperHelper::ensureInitialized(QString *errorMessage) m_manager->showDebuggerOutput(LogMisc, *errorMessage); m_manager->showQtDumperLibraryWarning(*errorMessage); } + if (loadDebug) + qDebug() << Q_FUNC_INFO << '\n' << ok; + return ok; } @@ -451,7 +467,7 @@ bool CdbDumperHelper::initKnownTypes(QString *errorMessage) *errorMessage = QtDumperHelper::msgDumperOutdated(dumperVersionRequired, m_helper.dumperVersion()); return false; } - if (loadDebug) + if (loadDebug || dumpDebug) qDebug() << Q_FUNC_INFO << m_helper.toString(true); return true; } @@ -492,8 +508,11 @@ bool CdbDumperHelper::callDumper(const QString &callCmd, const QByteArray &inBuf // by using 'gN' (go not handled -> pass handling to dumper __try/__catch block) 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')); + // Go in current thread. If an exception occurs in loop 2, + // let the dumper handle it. + QString goCmd = m_goCommand; + if (i) + goCmd = QLatin1Char('N'); if (!CdbDebugEnginePrivate::executeDebuggerCommand(m_cif->debugControl, goCmd, errorMessage)) return false; HRESULT hr = m_cif->debugControl->WaitForEvent(0, waitTimeOutMS); @@ -556,6 +575,18 @@ static inline QString msgNotHandled(const QString &type) CdbDumperHelper::DumpResult CdbDumperHelper::dumpType(const WatchData &wd, bool dumpChildren, QList<WatchData> *result, QString *errorMessage) { + if (dumpDebug) + qDebug() << ">dumpType() thread: " << m_dumperCallThread << " state: " << m_state << wd.type << QTime::currentTime().toString(); + const CdbDumperHelper::DumpResult rc = dumpTypeI(wd, dumpChildren, result, errorMessage); + if (dumpDebug) + qDebug() << "<dumpType() state: " << m_state << wd.type << " returns " << rc << *errorMessage << QTime::currentTime().toString(); + return rc; +} + +CdbDumperHelper::DumpResult CdbDumperHelper::dumpTypeI(const WatchData &wd, bool dumpChildren, + QList<WatchData> *result, QString *errorMessage) +{ + errorMessage->clear(); // Check failure cache and supported types if (m_state == Disabled) { *errorMessage = QLatin1String("Dumpers are disabled"); @@ -570,6 +601,20 @@ CdbDumperHelper::DumpResult CdbDumperHelper::dumpType(const WatchData &wd, bool return DumpNotHandled; } + // Do we have a thread + if (m_dumperCallThread == InvalidDumperCallThread) { + *errorMessage = QString::fromLatin1("No thread to call."); + if (loadDebug) + qDebug() << *errorMessage; + return DumpNotHandled; + } + + // Delay initialization as much as possible + if (isIntOrFloatType(wd.type)) { + *errorMessage = QString::fromLatin1("Unhandled POD: " ) + wd.type; + return DumpNotHandled; + } + // Ensure types are parsed and known. if (!ensureInitialized(errorMessage)) { *errorMessage = msgDumpFailed(wd, errorMessage); @@ -722,5 +767,18 @@ bool CdbDumperHelper::runTypeSizeQuery(const QString &typeName, int *size, QStri return true; } +unsigned long CdbDumperHelper::dumperCallThread() +{ + return m_dumperCallThread; +} + +void CdbDumperHelper::setDumperCallThread(unsigned long t) +{ + if (m_dumperCallThread != t) { + m_dumperCallThread = t; + m_goCommand = goCommand(m_dumperCallThread); + } +} + } // namespace Internal } // namespace Debugger diff --git a/src/plugins/debugger/cdb/cdbdumperhelper.h b/src/plugins/debugger/cdb/cdbdumperhelper.h index eaa7a1db7b2b38e39ce7129aa2baf68e43317190..ca95fe477938fa42333c39870fbe0206f886cd62 100644 --- a/src/plugins/debugger/cdb/cdbdumperhelper.h +++ b/src/plugins/debugger/cdb/cdbdumperhelper.h @@ -55,7 +55,17 @@ struct CdbComInterfaces; * dumpType() is the main query function to obtain a list of WatchData from * WatchData item produced by the smbol context. * Call disable(), should the debuggee crash (as performing debuggee - * calls is no longer possible, then).*/ + * calls is no longer possible, then). + * + * dumperCallThread specifies the thread to use when making the calls. + * As of Debugging Tools v 6.11.1.404 (6.10.2009), calls cannot be executed + * when the current thread is in some WaitFor...() function. The call will + * then hang (regardless whether that thread or some other, non-blocking thread + * is used), and the debuggee will be in running state afterwards (causing errors + * from ReadVirtual, etc). + * The current thread can be used when stepping or a breakpoint was + * hit. When interrupting the inferior, an artifical thread is created, + * that is not usable, either. */ class CdbDumperHelper { @@ -93,6 +103,10 @@ public: inline CdbComInterfaces *comInterfaces() const { return m_cif; } + enum { InvalidDumperCallThread = 0xFFFFFFFF }; + unsigned long dumperCallThread(); + void setDumperCallThread(unsigned long t); + private: enum CallLoadResult { CallLoadOk, CallLoadError, CallLoadNoQtApp, CallLoadAlreadyLoaded }; @@ -103,6 +117,9 @@ private: bool initResolveSymbols(QString *errorMessage); bool initKnownTypes(QString *errorMessage); + inline DumpResult dumpTypeI(const WatchData &d, bool dumpChildren, + QList<WatchData> *result, QString *errorMessage); + 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, @@ -134,6 +151,8 @@ private: QStringList m_failedTypes; QtDumperHelper m_helper; + unsigned long m_dumperCallThread; + QString m_goCommand; }; } // namespace Internal diff --git a/src/plugins/debugger/cdb/cdbexceptionutils.cpp b/src/plugins/debugger/cdb/cdbexceptionutils.cpp index 6404733308d30eedc90c16e92670cbb4d554025d..4975b697243adfcf0dc2424d33a80408be7298f0 100644 --- a/src/plugins/debugger/cdb/cdbexceptionutils.cpp +++ b/src/plugins/debugger/cdb/cdbexceptionutils.cpp @@ -41,7 +41,11 @@ enum { debugExc = 0 }; // Special exception codes. enum { cppExceptionCode = 0xe06d7363, startupCompleteTrap = 0x406d1388, rpcServerUnavailableExceptionCode = 0x6ba, - dllNotFoundExceptionCode = 0xc0000135 }; + dllNotFoundExceptionCode = 0xc0000135, + dllInitFailed = 0xc0000142, + missingSystemFile = 0xc0000143, + appInitFailed = 0xc0000143 + }; namespace Debugger { namespace Internal { @@ -172,6 +176,12 @@ void formatException(const EXCEPTION_RECORD64 *e, QTextStream &str) case dllNotFoundExceptionCode: str << "DLL not found"; break; + case dllInitFailed: + str << "DLL failed to initialize"; + break; + case missingSystemFile: + str << "System file is missing"; + break; case EXCEPTION_ACCESS_VIOLATION: { const bool writeOperation = e->ExceptionInformation[0]; str << (writeOperation ? "write" : "read") diff --git a/src/plugins/debugger/cdb/cdbstacktracecontext.cpp b/src/plugins/debugger/cdb/cdbstacktracecontext.cpp index 8cf72461fe4b7a33895274e4ab9a9fb2e8a6d39e..605eeb61dbfd4c05d6519b39305ca49ffbf77a62 100644 --- a/src/plugins/debugger/cdb/cdbstacktracecontext.cpp +++ b/src/plugins/debugger/cdb/cdbstacktracecontext.cpp @@ -40,6 +40,10 @@ namespace Debugger { namespace Internal { +const char *CdbStackTraceContext::winFuncFastSystemCallRet = "ntdll!KiFastSystemCallRet"; +const char *CdbStackTraceContext::winFuncDebugBreakPoint = "ntdll!DbgBreakPoint"; +const char *CdbStackTraceContext::winFuncWaitForPrefix = "kernel32!WaitFor"; + CdbStackTraceContext::CdbStackTraceContext(const QSharedPointer<CdbDumperHelper> &dumper) : m_dumper(dumper), m_cif(dumper->comInterfaces()), @@ -232,6 +236,7 @@ static inline bool getStoppedThreadState(const CdbComInterfaces &cif, ThreadData *t, QString *errorMessage) { + enum { MaxFrames = 2 }; ULONG currentThread; HRESULT hr = cif.debugSystemObjects->GetCurrentThreadId(¤tThread); if (FAILED(hr)) { @@ -246,29 +251,38 @@ static inline bool getStoppedThreadState(const CdbComInterfaces &cif, } } ULONG frameCount; - DEBUG_STACK_FRAME topFrame[1]; - hr = cif.debugControl->GetStackTrace(0, 0, 0, topFrame, 1, &frameCount); + // Ignore the top frame if it is "ntdll!KiFastSystemCallRet", which is + // not interesting for display. + DEBUG_STACK_FRAME frames[MaxFrames]; + hr = cif.debugControl->GetStackTrace(0, 0, 0, frames, MaxFrames, &frameCount); if (FAILED(hr)) { *errorMessage = msgGetThreadStateFailed(t->id, msgComFailed("GetStackTrace", hr)); return false; } - - t->address = topFrame[0].InstructionOffset; + // Ignore the top frame if it is "ntdll!KiFastSystemCallRet", which is + // not interesting for display. WCHAR wszBuf[MAX_PATH]; - - cif.debugSymbols->GetNameByOffsetWide(topFrame[0].InstructionOffset, wszBuf, MAX_PATH, 0, 0); - t->function = QString::fromUtf16(reinterpret_cast<const ushort *>(wszBuf)); - ULONG ulLine; - hr = cif.debugSymbols->GetLineByOffsetWide(topFrame[0].InstructionOffset, &ulLine, wszBuf, MAX_PATH, 0, 0); - if (SUCCEEDED(hr)) { - t->line = ulLine; - // Just display base name - t->file = QString::fromUtf16(reinterpret_cast<const ushort *>(wszBuf)); - if (!t->file.isEmpty()) { - const int slashPos = t->file.lastIndexOf(QLatin1Char('\\')); - if (slashPos != -1) - t->file.remove(0, slashPos + 1); - } + for (int frame = 0; frame < MaxFrames; frame++) { + cif.debugSymbols->GetNameByOffsetWide(frames[frame].InstructionOffset, wszBuf, MAX_PATH, 0, 0); + t->function = QString::fromUtf16(reinterpret_cast<const ushort *>(wszBuf)); + if (frame != 0 || t->function != QLatin1String(CdbStackTraceContext::winFuncFastSystemCallRet)) { + t->address = frames[frame].InstructionOffset; + cif.debugSymbols->GetNameByOffsetWide(frames[frame].InstructionOffset, wszBuf, MAX_PATH, 0, 0); + t->function = QString::fromUtf16(reinterpret_cast<const ushort *>(wszBuf)); + ULONG ulLine; + hr = cif.debugSymbols->GetLineByOffsetWide(frames[frame].InstructionOffset, &ulLine, wszBuf, MAX_PATH, 0, 0); + if (SUCCEEDED(hr)) { + t->line = ulLine; + // Just display base name + t->file = QString::fromUtf16(reinterpret_cast<const ushort *>(wszBuf)); + if (!t->file.isEmpty()) { + const int slashPos = t->file.lastIndexOf(QLatin1Char('\\')); + if (slashPos != -1) + t->file.remove(0, slashPos + 1); + } + } + break; + } // was not "ntdll!KiFastSystemCallRet" } return true; } diff --git a/src/plugins/debugger/cdb/cdbstacktracecontext.h b/src/plugins/debugger/cdb/cdbstacktracecontext.h index ff8c62d5b19dcfda20f91100474229942e0e09cb..3b0e2061fc89c31310ecd38fae2952464c6e0bff 100644 --- a/src/plugins/debugger/cdb/cdbstacktracecontext.h +++ b/src/plugins/debugger/cdb/cdbstacktracecontext.h @@ -63,6 +63,13 @@ class CdbStackTraceContext public: enum { maxFrames = 100 }; + // Some well known-functions + static const char *winFuncFastSystemCallRet; + // WaitFor... + static const char *winFuncWaitForPrefix; + // Dummy function used for interrupting a debuggee + static const char *winFuncDebugBreakPoint; + ~CdbStackTraceContext(); static CdbStackTraceContext *create(const QSharedPointer<CdbDumperHelper> &dumper, unsigned long threadid,