summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorhjk <hjk@qt.io>2023-01-03 15:12:43 +0100
committerhjk <hjk@qt.io>2023-04-06 13:04:43 +0000
commit46b9cd952a93b98335dfb5761025d7ca27f40d44 (patch)
treed03843214fd2ba2e7118c11019bd7aa13c9f539f
parent45c2e3fe58fd9b5a85450ff18e0a40e701ebf8d6 (diff)
downloadqt-creator-46b9cd952a93b98335dfb5761025d7ca27f40d44.tar.gz
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>
-rw-r--r--src/plugins/debugger/CMakeLists.txt1
-rw-r--r--src/plugins/debugger/dap/dapengine.cpp730
-rw-r--r--src/plugins/debugger/dap/dapengine.h97
-rw-r--r--src/plugins/debugger/debugger.qbs6
-rw-r--r--src/plugins/debugger/debuggerconstants.h1
-rw-r--r--src/plugins/debugger/debuggerengine.cpp1
-rw-r--r--src/plugins/debugger/debuggeritem.cpp7
-rw-r--r--src/plugins/debugger/debuggeritemmanager.cpp8
-rw-r--r--src/plugins/debugger/debuggerruncontrol.cpp4
9 files changed, 855 insertions, 0 deletions
diff --git a/src/plugins/debugger/CMakeLists.txt b/src/plugins/debugger/CMakeLists.txt
index f5f45deb4a..d06e82a27e 100644
--- a/src/plugins/debugger/CMakeLists.txt
+++ b/src/plugins/debugger/CMakeLists.txt
@@ -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
diff --git a/src/plugins/debugger/dap/dapengine.cpp b/src/plugins/debugger/dap/dapengine.cpp
new file mode 100644
index 0000000000..6efe29d37d
--- /dev/null
+++ b/src/plugins/debugger/dap/dapengine.cpp
@@ -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 &params = 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 &params = 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
diff --git a/src/plugins/debugger/dap/dapengine.h b/src/plugins/debugger/dap/dapengine.h
new file mode 100644
index 0000000000..045cb3044a
--- /dev/null
+++ b/src/plugins/debugger/dap/dapengine.h
@@ -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
diff --git a/src/plugins/debugger/debugger.qbs b/src/plugins/debugger/debugger.qbs
index 03b57c5885..be2adfc04d 100644
--- a/src/plugins/debugger/debugger.qbs
+++ b/src/plugins/debugger/debugger.qbs
@@ -123,6 +123,12 @@ Project {
}
Group {
+ name: "dap"
+ prefix: "dap/"
+ files: ["dapengine.cpp", "dapengine.h"]
+ }
+
+ Group {
name: "uvsc"
prefix: "uvsc/"
files: [
diff --git a/src/plugins/debugger/debuggerconstants.h b/src/plugins/debugger/debuggerconstants.h
index b071713a84..06ccb94f1b 100644
--- a/src/plugins/debugger/debuggerconstants.h
+++ b/src/plugins/debugger/debuggerconstants.h
@@ -99,6 +99,7 @@ enum DebuggerEngineType
CdbEngineType = 0x004,
PdbEngineType = 0x008,
LldbEngineType = 0x100,
+ DapEngineType = 0x200,
UvscEngineType = 0x1000
};
diff --git a/src/plugins/debugger/debuggerengine.cpp b/src/plugins/debugger/debuggerengine.cpp
index 1013fbe73c..d387ce142c 100644
--- a/src/plugins/debugger/debuggerengine.cpp
+++ b/src/plugins/debugger/debuggerengine.cpp
@@ -2587,6 +2587,7 @@ bool DebuggerRunParameters::isCppDebugging() const
return cppEngineType == GdbEngineType
|| cppEngineType == LldbEngineType
|| cppEngineType == CdbEngineType
+ || cppEngineType == DapEngineType
|| cppEngineType == UvscEngineType;
}
diff --git a/src/plugins/debugger/debuggeritem.cpp b/src/plugins/debugger/debuggeritem.cpp
index 0769d293de..a6daacd16d 100644
--- a/src/plugins/debugger/debuggeritem.cpp
+++ b/src/plugins/debugger/debuggeritem.cpp
@@ -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:
diff --git a/src/plugins/debugger/debuggeritemmanager.cpp b/src/plugins/debugger/debuggeritemmanager.cpp
index 1c9a6e68dc..ab1f53076d 100644
--- a/src/plugins/debugger/debuggeritemmanager.cpp
+++ b/src/plugins/debugger/debuggeritemmanager.cpp
@@ -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');
diff --git a/src/plugins/debugger/debuggerruncontrol.cpp b/src/plugins/debugger/debuggerruncontrol.cpp
index e8ddb33b2c..85b48880fe 100644
--- a/src/plugins/debugger/debuggerruncontrol.cpp
+++ b/src/plugins/debugger/debuggerruncontrol.cpp
@@ -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' +