[Lldb-commits] [lldb] [lldb-server] Add dynamic loader support to accelerator plugin protocol (PR #201447)
satyanarayana reddy janga via lldb-commits
lldb-commits at lists.llvm.org
Mon Jul 6 07:18:27 PDT 2026
https://github.com/satyajanga updated https://github.com/llvm/llvm-project/pull/201447
>From aa86f36dacdae734a51783308c0c129247f1b44c 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 | 34 ++++++++++
.../Utility/AcceleratorGDBRemotePackets.h | 64 +++++++++++++++++
.../lldb/Utility/StringExtractorGDBRemote.h | 1 +
.../GDBRemoteCommunicationServerLLGS.cpp | 33 +++++++++
.../GDBRemoteCommunicationServerLLGS.h | 3 +
.../gdb-remote/LLDBServerAcceleratorPlugin.h | 5 ++
.../Utility/AcceleratorGDBRemotePackets.cpp | 59 ++++++++++++++++
.../Utility/StringExtractorGDBRemote.cpp | 2 +
.../mock/TestMockAcceleratorPackets.py | 25 +++++++
.../Mock/LLDBServerMockAcceleratorPlugin.cpp | 12 ++++
.../Mock/LLDBServerMockAcceleratorPlugin.h | 3 +
.../AcceleratorGDBRemotePacketsTest.cpp | 68 +++++++++++++++++++
12 files changed, 309 insertions(+)
diff --git a/lldb/docs/resources/lldbgdbremote.md b/lldb/docs/resources/lldbgdbremote.md
index d4a377b048c61..ec4b90a0e9cab 100644
--- a/lldb/docs/resources/lldbgdbremote.md
+++ b/lldb/docs/resources/lldbgdbremote.md
@@ -2865,3 +2865,37 @@ 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 all libraries. If false, return only updates since the last query. |
+
+The response JSON has the following fields:
+
+| Key | Type | Description |
+|-----------------|-------|-------------|
+| `library_infos` | array | Array of library info objects, each with `pathname`, `load` (bool), and optional `load_address`, `loaded_sections`, `uuid`, `native_memory_address`, `native_memory_size`, `file_offset`, `file_size`. |
+
+Example:
+```
+LLDB SENDS: jAcceleratorPluginGetDynamicLoaderLibraryInfo:{"plugin_name":"mock","full":true}
+STUB REPLIES: {"library_infos":[{"pathname":"/usr/lib/libmock_accel.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..b33b210c822ef 100644
--- a/lldb/include/lldb/Utility/AcceleratorGDBRemotePackets.h
+++ b/lldb/include/lldb/Utility/AcceleratorGDBRemotePackets.h
@@ -166,6 +166,70 @@ bool fromJSON(const llvm::json::Value &value,
AcceleratorBreakpointHitResponse &data, llvm::json::Path path);
llvm::json::Value toJSON(const AcceleratorBreakpointHitResponse &data);
+/// A section load address entry for dynamic loader library info.
+struct AcceleratorSectionInfo {
+ /// Hierarchical section names. If there are multiple names, each successive
+ /// name is looked up as a child section of the previous one. Example:
+ /// ["PT_LOAD[0]", ".text"].
+ std::vector<std::string> names;
+ /// The load address of this section.
+ uint64_t load_address = 0;
+};
+
+bool fromJSON(const llvm::json::Value &value, AcceleratorSectionInfo &data,
+ llvm::json::Path path);
+llvm::json::Value toJSON(const AcceleratorSectionInfo &data);
+
+/// Information about a shared library for the accelerator dynamic loader.
+struct AcceleratorDynamicLoaderLibraryInfo {
+ /// Path to the shared library object file on disk.
+ std::string pathname;
+ /// UUID of the shared library, if known.
+ std::optional<std::string> uuid_str;
+ /// True if loading, false if unloading.
+ bool load = true;
+ /// Base load address for the entire object file. If set, all sections are
+ /// slid to match. If not set, use \a loaded_sections or file addresses.
+ std::optional<uint64_t> load_address;
+ /// Per-section load addresses for object files with sections loaded at
+ /// different addresses.
+ std::vector<AcceleratorSectionInfo> loaded_sections;
+ /// Address in the native process where the object file image can be read.
+ std::optional<uint64_t> native_memory_address;
+ /// Size of the in-memory image starting at \a native_memory_address.
+ std::optional<uint64_t> native_memory_size;
+ /// Byte offset within the file specified by \a pathname.
+ std::optional<uint64_t> file_offset;
+ /// Size in bytes of the object file within the containing file.
+ 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 {
+ /// Name of the accelerator plugin to query.
+ std::string plugin_name;
+ /// If true, return all libraries. If false, return only updates since the
+ /// last query.
+ 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 769705588fbdf..899e18ee0e657 100644
--- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp
+++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp
@@ -231,6 +231,11 @@ void GDBRemoteCommunicationServerLLGS::RegisterPacketHandlers() {
eServerPacketType_jAcceleratorPluginBreakpointHit,
&GDBRemoteCommunicationServerLLGS::
Handle_jAcceleratorPluginBreakpointHit);
+ RegisterMemberFunctionHandler(
+ StringExtractorGDBRemote::
+ eServerPacketType_jAcceleratorPluginGetDynamicLoaderLibraryInfo,
+ &GDBRemoteCommunicationServerLLGS::
+ Handle_jAcceleratorPluginGetDynamicLoaderLibraryInfo);
RegisterMemberFunctionHandler(StringExtractorGDBRemote::eServerPacketType_g,
&GDBRemoteCommunicationServerLLGS::Handle_g);
@@ -4637,3 +4642,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..6754e344427a7 100644
--- a/lldb/source/Utility/AcceleratorGDBRemotePackets.cpp
+++ b/lldb/source/Utility/AcceleratorGDBRemotePackets.cpp
@@ -142,4 +142,63 @@ json::Value toJSON(const AcceleratorBreakpointHitResponse &data) {
return obj;
}
+bool fromJSON(const 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 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.map("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 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 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..7cf662617e649 100644
--- a/lldb/test/API/accelerator/mock/TestMockAcceleratorPackets.py
+++ b/lldb/test/API/accelerator/mock/TestMockAcceleratorPackets.py
@@ -186,3 +186,28 @@ 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()
+
+ 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"], "/usr/lib/libmock_accel.so")
+ self.assertTrue(lib["load"])
+ self.assertEqual(lib["load_address"], 0x7F000000)
diff --git a/lldb/tools/lldb-server/Plugins/Accelerator/Mock/LLDBServerMockAcceleratorPlugin.cpp b/lldb/tools/lldb-server/Plugins/Accelerator/Mock/LLDBServerMockAcceleratorPlugin.cpp
index 6c1d23fef8a71..4433c6586a499 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 = "/usr/lib/libmock_accel.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..8f5d85718ac25 100644
--- a/lldb/unittests/Utility/AcceleratorGDBRemotePacketsTest.cpp
+++ b/lldb/unittests/Utility/AcceleratorGDBRemotePacketsTest.cpp
@@ -244,3 +244,71 @@ 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);
+}
>From cd906b677da2225067bdb8b34820dfad1f0f9076 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.
Selection is by name, not by triple: the target queries the server with a new
jLLDBSettings packet, and ProcessGDBRemote::GetDynamicLoader uses the returned
dyld_plugin_name ("accelerator-gdb-remote") to pick the loader. The loader 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 plugin implements the new NativeProcessProtocol hooks so
a self-contained end-to-end API test can exercise two library-provision paths:
a shared object on disk, and one embedded in a container file. Adds a
jLLDBSettings JSON round-trip unit test.
---
.../lldb/Host/common/NativeProcessProtocol.h | 15 ++
.../Utility/AcceleratorGDBRemotePackets.h | 12 ++
.../lldb/Utility/StringExtractorGDBRemote.h | 1 +
.../AcceleratorGDBRemote/CMakeLists.txt | 13 ++
.../DynamicLoaderAcceleratorGDBRemote.cpp | 155 ++++++++++++++++++
.../DynamicLoaderAcceleratorGDBRemote.h | 52 ++++++
.../Plugins/DynamicLoader/CMakeLists.txt | 1 +
.../GDBRemoteCommunicationClient.cpp | 50 ++++++
.../gdb-remote/GDBRemoteCommunicationClient.h | 13 ++
.../GDBRemoteCommunicationServerLLGS.cpp | 57 +++++--
.../GDBRemoteCommunicationServerLLGS.h | 2 +
.../Process/gdb-remote/ProcessGDBRemote.cpp | 12 +-
.../Utility/AcceleratorGDBRemotePackets.cpp | 9 +
.../Utility/StringExtractorGDBRemote.cpp | 2 +
.../API/accelerator/dynamic_loader/Makefile | 11 ++
.../TestMockAcceleratorDynamicLoader.py | 140 ++++++++++++++++
.../API/accelerator/dynamic_loader/gpu_lib.c | 6 +
.../API/accelerator/dynamic_loader/main.c | 20 +++
.../Mock/ProcessMockAccelerator.cpp | 45 +++++
.../Accelerator/Mock/ProcessMockAccelerator.h | 6 +
.../AcceleratorGDBRemotePacketsTest.cpp | 9 +
21 files changed, 616 insertions(+), 15 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/Makefile
create mode 100644 lldb/test/API/accelerator/dynamic_loader/TestMockAcceleratorDynamicLoader.py
create mode 100644 lldb/test/API/accelerator/dynamic_loader/gpu_lib.c
create mode 100644 lldb/test/API/accelerator/dynamic_loader/main.c
diff --git a/lldb/include/lldb/Host/common/NativeProcessProtocol.h b/lldb/include/lldb/Host/common/NativeProcessProtocol.h
index 435185a38f3f9..0c53d394070bb 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/Status.h"
@@ -162,6 +163,20 @@ class NativeProcessProtocol {
"Not implemented");
}
+ /// Return LLDB settings for this process (e.g. which DynamicLoader plugin to
+ /// use), or std::nullopt to use the defaults. Used to answer "jLLDBSettings".
+ virtual std::optional<LLDBSettings> GetLLDBSettings() { return std::nullopt; }
+
+ /// Return the accelerator dynamic loader library infos for this process, or
+ /// std::nullopt if the process does not support address-space aware dynamic
+ /// loading. Used to answer "jAcceleratorPluginGetDynamicLoaderLibraryInfo" on
+ /// a GPU connection.
+ virtual std::optional<AcceleratorDynamicLoaderResponse>
+ GetAcceleratorDynamicLoaderLibraryInfos(
+ const AcceleratorDynamicLoaderArgs &args) {
+ return std::nullopt;
+ }
+
virtual bool HasPendingLibraryEvents() { return false; }
virtual bool IsAlive() const;
diff --git a/lldb/include/lldb/Utility/AcceleratorGDBRemotePackets.h b/lldb/include/lldb/Utility/AcceleratorGDBRemotePackets.h
index b33b210c822ef..d0c7b789e06d5 100644
--- a/lldb/include/lldb/Utility/AcceleratorGDBRemotePackets.h
+++ b/lldb/include/lldb/Utility/AcceleratorGDBRemotePackets.h
@@ -230,6 +230,18 @@ bool fromJSON(const llvm::json::Value &value,
AcceleratorDynamicLoaderResponse &data, llvm::json::Path path);
llvm::json::Value toJSON(const AcceleratorDynamicLoaderResponse &data);
+/// LLDB-specific settings a server reports for a target via the "jLLDBSettings"
+/// packet.
+struct LLDBSettings {
+ /// Name of the DynamicLoader plugin LLDB should use for this target. If
+ /// empty, the loader is auto-selected from the target triple.
+ std::string dyld_plugin_name;
+};
+
+bool fromJSON(const llvm::json::Value &value, LLDBSettings &data,
+ llvm::json::Path path);
+llvm::json::Value toJSON(const LLDBSettings &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 0236891dfcde1..cee9ef89f424e 100644
--- a/lldb/include/lldb/Utility/StringExtractorGDBRemote.h
+++ b/lldb/include/lldb/Utility/StringExtractorGDBRemote.h
@@ -177,6 +177,7 @@ class StringExtractorGDBRemote : public StringExtractor {
eServerPacketType_jAcceleratorPluginInitialize,
eServerPacketType_jAcceleratorPluginBreakpointHit,
eServerPacketType_jAcceleratorPluginGetDynamicLoaderLibraryInfo,
+ eServerPacketType_jLLDBSettings,
eServerPacketType_qMemTags, // read memory tags
eServerPacketType_QMemTags, // write memory tags
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..1ae10e0e67209
--- /dev/null
+++ b/lldb/source/Plugins/DynamicLoader/AcceleratorGDBRemote/DynamicLoaderAcceleratorGDBRemote.cpp
@@ -0,0 +1,155 @@
+//===-- 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) {
+ // Only create an instance when asked for by name; this loader is selected via
+ // the target's jLLDBSettings dyld_plugin_name, never auto-selected.
+ if (force)
+ 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);
+
+ ProcessGDBRemote *gpu_process = static_cast<ProcessGDBRemote *>(m_process);
+ AcceleratorDynamicLoaderArgs args;
+ args.full = full;
+
+ Target &target = m_process->GetTarget();
+ ModuleList loaded_module_list;
+ std::optional<AcceleratorDynamicLoaderResponse> response =
+ gpu_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);
+
+ // The object file is either a whole file (pathname), or a slice of a
+ // containing file located by file_offset/file_size (e.g. a library embedded
+ // in the executable via an llvm-objcopy section).
+ 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) {
+ // Slide the whole object file to this base load address.
+ module_sp->SetLoadAddress(target, *info.load_address,
+ /*value_is_offset=*/true, changed);
+ } else if (!info.loaded_sections.empty()) {
+ // Load individual sections at their own addresses. Each entry names a
+ // section, or a path of nested section names to descend.
+ 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 {
+ // Load at the file addresses with no slide.
+ 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 for accelerator (GPU) targets. Gets library load and "
+ "unload info from an lldb-server accelerator plugin over gdb-remote "
+ "rather than from a rendezvous breakpoint.";
+}
diff --git a/lldb/source/Plugins/DynamicLoader/AcceleratorGDBRemote/DynamicLoaderAcceleratorGDBRemote.h b/lldb/source/Plugins/DynamicLoader/AcceleratorGDBRemote/DynamicLoaderAcceleratorGDBRemote.h
new file mode 100644
index 0000000000000..068a52a8e778e
--- /dev/null
+++ b/lldb/source/Plugins/DynamicLoader/AcceleratorGDBRemote/DynamicLoaderAcceleratorGDBRemote.h
@@ -0,0 +1,52 @@
+//===-- 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 served by an lldb-server
+/// accelerator plugin.
+///
+/// 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 the
+/// "jAcceleratorPluginGetDynamicLoaderLibraryInfo" packet and loads the modules
+/// into the target. It is selected by name (not by triple), via the target's
+/// "jLLDBSettings" dyld_plugin_name, so it is only used for accelerator
+/// targets.
+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:
+ /// Query the GDB server for the accelerator's loaded libraries and load them
+ /// into the target. If \a full is false, only apply updates since the last
+ /// query. 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 a3fe6661737a4..ac7635055a979 100644
--- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
+++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
@@ -298,6 +298,56 @@ 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;
+}
+
+std::optional<LLDBSettings> GDBRemoteCommunicationClient::GetLLDBSettings() {
+ if (m_supports_lldb_settings == eLazyBoolNo)
+ return std::nullopt;
+ if (m_lldb_settings)
+ return m_lldb_settings;
+
+ StringExtractorGDBRemote response;
+ response.SetResponseValidatorToJSON();
+ if (SendPacketAndWaitForResponse("jLLDBSettings", response) !=
+ PacketResult::Success)
+ return std::nullopt;
+ if (response.IsUnsupportedResponse() || response.IsErrorResponse()) {
+ m_supports_lldb_settings = eLazyBoolNo;
+ return std::nullopt;
+ }
+
+ llvm::Expected<LLDBSettings> parsed =
+ llvm::json::parse<LLDBSettings>(response.Peek(), "LLDBSettings");
+ if (!parsed) {
+ llvm::consumeError(parsed.takeError());
+ return std::nullopt;
+ }
+ m_supports_lldb_settings = eLazyBoolYes;
+ m_lldb_settings = *parsed;
+ return m_lldb_settings;
+}
+
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..a52ac3416716b 100644
--- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h
+++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h
@@ -377,6 +377,17 @@ class GDBRemoteCommunicationClient : public GDBRemoteClientBase {
llvm::Expected<AcceleratorBreakpointHitResponse>
AcceleratorBreakpointHit(const AcceleratorBreakpointHitArgs &args);
+ /// Send the "jAcceleratorPluginGetDynamicLoaderLibraryInfo" packet and return
+ /// the libraries the plugin (or the process) reports, or std::nullopt on
+ /// error.
+ std::optional<AcceleratorDynamicLoaderResponse>
+ GetAcceleratorDynamicLoaderLibraryInfos(
+ const AcceleratorDynamicLoaderArgs &args);
+
+ /// Send the "jLLDBSettings" packet and return the target's LLDB settings
+ /// (cached after the first query), or std::nullopt if unsupported.
+ std::optional<LLDBSettings> GetLLDBSettings();
+
LazyBool SupportsAllocDeallocMemory() // const
{
// Uncomment this to have lldb pretend the debug server doesn't respond to
@@ -614,6 +625,8 @@ class GDBRemoteCommunicationClient : public GDBRemoteClientBase {
LazyBool m_supports_multi_mem_read = eLazyBoolCalculate;
LazyBool m_supports_multi_breakpoint = eLazyBoolCalculate;
LazyBool m_supports_accelerator_plugins = eLazyBoolCalculate;
+ LazyBool m_supports_lldb_settings = eLazyBoolCalculate;
+ std::optional<LLDBSettings> m_lldb_settings;
bool m_supports_qProcessInfoPID : 1, m_supports_qfProcessInfo : 1,
m_supports_qUserName : 1, m_supports_qGroupName : 1,
diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp
index 899e18ee0e657..4fbf9ece31788 100644
--- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp
+++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp
@@ -236,6 +236,9 @@ void GDBRemoteCommunicationServerLLGS::RegisterPacketHandlers() {
eServerPacketType_jAcceleratorPluginGetDynamicLoaderLibraryInfo,
&GDBRemoteCommunicationServerLLGS::
Handle_jAcceleratorPluginGetDynamicLoaderLibraryInfo);
+ RegisterMemberFunctionHandler(
+ StringExtractorGDBRemote::eServerPacketType_jLLDBSettings,
+ &GDBRemoteCommunicationServerLLGS::Handle_jLLDBSettings);
RegisterMemberFunctionHandler(StringExtractorGDBRemote::eServerPacketType_g,
&GDBRemoteCommunicationServerLLGS::Handle_g);
@@ -4653,20 +4656,48 @@ 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 CPU 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 GPU 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());
+}
+
+GDBRemoteCommunication::PacketResult
+GDBRemoteCommunicationServerLLGS::Handle_jLLDBSettings(
+ StringExtractorGDBRemote &packet) {
+ if (!m_current_process)
+ return SendErrorResponse(Status::FromErrorString("no current process"));
+ std::optional<LLDBSettings> settings = m_current_process->GetLLDBSettings();
+ if (!settings)
+ return SendUnimplementedResponse("jLLDBSettings");
+ StreamGDBRemote stream;
+ stream.PutAsJSON(*settings, /*hex_ascii=*/false);
+ return SendPacketNoLock(stream.GetString());
}
diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h
index 3c8afe699bbaf..e9d074ff68208 100644
--- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h
+++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h
@@ -305,6 +305,8 @@ class GDBRemoteCommunicationServerLLGS
PacketResult Handle_jAcceleratorPluginGetDynamicLoaderLibraryInfo(
StringExtractorGDBRemote &packet);
+ PacketResult Handle_jLLDBSettings(StringExtractorGDBRemote &packet);
+
void SetCurrentThreadID(lldb::tid_t tid);
lldb::tid_t GetCurrentThreadID() const;
diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
index da45d79ba1118..aebbac2836010 100644
--- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
+++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
@@ -4525,8 +4525,16 @@ bool ProcessGDBRemote::StopNoticingNewThreads() {
}
DynamicLoader *ProcessGDBRemote::GetDynamicLoader() {
- if (m_dyld_up.get() == nullptr)
- m_dyld_up.reset(DynamicLoader::FindPlugin(this, ""));
+ if (m_dyld_up.get() == nullptr) {
+ // The GDB server can name a specific DynamicLoader plugin via jLLDBSettings
+ // (e.g. accelerator targets select "accelerator-gdb-remote"). Otherwise the
+ // loader is auto-selected from the target triple.
+ llvm::StringRef dyld_name;
+ std::optional<LLDBSettings> settings = m_gdb_comm.GetLLDBSettings();
+ if (settings)
+ dyld_name = settings->dyld_plugin_name;
+ m_dyld_up.reset(DynamicLoader::FindPlugin(this, dyld_name));
+ }
return m_dyld_up.get();
}
diff --git a/lldb/source/Utility/AcceleratorGDBRemotePackets.cpp b/lldb/source/Utility/AcceleratorGDBRemotePackets.cpp
index 6754e344427a7..56ae3fe1a3852 100644
--- a/lldb/source/Utility/AcceleratorGDBRemotePackets.cpp
+++ b/lldb/source/Utility/AcceleratorGDBRemotePackets.cpp
@@ -201,4 +201,13 @@ json::Value toJSON(const AcceleratorDynamicLoaderResponse &data) {
return Object{{"library_infos", data.library_infos}};
}
+bool fromJSON(const Value &value, LLDBSettings &data, Path path) {
+ ObjectMapper o(value, path);
+ return o && o.map("dyld_plugin_name", data.dyld_plugin_name);
+}
+
+json::Value toJSON(const LLDBSettings &data) {
+ return Object{{"dyld_plugin_name", data.dyld_plugin_name}};
+}
+
} // namespace lldb_private
diff --git a/lldb/source/Utility/StringExtractorGDBRemote.cpp b/lldb/source/Utility/StringExtractorGDBRemote.cpp
index 953a2cd3090d1..a8253d29ef285 100644
--- a/lldb/source/Utility/StringExtractorGDBRemote.cpp
+++ b/lldb/source/Utility/StringExtractorGDBRemote.cpp
@@ -340,6 +340,8 @@ StringExtractorGDBRemote::GetServerPacketType() const {
return eServerPacketType_jAcceleratorPluginBreakpointHit;
if (PACKET_STARTS_WITH("jAcceleratorPluginGetDynamicLoaderLibraryInfo:"))
return eServerPacketType_jAcceleratorPluginGetDynamicLoaderLibraryInfo;
+ if (PACKET_MATCHES("jLLDBSettings"))
+ return eServerPacketType_jLLDBSettings;
break;
case 'v':
diff --git a/lldb/test/API/accelerator/dynamic_loader/Makefile b/lldb/test/API/accelerator/dynamic_loader/Makefile
new file mode 100644
index 0000000000000..5e95a3b38ed44
--- /dev/null
+++ b/lldb/test/API/accelerator/dynamic_loader/Makefile
@@ -0,0 +1,11 @@
+C_SOURCES := main.c
+
+include Makefile.rules
+
+# Build a real shared library the accelerator dynamic loader can load. a.out is
+# deliberately not linked against it: the test only needs the .so to exist on
+# disk so the loader can pull it into the accelerator target.
+all: libgpu_lib.so
+
+libgpu_lib.so: gpu_lib.c
+ $(CC) $(CFLAGS) -fPIC -shared -o "$@" $<
diff --git a/lldb/test/API/accelerator/dynamic_loader/TestMockAcceleratorDynamicLoader.py b/lldb/test/API/accelerator/dynamic_loader/TestMockAcceleratorDynamicLoader.py
new file mode 100644
index 0000000000000..f9c426eb5af2a
--- /dev/null
+++ b/lldb/test/API/accelerator/dynamic_loader/TestMockAcceleratorDynamicLoader.py
@@ -0,0 +1,140 @@
+"""
+End-to-end test for the accelerator dynamic loader.
+
+When the client creates and connects the accelerator target (triggered by the
+mock accelerator plugin's connection hook), that target selects the
+"accelerator-gdb-remote" dynamic loader via the jLLDBSettings packet, instead of
+auto-selecting a loader from the triple. The loader then asks the accelerator
+GDB server for its loaded libraries via
+jAcceleratorPluginGetDynamicLoaderLibraryInfo and loads them into the target.
+
+This verifies two of the ways a library can be provided:
+ 1. As a whole shared object on disk.
+ 3. As a shared object embedded in a containing file, located by file offset
+ and size (as a library added to a container with llvm-objcopy would be).
+
+The in-memory case (2) needs host support that is out of scope here, so it is
+not exercised.
+"""
+
+import os
+
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import configuration
+
+
+class MockAcceleratorDynamicLoaderTestCase(TestBase):
+ NO_DEBUG_INFO_TESTCASE = True
+
+ def setUp(self):
+ super().setUp()
+ if "mock-accelerator" not in configuration.enabled_plugins:
+ self.skipTest("mock-accelerator plugin is not enabled")
+
+ def set_mock_env(self, name, value):
+ """Set an environment variable the mock plugin reads (it is inherited by
+ the lldb-server that hosts the plugin), restoring it after the test."""
+ previous = os.environ.get(name)
+ os.environ[name] = value
+
+ def restore():
+ if previous is None:
+ os.environ.pop(name, None)
+ else:
+ os.environ[name] = previous
+
+ self.addTearDownHook(restore)
+
+ def make_container(self, lib_path):
+ """Embed the library bytes in a larger container file, returning the
+ container path plus the (offset, size) of the embedded object."""
+ with open(lib_path, "rb") as f:
+ lib_bytes = f.read()
+ # Model a library embedded in a containing object file (as llvm-objcopy
+ # would add it to an executable): the container is itself a valid object
+ # file, with the embedded library located deeper in the file by
+ # file_offset/file_size. We reuse the library bytes as the outer object
+ # and page-align the embedded copy.
+ prefix = lib_bytes
+ pad = (-len(prefix)) % 0x1000
+ prefix += b"\x00" * pad
+ container_path = self.getBuildArtifact("container.bin")
+ with open(container_path, "wb") as f:
+ f.write(prefix)
+ f.write(lib_bytes)
+ return container_path, len(prefix), len(lib_bytes)
+
+ def find_module(self, target, basename):
+ for i in range(target.GetNumModules()):
+ module = target.GetModuleAtIndex(i)
+ if module.GetFileSpec().GetFilename() == basename:
+ return module
+ return None
+
+ def assert_loaded_at(self, target, module, base):
+ """The module's .text must resolve to a load address at or above the
+ base the loader slid it to, proving the load address was applied."""
+ section = module.FindSection(".text")
+ self.assertTrue(section.IsValid(), "library should have a .text section")
+ load_addr = section.GetLoadAddress(target)
+ self.assertNotEqual(
+ load_addr,
+ lldb.LLDB_INVALID_ADDRESS,
+ "library section should have a load address in the accelerator target",
+ )
+ self.assertGreaterEqual(load_addr, base)
+
+ @skipIfRemote
+ @add_test_categories(["llgs"])
+ def test_accelerator_dynamic_loader(self):
+ """The accelerator target loads the libraries reported by the server."""
+ self.build()
+ exe = self.getBuildArtifact("a.out")
+ lib_ondisk = self.getBuildArtifact("libgpu_lib.so")
+ container, offset, size = self.make_container(lib_ondisk)
+
+ # Tell the mock accelerator process which libraries to report, and where.
+ self.set_mock_env("LLDB_MOCK_ACCELERATOR_LIB_ONDISK", lib_ondisk)
+ self.set_mock_env("LLDB_MOCK_ACCELERATOR_LIB_CONTAINER", container)
+ self.set_mock_env("LLDB_MOCK_ACCELERATOR_LIB_OFFSET", str(offset))
+ self.set_mock_env("LLDB_MOCK_ACCELERATOR_LIB_SIZE", str(size))
+
+ target = self.dbg.CreateTarget(exe)
+ self.assertTrue(target, VALID_TARGET)
+
+ # Launch stops at the plugin's initialize breakpoint; continuing reaches
+ # the connection hook that creates the accelerator target.
+ process = target.LaunchSimple(None, None, self.get_process_working_directory())
+ self.assertTrue(process, PROCESS_IS_VALID)
+ self.assertState(process.GetState(), lldb.eStateStopped)
+ process.Continue()
+ self.assertState(process.GetState(), lldb.eStateStopped)
+
+ # The accelerator target now exists alongside the native target.
+ self.assertEqual(self.dbg.GetNumTargets(), 2)
+ accelerator_target = None
+ for i in range(self.dbg.GetNumTargets()):
+ candidate = self.dbg.GetTargetAtIndex(i)
+ if candidate != target:
+ accelerator_target = candidate
+ break
+ self.assertTrue(accelerator_target.IsValid())
+
+ # Scenario 1: the whole-file shared library was loaded.
+ ondisk_module = self.find_module(accelerator_target, "libgpu_lib.so")
+ self.assertIsNotNone(
+ ondisk_module,
+ "on-disk library should be loaded into the accelerator target",
+ )
+ self.assert_loaded_at(accelerator_target, ondisk_module, 0x10000000)
+
+ # Scenario 3: the embedded shared library, located by file offset/size,
+ # was loaded as a distinct module from its container file.
+ embedded_module = self.find_module(accelerator_target, "container.bin")
+ self.assertIsNotNone(
+ embedded_module,
+ "embedded library should be loaded into the accelerator target",
+ )
+ self.assert_loaded_at(accelerator_target, embedded_module, 0x20000000)
diff --git a/lldb/test/API/accelerator/dynamic_loader/gpu_lib.c b/lldb/test/API/accelerator/dynamic_loader/gpu_lib.c
new file mode 100644
index 0000000000000..00de86bc4351b
--- /dev/null
+++ b/lldb/test/API/accelerator/dynamic_loader/gpu_lib.c
@@ -0,0 +1,6 @@
+// A trivial shared library the accelerator dynamic loader loads into the
+// accelerator target. It only needs to be a real, parseable object file with
+// some code; the functions are never called.
+int gpu_lib_add(int a, int b) { return a + b; }
+
+int gpu_lib_mul(int a, int b) { return a * b; }
diff --git a/lldb/test/API/accelerator/dynamic_loader/main.c b/lldb/test/API/accelerator/dynamic_loader/main.c
new file mode 100644
index 0000000000000..18aed944b8d8f
--- /dev/null
+++ b/lldb/test/API/accelerator/dynamic_loader/main.c
@@ -0,0 +1,20 @@
+// The mock accelerator plugin sets its breakpoints on these dedicated, uniquely
+// named functions.
+void mock_gpu_accelerator_initialize(void) {}
+
+int mock_gpu_accelerator_compute(int x) { return x * 2; }
+
+int mock_gpu_accelerator_finish(void) { return 0; }
+
+// When the plugin's connection-trigger breakpoint on this function is hit, it
+// asks the client to create a second target and connect to the mock accelerator
+// GDB server.
+void mock_gpu_accelerator_connect(void) {}
+
+int main(void) {
+ mock_gpu_accelerator_initialize();
+ mock_gpu_accelerator_connect();
+ int result = mock_gpu_accelerator_compute(21);
+ mock_gpu_accelerator_finish();
+ return result == 42 ? 0 : 1;
+}
diff --git a/lldb/tools/lldb-server/Plugins/Accelerator/Mock/ProcessMockAccelerator.cpp b/lldb/tools/lldb-server/Plugins/Accelerator/Mock/ProcessMockAccelerator.cpp
index 174ab1a0143d4..2abc957419e1f 100644
--- a/lldb/tools/lldb-server/Plugins/Accelerator/Mock/ProcessMockAccelerator.cpp
+++ b/lldb/tools/lldb-server/Plugins/Accelerator/Mock/ProcessMockAccelerator.cpp
@@ -13,6 +13,8 @@
#include "lldb/Host/ProcessLaunchInfo.h"
#include "llvm/Support/Error.h"
+#include <cstdlib>
+
using namespace lldb;
using namespace lldb_private;
using namespace lldb_private::lldb_server;
@@ -110,3 +112,46 @@ ProcessMockAccelerator::GetFileLoadAddress(const llvm::StringRef &file_name,
lldb::addr_t &load_addr) {
return Status::FromErrorString("unimplemented");
}
+
+std::optional<LLDBSettings> ProcessMockAccelerator::GetLLDBSettings() {
+ // Tell the client to use the accelerator dynamic loader for this target, so
+ // libraries are provided by this plugin rather than by a rendezvous
+ // breakpoint.
+ LLDBSettings settings;
+ settings.dyld_plugin_name = "accelerator-gdb-remote";
+ return settings;
+}
+
+std::optional<AcceleratorDynamicLoaderResponse>
+ProcessMockAccelerator::GetAcceleratorDynamicLoaderLibraryInfos(
+ const AcceleratorDynamicLoaderArgs &args) {
+ // The test drives which libraries to report (and where they live) via the
+ // environment, so it can build real object files and point us at them.
+ AcceleratorDynamicLoaderResponse response;
+
+ // Scenario 1: a shared library provided as a whole file on disk, loaded at a
+ // fixed base address.
+ if (const char *path = ::getenv("LLDB_MOCK_ACCELERATOR_LIB_ONDISK")) {
+ AcceleratorDynamicLoaderLibraryInfo info;
+ info.pathname = path;
+ info.load = true;
+ info.load_address = 0x10000000;
+ response.library_infos.push_back(std::move(info));
+ }
+
+ // Scenario 3: a shared library embedded in a container file (e.g. added to
+ // the executable with llvm-objcopy), located by file offset and size.
+ if (const char *path = ::getenv("LLDB_MOCK_ACCELERATOR_LIB_CONTAINER")) {
+ AcceleratorDynamicLoaderLibraryInfo info;
+ info.pathname = path;
+ info.load = true;
+ info.load_address = 0x20000000;
+ if (const char *offset = ::getenv("LLDB_MOCK_ACCELERATOR_LIB_OFFSET"))
+ info.file_offset = std::strtoull(offset, nullptr, 0);
+ if (const char *size = ::getenv("LLDB_MOCK_ACCELERATOR_LIB_SIZE"))
+ info.file_size = std::strtoull(size, nullptr, 0);
+ 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 6fc07b5ac011a..482883da490bd 100644
--- a/lldb/tools/lldb-server/Plugins/Accelerator/Mock/ProcessMockAccelerator.h
+++ b/lldb/tools/lldb-server/Plugins/Accelerator/Mock/ProcessMockAccelerator.h
@@ -60,6 +60,12 @@ class ProcessMockAccelerator : public NativeProcessProtocol {
Status GetFileLoadAddress(const llvm::StringRef &file_name,
lldb::addr_t &load_addr) override;
+ std::optional<LLDBSettings> GetLLDBSettings() override;
+
+ std::optional<AcceleratorDynamicLoaderResponse>
+ GetAcceleratorDynamicLoaderLibraryInfos(
+ const AcceleratorDynamicLoaderArgs &args) override;
+
private:
mutable ArchSpec m_arch;
};
diff --git a/lldb/unittests/Utility/AcceleratorGDBRemotePacketsTest.cpp b/lldb/unittests/Utility/AcceleratorGDBRemotePacketsTest.cpp
index 8f5d85718ac25..8f606100354c0 100644
--- a/lldb/unittests/Utility/AcceleratorGDBRemotePacketsTest.cpp
+++ b/lldb/unittests/Utility/AcceleratorGDBRemotePacketsTest.cpp
@@ -312,3 +312,12 @@ TEST(AcceleratorGDBRemotePacketsTest, AcceleratorDynamicLoaderResponse) {
EXPECT_EQ("/path/to/lib.so", deserialized->library_infos[0].pathname);
EXPECT_TRUE(deserialized->library_infos[0].load);
}
+
+TEST(AcceleratorGDBRemotePacketsTest, LLDBSettings) {
+ LLDBSettings settings;
+ settings.dyld_plugin_name = "accelerator-gdb-remote";
+
+ Expected<LLDBSettings> deserialized = roundtripJSON(settings);
+ ASSERT_THAT_EXPECTED(deserialized, Succeeded());
+ EXPECT_EQ(settings.dyld_plugin_name, deserialized->dyld_plugin_name);
+}
More information about the lldb-commits
mailing list