[utilities/kate] /: gdbplugin: support execution environment prefix and path mapping

Christoph Cullmann null at kde.org
Mon Aug 25 17:27:31 BST 2025


Git commit e63b6269bba556983022fd793b2b7b4be7ea4599 by Christoph Cullmann, on behalf of Mark Nauwelaerts.
Committed on 25/08/2025 at 16:21.
Pushed by cullmann into branch 'master'.

gdbplugin: support execution environment prefix and path mapping

M  +23   -0    addons/gdbplugin/configview.cpp
M  +3    -0    addons/gdbplugin/configview.h
M  +49   -13   addons/gdbplugin/dap/client.cpp
M  +5    -2    addons/gdbplugin/dap/client.h
M  +29   -29   addons/gdbplugin/dap/entities.cpp
M  +17   -22   addons/gdbplugin/dap/entities.h
M  +2    -1    addons/gdbplugin/dapbackend.cpp
M  +2    -0    addons/gdbplugin/dapbackend.h
M  +199  -5    doc/kate/plugins.docbook

https://invent.kde.org/utilities/kate/-/commit/e63b6269bba556983022fd793b2b7b4be7ea4599

diff --git a/addons/gdbplugin/configview.cpp b/addons/gdbplugin/configview.cpp
index d68b096963..e963ed7625 100644
--- a/addons/gdbplugin/configview.cpp
+++ b/addons/gdbplugin/configview.cpp
@@ -41,6 +41,7 @@
 #include "plugin_kategdb.h"
 #include "sessionconfig.h"
 #include "target_json_keys.h"
+#include <exec_utils.h>
 #include <json_utils.h>
 #include <ktexteditor_utils.h>
 
@@ -431,6 +432,28 @@ DAPTargetConf ConfigView::currentDAPTarget(bool full, QString &errorMessage) con
         auto &settings = cfg.dapSettings->settings;
         settings = json::merge(settings, top);
 
+        // now we have all that, also consider execution prefix/environment settings
+        auto execConfig = Utils::ExecConfig::load(settings, projectConfig, {});
+        auto prefix = execConfig.prefix();
+        if (prefix.isArray()) {
+            // prepare map
+            cfg.dapSettings->pathMap = execConfig.init_mapping(view);
+            // adjust command-line
+            auto cmdline = settings.value(dap::settings::COMMAND).toArray();
+            auto newcmdline = prefix.toArray();
+            for (const auto &c : cmdline)
+                newcmdline += c;
+            settings[dap::settings::COMMAND] = newcmdline;
+            // also add environment for prefix runtime
+            auto M_ENV = QStringLiteral("environment");
+            auto env_v = settings[M_ENV];
+            auto env = env_v.isObject() ? env_v.toObject() : QJsonObject();
+            env[Utils::ExecConfig::ENV_KATE_EXEC_PLUGIN] = QStringLiteral("dap");
+            env[QStringLiteral("KATE_EXEC_SERVER")] = cfg.debugger;
+            env[QStringLiteral("KATE_EXEC_PROFILE")] = cfg.debuggerProfile;
+            settings[M_ENV] = env;
+        }
+
         // var expansion in cmdline
         auto cmdline = settings.value(dap::settings::COMMAND).toArray();
         QStringList cmd;
diff --git a/addons/gdbplugin/configview.h b/addons/gdbplugin/configview.h
index 8fcc09ccb7..ae79ad8a9d 100644
--- a/addons/gdbplugin/configview.h
+++ b/addons/gdbplugin/configview.h
@@ -16,6 +16,8 @@
 
 #include <optional>
 
+#include <exec_utils.h>
+
 class QPushButton;
 class QComboBox;
 class QFrame;
