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 <[email protected]> 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 <[email protected]> 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); +} _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
