forked from qt-creator/qt-creator
DAP: Add watcher feature
It is possible currently to add/remove watcher. Also it saves expanded structure during the debugging process. Note: It doesn't work for CMake because of the issue #25282. Change-Id: Ia0a48ca5cd0062617ae427b56b001ef87d5894ed Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
@@ -147,6 +147,7 @@ bool CMakeDapEngine::hasCapability(unsigned cap) const
|
||||
return cap & (ReloadModuleCapability
|
||||
| BreakConditionCapability
|
||||
| ShowModuleSymbolsCapability
|
||||
/*| AddWatcherCapability*/ // disable while the #25282 bug is not fixed
|
||||
/*| RunToLineCapability*/); // disable while the #25176 bug is not fixed
|
||||
}
|
||||
|
||||
|
||||
@@ -114,11 +114,19 @@ void DapClient::sendStepOver(int threadId)
|
||||
postRequest("next", QJsonObject{{"threadId", threadId}});
|
||||
}
|
||||
|
||||
void DapClient::evaluateVariable(const QString &expression, int frameId)
|
||||
{
|
||||
postRequest("evaluate",
|
||||
QJsonObject{{"expression", expression},
|
||||
{"frameId", frameId},
|
||||
{"context", "variables"}});
|
||||
}
|
||||
|
||||
void DapClient::stackTrace(int threadId)
|
||||
{
|
||||
QTC_ASSERT(threadId != -1, return);
|
||||
postRequest("stackTrace",
|
||||
QJsonObject{{"threadId", threadId}, {"startFrame", 0}, {"levels", 10}});
|
||||
QJsonObject{{"threadId", threadId}, {"startFrame", 0}, {"levels", 10}});
|
||||
}
|
||||
|
||||
void DapClient::scopes(int frameId)
|
||||
@@ -139,8 +147,8 @@ void DapClient::variables(int variablesReference)
|
||||
void DapClient::setBreakpoints(const QJsonArray &breakpoints, const FilePath &fileName)
|
||||
{
|
||||
postRequest("setBreakpoints",
|
||||
QJsonObject{{"source", QJsonObject{{"path", fileName.path()}}},
|
||||
{"breakpoints", breakpoints}});
|
||||
QJsonObject{{"source", QJsonObject{{"path", fileName.path()}}},
|
||||
{"breakpoints", breakpoints}});
|
||||
}
|
||||
|
||||
void DapClient::readOutput()
|
||||
@@ -214,6 +222,8 @@ void DapClient::emitSignals(const QJsonDocument &doc)
|
||||
type = DapResponseType::DapThreads;
|
||||
} else if (command == "pause") {
|
||||
type = DapResponseType::Pause;
|
||||
} else if (command == "evaluate") {
|
||||
type = DapResponseType::Evaluate;
|
||||
}
|
||||
emit responseReady(type, ob);
|
||||
return;
|
||||
|
||||
@@ -51,6 +51,7 @@ enum class DapResponseType
|
||||
StepOut,
|
||||
StepOver,
|
||||
Pause,
|
||||
Evaluate,
|
||||
Unknown
|
||||
};
|
||||
|
||||
@@ -93,11 +94,14 @@ public:
|
||||
void sendStepOut(int threadId);
|
||||
void sendStepOver(int threadId);
|
||||
|
||||
void evaluateVariable(const QString &expression, int frameId);
|
||||
|
||||
void stackTrace(int threadId);
|
||||
void scopes(int frameId);
|
||||
void threads();
|
||||
void variables(int variablesReference);
|
||||
void setBreakpoints(const QJsonArray &breakpoints, const Utils::FilePath &fileName);
|
||||
|
||||
void emitSignals(const QJsonDocument &doc);
|
||||
|
||||
signals:
|
||||
|
||||
@@ -63,6 +63,79 @@ using namespace Utils;
|
||||
|
||||
namespace Debugger::Internal {
|
||||
|
||||
VariablesHandler::VariablesHandler(DapEngine *dapEngine)
|
||||
: m_dapEngine(dapEngine)
|
||||
{}
|
||||
|
||||
void VariablesHandler::addVariable(const QString &iname, int variablesReference)
|
||||
{
|
||||
VariableItem varItem = {iname, variablesReference};
|
||||
bool wasEmpty = m_queue.empty();
|
||||
bool inserted = false;
|
||||
|
||||
for (auto i = m_queue.begin(); i != m_queue.end(); ++i) {
|
||||
if (i->iname > iname) {
|
||||
m_queue.insert(i, varItem);
|
||||
inserted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!inserted)
|
||||
m_queue.push_back(varItem);
|
||||
|
||||
if (wasEmpty) {
|
||||
startHandling();
|
||||
}
|
||||
}
|
||||
|
||||
void VariablesHandler::handleNext()
|
||||
{
|
||||
if (m_queue.empty())
|
||||
return;
|
||||
|
||||
m_queue.pop_front();
|
||||
startHandling();
|
||||
}
|
||||
|
||||
void VariablesHandler::startHandling()
|
||||
{
|
||||
if (m_queue.empty())
|
||||
return;
|
||||
|
||||
m_currentVarItem = m_queue.front();
|
||||
|
||||
WatchItem *watchItem = m_dapEngine->watchHandler()->findItem(m_currentVarItem.iname);
|
||||
int variablesReference = m_currentVarItem.variablesReference;
|
||||
|
||||
if (variablesReference == -1 && watchItem && watchItem->iname.startsWith("watch.")
|
||||
&& watchItem->iname.split('.').size() == 2) {
|
||||
watchItem->removeChildren();
|
||||
m_dapEngine->dapClient()->evaluateVariable(watchItem->name,
|
||||
m_dapEngine->currentStackFrameId());
|
||||
return;
|
||||
}
|
||||
|
||||
if (variablesReference == -1) {
|
||||
if (watchItem) {
|
||||
variablesReference = watchItem->variablesReference;
|
||||
} else {
|
||||
handleNext();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (variablesReference == 0) {
|
||||
handleNext();
|
||||
return;
|
||||
}
|
||||
|
||||
m_dapEngine->dapClient()->variables(variablesReference);
|
||||
}
|
||||
|
||||
DapEngine::DapEngine()
|
||||
: m_variablesHandler(std::make_unique<VariablesHandler>(this))
|
||||
{}
|
||||
|
||||
void DapEngine::executeDebuggerCommand(const QString &/*command*/)
|
||||
{
|
||||
QTC_ASSERT(state() == InferiorStopOk, qCDebug(logCategory()) << state());
|
||||
@@ -350,13 +423,6 @@ void DapEngine::refreshModules(const GdbMi &modules)
|
||||
handler->endUpdateAll();
|
||||
}
|
||||
|
||||
void DapEngine::requestModuleSymbols(const Utils::FilePath &/*moduleName*/)
|
||||
{
|
||||
// DebuggerCommand cmd("listSymbols");
|
||||
// cmd.arg("module", moduleName);
|
||||
// runCommand(cmd);
|
||||
}
|
||||
|
||||
void DapEngine::refreshState(const GdbMi &reportedState)
|
||||
{
|
||||
QString newState = reportedState.data();
|
||||
@@ -399,50 +465,32 @@ bool DapEngine::canHandleToolTip(const DebuggerToolTipContext &) const
|
||||
return state() == InferiorStopOk;
|
||||
}
|
||||
|
||||
void DapEngine::assignValueInDebugger(WatchItem *, const QString &/*expression*/, const QVariant &/*value*/)
|
||||
{
|
||||
//DebuggerCommand cmd("assignValue");
|
||||
//cmd.arg("expression", expression);
|
||||
//cmd.arg("value", value.toString());
|
||||
//runCommand(cmd);
|
||||
// postDirectCommand("global " + expression + ';' + expression + "=" + value.toString());
|
||||
updateLocals();
|
||||
}
|
||||
|
||||
void DapEngine::updateItem(const QString &iname)
|
||||
{
|
||||
WatchItem *item = watchHandler()->findItem(iname);
|
||||
if (item && m_currentWatchItem != item) {
|
||||
m_currentWatchItem = item;
|
||||
m_dapClient->variables(item->variablesReference);
|
||||
}
|
||||
}
|
||||
|
||||
void DapEngine::getVariableFromQueue()
|
||||
{
|
||||
WatchItem *item = nullptr;
|
||||
while (!item && !m_variablesReferenceInameQueue.empty()) {
|
||||
item = watchHandler()->findItem(m_variablesReferenceInameQueue.front());
|
||||
m_variablesReferenceInameQueue.pop();
|
||||
}
|
||||
|
||||
if (item) {
|
||||
m_currentWatchItem = item;
|
||||
m_dapClient->variables(item->variablesReference);
|
||||
}
|
||||
if (item && m_variablesHandler->currentItem().iname != item->iname)
|
||||
m_variablesHandler->addVariable(item->iname, item->variablesReference);
|
||||
}
|
||||
|
||||
void DapEngine::reexpandItems(const QSet<QString> &inames)
|
||||
{
|
||||
QList<QString> inamesVector = inames.values().toVector();
|
||||
QSet<QString> expandedInames = inames;
|
||||
for (auto inames : watchHandler()->watcherNames().keys())
|
||||
expandedInames.insert(watchHandler()->watcherName(inames));
|
||||
|
||||
QList<QString> inamesVector = expandedInames.values().toVector();
|
||||
inamesVector.sort();
|
||||
|
||||
for (const QString &iname : inamesVector) {
|
||||
if (iname.startsWith("local."))
|
||||
m_variablesReferenceInameQueue.push(iname);
|
||||
if (iname.startsWith("local.") || iname.startsWith("watch."))
|
||||
m_variablesHandler->addVariable(iname, -1);
|
||||
}
|
||||
}
|
||||
|
||||
getVariableFromQueue();
|
||||
void DapEngine::doUpdateLocals(const UpdateParameters ¶ms)
|
||||
{
|
||||
m_variablesHandler->addVariable(params.partialVariable, -1);
|
||||
}
|
||||
|
||||
QString DapEngine::errorMessage(QProcess::ProcessError error) const
|
||||
@@ -508,12 +556,6 @@ void DapEngine::handleResponse(DapResponseType type, const QJsonObject &response
|
||||
{
|
||||
const QString command = response.value("command").toString();
|
||||
|
||||
if (response.contains("success") && !response.value("success").toBool()) {
|
||||
showMessage(QString("DAP COMMAND FAILED: %1").arg(command));
|
||||
qCDebug(logCategory()) << "DAP COMMAND FAILED:" << command;
|
||||
return;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case DapResponseType::Initialize:
|
||||
qCDebug(logCategory()) << "initialize success";
|
||||
@@ -553,9 +595,18 @@ void DapEngine::handleResponse(DapResponseType type, const QJsonObject &response
|
||||
case DapResponseType::DapThreads:
|
||||
handleThreadsResponse(response);
|
||||
break;
|
||||
case DapResponseType::Evaluate:
|
||||
handleEvaluateResponse(response);
|
||||
break;
|
||||
default:
|
||||
showMessage("UNKNOWN RESPONSE:" + command);
|
||||
};
|
||||
|
||||
if (response.contains("success") && !response.value("success").toBool()) {
|
||||
showMessage(QString("DAP COMMAND FAILED: %1").arg(command));
|
||||
qCDebug(logCategory()) << "DAP COMMAND FAILED:" << command;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void DapEngine::handleStackTraceResponse(const QJsonObject &response)
|
||||
@@ -581,24 +632,17 @@ void DapEngine::handleScopesResponse(const QJsonObject &response)
|
||||
if (!response.value("success").toBool())
|
||||
return;
|
||||
|
||||
watchHandler()->removeAllData();
|
||||
watchHandler()->notifyUpdateStarted();
|
||||
|
||||
m_watchItems.clear();
|
||||
|
||||
QJsonArray scopes = response.value("body").toObject().value("scopes").toArray();
|
||||
for (const QJsonValueRef &scope : scopes) {
|
||||
const QString name = scope.toObject().value("name").toString();
|
||||
if (name == "Registers")
|
||||
continue;
|
||||
m_variablesReferenceQueue.push(scope.toObject().value("variablesReference").toInt());
|
||||
m_variablesHandler->addVariable("", scope.toObject().value("variablesReference").toInt());
|
||||
}
|
||||
|
||||
if (!m_variablesReferenceQueue.empty()) {
|
||||
m_isFirstLayer = true;
|
||||
m_dapClient->variables(m_variablesReferenceQueue.front());
|
||||
m_variablesReferenceQueue.pop();
|
||||
} else {
|
||||
if (m_variablesHandler->queueSize() == 0) {
|
||||
watchHandler()->notifyUpdateFinished();
|
||||
}
|
||||
}
|
||||
@@ -625,6 +669,28 @@ void DapEngine::handleThreadsResponse(const QJsonObject &response)
|
||||
}
|
||||
}
|
||||
|
||||
void DapEngine::handleEvaluateResponse(const QJsonObject &response)
|
||||
{
|
||||
WatchItem *watchItem = watchHandler()->findItem(
|
||||
m_variablesHandler->currentItem().iname);
|
||||
if (watchItem
|
||||
&& response.value("body").toObject().contains("variablesReference")) {
|
||||
watchItem->variablesReference
|
||||
= response.value("body").toObject().value("variablesReference").toInt();
|
||||
watchItem->value
|
||||
= response.value("body").toObject().value("result").toString();
|
||||
watchItem->type = response.value("body").toObject().value("type").toString();
|
||||
watchItem->wantsChildren = watchItem->variablesReference > 0;
|
||||
|
||||
watchItem->updateValueCache();
|
||||
watchItem->update();
|
||||
|
||||
m_variablesHandler->addVariable(watchItem->iname,
|
||||
watchItem->variablesReference);
|
||||
}
|
||||
m_variablesHandler->handleNext();
|
||||
}
|
||||
|
||||
void DapEngine::handleEvent(DapEventType type, const QJsonObject &event)
|
||||
{
|
||||
const QString eventType = event.value("event").toString();
|
||||
@@ -740,51 +806,42 @@ void DapEngine::handleBreakpointEvent(const QJsonObject &event)
|
||||
|
||||
void DapEngine::refreshLocals(const QJsonArray &variables)
|
||||
{
|
||||
WatchItem *currentItem = watchHandler()->findItem(m_variablesHandler->currentItem().iname);
|
||||
if (currentItem && currentItem->iname.startsWith("watch"))
|
||||
currentItem->removeChildren();
|
||||
|
||||
for (auto variable : variables) {
|
||||
WatchItem *item = new WatchItem;
|
||||
const QString name = variable.toObject().value("name").toString();
|
||||
if (m_isFirstLayer)
|
||||
item->iname = "local." + name;
|
||||
else
|
||||
item->iname = m_currentWatchItem->iname + "." + name;
|
||||
|
||||
item->iname = (currentItem ? currentItem->iname : "local") + "."
|
||||
+ name;
|
||||
item->name = name;
|
||||
item->type = variable.toObject().value("type").toString();
|
||||
item->value = variable.toObject().value("value").toString();
|
||||
item->address = variable.toObject().value("address").toInt();
|
||||
item->type = variable.toObject().value("type").toString();
|
||||
item->variablesReference = variable.toObject().value("variablesReference").toInt();
|
||||
item->wantsChildren = item->variablesReference > 0;
|
||||
|
||||
const int variablesReference = variable.toObject().value("variablesReference").toInt();
|
||||
item->variablesReference = variablesReference;
|
||||
if (variablesReference > 0)
|
||||
item->wantsChildren = true;
|
||||
|
||||
qCDebug(logCategory()) << "variable" << item->iname << variablesReference;
|
||||
if (m_isFirstLayer)
|
||||
m_watchItems.append(item);
|
||||
qCDebug(logCategory()) << "variable" << item->iname << item->variablesReference;
|
||||
if (currentItem)
|
||||
currentItem->appendChild(item);
|
||||
else
|
||||
m_currentWatchItem->appendChild(item);
|
||||
watchHandler()->insertItem(item);
|
||||
}
|
||||
|
||||
if (m_isFirstLayer) {
|
||||
if (m_variablesReferenceQueue.empty()) {
|
||||
for (auto item : m_watchItems)
|
||||
watchHandler()->insertItem(item);
|
||||
m_isFirstLayer = false;
|
||||
watchHandler()->notifyUpdateFinished();
|
||||
} else {
|
||||
m_dapClient->variables(m_variablesReferenceQueue.front());
|
||||
m_variablesReferenceQueue.pop();
|
||||
}
|
||||
return;
|
||||
QModelIndex idx = watchHandler()->model()->indexForItem(currentItem);
|
||||
if (currentItem && idx.isValid() && idx.data(LocalsExpandedRole).toBool()) {
|
||||
emit watchHandler()->model()->inameIsExpanded(currentItem->iname);
|
||||
emit watchHandler()->model()->itemIsExpanded(idx);
|
||||
}
|
||||
|
||||
if (m_currentWatchItem) {
|
||||
emit watchHandler()->model()->inameIsExpanded(m_currentWatchItem->iname);
|
||||
emit watchHandler()->model()->itemIsExpanded(
|
||||
watchHandler()->model()->indexForItem(m_currentWatchItem));
|
||||
if (m_variablesHandler->queueSize() == 1 && currentItem == nullptr) {
|
||||
watchHandler()->notifyUpdateFinished();
|
||||
}
|
||||
|
||||
getVariableFromQueue();
|
||||
m_variablesHandler->handleNext();
|
||||
}
|
||||
|
||||
void DapEngine::refreshStack(const QJsonArray &stackFrames)
|
||||
|
||||
@@ -20,6 +20,30 @@ class IDataProvider;
|
||||
class GdbMi;
|
||||
enum class DapResponseType;
|
||||
enum class DapEventType;
|
||||
class DapEngine;
|
||||
|
||||
class VariablesHandler {
|
||||
public:
|
||||
VariablesHandler(DapEngine *dapEngine);
|
||||
|
||||
struct VariableItem {
|
||||
QString iname;
|
||||
int variablesReference;
|
||||
};
|
||||
|
||||
void addVariable(const QString &iname, int variablesReference);
|
||||
void handleNext();
|
||||
|
||||
VariableItem currentItem() const { return m_currentVarItem; }
|
||||
int queueSize() const { return m_queue.size(); }
|
||||
|
||||
private:
|
||||
void startHandling();
|
||||
|
||||
DapEngine *m_dapEngine;
|
||||
std::list<VariableItem> m_queue;
|
||||
VariableItem m_currentVarItem;
|
||||
};
|
||||
|
||||
/*
|
||||
* A debugger engine for the debugger adapter protocol.
|
||||
@@ -27,9 +51,12 @@ enum class DapEventType;
|
||||
class DapEngine : public DebuggerEngine
|
||||
{
|
||||
public:
|
||||
DapEngine() = default;
|
||||
DapEngine();
|
||||
~DapEngine() override = default;
|
||||
|
||||
DapClient *dapClient() const { return m_dapClient; }
|
||||
int currentStackFrameId() const { return m_currentStackFrameId; }
|
||||
|
||||
protected:
|
||||
void executeStepIn(bool) override;
|
||||
void executeStepOut() override;
|
||||
@@ -55,13 +82,10 @@ protected:
|
||||
void updateBreakpoint(const Breakpoint &bp) override;
|
||||
void removeBreakpoint(const Breakpoint &bp) override;
|
||||
|
||||
void assignValueInDebugger(WatchItem *item,
|
||||
const QString &expr, const QVariant &value) override;
|
||||
void executeDebuggerCommand(const QString &command) override;
|
||||
|
||||
void loadSymbols(const Utils::FilePath &moduleName) override;
|
||||
void loadAllSymbols() override;
|
||||
void requestModuleSymbols(const Utils::FilePath &moduleName) override;
|
||||
void reloadModules() override;
|
||||
void reloadRegisters() override {}
|
||||
void reloadSourceFiles() override {}
|
||||
@@ -70,6 +94,7 @@ protected:
|
||||
bool supportsThreads() const { return true; }
|
||||
void updateItem(const QString &iname) override;
|
||||
void reexpandItems(const QSet<QString> &inames) override;
|
||||
void doUpdateLocals(const UpdateParameters ¶ms) override;
|
||||
void getVariableFromQueue();
|
||||
|
||||
void runCommand(const DebuggerCommand &cmd) override;
|
||||
@@ -102,6 +127,7 @@ protected:
|
||||
void handleStackTraceResponse(const QJsonObject &response);
|
||||
void handleScopesResponse(const QJsonObject &response);
|
||||
void handleThreadsResponse(const QJsonObject &response);
|
||||
void handleEvaluateResponse(const QJsonObject &response);
|
||||
|
||||
void handleEvent(DapEventType type, const QJsonObject &event);
|
||||
void handleBreakpointEvent(const QJsonObject &event);
|
||||
@@ -118,11 +144,7 @@ protected:
|
||||
int m_currentThreadId = -1;
|
||||
int m_currentStackFrameId = -1;
|
||||
|
||||
bool m_isFirstLayer = true;
|
||||
std::queue<int> m_variablesReferenceQueue;
|
||||
std::queue<QString> m_variablesReferenceInameQueue;
|
||||
WatchItem *m_currentWatchItem = nullptr;
|
||||
QList<WatchItem *> m_watchItems;
|
||||
std::unique_ptr<VariablesHandler> m_variablesHandler;
|
||||
|
||||
virtual const QLoggingCategory &logCategory()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user