From 3f3ad561c75089fcbd754a88e299e6c9be27a600 Mon Sep 17 00:00:00 2001 From: Friedemann Kleint Date: Tue, 25 Jan 2011 18:35:31 +0100 Subject: [PATCH] Debugger[CDB]: Reduce roundtrips when stepping. Report threads and frames along with stopped notification. Change special logic for step into and switching threads on interrupt accordingly. --- src/libs/qtcreatorcdbext/extensioncontext.cpp | 38 ++++-- src/libs/qtcreatorcdbext/extensioncontext.h | 4 +- .../qtcreatorcdbext/qtcreatorcdbextension.cpp | 6 +- src/libs/qtcreatorcdbext/stringutils.cpp | 5 +- src/libs/qtcreatorcdbext/stringutils.h | 2 +- src/plugins/debugger/cdb/cdbengine.cpp | 115 +++++++++++------- src/plugins/debugger/cdb/cdbengine.h | 10 +- 7 files changed, 117 insertions(+), 63 deletions(-) diff --git a/src/libs/qtcreatorcdbext/extensioncontext.cpp b/src/libs/qtcreatorcdbext/extensioncontext.cpp index 58d2d782464..8d5b07affd4 100644 --- a/src/libs/qtcreatorcdbext/extensioncontext.cpp +++ b/src/libs/qtcreatorcdbext/extensioncontext.cpp @@ -36,6 +36,7 @@ #include "eventcallback.h" #include "outputcallback.h" #include "stringutils.h" +#include "gdbmihelpers.h" #include @@ -154,19 +155,16 @@ ULONG ExtensionContext::executionStatus() const // Complete stop parameters with common parameters and report static inline ExtensionContext::StopReasonMap - completeStopReasons(ExtensionContext::StopReasonMap stopReasons, ULONG ex) + completeStopReasons(CIDebugClient *client, ExtensionContext::StopReasonMap stopReasons, ULONG ex) { typedef ExtensionContext::StopReasonMap::value_type StopReasonMapValue; stopReasons.insert(StopReasonMapValue(std::string("executionStatus"), toString(ex))); - IInterfacePointer client; - if (client.create()) { - if (const ULONG processId = currentProcessId(client.data())) - stopReasons.insert(StopReasonMapValue(std::string("processId"), toString(processId))); - const ULONG threadId = currentThreadId(client.data()); - stopReasons.insert(StopReasonMapValue(std::string("threadId"), toString(threadId))); - } + if (const ULONG processId = currentProcessId(client)) + stopReasons.insert(StopReasonMapValue(std::string("processId"), toString(processId))); + const ULONG threadId = currentThreadId(client); + stopReasons.insert(StopReasonMapValue(std::string("threadId"), toString(threadId))); // Any reason? const std::string reasonKey = std::string(ExtensionContext::stopReasonKeyC); if (stopReasons.find(reasonKey) == stopReasons.end()) @@ -174,14 +172,32 @@ static inline ExtensionContext::StopReasonMap return stopReasons; } -void ExtensionContext::notifyIdle() +void ExtensionContext::notifyIdleCommand(CIDebugClient *client) { discardSymbolGroup(); if (m_stateNotification) { - const StopReasonMap stopReasons = completeStopReasons(m_stopReason, executionStatus()); + // Format full thread and stack info along with completed stop reasons. + std::string errorMessage; + ExtensionCommandContext exc(client); + const StopReasonMap stopReasons = completeStopReasons(client, m_stopReason, executionStatus()); // Format std::ostringstream str; - formatGdbmiHash(str, stopReasons); + formatGdbmiHash(str, stopReasons, false); + const std::string threadInfo = gdbmiThreadList(exc.systemObjects(), exc.symbols(), + exc.control(), exc.advanced(), &errorMessage); + if (threadInfo.empty()) { + str << ",threaderror=" << gdbmiStringFormat(errorMessage); + } else { + str << ",threads=" << threadInfo; + } + const std::string stackInfo = gdbmiStack(exc.control(), exc.symbols(), + maxStackFrames, false, &errorMessage); + if (stackInfo.empty()) { + str << ",stackerror=" << gdbmiStringFormat(errorMessage); + } else { + str << ",stack=" << stackInfo; + } + str << '}'; reportLong('E', 0, "session_idle", str.str()); } m_stopReason.clear(); diff --git a/src/libs/qtcreatorcdbext/extensioncontext.h b/src/libs/qtcreatorcdbext/extensioncontext.h index 5c7baad4362..abeeb2cbe73 100644 --- a/src/libs/qtcreatorcdbext/extensioncontext.h +++ b/src/libs/qtcreatorcdbext/extensioncontext.h @@ -52,6 +52,8 @@ class ExtensionContext { ExtensionContext(); public: + enum { maxStackFrames = 200 }; + // Key used to report stop reason in StopReasonMap static const char *stopReasonKeyC; // Map of parameters reported with the next stop as GDBMI @@ -85,7 +87,7 @@ public: // Call from notify handler, tell engine about state. void notifyState(ULONG Notify); // register as '.idle_cmd' to notify creator about stop - void notifyIdle(); + void notifyIdleCommand(CIDebugClient *client); // Return symbol group for frame (cached as long as frame/thread do not change). LocalsSymbolGroup *symbolGroup(CIDebugSymbols *symbols, ULONG threadId, int frame, std::string *errorMessage); diff --git a/src/libs/qtcreatorcdbext/qtcreatorcdbextension.cpp b/src/libs/qtcreatorcdbext/qtcreatorcdbextension.cpp index 785efdcc8eb..45dccd50d90 100644 --- a/src/libs/qtcreatorcdbext/qtcreatorcdbextension.cpp +++ b/src/libs/qtcreatorcdbext/qtcreatorcdbextension.cpp @@ -859,9 +859,9 @@ extern "C" HRESULT CALLBACK modules(CIDebugClient *Client, PCSTR argsIn) // Report stop of debuggee to Creator. This is hooked up as .idle_cmd command // by the Creator engine to reliably get notified about stops. -extern "C" HRESULT CALLBACK idle(CIDebugClient *, PCSTR) +extern "C" HRESULT CALLBACK idle(CIDebugClient *client, PCSTR) { - ExtensionContext::instance().notifyIdle(); + ExtensionContext::instance().notifyIdleCommand(client); return S_OK; } @@ -922,7 +922,7 @@ extern "C" HRESULT CALLBACK stack(CIDebugClient *Client, PCSTR argsIn) int token; bool humanReadable = false; - unsigned maxFrames = 1000; + unsigned maxFrames = ExtensionContext::maxStackFrames; StringList tokens = commandTokens(argsIn, &token); if (!tokens.empty() && tokens.front() == "-h") { diff --git a/src/libs/qtcreatorcdbext/stringutils.cpp b/src/libs/qtcreatorcdbext/stringutils.cpp index c0e7fea3790..7f140a9215e 100644 --- a/src/libs/qtcreatorcdbext/stringutils.cpp +++ b/src/libs/qtcreatorcdbext/stringutils.cpp @@ -256,7 +256,7 @@ std::wstring dataToReadableHexW(const unsigned char *begin, const unsigned char } // Format a map as a GDBMI hash {key="value",..} -void formatGdbmiHash(std::ostream &os, const std::map &m) +void formatGdbmiHash(std::ostream &os, const std::map &m, bool closeHash) { typedef std::map::const_iterator It; const It begin = m.begin(); @@ -267,5 +267,6 @@ void formatGdbmiHash(std::ostream &os, const std::map os << ','; os << it->first << "=\"" << gdbmiStringFormat(it->second) << '"'; } - os << '}'; + if (closeHash) + os << '}'; } diff --git a/src/libs/qtcreatorcdbext/stringutils.h b/src/libs/qtcreatorcdbext/stringutils.h index 140c7676946..5df46ea4ca6 100644 --- a/src/libs/qtcreatorcdbext/stringutils.h +++ b/src/libs/qtcreatorcdbext/stringutils.h @@ -186,6 +186,6 @@ std::wstring dataToHexW(const unsigned char *begin, const unsigned char *end); std::wstring dataToReadableHexW(const unsigned char *begin, const unsigned char *end); // Format a map as a GDBMI hash {key="value",..} -void formatGdbmiHash(std::ostream &os, const std::map &); +void formatGdbmiHash(std::ostream &os, const std::map &, bool closeHash = true); #endif // SPLIT_H diff --git a/src/plugins/debugger/cdb/cdbengine.cpp b/src/plugins/debugger/cdb/cdbengine.cpp index efe58d0a057..397eb89e42e 100644 --- a/src/plugins/debugger/cdb/cdbengine.cpp +++ b/src/plugins/debugger/cdb/cdbengine.cpp @@ -929,9 +929,9 @@ unsigned CdbEngine::debuggerCapabilities() const void CdbEngine::executeStep() { - postCommand(QByteArray("t"), 0); // Step into-> t (trace) if (!m_operateByInstruction) m_sourceStepInto = true; // See explanation at handleStackTrace(). + postCommand(QByteArray("t"), 0); // Step into-> t (trace) STATE_DEBUG(state(), Q_FUNC_INFO, __LINE__, "notifyInferiorRunRequested") notifyInferiorRunRequested(); } @@ -1090,6 +1090,15 @@ void CdbEngine::assignValueInDebugger(const WatchData *w, const QString &expr, c updateLocals(); } +void CdbEngine::parseThreads(const GdbMi &data, int forceCurrentThreadId /* = -1 */) +{ + int currentThreadId; + Threads threads = ThreadsHandler::parseGdbmiThreads(data, ¤tThreadId); + threadsHandler()->setThreads(threads); + threadsHandler()->setCurrentThreadId(forceCurrentThreadId >= 0 ? + forceCurrentThreadId : currentThreadId); +} + void CdbEngine::handleThreads(const CdbExtensionCommandPtr &reply) { if (debug) @@ -1097,10 +1106,7 @@ void CdbEngine::handleThreads(const CdbExtensionCommandPtr &reply) if (reply->success) { GdbMi data; data.fromString(reply->reply); - int currentThreadId; - Threads threads = ThreadsHandler::parseGdbmiThreads(data, ¤tThreadId); - threadsHandler()->setThreads(threads); - threadsHandler()->setCurrentThreadId(currentThreadId); + parseThreads(data); // Continue sequence postCommandSequence(reply->commandSequence); } else { @@ -1572,13 +1578,11 @@ enum StopActionFlags StopInArtificialThread = 0x40 }; -unsigned CdbEngine::examineStopReason(const QByteArray &messageIn, +unsigned CdbEngine::examineStopReason(const GdbMi &stopReason, QString *message, QString *exceptionBoxMessage) { // 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(); @@ -1685,7 +1689,10 @@ void CdbEngine::handleSessionIdle(const QByteArray &messageBA) // Further examine stop and report to user QString message; QString exceptionBoxMessage; - const unsigned stopFlags = examineStopReason(messageBA, &message, &exceptionBoxMessage); + GdbMi stopReason; + stopReason.fromString(messageBA); + int forcedThreadId = -1; + const unsigned stopFlags = examineStopReason(stopReason, &message, &exceptionBoxMessage); // Do the non-blocking log reporting if (stopFlags & StopReportLog) showMessage(message, LogMisc); @@ -1707,17 +1714,37 @@ void CdbEngine::handleSessionIdle(const QByteArray &messageBA) STATE_DEBUG(state(), Q_FUNC_INFO, __LINE__, "notifyInferiorSpontaneousStop") notifyInferiorSpontaneousStop(); } + const bool sourceStepInto = m_sourceStepInto; + m_sourceStepInto = false; // Start sequence to get all relevant data. if (stopFlags & StopInArtificialThread) { showMessage(tr("Switching to main thread..."), LogMisc); postCommand("~0 s", 0); + forcedThreadId = 0; + // Re-fetch stack again. + postCommandSequence(CommandListStack); + } else { + const GdbMi stack = stopReason.findChild("stack"); + if (stack.isValid()) { + if (parseStackTrace(stack, sourceStepInto) & ParseStackStepInto) { + executeStep(); // Hit on a frame while step into, see parseStackTrace(). + return; + } + } else { + showMessage(QString::fromAscii(stopReason.findChild("stackerror").data()), LogError); + } } - unsigned sequence = CommandListStack|CommandListThreads; + const GdbMi threads = stopReason.findChild("threads"); + if (threads.isValid()) { + parseThreads(threads, forcedThreadId); + } else { + showMessage(QString::fromAscii(stopReason.findChild("threaderror").data()), LogError); + } + // Fire off remaining commands asynchronously if (debuggerCore()->isDockVisible(QLatin1String(Constants::DOCKWIDGET_REGISTER))) - sequence |= CommandListRegisters; + postCommandSequence(CommandListRegisters); if (debuggerCore()->isDockVisible(QLatin1String(Constants::DOCKWIDGET_MODULES))) - sequence |= CommandListModules; - postCommandSequence(sequence); + postCommandSequence(CommandListModules); } // After the sequence has been sent off and CDB is pondering the commands, // pop up a message box for exceptions. @@ -2176,13 +2203,8 @@ QString CdbEngine::normalizeFileName(const QString &f) // Parse frame from GDBMI. Duplicate of the gdb code, but that // has more processing. -static StackFrames parseFrames(const QByteArray &data) +static StackFrames parseFrames(const GdbMi &gdbmi) { - GdbMi gdbmi; - gdbmi.fromString(data); - if (!gdbmi.isValid()) - return StackFrames(); - StackFrames rc; const int count = gdbmi.childCount(); rc.reserve(count); @@ -2204,38 +2226,43 @@ static StackFrames parseFrames(const QByteArray &data) return rc; } -void CdbEngine::handleStackTrace(const CdbExtensionCommandPtr &command) +unsigned CdbEngine::parseStackTrace(const GdbMi &data, bool sourceStepInto) { // Parse frames, find current. Special handling for step into: // When stepping into on an actual function (source mode) by executing 't', an assembler // frame pointing at the jmp instruction is hit (noticeable by top function being // 'ILT+'). If that is the case, execute another 't' to step into the actual function. . // Note that executing 't 2' does not work since it steps 2 instructions on a non-call code line. - const bool sourceStepInto = m_sourceStepInto; - m_sourceStepInto = false; - if (command->success) { - int current = -1; - StackFrames frames = parseFrames(command->reply); - const int count = frames.size(); - for (int i = 0; i < count; i++) { - const bool hasFile = !frames.at(i).file.isEmpty(); - // jmp-frame hit by step into, do another 't' and abort sequence. - if (!hasFile && i == 0 && sourceStepInto && frames.at(i).function.contains(QLatin1String("ILT+"))) { - showMessage(QString::fromAscii("Step into: Call instruction hit, performing additional step..."), LogMisc); - executeStep(); - return; - } - if (hasFile) { - frames[i].file = QDir::cleanPath(normalizeFileName(frames.at(i).file)); - if (current == -1 && frames[i].usable) - current = i; - } + int current = -1; + StackFrames frames = parseFrames(data); + const int count = frames.size(); + for (int i = 0; i < count; i++) { + const bool hasFile = !frames.at(i).file.isEmpty(); + // jmp-frame hit by step into, do another 't' and abort sequence. + if (!hasFile && i == 0 && sourceStepInto && frames.at(i).function.contains(QLatin1String("ILT+"))) { + showMessage(QString::fromAscii("Step into: Call instruction hit, performing additional step..."), LogMisc); + return ParseStackStepInto; } - if (count && current == -1) // No usable frame, use assembly. - current = 0; - // Set - stackHandler()->setFrames(frames); - activateFrame(current); + if (hasFile) { + frames[i].file = QDir::cleanPath(normalizeFileName(frames.at(i).file)); + if (current == -1 && frames[i].usable) + current = i; + } + } + if (count && current == -1) // No usable frame, use assembly. + current = 0; + // Set + stackHandler()->setFrames(frames); + activateFrame(current); + return 0; +} + +void CdbEngine::handleStackTrace(const CdbExtensionCommandPtr &command) +{ + if (command->success) { + GdbMi data; + data.fromString(command->reply); + parseStackTrace(data, false); postCommandSequence(command->commandSequence); } else { showMessage(command->errorMessage, LogError); diff --git a/src/plugins/debugger/cdb/cdbengine.h b/src/plugins/debugger/cdb/cdbengine.h index 8d35d408537..f34262382e0 100644 --- a/src/plugins/debugger/cdb/cdbengine.h +++ b/src/plugins/debugger/cdb/cdbengine.h @@ -53,6 +53,7 @@ struct CdbBuiltinCommand; struct CdbExtensionCommand; struct CdbOptions; class ByteArrayInputStream; +class GdbMi; class CdbEngine : public Debugger::DebuggerEngine { @@ -164,9 +165,14 @@ private: SpecialStopSynchronizeBreakpoints, SpecialStopGetWidgetAt }; + enum ParseStackResultFlags // Flags returned by parseStackTrace + { + ParseStackStepInto = 1 // Need to execute a step, hit on a call frame in "Step into" + }; + bool startConsole(const DebuggerStartParameters &sp, QString *errorMessage); - unsigned examineStopReason(const QByteArray &messageIn, QString *message, + unsigned examineStopReason(const GdbMi &stopReason, QString *message, QString *exceptionBoxMessage); bool commandsPending() const; void handleExtensionMessage(char t, int token, const QByteArray &what, const QByteArray &message); @@ -205,6 +211,8 @@ private: void updateLocals(); int elapsedLogTime() const; void addLocalsOptions(ByteArrayInputStream &s) const; + unsigned parseStackTrace(const GdbMi &data, bool sourceStepInto); + void parseThreads(const GdbMi &, int forceCurrentThreadId = -1); const QByteArray m_creatorExtPrefix; const QByteArray m_tokenPrefix;