diff --git a/lldb/include/lldb/Host/common/NativeProcessProtocol.h b/lldb/include/lldb/Host/common/NativeProcessProtocol.h index 1d5fecfcd5c27..4d7b880ec61e0 100644 --- a/lldb/include/lldb/Host/common/NativeProcessProtocol.h +++ b/lldb/include/lldb/Host/common/NativeProcessProtocol.h @@ -17,6 +17,7 @@ #include "lldb/Utility/ArchSpec.h" #include "lldb/Utility/Iterable.h" #include "lldb/Utility/Status.h" +#include "lldb/Utility/GPUGDBRemotePackets.h" #include "lldb/Utility/TraceGDBRemotePackets.h" #include "lldb/Utility/UnimplementedError.h" #include "lldb/lldb-private-forward.h" @@ -179,6 +180,9 @@ class NativeProcessProtocol { // Accessors lldb::pid_t GetID() const { return m_pid; } + // Get process info + virtual bool GetProcessInfo(ProcessInstanceInfo &proc_info); + lldb::StateType GetState() const; bool IsRunning() const { @@ -187,7 +191,9 @@ class NativeProcessProtocol { bool IsStepping() const { return m_state == lldb::eStateStepping; } - bool CanResume() const { return m_state == lldb::eStateStopped; } + bool IsStopped() const { return m_state == lldb::eStateStopped; } + + bool CanResume() const { return IsStopped(); } lldb::ByteOrder GetByteOrder() const { return GetArchitecture().GetByteOrder(); @@ -252,6 +258,11 @@ class NativeProcessProtocol { virtual Status GetFileLoadAddress(const llvm::StringRef &file_name, lldb::addr_t &load_addr) = 0; + virtual std::optional + GetGPUDynamicLoaderLibraryInfos(const GPUDynamicLoaderArgs &args) { + return std::nullopt; + } + /// Extension flag constants, returned by Manager::GetSupportedExtensions() /// and passed to SetEnabledExtension() enum class Extension { @@ -264,8 +275,10 @@ class NativeProcessProtocol { memory_tagging = (1u << 6), savecore = (1u << 7), siginfo_read = (1u << 8), + gpu_plugins = (1u << 9), + gpu_dyld = (1u << 10), - LLVM_MARK_AS_BITMASK_ENUM(siginfo_read) + LLVM_MARK_AS_BITMASK_ENUM(gpu_dyld) }; class Manager { diff --git a/lldb/include/lldb/Target/DynamicLoader.h b/lldb/include/lldb/Target/DynamicLoader.h index 75bb6cb6bb907..6f18a6747e3dd 100644 --- a/lldb/include/lldb/Target/DynamicLoader.h +++ b/lldb/include/lldb/Target/DynamicLoader.h @@ -90,6 +90,17 @@ class DynamicLoader : public PluginInterface { /// loader often knows what the program entry point is. So the process and /// the dynamic loader can work together to detect this. virtual bool ProcessDidExec() { return false; } + + /// A function that allows dynamic loader to handle eStopReasonDynammicLoader + /// stop reasons. This is intended for dynamic loaders that aren't able to + /// set a breakpoint in the process, but rely on being notified by a driver or + /// debug services that shared libraries are available. + /// + /// \returns True if handled, false otherwise. + virtual bool HandleStopReasonDynammicLoader() { + return false; + } + /// Get whether the process should stop when images change. /// /// When images (executables and shared libraries) get loaded or unloaded, diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h index a8892e9c43225..e80182097bbc6 100644 --- a/lldb/include/lldb/Target/Process.h +++ b/lldb/include/lldb/Target/Process.h @@ -2311,6 +2311,21 @@ class Process : public std::enable_shared_from_this, SelectMostRelevant select_most_relevant = DoNoSelectMostRelevantFrame); + /// Wait for the process to resume. + /// + /// Given the current ProcessModID that identifies a current state, wait for + /// the process to resume. + /// + /// \param[in] curr_resume_id + /// The resume ID that identifies the resume ID from a stopped state. + /// + /// \param[in] timeout_sec + /// The maximum time in seconds to wait for the process to transition to + /// the eStateRunning state. If no timeout is supplied, block and wait + /// indefinitely. + Status WaitForNextResume(const uint32_t curr_resume_id, + std::optional timeout_sec); + uint32_t GetIOHandlerID() const { return m_iohandler_sync.GetValue(); } /// Waits for the process state to be running within a given msec timeout. diff --git a/lldb/include/lldb/Target/StopInfo.h b/lldb/include/lldb/Target/StopInfo.h index 368ec51d81891..4e7c107e3983a 100644 --- a/lldb/include/lldb/Target/StopInfo.h +++ b/lldb/include/lldb/Target/StopInfo.h @@ -174,6 +174,9 @@ class StopInfo : public std::enable_shared_from_this { static lldb::StopInfoSP CreateStopReasonHistoryBoundary(Thread &thread, const char *description); + static lldb::StopInfoSP + CreateStopReasonDyld(Thread &thread, const char *description = nullptr); + static lldb::StopInfoSP CreateStopReasonFork(Thread &thread, lldb::pid_t child_pid, lldb::tid_t child_tid); diff --git a/lldb/include/lldb/Target/Target.h b/lldb/include/lldb/Target/Target.h index 0d4e11b65339e..e631734d4ff84 100644 --- a/lldb/include/lldb/Target/Target.h +++ b/lldb/include/lldb/Target/Target.h @@ -1562,6 +1562,21 @@ class Target : public std::enable_shared_from_this, /// Print all the signals set in this target. void PrintDummySignals(Stream &strm, Args &signals); + + lldb::TargetSP GetGPUPluginTarget(llvm::StringRef plugin_name) { + return m_gpu_plugin_targets.lookup(plugin_name).lock(); + } + + void SetGPUPluginTarget(llvm::StringRef plugin_name, + lldb::TargetSP target_sp) { + m_native_target_gpu_wp = shared_from_this(); + m_gpu_plugin_targets[plugin_name] = target_sp; + } + + lldb::TargetSP GetNativeTargetForGPU() { + return m_native_target_gpu_wp.lock(); + } + protected: /// Implementing of ModuleList::Notifier. @@ -1650,6 +1665,14 @@ class Target : public std::enable_shared_from_this, /// signals you will have. llvm::StringMap m_dummy_signals; + /// If a process spawns another target for a GPU plug-in, this map tracks the + /// associated plug-in targets so they can be accessed. + llvm::StringMap m_gpu_plugin_targets; + /// If a target has a parent target, this can be used to synchronize the two + /// targets. For example if a GPU target wants to resume but it requires its + /// native target to resume as well, we can use this to make this happen. + lldb::TargetWP m_native_target_gpu_wp; + static void ImageSearchPathsChanged(const PathMappingList &path_list, void *baton); diff --git a/lldb/include/lldb/Utility/ArchSpec.h b/lldb/include/lldb/Utility/ArchSpec.h index 7e9bc23a75acb..b6d6e4801302c 100644 --- a/lldb/include/lldb/Utility/ArchSpec.h +++ b/lldb/include/lldb/Utility/ArchSpec.h @@ -236,6 +236,12 @@ class ArchSpec { eCore_wasm32, + eCore_amd_gpu_r600, + eCore_amd_gpu_gcn, + + eCore_nvidia_nvptx, + eCore_nvidia_nvptx64, + kNumCores, kCore_invalid, diff --git a/lldb/include/lldb/Utility/GDBRemote.h b/lldb/include/lldb/Utility/GDBRemote.h index ab700c00474ae..6f979404e1335 100644 --- a/lldb/include/lldb/Utility/GDBRemote.h +++ b/lldb/include/lldb/Utility/GDBRemote.h @@ -14,11 +14,11 @@ #include "lldb/lldb-enumerations.h" #include "lldb/lldb-public.h" #include "llvm/Support/raw_ostream.h" +#include "llvm/Support/JSON.h" #include #include #include -#include namespace lldb_private { @@ -43,6 +43,62 @@ class StreamGDBRemote : public StreamString { /// Number of bytes written. // TODO: Convert this function to take ArrayRef int PutEscapedBytes(const void *s, size_t src_len); + + /// Convert an object into JSON and add the JSON text to the packet. + /// + /// Any special characters in the JSON will be escaped to make sure it doesn't + /// interfere with the GDB remote protocol packet format. + /// + /// \param[in] obj + /// The object to convert to JSON which must have a method written that + /// converts the object to a llvm::json::Value: + /// + /// \code llvm::json::Value toJSON(const T &obj); + /// + /// \param[in] hex_ascii + /// If \a true then encode JSON as hex ASCII bytes. If \a false, then + /// encode as an escaped string value. + /// + /// \return + /// Number of bytes written. + template int PutAsJSON(const T &obj, bool hex_ascii) { + std::string json_string; + llvm::raw_string_ostream os(json_string); + os << toJSON(obj); + if (hex_ascii) + return PutStringAsRawHex8(json_string); + else + return PutEscapedBytes(json_string.c_str(), json_string.size()); + } + /// Convert an array of objects into JSON and add the JSON text to the packet. + /// + /// Any special characters in the JSON will be escaped to make sure it doesn't + /// interfere with the GDB remote protocol packet format. + /// + /// \param[in] array + /// An array of objects to convert to JSON. The object't type must have a + /// method written that converts the object to a llvm::json::Value: + /// + /// \code llvm::json::Value toJSON(const T &obj); + /// + /// \return + /// Number of bytes written. + template int PutAsJSONArray(const std::vector &array) { + std::string json_string; + llvm::raw_string_ostream os(json_string); + bool first = true; + os << "["; + for (auto &obj: array) { + if (first) + first = false; + else + os << ","; + os << toJSON(obj); + } + os << "]"; + return PutEscapedBytes(json_string.data(), json_string.size()); + } + }; /// GDB remote packet as used by the GDB remote communication history. Packets diff --git a/lldb/include/lldb/Utility/GPUGDBRemotePackets.h b/lldb/include/lldb/Utility/GPUGDBRemotePackets.h new file mode 100644 index 0000000000000..736fc4d70a014 --- /dev/null +++ b/lldb/include/lldb/Utility/GPUGDBRemotePackets.h @@ -0,0 +1,301 @@ +//===-- GPUGDBRemotePackets.h -----------------------------------*- C++ -*-===// +// +// 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_UTILITY_GPUGDBREMOTEPACKETS_H +#define LLDB_UTILITY_GPUGDBREMOTEPACKETS_H + +#include "lldb/lldb-types.h" +#include "llvm/Support/JSON.h" +#include +#include + +/// See docs/lldb-gdb-remote.txt for more information. +namespace lldb_private { + +/// A class that represents a symbol value +struct SymbolValue { + /// Name of the symbol. + std::string name; + /// If the optional doesn't have a value, then the symbol was not available. + std::optional value; +}; +bool fromJSON(const llvm::json::Value &value, SymbolValue &data, + llvm::json::Path path); + +llvm::json::Value toJSON(const SymbolValue &data); + +///----------------------------------------------------------------------------- +/// GPUBreakpointByName +/// +/// A structure that contains information on how to set a breakpoint by function +/// name with optional shared library name. +///----------------------------------------------------------------------------- + +struct GPUBreakpointByName { + /// An optional breakpoint shared library name to limit the scope of the + /// breakpoint to a specific shared library. + std::optional shlib; + /// The name of the function to set a breakpoint at. + std::string function_name; +}; + +bool fromJSON(const llvm::json::Value &value, GPUBreakpointByName &data, + llvm::json::Path path); + +llvm::json::Value toJSON(const GPUBreakpointByName &data); + +///----------------------------------------------------------------------------- +/// GPUBreakpointByAddress +/// +/// A structure that contains information on how to set a breakpoint by address. +///----------------------------------------------------------------------------- +struct GPUBreakpointByAddress { + /// A valid load address in the current native debug target. + lldb::addr_t load_address; +}; + +bool fromJSON(const llvm::json::Value &value, GPUBreakpointByAddress &data, + llvm::json::Path path); + +llvm::json::Value toJSON(const GPUBreakpointByAddress &data); + +///----------------------------------------------------------------------------- +/// GPUBreakpointInfo +/// +/// A breakpoint definition structure. +/// +/// Clients should either fill in the \a name_info or the \a addr_info. If the +/// breakpoint callback needs some symbols from the native process, they can +/// fill in the array of symbol names with any symbol names that are needed. +/// These symbol values will be delivered in the breakpoint callback to the GPU +/// plug-in. +///----------------------------------------------------------------------------- +struct GPUBreakpointInfo { + std::string identifier; + /// An optional breakpoint by name info. + std::optional name_info; + /// An optional load address to set a breakpoint at in the native process. + std::optional addr_info; + /// Names of symbols that should be supplied when the breakpoint is hit. + std::vector symbol_names; +}; + +bool fromJSON(const llvm::json::Value &value, GPUBreakpointInfo &data, + llvm::json::Path path); + +llvm::json::Value toJSON(const GPUBreakpointInfo &data); + +struct GPUPluginBreakpointHitArgs { + GPUPluginBreakpointHitArgs() = default; + GPUPluginBreakpointHitArgs(llvm::StringRef plugin_name) + : plugin_name(plugin_name) {} + + std::string plugin_name; + GPUBreakpointInfo breakpoint; + std::vector symbol_values; + + std::optional GetSymbolValue(llvm::StringRef symbol_name) const; +}; + +bool fromJSON(const llvm::json::Value &value, GPUPluginBreakpointHitArgs &data, + llvm::json::Path path); + +llvm::json::Value toJSON(const GPUPluginBreakpointHitArgs &data); + +///----------------------------------------------------------------------------- +/// GPUPluginConnectionInfo +/// +/// A structure that contains all of the information needed for LLDB to create +/// a reverse connection to a GPU GDB server +///----------------------------------------------------------------------------- +struct GPUPluginConnectionInfo { + /// A target executable path to use when creating the target. + std::optional exe_path; + /// The name of the platform to select when creating the target. + std::optional platform_name; + /// The target triple to use as the architecture when creating the target. + std::optional triple; + /// The connection URL to use with "process connect ". + std::string connect_url; +}; + +bool fromJSON(const llvm::json::Value &value, GPUPluginConnectionInfo &data, + llvm::json::Path path); + +llvm::json::Value toJSON(const GPUPluginConnectionInfo &data); + +///----------------------------------------------------------------------------- +/// GPUActions +/// +/// A structure used by the native process that is debugging the GPU that +/// contains actions to be performed after: +/// +/// - GPU Initilization in response to the "jGPUPluginInitialize" packet sent to +/// the native process' lldb-server that contains GPU plugins. This packet is +/// sent to the ProcessGDBRemote for the native process one time when a native +/// process is being attached or launched. +/// +/// - When a native breakpoint that was requested by the GPU plugin is hit, the +/// native process in LLDB will call into the native process' GDB server and +/// have it call the GPU plug-in method: +/// +/// GPUPluginBreakpointHitResponse +/// LLDBServerPlugin::BreakpointWasHit(GPUPluginBreakpointHitArgs &args); +/// +/// The GPUPluginBreakpointHitResponse contains a GPUActions member that will +/// be encoded and sent back to the ProcessGDBRemote for the native process. +/// +/// - Anytime the native process stops, the native process' GDB server will ask +/// each GPU plug-in if there are any actions it would like to report, the +/// native process' lldb-server will call the GPU plug-in method: +/// +/// std::optional LLDBServerPlugin::NativeProcessIsStopping(); +/// +/// If GPUActions are returned from this method, they will be encoded into the +/// native process' stop reply packet and handled in ProcessGDBRemote for the +/// native process. +///----------------------------------------------------------------------------- +struct GPUActions { + GPUActions() = default; + GPUActions(llvm::StringRef plugin_name) : plugin_name(plugin_name) {} + + /// The name of the plugin. + std::string plugin_name; + /// New breakpoints to set. Nothing to set if this is empty. + std::vector breakpoints; + /// If a GPU connection is available return a connect URL to use to reverse + /// connect to the GPU GDB server as a separate process. + std::optional connect_info; + /// Set this to true if the native plug-in should tell the ProcessGDBRemote + /// in LLDB for the GPU process to load libraries. This allows the native + /// process to be notified that it should query for the shared libraries on + /// the GPU connection. + bool load_libraries = false; + /// Set this to true if the native plug-in resume the GPU process. + bool resume_gpu_process = false; + /// Set this to true if the native plug-in sync with the GPU process and wait + /// for it to return to a running state. + bool wait_for_gpu_process_to_resume = false; +}; + +bool fromJSON(const llvm::json::Value &value, GPUActions &data, + llvm::json::Path path); + +llvm::json::Value toJSON(const GPUActions &data); + +struct GPUSectionInfo { + /// Name of the section to load. If there are multiple sections, each section + /// will be looked up and then a child section within the previous section + /// will be looked up. This allows plug-ins to specify a hiearchy of sections + /// in the case where section names are not unique. A valid example looks + /// like: ["PT_LOAD[0]", ".text"]. If there is only one section name, LLDB + /// will find the first section that matches that name. + std::vector names; + /// The load address of this section only. If this value is valid, then this + /// section is loaded at this address, else child sections can be loaded + /// individually. + lldb::addr_t load_address; +}; + +bool fromJSON(const llvm::json::Value &value, GPUSectionInfo &data, + llvm::json::Path path); + +llvm::json::Value toJSON(const GPUSectionInfo &data); + +struct GPUDynamicLoaderLibraryInfo { + /// The path to the shared library object file on disk. + std::string pathname; + /// The UUID of the shared library if it is known. + std::optional uuid_str; + /// Set to true if this shared library is being loaded, false if the library + /// is being unloaded. + bool load; + /// The address where the object file is loaded. If this member has a value + /// the object file is loaded at an address and all sections should be slid to + /// match this base address. If this member doesn't have a value, then + /// individual section's load address must be specified individually if + /// \a loaded_sections has a value. If this doesn't have a value and the + /// \a loaded_Section doesn't have a value, this library will be unloaded. + std::optional load_address; + + /// If the object file specified by this structure has sections that get + /// loaded at different times then this will not be empty. If it is empty + /// the \a load_address must be specified if \a load is true. + std::vector loaded_sections; + + /// If this library is only available as an in memory image of an object file + /// in the native process, then this address holds the address from which the + /// image can be read. + std::optional native_memory_address; + /// If this library is only available as an in memory image of an object file + /// in the native process, then this size of the in memory image that starts + /// at \a native_memory_address. + std::optional native_memory_size; + /// If the library exists inside of a file at an offset, \a file_offset will + /// have a value that is the offset in bytes from the start of the file + /// specified by \a pathname. + std::optional file_offset; + /// If the library exists inside of a file at an offset, \a file_size will + /// have a value that indicates the size in bytes of the object file. + std::optional file_size; +}; + +bool fromJSON(const llvm::json::Value &value, GPUDynamicLoaderLibraryInfo &data, + llvm::json::Path path); + +llvm::json::Value toJSON(const GPUDynamicLoaderLibraryInfo &data); + +///----------------------------------------------------------------------------- +/// GPUPluginBreakpointHitResponse +/// +/// A response structure from the GPU plugin from hitting a native breakpoint +/// set by the GPU plugin. +///----------------------------------------------------------------------------- +struct GPUPluginBreakpointHitResponse { + GPUPluginBreakpointHitResponse() = default; + GPUPluginBreakpointHitResponse(llvm::StringRef plugin_name) + : actions(plugin_name) {} + + ///< Set to true if this berakpoint should be disabled. + bool disable_bp = false; + /// Optional new breakpoints to set. + GPUActions actions; +}; + +bool fromJSON(const llvm::json::Value &value, + GPUPluginBreakpointHitResponse &data, llvm::json::Path path); + +llvm::json::Value toJSON(const GPUPluginBreakpointHitResponse &data); + +struct GPUDynamicLoaderArgs { + /// Set to true to get all shared library information. Set to false to get + /// only the libraries that were updated since the last call to + /// the "jGPUPluginGetDynamicLoaderLibraryInfo" packet. + bool full; +}; + +bool fromJSON(const llvm::json::Value &value, GPUDynamicLoaderArgs &data, + llvm::json::Path path); + +llvm::json::Value toJSON(const GPUDynamicLoaderArgs &data); + +struct GPUDynamicLoaderResponse { + /// Set to true to get all shared library information. Set to false to get + /// only the libraries that were updated since the last call to + /// the "jGPUPluginGetDynamicLoaderLibraryInfo" packet. + std::vector library_infos; +}; + +bool fromJSON(const llvm::json::Value &value, GPUDynamicLoaderResponse &data, + llvm::json::Path path); + +llvm::json::Value toJSON(const GPUDynamicLoaderResponse &data); + +} // namespace lldb_private + +#endif // LLDB_UTILITY_GPUGDBREMOTEPACKETS_H diff --git a/lldb/include/lldb/Utility/StringExtractorGDBRemote.h b/lldb/include/lldb/Utility/StringExtractorGDBRemote.h index dd468ef5bddef..b6c03b91afb97 100644 --- a/lldb/include/lldb/Utility/StringExtractorGDBRemote.h +++ b/lldb/include/lldb/Utility/StringExtractorGDBRemote.h @@ -12,6 +12,7 @@ #include "lldb/Utility/Status.h" #include "lldb/Utility/StringExtractor.h" #include "llvm/ADT/StringRef.h" +#include "llvm/Support/JSON.h" #include #include @@ -181,6 +182,11 @@ class StringExtractorGDBRemote : public StringExtractor { eServerPacketType_vStopped, eServerPacketType_vCtrlC, eServerPacketType_vStdio, + + // GPU plug-in packets. + eServerPacketType_jGPUPluginInitialize, + eServerPacketType_jGPUPluginBreakpointHit, + eServerPacketType_jGPUPluginGetDynamicLoaderLibraryInfo, }; ServerPacketType GetServerPacketType() const; @@ -214,6 +220,25 @@ class StringExtractorGDBRemote : public StringExtractor { std::optional> GetPidTid(lldb::pid_t default_pid); + + template std::optional GetFromJSONText() { + llvm::Expected info = llvm::json::parse(Peek(), ""); + if (info) + return *info; + llvm::consumeError(info.takeError()); + return std::nullopt; + } + + template std::optional GetFromJSONHexASCII() { + std::string json; + GetHexByteString(json); + llvm::Expected info = llvm::json::parse(json.c_str(), ""); + if (info) + return *info; + llvm::consumeError(info.takeError()); + return std::nullopt; + } + protected: ResponseValidatorCallback m_validator = nullptr; void *m_validator_baton = nullptr; diff --git a/lldb/include/lldb/lldb-enumerations.h b/lldb/include/lldb/lldb-enumerations.h index 69e8671b6e21b..cbf076898d38b 100644 --- a/lldb/include/lldb/lldb-enumerations.h +++ b/lldb/include/lldb/lldb-enumerations.h @@ -260,6 +260,11 @@ enum StopReason { // Indicates that execution stopped because the debugger backend relies // on recorded data and we reached the end of that data. eStopReasonHistoryBoundary, + /// Inicates that the program stopped for a dynamic loader plugin to do some + /// work. The process will auto continue after the plugin is done loading or + /// unloading some shared libraries unless the target setting named + /// "target.process.stop-on-sharedlibrary-events" it set to true. + eStopReasonDynammicLoader }; /// Command Return Status Types. diff --git a/lldb/source/API/SBModuleSpec.cpp b/lldb/source/API/SBModuleSpec.cpp index fbbcfeac20178..548b056fac277 100644 --- a/lldb/source/API/SBModuleSpec.cpp +++ b/lldb/source/API/SBModuleSpec.cpp @@ -170,7 +170,7 @@ uint64_t SBModuleSpec::GetObjectSize() { void SBModuleSpec::SetObjectSize(uint64_t object_size) { LLDB_INSTRUMENT_VA(this, object_size); - + m_opaque_up->SetObjectSize(object_size); } diff --git a/lldb/source/API/SBThread.cpp b/lldb/source/API/SBThread.cpp index d9469fc1390d6..73f4145749349 100644 --- a/lldb/source/API/SBThread.cpp +++ b/lldb/source/API/SBThread.cpp @@ -173,6 +173,7 @@ size_t SBThread::GetStopReasonDataCount() { case eStopReasonProcessorTrace: case eStopReasonVForkDone: case eStopReasonHistoryBoundary: + case lldb::eStopReasonDynammicLoader: // There is no data for these stop reasons. return 0; @@ -235,6 +236,7 @@ uint64_t SBThread::GetStopReasonDataAtIndex(uint32_t idx) { case eStopReasonProcessorTrace: case eStopReasonVForkDone: case eStopReasonHistoryBoundary: + case lldb::eStopReasonDynammicLoader: // There is no data for these stop reasons. return 0; diff --git a/lldb/source/Core/Module.cpp b/lldb/source/Core/Module.cpp index 90997dada3666..4296b066169df 100644 --- a/lldb/source/Core/Module.cpp +++ b/lldb/source/Core/Module.cpp @@ -151,15 +151,17 @@ Module::Module(const ModuleSpec &module_spec) module_spec.GetObjectName().IsEmpty() ? "" : ")"); auto data_sp = module_spec.GetData(); - lldb::offset_t file_size = 0; - if (data_sp) - file_size = data_sp->GetByteSize(); // First extract all module specifications from the file using the local file // path. If there are no specifications, then don't fill anything in ModuleSpecList modules_specs; + if (ObjectFile::GetModuleSpecifications( - module_spec.GetFileSpec(), 0, file_size, modules_specs, data_sp) == 0) + module_spec.GetFileSpec(), + module_spec.GetObjectOffset(), + module_spec.GetObjectSize(), + modules_specs, + data_sp) == 0) return; // Now make sure that one of the module specifications matches what we just @@ -1514,6 +1516,9 @@ bool Module::MatchesModuleSpec(const ModuleSpec &module_ref) { if (!FileSpec::Match(platform_file_spec, GetPlatformFileSpec())) return false; + if (m_object_offset != module_ref.GetObjectOffset()) + return false; + const ArchSpec &arch = module_ref.GetArchitecture(); if (arch.IsValid()) { if (!m_arch.IsCompatibleMatch(arch)) diff --git a/lldb/source/Host/common/NativeProcessProtocol.cpp b/lldb/source/Host/common/NativeProcessProtocol.cpp index 405acbb5662d6..0a579d64a8f8f 100644 --- a/lldb/source/Host/common/NativeProcessProtocol.cpp +++ b/lldb/source/Host/common/NativeProcessProtocol.cpp @@ -766,4 +766,8 @@ void NativeProcessProtocol::DoStopIDBumped(uint32_t /* newBumpId */) { // Default implementation does nothing. } +bool NativeProcessProtocol::GetProcessInfo(ProcessInstanceInfo &proc_info) { + return Host::GetProcessInfo(m_pid, proc_info); +} + NativeProcessProtocol::Manager::~Manager() = default; diff --git a/lldb/source/Plugins/DynamicLoader/CMakeLists.txt b/lldb/source/Plugins/DynamicLoader/CMakeLists.txt index 01aba34b94169..333680ff9359f 100644 --- a/lldb/source/Plugins/DynamicLoader/CMakeLists.txt +++ b/lldb/source/Plugins/DynamicLoader/CMakeLists.txt @@ -13,3 +13,4 @@ add_subdirectory(Static) add_subdirectory(Hexagon-DYLD) add_subdirectory(Windows-DYLD) add_subdirectory(wasm-DYLD) +add_subdirectory(GDBRemoteGPU) diff --git a/lldb/source/Plugins/DynamicLoader/GDBRemoteGPU/CMakeLists.txt b/lldb/source/Plugins/DynamicLoader/GDBRemoteGPU/CMakeLists.txt new file mode 100644 index 0000000000000..5a421999d1bcf --- /dev/null +++ b/lldb/source/Plugins/DynamicLoader/GDBRemoteGPU/CMakeLists.txt @@ -0,0 +1,10 @@ +add_lldb_library(lldbPluginDynamicLoaderGDBRemoteGPU PLUGIN + DynamicLoaderGDBRemoteGPU.cpp + + LINK_LIBS + lldbCore + lldbHost + lldbSymbol + lldbTarget + lldbUtility + ) diff --git a/lldb/source/Plugins/DynamicLoader/GDBRemoteGPU/DynamicLoaderGDBRemoteGPU.cpp b/lldb/source/Plugins/DynamicLoader/GDBRemoteGPU/DynamicLoaderGDBRemoteGPU.cpp new file mode 100644 index 0000000000000..f587f5a2f4cba --- /dev/null +++ b/lldb/source/Plugins/DynamicLoader/GDBRemoteGPU/DynamicLoaderGDBRemoteGPU.cpp @@ -0,0 +1,185 @@ +//===-- DynamicLoaderGDBRemoteGPU.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 "lldb/Core/Module.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Core/Section.h" +#include "lldb/Symbol/ObjectFile.h" +#include "lldb/Target/SectionLoadList.h" +#include "lldb/Target/Target.h" +#include "lldb/Utility/DataBufferHeap.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" + +#include "DynamicLoaderGDBRemoteGPU.h" +#include "Plugins/Process/gdb-remote/ProcessGDBRemote.h" + +using namespace lldb; +using namespace lldb_private; +using namespace lldb_private::process_gdb_remote; + +LLDB_PLUGIN_DEFINE(DynamicLoaderGDBRemoteGPU) + +// Create an instance of this class. This function is filled into the plugin +// info class that gets handed out by the plugin factory and allows the lldb to +// instantiate an instance of this class. +DynamicLoader *DynamicLoaderGDBRemoteGPU::CreateInstance(Process *process, + bool force) { + // Only create an instance if the clients ask for this plugin by name. This + // plugin will be created by the ProcessGDBRemote class by asking for it by + // name. + if (force) + return new DynamicLoaderGDBRemoteGPU(process); + return nullptr; +} + +// Constructor +DynamicLoaderGDBRemoteGPU::DynamicLoaderGDBRemoteGPU(Process *process) + : DynamicLoader(process) {} + +/// Called after attaching a process. +/// +/// Allow DynamicLoader plug-ins to execute some code after +/// attaching to a process. +void DynamicLoaderGDBRemoteGPU::DidAttach() { LoadModulesFromGDBServer(true); } + +/// Called after attaching a process. +/// +/// Allow DynamicLoader plug-ins to execute some code after +/// attaching to a process. +void DynamicLoaderGDBRemoteGPU::DidLaunch() { LoadModulesFromGDBServer(true); } + +bool DynamicLoaderGDBRemoteGPU::HandleStopReasonDynammicLoader() { + return LoadModulesFromGDBServer(false); +} + +bool DynamicLoaderGDBRemoteGPU::LoadModulesFromGDBServer(bool full) { + Log *log = GetLog(LLDBLog::DynamicLoader); + + ProcessGDBRemote *gdb_process = static_cast(m_process); + ModuleList loaded_module_list; + GPUDynamicLoaderArgs args; + args.full = full; + Target &target = m_process->GetTarget(); + std::optional response = + gdb_process->GetGDBRemote().GetGPUDynamicLoaderLibraryInfos(args); + if (!response) { + LLDB_LOG(log, "Failed to get dynamic loading info from GDB server"); + return false; + } + for (const GPUDynamicLoaderLibraryInfo &info : response->library_infos) { + std::shared_ptr data_sp; + // Read the object file from memory if requested. + if (info.native_memory_address && info.native_memory_size) { + LLDB_LOG(log, "Reading \"{0}\" from memory at {1:x}", info.pathname, + *info.native_memory_address); + data_sp = std::make_shared(*info.native_memory_size, 0); + Status error; + // TODO: we are assuming we can read the memory from the GPU process + // since the memory is shared with the host process. + const size_t bytes_read = m_process->ReadMemory( + *info.native_memory_address, data_sp->GetBytes(), + data_sp->GetByteSize(), error); + if (bytes_read != *info.native_memory_size) { + LLDB_LOG(log, "Failed to read \"{0}\" from memory at {1:x}: {2}", + info.pathname, *info.native_memory_address, error); + data_sp.reset(); + } + } + // Extract the UUID if available. + UUID uuid; + if (info.uuid_str) + uuid.SetFromStringRef(*info.uuid_str); + // Create a module specification from the info we got. + ModuleSpec module_spec(FileSpec(info.pathname), uuid, data_sp); + if (info.file_offset) + module_spec.SetObjectOffset(*info.file_offset); + if (info.file_size) + module_spec.SetObjectSize(*info.file_size); + // Get or create the module from the module spec. + ModuleSP module_sp = target.GetOrCreateModule(module_spec, + /*notify=*/true); + if (module_sp) { + LLDB_LOG(log, "Created module for \"{0}\": {1:x}", + info.pathname, module_sp.get()); + bool changed = false; + if (info.load_address) { + LLDB_LOG(log, "Setting load address for module \"{0}\" to {1:x}", + info.pathname, *info.load_address); + + module_sp->SetLoadAddress(target, *info.load_address, + /*value_is_offset=*/true , changed); + } else if (!info.loaded_sections.empty()) { + + // Set the load address of the module to the first loaded section. + bool warn_multiple = true; + for (const GPUSectionInfo § : info.loaded_sections) { + if (sect.names.empty()) + continue; + // Find the section by name using the names specified. If there is + // only on name, them find it. If there are multiple names, the top + // most section names comes first and then we find child sections + // by name within the previous section. + SectionSP section_sp; + for (uint32_t i=0; iGetChildren().FindSectionByName(name); + else + section_sp = module_sp->GetSectionList()->FindSectionByName(name); + if (!section_sp) + break; + } + if (section_sp) { + LLDB_LOG(log, "Loading module \"{0}\" section \"{1} to {2:x}", + info.pathname, section_sp->GetName(), sect.load_address); + changed = target.SetSectionLoadAddress(section_sp, + sect.load_address, + warn_multiple); + } else { + LLDB_LOG(log, "Failed to find section \"{0}\"", + section_sp->GetName()); + } + } + } + if (changed) { + LLDB_LOG(log, "Module \"{0}\" was loaded, notifying target", + info.pathname); + loaded_module_list.AppendIfNeeded(module_sp); + } + } + } + target.ModulesDidLoad(loaded_module_list); + return true; // Handled the request. +} + +ThreadPlanSP +DynamicLoaderGDBRemoteGPU::GetStepThroughTrampolinePlan(Thread &thread, + bool stop_others) { + return ThreadPlanSP(); +} + +Status DynamicLoaderGDBRemoteGPU::CanLoadImage() { + return Status::FromErrorString( + "can't load images on GPU targets"); +} + +void DynamicLoaderGDBRemoteGPU::Initialize() { + PluginManager::RegisterPlugin(GetPluginNameStatic(), + GetPluginDescriptionStatic(), CreateInstance); +} + +void DynamicLoaderGDBRemoteGPU::Terminate() { + PluginManager::UnregisterPlugin(CreateInstance); +} + +llvm::StringRef DynamicLoaderGDBRemoteGPU::GetPluginDescriptionStatic() { + return "Dynamic loader plug-in for GPU targets that uses GDB remote packets " + "tailored for GPUs to get the library load and unload information from" + " the lldb-server GPU plug-in GDB server connection."; +} diff --git a/lldb/source/Plugins/DynamicLoader/GDBRemoteGPU/DynamicLoaderGDBRemoteGPU.h b/lldb/source/Plugins/DynamicLoader/GDBRemoteGPU/DynamicLoaderGDBRemoteGPU.h new file mode 100644 index 0000000000000..0284f98fdcaf3 --- /dev/null +++ b/lldb/source/Plugins/DynamicLoader/GDBRemoteGPU/DynamicLoaderGDBRemoteGPU.h @@ -0,0 +1,75 @@ +//===-- DynamicLoaderGDBRemoteGPU.h -----------------------------*- C++ -*-===// +// +// 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_STATIC_DYNAMICLOADERGDBREMOTEGPU_H +#define LLDB_SOURCE_PLUGINS_DYNAMICLOADER_STATIC_DYNAMICLOADERGDBREMOTEGPU_H + +#include "lldb/Target/DynamicLoader.h" +#include "lldb/Target/Process.h" +#include "lldb/Utility/FileSpec.h" +#include "lldb/Utility/UUID.h" + +/// A dynamic loader class for lldb-server GPU plug-ins. +/// +/// GPUs have special requirements for loading and unloading shared libraries +/// and this class implements the DynamicLoader interface to support these +/// targets. The lldb-server GPU plug-ins implement functions that return the +/// information needed to load and unload shared libraries by handling the +/// "jGPUPluginGetDynamicLoaderLibraryInfo" packet. Many GPUs have drivers that +/// coordinate the loading and unloading of shared libraries, but they don't use +/// the standard method of setting a breakpoint in the target and handle the +/// breakpoint callback in the dynamic loader plug-in. Instead, the drivers +/// have callbacks or notifications that tell the lldb-server GPU plug-in when +/// a shared library is loaded or unloaded. +class DynamicLoaderGDBRemoteGPU : public lldb_private::DynamicLoader { +public: + DynamicLoaderGDBRemoteGPU(lldb_private::Process *process); + + // Static Functions + static void Initialize(); + + static void Terminate(); + + static llvm::StringRef GetPluginNameStatic() { return "gdb-remote-gpu"; } + + static llvm::StringRef GetPluginDescriptionStatic(); + + static lldb_private::DynamicLoader * + CreateInstance(lldb_private::Process *process, bool force); + + /// Called after attaching a process. + /// + /// Allow DynamicLoader plug-ins to execute some code after + /// attaching to a process. + void DidAttach() override; + + void DidLaunch() override; + + bool HandleStopReasonDynammicLoader() override; + + lldb::ThreadPlanSP GetStepThroughTrampolinePlan(lldb_private::Thread &thread, + bool stop_others) override; + + lldb_private::Status CanLoadImage() override; + + // PluginInterface protocol + llvm::StringRef GetPluginName() override { return GetPluginNameStatic(); } + +private: + /// Load all modules by sending a "jGPUPluginGetDynamicLoaderLibraryInfo" + /// packet to the GDB server. + /// + /// \param[in] full + /// If true, load all modules. If false, load or unload only new modules. + /// + /// \returns True if the GDB server supports the packet named + /// "jGPUPluginGetDynamicLoaderLibraryInfo", false otherwise. + bool LoadModulesFromGDBServer(bool full); +}; + +#endif // LLDB_SOURCE_PLUGINS_DYNAMICLOADER_STATIC_DYNAMICLOADERGDBREMOTEGPU_H diff --git a/lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.cpp b/lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.cpp index f69358de6a288..687f0efa06ec1 100644 --- a/lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.cpp +++ b/lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.cpp @@ -603,8 +603,8 @@ size_t ObjectFileELF::GetModuleSpecifications( // // Validate it is ok to remove GetOsFromOSABI GetOsFromOSABI(header.e_ident[EI_OSABI], ostype); - assert(spec_ostype == ostype); - if (spec_ostype != llvm::Triple::OSType::UnknownOS) { + if (ostype != llvm::Triple::OSType::UnknownOS && + spec_ostype != llvm::Triple::OSType::UnknownOS) { LLDB_LOGF(log, "ObjectFileELF::%s file '%s' set ELF module OS type " "from ELF header OSABI.", @@ -1370,7 +1370,6 @@ size_t ObjectFileELF::GetSectionHeaderInfo(SectionHeaderColl §ion_headers, // We'll refine this with note data as we parse the notes. if (arch_spec.GetTriple().getOS() == llvm::Triple::OSType::UnknownOS) { llvm::Triple::OSType ostype; - llvm::Triple::OSType spec_ostype; const uint32_t sub_type = subTypeFromElfHeader(header); arch_spec.SetArchitecture(eArchTypeELF, header.e_machine, sub_type, header.e_ident[EI_OSABI]); @@ -1382,9 +1381,6 @@ size_t ObjectFileELF::GetSectionHeaderInfo(SectionHeaderColl §ion_headers, // notes at all and have EI_OSABI flag set to System V, as result the OS // will be set to UnknownOS. GetOsFromOSABI(header.e_ident[EI_OSABI], ostype); - spec_ostype = arch_spec.GetTriple().getOS(); - assert(spec_ostype == ostype); - UNUSED_IF_ASSERT_DISABLED(spec_ostype); } if (arch_spec.GetMachine() == llvm::Triple::mips || diff --git a/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp b/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp index 9c798cb1cc8f2..52c991e3220d5 100644 --- a/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp +++ b/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp @@ -330,7 +330,7 @@ NativeProcessLinux::Manager::GetSupportedExtensions() const { NativeProcessLinux::Extension supported = Extension::multiprocess | Extension::fork | Extension::vfork | Extension::pass_signals | Extension::auxv | Extension::libraries_svr4 | - Extension::siginfo_read; + Extension::siginfo_read | Extension::gpu_plugins; #ifdef __aarch64__ // At this point we do not have a process so read auxv directly. diff --git a/lldb/source/Plugins/Process/gdb-remote/CMakeLists.txt b/lldb/source/Plugins/Process/gdb-remote/CMakeLists.txt index 5c39384fa31b9..2220aaa0d806f 100644 --- a/lldb/source/Plugins/Process/gdb-remote/CMakeLists.txt +++ b/lldb/source/Plugins/Process/gdb-remote/CMakeLists.txt @@ -29,6 +29,7 @@ add_lldb_library(lldbPluginProcessGDBRemote PLUGIN GDBRemoteCommunicationServerPlatform.cpp GDBRemoteRegisterContext.cpp GDBRemoteRegisterFallback.cpp + LLDBServerPlugin.cpp ProcessGDBRemote.cpp ProcessGDBRemoteLog.cpp ThreadGDBRemote.cpp diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteClientBase.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteClientBase.cpp index 406fa06ea011a..81d498ca238b9 100644 --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteClientBase.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteClientBase.cpp @@ -32,7 +32,7 @@ static const seconds kWakeupInterval(5); GDBRemoteClientBase::ContinueDelegate::~ContinueDelegate() = default; GDBRemoteClientBase::GDBRemoteClientBase(const char *comm_name) - : GDBRemoteCommunication(), Broadcaster(nullptr, comm_name), + : GDBRemoteCommunication(comm_name), Broadcaster(nullptr, comm_name), m_async_count(0), m_is_running(false), m_should_stop(false) {} StateType GDBRemoteClientBase::SendContinuePacketAndWaitForResponse( diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunication.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunication.cpp index d7e4b2b9546b2..78337af7227d1 100644 --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunication.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunication.cpp @@ -45,7 +45,7 @@ using namespace lldb_private; using namespace lldb_private::process_gdb_remote; // GDBRemoteCommunication constructor -GDBRemoteCommunication::GDBRemoteCommunication() +GDBRemoteCommunication::GDBRemoteCommunication(llvm::StringRef name) : Communication(), #ifdef LLDB_CONFIGURATION_DEBUG m_packet_timeout(1000), @@ -54,7 +54,7 @@ GDBRemoteCommunication::GDBRemoteCommunication() #endif m_echo_number(0), m_supports_qEcho(eLazyBoolCalculate), m_history(512), m_send_acks(true), m_is_platform(false), - m_compression_type(CompressionType::None) { + m_compression_type(CompressionType::None), m_listen_url(), m_name(name) { } // Destructor @@ -83,7 +83,8 @@ size_t GDBRemoteCommunication::SendAck() { ConnectionStatus status = eConnectionStatusSuccess; char ch = '+'; const size_t bytes_written = WriteAll(&ch, 1, status, nullptr); - LLDB_LOGF(log, "<%4" PRIu64 "> send packet: %c", (uint64_t)bytes_written, ch); + LLDB_LOGF(log, "%s <%4" PRIu64 "> send packet: %c", m_name.c_str(), + (uint64_t)bytes_written, ch); m_history.AddPacket(ch, GDBRemotePacket::ePacketTypeSend, bytes_written); return bytes_written; } @@ -93,7 +94,8 @@ size_t GDBRemoteCommunication::SendNack() { ConnectionStatus status = eConnectionStatusSuccess; char ch = '-'; const size_t bytes_written = WriteAll(&ch, 1, status, nullptr); - LLDB_LOGF(log, "<%4" PRIu64 "> send packet: %c", (uint64_t)bytes_written, ch); + LLDB_LOGF(log, "%s <%4" PRIu64 "> send packet: %c", m_name.c_str(), + (uint64_t)bytes_written, ch); m_history.AddPacket(ch, GDBRemotePacket::ePacketTypeSend, bytes_written); return bytes_written; } @@ -164,8 +166,9 @@ GDBRemoteCommunication::SendRawPacketNoLock(llvm::StringRef packet, if (binary_start_offset) { StreamString strm; // Print non binary data header - strm.Printf("<%4" PRIu64 "> send packet: %.*s", (uint64_t)bytes_written, - (int)binary_start_offset, packet_data); + strm.Printf("%s <%4" PRIu64 "> send packet: %.*s", m_name.c_str(), + (uint64_t)bytes_written, (int)binary_start_offset, + packet_data); const uint8_t *p; // Print binary data exactly as sent for (p = (const uint8_t *)packet_data + binary_start_offset; *p != '#'; @@ -175,7 +178,7 @@ GDBRemoteCommunication::SendRawPacketNoLock(llvm::StringRef packet, strm.Printf("%*s", (int)3, p); log->PutString(strm.GetString()); } else - LLDB_LOGF(log, "<%4" PRIu64 "> send packet: %.*s", + LLDB_LOGF(log, "%s <%4" PRIu64 "> send packet: %.*s", m_name.c_str(), (uint64_t)bytes_written, (int)packet_length, packet_data); } @@ -736,11 +739,11 @@ GDBRemoteCommunication::CheckForPacket(const uint8_t *src, size_t src_len, StreamString strm; // Packet header... if (CompressionIsEnabled()) - strm.Printf("<%4" PRIu64 ":%" PRIu64 "> read packet: %c", - (uint64_t)original_packet_size, (uint64_t)total_length, - m_bytes[0]); + strm.Printf("%s <%4" PRIu64 ":%" PRIu64 "> read packet: %c", + m_name.c_str(), (uint64_t)original_packet_size, + (uint64_t)total_length, m_bytes[0]); else - strm.Printf("<%4" PRIu64 "> read packet: %c", + strm.Printf("%s <%4" PRIu64 "> read packet: %c", m_name.c_str(), (uint64_t)total_length, m_bytes[0]); for (size_t i = content_start; i < content_end; ++i) { // Remove binary escaped bytes when displaying the packet... @@ -760,13 +763,14 @@ GDBRemoteCommunication::CheckForPacket(const uint8_t *src, size_t src_len, log->PutString(strm.GetString()); } else { if (CompressionIsEnabled()) - LLDB_LOGF(log, "<%4" PRIu64 ":%" PRIu64 "> read packet: %.*s", - (uint64_t)original_packet_size, (uint64_t)total_length, - (int)(total_length), m_bytes.c_str()); - else - LLDB_LOGF(log, "<%4" PRIu64 "> read packet: %.*s", + LLDB_LOGF(log, "%s <%4" PRIu64 ":%" PRIu64 "> read packet: %.*s", + m_name.c_str(), (uint64_t)original_packet_size, (uint64_t)total_length, (int)(total_length), m_bytes.c_str()); + else + LLDB_LOGF(log, "%s <%4" PRIu64 "> read packet: %.*s", + m_name.c_str(), (uint64_t)total_length, + (int)(total_length), m_bytes.c_str()); } } diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunication.h b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunication.h index 35bf5eb2e3f0d..e4dcf45890020 100644 --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunication.h +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunication.h @@ -12,9 +12,11 @@ #include "GDBRemoteCommunicationHistory.h" #include "lldb/Core/Communication.h" #include "lldb/Host/Config.h" +#include "lldb/Host/HostThread.h" #include "lldb/Host/Socket.h" #include "lldb/Utility/Args.h" #include "lldb/Utility/StringExtractorGDBRemote.h" +#include #include #include @@ -104,7 +106,9 @@ class GDBRemoteCommunication : public Communication { bool m_timeout_modified; }; - GDBRemoteCommunication(); + /// \param[in] name + /// The name of the communication channel. + GDBRemoteCommunication(llvm::StringRef name); ~GDBRemoteCommunication() override; @@ -190,7 +194,15 @@ class GDBRemoteCommunication : public Communication { bool DecompressPacket(); private: -#if HAVE_LIBCOMPRESSION + // Promise used to grab the port number from listening thread + std::promise m_port_promise; + + HostThread m_listen_thread; + std::string m_listen_url; + + std::string m_name; + +#if defined(HAVE_LIBCOMPRESSION) CompressionType m_decompression_scratch_type = CompressionType::None; void *m_decompression_scratch = nullptr; #endif diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp index 7d2bd452acca9..38623157a24cf 100644 --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp @@ -15,6 +15,7 @@ #include #include +#include "lldb/Core/Debugger.h" #include "lldb/Core/ModuleSpec.h" #include "lldb/Host/HostInfo.h" #include "lldb/Host/SafeMachO.h" @@ -34,6 +35,7 @@ #include "ProcessGDBRemote.h" #include "ProcessGDBRemoteLog.h" #include "lldb/Host/Config.h" +#include "lldb/Utility/GPUGDBRemotePackets.h" #include "lldb/Utility/StringExtractorGDBRemote.h" #include "llvm/ADT/STLExtras.h" @@ -365,7 +367,7 @@ void GDBRemoteCommunicationClient::GetRemoteQSupported() { m_x_packet_state.reset(); m_supports_reverse_continue = eLazyBoolNo; m_supports_reverse_step = eLazyBoolNo; - + m_supports_gpu_plugins = eLazyBoolNo; m_max_packet_size = UINT64_MAX; // It's supposed to always be there, but if // not, we assume no limit @@ -420,10 +422,15 @@ void GDBRemoteCommunicationClient::GetRemoteQSupported() { m_uses_native_signals = eLazyBoolYes; else if (x == "binary-upload+") m_x_packet_state = xPacketState::Prefixed; + else if (x == "gpu-plugins+") + m_supports_gpu_plugins = eLazyBoolYes; + else if (x == "gpu-dyld+") + m_supports_gdb_remote_gpu_dyld = eLazyBoolYes; else if (x == "ReverseContinue+") m_supports_reverse_continue = eLazyBoolYes; else if (x == "ReverseStep+") m_supports_reverse_step = eLazyBoolYes; + // Look for a list of compressions in the features list e.g. // qXfer:features:read+;PacketSize=20000;qEcho+;SupportedCompressions=zlib- // deflate,lzma @@ -599,6 +606,109 @@ StructuredData::ObjectSP GDBRemoteCommunicationClient::GetThreadsInfo() { return object_sp; } +std::optional> +GDBRemoteCommunicationClient::GetGPUInitializeActions() { + // Get JSON information containing any breakpoints and other information + // required by any GPU plug-ins using the "jGPUPluginInitialize" packet. + // + // GPU plug-ins might require breakpoints to be set in the native program + // that controls the GPUs. This packet allows the GPU plug-ins to set one or + // more breakpoints in the native program and get a callback when those + // breakpoints get hit. + + if (m_supports_gpu_plugins == eLazyBoolYes) { + StringExtractorGDBRemote response; + response.SetResponseValidatorToJSON(); + if (SendPacketAndWaitForResponse("jGPUPluginInitialize", response) == + PacketResult::Success) { + if (response.IsUnsupportedResponse()) { + m_supports_gpu_plugins = eLazyBoolNo; + return std::nullopt; + } + if (response.IsErrorResponse()) { + Debugger::ReportError(response.GetStatus().AsCString()); + return std::nullopt; + } + if (llvm::Expected> info = + llvm::json::parse>(response.Peek(), + "GPUActions")) { + return std::move(*info); + } else { + // We don't show JSON parsing errors to the user because they won't + // make sense to them. + llvm::consumeError(info.takeError()); + Debugger::ReportError( + llvm::formatv("malformed jGPUPluginInitialize response packet. {0}", + response.GetStringRef())); + } + } + } + + return std::nullopt; +} + +std::optional +GDBRemoteCommunicationClient::GPUBreakpointHit( + const GPUPluginBreakpointHitArgs &args) { + StreamGDBRemote packet; + packet.PutCString("jGPUPluginBreakpointHit:"); + packet.PutAsJSON(args, /*hex_ascii=*/false); + StringExtractorGDBRemote response; + if (SendPacketAndWaitForResponse(packet.GetString(), response) == + PacketResult::Success) { + if (response.IsErrorResponse()) { + Debugger::ReportError(response.GetStatus().AsCString()); + return std::nullopt; + } + if (llvm::Expected info = + llvm::json::parse( + response.Peek(), "GPUPluginBreakpointHitResponse")) { + return *info; + } else { + // We don't show JSON parsing errors to the user because they won't + // make sense to them. + llvm::consumeError(info.takeError()); + Debugger::ReportError(llvm::formatv("malformed jGPUPluginBreakpointHit " + "response packet. {0}", + response.GetStringRef())); + } + } + + return std::nullopt; +} + +std::optional +GDBRemoteCommunicationClient::GetGPUDynamicLoaderLibraryInfos( + const GPUDynamicLoaderArgs &args) { + StreamGDBRemote packet; + packet.PutCString("jGPUPluginGetDynamicLoaderLibraryInfo:"); + packet.PutAsJSON(args, /*hex_ascii=*/false); + StringExtractorGDBRemote response; + if (SendPacketAndWaitForResponse(packet.GetString(), response) == + PacketResult::Success) { + if (response.IsErrorResponse()) { + Debugger::ReportError(response.GetStatus().AsCString()); + return std::nullopt; + } + + if (llvm::Expected info = + llvm::json::parse( + response.Peek(), "GPUDynamicLoaderResponse")) { + return *info; + } else { + // We don't show JSON parsing errors to the user because they won't + // make sense to them. + llvm::consumeError(info.takeError()); + Debugger::ReportError( + llvm::formatv("malformed jGPUPluginGetDynamicLoaderLibraryInfo " + "response packet. {0}", + response.GetStringRef())); + } + } + + return std::nullopt; +} + bool GDBRemoteCommunicationClient::GetThreadExtendedInfoSupported() { if (m_supports_jThreadExtendedInfo == eLazyBoolCalculate) { StringExtractorGDBRemote response; @@ -1232,8 +1342,8 @@ bool GDBRemoteCommunicationClient::GetHostInfo(bool force) { Log *log = GetLog(GDBRLog::Process); if (force || m_qHostInfo_is_valid == eLazyBoolCalculate) { - // host info computation can require DNS traffic and shelling out to external processes. - // Increase the timeout to account for that. + // host info computation can require DNS traffic and shelling out to + // external processes. Increase the timeout to account for that. ScopedTimeout timeout(*this, seconds(10)); m_qHostInfo_is_valid = eLazyBoolNo; StringExtractorGDBRemote response; @@ -2255,7 +2365,7 @@ bool GDBRemoteCommunicationClient::GetCurrentProcessInfo(bool allow_lazy) { !vendor_name.empty()) { llvm::Triple triple(llvm::Twine("-") + vendor_name + "-" + os_name); if (!environment.empty()) - triple.setEnvironmentName(environment); + triple.setEnvironmentName(environment); assert(triple.getObjectFormat() != llvm::Triple::UnknownObjectFormat); assert(triple.getObjectFormat() != llvm::Triple::Wasm); @@ -2290,7 +2400,8 @@ bool GDBRemoteCommunicationClient::GetCurrentProcessInfo(bool allow_lazy) { } m_process_arch.GetTriple().setVendorName(llvm::StringRef(vendor_name)); m_process_arch.GetTriple().setOSName(llvm::StringRef(os_name)); - m_process_arch.GetTriple().setEnvironmentName(llvm::StringRef(environment)); + m_process_arch.GetTriple().setEnvironmentName( + llvm::StringRef(environment)); } return true; } @@ -2918,7 +3029,7 @@ GDBRemoteCommunicationClient::GetCurrentProcessAndThreadIDs( ids.push_back(*pid_tid); ch = response.GetChar(); // Skip the command separator - } while (ch == ','); // Make sure we got a comma separator + } while (ch == ','); // Make sure we got a comma separator } } @@ -4373,3 +4484,5 @@ llvm::Expected GDBRemoteCommunicationClient::KillProcess(lldb::pid_t pid) { "unexpected response to k packet: %s", response.GetStringRef().str().c_str()); } + +void GDBRemoteCommunicationClient::SetFilePassingFD(int fd) {} diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h index a765e95bf9814..e776376195234 100644 --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h @@ -22,6 +22,7 @@ #include "lldb/Utility/AddressableBits.h" #include "lldb/Utility/ArchSpec.h" #include "lldb/Utility/GDBRemote.h" +#include "lldb/Utility/GPUGDBRemotePackets.h" #include "lldb/Utility/ProcessInfo.h" #include "lldb/Utility/StructuredData.h" #include "lldb/Utility/TraceGDBRemotePackets.h" @@ -405,10 +406,10 @@ class GDBRemoteCommunicationClient : public GDBRemoteClientBase { llvm::ErrorOr CalculateMD5(const FileSpec &file_spec); - lldb::DataBufferSP ReadRegister( - lldb::tid_t tid, - uint32_t - reg_num); // Must be the eRegisterKindProcessPlugin register number + lldb::DataBufferSP + ReadRegister(lldb::tid_t tid, + uint32_t reg_num); // Must be the eRegisterKindProcessPlugin + // register number lldb::DataBufferSP ReadAllRegisters(lldb::tid_t tid); @@ -433,6 +434,14 @@ class GDBRemoteCommunicationClient : public GDBRemoteClientBase { StructuredData::ObjectSP GetThreadsInfo(); + std::optional> GetGPUInitializeActions(); + + std::optional + GPUBreakpointHit(const GPUPluginBreakpointHitArgs &args); + + std::optional + GetGPUDynamicLoaderLibraryInfos(const GPUDynamicLoaderArgs &args); + bool GetThreadExtendedInfoSupported(); bool GetLoadedDynamicLibrariesInfosSupported(); @@ -530,6 +539,17 @@ class GDBRemoteCommunicationClient : public GDBRemoteClientBase { llvm::Expected KillProcess(lldb::pid_t pid); + // If this function is called with a valid file descriptor, a thread will be + // started that will listen for messages on fd from the socket pair. This + // allows us to pass file descriptors from the lldb-server to this client to + // allow another GDB remote connection to be started. This is used for GPU + // debugging on a local machine. + void SetFilePassingFD(int fd); + + bool SupportsGPUDynamicLoader() const { + return m_supports_gdb_remote_gpu_dyld == eLazyBoolYes; + } + protected: LazyBool m_supports_not_sending_acks = eLazyBoolCalculate; LazyBool m_supports_thread_suffix = eLazyBoolCalculate; @@ -570,11 +590,12 @@ class GDBRemoteCommunicationClient : public GDBRemoteClientBase { LazyBool m_supports_multiprocess = eLazyBoolCalculate; LazyBool m_supports_memory_tagging = eLazyBoolCalculate; LazyBool m_supports_qSaveCore = eLazyBoolCalculate; + LazyBool m_supports_gdb_remote_gpu_dyld = eLazyBoolCalculate; LazyBool m_uses_native_signals = eLazyBoolCalculate; std::optional m_x_packet_state; LazyBool m_supports_reverse_continue = eLazyBoolCalculate; LazyBool m_supports_reverse_step = eLazyBoolCalculate; - + LazyBool m_supports_gpu_plugins = eLazyBoolCalculate; bool m_supports_qProcessInfoPID : 1, m_supports_qfProcessInfo : 1, m_supports_qUserName : 1, m_supports_qGroupName : 1, m_supports_qThreadStopInfo : 1, m_supports_z0 : 1, m_supports_z1 : 1, @@ -583,8 +604,7 @@ class GDBRemoteCommunicationClient : public GDBRemoteClientBase { m_supports_qSymbol : 1, m_qSymbol_requests_done : 1, m_supports_qModuleInfo : 1, m_supports_jThreadsInfo : 1, m_supports_jModulesInfo : 1, m_supports_vFileSize : 1, - m_supports_vFileMode : 1, m_supports_vFileExists : 1, - m_supports_vRun : 1; + m_supports_vFileMode : 1, m_supports_vFileExists : 1, m_supports_vRun : 1; /// Current gdb remote protocol process identifier for all other operations lldb::pid_t m_curr_pid = LLDB_INVALID_PROCESS_ID; @@ -619,8 +639,8 @@ class GDBRemoteCommunicationClient : public GDBRemoteClientBase { UINT32_MAX; // from reply to qGDBServerVersion, zero if // qGDBServerVersion is not supported std::chrono::seconds m_default_packet_timeout; - int m_target_vm_page_size = 0; // target system VM page size; 0 unspecified - uint64_t m_max_packet_size = 0; // as returned by qSupported + int m_target_vm_page_size = 0; // target system VM page size; 0 unspecified + uint64_t m_max_packet_size = 0; // as returned by qSupported std::string m_qSupported_response; // the complete response to qSupported bool m_supported_async_json_packets_is_valid = false; @@ -628,7 +648,8 @@ class GDBRemoteCommunicationClient : public GDBRemoteClientBase { std::vector m_qXfer_memory_map; bool m_qXfer_memory_map_loaded = false; - + // Used to pass a file descriptor from the GDB server back to the client. + std::optional m_file_passing_fd; bool GetCurrentProcessInfo(bool allow_lazy_pid = true); bool GetGDBServerVersion(); diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServer.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServer.cpp index 5bd29ae40aa9e..4c0c1e9348b6d 100644 --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServer.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServer.cpp @@ -24,8 +24,8 @@ using namespace lldb_private; using namespace lldb_private::process_gdb_remote; using namespace llvm; -GDBRemoteCommunicationServer::GDBRemoteCommunicationServer() - : GDBRemoteCommunication(), m_exit_now(false) { +GDBRemoteCommunicationServer::GDBRemoteCommunicationServer(llvm::StringRef name) + : GDBRemoteCommunication(name), m_exit_now(false) { RegisterPacketHandler( StringExtractorGDBRemote::eServerPacketType_QEnableErrorStrings, [this](StringExtractorGDBRemote packet, Status &error, bool &interrupt, diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServer.h b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServer.h index ccbcd0ac5bf58..b40800ed7c70e 100644 --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServer.h +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServer.h @@ -31,7 +31,9 @@ class GDBRemoteCommunicationServer : public GDBRemoteCommunication { std::function; - GDBRemoteCommunicationServer(); + /// \param[in] name + /// The name of the communication channel. + GDBRemoteCommunicationServer(llvm::StringRef name); ~GDBRemoteCommunicationServer() override; diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerCommon.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerCommon.cpp index 4a1117222f34c..ca2011aa641b0 100644 --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerCommon.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerCommon.cpp @@ -58,8 +58,9 @@ const static uint32_t g_default_packet_timeout_sec = 0; // not specified #endif // GDBRemoteCommunicationServerCommon constructor -GDBRemoteCommunicationServerCommon::GDBRemoteCommunicationServerCommon() - : GDBRemoteCommunicationServer(), m_process_launch_info(), +GDBRemoteCommunicationServerCommon::GDBRemoteCommunicationServerCommon( + llvm::StringRef name) + : GDBRemoteCommunicationServer(name), m_process_launch_info(), m_process_launch_error(), m_proc_infos(), m_proc_infos_index(0) { RegisterMemberFunctionHandler(StringExtractorGDBRemote::eServerPacketType_A, &GDBRemoteCommunicationServerCommon::Handle_A); diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerCommon.h b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerCommon.h index b4f1eb3e61c41..36a39efcaef60 100644 --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerCommon.h +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerCommon.h @@ -25,7 +25,9 @@ class ProcessGDBRemote; class GDBRemoteCommunicationServerCommon : public GDBRemoteCommunicationServer { public: - GDBRemoteCommunicationServerCommon(); + /// \param[in] name + /// The name of the communication channel. + GDBRemoteCommunicationServerCommon(llvm::StringRef name); ~GDBRemoteCommunicationServerCommon() override; diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp index 89d2730cfccd0..f5fea122b27fd 100644 --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp @@ -17,6 +17,7 @@ #include #include "GDBRemoteCommunicationServerLLGS.h" +#include "LLDBServerPlugin.h" #include "lldb/Host/ConnectionFileDescriptor.h" #include "lldb/Host/Debug.h" #include "lldb/Host/File.h" @@ -34,6 +35,7 @@ #include "lldb/Utility/DataBuffer.h" #include "lldb/Utility/Endian.h" #include "lldb/Utility/GDBRemote.h" +#include "lldb/Utility/GPUGDBRemotePackets.h" #include "lldb/Utility/LLDBAssert.h" #include "lldb/Utility/LLDBLog.h" #include "lldb/Utility/Log.h" @@ -41,6 +43,7 @@ #include "lldb/Utility/StreamString.h" #include "lldb/Utility/UnimplementedError.h" #include "lldb/Utility/UriParser.h" +#include "llvm/Support/DynamicLibrary.h" #include "llvm/Support/JSON.h" #include "llvm/Support/ScopedPrinter.h" #include "llvm/TargetParser/Triple.h" @@ -54,6 +57,7 @@ using namespace lldb_private; using namespace lldb_private::process_gdb_remote; using namespace llvm; +sys::DynamicLibrary g_gpu_plugin; // GDBRemote Errors namespace { @@ -68,8 +72,9 @@ enum GDBRemoteServerError { // GDBRemoteCommunicationServerLLGS constructor GDBRemoteCommunicationServerLLGS::GDBRemoteCommunicationServerLLGS( - MainLoop &mainloop, NativeProcessProtocol::Manager &process_manager) - : GDBRemoteCommunicationServerCommon(), m_mainloop(mainloop), + MainLoop &mainloop, NativeProcessProtocol::Manager &process_manager, + llvm::StringRef name) + : GDBRemoteCommunicationServerCommon(name), m_mainloop(mainloop), m_process_manager(process_manager), m_current_process(nullptr), m_continue_process(nullptr), m_stdio_communication() { RegisterPacketHandlers(); @@ -251,9 +256,19 @@ void GDBRemoteCommunicationServerLLGS::RegisterPacketHandlers() { RegisterMemberFunctionHandler( StringExtractorGDBRemote::eServerPacketType_vCtrlC, &GDBRemoteCommunicationServerLLGS::Handle_vCtrlC); -} + RegisterMemberFunctionHandler( + StringExtractorGDBRemote::eServerPacketType_jGPUPluginInitialize, + &GDBRemoteCommunicationServerLLGS::Handle_jGPUPluginInitialize); + RegisterMemberFunctionHandler( + StringExtractorGDBRemote::eServerPacketType_jGPUPluginBreakpointHit, + &GDBRemoteCommunicationServerLLGS::Handle_jGPUPluginBreakpointHit); + RegisterMemberFunctionHandler( + StringExtractorGDBRemote::eServerPacketType_jGPUPluginGetDynamicLoaderLibraryInfo, + &GDBRemoteCommunicationServerLLGS::Handle_jGPUPluginGetDynamicLoaderLibraryInfo); + } -void GDBRemoteCommunicationServerLLGS::SetLaunchInfo(const ProcessLaunchInfo &info) { +void GDBRemoteCommunicationServerLLGS::SetLaunchInfo( + const ProcessLaunchInfo &info) { m_process_launch_info = info; } @@ -614,10 +629,11 @@ static void CollectRegNums(const uint32_t *reg_num, StreamString &response, } } -static void WriteRegisterValueInHexFixedWidth( - StreamString &response, NativeRegisterContext ®_ctx, - const RegisterInfo ®_info, const RegisterValue *reg_value_p, - lldb::ByteOrder byte_order) { +static void WriteRegisterValueInHexFixedWidth(StreamString &response, + NativeRegisterContext ®_ctx, + const RegisterInfo ®_info, + const RegisterValue *reg_value_p, + lldb::ByteOrder byte_order) { RegisterValue reg_value; if (!reg_value_p) { Status error = reg_ctx.ReadRegister(®_info, reg_value); @@ -643,7 +659,7 @@ static std::optional GetRegistersAsJSON(NativeThreadProtocol &thread) { Log *log = GetLog(LLDBLog::Thread); - NativeRegisterContext& reg_ctx = thread.GetRegisterContext(); + NativeRegisterContext ®_ctx = thread.GetRegisterContext(); json::Object register_object; @@ -682,8 +698,8 @@ GetRegistersAsJSON(NativeThreadProtocol &thread) { } StreamString stream; - WriteRegisterValueInHexFixedWidth(stream, reg_ctx, *reg_info_p, - ®_value, lldb::eByteOrderBig); + WriteRegisterValueInHexFixedWidth(stream, reg_ctx, *reg_info_p, ®_value, + lldb::eByteOrderBig); register_object.try_emplace(llvm::to_string(reg_num), stream.GetString().str()); @@ -716,6 +732,8 @@ static const char *GetStopReasonString(StopReason stop_reason) { return "vforkdone"; case eStopReasonInterrupt: return "async interrupt"; + case eStopReasonDynammicLoader: + return "dyld"; case eStopReasonHistoryBoundary: case eStopReasonInstrumentation: case eStopReasonInvalid: @@ -794,8 +812,7 @@ GetJSONThreadsInfo(NativeProcessProtocol &process, bool abridged) { return threads_array; } -StreamString -GDBRemoteCommunicationServerLLGS::PrepareStopReplyPacketForThread( +StreamString GDBRemoteCommunicationServerLLGS::PrepareStopReplyPacketForThread( NativeThreadProtocol &thread) { Log *log = GetLog(LLDBLog::Process | LLDBLog::Thread); @@ -999,7 +1016,8 @@ GDBRemoteCommunicationServerLLGS::PrepareStopReplyPacketForThread( GDBRemoteCommunication::PacketResult GDBRemoteCommunicationServerLLGS::SendStopReplyPacketForThread( - NativeProcessProtocol &process, lldb::tid_t tid, bool force_synchronous) { + NativeProcessProtocol &process, lldb::tid_t tid, bool force_synchronous, + llvm::StringRef extra_stop_reply_args) { // Ensure we can get info on the given thread. NativeThreadProtocol *thread = process.GetThreadByID(tid); if (!thread) @@ -1009,6 +1027,9 @@ GDBRemoteCommunicationServerLLGS::SendStopReplyPacketForThread( if (response.Empty()) return SendErrorResponse(42); + if (!extra_stop_reply_args.empty()) + response.PutCString(extra_stop_reply_args); + if (m_non_stop && !force_synchronous) { PacketResult ret = SendNotificationPacketNoLock( "Stop", m_stop_notification_queue, response.GetString()); @@ -1083,8 +1104,20 @@ void GDBRemoteCommunicationServerLLGS::HandleInferiorState_Stopped( Log *log = GetLog(LLDBLog::Process); LLDB_LOGF(log, "GDBRemoteCommunicationServerLLGS::%s called", __FUNCTION__); + // Check if any plug-ins have new connections + StreamGDBRemote extra_stop_reply_args; + for (auto &plugin_up : m_plugins) { + if (std::optional connection_info = + plugin_up->NativeProcessIsStopping()) { + extra_stop_reply_args.PutCString("gpu-actions:"); + extra_stop_reply_args.PutAsJSON(*connection_info, /*hex_ascii=*/true); + extra_stop_reply_args.PutChar(';'); + } + } + PacketResult result = SendStopReasonForState( - *process, StateType::eStateStopped, /*force_synchronous=*/false); + *process, StateType::eStateStopped, /*force_synchronous=*/false, + extra_stop_reply_args.GetData()); if (result != PacketResult::Success) { LLDB_LOGF(log, "GDBRemoteCommunicationServerLLGS::%s failed to send stop " @@ -1104,6 +1137,7 @@ void GDBRemoteCommunicationServerLLGS::ProcessStateChanged( __FUNCTION__, process->GetID(), StateAsCString(state)); } + switch (state) { case StateType::eStateRunning: break; @@ -1379,21 +1413,30 @@ GDBRemoteCommunicationServerLLGS::Handle_jLLDBTraceGetBinaryData( return SendErrorResponse(bytes.takeError()); } +void PrintSomething() { puts(__PRETTY_FUNCTION__); } + +typedef void (*LLDBServerPluginInitialize)(); + +template static T FuncPtr(void *Ptr) { + union { + T F; + void *P; + } Tmp; + Tmp.P = Ptr; + return Tmp.F; +} + GDBRemoteCommunication::PacketResult GDBRemoteCommunicationServerLLGS::Handle_qProcessInfo( StringExtractorGDBRemote &packet) { // Fail if we don't have a current process. + if (!m_current_process || (m_current_process->GetID() == LLDB_INVALID_PROCESS_ID)) return SendErrorResponse(68); - lldb::pid_t pid = m_current_process->GetID(); - - if (pid == LLDB_INVALID_PROCESS_ID) - return SendErrorResponse(1); - ProcessInstanceInfo proc_info; - if (!Host::GetProcessInfo(pid, proc_info)) + if (!m_current_process->GetProcessInfo(proc_info)) return SendErrorResponse(1); StreamString response; @@ -1887,7 +1930,6 @@ GDBRemoteCommunication::PacketResult GDBRemoteCommunicationServerLLGS::Handle_stop_reason( StringExtractorGDBRemote &packet) { // Handle the $? gdbremote command. - if (m_non_stop) { // Clear the notification queue first, except for pending exit // notifications. @@ -1929,7 +1971,7 @@ GDBRemoteCommunicationServerLLGS::Handle_stop_reason( GDBRemoteCommunication::PacketResult GDBRemoteCommunicationServerLLGS::SendStopReasonForState( NativeProcessProtocol &process, lldb::StateType process_state, - bool force_synchronous) { + bool force_synchronous, llvm::StringRef extra_stop_reply_args) { Log *log = GetLog(LLDBLog::Process); if (m_disabling_non_stop) { @@ -1963,7 +2005,8 @@ GDBRemoteCommunicationServerLLGS::SendStopReasonForState( // Make sure we set the current thread so g and p packets return the data // the gdb will expect. SetCurrentThreadID(tid); - return SendStopReplyPacketForThread(process, tid, force_synchronous); + return SendStopReplyPacketForThread(process, tid, force_synchronous, + extra_stop_reply_args); } case eStateInvalid: @@ -3104,8 +3147,7 @@ GDBRemoteCommunicationServerLLGS::BuildTargetXml() { } response.Indent(); - response.Printf("name, reg_info->byte_size * 8, reg_index); if (!reg_context.RegisterOffsetIsDynamic()) @@ -3316,7 +3358,7 @@ GDBRemoteCommunicationServerLLGS::Handle_QSaveRegisterState( } // Grab the register context for the thread. - NativeRegisterContext& reg_context = thread->GetRegisterContext(); + NativeRegisterContext ®_context = thread->GetRegisterContext(); // Save registers to a buffer. WritableDataBufferSP register_data_sp; @@ -3577,8 +3619,7 @@ GDBRemoteCommunicationServerLLGS::Handle_D(StringExtractorGDBRemote &packet) { for (auto it = m_debugged_processes.begin(); it != m_debugged_processes.end();) { if (pid == LLDB_INVALID_PROCESS_ID || pid == it->first) { - LLDB_LOGF(log, - "GDBRemoteCommunicationServerLLGS::%s detaching %" PRId64, + LLDB_LOGF(log, "GDBRemoteCommunicationServerLLGS::%s detaching %" PRId64, __FUNCTION__, it->first); if (llvm::Error e = it->second.process_up->Detach().ToError()) detach_error = llvm::joinErrors(std::move(detach_error), std::move(e)); @@ -3622,7 +3663,8 @@ GDBRemoteCommunicationServerLLGS::Handle_qThreadStopInfo( return SendErrorResponse(0x15); } return SendStopReplyPacketForThread(*m_current_process, tid, - /*force_synchronous=*/true); + /*force_synchronous=*/true, + llvm::StringRef()); } GDBRemoteCommunication::PacketResult @@ -3653,6 +3695,67 @@ GDBRemoteCommunicationServerLLGS::Handle_jThreadsInfo( return SendPacketNoLock(escaped_response.GetString()); } +GDBRemoteCommunication::PacketResult +GDBRemoteCommunicationServerLLGS::Handle_jGPUPluginInitialize( + StringExtractorGDBRemote &) { + std::vector gpu_actions; + for (auto &plugin_up: m_plugins) + gpu_actions.push_back(plugin_up->GetInitializeActions()); + StreamGDBRemote response; + response.PutAsJSONArray(gpu_actions); + return SendPacketNoLock(response.GetString()); +} + +GDBRemoteCommunication::PacketResult +GDBRemoteCommunicationServerLLGS::Handle_jGPUPluginBreakpointHit( + StringExtractorGDBRemote &packet) { + + packet.ConsumeFront("jGPUPluginBreakpointHit:"); + Expected args = + json::parse(packet.Peek(), + "GPUPluginBreakpointHitArgs"); + if (!args) + return SendErrorResponse(args.takeError()); + + for (auto &plugin_up: m_plugins) { + if (plugin_up->GetPluginName() == args->plugin_name) { + Expected bp_response = + plugin_up->BreakpointWasHit(*args); + if (!bp_response) + return SendErrorResponse(bp_response.takeError()); + + StreamGDBRemote response; + response.PutAsJSON(*bp_response, /*hex_ascii=*/false); + return SendPacketNoLock(response.GetString()); + } + } + return SendErrorResponse(Status::FromErrorString("Invalid plugin name.")); +} + + +GDBRemoteCommunication::PacketResult +GDBRemoteCommunicationServerLLGS::Handle_jGPUPluginGetDynamicLoaderLibraryInfo( + StringExtractorGDBRemote &packet) { + + packet.ConsumeFront("jGPUPluginGetDynamicLoaderLibraryInfo:"); + Expected args = + json::parse(packet.Peek(), "GPUDynamicLoaderArgs"); + if (!args) + return SendErrorResponse(args.takeError()); + + + if (!m_current_process) + return SendErrorResponse(Status::FromErrorString("invalid process")); + std::optional libraries_response = + m_current_process->GetGPUDynamicLoaderLibraryInfos(*args); + if (!libraries_response) + return SendErrorResponse(Status::FromErrorString( + "jGPUPluginGetDynamicLoaderLibraryInfo not supported")); + StreamGDBRemote response; + response.PutAsJSON(*libraries_response, /*hex_ascii=*/false); + return SendPacketNoLock(response.GetString()); +} + GDBRemoteCommunication::PacketResult GDBRemoteCommunicationServerLLGS::Handle_qWatchpointSupportInfo( StringExtractorGDBRemote &packet) { @@ -3727,7 +3830,7 @@ GDBRemoteCommunicationServerLLGS::Handle_QPassSignals( break; // End of string if (separator != ';') return SendIllFormedResponse(packet, "Invalid separator," - " expected semicolon."); + " expected semicolon."); } // Fail if we don't have a current process. @@ -4220,7 +4323,7 @@ std::vector GDBRemoteCommunicationServerLLGS::HandleFeatures( "QThreadSuffixSupported+", "QListThreadsInStopReply+", "qXfer:features:read+", - "QNonStop+", + "QNonStop+" }); // report server-only features @@ -4238,6 +4341,10 @@ std::vector GDBRemoteCommunicationServerLLGS::HandleFeatures( ret.push_back("memory-tagging+"); if (bool(plugin_features & Extension::savecore)) ret.push_back("qSaveCore+"); + if (bool(plugin_features & Extension::gpu_plugins)) + ret.push_back("gpu-plugins+"); + if (bool(plugin_features & Extension::gpu_dyld)) + ret.push_back("gpu-dyld+"); // check for client features m_extensions_supported = {}; @@ -4329,3 +4436,8 @@ lldb_private::process_gdb_remote::LLGSArgToURL(llvm::StringRef url_arg, return (reverse_connect ? "unix-connect://" : "unix-accept://") + url_arg.str(); } + +void GDBRemoteCommunicationServerLLGS::InstallPlugin( + std::unique_ptr plugin_up) { + m_plugins.emplace_back(std::move(plugin_up)); +} diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h index 646b6a102abf6..4fe653e232ec5 100644 --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h @@ -25,6 +25,10 @@ class StringExtractorGDBRemote; namespace lldb_private { +namespace lldb_server { + class LLDBServerPlugin; +} + namespace process_gdb_remote { class ProcessGDBRemote; @@ -33,10 +37,15 @@ class GDBRemoteCommunicationServerLLGS : public GDBRemoteCommunicationServerCommon, public NativeProcessProtocol::NativeDelegate { public: - // Constructors and Destructors + /// \param[in] mainloop + /// The main loop. + /// \param[in] process_manager + /// The process manager. + /// \param[in] name + /// The name of the communication channel. GDBRemoteCommunicationServerLLGS( - MainLoop &mainloop, - NativeProcessProtocol::Manager &process_manager); + MainLoop &mainloop, NativeProcessProtocol::Manager &process_manager, + llvm::StringRef name); void SetLaunchInfo(const ProcessLaunchInfo &info); @@ -86,6 +95,8 @@ class GDBRemoteCommunicationServerLLGS Status InitializeConnection(std::unique_ptr connection); + void InstallPlugin(std::unique_ptr plugin_up); + struct DebuggedProcess { enum class Flag { vkilled = (1u << 0), @@ -97,6 +108,10 @@ class GDBRemoteCommunicationServerLLGS Flag flags; }; + NativeProcessProtocol *GetCurrentProcess() { + return m_current_process; + } + protected: MainLoop &m_mainloop; MainLoop::ReadHandleUP m_network_handle_up; @@ -124,6 +139,8 @@ class GDBRemoteCommunicationServerLLGS NativeProcessProtocol::Extension m_extensions_supported = {}; + std::vector> m_plugins; + // Typically we would use a SmallVector for this data but in this context we // don't know how much data we're recieving so we would have to heap allocate // a lot, or have a very large stack frame. So it's a member instead. @@ -137,11 +154,13 @@ class GDBRemoteCommunicationServerLLGS PacketResult SendStopReplyPacketForThread(NativeProcessProtocol &process, lldb::tid_t tid, - bool force_synchronous); + bool force_synchronous, + llvm::StringRef extra_stop_reply_args = {}); PacketResult SendStopReasonForState(NativeProcessProtocol &process, lldb::StateType process_state, - bool force_synchronous); + bool force_synchronous, + llvm::StringRef extra_stop_reply_args = {}); void EnqueueStopReplyPackets(lldb::tid_t thread_to_skip); @@ -271,6 +290,13 @@ class GDBRemoteCommunicationServerLLGS PacketResult Handle_T(StringExtractorGDBRemote &packet); + PacketResult Handle_jGPUPluginInitialize(StringExtractorGDBRemote &packet); + + PacketResult Handle_jGPUPluginBreakpointHit(StringExtractorGDBRemote &packet); + + PacketResult Handle_jGPUPluginGetDynamicLoaderLibraryInfo( + StringExtractorGDBRemote &packet); + void SetCurrentThreadID(lldb::tid_t tid); lldb::tid_t GetCurrentThreadID() const; diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerPlatform.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerPlatform.cpp index 5876c3a9434a1..489199ec38df3 100644 --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerPlatform.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerPlatform.cpp @@ -48,7 +48,8 @@ using namespace lldb_private; GDBRemoteCommunicationServerPlatform::GDBRemoteCommunicationServerPlatform( FileSpec debugserver_path, const Socket::SocketProtocol socket_protocol, uint16_t gdbserver_port) - : m_debugserver_path(std::move(debugserver_path)), + : GDBRemoteCommunicationServerCommon("gdb-server-platform"), + m_debugserver_path(std::move(debugserver_path)), m_socket_protocol(socket_protocol), m_gdbserver_port(gdbserver_port) { RegisterMemberFunctionHandler( diff --git a/lldb/source/Plugins/Process/gdb-remote/LLDBServerPlugin.cpp b/lldb/source/Plugins/Process/gdb-remote/LLDBServerPlugin.cpp new file mode 100644 index 0000000000000..439f6b6af249c --- /dev/null +++ b/lldb/source/Plugins/Process/gdb-remote/LLDBServerPlugin.cpp @@ -0,0 +1,41 @@ +//===-- LLDBServerPlugin.cpp ------------------------------------*- C++ -*-===// +// +// 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 "LLDBServerPlugin.h" +#include "GDBRemoteCommunicationServerLLGS.h" +#include +#include + +using namespace lldb_private; +using namespace lldb_server; + + +LLDBServerPlugin::LLDBServerPlugin(GDBServer &native_process, MainLoop &main_loop) : + m_native_process(native_process), m_main_loop(main_loop) {} + +LLDBServerPlugin::~LLDBServerPlugin() {} + + +lldb::StateType +LLDBServerPlugin::HaltNativeProcessIfNeeded(bool &was_halted, + uint32_t timeout_sec) { + using namespace std::chrono; + NativeProcessProtocol *process = m_native_process.GetCurrentProcess(); + if (process->IsRunning()) { + was_halted = true; + process->Halt(); + + auto end_time = steady_clock::now() + seconds(timeout_sec); + while (std::chrono::steady_clock::now() < end_time) { + std::this_thread::sleep_for(milliseconds(250)); + if (process->IsStopped()) + break; + } + } + return process->GetState(); +} diff --git a/lldb/source/Plugins/Process/gdb-remote/LLDBServerPlugin.h b/lldb/source/Plugins/Process/gdb-remote/LLDBServerPlugin.h new file mode 100644 index 0000000000000..a5c98ce26c177 --- /dev/null +++ b/lldb/source/Plugins/Process/gdb-remote/LLDBServerPlugin.h @@ -0,0 +1,138 @@ +//===-- LLDBServerPlugin.h --------------------------------------*- C++ -*-===// +// +// 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_TOOLS_LLDB_SERVER_LLDBSERVERPLUGIN_H +#define LLDB_TOOLS_LLDB_SERVER_LLDBSERVERPLUGIN_H + +#include "lldb/Host/MainLoop.h" +#include "lldb/Host/common/NativeProcessProtocol.h" +#include "lldb/Utility/GPUGDBRemotePackets.h" +#include "lldb/lldb-types.h" +#include "llvm/Support/JSON.h" + +#include +#include +#include +#include +#include + +namespace lldb_private { + +namespace process_gdb_remote { +class GDBRemoteCommunicationServerLLGS; +} // namespace process_gdb_remote + +namespace lldb_server { + +class LLDBServerPlugin { +protected: + // Add a version field to allow the APIs to change over time. + using GDBServer = process_gdb_remote::GDBRemoteCommunicationServerLLGS; + using Manager = NativeProcessProtocol::Manager; + GDBServer &m_native_process; + MainLoop &m_main_loop; + std::unique_ptr m_process_manager_up; + std::unique_ptr m_gdb_server; + bool m_is_listening = false; + bool m_is_connected = false; + +public: + LLDBServerPlugin(GDBServer &native_process, MainLoop &main_loop); + virtual ~LLDBServerPlugin(); + + /// Check if we are already connected. + bool IsConnected() const { return m_is_connected; } + + virtual llvm::StringRef GetPluginName() = 0; + + /// Stop the native process if it is running. + /// + /// Some plug-ins might want to stop the native process if it is running so + /// that the plug-in can return some GPUActions from the call to the + /// NativeProcessIsStopping(). This function will trigger the native process + /// to stop only if it is running. + /// + /// \param[out] was_halted The \a was_halted parameter will be set to + /// true if the process was running and was halted. It will be false if the + /// process was already stopped. + /// + /// \param[out] timeout_sec The timeout in seconds to wait for the process to + /// enter the stopped state. + /// + /// \return The actual state of the process in case the process was not able + /// to be stopped within the specified timeout. + lldb::StateType HaltNativeProcessIfNeeded(bool &was_halted, + uint32_t timeout_sec = 5); + + /// Get notified when the process is stopping. + /// + /// This function will get called each time native process stops as the stop + /// reply packet is being created. If the plug-in is ready to be activated, + /// return a GPUPluginConnectionInfo with a value connection URL to use with + /// "process connect" which can connect to this plug-in. Plug-ins should wait + /// for a connection to be made before trying to do any blocking code. The + /// plug-in should assume the users do not want to use any features unless a + /// connection is made. + virtual std::optional NativeProcessIsStopping() { + return std::nullopt; + }; + + /// Get the GPU plug-in initialization actions. + /// + /// Each GPU plugin can return a structure that describes the GPU plug-in and + /// any actions that should be performed right away .Actions include setting + /// any breakpoints it requires in the native process. GPU plug-ins might want + /// to set breakpoints in the native process to know when the GPU has been + /// initialized, or when the GPU has shared libraries that get loaded. + /// They can do this by populating returning any actions needed when this + /// function is called. + /// + /// The contents of this structure will be converted to JSON and sent to the + /// LLDB client. The structure allows plug-ins to set breakpoints by name + /// and to also request symbol values that should be sent when the breakpoint + /// gets hit. When the breakpoint is hit, the BreakpointWasHit(...) method + /// will get called with a structure that identifies the plugin, + /// breakpoint and it will supply any requested symbol values. + virtual GPUActions GetInitializeActions() = 0; + + /// Get a file descriptor to listen for in the ptrace epoll loop. + /// + /// When polling for process ptrace events, plug-ins can supply extra file + /// descriptors that should be listened to. When a file descriptor has + /// events, the LLDBServerPlugin::HandleFileDescriptorEvent(...) function + /// will get called synchronously from the event loop listening for events. + /// This allows synchronization with the ptrace event loop. + /// + /// \param idx The index of the file descriptor to add. + /// + /// \return A valid file descriptor if \a idx is a valid index, or -1. + virtual int GetEventFileDescriptorAtIndex(size_t idx) { return -1; } + + /// Handle a file descriptor event that was registered with the lldb-server + /// from previous calls to LLDBServerPlugin::GetEventFileDescriptorAtIndex() + /// + /// \param fd The file descriptor event to handle. + virtual bool HandleEventFileDescriptorEvent(int fd) { return false; } + + /// Called when a breakpoint is hit in the native process. + /// + /// LLDBServerPlugin objects can set breakpoints in the native process by + /// calling m_process.SetBreakpoint(...) to help implement funcionality, + /// such as dynamic library loading in GPUs or to synchronize in any other + /// way with the native process. + virtual llvm::Expected + BreakpointWasHit(GPUPluginBreakpointHitArgs &args) = 0; + +protected: + std::mutex m_connect_mutex; +}; + +} // namespace lldb_server +} // namespace lldb_private + +#endif // LLDB_TOOLS_LLDB_SERVER_LLDBSERVERPLUGIN_H diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp index a2c34ddfc252e..b1b3f01a854bc 100644 --- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp @@ -23,6 +23,7 @@ #include #include +#include "lldb/Breakpoint/StoppointCallbackContext.h" #include "lldb/Breakpoint/Watchpoint.h" #include "lldb/Breakpoint/WatchpointAlgorithms.h" #include "lldb/Breakpoint/WatchpointResource.h" @@ -80,12 +81,14 @@ #include "Plugins/Process/Utility/GDBRemoteSignals.h" #include "Plugins/Process/Utility/InferiorCallPOSIX.h" #include "Plugins/Process/Utility/StopInfoMachException.h" +#include "Plugins/DynamicLoader/GDBRemoteGPU/DynamicLoaderGDBRemoteGPU.h" #include "ProcessGDBRemote.h" #include "ProcessGDBRemoteLog.h" #include "ThreadGDBRemote.h" #include "lldb/Host/Host.h" #include "lldb/Utility/StringExtractorGDBRemote.h" + #include "llvm/ADT/ScopeExit.h" #include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringSwitch.h" @@ -620,7 +623,6 @@ Status ProcessGDBRemote::WillLaunchOrAttach() { m_stdio_communication.Clear(); return error; } - // Process Control Status ProcessGDBRemote::DoLaunch(lldb_private::Module *exe_module, ProcessLaunchInfo &launch_info) { @@ -813,6 +815,213 @@ Status ProcessGDBRemote::DoLaunch(lldb_private::Module *exe_module, return error; } +class GPUBreakpointCallbackBaton : public TypedBaton { + public: + explicit GPUBreakpointCallbackBaton(std::unique_ptr Data) + : TypedBaton(std::move(Data)) {} +}; + +typedef std::shared_ptr GPUBreakpointCallbackBatonSP; + +bool ProcessGDBRemote::GPUBreakpointHitCallback( + void *baton, + StoppointCallbackContext *context, + lldb::user_id_t break_id, + lldb::user_id_t break_loc_id) { + ProcessSP process_sp = context->exe_ctx_ref.GetProcessSP(); + ProcessGDBRemote *process = static_cast(process_sp.get()); + return process->GPUBreakpointHit(baton, context, break_id, break_loc_id); +} + +bool ProcessGDBRemote::GPUBreakpointHit(void *baton, + StoppointCallbackContext *context, + lldb::user_id_t break_id, + lldb::user_id_t break_loc_id) { + GPUPluginBreakpointHitArgs *callback_data = + static_cast(baton); + // Make a copy of the args so we can fill in any needed symbol values prior + // to notifying lldb-server. + GPUPluginBreakpointHitArgs args = *callback_data; + Target &target = GetTarget(); + const size_t num_symbols = args.breakpoint.symbol_names.size(); + if (num_symbols > 0) { + args.symbol_values.resize(num_symbols); + for (size_t i=0; iGetAddress().GetLoadAddress(&target); + if (load_addr != LLDB_INVALID_ADDRESS) { + args.symbol_values[i].value = load_addr; + break; + } + } + } + } + } + std::optional response = + m_gdb_comm.GPUBreakpointHit(args); + if (response) { + // Disable the breakpoint if requested, but leave it around so we can see + // the hit count and other stats on the breakpoint. + if (response->disable_bp) { + BreakpointSP bp_sp = target.GetBreakpointByID(break_id); + if (bp_sp) + bp_sp->SetEnabled(false); + } + if (Status err = HandleGPUActions(response->actions); err.Fail()) { + Debugger::ReportError( + llvm::formatv("HandleGPUActions failed. Error: {0}\nActions:\n{1}\n", + err.AsCString(), toJSON(response->actions))); + } + } + return false; // Don't stop, auto continue. +} + +Status ProcessGDBRemote::HandleGPUActions(const GPUActions &gpu_action) { + Status error; + if (!gpu_action.breakpoints.empty()) + HandleGPUBreakpoints(gpu_action); + if (gpu_action.connect_info) { + error = HandleConnectionRequest(gpu_action); + if (error.Fail()) + return error; + } + + // Any commands below require a GPU process and it needs to have published + // its public state as stopped, otherwise the following unwanted behaviors + // might occur: + // - if the state is running, then these actions will fail + // - if the public state is not yet stopped, then there might be race + // conditions in which the action triggered by the code below ends up + // using stale information, as the GPU target hasn't finished processing + // its internal metadata. It's worth mentioning that there are multiple + // threads operating on the GPU target. + if (!(gpu_action.load_libraries || gpu_action.resume_gpu_process || + gpu_action.wait_for_gpu_process_to_resume)) + return error; + lldb::TargetSP gpu_target_sp = + GetTarget().GetGPUPluginTarget(gpu_action.plugin_name); + if (!gpu_target_sp) + return error; + lldb::ProcessSP gpu_process_sp = gpu_target_sp->GetProcessSP(); + if (!gpu_process_sp) + return error; + // Save the resume ID in case we need to wait for the process to resume below. + const uint32_t gpu_process_resume_id = gpu_process_sp->GetResumeID(); + if (gpu_action.load_libraries) { + llvm::Error error = gpu_process_sp->LoadModules(); + if (error) + return Status::FromError(std::move(error)); + } + if (gpu_action.resume_gpu_process) { + error = gpu_process_sp->Resume(); + if (error.Fail()) + return error; + } + if (gpu_action.wait_for_gpu_process_to_resume) { + error = gpu_process_sp->WaitForNextResume(gpu_process_resume_id, 5); + if (error.Fail()) + return error; + } + return Status(); +} + +Status ProcessGDBRemote::HandleConnectionRequest(const GPUActions &gpu_action) { + const GPUPluginConnectionInfo &connection_info = + gpu_action.connect_info.value(); + Log *log = GetLog(GDBRLog::Plugin); + LLDB_LOG(log, "ProcessGDBRemote::HandleConnectionRequest()"); + auto &debugger = GetTarget().GetDebugger(); + TargetSP gpu_target_sp; + llvm::StringRef exe_path; + llvm::StringRef triple; + OptionGroupPlatform *platform_options = nullptr; + + if (connection_info.exe_path) + exe_path = *connection_info.exe_path; + if (connection_info.triple) + triple = *connection_info.triple; + // Create an empty target for our GPU. + Status error(debugger.GetTargetList().CreateTarget( + debugger, exe_path, triple, eLoadDependentsNo, platform_options, + gpu_target_sp)); + if (error.Fail()) + return error; + if (!gpu_target_sp) + return Status::FromErrorString("failed to create target"); + + PlatformSP platform_sp = gpu_target_sp->GetPlatform(); + if (!platform_sp) + return Status::FromErrorString("invalid platform for target needed for " + "connecting to process"); + + // We wait for the process to fully stop before we can query or alter it via + // GPUActions. + ProcessSP process_sp = platform_sp->ConnectProcessSynchronous( + connection_info.connect_url, GetPluginNameStatic(), debugger, + *debugger.GetAsyncOutputStream(), gpu_target_sp.get(), error); + if (error.Fail()) + return error; + if (!process_sp) + return Status::FromErrorString("invalid process after connecting"); + + GetTarget().SetGPUPluginTarget(gpu_action.plugin_name, + process_sp->GetTarget().shared_from_this()); + LLDB_LOG(log, "ProcessGDBRemote::HandleConnectionRequest(): successfully " + "created process!!!"); + return Status(); +} + +void ProcessGDBRemote::HandleGPUBreakpoints(const GPUActions &gpu_action) { + Target &target = GetTarget(); + for (const auto &bp: gpu_action.breakpoints) { + // Create data that will live with the breakpoint so when we hit the + // breakpoint and the GPUBreakpointHitCallback is called, we can use this + // data. + auto args_up = std::make_unique(); + args_up->plugin_name = gpu_action.plugin_name; + args_up->breakpoint = bp; + FileSpecList bp_modules; + BreakpointSP bp_sp; + if (bp.name_info) { + if (bp.name_info->shlib && !bp.name_info->shlib->empty()) + bp_modules.Append(FileSpec(*bp.name_info->shlib, + llvm::sys::path::Style::native)); + bp_sp = target.CreateBreakpoint( + &bp_modules, // Containing modules. + nullptr, // Containing source files. + bp.name_info->function_name.c_str(), // Function name. + eFunctionNameTypeFull, // Function name type. + eLanguageTypeUnknown, // Language type + 0, // Byte offset. + eLazyBoolNo, // Skip prologue. + true, // Internal breakpoint. + false); // Request hardware. + } else if (bp.addr_info) { + bp_sp = target.CreateBreakpoint(bp.addr_info->load_address, + /*internal=*/true, + /*request_hardware=*/false); + } + if (bp_sp) { + // Create some JSON we can send back to the lldb-server + // that identifies the plug-in and the breakpoint for when + // the breakpoint gets hit. + auto baton_sp = + std::make_shared(std::move(args_up)); + bp_sp->SetCallback(GPUBreakpointHitCallback, baton_sp, + /*is_synchronous=*/true); + } + } +} + Status ProcessGDBRemote::ConnectToDebugserver(llvm::StringRef connect_url) { Status error; // Only connect if we have a valid connect URL @@ -866,7 +1075,15 @@ Status ProcessGDBRemote::ConnectToDebugserver(llvm::StringRef connect_url) { m_gdb_comm.GetVContSupported('c'); m_gdb_comm.GetVAttachOrWaitSupported(); m_gdb_comm.EnableErrorStringInPacket(); - + if (auto init_actions = m_gdb_comm.GetGPUInitializeActions()) { + for (const auto &init_action : *init_actions) { + if (Status err = HandleGPUActions(init_action); err.Fail()) { + Debugger::ReportError(llvm::formatv( + "HandleGPUActions failed. Error: {0}\nActions:\n{1}\n", + err.AsCString(), toJSON(init_action))); + } + } + } // First dispatch any commands from the platform: auto handle_cmds = [&] (const Args &args) -> void { for (const Args::ArgEntry &entry : args) { @@ -1934,6 +2151,19 @@ ThreadSP ProcessGDBRemote::SetThreadStopInfo( thread_sp->SetStopInfo( StopInfo::CreateStopReasonVForkDone(*thread_sp)); handled = true; + } else if (reason == "dyld") { + // When plugins can't set a breakpoint, they might stop with a "dyld" + // reason to indicate that they need to load shared libraries. + auto error = LoadModules(); + if (error) { + Log *log(GetLog(GDBRLog::Process)); + LLDB_LOG_ERROR(log, std::move(error), "Failed to load modules: {0}"); + } + // TODO: create dyld stop reason, or auto resume depending on value + // of setting that specifies if we should stop for shared library + // load events. + thread_sp->SetStopInfo(StopInfo::CreateStopReasonDyld(*thread_sp)); + handled = true; } } @@ -2396,6 +2626,16 @@ StateType ProcessGDBRemote::SetThreadStopInfo(StringExtractor &stop_packet) { if (!value.getAsInteger(0, addressing_bits)) { addressable_bits.SetHighmemAddressableBits(addressing_bits); } + } else if (key.compare("gpu-actions") == 0) { + StringExtractorGDBRemote extractor(value); + if (std::optional gpu_actions = + extractor.GetFromJSONHexASCII()) { + if (Status err = HandleGPUActions(*gpu_actions); err.Fail()) { + Debugger::ReportError(llvm::formatv( + "HandleGPUActions failed. Error: {0}\nActions:\n{1}\n", + err.AsCString(), toJSON(*gpu_actions))); + } + } } else if (key.size() == 2 && ::isxdigit(key[0]) && ::isxdigit(key[1])) { uint32_t reg = UINT32_MAX; if (!key.getAsInteger(16, reg)) @@ -3968,8 +4208,12 @@ bool ProcessGDBRemote::StopNoticingNewThreads() { } DynamicLoader *ProcessGDBRemote::GetDynamicLoader() { - if (m_dyld_up.get() == nullptr) - m_dyld_up.reset(DynamicLoader::FindPlugin(this, "")); + if (m_dyld_up.get() == nullptr) { + llvm::StringRef dyld_plugin_name; + if (m_gdb_comm.SupportsGPUDynamicLoader()) + dyld_plugin_name = DynamicLoaderGDBRemoteGPU::GetPluginNameStatic(); + m_dyld_up.reset(DynamicLoader::FindPlugin(this, dyld_plugin_name)); + } return m_dyld_up.get(); } @@ -5202,6 +5446,29 @@ lldb::ModuleSP ProcessGDBRemote::LoadModuleAtAddress(const FileSpec &file, llvm::Error ProcessGDBRemote::LoadModules() { using lldb_private::process_gdb_remote::ProcessGDBRemote; + /// See if the dynamic loader knows how to load the modules when requested. + /// This can get triggered in multiple ways: + /// - If a breakpoint in the native process that was set by any GPUActions + /// gets hit, and the breakpoint hit response from the GPU plug-in via an + /// instance of GPUPluginBreakpointHitResponse has the "load_libraries" + /// bool member variable is set to true. This is the preferred method if + /// it is possible for a breakpoint to be set in the native process as it + /// allows the native process to be stopped while GPU shared libraries are + /// loaded. The breakpoint will auto resume the process after the GPU + /// shared libraries are loaded. + /// - Stop reason for a thread in GPU process is set to the + /// eStopReasonDynammicLoader stop reason. This is used when the GPU process + /// doesn't require synchronization with the native process. If the GPU + /// can't set a breakpoint in GPU code and the GPU driver gets a + /// notification that shared libraries are available. This should be used + /// if we want to stop for shared library loading and LLDB should auto + /// continue the process. It doesn't do this yet, but it can and will in the + /// future if we need this method of shared library load notification. + /// - The GPU process stop reply packet contains for a GPU thread has the + /// "library;" key in the key value pairs. + if (m_dyld_up && m_dyld_up->HandleStopReasonDynammicLoader()) + return llvm::ErrorSuccess(); + // request a list of loaded libraries from GDBServer llvm::Expected module_list = GetLoadedModuleList(); if (!module_list) diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h index 7ae33837fd067..543d76325ea19 100644 --- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h +++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h @@ -27,11 +27,13 @@ #include "lldb/Utility/Broadcaster.h" #include "lldb/Utility/ConstString.h" #include "lldb/Utility/GDBRemote.h" +#include "lldb/Utility/GPUGDBRemotePackets.h" #include "lldb/Utility/Status.h" #include "lldb/Utility/StreamString.h" #include "lldb/Utility/StringExtractor.h" #include "lldb/Utility/StringList.h" #include "lldb/Utility/StructuredData.h" + #include "lldb/lldb-private-forward.h" #include "GDBRemoteCommunicationClient.h" @@ -279,6 +281,7 @@ class ProcessGDBRemote : public Process, // registers and memory for all // threads if "jThreadsInfo" // packet is supported + StructuredData::ObjectSP m_plugin_metadata_sp; tid_collection m_continue_c_tids; // 'c' for continue tid_sig_collection m_continue_C_tids; // 'C' for continue with signal tid_collection m_continue_s_tids; // 's' for step @@ -436,6 +439,20 @@ class ProcessGDBRemote : public Process, /// Remove the breakpoints associated with thread creation from the Target. void RemoveNewThreadBreakpoints(); + static bool GPUBreakpointHitCallback(void *baton, + StoppointCallbackContext *context, + lldb::user_id_t break_id, + lldb::user_id_t break_loc_id); + + bool GPUBreakpointHit(void *baton, StoppointCallbackContext *context, + lldb::user_id_t break_id, lldb::user_id_t break_loc_id); + + void HandleGPUBreakpoints(const GPUActions &gpu_action); + + Status HandleConnectionRequest(const GPUActions &gpu_action); + + Status HandleGPUActions(const GPUActions &gpu_action); + // ContinueDelegate interface void HandleAsyncStdout(llvm::StringRef out) override; void HandleAsyncMisc(llvm::StringRef data) override; diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemoteLog.cpp b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemoteLog.cpp index 3322f6b8048ab..76e1fb196424e 100644 --- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemoteLog.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemoteLog.cpp @@ -31,6 +31,7 @@ static constexpr Log::Category g_categories[] = { {{"step"}, {"log step related activities"}, GDBRLog::Step}, {{"thread"}, {"log thread events and activities"}, GDBRLog::Thread}, {{"watch"}, {"log watchpoint related activities"}, GDBRLog::Watchpoints}, + {{"plugin"}, {"log internal lldb-server plugin messages"}, GDBRLog::Plugin}, }; static Log::Channel g_channel(g_categories, GDBRLog::Packets); diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemoteLog.h b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemoteLog.h index 66b2f00f1ea96..db129022eb32f 100644 --- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemoteLog.h +++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemoteLog.h @@ -27,6 +27,7 @@ enum class GDBRLog : Log::MaskType { Step = Log::ChannelFlag<8>, Thread = Log::ChannelFlag<9>, Watchpoints = Log::ChannelFlag<10>, + Plugin = Log::ChannelFlag<11>, LLVM_MARK_AS_BITMASK_ENUM(Watchpoints) }; LLVM_ENABLE_BITMASK_ENUMS_IN_NAMESPACE(); diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp index bba1230c79920..4e06ffccd77b0 100644 --- a/lldb/source/Target/Process.cpp +++ b/lldb/source/Target/Process.cpp @@ -2563,6 +2563,38 @@ Status Process::DisableWatchpoint(WatchpointSP wp_sp, bool notify) { return error; } +Status Process::WaitForNextResume(const uint32_t curr_resume_id, + std::optional opt_timeout_seconds) { + ListenerSP listener_sp; + { + // Don't let the private state change on us while we do some checking by + // locking the private state mutex. The resume ID can't be bumped while we + // hold this lock. We also need to start listening for state changed events + // prior to letting go of this mutex to ensure there is no race condition. + std::lock_guard guard(m_private_state.GetMutex()); + if (GetResumeID() != curr_resume_id) + return Status(); ///< Process already resumed. + + listener_sp = Listener::MakeListener("Process::WaitForNextResume"); + listener_sp->StartListeningForEvents(this, eBroadcastBitStateChanged); + } + + lldb::EventSP event_sp; + Timeout timeout(std::nullopt); + if (opt_timeout_seconds.has_value()) + timeout = std::chrono::seconds(*opt_timeout_seconds); + listener_sp->GetEvent(event_sp, timeout); + + if (!event_sp) + return Status::FromErrorString("timeout waiting for process to resume"); + + // Don't let the private state change on us while we do some checking. + std::lock_guard guard(m_private_state.GetMutex()); + if (GetResumeID() != curr_resume_id) + return Status(); // Success + return Status::FromErrorString("process was not resumed"); +} + StateType Process::WaitForProcessStopPrivate(EventSP &event_sp, const Timeout &timeout) { diff --git a/lldb/source/Target/StopInfo.cpp b/lldb/source/Target/StopInfo.cpp index 3160446ae1d17..746baaf2b657a 100644 --- a/lldb/source/Target/StopInfo.cpp +++ b/lldb/source/Target/StopInfo.cpp @@ -1469,6 +1469,31 @@ class StopInfoVForkDone : public StopInfo { bool m_performed_action = false; }; +/// A stop reason for causing shared libraries to load. This will create a stop +/// info that will resume the process by returning the right value for +/// StopInfo::ShouldStopSynchronous(...). This allows a process to stop and +/// report it wants to load/unload shared libraries and auto continue after all +/// libraries have been loaded/unloaded. +class StopInfoDyld : public StopInfo { +public: + StopInfoDyld(Thread &thread, const char *description) + : StopInfo(thread, 0) { + SetDescription(description ? description : "dynamic loader"); + } + ~StopInfoDyld() override = default; + StopReason GetStopReason() const override { + return lldb::eStopReasonDynammicLoader; + } + bool ShouldStopSynchronous(Event *event_ptr) override { + if (ThreadSP thread_sp = GetThread()) { + ProcessSP process_sp = thread_sp->GetProcess(); + if (process_sp) + return process_sp->GetStopOnSharedLibraryEvents(); + } + return false; + } +}; + } // namespace lldb_private StopInfoSP StopInfo::CreateStopReasonWithBreakpointSiteID(Thread &thread, @@ -1531,6 +1556,12 @@ StopInfoSP StopInfo::CreateStopReasonHistoryBoundary(Thread &thread, return StopInfoSP(new StopInfoHistoryBoundary(thread, description)); } +StopInfoSP StopInfo::CreateStopReasonDyld(Thread &thread, + const char *description) { + return StopInfoSP(new StopInfoDyld(thread, description)); +} + + StopInfoSP StopInfo::CreateStopReasonWithExec(Thread &thread) { return StopInfoSP(new StopInfoExec(thread)); } diff --git a/lldb/source/Target/Thread.cpp b/lldb/source/Target/Thread.cpp index c68894808eacc..710f44b5dedab 100644 --- a/lldb/source/Target/Thread.cpp +++ b/lldb/source/Target/Thread.cpp @@ -1758,8 +1758,10 @@ std::string Thread::StopReasonAsString(lldb::StopReason reason) { return "processor trace"; case eStopReasonInterrupt: return "async interrupt"; - case eStopReasonHistoryBoundary: + case eStopReasonHistoryBoundary: return "history boundary"; + case eStopReasonDynammicLoader: + return "dynamic loader"; } return "StopReason = " + std::to_string(reason); diff --git a/lldb/source/Utility/ArchSpec.cpp b/lldb/source/Utility/ArchSpec.cpp index 70b9800f4dade..f3de45138e4ea 100644 --- a/lldb/source/Utility/ArchSpec.cpp +++ b/lldb/source/Utility/ArchSpec.cpp @@ -248,7 +248,15 @@ static const CoreDefinition g_core_definitions[] = { {eByteOrderLittle, 4, 1, 4, llvm::Triple::wasm32, ArchSpec::eCore_wasm32, "wasm32"}, -}; + {eByteOrderLittle, 4, 4, 4, llvm::Triple::r600, + ArchSpec::eCore_amd_gpu_r600,"r600"}, + {eByteOrderLittle, 8, 4, 4, llvm::Triple::amdgcn, + ArchSpec::eCore_amd_gpu_gcn, "amdgcn"}, + {eByteOrderLittle, 4, 4, 4, llvm::Triple::nvptx, + ArchSpec::eCore_nvidia_nvptx,"nvptx"}, + {eByteOrderLittle, 8, 4, 4, llvm::Triple::nvptx64, + ArchSpec::eCore_nvidia_nvptx64, "nvptx64"}, + }; // Ensure that we have an entry in the g_core_definitions for each core. If you // comment out an entry above, you will need to comment out the corresponding @@ -434,6 +442,16 @@ static const ArchDefinitionEntry g_elf_arch_entries[] = { {ArchSpec::eCore_loongarch64, llvm::ELF::EM_LOONGARCH, ArchSpec::eLoongArchSubType_loongarch64, 0xFFFFFFFFu, 0xFFFFFFFFu}, // loongarch64 + // TODO: add data that says what the address byte size is in there + // structures so we can tell r600 from gcn + // {ArchSpec::eCore_amd_gpu_r600, llvm::ELF::EM_AMDGPU, LLDB_INVALID_CPUTYPE, + // 0xFFFFFFFFu, 0xFFFFFFFFu}, + {ArchSpec::eCore_amd_gpu_gcn, llvm::ELF::EM_AMDGPU, LLDB_INVALID_CPUTYPE, + 0xFFFFFFFFu, 0xFFFFFFFFu}, + {ArchSpec::eCore_nvidia_nvptx, llvm::ELF::EM_CUDA, LLDB_INVALID_CPUTYPE, + 0xFFFFFFFFu, 0xFFFFFFFFu}, + {ArchSpec::eCore_nvidia_nvptx64, llvm::ELF::EM_CUDA, LLDB_INVALID_CPUTYPE, + 0xFFFFFFFFu, 0xFFFFFFFFu}, }; static const ArchDefinition g_elf_arch_def = { @@ -922,6 +940,9 @@ bool ArchSpec::SetArchitecture(ArchitectureType arch_type, uint32_t cpu, case llvm::ELF::ELFOSABI_STANDALONE: m_triple.setOS(llvm::Triple::OSType::UnknownOS); break; + case llvm::ELF::ELFOSABI_AMDGPU_HSA: + m_triple.setOS(llvm::Triple::OSType::AMDHSA); + break; } } else if (arch_type == eArchTypeCOFF && os == llvm::Triple::Win32) { m_triple.setVendor(llvm::Triple::PC); diff --git a/lldb/source/Utility/CMakeLists.txt b/lldb/source/Utility/CMakeLists.txt index 1dd4d63f7016f..7679166e7481a 100644 --- a/lldb/source/Utility/CMakeLists.txt +++ b/lldb/source/Utility/CMakeLists.txt @@ -45,6 +45,7 @@ add_lldb_library(lldbUtility NO_INTERNAL_DEPENDENCIES FileSpec.cpp FileSpecList.cpp GDBRemote.cpp + GPUGDBRemotePackets.cpp IOObject.cpp LLDBAssert.cpp LLDBLog.cpp diff --git a/lldb/source/Utility/GPUGDBRemotePackets.cpp b/lldb/source/Utility/GPUGDBRemotePackets.cpp new file mode 100644 index 0000000000000..a79f376bd33b6 --- /dev/null +++ b/lldb/source/Utility/GPUGDBRemotePackets.cpp @@ -0,0 +1,250 @@ +//===-- GPUGDBRemotePackets.cpp -------------------------------*- C++ -*-===// +// +// 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 "lldb/Utility/GPUGDBRemotePackets.h" + +using namespace llvm; +using namespace llvm::json; + +namespace lldb_private { + +//------------------------------------------------------------------------------ +// SymbolValue +//------------------------------------------------------------------------------ + +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); +} + +json::Value toJSON(const SymbolValue &data) { + return json::Value(Object{{"name", data.name}, {"value", data.value}}); +} + +///----------------------------------------------------------------------------- +/// GPUBreakpointByName +/// +/// A structure that contains information on how to set a breakpoint by function +/// name with optional shared library name. +///----------------------------------------------------------------------------- + +bool fromJSON(const llvm::json::Value &value, GPUBreakpointByName &data, + llvm::json::Path path) { + ObjectMapper o(value, path); + return o && o.mapOptional("shlib", data.shlib) && + o.map("function_name", data.function_name); +} + +llvm::json::Value toJSON(const GPUBreakpointByName &data) { + return json::Value( + Object{{"shlib", data.shlib}, {"function_name", data.function_name}}); +} + +///----------------------------------------------------------------------------- +/// GPUBreakpointByAddress +/// +/// A structure that contains information on how to set a breakpoint by address. +///----------------------------------------------------------------------------- + +bool fromJSON(const llvm::json::Value &value, GPUBreakpointByAddress &data, + llvm::json::Path path) { + ObjectMapper o(value, path); + return o && o.map("load_address", data.load_address); +} + +llvm::json::Value toJSON(const GPUBreakpointByAddress &data) { + return json::Value(Object{{"load_address", data.load_address}}); +} + +//------------------------------------------------------------------------------ +// GPUBreakpointInfo +//------------------------------------------------------------------------------ +bool fromJSON(const llvm::json::Value &value, GPUBreakpointInfo &data, + llvm::json::Path path) { + ObjectMapper o(value, path); + return o && o.map("identifier", data.identifier) && + o.mapOptional("name_info", data.name_info) && + o.mapOptional("addr_info", data.addr_info) && + o.map("symbol_names", data.symbol_names); +} + +llvm::json::Value toJSON(const GPUBreakpointInfo &data) { + return json::Value(Object{ + {"identifier", data.identifier}, + {"name_info", data.name_info}, + {"addr_info", data.addr_info}, + {"symbol_names", data.symbol_names}, + }); +} + +//------------------------------------------------------------------------------ +// GPUPluginConnectionInfo +//------------------------------------------------------------------------------ +bool fromJSON(const llvm::json::Value &value, GPUPluginConnectionInfo &data, + llvm::json::Path path) { + ObjectMapper o(value, path); + return o && o.mapOptional("exe_path", data.exe_path) && + o.mapOptional("platform_name", data.platform_name) && + o.mapOptional("triple", data.triple) && + o.map("connect_url", data.connect_url); +} + +llvm::json::Value toJSON(const GPUPluginConnectionInfo &data) { + return json::Value(Object{ + {"exe_path", data.exe_path}, + {"platform_name", data.platform_name}, + {"triple", data.triple}, + {"connect_url", data.connect_url}, + }); +} + +//------------------------------------------------------------------------------ +// GPUPluginBreakpointHitArgs +//------------------------------------------------------------------------------ +bool fromJSON(const json::Value &value, GPUPluginBreakpointHitArgs &data, + Path path) { + ObjectMapper o(value, path); + return o && o.map("plugin_name", data.plugin_name) && + o.map("breakpoint", data.breakpoint) && + o.map("symbol_values", data.symbol_values); +} + +json::Value toJSON(const GPUPluginBreakpointHitArgs &data) { + return json::Value(Object{ + {"plugin_name", data.plugin_name}, + {"breakpoint", data.breakpoint}, + {"symbol_values", data.symbol_values}, + }); +} + +std::optional +GPUPluginBreakpointHitArgs::GetSymbolValue(llvm::StringRef symbol_name) const { + for (const auto &symbol : symbol_values) + if (symbol_name == symbol.name) + return symbol.value; + return std::nullopt; +} + +//------------------------------------------------------------------------------ +// GPUActions +//------------------------------------------------------------------------------ +bool fromJSON(const llvm::json::Value &value, GPUActions &data, + llvm::json::Path path) { + ObjectMapper o(value, path); + return o && o.map("plugin_name", data.plugin_name) && + o.map("breakpoints", data.breakpoints) && + o.mapOptional("connect_info", data.connect_info) && + o.map("load_libraries", data.load_libraries) && + o.map("resume_gpu_process", data.resume_gpu_process) && + o.map("wait_for_gpu_process_to_resume", + data.wait_for_gpu_process_to_resume); +} + +llvm::json::Value toJSON(const GPUActions &data) { + return json::Value(Object{ + {"plugin_name", data.plugin_name}, + {"breakpoints", data.breakpoints}, + {"connect_info", data.connect_info}, + {"load_libraries", data.load_libraries}, + {"resume_gpu_process", data.resume_gpu_process}, + {"wait_for_gpu_process_to_resume", data.wait_for_gpu_process_to_resume}, + }); +} + +//------------------------------------------------------------------------------ +// GPUPluginBreakpointHitResponse +//------------------------------------------------------------------------------ + +bool fromJSON(const llvm::json::Value &value, + GPUPluginBreakpointHitResponse &data, llvm::json::Path path) { + ObjectMapper o(value, path); + return o && o.map("disable_bp", data.disable_bp) && + o.map("actions", data.actions); +} + +llvm::json::Value toJSON(const GPUPluginBreakpointHitResponse &data) { + return json::Value(Object{ + {"disable_bp", data.disable_bp}, + {"actions", data.actions}, + }); +} + +//------------------------------------------------------------------------------ +// GPUSectionInfo +//------------------------------------------------------------------------------ + +bool fromJSON(const llvm::json::Value &value, GPUSectionInfo &data, + llvm::json::Path path) { + ObjectMapper o(value, path); + return o && o.map("names", data.names) && + o.map("load_address", data.load_address); +} + +llvm::json::Value toJSON(const GPUSectionInfo &data) { + return json::Value( + Object{{"names", data.names}, {"load_address", data.load_address}}); +} + +//------------------------------------------------------------------------------ +// GPUDynamicLoaderLibraryInfo +//------------------------------------------------------------------------------ + +bool fromJSON(const llvm::json::Value &value, GPUDynamicLoaderLibraryInfo &data, + llvm::json::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("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) && + o.map("loaded_sections", data.loaded_sections); +} + +llvm::json::Value toJSON(const GPUDynamicLoaderLibraryInfo &data) { + return json::Value( + Object{{"pathname", data.pathname}, + {"uuid", data.uuid_str}, + {"load", data.load}, + {"load_address", data.load_address}, + {"native_memory_address", data.native_memory_address}, + {"native_memory_size", data.native_memory_size}, + {"file_offset", data.file_offset}, + {"file_size", data.file_size}, + {"loaded_sections", data.loaded_sections}}); +} + +//------------------------------------------------------------------------------ +// GPUDynamicLoaderArgs +//------------------------------------------------------------------------------ + +bool fromJSON(const llvm::json::Value &value, GPUDynamicLoaderArgs &data, + llvm::json::Path path) { + ObjectMapper o(value, path); + return o && o.map("full", data.full); +} + +llvm::json::Value toJSON(const GPUDynamicLoaderArgs &data) { + return json::Value(Object{{"full", data.full}}); +} + +//------------------------------------------------------------------------------ +// GPUDynamicLoaderResponse +//------------------------------------------------------------------------------ +bool fromJSON(const llvm::json::Value &value, GPUDynamicLoaderResponse &data, + llvm::json::Path path) { + ObjectMapper o(value, path); + return o && o.map("library_infos", data.library_infos); +} + +llvm::json::Value toJSON(const GPUDynamicLoaderResponse &data) { + return json::Value(Object{{"library_infos", data.library_infos}}); +} + +} // namespace lldb_private diff --git a/lldb/source/Utility/StringExtractorGDBRemote.cpp b/lldb/source/Utility/StringExtractorGDBRemote.cpp index c5755b2733605..09c94a5975565 100644 --- a/lldb/source/Utility/StringExtractorGDBRemote.cpp +++ b/lldb/source/Utility/StringExtractorGDBRemote.cpp @@ -320,7 +320,12 @@ StringExtractorGDBRemote::GetServerPacketType() const { return eServerPacketType_jSignalsInfo; if (PACKET_MATCHES("jThreadsInfo")) return eServerPacketType_jThreadsInfo; - + if (PACKET_MATCHES("jGPUPluginInitialize")) + return eServerPacketType_jGPUPluginInitialize; + if (PACKET_STARTS_WITH("jGPUPluginBreakpointHit:")) + return eServerPacketType_jGPUPluginBreakpointHit; + if (PACKET_STARTS_WITH("jGPUPluginGetDynamicLoaderLibraryInfo:")) + return eServerPacketType_jGPUPluginGetDynamicLoaderLibraryInfo; if (PACKET_MATCHES("jLLDBTraceSupported")) return eServerPacketType_jLLDBTraceSupported; if (PACKET_STARTS_WITH("jLLDBTraceStop:")) diff --git a/lldb/tools/lldb-server/CMakeLists.txt b/lldb/tools/lldb-server/CMakeLists.txt index 1d8dc72a3f872..3248063f0e8e6 100644 --- a/lldb/tools/lldb-server/CMakeLists.txt +++ b/lldb/tools/lldb-server/CMakeLists.txt @@ -42,6 +42,14 @@ if(APPLE_EMBEDDED) endif() endif() +if(DEFINED ROCM_PATH) + add_definitions(-DLLDB_ENABLE_AMDGPU_PLUGIN=1) + list(APPEND LLDB_PLUGINS lldbServerPluginAMDGPU) +else() + add_definitions(-DLLDB_ENABLE_MOCKGPU_PLUGIN=1) + list(APPEND LLDB_PLUGINS lldbServerPluginMockGPU) +endif() + add_lldb_tool(lldb-server lldb-gdbserver.cpp lldb-platform.cpp @@ -63,11 +71,16 @@ add_lldb_tool(lldb-server lldbPluginInstructionMIPS64 lldbPluginInstructionRISCV ${LLDB_SYSTEM_LIBS} + lldbServerPluginMockGPU ) +set_target_properties(lldb-server PROPERTIES ENABLE_EXPORTS 1) + add_dependencies(lldb-server LLGSOptionsTableGen ${tablegen_deps} ) target_include_directories(lldb-server PRIVATE "${LLDB_SOURCE_DIR}/source") target_link_libraries(lldb-server PRIVATE ${LLDB_SYSTEM_LIBS}) + +add_subdirectory(Plugins) diff --git a/lldb/tools/lldb-server/Plugins/AMDGPU/CMakeLists.txt b/lldb/tools/lldb-server/Plugins/AMDGPU/CMakeLists.txt new file mode 100644 index 0000000000000..99493a3b03fb6 --- /dev/null +++ b/lldb/tools/lldb-server/Plugins/AMDGPU/CMakeLists.txt @@ -0,0 +1,27 @@ + +message(STATUS "ROCM_PATH is set to: '${ROCM_PATH}'") +if(NOT ROCM_PATH) + message(FATAL_ERROR "ROCM_PATH must be specified. Use -DROCM_PATH=/path/to/rocm") +endif() + +if (NOT EXISTS ${ROCM_PATH}) + message(FATAL_ERROR "ROCM_PATH does not exist: '${ROCM_PATH}'") +endif() + +# Find AMD-dbgapi package using the provided CMake config +set(amd-dbgapi_DIR "${ROCM_PATH}/lib/cmake/amd-dbgapi" CACHE PATH "Path to amd-dbgapi cmake config") +find_package(amd-dbgapi REQUIRED CONFIG) + +include_directories(${ROCM_PATH}/include) + +add_lldb_library(lldbServerPluginAMDGPU + LLDBServerPluginAMDGPU.cpp + ProcessAMDGPU.cpp + RegisterContextAMDGPU.cpp + ThreadAMDGPU.cpp + + LINK_LIBS + amd-dbgapi +) +set_target_properties(lldbServerPluginAMDGPU PROPERTIES LINK_FLAGS "-Wl,-rpath,${ROCM_PATH}/lib") +target_include_directories(lldbServerPluginAMDGPU PRIVATE "${LLDB_SOURCE_DIR}/source" ${ROCM_PATH}/include "../..") diff --git a/lldb/tools/lldb-server/Plugins/AMDGPU/LLDBServerPluginAMDGPU.cpp b/lldb/tools/lldb-server/Plugins/AMDGPU/LLDBServerPluginAMDGPU.cpp new file mode 100644 index 0000000000000..da39086e0f533 --- /dev/null +++ b/lldb/tools/lldb-server/Plugins/AMDGPU/LLDBServerPluginAMDGPU.cpp @@ -0,0 +1,588 @@ +//===-- LLDBServerPluginAMDGPU.cpp -----------------------------*- C++ -*-===// +// +// 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 "LLDBServerPluginAMDGPU.h" +#include "Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h" +#include "Plugins/Process/gdb-remote/ProcessGDBRemoteLog.h" +#include "ProcessAMDGPU.h" +#include "ThreadAMDGPU.h" +#include "lldb/Host/common/TCPSocket.h" +#include "lldb/Host/posix/ConnectionFileDescriptorPosix.h" +#include "llvm/Support/Error.h" + +#include +#include +#include +#include +#include +#include +#include + +using namespace lldb; +using namespace lldb_private; +using namespace lldb_private::lldb_server; +using namespace lldb_private::process_gdb_remote; + +static amd_dbgapi_status_t amd_dbgapi_client_process_get_info_callback( + amd_dbgapi_client_process_id_t client_process_id, + amd_dbgapi_client_process_info_t query, size_t value_size, void *value) { + LLDBServerPluginAMDGPU *debugger = + reinterpret_cast(client_process_id); + lldb::pid_t pid = debugger->GetNativeProcess()->GetID(); + LLDB_LOGF(GetLog(GDBRLog::Plugin), + "amd_dbgapi_client_process_get_info_callback callback, with query " + "%d, pid %lu", + query, (unsigned long)pid); + switch (query) { + case AMD_DBGAPI_CLIENT_PROCESS_INFO_OS_PID: { + if (value_size != sizeof(amd_dbgapi_os_process_id_t)) + return AMD_DBGAPI_STATUS_ERROR_INVALID_ARGUMENT_COMPATIBILITY; + *static_cast(value) = pid; + return AMD_DBGAPI_STATUS_SUCCESS; + } + case AMD_DBGAPI_CLIENT_PROCESS_INFO_CORE_STATE: { + return AMD_DBGAPI_STATUS_SUCCESS; + } + } + return AMD_DBGAPI_STATUS_SUCCESS; +} + +static amd_dbgapi_status_t amd_dbgapi_insert_breakpoint_callback( + amd_dbgapi_client_process_id_t client_process_id, + amd_dbgapi_global_address_t address, + amd_dbgapi_breakpoint_id_t breakpoint_id) { + LLDB_LOGF(GetLog(GDBRLog::Plugin), + "insert_breakpoint callback at address: 0x%" PRIx64, address); + LLDBServerPluginAMDGPU *debugger = + reinterpret_cast(client_process_id); + debugger->GetNativeProcess()->Halt(); + LLDB_LOGF(GetLog(GDBRLog::Plugin), "insert_breakpoint callback success"); + LLDBServerPluginAMDGPU::GPUInternalBreakpoinInfo bp_info; + bp_info.addr = address; + bp_info.breakpoind_id = breakpoint_id; + debugger->m_gpu_internal_bp.emplace(std::move(bp_info)); + debugger->m_wait_for_gpu_internal_bp_stop = true; + return AMD_DBGAPI_STATUS_SUCCESS; +} + +/* remove_breakpoint callback. */ + +static amd_dbgapi_status_t amd_dbgapi_remove_breakpoint_callback( + amd_dbgapi_client_process_id_t client_process_id, + amd_dbgapi_breakpoint_id_t breakpoint_id) { + LLDB_LOGF(GetLog(GDBRLog::Plugin), "remove_breakpoint callback for %" PRIu64, + breakpoint_id.handle); + return AMD_DBGAPI_STATUS_SUCCESS; +} + +/* xfer_global_memory callback. */ + +static amd_dbgapi_status_t amd_dbgapi_xfer_global_memory_callback( + amd_dbgapi_client_process_id_t client_process_id, + amd_dbgapi_global_address_t global_address, amd_dbgapi_size_t *value_size, + void *read_buffer, const void *write_buffer) { + LLDB_LOGF(GetLog(GDBRLog::Plugin), "xfer_global_memory callback"); + + return AMD_DBGAPI_STATUS_SUCCESS; +} + +static void amd_dbgapi_log_message_callback(amd_dbgapi_log_level_t level, + const char *message) { + LLDB_LOGF(GetLog(GDBRLog::Plugin), "ROCdbgapi [%d]: %s", level, message); +} + +static amd_dbgapi_callbacks_t s_dbgapi_callbacks = { + malloc, + free, + amd_dbgapi_client_process_get_info_callback, + amd_dbgapi_insert_breakpoint_callback, + amd_dbgapi_remove_breakpoint_callback, + amd_dbgapi_xfer_global_memory_callback, + amd_dbgapi_log_message_callback, +}; + +LLDBServerPluginAMDGPU::LLDBServerPluginAMDGPU( + LLDBServerPlugin::GDBServer &native_process, MainLoop &main_loop) + : LLDBServerPlugin(native_process, main_loop) { + m_process_manager_up.reset(new ProcessManagerAMDGPU(main_loop)); + m_gdb_server.reset(new GDBRemoteCommunicationServerLLGS( + m_main_loop, *m_process_manager_up, "amd-gpu.server")); +} + +LLDBServerPluginAMDGPU::~LLDBServerPluginAMDGPU() { CloseFDs(); } + +llvm::StringRef LLDBServerPluginAMDGPU::GetPluginName() { return "amd-gpu"; } + +void LLDBServerPluginAMDGPU::CloseFDs() { + if (m_fds[0] != -1) { + close(m_fds[0]); + m_fds[0] = -1; + } + if (m_fds[1] != -1) { + close(m_fds[1]); + m_fds[1] = -1; + } +} + +int LLDBServerPluginAMDGPU::GetEventFileDescriptorAtIndex(size_t idx) { + if (idx != 0) + return -1; + if (m_fds[0] == -1) { + if (socketpair(AF_UNIX, SOCK_STREAM, 0, m_fds) == -1) { + m_fds[0] = -1; + m_fds[1] = -1; + } + } + return m_fds[0]; +} + +bool LLDBServerPluginAMDGPU::initRocm() { + // Initialize AMD Debug API with callbacks + amd_dbgapi_status_t status = amd_dbgapi_initialize(&s_dbgapi_callbacks); + if (status != AMD_DBGAPI_STATUS_SUCCESS) { + LLDB_LOGF(GetLog(GDBRLog::Plugin), "Failed to initialize AMD debug API"); + exit(-1); + } + + // Attach to the process with AMD Debug API + status = amd_dbgapi_process_attach( + reinterpret_cast( + this), // Use pid_ as client_process_id + &m_gpu_pid); + if (status != AMD_DBGAPI_STATUS_SUCCESS) { + LLDB_LOGF(GetLog(GDBRLog::Plugin), + "Failed to attach to process with AMD debug API: %d", status); + amd_dbgapi_finalize(); + return false; + } + + // Get the process notifier + status = + amd_dbgapi_process_get_info(m_gpu_pid, AMD_DBGAPI_PROCESS_INFO_NOTIFIER, + sizeof(m_notifier_fd), &m_notifier_fd); + + if (status != AMD_DBGAPI_STATUS_SUCCESS) { + LLDB_LOGF(GetLog(GDBRLog::Plugin), "Failed to get process notifier: %d", + status); + amd_dbgapi_process_detach(m_gpu_pid); + amd_dbgapi_finalize(); + return false; + } + + // event_handler_.addNotifierFd(m_notifier_fd); + LLDB_LOGF(GetLog(GDBRLog::Plugin), "Process notifier fd: %d", m_notifier_fd); + + amd_dbgapi_event_id_t eventId; + amd_dbgapi_event_kind_t eventKind; + // Process all pending events + if (amd_dbgapi_process_next_pending_event(m_gpu_pid, &eventId, &eventKind) == + AMD_DBGAPI_STATUS_SUCCESS) { + GetGPUProcess()->handleDebugEvent(eventId, eventKind); + + // Mark event as processed + amd_dbgapi_event_processed(eventId); + } + + amd_dbgapi_architecture_id_t architecture_id; + // TODO: do not hardcode the device id + status = amd_dbgapi_get_architecture(0x04C, &architecture_id); + if (status != AMD_DBGAPI_STATUS_SUCCESS) { + // Handle error + LLDB_LOGF(GetLog(GDBRLog::Plugin), "amd_dbgapi_get_architecture failed"); + return false; + } + m_architecture_id = architecture_id; + + return true; +} + +bool LLDBServerPluginAMDGPU::processGPUEvent() { + LLDB_LOGF(GetLog(GDBRLog::Plugin), "processGPUEvent"); + char buf[256]; + ssize_t bytesRead = 0; + bool result = false; + do { + do { + bytesRead = read(m_notifier_fd, buf, sizeof(buf)); + } while (bytesRead <= 0); + + auto *process = GetGPUProcess(); + process->m_wave_ids.clear(); + amd_dbgapi_status_t status = amd_dbgapi_process_set_progress( + m_gpu_pid, AMD_DBGAPI_PROGRESS_NO_FORWARD); + assert(status == AMD_DBGAPI_STATUS_SUCCESS); + process_event_queue(AMD_DBGAPI_EVENT_KIND_NONE); + if (process->m_gpu_state == ProcessAMDGPU::State::GPUStopped) { + for (auto wave_id : process->m_wave_ids) { + process->AddThread(wave_id); + } + process->Halt(); + } + status = + amd_dbgapi_process_set_progress(m_gpu_pid, AMD_DBGAPI_PROGRESS_NORMAL); + assert(status == AMD_DBGAPI_STATUS_SUCCESS); + break; + } while (true); + return result; +} + +bool LLDBServerPluginAMDGPU::HandleEventFileDescriptorEvent(int fd) { + return processGPUEvent(); +} + +void LLDBServerPluginAMDGPU::AcceptAndMainLoopThread( + std::unique_ptr listen_socket_up) { + Log *log = GetLog(GDBRLog::Plugin); + LLDB_LOGF(log, "%s spawned", __PRETTY_FUNCTION__); + Socket *socket = nullptr; + Status error = listen_socket_up->Accept(std::chrono::seconds(30), socket); + // Scope for lock guard. + { + // Protect access to m_is_listening and m_is_connected. + std::lock_guard guard(m_connect_mutex); + m_is_listening = false; + if (error.Fail()) { + LLDB_LOGF(log, "%s error returned from Accept(): %s", __PRETTY_FUNCTION__, + error.AsCString()); + return; + } + m_is_connected = true; + } + + LLDB_LOGF(log, "%s initializing connection", __PRETTY_FUNCTION__); + std::unique_ptr connection_up( + new ConnectionFileDescriptor(std::unique_ptr(socket))); + m_gdb_server->InitializeConnection(std::move(connection_up)); + LLDB_LOGF(log, "%s running main loop", __PRETTY_FUNCTION__); + m_main_loop_status = m_main_loop.Run(); + LLDB_LOGF(log, "%s main loop exited!", __PRETTY_FUNCTION__); + if (m_main_loop_status.Fail()) { + LLDB_LOGF(log, "%s main loop exited with an error: %s", __PRETTY_FUNCTION__, + m_main_loop_status.AsCString()); + } + // Protect access to m_is_connected. + std::lock_guard guard(m_connect_mutex); + m_is_connected = false; +} + +std::optional +LLDBServerPluginAMDGPU::CreateConnection() { + std::lock_guard guard(m_connect_mutex); + Log *log = GetLog(GDBRLog::Plugin); + LLDB_LOGF(log, "%s called", __PRETTY_FUNCTION__); + if (m_is_connected) { + LLDB_LOGF(log, "%s already connected", __PRETTY_FUNCTION__); + return std::nullopt; + } + if (m_is_listening) { + LLDB_LOGF(log, "%s already listening", __PRETTY_FUNCTION__); + return std::nullopt; + } + m_is_listening = true; + LLDB_LOGF(log, "%s trying to listen on port 0", __PRETTY_FUNCTION__); + llvm::Expected> sock = + Socket::TcpListen("localhost:0", 5); + if (sock) { + GPUPluginConnectionInfo connection_info; + const uint16_t listen_port = (*sock)->GetLocalPortNumber(); + connection_info.connect_url = + llvm::formatv("connect://localhost:{}", listen_port); + LLDB_LOGF(log, "%s listening to %u", __PRETTY_FUNCTION__, listen_port); + // std::thread t(&LLDBServerPluginAMDGPU::AcceptAndMainLoopThread, this, + // std::move(*sock)); + // t.detach(); + + // Store the socket in the member variable to keep it alive + m_listen_socket = std::move(*sock); + auto extra_args = + llvm::formatv("gpu-url:connect://localhost:{};", listen_port); + m_is_connected = false; + llvm::Expected> res = + m_listen_socket->Accept( + m_main_loop, [this](std::unique_ptr socket) { + Log *log = GetLog(GDBRLog::Plugin); + LLDB_LOGF(log, + "LLDBServerPluginAMDGPU::AcceptAndMainLoopThread() " + "initializing connection"); + std::unique_ptr connection_up( + new ConnectionFileDescriptor(std::move(socket))); + this->m_gdb_server->InitializeConnection( + std::move(connection_up)); + m_is_connected = true; + }); + if (res) { + m_read_handles = std::move(*res); + } else { + LLDB_LOGF( + log, + "LLDBServerPluginAMDGPU::GetConnectionURL() failed to accept: %s", + llvm::toString(res.takeError()).c_str()); + } + + return connection_info; + } else { + std::string error = llvm::toString(sock.takeError()); + LLDB_LOGF(log, "%s failed to listen to localhost:0: %s", + __PRETTY_FUNCTION__, error.c_str()); + } + m_is_listening = false; + return std::nullopt; +} + +std::optional LLDBServerPluginAMDGPU::NativeProcessIsStopping() { + Log *log = GetLog(GDBRLog::Plugin); + if (!m_is_connected) { + initRocm(); + ProcessManagerAMDGPU *manager = + (ProcessManagerAMDGPU *)m_process_manager_up.get(); + manager->m_debugger = this; + + GPUActions actions; + actions.plugin_name = GetPluginName(); + + Status error; + LLDBServerPluginAMDGPU *amdGPUPlugin = this; + m_gpu_event_io_obj_sp = std::make_shared(m_notifier_fd); + m_gpu_event_read_up = m_main_loop.RegisterReadObject( + m_gpu_event_io_obj_sp, + [amdGPUPlugin](MainLoopBase &) { + amdGPUPlugin->HandleEventFileDescriptorEvent( + amdGPUPlugin->m_notifier_fd); + }, + error); + if (error.Fail()) { + LLDB_LOGF(log, "LLDBServerPluginAMDGPU::NativeProcessIsStopping() failed " + "to RegisterReadObject"); + // TODO: how to report this error? + } else { + LLDB_LOGF( + log, + "LLDBServerPluginAMDGPU::LLDBServerPluginAMDGPU() faking launch..."); + ProcessLaunchInfo info; + info.GetFlags().Set(eLaunchFlagStopAtEntry | eLaunchFlagDebug | + eLaunchFlagDisableASLR); + Args args; + args.AppendArgument("/pretend/path/to/amdgpu"); + args.AppendArgument("--option1"); + args.AppendArgument("--option2"); + args.AppendArgument("--option3"); + info.SetArguments(args, true); + info.GetEnvironment() = Host::GetEnvironment(); + info.SetProcessID(m_gpu_pid.handle); + m_gdb_server->SetLaunchInfo(info); + Status error = m_gdb_server->LaunchProcess(); + if (error.Fail()) { + LLDB_LOGF(log, + "LLDBServerPluginAMDGPU::LLDBServerPluginAMDGPU() failed to " + "launch: %s", + error.AsCString()); + } else { + LLDB_LOGF(log, "LLDBServerPluginAMDGPU::LLDBServerPluginAMDGPU() " + "launched successfully"); + } + actions.connect_info = CreateConnection(); + } + return actions; + } else { + if (m_wait_for_gpu_internal_bp_stop && m_gpu_internal_bp.has_value()) { + LLDB_LOGF(GetLog(GDBRLog::Plugin), "Please set gpu breakpoint at 0x%p", + (void *)m_gpu_internal_bp->addr); + GPUActions actions; + actions.plugin_name = GetPluginName(); + + GPUBreakpointByAddress bp_addr; + bp_addr.load_address = m_gpu_internal_bp->addr; + + GPUBreakpointInfo bp; + bp.identifier = "GPU loader breakpoint"; + bp.addr_info.emplace(bp_addr); + + std::vector breakpoints; + breakpoints.emplace_back(std::move(bp)); + + actions.breakpoints = std::move(breakpoints); + m_wait_for_gpu_internal_bp_stop = false; + return actions; + } + } + return std::nullopt; +} + +bool LLDBServerPluginAMDGPU::HandleGPUInternalBreakpointHit( + const GPUInternalBreakpoinInfo &bp, bool &has_new_libraries) { + LLDB_LOGF(GetLog(GDBRLog::Plugin), + "Hit GPU loader breakpoint at address: 0x%" PRIx64, bp.addr); + has_new_libraries = false; + amd_dbgapi_breakpoint_id_t breakpoint_id{bp.breakpoind_id}; + amd_dbgapi_breakpoint_action_t action; + + auto status = amd_dbgapi_report_breakpoint_hit( + breakpoint_id, reinterpret_cast(this), + &action); + + if (status != AMD_DBGAPI_STATUS_SUCCESS) { + LLDB_LOGF(GetLog(GDBRLog::Plugin), + "amd_dbgapi_report_breakpoint_hit failed: %d", status); + return false; + } + + if (action == AMD_DBGAPI_BREAKPOINT_ACTION_RESUME) { + LLDB_LOGF(GetLog(GDBRLog::Plugin), "AMD_DBGAPI_BREAKPOINT_ACTION_RESUME"); + return true; + } else if (action == AMD_DBGAPI_BREAKPOINT_ACTION_HALT) { + LLDB_LOGF(GetLog(GDBRLog::Plugin), "AMD_DBGAPI_BREAKPOINT_ACTION_HALT"); + + amd_dbgapi_event_id_t resume_event_id = + process_event_queue(AMD_DBGAPI_EVENT_KIND_BREAKPOINT_RESUME); + amd_dbgapi_event_processed(resume_event_id); + if (!GetGPUProcess()->GetGPUModules().empty()) { + has_new_libraries = true; + } + return true; + } else { + LLDB_LOGF(GetLog(GDBRLog::Plugin), "Unknown action: %d", action); + return false; + } + return true; +} + +amd_dbgapi_event_id_t LLDBServerPluginAMDGPU::process_event_queue( + amd_dbgapi_event_kind_t until_event_kind) { + while (true) { + amd_dbgapi_event_id_t event_id; + amd_dbgapi_event_kind_t event_kind; + amd_dbgapi_status_t status = amd_dbgapi_process_next_pending_event( + m_gpu_pid, &event_id, &event_kind); + + if (status != AMD_DBGAPI_STATUS_SUCCESS) { + LLDB_LOGF(GetLog(GDBRLog::Plugin), + "amd_dbgapi_process_next_pending_event failed: %d", status); + return AMD_DBGAPI_EVENT_NONE; + } + + if (event_kind != AMD_DBGAPI_EVENT_KIND_NONE) + LLDB_LOGF(GetLog(GDBRLog::Plugin), + "event_kind != AMD_DBGAPI_EVENT_KIND_NONE: %d", event_kind); + + if (event_id.handle == AMD_DBGAPI_EVENT_NONE.handle || + event_kind == until_event_kind) + return event_id; + + GetGPUProcess()->handleDebugEvent(event_id, event_kind); + amd_dbgapi_event_processed(event_id); + } + return AMD_DBGAPI_EVENT_NONE; +} + +bool LLDBServerPluginAMDGPU::SetGPUBreakpoint(uint64_t addr, + const uint8_t *bp_instruction, + size_t size) { + struct BreakpointInfo { + uint64_t addr; + std::vector original_bytes; + std::vector breakpoint_instruction; + std::optional gpu_breakpoint_id; + }; + + BreakpointInfo bp; + bp.addr = addr; + bp.breakpoint_instruction.assign(bp_instruction, bp_instruction + size); + bp.original_bytes.resize(size); + bp.gpu_breakpoint_id = + std::nullopt; // No GPU breakpoint ID for ptrace version + + // TODO: use memory read/write API from native process instead of ptrace + // directly. + auto pid = GetNativeProcess()->GetID(); + // Read original bytes word by word + std::vector original_words; + for (size_t i = 0; i < size; i += sizeof(long)) { + long word = ptrace(PTRACE_PEEKDATA, pid, addr + i, nullptr); + assert(word != -1 && errno == 0); + + original_words.push_back(word); + // Copy bytes from the word into our original_bytes + size_t bytes_to_copy = std::min(sizeof(long), size - i); + memcpy(&bp.original_bytes[i], &word, bytes_to_copy); + } + + // Write breakpoint instruction word by word + for (size_t i = 0; i < size; i += sizeof(long)) { + long word = original_words[i / sizeof(long)]; + size_t bytes_to_copy = std::min(sizeof(long), size - i); + memcpy(&word, &bp_instruction[i], bytes_to_copy); + + auto ret = ptrace(PTRACE_POKEDATA, pid, addr + i, word); + assert(ret != -1 && errno == 0); + } + return true; +} + +bool LLDBServerPluginAMDGPU::CreateGPUBreakpoint(uint64_t addr) { + // Get breakpoint instruction + const uint8_t *bp_instruction; + amd_dbgapi_status_t status = amd_dbgapi_architecture_get_info( + m_architecture_id, AMD_DBGAPI_ARCHITECTURE_INFO_BREAKPOINT_INSTRUCTION, + sizeof(bp_instruction), &bp_instruction); + if (status != AMD_DBGAPI_STATUS_SUCCESS) { + LLDB_LOGF(GetLog(GDBRLog::Plugin), + "AMD_DBGAPI_ARCHITECTURE_INFO_BREAKPOINT_INSTRUCTION failed"); + return false; + } + + // Get breakpoint instruction size + size_t bp_size; + status = amd_dbgapi_architecture_get_info( + m_architecture_id, + AMD_DBGAPI_ARCHITECTURE_INFO_BREAKPOINT_INSTRUCTION_SIZE, sizeof(bp_size), + &bp_size); + if (status != AMD_DBGAPI_STATUS_SUCCESS) { + LLDB_LOGF( + GetLog(GDBRLog::Plugin), + "AMD_DBGAPI_ARCHITECTURE_INFO_BREAKPOINT_INSTRUCTION_SIZE failed"); + return false; + } + + // Now call SetGPUBreakpoint with the retrieved instruction and size + return SetGPUBreakpoint(addr, bp_instruction, bp_size); +} + +llvm::Expected +LLDBServerPluginAMDGPU::BreakpointWasHit(GPUPluginBreakpointHitArgs &args) { + Log *log = GetLog(GDBRLog::Plugin); + std::string json_string; + std::string &bp_identifier = args.breakpoint.identifier; + llvm::raw_string_ostream os(json_string); + os << toJSON(args); + LLDB_LOGF(log, "LLDBServerPluginAMDGPU::BreakpointWasHit(\"%s\"):\nJSON:\n%s", + bp_identifier.c_str(), json_string.c_str()); + + GPUPluginBreakpointHitResponse response; + response.actions.plugin_name = GetPluginName(); + if (bp_identifier == "GPU loader breakpoint") { + bool has_new_libraries = false; + bool success = HandleGPUInternalBreakpointHit(m_gpu_internal_bp.value(), + has_new_libraries); + assert(success); + if (has_new_libraries) { + response.actions.wait_for_gpu_process_to_resume = true; + auto process = m_gdb_server->GetCurrentProcess(); + ThreadAMDGPU *thread = (ThreadAMDGPU *)process->GetCurrentThread(); + thread->SetStopReason(lldb::eStopReasonDynammicLoader); + process->Halt(); + } + } + return response; +} + +GPUActions LLDBServerPluginAMDGPU::GetInitializeActions() { + GPUActions init_actions; + init_actions.plugin_name = GetPluginName(); + + return init_actions; +} diff --git a/lldb/tools/lldb-server/Plugins/AMDGPU/LLDBServerPluginAMDGPU.h b/lldb/tools/lldb-server/Plugins/AMDGPU/LLDBServerPluginAMDGPU.h new file mode 100644 index 0000000000000..7f657879b6ca4 --- /dev/null +++ b/lldb/tools/lldb-server/Plugins/AMDGPU/LLDBServerPluginAMDGPU.h @@ -0,0 +1,110 @@ +//===-- LLDBServerPluginAMDGPU.h -------------------------------*- C++ -*-===// +// +// 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_TOOLS_LLDB_SERVER_LLDBSERVERPLUGINAMDGPU_H +#define LLDB_TOOLS_LLDB_SERVER_LLDBSERVERPLUGINAMDGPU_H + +#include "Plugins/Process/gdb-remote/LLDBServerPlugin.h" +#include "lldb/Utility/Status.h" + +#include "Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h" +#include "ProcessAMDGPU.h" +#include + +namespace lldb_private { + +class TCPSocket; + +namespace lldb_server { + +class GPUIOObject : public IOObject { +public: + GPUIOObject(int notifier_fd) + : lldb_private::IOObject(eFDTypeSocket), m_notifier_fd(notifier_fd) {} + + Status Read(void *buf, size_t &num_bytes) override { + Status error; + return error; + } + Status Write(const void *buf, size_t &num_bytes) override { + Status error; + return error; + } + virtual bool IsValid() const override { return true; } + virtual Status Close() override { + Status error; + return error; + } + + virtual WaitableHandle GetWaitableHandle() override { return m_notifier_fd; } + +private: + int m_notifier_fd = -1; +}; + +class LLDBServerPluginAMDGPU : public LLDBServerPlugin { +public: + LLDBServerPluginAMDGPU(LLDBServerPlugin::GDBServer &native_process, + MainLoop &main_loop); + ~LLDBServerPluginAMDGPU() override; + llvm::StringRef GetPluginName() override; + int GetEventFileDescriptorAtIndex(size_t idx) override; + bool HandleEventFileDescriptorEvent(int fd) override; + GPUActions GetInitializeActions() override; + std::optional NativeProcessIsStopping() override; + llvm::Expected + BreakpointWasHit(GPUPluginBreakpointHitArgs &args) override; + + NativeProcessProtocol *GetNativeProcess() { + return m_native_process.GetCurrentProcess(); + } + ProcessAMDGPU *GetGPUProcess() { + return (ProcessAMDGPU *)m_gdb_server->GetCurrentProcess(); + } + + bool CreateGPUBreakpoint(uint64_t addr); + + // TODO: make this private + struct GPUInternalBreakpoinInfo { + uint64_t addr; + amd_dbgapi_breakpoint_id_t breakpoind_id; + }; + std::optional m_gpu_internal_bp; + bool m_wait_for_gpu_internal_bp_stop = false; + amd_dbgapi_architecture_id_t m_architecture_id = AMD_DBGAPI_ARCHITECTURE_NONE; + +private: + std::optional CreateConnection(); + void CloseFDs(); + void AcceptAndMainLoopThread(std::unique_ptr listen_socket_up); + + bool initRocm(); + bool HandleGPUInternalBreakpointHit(const GPUInternalBreakpoinInfo &bp, + bool &has_new_libraries); + amd_dbgapi_event_id_t + process_event_queue(amd_dbgapi_event_kind_t until_event_kind); + bool processGPUEvent(); + bool SetGPUBreakpoint(uint64_t addr, const uint8_t *bp_instruction, + size_t size); + + // Used with a socketpair to get events on the native ptrace event queue. + int m_fds[2] = {-1, -1}; + Status m_main_loop_status; + MainLoopBase::ReadHandleUP m_gpu_event_read_up; + std::vector m_read_handles; + std::unique_ptr m_listen_socket; // Keep socket alive for main_loop + std::shared_ptr m_gpu_event_io_obj_sp; + + amd_dbgapi_process_id_t m_gpu_pid = AMD_DBGAPI_PROCESS_NONE; + int m_notifier_fd = -1; +}; + +} // namespace lldb_server +} // namespace lldb_private + +#endif // LLDB_TOOLS_LLDB_SERVER_LLDBSERVERPLUGINAMDGPU_H diff --git a/lldb/tools/lldb-server/Plugins/AMDGPU/ProcessAMDGPU.cpp b/lldb/tools/lldb-server/Plugins/AMDGPU/ProcessAMDGPU.cpp new file mode 100644 index 0000000000000..a5bce9ece9fd8 --- /dev/null +++ b/lldb/tools/lldb-server/Plugins/AMDGPU/ProcessAMDGPU.cpp @@ -0,0 +1,523 @@ +//===-- ProcessAMDGPU.cpp --------------------------------------*- C++ -*-===// +// +// 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 "ProcessAMDGPU.h" +#include "ThreadAMDGPU.h" + +#include "LLDBServerPluginAMDGPU.h" +#include "Plugins/Process/gdb-remote/ProcessGDBRemoteLog.h" +#include "lldb/Host/ProcessLaunchInfo.h" +#include "lldb/Utility/ProcessInfo.h" +#include "lldb/Utility/Status.h" +#include "lldb/Utility/UnimplementedError.h" +#include "llvm/Support/Error.h" + +#include +#include + +using namespace lldb; +using namespace lldb_private; +using namespace lldb_private::lldb_server; +using namespace lldb_private::process_gdb_remote; + +ProcessAMDGPU::ProcessAMDGPU(lldb::pid_t pid, NativeDelegate &delegate, + LLDBServerPluginAMDGPU *plugin) + : NativeProcessProtocol(pid, -1, delegate), m_debugger(plugin) { + m_state = eStateStopped; + UpdateThreads(); +} + +Status ProcessAMDGPU::Resume(const ResumeActionList &resume_actions) { + SetState(StateType::eStateRunning, true); + ThreadAMDGPU *thread = (ThreadAMDGPU *)GetCurrentThread(); + thread->GetRegisterContext().InvalidateAllRegisters(); + // if (!m_debugger->resume_process()) { + // return Status::FromErrorString("resume_process failed"); + // } + return Status(); +} + +Status ProcessAMDGPU::Halt() { + SetState(StateType::eStateStopped, true); + return Status(); +} + +Status ProcessAMDGPU::Detach() { + SetState(StateType::eStateDetached, true); + return Status(); +} + +/// Sends a process a UNIX signal \a signal. +/// +/// \return +/// Returns an error object. +Status ProcessAMDGPU::Signal(int signo) { + return Status::FromErrorString("unimplemented"); +} + +/// Tells a process to interrupt all operations as if by a Ctrl-C. +/// +/// The default implementation will send a local host's equivalent of +/// a SIGSTOP to the process via the NativeProcessProtocol::Signal() +/// operation. +/// +/// \return +/// Returns an error object. +Status ProcessAMDGPU::Interrupt() { return Status(); } + +Status ProcessAMDGPU::Kill() { return Status(); } + +Status ProcessAMDGPU::ReadMemory(lldb::addr_t addr, void *buf, size_t size, + size_t &bytes_read) { + return Status::FromErrorString("unimplemented"); +} + +Status ProcessAMDGPU::WriteMemory(lldb::addr_t addr, const void *buf, + size_t size, size_t &bytes_written) { + return Status::FromErrorString("unimplemented"); +} + +lldb::addr_t ProcessAMDGPU::GetSharedLibraryInfoAddress() { + return LLDB_INVALID_ADDRESS; +} + +size_t ProcessAMDGPU::UpdateThreads() { + if (m_threads.empty()) { + lldb::tid_t tid = 3456; + m_threads.push_back(std::make_unique(*this, 3456)); + // ThreadAMDGPU &thread = static_cast(*m_threads.back()); + SetCurrentThreadID(tid); + } + return m_threads.size(); +} + +const ArchSpec &ProcessAMDGPU::GetArchitecture() const { + m_arch = ArchSpec("amdgpu"); + return m_arch; +} + +// Breakpoint functions +Status ProcessAMDGPU::SetBreakpoint(lldb::addr_t addr, uint32_t size, + bool hardware) { + bool success = m_debugger->CreateGPUBreakpoint(addr); + if (!success) { + return Status::FromErrorString("CreateGPUBreakpoint failed"); + } + return Status(); +} + +llvm::ErrorOr> +ProcessAMDGPU::GetAuxvData() const { + return nullptr; // TODO: try to return + // llvm::make_error(); +} + +Status ProcessAMDGPU::GetLoadedModuleFileSpec(const char *module_path, + FileSpec &file_spec) { + return Status::FromErrorString("unimplemented"); +} + +Status ProcessAMDGPU::GetFileLoadAddress(const llvm::StringRef &file_name, + lldb::addr_t &load_addr) { + return Status::FromErrorString("unimplemented"); +} + +void ProcessAMDGPU::SetLaunchInfo(ProcessLaunchInfo &launch_info) { + static_cast(m_process_info) = + static_cast(launch_info); +} + +bool ProcessAMDGPU::GetProcessInfo(ProcessInstanceInfo &proc_info) { + Log *log = GetLog(GDBRLog::Plugin); + LLDB_LOGF(log, "ProcessAMDGPU::%s() entered", __FUNCTION__); + m_process_info.SetProcessID(m_pid); + m_process_info.SetArchitecture(GetArchitecture()); + proc_info = m_process_info; + return true; +} + +std::optional +ParseLibraryInfo(llvm::StringRef lib_spec) { + // This function will parse the shared library string that AMDs GPU driver + // sends to the debugger. The format is one of: + // file://#offset=&size= + // memory://#offset=&size= + lldb_private::GPUDynamicLoaderLibraryInfo lib_info; + lib_info.load = true; + + auto get_offset_and_size = [](llvm::StringRef &values, + std::optional &offset, + std::optional &size) { + offset = std::nullopt; + size = std::nullopt; + llvm::StringRef value; + uint64_t uint_value; + std::tie(value, values) = values.split('&'); + while (!value.empty()) { + if (value.consume_front("offset=")) { + if (!value.getAsInteger(0, uint_value)) + offset = uint_value; + } else if (value.consume_front("size=")) { + if (!value.getAsInteger(0, uint_value)) + size = uint_value; + } + std::tie(value, values) = values.split('&'); + } + }; + + if (lib_spec.consume_front("file://")) { + llvm::StringRef path, values; + std::tie(path, values) = lib_spec.split('#'); + if (path.empty()) + return std::nullopt; + get_offset_and_size(values, lib_info.file_offset, lib_info.file_size); + } else if (lib_spec.consume_front("memory://")) { + llvm::StringRef name, values; + std::tie(name, values) = lib_spec.split('#'); + if (name.empty()) + return std::nullopt; + lib_info.pathname = name.str(); + get_offset_and_size(values, lib_info.native_memory_address, + lib_info.native_memory_size); + // We must have a valid address and size for memory objects. + if (!(lib_info.native_memory_address.has_value() && + lib_info.native_memory_size.has_value())) + return std::nullopt; + } else { + return std::nullopt; + } + // TODO: do we need this expanding into a URL or is this for JSON? + lib_info.pathname.clear(); + for (char c : path) { + if (c == '#') + lib_info.pathname += "%23"; + else if (c == '$') + lib_info.pathname += "%24"; + else if (c == '}') + lib_info.pathname += "%7D"; + else if (c == '&') + lib_info.pathname += "&"; + else + lib_info.pathname += c; + } + + return lib_info; +} + + +std::optional +ProcessAMDGPU::GetGPUDynamicLoaderLibraryInfos( + const GPUDynamicLoaderArgs &args) { + Log *log = GetLog(GDBRLog::Plugin); + LLDB_LOGF(log, "ProcessAMDGPU::%s() entered", __FUNCTION__); + + GPUDynamicLoaderResponse response; + + const auto &gpu_modules = m_gpu_modules; + + LLDB_LOGF(log, "ProcessAMDGPU::%s() found %zu GPU modules", __FUNCTION__, + gpu_modules.size()); + + // Convert each GPU module to an SVR4LibraryInfo object + for (const auto &[addr, module] : gpu_modules) { + if (module.is_loaded) { + if (auto lib_info = ParseLibraryInfo(module.path)) { + LLDB_LOGF(log, + "ProcessAMDGPU::%s() adding library: path=%s, addr=0x%" PRIx64 + ", offset=%" PRIu64 ", size=%" PRIu64, + __FUNCTION__, lib_info->pathname.c_str(), + lib_info->load_address.value(), lib_info->file_offset.value(), + lib_info->file_size.value()); + response.library_infos.push_back(*lib_info); + } else { + LLDB_LOGF(log, + "ProcessAMDGPU::%s() failed to parse module path \"%s\"", + __FUNCTION__, module.path.c_str()); + } + } + } + + return response; +} + +llvm::Expected> +ProcessManagerAMDGPU::Launch( + ProcessLaunchInfo &launch_info, + NativeProcessProtocol::NativeDelegate &native_delegate) { + lldb::pid_t pid = launch_info.GetProcessID(); + auto proc_up = + std::make_unique(pid, native_delegate, m_debugger); + proc_up->SetLaunchInfo(launch_info); + return proc_up; +} + +llvm::Expected> +ProcessManagerAMDGPU::Attach( + lldb::pid_t pid, NativeProcessProtocol::NativeDelegate &native_delegate) { + return llvm::createStringError("Unimplemented function"); +} + +bool ProcessAMDGPU::handleWaveStop(amd_dbgapi_event_id_t eventId) { + amd_dbgapi_wave_id_t wave_id; + auto status = amd_dbgapi_event_get_info(eventId, AMD_DBGAPI_EVENT_INFO_WAVE, + sizeof(wave_id), &wave_id); + if (status != AMD_DBGAPI_STATUS_SUCCESS) { + LLDB_LOGF(GetLog(GDBRLog::Plugin), "amd_dbgapi_event_get_info failed: %d", + status); + return false; + } + amd_dbgapi_wave_stop_reasons_t stop_reason; + status = amd_dbgapi_wave_get_info(wave_id, AMD_DBGAPI_WAVE_INFO_STOP_REASON, + sizeof(stop_reason), &stop_reason); + if (status != AMD_DBGAPI_STATUS_SUCCESS) { + LLDB_LOGF(GetLog(GDBRLog::Plugin), "amd_dbgapi_wave_get_info failed: %d", + status); + return false; + } + if ((stop_reason & AMD_DBGAPI_WAVE_STOP_REASON_BREAKPOINT) != 0) { + // auto ip = getPC(); + uint64_t pc; + status = amd_dbgapi_wave_get_info(wave_id, AMD_DBGAPI_WAVE_INFO_PC, + sizeof(pc), &pc); + if (status != AMD_DBGAPI_STATUS_SUCCESS) { + LLDB_LOGF(GetLog(GDBRLog::Plugin), "amd_dbgapi_wave_get_info failed: %d", + status); + exit(-1); + } + pc -= 4; + amd_dbgapi_register_id_t pc_register_id; + status = amd_dbgapi_architecture_get_info( + m_debugger->m_architecture_id, AMD_DBGAPI_ARCHITECTURE_INFO_PC_REGISTER, + sizeof(pc_register_id), &pc_register_id); + if (status != AMD_DBGAPI_STATUS_SUCCESS) { + LLDB_LOGF(GetLog(GDBRLog::Plugin), + "amd_dbgapi_architecture_get_info failed: %d", status); + exit(-1); + } + status = + amd_dbgapi_write_register(wave_id, pc_register_id, 0, sizeof(pc), &pc); + if (status != AMD_DBGAPI_STATUS_SUCCESS) { + LLDB_LOGF(GetLog(GDBRLog::Plugin), "amd_dbgapi_write_register failed: %d", + status); + exit(-1); + } + // RemoveGPUBreakpoint(pc); + // auto thread = std::make_unique(*this, wave_id.handle, + // wave_id); thread->SetStopReason(lldb::eStopReasonBreakpoint); + m_wave_ids.emplace_back(wave_id); + + if (m_threads.size() == 1 && m_gpu_state == State::Initializing) { + m_threads.clear(); + SetCurrentThreadID(wave_id.handle); + } + + LLDB_LOGF(GetLog(GDBRLog::Plugin), + "Wave stopped due to breakpoint at: 0x%" PRIx64 + " with wave id: %" PRIu64 " " + "event id: %" PRIu64, + pc, wave_id.handle, eventId.handle); + return true; + } else { + LLDB_LOGF(GetLog(GDBRLog::Plugin), "Wave stopped due to unknown reason: %d", + stop_reason); + } + return false; +} + +static const char *event_kind_str(amd_dbgapi_event_kind_t kind) { + switch (kind) { + case AMD_DBGAPI_EVENT_KIND_NONE: + return "NONE"; + + case AMD_DBGAPI_EVENT_KIND_WAVE_STOP: + return "WAVE_STOP"; + + case AMD_DBGAPI_EVENT_KIND_WAVE_COMMAND_TERMINATED: + return "WAVE_COMMAND_TERMINATED"; + + case AMD_DBGAPI_EVENT_KIND_CODE_OBJECT_LIST_UPDATED: + return "CODE_OBJECT_LIST_UPDATED"; + + case AMD_DBGAPI_EVENT_KIND_BREAKPOINT_RESUME: + return "BREAKPOINT_RESUME"; + + case AMD_DBGAPI_EVENT_KIND_RUNTIME: + return "RUNTIME"; + + case AMD_DBGAPI_EVENT_KIND_QUEUE_ERROR: + return "QUEUE_ERROR"; + } + assert(false && "unhandled amd_dbgapi_event_kind_t value"); +} + +bool ProcessAMDGPU::handleDebugEvent(amd_dbgapi_event_id_t eventId, + amd_dbgapi_event_kind_t eventKind) { + LLDB_LOGF(GetLog(GDBRLog::Plugin), "handleDebugEvent(%" PRIu64 ", %s)", + eventId.handle, event_kind_str(eventKind)); + bool result = false; + if (eventKind == AMD_DBGAPI_EVENT_KIND_NONE) + return result; + + amd_dbgapi_runtime_state_t runtimeState = AMD_DBGAPI_RUNTIME_STATE_UNLOADED; + + // Get runtime state for the event + amd_dbgapi_status_t status = + amd_dbgapi_event_get_info(eventId, AMD_DBGAPI_EVENT_INFO_RUNTIME_STATE, + sizeof(runtimeState), &runtimeState); + + if (status == AMD_DBGAPI_STATUS_SUCCESS) { + // Handle different runtime states + switch (runtimeState) { + case AMD_DBGAPI_RUNTIME_STATE_LOADED_SUCCESS: + LLDB_LOGF(GetLog(GDBRLog::Plugin), "Runtime loaded successfully"); + break; + case AMD_DBGAPI_RUNTIME_STATE_LOADED_ERROR_RESTRICTION: + LLDB_LOGF(GetLog(GDBRLog::Plugin), "Runtime load restricted"); + break; + case AMD_DBGAPI_RUNTIME_STATE_UNLOADED: + LLDB_LOGF(GetLog(GDBRLog::Plugin), "Runtime unloaded"); + break; + } + } + + // Handle event kinds + switch (eventKind) { + case AMD_DBGAPI_EVENT_KIND_WAVE_STOP: { + LLDB_LOGF(GetLog(GDBRLog::Plugin), "Wave stop event received"); + + // Handle wave stop + result = handleWaveStop(eventId); + m_gpu_state = State::GPUStopped; + break; + } + + // case AMD_DBGAPI_EVENT_KIND_BREAKPOINT: { + // std::cout << "Breakpoint event received" << std::endl; + + // // Get breakpoint information for this event + // amd_dbgapi_breakpoint_id_t breakpointId; + // if (amd_dbgapi_event_get_info(eventId, + // AMD_DBGAPI_EVENT_INFO_BREAKPOINT, + // sizeof(breakpointId), &breakpointId) + // == + // AMD_DBGAPI_STATUS_SUCCESS) { + // std::cout << "Breakpoint ID: " << breakpointId << std::endl; + // } + // break; + // } + + case AMD_DBGAPI_EVENT_KIND_RUNTIME: { + LLDB_LOGF(GetLog(GDBRLog::Plugin), + "Runtime event received, runtimeState: %d", runtimeState); + + // Additional runtime-specific handling based on state + if (runtimeState == AMD_DBGAPI_RUNTIME_STATE_LOADED_SUCCESS) { + // Runtime is now loaded, we can set breakpoints or perform other + // initialization + } + break; + } + + case AMD_DBGAPI_EVENT_KIND_CODE_OBJECT_LIST_UPDATED: { + LLDB_LOGF(GetLog(GDBRLog::Plugin), "Code object event received"); + + amd_dbgapi_code_object_id_t *code_object_list; + size_t count; + + amd_dbgapi_process_id_t gpu_pid{GetID()}; + amd_dbgapi_status_t status = amd_dbgapi_process_code_object_list( + gpu_pid, &count, &code_object_list, nullptr); + if (status != AMD_DBGAPI_STATUS_SUCCESS) { + LLDB_LOGF(GetLog(GDBRLog::Plugin), "Failed to get code object list: %d", + status); + return result; + } + + m_gpu_modules.clear(); + for (size_t i = 0; i < count; ++i) { + uint64_t l_addr; + char *uri_bytes; + + status = amd_dbgapi_code_object_get_info( + code_object_list[i], AMD_DBGAPI_CODE_OBJECT_INFO_LOAD_ADDRESS, + sizeof(l_addr), &l_addr); + if (status != AMD_DBGAPI_STATUS_SUCCESS) + continue; + + status = amd_dbgapi_code_object_get_info( + code_object_list[i], AMD_DBGAPI_CODE_OBJECT_INFO_URI_NAME, + sizeof(uri_bytes), &uri_bytes); + if (status != AMD_DBGAPI_STATUS_SUCCESS) + continue; + + LLDB_LOGF(GetLog(GDBRLog::Plugin), + "Code object %zu: %s at address %" PRIu64, i, uri_bytes, + l_addr); + + if (m_gpu_modules.find(l_addr) == m_gpu_modules.end()) { + GPUModule mod = parseCodeObjectUrl(uri_bytes, l_addr); + m_gpu_modules[l_addr] = mod; + } + } + break; + } + + default: + LLDB_LOGF(GetLog(GDBRLog::Plugin), "Unknown event kind: %d", eventKind); + break; + } + return result; +} + +ProcessAMDGPU::GPUModule +ProcessAMDGPU::parseCodeObjectUrl(const std::string &url, + uint64_t load_address) { + GPUModule info; + info.path = url; + info.base_address = load_address; + info.offset = 0; + info.size = 0; + info.is_loaded = true; + + // Find offset parameter + size_t offset_pos = url.find("#offset="); + if (offset_pos != std::string::npos) { + offset_pos += 8; // Skip "#offset=" + size_t amp_pos = url.find('&', offset_pos); + std::string offset_str; + + if (amp_pos != std::string::npos) { + offset_str = url.substr(offset_pos, amp_pos - offset_pos); + } else { + offset_str = url.substr(offset_pos); + } + + // Handle hex format (0x prefix) + if (offset_str.substr(0, 2) == "0x") { + info.offset = std::stoull(offset_str.substr(2), nullptr, 16); + } else { + info.offset = std::stoull(offset_str); + } + } + + // Find size parameter + size_t size_pos = url.find("&size="); + if (size_pos != std::string::npos) { + size_pos += 6; // Skip "&size=" + std::string size_str = url.substr(size_pos); + info.size = std::stoull(size_str); + } + + return info; +} + +void ProcessAMDGPU::AddThread(amd_dbgapi_wave_id_t wave_id) { + auto thread = std::make_unique(*this, wave_id.handle, wave_id); + thread->SetStopReason(lldb::eStopReasonBreakpoint); + m_threads.emplace_back(std::move(thread)); +} diff --git a/lldb/tools/lldb-server/Plugins/AMDGPU/ProcessAMDGPU.h b/lldb/tools/lldb-server/Plugins/AMDGPU/ProcessAMDGPU.h new file mode 100644 index 0000000000000..7c6d3956c3982 --- /dev/null +++ b/lldb/tools/lldb-server/Plugins/AMDGPU/ProcessAMDGPU.h @@ -0,0 +1,143 @@ +//===-- ProcessAMDGPU.h ----------------------------------------*- C++ -*-===// +// +// 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_TOOLS_LLDB_SERVER_PROCESSAMDGPU_H +#define LLDB_TOOLS_LLDB_SERVER_PROCESSAMDGPU_H + +#include "lldb/Host/common/NativeProcessProtocol.h" +#include "lldb/Utility/ProcessInfo.h" +#include + +namespace lldb_private { +namespace lldb_server { + +class LLDBServerPluginAMDGPU; +/// \class ProcessAMDGPU +/// Abstract class that extends \a NativeProcessProtocol for a mock GPU. This +/// class is used to unit testing the GPU plugins in lldb-server. +class ProcessAMDGPU : public NativeProcessProtocol { + // TODO: change NativeProcessProtocol::GetArchitecture() to return by value + mutable ArchSpec m_arch; + ProcessInstanceInfo m_process_info; + +public: + ProcessAMDGPU(lldb::pid_t pid, NativeDelegate &delegate, LLDBServerPluginAMDGPU *plugin); + + Status Resume(const ResumeActionList &resume_actions) override; + + Status Halt() override; + + Status Detach() override; + + /// Sends a process a UNIX signal \a signal. + /// + /// \return + /// Returns an error object. + Status Signal(int signo) override; + + /// Tells a process to interrupt all operations as if by a Ctrl-C. + /// + /// The default implementation will send a local host's equivalent of + /// a SIGSTOP to the process via the NativeProcessProtocol::Signal() + /// operation. + /// + /// \return + /// Returns an error object. + Status Interrupt() override; + + Status Kill() override; + + Status ReadMemory(lldb::addr_t addr, void *buf, size_t size, + size_t &bytes_read) override; + + Status WriteMemory(lldb::addr_t addr, const void *buf, size_t size, + size_t &bytes_written) override; + + lldb::addr_t GetSharedLibraryInfoAddress() override; + + size_t UpdateThreads() override; + + const ArchSpec &GetArchitecture() const override; + + // Breakpoint functions + Status SetBreakpoint(lldb::addr_t addr, uint32_t size, + bool hardware) override; + + llvm::ErrorOr> + GetAuxvData() const override; + + Status GetLoadedModuleFileSpec(const char *module_path, + FileSpec &file_spec) override; + + Status GetFileLoadAddress(const llvm::StringRef &file_name, + lldb::addr_t &load_addr) override; + + bool GetProcessInfo(ProcessInstanceInfo &info) override; + + // Custom accessors + void SetLaunchInfo(ProcessLaunchInfo &launch_info); + + std::optional + GetGPUDynamicLoaderLibraryInfos(const GPUDynamicLoaderArgs &args) override; + + bool handleWaveStop(amd_dbgapi_event_id_t eventId); + + bool handleDebugEvent(amd_dbgapi_event_id_t eventId, + amd_dbgapi_event_kind_t eventKind); + + struct GPUModule { + std::string path; + uint64_t base_address; + uint64_t offset; + uint64_t size; + bool is_loaded; + }; + std::unordered_map& GetGPUModules() { + return m_gpu_modules; + } + + GPUModule parseCodeObjectUrl(const std::string &url, uint64_t load_address); + void AddThread(amd_dbgapi_wave_id_t wave_id); + + LLDBServerPluginAMDGPU* m_debugger = nullptr; + std::unordered_map m_gpu_modules; + + enum class State { + Initializing, + ModuleLoadStopped, + Running, + GPUStopped, + }; + State m_gpu_state = State::Initializing; + std::vector m_wave_ids; +}; + +class ProcessManagerAMDGPU : public NativeProcessProtocol::Manager { +public: + ProcessManagerAMDGPU(MainLoop &mainloop) + : NativeProcessProtocol::Manager(mainloop) {} + + llvm::Expected> + Launch(ProcessLaunchInfo &launch_info, + NativeProcessProtocol::NativeDelegate &native_delegate) override; + + NativeProcessProtocol::Extension GetSupportedExtensions() const override { + return NativeProcessProtocol::Extension::gpu_dyld; + } + + llvm::Expected> + Attach(lldb::pid_t pid, + NativeProcessProtocol::NativeDelegate &native_delegate) override; + + LLDBServerPluginAMDGPU* m_debugger = nullptr; +}; + +} // namespace lldb_server +} // namespace lldb_private + +#endif diff --git a/lldb/tools/lldb-server/Plugins/AMDGPU/RegisterContextAMDGPU.cpp b/lldb/tools/lldb-server/Plugins/AMDGPU/RegisterContextAMDGPU.cpp new file mode 100644 index 0000000000000..670a0eae75df2 --- /dev/null +++ b/lldb/tools/lldb-server/Plugins/AMDGPU/RegisterContextAMDGPU.cpp @@ -0,0 +1,519 @@ +//===-- RegisterContextAMDGPU.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 "RegisterContextAMDGPU.h" + +#include "LLDBServerPluginAMDGPU.h" +#include "ProcessAMDGPU.h" +#include "ThreadAMDGPU.h" +#include "lldb/Host/HostInfo.h" +#include "lldb/Host/common/NativeProcessProtocol.h" +#include "lldb/Host/common/NativeThreadProtocol.h" +#include "lldb/Utility/DataBufferHeap.h" +#include "lldb/Utility/RegisterValue.h" +#include "lldb/Utility/Status.h" + +#include "Plugins/Process/gdb-remote/ProcessGDBRemoteLog.h" +#include +#include + +using namespace lldb; +using namespace lldb_private; +using namespace lldb_private::lldb_server; +using namespace lldb_private::process_gdb_remote; + +static size_t kNumRegs = 0; +static std::vector g_reg_sets; +/// Define all of the information about all registers. The register info structs +/// are accessed by the LLDB register numbers, which are defined above. +static std::vector g_reg_infos; +size_t g_register_buffer_size = 0; +static uint32_t s_gpu_pc_reg_num = 0; +static std::unordered_map + g_lldb_num_to_amd_reg_id; + +bool RegisterContextAMDGPU::InitRegisterInfos() { + if (!g_reg_infos.empty()) + return true; + amd_dbgapi_status_t status; + ThreadAMDGPU *thread = (ThreadAMDGPU *)&m_thread; + amd_dbgapi_architecture_id_t architecture_id = + thread->GetProcess().m_debugger->m_architecture_id; + // Define custom hash functions for register IDs + struct RegisterClassIdHash { + std::size_t operator()(const amd_dbgapi_register_class_id_t &id) const { + return std::hash{}(id.handle); + } + }; + struct RegisterClassIdEqual { + bool operator()(const amd_dbgapi_register_class_id_t &lhs, + const amd_dbgapi_register_class_id_t &rhs) const { + return lhs.handle == rhs.handle; + } + }; + + struct RegisterIdHash { + std::size_t operator()(const amd_dbgapi_register_id_t &id) const { + return std::hash{}(id.handle); + } + }; + struct RegisterIdEqual { + bool operator()(const amd_dbgapi_register_id_t &lhs, + const amd_dbgapi_register_id_t &rhs) const { + return lhs.handle == rhs.handle; + } + }; + + /* Get register class ids. */ + size_t register_class_count; + amd_dbgapi_register_class_id_t *register_class_ids; + status = amd_dbgapi_architecture_register_class_list( + architecture_id, ®ister_class_count, ®ister_class_ids); + if (status != AMD_DBGAPI_STATUS_SUCCESS) { + LLDB_LOGF(GetLog(GDBRLog::Plugin), + "Failed to get register class list from amd-dbgapi"); + return false; + } + + // Get register class names. + std::unordered_map + register_class_names; + for (size_t i = 0; i < register_class_count; ++i) { + char *bytes; + status = amd_dbgapi_architecture_register_class_get_info( + register_class_ids[i], AMD_DBGAPI_REGISTER_CLASS_INFO_NAME, + sizeof(bytes), &bytes); + if (status != AMD_DBGAPI_STATUS_SUCCESS) { + LLDB_LOGF(GetLog(GDBRLog::Plugin), + "Failed to get register class name from amd-dbgapi"); + return false; + } + + // gdb::unique_xmalloc_ptr name(bytes); + register_class_names.emplace(register_class_ids[i], bytes); + } + + /* Get all register count. */ + size_t register_count; + amd_dbgapi_register_id_t *register_ids; + status = amd_dbgapi_architecture_register_list( + architecture_id, ®ister_count, ®ister_ids); + if (status != AMD_DBGAPI_STATUS_SUCCESS) { + LLDB_LOGF(GetLog(GDBRLog::Plugin), + "Failed to get register list from amd-dbgapi"); + return false; + } + kNumRegs = register_count; + + std::unordered_map, RegisterClassIdHash, + RegisterClassIdEqual> + register_class_to_register_ids; + for (size_t i = 0; i < register_class_count; ++i) { + for (size_t j = 0; j < register_count; ++j) { + amd_dbgapi_register_class_state_t register_class_state; + status = amd_dbgapi_register_is_in_register_class( + register_class_ids[i], register_ids[j], ®ister_class_state); + if (status == AMD_DBGAPI_STATUS_SUCCESS && + register_class_state == AMD_DBGAPI_REGISTER_CLASS_STATE_MEMBER) { + register_class_to_register_ids[register_class_ids[i]].push_back( + register_ids[j]); + break; // TODO: can a register be in multiple classes? + } + } + } + + std::vector all_register_properties; + all_register_properties.resize(register_count); + for (size_t regnum = 0; regnum < register_count; ++regnum) { + auto ®ister_properties = all_register_properties[regnum]; + if (amd_dbgapi_register_get_info( + register_ids[regnum], AMD_DBGAPI_REGISTER_INFO_PROPERTIES, + sizeof(register_properties), + ®ister_properties) != AMD_DBGAPI_STATUS_SUCCESS) { + LLDB_LOGF(GetLog(GDBRLog::Plugin), + "Failed to get register properties from amd-dbgapi"); + return false; + } + } + + std::vector dwarf_regnum_to_gdb_regnum; + std::unordered_map + register_names; + for (size_t i = 0; i < register_count; ++i) { + /* Get register name. */ + char *bytes; + status = amd_dbgapi_register_get_info( + register_ids[i], AMD_DBGAPI_REGISTER_INFO_NAME, sizeof(bytes), &bytes); + if (status == AMD_DBGAPI_STATUS_SUCCESS) { + register_names[register_ids[i]] = bytes; + free(bytes); + } + + /* Get register DWARF number. */ + uint64_t dwarf_num; + status = amd_dbgapi_register_get_info(register_ids[i], + AMD_DBGAPI_REGISTER_INFO_DWARF, + sizeof(dwarf_num), &dwarf_num); + if (status == AMD_DBGAPI_STATUS_SUCCESS) { + if (dwarf_num >= dwarf_regnum_to_gdb_regnum.size()) + dwarf_regnum_to_gdb_regnum.resize(dwarf_num + 1, -1); + + dwarf_regnum_to_gdb_regnum[dwarf_num] = i; + } + } + + amd_dbgapi_register_id_t pc_register_id; + status = amd_dbgapi_architecture_get_info( + architecture_id, AMD_DBGAPI_ARCHITECTURE_INFO_PC_REGISTER, + sizeof(pc_register_id), &pc_register_id); + if (status != AMD_DBGAPI_STATUS_SUCCESS) { + LLDB_LOGF(GetLog(GDBRLog::Plugin), + "Failed to get PC register from amd-dbgapi"); + return false; + } + // Initialize g_reg_infos with register information from AMD dbgapi + g_reg_infos.resize(register_count); + + // Map from register class ID to register numbers for that class + std::unordered_map, + RegisterClassIdHash, RegisterClassIdEqual> + register_class_to_lldb_regnums; + // Populate g_reg_infos with register information from AMD dbgapi + for (size_t i = 0; i < register_count; ++i) { + amd_dbgapi_register_id_t reg_id = register_ids[i]; + RegisterInfo ®_info = g_reg_infos[i]; + + // Set register name from AMD dbgapi + auto name_it = register_names.find(reg_id); + if (name_it != register_names.end()) { + reg_info.name = strdup(name_it->second.c_str()); + reg_info.alt_name = nullptr; + } else { + // Fallback name if not found + char name[16]; + snprintf(name, sizeof(name), "reg%zu", i); + reg_info.name = strdup(name); + reg_info.alt_name = nullptr; + } + + // Get register size from AMD dbgapi + uint64_t reg_size; + status = amd_dbgapi_register_get_info(reg_id, AMD_DBGAPI_REGISTER_INFO_SIZE, + sizeof(reg_size), ®_size); + if (status == AMD_DBGAPI_STATUS_SUCCESS) { + reg_info.byte_size = reg_size; + } else { + reg_info.byte_size = 8; // Default to 64-bit registers + } + reg_info.byte_offset = g_register_buffer_size; // Simple offset calculation + g_register_buffer_size += reg_info.byte_size; + + // Set encoding and format based on register name + std::string reg_name = + name_it != register_names.end() ? name_it->second : ""; + + // Check if register name contains indicators of its type + // TODO: is this the correct way to do this? + if (reg_name.find("float") != std::string::npos || + reg_name.find("fp") != std::string::npos) { + reg_info.encoding = eEncodingIEEE754; + reg_info.format = eFormatFloat; + } else if (reg_name.find("vec") != std::string::npos || + reg_name.find("simd") != std::string::npos) { + reg_info.encoding = eEncodingVector; + reg_info.format = eFormatVectorOfUInt8; + } else if (reg_info.byte_size > 8) { + // TODO: check AMD_DBGAPI_REGISTER_INFO_TYPE and assign encoding/format. + reg_info.encoding = eEncodingVector; + reg_info.format = eFormatVectorOfUInt8; + } else { + // Default for other types + reg_info.encoding = eEncodingUint; + reg_info.format = eFormatHex; + } + + // Set register kinds + reg_info.kinds[eRegisterKindLLDB] = i; // LLDB register number is the index + g_lldb_num_to_amd_reg_id[i] = + reg_id; // Map from LLDB register number to AMD + + // Set DWARF register number if available + uint64_t dwarf_num; + status = amd_dbgapi_register_get_info( + reg_id, AMD_DBGAPI_REGISTER_INFO_DWARF, sizeof(dwarf_num), &dwarf_num); + if (status == AMD_DBGAPI_STATUS_SUCCESS) { + reg_info.kinds[eRegisterKindDWARF] = dwarf_num; + } else { + reg_info.kinds[eRegisterKindDWARF] = LLDB_INVALID_REGNUM; + } + + // Set EH_FRAME register number (same as DWARF for now) + reg_info.kinds[eRegisterKindEHFrame] = reg_info.kinds[eRegisterKindDWARF]; + + // Set generic register kind + reg_info.kinds[eRegisterKindGeneric] = LLDB_INVALID_REGNUM; + + // Check if this is the PC register + if (reg_id.handle == pc_register_id.handle) { + reg_info.kinds[eRegisterKindGeneric] = LLDB_REGNUM_GENERIC_PC; + s_gpu_pc_reg_num = i; + } + + // Add this register indices belong to its register classes + for (size_t j = 0; j < register_class_count; ++j) { + amd_dbgapi_register_class_state_t register_class_state; + status = amd_dbgapi_register_is_in_register_class( + register_class_ids[j], reg_id, ®ister_class_state); + if (status == AMD_DBGAPI_STATUS_SUCCESS && + register_class_state == AMD_DBGAPI_REGISTER_CLASS_STATE_MEMBER) { + register_class_to_lldb_regnums[register_class_ids[j]].push_back(i); + } + } + } + + // Create register sets from register classes + g_reg_sets.clear(); + + for (size_t i = 0; i < register_class_count; ++i) { + auto class_id = register_class_ids[i]; + auto name_it = register_class_names.find(class_id); + if (name_it == register_class_names.end()) { + continue; // Skip if no name found + } + + auto regnums_it = register_class_to_lldb_regnums.find(class_id); + if (regnums_it == register_class_to_lldb_regnums.end() || + regnums_it->second.empty()) { + continue; // Skip if no registers in this class + } + + // Create a new register set for this class + RegisterSet reg_set; + reg_set.name = strdup(name_it->second.c_str()); + + // Create short name from the full name (use first word or first few chars) + std::string short_name = name_it->second; + size_t space_pos = short_name.find(' '); + if (space_pos != std::string::npos) { + short_name = short_name.substr(0, space_pos); + } else if (short_name.length() > 3) { + short_name = short_name.substr(0, 3); + } + std::transform(short_name.begin(), short_name.end(), short_name.begin(), + ::tolower); + reg_set.short_name = strdup(short_name.c_str()); + + // Get register numbers for this class + const auto ®nums = regnums_it->second; + + // Store register numbers in a static container to ensure they live + // for the duration of the program + static std::vector> all_reg_nums; + all_reg_nums.push_back(regnums); + + // Point the RegisterSet's registers field to the data in our static vector + reg_set.registers = all_reg_nums.back().data(); + reg_set.num_registers = all_reg_nums.back().size(); + g_reg_sets.push_back(reg_set); + } + return true; +} + +RegisterContextAMDGPU::RegisterContextAMDGPU( + NativeThreadProtocol &native_thread) + : NativeRegisterContext(native_thread) { + InitRegisterInfos(); + InitRegisters(); + // Only doing this for the Mock GPU class, don't do this in real GPU classes. + // ReadRegs(); +} + +void RegisterContextAMDGPU::InitRegisters() { + m_regs.data.resize(g_register_buffer_size); + m_regs_valid.resize(kNumRegs, false); +} + +void RegisterContextAMDGPU::InvalidateAllRegisters() { + // Do what ever book keeping we need to do to indicate that all register + // values are now invalid. + for (uint32_t i = 0; i < kNumRegs; ++i) + m_regs_valid[i] = false; +} + +Status RegisterContextAMDGPU::ReadReg(const RegisterInfo *reg_info) { + Status error; + const uint32_t lldb_reg_num = reg_info->kinds[eRegisterKindLLDB]; + assert(lldb_reg_num < kNumRegs); + auto amd_reg_id = g_lldb_num_to_amd_reg_id[lldb_reg_num]; + ThreadAMDGPU *thread = (ThreadAMDGPU *)&m_thread; + auto wave_id = thread->GetWaveId(); + if (!wave_id) { + // Swallow the error because so that we are returning the dummy register + // vlaues. + return error; + } + amd_dbgapi_register_exists_t exists; + amd_dbgapi_status_t amd_status = + amd_dbgapi_wave_register_exists(wave_id.value(), amd_reg_id, &exists); + if (amd_status != AMD_DBGAPI_STATUS_SUCCESS) { + error.FromErrorStringWithFormat( + "Failed to check register %s existence due to error %d", + reg_info->name, amd_status); + return error; + } + if (exists != AMD_DBGAPI_REGISTER_PRESENT) { + error = Status::FromErrorStringWithFormat( + "Failed to read register %s due to register not present", + reg_info->name); + return error; + } + + amd_status = amd_dbgapi_prefetch_register(wave_id.value(), amd_reg_id, + m_regs.data.size() - lldb_reg_num); + if (amd_status != AMD_DBGAPI_STATUS_SUCCESS) { + error = Status::FromErrorStringWithFormat( + "Failed to prefetch register %s due to error %d", reg_info->name, + amd_status); + return error; + } + + // Read the register value + amd_status = amd_dbgapi_read_register( + wave_id.value(), amd_reg_id, 0, reg_info->byte_size, + m_regs.data.data() + reg_info->byte_offset); + if (amd_status != AMD_DBGAPI_STATUS_SUCCESS) { + error = Status::FromErrorStringWithFormat( + "Failed to read register %s due to error %d", reg_info->name, + amd_status); + return error; + } + m_regs_valid[lldb_reg_num] = true; + return error; +} + +Status RegisterContextAMDGPU::ReadRegs() { + ThreadAMDGPU *thread = (ThreadAMDGPU *)&m_thread; + if (thread != nullptr) { + auto wave_id = thread->GetWaveId(); + bool wave_stopped = wave_id.has_value(); + if (wave_stopped) { + for (uint32_t i = 0; i < g_reg_infos.size(); ++i) { + Status error = ReadReg(&g_reg_infos[i]); + if (error.Fail()) + return error; + } + } + } else { + // Fill all registers with unique values. + for (uint32_t i = 0; i < g_reg_infos.size(); ++i) { + memcpy(m_regs.data.data() + g_reg_infos[i].byte_offset, &i, sizeof(i)); + } + } + return Status(); +} + +uint32_t RegisterContextAMDGPU::GetRegisterSetCount() const { + return g_reg_sets.size(); +} + +uint32_t RegisterContextAMDGPU::GetRegisterCount() const { return kNumRegs; } + +uint32_t RegisterContextAMDGPU::GetUserRegisterCount() const { + return GetRegisterCount(); +} + +const RegisterInfo * +RegisterContextAMDGPU::GetRegisterInfoAtIndex(uint32_t reg) const { + if (reg < kNumRegs) + return &g_reg_infos[reg]; + return nullptr; +} + +const RegisterSet * +RegisterContextAMDGPU::GetRegisterSet(uint32_t set_index) const { + if (set_index < GetRegisterSetCount()) + return &g_reg_sets[set_index]; + return nullptr; +} + +Status RegisterContextAMDGPU::ReadRegister(const RegisterInfo *reg_info, + RegisterValue ®_value) { + Status error; + const uint32_t lldb_reg_num = reg_info->kinds[eRegisterKindLLDB]; + if (!m_regs_valid[lldb_reg_num]) { + error = ReadReg(reg_info); + } + if (error.Fail()) + return error; + reg_value.SetBytes(m_regs.data.data() + reg_info->byte_offset, + reg_info->byte_size, lldb::eByteOrderLittle); + return Status(); +} + +Status RegisterContextAMDGPU::WriteRegister(const RegisterInfo *reg_info, + const RegisterValue ®_value) { + const uint32_t lldb_reg_num = reg_info->kinds[eRegisterKindLLDB]; + const void *new_value = reg_value.GetBytes(); + memcpy(m_regs.data.data() + reg_info->byte_offset, new_value, + reg_info->byte_size); + m_regs_valid[lldb_reg_num] = true; + return Status(); +} + +Status RegisterContextAMDGPU::ReadAllRegisterValues( + lldb::WritableDataBufferSP &data_sp) { + ReadRegs(); // Read all registers first + const size_t regs_byte_size = m_regs.data.size(); + data_sp.reset(new DataBufferHeap(regs_byte_size, 0)); + uint8_t *dst = data_sp->GetBytes(); + memcpy(dst, &m_regs.data[0], regs_byte_size); + return Status(); +} + +Status RegisterContextAMDGPU::WriteAllRegisterValues( + const lldb::DataBufferSP &data_sp) { + const size_t regs_byte_size = m_regs.data.size(); + + if (!data_sp) { + return Status::FromErrorStringWithFormat( + "RegisterContextAMDGPU::%s invalid data_sp provided", __FUNCTION__); + } + + if (data_sp->GetByteSize() != regs_byte_size) { + return Status::FromErrorStringWithFormat( + "RegisterContextAMDGPU::%s data_sp contained mismatched " + "data size, expected %" PRIu64 ", actual %" PRIu64, + __FUNCTION__, regs_byte_size, data_sp->GetByteSize()); + } + + const uint8_t *src = data_sp->GetBytes(); + if (src == nullptr) { + return Status::FromErrorStringWithFormat( + "RegisterContextAMDGPU::%s " + "DataBuffer::GetBytes() returned a null " + "pointer", + __FUNCTION__); + } + memcpy(&m_regs.data[0], src, regs_byte_size); + return Status(); +} + +std::vector +RegisterContextAMDGPU::GetExpeditedRegisters(ExpeditedRegs expType) const { + static std::vector g_expedited_regs; + if (g_expedited_regs.empty()) { + // We can't expedite all registers because that would cause jThreadsInfo to + // fetch registers from all stopped waves eagarly which would be too slow + // and unnecessary. + g_expedited_regs.push_back(s_gpu_pc_reg_num); + } + return g_expedited_regs; +} diff --git a/lldb/tools/lldb-server/Plugins/AMDGPU/RegisterContextAMDGPU.h b/lldb/tools/lldb-server/Plugins/AMDGPU/RegisterContextAMDGPU.h new file mode 100644 index 0000000000000..938d6d6bc7e0b --- /dev/null +++ b/lldb/tools/lldb-server/Plugins/AMDGPU/RegisterContextAMDGPU.h @@ -0,0 +1,65 @@ +//===-- RegisterContextAMDGPU.h --------------------------------*- C++ -*-===// +// +// 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_TOOLS_LLDB_SERVER_REGISTERCONTEXTAMDGPU_H +#define LLDB_TOOLS_LLDB_SERVER_REGISTERCONTEXTAMDGPU_H + +// #include "Plugins/Process/Utility/NativeRegisterContextRegisterInfo.h" +#include "lldb/Host/common/NativeRegisterContext.h" +#include "lldb/lldb-forward.h" + +namespace lldb_private { +namespace lldb_server { + +class RegisterContextAMDGPU : public NativeRegisterContext { +public: + RegisterContextAMDGPU(NativeThreadProtocol &native_thread); + + uint32_t GetRegisterCount() const override; + + uint32_t GetUserRegisterCount() const override; + + const RegisterInfo *GetRegisterInfoAtIndex(uint32_t reg) const override; + + uint32_t GetRegisterSetCount() const override; + + const RegisterSet *GetRegisterSet(uint32_t set_index) const override; + + Status ReadRegister(const RegisterInfo *reg_info, + RegisterValue ®_value) override; + + Status WriteRegister(const RegisterInfo *reg_info, + const RegisterValue ®_value) override; + + Status ReadAllRegisterValues(lldb::WritableDataBufferSP &data_sp) override; + + Status WriteAllRegisterValues(const lldb::DataBufferSP &data_sp) override; + + std::vector + GetExpeditedRegisters(ExpeditedRegs expType) const override; + + void InvalidateAllRegisters(); + +private: + bool InitRegisterInfos(); + void InitRegisters(); + + Status ReadRegs(); + Status ReadReg(const RegisterInfo *reg_info); + + // All AMD GPU registers are contained in this buffer. + struct { + std::vector data; + } m_regs; + std::vector m_regs_valid; +}; + +} // namespace lldb_server +} // namespace lldb_private + +#endif // #ifndef LLDB_TOOLS_LLDB_SERVER_REGISTERCONTEXTAMDGPU_H diff --git a/lldb/tools/lldb-server/Plugins/AMDGPU/ThreadAMDGPU.cpp b/lldb/tools/lldb-server/Plugins/AMDGPU/ThreadAMDGPU.cpp new file mode 100644 index 0000000000000..0c724afa95894 --- /dev/null +++ b/lldb/tools/lldb-server/Plugins/AMDGPU/ThreadAMDGPU.cpp @@ -0,0 +1,63 @@ +//===-- ThreadAMDGPU.cpp ------------------------------------- -*- C++ -*-===// +// +// 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 "ThreadAMDGPU.h" +#include "ProcessAMDGPU.h" + +using namespace lldb_private; +using namespace lldb_server; + +ThreadAMDGPU::ThreadAMDGPU(ProcessAMDGPU &process, lldb::tid_t tid, + std::optional wave_id) + : NativeThreadProtocol(process, tid), m_reg_context(*this), + m_wave_id(wave_id) { + m_stop_info.reason = lldb::eStopReasonSignal; + m_stop_info.signo = SIGTRAP; +} + +// NativeThreadProtocol Interface +std::string ThreadAMDGPU::GetName() { + if (!m_wave_id) + return "AMD Native Shadow Thread"; + else + return std::string("AMD GPU Thread ") + + std::to_string(m_wave_id.value().handle); +} + +lldb::StateType ThreadAMDGPU::GetState() { return lldb::eStateStopped; } + +bool ThreadAMDGPU::GetStopReason(ThreadStopInfo &stop_info, + std::string &description) { + stop_info = m_stop_info; + description = m_description; + return true; +} + +Status ThreadAMDGPU::SetWatchpoint(lldb::addr_t addr, size_t size, + uint32_t watch_flags, bool hardware) { + return Status::FromErrorString("unimplemented"); +} + +Status ThreadAMDGPU::RemoveWatchpoint(lldb::addr_t addr) { + return Status::FromErrorString("unimplemented"); +} + +Status ThreadAMDGPU::SetHardwareBreakpoint(lldb::addr_t addr, size_t size) { + return Status::FromErrorString("unimplemented"); +} + +Status ThreadAMDGPU::RemoveHardwareBreakpoint(lldb::addr_t addr) { + return Status::FromErrorString("unimplemented"); +} + +ProcessAMDGPU &ThreadAMDGPU::GetProcess() { + return static_cast(m_process); +} + +const ProcessAMDGPU &ThreadAMDGPU::GetProcess() const { + return static_cast(m_process); +} diff --git a/lldb/tools/lldb-server/Plugins/AMDGPU/ThreadAMDGPU.h b/lldb/tools/lldb-server/Plugins/AMDGPU/ThreadAMDGPU.h new file mode 100644 index 0000000000000..02991a20bfdd9 --- /dev/null +++ b/lldb/tools/lldb-server/Plugins/AMDGPU/ThreadAMDGPU.h @@ -0,0 +1,75 @@ +//===-- ThreadAMDGPU.h --------------------------------------- -*- C++ -*-===// +// +// 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_TOOLS_LLDB_SERVER_THREADAMDGPU_H +#define LLDB_TOOLS_LLDB_SERVER_THREADAMDGPU_H + +#include "RegisterContextAMDGPU.h" +#include "lldb/Host/common/NativeThreadProtocol.h" +#include "lldb/lldb-private-forward.h" +#include +#include + +namespace lldb_private { +namespace lldb_server { +class ProcessAMDGPU; + +class NativeProcessLinux; + +class ThreadAMDGPU : public NativeThreadProtocol { + friend class ProcessAMDGPU; + +public: + ThreadAMDGPU(ProcessAMDGPU &process, lldb::tid_t tid, std::optional wave_id = std::nullopt); + + // NativeThreadProtocol Interface + std::string GetName() override; + + lldb::StateType GetState() override; + + bool GetStopReason(ThreadStopInfo &stop_info, + std::string &description) override; + + void SetStopReason(lldb::StopReason reason) { + m_stop_info.reason = reason; + } + + RegisterContextAMDGPU &GetRegisterContext() override { + return m_reg_context; + } + + Status SetWatchpoint(lldb::addr_t addr, size_t size, uint32_t watch_flags, + bool hardware) override; + + Status RemoveWatchpoint(lldb::addr_t addr) override; + + Status SetHardwareBreakpoint(lldb::addr_t addr, size_t size) override; + + Status RemoveHardwareBreakpoint(lldb::addr_t addr) override; + + ProcessAMDGPU &GetProcess(); + + const ProcessAMDGPU &GetProcess() const; + + std::optional GetWaveId() const { + return m_wave_id; + } + +private: + // Member Variables + lldb::StateType m_state; + ThreadStopInfo m_stop_info; + std::string m_description = ""; + RegisterContextAMDGPU m_reg_context; + std::string m_stop_description; + std::optional m_wave_id; +}; +} // namespace lldb_server +} // namespace lldb_private + +#endif // #ifndef LLDB_TOOLS_LLDB_SERVER_THREADAMDGPU_H diff --git a/lldb/tools/lldb-server/Plugins/CMakeLists.txt b/lldb/tools/lldb-server/Plugins/CMakeLists.txt new file mode 100644 index 0000000000000..e1431e20265f6 --- /dev/null +++ b/lldb/tools/lldb-server/Plugins/CMakeLists.txt @@ -0,0 +1,4 @@ +if(DEFINED ROCM_PATH AND EXISTS ${ROCM_PATH}) + add_subdirectory(AMDGPU) +endif() +add_subdirectory(MockGPU) diff --git a/lldb/tools/lldb-server/Plugins/MockGPU/CMakeLists.txt b/lldb/tools/lldb-server/Plugins/MockGPU/CMakeLists.txt new file mode 100644 index 0000000000000..6edfdc259ac67 --- /dev/null +++ b/lldb/tools/lldb-server/Plugins/MockGPU/CMakeLists.txt @@ -0,0 +1,9 @@ + +add_lldb_library(lldbServerPluginMockGPU + LLDBServerPluginMockGPU.cpp + ProcessMockGPU.cpp + RegisterContextMockGPU.cpp + ThreadMockGPU.cpp +) + +target_include_directories(lldbServerPluginMockGPU PRIVATE "${LLDB_SOURCE_DIR}/source" "../..") diff --git a/lldb/tools/lldb-server/Plugins/MockGPU/LLDBServerPluginMockGPU.cpp b/lldb/tools/lldb-server/Plugins/MockGPU/LLDBServerPluginMockGPU.cpp new file mode 100644 index 0000000000000..8630fa1a669cb --- /dev/null +++ b/lldb/tools/lldb-server/Plugins/MockGPU/LLDBServerPluginMockGPU.cpp @@ -0,0 +1,232 @@ +//===-- LLDBServerPluginMockGPU.cpp -----------------------------*- C++ -*-===// +// +// 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 "LLDBServerPluginMockGPU.h" +#include "ProcessMockGPU.h" +#include "lldb/Host/common/TCPSocket.h" +#include "llvm/Support/Error.h" +#include "Plugins/Process/gdb-remote/ProcessGDBRemoteLog.h" +#include "Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h" +#include "lldb/Host/posix/ConnectionFileDescriptorPosix.h" + +#include +#include +#include +#include +#include + +using namespace lldb; +using namespace lldb_private; +using namespace lldb_private::lldb_server; +using namespace lldb_private::process_gdb_remote; + +LLDBServerPluginMockGPU::LLDBServerPluginMockGPU( + LLDBServerPlugin::GDBServer &native_process, MainLoop &main_loop) + : LLDBServerPlugin(native_process, main_loop) { + m_process_manager_up.reset(new ProcessMockGPU::Manager(m_main_loop)); + m_gdb_server.reset(new GDBRemoteCommunicationServerLLGS( + m_main_loop, *m_process_manager_up, "mock-gpu.server")); + + Log *log = GetLog(GDBRLog::Plugin); + LLDB_LOGF(log, "LLDBServerPluginMockGPU::LLDBServerPluginMockGPU() faking launch..."); + ProcessLaunchInfo info; + info.GetFlags().Set(eLaunchFlagStopAtEntry | eLaunchFlagDebug | + eLaunchFlagDisableASLR); + Args args; + args.AppendArgument("/pretend/path/to/mockgpu"); + args.AppendArgument("--option1"); + args.AppendArgument("--option2"); + args.AppendArgument("--option3"); + info.SetArguments(args, true); + info.GetEnvironment() = Host::GetEnvironment(); + m_gdb_server->SetLaunchInfo(info); + Status error = m_gdb_server->LaunchProcess(); + if (error.Fail()) { + LLDB_LOGF(log, "LLDBServerPluginMockGPU::LLDBServerPluginMockGPU() failed to launch: %s", error.AsCString()); + } else { + LLDB_LOGF(log, "LLDBServerPluginMockGPU::LLDBServerPluginMockGPU() launched successfully"); + } +} + +LLDBServerPluginMockGPU::~LLDBServerPluginMockGPU() { + CloseFDs(); +} + +llvm::StringRef LLDBServerPluginMockGPU::GetPluginName() { + return "mock-gpu"; +} + +void LLDBServerPluginMockGPU::CloseFDs() { + if (m_fds[0] != -1) { + close(m_fds[0]); + m_fds[0] = -1; + } + if (m_fds[1] != -1) { + close(m_fds[1]); + m_fds[1] = -1; + } +} + +int LLDBServerPluginMockGPU::GetEventFileDescriptorAtIndex(size_t idx) { + if (idx != 0) + return -1; + if (m_fds[0] == -1) { + if (socketpair(AF_UNIX, SOCK_STREAM, 0, m_fds) == -1) { + m_fds[0] = -1; + m_fds[1] = -1; + } + } + return m_fds[0]; +} + + +bool LLDBServerPluginMockGPU::HandleEventFileDescriptorEvent(int fd) { + if (fd == m_fds[0]) { + char buf[1]; + // Read 1 bytes from the fd + read(m_fds[0], buf, sizeof(buf)); + return true; + } + return false; +} + +void LLDBServerPluginMockGPU::AcceptAndMainLoopThread( + std::unique_ptr listen_socket_up) { + Log *log = GetLog(GDBRLog::Plugin); + LLDB_LOGF(log, "%s spawned", __PRETTY_FUNCTION__); + Socket *socket = nullptr; + Status error = listen_socket_up->Accept(std::chrono::seconds(30), socket); + // Scope for lock guard. + { + // Protect access to m_is_listening and m_is_connected. + std::lock_guard guard(m_connect_mutex); + m_is_listening = false; + if (error.Fail()) { + LLDB_LOGF(log, "%s error returned from Accept(): %s", __PRETTY_FUNCTION__, + error.AsCString()); + return; + } + m_is_connected = true; + } + + LLDB_LOGF(log, "%s initializing connection", __PRETTY_FUNCTION__); + std::unique_ptr connection_up( + new ConnectionFileDescriptor(std::unique_ptr(socket))); + m_gdb_server->InitializeConnection(std::move(connection_up)); + LLDB_LOGF(log, "%s running main loop", __PRETTY_FUNCTION__); + m_main_loop_status = m_main_loop.Run(); + LLDB_LOGF(log, "%s main loop exited!", __PRETTY_FUNCTION__); + if (m_main_loop_status.Fail()) { + LLDB_LOGF(log, "%s main loop exited with an error: %s", __PRETTY_FUNCTION__, + m_main_loop_status.AsCString()); + + } + // Protect access to m_is_connected. + std::lock_guard guard(m_connect_mutex); + m_is_connected = false; +} + +std::optional LLDBServerPluginMockGPU::CreateConnection() { + std::lock_guard guard(m_connect_mutex); + Log *log = GetLog(GDBRLog::Plugin); + LLDB_LOGF(log, "%s called", __PRETTY_FUNCTION__); + if (m_is_connected) { + LLDB_LOGF(log, "%s already connected", __PRETTY_FUNCTION__); + return std::nullopt; + } + if (m_is_listening) { + LLDB_LOGF(log, "%s already listening", __PRETTY_FUNCTION__); + return std::nullopt; + } + m_is_listening = true; + LLDB_LOGF(log, "%s trying to listen on port 0", __PRETTY_FUNCTION__); + llvm::Expected> sock = + Socket::TcpListen("localhost:0", 5); + if (sock) { + GPUPluginConnectionInfo connection_info; + const uint16_t listen_port = (*sock)->GetLocalPortNumber(); + connection_info.connect_url = llvm::formatv("connect://localhost:{}", + listen_port); + LLDB_LOGF(log, "%s listening to %u", __PRETTY_FUNCTION__, listen_port); + std::thread t(&LLDBServerPluginMockGPU::AcceptAndMainLoopThread, this, + std::move(*sock)); + t.detach(); + return connection_info; + } else { + std::string error = llvm::toString(sock.takeError()); + LLDB_LOGF(log, "%s failed to listen to localhost:0: %s", + __PRETTY_FUNCTION__, error.c_str()); + } + m_is_listening = false; + return std::nullopt; +} + +std::optional LLDBServerPluginMockGPU::NativeProcessIsStopping() { + NativeProcessProtocol *native_process = m_native_process.GetCurrentProcess(); + // Show that we can return a valid GPUActions object from a stop event. + if (native_process->GetStopID() == 3) { + GPUActions actions; + actions.plugin_name = GetPluginName(); + GPUBreakpointInfo bp; + bp.identifier = "3rd stop breakpoint"; + bp.name_info = {"a.out", "gpu_third_stop"}; + actions.breakpoints.emplace_back(std::move(bp)); + return actions; + } + return std::nullopt; +} + +llvm::Expected +LLDBServerPluginMockGPU::BreakpointWasHit(GPUPluginBreakpointHitArgs &args) { + Log *log = GetLog(GDBRLog::Plugin); + std::string json_string; + std::string &bp_identifier = args.breakpoint.identifier; + llvm::raw_string_ostream os(json_string); + os << toJSON(args); + LLDB_LOGF(log, "LLDBServerPluginMockGPU::BreakpointWasHit(\"%s\"):\nJSON:\n%s", + bp_identifier.c_str(), json_string.c_str()); + + GPUPluginBreakpointHitResponse response; + response.actions.plugin_name = GetPluginName(); + if (bp_identifier == "gpu_initialize") { + response.disable_bp = true; + LLDB_LOGF(log, "LLDBServerPluginMockGPU::BreakpointWasHit(\"%s\") disabling breakpoint", + bp_identifier.c_str()); + response.actions.connect_info = CreateConnection(); + + // We asked for the symbol "gpu_shlib_load" to be delivered as a symbol + // value when the "gpu_initialize" breakpoint was set. So we will use this + // to set a breakpoint by address to test setting breakpoints by address. + std::optional gpu_shlib_load_addr = + args.GetSymbolValue("gpu_shlib_load"); + if (gpu_shlib_load_addr) { + GPUBreakpointInfo bp; + bp.identifier = "gpu_shlib_load"; + bp.addr_info = {*gpu_shlib_load_addr}; + bp.symbol_names.push_back("g_shlib_list"); + bp.symbol_names.push_back("invalid_symbol"); + response.actions.breakpoints.emplace_back(std::move(bp)); + } + } else if (bp_identifier == "gpu_shlib_load") { + // Tell the native process to tell the GPU process to load libraries. + response.actions.load_libraries = true; + } + return response; +} + +GPUActions LLDBServerPluginMockGPU::GetInitializeActions() { + GPUActions init_actions; + init_actions.plugin_name = GetPluginName(); + + GPUBreakpointInfo bp1; + bp1.identifier = "gpu_initialize"; + bp1.name_info = {"a.out", "gpu_initialize"}; + bp1.symbol_names.push_back("gpu_shlib_load"); + init_actions.breakpoints.emplace_back(std::move(bp1)); + return init_actions; +} diff --git a/lldb/tools/lldb-server/Plugins/MockGPU/LLDBServerPluginMockGPU.h b/lldb/tools/lldb-server/Plugins/MockGPU/LLDBServerPluginMockGPU.h new file mode 100644 index 0000000000000..55c5307afa6c4 --- /dev/null +++ b/lldb/tools/lldb-server/Plugins/MockGPU/LLDBServerPluginMockGPU.h @@ -0,0 +1,87 @@ +//===-- LLDBServerPluginMockGPU.h -------------------------------*- C++ -*-===// +// +// 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_TOOLS_LLDB_SERVER_LLDBSERVERPLUGINMOCKGPU_H +#define LLDB_TOOLS_LLDB_SERVER_LLDBSERVERPLUGINMOCKGPU_H + +#include "Plugins/Process/gdb-remote/LLDBServerPlugin.h" +#include "lldb/Utility/Status.h" + +// This is a mock GPU plugin that is used for testing the LLDBServerPlugin. It +// should be run with the following code as the main binary: +/* + +$ cat main.cpp +#include + +struct ShlibInfo { + const char *path = nullptr; + ShlibInfo *next = nullptr; +}; + +ShlibInfo g_shlib_list = { "/tmp/a.out", nullptr}; + +int gpu_initialize() { + return puts(__FUNCTION__); +} +int gpu_shlib_load() { + return puts(__FUNCTION__); +} +int gpu_third_stop() { + return puts(__FUNCTION__); +} +int main(int argc, const char **argv) { + gpu_initialize(); + gpu_shlib_load(); + gpu_third_stop(); + gpu_shlib_load(); + return 0; // Break here +} +$ clang++ -g -O0 -o a.out main.cpp +$ ninja lldb lldb-server +$ ./bin/lldb a.out -o 'b /Break here/ -o run + +*/ +// If the above code is run, you will be stopped at the breakpoint and the Mock +// GPU target will be selected. Try doing a "reg read --all" to see the state +// of the GPU registers. Then you can select the native process target with +// "target select 0" and issue commands to the native process, and then select +// the GPU target with "target select 1" and issue commands to the GPU target. + +namespace lldb_private { + +class TCPSocket; + +namespace lldb_server { + +class LLDBServerPluginMockGPU : public LLDBServerPlugin { +public: + LLDBServerPluginMockGPU(LLDBServerPlugin::GDBServer &native_process, MainLoop &main_loop); + ~LLDBServerPluginMockGPU() override; + llvm::StringRef GetPluginName() override; + int GetEventFileDescriptorAtIndex(size_t idx) override; + bool HandleEventFileDescriptorEvent(int fd) override; + GPUActions GetInitializeActions() override; + std::optional NativeProcessIsStopping() override; + llvm::Expected + BreakpointWasHit(GPUPluginBreakpointHitArgs &args) override; + +private: + std::optional CreateConnection(); + void CloseFDs(); + void AcceptAndMainLoopThread(std::unique_ptr listen_socket_up); + + // Used with a socketpair to get events on the native ptrace event queue. + int m_fds[2] = {-1, -1}; + Status m_main_loop_status; +}; + +} // namespace lldb_server +} // namespace lldb_private + +#endif // LLDB_TOOLS_LLDB_SERVER_LLDBSERVERPLUGINMOCKGPU_H diff --git a/lldb/tools/lldb-server/Plugins/MockGPU/ProcessMockGPU.cpp b/lldb/tools/lldb-server/Plugins/MockGPU/ProcessMockGPU.cpp new file mode 100644 index 0000000000000..4518df6db8e22 --- /dev/null +++ b/lldb/tools/lldb-server/Plugins/MockGPU/ProcessMockGPU.cpp @@ -0,0 +1,214 @@ +//===-- ProcessMockGPU.cpp --------------------------------------*- C++ -*-===// +// +// 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 "ProcessMockGPU.h" +#include "ThreadMockGPU.h" + +#include "lldb/Host/ProcessLaunchInfo.h" +#include "lldb/Utility/ProcessInfo.h" +#include "lldb/Utility/Status.h" +#include "lldb/Utility/UnimplementedError.h" +#include "llvm/Support/Error.h" +#include "Plugins/Process/gdb-remote/ProcessGDBRemoteLog.h" + + +using namespace lldb; +using namespace lldb_private; +using namespace lldb_private::lldb_server; +using namespace lldb_private::process_gdb_remote; + +ProcessMockGPU::ProcessMockGPU(lldb::pid_t pid, NativeDelegate &delegate) + : NativeProcessProtocol(pid, -1, delegate) { + m_state = eStateStopped; + UpdateThreads(); +} + +Status ProcessMockGPU::Resume(const ResumeActionList &resume_actions) { + SetState(StateType::eStateRunning, true); + return Status(); +} + +Status ProcessMockGPU::Halt() { + SetState(StateType::eStateStopped, true); + return Status(); +} + +Status ProcessMockGPU::Detach() { + SetState(StateType::eStateDetached, true); + return Status(); +} + +/// Sends a process a UNIX signal \a signal. +/// +/// \return +/// Returns an error object. +Status ProcessMockGPU::Signal(int signo) { + return Status::FromErrorString("unimplemented"); +} + +/// Tells a process to interrupt all operations as if by a Ctrl-C. +/// +/// The default implementation will send a local host's equivalent of +/// a SIGSTOP to the process via the NativeProcessProtocol::Signal() +/// operation. +/// +/// \return +/// Returns an error object. +Status ProcessMockGPU::Interrupt() { return Status(); } + +Status ProcessMockGPU::Kill() { return Status(); } + +Status ProcessMockGPU::ReadMemory(lldb::addr_t addr, void *buf, size_t size, + size_t &bytes_read) { + return Status::FromErrorString("unimplemented"); +} + +Status ProcessMockGPU::WriteMemory(lldb::addr_t addr, const void *buf, + size_t size, size_t &bytes_written) { + return Status::FromErrorString("unimplemented"); +} + +lldb::addr_t ProcessMockGPU::GetSharedLibraryInfoAddress() { + return LLDB_INVALID_ADDRESS; +} + +size_t ProcessMockGPU::UpdateThreads() { + if (m_threads.empty()) { + lldb::tid_t tid = 3456; + m_threads.push_back(std::make_unique(*this, 3456)); + // ThreadMockGPU &thread = static_cast(*m_threads.back()); + SetCurrentThreadID(tid); + } + return m_threads.size(); +} + +const ArchSpec &ProcessMockGPU::GetArchitecture() const { + m_arch = ArchSpec("mockgpu"); + return m_arch; +} + +// Breakpoint functions +Status ProcessMockGPU::SetBreakpoint(lldb::addr_t addr, uint32_t size, + bool hardware) { + return Status::FromErrorString("unimplemented"); +} + +llvm::ErrorOr> +ProcessMockGPU::GetAuxvData() const { + return nullptr; // TODO: try to return + // llvm::make_error(); +} + +Status ProcessMockGPU::GetLoadedModuleFileSpec(const char *module_path, + FileSpec &file_spec) { + return Status::FromErrorString("unimplemented"); +} + +Status ProcessMockGPU::GetFileLoadAddress(const llvm::StringRef &file_name, + lldb::addr_t &load_addr) { + return Status::FromErrorString("unimplemented"); +} + +void ProcessMockGPU::SetLaunchInfo(ProcessLaunchInfo &launch_info) { + static_cast(m_process_info) = + static_cast(launch_info); +} + +bool ProcessMockGPU::GetProcessInfo(ProcessInstanceInfo &proc_info) { + Log *log = GetLog(GDBRLog::Plugin); + LLDB_LOGF(log, "ProcessMockGPU::%s() entered", __FUNCTION__); + m_process_info.SetProcessID(m_pid); + m_process_info.SetArchitecture(GetArchitecture()); + proc_info = m_process_info; + return true; +} + +std::optional +ProcessMockGPU::GetGPUDynamicLoaderLibraryInfos(const GPUDynamicLoaderArgs &args) { + GPUDynamicLoaderResponse response; + // First example of a shared library. This is for cases where there is a file + // on disk that contains an object file that can be loaded into the process + // and everything should be slid to the load address. All sections within this + // file will be loaded at their file address + 0x20000. This is very typical + // for ELF files. + GPUDynamicLoaderLibraryInfo lib1; + lib1.pathname = "/usr/lib/lib1.so"; + lib1.uuid_str = "A5D69E75-92DE-3FAB-BD95-5171EAE860CC"; + lib1.load_address = 0x20000; + response.library_infos.push_back(lib1); + // Second example of a shared library. This is for cases where there is an + // object file contained within another object file at some file offset with + // a file size. This one is slid to 0x30000, and all sections will get slid + // by the same amount. + GPUDynamicLoaderLibraryInfo lib2; + lib2.pathname = "/tmp/a.out"; + lib1.uuid_str = "9F6F8018-B2D8-3946-8F38-38B0B890CC31"; + lib2.load_address = 0x30000; + lib2.file_offset = 0x1000; + lib2.file_size = 0x500; + response.library_infos.push_back(lib2); + /// Third example of a shared library. This is for cases where there the + /// object file is loaded into the memory of the native process. LLDB will + /// need create an in memory object file using the data in this info. + GPUDynamicLoaderLibraryInfo lib3; + lib3.pathname = "/usr/lib/lib3.so"; + lib3.native_memory_address = 0x4500000; + lib3.native_memory_size = 0x2000; + response.library_infos.push_back(lib3); + + /// Fourth example of a shared library where we load each of the top level + /// sections of an object file at different addresses. + GPUDynamicLoaderLibraryInfo lib4; + lib4.pathname = "/usr/lib/lib4.so"; + lib4.loaded_sections.push_back({{"PT_LOAD[0]"}, 0x0e0000}); + lib4.loaded_sections.push_back({{"PT_LOAD[1]"}, 0x100000}); + lib4.loaded_sections.push_back({{"PT_LOAD[2]"}, 0x0f0000}); + lib4.loaded_sections.push_back({{"PT_LOAD[3]"}, 0x020000}); + response.library_infos.push_back(lib4); + + /// Fifth example of a shared library. This is for cases where there the + /// object file is loaded individual sections are loaded at different + /// addresses instead of having a single load address for the entire object + /// file. This allows GPU plug-ins to load sections at different addresses + /// as they are loaded by the GPU driver. Sections can be created for + /// functions in the ObjectFileELF plug-in when parsing the GPU ELF file so + /// that individual functions can be loaded at different addresses as the + /// driver loads them. + GPUDynamicLoaderLibraryInfo lib5; + lib5.pathname = "/usr/lib/lib5.so"; + /// Here we are going to assume that the .text section has functions that + /// create sections for each function in the object file. Then each function + /// can be loaded at a different address as the driver loads them. + lib5.loaded_sections.push_back({{"PT_LOAD[1]", ".text", "foo"}, 0x80000}); + lib5.loaded_sections.push_back({{"PT_LOAD[1]", ".text", "bar"}, 0x80200}); + response.library_infos.push_back(lib5); + return response; +} + + +llvm::Expected> +ProcessMockGPU::Manager::Launch( + ProcessLaunchInfo &launch_info, + NativeProcessProtocol::NativeDelegate &native_delegate) { + lldb::pid_t pid = 1234; + auto proc_up = std::make_unique(pid, native_delegate); + proc_up->SetLaunchInfo(launch_info); + return proc_up; +} + +llvm::Expected> +ProcessMockGPU::Manager::Attach( + lldb::pid_t pid, NativeProcessProtocol::NativeDelegate &native_delegate) { + return llvm::createStringError("Unimplemented function"); +} + + +ProcessMockGPU::Extension +ProcessMockGPU::Manager::GetSupportedExtensions() const { + return Extension::gpu_dyld; +} diff --git a/lldb/tools/lldb-server/Plugins/MockGPU/ProcessMockGPU.h b/lldb/tools/lldb-server/Plugins/MockGPU/ProcessMockGPU.h new file mode 100644 index 0000000000000..d1326b83c309e --- /dev/null +++ b/lldb/tools/lldb-server/Plugins/MockGPU/ProcessMockGPU.h @@ -0,0 +1,108 @@ +//===-- ProcessMockGPU.h ----------------------------------------*- C++ -*-===// +// +// 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_TOOLS_LLDB_SERVER_PROCESSMOCKGPU_H +#define LLDB_TOOLS_LLDB_SERVER_PROCESSMOCKGPU_H + +#include "lldb/Host/common/NativeProcessProtocol.h" +#include "lldb/Utility/ProcessInfo.h" + +namespace lldb_private { +namespace lldb_server { + +/// \class ProcessMockGPU +/// Abstract class that extends \a NativeProcessProtocol for a mock GPU. This +/// class is used to unit testing the GPU plugins in lldb-server. +class ProcessMockGPU : public NativeProcessProtocol { + // TODO: change NativeProcessProtocol::GetArchitecture() to return by value + mutable ArchSpec m_arch; + ProcessInstanceInfo m_process_info; + +public: + +class Manager : public NativeProcessProtocol::Manager { + public: + Manager(MainLoop &mainloop) + : NativeProcessProtocol::Manager(mainloop) {} + + llvm::Expected> + Launch(ProcessLaunchInfo &launch_info, + NativeProcessProtocol::NativeDelegate &native_delegate) override; + + llvm::Expected> + Attach(lldb::pid_t pid, + NativeProcessProtocol::NativeDelegate &native_delegate) override; + + Extension GetSupportedExtensions() const override; + }; + + ProcessMockGPU(lldb::pid_t pid, NativeDelegate &delegate); + + Status Resume(const ResumeActionList &resume_actions) override; + + Status Halt() override; + + Status Detach() override; + + /// Sends a process a UNIX signal \a signal. + /// + /// \return + /// Returns an error object. + Status Signal(int signo) override; + + /// Tells a process to interrupt all operations as if by a Ctrl-C. + /// + /// The default implementation will send a local host's equivalent of + /// a SIGSTOP to the process via the NativeProcessProtocol::Signal() + /// operation. + /// + /// \return + /// Returns an error object. + Status Interrupt() override; + + Status Kill() override; + + Status ReadMemory(lldb::addr_t addr, void *buf, size_t size, + size_t &bytes_read) override; + + Status WriteMemory(lldb::addr_t addr, const void *buf, size_t size, + size_t &bytes_written) override; + + lldb::addr_t GetSharedLibraryInfoAddress() override; + + size_t UpdateThreads() override; + + const ArchSpec &GetArchitecture() const override; + + // Breakpoint functions + Status SetBreakpoint(lldb::addr_t addr, uint32_t size, + bool hardware) override; + + llvm::ErrorOr> + GetAuxvData() const override; + + Status GetLoadedModuleFileSpec(const char *module_path, + FileSpec &file_spec) override; + + Status GetFileLoadAddress(const llvm::StringRef &file_name, + lldb::addr_t &load_addr) override; + + bool GetProcessInfo(ProcessInstanceInfo &info) override; + + std::optional + GetGPUDynamicLoaderLibraryInfos(const GPUDynamicLoaderArgs &args) override; + + // Custom accessors + void SetLaunchInfo(ProcessLaunchInfo &launch_info); +}; + + +} // namespace lldb_server +} // namespace lldb_private + +#endif diff --git a/lldb/tools/lldb-server/Plugins/MockGPU/RegisterContextMockGPU.cpp b/lldb/tools/lldb-server/Plugins/MockGPU/RegisterContextMockGPU.cpp new file mode 100644 index 0000000000000..18a3ffc35fa07 --- /dev/null +++ b/lldb/tools/lldb-server/Plugins/MockGPU/RegisterContextMockGPU.cpp @@ -0,0 +1,627 @@ +//===-- RegisterContextMockGPU.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 "RegisterContextMockGPU.h" + +#include "lldb/Host/HostInfo.h" +#include "lldb/Host/common/NativeProcessProtocol.h" +#include "lldb/Host/common/NativeThreadProtocol.h" +#include "lldb/Utility/DataBufferHeap.h" +#include "lldb/Utility/RegisterValue.h" +#include "lldb/Utility/Status.h" + +using namespace lldb; +using namespace lldb_private; +using namespace lldb_private::lldb_server; + +/// LLDB register numbers must start at 0 and be contiguous with no gaps. +enum LLDBRegNum : uint32_t { + LLDB_R0 = 0, + LLDB_R1, + LLDB_R2, + LLDB_R3, + LLDB_R4, + LLDB_R5, + LLDB_R6, + LLDB_R7, + LLDB_SP, + LLDB_FP, + LLDB_PC, + LLDB_Flags, + LLDB_V0, + LLDB_V1, + LLDB_V2, + LLDB_V3, + LLDB_V4, + LLDB_V5, + LLDB_V6, + LLDB_V7, + kNumRegs +}; + +/// DWARF register numbers should match the register numbers that the compiler +/// uses in the DWARF debug info. They can be any number and do not need to +/// be in increasing order or consective, there can be gaps. The compiler has +/// dedicated register numbers for any DWARF that references registers, like +/// location expressions and .debug_frame unwind info. +enum DWARFRegNum : uint32_t { + DWARF_R0 = 128, + DWARF_R1, + DWARF_R2, + DWARF_R3, + DWARF_R4, + DWARF_R5, + DWARF_R6, + DWARF_R7, + DWARF_SP, + DWARF_FP, + DWARF_PC, + DWARF_Flags = LLDB_INVALID_REGNUM, // This register does not exist in DWARF + DWARF_V0 = 256, + DWARF_V1, + DWARF_V2, + DWARF_V3, + DWARF_V4, + DWARF_V5, + DWARF_V6, + DWARF_V7, +}; + +/// Compiler registers should match the register numbers that the compiler +/// uses in runtime information. They can be any number and do not need to +/// be in increasing order or consective, there can be gaps. The compiler has +/// dedicated register numbers for any runtime information that references +/// registers, like .eh_frame unwind info. Many times these numbers match the +/// DWARF register numbers, but not always. +enum CompilerRegNum : uint32_t { + EH_FRAME_R0 = 1000, + EH_FRAME_R1, + EH_FRAME_R2, + EH_FRAME_R3, + EH_FRAME_R4, + EH_FRAME_R5, + EH_FRAME_R6, + EH_FRAME_R7, + EH_FRAME_SP, + EH_FRAME_FP, + EH_FRAME_PC, + EH_FRAME_Flags = LLDB_INVALID_REGNUM, // Not accessed by runtime info. + EH_FRAME_V0 = 2000, + EH_FRAME_V1, + EH_FRAME_V2, + EH_FRAME_V3, + EH_FRAME_V4, + EH_FRAME_V5, + EH_FRAME_V6, + EH_FRAME_V7, +}; + +uint32_t g_gpr_regnums[] = {LLDB_R0, LLDB_R1, LLDB_R2, LLDB_R3, + LLDB_R4, LLDB_R5, LLDB_R6, LLDB_R7, + LLDB_SP, LLDB_FP, LLDB_PC, LLDB_Flags}; +uint32_t g_vec_regnums[] = {LLDB_V0, LLDB_V1, LLDB_V2, LLDB_V3, + LLDB_V4, LLDB_V5, LLDB_V6, LLDB_V7}; + +static const RegisterSet g_reg_sets[] = { + {"General Purpose Registers", "gpr", + sizeof(g_gpr_regnums) / sizeof(g_gpr_regnums[0]), g_gpr_regnums}, + {"Vector Registers", "vector", + sizeof(g_vec_regnums) / sizeof(g_vec_regnums[0]), g_vec_regnums}, +}; + +/// Define all of the information about all registers. The register info structs +/// are accessed by the LLDB register numbers, which are defined above. +#define REG_OFFSET(Reg) offsetof(RegisterContextMockGPU::RegisterContext, Reg) +static const RegisterInfo g_reg_infos[LLDBRegNum::kNumRegs] = { + { + "R0", // RegisterInfo::name + nullptr, // RegisterInfo::alt_name + 8, // RegisterInfo::byte_size + REG_OFFSET(R0),// RegisterInfo::byte_offset + eEncodingUint, // RegisterInfo::encoding + eFormatHex, // RegisterInfo::format + { + // RegisterInfo::kinds[] + EH_FRAME_R0, // RegisterInfo::kinds[eRegisterKindEHFrame] + DWARF_R0, // RegisterInfo::kinds[eRegisterKindDWARF] + LLDB_REGNUM_GENERIC_ARG1, // RegisterInfo::kinds[eRegisterKindGeneric] + LLDB_R0, // RegisterInfo::kinds[eRegisterKindProcessPlugin] + LLDB_R0 // RegisterInfo::kinds[eRegisterKindLLDB] + }, + nullptr, // RegisterInfo::value_regs + nullptr, // RegisterInfo::invalidate_regs + nullptr, // RegisterInfo::flags_type + }, + { + "R1", // RegisterInfo::name + nullptr, // RegisterInfo::alt_name + 8, // RegisterInfo::byte_size + REG_OFFSET(R1),// RegisterInfo::byte_offset + eEncodingUint, // RegisterInfo::encoding + eFormatHex, // RegisterInfo::format + { + // RegisterInfo::kinds[] + EH_FRAME_R1, // RegisterInfo::kinds[eRegisterKindEHFrame] + DWARF_R1, // RegisterInfo::kinds[eRegisterKindDWARF] + LLDB_REGNUM_GENERIC_ARG2, // RegisterInfo::kinds[eRegisterKindGeneric] + LLDB_R1, // RegisterInfo::kinds[eRegisterKindProcessPlugin] + LLDB_R1 // RegisterInfo::kinds[eRegisterKindLLDB] + }, + nullptr, // RegisterInfo::value_regs + nullptr, // RegisterInfo::invalidate_regs + nullptr, // RegisterInfo::flags_type + }, + { + "R2", // RegisterInfo::name + nullptr, // RegisterInfo::alt_name + 8, // RegisterInfo::byte_size + REG_OFFSET(R2),// RegisterInfo::byte_offset + eEncodingUint, // RegisterInfo::encoding + eFormatHex, // RegisterInfo::format + { + // RegisterInfo::kinds[] + EH_FRAME_R2, // RegisterInfo::kinds[eRegisterKindEHFrame] + DWARF_R2, // RegisterInfo::kinds[eRegisterKindDWARF] + LLDB_REGNUM_GENERIC_ARG3, // RegisterInfo::kinds[eRegisterKindGeneric] + LLDB_R2, // RegisterInfo::kinds[eRegisterKindProcessPlugin] + LLDB_R2 // RegisterInfo::kinds[eRegisterKindLLDB] + }, + nullptr, // RegisterInfo::value_regs + nullptr, // RegisterInfo::invalidate_regs + nullptr, // RegisterInfo::flags_type + }, + { + "R3", // RegisterInfo::name + nullptr, // RegisterInfo::alt_name + 8, // RegisterInfo::byte_size + REG_OFFSET(R3),// RegisterInfo::byte_offset + eEncodingUint, // RegisterInfo::encoding + eFormatHex, // RegisterInfo::format + { + // RegisterInfo::kinds[] + EH_FRAME_R3, // RegisterInfo::kinds[eRegisterKindEHFrame] + DWARF_R3, // RegisterInfo::kinds[eRegisterKindDWARF] + LLDB_REGNUM_GENERIC_ARG4, // RegisterInfo::kinds[eRegisterKindGeneric] + LLDB_R3, // RegisterInfo::kinds[eRegisterKindProcessPlugin] + LLDB_R3 // RegisterInfo::kinds[eRegisterKindLLDB] + }, + nullptr, // RegisterInfo::value_regs + nullptr, // RegisterInfo::invalidate_regs + nullptr, // RegisterInfo::flags_type + }, + { + "R4", // RegisterInfo::name + nullptr, // RegisterInfo::alt_name + 8, // RegisterInfo::byte_size + REG_OFFSET(R4),// RegisterInfo::byte_offset + eEncodingUint, // RegisterInfo::encoding + eFormatHex, // RegisterInfo::format + { + // RegisterInfo::kinds[] + EH_FRAME_R4, // RegisterInfo::kinds[eRegisterKindEHFrame] + DWARF_R4, // RegisterInfo::kinds[eRegisterKindDWARF] + LLDB_INVALID_REGNUM, // RegisterInfo::kinds[eRegisterKindGeneric] + LLDB_R4, // RegisterInfo::kinds[eRegisterKindProcessPlugin] + LLDB_R4 // RegisterInfo::kinds[eRegisterKindLLDB] + }, + nullptr, // RegisterInfo::value_regs + nullptr, // RegisterInfo::invalidate_regs + nullptr, // RegisterInfo::flags_type + }, + { + "R5", // RegisterInfo::name + nullptr, // RegisterInfo::alt_name + 8, // RegisterInfo::byte_size + REG_OFFSET(R5),// RegisterInfo::byte_offset + eEncodingUint, // RegisterInfo::encoding + eFormatHex, // RegisterInfo::format + { + // RegisterInfo::kinds[] + EH_FRAME_R5, // RegisterInfo::kinds[eRegisterKindEHFrame] + DWARF_R5, // RegisterInfo::kinds[eRegisterKindDWARF] + LLDB_INVALID_REGNUM, // RegisterInfo::kinds[eRegisterKindGeneric] + LLDB_R5, // RegisterInfo::kinds[eRegisterKindProcessPlugin] + LLDB_R5 // RegisterInfo::kinds[eRegisterKindLLDB] + }, + nullptr, // RegisterInfo::value_regs + nullptr, // RegisterInfo::invalidate_regs + nullptr, // RegisterInfo::flags_type + }, + { + "R6", // RegisterInfo::name + nullptr, // RegisterInfo::alt_name + 8, // RegisterInfo::byte_size + REG_OFFSET(R6),// RegisterInfo::byte_offset + eEncodingUint, // RegisterInfo::encoding + eFormatHex, // RegisterInfo::format + { + // RegisterInfo::kinds[] + EH_FRAME_R6, // RegisterInfo::kinds[eRegisterKindEHFrame] + DWARF_R6, // RegisterInfo::kinds[eRegisterKindDWARF] + LLDB_INVALID_REGNUM, // RegisterInfo::kinds[eRegisterKindGeneric] + LLDB_R6, // RegisterInfo::kinds[eRegisterKindProcessPlugin] + LLDB_R6 // RegisterInfo::kinds[eRegisterKindLLDB] + }, + nullptr, // RegisterInfo::value_regs + nullptr, // RegisterInfo::invalidate_regs + nullptr, // RegisterInfo::flags_type + }, + { + "R7", // RegisterInfo::name + nullptr, // RegisterInfo::alt_name + 8, // RegisterInfo::byte_size + REG_OFFSET(R7),// RegisterInfo::byte_offset + eEncodingUint, // RegisterInfo::encoding + eFormatHex, // RegisterInfo::format + { + // RegisterInfo::kinds[] + EH_FRAME_R7, // RegisterInfo::kinds[eRegisterKindEHFrame] + DWARF_R7, // RegisterInfo::kinds[eRegisterKindDWARF] + LLDB_INVALID_REGNUM, // RegisterInfo::kinds[eRegisterKindGeneric] + LLDB_R7, // RegisterInfo::kinds[eRegisterKindProcessPlugin] + LLDB_R7 // RegisterInfo::kinds[eRegisterKindLLDB] + }, + nullptr, // RegisterInfo::value_regs + nullptr, // RegisterInfo::invalidate_regs + nullptr, // RegisterInfo::flags_type + }, + { + "SP", // RegisterInfo::name + nullptr, // RegisterInfo::alt_name + 8, // RegisterInfo::byte_size + REG_OFFSET(SP),// RegisterInfo::byte_offset + eEncodingUint, // RegisterInfo::encoding + eFormatHex, // RegisterInfo::format + { + // RegisterInfo::kinds[] + EH_FRAME_SP, // RegisterInfo::kinds[eRegisterKindEHFrame] + DWARF_SP, // RegisterInfo::kinds[eRegisterKindDWARF] + LLDB_REGNUM_GENERIC_SP, // RegisterInfo::kinds[eRegisterKindGeneric] + LLDB_SP, // RegisterInfo::kinds[eRegisterKindProcessPlugin] + LLDB_SP // RegisterInfo::kinds[eRegisterKindLLDB] + }, + nullptr, // RegisterInfo::value_regs + nullptr, // RegisterInfo::invalidate_regs + nullptr, // RegisterInfo::flags_type + }, + { + "FP", // RegisterInfo::name + nullptr, // RegisterInfo::alt_name + 8, // RegisterInfo::byte_size + REG_OFFSET(FP),// RegisterInfo::byte_offset + eEncodingUint, // RegisterInfo::encoding + eFormatHex, // RegisterInfo::format + { + // RegisterInfo::kinds[] + EH_FRAME_FP, // RegisterInfo::kinds[eRegisterKindEHFrame] + DWARF_FP, // RegisterInfo::kinds[eRegisterKindDWARF] + LLDB_REGNUM_GENERIC_FP, // RegisterInfo::kinds[eRegisterKindGeneric] + LLDB_FP, // RegisterInfo::kinds[eRegisterKindProcessPlugin] + LLDB_FP // RegisterInfo::kinds[eRegisterKindLLDB] + }, + nullptr, // RegisterInfo::value_regs + nullptr, // RegisterInfo::invalidate_regs + nullptr, // RegisterInfo::flags_type + }, + { + "PC", // RegisterInfo::name + nullptr, // RegisterInfo::alt_name + 8, // RegisterInfo::byte_size + REG_OFFSET(PC),// RegisterInfo::byte_offset + eEncodingUint, // RegisterInfo::encoding + eFormatHex, // RegisterInfo::format + { + // RegisterInfo::kinds[] + EH_FRAME_PC, // RegisterInfo::kinds[eRegisterKindEHFrame] + DWARF_PC, // RegisterInfo::kinds[eRegisterKindDWARF] + LLDB_REGNUM_GENERIC_PC, // RegisterInfo::kinds[eRegisterKindGeneric] + LLDB_PC, // RegisterInfo::kinds[eRegisterKindProcessPlugin] + LLDB_PC // RegisterInfo::kinds[eRegisterKindLLDB] + }, + nullptr, // RegisterInfo::value_regs + nullptr, // RegisterInfo::invalidate_regs + nullptr, // RegisterInfo::flags_type + }, + { + "Flags", // RegisterInfo::name + nullptr, // RegisterInfo::alt_name + 8, // RegisterInfo::byte_size + REG_OFFSET(Flags),// RegisterInfo::byte_offset + eEncodingUint, // RegisterInfo::encoding + eFormatHex, // RegisterInfo::format + { + // RegisterInfo::kinds[] + EH_FRAME_Flags, // RegisterInfo::kinds[eRegisterKindEHFrame] + DWARF_Flags, // RegisterInfo::kinds[eRegisterKindDWARF] + LLDB_REGNUM_GENERIC_FLAGS, // RegisterInfo::kinds[eRegisterKindGeneric] + LLDB_Flags, // RegisterInfo::kinds[eRegisterKindProcessPlugin] + LLDB_Flags // RegisterInfo::kinds[eRegisterKindLLDB] + }, + nullptr, // RegisterInfo::value_regs + nullptr, // RegisterInfo::invalidate_regs + nullptr, // RegisterInfo::flags_type + }, + { + "V0", // RegisterInfo::name + nullptr, // RegisterInfo::alt_name + 8, // RegisterInfo::byte_size + REG_OFFSET(V0), // RegisterInfo::byte_offset + eEncodingVector, // RegisterInfo::encoding + eFormatVectorOfUInt32, // RegisterInfo::format + { + // RegisterInfo::kinds[] + EH_FRAME_V0, // RegisterInfo::kinds[eRegisterKindEHFrame] + DWARF_V0, // RegisterInfo::kinds[eRegisterKindDWARF] + LLDB_INVALID_REGNUM, // RegisterInfo::kinds[eRegisterKindGeneric] + LLDB_V0, // RegisterInfo::kinds[eRegisterKindProcessPlugin] + LLDB_V0 // RegisterInfo::kinds[eRegisterKindLLDB] + }, + nullptr, // RegisterInfo::value_regs + nullptr, // RegisterInfo::invalidate_regs + nullptr, // RegisterInfo::flags_type + }, + { + "V1", // RegisterInfo::name + nullptr, // RegisterInfo::alt_name + 8, // RegisterInfo::byte_size + REG_OFFSET(V1), // RegisterInfo::byte_offset + eEncodingVector, // RegisterInfo::encoding + eFormatVectorOfUInt32, // RegisterInfo::format + { + // RegisterInfo::kinds[] + EH_FRAME_V1, // RegisterInfo::kinds[eRegisterKindEHFrame] + DWARF_V1, // RegisterInfo::kinds[eRegisterKindDWARF] + LLDB_INVALID_REGNUM, // RegisterInfo::kinds[eRegisterKindGeneric] + LLDB_V1, // RegisterInfo::kinds[eRegisterKindProcessPlugin] + LLDB_V1 // RegisterInfo::kinds[eRegisterKindLLDB] + }, + nullptr, // RegisterInfo::value_regs + nullptr, // RegisterInfo::invalidate_regs + nullptr, // RegisterInfo::flags_type + }, + { + "V2", // RegisterInfo::name + nullptr, // RegisterInfo::alt_name + 8, // RegisterInfo::byte_size + REG_OFFSET(V2), // RegisterInfo::byte_offset + eEncodingVector, // RegisterInfo::encoding + eFormatVectorOfUInt32, // RegisterInfo::format + { + // RegisterInfo::kinds[] + EH_FRAME_V2, // RegisterInfo::kinds[eRegisterKindEHFrame] + DWARF_V2, // RegisterInfo::kinds[eRegisterKindDWARF] + LLDB_INVALID_REGNUM, // RegisterInfo::kinds[eRegisterKindGeneric] + LLDB_V2, // RegisterInfo::kinds[eRegisterKindProcessPlugin] + LLDB_V2 // RegisterInfo::kinds[eRegisterKindLLDB] + }, + nullptr, // RegisterInfo::value_regs + nullptr, // RegisterInfo::invalidate_regs + nullptr, // RegisterInfo::flags_type + }, + { + "V3", // RegisterInfo::name + nullptr, // RegisterInfo::alt_name + 8, // RegisterInfo::byte_size + REG_OFFSET(V3), // RegisterInfo::byte_offset + eEncodingVector, // RegisterInfo::encoding + eFormatVectorOfUInt32, // RegisterInfo::format + { + // RegisterInfo::kinds[] + EH_FRAME_V3, // RegisterInfo::kinds[eRegisterKindEHFrame] + DWARF_V3, // RegisterInfo::kinds[eRegisterKindDWARF] + LLDB_INVALID_REGNUM, // RegisterInfo::kinds[eRegisterKindGeneric] + LLDB_V3, // RegisterInfo::kinds[eRegisterKindProcessPlugin] + LLDB_V3 // RegisterInfo::kinds[eRegisterKindLLDB] + }, + nullptr, // RegisterInfo::value_regs + nullptr, // RegisterInfo::invalidate_regs + nullptr, // RegisterInfo::flags_type + }, + { + "V4", // RegisterInfo::name + nullptr, // RegisterInfo::alt_name + 8, // RegisterInfo::byte_size + REG_OFFSET(V4), // RegisterInfo::byte_offset + eEncodingVector, // RegisterInfo::encoding + eFormatVectorOfUInt32, // RegisterInfo::format + { + // RegisterInfo::kinds[] + EH_FRAME_V4, // RegisterInfo::kinds[eRegisterKindEHFrame] + DWARF_V4, // RegisterInfo::kinds[eRegisterKindDWARF] + LLDB_INVALID_REGNUM, // RegisterInfo::kinds[eRegisterKindGeneric] + LLDB_V4, // RegisterInfo::kinds[eRegisterKindProcessPlugin] + LLDB_V4 // RegisterInfo::kinds[eRegisterKindLLDB] + }, + nullptr, // RegisterInfo::value_regs + nullptr, // RegisterInfo::invalidate_regs + nullptr, // RegisterInfo::flags_type + }, + { + "V5", // RegisterInfo::name + nullptr, // RegisterInfo::alt_name + 8, // RegisterInfo::byte_size + REG_OFFSET(V5), // RegisterInfo::byte_offset + eEncodingVector, // RegisterInfo::encoding + eFormatVectorOfUInt32, // RegisterInfo::format + { + // RegisterInfo::kinds[] + EH_FRAME_V5, // RegisterInfo::kinds[eRegisterKindEHFrame] + DWARF_V5, // RegisterInfo::kinds[eRegisterKindDWARF] + LLDB_INVALID_REGNUM, // RegisterInfo::kinds[eRegisterKindGeneric] + LLDB_V5, // RegisterInfo::kinds[eRegisterKindProcessPlugin] + LLDB_V5 // RegisterInfo::kinds[eRegisterKindLLDB] + }, + nullptr, // RegisterInfo::value_regs + nullptr, // RegisterInfo::invalidate_regs + nullptr, // RegisterInfo::flags_type + }, + { + "V6", // RegisterInfo::name + nullptr, // RegisterInfo::alt_name + 8, // RegisterInfo::byte_size + REG_OFFSET(V6), // RegisterInfo::byte_offset + eEncodingVector, // RegisterInfo::encoding + eFormatVectorOfUInt32, // RegisterInfo::format + { + // RegisterInfo::kinds[] + EH_FRAME_V6, // RegisterInfo::kinds[eRegisterKindEHFrame] + DWARF_V6, // RegisterInfo::kinds[eRegisterKindDWARF] + LLDB_INVALID_REGNUM, // RegisterInfo::kinds[eRegisterKindGeneric] + LLDB_V6, // RegisterInfo::kinds[eRegisterKindProcessPlugin] + LLDB_V6 // RegisterInfo::kinds[eRegisterKindLLDB] + }, + nullptr, // RegisterInfo::value_regs + nullptr, // RegisterInfo::invalidate_regs + nullptr, // RegisterInfo::flags_type + }, + { + "V7", // RegisterInfo::name + nullptr, // RegisterInfo::alt_name + 8, // RegisterInfo::byte_size + REG_OFFSET(V7), // RegisterInfo::byte_offset + eEncodingVector, // RegisterInfo::encoding + eFormatVectorOfUInt32, // RegisterInfo::format + { + // RegisterInfo::kinds[] + EH_FRAME_V7, // RegisterInfo::kinds[eRegisterKindEHFrame] + DWARF_V7, // RegisterInfo::kinds[eRegisterKindDWARF] + LLDB_INVALID_REGNUM, // RegisterInfo::kinds[eRegisterKindGeneric] + LLDB_V7, // RegisterInfo::kinds[eRegisterKindProcessPlugin] + LLDB_V7 // RegisterInfo::kinds[eRegisterKindLLDB] + }, + nullptr, // RegisterInfo::value_regs + nullptr, // RegisterInfo::invalidate_regs + nullptr, // RegisterInfo::flags_type + }, +}; +RegisterContextMockGPU::RegisterContextMockGPU( + NativeThreadProtocol &native_thread) + : NativeRegisterContext(native_thread) { + InitRegisters(); + // Only doing this for the Mock GPU class, don't do this in real GPU classes. + ReadRegs(); +} + +void RegisterContextMockGPU::InitRegisters() { + for (size_t i = 0; i < kNumRegs; ++i) + m_regs.data[i] = 0; + m_reg_value_is_valid.resize(kNumRegs, false); +} + +void RegisterContextMockGPU::InvalidateAllRegisters() { + // Do what ever book keeping we need to do to indicate that all register + // values are now invalid. + for (uint32_t i = 0; i < kNumRegs; ++i) + m_reg_value_is_valid[i] = false; +} + +Status RegisterContextMockGPU::ReadRegs() { + // Fill all registers with unique values. + for (uint32_t i = 0; i < kNumRegs; ++i) { + m_reg_value_is_valid[i] = true; + m_regs.data[i] = i; + } + return Status(); +} + +uint32_t RegisterContextMockGPU::GetRegisterSetCount() const { + return sizeof(g_reg_sets) / sizeof(g_reg_sets[0]); +} + +uint32_t RegisterContextMockGPU::GetRegisterCount() const { return kNumRegs; } + +uint32_t RegisterContextMockGPU::GetUserRegisterCount() const { + return GetRegisterCount(); +} + +const RegisterInfo * +RegisterContextMockGPU::GetRegisterInfoAtIndex(uint32_t reg) const { + if (reg < kNumRegs) + return &g_reg_infos[reg]; + return nullptr; +} + +const RegisterSet * +RegisterContextMockGPU::GetRegisterSet(uint32_t set_index) const { + if (set_index < GetRegisterSetCount()) + return &g_reg_sets[set_index]; + return nullptr; +} + +Status RegisterContextMockGPU::ReadRegister(const RegisterInfo *reg_info, + RegisterValue ®_value) { + Status error; + const uint32_t lldb_reg_num = reg_info->kinds[eRegisterKindLLDB]; + if (!m_reg_value_is_valid[lldb_reg_num]) + error = ReadRegs(); + if (error.Fail()) + return error; + reg_value.SetUInt64(m_regs.data[lldb_reg_num]); + return Status(); +} + +Status RegisterContextMockGPU::WriteRegister(const RegisterInfo *reg_info, + const RegisterValue ®_value) { + const uint32_t lldb_reg_num = reg_info->kinds[eRegisterKindLLDB]; + bool success = false; + uint64_t new_value = reg_value.GetAsUInt64(UINT64_MAX, &success); + if (!success) + return Status::FromErrorString("register write failed"); + m_regs.data[lldb_reg_num] = new_value; + m_reg_value_is_valid[lldb_reg_num] = true; + return Status(); +} + +Status RegisterContextMockGPU::ReadAllRegisterValues( + lldb::WritableDataBufferSP &data_sp) { + ReadRegs(); // Read all registers first + const size_t regs_byte_size = sizeof(m_regs); + data_sp.reset(new DataBufferHeap(regs_byte_size, 0)); + uint8_t *dst = data_sp->GetBytes(); + memcpy(dst, &m_regs.data[0], regs_byte_size); + return Status(); +} + +Status RegisterContextMockGPU::WriteAllRegisterValues( + const lldb::DataBufferSP &data_sp) { + const size_t regs_byte_size = sizeof(m_regs); + + if (!data_sp) { + return Status::FromErrorStringWithFormat( + "RegisterContextMockGPU::%s invalid data_sp provided", __FUNCTION__); + } + + if (data_sp->GetByteSize() != regs_byte_size) { + return Status::FromErrorStringWithFormat( + "RegisterContextMockGPU::%s data_sp contained mismatched " + "data size, expected %" PRIu64 ", actual %" PRIu64, + __FUNCTION__, regs_byte_size, data_sp->GetByteSize()); + } + + const uint8_t *src = data_sp->GetBytes(); + if (src == nullptr) { + return Status::FromErrorStringWithFormat( + "RegisterContextMockGPU::%s " + "DataBuffer::GetBytes() returned a null " + "pointer", + __FUNCTION__); + } + memcpy(&m_regs.data[0], src, regs_byte_size); + return Status(); +} + +std::vector +RegisterContextMockGPU::GetExpeditedRegisters(ExpeditedRegs expType) const { + static std::vector g_expedited_regs; + if (g_expedited_regs.empty()) { + g_expedited_regs.push_back(LLDB_PC); + g_expedited_regs.push_back(LLDB_SP); + g_expedited_regs.push_back(LLDB_FP); + } + return g_expedited_regs; +} diff --git a/lldb/tools/lldb-server/Plugins/MockGPU/RegisterContextMockGPU.h b/lldb/tools/lldb-server/Plugins/MockGPU/RegisterContextMockGPU.h new file mode 100644 index 0000000000000..e80b7fec263a3 --- /dev/null +++ b/lldb/tools/lldb-server/Plugins/MockGPU/RegisterContextMockGPU.h @@ -0,0 +1,89 @@ +//===-- RegisterContextMockGPU.h --------------------------------*- C++ -*-===// +// +// 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_TOOLS_LLDB_SERVER_REGISTERCONTEXTMOCKGPU_H +#define LLDB_TOOLS_LLDB_SERVER_REGISTERCONTEXTMOCKGPU_H + +// #include "Plugins/Process/Utility/NativeRegisterContextRegisterInfo.h" +#include "lldb/Host/common/NativeRegisterContext.h" +#include "lldb/lldb-forward.h" + +namespace lldb_private { +namespace lldb_server { + +class RegisterContextMockGPU : public NativeRegisterContext { +public: + RegisterContextMockGPU(NativeThreadProtocol &native_thread); + + uint32_t GetRegisterCount() const override; + + uint32_t GetUserRegisterCount() const override; + + const RegisterInfo *GetRegisterInfoAtIndex(uint32_t reg) const override; + + uint32_t GetRegisterSetCount() const override; + + const RegisterSet *GetRegisterSet(uint32_t set_index) const override; + + Status ReadRegister(const RegisterInfo *reg_info, + RegisterValue ®_value) override; + + Status WriteRegister(const RegisterInfo *reg_info, + const RegisterValue ®_value) override; + + Status ReadAllRegisterValues(lldb::WritableDataBufferSP &data_sp) override; + + Status WriteAllRegisterValues(const lldb::DataBufferSP &data_sp) override; + + std::vector + GetExpeditedRegisters(ExpeditedRegs expType) const override; + + // A storage stucture for all registers; + struct RegisterContext { + uint64_t R0; + uint64_t R1; + uint64_t R2; + uint64_t R3; + uint64_t R4; + uint64_t R5; + uint64_t R6; + uint64_t R7; + uint64_t SP; + uint64_t FP; + uint64_t PC; + uint64_t Flags; + uint64_t V0; + uint64_t V1; + uint64_t V2; + uint64_t V3; + uint64_t V4; + uint64_t V5; + uint64_t V6; + uint64_t V7; + }; + +private: + void InitRegisters(); + void InvalidateAllRegisters(); + Status ReadRegs(); + + + // All mock GPU registers are contained in this buffer. + union { + /// Allow for indexed access to each register value. + uint64_t data[sizeof(RegisterContext)/sizeof(uint64_t)]; + /// Allow for direct access to the register values by name. + RegisterContext regs; + } m_regs; + std::vector m_reg_value_is_valid; +}; + +} // namespace lldb_server +} // namespace lldb_private + +#endif // #ifndef LLDB_TOOLS_LLDB_SERVER_REGISTERCONTEXTMOCKGPU_H diff --git a/lldb/tools/lldb-server/Plugins/MockGPU/ThreadMockGPU.cpp b/lldb/tools/lldb-server/Plugins/MockGPU/ThreadMockGPU.cpp new file mode 100644 index 0000000000000..31024fc99fe58 --- /dev/null +++ b/lldb/tools/lldb-server/Plugins/MockGPU/ThreadMockGPU.cpp @@ -0,0 +1,54 @@ +//===-- ThreadMockGPU.cpp ------------------------------------- -*- C++ -*-===// +// +// 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 "ThreadMockGPU.h" +#include "ProcessMockGPU.h" + +using namespace lldb_private; +using namespace lldb_server; + +ThreadMockGPU::ThreadMockGPU(ProcessMockGPU &process, lldb::tid_t tid) + : NativeThreadProtocol(process, tid), m_reg_context(*this) { + m_stop_info.reason = lldb::eStopReasonTrace; // lldb::eStopReasonDynammicLoader; +} + +// NativeThreadProtocol Interface +std::string ThreadMockGPU::GetName() { return "Mock GPU Thread Name"; } + +lldb::StateType ThreadMockGPU::GetState() { return lldb::eStateStopped; } + +bool ThreadMockGPU::GetStopReason(ThreadStopInfo &stop_info, + std::string &description) { + stop_info = m_stop_info; + description = "Mock GPU Thread Stop Reason"; + return true; +} + +Status ThreadMockGPU::SetWatchpoint(lldb::addr_t addr, size_t size, + uint32_t watch_flags, bool hardware) { + return Status::FromErrorString("unimplemented"); +} + +Status ThreadMockGPU::RemoveWatchpoint(lldb::addr_t addr) { + return Status::FromErrorString("unimplemented"); +} + +Status ThreadMockGPU::SetHardwareBreakpoint(lldb::addr_t addr, size_t size) { + return Status::FromErrorString("unimplemented"); +} + +Status ThreadMockGPU::RemoveHardwareBreakpoint(lldb::addr_t addr) { + return Status::FromErrorString("unimplemented"); +} + +ProcessMockGPU &ThreadMockGPU::GetProcess() { + return static_cast(m_process); +} + +const ProcessMockGPU &ThreadMockGPU::GetProcess() const { + return static_cast(m_process); +} diff --git a/lldb/tools/lldb-server/Plugins/MockGPU/ThreadMockGPU.h b/lldb/tools/lldb-server/Plugins/MockGPU/ThreadMockGPU.h new file mode 100644 index 0000000000000..1b46706611c22 --- /dev/null +++ b/lldb/tools/lldb-server/Plugins/MockGPU/ThreadMockGPU.h @@ -0,0 +1,64 @@ +//===-- ThreadMockGPU.h --------------------------------------- -*- C++ -*-===// +// +// 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_TOOLS_LLDB_SERVER_THREADMOCKGPU_H +#define LLDB_TOOLS_LLDB_SERVER_THREADMOCKGPU_H + +#include "RegisterContextMockGPU.h" +#include "lldb/Host/common/NativeThreadProtocol.h" +#include "lldb/lldb-private-forward.h" +#include + +namespace lldb_private { +namespace lldb_server { +class ProcessMockGPU; + +class NativeProcessLinux; + +class ThreadMockGPU : public NativeThreadProtocol { + friend class ProcessMockGPU; + +public: + ThreadMockGPU(ProcessMockGPU &process, lldb::tid_t tid); + + // NativeThreadProtocol Interface + std::string GetName() override; + + lldb::StateType GetState() override; + + bool GetStopReason(ThreadStopInfo &stop_info, + std::string &description) override; + + RegisterContextMockGPU &GetRegisterContext() override { + return m_reg_context; + } + + Status SetWatchpoint(lldb::addr_t addr, size_t size, uint32_t watch_flags, + bool hardware) override; + + Status RemoveWatchpoint(lldb::addr_t addr) override; + + Status SetHardwareBreakpoint(lldb::addr_t addr, size_t size) override; + + Status RemoveHardwareBreakpoint(lldb::addr_t addr) override; + + ProcessMockGPU &GetProcess(); + + const ProcessMockGPU &GetProcess() const; + +private: + // Member Variables + lldb::StateType m_state; + ThreadStopInfo m_stop_info; + RegisterContextMockGPU m_reg_context; + std::string m_stop_description; +}; +} // namespace lldb_server +} // namespace lldb_private + +#endif // #ifndef LLDB_TOOLS_LLDB_SERVER_THREADMOCKGPU_H diff --git a/lldb/tools/lldb-server/lldb-gdbserver.cpp b/lldb/tools/lldb-server/lldb-gdbserver.cpp index e1dc15d83069f..4f818c15dbe5b 100644 --- a/lldb/tools/lldb-server/lldb-gdbserver.cpp +++ b/lldb/tools/lldb-server/lldb-gdbserver.cpp @@ -47,6 +47,14 @@ #include "Plugins/Process/Windows/Common/NativeProcessWindows.h" #endif +#if defined(LLDB_ENABLE_AMDGPU_PLUGIN) +#include "Plugins/AMDGPU/LLDBServerPluginAMDGPU.h" +typedef lldb_private::lldb_server::LLDBServerPluginAMDGPU LLDBServerGPUPlugin; +#elif defined(LLDB_ENABLE_MOCKGPU_PLUGIN) +#include "Plugins/MockGPU/LLDBServerPluginMockGPU.h" +typedef lldb_private::lldb_server::LLDBServerPluginMockGPU LLDBServerGPUPlugin; +#endif + #ifndef LLGS_PROGRAM_NAME #define LLGS_PROGRAM_NAME "lldb-server" #endif @@ -89,7 +97,8 @@ class NativeProcessManager : public NativeProcessProtocol::Manager { } }; #endif -} + +} // namespace #ifndef _WIN32 // Watch for signals @@ -132,9 +141,8 @@ void handle_attach(GDBRemoteCommunicationServerLLGS &gdb_server, const long int pid = strtol(attach_target.c_str(), &end_p, 10); // We'll call it a match if the entire argument is consumed. - if (end_p && - static_cast(end_p - attach_target.c_str()) == - attach_target.size()) + if (end_p && static_cast(end_p - attach_target.c_str()) == + attach_target.size()) handle_attach_to_pid(gdb_server, static_cast(pid)); else handle_attach_to_process_name(gdb_server, attach_target); @@ -435,7 +443,7 @@ int main_gdbserver(int argc, char *argv[]) { if (!LLDBServerUtilities::SetupLogging( log_file, log_channels, LLDB_LOG_OPTION_PREPEND_TIMESTAMP | - LLDB_LOG_OPTION_PREPEND_FILE_FUNCTION)) + LLDB_LOG_OPTION_PREPEND_PROC_AND_THREAD)) return -1; std::vector Inputs; @@ -451,7 +459,22 @@ int main_gdbserver(int argc, char *argv[]) { } NativeProcessManager manager(mainloop); - GDBRemoteCommunicationServerLLGS gdb_server(mainloop, manager); + GDBRemoteCommunicationServerLLGS gdb_server(mainloop, manager, "gdb-server"); + +#if defined(LLDB_ENABLE_AMDGPU_PLUGIN) || defined(LLDB_ENABLE_MOCKGPU_PLUGIN) +#if defined(LLDB_ENABLE_AMDGPU_PLUGIN) + // AMD GPU plugin requires to use the same mainloop as the native process. + // This is because AMD debug API has to be called from the same thread as the + // ptrace() thread. + MainLoop &gpu_mainloop = mainloop; +#else + // Any GPU plugins can use a separate mainloop. + MainLoop gpu_mainloop; +#endif + // Install GPU plugin. + gdb_server.InstallPlugin( + std::make_unique(gdb_server, gpu_mainloop)); +#endif llvm::StringRef host_and_port; if (!Inputs.empty() && connection_fd == SharedSocket::kInvalidFD) { @@ -475,8 +498,8 @@ int main_gdbserver(int argc, char *argv[]) { printf("%s-%s\n", LLGS_PROGRAM_NAME, LLGS_VERSION_STR); ConnectToRemote(mainloop, gdb_server, reverse_connect, host_and_port, - progname, subcommand, named_pipe_path.c_str(), - unnamed_pipe, connection_fd); + progname, subcommand, named_pipe_path.c_str(), unnamed_pipe, + connection_fd); if (!gdb_server.IsConnected()) { fprintf(stderr, "no connection information provided, unable to run\n"); diff --git a/llvm/cmake/modules/CrossCompile.cmake b/llvm/cmake/modules/CrossCompile.cmake index bfbd9cfd4063f..6bcfa4bb7051f 100644 --- a/llvm/cmake/modules/CrossCompile.cmake +++ b/llvm/cmake/modules/CrossCompile.cmake @@ -101,6 +101,7 @@ function(llvm_create_cross_target project_name target_name toolchain buildtype) -DLLVM_INCLUDE_BENCHMARKS=OFF -DLLVM_INCLUDE_TESTS=OFF -DLLVM_TABLEGEN_FLAGS="${LLVM_TABLEGEN_FLAGS}" + -DROCM_PATH=${ROCM_PATH} ${build_type_flags} ${linker_flag} ${external_clang_dir} ${libc_flags} ${ARGN} WORKING_DIRECTORY ${${project_name}_${target_name}_BUILD}