From c2eada278e0fd07decd65fdbc8d5b7993088cc22 Mon Sep 17 00:00:00 2001 From: hjk Date: Tue, 13 Dec 2016 09:46:51 +0100 Subject: [PATCH] Debugger: Fix Window grabbing on GDB Needs to make namespace detection work without valid frame Task-number: QTCREATORBUG-17326 Change-Id: Ia7c7017db4ef384d4f246e11a5601d01f4f366f1 Reviewed-by: hjk --- share/qtcreator/debugger/gdbbridge.py | 52 ++++----- share/qtcreator/debugger/lldbbridge.py | 16 ++- src/plugins/debugger/debuggerprotocol.h | 17 +++ src/plugins/debugger/gdb/gdbengine.cpp | 134 +++++++++++------------ src/plugins/debugger/gdb/gdbengine.h | 12 +- src/plugins/debugger/lldb/lldbengine.cpp | 59 ++++++++-- src/plugins/debugger/lldb/lldbengine.h | 8 +- src/plugins/debugger/watchhandler.cpp | 69 +++++++++--- src/plugins/debugger/watchhandler.h | 4 +- 9 files changed, 245 insertions(+), 126 deletions(-) diff --git a/share/qtcreator/debugger/gdbbridge.py b/share/qtcreator/debugger/gdbbridge.py index ae3336569d5..8bb66e1d303 100644 --- a/share/qtcreator/debugger/gdbbridge.py +++ b/share/qtcreator/debugger/gdbbridge.py @@ -33,6 +33,7 @@ import os import os.path import sys import struct +import tempfile import types from dumper import * @@ -1023,27 +1024,21 @@ class Dumper(DumperBase): for objfile in gdb.objfiles(): name = objfile.filename if name.find('/libQt5Core') >= 0: + fd, tmppath = tempfile.mkstemp() + os.close(fd) + cmd = 'maint print msymbols %s "%s"' % (tmppath, name) + symbols = gdb.execute(cmd, to_string = True) ns = '' - - # This only works when called from a valid frame. - try: - cand = 'QArrayData::shared_null' - symbol = gdb.lookup_symbol(cand)[0] - if symbol: - ns = symbol.name[:-len(cand)] - except: - symbol = None - - if symbol is None: - try: - # Some GDB 7.11.1 on Arch Linux. - cand = 'QArrayData::shared_null[0]' - val = gdb.parse_and_eval(cand) - if val.type is not None: - typeobj = val.type.unqualified() - ns = typeobj.name[:-len('QArrayData')] - except: - pass + with open(tmppath) as f: + for line in f: + if line.find('msgHandlerGrabbed ') >= 0: + # [11] b 0x7ffff683c000 _ZN4MynsL17msgHandlerGrabbedE + # section .tbss Myns::msgHandlerGrabbed qlogging.cpp + ns = re.split('_ZN(\d*)(\w*)L17msgHandlerGrabbedE ', line)[2] + if len(ns): + ns += '::' + break + os.remove(tmppath) lenns = len(ns) strns = ('%d%s' % (lenns - 2, ns[:lenns - 2])) if lenns else '' @@ -1054,10 +1049,7 @@ class Dumper(DumperBase): sym = '_ZN7QObject11customEventEP6QEvent' self.qtCustomEventFunc = self.findSymbol(sym) - if lenns: - sym = '_ZN%s7QObject11customEventEPNS_6QEventE@plt' % strns - else: - sym = '_ZN7QObject11customEventEP6QEvent@plt' + sym += '@plt' self.qtCustomEventPltFunc = self.findSymbol(sym) sym = '_ZNK7%sQObject8propertyEPKc' % strns @@ -1093,6 +1085,17 @@ class Dumper(DumperBase): cmd = 'set variable (%s)=%s' % (expr, value) gdb.execute(cmd) + def watchPoint(self, args): + ns = self.qtNamespace() + lenns = len(ns) + strns = ('%d%s' % (lenns - 2, ns[:lenns - 2])) if lenns else '' + sym = '_ZN%s12QApplication8widgetAtEii' % strns + expr = '%s(%s,%s)' % (sym, args['x'], args['y']) + res = self.parseAndEvaluate(expr) + p = 0 if res is None else res.pointer() + n = ("'%sQWidget'" % ns) if lenns else 'QWidget' + safePrint('{selected="0x%x",expr="(%s*)0x%x"}' % (p, n, p)) + def nativeDynamicTypeName(self, address, baseType): # Needed for Gdb13393 test. nativeType = self.lookupNativeType(baseType.name) @@ -1361,7 +1364,6 @@ class Dumper(DumperBase): def profile1(self, args): '''Internal profiling''' - import tempfile import cProfile tempDir = tempfile.gettempdir() + '/bbprof' cProfile.run('theDumper.fetchVariables(%s)' % args, tempDir) diff --git a/share/qtcreator/debugger/lldbbridge.py b/share/qtcreator/debugger/lldbbridge.py index 8bd31a2f68e..f10d924c2a1 100644 --- a/share/qtcreator/debugger/lldbbridge.py +++ b/share/qtcreator/debugger/lldbbridge.py @@ -1646,7 +1646,7 @@ class Dumper(DumperBase): result += ',data="%s %s"' % (insn.GetMnemonic(self.target), insn.GetOperands(self.target)) result += ',function="%s"' % functionName - rawData = insn.GetData(lldb.target).uint8s + rawData = insn.GetData(self.target).uint8s result += ',rawdata="%s"' % ' '.join(["%02x" % x for x in rawData]) if comment: result += ',comment="%s"' % self.hexencode(comment) @@ -1678,6 +1678,20 @@ class Dumper(DumperBase): lhs.SetValueFromCString(value, error) self.reportResult(self.describeError(error), args) + def watchPoint(self, args): + self.reportToken(args) + ns = self.qtNamespace() + lenns = len(ns) + funcs = self.target.FindGlobalFunctions('.*QApplication::widgetAt', 2, 1) + func = funcs[1] + addr = func.GetFunction().GetStartAddress().GetLoadAddress(self.target) + expr = '((void*(*)(int,int))0x%x)' % addr + #expr = '%sQApplication::widgetAt(%s,%s)' % (ns, args['x'], args['y']) + res = self.parseAndEvaluate(expr) + p = 0 if res is None else res.pointer() + n = ns + 'QWidget' + self.reportResult('selected="0x%x",expr="(%s*)0x%x"' % (p, n, p), args) + def createResolvePendingBreakpointsHookBreakpoint(self, args): bp = self.target.BreakpointCreateByName('qt_qmlDebugConnectorOpen') bp.SetOneShot(True) diff --git a/src/plugins/debugger/debuggerprotocol.h b/src/plugins/debugger/debuggerprotocol.h index e72ffe36063..5204a11a7aa 100644 --- a/src/plugins/debugger/debuggerprotocol.h +++ b/src/plugins/debugger/debuggerprotocol.h @@ -75,6 +75,23 @@ private: void argHelper(const char *name, const QByteArray &value); }; +class DebuggerCommandSequence +{ +public: + DebuggerCommandSequence() {} + bool isEmpty() const { return m_commands.isEmpty(); } + bool wantContinue() const { return m_continue; } + const QList &commands() const { return m_commands; } + void append(const DebuggerCommand &cmd, bool wantContinue) { + m_commands.append(cmd); + m_continue = wantContinue; + } + +public: + QList m_commands; + bool m_continue = false; +}; + // FIXME: rename into GdbMiValue class GdbMi { diff --git a/src/plugins/debugger/gdb/gdbengine.cpp b/src/plugins/debugger/gdb/gdbengine.cpp index 2e750e14ff3..88f232822bd 100644 --- a/src/plugins/debugger/gdb/gdbengine.cpp +++ b/src/plugins/debugger/gdb/gdbengine.cpp @@ -138,18 +138,6 @@ static int ¤tToken() return token; } -static QString parsePlainConsoleStream(const DebuggerResponse &response) -{ - QString out = response.consoleStreamOutput; - // FIXME: proper decoding needed - if (out.endsWith("\\n")) - out.chop(2); - while (out.endsWith('\n') || out.endsWith(' ')) - out.chop(1); - int pos = out.indexOf(" = "); - return out.mid(pos + 3); -} - static bool isMostlyHarmlessMessage(const QStringRef &msg) { return msg == "warning: GDB: Failed to set controlling terminal: " @@ -229,7 +217,6 @@ GdbEngine::GdbEngine(const DebuggerRunParameters &startParameters) m_commandsDoneCallback = 0; m_stackNeeded = false; m_terminalTrap = startParameters.useTerminal; - m_temporaryStopPending = false; m_fullStartDone = false; m_systemDumpersLoaded = false; m_rerunPending = false; @@ -905,10 +892,6 @@ void GdbEngine::runCommand(const DebuggerCommand &command) const int token = ++currentToken(); DebuggerCommand cmd = command; - if (command.flags & PythonCommand) { - cmd.arg("token", token); - cmd.function = "python theDumper." + cmd.function + "(" + cmd.argsToPython() + ")"; - } if (!stateAcceptsGdbCommands(state())) { showMessage(QString("NO GDB PROCESS RUNNING, CMD IGNORED: %1 %2") @@ -916,7 +899,7 @@ void GdbEngine::runCommand(const DebuggerCommand &command) return; } - if (command.flags & RebuildBreakpointModel) { + if (cmd.flags & RebuildBreakpointModel) { ++m_pendingBreakpointRequests; PENDING_DEBUG(" BREAKPOINT MODEL:" << cmd.function << "INCREMENTS PENDING TO" << m_pendingBreakpointRequests); @@ -925,32 +908,44 @@ void GdbEngine::runCommand(const DebuggerCommand &command) << "LEAVES PENDING BREAKPOINT AT" << m_pendingBreakpointRequests); } - if (!(command.flags & Discardable)) - ++m_nonDiscardableCount; - - if (command.flags & NeedsStop) { + if (cmd.flags & (NeedsTemporaryStop|NeedsFullStop)) { showMessage("RUNNING NEEDS-STOP COMMAND " + cmd.function); + const bool wantContinue = bool(cmd.flags & NeedsTemporaryStop); + cmd.flags &= ~(NeedsTemporaryStop|NeedsFullStop); if (state() == InferiorStopRequested) { - if (command.flags & LosesChild) { + if (cmd.flags & LosesChild) { notifyInferiorIll(); return; } showMessage("CHILD ALREADY BEING INTERRUPTED. STILL HOPING."); // Calling shutdown() here breaks all situations where two // NeedsStop commands are issued in quick succession. - } else if (!m_temporaryStopPending && state() == InferiorRunOk) { - showStatusMessage(tr("Stopping temporarily"), 1000); - m_temporaryStopPending = true; - requestInterruptInferior(); + m_onStop.append(cmd, wantContinue); + return; } + if (state() == InferiorRunOk) { + showStatusMessage(tr("Stopping temporarily"), 1000); + m_onStop.append(cmd, wantContinue); + requestInterruptInferior(); + return; + } + showMessage("UNSAFE STATE FOR QUEUED COMMAND. EXECUTING IMMEDIATELY"); + } + + if (!(cmd.flags & Discardable)) + ++m_nonDiscardableCount; + + if (cmd.flags & PythonCommand) { + cmd.arg("token", token); + cmd.function = "python theDumper." + cmd.function + "(" + cmd.argsToPython() + ")"; } QTC_ASSERT(m_gdbProc.state() == QProcess::Running, return); cmd.postTime = QTime::currentTime().msecsSinceStartOfDay(); m_commandForToken[token] = cmd; - m_flagsForToken[token] = command.flags; - if (command.flags & ConsoleCommand) + m_flagsForToken[token] = cmd.flags; + if (cmd.flags & ConsoleCommand) cmd.function = "-interpreter-exec console \"" + cmd.function + '"'; cmd.function = QString::number(token) + cmd.function; showMessage(cmd.function, LogInput); @@ -1363,11 +1358,15 @@ void GdbEngine::handleStopResponse(const GdbMi &data) return; } - if (m_temporaryStopPending) { - showMessage("INTERNAL CONTINUE AFTER TEMPORARY STOP", LogMisc); - m_temporaryStopPending = false; + if (!m_onStop.isEmpty()) { notifyInferiorStopOk(); - continueInferiorInternal(); + showMessage("HANDLING QUEUED COMMANDS AFTER TEMPORARY STOP", LogMisc); + DebuggerCommandSequence seq = m_onStop; + m_onStop = DebuggerCommandSequence(); + for (const DebuggerCommand &cmd : seq.commands()) + runCommand(cmd); + if (seq.wantContinue()) + continueInferiorInternal(); return; } @@ -1843,7 +1842,7 @@ void GdbEngine::shutdownInferior() DebuggerCommand cmd; cmd.function = QLatin1String(runParameters().closeMode == DetachAtClose ? "detach" : "kill"); cmd.callback = CB(handleInferiorShutdown); - cmd.flags = NeedsStop|LosesChild; + cmd.flags = NeedsTemporaryStop|LosesChild; runCommand(cmd); } @@ -1978,6 +1977,7 @@ bool GdbEngine::hasCapability(unsigned cap) const | WatchpointByAddressCapability | WatchpointByExpressionCapability | AddWatcherCapability + | AddWatcherWhileRunningCapability | WatchWidgetsCapability | ShowModuleSymbolsCapability | ShowModuleSectionsCapability @@ -2559,7 +2559,7 @@ void GdbEngine::handleBreakInsert1(const DebuggerResponse &response, Breakpoint const GdbMi mainbkpt = response.data["bkpt"]; bp.notifyBreakpointRemoveProceeding(); DebuggerCommand cmd("-break-delete " + mainbkpt["number"].data(), - NeedsStop | RebuildBreakpointModel); + NeedsTemporaryStop | RebuildBreakpointModel); runCommand(cmd); bp.notifyBreakpointRemoveOk(); return; @@ -2586,14 +2586,14 @@ void GdbEngine::handleBreakInsert1(const DebuggerResponse &response, Breakpoint const int lineNumber = bp.lineNumber(); DebuggerCommand cmd("trace \"" + GdbMi::escapeCString(fileName) + "\":" + QString::number(lineNumber), - NeedsStop | RebuildBreakpointModel); + NeedsTemporaryStop | RebuildBreakpointModel); runCommand(cmd); } else { // Some versions of gdb like "GNU gdb (GDB) SUSE (6.8.91.20090930-2.4)" // know how to do pending breakpoints using CLI but not MI. So try // again with MI. DebuggerCommand cmd("break " + breakpointLocation2(bp.parameters()), - NeedsStop | RebuildBreakpointModel); + NeedsTemporaryStop | RebuildBreakpointModel); cmd.callback = [this, bp](const DebuggerResponse &r) { handleBreakInsert2(r, bp); }; runCommand(cmd); } @@ -2730,7 +2730,7 @@ void GdbEngine::insertBreakpoint(Breakpoint bp) const BreakpointParameters &data = bp.parameters(); if (!data.isCppBreakpoint()) { - DebuggerCommand cmd("insertInterpreterBreakpoint", PythonCommand | NeedsStop); + DebuggerCommand cmd("insertInterpreterBreakpoint", PythonCommand | NeedsTemporaryStop); bp.addToCommand(&cmd); cmd.callback = [this, bp](const DebuggerResponse &r) { handleInsertInterpreterBreakpoint(r, bp); }; runCommand(cmd); @@ -2752,7 +2752,7 @@ void GdbEngine::insertBreakpoint(Breakpoint bp) } else if (type == BreakpointAtFork) { cmd.function = "catch fork"; cmd.callback = handleCatch; - cmd.flags = NeedsStop | RebuildBreakpointModel; + cmd.flags = NeedsTemporaryStop | RebuildBreakpointModel; runCommand(cmd); // Another one... cmd.function = "catch vfork"; @@ -2789,7 +2789,7 @@ void GdbEngine::insertBreakpoint(Breakpoint bp) cmd.function += breakpointLocation(bp.parameters()); cmd.callback = [this, bp](const DebuggerResponse &r) { handleBreakInsert1(r, bp); }; } - cmd.flags = NeedsStop | RebuildBreakpointModel; + cmd.flags = NeedsTemporaryStop | RebuildBreakpointModel; runCommand(cmd); } @@ -2838,7 +2838,7 @@ void GdbEngine::changeBreakpoint(Breakpoint bp) bp.notifyBreakpointChangeOk(); return; } - cmd.flags = NeedsStop | RebuildBreakpointModel; + cmd.flags = NeedsTemporaryStop | RebuildBreakpointModel; runCommand(cmd); } @@ -2860,7 +2860,7 @@ void GdbEngine::removeBreakpoint(Breakpoint bp) // We already have a fully inserted breakpoint. bp.notifyBreakpointRemoveProceeding(); showMessage(QString("DELETING BP %1 IN %2").arg(br.id.toString()).arg(bp.fileName())); - DebuggerCommand cmd("-break-delete " + br.id.toString(), NeedsStop | RebuildBreakpointModel); + DebuggerCommand cmd("-break-delete " + br.id.toString(), NeedsTemporaryStop | RebuildBreakpointModel); runCommand(cmd); // Pretend it succeeds without waiting for response. Feels better. @@ -2996,7 +2996,7 @@ void GdbEngine::requestModuleSymbols(const QString &modulePath) return; QString fileName = tf.fileName(); tf.close(); - DebuggerCommand cmd("maint print msymbols \"" + fileName + "\" " + modulePath, NeedsStop); + DebuggerCommand cmd("maint print msymbols \"" + fileName + "\" " + modulePath, NeedsTemporaryStop); cmd.callback = [modulePath, fileName](const DebuggerResponse &r) { handleShowModuleSymbols(r, modulePath, fileName); }; @@ -3006,7 +3006,7 @@ void GdbEngine::requestModuleSymbols(const QString &modulePath) void GdbEngine::requestModuleSections(const QString &moduleName) { // There seems to be no way to get the symbols from a single .so. - DebuggerCommand cmd("maint info section ALLOBJ", NeedsStop); + DebuggerCommand cmd("maint info section ALLOBJ", NeedsTemporaryStop); cmd.callback = [this, moduleName](const DebuggerResponse &r) { handleShowModuleSections(r, moduleName); }; @@ -3059,7 +3059,7 @@ void GdbEngine::reloadModules() void GdbEngine::reloadModulesInternal() { - runCommand({"info shared", NeedsStop, CB(handleModulesList)}); + runCommand({"info shared", NeedsTemporaryStop, CB(handleModulesList)}); } static QString nameFromPath(const QString &path) @@ -3140,7 +3140,7 @@ void GdbEngine::reloadSourceFiles() { if ((state() == InferiorRunOk || state() == InferiorStopOk) && !m_sourcesListUpdating) { m_sourcesListUpdating = true; - DebuggerCommand cmd("-file-list-exec-source-files", NeedsStop); + DebuggerCommand cmd("-file-list-exec-source-files", NeedsTemporaryStop); cmd.callback = [this](const DebuggerResponse &response) { m_sourcesListUpdating = false; if (response.resultClass == ResultDone) { @@ -3349,7 +3349,7 @@ void GdbEngine::createSnapshot() fileName = tf.fileName(); tf.close(); // This must not be quoted, it doesn't work otherwise. - DebuggerCommand cmd("gcore " + fileName, NeedsStop | ConsoleCommand); + DebuggerCommand cmd("gcore " + fileName, NeedsTemporaryStop | ConsoleCommand); cmd.callback = [this, fileName](const DebuggerResponse &r) { handleMakeSnapshot(r, fileName); }; runCommand(cmd); } else { @@ -3613,30 +3613,24 @@ void GdbEngine::assignValueInDebugger(WatchItem *item, void GdbEngine::watchPoint(const QPoint &pnt) { - QString x = QString::number(pnt.x()); - QString y = QString::number(pnt.y()); - runCommand({"print " + qtNamespace() + "QApplication::widgetAt(" + x + ',' + y + ')', - NeedsStop, CB(handleWatchPoint)}); + DebuggerCommand cmd("watchPoint", PythonCommand|NeedsFullStop); + cmd.arg("x", pnt.x()); + cmd.arg("y", pnt.y()); + cmd.callback = CB(handleWatchPoint); + runCommand(cmd); } void GdbEngine::handleWatchPoint(const DebuggerResponse &response) { if (response.resultClass == ResultDone) { - // "$5 = (void *) 0xbfa7ebfc\n" - const QString ba = parsePlainConsoleStream(response); - const int pos0x = ba.indexOf("0x"); - if (pos0x == -1) { - showStatusMessage(tr("Cannot read widget data: %1").arg(ba)); - } else { - const QString addr = ba.mid(pos0x); - if (addr.toULongLong(0, 0)) { // Non-null pointer - const QString type = "::" + qtNamespace() + "QWidget"; - const QString exp = QString("{%1}%2").arg(type).arg(addr); - watchHandler()->watchExpression(exp); - } else { - showStatusMessage(tr("Could not find a widget.")); - } - } + GdbMi res; + res.fromString(response.consoleStreamOutput); + qulonglong addr = res["selected"].toAddress(); + if (addr == 0) + showStatusMessage(tr("Could not find a widget.")); + // Add the watcher entry nevertheless, as that's the place where + // the user expects visual feedback. + watchHandler()->watchExpression(res["expr"].data(), QString(), true); } } @@ -3658,7 +3652,7 @@ public: void GdbEngine::changeMemory(MemoryAgent *agent, quint64 addr, const QByteArray &data) { Q_UNUSED(agent) - DebuggerCommand cmd("-data-write-memory 0x" + QString::number(addr, 16) + " d 1", NeedsStop); + DebuggerCommand cmd("-data-write-memory 0x" + QString::number(addr, 16) + " d 1", NeedsTemporaryStop); foreach (unsigned char c, data) cmd.function += ' ' + QString::number(uint(c)); cmd.callback = CB(handleVarAssign); @@ -3681,7 +3675,7 @@ void GdbEngine::fetchMemoryHelper(const MemoryAgentCookie &ac) DebuggerCommand cmd("-data-read-memory 0x" + QString::number(ac.base + ac.offset, 16) + " x 1 1 " + QString::number(ac.length), - NeedsStop); + NeedsTemporaryStop); cmd.callback = [this, ac](const DebuggerResponse &r) { handleFetchMemory(r, ac); }; runCommand(cmd); } @@ -4179,7 +4173,7 @@ void GdbEngine::resetInferior() foreach (QString command, commands.split('\n')) { command = command.trimmed(); if (!command.isEmpty()) - runCommand({command, ConsoleCommand | NeedsStop}); + runCommand({command, ConsoleCommand | NeedsTemporaryStop}); } } m_rerunPending = true; @@ -4325,7 +4319,7 @@ void GdbEngine::handleAdapterCrashed(const QString &msg) void GdbEngine::createFullBacktrace() { - DebuggerCommand cmd("thread apply all bt full", NeedsStop | ConsoleCommand); + DebuggerCommand cmd("thread apply all bt full", NeedsTemporaryStop | ConsoleCommand); cmd.callback = [this](const DebuggerResponse &response) { if (response.resultClass == ResultDone) { Internal::openTextEditor("Backtrace $", diff --git a/src/plugins/debugger/gdb/gdbengine.h b/src/plugins/debugger/gdb/gdbengine.h index 1ee9aecb03e..6751b780b13 100644 --- a/src/plugins/debugger/gdb/gdbengine.h +++ b/src/plugins/debugger/gdb/gdbengine.h @@ -44,6 +44,8 @@ #include #include +#include + namespace Debugger { namespace Internal { @@ -151,11 +153,13 @@ private: ////////// Gdb Command Management ////////// enum GdbCommandFlag { NoFlags = 0, // The command needs a stopped inferior. - NeedsStop = 1, + NeedsTemporaryStop = 1, // No need to wait for the reply before continuing inferior. Discardable = 2, // Needs a dummy extra command to force GDB output flushing. NeedsFlush = 4, + // The command needs a stopped inferior and will stay stopped afterward. + NeedsFullStop = 8, // Callback expects ResultRunning instead of ResultDone. RunRequest = 16, // Callback expects ResultExit instead of ResultDone. @@ -267,6 +271,7 @@ private: ////////// View & Data Stuff ////////// void selectThread(ThreadId threadId) override; void activateFrame(int index) override; + void handleAutoContinueInferior(); // // Breakpoint specific stuff @@ -378,7 +383,7 @@ protected: void changeMemory(MemoryAgent *agent, quint64 addr, const QByteArray &data) override; void handleFetchMemory(const DebuggerResponse &response, MemoryAgentCookie ac); - virtual void watchPoint(const QPoint &) override; + void watchPoint(const QPoint &) override; void handleWatchPoint(const DebuggerResponse &response); void showToolTip(); @@ -420,10 +425,11 @@ protected: QString m_lastWinException; QString m_lastMissingDebugInfo; bool m_terminalTrap; - bool m_temporaryStopPending; bool usesExecInterrupt() const; bool usesTargetAsync() const; + DebuggerCommandSequence m_onStop; + QHash m_scheduledTestResponses; QSet m_testCases; diff --git a/src/plugins/debugger/lldb/lldbengine.cpp b/src/plugins/debugger/lldb/lldbengine.cpp index e24dea5d77f..6dab6015378 100644 --- a/src/plugins/debugger/lldb/lldbengine.cpp +++ b/src/plugins/debugger/lldb/lldbengine.cpp @@ -112,25 +112,34 @@ void LldbEngine::executeDebuggerCommand(const QString &command, DebuggerLanguage runCommand(cmd); } -void LldbEngine::runCommand(const DebuggerCommand &cmd) +void LldbEngine::runCommand(const DebuggerCommand &command) { if (m_lldbProc.state() != QProcess::Running) { // This can legally happen e.g. through a reloadModule() // triggered by changes in view visibility. showMessage(QString("NO LLDB PROCESS RUNNING, CMD IGNORED: %1 %2") - .arg(cmd.function).arg(state())); + .arg(command.function).arg(state())); return; } const int tok = ++currentToken(); - DebuggerCommand command = cmd; - command.arg("token", tok); + DebuggerCommand cmd = command; + cmd.arg("token", tok); QString token = QString::number(tok); - QString function = command.function + "(" + command.argsToPython() + ")"; + QString function = cmd.function + "(" + cmd.argsToPython() + ")"; QString msg = token + function + '\n'; - if (cmd.flags == LldbEngine::Silent) + if (cmd.flags == Silent) msg.replace(QRegularExpression("\"environment\":.[^]]*."), ""); + if (cmd.flags == NeedsFullStop) { + cmd.flags &= ~NeedsFullStop; + if (state() == InferiorRunOk) { + showStatusMessage(tr("Stopping temporarily"), 1000); + m_onStop.append(cmd, false); + requestInterruptInferior(); + return; + } + } showMessage(msg, LogInput); - m_commandForToken[currentToken()] = command; + m_commandForToken[currentToken()] = cmd; m_lldbProc.write("script theDumper." + function.toUtf8() + "\n"); } @@ -139,6 +148,22 @@ void LldbEngine::debugLastCommand() runCommand(m_lastDebuggableCommand); } +void LldbEngine::watchPoint(const QPoint &pnt) +{ + DebuggerCommand cmd("watchPoint", NeedsFullStop); + cmd.arg("x", pnt.x()); + cmd.arg("y", pnt.y()); + cmd.callback = [this](const DebuggerResponse &response) { + qulonglong addr = response.data["selected"].toAddress(); + if (addr == 0) + showStatusMessage(tr("Could not find a widget.")); + // Add the watcher entry nevertheless, as that's the place where + // the user expects visual feedback. + watchHandler()->watchExpression(response.data["expr"].data(), QString(), true); + }; + runCommand(cmd); +} + void LldbEngine::shutdownInferior() { QTC_ASSERT(state() == InferiorShutdownRequested, qDebug() << state()); @@ -907,11 +932,21 @@ void LldbEngine::handleStateNotification(const GdbMi &reportedState) m_continueAtNextSpontaneousStop = true; else if (newState == "stopped") { notifyInferiorSpontaneousStop(); - if (m_continueAtNextSpontaneousStop) { - m_continueAtNextSpontaneousStop = false; - continueInferior(); + if (m_onStop.isEmpty()) { + if (m_continueAtNextSpontaneousStop) { + m_continueAtNextSpontaneousStop = false; + continueInferior(); + } else { + updateAll(); + } } else { - updateAll(); + showMessage("HANDLING QUEUED COMMANDS AFTER TEMPORARY STOP", LogMisc); + DebuggerCommandSequence seq = m_onStop; + m_onStop = DebuggerCommandSequence(); + for (const DebuggerCommand &cmd : seq.commands()) + runCommand(cmd); + if (seq.wantContinue()) + continueInferior(); } } else if (newState == "inferiorstopok") { notifyInferiorStopOk(); @@ -1098,8 +1133,8 @@ bool LldbEngine::hasCapability(unsigned cap) const | CreateFullBacktraceCapability | WatchpointByAddressCapability | WatchpointByExpressionCapability - | AddWatcherCapability | WatchWidgetsCapability + | AddWatcherCapability | ShowModuleSymbolsCapability | ShowModuleSectionsCapability | CatchCapability diff --git a/src/plugins/debugger/lldb/lldbengine.h b/src/plugins/debugger/lldb/lldbengine.h index 3cc52f5ea34..5da0e2b3e20 100644 --- a/src/plugins/debugger/lldb/lldbengine.h +++ b/src/plugins/debugger/lldb/lldbengine.h @@ -64,7 +64,9 @@ public: enum LldbCommandFlag { NoFlags = 0, // Do not echo to log. - Silent = 1 + Silent = 1, + // The command needs a stopped inferior and will stay stopped afterward. + NeedsFullStop = 8, }; signals: @@ -150,6 +152,9 @@ private: void runCommand(const DebuggerCommand &cmd) override; void debugLastCommand() override; + void watchPoint(const QPoint &) override; + void handleWatchPoint(const DebuggerResponse &response); + private: DebuggerCommand m_lastDebuggableCommand; @@ -163,6 +168,7 @@ private: QMap, int> m_disassemblerAgents; QHash m_commandForToken; + DebuggerCommandSequence m_onStop; // Console handling. void stubError(const QString &msg); diff --git a/src/plugins/debugger/watchhandler.cpp b/src/plugins/debugger/watchhandler.cpp index b5652e17829..6eac1679f16 100644 --- a/src/plugins/debugger/watchhandler.cpp +++ b/src/plugins/debugger/watchhandler.cpp @@ -66,6 +66,7 @@ #include #include #include +#include #include #include #include @@ -89,6 +90,7 @@ enum { debugModel = 0 }; #define MODEL_DEBUG(s) do { if (debugModel) qDebug() << s; } while (0) static QMap theWatcherNames; // Keep order, QTCREATORBUG-12308. +static QSet theTemporaryWatchers; // Used for 'watched widgets'. static int theWatcherCount = 0; static QHash theTypeFormats; static QHash theIndividualFormats; @@ -430,6 +432,11 @@ public: void removeWatchItem(WatchItem *item); void inputNewExpression(); + void grabWidget(); + void ungrabWidget(); + void timerEvent(QTimerEvent *event) override; + int m_grabWidgetTimerId = -1; + public: WatchHandler *m_handler; // Not owned. DebuggerEngine *m_engine; // Not owned. @@ -446,7 +453,6 @@ public: QSet m_expandedINames; QTimer m_requestUpdateTimer; - bool m_grabbing = false; QHash m_reportedTypeInfo; QHash m_reportedTypeFormats; // Type name -> Dumper Formats @@ -1067,13 +1073,6 @@ bool WatchModel::setData(const QModelIndex &idx, const QVariant &value, int role return true; } - if (ev.as(QEvent::MouseButtonPress)) { - m_grabbing = false; - ev.view()->releaseMouse(); - m_engine->watchPoint(ev.globalPos()); - return true; - } - if (ev.as(QEvent::MouseButtonDblClick)) { if (item && !item->parent()) { // if item is the invisible root item inputNewExpression(); @@ -1268,6 +1267,46 @@ static QString variableToolTip(const QString &name, const QString &type, quint64 WatchModel::tr("%1 %2").arg(type, name); } +void WatchModel::grabWidget() +{ + qApp->setOverrideCursor(Qt::CrossCursor); + m_grabWidgetTimerId = startTimer(30); + ICore::mainWindow()->grabMouse(); +} + +void WatchModel::timerEvent(QTimerEvent *event) +{ + if (event->timerId() == m_grabWidgetTimerId) { + QPoint pnt = QCursor::pos(); + Qt::KeyboardModifiers mods = QApplication::queryKeyboardModifiers(); + QString msg; + if (mods == Qt::NoModifier) { + msg = tr("Press Ctrl to select widget at (%1, %2). " + "Press any other keyboard modifier to stop selection.") + .arg(pnt.x()).arg(pnt.y()); + } else { + if (mods == Qt::CTRL) { + msg = tr("Selecting widget at (%1, %2).").arg(pnt.x()).arg(pnt.y()); + m_engine->watchPoint(pnt); + } else { + msg = tr("Selection aborted."); + } + ungrabWidget(); + } + showMessage(msg, StatusBar); + } else { + WatchModelBase::timerEvent(event); + } +} + +void WatchModel::ungrabWidget() +{ + ICore::mainWindow()->releaseMouse(); + qApp->restoreOverrideCursor(); + killTimer(m_grabWidgetTimerId); + m_grabWidgetTimerId = -1; +} + int WatchModel::memberVariableRecursion(WatchItem *item, const QString &name, quint64 start, quint64 end, @@ -1608,10 +1647,9 @@ bool WatchModel::contextMenuEvent(const ItemViewEvent &ev) canRemoveWatches && !m_handler->watchedExpressions().isEmpty(), [this] { clearWatches(); }); -// FIXME: -// addAction(menu, tr("Select Widget to Add into Expression Evaluator"), -// canHandleWatches && canInsertWatches && m_engine->hasCapability(WatchWidgetsCapability), -// [this, ev] { ev.view()->grabMouse(Qt::CrossCursor); m_grabbing = true; }); + addAction(menu, tr("Select Widget to Add into Expression Evaluator"), + state == InferiorRunOk && m_engine->hasCapability(WatchWidgetsCapability), + [this] { grabWidget(); }); menu->addSeparator(); menu->addMenu(createFormatMenu(item)); @@ -1901,6 +1939,9 @@ void WatchHandler::cleanup() { m_model->m_expandedINames.clear(); theWatcherNames.remove(QString()); + for (const QString &exp : theTemporaryWatchers) + theWatcherNames.remove(exp); + theTemporaryWatchers.clear(); saveWatchers(); m_model->reinitialize(); emit m_model->updateFinished(); @@ -2080,13 +2121,15 @@ QString WatchHandler::watcherName(const QString &exp) } // If \a name is empty, \a exp will be used as name. -void WatchHandler::watchExpression(const QString &exp, const QString &name) +void WatchHandler::watchExpression(const QString &exp, const QString &name, bool temporary) { // Do not insert the same entry more then once. if (exp.isEmpty() || theWatcherNames.contains(exp)) return; theWatcherNames[exp] = theWatcherCount++; + if (temporary) + theTemporaryWatchers.insert(exp); auto item = new WatchItem; item->exp = exp; diff --git a/src/plugins/debugger/watchhandler.h b/src/plugins/debugger/watchhandler.h index ed40d106e90..16846719db9 100644 --- a/src/plugins/debugger/watchhandler.h +++ b/src/plugins/debugger/watchhandler.h @@ -66,7 +66,9 @@ public: WatchModelBase *model() const; void cleanup(); - void watchExpression(const QString &exp, const QString &name = QString()); + void grabWidget(QWidget *viewParent); + void watchExpression(const QString &exp, const QString &name = QString(), + bool temporary = false); void updateWatchExpression(WatchItem *item, const QString &newExp); void watchVariable(const QString &exp);