[Lldb-commits] [lldb] [lldb] Add accelerator dynamic loader (PR #217393)
satyanarayana reddy janga via lldb-commits
lldb-commits at lists.llvm.org
Wed Aug 19 09:58:47 PDT 2026
https://github.com/satyajanga created https://github.com/llvm/llvm-project/pull/217393
None
>From 92201e957f5af130f46ca1b0f5787727f688ce74 Mon Sep 17 00:00:00 2001
From: satya janga <satyajanga at fb.com>
Date: Wed, 3 Jun 2026 13:20:04 -0700
Subject: [PATCH 1/2] [lldb-server] Add dynamic loader support to accelerator
plugin protocol
Add jAcceleratorPluginGetDynamicLoaderLibraryInfo packet for querying
shared library information from accelerator plugins. This includes:
- AcceleratorSectionInfo for per-section load addresses
- AcceleratorDynamicLoaderLibraryInfo with pathname, UUID, load address,
native memory location, file offset/size, and loaded sections
- AcceleratorDynamicLoaderArgs and AcceleratorDynamicLoaderResponse
- GetDynamicLoaderLibraryInfos virtual method on the plugin interface
- LLGS packet handler with plugin name routing
- Mock plugin implementation returning a test library
- Unit tests for all new JSON serialization types
- API test verifying the packet round-trip
---
lldb/docs/resources/lldbgdbremote.md | 66 +++++++++++++++
.../Utility/AcceleratorGDBRemotePackets.h | 66 ++++++++++++++-
.../lldb/Utility/StringExtractorGDBRemote.h | 1 +
.../GDBRemoteCommunicationServerLLGS.cpp | 33 ++++++++
.../GDBRemoteCommunicationServerLLGS.h | 3 +
.../gdb-remote/LLDBServerAcceleratorPlugin.h | 5 ++
.../Utility/AcceleratorGDBRemotePackets.cpp | 80 +++++++++++++++---
.../Utility/StringExtractorGDBRemote.cpp | 2 +
.../mock/TestMockAcceleratorPackets.py | 55 +++++++++++++
.../Mock/LLDBServerMockAcceleratorPlugin.cpp | 12 +++
.../Mock/LLDBServerMockAcceleratorPlugin.h | 3 +
.../AcceleratorGDBRemotePacketsTest.cpp | 81 +++++++++++++++++++
12 files changed, 395 insertions(+), 12 deletions(-)
diff --git a/lldb/docs/resources/lldbgdbremote.md b/lldb/docs/resources/lldbgdbremote.md
index 93090c19c5ec0..2b64b663c9461 100644
--- a/lldb/docs/resources/lldbgdbremote.md
+++ b/lldb/docs/resources/lldbgdbremote.md
@@ -2862,3 +2862,69 @@ STUB REPLIES: {"disable_bp":true,"auto_resume_native":false,"actions":{"plugin_
**Priority To Implement:** Required for hardware accelerator debugging
support. Not needed for non-hardware-accelerator debugging.
+
+### jAcceleratorPluginGetDynamicLoaderLibraryInfo
+
+Requests shared library information from an accelerator plugin. The client
+sends this packet when it needs to load or update the accelerator's shared
+library list. This packet requires the `accelerator-plugins+` feature from
+`qSupported`.
+
+```
+LLDB SENDS: jAcceleratorPluginGetDynamicLoaderLibraryInfo:<json>
+STUB REPLIES: <json_response>
+```
+
+The request JSON has the following fields:
+
+| Key | Type | Description |
+|---------------|--------|-------------|
+| `plugin_name` | string | Name of the accelerator plugin to query. |
+| `full` | bool | If true, return every library the plugin knows about. If false, return only what changed since the last query. |
+
+A plugin may track thousands of code objects, so `full` lets a client that is
+already up to date ask only for the delta instead of re-receiving the whole
+list on every stop. It is the plugin that decides what "changed since the last
+query" means, and the plugin that keeps that state.
+
+Because that state lives in the plugin and is not per-client, a client cannot
+assume it starts from a known point: an earlier client may have consumed the
+pending changes with `full=false`. The first query of a session must therefore
+use `full=true`, and a client should also use it whenever it discards its own
+module list and needs to rebuild it.
+
+The response JSON is an object with a single `library_infos` key, holding an
+array of library info objects:
+
+| Key | Type | Description |
+|-------------------------|--------|-------------|
+| `pathname` | string | Path to the object file. |
+| `load` | bool | True when the library is being loaded, false when it is being unloaded. |
+| `load_address` | int | (optional) Base address the whole object file is slid to. |
+| `loaded_sections` | array | (optional) Per-section load addresses, for object files whose sections load at independent addresses. Each entry has `names` (the section name, or a path of nested section names to descend) and `load_address`. |
+| `uuid` | string | (optional) UUID of the object file, if the plugin knows it. |
+| `native_memory_address` | int | (optional) Address **in the native (host) process** where the object file image can be read, for a library that only exists in memory. |
+| `native_memory_size` | int | (optional) Size of that in-memory image. |
+| `file_offset` | int | (optional) Byte offset of the object file within `pathname`, for a library embedded in a containing file. |
+| `file_size` | int | (optional) Size of the object file within that containing file. |
+
+`load_address`, `loaded_sections` and neither-of-the-two are three different
+requests, and are resolved in that order:
+
+* `load_address` present: slide the whole object file to that address.
+* otherwise `loaded_sections` non-empty: load only the named sections, each at
+ its own address.
+* otherwise: load at the file addresses with no slide.
+
+An absent `loaded_sections` and an empty `loaded_sections` therefore mean the
+same thing here: no per-section addresses were supplied. A plugin that wants
+sections loaded must send a non-empty array.
+
+Example:
+```
+LLDB SENDS: jAcceleratorPluginGetDynamicLoaderLibraryInfo:{"plugin_name":"mock","full":true}
+STUB REPLIES: {"library_infos":[{"pathname":"/path/to/lib.so","load":true,"load_address":2130706432,"loaded_sections":[]}]}
+```
+
+**Priority To Implement:** Required for hardware accelerator debugging
+support. Not needed for non-hardware-accelerator debugging.
diff --git a/lldb/include/lldb/Utility/AcceleratorGDBRemotePackets.h b/lldb/include/lldb/Utility/AcceleratorGDBRemotePackets.h
index faa7f5575f157..b804b216f51d0 100644
--- a/lldb/include/lldb/Utility/AcceleratorGDBRemotePackets.h
+++ b/lldb/include/lldb/Utility/AcceleratorGDBRemotePackets.h
@@ -9,6 +9,7 @@
#ifndef LLDB_UTILITY_ACCELERATORGDBREMOTEPACKETS_H
#define LLDB_UTILITY_ACCELERATORGDBREMOTEPACKETS_H
+#include "lldb/lldb-types.h"
#include "llvm/Support/JSON.h"
#include <cstdint>
#include <optional>
@@ -21,7 +22,7 @@ struct SymbolValue {
/// Symbol name as requested in AcceleratorBreakpointInfo::symbol_names.
std::string name;
/// Load address of the symbol in the native process, or nullopt if not found.
- std::optional<uint64_t> value;
+ std::optional<lldb::addr_t> value;
};
bool fromJSON(const llvm::json::Value &value, SymbolValue &data,
@@ -41,7 +42,7 @@ llvm::json::Value toJSON(const AcceleratorBreakpointByName &data);
struct AcceleratorBreakpointByAddress {
/// Load address in the native debug target.
- uint64_t load_address = 0;
+ lldb::addr_t load_address = 0;
};
bool fromJSON(const llvm::json::Value &value,
@@ -78,7 +79,7 @@ struct AcceleratorBreakpointHitArgs {
AcceleratorBreakpointInfo breakpoint;
std::vector<SymbolValue> symbol_values;
- std::optional<uint64_t> GetSymbolValue(llvm::StringRef symbol_name) const;
+ std::optional<lldb::addr_t> GetSymbolValue(llvm::StringRef symbol_name) const;
};
bool fromJSON(const llvm::json::Value &value,
@@ -166,6 +167,65 @@ bool fromJSON(const llvm::json::Value &value,
AcceleratorBreakpointHitResponse &data, llvm::json::Path path);
llvm::json::Value toJSON(const AcceleratorBreakpointHitResponse &data);
+struct AcceleratorSectionInfo {
+ /// Each name is looked up as a child of the previous one, e.g.
+ /// ["PT_LOAD[0]", ".text"].
+ std::vector<std::string> names;
+ lldb::addr_t load_address = 0;
+};
+
+bool fromJSON(const llvm::json::Value &value, AcceleratorSectionInfo &data,
+ llvm::json::Path path);
+llvm::json::Value toJSON(const AcceleratorSectionInfo &data);
+
+struct AcceleratorDynamicLoaderLibraryInfo {
+ std::string pathname;
+ std::optional<std::string> uuid_str;
+ /// False means unload.
+ bool load = true;
+ /// Slides the whole object file. If unset, use \a loaded_sections or the
+ /// file addresses.
+ std::optional<lldb::addr_t> load_address;
+ /// Used when sections load at independent addresses. Absent and empty mean
+ /// the same thing: no per-section addresses were supplied.
+ std::vector<AcceleratorSectionInfo> loaded_sections;
+ /// Where the image can be read in the native process, meaning the host
+ /// process driving the accelerator, not the accelerator itself. Set for a
+ /// library that only exists in memory.
+ std::optional<lldb::addr_t> native_memory_address;
+ std::optional<uint64_t> native_memory_size;
+ /// Slice of \a pathname holding the object file, when embedded in a
+ /// container.
+ std::optional<uint64_t> file_offset;
+ std::optional<uint64_t> file_size;
+};
+
+bool fromJSON(const llvm::json::Value &value,
+ AcceleratorDynamicLoaderLibraryInfo &data, llvm::json::Path path);
+llvm::json::Value toJSON(const AcceleratorDynamicLoaderLibraryInfo &data);
+
+/// Arguments for the jAcceleratorPluginGetDynamicLoaderLibraryInfo packet.
+struct AcceleratorDynamicLoaderArgs {
+ std::string plugin_name;
+ /// If false, return only what changed since the last query. That state lives
+ /// in the plugin rather than per-client, so the first query of a session
+ /// must use true.
+ bool full = true;
+};
+
+bool fromJSON(const llvm::json::Value &value,
+ AcceleratorDynamicLoaderArgs &data, llvm::json::Path path);
+llvm::json::Value toJSON(const AcceleratorDynamicLoaderArgs &data);
+
+/// Response from the jAcceleratorPluginGetDynamicLoaderLibraryInfo packet.
+struct AcceleratorDynamicLoaderResponse {
+ std::vector<AcceleratorDynamicLoaderLibraryInfo> library_infos;
+};
+
+bool fromJSON(const llvm::json::Value &value,
+ AcceleratorDynamicLoaderResponse &data, llvm::json::Path path);
+llvm::json::Value toJSON(const AcceleratorDynamicLoaderResponse &data);
+
} // namespace lldb_private
#endif // LLDB_UTILITY_ACCELERATORGDBREMOTEPACKETS_H
diff --git a/lldb/include/lldb/Utility/StringExtractorGDBRemote.h b/lldb/include/lldb/Utility/StringExtractorGDBRemote.h
index 624a2febe857e..0236891dfcde1 100644
--- a/lldb/include/lldb/Utility/StringExtractorGDBRemote.h
+++ b/lldb/include/lldb/Utility/StringExtractorGDBRemote.h
@@ -176,6 +176,7 @@ class StringExtractorGDBRemote : public StringExtractor {
eServerPacketType_jMultiBreakpoint,
eServerPacketType_jAcceleratorPluginInitialize,
eServerPacketType_jAcceleratorPluginBreakpointHit,
+ eServerPacketType_jAcceleratorPluginGetDynamicLoaderLibraryInfo,
eServerPacketType_qMemTags, // read memory tags
eServerPacketType_QMemTags, // write memory tags
diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp
index 2e824282079b4..791c02fd96b20 100644
--- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp
+++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp
@@ -233,6 +233,11 @@ void GDBRemoteCommunicationServerLLGS::RegisterPacketHandlers() {
eServerPacketType_jAcceleratorPluginBreakpointHit,
&GDBRemoteCommunicationServerLLGS::
Handle_jAcceleratorPluginBreakpointHit);
+ RegisterMemberFunctionHandler(
+ StringExtractorGDBRemote::
+ eServerPacketType_jAcceleratorPluginGetDynamicLoaderLibraryInfo,
+ &GDBRemoteCommunicationServerLLGS::
+ Handle_jAcceleratorPluginGetDynamicLoaderLibraryInfo);
RegisterMemberFunctionHandler(StringExtractorGDBRemote::eServerPacketType_g,
&GDBRemoteCommunicationServerLLGS::Handle_g);
@@ -4643,3 +4648,31 @@ GDBRemoteCommunicationServerLLGS::Handle_jAcceleratorPluginBreakpointHit(
return SendErrorResponse(
Status::FromErrorString("unknown accelerator plugin name"));
}
+
+GDBRemoteCommunication::PacketResult GDBRemoteCommunicationServerLLGS::
+ Handle_jAcceleratorPluginGetDynamicLoaderLibraryInfo(
+ StringExtractorGDBRemote &packet) {
+ packet.ConsumeFront("jAcceleratorPluginGetDynamicLoaderLibraryInfo:");
+ llvm::Expected<AcceleratorDynamicLoaderArgs> args =
+ llvm::json::parse<AcceleratorDynamicLoaderArgs>(
+ packet.Peek(), "AcceleratorDynamicLoaderArgs");
+ if (!args)
+ return SendErrorResponse(args.takeError());
+
+ for (std::unique_ptr<lldb_server::LLDBServerAcceleratorPlugin> &plugin_up :
+ m_accelerator_plugins) {
+ if (plugin_up->GetPluginName() == args->plugin_name) {
+ std::optional<AcceleratorDynamicLoaderResponse> response =
+ plugin_up->GetDynamicLoaderLibraryInfos(*args);
+ if (response) {
+ StreamGDBRemote stream;
+ stream.PutAsJSON(*response, /*hex_ascii=*/false);
+ return SendPacketNoLock(stream.GetString());
+ }
+ return SendErrorResponse(
+ Status::FromErrorString("no dynamic loader info available"));
+ }
+ }
+ return SendErrorResponse(
+ Status::FromErrorString("unknown accelerator plugin name"));
+}
diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h
index e5b4c9ec0bed0..3c8afe699bbaf 100644
--- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h
+++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h
@@ -302,6 +302,9 @@ class GDBRemoteCommunicationServerLLGS
PacketResult
Handle_jAcceleratorPluginBreakpointHit(StringExtractorGDBRemote &packet);
+ PacketResult Handle_jAcceleratorPluginGetDynamicLoaderLibraryInfo(
+ StringExtractorGDBRemote &packet);
+
void SetCurrentThreadID(lldb::tid_t tid);
lldb::tid_t GetCurrentThreadID() const;
diff --git a/lldb/source/Plugins/Process/gdb-remote/LLDBServerAcceleratorPlugin.h b/lldb/source/Plugins/Process/gdb-remote/LLDBServerAcceleratorPlugin.h
index f16ee82a39d95..2e585d9a64c6a 100644
--- a/lldb/source/Plugins/Process/gdb-remote/LLDBServerAcceleratorPlugin.h
+++ b/lldb/source/Plugins/Process/gdb-remote/LLDBServerAcceleratorPlugin.h
@@ -47,6 +47,11 @@ class LLDBServerAcceleratorPlugin {
++m_accelerator_action_identifier);
}
+ virtual std::optional<AcceleratorDynamicLoaderResponse>
+ GetDynamicLoaderLibraryInfos(const AcceleratorDynamicLoaderArgs &args) {
+ return std::nullopt;
+ }
+
protected:
GDBServer &m_native_gdb_server;
MainLoop &m_native_main_loop;
diff --git a/lldb/source/Utility/AcceleratorGDBRemotePackets.cpp b/lldb/source/Utility/AcceleratorGDBRemotePackets.cpp
index 3b9edada64c65..169e3ac8017b5 100644
--- a/lldb/source/Utility/AcceleratorGDBRemotePackets.cpp
+++ b/lldb/source/Utility/AcceleratorGDBRemotePackets.cpp
@@ -13,7 +13,7 @@ using namespace llvm::json;
namespace lldb_private {
-bool fromJSON(const Value &value, SymbolValue &data, Path path) {
+bool fromJSON(const json::Value &value, SymbolValue &data, Path path) {
ObjectMapper o(value, path);
return o && o.map("name", data.name) && o.map("value", data.value);
}
@@ -22,7 +22,7 @@ json::Value toJSON(const SymbolValue &data) {
return Object{{"name", data.name}, {"value", data.value}};
}
-bool fromJSON(const Value &value, AcceleratorBreakpointByName &data,
+bool fromJSON(const json::Value &value, AcceleratorBreakpointByName &data,
Path path) {
ObjectMapper o(value, path);
return o && o.mapOptional("shlib", data.shlib) &&
@@ -33,7 +33,7 @@ json::Value toJSON(const AcceleratorBreakpointByName &data) {
return Object{{"shlib", data.shlib}, {"function_name", data.function_name}};
}
-bool fromJSON(const Value &value, AcceleratorBreakpointByAddress &data,
+bool fromJSON(const json::Value &value, AcceleratorBreakpointByAddress &data,
Path path) {
ObjectMapper o(value, path);
return o && o.map("load_address", data.load_address);
@@ -43,7 +43,8 @@ json::Value toJSON(const AcceleratorBreakpointByAddress &data) {
return Object{{"load_address", static_cast<int64_t>(data.load_address)}};
}
-bool fromJSON(const Value &value, AcceleratorBreakpointInfo &data, Path path) {
+bool fromJSON(const json::Value &value, AcceleratorBreakpointInfo &data,
+ Path path) {
ObjectMapper o(value, path);
return o && o.map("identifier", data.identifier) &&
o.mapOptional("by_name", data.by_name) &&
@@ -60,7 +61,7 @@ json::Value toJSON(const AcceleratorBreakpointInfo &data) {
};
}
-bool fromJSON(const Value &value, AcceleratorBreakpointHitArgs &data,
+bool fromJSON(const json::Value &value, AcceleratorBreakpointHitArgs &data,
Path path) {
ObjectMapper o(value, path);
return o && o.map("plugin_name", data.plugin_name) &&
@@ -76,7 +77,7 @@ json::Value toJSON(const AcceleratorBreakpointHitArgs &data) {
};
}
-std::optional<uint64_t>
+std::optional<lldb::addr_t>
AcceleratorBreakpointHitArgs::GetSymbolValue(StringRef symbol_name) const {
auto it = llvm::find_if(symbol_values, [&](const SymbolValue &symbol) {
return symbol.name == symbol_name;
@@ -86,7 +87,8 @@ AcceleratorBreakpointHitArgs::GetSymbolValue(StringRef symbol_name) const {
return std::nullopt;
}
-bool fromJSON(const Value &value, AcceleratorConnectionInfo &data, Path path) {
+bool fromJSON(const json::Value &value, AcceleratorConnectionInfo &data,
+ Path path) {
ObjectMapper o(value, path);
return o && o.map("connect_url", data.connect_url) &&
o.map("platform_name", data.platform_name) &&
@@ -103,7 +105,7 @@ json::Value toJSON(const AcceleratorConnectionInfo &data) {
};
}
-bool fromJSON(const Value &value, AcceleratorActions &data, Path path) {
+bool fromJSON(const json::Value &value, AcceleratorActions &data, Path path) {
ObjectMapper o(value, path);
return o && o.map("plugin_name", data.plugin_name) &&
o.map("session_name", data.session_name) &&
@@ -124,7 +126,7 @@ json::Value toJSON(const AcceleratorActions &data) {
return obj;
}
-bool fromJSON(const Value &value, AcceleratorBreakpointHitResponse &data,
+bool fromJSON(const json::Value &value, AcceleratorBreakpointHitResponse &data,
Path path) {
ObjectMapper o(value, path);
return o && o.map("disable_bp", data.disable_bp) &&
@@ -142,4 +144,64 @@ json::Value toJSON(const AcceleratorBreakpointHitResponse &data) {
return obj;
}
+bool fromJSON(const json::Value &value, AcceleratorSectionInfo &data,
+ Path path) {
+ ObjectMapper o(value, path);
+ return o && o.map("names", data.names) &&
+ o.map("load_address", data.load_address);
+}
+
+json::Value toJSON(const AcceleratorSectionInfo &data) {
+ return Object{{"names", data.names},
+ {"load_address", static_cast<int64_t>(data.load_address)}};
+}
+
+bool fromJSON(const json::Value &value,
+ AcceleratorDynamicLoaderLibraryInfo &data, Path path) {
+ ObjectMapper o(value, path);
+ return o && o.map("pathname", data.pathname) &&
+ o.mapOptional("uuid", data.uuid_str) && o.map("load", data.load) &&
+ o.mapOptional("load_address", data.load_address) &&
+ o.mapOptional("loaded_sections", data.loaded_sections) &&
+ o.mapOptional("native_memory_address", data.native_memory_address) &&
+ o.mapOptional("native_memory_size", data.native_memory_size) &&
+ o.mapOptional("file_offset", data.file_offset) &&
+ o.mapOptional("file_size", data.file_size);
+}
+
+json::Value toJSON(const AcceleratorDynamicLoaderLibraryInfo &data) {
+ return Object{
+ {"pathname", data.pathname},
+ {"uuid", data.uuid_str},
+ {"load", data.load},
+ {"load_address", data.load_address},
+ {"loaded_sections", data.loaded_sections},
+ {"native_memory_address", data.native_memory_address},
+ {"native_memory_size", data.native_memory_size},
+ {"file_offset", data.file_offset},
+ {"file_size", data.file_size},
+ };
+}
+
+bool fromJSON(const json::Value &value, AcceleratorDynamicLoaderArgs &data,
+ Path path) {
+ ObjectMapper o(value, path);
+ return o && o.map("plugin_name", data.plugin_name) &&
+ o.map("full", data.full);
+}
+
+json::Value toJSON(const AcceleratorDynamicLoaderArgs &data) {
+ return Object{{"plugin_name", data.plugin_name}, {"full", data.full}};
+}
+
+bool fromJSON(const json::Value &value, AcceleratorDynamicLoaderResponse &data,
+ Path path) {
+ ObjectMapper o(value, path);
+ return o && o.map("library_infos", data.library_infos);
+}
+
+json::Value toJSON(const AcceleratorDynamicLoaderResponse &data) {
+ return Object{{"library_infos", data.library_infos}};
+}
+
} // namespace lldb_private
diff --git a/lldb/source/Utility/StringExtractorGDBRemote.cpp b/lldb/source/Utility/StringExtractorGDBRemote.cpp
index 6fc3b63e02dd1..953a2cd3090d1 100644
--- a/lldb/source/Utility/StringExtractorGDBRemote.cpp
+++ b/lldb/source/Utility/StringExtractorGDBRemote.cpp
@@ -338,6 +338,8 @@ StringExtractorGDBRemote::GetServerPacketType() const {
return eServerPacketType_jAcceleratorPluginInitialize;
if (PACKET_STARTS_WITH("jAcceleratorPluginBreakpointHit:"))
return eServerPacketType_jAcceleratorPluginBreakpointHit;
+ if (PACKET_STARTS_WITH("jAcceleratorPluginGetDynamicLoaderLibraryInfo:"))
+ return eServerPacketType_jAcceleratorPluginGetDynamicLoaderLibraryInfo;
break;
case 'v':
diff --git a/lldb/test/API/accelerator/mock/TestMockAcceleratorPackets.py b/lldb/test/API/accelerator/mock/TestMockAcceleratorPackets.py
index 941e0c97a8f2e..e40e6491e59a0 100644
--- a/lldb/test/API/accelerator/mock/TestMockAcceleratorPackets.py
+++ b/lldb/test/API/accelerator/mock/TestMockAcceleratorPackets.py
@@ -186,3 +186,58 @@ def test_jAcceleratorPluginBreakpointHit_returns_connect_info(self):
connect_info["connect_url"],
)
self.assertTrue(connect_info["synchronous"])
+
+ @add_test_categories(["llgs"])
+ def test_jAcceleratorPluginGetDynamicLoaderLibraryInfo(self):
+ self.build()
+ self.set_inferior_startup_launch()
+ self.prep_debug_monitor_and_inferior()
+
+ self.add_qSupported_packets()
+ self.expect_gdbremote_sequence()
+
+ # Only "full" is exercised. The mock could answer full=False, but what
+ # a delta contains is entirely up to the plugin, so doing so would only
+ # test the mock's own bookkeeping. That belongs with a real plugin.
+ dyld_args = {"plugin_name": "mock", "full": True}
+ dyld_json = json.dumps(dyld_args, separators=(",", ":"))
+ escaped_json = escape_binary(dyld_json)
+ response = self.send_and_decode_json(
+ "jAcceleratorPluginGetDynamicLoaderLibraryInfo:" + escaped_json
+ )
+
+ self.assertIn("library_infos", response)
+ libs = response["library_infos"]
+ self.assertGreater(len(libs), 0)
+
+ lib = libs[0]
+ self.assertEqual(lib["pathname"], "/path/to/lib.so")
+ self.assertTrue(lib["load"])
+ self.assertEqual(lib["load_address"], 0x7F000000)
+
+ @add_test_categories(["llgs"])
+ def test_jAcceleratorPluginGetDynamicLoaderLibraryInfo_unknown_plugin(self):
+ self.build()
+ self.set_inferior_startup_launch()
+ self.prep_debug_monitor_and_inferior()
+
+ self.add_qSupported_packets()
+ self.expect_gdbremote_sequence()
+
+ dyld_args = {"plugin_name": "no-such-plugin", "full": True}
+ dyld_json = json.dumps(dyld_args, separators=(",", ":"))
+ escaped_json = escape_binary(dyld_json)
+ self.test_sequence.add_log_lines(
+ [
+ "read packet: $jAcceleratorPluginGetDynamicLoaderLibraryInfo:%s#00"
+ % escaped_json,
+ {
+ "direction": "send",
+ "regex": r"^\$(E[0-9a-fA-F]{2}.*)#[0-9a-fA-F]{2}",
+ "capture": {1: "error"},
+ },
+ ],
+ True,
+ )
+ context = self.expect_gdbremote_sequence()
+ self.assertIsNotNone(context.get("error"))
diff --git a/lldb/tools/lldb-server/Plugins/Accelerator/Mock/LLDBServerMockAcceleratorPlugin.cpp b/lldb/tools/lldb-server/Plugins/Accelerator/Mock/LLDBServerMockAcceleratorPlugin.cpp
index 6c1d23fef8a71..0cce2955f6e0d 100644
--- a/lldb/tools/lldb-server/Plugins/Accelerator/Mock/LLDBServerMockAcceleratorPlugin.cpp
+++ b/lldb/tools/lldb-server/Plugins/Accelerator/Mock/LLDBServerMockAcceleratorPlugin.cpp
@@ -220,3 +220,15 @@ LLDBServerMockAcceleratorPlugin::CreateConnection() {
info.synchronous = true;
return info;
}
+
+std::optional<AcceleratorDynamicLoaderResponse>
+LLDBServerMockAcceleratorPlugin::GetDynamicLoaderLibraryInfos(
+ const AcceleratorDynamicLoaderArgs &args) {
+ AcceleratorDynamicLoaderResponse response;
+ AcceleratorDynamicLoaderLibraryInfo lib;
+ lib.pathname = "/path/to/lib.so";
+ lib.load = true;
+ lib.load_address = 0x7f000000;
+ response.library_infos.push_back(std::move(lib));
+ return response;
+}
diff --git a/lldb/tools/lldb-server/Plugins/Accelerator/Mock/LLDBServerMockAcceleratorPlugin.h b/lldb/tools/lldb-server/Plugins/Accelerator/Mock/LLDBServerMockAcceleratorPlugin.h
index b07266d0ef65f..36f6aa2500ba8 100644
--- a/lldb/tools/lldb-server/Plugins/Accelerator/Mock/LLDBServerMockAcceleratorPlugin.h
+++ b/lldb/tools/lldb-server/Plugins/Accelerator/Mock/LLDBServerMockAcceleratorPlugin.h
@@ -34,6 +34,9 @@ class LLDBServerMockAcceleratorPlugin : public LLDBServerAcceleratorPlugin {
llvm::Expected<AcceleratorBreakpointHitResponse>
BreakpointWasHit(AcceleratorBreakpointHitArgs &args) override;
+ std::optional<AcceleratorDynamicLoaderResponse> GetDynamicLoaderLibraryInfos(
+ const AcceleratorDynamicLoaderArgs &args) override;
+
private:
// Lazily bring up the mock accelerator GDB server and return its connection
// info. Called on the connection breakpoint hit, so inferiors that never
diff --git a/lldb/unittests/Utility/AcceleratorGDBRemotePacketsTest.cpp b/lldb/unittests/Utility/AcceleratorGDBRemotePacketsTest.cpp
index 571dd2cab124a..8d64b4a8acf78 100644
--- a/lldb/unittests/Utility/AcceleratorGDBRemotePacketsTest.cpp
+++ b/lldb/unittests/Utility/AcceleratorGDBRemotePacketsTest.cpp
@@ -244,3 +244,84 @@ TEST(AcceleratorGDBRemotePacketsTest, AcceleratorActionsWithoutConnectInfo) {
ASSERT_THAT_EXPECTED(deserialized, Succeeded());
EXPECT_FALSE(deserialized->connect_info.has_value());
}
+
+TEST(AcceleratorGDBRemotePacketsTest, AcceleratorSectionInfo) {
+ AcceleratorSectionInfo section;
+ section.names = {"PT_LOAD[0]", ".text"};
+ section.load_address = 0x400000;
+
+ Expected<AcceleratorSectionInfo> deserialized = roundtripJSON(section);
+ ASSERT_THAT_EXPECTED(deserialized, Succeeded());
+ EXPECT_EQ(section.names, deserialized->names);
+ EXPECT_EQ(section.load_address, deserialized->load_address);
+}
+
+TEST(AcceleratorGDBRemotePacketsTest, AcceleratorDynamicLoaderLibraryInfo) {
+ AcceleratorDynamicLoaderLibraryInfo lib;
+ lib.pathname = "/usr/lib/libgpu.so";
+ lib.uuid_str = "AABBCCDD";
+ lib.load = true;
+ lib.load_address = 0x7f000000;
+ lib.native_memory_address = 0x1000;
+ lib.native_memory_size = 0x2000;
+ lib.file_offset = 4096;
+ lib.file_size = 8192;
+
+ AcceleratorSectionInfo section;
+ section.names = {".text"};
+ section.load_address = 0x7f001000;
+ lib.loaded_sections.push_back(std::move(section));
+
+ Expected<AcceleratorDynamicLoaderLibraryInfo> deserialized =
+ roundtripJSON(lib);
+ ASSERT_THAT_EXPECTED(deserialized, Succeeded());
+ EXPECT_EQ(lib.pathname, deserialized->pathname);
+ EXPECT_EQ(lib.uuid_str, deserialized->uuid_str);
+ EXPECT_EQ(lib.load, deserialized->load);
+ EXPECT_EQ(lib.load_address, deserialized->load_address);
+ EXPECT_EQ(lib.native_memory_address, deserialized->native_memory_address);
+ EXPECT_EQ(lib.native_memory_size, deserialized->native_memory_size);
+ EXPECT_EQ(lib.file_offset, deserialized->file_offset);
+ EXPECT_EQ(lib.file_size, deserialized->file_size);
+ ASSERT_EQ(1u, deserialized->loaded_sections.size());
+ EXPECT_EQ(0x7f001000u, deserialized->loaded_sections[0].load_address);
+}
+
+TEST(AcceleratorGDBRemotePacketsTest, AcceleratorDynamicLoaderArgs) {
+ AcceleratorDynamicLoaderArgs args;
+ args.plugin_name = "mock";
+ args.full = true;
+
+ Expected<AcceleratorDynamicLoaderArgs> deserialized = roundtripJSON(args);
+ ASSERT_THAT_EXPECTED(deserialized, Succeeded());
+ EXPECT_EQ(args.plugin_name, deserialized->plugin_name);
+ EXPECT_EQ(args.full, deserialized->full);
+}
+
+TEST(AcceleratorGDBRemotePacketsTest, AcceleratorDynamicLoaderResponse) {
+ AcceleratorDynamicLoaderResponse response;
+ AcceleratorDynamicLoaderLibraryInfo lib;
+ lib.pathname = "/path/to/lib.so";
+ lib.load = true;
+ response.library_infos.push_back(std::move(lib));
+
+ Expected<AcceleratorDynamicLoaderResponse> deserialized =
+ roundtripJSON(response);
+ ASSERT_THAT_EXPECTED(deserialized, Succeeded());
+ ASSERT_EQ(1u, deserialized->library_infos.size());
+ EXPECT_EQ("/path/to/lib.so", deserialized->library_infos[0].pathname);
+ EXPECT_TRUE(deserialized->library_infos[0].load);
+}
+
+TEST(AcceleratorGDBRemotePacketsTest, DynamicLoaderLibraryInfoOptionalFields) {
+ // A server with no per-section addresses can omit "loaded_sections".
+ Expected<AcceleratorDynamicLoaderLibraryInfo> deserialized =
+ json::parse<AcceleratorDynamicLoaderLibraryInfo>(
+ R"({"pathname":"/path/to/lib.so","load":true})",
+ "AcceleratorDynamicLoaderLibraryInfo");
+ ASSERT_THAT_EXPECTED(deserialized, Succeeded());
+ EXPECT_EQ("/path/to/lib.so", deserialized->pathname);
+ EXPECT_TRUE(deserialized->load);
+ EXPECT_TRUE(deserialized->loaded_sections.empty());
+ EXPECT_FALSE(deserialized->load_address.has_value());
+}
>From 40183939353b57bc84b12dc44a756f7b19f5e337 Mon Sep 17 00:00:00 2001
From: satya janga <satyajanga at fb.com>
Date: Sun, 5 Jul 2026 08:01:36 -0700
Subject: [PATCH 2/2] [lldb] Add accelerator dynamic loader over gdb-remote
Accelerator (e.g. GPU) targets do not use a rendezvous breakpoint the way
SVR4 loaders do; their runtime tells the lldb-server accelerator plugin when
libraries load or unload. This adds a DynamicLoader that gets that list from
the server and loads the modules into the target.
The loader is selected for accelerator architectures debugged over gdb-remote:
CreateInstance claims AMDGPU and NVPTX triples backed by a ProcessGDBRemote.
It then asks for libraries via jAcceleratorPluginGetDynamicLoaderLibraryInfo
and loads each, honoring a whole-file path or a slice of a containing file
located by file offset/size.
The mock accelerator process implements the new NativeProcessProtocol hook so
the same packet is served on an accelerator connection. Tests use a mock GDB
server reporting an AMDGPU target, covering both library-provision paths and
that a host target does not select this loader.
---
.../lldb/Host/common/NativeProcessProtocol.h | 9 +
.../AcceleratorGDBRemote/CMakeLists.txt | 13 ++
.../DynamicLoaderAcceleratorGDBRemote.cpp | 156 ++++++++++++++++++
.../DynamicLoaderAcceleratorGDBRemote.h | 46 ++++++
.../Plugins/DynamicLoader/CMakeLists.txt | 1 +
.../GDBRemoteCommunicationClient.cpp | 23 +++
.../gdb-remote/GDBRemoteCommunicationClient.h | 5 +
.../GDBRemoteCommunicationServerLLGS.cpp | 41 +++--
.../TestAcceleratorDynamicLoader.py | 153 +++++++++++++++++
.../dynamic_loader/accelerator.yaml | 21 +++
.../dynamic_loader/accelerator_lib.yaml | 21 +++
.../dynamic_loader/embedded_lib.yaml | 21 +++
.../API/accelerator/dynamic_loader/host.yaml | 20 +++
.../Mock/ProcessMockAccelerator.cpp | 12 ++
.../Accelerator/Mock/ProcessMockAccelerator.h | 4 +
15 files changed, 533 insertions(+), 13 deletions(-)
create mode 100644 lldb/source/Plugins/DynamicLoader/AcceleratorGDBRemote/CMakeLists.txt
create mode 100644 lldb/source/Plugins/DynamicLoader/AcceleratorGDBRemote/DynamicLoaderAcceleratorGDBRemote.cpp
create mode 100644 lldb/source/Plugins/DynamicLoader/AcceleratorGDBRemote/DynamicLoaderAcceleratorGDBRemote.h
create mode 100644 lldb/test/API/accelerator/dynamic_loader/TestAcceleratorDynamicLoader.py
create mode 100644 lldb/test/API/accelerator/dynamic_loader/accelerator.yaml
create mode 100644 lldb/test/API/accelerator/dynamic_loader/accelerator_lib.yaml
create mode 100644 lldb/test/API/accelerator/dynamic_loader/embedded_lib.yaml
create mode 100644 lldb/test/API/accelerator/dynamic_loader/host.yaml
diff --git a/lldb/include/lldb/Host/common/NativeProcessProtocol.h b/lldb/include/lldb/Host/common/NativeProcessProtocol.h
index 67206c4b55b79..0befe0d211fa7 100644
--- a/lldb/include/lldb/Host/common/NativeProcessProtocol.h
+++ b/lldb/include/lldb/Host/common/NativeProcessProtocol.h
@@ -14,6 +14,7 @@
#include "NativeWatchpointList.h"
#include "lldb/Host/Host.h"
#include "lldb/Host/MainLoop.h"
+#include "lldb/Utility/AcceleratorGDBRemotePackets.h"
#include "lldb/Utility/ArchSpec.h"
#include "lldb/Utility/Iterable.h"
#include "lldb/Utility/ProcessAddress.h"
@@ -163,6 +164,14 @@ class NativeProcessProtocol {
"Not implemented");
}
+ /// Answers "jAcceleratorPluginGetDynamicLoaderLibraryInfo" on an accelerator
+ /// connection. std::nullopt if this process does not provide libraries.
+ virtual std::optional<AcceleratorDynamicLoaderResponse>
+ GetAcceleratorDynamicLoaderLibraryInfos(
+ const AcceleratorDynamicLoaderArgs &args) {
+ return std::nullopt;
+ }
+
virtual bool HasPendingLibraryEvents() { return false; }
virtual bool IsAlive() const;
diff --git a/lldb/source/Plugins/DynamicLoader/AcceleratorGDBRemote/CMakeLists.txt b/lldb/source/Plugins/DynamicLoader/AcceleratorGDBRemote/CMakeLists.txt
new file mode 100644
index 0000000000000..b5cf71d1f8b31
--- /dev/null
+++ b/lldb/source/Plugins/DynamicLoader/AcceleratorGDBRemote/CMakeLists.txt
@@ -0,0 +1,13 @@
+add_lldb_library(lldbPluginDynamicLoaderAcceleratorGDBRemote PLUGIN
+ DynamicLoaderAcceleratorGDBRemote.cpp
+
+ LINK_LIBS
+ lldbCore
+ lldbHost
+ lldbSymbol
+ lldbTarget
+ lldbUtility
+ lldbPluginProcessGDBRemote
+ LINK_COMPONENTS
+ Support
+ )
diff --git a/lldb/source/Plugins/DynamicLoader/AcceleratorGDBRemote/DynamicLoaderAcceleratorGDBRemote.cpp b/lldb/source/Plugins/DynamicLoader/AcceleratorGDBRemote/DynamicLoaderAcceleratorGDBRemote.cpp
new file mode 100644
index 0000000000000..9c6a391525a08
--- /dev/null
+++ b/lldb/source/Plugins/DynamicLoader/AcceleratorGDBRemote/DynamicLoaderAcceleratorGDBRemote.cpp
@@ -0,0 +1,156 @@
+//===-- DynamicLoaderAcceleratorGDBRemote.cpp ----------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "DynamicLoaderAcceleratorGDBRemote.h"
+#include "Plugins/Process/gdb-remote/ProcessGDBRemote.h"
+#include "lldb/Core/Module.h"
+#include "lldb/Core/PluginManager.h"
+#include "lldb/Core/Section.h"
+#include "lldb/Target/Target.h"
+#include "lldb/Utility/LLDBLog.h"
+#include "lldb/Utility/Log.h"
+
+using namespace lldb;
+using namespace lldb_private;
+using namespace lldb_private::process_gdb_remote;
+
+LLDB_PLUGIN_DEFINE(DynamicLoaderAcceleratorGDBRemote)
+
+DynamicLoader *
+DynamicLoaderAcceleratorGDBRemote::CreateInstance(Process *process,
+ bool force) {
+ // The library list comes from the accelerator's GDB server.
+ if (process->GetPluginName() != ProcessGDBRemote::GetPluginNameStatic())
+ return nullptr;
+ if (force)
+ return new DynamicLoaderAcceleratorGDBRemote(process);
+
+ const llvm::Triple &triple =
+ process->GetTarget().GetArchitecture().GetTriple();
+ if (triple.isAMDGPU() || triple.isNVPTX())
+ return new DynamicLoaderAcceleratorGDBRemote(process);
+ return nullptr;
+}
+
+DynamicLoaderAcceleratorGDBRemote::DynamicLoaderAcceleratorGDBRemote(
+ Process *process)
+ : DynamicLoader(process) {}
+
+void DynamicLoaderAcceleratorGDBRemote::DidAttach() {
+ LoadModulesFromGDBServer(/*full=*/true);
+}
+
+void DynamicLoaderAcceleratorGDBRemote::DidLaunch() {
+ LoadModulesFromGDBServer(/*full=*/true);
+}
+
+bool DynamicLoaderAcceleratorGDBRemote::LoadModulesFromGDBServer(bool full) {
+ Log *log = GetLog(LLDBLog::DynamicLoader);
+
+ // Safe: CreateInstance only builds this loader for a ProcessGDBRemote.
+ ProcessGDBRemote *gdb_process = static_cast<ProcessGDBRemote *>(m_process);
+ AcceleratorDynamicLoaderArgs args;
+ args.full = full;
+
+ Target &target = m_process->GetTarget();
+ ModuleList loaded_module_list;
+ std::optional<AcceleratorDynamicLoaderResponse> response =
+ gdb_process->GetGDBRemote().GetAcceleratorDynamicLoaderLibraryInfos(args);
+ if (!response) {
+ LLDB_LOG(log, "failed to get dynamic loader info from the GDB server");
+ return false;
+ }
+
+ for (const AcceleratorDynamicLoaderLibraryInfo &info :
+ response->library_infos) {
+ UUID uuid;
+ if (info.uuid_str)
+ uuid.SetFromStringRef(*info.uuid_str);
+
+ // Either a whole file, or a slice of a containing file.
+ ModuleSpec module_spec(FileSpec(info.pathname), uuid);
+ if (info.file_offset)
+ module_spec.SetObjectOffset(*info.file_offset);
+ if (info.file_size)
+ module_spec.SetObjectSize(*info.file_size);
+
+ if (!info.load) {
+ ModuleList matching_module_list;
+ target.GetImages().FindModules(module_spec, matching_module_list);
+ matching_module_list.ForEach(
+ [this](const ModuleSP &module_sp) -> IterationAction {
+ UnloadSections(module_sp);
+ return IterationAction::Continue;
+ });
+ continue;
+ }
+
+ ModuleSP module_sp = target.GetOrCreateModule(module_spec, /*notify=*/true);
+ if (!module_sp)
+ continue;
+
+ bool changed = false;
+ if (info.load_address) {
+ module_sp->SetLoadAddress(target, *info.load_address,
+ /*value_is_offset=*/true, changed);
+ } else if (!info.loaded_sections.empty()) {
+ for (const AcceleratorSectionInfo § : info.loaded_sections) {
+ if (sect.names.empty())
+ continue;
+ SectionSP section_sp;
+ for (const std::string &name : sect.names) {
+ ConstString section_name(name);
+ if (section_sp)
+ section_sp =
+ section_sp->GetChildren().FindSectionByName(section_name);
+ else
+ section_sp =
+ module_sp->GetSectionList()->FindSectionByName(section_name);
+ if (!section_sp)
+ break;
+ }
+ if (section_sp)
+ changed |= target.SetSectionLoadAddress(section_sp, sect.load_address,
+ /*warn_multiple=*/true);
+ }
+ } else {
+ // No slide: load at the file addresses.
+ module_sp->SetLoadAddress(target, 0, /*value_is_offset=*/true, changed);
+ }
+
+ if (changed)
+ loaded_module_list.AppendIfNeeded(module_sp);
+ }
+
+ target.ModulesDidLoad(loaded_module_list);
+ return true;
+}
+
+ThreadPlanSP DynamicLoaderAcceleratorGDBRemote::GetStepThroughTrampolinePlan(
+ Thread &thread, bool stop_others) {
+ return ThreadPlanSP();
+}
+
+Status DynamicLoaderAcceleratorGDBRemote::CanLoadImage() {
+ return Status::FromErrorString("can't load images on accelerator targets");
+}
+
+void DynamicLoaderAcceleratorGDBRemote::Initialize() {
+ PluginManager::RegisterPlugin(GetPluginNameStatic(),
+ GetPluginDescriptionStatic(), CreateInstance);
+}
+
+void DynamicLoaderAcceleratorGDBRemote::Terminate() {
+ PluginManager::UnregisterPlugin(CreateInstance);
+}
+
+llvm::StringRef
+DynamicLoaderAcceleratorGDBRemote::GetPluginDescriptionStatic() {
+ return "Dynamic loader plug-in that gets shared library loads/unloads from "
+ "an lldb-server accelerator plugin.";
+}
diff --git a/lldb/source/Plugins/DynamicLoader/AcceleratorGDBRemote/DynamicLoaderAcceleratorGDBRemote.h b/lldb/source/Plugins/DynamicLoader/AcceleratorGDBRemote/DynamicLoaderAcceleratorGDBRemote.h
new file mode 100644
index 0000000000000..1af218b7acd8c
--- /dev/null
+++ b/lldb/source/Plugins/DynamicLoader/AcceleratorGDBRemote/DynamicLoaderAcceleratorGDBRemote.h
@@ -0,0 +1,46 @@
+//===-- DynamicLoaderAcceleratorGDBRemote.h -------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_SOURCE_PLUGINS_DYNAMICLOADER_ACCELERATORGDBREMOTE_DYNAMICLOADERACCELERATORGDBREMOTE_H
+#define LLDB_SOURCE_PLUGINS_DYNAMICLOADER_ACCELERATORGDBREMOTE_DYNAMICLOADERACCELERATORGDBREMOTE_H
+
+#include "lldb/Target/DynamicLoader.h"
+
+/// Dynamic loader for accelerator (e.g. GPU) targets.
+///
+/// Accelerators don't set a rendezvous breakpoint the way SVR4 loaders do;
+/// their runtime tells the lldb-server plugin when libraries load or unload.
+/// This loader asks the server for that list via
+/// "jAcceleratorPluginGetDynamicLoaderLibraryInfo".
+class DynamicLoaderAcceleratorGDBRemote : public lldb_private::DynamicLoader {
+public:
+ DynamicLoaderAcceleratorGDBRemote(lldb_private::Process *process);
+
+ static void Initialize();
+ static void Terminate();
+ static llvm::StringRef GetPluginNameStatic() {
+ return "accelerator-gdb-remote";
+ }
+ static llvm::StringRef GetPluginDescriptionStatic();
+ static lldb_private::DynamicLoader *
+ CreateInstance(lldb_private::Process *process, bool force);
+
+ void DidAttach() override;
+ void DidLaunch() override;
+ lldb::ThreadPlanSP GetStepThroughTrampolinePlan(lldb_private::Thread &thread,
+ bool stop_others) override;
+ lldb_private::Status CanLoadImage() override;
+
+ llvm::StringRef GetPluginName() override { return GetPluginNameStatic(); }
+
+private:
+ /// Returns true if the server answered the packet.
+ bool LoadModulesFromGDBServer(bool full);
+};
+
+#endif // LLDB_SOURCE_PLUGINS_DYNAMICLOADER_ACCELERATORGDBREMOTE_DYNAMICLOADERACCELERATORGDBREMOTE_H
diff --git a/lldb/source/Plugins/DynamicLoader/CMakeLists.txt b/lldb/source/Plugins/DynamicLoader/CMakeLists.txt
index 01aba34b94169..97604bb4824db 100644
--- a/lldb/source/Plugins/DynamicLoader/CMakeLists.txt
+++ b/lldb/source/Plugins/DynamicLoader/CMakeLists.txt
@@ -5,6 +5,7 @@ set_property(DIRECTORY PROPERTY LLDB_TOLERATED_PLUGIN_DEPENDENCIES
TypeSystem
)
+add_subdirectory(AcceleratorGDBRemote)
add_subdirectory(Darwin-Kernel)
add_subdirectory(FreeBSD-Kernel)
add_subdirectory(MacOSX-DYLD)
diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
index b440869f25984..4dc14d8ff8a4f 100644
--- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
+++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
@@ -300,6 +300,29 @@ GDBRemoteCommunicationClient::AcceleratorBreakpointHit(
response.GetStringRef(), llvm::toString(hit_response.takeError()));
}
+std::optional<AcceleratorDynamicLoaderResponse>
+GDBRemoteCommunicationClient::GetAcceleratorDynamicLoaderLibraryInfos(
+ const AcceleratorDynamicLoaderArgs &args) {
+ StreamGDBRemote packet;
+ packet.PutCString("jAcceleratorPluginGetDynamicLoaderLibraryInfo:");
+ packet.PutAsJSON(args, /*hex_ascii=*/false);
+
+ StringExtractorGDBRemote response;
+ if (SendPacketAndWaitForResponse(packet.GetString(), response) !=
+ PacketResult::Success ||
+ response.IsErrorResponse())
+ return std::nullopt;
+
+ llvm::Expected<AcceleratorDynamicLoaderResponse> parsed =
+ llvm::json::parse<AcceleratorDynamicLoaderResponse>(
+ response.Peek(), "AcceleratorDynamicLoaderResponse");
+ if (!parsed) {
+ llvm::consumeError(parsed.takeError());
+ return std::nullopt;
+ }
+ return *parsed;
+}
+
bool GDBRemoteCommunicationClient::QueryNoAckModeSupported() {
if (m_supports_not_sending_acks == eLazyBoolCalculate) {
m_send_acks = true;
diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h
index 3a0a34f840c21..9f946f030c4ff 100644
--- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h
+++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h
@@ -377,6 +377,11 @@ class GDBRemoteCommunicationClient : public GDBRemoteClientBase {
llvm::Expected<AcceleratorBreakpointHitResponse>
AcceleratorBreakpointHit(const AcceleratorBreakpointHitArgs &args);
+ /// Returns std::nullopt if the packet failed or the response did not parse.
+ std::optional<AcceleratorDynamicLoaderResponse>
+ GetAcceleratorDynamicLoaderLibraryInfos(
+ const AcceleratorDynamicLoaderArgs &args);
+
LazyBool SupportsAllocDeallocMemory() // const
{
// Uncomment this to have lldb pretend the debug server doesn't respond to
diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp
index 791c02fd96b20..159f423ca0d66 100644
--- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp
+++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp
@@ -4659,20 +4659,35 @@ GDBRemoteCommunication::PacketResult GDBRemoteCommunicationServerLLGS::
if (!args)
return SendErrorResponse(args.takeError());
- for (std::unique_ptr<lldb_server::LLDBServerAcceleratorPlugin> &plugin_up :
- m_accelerator_plugins) {
- if (plugin_up->GetPluginName() == args->plugin_name) {
- std::optional<AcceleratorDynamicLoaderResponse> response =
- plugin_up->GetDynamicLoaderLibraryInfos(*args);
- if (response) {
- StreamGDBRemote stream;
- stream.PutAsJSON(*response, /*hex_ascii=*/false);
- return SendPacketNoLock(stream.GetString());
+ // On the native connection, forward to the named accelerator plugin.
+ if (!m_accelerator_plugins.empty()) {
+ for (std::unique_ptr<lldb_server::LLDBServerAcceleratorPlugin> &plugin_up :
+ m_accelerator_plugins) {
+ if (plugin_up->GetPluginName() == args->plugin_name) {
+ std::optional<AcceleratorDynamicLoaderResponse> response =
+ plugin_up->GetDynamicLoaderLibraryInfos(*args);
+ if (response) {
+ StreamGDBRemote stream;
+ stream.PutAsJSON(*response, /*hex_ascii=*/false);
+ return SendPacketNoLock(stream.GetString());
+ }
+ return SendErrorResponse(
+ Status::FromErrorString("no dynamic loader info available"));
}
- return SendErrorResponse(
- Status::FromErrorString("no dynamic loader info available"));
}
+ return SendErrorResponse(
+ Status::FromErrorString("unknown accelerator plugin name"));
}
- return SendErrorResponse(
- Status::FromErrorString("unknown accelerator plugin name"));
+
+ // On the accelerator connection, ask the process directly.
+ if (!m_current_process)
+ return SendErrorResponse(Status::FromErrorString("no current process"));
+ std::optional<AcceleratorDynamicLoaderResponse> response =
+ m_current_process->GetAcceleratorDynamicLoaderLibraryInfos(*args);
+ if (!response)
+ return SendErrorResponse(
+ Status::FromErrorString("dynamic loader library info not supported"));
+ StreamGDBRemote stream;
+ stream.PutAsJSON(*response, /*hex_ascii=*/false);
+ return SendPacketNoLock(stream.GetString());
}
diff --git a/lldb/test/API/accelerator/dynamic_loader/TestAcceleratorDynamicLoader.py b/lldb/test/API/accelerator/dynamic_loader/TestAcceleratorDynamicLoader.py
new file mode 100644
index 0000000000000..30740642fbd07
--- /dev/null
+++ b/lldb/test/API/accelerator/dynamic_loader/TestAcceleratorDynamicLoader.py
@@ -0,0 +1,153 @@
+"""
+Test the accelerator dynamic loader against a mock GDB server.
+
+The loader is selected for accelerator architectures debugged over gdb-remote.
+It then asks the server for the loaded libraries via
+jAcceleratorPluginGetDynamicLoaderLibraryInfo and loads them into the target.
+"""
+
+import json
+import os
+
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.gdbclientutils import *
+from lldbsuite.test.lldbgdbclient import GDBRemoteTestBase
+
+DYLD_PACKET = "jAcceleratorPluginGetDynamicLoaderLibraryInfo:"
+
+
+class AcceleratorResponder(MockGDBServerResponder):
+ """Serves a fixed set of library infos, and counts how often it is asked."""
+
+ def __init__(self, library_infos):
+ MockGDBServerResponder.__init__(self)
+ self.library_infos = library_infos
+ self.dyld_queries = 0
+
+ def qSupported(self, client_supported):
+ return super().qSupported(client_supported) + ";qXfer:features:read+"
+
+ def qXferRead(self, obj, annex, offset, length):
+ # An accelerator architecture has no built-in register set in lldb.
+ if obj == "features" and annex == "target.xml":
+ return (
+ """<?xml version="1.0"?>
+ <target version="1.0">
+ <feature name="org.llvm.accelerator">
+ <reg name="pc" bitsize="64" regnum="0" type="code_ptr" group="general"/>
+ </feature>
+ </target>""",
+ False,
+ )
+ return None, False
+
+ def readRegisters(self):
+ return "00" * 8
+
+ def other(self, packet):
+ if packet.startswith(DYLD_PACKET):
+ self.dyld_queries += 1
+ # "}" is the gdb-remote escape character.
+ return escape_binary(
+ json.dumps({"library_infos": self.library_infos}, separators=(",", ":"))
+ )
+ return ""
+
+
+class TestAcceleratorDynamicLoader(GDBRemoteTestBase):
+ def make_library(self):
+ """Build the library object file the server will report."""
+ path = self.getBuildArtifact("accelerator_lib.so")
+ self.yaml2obj("accelerator_lib.yaml", path)
+ return path
+
+ def make_container(self, outer_path):
+ """Embed a library in a larger file, as llvm-objcopy would when adding
+ it to a container. Returns the container path and the (offset, size) of
+ the embedded object.
+
+ The embedded object puts .text at a different address than the outer
+ one, so the assertions fail if the slice is ignored and the container is
+ parsed from offset 0."""
+ embedded_path = self.getBuildArtifact("embedded_lib.so")
+ self.yaml2obj("embedded_lib.yaml", embedded_path)
+ with open(outer_path, "rb") as f:
+ outer_bytes = f.read()
+ with open(embedded_path, "rb") as f:
+ embedded_bytes = f.read()
+ # The container must itself be a valid object file.
+ prefix = outer_bytes + b"\x00" * ((-len(outer_bytes)) % 0x1000)
+ container_path = self.getBuildArtifact("container.bin")
+ with open(container_path, "wb") as f:
+ f.write(prefix)
+ f.write(embedded_bytes)
+ return container_path, len(prefix), len(embedded_bytes)
+
+ def find_module(self, target, path):
+ basename = os.path.basename(path)
+ for i in range(target.GetNumModules()):
+ module = target.GetModuleAtIndex(i)
+ if module.GetFileSpec().GetFilename() == basename:
+ return module
+ return None
+
+ def connect_accelerator(self, library_infos):
+ self.server.responder = AcceleratorResponder(library_infos)
+ target = self.createTarget("accelerator.yaml")
+ process = self.connect(target)
+ self.assertTrue(process.IsValid(), "Process is valid")
+ return target
+
+ def assert_text_loaded_at(self, target, module, expected):
+ section = module.FindSection(".text")
+ self.assertTrue(section.IsValid(), "library should have a .text section")
+ self.assertEqual(section.GetLoadAddress(target), expected)
+
+ def test_whole_file_library(self):
+ """A library given as a whole file is loaded at the reported address."""
+ lib = self.make_library()
+ target = self.connect_accelerator(
+ [{"pathname": lib, "load": True, "load_address": 0x10000000}]
+ )
+
+ module = self.find_module(target, lib)
+ self.assertIsNotNone(module, "library should be loaded into the target")
+ # load_address slides the file, so .text (file address 0x1000) lands
+ # 0x1000 past the base.
+ self.assert_text_loaded_at(target, module, 0x10001000)
+
+ def test_library_in_container(self):
+ """A library embedded in a container file is located by offset/size."""
+ lib = self.make_library()
+ container, offset, size = self.make_container(lib)
+ target = self.connect_accelerator(
+ [
+ {
+ "pathname": container,
+ "load": True,
+ "load_address": 0x20000000,
+ "file_offset": offset,
+ "file_size": size,
+ }
+ ]
+ )
+
+ module = self.find_module(target, container)
+ self.assertIsNotNone(module, "embedded library should be loaded")
+ # The embedded object has .text at 0x3000; the outer one has it at
+ # 0x1000, so this only holds if the slice was used.
+ self.assert_text_loaded_at(target, module, 0x20003000)
+
+ def test_not_selected_for_host_target(self):
+ """The loader is not used for a non-accelerator architecture."""
+ self.server.responder = AcceleratorResponder([])
+ target = self.createTarget("host.yaml")
+ process = self.connect(target)
+ self.assertTrue(process.IsValid(), "Process is valid")
+
+ self.assertEqual(
+ self.server.responder.dyld_queries,
+ 0,
+ "a host target must not query the accelerator dynamic loader",
+ )
diff --git a/lldb/test/API/accelerator/dynamic_loader/accelerator.yaml b/lldb/test/API/accelerator/dynamic_loader/accelerator.yaml
new file mode 100644
index 0000000000000..8a255dcba2de2
--- /dev/null
+++ b/lldb/test/API/accelerator/dynamic_loader/accelerator.yaml
@@ -0,0 +1,21 @@
+--- !ELF
+FileHeader:
+ Class: ELFCLASS64
+ Data: ELFDATA2LSB
+ Type: ET_EXEC
+ Machine: EM_AMDGPU
+ OSABI: ELFOSABI_AMDGPU_HSA
+Sections:
+ - Name: .text
+ Type: SHT_PROGBITS
+ Flags: [ SHF_ALLOC, SHF_EXECINSTR ]
+ Address: 0x1000
+ AddressAlign: 0x1000
+ Content: "00000000"
+ProgramHeaders:
+ - Type: PT_LOAD
+ Flags: [ PF_X, PF_R ]
+ VAddr: 0x1000
+ Align: 0x1000
+ FirstSec: .text
+ LastSec: .text
diff --git a/lldb/test/API/accelerator/dynamic_loader/accelerator_lib.yaml b/lldb/test/API/accelerator/dynamic_loader/accelerator_lib.yaml
new file mode 100644
index 0000000000000..e7da8940a9fd3
--- /dev/null
+++ b/lldb/test/API/accelerator/dynamic_loader/accelerator_lib.yaml
@@ -0,0 +1,21 @@
+--- !ELF
+FileHeader:
+ Class: ELFCLASS64
+ Data: ELFDATA2LSB
+ Type: ET_DYN
+ Machine: EM_AMDGPU
+ OSABI: ELFOSABI_AMDGPU_HSA
+Sections:
+ - Name: .text
+ Type: SHT_PROGBITS
+ Flags: [ SHF_ALLOC, SHF_EXECINSTR ]
+ Address: 0x1000
+ AddressAlign: 0x1000
+ Content: "00000000"
+ProgramHeaders:
+ - Type: PT_LOAD
+ Flags: [ PF_X, PF_R ]
+ VAddr: 0x1000
+ Align: 0x1000
+ FirstSec: .text
+ LastSec: .text
diff --git a/lldb/test/API/accelerator/dynamic_loader/embedded_lib.yaml b/lldb/test/API/accelerator/dynamic_loader/embedded_lib.yaml
new file mode 100644
index 0000000000000..5d59cb1620de2
--- /dev/null
+++ b/lldb/test/API/accelerator/dynamic_loader/embedded_lib.yaml
@@ -0,0 +1,21 @@
+--- !ELF
+FileHeader:
+ Class: ELFCLASS64
+ Data: ELFDATA2LSB
+ Type: ET_DYN
+ Machine: EM_AMDGPU
+ OSABI: ELFOSABI_AMDGPU_HSA
+Sections:
+ - Name: .text
+ Type: SHT_PROGBITS
+ Flags: [ SHF_ALLOC, SHF_EXECINSTR ]
+ Address: 0x3000
+ AddressAlign: 0x1000
+ Content: "00000000"
+ProgramHeaders:
+ - Type: PT_LOAD
+ Flags: [ PF_X, PF_R ]
+ VAddr: 0x3000
+ Align: 0x1000
+ FirstSec: .text
+ LastSec: .text
diff --git a/lldb/test/API/accelerator/dynamic_loader/host.yaml b/lldb/test/API/accelerator/dynamic_loader/host.yaml
new file mode 100644
index 0000000000000..a7efe4fed40b1
--- /dev/null
+++ b/lldb/test/API/accelerator/dynamic_loader/host.yaml
@@ -0,0 +1,20 @@
+--- !ELF
+FileHeader:
+ Class: ELFCLASS64
+ Data: ELFDATA2LSB
+ Type: ET_EXEC
+ Machine: EM_X86_64
+Sections:
+ - Name: .text
+ Type: SHT_PROGBITS
+ Flags: [ SHF_ALLOC, SHF_EXECINSTR ]
+ Address: 0x1000
+ AddressAlign: 0x1000
+ Content: "c3"
+ProgramHeaders:
+ - Type: PT_LOAD
+ Flags: [ PF_X, PF_R ]
+ VAddr: 0x1000
+ Align: 0x1000
+ FirstSec: .text
+ LastSec: .text
diff --git a/lldb/tools/lldb-server/Plugins/Accelerator/Mock/ProcessMockAccelerator.cpp b/lldb/tools/lldb-server/Plugins/Accelerator/Mock/ProcessMockAccelerator.cpp
index 8ee3510e67e0b..2174ad650a5b4 100644
--- a/lldb/tools/lldb-server/Plugins/Accelerator/Mock/ProcessMockAccelerator.cpp
+++ b/lldb/tools/lldb-server/Plugins/Accelerator/Mock/ProcessMockAccelerator.cpp
@@ -111,3 +111,15 @@ ProcessMockAccelerator::GetFileLoadAddress(const llvm::StringRef &file_name,
lldb::addr_t &load_addr) {
return Status::FromErrorString("unimplemented");
}
+
+std::optional<AcceleratorDynamicLoaderResponse>
+ProcessMockAccelerator::GetAcceleratorDynamicLoaderLibraryInfos(
+ const AcceleratorDynamicLoaderArgs &args) {
+ AcceleratorDynamicLoaderResponse response;
+ AcceleratorDynamicLoaderLibraryInfo info;
+ info.pathname = "/path/to/lib.so";
+ info.load = true;
+ info.load_address = 0x10000000;
+ response.library_infos.push_back(std::move(info));
+ return response;
+}
diff --git a/lldb/tools/lldb-server/Plugins/Accelerator/Mock/ProcessMockAccelerator.h b/lldb/tools/lldb-server/Plugins/Accelerator/Mock/ProcessMockAccelerator.h
index 6346773e40141..cbd473cdd19b1 100644
--- a/lldb/tools/lldb-server/Plugins/Accelerator/Mock/ProcessMockAccelerator.h
+++ b/lldb/tools/lldb-server/Plugins/Accelerator/Mock/ProcessMockAccelerator.h
@@ -60,6 +60,10 @@ class ProcessMockAccelerator : public NativeProcessProtocol {
Status GetFileLoadAddress(const llvm::StringRef &file_name,
lldb::addr_t &load_addr) override;
+ std::optional<AcceleratorDynamicLoaderResponse>
+ GetAcceleratorDynamicLoaderLibraryInfos(
+ const AcceleratorDynamicLoaderArgs &args) override;
+
private:
mutable ArchSpec m_arch;
};
More information about the lldb-commits
mailing list