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 <hjk@qt.io>
This commit is contained in:
hjk
2016-12-13 09:46:51 +01:00
parent 6c8d6c97e5
commit c2eada278e
9 changed files with 245 additions and 126 deletions

View File

@@ -33,6 +33,7 @@ import os
import os.path import os.path
import sys import sys
import struct import struct
import tempfile
import types import types
from dumper import * from dumper import *
@@ -1023,27 +1024,21 @@ class Dumper(DumperBase):
for objfile in gdb.objfiles(): for objfile in gdb.objfiles():
name = objfile.filename name = objfile.filename
if name.find('/libQt5Core') >= 0: 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 = '' ns = ''
with open(tmppath) as f:
# This only works when called from a valid frame. for line in f:
try: if line.find('msgHandlerGrabbed ') >= 0:
cand = 'QArrayData::shared_null' # [11] b 0x7ffff683c000 _ZN4MynsL17msgHandlerGrabbedE
symbol = gdb.lookup_symbol(cand)[0] # section .tbss Myns::msgHandlerGrabbed qlogging.cpp
if symbol: ns = re.split('_ZN(\d*)(\w*)L17msgHandlerGrabbedE ', line)[2]
ns = symbol.name[:-len(cand)] if len(ns):
except: ns += '::'
symbol = None break
os.remove(tmppath)
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
lenns = len(ns) lenns = len(ns)
strns = ('%d%s' % (lenns - 2, ns[:lenns - 2])) if lenns else '' strns = ('%d%s' % (lenns - 2, ns[:lenns - 2])) if lenns else ''
@@ -1054,10 +1049,7 @@ class Dumper(DumperBase):
sym = '_ZN7QObject11customEventEP6QEvent' sym = '_ZN7QObject11customEventEP6QEvent'
self.qtCustomEventFunc = self.findSymbol(sym) self.qtCustomEventFunc = self.findSymbol(sym)
if lenns: sym += '@plt'
sym = '_ZN%s7QObject11customEventEPNS_6QEventE@plt' % strns
else:
sym = '_ZN7QObject11customEventEP6QEvent@plt'
self.qtCustomEventPltFunc = self.findSymbol(sym) self.qtCustomEventPltFunc = self.findSymbol(sym)
sym = '_ZNK7%sQObject8propertyEPKc' % strns sym = '_ZNK7%sQObject8propertyEPKc' % strns
@@ -1093,6 +1085,17 @@ class Dumper(DumperBase):
cmd = 'set variable (%s)=%s' % (expr, value) cmd = 'set variable (%s)=%s' % (expr, value)
gdb.execute(cmd) 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): def nativeDynamicTypeName(self, address, baseType):
# Needed for Gdb13393 test. # Needed for Gdb13393 test.
nativeType = self.lookupNativeType(baseType.name) nativeType = self.lookupNativeType(baseType.name)
@@ -1361,7 +1364,6 @@ class Dumper(DumperBase):
def profile1(self, args): def profile1(self, args):
'''Internal profiling''' '''Internal profiling'''
import tempfile
import cProfile import cProfile
tempDir = tempfile.gettempdir() + '/bbprof' tempDir = tempfile.gettempdir() + '/bbprof'
cProfile.run('theDumper.fetchVariables(%s)' % args, tempDir) cProfile.run('theDumper.fetchVariables(%s)' % args, tempDir)

View File

@@ -1646,7 +1646,7 @@ class Dumper(DumperBase):
result += ',data="%s %s"' % (insn.GetMnemonic(self.target), result += ',data="%s %s"' % (insn.GetMnemonic(self.target),
insn.GetOperands(self.target)) insn.GetOperands(self.target))
result += ',function="%s"' % functionName 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]) result += ',rawdata="%s"' % ' '.join(["%02x" % x for x in rawData])
if comment: if comment:
result += ',comment="%s"' % self.hexencode(comment) result += ',comment="%s"' % self.hexencode(comment)
@@ -1678,6 +1678,20 @@ class Dumper(DumperBase):
lhs.SetValueFromCString(value, error) lhs.SetValueFromCString(value, error)
self.reportResult(self.describeError(error), args) 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): def createResolvePendingBreakpointsHookBreakpoint(self, args):
bp = self.target.BreakpointCreateByName('qt_qmlDebugConnectorOpen') bp = self.target.BreakpointCreateByName('qt_qmlDebugConnectorOpen')
bp.SetOneShot(True) bp.SetOneShot(True)

View File

