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