From a60b3f67467027668c6bcc01365ec49dd8e73770 Mon Sep 17 00:00:00 2001 From: Friedemann Kleint Date: Wed, 12 Jan 2011 13:49:51 +0100 Subject: [PATCH] Debugger[New CDB]: Improve 32bit debugging on 64bit systems. - Ignore WOW64 breakpoints. Restructure code to examine stop reason before notifications to be able handle special stop reasons in a simpler way. - Fix autodetection to look into %ProgramFiles% (x64) as well. --- src/plugins/debugger/cdb/cdbengine.cpp | 170 ++++++++++++-------- src/plugins/debugger/cdb/cdbengine.h | 2 + src/plugins/debugger/cdb/cdboptions.cpp | 71 +++++--- src/plugins/debugger/shared/dbgwinutils.cpp | 3 + src/plugins/debugger/shared/dbgwinutils.h | 3 +- 5 files changed, 158 insertions(+), 91 deletions(-) diff --git a/src/plugins/debugger/cdb/cdbengine.cpp b/src/plugins/debugger/cdb/cdbengine.cpp index eb30aa28561..5eae14b6a0f 100644 --- a/src/plugins/debugger/cdb/cdbengine.cpp +++ b/src/plugins/debugger/cdb/cdbengine.cpp @@ -1361,19 +1361,85 @@ static const char *cdbStatusName(unsigned long s) return "unknown"; } -void CdbEngine::handleSessionIdle(const QByteArray &message) +/* Examine how to react to a stop. */ +enum StopActionFlags +{ + // Report options + StopReportLog = 0x1, + StopReportStatusMessage = 0x2, + StopReportParseError = 0x2, + StopShowExceptionMessageBox = 0x4, + // Notify stop or just continue + StopNotifyStop = 0x8, + StopIgnoreContinue = 0x10 +}; + +unsigned CdbEngine::examineStopReason(const QByteArray &messageIn, + QString *message, + QString *exceptionBoxMessage) const +{ + // Report stop reason (GDBMI) + GdbMi stopReason; + stopReason.fromString(messageIn); + if (debug) + qDebug("%s", stopReason.toString(true, 4).constData()); + const QByteArray reason = stopReason.findChild("reason").data(); + if (reason.isEmpty()) { + *message = tr("Malformed stop response received."); + return StopReportParseError|StopNotifyStop; + } + const int threadId = stopReason.findChild("threadId").data().toInt(); + if (reason == "breakpoint") { + const int number = stopReason.findChild("breakpointId").data().toInt(); + const BreakpointId id = breakHandler()->findBreakpointByNumber(number); + if (id && breakHandler()->type(id) == Watchpoint) { + *message = msgWatchpointTriggered(id, number, breakHandler()->address(id), QString::number(threadId)); + } else { + *message = msgBreakpointTriggered(id, number, QString::number(threadId)); + } + return StopReportStatusMessage|StopNotifyStop; + } + if (reason == "exception") { + WinException exception; + exception.fromGdbMI(stopReason); +#ifdef Q_OS_WIN + // It is possible to hit on a startup trap while stepping (if something + // pulls DLLs. Avoid showing a 'stopped' Message box. + if (exception.exceptionCode == winExceptionStartupCompleteTrap) + return StopNotifyStop; + const QString description = exception.toString(); + // WOW 64 breakpoint: just report in log and continue + if (exception.exceptionCode == winExceptionWX86Breakpoint) { + *message = description; + return StopIgnoreContinue|StopReportLog; + } + if (isDebuggerWinException(exception.exceptionCode)) { + *message = msgInterrupted(); + return StopReportStatusMessage|StopNotifyStop; + } +#endif + *exceptionBoxMessage = msgStoppedByException(description, QString::number(threadId)); + *message = description; + return StopShowExceptionMessageBox|StopReportStatusMessage|StopNotifyStop; + } + *message = msgStopped(QLatin1String(reason)); + return StopReportStatusMessage|StopNotifyStop; +} + +void CdbEngine::handleSessionIdle(const QByteArray &messageBA) { if (!m_hasDebuggee) return; if (debug) qDebug("CdbEngine::handleSessionIdle %dms '%s' in state '%s', special mode %d", - elapsedLogTime(), message.constData(), + elapsedLogTime(), messageBA.constData(), stateName(state()), m_specialStopMode); // Switch source level debugging syncOperateByInstruction(m_operateByInstructionPending); + // Engine-special stop reasons: Breakpoints and setup const SpecialStopMode specialStopMode = m_specialStopMode; m_specialStopMode = NoSpecialStop; @@ -1387,79 +1453,53 @@ void CdbEngine::handleSessionIdle(const QByteArray &message) case NoSpecialStop: break; } - switch(state()) { // Temporary stop at beginning - case EngineSetupRequested: + + if (state() == EngineSetupRequested) { // Temporary stop at beginning if (debug) qDebug("notifyEngineSetupOk"); notifyEngineSetupOk(); return; - case InferiorSetupRequested: - return; - case InferiorStopRequested: - case InferiorRunOk: - break; // Proper stop of inferior handled below. + } - default: - qWarning("WARNING: CdbEngine::handleSessionAccessible called in state %s", stateName(state())); + // Further examine stop and report to user + QString message; + QString exceptionBoxMessage; + const unsigned stopFlags = examineStopReason(messageBA, &message, &exceptionBoxMessage); + // Do the non-blocking log reporting + if (stopFlags & StopReportLog) + showMessage(message, LogMisc); + if (stopFlags & StopReportStatusMessage) + showStatusMessage(message); + if (stopFlags & StopReportParseError) + showMessage(message, LogError); + // Ignore things like WOW64 + if (stopFlags & StopIgnoreContinue) { + postCommand("g", 0); return; } - // Handle stop. - if (state() == InferiorStopRequested) { - if (debug) - qDebug("notifyInferiorStopOk"); - notifyInferiorStopOk(); - } else { - if (debug) - qDebug("notifyInferiorSpontaneousStop"); - notifyInferiorSpontaneousStop(); - } - // Start sequence to get all relevant data. Hack: Avoid module reload? - unsigned sequence = CommandListStack|CommandListThreads; - if (debuggerCore()->isDockVisible(QLatin1String(Constants::DOCKWIDGET_REGISTER))) - sequence |= CommandListRegisters; - if (debuggerCore()->isDockVisible(QLatin1String(Constants::DOCKWIDGET_MODULES))) - sequence |= CommandListModules; - postCommandSequence(sequence); - // Report stop reason (GDBMI) - GdbMi stopReason; - stopReason.fromString(message); - if (debug) - qDebug("%s", stopReason.toString(true, 4).constData()); - const QByteArray reason = stopReason.findChild("reason").data(); - if (reason.isEmpty()) { - showStatusMessage(tr("Malformed stop response received."), LogError); - return; - } - const int threadId = stopReason.findChild("threadId").data().toInt(); - if (reason == "breakpoint") { - const int number = stopReason.findChild("breakpointId").data().toInt(); - const BreakpointId id = breakHandler()->findBreakpointByNumber(number); - if (id && breakHandler()->type(id) == Watchpoint) { - showStatusMessage(msgWatchpointTriggered(id, number, breakHandler()->address(id), QString::number(threadId))); + // Notify about state and send off command sequence to get stack, etc. + if (stopFlags & StopNotifyStop) { + if (state() == InferiorStopRequested) { + if (debug) + qDebug("notifyInferiorStopOk"); + notifyInferiorStopOk(); } else { - showStatusMessage(msgBreakpointTriggered(id, number, QString::number(threadId))); + if (debug) + qDebug("notifyInferiorSpontaneousStop"); + notifyInferiorSpontaneousStop(); } - return; + // Start sequence to get all relevant data. + unsigned sequence = CommandListStack|CommandListThreads; + if (debuggerCore()->isDockVisible(QLatin1String(Constants::DOCKWIDGET_REGISTER))) + sequence |= CommandListRegisters; + if (debuggerCore()->isDockVisible(QLatin1String(Constants::DOCKWIDGET_MODULES))) + sequence |= CommandListModules; + postCommandSequence(sequence); } - if (reason == "exception") { - WinException exception; - exception.fromGdbMI(stopReason); -#ifdef Q_OS_WIN - // It is possible to hit on a startup trap while stepping (if something - // pulls DLLs. Avoid showing a 'stopped' Message box. - if (exception.exceptionCode == winExceptionStartupCompleteTrap) - return; - if (isDebuggerWinException(exception.exceptionCode)) { - showStatusMessage(msgInterrupted()); - return; - } -#endif - const QString description = exception.toString(); - showStatusMessage(msgStoppedByException(description, QString::number(threadId))); - showStoppedByExceptionMessageBox(description); - return; - } - showStatusMessage(msgStopped(QLatin1String(reason))); + // After the sequence has been sent off and CDB is pondering the commands, + // pop up a message box for exceptions. + if (stopFlags & StopShowExceptionMessageBox) + showStoppedByExceptionMessageBox(exceptionBoxMessage); } void CdbEngine::handleSessionAccessible(unsigned long cdbExState) diff --git a/src/plugins/debugger/cdb/cdbengine.h b/src/plugins/debugger/cdb/cdbengine.h index 403f8e6d584..539a6d5f810 100644 --- a/src/plugins/debugger/cdb/cdbengine.h +++ b/src/plugins/debugger/cdb/cdbengine.h @@ -152,6 +152,8 @@ private slots: private: enum SpecialStopMode { NoSpecialStop, SpecialStopSynchronizeBreakpoints }; + unsigned examineStopReason(const QByteArray &messageIn, QString *message, + QString *exceptionBoxMessage) const; bool commandsPending() const; void handleExtensionMessage(char t, int token, const QByteArray &what, const QByteArray &message); bool doSetupEngine(QString *errorMessage); diff --git a/src/plugins/debugger/cdb/cdboptions.cpp b/src/plugins/debugger/cdb/cdboptions.cpp index ceab8ef0a68..466c93c236b 100644 --- a/src/plugins/debugger/cdb/cdboptions.cpp +++ b/src/plugins/debugger/cdb/cdboptions.cpp @@ -174,6 +174,21 @@ bool CdbOptions::equals(const CdbOptions &rhs) const && breakEvents == rhs.breakEvents; } +// Check the CDB executable and accumulate the list of checked paths +// for reporting. +static QString checkCdbExecutable(const QString &programDir, const QString &postfix, + QStringList *checkedDirectories = 0) +{ + QString executable = programDir; + executable += QLatin1String("/Debugging Tools For Windows"); + executable += postfix; + if (checkedDirectories) + checkedDirectories->push_back(QDir::toNativeSeparators(executable)); + executable += QLatin1String("/cdb.exe"); + const QFileInfo fi(executable); + return fi.isFile() && fi.isExecutable() ? fi.absoluteFilePath() : QString(); +} + bool CdbOptions::autoDetectExecutable(QString *outPath, bool *is64bitIn /* = 0 */, QStringList *checkedDirectories /* = 0 */) { @@ -182,48 +197,54 @@ bool CdbOptions::autoDetectExecutable(QString *outPath, bool *is64bitIn /* = 0 static const char *postFixes[] = {" (x64)", " 64-bit", " (x86)", " (x32)" }; enum { first32bitIndex = 2 }; + outPath->clear(); if (checkedDirectories) checkedDirectories->clear(); - outPath->clear(); - const QByteArray programDirB = qgetenv("ProgramFiles"); - if (programDirB.isEmpty()) + const QString programDir = QString::fromLocal8Bit(qgetenv("ProgramFiles")); + if (programDir.isEmpty()) return false; - const QString programDir = QString::fromLocal8Bit(programDirB) + QLatin1Char('/'); - const QString installDir = QLatin1String("Debugging Tools For Windows"); - const QString executable = QLatin1String("/cdb.exe"); - - QString path = programDir + installDir; - if (checkedDirectories) - checkedDirectories->push_back(path); - const QFileInfo fi(path + executable); - // Plain system installation - if (fi.isFile() && fi.isExecutable()) { - *outPath = fi.absoluteFilePath(); - if (is64bitIn) #ifdef Q_OS_WIN - *is64bitIn = Utils::winIs64BitSystem(); + const bool systemIs64Bit = Utils::winIs64BitSystem(); #else - *is64bitIn = false; + const bool systemIs64Bit = false; #endif + // Plain system installation. 32/64 Bit matches the system. + *outPath = checkCdbExecutable(programDir, QString(), checkedDirectories); + if (!outPath->isEmpty()) { + if (is64bitIn) + *is64bitIn = systemIs64Bit; return true; } // Try the post fixes - const int rootLength = path.size(); for (unsigned i = 0; i < sizeof(postFixes)/sizeof(const char*); i++) { - path.truncate(rootLength); - path += QLatin1String(postFixes[i]); - if (checkedDirectories) - checkedDirectories->push_back(path); - const QFileInfo fi2(path + executable); - if (fi2.isFile() && fi2.isExecutable()) { + *outPath = checkCdbExecutable(programDir, QLatin1String(postFixes[i]), checkedDirectories); + if (!outPath->isEmpty()) { if (is64bitIn) *is64bitIn = i < first32bitIndex; - *outPath = fi2.absoluteFilePath(); return true; } } + // A 32bit-compile running on a 64bit system sees the 64 bit installation + // as "$ProgramFiles (x64)/Debugging Tools..." and (untested), a 64 bit- + // compile running on a 64bit system sees the 32 bit installation as + // "$ProgramFiles (x86)/Debugging Tools..." (assuming this works at all) +#ifdef Q_OS_WIN64 + *outPath = checkCdbExecutable(programDir + QLatin1String(" (x32)"), QString(), checkedDirectories); + if (!outPath->isEmpty()) { + if (is64bitIn) + *is64bitIn = false; + return true; + } +#else + *outPath = checkCdbExecutable(programDir + QLatin1String(" (x64)"), QString(), checkedDirectories); + if (!outPath->isEmpty()) { + if (is64bitIn) + *is64bitIn = true; + return true; + } +#endif return false; } diff --git a/src/plugins/debugger/shared/dbgwinutils.cpp b/src/plugins/debugger/shared/dbgwinutils.cpp index bfe38f4011f..42e557fde51 100644 --- a/src/plugins/debugger/shared/dbgwinutils.cpp +++ b/src/plugins/debugger/shared/dbgwinutils.cpp @@ -292,6 +292,9 @@ void formatWindowsException(unsigned long code, quint64 address, case winExceptionRpcServerInvalid: str << "Invalid RPC server"; break; + case winExceptionWX86Breakpoint: + str << "Win32 x86 emulation subsystem breakpoint hit"; + break; case EXCEPTION_ACCESS_VIOLATION: { const bool writeOperation = info1; str << (writeOperation ? "write" : "read") diff --git a/src/plugins/debugger/shared/dbgwinutils.h b/src/plugins/debugger/shared/dbgwinutils.h index 4920b3fe513..8327cfd18c4 100644 --- a/src/plugins/debugger/shared/dbgwinutils.h +++ b/src/plugins/debugger/shared/dbgwinutils.h @@ -75,7 +75,8 @@ enum { winExceptionCppException = 0xe06d7363, winExceptionDllEntryPointNoFound = 0xc0000139, winExceptionDllInitFailed = 0xc0000142, winExceptionMissingSystemFile = 0xc0000143, - winExceptionAppInitFailed = 0xc0000143 + winExceptionAppInitFailed = 0xc0000143, + winExceptionWX86Breakpoint = 0x4000001f }; // Format windows Exception