diff options
author | hjk <hjk@qt.io> | 2023-01-03 15:12:43 +0100 |
---|---|---|
committer | hjk <hjk@qt.io> | 2023-04-06 13:04:43 +0000 |
commit | 46b9cd952a93b98335dfb5761025d7ca27f40d44 (patch) | |
tree | d03843214fd2ba2e7118c11019bd7aa13c9f539f | |
parent | 45c2e3fe58fd9b5a85450ff18e0a40e701ebf8d6 (diff) | |
download | qt-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.txt | 1 | ||||
-rw-r--r-- | src/plugins/debugger/dap/dapengine.cpp | 730 | ||||
-rw-r--r-- | src/plugins/debugger/dap/dapengine.h | 97 | ||||
-rw-r--r-- | src/plugins/debugger/debugger.qbs | 6 | ||||
-rw-r--r-- | src/plugins/debugger/debuggerconstants.h | 1 | ||||
-rw-r--r-- | src/plugins/debugger/debuggerengine.cpp | 1 | ||||
-rw-r--r-- | src/plugins/debugger/debuggeritem.cpp | 7 | ||||
-rw-r--r-- | src/plugins/debugger/debuggeritemmanager.cpp | 8 | ||||
-rw-r--r-- | src/plugins/debugger/debuggerruncontrol.cpp | 4 |
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 ¶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 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' + |