forked from qt-creator/qt-creator
Debugger: add callback for interrupt inferior
This allows to run any command without knowing the current state of the debugger engine. Replacing the old special stop mode for any command that may be triggered while the engine is running, with a check in runCommand. If the engine is running while a command is triggered a callback is added to the interrupt that will execute the command as soon as the engine is accessible again. Change-Id: I8bb1914b92da2b03e76c2c2ec8293d13041c72fd Reviewed-by: Christian Stenger <christian.stenger@qt.io>
This commit is contained in:
@@ -151,35 +151,6 @@ namespace Internal {
|
||||
|
||||
static const char localsPrefixC[] = "local.";
|
||||
|
||||
struct MemoryViewCookie
|
||||
{
|
||||
explicit MemoryViewCookie(MemoryAgent *a = nullptr, quint64 addr = 0, quint64 l = 0)
|
||||
: agent(a), address(addr), length(l)
|
||||
{}
|
||||
|
||||
MemoryAgent *agent;
|
||||
quint64 address;
|
||||
quint64 length;
|
||||
};
|
||||
|
||||
struct MemoryChangeCookie
|
||||
{
|
||||
explicit MemoryChangeCookie(quint64 addr = 0, const QByteArray &d = QByteArray()) :
|
||||
address(addr), data(d) {}
|
||||
|
||||
quint64 address;
|
||||
QByteArray data;
|
||||
};
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace Debugger
|
||||
|
||||
Q_DECLARE_METATYPE(Debugger::Internal::MemoryViewCookie)
|
||||
Q_DECLARE_METATYPE(Debugger::Internal::MemoryChangeCookie)
|
||||
|
||||
namespace Debugger {
|
||||
namespace Internal {
|
||||
|
||||
// Base data structure for command queue entries with callback
|
||||
class CdbCommand
|
||||
{
|
||||
@@ -251,11 +222,10 @@ void CdbEngine::init()
|
||||
{
|
||||
m_effectiveStartMode = NoStartMode;
|
||||
m_accessible = false;
|
||||
m_specialStopMode = NoSpecialStop;
|
||||
m_stopMode = NoStopRequested;
|
||||
m_nextCommandToken = 0;
|
||||
m_currentBuiltinResponseToken = -1;
|
||||
m_operateByInstructionPending = action(OperateByInstruction)->isChecked();
|
||||
m_operateByInstruction = true; // Default CDB setting
|
||||
m_operateByInstruction = true;
|
||||
m_hasDebuggee = false;
|
||||
m_sourceStepInto = false;
|
||||
m_watchPointX = m_watchPointY = 0;
|
||||
@@ -270,7 +240,7 @@ void CdbEngine::init()
|
||||
m_pendingBreakpointMap.clear();
|
||||
m_insertSubBreakpointMap.clear();
|
||||
m_pendingSubBreakpointMap.clear();
|
||||
m_customSpecialStopData.clear();
|
||||
m_interrupCallbacks.clear();
|
||||
m_symbolAddressCache.clear();
|
||||
m_coreStopReason.reset();
|
||||
|
||||
@@ -303,19 +273,8 @@ CdbEngine::~CdbEngine()
|
||||
|
||||
void CdbEngine::operateByInstructionTriggered(bool operateByInstruction)
|
||||
{
|
||||
// To be set next time session becomes accessible
|
||||
m_operateByInstructionPending = operateByInstruction;
|
||||
if (state() == InferiorStopOk)
|
||||
syncOperateByInstruction(operateByInstruction);
|
||||
}
|
||||
|
||||
void CdbEngine::syncOperateByInstruction(bool operateByInstruction)
|
||||
{
|
||||
if (debug)
|
||||
qDebug("syncOperateByInstruction current: %d new %d", m_operateByInstruction, operateByInstruction);
|
||||
if (m_operateByInstruction == operateByInstruction)
|
||||
return;
|
||||
QTC_ASSERT(m_accessible, return);
|
||||
m_operateByInstruction = operateByInstruction;
|
||||
runCommand({QLatin1String(m_operateByInstruction ? "l-t" : "l+t"), NoFlags});
|
||||
runCommand({QLatin1String(m_operateByInstruction ? "l-s" : "l+s"), NoFlags});
|
||||
@@ -566,6 +525,7 @@ void CdbEngine::handleInitialSessionIdle()
|
||||
const DebuggerRunParameters &rp = runParameters();
|
||||
if (!rp.commandsAfterConnect.isEmpty())
|
||||
runCommand({rp.commandsAfterConnect, NoFlags});
|
||||
operateByInstructionTriggered(action(OperateByInstruction)->isChecked());
|
||||
// QmlCppEngine expects the QML engine to be connected before any breakpoints are hit
|
||||
// (attemptBreakpointSynchronization() will be directly called then)
|
||||
attemptBreakpointSynchronization();
|
||||
@@ -864,14 +824,7 @@ void CdbEngine::interruptInferior()
|
||||
notifyInferiorRunOk();
|
||||
return;
|
||||
}
|
||||
doInterruptInferior(NoSpecialStop);
|
||||
}
|
||||
|
||||
void CdbEngine::doInterruptInferiorCustomSpecialStop(const QVariant &v)
|
||||
{
|
||||
if (m_specialStopMode == NoSpecialStop)
|
||||
doInterruptInferior(CustomSpecialStop);
|
||||
m_customSpecialStopData.push_back(v);
|
||||
doInterruptInferior();
|
||||
}
|
||||
|
||||
void CdbEngine::handleDoInterruptInferior(const QString &errorMessage)
|
||||
@@ -886,11 +839,20 @@ void CdbEngine::handleDoInterruptInferior(const QString &errorMessage)
|
||||
m_signalOperation.clear();
|
||||
}
|
||||
|
||||
void CdbEngine::doInterruptInferior(SpecialStopMode sm)
|
||||
void CdbEngine::doInterruptInferior(const InterruptCallback &callback)
|
||||
{
|
||||
showMessage(QString("Interrupting process %1...").arg(inferiorPid()), LogMisc);
|
||||
if (callback) {
|
||||
m_interrupCallbacks.push_back(callback);
|
||||
if (!m_initialSessionIdleHandled)
|
||||
return;
|
||||
if (m_stopMode == NoStopRequested)
|
||||
m_stopMode = Callback;
|
||||
} else {
|
||||
m_stopMode = Interrupt;
|
||||
}
|
||||
|
||||
QTC_ASSERT(!m_signalOperation, notifyInferiorStopFailed(); return;);
|
||||
showMessage(QString("Interrupting process %1...").arg(inferiorPid()), LogMisc);
|
||||
QTC_ASSERT(!m_signalOperation, notifyInferiorStopFailed(); return);
|
||||
if (DebuggerRunTool *rt = runTool()) {
|
||||
IDevice::ConstPtr device = rt->device();
|
||||
if (!device)
|
||||
@@ -898,7 +860,6 @@ void CdbEngine::doInterruptInferior(SpecialStopMode sm)
|
||||
if (device)
|
||||
m_signalOperation = device->signalOperation();
|
||||
}
|
||||
m_specialStopMode = sm;
|
||||
QTC_ASSERT(m_signalOperation, notifyInferiorStopFailed(); return;);
|
||||
connect(m_signalOperation.data(), &DeviceProcessSignalOperation::finished,
|
||||
this, &CdbEngine::handleDoInterruptInferior);
|
||||
@@ -1064,9 +1025,12 @@ void CdbEngine::runCommand(const DebuggerCommand &dbgCmd)
|
||||
{
|
||||
QString cmd = dbgCmd.function + dbgCmd.argsToString();
|
||||
if (!m_accessible) {
|
||||
const QString msg = QString("Attempt to issue command \"%1\" to non-accessible session (%2)")
|
||||
doInterruptInferior([this, dbgCmd](){
|
||||
runCommand(dbgCmd);
|
||||
});
|
||||
const QString msg = QString("Attempt to issue command \"%1\" to non-accessible session (%2)... interrupting")
|
||||
.arg(cmd, stateName(state()));
|
||||
showMessage(msg, LogError);
|
||||
showMessage(msg, LogMisc);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1492,34 +1456,26 @@ void CdbEngine::handleResolveSymbolHelper(const QList<quint64> &addresses, Disas
|
||||
}
|
||||
}
|
||||
|
||||
void CdbEngine::fetchMemory(MemoryAgent *agent, quint64 addr, quint64 length)
|
||||
void CdbEngine::fetchMemory(MemoryAgent *agent, quint64 address, quint64 length)
|
||||
{
|
||||
if (debug)
|
||||
qDebug("CdbEngine::fetchMemory %llu bytes from 0x%llx", length, addr);
|
||||
const MemoryViewCookie cookie(agent, addr, length);
|
||||
if (m_accessible)
|
||||
postFetchMemory(cookie);
|
||||
else
|
||||
doInterruptInferiorCustomSpecialStop(qVariantFromValue(cookie));
|
||||
}
|
||||
|
||||
void CdbEngine::postFetchMemory(const MemoryViewCookie &cookie)
|
||||
{
|
||||
qDebug("CdbEngine::fetchMemory %llu bytes from 0x%llx", length, address);
|
||||
DebuggerCommand cmd("memory", ExtensionCommand);
|
||||
QString args;
|
||||
StringInputStream str(args);
|
||||
str << cookie.address << ' ' << cookie.length;
|
||||
str << address << ' ' << length;
|
||||
cmd.args = args;
|
||||
cmd.callback = [this, cookie](const DebuggerResponse &response) {
|
||||
if (!cookie.agent)
|
||||
cmd.callback = [this, agent = QPointer<MemoryAgent>(agent), address, length]
|
||||
(const DebuggerResponse &response) {
|
||||
if (!agent)
|
||||
return;
|
||||
if (response.resultClass == ResultDone) {
|
||||
const QByteArray data = QByteArray::fromHex(response.data.data().toUtf8());
|
||||
if (unsigned(data.size()) == cookie.length)
|
||||
cookie.agent->addData(cookie.address, data);
|
||||
if (unsigned(data.size()) == length)
|
||||
agent->addData(address, data);
|
||||
} else {
|
||||
showMessage(response.data["msg"].data(), LogWarning);
|
||||
cookie.agent->addData(cookie.address, QByteArray(int(cookie.length), char()));
|
||||
agent->addData(address, QByteArray(int(length), char()));
|
||||
}
|
||||
};
|
||||
runCommand(cmd);
|
||||
@@ -1528,13 +1484,8 @@ void CdbEngine::postFetchMemory(const MemoryViewCookie &cookie)
|
||||
void CdbEngine::changeMemory(MemoryAgent *, quint64 addr, const QByteArray &data)
|
||||
{
|
||||
QTC_ASSERT(!data.isEmpty(), return);
|
||||
if (!m_accessible) {
|
||||
const MemoryChangeCookie cookie(addr, data);
|
||||
doInterruptInferiorCustomSpecialStop(qVariantFromValue(cookie));
|
||||
} else {
|
||||
runCommand({cdbWriteMemoryCommand(addr, data), NoFlags});
|
||||
}
|
||||
}
|
||||
|
||||
void CdbEngine::reloadModules()
|
||||
{
|
||||
@@ -1820,6 +1771,8 @@ unsigned CdbEngine::examineStopReason(const GdbMi &stopReason,
|
||||
return rc;
|
||||
}
|
||||
if (reason == "exception") {
|
||||
if (m_stopMode == Callback)
|
||||
rc |= StopIgnoreContinue;
|
||||
WinException exception;
|
||||
exception.fromGdbMI(stopReason);
|
||||
QString description = exception.toString();
|
||||
@@ -1862,6 +1815,7 @@ void CdbEngine::processStop(const GdbMi &stopReason, bool conditionalBreakPointT
|
||||
ThreadId forcedThreadId;
|
||||
const unsigned stopFlags = examineStopReason(stopReason, &message, &exceptionBoxMessage,
|
||||
conditionalBreakPointTriggered);
|
||||
m_stopMode = NoStopRequested;
|
||||
// Do the non-blocking log reporting
|
||||
if (stopFlags & StopReportLog)
|
||||
showMessage(message, LogMisc);
|
||||
@@ -2049,9 +2003,9 @@ void CdbEngine::handleSessionAccessible(unsigned long cdbExState)
|
||||
return;
|
||||
|
||||
if (debug)
|
||||
qDebug("CdbEngine::handleSessionAccessible %dms in state '%s'/'%s', special mode %d",
|
||||
qDebug("CdbEngine::handleSessionAccessible %dms in state '%s'/'%s'",
|
||||
elapsedLogTime(), cdbStatusName(cdbExState),
|
||||
qPrintable(stateName(state())), m_specialStopMode);
|
||||
qPrintable(stateName(state())));
|
||||
|
||||
switch (s) {
|
||||
case EngineShutdownRequested:
|
||||
@@ -2074,9 +2028,9 @@ void CdbEngine::handleSessionInaccessible(unsigned long cdbExState)
|
||||
return;
|
||||
|
||||
if (debug)
|
||||
qDebug("CdbEngine::handleSessionInaccessible %dms in state '%s', '%s', special mode %d",
|
||||
qDebug("CdbEngine::handleSessionInaccessible %dms in state '%s', '%s'",
|
||||
elapsedLogTime(), cdbStatusName(cdbExState),
|
||||
qPrintable(stateName(state())), m_specialStopMode);
|
||||
qPrintable(stateName(state())));
|
||||
|
||||
switch (state()) {
|
||||
case EngineSetupRequested:
|
||||
@@ -2113,37 +2067,13 @@ void CdbEngine::handleSessionIdle(const QString &message)
|
||||
return;
|
||||
|
||||
if (debug)
|
||||
qDebug("CdbEngine::handleSessionIdle %dms '%s' in state '%s', special mode %d",
|
||||
qDebug("CdbEngine::handleSessionIdle %dms '%s' in state '%s'",
|
||||
elapsedLogTime(), qPrintable(message),
|
||||
qPrintable(stateName(state())), m_specialStopMode);
|
||||
qPrintable(stateName(state())));
|
||||
|
||||
// Switch source level debugging
|
||||
syncOperateByInstruction(m_operateByInstructionPending);
|
||||
|
||||
// Engine-special stop reasons: Breakpoints and setup
|
||||
const SpecialStopMode specialStopMode = m_specialStopMode;
|
||||
|
||||
m_specialStopMode = NoSpecialStop;
|
||||
|
||||
switch (specialStopMode) {
|
||||
case SpecialStopSynchronizeBreakpoints:
|
||||
if (debug)
|
||||
qDebug("attemptBreakpointSynchronization in special stop");
|
||||
attemptBreakpointSynchronization();
|
||||
doContinueInferior();
|
||||
return;
|
||||
case SpecialStopGetWidgetAt:
|
||||
postWidgetAtCommand();
|
||||
return;
|
||||
case CustomSpecialStop:
|
||||
foreach (const QVariant &data, m_customSpecialStopData)
|
||||
handleCustomSpecialStop(data);
|
||||
m_customSpecialStopData.clear();
|
||||
doContinueInferior();
|
||||
return;
|
||||
case NoSpecialStop:
|
||||
break;
|
||||
}
|
||||
for (const InterruptCallback &callback : m_interrupCallbacks)
|
||||
callback();
|
||||
m_interrupCallbacks.clear();
|
||||
|
||||
if (!m_initialSessionIdleHandled) { // Temporary stop at beginning
|
||||
handleInitialSessionIdle();
|
||||
@@ -2603,12 +2533,6 @@ void CdbEngine::attemptBreakpointSynchronization()
|
||||
if (!changed)
|
||||
return;
|
||||
|
||||
if (!m_accessible) {
|
||||
// No nested calls.
|
||||
if (m_specialStopMode != SpecialStopSynchronizeBreakpoints)
|
||||
doInterruptInferior(SpecialStopSynchronizeBreakpoints);
|
||||
return;
|
||||
}
|
||||
// Add/Change breakpoints and store pending ones in map, since
|
||||
// Breakhandler::setResponse() on pending breakpoints clears the pending flag.
|
||||
// handleBreakPoints will the complete that information and set it on the break handler.
|
||||
@@ -3076,44 +3000,11 @@ 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(stateName(state())), LogWarning);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void CdbEngine::postWidgetAtCommand()
|
||||
{
|
||||
DebuggerCommand cmd("widgetat", ExtensionCommand);
|
||||
cmd.args = QString("%1 %2").arg(m_watchPointX, m_watchPointY);
|
||||
cmd.args = QString("%1 %2").arg(p.x(), p.y());
|
||||
runCommand(cmd);
|
||||
}
|
||||
|
||||
void CdbEngine::handleCustomSpecialStop(const QVariant &v)
|
||||
{
|
||||
if (v.canConvert<MemoryChangeCookie>()) {
|
||||
const MemoryChangeCookie changeData = qvariant_cast<MemoryChangeCookie>(v);
|
||||
runCommand({cdbWriteMemoryCommand(changeData.address, changeData.data), NoFlags});
|
||||
return;
|
||||
}
|
||||
if (v.canConvert<MemoryViewCookie>()) {
|
||||
postFetchMemory(qvariant_cast<MemoryViewCookie>(v));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace Debugger
|
||||
|
||||
|
@@ -129,13 +129,12 @@ private:
|
||||
bool exists;
|
||||
};
|
||||
|
||||
enum SpecialStopMode
|
||||
{
|
||||
NoSpecialStop,
|
||||
SpecialStopSynchronizeBreakpoints,
|
||||
SpecialStopGetWidgetAt,
|
||||
CustomSpecialStop // Associated with m_specialStopData, handleCustomSpecialStop()
|
||||
enum StopMode {
|
||||
NoStopRequested,
|
||||
Interrupt,
|
||||
Callback
|
||||
};
|
||||
|
||||
enum ParseStackResultFlags // Flags returned by parseStackTrace
|
||||
{
|
||||
ParseStackStepInto = 1, // Need to execute a step, hit on a call frame in "Step into"
|
||||
@@ -160,16 +159,12 @@ private:
|
||||
void handleSessionAccessible(unsigned long cdbExState);
|
||||
void handleSessionInaccessible(unsigned long cdbExState);
|
||||
void handleSessionIdle(const QString &message);
|
||||
void doInterruptInferior(SpecialStopMode sm);
|
||||
void doInterruptInferiorCustomSpecialStop(const QVariant &v);
|
||||
using InterruptCallback = std::function<void()>;
|
||||
void doInterruptInferior(const InterruptCallback &cb = InterruptCallback());
|
||||
void doContinueInferior();
|
||||
void parseOutputLine(QString line);
|
||||
bool isCdbProcessRunning() const { return m_process.state() != QProcess::NotRunning; }
|
||||
bool canInterruptInferior() const;
|
||||
void syncOperateByInstruction(bool operateByInstruction);
|
||||
void postWidgetAtCommand();
|
||||
void handleCustomSpecialStop(const QVariant &v);
|
||||
void postFetchMemory(const MemoryViewCookie &c);
|
||||
inline void postDisassemblerCommand(quint64 address, DisassemblerAgent *agent);
|
||||
void postDisassemblerCommand(quint64 address, quint64 endAddress,
|
||||
DisassemblerAgent *agent);
|
||||
@@ -215,7 +210,7 @@ private:
|
||||
QByteArray m_outputBuffer;
|
||||
//! Debugger accessible (expecting commands)
|
||||
bool m_accessible = false;
|
||||
SpecialStopMode m_specialStopMode = NoSpecialStop;
|
||||
StopMode m_stopMode = NoStopRequested;
|
||||
ProjectExplorer::DeviceProcessSignalOperation::Ptr m_signalOperation;
|
||||
int m_nextCommandToken = 0;
|
||||
QHash<int, DebuggerCommand> m_commandForToken;
|
||||
@@ -223,7 +218,6 @@ private:
|
||||
int m_currentBuiltinResponseToken = -1;
|
||||
QMap<QString, NormalizedSourceFileName> m_normalizedFileCache;
|
||||
const QString m_extensionCommandPrefix; //!< Library name used as prefix
|
||||
bool m_operateByInstructionPending = true; //!< Creator operate by instruction action changed.
|
||||
bool m_operateByInstruction = true; // Default CDB setting.
|
||||
bool m_hasDebuggee = false;
|
||||
enum Wow64State {
|
||||
@@ -245,7 +239,7 @@ private:
|
||||
QHash<QString, QString> m_fileNameModuleHash;
|
||||
QMultiHash<QString, quint64> m_symbolAddressCache;
|
||||
bool m_ignoreCdbOutput = false;
|
||||
QVariantList m_customSpecialStopData;
|
||||
QList<InterruptCallback> m_interrupCallbacks;
|
||||
QList<SourcePathMapping> m_sourcePathMappings;
|
||||
QScopedPointer<GdbMi> m_coreStopReason;
|
||||
int m_pythonVersion = 0; // 0xMMmmpp MM = major; mm = minor; pp = patch
|
||||
|
Reference in New Issue
Block a user