@@ -75,6 +75,23 @@ private:
void argHelper(const char *name, const QByteArray &value); 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<DebuggerCommand> &commands() const { return m_commands; }
void append(const DebuggerCommand &cmd, bool wantContinue) {
m_commands.append(cmd);
m_continue = wantContinue;
}
public:
QList<DebuggerCommand> m_commands;
bool m_continue = false;
};
// FIXME: rename into GdbMiValue // FIXME: rename into GdbMiValue
class GdbMi class GdbMi
{ {

View File

@@ -138,18 +138,6 @@ static int &currentToken()
return token; 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) static bool isMostlyHarmlessMessage(const QStringRef &msg)
{ {
return msg == "warning: GDB: Failed to set controlling terminal: " return msg == "warning: GDB: Failed to set controlling terminal: "
@@ -229,7 +217,6 @@ GdbEngine::GdbEngine(const DebuggerRunParameters &startParameters)
m_commandsDoneCallback = 0; m_commandsDoneCallback = 0;
m_stackNeeded = false; m_stackNeeded = false;
m_terminalTrap = startParameters.useTerminal; m_terminalTrap = startParameters.useTerminal;
m_temporaryStopPending = false;
m_fullStartDone = false; m_fullStartDone = false;
m_systemDumpersLoaded = false; m_systemDumpersLoaded = false;
m_rerunPending = false; m_rerunPending = false;
@@ -905,10 +892,6 @@ void GdbEngine::runCommand(const DebuggerCommand &command)
const int token = ++currentToken(); const int token = ++currentToken();
DebuggerCommand cmd = command; DebuggerCommand cmd = command;
if (command.flags & PythonCommand) {
cmd.arg("token", token);
cmd.function = "python theDumper." + cmd.function + "(" + cmd.argsToPython() + ")";
}
if (!stateAcceptsGdbCommands(state())) { if (!stateAcceptsGdbCommands(state())) {
showMessage(QString("NO GDB PROCESS RUNNING, CMD IGNORED: %1 %2") showMessage(QString("NO GDB PROCESS RUNNING, CMD IGNORED: %1 %2")
@@ -916,7 +899,7 @@ void GdbEngine::runCommand(const DebuggerCommand &command)
return; return;
} }
if (command.flags & RebuildBreakpointModel) { if (cmd.flags & RebuildBreakpointModel) {
++m_pendingBreakpointRequests; ++m_pendingBreakpointRequests;
PENDING_DEBUG(" BREAKPOINT MODEL:" << cmd.function PENDING_DEBUG(" BREAKPOINT MODEL:" << cmd.function
<< "INCREMENTS PENDING TO" << m_pendingBreakpointRequests); << "INCREMENTS PENDING TO" << m_pendingBreakpointRequests);
@@ -925,32 +908,44 @@ void GdbEngine::runCommand(const DebuggerCommand &command)
<< "LEAVES PENDING BREAKPOINT AT" << m_pendingBreakpointRequests); << "LEAVES PENDING BREAKPOINT AT" << m_pendingBreakpointRequests);
} }
if (!(command.flags & Discardable)) if (cmd.flags & (NeedsTemporaryStop|NeedsFullStop)) {
++m_nonDiscardableCount;
if (command.flags & NeedsStop) {
showMessage("RUNNING NEEDS-STOP COMMAND " + cmd.function); showMessage("RUNNING NEEDS-STOP COMMAND " + cmd.function);
const bool wantContinue = bool(cmd.flags & NeedsTemporaryStop);
cmd.flags &= ~(NeedsTemporaryStop|NeedsFullStop);
if (state() == InferiorStopRequested) { if (state() == InferiorStopRequested) {
if (command.flags & LosesChild) { if (cmd.flags & LosesChild) {
notifyInferiorIll(); notifyInferiorIll();
return; return;
} }
showMessage("CHILD ALREADY BEING INTERRUPTED. STILL HOPING."); showMessage("CHILD ALREADY BEING INTERRUPTED. STILL HOPING.");
// Calling shutdown() here breaks all situations where two // Calling shutdown() here breaks all situations where two
// NeedsStop commands are issued in quick succession. // NeedsStop commands are issued in quick succession.
} else if (!m_temporaryStopPending && state() == InferiorRunOk) { m_onStop.append(cmd, wantContinue);
showStatusMessage(tr("Stopping temporarily"), 1000); return;
m_temporaryStopPending = true;
requestInterruptInferior();
} }
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); QTC_ASSERT(m_gdbProc.state() == QProcess::Running, return);
cmd.postTime = QTime::currentTime().msecsSinceStartOfDay(); cmd.postTime = QTime::currentTime().msecsSinceStartOfDay();
m_commandForToken[token] = cmd; m_commandForToken[token] = cmd;
m_flagsForToken[token] = command.flags; m_flagsForToken[token] = cmd.flags;
if (command.flags & ConsoleCommand) if (cmd.flags & ConsoleCommand)
cmd.function = "-interpreter-exec console \"" + cmd.function + '"'; cmd.function = "-interpreter-exec console \"" + cmd.function + '"';
cmd.function = QString::number(token) + cmd.function; cmd.function = QString::number(token) + cmd.function;
showMessage(cmd.function, LogInput); showMessage(cmd.function, LogInput);
@@ -1363,11 +1358,15 @@ void GdbEngine::handleStopResponse(const GdbMi &data)
return; return;
} }
if (m_temporaryStopPending) { if (!m_onStop.isEmpty()) {
showMessage("INTERNAL CONTINUE AFTER TEMPORARY STOP", LogMisc);
m_temporaryStopPending = false;
notifyInferiorStopOk(); 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; return;
} }
@@ -1843,7 +1842,7 @@ void GdbEngine::shutdownInferior()
DebuggerCommand cmd; DebuggerCommand cmd;
cmd.function = QLatin1String(runParameters().closeMode == DetachAtClose ? "detach" : "kill"); cmd.function = QLatin1String(runParameters().closeMode == DetachAtClose ? "detach" : "kill");
cmd.callback = CB(handleInferiorShutdown); cmd.callback = CB(handleInferiorShutdown);
cmd.flags = NeedsStop|LosesChild; cmd.flags = NeedsTemporaryStop|LosesChild;
runCommand(cmd); runCommand(cmd);
} }
@@ -1978,6 +1977,7 @@ bool GdbEngine::hasCapability(unsigned cap) const
| WatchpointByAddressCapability | WatchpointByAddressCapability
| WatchpointByExpressionCapability | WatchpointByExpressionCapability
| AddWatcherCapability | AddWatcherCapability
| AddWatcherWhileRunningCapability
| WatchWidgetsCapability | WatchWidgetsCapability
| ShowModuleSymbolsCapability | ShowModuleSymbolsCapability
| ShowModuleSectionsCapability | ShowModuleSectionsCapability
@@ -2559,7 +2559,7 @@ void GdbEngine::handleBreakInsert1(const DebuggerResponse &response, Breakpoint
const GdbMi mainbkpt = response.data["bkpt"]; const GdbMi mainbkpt = response.data["bkpt"];
bp.notifyBreakpointRemoveProceeding(); bp.notifyBreakpointRemoveProceeding();
DebuggerCommand cmd("-break-delete " + mainbkpt["number"].data(), DebuggerCommand cmd("-break-delete " + mainbkpt["number"].data(),
NeedsStop | RebuildBreakpointModel); NeedsTemporaryStop | RebuildBreakpointModel);
runCommand(cmd); runCommand(cmd);
bp.notifyBreakpointRemoveOk(); bp.notifyBreakpointRemoveOk();
return; return;
@@ -2586,14 +2586,14 @@ void GdbEngine::handleBreakInsert1(const DebuggerResponse &response, Breakpoint
const int lineNumber = bp.lineNumber(); const int lineNumber = bp.lineNumber();
DebuggerCommand cmd("trace \"" + GdbMi::escapeCString(fileName) + "\":" DebuggerCommand cmd("trace \"" + GdbMi::escapeCString(fileName) + "\":"
+ QString::number(lineNumber), + QString::number(lineNumber),
NeedsStop | RebuildBreakpointModel); NeedsTemporaryStop | RebuildBreakpointModel);
runCommand(cmd); runCommand(cmd);
} else { } else {
// Some versions of gdb like "GNU gdb (GDB) SUSE (6.8.91.20090930-2.4)" // 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 // know how to do pending breakpoints using CLI but not MI. So try
// again with MI. // again with MI.
DebuggerCommand cmd("break " + breakpointLocation2(bp.parameters()), DebuggerCommand cmd("break " + breakpointLocation2(bp.parameters()),
NeedsStop | RebuildBreakpointModel); NeedsTemporaryStop | RebuildBreakpointModel);
cmd.callback = [this, bp](const DebuggerResponse &r) { handleBreakInsert2(r, bp); }; cmd.callback = [this, bp](const DebuggerResponse &r) { handleBreakInsert2(r, bp); };
runCommand(cmd); runCommand(cmd);
} }
@@ -2730,7 +2730,7 @@ void GdbEngine::insertBreakpoint(Breakpoint bp)
const BreakpointParameters &data = bp.parameters(); const BreakpointParameters &data = bp.parameters();
if (!data.isCppBreakpoint()) { if (!data.isCppBreakpoint()) {
DebuggerCommand cmd("insertInterpreterBreakpoint", PythonCommand | NeedsStop); DebuggerCommand cmd("insertInterpreterBreakpoint", PythonCommand | NeedsTemporaryStop);
bp.addToCommand(&cmd); bp.addToCommand(&cmd);
cmd.callback = [this, bp](const DebuggerResponse &r) { handleInsertInterpreterBreakpoint(r, bp); }; cmd.callback = [this, bp](const DebuggerResponse &r) { handleInsertInterpreterBreakpoint(r, bp); };
runCommand(cmd); runCommand(cmd);
@@ -2752,7 +2752,7 @@ void GdbEngine::insertBreakpoint(Breakpoint bp)
} else if (type == BreakpointAtFork) { } else if (type == BreakpointAtFork) {
cmd.function = "catch fork"; cmd.function = "catch fork";
cmd.callback = handleCatch; cmd.callback = handleCatch;
cmd.flags = NeedsStop | RebuildBreakpointModel; cmd.flags = NeedsTemporaryStop | RebuildBreakpointModel;
runCommand(cmd); runCommand(cmd);
// Another one... // Another one...
cmd.function = "catch vfork"; cmd.function = "catch vfork";
@@ -2789,7 +2789,7 @@ void GdbEngine::insertBreakpoint(Breakpoint bp)
cmd.function += breakpointLocation(bp.parameters()); cmd.function += breakpointLocation(bp.parameters());
cmd.callback = [this, bp](const DebuggerResponse &r) { handleBreakInsert1(r, bp); }; cmd.callback = [this, bp](const DebuggerResponse &r) { handleBreakInsert1(r, bp); };
} }
cmd.flags = NeedsStop | RebuildBreakpointModel; cmd.flags = NeedsTemporaryStop | RebuildBreakpointModel;
runCommand(cmd); runCommand(cmd);
} }
@@ -2838,7 +2838,7 @@ void GdbEngine::changeBreakpoint(Breakpoint bp)
bp.notifyBreakpointChangeOk(); bp.notifyBreakpointChangeOk();
return; return;
} }
cmd.flags = NeedsStop | RebuildBreakpointModel; cmd.flags = NeedsTemporaryStop | RebuildBreakpointModel;
runCommand(cmd); runCommand(cmd);
} }
@@ -2860,7 +2860,7 @@ void GdbEngine::removeBreakpoint(Breakpoint bp)
// We already have a fully inserted breakpoint. // We already have a fully inserted breakpoint.
bp.notifyBreakpointRemoveProceeding(); bp.notifyBreakpointRemoveProceeding();
showMessage(QString("DELETING BP %1 IN %2").arg(br.id.toString()).arg(bp.fileName())); 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); runCommand(cmd);
// Pretend it succeeds without waiting for response. Feels better. // Pretend it succeeds without waiting for response. Feels better.
@@ -2996,7 +2996,7 @@ void GdbEngine::requestModuleSymbols(const QString &modulePath)
return; return;
QString fileName = tf.fileName(); QString fileName = tf.fileName();
tf.close(); 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) { cmd.callback = [modulePath, fileName](const DebuggerResponse &r) {
handleShowModuleSymbols(r, modulePath, fileName); handleShowModuleSymbols(r, modulePath, fileName);
}; };
@@ -3006,7 +3006,7 @@ void GdbEngine::requestModuleSymbols(const QString &modulePath)
void GdbEngine::requestModuleSections(const QString &moduleName) void GdbEngine::requestModuleSections(const QString &moduleName)
{ {
// There seems to be no way to get the symbols from a single .so. // 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) { cmd.callback = [this, moduleName](const DebuggerResponse &r) {
handleShowModuleSections(r, moduleName); handleShowModuleSections(r, moduleName);
}; };
@@ -3059,7 +3059,7 @@ void GdbEngine::reloadModules()
void GdbEngine::reloadModulesInternal() void GdbEngine::reloadModulesInternal()
{ {
runCommand({"info shared", NeedsStop, CB(handleModulesList)}); runCommand({"info shared", NeedsTemporaryStop, CB(handleModulesList)});
} }
static QString nameFromPath(const QString &path) static QString nameFromPath(const QString &path)
@@ -3140,7 +3140,7 @@ void GdbEngine::reloadSourceFiles()
{ {
if ((state() == InferiorRunOk || state() == InferiorStopOk) && !m_sourcesListUpdating) { if ((state() == InferiorRunOk || state() == InferiorStopOk) && !m_sourcesListUpdating) {
m_sourcesListUpdating = true; 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) { cmd.callback = [this](const DebuggerResponse &response) {
m_sourcesListUpdating = false; m_sourcesListUpdating = false;
if (response.resultClass == ResultDone) { if (response.resultClass == ResultDone) {
@@ -3349,7 +3349,7 @@ void GdbEngine::createSnapshot()
fileName = tf.fileName(); fileName = tf.fileName();
tf.close(); tf.close();
// This must not be quoted, it doesn't work otherwise. // 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); }; cmd.callback = [this, fileName](const DebuggerResponse &r) { handleMakeSnapshot(r, fileName); };
runCommand(cmd); runCommand(cmd);
} else { } else {
@@ -3613,30 +3613,24 @@ void GdbEngine::assignValueInDebugger(WatchItem *item,
void GdbEngine::watchPoint(const QPoint &pnt) void GdbEngine::watchPoint(const QPoint &pnt)
{ {
QString x = QString::number(pnt.x()); DebuggerCommand cmd("watchPoint", PythonCommand|NeedsFullStop);
QString y = QString::number(pnt.y()); cmd.arg("x", pnt.x());
runCommand({"print " + qtNamespace() + "QApplication::widgetAt(" + x + ',' + y + ')', cmd.arg("y", pnt.y());
NeedsStop, CB(handleWatchPoint)}); cmd.callback = CB(handleWatchPoint);
runCommand(cmd);
} }
void GdbEngine::handleWatchPoint(const DebuggerResponse &response) void GdbEngine::handleWatchPoint(const DebuggerResponse &response)
{ {
if (response.resultClass == ResultDone) { if (response.resultClass == ResultDone) {
// "$5 = (void *) 0xbfa7ebfc\n" GdbMi res;
const QString ba = parsePlainConsoleStream(response); res.fromString(response.consoleStreamOutput);
const int pos0x = ba.indexOf("0x"); qulonglong addr = res["selected"].toAddress();
if (pos0x == -1) { if (addr == 0)
showStatusMessage(tr("Cannot read widget data: %1").arg(ba)); showStatusMessage(tr("Could not find a widget."));
} else { // Add the watcher entry nevertheless, as that's the place where
const QString addr = ba.mid(pos0x); // the user expects visual feedback.
if (addr.toULongLong(0, 0)) { // Non-null pointer watchHandler()->watchExpression(res["expr"].data(), QString(), true);
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."));
}
}
} }
} }
@@ -3658,7 +3652,7 @@ public:
void GdbEngine::changeMemory(MemoryAgent *agent, quint64 addr, const QByteArray &data) void GdbEngine::changeMemory(MemoryAgent *agent, quint64 addr, const QByteArray &data)
{ {
Q_UNUSED(agent) 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) foreach (unsigned char c, data)
cmd.function += ' ' + QString::number(uint(c)); cmd.function += ' ' + QString::number(uint(c));
cmd.callback = CB(handleVarAssign); cmd.callback = CB(handleVarAssign);
@@ -3681,7 +3675,7 @@ void GdbEngine::fetchMemoryHelper(const MemoryAgentCookie &ac)
DebuggerCommand cmd("-data-read-memory 0x" DebuggerCommand cmd("-data-read-memory 0x"
+ QString::number(ac.base + ac.offset, 16) + " x 1 1 " + QString::number(ac.base + ac.offset, 16) + " x 1 1 "
+ QString::number(ac.length), + QString::number(ac.length),
NeedsStop); NeedsTemporaryStop);
cmd.callback = [this, ac](const DebuggerResponse &r) { handleFetchMemory(r, ac); }; cmd.callback = [this, ac](const DebuggerResponse &r) { handleFetchMemory(r, ac); };
runCommand(cmd); runCommand(cmd);
} }
@@ -4179,7 +4173,7 @@ void GdbEngine::resetInferior()
foreach (QString command, commands.split('\n')) { foreach (QString command, commands.split('\n')) {
command = command.trimmed(); command = command.trimmed();
if (!command.isEmpty()) if (!command.isEmpty())
runCommand({command, ConsoleCommand | NeedsStop}); runCommand({command, ConsoleCommand | NeedsTemporaryStop});
} }
} }
m_rerunPending = true; m_rerunPending = true;
@@ -4325,7 +4319,7 @@ void GdbEngine::handleAdapterCrashed(const QString &msg)
void GdbEngine::createFullBacktrace() 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) { cmd.callback = [this](const DebuggerResponse &response) {
if (response.resultClass == ResultDone) { if (response.resultClass == ResultDone) {
Internal::openTextEditor("Backtrace $", Internal::openTextEditor("Backtrace $",

View File

@@ -44,6 +44,8 @@
#include <QTime> #include <QTime>
#include <QTimer> #include <QTimer>
#include <functional>
namespace Debugger { namespace Debugger {
namespace Internal { namespace Internal {
@@ -151,11 +153,13 @@ private: ////////// Gdb Command Management //////////
enum GdbCommandFlag { enum GdbCommandFlag {
NoFlags = 0, NoFlags = 0,
// The command needs a stopped inferior. // The command needs a stopped inferior.
NeedsStop = 1, NeedsTemporaryStop = 1,
// No need to wait for the reply before continuing inferior. // No need to wait for the reply before continuing inferior.
Discardable = 2, Discardable = 2,
// Needs a dummy extra command to force GDB output flushing. // Needs a dummy extra command to force GDB output flushing.
NeedsFlush = 4, NeedsFlush = 4,
// The command needs a stopped inferior and will stay stopped afterward.
NeedsFullStop = 8,
// Callback expects ResultRunning instead of ResultDone. // Callback expects ResultRunning instead of ResultDone.
RunRequest = 16, RunRequest = 16,
// Callback expects ResultExit instead of ResultDone. // Callback expects ResultExit instead of ResultDone.
@@ -267,6 +271,7 @@ private: ////////// View & Data Stuff //////////
void selectThread(ThreadId threadId) override; void selectThread(ThreadId threadId) override;
void activateFrame(int index) override; void activateFrame(int index) override;
void handleAutoContinueInferior();
// //
// Breakpoint specific stuff // Breakpoint specific stuff
@@ -378,7 +383,7 @@ protected:
void changeMemory(MemoryAgent *agent, quint64 addr, const QByteArray &data) override; void changeMemory(MemoryAgent *agent, quint64 addr, const QByteArray &data) override;
void handleFetchMemory(const DebuggerResponse &response, MemoryAgentCookie ac); void handleFetchMemory(const DebuggerResponse &response, MemoryAgentCookie ac);
virtual void watchPoint(const QPoint &) override; void watchPoint(const QPoint &) override;
void handleWatchPoint(const DebuggerResponse &response); void handleWatchPoint(const DebuggerResponse &response);
void showToolTip(); void showToolTip();
@@ -420,10 +425,11 @@ protected:
QString m_lastWinException; QString m_lastWinException;
QString m_lastMissingDebugInfo; QString m_lastMissingDebugInfo;
bool m_terminalTrap; bool m_terminalTrap;
bool m_temporaryStopPending;
bool usesExecInterrupt() const; bool usesExecInterrupt() const;
bool usesTargetAsync() const; bool usesTargetAsync() const;
DebuggerCommandSequence m_onStop;
QHash<int, QString> m_scheduledTestResponses; QHash<int, QString> m_scheduledTestResponses;
QSet<int> m_testCases; QSet<int> m_testCases;

View File

@@ -112,25 +112,34 @@ void LldbEngine::executeDebuggerCommand(const QString &command, DebuggerLanguage
runCommand(cmd); runCommand(cmd);
} }
void LldbEngine::runCommand(const DebuggerCommand &cmd) void LldbEngine::runCommand(const DebuggerCommand &command)
{ {
if (m_lldbProc.state() != QProcess::Running) { if (m_lldbProc.state() != QProcess::Running) {
// This can legally happen e.g. through a reloadModule() // This can legally happen e.g. through a reloadModule()
// triggered by changes in view visibility. // triggered by changes in view visibility.
showMessage(QString("NO LLDB PROCESS RUNNING, CMD IGNORED: %1 %2") showMessage(QString("NO LLDB PROCESS RUNNING, CMD IGNORED: %1 %2")
.arg(cmd.function).arg(state())); .arg(command.function).arg(state()));
return; return;
} }
const int tok = ++currentToken(); const int tok = ++currentToken();
DebuggerCommand command = cmd; DebuggerCommand cmd = command;
command.arg("token", tok); cmd.arg("token", tok);
QString token = QString::number(tok); QString token = QString::number(tok);
QString function = command.function + "(" + command.argsToPython() + ")"; QString function = cmd.function + "(" + cmd.argsToPython() + ")";
QString msg = token + function + '\n'; QString msg = token + function + '\n';
if (cmd.flags == LldbEngine::Silent) if (cmd.flags == Silent)
msg.replace(QRegularExpression("\"environment\":.[^]]*."), "<environment suppressed>"); msg.replace(QRegularExpression("\"environment\":.[^]]*."), "<environment suppressed>");
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); showMessage(msg, LogInput);
m_commandForToken[currentToken()] = command; m_commandForToken[currentToken()] = cmd;
m_lldbProc.write("script theDumper." + function.toUtf8() + "\n"); m_lldbProc.write("script theDumper." + function.toUtf8() + "\n");
} }
@@ -139,6 +148,22 @@ void LldbEngine::debugLastCommand()
runCommand(m_lastDebuggableCommand); 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() void LldbEngine::shutdownInferior()
{ {
QTC_ASSERT(state() == InferiorShutdownRequested, qDebug() << state()); QTC_ASSERT(state() == InferiorShutdownRequested, qDebug() << state());
@@ -907,11 +932,21 @@ void LldbEngine::handleStateNotification(const GdbMi &reportedState)
m_continueAtNextSpontaneousStop = true; m_continueAtNextSpontaneousStop = true;
else if (newState == "stopped") { else if (newState == "stopped") {
notifyInferiorSpontaneousStop(); notifyInferiorSpontaneousStop();
if (m_continueAtNextSpontaneousStop) { if (m_onStop.isEmpty()) {
m_continueAtNextSpontaneousStop = false; if (m_continueAtNextSpontaneousStop) {
continueInferior(); m_continueAtNextSpontaneousStop = false;
continueInferior();
} else {
updateAll();
}
} else { } 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") { } else if (newState == "inferiorstopok") {
notifyInferiorStopOk(); notifyInferiorStopOk();
@@ -1098,8 +1133,8 @@ bool LldbEngine::hasCapability(unsigned cap) const
| CreateFullBacktraceCapability | CreateFullBacktraceCapability
| WatchpointByAddressCapability | WatchpointByAddressCapability
| WatchpointByExpressionCapability | WatchpointByExpressionCapability
| AddWatcherCapability
| WatchWidgetsCapability | WatchWidgetsCapability
| AddWatcherCapability
| ShowModuleSymbolsCapability | ShowModuleSymbolsCapability
| ShowModuleSectionsCapability | ShowModuleSectionsCapability
| CatchCapability | CatchCapability

View File

@@ -64,7 +64,9 @@ public:
enum LldbCommandFlag { enum LldbCommandFlag {
NoFlags = 0, NoFlags = 0,
// Do not echo to log. // Do not echo to log.
Silent = 1 Silent = 1,
// The command needs a stopped inferior and will stay stopped afterward.
NeedsFullStop = 8,
}; };
signals: signals:
@@ -150,6 +152,9 @@ private:
void runCommand(const DebuggerCommand &cmd) override; void runCommand(const DebuggerCommand &cmd) override;
void debugLastCommand() override; void debugLastCommand() override;
void watchPoint(const QPoint &) override;
void handleWatchPoint(const DebuggerResponse &response);
private: private:
DebuggerCommand m_lastDebuggableCommand; DebuggerCommand m_lastDebuggableCommand;
@@ -163,6 +168,7 @@ private:
QMap<QPointer<DisassemblerAgent>, int> m_disassemblerAgents; QMap<QPointer<DisassemblerAgent>, int> m_disassemblerAgents;
QHash<int, DebuggerCommand> m_commandForToken; QHash<int, DebuggerCommand> m_commandForToken;
DebuggerCommandSequence m_onStop;
// Console handling. // Console handling.
void stubError(const QString &msg); void stubError(const QString &msg);

View File

@@ -66,6 +66,7 @@
#include <QMimeData> #include <QMimeData>
#include <QPainter> #include <QPainter>
#include <QProcess> #include <QProcess>
#include <QSet>
#include <QTabWidget> #include <QTabWidget>
#include <QTextEdit> #include <QTextEdit>
#include <QTableWidget> #include <QTableWidget>
@@ -89,6 +90,7 @@ enum { debugModel = 0 };
#define MODEL_DEBUG(s) do { if (debugModel) qDebug() << s; } while (0) #define MODEL_DEBUG(s) do { if (debugModel) qDebug() << s; } while (0)
static QMap<QString, int> theWatcherNames; // Keep order, QTCREATORBUG-12308. static QMap<QString, int> theWatcherNames; // Keep order, QTCREATORBUG-12308.
static QSet<QString> theTemporaryWatchers; // Used for 'watched widgets'.
static int theWatcherCount = 0; static int theWatcherCount = 0;
static QHash<QString, int> theTypeFormats; static QHash<QString, int> theTypeFormats;
static QHash<QString, int> theIndividualFormats; static QHash<QString, int> theIndividualFormats;
@@ -430,6 +432,11 @@ public:
void removeWatchItem(WatchItem *item); void removeWatchItem(WatchItem *item);
void inputNewExpression(); void inputNewExpression();
void grabWidget();
void ungrabWidget();
void timerEvent(QTimerEvent *event) override;
int m_grabWidgetTimerId = -1;
public: public:
WatchHandler *m_handler; // Not owned. WatchHandler *m_handler; // Not owned.
DebuggerEngine *m_engine; // Not owned. DebuggerEngine *m_engine; // Not owned.
@@ -446,7 +453,6 @@ public:
QSet<QString> m_expandedINames; QSet<QString> m_expandedINames;
QTimer m_requestUpdateTimer; QTimer m_requestUpdateTimer;
bool m_grabbing = false;
QHash<QString, TypeInfo> m_reportedTypeInfo; QHash<QString, TypeInfo> m_reportedTypeInfo;
QHash<QString, DisplayFormats> m_reportedTypeFormats; // Type name -> Dumper Formats QHash<QString, DisplayFormats> m_reportedTypeFormats; // Type name -> Dumper Formats
@@ -1067,13 +1073,6 @@ bool WatchModel::setData(const QModelIndex &idx, const QVariant &value, int role
return true; return true;
} }
if (ev.as<QMouseEvent>(QEvent::MouseButtonPress)) {
m_grabbing = false;
ev.view()->releaseMouse();
m_engine->watchPoint(ev.globalPos());
return true;
}
if (ev.as<QMouseEvent>(QEvent::MouseButtonDblClick)) { if (ev.as<QMouseEvent>(QEvent::MouseButtonDblClick)) {
if (item && !item->parent()) { // if item is the invisible root item if (item && !item->parent()) { // if item is the invisible root item
inputNewExpression(); inputNewExpression();
@@ -1268,6 +1267,46 @@ static QString variableToolTip(const QString &name, const QString &type, quint64
WatchModel::tr("<i>%1</i> %2").arg(type, name); WatchModel::tr("<i>%1</i> %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, int WatchModel::memberVariableRecursion(WatchItem *item,
const QString &name, const QString &name,
quint64 start, quint64 end, quint64 start, quint64 end,
@@ -1608,10 +1647,9 @@ bool WatchModel::contextMenuEvent(const ItemViewEvent &ev)
canRemoveWatches && !m_handler->watchedExpressions().isEmpty(), canRemoveWatches && !m_handler->watchedExpressions().isEmpty(),
[this] { clearWatches(); }); [this] { clearWatches(); });
// FIXME: addAction(menu, tr("Select Widget to Add into Expression Evaluator"),
// addAction(menu, tr("Select Widget to Add into Expression Evaluator"), state == InferiorRunOk && m_engine->hasCapability(WatchWidgetsCapability),
// canHandleWatches && canInsertWatches && m_engine->hasCapability(WatchWidgetsCapability), [this] { grabWidget(); });
// [this, ev] { ev.view()->grabMouse(Qt::CrossCursor); m_grabbing = true; });
menu->addSeparator(); menu->addSeparator();
menu->addMenu(createFormatMenu(item)); menu->addMenu(createFormatMenu(item));
@@ -1901,6 +1939,9 @@ void WatchHandler::cleanup()
{ {
m_model->m_expandedINames.clear(); m_model->m_expandedINames.clear();
theWatcherNames.remove(QString()); theWatcherNames.remove(QString());
for (const QString &exp : theTemporaryWatchers)
theWatcherNames.remove(exp);
theTemporaryWatchers.clear();
saveWatchers(); saveWatchers();
m_model->reinitialize(); m_model->reinitialize();
emit m_model->updateFinished(); 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. // 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. // Do not insert the same entry more then once.
if (exp.isEmpty() || theWatcherNames.contains(exp)) if (exp.isEmpty() || theWatcherNames.contains(exp))
return; return;
theWatcherNames[exp] = theWatcherCount++; theWatcherNames[exp] = theWatcherCount++;
if (temporary)
theTemporaryWatchers.insert(exp);
auto item = new WatchItem; auto item = new WatchItem;
item->exp = exp; item->exp = exp;

View File

@@ -66,7 +66,9 @@ public:
WatchModelBase *model() const; WatchModelBase *model() const;
void cleanup(); 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 updateWatchExpression(WatchItem *item, const QString &newExp);
void watchVariable(const QString &exp); void watchVariable(const QString &exp);