Commit e4f15f79 authored by Héctor Mesa Jiménez's avatar Héctor Mesa Jiménez Committed by Christoph Cullmann
Browse files

gdbplugin: Debugger Adapter Protocol backend

Profiles for:
- delve (go)
- debugpy (python)
- perl language server

Profiles are defined in a resource json file, but can be overriden in a
user config file (<user config>/gdbplugin/dap.json).

A profile allows to define variables, which will be shown at the GUI
panel. The format variable is:

	${variable}
	${variable.name}
	${#variable.name}

A variable is considered as a string, but can be transformed using
filters:

	${variable|list} -> transform to a string list (parsed as a list
of arguments)
	${variable|int} -> transform to integer
	${variable|bool} -> transform to boolean
	${variable|base} -> consider the variable a file, and return a
basename
	${variable|dir} -> consider the variable a file, and return the
parent directory

There are 6 special variables:

- ${file}: use the GDB profile widget for the executable path.
- ${workdir}: use the GDB profile widget for the working directory.
- ${args}: use the GDB profile widget for the arguments.
- ${pid}: use an integer input widget.
- ${#run.host} -> reference to the json attribute "run.host"
- ${#run.port} -> reference to the json attribute "run.port"

The bottom-panel allows to choose the backend (GDB or a DAP server).
The input widgets are adapted to match the variables in the selected
profile.

In the lateral panel, variables a grouped by scopes (provided by the
client).

Debugging actions are enabled only if the client supports them.
parent eb5736ac
......@@ -5,19 +5,48 @@ endif()
kate_add_plugin(kategdbplugin)
target_compile_definitions(kategdbplugin PRIVATE TRANSLATION_DOMAIN="kategdbplugin")
target_link_libraries(kategdbplugin PRIVATE KF5::I18n KF5::TextEditor)
target_link_libraries(kategdbplugin PRIVATE kateshared KF5::I18n KF5::TextEditor)
include(ECMQtDeclareLoggingCategory)
ecm_qt_declare_logging_category(
DEBUG_SOURCES
HEADER dapclient_debug.h
IDENTIFIER DAPCLIENT
CATEGORY_NAME "kategdbplugin"
)
ki18n_wrap_ui(kategdbplugin advanced_settings.ui)
set(DAP_SOURCES
dap/bus.cpp
dap/socketbus.cpp
dap/processbus.cpp
dap/client.cpp
dap/entities.cpp
dap/messages.h
dap/settings.cpp
dap/socketprocessbus.cpp
dap/bus_selector.cpp
)
target_sources(
kategdbplugin
PRIVATE
plugin_kategdb.cpp
debugview_iface.cpp
debugview.cpp
configview.cpp
ioview.cpp
localsview.cpp
advanced_settings.cpp
${DAP_SOURCES}
${DEBUG_SOURCES}
json_placeholders.cpp
debugview_dap.cpp
gdbvariableparser.cpp
backend.cpp
plugin.qrc
)
/**
* Description: Common interface for GDB and DAP clients
*
* SPDX-FileCopyrightText: 2022 Héctor Mesa Jiménez <wmj.py@gmx.com>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "backend.h"
#include "debugview.h"
#include "debugview_dap.h"
#include <KMessageBox>
#include <optional>
Backend::Backend(QObject *parent)
: DebugViewInterface(parent)
, m_debugger(nullptr)
{
}
void Backend::runDebugger(const GDBTargetConf &conf, const QStringList &ioFifos)
{
if (m_debugger && m_debugger->debuggerRunning()) {
KMessageBox::sorry(nullptr, i18n("A debugging session is on course. Please, use re-run or stop the current session."));
return;
}
DebugView *gdb;
if (m_mode != GDB) {
unbind();
m_debugger = gdb = new DebugView(this);
m_mode = GDB;
bind();
} else {
gdb = dynamic_cast<DebugView *>(m_debugger);
}
gdb->runDebugger(conf, ioFifos);
if (m_displayQueryLocals) {
gdb->slotQueryLocals(*m_displayQueryLocals);
}
}
void Backend::runDebugger(const DAPTargetConf &conf)
{
if (m_debugger && m_debugger->debuggerRunning()) {
KMessageBox::sorry(nullptr, i18n("A debugging session is on course. Please, use re-run or stop the current session."));
return;
}
DapDebugView *dap;
unbind();
m_debugger = dap = new DapDebugView(this);
m_mode = DAP;
bind();
dap->runDebugger(conf);
if (m_displayQueryLocals) {
dap->slotQueryLocals(*m_displayQueryLocals);
}
}
void Backend::bind()
{
connect(m_debugger, &DebugViewInterface::debugLocationChanged, this, &DebugViewInterface::debugLocationChanged);
connect(m_debugger, &DebugViewInterface::breakPointSet, this, &DebugViewInterface::breakPointSet);
connect(m_debugger, &DebugViewInterface::breakPointCleared, this, &DebugViewInterface::breakPointCleared);
connect(m_debugger, &DebugViewInterface::clearBreakpointMarks, this, &DebugViewInterface::clearBreakpointMarks);
connect(m_debugger, &DebugViewInterface::stackFrameInfo, this, &DebugViewInterface::stackFrameInfo);
connect(m_debugger, &DebugViewInterface::stackFrameChanged, this, &DebugViewInterface::stackFrameChanged);
connect(m_debugger, &DebugViewInterface::threadInfo, this, &DebugViewInterface::threadInfo);
connect(m_debugger, &DebugViewInterface::variableInfo, this, &DebugViewInterface::variableInfo);
connect(m_debugger, &DebugViewInterface::variableScopeOpened, this, &DebugViewInterface::variableScopeOpened);
connect(m_debugger, &DebugViewInterface::variableScopeClosed, this, &DebugViewInterface::variableScopeClosed);
connect(m_debugger, &DebugViewInterface::outputText, this, &DebugViewInterface::outputText);
connect(m_debugger, &DebugViewInterface::outputError, this, &DebugViewInterface::outputError);
connect(m_debugger, &DebugViewInterface::readyForInput, this, &DebugViewInterface::readyForInput);
connect(m_debugger, &DebugViewInterface::programEnded, this, &DebugViewInterface::programEnded);
connect(m_debugger, &DebugViewInterface::gdbEnded, this, &DebugViewInterface::gdbEnded);
connect(m_debugger, &DebugViewInterface::sourceFileNotFound, this, &DebugViewInterface::sourceFileNotFound);
connect(m_debugger, &DebugViewInterface::scopesInfo, this, &DebugViewInterface::scopesInfo);
connect(m_debugger, &DebugViewInterface::debuggerCapabilitiesChanged, this, &DebugViewInterface::debuggerCapabilitiesChanged);
connect(m_debugger, &DebugViewInterface::debuggeeOutput, this, &DebugViewInterface::debuggeeOutput);
}
void Backend::unbind()
{
if (!m_debugger)
return;
disconnect(m_debugger, nullptr, this, nullptr);
delete m_debugger;
}
bool Backend::debuggerRunning() const
{
return m_debugger && m_debugger->debuggerRunning();
}
bool Backend::debuggerBusy() const
{
return !m_debugger || m_debugger->debuggerBusy();
}
bool Backend::hasBreakpoint(QUrl const &url, int line) const
{
return m_debugger && m_debugger->hasBreakpoint(url, line);
}
bool Backend::supportsMovePC() const
{
return m_debugger && m_debugger->supportsMovePC();
}
bool Backend::supportsRunToCursor() const
{
return m_debugger && m_debugger->supportsRunToCursor();
}
bool Backend::canSetBreakpoints() const
{
return m_debugger && m_debugger->canSetBreakpoints();
}
bool Backend::canMove() const
{
return m_debugger && m_debugger->canMove();
}
void Backend::toggleBreakpoint(QUrl const &url, int line)
{
if (m_debugger)
m_debugger->toggleBreakpoint(url, line);
}
void Backend::movePC(QUrl const &url, int line)
{
if (m_debugger)
m_debugger->movePC(url, line);
}
void Backend::runToCursor(QUrl const &url, int line)
{
if (m_debugger)
m_debugger->runToCursor(url, line);
}
void Backend::issueCommand(QString const &cmd)
{
if (m_debugger)
m_debugger->issueCommand(cmd);
}
QString Backend::targetName() const
{
if (m_debugger)
return m_debugger->targetName();
return QString();
}
void Backend::setFileSearchPaths(const QStringList &paths)
{
if (m_debugger)
m_debugger->setFileSearchPaths(paths);
}
void Backend::slotInterrupt()
{
if (m_debugger)
m_debugger->slotInterrupt();
}
void Backend::slotStepInto()
{
if (m_debugger)
m_debugger->slotStepInto();
}
void Backend::slotStepOver()
{
if (m_debugger)
m_debugger->slotStepOver();
}
void Backend::slotStepOut()
{
if (m_debugger)
m_debugger->slotStepOut();
}
void Backend::slotContinue()
{
if (m_debugger)
m_debugger->slotContinue();
}
void Backend::slotKill()
{
if (m_debugger)
m_debugger->slotKill();
}
void Backend::slotReRun()
{
if (m_debugger)
m_debugger->slotReRun();
}
QString Backend::slotPrintVariable(const QString &variable)
{
if (m_debugger)
return m_debugger->slotPrintVariable(variable);
return QString();
}
void Backend::slotQueryLocals(bool display)
{
if (m_debugger) {
m_debugger->slotQueryLocals(display);
m_displayQueryLocals = std::nullopt;
} else {
m_displayQueryLocals = display;
}
}
void Backend::changeStackFrame(int index)
{
if (m_debugger)
m_debugger->changeStackFrame(index);
}
void Backend::changeThread(int thread)
{
if (m_debugger)
m_debugger->changeThread(thread);
}
void Backend::changeScope(int scopeId)
{
if (m_debugger)
m_debugger->changeScope(scopeId);
}
/**
* Description: Debugger backend selector
*
* SPDX-FileCopyrightText: 2022 Héctor Mesa Jiménez <wmj.py@gmx.com>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef BACKEND_H_
#define BACKEND_H_
#include <QObject>
#include <memory>
#include <optional>
#include "configview.h"
#include "debugview_iface.h"
class Backend : public DebugViewInterface
{
Q_OBJECT
public:
Backend(QObject *parent);
~Backend() override = default;
void runDebugger(const GDBTargetConf &conf, const QStringList &ioFifos);
void runDebugger(const DAPTargetConf &conf);
bool debuggerRunning() const override;
bool debuggerBusy() const override;
bool hasBreakpoint(QUrl const &url, int line) const override;
bool supportsMovePC() const override;
bool supportsRunToCursor() const override;
bool canSetBreakpoints() const override;
bool canMove() const override;
void toggleBreakpoint(QUrl const &url, int line) override;
void movePC(QUrl const &url, int line) override;
void runToCursor(QUrl const &url, int line) override;
void issueCommand(QString const &cmd) override;
QString targetName() const override;
void setFileSearchPaths(const QStringList &paths) override;
public Q_SLOTS:
void slotInterrupt() override;
void slotStepInto() override;
void slotStepOver() override;
void slotStepOut() override;
void slotContinue() override;
void slotKill() override;
void slotReRun() override;
QString slotPrintVariable(const QString &variable) override;
void slotQueryLocals(bool display) override;
void changeStackFrame(int index) override;
void changeThread(int thread) override;
void changeScope(int scopeId) override;
private:
enum DebugMode { NONE, GDB, DAP } m_mode = NONE;
void bind();
void unbind();
DebugViewInterface *m_debugger;
std::optional<bool> m_displayQueryLocals = std::nullopt;
};
#endif
This diff is collapsed.
......@@ -17,11 +17,17 @@
#include <QBoxLayout>
#include <QCheckBox>
#include <QComboBox>
#include <QHash>
#include <QJsonObject>
#include <QLabel>
#include <QLineEdit>
#include <QResizeEvent>
#include <QSpinBox>
#include <QToolButton>
#include <QVariantHash>
#include <QWidget>
#include <optional>
#include <utility>
#include <QList>
......@@ -40,11 +46,25 @@ struct GDBTargetConf {
QStringList srcPaths;
};
struct DAPAdapterSettings {
int index;
QJsonObject settings;
QStringList variables;
};
struct DAPTargetConf {
QString targetName;
QString debugger;
QString debuggerProfile;
QVariantHash variables;
std::optional<DAPAdapterSettings> dapSettings;
};
class ConfigView : public QWidget
{
Q_OBJECT
public:
enum TargetStringOrder { NameIndex = 0, ExecIndex, WorkDirIndex, ArgsIndex, GDBIndex, CustomStartIndex };
enum TargetStringOrder { NameIndex = 0, ExecIndex, WorkDirIndex, ArgsIndex, GDBIndex, DebuggerKey, DebuggerProfile, DAPVariables, CustomStartIndex };
ConfigView(QWidget *parent, KTextEditor::MainWindow *mainWin);
~ConfigView() override;
......@@ -55,9 +75,11 @@ public:
void readConfig(const KConfigGroup &config);
void writeConfig(KConfigGroup &config);
const GDBTargetConf currentTarget() const;
const GDBTargetConf currentGDBTarget() const;
const DAPTargetConf currentDAPTarget(bool full = false) const;
bool takeFocusAlways() const;
bool showIOTab() const;
bool debuggerIsGDB() const;
Q_SIGNALS:
void showIO(bool show);
......@@ -79,11 +101,17 @@ protected:
private:
void saveCurrentToIndex(int index);
void loadFromIndex(int index);
int loadFromIndex(int index);
void setAdvancedOptions();
std::pair<QLabel *, QLineEdit *> &getDapField(const QString &fieldName);
void refreshUI();
void readDAPSettings();
private:
KTextEditor::MainWindow *m_mainWindow;
QComboBox *m_clientCombo;
QComboBox *m_targetCombo;
int m_currentTarget = 0;
QToolButton *m_addTarget;
......@@ -96,6 +124,7 @@ private:
QLineEdit *m_workingDirectory;
QToolButton *m_browseDir;
QSpinBox *m_processId;
QLineEdit *m_arguments;
......@@ -108,8 +137,12 @@ private:
QLabel *m_execLabel;
QLabel *m_workDirLabel;
QLabel *m_argumentsLabel;
QLabel *m_processIdLabel;
KSelectAction *m_targetSelectAction = nullptr;
QHash<QString, std::pair<QLabel *, QLineEdit *>> m_dapFields;
QHash<QString, QHash<QString, DAPAdapterSettings>> m_dapAdapterSettings;
AdvancedGDBSettings *m_advanced;
};
......
/*
SPDX-FileCopyrightText: 2022 Héctor Mesa Jiménez <wmj.py@gmx.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "bus.h"
namespace dap
{
Bus::Bus(QObject *parent)
: QObject(parent)
, m_state(State::None)
{
}
Bus::State Bus::state() const
{
return m_state;
}
void Bus::setState(State state)
{
if (state == m_state)
return;
m_state = state;
Q_EMIT stateChanged(state);
switch (state) {
case State::Running:
Q_EMIT running();
break;
case State::Closed:
Q_EMIT closed();
break;
default:;
}
}
}
/*
SPDX-FileCopyrightText: 2022 Héctor Mesa Jiménez <wmj.py@gmx.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef DAP_BUS_H
#define DAP_BUS_H
#include <QObject>
class QJsonObject;
class QByteArray;
namespace dap
{
namespace settings
{
class BusSettings;
}
class Bus : public QObject
{
Q_OBJECT
public:
enum class State { None, Running, Closed };
explicit Bus(QObject *parent = nullptr);
virtual ~Bus() = default;
virtual QByteArray read() = 0;
virtual quint16 write(const QByteArray &data) = 0;
State state() const;
virtual bool start(const settings::BusSettings &configuration) = 0;
virtual void close() = 0;
Q_SIGNALS:
void readyRead();
void stateChanged(State state);
void running();
void closed();
void error(const QString &errorMessage);
void serverOutput(const QString &message);
void processOutput(const QString &message);
protected:
void setState(State state);
private:
State m_state;
};
}
#endif // DAP_BUS_H
/*
SPDX-FileCopyrightText: 2022 Héctor Mesa Jiménez <wmj.py@gmx.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include <QJsonObject>
#include "bus.h"
#include "processbus.h"
#include "socketbus.h"
#include "socketprocessbus.h"
#include "settings.h"
#include "bus_selector.h"
namespace dap
{
Bus *createBus(const settings::BusSettings &configuration)
{
const bool has_command = configuration.hasCommand();
const bool has_connection = configuration.hasConnection();
if (has_command && has_connection) {
return new SocketProcessBus();
}
if (has_command) {
return new ProcessBus();
}
if (has_connection) {
return new SocketBus();
}
return nullptr;
}
}
/*
SPDX-FileCopyrightText: 2022 Héctor Mesa Jiménez <wmj.py@gmx.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef DAP_BUS_SELECTOR_H
#define DAP_BUS_SELECTOR_H
class QJsonObject;
namespace dap
{