@@ -55,6 +57,7 @@ struct DAPAdapterSettings {
     // profile object = merged run/configuration
     QJsonObject settings;
     QStringList variables;
+    Utils::PathMappingPtr pathMap;
 };
 
 struct DAPTargetConf {
diff --git a/addons/gdbplugin/dap/client.cpp b/addons/gdbplugin/dap/client.cpp
index 211ec1339b..00b9cd5e73 100644
--- a/addons/gdbplugin/dap/client.cpp
+++ b/addons/gdbplugin/dap/client.cpp
@@ -6,6 +6,7 @@
 #include "client.h"
 #include <QJsonArray>
 #include <QJsonDocument>
+#include <QFileInfo>
 #include <limits>
 
 #include "dap/bus_selector.h"
@@ -13,26 +14,61 @@
 
 #include "messages.h"
 
+#include <exec_utils.h>
+
 namespace dap
 {
 constexpr int MAX_HEADER_SIZE = 1 << 16;
 
-Client::Client(const settings::ProtocolSettings &protocolSettings, Bus *bus, QObject *parent)
+struct ClientMessageContext : public MessageContext {
+    Utils::PathMappingPtr pathMap;
+
+    ClientMessageContext(Utils::PathMappingPtr pm)
+        : pathMap(pm)
+    {
+    }
+
+    QString toRemote(const QUrl &url) override
+    {
+        if (!pathMap)
+            return url.toLocalFile();
+
+        auto result = Utils::mapPath(*pathMap, url, true).toLocalFile();
+        // if not able to map (e.g. src not present in remote env), then pass as-is
+        // e.g. gdb might be able to find some heuristic match in symbol info
+        return result.isEmpty() ? url.toLocalFile() : result;
+    }
+
+    virtual QUrl toLocal(const QString &path) override
+    {
+        auto url = QUrl::fromLocalFile(path);
+        // pass along relative paths; can not be reasonably mapped
+        // is a matter of search paths to resolve those
+        if (!pathMap || QFileInfo(path).isRelative())
+            return url;
+
+        return Utils::mapPath(*pathMap, url, false);
+    }
+};
+
+Client::Client(const settings::ProtocolSettings &protocolSettings, Bus *bus, Utils::PathMappingPtr pm, QObject *parent)
     : QObject(parent)
     , m_bus(bus)
     , m_managedBus(false)
     , m_protocol(protocolSettings)
     , m_launchCommand(extractCommand(protocolSettings.launchRequest))
 {
+    m_msgContext = std::make_unique<ClientMessageContext>(pm);
     bind();
 }
 
-Client::Client(const settings::ClientSettings &clientSettings, QObject *parent)
+Client::Client(const settings::ClientSettings &clientSettings, Utils::PathMappingPtr pm, QObject *parent)
     : QObject(parent)
     , m_managedBus(true)
     , m_protocol(clientSettings.protocolSettings)
     , m_launchCommand(extractCommand(clientSettings.protocolSettings.launchRequest))
 {
+    m_msgContext = std::make_unique<ClientMessageContext>(pm);
     m_bus = createBus(clientSettings.busSettings);
     m_bus->setParent(this);
     bind();
@@ -191,7 +227,7 @@ void Client::processEvent(const QJsonObject &msg)
         const int exitCode = body[QStringLiteral("exitCode")].toInt(-1);
         Q_EMIT debuggeeExited(exitCode);
     } else if (DAP_OUTPUT == event) {
-        Q_EMIT outputProduced(Output(body));
+        Q_EMIT outputProduced(Output(body, *m_msgContext));
     } else if (QStringLiteral("process") == event) {
         Q_EMIT debuggingProcess(ProcessInfo(body));
     } else if (QStringLiteral("thread") == event) {
@@ -203,7 +239,7 @@ void Client::processEvent(const QJsonObject &msg)
     } else if (QStringLiteral("continued") == event) {
         Q_EMIT debuggeeContinued(ContinuedEvent(body));
     } else if (DAP_BREAKPOINT == event) {
-        Q_EMIT breakpointChanged(BreakpointEvent(body));
+        Q_EMIT breakpointChanged(BreakpointEvent(body, *m_msgContext));
     } else {
         qCWarning(DAPCLIENT, "unsupported event: %ls", qUtf16Printable(event));
     }
@@ -242,7 +278,7 @@ void Client::processResponseStackTrace(const Response &response, const QJsonValu
 {
     const int threadId = request.toObject()[DAP_THREAD_ID].toInt();
     if (response.success) {
-        Q_EMIT stackTrace(threadId, StackTraceInfo(response.body.toObject()));
+        Q_EMIT stackTrace(threadId, StackTraceInfo(response.body.toObject(), *m_msgContext));
     } else {
         Q_EMIT stackTrace(threadId, StackTraceInfo());
     }
@@ -252,7 +288,7 @@ void Client::processResponseScopes(const Response &response, const QJsonValue &r
 {
     const int frameId = request.toObject()[DAP_FRAME_ID].toInt();
     if (response.success) {
-        Q_EMIT scopes(frameId, Scope::parseList(response.body.toObject()[DAP_SCOPES].toArray()));
+        Q_EMIT scopes(frameId, Scope::parseList(response.body.toObject()[DAP_SCOPES].toArray(), *m_msgContext));
     } else {
         Q_EMIT scopes(frameId, QList<Scope>());
     }
@@ -314,7 +350,7 @@ void Client::processResponseDisconnect(const Response &response, const QJsonValu
 void Client::processResponseSource(const Response &response, const QJsonValue &request)
 {
     const auto req = request.toObject();
-    const auto path = QUrl::fromLocalFile(req[DAP_SOURCE].toObject()[DAP_PATH].toString());
+    const auto path = m_msgContext->toLocal(req[DAP_SOURCE].toObject()[DAP_PATH].toString());
     const auto reference = req[DAP_SOURCE_REFERENCE].toInt(0);
     if (response.success) {
         Q_EMIT sourceContent(path, reference, SourceContent(response.body.toObject()));
@@ -326,13 +362,13 @@ void Client::processResponseSource(const Response &response, const QJsonValue &r
 
 void Client::processResponseSetBreakpoints(const Response &response, const QJsonValue &request)
 {
-    const auto source = Source(request.toObject()[DAP_SOURCE].toObject());
+    const auto source = Source(request.toObject()[DAP_SOURCE].toObject(), *m_msgContext);
     if (response.success) {
         const auto resp = response.body.toObject();
         if (resp.contains(DAP_BREAKPOINTS)) {
             QList<Breakpoint> breakpoints;
             for (const auto &item : resp[DAP_BREAKPOINTS].toArray()) {
-                breakpoints.append(Breakpoint(item.toObject()));
+                breakpoints.append(Breakpoint(item.toObject(), *m_msgContext));
             }
             Q_EMIT sourceBreakpoints(source.path, source.sourceReference.value_or(0), breakpoints);
         } else {
@@ -360,7 +396,7 @@ void Client::processResponseEvaluate(const Response &response, const QJsonValue
 void Client::processResponseGotoTargets(const Response &response, const QJsonValue &request)
 {
     const auto &req = request.toObject();
-    const auto source = Source(req[DAP_SOURCE].toObject());
+    const auto source = Source(req[DAP_SOURCE].toObject(), *m_msgContext);
     const int line = req[DAP_LINE].toInt();
     if (response.success) {
         Q_EMIT gotoTargets(source, line, GotoTarget::parseList(response.body.toObject()[QStringLiteral("targets")].toArray()));
@@ -621,7 +657,7 @@ void Client::requestSource(const Source &source)
     if (reference == 0 && source.path.scheme() == Source::referenceScheme())
         reference = source.path.path().toInt();
     QJsonObject arguments{{DAP_SOURCE_REFERENCE, reference}};
-    const QJsonObject sourceArg{{DAP_SOURCE_REFERENCE, reference}, {DAP_PATH, source.path.path()}};
+    const QJsonObject sourceArg{{DAP_SOURCE_REFERENCE, reference}, {DAP_PATH, m_msgContext->toRemote(source.path)}};
 
     arguments[DAP_SOURCE] = sourceArg;
 
@@ -639,7 +675,7 @@ void Client::requestSetBreakpoints(const Source &source, const QList<SourceBreak
     for (const auto &item : breakpoints) {
         bpoints.append(item.toJson());
     }
-    QJsonObject arguments{{DAP_SOURCE, source.toJson()}, {DAP_BREAKPOINTS, bpoints}, {QStringLiteral("sourceModified"), sourceModified}};
+    QJsonObject arguments{{DAP_SOURCE, source.toJson(*m_msgContext)}, {DAP_BREAKPOINTS, bpoints}, {QStringLiteral("sourceModified"), sourceModified}};
 
     this->write(makeRequest(QStringLiteral("setBreakpoints"), arguments, &Client::processResponseSetBreakpoints));
 }
@@ -669,7 +705,7 @@ void Client::requestGotoTargets(const QUrl &path, const int line, const std::opt
 
 void Client::requestGotoTargets(const Source &source, const int line, const std::optional<int> column)
 {
-    QJsonObject arguments{{DAP_SOURCE, source.toJson()}, {DAP_LINE, line}};
+    QJsonObject arguments{{DAP_SOURCE, source.toJson(*m_msgContext)}, {DAP_LINE, line}};
     if (column) {
         arguments[DAP_COLUMN] = *column;
     }
diff --git a/addons/gdbplugin/dap/client.h b/addons/gdbplugin/dap/client.h
index c45d6ef175..8d3898977b 100644
--- a/addons/gdbplugin/dap/client.h
+++ b/addons/gdbplugin/dap/client.h
@@ -16,6 +16,8 @@
 #include "entities.h"
 #include "settings.h"
 
+#include <exec_utils.h>
+
 namespace dap
 {
 class Client : public QObject
@@ -31,9 +33,9 @@ public:
     };
     Q_ENUM(State)
 
-    Client(const settings::ProtocolSettings &protocolSettings, Bus *bus, QObject *parent = nullptr);
+    Client(const settings::ProtocolSettings &protocolSettings, Bus *bus, Utils::PathMappingPtr, QObject *parent = nullptr);
 
-    Client(const settings::ClientSettings &clientSettings, QObject *parent = nullptr);
+    Client(const settings::ClientSettings &clientSettings, Utils::PathMappingPtr, QObject *parent = nullptr);
 
     ~Client() override;
 
@@ -213,6 +215,7 @@ private:
 
     settings::ProtocolSettings m_protocol;
     QString m_launchCommand;
+    std::unique_ptr<MessageContext> m_msgContext;
 };
 
 }
diff --git a/addons/gdbplugin/dap/entities.cpp b/addons/gdbplugin/dap/entities.cpp
index ddd15d10af..0cf4979149 100644
--- a/addons/gdbplugin/dap/entities.cpp
+++ b/addons/gdbplugin/dap/entities.cpp
@@ -38,13 +38,13 @@ static std::optional<QString> parseOptionalString(const QJsonValue &value)
     return value.toString();
 }
 
-template<typename T>
-static std::optional<T> parseOptionalObject(const QJsonValue &value)
+template<typename T, typename... Args>
+static std::optional<T> parseOptionalObject(const QJsonValue &value, Args &&...args)
 {
     if (value.isNull() || value.isUndefined() || !value.isObject()) {
         return std::nullopt;
     }
-    return T(value.toObject());
+    return T(value.toObject(), std::forward<Args>(args)...);
 }
 
 template<typename T>
@@ -61,12 +61,12 @@ static std::optional<QHash<QString, T>> parseOptionalMap(const QJsonValue &value
     return map;
 }
 
-template<typename T>
-static QList<T> parseObjectList(const QJsonArray &array)
+template<typename T, typename... Args>
+static QList<T> parseObjectList(const QJsonArray &array, Args &&...args)
 {
     QList<T> out;
     for (const auto &item : array) {
-        out << T(item.toObject());
+        out << T(item.toObject(), std::forward<Args>(args)...);
     }
     return out;
 }
@@ -93,12 +93,12 @@ static std::optional<QList<int>> parseOptionalIntList(const QJsonValue &value)
     return values;
 }
 
-template<typename T>
-static QJsonArray toJsonArray(const QList<T> &items)
+template<typename T, typename... Args>
+static QJsonArray toJsonArray(const QList<T> &items, Args &&...args)
 {
     QJsonArray out;
     for (const auto &item : items) {
-        out << item.toJson();
+        out << item.toJson(std::forward<Args>(args)...);
     }
     return out;
 }
@@ -153,12 +153,12 @@ ProcessInfo::ProcessInfo(const QJsonObject &body)
 {
 }
 
-Output::Output(const QJsonObject &body)
+Output::Output(const QJsonObject &body, MessageContext &ctx)
     : category(Category::Unknown)
     , output(body[DAP_OUTPUT].toString())
     , group(std::nullopt)
     , variablesReference(parseOptionalInt(body[DAP_VARIABLES_REFERENCE]))
-    , source(parseOptionalObject<Source>(DAP_SOURCE))
+    , source(parseOptionalObject<Source>(DAP_SOURCE, ctx))
     , line(parseOptionalInt(body[DAP_LINE]))
     , column(parseOptionalInt(body[DAP_COLUMN]))
     , data(body[DAP_DATA])
@@ -223,9 +223,9 @@ QUrl Source::getUnifiedId(const QUrl &path, std::optional<int> sourceReference)
     return path;
 }
 
-Source::Source(const QJsonObject &body)
+Source::Source(const QJsonObject &body, MessageContext &ctx)
     : name(body[DAP_NAME].toString())
-    , path(QUrl::fromLocalFile(body[DAP_PATH].toString()))
+    , path(ctx.toLocal(body[DAP_PATH].toString()))
     , sourceReference(parseOptionalInt(body[DAP_SOURCE_REFERENCE]))
     , presentationHint(parseOptionalString(body[DAP_PRESENTATION_HINT]))
     , origin(body[DAP_ORIGIN].toString())
@@ -235,7 +235,7 @@ Source::Source(const QJsonObject &body)
     if (body.contains(DAP_SOURCES)) {
         const auto values = body[DAP_SOURCES].toArray();
         for (const auto &item : values) {
-            sources << Source(item.toObject());
+            sources << Source(item.toObject(), ctx);
         }
     }
 
@@ -253,14 +253,14 @@ Source::Source(const QUrl &path)
 {
 }
 
-QJsonObject Source::toJson() const
+QJsonObject Source::toJson(MessageContext &ctx) const
 {
     QJsonObject out;
     if (!name.isEmpty()) {
         out[DAP_NAME] = name;
     }
     if (!path.isEmpty()) {
-        out[DAP_PATH] = path.path();
+        out[DAP_PATH] = ctx.toRemote(path);
     }
     if (sourceReference) {
         out[DAP_SOURCE_REFERENCE] = *sourceReference;
@@ -275,7 +275,7 @@ QJsonObject Source::toJson() const
         out[DAP_ADAPTER_DATA] = adapterData;
     }
     if (!sources.isEmpty()) {
-        out[DAP_SOURCES] = toJsonArray(sources);
+        out[DAP_SOURCES] = toJsonArray(sources, ctx);
     }
     if (!checksums.isEmpty()) {
         out[DAP_CHECKSUMS] = toJsonArray(checksums);
@@ -343,10 +343,10 @@ QList<Thread> Thread::parseList(const QJsonArray &threads)
     return parseObjectList<Thread>(threads);
 }
 
-StackFrame::StackFrame(const QJsonObject &body)
+StackFrame::StackFrame(const QJsonObject &body, MessageContext &ctx)
     : id(body[DAP_ID].toInt())
     , name(body[DAP_NAME].toString())
-    , source(parseOptionalObject<Source>(body[DAP_SOURCE]))
+    , source(parseOptionalObject<Source>(body[DAP_SOURCE], ctx))
     , line(body[DAP_LINE].toInt())
     , column(body[DAP_COLUMN].toInt())
     , endLine(parseOptionalInt(body[QStringLiteral("endLine")]))
@@ -358,8 +358,8 @@ StackFrame::StackFrame(const QJsonObject &body)
 {
 }
 
-StackTraceInfo::StackTraceInfo(const QJsonObject &body)
-    : stackFrames(parseObjectList<StackFrame>(body[QStringLiteral("stackFrames")].toArray()))
+StackTraceInfo::StackTraceInfo(const QJsonObject &body, MessageContext &ctx)
+    : stackFrames(parseObjectList<StackFrame>(body[QStringLiteral("stackFrames")].toArray(), ctx))
     , totalFrames(parseOptionalInt(body[QStringLiteral("totalFrames")]))
 {
 }
@@ -385,14 +385,14 @@ ModuleEvent::ModuleEvent(const QJsonObject &body)
 {
 }
 
-Scope::Scope(const QJsonObject &body)
+Scope::Scope(const QJsonObject &body, MessageContext &ctx)
     : name(body[DAP_NAME].toString())
     , presentationHint(parseOptionalString(body[DAP_PRESENTATION_HINT]))
     , variablesReference(body[DAP_VARIABLES_REFERENCE].toInt())
     , namedVariables(parseOptionalInt(body[QStringLiteral("namedVariables")]))
     , indexedVariables(parseOptionalInt(body[QStringLiteral("indexedVariables")]))
     , expensive(parseOptionalBool(body[QStringLiteral("expensive")]))
-    , source(parseOptionalObject<Source>(body[QStringLiteral("source")]))
+    , source(parseOptionalObject<Source>(body[QStringLiteral("source")], ctx))
     , line(parseOptionalInt(body[QStringLiteral("line")]))
     , column(parseOptionalInt(body[QStringLiteral("column")]))
     , endLine(parseOptionalInt(body[QStringLiteral("endLine")]))
@@ -406,9 +406,9 @@ Scope::Scope(int variablesReference, QString name)
 {
 }
 
-QList<Scope> Scope::parseList(const QJsonArray &scopes)
+QList<Scope> Scope::parseList(const QJsonArray &scopes, MessageContext &ctx)
 {
-    return parseObjectList<Scope>(scopes);
+    return parseObjectList<Scope>(scopes, ctx);
 }
 
 Variable::Variable(const QJsonObject &body)
@@ -503,11 +503,11 @@ QJsonObject SourceBreakpoint::toJson() const
     return out;
 }
 
-Breakpoint::Breakpoint(const QJsonObject &body)
+Breakpoint::Breakpoint(const QJsonObject &body, MessageContext &ctx)
     : id(parseOptionalInt(body[DAP_ID]))
     , verified(body[QStringLiteral("verified")].toBool())
     , message(parseOptionalString(body[QStringLiteral("message")]))
-    , source(parseOptionalObject<Source>(body[DAP_SOURCE]))
+    , source(parseOptionalObject<Source>(body[DAP_SOURCE], ctx))
     , line(parseOptionalInt(body[DAP_LINE]))
     , column(parseOptionalInt(body[DAP_COLUMN]))
     , endLine(parseOptionalInt(body[DAP_END_LINE]))
@@ -522,9 +522,9 @@ Breakpoint::Breakpoint(const int line)
 {
 }
 
-BreakpointEvent::BreakpointEvent(const QJsonObject &body)
+BreakpointEvent::BreakpointEvent(const QJsonObject &body, MessageContext &ctx)
     : reason(body[DAP_REASON].toString())
-    , breakpoint(Breakpoint(body[DAP_BREAKPOINT].toObject()))
+    , breakpoint(Breakpoint(body[DAP_BREAKPOINT].toObject(), ctx))
 {
 }
 
diff --git a/addons/gdbplugin/dap/entities.h b/addons/gdbplugin/dap/entities.h
index 4214ed8454..e71e38fe06 100644
--- a/addons/gdbplugin/dap/entities.h
+++ b/addons/gdbplugin/dap/entities.h
@@ -103,6 +103,12 @@ struct ProcessInfo {
     ProcessInfo(const QJsonObject &body);
 };
 
+struct MessageContext {
+    virtual ~MessageContext() = default;
+    virtual QString toRemote(const QUrl &) = 0;
+    virtual QUrl toLocal(const QString &) = 0;
+};
+
 struct Checksum {
     QString checksum;
     QString algorithm;
@@ -128,10 +134,10 @@ struct Source {
     static QUrl getUnifiedId(const QUrl &path, std::optional<int> sourceReference);
 
     Source() = default;
-    Source(const QJsonObject &body);
+    Source(const QJsonObject &body, MessageContext &);
     Source(const QUrl &path);
 
-    QJsonObject toJson() const;
+    QJsonObject toJson(MessageContext &ctx) const;
 };
 
 struct SourceContent {
@@ -212,7 +218,7 @@ struct Breakpoint {
     std::optional<int> offset;
 
     Breakpoint() = default;
-    Breakpoint(const QJsonObject &body);
+    Breakpoint(const QJsonObject &body, MessageContext &);
     Breakpoint(const int line);
 };
 
@@ -220,14 +226,7 @@ class Output
 {
     Q_GADGET
 public:
-    enum class Category {
-        Console,
-        Important,
-        Stdout,
-        Stderr,
-        Telemetry,
-        Unknown
-    };
+    enum class Category { Console, Important, Stdout, Stderr, Telemetry, Unknown };
 
     Q_ENUM(Category)
 
@@ -247,7 +246,7 @@ public:
     QJsonValue data;
 
     Output() = default;
-    Output(const QJsonObject &body);
+    Output(const QJsonObject &body, MessageContext &ctx);
     Output(const QString &output, const Category &category);
 
     bool isSpecialOutput() const;
@@ -361,7 +360,7 @@ struct BreakpointEvent {
     Breakpoint breakpoint;
 
     BreakpointEvent() = default;
-    BreakpointEvent(const QJsonObject &body);
+    BreakpointEvent(const QJsonObject &body, MessageContext &);
 };
 
 struct Thread {
@@ -390,7 +389,7 @@ struct StackFrame {
     std::optional<QString> presentationHint;
 
     StackFrame() = default;
-    StackFrame(const QJsonObject &body);
+    StackFrame(const QJsonObject &body, MessageContext &ctx);
 };
 
 struct StackTraceInfo {
@@ -398,7 +397,7 @@ struct StackTraceInfo {
     std::optional<int> totalFrames;
 
     StackTraceInfo() = default;
-    StackTraceInfo(const QJsonObject &body);
+    StackTraceInfo(const QJsonObject &body, MessageContext &ctx);
 };
 
 struct Scope {
@@ -415,18 +414,14 @@ struct Scope {
     std::optional<int> endColumn;
 
     Scope() = default;
-    Scope(const QJsonObject &body);
+    Scope(const QJsonObject &body, MessageContext &ctx);
     Scope(int variablesReference, QString name);
 
-    static QList<Scope> parseList(const QJsonArray &scopes);
+    static QList<Scope> parseList(const QJsonArray &scopes, MessageContext &ctx);
 };
 
 struct Variable {
-    enum Type {
-        Indexed = 1,
-        Named = 2,
-        Both = 3
-    };
+    enum Type { Indexed = 1, Named = 2, Both = 3 };
 
     QString name;
     QString value;
diff --git a/addons/gdbplugin/dapbackend.cpp b/addons/gdbplugin/dapbackend.cpp
index a757cbde18..75bd9acd98 100644
--- a/addons/gdbplugin/dapbackend.cpp
+++ b/addons/gdbplugin/dapbackend.cpp
@@ -163,6 +163,7 @@ dap::settings::ClientSettings &DapBackend::target2dap(const DAPTargetConf &targe
     Q_EMIT outputText(QString::fromLocal8Bit(QJsonDocument(out).toJson()) + QStringLiteral("\n"));
 
     m_settings = dap::settings::ClientSettings(out);
+    m_pathMap = target.dapSettings->pathMap;
     return *m_settings;
 }
 
@@ -174,7 +175,7 @@ void DapBackend::start()
     }
     unsetClient();
 
-    m_client = new dap::Client(*m_settings, this);
+    m_client = new dap::Client(*m_settings, m_pathMap, this);
 
     Q_EMIT debuggerCapabilitiesChanged();
 
diff --git a/addons/gdbplugin/dapbackend.h b/addons/gdbplugin/dapbackend.h
index 617689b019..580372bd70 100644
--- a/addons/gdbplugin/dapbackend.h
+++ b/addons/gdbplugin/dapbackend.h
@@ -155,6 +155,8 @@ private:
 
     dap::Client *m_client = nullptr;
     std::optional<dap::settings::ClientSettings> m_settings;
+    Utils::PathMappingPtr m_pathMap;
+
     State m_state;
     Task m_task;
 
diff --git a/doc/kate/plugins.docbook b/doc/kate/plugins.docbook
index 5bee2bfbac..f59dfb7984 100644
--- a/doc/kate/plugins.docbook
+++ b/doc/kate/plugins.docbook
@@ -1595,8 +1595,12 @@ for opened documents on hover.</para></listitem>
 <sect2 id="gdb-intro">
 <title>Introduction</title>
 
-<para>&kate;'s &gdb; plugin provides a simple frontend to the popular &GNU;
-Project Debugger.</para>
+<para>&kate;'s &gdb; plugin provides a simple frontend to any debugger that supports the
+<ulink url="https://microsoft.github.io/debug-adapter-protocol/">Debugger Adapter Protocol</ulink>.
+In particular, that includes the GNU Project Debugger, aka GDB, as
+<ulink url="https://sourceware.org/gdb/current/onlinedocs/gdb.html/Debugger-Adapter-Protocol.html">outlined
+here</ulink>.
+</para>
 
 <important>
 <para>Previous experience with &gdb; is strongly recommended.  For more
@@ -1608,9 +1612,6 @@ information on using &gdb;, visit <ulink url="https://www.gnu.org/software/gdb/"
 <link linkend="config-dialog-plugins">the Plugins section of &kate;'s
 configuration</link>.</para>
 
-<para>For the plugin to work properly, you must have a source file (of any type
-supported by &gdb;) and an executable.</para>
-
 <tip>
 <para>If you compile using &gcc;/<command>g++</command> you might want to use
 the <command><parameter>-ggdb</parameter></command> command line argument.
@@ -1618,11 +1619,21 @@ the <command><parameter>-ggdb</parameter></command> command line argument.
 </tip>
 
 <para>After these preparations are made, open the source file in &kate;,
+select the "debugger profile",
 enter the path to the executable in the <guilabel>Settings</guilabel> tab of the
 <guilabel>Debug View</guilabel> tool view, and select
 <menuchoice><guimenu>Debug</guimenu><guimenuitem>Start Debugging</guimenuitem></menuchoice>
 from the menu to get started.</para>
 
+<para>
+The "debugger profile" selects the DAP server to use (e.g. GDB) and the way
+in which to launch this server.  A typical case is to have the server launch
+a process as specified above, but it may also attach to a running process
+(in which case a PID will have to be specified rather than an executable).
+There may also be other modes which are specific to the language and DAP server.
+See also later for additional background and configuration details on this.
+</para>
+
 </sect2>
 
 <sect2 id="gdb-menus">
@@ -1888,6 +1899,189 @@ writing much of this section.</para>
 
 </sect2>
 
+<sect2 id="gdb-configuration">
+<title>Configuration</title>
+
+<para>
+The plugin's configuration page defines the "debugger profiles" which can be
+selected.  The default configuration (JSON) is shown there, and it can be "overlayed"
+by a user provided of similar form.  An example excerpt is as follows:
+
+<screen>
+{
+    "dap": {
+        "debugpy": {
+            "url": "https://github.com/microsoft/debugpy",
+            "run": {
+                "command": ["python", "-m", "debugpy", "--listen", "${#run.port}", "--wait-for-client"],
+                "port": 0,
+                "supportsSourceRequest": false
+            },
+            "configurations": {
+                "launch": {
+                    "commandArgs": ["${file}", "${args|list}"],
+                    "request": {
+                        "command": "attach",
+                        "stopOnEntry": true,
+                        "redirectOutput": true
+                    }
+                },
+                "attach": {
+                    "commandArgs": ["--pid", "${pid}"],
+                    "request": {
+                        "command": "attach",
+                        "stopOnEntry": true,
+                        "redirectOutput": true
+                    }
+                },
+        },
+        "gdb": {
+            "url": "gdb",
+            "run": {
+                "command": [
+                    "gdb",
+                    "-i",
+                    "dap"
+                ],
+                "redirectStderr": true,
+                "redirectStdout": true,
+                "supportsSourceRequest": true
+            },
+            "configurations": {
+                "launch (debug)": {
+                    "request": {
+                        "command": "launch",
+                        "mode": "debug",
+                        "program": "${file}",
+                        "args": "${args|list}",
+                        "cwd": "${workdir}"
+                    }
+                }
+            }
+        }
+    }
+}
+</screen>
+
+Each of the entries in <literal>configurations</literal> is combined with
+the <literal>run</literal> data and forms a "profile".  This specifies
+the DAP server to launch along with its arguments, where the latter are specific
+to the profile (<literal>commandArgs</literal>).  The other parts specify
+the DAP protocol request (<literal>launch</literal> or <literal>attach</literal>),
+along with DAP specific extensions.
+</para>
+
+<para>
+Of course, the specified server should be installed (and typically also in
+<literal>PATH</literal> for proper execution).
+</para>
+
+<para>
+Various stages of override/merge are applied;
+user configuration (loaded from file) overrides (internal) default configuration,
+and the "dap" entry in <literal>.kateproject</literal> project configuration in turn
+overrides.
+</para>
+
+
+<sect3 id="gdb-exec">
+<title>Execution environment setup</title>
+
+<para>
+More background and specifics can be found in the
+<link linkend="lspclient-exec">LSP Client Execution environment</link> section below.
+But suffice it to say here it may be needed to run the debuggee process
+(and the DAP server) in a "special environment", whether merely defined by environment
+variables or some container (providing the required dependencies and circumstances
+for proper execution).
+</para>
+
+<para>
+Similar to the example in the referenced section, the following configuration
+may be provided in a <literal>.kateproject</literal>.
+
+<screen>
+{
+    // this may also be an array of objects
+    "exec": {
+        "hostname": "foobar"
+        // the command could also be an array of string
+        "prefix": "podman exec -i foobarcontainer",
+        "mapRemoteRoot": true,
+        "pathMappings": [
+            // either of the following forms are possible
+            [ "/dir/on/host", "/mounted/in/container" ]
+            { "localRoot": "/local/dir", "remoteRoot": "/remote/dir" }
+        ]
+    },
+    "dap": {
+        "debugpy": {
+            "run": {
+                // in this section, it applies to all configurations
+                // this will match/join with the above object
+                "exec": { "hostname": "foobar" },
+                // if server is connected to,
+                // optionally specify explicit port (which is suitable published/forwarded)
+                "port": 5678,
+                // the server may then also have to accept more than localhost
+                "host": "0.0.0.0"
+            }
+        }
+    }
+}
+</screen>
+
+The referenced section should be consulted for details, but in essence
+the <literal>prefix</literal> will be prepended before the DAP server
+command line specified elsewhere.  The effect is that the server is run
+within the specified container and then in turn also the launched process.
+The <literal>pathMapping</literal> arranges for transformation of filepaths
+between editor's view and DAP server (container) view, e.g. when dealing with
+setting of breakpoints or handling of reported backtraces.
+Note that such mapping is optional and may or may not be useful.  When dealing
+with C/C++ code that is compiled on "host", the symbol info references source
+files on host which do not exist at all in the other environment.  However,
+in other scripted (e.g. python) circumstances, actual runtime files are
+referenced (on the other environment).
+</para>
+
+<para>
+The following must evidently be kept in mind;
+<itemizedlist>
+<listitem>
+<para>The DAP server must be present in the environment/container,
+which must be configured to support proper debugger operation
+(so, if needed, privileged, capabilities).
+</para>
+</listitem>
+<listitem>
+<para>Communication between editor and DAP must be possible.  In case of
+a container, the latter should either use host networking, or provide a suitable
+mapped/published port along with corresponding config snippet as in above example
+(as an auto-selected one in case of port 0 would not make it through).
+</para>
+</listitem>
+<listitem>
+<para>
+A specified executable/PID should be in "container" perspective, as well as
+any (debuggee executable) arguments.
+</para>
+</listitem>
+</itemizedlist>
+
+Also, as in the LSP case, some environment variables are set;
+<literal>KATE_EXEC_PLUGIN</literal> is set to <literal>dap</literal>,
+<literal>KATE_EXEC_SERVER</literal> is set to the debugger/language type
+(e.g. <literal>python</literal>) and
+<literal>KATE_EXEC_PROFILE</literal> is set to the configuration entry
+(e.g. <literal>launch</literal>).
+
+</para>
+
+</sect3>
+
+</sect2>
+
 </sect1>
 
 <sect1 id="kate-application-plugin-projects">



More information about the kde-doc-english mailing list