https://github.com/satyajanga updated https://github.com/llvm/llvm-project/pull/217393
>From bc0dc9a41a02bf7230b866e51abd19266dfd9eb2 Mon Sep 17 00:00:00 2001 From: satya janga <[email protected]> Date: Sun, 5 Jul 2026 08:01:36 -0700 Subject: [PATCH] [lldb] Add accelerator dynamic loader over gdb-remote Accelerator targets such as GPUs do not use a rendezvous breakpoint in the same way as SVR4 loaders. Their runtime instead tells the lldb-server accelerator plugin when libraries load or unload. Add a DynamicLoader that queries jAcceleratorPluginGetDynamicLoaderLibraryInfo and applies the returned module loads. The loader handles whole-file paths and containing files with reported offset and size metadata. When a file contains a Clang offload bundle, the target architecture selects its device image. Select the loader for AMDGPU and NVPTX targets backed by ProcessGDBRemote. Extend the mock accelerator process to serve the packet on an accelerator connection. Test whole-file loading, loading an AMDGPU image from a real Clang offload bundle inside a host ELF, and avoiding this loader for host targets. --- lldb/docs/resources/lldbgdbremote.md | 14 +- .../lldb/Host/common/NativeProcessProtocol.h | 9 + .../Utility/AcceleratorGDBRemotePackets.h | 9 + .../AcceleratorGDBRemote/CMakeLists.txt | 13 + .../DynamicLoaderAcceleratorGDBRemote.cpp | 152 +++++++++++ .../DynamicLoaderAcceleratorGDBRemote.h | 46 ++++ .../Plugins/DynamicLoader/CMakeLists.txt | 1 + .../GDBRemoteCommunicationClient.cpp | 51 ++-- .../gdb-remote/GDBRemoteCommunicationClient.h | 20 +- .../GDBRemoteCommunicationServerLLGS.cpp | 60 +++-- .../GDBRemoteCommunicationServerLLGS.h | 9 + .../gdb-remote/LLDBServerAcceleratorPlugin.h | 5 + .../Process/gdb-remote/ProcessGDBRemote.cpp | 19 +- .../Process/gdb-remote/ProcessGDBRemote.h | 4 + .../Utility/AcceleratorGDBRemotePackets.cpp | 21 ++ .../TestAcceleratorDynamicLoader.py | 243 ++++++++++++++++++ .../dynamic_loader/accelerator.yaml | 23 ++ .../dynamic_loader/accelerator_lib.yaml | 23 ++ .../dynamic_loader/embedded_lib.yaml | 23 ++ .../API/accelerator/dynamic_loader/host.yaml | 20 ++ .../mock/TestMockAcceleratorDynamicLoader.py | 90 +++++++ .../mock/TestMockAcceleratorPackets.py | 9 +- .../API/accelerator/mock/accelerator_lib.yaml | 23 ++ .../Mock/LLDBServerMockAcceleratorPlugin.cpp | 17 +- .../Mock/LLDBServerMockAcceleratorPlugin.h | 3 + .../Mock/ProcessMockAccelerator.cpp | 27 +- .../Accelerator/Mock/ProcessMockAccelerator.h | 24 +- .../AcceleratorGDBRemotePacketsTest.cpp | 41 +++ 28 files changed, 931 insertions(+), 68 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 create mode 100644 lldb/test/API/accelerator/mock/TestMockAcceleratorDynamicLoader.py create mode 100644 lldb/test/API/accelerator/mock/accelerator_lib.yaml diff --git a/lldb/docs/resources/lldbgdbremote.md b/lldb/docs/resources/lldbgdbremote.md index ac9a748461bd9d..6d6662fc38dead 100644 --- a/lldb/docs/resources/lldbgdbremote.md +++ b/lldb/docs/resources/lldbgdbremote.md @@ -2906,7 +2906,7 @@ will fail. ``` LLDB SENDS: jAcceleratorPluginInitialize -STUB REPLIES: [<accelerator_action>,...] +STUB REPLIES: {"actions":[<accelerator_action>,...],"dynamic_loader_plugin_name":<string>} ``` Each `accelerator_action` is a JSON object with the following required fields: @@ -2917,14 +2917,22 @@ Each `accelerator_action` is a JSON object with the following required fields: | `session_name` | string | Human-readable label for the accelerator target, stored on the Target object to distinguish it from the CPU target (e.g. `"AMD GPU Session"`). May be empty. | | `identifier` | integer | Identifier for this action, unique within the scope of its `plugin_name`. To refer to a specific action, use the combination of `plugin_name` and `identifier`. | +The `jAcceleratorPluginInitialize` response may also include a +`dynamic_loader_plugin_name` string. This names the LLDB `DynamicLoader` +plugin to use for the current GDB-remote connection. LLDB selects that plugin +explicitly; if the field is absent or empty, LLDB uses its normal automatic +selection. A server for an accelerator connection can use this field to select +either LLDB's generic `accelerator-gdb-remote` loader or a vendor-specific +loader without requiring an architecture check in LLDB. + There can be multiple accelerator plugins installed, each with a globally -unique `plugin_name`. The response is a JSON array with one entry per +unique `plugin_name`. The response's `actions` array has one entry per installed plugin. Example: ``` LLDB SENDS: jAcceleratorPluginInitialize -STUB REPLIES: [{"plugin_name":"amdgpu","session_name":"AMD GPU Session","identifier":0}] +STUB REPLIES: {"actions":[{"plugin_name":"amdgpu","session_name":"AMD GPU Session","identifier":0}],"dynamic_loader_plugin_name":"accelerator-gdb-remote"} ``` If no accelerator plugins are installed, the server does not advertise the diff --git a/lldb/include/lldb/Host/common/NativeProcessProtocol.h b/lldb/include/lldb/Host/common/NativeProcessProtocol.h index 209cb068aba215..41f9934a1ef0a5 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/AddressSpace.h" #include "lldb/Utility/ArchSpec.h" #include "lldb/Utility/Iterable.h" @@ -170,6 +171,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/include/lldb/Utility/AcceleratorGDBRemotePackets.h b/lldb/include/lldb/Utility/AcceleratorGDBRemotePackets.h index dea958c15eebd2..461c73d5818ece 100644 --- a/lldb/include/lldb/Utility/AcceleratorGDBRemotePackets.h +++ b/lldb/include/lldb/Utility/AcceleratorGDBRemotePackets.h @@ -150,6 +150,15 @@ bool fromJSON(const llvm::json::Value &value, AcceleratorActions &data, llvm::json::Path path); llvm::json::Value toJSON(const AcceleratorActions &data); +struct AcceleratorInitializeResponse { + std::vector<AcceleratorActions> actions; + std::optional<std::string> dynamic_loader_plugin_name; +}; + +bool fromJSON(const llvm::json::Value &value, + AcceleratorInitializeResponse &data, llvm::json::Path path); +llvm::json::Value toJSON(const AcceleratorInitializeResponse &data); + /// Response from the plugin when a breakpoint is hit. struct AcceleratorBreakpointHitResponse { /// Set to true if this breakpoint should be disabled. diff --git a/lldb/source/Plugins/DynamicLoader/AcceleratorGDBRemote/CMakeLists.txt b/lldb/source/Plugins/DynamicLoader/AcceleratorGDBRemote/CMakeLists.txt new file mode 100644 index 00000000000000..b5cf71d1f8b31a --- /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 00000000000000..83c305556d7ad5 --- /dev/null +++ b/lldb/source/Plugins/DynamicLoader/AcceleratorGDBRemote/DynamicLoaderAcceleratorGDBRemote.cpp @@ -0,0 +1,152 @@ +//===----------------------------------------------------------------------===// +// +// 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 (!force || + process->GetPluginName() != ProcessGDBRemote::GetPluginNameStatic()) + return nullptr; + return new DynamicLoaderAcceleratorGDBRemote(process); +} + +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); + + // CreateInstance ensures that m_process is 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); + // Select the target device image when the file contains an offload bundle. + module_spec.GetArchitecture() = target.GetArchitecture(); + 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 00000000000000..b5080322ed714d --- /dev/null +++ b/lldb/source/Plugins/DynamicLoader/AcceleratorGDBRemote/DynamicLoaderAcceleratorGDBRemote.h @@ -0,0 +1,46 @@ +//===----------------------------------------------------------------------===// +// +// 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 01aba34b941697..97604bb4824db4 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 53665cd4097394..c667fee225da3c 100644 --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp @@ -239,15 +239,13 @@ bool GDBRemoteCommunicationClient::GetWasmInstanceSupported() { return m_supports_wasm_instance == eLazyBoolYes; } -llvm::Expected<std::vector<AcceleratorActions>> -GDBRemoteCommunicationClient::GetAcceleratorInitializeActions() { - // Get the initial actions (e.g. breakpoints to set) requested by any - // accelerator plugins using the "jAcceleratorPluginInitialize" packet. This - // is sent once when a native process is launched or attached. The empty - // state (no plugins / no actions) is modelled as an empty vector; errors are - // returned to the caller to report. +llvm::Expected<AcceleratorInitializeResponse> +GDBRemoteCommunicationClient::GetAcceleratorInitializeResponse() { + // Get the initial actions (e.g. breakpoints to set) and connection settings + // requested by accelerator plugins. This is sent once when a process is + // launched or attached. if (!GetAcceleratorPluginsSupported()) - return std::vector<AcceleratorActions>(); + return AcceleratorInitializeResponse(); StringExtractorGDBRemote response; response.SetResponseValidatorToJSON(); @@ -257,23 +255,23 @@ GDBRemoteCommunicationClient::GetAcceleratorInitializeActions() { "failed to send jAcceleratorPluginInitialize packet"); if (response.IsUnsupportedResponse()) - return std::vector<AcceleratorActions>(); + return AcceleratorInitializeResponse(); if (response.IsErrorResponse()) return response.GetStatus().takeError(); - llvm::Expected<std::vector<AcceleratorActions>> actions = - llvm::json::parse<std::vector<AcceleratorActions>>(response.Peek(), - "AcceleratorActions"); - if (actions) - return actions; + llvm::Expected<AcceleratorInitializeResponse> initialize_response = + llvm::json::parse<AcceleratorInitializeResponse>( + response.Peek(), "AcceleratorInitializeResponse"); + if (initialize_response) + return initialize_response; // A bare JSON parse error (e.g. "missing comma at line 4") is meaningless on // its own, so include both the full response and the parse error; the caller // logs this and the user can spot the problem in the response. return llvm::createStringErrorV( "malformed jAcceleratorPluginInitialize response '{0}': {1}", - response.GetStringRef(), llvm::toString(actions.takeError())); + response.GetStringRef(), llvm::toString(initialize_response.takeError())); } llvm::Expected<AcceleratorBreakpointHitResponse> @@ -306,6 +304,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 e0654e13432b16..624b4ee11a0e4f 100644 --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h @@ -371,14 +371,13 @@ class GDBRemoteCommunicationClient : public GDBRemoteClientBase { /// response. bool GetWasmInstanceSupported(); - /// Send the "jAcceleratorPluginInitialize" packet and return the actions - /// requested by each accelerator plugin installed in lldb-server. The packet - /// is only sent if the lldb-server advertised accelerator plugin support via - /// "accelerator-plugins+" in its qSupported response; otherwise (and when no - /// plugin returns actions) this returns an empty vector. Errors are returned - /// for the caller to report. - llvm::Expected<std::vector<AcceleratorActions>> - GetAcceleratorInitializeActions(); + /// Send the "jAcceleratorPluginInitialize" packet and return its response. + /// The packet is only sent if lldb-server advertised accelerator plugin + /// support via "accelerator-plugins+" in its qSupported response; otherwise + /// this returns an empty response. Errors are returned for the caller to + /// report. + llvm::Expected<AcceleratorInitializeResponse> + GetAcceleratorInitializeResponse(); /// Send the "jAcceleratorPluginBreakpointHit" packet to notify the /// accelerator plugin that one of its requested breakpoints was hit, and @@ -389,6 +388,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 516f88aaf60768..51a210eed25061 100644 --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp @@ -4563,7 +4563,9 @@ std::vector<std::string> GDBRemoteCommunicationServerLLGS::HandleFeatures( ret.push_back("address-spaces+"); m_address_space_suffix_supported = true; } - if (!m_accelerator_plugins.empty()) + if (!m_accelerator_plugins.empty() || + (m_owning_accelerator_plugin && + !m_owning_accelerator_plugin->GetDynamicLoaderPluginName().empty())) ret.push_back("accelerator-plugins+"); // check for client features @@ -4667,14 +4669,21 @@ void GDBRemoteCommunicationServerLLGS::InstallPlugin( GDBRemoteCommunication::PacketResult GDBRemoteCommunicationServerLLGS::Handle_jAcceleratorPluginInitialize( StringExtractorGDBRemote &) { - std::vector<AcceleratorActions> accelerator_actions; + AcceleratorInitializeResponse initialize_response; for (std::unique_ptr<lldb_server::LLDBServerAcceleratorPlugin> &plugin_up : m_accelerator_plugins) { if (auto actions = plugin_up->GetInitializeActions()) - accelerator_actions.push_back(std::move(*actions)); + initialize_response.actions.push_back(std::move(*actions)); + } + if (m_owning_accelerator_plugin) { + llvm::StringRef dynamic_loader_plugin_name = + m_owning_accelerator_plugin->GetDynamicLoaderPluginName(); + if (!dynamic_loader_plugin_name.empty()) + initialize_response.dynamic_loader_plugin_name = + dynamic_loader_plugin_name; } StreamGDBRemote response; - response.PutAsJSONArray(accelerator_actions, /*hex_ascii=*/false); + response.PutAsJSON(initialize_response, /*hex_ascii=*/false); return SendPacketNoLock(response.GetString()); } @@ -4715,20 +4724,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) - return SendErrorResponse( - Status::FromErrorString("no dynamic loader info available")); - - 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) + return SendErrorResponse( + Status::FromErrorString("no dynamic loader info available")); + + StreamGDBRemote stream; + stream.PutAsJSON(*response, /*hex_ascii=*/false); + return SendPacketNoLock(stream.GetString()); + } } + 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/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h index 873f24d10a6277..d71711330ae84a 100644 --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h @@ -77,6 +77,13 @@ class GDBRemoteCommunicationServerLLGS void InstallPlugin( std::unique_ptr<lldb_server::LLDBServerAcceleratorPlugin> plugin_up); + /// Associate this server with the accelerator plugin that owns the + /// connection. The plugin must outlive this server. + void + SetOwningAcceleratorPlugin(lldb_server::LLDBServerAcceleratorPlugin &plugin) { + m_owning_accelerator_plugin = &plugin; + } + // NativeProcessProtocol::NativeDelegate overrides void InitializeDelegate(NativeProcessProtocol *process) override; @@ -120,6 +127,8 @@ class GDBRemoteCommunicationServerLLGS NativeProcessProtocol::Manager &m_process_manager; std::vector<std::unique_ptr<lldb_server::LLDBServerAcceleratorPlugin>> m_accelerator_plugins; + lldb_server::LLDBServerAcceleratorPlugin *m_owning_accelerator_plugin = + nullptr; lldb::tid_t m_current_tid = LLDB_INVALID_THREAD_ID; lldb::tid_t m_continue_tid = LLDB_INVALID_THREAD_ID; NativeProcessProtocol *m_current_process; diff --git a/lldb/source/Plugins/Process/gdb-remote/LLDBServerAcceleratorPlugin.h b/lldb/source/Plugins/Process/gdb-remote/LLDBServerAcceleratorPlugin.h index 2e585d9a64c6aa..00f7f201b3b021 100644 --- a/lldb/source/Plugins/Process/gdb-remote/LLDBServerAcceleratorPlugin.h +++ b/lldb/source/Plugins/Process/gdb-remote/LLDBServerAcceleratorPlugin.h @@ -35,6 +35,11 @@ class LLDBServerAcceleratorPlugin { virtual llvm::StringRef GetPluginName() = 0; + /// Name of the DynamicLoader plugin that LLDB should use for the + /// accelerator connection owned by this plugin. An empty name leaves + /// DynamicLoader selection to LLDB. + virtual llvm::StringRef GetDynamicLoaderPluginName() { return {}; } + virtual std::optional<AcceleratorActions> GetInitializeActions() = 0; virtual llvm::Expected<AcceleratorBreakpointHitResponse> diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp index 151932b67dca9f..8914ce6f2adf7c 100644 --- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp @@ -1147,13 +1147,17 @@ void ProcessGDBRemote::DidLaunchOrAttach(ArchSpec &process_arch) { // Ask any accelerator plugins installed in lldb-server for their initial // actions (e.g. breakpoints to set in the native process). - llvm::Expected<std::vector<AcceleratorActions>> init_actions = - m_gdb_comm.GetAcceleratorInitializeActions(); - if (!init_actions) { - LLDB_LOG_ERROR(log, init_actions.takeError(), - "failed to get accelerator initialize actions: {0}"); + m_accelerator_dynamic_loader_plugin_name.clear(); + llvm::Expected<AcceleratorInitializeResponse> init_response = + m_gdb_comm.GetAcceleratorInitializeResponse(); + if (!init_response) { + LLDB_LOG_ERROR(log, init_response.takeError(), + "failed to get accelerator initialize response: {0}"); } else { - for (const AcceleratorActions &actions : *init_actions) { + if (init_response->dynamic_loader_plugin_name) + m_accelerator_dynamic_loader_plugin_name = + *init_response->dynamic_loader_plugin_name; + for (const AcceleratorActions &actions : init_response->actions) { if (llvm::Error error = HandleAcceleratorActions(actions)) LLDB_LOG_ERROR(log, std::move(error), "failed to handle accelerator actions: {0}"); @@ -4607,7 +4611,8 @@ bool ProcessGDBRemote::StopNoticingNewThreads() { DynamicLoader *ProcessGDBRemote::GetDynamicLoader() { if (m_dyld_up.get() == nullptr) - m_dyld_up.reset(DynamicLoader::FindPlugin(this, "")); + m_dyld_up.reset(DynamicLoader::FindPlugin( + this, m_accelerator_dynamic_loader_plugin_name)); return m_dyld_up.get(); } diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h index d23af7f41849fd..c894cd736f2a02 100644 --- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h +++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h @@ -525,6 +525,10 @@ class ProcessGDBRemote : public Process, /// guards against handling them (e.g. setting the same breakpoints) twice. std::map<std::string, int64_t> m_processed_accelerator_actions; + /// DynamicLoader plugin selected by this connection's accelerator + /// initialization response. Empty means normal auto-selection. + std::string m_accelerator_dynamic_loader_plugin_name; + // ContinueDelegate interface void HandleAsyncStdout(llvm::StringRef out) override; void HandleAsyncMisc(llvm::StringRef data) override; diff --git a/lldb/source/Utility/AcceleratorGDBRemotePackets.cpp b/lldb/source/Utility/AcceleratorGDBRemotePackets.cpp index 180b2121c6155f..9990c244c08dff 100644 --- a/lldb/source/Utility/AcceleratorGDBRemotePackets.cpp +++ b/lldb/source/Utility/AcceleratorGDBRemotePackets.cpp @@ -124,6 +124,27 @@ json::Value toJSON(const AcceleratorActions &data) { return obj; } +bool fromJSON(const Value &value, AcceleratorInitializeResponse &data, + Path path) { + // Older servers returned the actions array directly. + if (value.getAsArray()) { + data.dynamic_loader_plugin_name = std::nullopt; + return json::fromJSON(value, data.actions, path); + } + + ObjectMapper o(value, path); + return o && o.map("actions", data.actions) && + o.mapOptional("dynamic_loader_plugin_name", + data.dynamic_loader_plugin_name); +} + +json::Value toJSON(const AcceleratorInitializeResponse &data) { + Object obj{{"actions", data.actions}}; + if (data.dynamic_loader_plugin_name) + obj["dynamic_loader_plugin_name"] = *data.dynamic_loader_plugin_name; + return obj; +} + bool fromJSON(const Value &value, AcceleratorBreakpointHitResponse &data, Path path) { ObjectMapper o(value, path); 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 00000000000000..bd77eba1de8f05 --- /dev/null +++ b/lldb/test/API/accelerator/dynamic_loader/TestAcceleratorDynamicLoader.py @@ -0,0 +1,243 @@ +""" +Test the accelerator dynamic loader against a mock GDB server. + +The server selects the loader by name during accelerator initialization. The +loader then asks the server for the loaded libraries via +jAcceleratorPluginGetDynamicLoaderLibraryInfo and loads them into the target. +""" + +import json +import os +import struct + +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.gdbclientutils import * +from lldbsuite.test.lldbgdbclient import GDBRemoteTestBase + +DYLD_PACKET = "jAcceleratorPluginGetDynamicLoaderLibraryInfo:" +BUNDLE_MAGIC = b"__CLANG_OFFLOAD_BUNDLE__" +BUNDLE_SECTION_OFFSET = 0x2000 + +CONTAINER_YAML = """\ +--- !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" + - Name: .hip_fatbin + Type: SHT_PROGBITS + Offset: 0x2000 + AddressAlign: 0x1000 + Content: "%s" +""" + + +def make_clang_offload_bundle(entries): + """Return a legacy Clang offload bundle and its entry offsets.""" + ids = [entry_id.encode("utf-8") for entry_id, _ in entries] + payload_offset = len(BUNDLE_MAGIC) + 8 + payload_offset += sum(24 + len(entry_id) for entry_id in ids) + + descriptors = bytearray() + payloads = bytearray() + offsets = [] + for entry_id, (_, payload) in zip(ids, entries): + offsets.append(payload_offset) + descriptors.extend( + struct.pack("<QQQ", payload_offset, len(payload), len(entry_id)) + ) + descriptors.extend(entry_id) + payloads.extend(payload) + payload_offset += len(payload) + + return ( + BUNDLE_MAGIC + struct.pack("<Q", len(entries)) + descriptors + payloads, + offsets, + ) + + +class AcceleratorResponder(MockGDBServerResponder): + """Serves a fixed set of library infos, and counts how often it is asked.""" + + def __init__( + self, + library_infos, + dynamic_loader_plugin_name="accelerator-gdb-remote", + ): + MockGDBServerResponder.__init__(self) + self.library_infos = library_infos + self.dynamic_loader_plugin_name = dynamic_loader_plugin_name + self.dyld_queries = 0 + self.last_dyld_packet = None + + def qSupported(self, client_supported): + return ( + super().qSupported(client_supported) + + ";qXfer:features:read+;accelerator-plugins+" + ) + + 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 == "jAcceleratorPluginInitialize": + action = { + "plugin_name": "mock", + "session_name": "", + "identifier": 0, + "breakpoints": [], + } + response = {"actions": [action]} + if self.dynamic_loader_plugin_name is not None: + response["dynamic_loader_plugin_name"] = self.dynamic_loader_plugin_name + return escape_binary(json.dumps(response, separators=(",", ":"))) + if packet.startswith(DYLD_PACKET): + self.dyld_queries += 1 + self.last_dyld_packet = packet + # "}" is the gdb-remote escape character. + return escape_binary( + json.dumps({"library_infos": self.library_infos}, separators=(",", ":")) + ) + return "" + + +class TestAcceleratorDynamicLoader(GDBRemoteTestBase): + def make_container(self): + """Embed a library in a Clang offload bundle inside a host ELF. + + Returns the container path and the (offset, size) of the device object. + + The embedded object puts .text at a different address than the outer + one, making it observable whether LLDB selected the bundled device + image or the host ELF. + """ + embedded_path = self.getBuildArtifact("embedded_lib.so") + self.yaml2obj("embedded_lib.yaml", embedded_path) + with open(embedded_path, "rb") as f: + embedded_bytes = f.read() + + bundle, offsets = make_clang_offload_bundle( + [ + ("host-x86_64-unknown-linux--", b""), + ("hipv4-amdgpu-amd-amdhsa--gfx942", embedded_bytes), + ] + ) + yaml_path = self.getBuildArtifact("container.yaml") + with open(yaml_path, "w") as f: + f.write(CONTAINER_YAML % bundle.hex()) + container_path = self.getBuildArtifact("container.bin") + self.yaml2obj(yaml_path, container_path) + offset = BUNDLE_SECTION_OFFSET + offsets[1] + with open(container_path, "rb") as f: + container_bytes = f.read() + self.assertEqual( + container_bytes[offset : offset + len(embedded_bytes)], embedded_bytes + ) + return container_path, offset, 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") + self.assertIsNotNone(self.server.responder.last_dyld_packet) + self.assertIn('"full":true', self.server.responder.last_dyld_packet) + 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.getBuildArtifact("accelerator_lib.so") + self.yaml2obj("accelerator_lib.yaml", lib) + 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 Clang bundle is loaded from the host ELF.""" + container, offset, size = self.make_container() + 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 bundled device object has .text at 0x3000; the host has it at + # 0x1000. + self.assert_text_loaded_at(target, module, 0x20003000) + + def test_not_selected_without_server_request(self): + """The loader is not used unless the server selects it by name.""" + self.server.responder = AcceleratorResponder( + [], dynamic_loader_plugin_name=None + ) + target = self.createTarget("accelerator.yaml") + process = self.connect(target) + self.assertTrue(process.IsValid(), "Process is valid") + + self.assertEqual( + self.server.responder.dyld_queries, + 0, + "the target architecture must not select the accelerator loader", + ) + self.assertIsNone(self.server.responder.last_dyld_packet) + + def test_selected_by_name_for_host_target(self): + """An explicit server selection does not depend on target 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, 1) + self.assertIsNotNone(self.server.responder.last_dyld_packet) + self.assertIn('"full":true', self.server.responder.last_dyld_packet) 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 00000000000000..2c4fe0fcd752c0 --- /dev/null +++ b/lldb/test/API/accelerator/dynamic_loader/accelerator.yaml @@ -0,0 +1,23 @@ +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_EXEC + Machine: EM_AMDGPU + OSABI: ELFOSABI_AMDGPU_HSA + ABIVersion: 0x1 + Flags: [ EF_AMDGPU_MACH_AMDGCN_GFX942 ] +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 00000000000000..ccdd8b34aa929e --- /dev/null +++ b/lldb/test/API/accelerator/dynamic_loader/accelerator_lib.yaml @@ -0,0 +1,23 @@ +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_DYN + Machine: EM_AMDGPU + OSABI: ELFOSABI_AMDGPU_HSA + ABIVersion: 0x1 + Flags: [ EF_AMDGPU_MACH_AMDGCN_GFX942 ] +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 00000000000000..cb89940712ae35 --- /dev/null +++ b/lldb/test/API/accelerator/dynamic_loader/embedded_lib.yaml @@ -0,0 +1,23 @@ +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_DYN + Machine: EM_AMDGPU + OSABI: ELFOSABI_AMDGPU_HSA + ABIVersion: 0x1 + Flags: [ EF_AMDGPU_MACH_AMDGCN_GFX942 ] +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 00000000000000..a7efe4fed40b1d --- /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/test/API/accelerator/mock/TestMockAcceleratorDynamicLoader.py b/lldb/test/API/accelerator/mock/TestMockAcceleratorDynamicLoader.py new file mode 100644 index 00000000000000..71085147829715 --- /dev/null +++ b/lldb/test/API/accelerator/mock/TestMockAcceleratorDynamicLoader.py @@ -0,0 +1,90 @@ +"""End-to-end test for the mock accelerator's dynamic loader.""" + +import os + +import lldb +from lldbsuite.test import configuration +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * + + +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 inherited by lldb-server.""" + 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 accelerator_target(self, native_target): + for candidate in self.dbg: + if candidate != native_target: + return candidate + return None + + @skipIfRemote + @skipIfDarwin # qProcessInfo cannot encode an AMDGPU architecture on Darwin. + @add_test_categories(["llgs"]) + def test_library_from_accelerator_process(self): + """The accelerator loader queries the second LLGS process directly.""" + self.build() + + library_path = self.getBuildArtifact("accelerator_lib.so") + self.yaml2obj("accelerator_lib.yaml", library_path) + + # No installed platform claims AMDGPU, so retain the selected host + # platform while creating a target with the requested architecture. + self.set_mock_env("LLDB_MOCK_ACCELERATOR_PLATFORM", "") + self.set_mock_env("LLDB_MOCK_ACCELERATOR_TRIPLE", "amdgpu-amd-amdhsa--gfx942") + self.set_mock_env("LLDB_MOCK_ACCELERATOR_LIBRARY_PATH", library_path) + + # This test does not exercise instruction decoding. Avoid asking the + # generic AMDGPU target to disassemble its synthetic stop PC. + self.runCmd("settings set stop-disassembly-display never") + + native_target = self.dbg.CreateTarget(self.getBuildArtifact("a.out")) + self.assertTrue(native_target, VALID_TARGET) + + # The first stop initializes the plugin. Continuing to its connection + # hook synchronously creates the accelerator target and queries its + # ProcessMockAccelerator for loaded libraries. + native_process = native_target.LaunchSimple( + None, None, self.get_process_working_directory() + ) + self.assertTrue(native_process, PROCESS_IS_VALID) + self.assertState(native_process.GetState(), lldb.eStateStopped) + native_process.Continue() + self.assertState(native_process.GetState(), lldb.eStateStopped) + + self.assertEqual(self.dbg.GetNumTargets(), 2) + accelerator_target = self.accelerator_target(native_target) + self.assertIsNotNone(accelerator_target) + self.assertTrue(accelerator_target.IsValid()) + accelerator_process = accelerator_target.GetProcess() + self.assertTrue(accelerator_process.IsValid()) + self.assertState(accelerator_process.GetState(), lldb.eStateStopped) + + module = None + library_basename = os.path.basename(library_path) + for candidate in accelerator_target.module_iter(): + if candidate.GetFileSpec().GetFilename() == library_basename: + module = candidate + break + self.assertIsNotNone(module, "accelerator library should be loaded") + + text = module.FindSection(".text") + self.assertTrue(text.IsValid()) + self.assertEqual(text.GetLoadAddress(accelerator_target), 0x10001000) diff --git a/lldb/test/API/accelerator/mock/TestMockAcceleratorPackets.py b/lldb/test/API/accelerator/mock/TestMockAcceleratorPackets.py index e40e6491e59a05..bf55668e0e7faf 100644 --- a/lldb/test/API/accelerator/mock/TestMockAcceleratorPackets.py +++ b/lldb/test/API/accelerator/mock/TestMockAcceleratorPackets.py @@ -68,11 +68,18 @@ def test_jAcceleratorPluginInitialize_returns_breakpoints(self): self.add_qSupported_packets() self.expect_gdbremote_sequence() - actions = self.send_and_decode_json("jAcceleratorPluginInitialize") + response = self.send_and_decode_json("jAcceleratorPluginInitialize") + self.assertIsInstance(response, dict) + self.assertIn("actions", response) + actions = response["actions"] self.assertIsInstance(actions, list) mock_action = get_accelerator_action(actions, "mock") self.assertIsNotNone(mock_action) + # The native connection must not select the accelerator target's + # dynamic loader. That is advertised by the second LLGS connection. + self.assertNotIn("dynamic_loader_plugin_name", response) + self.assertNotIn("dynamic_loader_plugin_name", mock_action) self.assertIn("breakpoints", mock_action) breakpoints = mock_action["breakpoints"] diff --git a/lldb/test/API/accelerator/mock/accelerator_lib.yaml b/lldb/test/API/accelerator/mock/accelerator_lib.yaml new file mode 100644 index 00000000000000..ccdd8b34aa929e --- /dev/null +++ b/lldb/test/API/accelerator/mock/accelerator_lib.yaml @@ -0,0 +1,23 @@ +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_DYN + Machine: EM_AMDGPU + OSABI: ELFOSABI_AMDGPU_HSA + ABIVersion: 0x1 + Flags: [ EF_AMDGPU_MACH_AMDGCN_GFX942 ] +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/tools/lldb-server/Plugins/Accelerator/Mock/LLDBServerMockAcceleratorPlugin.cpp b/lldb/tools/lldb-server/Plugins/Accelerator/Mock/LLDBServerMockAcceleratorPlugin.cpp index 0cce2955f6e0de..62002010c36387 100644 --- a/lldb/tools/lldb-server/Plugins/Accelerator/Mock/LLDBServerMockAcceleratorPlugin.cpp +++ b/lldb/tools/lldb-server/Plugins/Accelerator/Mock/LLDBServerMockAcceleratorPlugin.cpp @@ -31,7 +31,7 @@ using namespace lldb_private::lldb_server; using namespace lldb_private::process_gdb_remote; // Read a mock-accelerator setting from an environment variable so tests can -// configure the connection the mock advertises; falls back to default_value. +// configure the mock; falls back to default_value. static std::string GetMockEnvSetting(const char *env_var, std::string default_value) { if (const char *value = ::getenv(env_var)) @@ -150,12 +150,19 @@ std::optional<AcceleratorConnectionInfo> LLDBServerMockAcceleratorPlugin::CreateConnection() { Log *log = GetLog(GDBRLog::Plugin); + std::string triple = + GetMockEnvSetting("LLDB_MOCK_ACCELERATOR_TRIPLE", + HostInfo::GetArchitecture().GetTriple().str()); + std::string dynamic_loader_library_path = GetMockEnvSetting( + "LLDB_MOCK_ACCELERATOR_LIBRARY_PATH", "/path/to/lib.so"); + // An in-process gdb-remote server backed by a synthetic // ProcessMockAccelerator; no real process is launched. - m_process_manager_up = - std::make_unique<ProcessMockAccelerator::Manager>(m_mock_main_loop); + m_process_manager_up = std::make_unique<ProcessMockAccelerator::Manager>( + m_mock_main_loop, ArchSpec(triple), dynamic_loader_library_path); m_accelerator_gdb_server = std::make_unique<GDBRemoteCommunicationServerLLGS>( m_mock_main_loop, *m_process_manager_up); + m_accelerator_gdb_server->SetOwningAcceleratorPlugin(*this); // LLGS creates its current process from a launch; the manager ignores the // path but requires a non-empty argument list. @@ -214,9 +221,7 @@ LLDBServerMockAcceleratorPlugin::CreateConnection() { // invalid-platform and incompatible-triple failures. info.platform_name = GetMockEnvSetting("LLDB_MOCK_ACCELERATOR_PLATFORM", "host"); - info.triple = - GetMockEnvSetting("LLDB_MOCK_ACCELERATOR_TRIPLE", - HostInfo::GetArchitecture().GetTriple().str()); + info.triple = std::move(triple); info.synchronous = true; return info; } diff --git a/lldb/tools/lldb-server/Plugins/Accelerator/Mock/LLDBServerMockAcceleratorPlugin.h b/lldb/tools/lldb-server/Plugins/Accelerator/Mock/LLDBServerMockAcceleratorPlugin.h index 36f6aa2500ba80..8cf05bfdc42982 100644 --- a/lldb/tools/lldb-server/Plugins/Accelerator/Mock/LLDBServerMockAcceleratorPlugin.h +++ b/lldb/tools/lldb-server/Plugins/Accelerator/Mock/LLDBServerMockAcceleratorPlugin.h @@ -30,6 +30,9 @@ class LLDBServerMockAcceleratorPlugin : public LLDBServerAcceleratorPlugin { ~LLDBServerMockAcceleratorPlugin() override; llvm::StringRef GetPluginName() override; + llvm::StringRef GetDynamicLoaderPluginName() override { + return "accelerator-gdb-remote"; + } std::optional<AcceleratorActions> GetInitializeActions() override; llvm::Expected<AcceleratorBreakpointHitResponse> BreakpointWasHit(AcceleratorBreakpointHitArgs &args) override; diff --git a/lldb/tools/lldb-server/Plugins/Accelerator/Mock/ProcessMockAccelerator.cpp b/lldb/tools/lldb-server/Plugins/Accelerator/Mock/ProcessMockAccelerator.cpp index 6ae74d290d7150..9ebf94e8bd2842 100644 --- a/lldb/tools/lldb-server/Plugins/Accelerator/Mock/ProcessMockAccelerator.cpp +++ b/lldb/tools/lldb-server/Plugins/Accelerator/Mock/ProcessMockAccelerator.cpp @@ -9,7 +9,6 @@ #include "ProcessMockAccelerator.h" #include "ThreadMockAccelerator.h" -#include "lldb/Host/HostInfo.h" #include "lldb/Host/ProcessLaunchInfo.h" #include "llvm/Support/Error.h" @@ -26,7 +25,8 @@ static constexpr lldb::tid_t kMockTid = 3456; llvm::Expected<std::unique_ptr<NativeProcessProtocol>> ProcessMockAccelerator::Manager::Launch(ProcessLaunchInfo &launch_info, NativeDelegate &native_delegate) { - return std::make_unique<ProcessMockAccelerator>(kMockPid, native_delegate); + return std::make_unique<ProcessMockAccelerator>( + kMockPid, m_arch, m_dynamic_loader_library_path, native_delegate); } llvm::Expected<std::unique_ptr<NativeProcessProtocol>> @@ -36,9 +36,12 @@ ProcessMockAccelerator::Manager::Attach(lldb::pid_t pid, "accelerator process"); } -ProcessMockAccelerator::ProcessMockAccelerator(lldb::pid_t pid, - NativeDelegate &delegate) - : NativeProcessProtocol(pid, /*terminal_fd=*/-1, delegate) { +ProcessMockAccelerator::ProcessMockAccelerator( + lldb::pid_t pid, ArchSpec arch, std::string dynamic_loader_library_path, + NativeDelegate &delegate) + : NativeProcessProtocol(pid, /*terminal_fd=*/-1, delegate), + m_arch(std::move(arch)), + m_dynamic_loader_library_path(std::move(dynamic_loader_library_path)) { m_state = eStateStopped; UpdateThreads(); } @@ -99,8 +102,6 @@ size_t ProcessMockAccelerator::UpdateThreads() { } const ArchSpec &ProcessMockAccelerator::GetArchitecture() const { - if (!m_arch.IsValid()) - m_arch = HostInfo::GetArchitecture(); return m_arch; } @@ -129,3 +130,15 @@ std::vector<AddressSpaceInfo> ProcessMockAccelerator::GetAddressSpaces() { return {{"global", 1, /*is_thread_specific=*/false}, {"local", 2, /*is_thread_specific=*/true}}; } + +std::optional<AcceleratorDynamicLoaderResponse> +ProcessMockAccelerator::GetAcceleratorDynamicLoaderLibraryInfos( + const AcceleratorDynamicLoaderArgs &args) { + AcceleratorDynamicLoaderResponse response; + AcceleratorDynamicLoaderLibraryInfo info; + info.pathname = m_dynamic_loader_library_path; + 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 af1631ebc46084..882f7e199df834 100644 --- a/lldb/tools/lldb-server/Plugins/Accelerator/Mock/ProcessMockAccelerator.h +++ b/lldb/tools/lldb-server/Plugins/Accelerator/Mock/ProcessMockAccelerator.h @@ -12,6 +12,9 @@ #include "lldb/Host/common/NativeProcessProtocol.h" #include "lldb/Utility/ArchSpec.h" +#include <string> +#include <utility> + namespace lldb_private { namespace lldb_server { @@ -22,7 +25,11 @@ class ProcessMockAccelerator : public NativeProcessProtocol { public: class Manager : public NativeProcessProtocol::Manager { public: - using NativeProcessProtocol::Manager::Manager; + Manager(MainLoop &mainloop, ArchSpec arch, + std::string dynamic_loader_library_path) + : NativeProcessProtocol::Manager(mainloop), m_arch(std::move(arch)), + m_dynamic_loader_library_path( + std::move(dynamic_loader_library_path)) {} llvm::Expected<std::unique_ptr<NativeProcessProtocol>> Launch(ProcessLaunchInfo &launch_info, @@ -34,9 +41,15 @@ class ProcessMockAccelerator : public NativeProcessProtocol { Extension GetSupportedExtensions() const override { return Extension::address_spaces; } + + private: + ArchSpec m_arch; + std::string m_dynamic_loader_library_path; }; - ProcessMockAccelerator(lldb::pid_t pid, NativeDelegate &delegate); + ProcessMockAccelerator(lldb::pid_t pid, ArchSpec arch, + std::string dynamic_loader_library_path, + NativeDelegate &delegate); Status Resume(const ResumeActionList &resume_actions) override; Status Halt() override; @@ -66,8 +79,13 @@ class ProcessMockAccelerator : public NativeProcessProtocol { std::vector<AddressSpaceInfo> GetAddressSpaces() override; + std::optional<AcceleratorDynamicLoaderResponse> + GetAcceleratorDynamicLoaderLibraryInfos( + const AcceleratorDynamicLoaderArgs &args) override; + private: - mutable ArchSpec m_arch; + ArchSpec m_arch; + std::string m_dynamic_loader_library_path; }; } // namespace lldb_server diff --git a/lldb/unittests/Utility/AcceleratorGDBRemotePacketsTest.cpp b/lldb/unittests/Utility/AcceleratorGDBRemotePacketsTest.cpp index 8d64b4a8acf78f..208eb32c9bb614 100644 --- a/lldb/unittests/Utility/AcceleratorGDBRemotePacketsTest.cpp +++ b/lldb/unittests/Utility/AcceleratorGDBRemotePacketsTest.cpp @@ -148,6 +148,47 @@ TEST(AcceleratorGDBRemotePacketsTest, AcceleratorActionsEmpty) { EXPECT_TRUE(deserialized->breakpoints.empty()); } +TEST(AcceleratorGDBRemotePacketsTest, AcceleratorInitializeResponse) { + AcceleratorInitializeResponse response; + response.actions.emplace_back("mock", 1); + response.actions.back().session_name = "Mock Session"; + response.dynamic_loader_plugin_name = "accelerator-gdb-remote"; + + Expected<AcceleratorInitializeResponse> deserialized = + roundtripJSON(response); + ASSERT_THAT_EXPECTED(deserialized, Succeeded()); + ASSERT_EQ(1u, deserialized->actions.size()); + EXPECT_EQ("mock", deserialized->actions[0].plugin_name); + EXPECT_EQ("Mock Session", deserialized->actions[0].session_name); + EXPECT_EQ("accelerator-gdb-remote", deserialized->dynamic_loader_plugin_name); +} + +TEST(AcceleratorGDBRemotePacketsTest, + AcceleratorInitializeResponseWithoutDynamicLoader) { + AcceleratorInitializeResponse response; + response.actions.emplace_back("mock", 2); + + Expected<AcceleratorInitializeResponse> deserialized = + roundtripJSON(response); + ASSERT_THAT_EXPECTED(deserialized, Succeeded()); + ASSERT_EQ(1u, deserialized->actions.size()); + EXPECT_EQ("mock", deserialized->actions[0].plugin_name); + EXPECT_EQ(std::nullopt, deserialized->dynamic_loader_plugin_name); +} + +TEST(AcceleratorGDBRemotePacketsTest, + AcceleratorInitializeResponseLegacyActionsArray) { + Expected<AcceleratorInitializeResponse> deserialized = json::parse< + AcceleratorInitializeResponse>( + R"([{"plugin_name":"mock","session_name":"Mock Session","identifier":3,"breakpoints":[]}])", + "AcceleratorInitializeResponse"); + ASSERT_THAT_EXPECTED(deserialized, Succeeded()); + ASSERT_EQ(1u, deserialized->actions.size()); + EXPECT_EQ("mock", deserialized->actions[0].plugin_name); + EXPECT_EQ("Mock Session", deserialized->actions[0].session_name); + EXPECT_EQ(std::nullopt, deserialized->dynamic_loader_plugin_name); +} + TEST(AcceleratorGDBRemotePacketsTest, AcceleratorBreakpointHitResponseNoActions) { AcceleratorBreakpointHitResponse response; _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
