From 11c6ca71ac76e57e68fe334cb2a9effd6c6d444d Mon Sep 17 00:00:00 2001 From: Friedemann Kleint Date: Tue, 18 Jan 2011 11:40:45 +0100 Subject: [PATCH] Debugger[New CDB]: Add support for "Select Widget to Watch". in stopped state. Add helper for executing calls to ExtensionContext including recording of output in OutputCallback. Extend symbol resolution to return addresses as well since QApplication::widgetAt() is ambiguous and needs to be called by address. Add 'widgetat' extension command to return the widget. --- src/libs/qtcreatorcdbext/extensioncontext.cpp | 102 ++++++++++++++---- src/libs/qtcreatorcdbext/extensioncontext.h | 9 +- src/libs/qtcreatorcdbext/gdbmihelpers.cpp | 50 +++++++++ src/libs/qtcreatorcdbext/gdbmihelpers.h | 7 ++ src/libs/qtcreatorcdbext/outputcallback.cpp | 20 +++- src/libs/qtcreatorcdbext/outputcallback.h | 5 + src/libs/qtcreatorcdbext/qtcreatorcdbext.def | 1 + .../qtcreatorcdbext/qtcreatorcdbextension.cpp | 34 ++++++ src/libs/qtcreatorcdbext/symbolgroup.cpp | 10 +- src/libs/qtcreatorcdbext/symbolgroupvalue.cpp | 28 ++++- src/libs/qtcreatorcdbext/symbolgroupvalue.h | 13 ++- src/plugins/debugger/cdb/cdbengine.cpp | 75 ++++++++++++- src/plugins/debugger/cdb/cdbengine.h | 12 ++- 13 files changed, 329 insertions(+), 37 deletions(-) diff --git a/src/libs/qtcreatorcdbext/extensioncontext.cpp b/src/libs/qtcreatorcdbext/extensioncontext.cpp index 0b97395afe2..58d2d782464 100644 --- a/src/libs/qtcreatorcdbext/extensioncontext.cpp +++ b/src/libs/qtcreatorcdbext/extensioncontext.cpp @@ -48,7 +48,7 @@ const char *ExtensionContext::stopReasonKeyC = "reason"; ExtensionContext::ExtensionContext() : m_hookedClient(0), m_oldEventCallback(0), m_oldOutputCallback(0), - m_creatorEventCallback(0), m_creatorOutputCallback(0) + m_creatorEventCallback(0), m_creatorOutputCallback(0), m_stateNotification(true) { } @@ -81,6 +81,20 @@ void ExtensionContext::hookCallbacks(CIDebugClient *client) } } +void ExtensionContext::startRecordingOutput() +{ + if (m_creatorOutputCallback) { + m_creatorOutputCallback->startRecording(); + } else { + report('X', 0, 0, "Error", "ExtensionContext::startRecordingOutput() called with no output hooked.\n"); + } +} + +std::wstring ExtensionContext::stopRecordingOutput() +{ + return m_creatorOutputCallback ? m_creatorOutputCallback->stopRecording() : std::wstring(); +} + void ExtensionContext::setStopReason(const StopReasonMap &r, const std::string &reason) { m_stopReason = r; @@ -163,37 +177,41 @@ static inline ExtensionContext::StopReasonMap void ExtensionContext::notifyIdle() { discardSymbolGroup(); - - const StopReasonMap stopReasons = completeStopReasons(m_stopReason, executionStatus()); - m_stopReason.clear(); - // Format - std::ostringstream str; - formatGdbmiHash(str, stopReasons); - reportLong('E', 0, "session_idle", str.str()); + if (m_stateNotification) { + const StopReasonMap stopReasons = completeStopReasons(m_stopReason, executionStatus()); + // Format + std::ostringstream str; + formatGdbmiHash(str, stopReasons); + reportLong('E', 0, "session_idle", str.str()); + } m_stopReason.clear(); } void ExtensionContext::notifyState(ULONG Notify) { const ULONG ex = executionStatus(); - switch (Notify) { - case DEBUG_NOTIFY_SESSION_ACTIVE: - report('E', 0, 0, "session_active", "%u", ex); - break; - case DEBUG_NOTIFY_SESSION_ACCESSIBLE: // Meaning, commands accepted - report('E', 0, 0, "session_accessible", "%u", ex); - break; - case DEBUG_NOTIFY_SESSION_INACCESSIBLE: - report('E', 0, 0, "session_inaccessible", "%u", ex); - break; - case DEBUG_NOTIFY_SESSION_INACTIVE: - report('E', 0, 0, "session_inactive", "%u", ex); + if (m_stateNotification) { + switch (Notify) { + case DEBUG_NOTIFY_SESSION_ACTIVE: + report('E', 0, 0, "session_active", "%u", ex); + break; + case DEBUG_NOTIFY_SESSION_ACCESSIBLE: // Meaning, commands accepted + report('E', 0, 0, "session_accessible", "%u", ex); + break; + case DEBUG_NOTIFY_SESSION_INACCESSIBLE: + report('E', 0, 0, "session_inaccessible", "%u", ex); + break; + case DEBUG_NOTIFY_SESSION_INACTIVE: + report('E', 0, 0, "session_inactive", "%u", ex); + break; + } + } + if (Notify == DEBUG_NOTIFY_SESSION_INACTIVE) { discardSymbolGroup(); discardWatchesSymbolGroup(); // We lost the debuggee, at this point restore output. if (ex & DEBUG_STATUS_NO_DEBUGGEE) unhookCallbacks(); - break; } } @@ -280,6 +298,48 @@ bool ExtensionContext::reportLong(char code, int token, const char *serviceName, return true; } +bool ExtensionContext::call(const std::string &functionCall, + std::wstring *output, + std::string *errorMessage) +{ + if (!m_creatorOutputCallback) { + *errorMessage = "Attempt to issue a call with no output hooked."; + return false; + } + // Set up arguments + const std::string call = ".call " + functionCall; + HRESULT hr = m_control->Execute(DEBUG_OUTCTL_ALL_CLIENTS, call.c_str(), DEBUG_EXECUTE_ECHO); + if (FAILED(hr)) { + *errorMessage = msgDebugEngineComFailed("Execute", hr); + return 0; + } + // Execute in current thread. TODO: This must not crash, else we are in an inconsistent state + // (need to call 'gh', etc.) + hr = m_control->Execute(DEBUG_OUTCTL_ALL_CLIENTS, "~. g", DEBUG_EXECUTE_ECHO); + if (FAILED(hr)) { + *errorMessage = msgDebugEngineComFailed("Execute", hr); + return 0; + } + // Wait until finished + startRecordingOutput(); + m_stateNotification = false; + m_control->WaitForEvent(0, INFINITE); + *output = stopRecordingOutput(); + m_stateNotification = true; + // Crude attempt at recovering from a crash: Issue 'gN' (go with exception not handled). + const bool crashed = output->find(L"This exception may be expected and handled.") != std::string::npos; + if (crashed) { + m_stopReason.clear(); + m_stateNotification = false; + hr = m_control->Execute(DEBUG_OUTCTL_ALL_CLIENTS, "~. gN", DEBUG_EXECUTE_ECHO); + m_control->WaitForEvent(0, INFINITE); + m_stateNotification = true; + *errorMessage = "A crash occurred while calling: " + functionCall; + return false; + } + return true; +} + // Exported C-functions extern "C" { diff --git a/src/libs/qtcreatorcdbext/extensioncontext.h b/src/libs/qtcreatorcdbext/extensioncontext.h index 8ff9807cb73..27a3f76b22e 100644 --- a/src/libs/qtcreatorcdbext/extensioncontext.h +++ b/src/libs/qtcreatorcdbext/extensioncontext.h @@ -42,6 +42,7 @@ class LocalsSymbolGroup; class WatchesSymbolGroup; +class OutputCallback; // Global singleton with context. // Caches a symbolgroup per frame and thread as long as the session is accessible. @@ -97,6 +98,11 @@ public: // Set a stop reason to be reported with the next idle notification (exception). void setStopReason(const StopReasonMap &, const std::string &reason = std::string()); + void startRecordingOutput(); + std::wstring stopRecordingOutput(); + // Execute a function call and record the output. + bool call(const std::string &functionCall, std::wstring *output, std::string *errorMessage); + private: bool isInitialized() const; void discardSymbolGroup(); @@ -109,9 +115,10 @@ private: IDebugEventCallbacks *m_oldEventCallback; IDebugOutputCallbacksWide *m_oldOutputCallback; IDebugEventCallbacks *m_creatorEventCallback; - IDebugOutputCallbacksWide *m_creatorOutputCallback; + OutputCallback *m_creatorOutputCallback; StopReasonMap m_stopReason; + bool m_stateNotification; }; // Context for extension commands to be instantiated on stack in a command handler. diff --git a/src/libs/qtcreatorcdbext/gdbmihelpers.cpp b/src/libs/qtcreatorcdbext/gdbmihelpers.cpp index d36fb6f7cad..6826df9b83a 100644 --- a/src/libs/qtcreatorcdbext/gdbmihelpers.cpp +++ b/src/libs/qtcreatorcdbext/gdbmihelpers.cpp @@ -35,6 +35,8 @@ #include "stringutils.h" #include "iinterfacepointer.h" #include "base64.h" +#include "symbolgroupvalue.h" +#include "extensioncontext.h" #include @@ -620,3 +622,51 @@ std::string gdbmiStack(CIDebugControl *debugControl, str << ']'; return str.str(); } + +// Find the widget of the application by calling QApplication::widgetAt(). +// Return "Qualified_ClassName:Address" + +static inline std::string msgWidgetParseError(std::wstring wo) +{ + replace(wo, L'\n', L';'); + return "Output parse error :" + wStringToString(wo); +} + +std::string widgetAt(const SymbolGroupValueContext &ctx, int x, int y, std::string *errorMessage) +{ + typedef SymbolGroupValue::SymbolList SymbolList; + // First, resolve symbol since there are ambiguities. Take the first one which is the + // overload for (int,int) and call by address instead off name to overcome that. + const std::string func = QtInfo::get(ctx).prependQtGuiModule("QApplication::widgetAt"); + const SymbolList symbols = SymbolGroupValue::resolveSymbol(func.c_str(), ctx, errorMessage); + if (symbols.empty()) + return std::string(); // Not a gui application, likely + std::ostringstream callStr; + callStr << std::showbase << std::hex << symbols.front().second + << std::noshowbase << std::dec << '(' << x << ',' << y << ')'; + std::wstring wOutput; + if (!ExtensionContext::instance().call(callStr.str(), &wOutput, errorMessage)) + return std::string(); + // Returns: ".call returns\nclass QWidget * 0x00000000`022bf100\nbla...". + // Chop lines in front and after 'class ...' and convert first line. + const std::wstring::size_type classPos = wOutput.find(L"class "); + if (classPos == std::wstring::npos) { + *errorMessage = msgWidgetParseError(wOutput); + return std::string(); + } + wOutput.erase(0, classPos + 6); + const std::wstring::size_type nlPos = wOutput.find(L'\n'); + if (nlPos != std::wstring::npos) + wOutput.erase(nlPos, wOutput.size() - nlPos); + const std::string::size_type addressPos = wOutput.find(L" * 0x"); + if (addressPos == std::string::npos) { + *errorMessage = msgWidgetParseError(wOutput); + return std::string(); + } + // "QWidget * 0x00000000`022bf100" -> "QWidget:0x00000000022bf100" + wOutput.replace(addressPos, 3, L":"); + const std::string::size_type sepPos = wOutput.find(L'`'); + if (sepPos != std::string::npos) + wOutput.erase(sepPos, 1); + return wStringToString(wOutput); +} diff --git a/src/libs/qtcreatorcdbext/gdbmihelpers.h b/src/libs/qtcreatorcdbext/gdbmihelpers.h index fee71a42313..849a05faebb 100644 --- a/src/libs/qtcreatorcdbext/gdbmihelpers.h +++ b/src/libs/qtcreatorcdbext/gdbmihelpers.h @@ -37,6 +37,8 @@ #include "common.h" #include +struct SymbolGroupValueContext; + /* Various helpers to the extension commands to retrieve debuggee information * in suitable formats for the debugger engine. */ @@ -159,4 +161,9 @@ std::string gdbmiStack(CIDebugControl *debugControl, CIDebugSymbols *debugSymbol unsigned maxFrames, bool humanReadable, std::string *errorMessage); +// Find the widget of the application at (x,y) by calling QApplication::widgetAt(). +// Return a string of "Qualified_ClassName:Address" +std::string widgetAt(const SymbolGroupValueContext &ctx, + int x, int y, std::string *errorMessage); + #endif // THREADLIST_H diff --git a/src/libs/qtcreatorcdbext/outputcallback.cpp b/src/libs/qtcreatorcdbext/outputcallback.cpp index 63dde47e5ea..cb3c57ed114 100644 --- a/src/libs/qtcreatorcdbext/outputcallback.cpp +++ b/src/libs/qtcreatorcdbext/outputcallback.cpp @@ -38,7 +38,8 @@ #include -OutputCallback::OutputCallback(IDebugOutputCallbacksWide *wrapped) : m_wrapped(wrapped) +OutputCallback::OutputCallback(IDebugOutputCallbacksWide *wrapped) : + m_wrapped(wrapped), m_recording(false) { } @@ -85,6 +86,9 @@ STDMETHODIMP OutputCallback::Output( IN PCWSTR text ) { + + if (m_recording) + m_recorded.append(text); // Do not unconditionally output ourselves here, as this causes an endless // recursion. Suppress prompts (note that sequences of prompts may mess parsing up) if (!m_wrapped || mask == DEBUG_OUTPUT_PROMPT) @@ -100,3 +104,17 @@ STDMETHODIMP OutputCallback::Output( ExtensionContext::instance().reportLong('E', 0, "debuggee_output", str.str().c_str()); return S_OK; } + +void OutputCallback::startRecording() +{ + m_recorded.clear(); + m_recording = true; +} + +std::wstring OutputCallback::stopRecording() +{ + const std::wstring rc = m_recorded; + m_recorded.clear(); + m_recording = false; + return rc; +} diff --git a/src/libs/qtcreatorcdbext/outputcallback.h b/src/libs/qtcreatorcdbext/outputcallback.h index 4ba81a218b8..4432ab3821b 100644 --- a/src/libs/qtcreatorcdbext/outputcallback.h +++ b/src/libs/qtcreatorcdbext/outputcallback.h @@ -63,8 +63,13 @@ public: IN PCWSTR text ); + void startRecording(); + std::wstring stopRecording(); + private: IDebugOutputCallbacksWide *m_wrapped; + bool m_recording; + std::wstring m_recorded; }; #endif // DEBUGEVENTOUTPUT_H diff --git a/src/libs/qtcreatorcdbext/qtcreatorcdbext.def b/src/libs/qtcreatorcdbext/qtcreatorcdbext.def index e8c47d447e8..bc91011c9b5 100644 --- a/src/libs/qtcreatorcdbext/qtcreatorcdbext.def +++ b/src/libs/qtcreatorcdbext/qtcreatorcdbext.def @@ -20,4 +20,5 @@ shutdownex test stack addwatch +widgetat KnownStructOutput diff --git a/src/libs/qtcreatorcdbext/qtcreatorcdbextension.cpp b/src/libs/qtcreatorcdbext/qtcreatorcdbextension.cpp index 706bb055ac0..ef3fd00ec0a 100644 --- a/src/libs/qtcreatorcdbext/qtcreatorcdbextension.cpp +++ b/src/libs/qtcreatorcdbext/qtcreatorcdbextension.cpp @@ -100,6 +100,7 @@ enum Command { CmdStack, CmdShutdownex, CmdAddWatch, + CmdWidgetAt, CmdTest }; @@ -155,6 +156,7 @@ static const CommandDescription commandDescriptions[] = { {"stack","Prints stack in GDBMI format.","[-t token] [max-frames]"}, {"shutdownex","Unhooks output callbacks.\nNeeds to be called explicitly only in case of remote debugging.",""}, {"addwatch","Add watch expression"," "}, +{"widgetat","Return address of widget at position"," "}, {"test","Testing command","-T type | -w watch-expression"} }; @@ -943,6 +945,38 @@ extern "C" HRESULT CALLBACK shutdownex(CIDebugClient *, PCSTR) return S_OK; } +extern "C" HRESULT CALLBACK widgetat(CIDebugClient *client, PCSTR argsIn) +{ + ExtensionCommandContext exc(client); + int token = 0; + std::string widgetAddress; + std::string errorMessage; + + do { + int x = -1; + int y = -1; + + const StringVector tokens = commandTokens(argsIn, &token); + if (tokens.size() != 2) { + errorMessage = singleLineUsage(commandDescriptions[CmdWidgetAt]); + break; + } + if (!integerFromString(tokens.front(), &x) || !integerFromString(tokens.at(1), &y)) { + errorMessage = singleLineUsage(commandDescriptions[CmdWidgetAt]); + break; + } + widgetAddress = widgetAt(SymbolGroupValueContext(exc.dataSpaces(), exc.symbols()), + x, y, &errorMessage); + } while (false); + + if (widgetAddress.empty()) { + ExtensionContext::instance().report('N', token, 0, "widgetat", errorMessage.c_str()); + } else { + ExtensionContext::instance().reportLong('R', token, "widgetat", widgetAddress); + } + return S_OK; +} + extern "C" HRESULT CALLBACK test(CIDebugClient *client, PCSTR argsIn) { enum Mode { Invalid, TestType, TestFixWatchExpression }; diff --git a/src/libs/qtcreatorcdbext/symbolgroup.cpp b/src/libs/qtcreatorcdbext/symbolgroup.cpp index 1b5de07e550..6aaa6185a83 100644 --- a/src/libs/qtcreatorcdbext/symbolgroup.cpp +++ b/src/libs/qtcreatorcdbext/symbolgroup.cpp @@ -701,7 +701,6 @@ static bool parseWatchExpression(const std::string &expression, for ( ; pos < size ; pos++) { const char c = expression.at(pos); const WatchExpressionParseState nextState = nextWatchExpressionParseState(state, c, &templateLevel); - DebugPrint() << c << ' ' << pos << ' ' << state << ' ' << nextState << ' ' << templateLevel; if (nextState == WEPS_Error) return false; if (nextState != state && state == WEPS_WithinType) @@ -767,16 +766,19 @@ bool WatchesSymbolGroup::addWatch(CIDebugSymbols *s, std::string iname, const st return true; } -// Compile map of current state root-iname->root-expression +// Compile map of current state root-iname->root-expression (top-level) WatchesSymbolGroup::InameExpressionMap WatchesSymbolGroup::currentInameExpressionMap() const { + // Skip additional, expanded nodes InameExpressionMap rc; - if (unsigned size = unsigned(root()->children().size())) + if (unsigned size = unsigned(root()->children().size())) { for (unsigned i = 0; i < size; i++) { const AbstractSymbolGroupNode *n = root()->childAt(i); - rc.insert(InameExpressionMap::value_type(n->iName(), n->name())); + if (n->testFlags(SymbolGroupNode::WatchNode)) + rc.insert(InameExpressionMap::value_type(n->iName(), n->name())); } + } return rc; } diff --git a/src/libs/qtcreatorcdbext/symbolgroupvalue.cpp b/src/libs/qtcreatorcdbext/symbolgroupvalue.cpp index ec5fd2b54e1..1bcd596ff3d 100644 --- a/src/libs/qtcreatorcdbext/symbolgroupvalue.cpp +++ b/src/libs/qtcreatorcdbext/symbolgroupvalue.cpp @@ -398,7 +398,7 @@ static inline std::string resolveQtSymbol(const char *symbolC, std::string defaultPattern = defaultModuleNameC; defaultPattern.push_back('!'); defaultPattern += symbolC; - const StringList defaultMatches = SymbolGroupValue::resolveSymbol(defaultPattern.c_str(), ctx); + const StringList defaultMatches = SymbolGroupValue::resolveSymbolName(defaultPattern.c_str(), ctx); const SubStringPredicate modulePattern(modulePatternC); const StringListConstIt defaultIt = std::find_if(defaultMatches.begin(), defaultMatches.end(), modulePattern); if (defaultIt != defaultMatches.end()) @@ -406,7 +406,7 @@ static inline std::string resolveQtSymbol(const char *symbolC, // Fail, now try a search with '*qstrdup' in all modules. This might return several matches // like 'QtCored4!qstrdup', 'QGuid4!qstrdup' const std::string wildCardPattern = std::string(1, '*') + symbolC; - const StringList allMatches = SymbolGroupValue::resolveSymbol(wildCardPattern.c_str(), ctx); + const StringList allMatches = SymbolGroupValue::resolveSymbolName(wildCardPattern.c_str(), ctx); const StringListConstIt allIt = std::find_if(allMatches.begin(), allMatches.end(), modulePattern); return allIt != allMatches.end() ? *allIt : std::string(); } @@ -491,12 +491,29 @@ std::ostream &operator<<(std::ostream &os, const QtInfo &i) } std::list + SymbolGroupValue::resolveSymbolName(const char *pattern, + const SymbolGroupValueContext &c, + std::string *errorMessage /* = 0 */) +{ + // Extract the names + const SymbolList symbols = resolveSymbol(pattern, c, errorMessage); + std::list rc; + if (!symbols.empty()) { + const SymbolList::const_iterator cend = symbols.end(); + for (SymbolList::const_iterator it = symbols.begin(); it != cend; ++it) + rc.push_back(it->first); + } + return rc; + +} + +SymbolGroupValue::SymbolList SymbolGroupValue::resolveSymbol(const char *pattern, const SymbolGroupValueContext &c, std::string *errorMessage /* = 0 */) { enum { bufSize = 2048 }; - std::list rc; + std::list rc; if (errorMessage) errorMessage->clear(); // Is it an incomplete symbol? @@ -518,12 +535,13 @@ std::list return rc; } char buf[bufSize]; + ULONG64 offset; while (true) { - hr = c.symbols->GetNextSymbolMatch(handle, buf, bufSize - 1, 0, 0); + hr = c.symbols->GetNextSymbolMatch(handle, buf, bufSize - 1, 0, &offset); if (hr == E_NOINTERFACE) break; if (hr == S_OK) - rc.push_back(std::string(buf)); + rc.push_back(Symbol(std::string(buf), offset)); } c.symbols->EndSymbolMatch(handle); return rc; diff --git a/src/libs/qtcreatorcdbext/symbolgroupvalue.h b/src/libs/qtcreatorcdbext/symbolgroupvalue.h index 689a5123404..3375f3e6b78 100644 --- a/src/libs/qtcreatorcdbext/symbolgroupvalue.h +++ b/src/libs/qtcreatorcdbext/symbolgroupvalue.h @@ -70,6 +70,9 @@ class SymbolGroupValue explicit SymbolGroupValue(const std::string &parentError); public: + typedef std::pair Symbol; + typedef std::list SymbolList; + explicit SymbolGroupValue(SymbolGroupNode *node, const SymbolGroupValueContext &c); SymbolGroupValue(); @@ -129,9 +132,13 @@ public: const SymbolGroupValueContext &ctx, const std::string ¤tModule = std::string()); - static std::list resolveSymbol(const char *pattern, - const SymbolGroupValueContext &c, - std::string *errorMessage = 0); + static std::list resolveSymbolName(const char *pattern, + const SymbolGroupValueContext &c, + std::string *errorMessage = 0); + static SymbolList resolveSymbol(const char *pattern, + const SymbolGroupValueContext &c, + std::string *errorMessage = 0); + static unsigned pointerSize(); static unsigned intSize(); diff --git a/src/plugins/debugger/cdb/cdbengine.cpp b/src/plugins/debugger/cdb/cdbengine.cpp index 78ec65d5b04..d6d8440035b 100644 --- a/src/plugins/debugger/cdb/cdbengine.cpp +++ b/src/plugins/debugger/cdb/cdbengine.cpp @@ -353,7 +353,9 @@ CdbEngine::CdbEngine(const DebuggerStartParameters &sp, m_hasDebuggee(false), m_elapsedLogTime(0), m_sourceStepInto(false), - m_wX86BreakpointCount(0) + m_wX86BreakpointCount(0), + m_watchPointX(0), + m_watchPointY(0) { Utils::SavedAction *assemblerAction = theAssemblerAction(); m_operateByInstructionPending = assemblerAction->isChecked(); @@ -1443,6 +1445,12 @@ unsigned CdbEngine::examineStopReason(const QByteArray &messageIn, *message = tr("Malformed stop response received."); return StopReportParseError|StopNotifyStop; } + // Additional stop messages occurring for debuggee function calls (widgetAt, etc). Just log. + if (state() == InferiorStopOk) { + *message = QString::fromLatin1("Ignored stop notification from function call (%1)."). + arg(QString::fromAscii(reason)); + return StopReportLog; + } const int threadId = stopReason.findChild("threadId").data().toInt(); if (reason == "breakpoint") { const int number = stopReason.findChild("breakpointId").data().toInt(); @@ -1511,6 +1519,9 @@ void CdbEngine::handleSessionIdle(const QByteArray &messageBA) attemptBreakpointSynchronization(); doContinueInferior(); return; + case SpecialStopGetWidgetAt: + postWidgetAtCommand(); + return; case NoSpecialStop: break; } @@ -2112,5 +2123,67 @@ void CdbEngine::postCommandSequence(unsigned mask) } } +void CdbEngine::handleWidgetAt(const CdbExtensionCommandPtr &reply) +{ + bool success = false; + QString message; + do { + if (!reply->success) { + message = QString::fromAscii(reply->errorMessage); + break; + } + // Should be "namespace::QWidget:0x555" + QString watchExp = QString::fromAscii(reply->reply); + const int sepPos = watchExp.lastIndexOf(QLatin1Char(':')); + if (sepPos == -1) { + message = QString::fromAscii("Invalid output: %1").arg(watchExp); + break; + } + // 0x000 -> nothing found + if (!watchExp.mid(sepPos + 1).toULongLong(0, 0)) { + message = QString::fromAscii("No widget could be found at %1, %2.").arg(m_watchPointX).arg(m_watchPointY); + break; + } + // Turn into watch expression: "*(namespace::QWidget*)0x555" + watchExp.replace(sepPos, 1, QLatin1String("*)")); + watchExp.insert(0, QLatin1String("*(")); + watchHandler()->watchExpression(watchExp); + success = true; + } while (false); + if (!success) + showMessage(message, LogWarning); + m_watchPointX = m_watchPointY = 0; +} + +void CdbEngine::watchPoint(const QPoint &p) +{ + m_watchPointX = p.x(); + m_watchPointY = p.y(); + switch (state()) { + case InferiorStopOk: + postWidgetAtCommand(); + break; + case InferiorRunOk: + // "Select Widget to Watch" from a running application is currently not + // supported. It could be implemented via SpecialStopGetWidgetAt-mode, + // but requires some work as not to confuse the engine by state-change notifications + // emitted by the debuggee function call. + showMessage(tr("\"Select Widget to Watch\": Please stop the application first."), LogWarning); + break; + default: + showMessage(tr("\"Select Widget to Watch\": Not supported in state '%1'."). + arg(QString::fromAscii(stateName(state()))), LogWarning); + break; + } +} + +void CdbEngine::postWidgetAtCommand() +{ + QByteArray arguments = QByteArray::number(m_watchPointX); + arguments.append(' '); + arguments.append(QByteArray::number(m_watchPointY)); + postExtensionCommand("widgetat", arguments, 0, &CdbEngine::handleWidgetAt, 0); +} + } // namespace Internal } // namespace Debugger diff --git a/src/plugins/debugger/cdb/cdbengine.h b/src/plugins/debugger/cdb/cdbengine.h index bec8579cd08..caf74fad626 100644 --- a/src/plugins/debugger/cdb/cdbengine.h +++ b/src/plugins/debugger/cdb/cdbengine.h @@ -89,6 +89,7 @@ public: virtual void updateWatchData(const WatchData &data, const WatchUpdateFlags & flags = WatchUpdateFlags()); virtual unsigned debuggerCapabilities() const; + virtual void watchPoint(const QPoint &); virtual void setRegisterValue(int regnr, const QString &value); virtual void executeStep(); @@ -150,7 +151,12 @@ private slots: void operateByInstructionTriggered(bool); private: - enum SpecialStopMode { NoSpecialStop, SpecialStopSynchronizeBreakpoints }; + enum SpecialStopMode + { + NoSpecialStop, + SpecialStopSynchronizeBreakpoints, + SpecialStopGetWidgetAt + }; unsigned examineStopReason(const QByteArray &messageIn, QString *message, QString *exceptionBoxMessage); @@ -166,6 +172,7 @@ private: inline bool isCdbProcessRunning() const { return m_process.state() != QProcess::NotRunning; } bool canInterruptInferior() const; void syncOperateByInstruction(bool operateByInstruction); + void postWidgetAtCommand(); // Builtin commands void dummyHandler(const CdbBuiltinCommandPtr &); @@ -182,6 +189,7 @@ private: void handleRegisters(const CdbExtensionCommandPtr &reply); void handleModules(const CdbExtensionCommandPtr &reply); void handleMemory(const CdbExtensionCommandPtr &); + void handleWidgetAt(const CdbExtensionCommandPtr &); QString normalizeFileName(const QString &f); void updateLocalVariable(const QByteArray &iname); @@ -215,6 +223,8 @@ private: QByteArray m_extensionMessageBuffer; bool m_sourceStepInto; unsigned m_wX86BreakpointCount; + int m_watchPointX; + int m_watchPointY; }; } // namespace Internal