forked from qt-creator/qt-creator
Debugger: Add skeleton for a debugger adapter protocol using engine
- Support the launch of an application - Support continue/pause an application - Support add breakpoint only after an app is launched (I.e. preset breakpoints won't work) - Support stop the debug session - "Remove one break breakpoint" works but removes all breakpoints ToDo: - Polish the transition between stages - Fix breakpoints handling - Add support "Step in", "Step out" and "Step Over" Task-number: QTCREATORBUG-27279 Change-Id: I5c32ce713f5a0f19cc3b9d995cbbadd8adf6a413 Reviewed-by: Artem Sokolovskii <artem.sokolovskii@qt.io> Reviewed-by: hjk <hjk@qt.io> Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
This commit is contained in:
@@ -27,6 +27,7 @@ add_qtc_plugin(Debugger
|
||||
console/consoleitemmodel.cpp console/consoleitemmodel.h
|
||||
console/consoleproxymodel.cpp console/consoleproxymodel.h
|
||||
console/consoleview.cpp console/consoleview.h
|
||||
dap/dapengine.cpp dap/dapengine.h
|
||||
debugger.qrc
|
||||
debugger_global.h
|
||||
debuggeractions.cpp debuggeractions.h
|
||||
|
730
src/plugins/debugger/dap/dapengine.cpp
Normal file
730
src/plugins/debugger/dap/dapengine.cpp
Normal file
@@ -0,0 +1,730 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
|
||||
|
||||
#include "dapengine.h"
|
||||
|
||||
#include <debugger/breakhandler.h>
|
||||
#include <debugger/debuggeractions.h>
|
||||
#include <debugger/debuggercore.h>
|
||||
#include <debugger/debuggerdialogs.h>
|
||||
#include <debugger/debuggerplugin.h>
|
||||
#include <debugger/debuggerprotocol.h>
|
||||
#include <debugger/debuggertooltipmanager.h>
|
||||
#include <debugger/debuggertr.h>
|
||||
#include <debugger/moduleshandler.h>
|
||||
#include <debugger/procinterrupt.h>
|
||||
#include <debugger/registerhandler.h>
|
||||
#include <debugger/sourceutils.h>
|
||||
#include <debugger/stackhandler.h>
|
||||
#include <debugger/threaddata.h>
|
||||
#include <debugger/watchhandler.h>
|
||||
#include <debugger/watchutils.h>
|
||||
|
||||
#include <utils/algorithm.h>
|
||||
#include <utils/environment.h>
|
||||
#include <utils/qtcassert.h>
|
||||
#include <utils/qtcprocess.h>
|
||||
|
||||
#include <coreplugin/idocument.h>
|
||||
#include <coreplugin/icore.h>
|
||||
#include <coreplugin/messagebox.h>
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QTimer>
|
||||
#include <QVariant>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QThread>
|
||||
|
||||
using namespace Core;
|
||||
using namespace Utils;
|
||||
|
||||
namespace Debugger::Internal {
|
||||
|
||||
DapEngine::DapEngine()
|
||||
{
|
||||
setObjectName("DapEngine");
|
||||
setDebuggerName("DAP");
|
||||
}
|
||||
|
||||
void DapEngine::executeDebuggerCommand(const QString &command)
|
||||
{
|
||||
QTC_ASSERT(state() == InferiorStopOk, qDebug() << state());
|
||||
// if (state() == DebuggerNotReady) {
|
||||
// showMessage("DAP PROCESS NOT RUNNING, PLAIN CMD IGNORED: " + command);
|
||||
// return;
|
||||
// }
|
||||
// QTC_ASSERT(m_proc.isRunning(), notifyEngineIll());
|
||||
// postDirectCommand(command);
|
||||
}
|
||||
|
||||
void DapEngine::postDirectCommand(const QJsonObject &ob)
|
||||
{
|
||||
static int seq = 1;
|
||||
QJsonObject obseq = ob;
|
||||
obseq.insert("seq", seq++);
|
||||
|
||||
const QByteArray data = QJsonDocument(obseq).toJson(QJsonDocument::Compact);
|
||||
const QByteArray msg = "Content-Length: " + QByteArray::number(data.size()) + "\r\n\r\n" + data;
|
||||
qDebug() << msg;
|
||||
|
||||
m_proc.writeRaw(msg);
|
||||
|
||||
showMessage(QString::fromUtf8(msg), LogInput);
|
||||
}
|
||||
|
||||
void DapEngine::runCommand(const DebuggerCommand &cmd)
|
||||
{
|
||||
if (state() == EngineSetupRequested) { // cmd has been triggered too early
|
||||
showMessage("IGNORED COMMAND: " + cmd.function);
|
||||
return;
|
||||
}
|
||||
QTC_ASSERT(m_proc.isRunning(), notifyEngineIll());
|
||||
// postDirectCommand(cmd.args.toObject());
|
||||
// const QByteArray data = QJsonDocument(cmd.args.toObject()).toJson(QJsonDocument::Compact);
|
||||
// m_proc.writeRaw("Content-Length: " + QByteArray::number(data.size()) + "\r\n" + data + "\r\n");
|
||||
|
||||
// showMessage(QString::fromUtf8(data), LogInput);
|
||||
}
|
||||
|
||||
void DapEngine::shutdownInferior()
|
||||
{
|
||||
QTC_ASSERT(state() == InferiorShutdownRequested, qDebug() << state());
|
||||
postDirectCommand({{"command", "terminate"},
|
||||
{"type", "request"}});
|
||||
|
||||
qDebug() << "DapEngine::shutdownInferior()";
|
||||
notifyInferiorShutdownFinished();
|
||||
}
|
||||
|
||||
void DapEngine::shutdownEngine()
|
||||
{
|
||||
QTC_ASSERT(state() == EngineShutdownRequested, qDebug() << state());
|
||||
|
||||
qDebug() << "DapEngine::shutdownEngine()";
|
||||
m_proc.kill();
|
||||
}
|
||||
|
||||
void DapEngine::setupEngine()
|
||||
{
|
||||
QTC_ASSERT(state() == EngineSetupRequested, qDebug() << state());
|
||||
|
||||
connect(&m_proc, &QtcProcess::started, this, &DapEngine::handleDabStarted);
|
||||
connect(&m_proc, &QtcProcess::done, this, &DapEngine::handleDapDone);
|
||||
connect(&m_proc, &QtcProcess::readyReadStandardOutput, this, &DapEngine::readDapStandardOutput);
|
||||
connect(&m_proc, &QtcProcess::readyReadStandardError, this, &DapEngine::readDapStandardError);
|
||||
|
||||
const DebuggerRunParameters &rp = runParameters();
|
||||
const CommandLine cmd{rp.debugger.command.executable(), {"-i", "dap"}};
|
||||
showMessage("STARTING " + cmd.toUserOutput());
|
||||
m_proc.setProcessMode(ProcessMode::Writer);
|
||||
m_proc.setEnvironment(rp.debugger.environment);
|
||||
m_proc.setCommand(cmd);
|
||||
m_proc.start();
|
||||
notifyEngineRunAndInferiorRunOk();
|
||||
}
|
||||
|
||||
// From the docs:
|
||||
// The sequence of events/requests is as follows:
|
||||
// * adapters sends initialized event (after the initialize request has returned)
|
||||
// * client sends zero or more setBreakpoints requests
|
||||
// * client sends one setFunctionBreakpoints request
|
||||
// (if corresponding capability supportsFunctionBreakpoints is true)
|
||||
// * client sends a setExceptionBreakpoints request if one or more exceptionBreakpointFilters
|
||||
// have been defined (or if supportsConfigurationDoneRequest is not true)
|
||||
// * client sends other future configuration requests
|
||||
// * client sends one configurationDone request to indicate the end of the configuration.
|
||||
|
||||
void DapEngine::handleDabStarted()
|
||||
{
|
||||
notifyEngineSetupOk();
|
||||
QTC_ASSERT(state() == EngineRunRequested, qDebug() << state());
|
||||
|
||||
// CHECK_STATE(EngineRunRequested);
|
||||
|
||||
postDirectCommand({
|
||||
{"command", "initialize"},
|
||||
{"type", "request"},
|
||||
{"arguments", QJsonObject {
|
||||
{"clientID", "QtCreator"}, // The ID of the client using this adapter.
|
||||
{"clientName", "QtCreator"} // The human-readable name of the client using this adapter.
|
||||
}}
|
||||
});
|
||||
|
||||
qDebug() << "handleDabStarted";
|
||||
}
|
||||
|
||||
void DapEngine::handleDabConfigurationDone()
|
||||
{
|
||||
QTC_ASSERT(state() == EngineRunRequested, qDebug() << state());
|
||||
|
||||
// CHECK_STATE(EngineRunRequested);
|
||||
|
||||
postDirectCommand({{"command", "configurationDone"}, {"type", "request"}});
|
||||
|
||||
qDebug() << "handleDabConfigurationDone";
|
||||
}
|
||||
|
||||
|
||||
void DapEngine::handleDabLaunch()
|
||||
{
|
||||
QTC_ASSERT(state() == EngineRunRequested, qDebug() << state());
|
||||
|
||||
// CHECK_STATE(EngineRunRequested);
|
||||
|
||||
postDirectCommand(
|
||||
{{"command", "launch"},
|
||||
{"type", "request"},
|
||||
// {"program", runParameters().inferior.command.executable().path()},
|
||||
{"arguments",
|
||||
QJsonObject{
|
||||
{"noDebug", false},
|
||||
{"program", runParameters().inferior.command.executable().path()},
|
||||
// {"args", runParameters().inferior.command.arguments()},
|
||||
// {"cwd", runParameters().inferior.workingDirectory},
|
||||
// {"env", QJsonObject::fromVariantMap(runParameters().inferior.environment.toStringMap())}
|
||||
{"__restart", ""}
|
||||
}}});
|
||||
qDebug() << "handleDabLaunch";
|
||||
}
|
||||
|
||||
void DapEngine::interruptInferior()
|
||||
{
|
||||
QString error;
|
||||
|
||||
postDirectCommand({{"command", "pause"},
|
||||
{"type", "request"}});
|
||||
|
||||
qDebug() << "DapEngine::interruptInferior()";
|
||||
|
||||
|
||||
// interruptProcess(m_proc.processId(), GdbEngineType, &error);
|
||||
// notifyInferiorExited();
|
||||
notifyInferiorStopOk();
|
||||
}
|
||||
|
||||
void DapEngine::executeStepIn(bool)
|
||||
{
|
||||
notifyInferiorRunRequested();
|
||||
notifyInferiorRunOk();
|
||||
// postDirectCommand("step");
|
||||
}
|
||||
|
||||
void DapEngine::executeStepOut()
|
||||
{
|
||||
notifyInferiorRunRequested();
|
||||
notifyInferiorRunOk();
|
||||
// postDirectCommand("return");
|
||||
}
|
||||
|
||||
void DapEngine::executeStepOver(bool)
|
||||
{
|
||||
notifyInferiorRunRequested();
|
||||
notifyInferiorRunOk();
|
||||
// postDirectCommand("next");
|
||||
}
|
||||
|
||||
void DapEngine::continueInferior()
|
||||
{
|
||||
postDirectCommand({{"command", "continue"},
|
||||
{"type", "request"},
|
||||
{"arguments",
|
||||
QJsonObject{
|
||||
{"threadId", 1}, // The ID of the client using this adapter.
|
||||
}}});
|
||||
|
||||
qDebug() << "continueInferior";
|
||||
// notifyInferiorRunRequested();
|
||||
// notifyInferiorRunOk();
|
||||
|
||||
// Callback will be triggered e.g. when breakpoint is hit.
|
||||
// postDirectCommand("continue");
|
||||
}
|
||||
|
||||
void DapEngine::executeRunToLine(const ContextData &data)
|
||||
{
|
||||
Q_UNUSED(data)
|
||||
QTC_CHECK("FIXME: DapEngine::runToLineExec()" && false);
|
||||
}
|
||||
|
||||
void DapEngine::executeRunToFunction(const QString &functionName)
|
||||
{
|
||||
Q_UNUSED(functionName)
|
||||
QTC_CHECK("FIXME: DapEngine::runToFunctionExec()" && false);
|
||||
}
|
||||
|
||||
void DapEngine::executeJumpToLine(const ContextData &data)
|
||||
{
|
||||
Q_UNUSED(data)
|
||||
QTC_CHECK("FIXME: DapEngine::jumpToLineExec()" && false);
|
||||
}
|
||||
|
||||
void DapEngine::activateFrame(int frameIndex)
|
||||
{
|
||||
if (state() != InferiorStopOk && state() != InferiorUnrunnable)
|
||||
return;
|
||||
|
||||
StackHandler *handler = stackHandler();
|
||||
QTC_ASSERT(frameIndex < handler->stackSize(), return);
|
||||
handler->setCurrentIndex(frameIndex);
|
||||
gotoLocation(handler->currentFrame());
|
||||
updateLocals();
|
||||
}
|
||||
|
||||
void DapEngine::selectThread(const Thread &thread)
|
||||
{
|
||||
Q_UNUSED(thread)
|
||||
}
|
||||
|
||||
bool DapEngine::acceptsBreakpoint(const BreakpointParameters &) const
|
||||
{
|
||||
return true; // FIXME: Too bold.
|
||||
}
|
||||
|
||||
void DapEngine::insertBreakpoint(const Breakpoint &bp)
|
||||
{
|
||||
QTC_ASSERT(bp, return);
|
||||
QTC_CHECK(bp->state() == BreakpointInsertionRequested);
|
||||
notifyBreakpointInsertProceeding(bp);
|
||||
|
||||
QString loc;
|
||||
const BreakpointParameters ¶ms = bp->requestedParameters();
|
||||
if (params.type == BreakpointByFunction)
|
||||
loc = params.functionName;
|
||||
else
|
||||
loc = params.fileName.toString() + ':' + QString::number(params.lineNumber);
|
||||
|
||||
postDirectCommand(
|
||||
{{"command", "setBreakpoints"},
|
||||
{"type", "request"},
|
||||
{"arguments",
|
||||
QJsonObject{{"source", QJsonObject{{"path", params.fileName.toString()}}},
|
||||
{"breakpoints",
|
||||
QJsonArray{QJsonObject{{"id", 1}, {"line", params.lineNumber}}}}
|
||||
|
||||
}}});
|
||||
}
|
||||
|
||||
void DapEngine::updateBreakpoint(const Breakpoint &bp)
|
||||
{
|
||||
// QTC_ASSERT(bp, return);
|
||||
// const BreakpointState state = bp->state();
|
||||
// if (QTC_GUARD(state == BreakpointUpdateRequested))
|
||||
// notifyBreakpointChangeProceeding(bp);
|
||||
// if (bp->responseId().isEmpty()) // FIXME postpone update somehow (QTimer::singleShot?)
|
||||
// return;
|
||||
|
||||
// // FIXME figure out what needs to be changed (there might be more than enabled state)
|
||||
// const BreakpointParameters &requested = bp->requestedParameters();
|
||||
// if (requested.enabled != bp->isEnabled()) {
|
||||
// if (bp->isEnabled())
|
||||
// postDirectCommand("disable " + bp->responseId());
|
||||
// else
|
||||
// postDirectCommand("enable " + bp->responseId());
|
||||
// bp->setEnabled(!bp->isEnabled());
|
||||
// }
|
||||
// // Pretend it succeeds without waiting for response.
|
||||
notifyBreakpointChangeOk(bp);
|
||||
}
|
||||
|
||||
void DapEngine::removeBreakpoint(const Breakpoint &bp)
|
||||
{
|
||||
QTC_ASSERT(bp, return);
|
||||
QTC_CHECK(bp->state() == BreakpointRemoveRequested);
|
||||
// notifyBreakpointRemoveProceeding(bp);
|
||||
const BreakpointParameters ¶ms = bp->requestedParameters();
|
||||
postDirectCommand({{"command", "setBreakpoints"},
|
||||
{"type", "request"},
|
||||
{"arguments",
|
||||
QJsonObject{{"source", QJsonObject{{"path", params.fileName.toString()}}},
|
||||
{"breakpoints", QJsonArray{}}}}});
|
||||
qDebug() << "removeBreakpoint";
|
||||
|
||||
// notifyBreakpointRemoveOk(bp);
|
||||
}
|
||||
|
||||
void DapEngine::loadSymbols(const Utils::FilePath &/*moduleName*/)
|
||||
{
|
||||
}
|
||||
|
||||
void DapEngine::loadAllSymbols()
|
||||
{
|
||||
}
|
||||
|
||||
void DapEngine::reloadModules()
|
||||
{
|
||||
runCommand({"listModules"});
|
||||
}
|
||||
|
||||
void DapEngine::refreshModules(const GdbMi &modules)
|
||||
{
|
||||
ModulesHandler *handler = modulesHandler();
|
||||
handler->beginUpdateAll();
|
||||
for (const GdbMi &item : modules) {
|
||||
Module module;
|
||||
module.moduleName = item["name"].data();
|
||||
QString path = item["value"].data();
|
||||
int pos = path.indexOf("' from '");
|
||||
if (pos != -1) {
|
||||
path = path.mid(pos + 8);
|
||||
if (path.size() >= 2)
|
||||
path.chop(2);
|
||||
} else if (path.startsWith("<module '")
|
||||
&& path.endsWith("' (built-in)>")) {
|
||||
path = "(builtin)";
|
||||
}
|
||||
module.modulePath = FilePath::fromString(path);
|
||||
handler->updateModule(module);
|
||||
}
|
||||
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();
|
||||
if (newState == "stopped") {
|
||||
notifyInferiorSpontaneousStop();
|
||||
updateAll();
|
||||
} else if (newState == "inferiorexited") {
|
||||
notifyInferiorExited();
|
||||
}
|
||||
}
|
||||
|
||||
void DapEngine::refreshLocation(const GdbMi &reportedLocation)
|
||||
{
|
||||
StackFrame frame;
|
||||
frame.file = FilePath::fromString(reportedLocation["file"].data());
|
||||
frame.line = reportedLocation["line"].toInt();
|
||||
frame.usable = frame.file.isReadableFile();
|
||||
if (state() == InferiorRunOk) {
|
||||
showMessage(QString("STOPPED AT: %1:%2").arg(frame.file.toUserOutput()).arg(frame.line));
|
||||
gotoLocation(frame);
|
||||
notifyInferiorSpontaneousStop();
|
||||
updateAll();
|
||||
}
|
||||
}
|
||||
|
||||
void DapEngine::refreshSymbols(const GdbMi &symbols)
|
||||
{
|
||||
QString moduleName = symbols["module"].data();
|
||||
Symbols syms;
|
||||
for (const GdbMi &item : symbols["symbols"]) {
|
||||
Symbol symbol;
|
||||
symbol.name = item["name"].data();
|
||||
syms.append(symbol);
|
||||
}
|
||||
showModuleSymbols(FilePath::fromString(moduleName), syms);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
Q_UNUSED(iname)
|
||||
updateAll();
|
||||
}
|
||||
|
||||
QString DapEngine::errorMessage(QProcess::ProcessError error) const
|
||||
{
|
||||
switch (error) {
|
||||
case QProcess::FailedToStart:
|
||||
return Tr::tr("The DAP process failed to start. Either the "
|
||||
"invoked program \"%1\" is missing, or you may have insufficient "
|
||||
"permissions to invoke the program.")
|
||||
.arg(m_proc.commandLine().executable().toUserOutput());
|
||||
case QProcess::Crashed:
|
||||
return Tr::tr("The DAP process crashed some time after starting "
|
||||
"successfully.");
|
||||
case QProcess::Timedout:
|
||||
return Tr::tr("The last waitFor...() function timed out. "
|
||||
"The state of QProcess is unchanged, and you can try calling "
|
||||
"waitFor...() again.");
|
||||
case QProcess::WriteError:
|
||||
return Tr::tr("An error occurred when attempting to write "
|
||||
"to the DAP process. For example, the process may not be running, "
|
||||
"or it may have closed its input channel.");
|
||||
case QProcess::ReadError:
|
||||
return Tr::tr("An error occurred when attempting to read from "
|
||||
"the DAP process. For example, the process may not be running.");
|
||||
default:
|
||||
return Tr::tr("An unknown error in the DAP process occurred.") + ' ';
|
||||
}
|
||||
}
|
||||
|
||||
void DapEngine::handleDapDone()
|
||||
{
|
||||
if (m_proc.result() == ProcessResult::StartFailed) {
|
||||
notifyEngineSetupFailed();
|
||||
showMessage("ADAPTER START FAILED");
|
||||
ICore::showWarningWithOptions(Tr::tr("Adapter start failed"), m_proc.exitMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
const QProcess::ProcessError error = m_proc.error();
|
||||
if (error != QProcess::UnknownError) {
|
||||
showMessage("HANDLE DAP ERROR");
|
||||
if (error != QProcess::Crashed)
|
||||
AsynchronousMessageBox::critical(Tr::tr("DAP I/O Error"), errorMessage(error));
|
||||
if (error == QProcess::FailedToStart)
|
||||
return;
|
||||
}
|
||||
showMessage(QString("DAP PROCESS FINISHED, status %1, code %2")
|
||||
.arg(m_proc.exitStatus()).arg(m_proc.exitCode()));
|
||||
notifyEngineSpontaneousShutdown();
|
||||
}
|
||||
|
||||
void DapEngine::readDapStandardError()
|
||||
{
|
||||
QString err = m_proc.readAllStandardError();
|
||||
//qWarning() << "Unexpected DAP stderr:" << err;
|
||||
showMessage("Unexpected DAP stderr: " + err);
|
||||
//handleOutput(err);
|
||||
}
|
||||
|
||||
void DapEngine::readDapStandardOutput()
|
||||
{
|
||||
m_inbuffer.append(m_proc.readAllStandardOutput().toUtf8());
|
||||
// qDebug() << m_inbuffer;
|
||||
|
||||
while (true) {
|
||||
// Something like
|
||||
// Content-Length: 128\r\n
|
||||
// {"type": "event", "event": "output", "body": {"category": "stdout", "output": "...\n"}, "seq": 1}\r\n
|
||||
// FIXME: There coud be more than one header line.
|
||||
int pos1 = m_inbuffer.indexOf("Content-Length:");
|
||||
if (pos1 == -1)
|
||||
break;
|
||||
pos1 += 15;
|
||||
|
||||
int pos2 = m_inbuffer.indexOf('\n', pos1);
|
||||
if (pos2 == -1)
|
||||
break;
|
||||
|
||||
const int len = m_inbuffer.mid(pos1, pos2 - pos1).trimmed().toInt();
|
||||
if (len < 4)
|
||||
break;
|
||||
|
||||
pos2 += 3; // Skip \r\n\r
|
||||
|
||||
if (pos2 + len > m_inbuffer.size())
|
||||
break;
|
||||
|
||||
QJsonParseError error;
|
||||
const auto doc = QJsonDocument::fromJson(m_inbuffer.mid(pos2, len), &error);
|
||||
|
||||
m_inbuffer = m_inbuffer.mid(pos2 + len);
|
||||
|
||||
handleOutput(doc);
|
||||
}
|
||||
}
|
||||
|
||||
void DapEngine::handleOutput(const QJsonDocument &data)
|
||||
{
|
||||
QJsonObject ob = data.object();
|
||||
|
||||
const QJsonValue t = ob.value("type");
|
||||
const QString type = t.toString();
|
||||
qDebug() << "response" << ob;
|
||||
|
||||
if (type == "response") {
|
||||
const QString command = ob.value("command").toString();
|
||||
if (command == "configurationDone") {
|
||||
showMessage("configurationDone", LogDebug);
|
||||
qDebug() << "configurationDone success";
|
||||
notifyInferiorRunOk();
|
||||
|
||||
claimInitialBreakpoints();
|
||||
return;
|
||||
}
|
||||
|
||||
if (command == "continue") {
|
||||
showMessage("continue", LogDebug);
|
||||
qDebug() << "continue success";
|
||||
notifyInferiorRunOk();
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (type == "event") {
|
||||
const QString event = ob.value("event").toString();
|
||||
if (event == "output") {
|
||||
const QJsonObject body = ob.value("body").toObject();
|
||||
const QString category = body.value("category").toString();
|
||||
const QString output = body.value("output").toString();
|
||||
if (category == "stdout")
|
||||
showMessage(output, AppOutput);
|
||||
else if (category == "stderr")
|
||||
showMessage(output, AppError);
|
||||
else
|
||||
showMessage(output, LogDebug);
|
||||
return;
|
||||
}
|
||||
qDebug() << data;
|
||||
|
||||
if (event == "initialized") {
|
||||
showMessage(event, LogDebug);
|
||||
qDebug() << "initialize success";
|
||||
handleDabLaunch();
|
||||
handleDabConfigurationDone();
|
||||
return;
|
||||
}
|
||||
|
||||
if (event == "initialized") {
|
||||
showMessage(event, LogDebug);
|
||||
return;
|
||||
}
|
||||
|
||||
if (event == "stopped") {
|
||||
notifyInferiorSpontaneousStop();
|
||||
return;
|
||||
}
|
||||
|
||||
showMessage("UNKNOWN EVENT:" + event);
|
||||
return;
|
||||
}
|
||||
|
||||
showMessage("UNKNOWN TYPE:" + type);
|
||||
}
|
||||
|
||||
void DapEngine::refreshLocals(const GdbMi &vars)
|
||||
{
|
||||
WatchHandler *handler = watchHandler();
|
||||
handler->resetValueCache();
|
||||
handler->insertItems(vars);
|
||||
handler->notifyUpdateFinished();
|
||||
|
||||
updateToolTips();
|
||||
}
|
||||
|
||||
void DapEngine::refreshStack(const GdbMi &stack)
|
||||
{
|
||||
StackHandler *handler = stackHandler();
|
||||
StackFrames frames;
|
||||
for (const GdbMi &item : stack["frames"]) {
|
||||
StackFrame frame;
|
||||
frame.level = item["level"].data();
|
||||
frame.file = FilePath::fromString(item["file"].data());
|
||||
frame.function = item["function"].data();
|
||||
frame.module = item["function"].data();
|
||||
frame.line = item["line"].toInt();
|
||||
frame.address = item["address"].toAddress();
|
||||
GdbMi usable = item["usable"];
|
||||
if (usable.isValid())
|
||||
frame.usable = usable.data().toInt();
|
||||
else
|
||||
frame.usable = frame.file.isReadableFile();
|
||||
frames.append(frame);
|
||||
}
|
||||
bool canExpand = stack["hasmore"].toInt();
|
||||
//action(ExpandStack)->setEnabled(canExpand);
|
||||
handler->setFrames(frames, canExpand);
|
||||
|
||||
int index = stackHandler()->firstUsableIndex();
|
||||
handler->setCurrentIndex(index);
|
||||
if (index >= 0 && index < handler->stackSize())
|
||||
gotoLocation(handler->frameAt(index));
|
||||
}
|
||||
|
||||
void DapEngine::updateAll()
|
||||
{
|
||||
runCommand({"stackListFrames"});
|
||||
updateLocals();
|
||||
}
|
||||
|
||||
void DapEngine::updateLocals()
|
||||
{
|
||||
// DebuggerCommand cmd("updateData");
|
||||
// cmd.arg("nativeMixed", isNativeMixedActive());
|
||||
// watchHandler()->appendFormatRequests(&cmd);
|
||||
// watchHandler()->appendWatchersAndTooltipRequests(&cmd);
|
||||
|
||||
// const bool alwaysVerbose = qtcEnvironmentVariableIsSet("QTC_DEBUGGER_PYTHON_VERBOSE");
|
||||
// cmd.arg("passexceptions", alwaysVerbose);
|
||||
// cmd.arg("fancy", debuggerSettings()->useDebuggingHelpers.value());
|
||||
|
||||
// //cmd.arg("resultvarname", m_resultVarName);
|
||||
// //m_lastDebuggableCommand = cmd;
|
||||
// //m_lastDebuggableCommand.args.replace("\"passexceptions\":0", "\"passexceptions\":1");
|
||||
// cmd.arg("frame", stackHandler()->currentIndex());
|
||||
|
||||
// watchHandler()->notifyUpdateStarted();
|
||||
// runCommand(cmd);
|
||||
}
|
||||
|
||||
bool DapEngine::hasCapability(unsigned cap) const
|
||||
{
|
||||
return cap & (ReloadModuleCapability
|
||||
| BreakConditionCapability
|
||||
| ShowModuleSymbolsCapability);
|
||||
}
|
||||
|
||||
void DapEngine::claimInitialBreakpoints()
|
||||
{
|
||||
BreakpointManager::claimBreakpointsForEngine(this);
|
||||
qDebug() << "claimInitialBreakpoints";
|
||||
const Breakpoints bps = breakHandler()->breakpoints();
|
||||
for (const Breakpoint &bp : bps)
|
||||
qDebug() << "breakpoit: " << bp->fileName() << bp->lineNumber();
|
||||
qDebug() << "claimInitialBreakpoints end";
|
||||
|
||||
// const DebuggerRunParameters &rp = runParameters();
|
||||
// if (rp.startMode != AttachToCore) {
|
||||
// showStatusMessage(Tr::tr("Setting breakpoints..."));
|
||||
// showMessage(Tr::tr("Setting breakpoints..."));
|
||||
// BreakpointManager::claimBreakpointsForEngine(this);
|
||||
|
||||
// const DebuggerSettings &s = *debuggerSettings();
|
||||
// const bool onAbort = s.breakOnAbort.value();
|
||||
// const bool onWarning = s.breakOnWarning.value();
|
||||
// const bool onFatal = s.breakOnFatal.value();
|
||||
// if (onAbort || onWarning || onFatal) {
|
||||
// DebuggerCommand cmd("createSpecialBreakpoints");
|
||||
// cmd.arg("breakonabort", onAbort);
|
||||
// cmd.arg("breakonwarning", onWarning);
|
||||
// cmd.arg("breakonfatal", onFatal);
|
||||
// runCommand(cmd);
|
||||
// }
|
||||
// }
|
||||
|
||||
// // It is ok to cut corners here and not wait for createSpecialBreakpoints()'s
|
||||
// // response, as the command is synchronous from Creator's point of view,
|
||||
// // and even if it fails (e.g. due to stripped binaries), continuing with
|
||||
// // the start up is the best we can do.
|
||||
|
||||
// if (!rp.commandsAfterConnect.isEmpty()) {
|
||||
// const QString commands = expand(rp.commandsAfterConnect);
|
||||
// for (const QString &command : commands.split('\n'))
|
||||
// runCommand({command, NativeCommand});
|
||||
// }
|
||||
}
|
||||
|
||||
DebuggerEngine *createDapEngine()
|
||||
{
|
||||
return new DapEngine;
|
||||
}
|
||||
|
||||
} // Debugger::Internal
|
97
src/plugins/debugger/dap/dapengine.h
Normal file
97
src/plugins/debugger/dap/dapengine.h
Normal file
@@ -0,0 +1,97 @@
|
||||
// Copyright (C) 2023 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <debugger/debuggerengine.h>
|
||||
#include <utils/qtcprocess.h>
|
||||
|
||||
#include <QVariant>
|
||||
|
||||
namespace Debugger::Internal {
|
||||
|
||||
class DebuggerCommand;
|
||||
class GdbMi;
|
||||
|
||||
/*
|
||||
* A debugger engine for the debugger adapter protocol.
|
||||
*/
|
||||
|
||||
class DapEngine : public DebuggerEngine
|
||||
{
|
||||
public:
|
||||
DapEngine();
|
||||
|
||||
private:
|
||||
void executeStepIn(bool) override;
|
||||
void executeStepOut() override;
|
||||
void executeStepOver(bool) override;
|
||||
|
||||
void setupEngine() override;
|
||||
void shutdownInferior() override;
|
||||
void shutdownEngine() override;
|
||||
|
||||
bool canHandleToolTip(const DebuggerToolTipContext &) const override;
|
||||
|
||||
void continueInferior() override;
|
||||
void interruptInferior() override;
|
||||
|
||||
void executeRunToLine(const ContextData &data) override;
|
||||
void executeRunToFunction(const QString &functionName) override;
|
||||
void executeJumpToLine(const ContextData &data) override;
|
||||
|
||||
void activateFrame(int index) override;
|
||||
void selectThread(const Thread &thread) override;
|
||||
|
||||
bool acceptsBreakpoint(const BreakpointParameters &bp) const override;
|
||||
void insertBreakpoint(const Breakpoint &bp) override;
|
||||
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 {}
|
||||
void reloadFullStack() override {}
|
||||
|
||||
bool supportsThreads() const { return true; }
|
||||
void updateItem(const QString &iname) override;
|
||||
|
||||
void runCommand(const DebuggerCommand &cmd) override;
|
||||
void postDirectCommand(const QJsonObject &ob);
|
||||
|
||||
void refreshLocation(const GdbMi &reportedLocation);
|
||||
void refreshStack(const GdbMi &stack);
|
||||
void refreshLocals(const GdbMi &vars);
|
||||
void refreshModules(const GdbMi &modules);
|
||||
void refreshState(const GdbMi &reportedState);
|
||||
void refreshSymbols(const GdbMi &symbols);
|
||||
|
||||
QString errorMessage(QProcess::ProcessError error) const;
|
||||
bool hasCapability(unsigned cap) const override;
|
||||
|
||||
void claimInitialBreakpoints();
|
||||
|
||||
void handleDabStarted();
|
||||
void handleDabLaunch();
|
||||
void handleDabConfigurationDone();
|
||||
|
||||
void handleDapDone();
|
||||
void readDapStandardOutput();
|
||||
void readDapStandardError();
|
||||
void handleOutput(const QJsonDocument &data);
|
||||
void handleResponse(const QString &ba);
|
||||
void updateAll() override;
|
||||
void updateLocals() override;
|
||||
|
||||
QByteArray m_inbuffer;
|
||||
Utils::QtcProcess m_proc;
|
||||
};
|
||||
|
||||
} // Debugger::Internal
|
@@ -122,6 +122,12 @@ Project {
|
||||
files: ["pdbengine.cpp", "pdbengine.h"]
|
||||
}
|
||||
|
||||
Group {
|
||||
name: "dap"
|
||||
prefix: "dap/"
|
||||
files: ["dapengine.cpp", "dapengine.h"]
|
||||
}
|
||||
|
||||
Group {
|
||||
name: "uvsc"
|
||||
prefix: "uvsc/"
|
||||
|
@@ -99,6 +99,7 @@ enum DebuggerEngineType
|
||||
CdbEngineType = 0x004,
|
||||
PdbEngineType = 0x008,
|
||||
LldbEngineType = 0x100,
|
||||
DapEngineType = 0x200,
|
||||
UvscEngineType = 0x1000
|
||||
};
|
||||
|
||||
|
@@ -2587,6 +2587,7 @@ bool DebuggerRunParameters::isCppDebugging() const
|
||||
return cppEngineType == GdbEngineType
|
||||
|| cppEngineType == LldbEngineType
|
||||
|| cppEngineType == CdbEngineType
|
||||
|| cppEngineType == DapEngineType
|
||||
|| cppEngineType == UvscEngineType;
|
||||
}
|
||||
|
||||
|
@@ -174,8 +174,12 @@ void DebuggerItem::reinitializeFromFile(QString *error, Utils::Environment *cust
|
||||
return;
|
||||
}
|
||||
m_abis.clear();
|
||||
|
||||
if (output.contains("gdb")) {
|
||||
m_engineType = GdbEngineType;
|
||||
// FIXME: HACK while introducing DAP support
|
||||
if (m_command.fileName().endsWith("-dap"))
|
||||
m_engineType = DapEngineType;
|
||||
|
||||
// Version
|
||||
bool isMacGdb, isQnxGdb;
|
||||
@@ -211,6 +215,7 @@ void DebuggerItem::reinitializeFromFile(QString *error, Utils::Environment *cust
|
||||
//! \note If unable to determine the GDB ABI, no ABI is appended to m_abis here.
|
||||
return;
|
||||
}
|
||||
|
||||
if (output.contains("lldb") || output.startsWith("LLDB")) {
|
||||
m_engineType = LldbEngineType;
|
||||
m_abis = Abi::abisOfBinary(m_command);
|
||||
@@ -278,6 +283,8 @@ QString DebuggerItem::engineTypeName() const
|
||||
return QLatin1String("CDB");
|
||||
case LldbEngineType:
|
||||
return QLatin1String("LLDB");
|
||||
case DapEngineType:
|
||||
return QLatin1String("DAP");
|
||||
case UvscEngineType:
|
||||
return QLatin1String("UVSC");
|
||||
default:
|
||||
|
@@ -813,6 +813,14 @@ void DebuggerItemManagerPrivate::autoDetectGdbOrLldbDebuggers(const FilePaths &s
|
||||
item.setUnexpandedDisplayName(name.arg(item.engineTypeName()).arg(command.toUserOutput()));
|
||||
m_model->addDebugger(item);
|
||||
logMessages.append(Tr::tr("Found: \"%1\"").arg(command.toUserOutput()));
|
||||
|
||||
if (item.engineType() == GdbEngineType) {
|
||||
if (item.version().startsWith("GNU gdb (GDB) 14.0.50.2023")) {
|
||||
// FIXME: Use something more robust
|
||||
item.setEngineType(DapEngineType);
|
||||
m_model->addDebugger(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (logMessage)
|
||||
*logMessage = logMessages.join('\n');
|
||||
|
@@ -70,6 +70,7 @@ DebuggerEngine *createPdbEngine();
|
||||
DebuggerEngine *createQmlEngine();
|
||||
DebuggerEngine *createLldbEngine();
|
||||
DebuggerEngine *createUvscEngine();
|
||||
DebuggerEngine *createDapEngine();
|
||||
|
||||
static QString noEngineMessage()
|
||||
{
|
||||
@@ -509,6 +510,9 @@ void DebuggerRunTool::start()
|
||||
case UvscEngineType:
|
||||
m_engine = createUvscEngine();
|
||||
break;
|
||||
case DapEngineType:
|
||||
m_engine = createDapEngine();
|
||||
break;
|
||||
default:
|
||||
if (!m_runParameters.isQmlDebugging) {
|
||||
reportFailure(noEngineMessage() + '\n' +
|
||||
|
Reference in New Issue
Block a user