Author: satyanarayana reddy janga Date: 2026-09-02T10:46:41-05:00 New Revision: 54d941486fec3a81f9d8e5a595947d540eace85c
URL: https://github.com/llvm/llvm-project/commit/54d941486fec3a81f9d8e5a595947d540eace85c DIFF: https://github.com/llvm/llvm-project/commit/54d941486fec3a81f9d8e5a595947d540eace85c.diff LOG: [lldb] Add generic address space support (#214089) Relevant RFC: https://discourse.llvm.org/t/rfc-address-spaces-support-in-lldb/91222/ Stacked on #206370 and the https://github.com/llvm/llvm-project/pull/214088 This PR - Adds new gdb remote packets - jAddressSpacesInfo, for retreiving the address spaces from lldb-server. - supports to enable the feature using the `address-spaces+` in `qSupported` - supprot `;address_space:` suffic in memory-read packets. writes are natural extension. - SB API, and docs. https://github.com/llvm/llvm-project/pull/217092 contains an end to end test to validate the address spaces. Added: lldb/test/API/functionalities/gdb_remote_client/TestAddressSpaceMemoryRead.py lldb/test/API/tools/lldb-server/TestGdbRemoteAddressSpaces.py Modified: lldb/bindings/python/python-typemaps.swig lldb/docs/resources/lldbgdbremote.md lldb/include/lldb/API/SBAddress.h lldb/include/lldb/API/SBDefines.h lldb/include/lldb/API/SBProcess.h lldb/include/lldb/Host/common/NativeProcessProtocol.h lldb/include/lldb/Target/Process.h lldb/include/lldb/Utility/StringExtractorGDBRemote.h lldb/packages/Python/lldbsuite/test/tools/lldb-server/gdbremote_testcase.py lldb/source/API/SBAddress.cpp lldb/source/API/SBProcess.cpp lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp lldb/source/Target/Process.cpp lldb/source/Utility/StringExtractorGDBRemote.cpp lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp Removed: ################################################################################ diff --git a/lldb/bindings/python/python-typemaps.swig b/lldb/bindings/python/python-typemaps.swig index f5501bc23043c..9df489c8034f2 100644 --- a/lldb/bindings/python/python-typemaps.swig +++ b/lldb/bindings/python/python-typemaps.swig @@ -359,6 +359,12 @@ AND call SWIG_fail at the same time, because it will result in a double free. $1 = (void *)malloc($2); } +// typecheck so overloaded methods taking a buffer (e.g. SBProcess::ReadMemory) +// can participate in overload resolution. +%typemap(typecheck, precedence=SWIG_TYPECHECK_INTEGER) (void *buf, size_t size) { + $1 = PyLong_Check($input) ? 1 : 0; +} + // Return the buffer. Discarding any previous return result // See also SBProcess::ReadMemory. %typemap(argout) (void *buf, size_t size) { diff --git a/lldb/docs/resources/lldbgdbremote.md b/lldb/docs/resources/lldbgdbremote.md index af12b45b8b86b..3712573e37dde 100644 --- a/lldb/docs/resources/lldbgdbremote.md +++ b/lldb/docs/resources/lldbgdbremote.md @@ -822,6 +822,82 @@ This is a performance optimization, which speeds up debugging by avoiding multiple round-trips for retrieving thread information. The information from this packet can be retrieved using a combination of `qThreadStopInfo` and `m` packets. +## jAddressSpacesInfo + +Ask the server for the address spaces the process exposes. + +Most processes have a single, flat address space, but some (such as GPUs) have +multiple address spaces where the same numeric address refers to diff erent +storage depending on the address space (for example global, local, private or +generic memory). This packet lets the client discover those address spaces. + +This packet requires the `address-spaces+` feature from `qSupported`, which a +server only advertises when its process exposes address spaces. + +The response is a JSON array of dictionaries, one per address space: +``` + [ + {"name":"global","space_id":1,"is_thread_specific":false}, + {"name":"local","space_id":2,"is_thread_specific":true} + ] +``` + +Each dictionary has the following keys: + +* `name`: the human readable name of the address space. +* `space_id`: the integer identifier of the address space. +* `is_thread_specific`: true if the address space is thread specific. + +The client only sends this packet when the server advertised `address-spaces+` +in its `qSupported` response. If a server that advertised the feature has no +address spaces to report, it replies with an unsupported (empty) response. + +**Priority To Implement:** Required for targets that use address spaces, not +needed for targets that don't need address spaces. + +## address-spaces (qSupported feature) + +A server advertises `address-spaces+` in its `qSupported` response when its +process exposes address spaces. This single feature implies both the +`jAddressSpacesInfo` packet and the optional `address_space:<hex-id>;` suffix on +the memory packets described below. + +### The `address_space` suffix + +To read from a specific address space, the client appends an optional +`address_space:<hex-id>;` key-value suffix to the existing memory packets rather +than introducing a dedicated packet, where `<hex-id>` is the address space id +reported by `jAddressSpacesInfo`, in base 16 like the other numeric values in +these packets. An id of `0`, or the absence of the suffix, means the default +address space and behaves exactly as before, so address-space-unaware stubs and +reads are unaffected. + +``` +send packet: $x1000,4;address_space:1a; +read packet: $<binary encoding of the 4 bytes at 0x1000 in address space 0x1a> +``` + +If `jAddressSpacesInfo` reported the address space as `is_thread_specific`, a +`thread:<hex-tid>;` key-value pair is appended to identify the thread the +address belongs to. It is only sent for thread specific address spaces. + +``` +send packet: $x1000,4;address_space:1a;thread:1a2b; +read packet: $<binary encoding of the 4 bytes at 0x1000 in address space 0x1a of thread 0x1a2b> +``` + +Packets that currently accept the suffix: + +* `m` / `x`: read memory from a specific address space. + +Because the suffix is an optional key-value pair on the existing packets, the +same mechanism can be extended to other address-bearing packets (memory writes +`M` / `X`, breakpoints `z` / `Z`, etc.) as the need arises, without introducing +new packets or bifurcating the address-space-aware and unaware code paths. + +**Priority To Implement:** Required for targets that expose more than one +address space, not needed for targets that have a single address space. + ## MultiMemRead Read memory from multiple memory ranges. @@ -2770,6 +2846,11 @@ xADDRESS,LENGTH where both `ADDRESS` and `LENGTH` are big-endian base 16 values. +The `x` packet may also carry an optional `address_space:<hex-id>;` suffix to read +from a non-default address space, followed by `thread:<hex-tid>;` when that +address space is thread specific; see +[the address-spaces feature](#address-spaces-qsupported-feature). + To test if this packet is available, send a addr/len of 0: ``` x0,0 diff --git a/lldb/include/lldb/API/SBAddress.h b/lldb/include/lldb/API/SBAddress.h index 430dad4862dbf..e78641739f7c0 100644 --- a/lldb/include/lldb/API/SBAddress.h +++ b/lldb/include/lldb/API/SBAddress.h @@ -130,6 +130,36 @@ class LLDB_API SBAddress { bool LLDB_API operator==(const SBAddress &lhs, const SBAddress &rhs); #endif +/// A memory address, optionally in a non-default address space. +class LLDB_API SBProcessAddress { +public: + SBProcessAddress(const SBProcessAddress &rhs); + + /// A load address in the default address space. + SBProcessAddress(lldb::addr_t load_addr); + + /// An address in the address space with the given id (0 = default). + SBProcessAddress(lldb::addr_t addr, lldb::addr_space_t address_space_id); + + /// An address in a thread specific address space. + SBProcessAddress(lldb::addr_t addr, lldb::addr_space_t address_space_id, + lldb::SBThread thread); + + ~SBProcessAddress(); + + const lldb::SBProcessAddress &operator=(const lldb::SBProcessAddress &rhs); + +protected: + friend class SBProcess; + + lldb_private::ProcessAddress &ref(); + + const lldb_private::ProcessAddress &ref() const; + +private: + std::unique_ptr<lldb_private::ProcessAddress> m_opaque_up; +}; + } // namespace lldb #endif // LLDB_API_SBADDRESS_H diff --git a/lldb/include/lldb/API/SBDefines.h b/lldb/include/lldb/API/SBDefines.h index 7ec8e56067aa6..951a656c05016 100644 --- a/lldb/include/lldb/API/SBDefines.h +++ b/lldb/include/lldb/API/SBDefines.h @@ -45,6 +45,7 @@ namespace lldb { class LLDB_API SBAddress; class LLDB_API SBAddressRange; class LLDB_API SBAddressRangeList; +class LLDB_API SBProcessAddress; class LLDB_API SBAttachInfo; class LLDB_API SBBlock; class LLDB_API SBBreakpoint; diff --git a/lldb/include/lldb/API/SBProcess.h b/lldb/include/lldb/API/SBProcess.h index f42b30007a64b..58e77434c4dec 100644 --- a/lldb/include/lldb/API/SBProcess.h +++ b/lldb/include/lldb/API/SBProcess.h @@ -199,6 +199,13 @@ class LLDB_API SBProcess { size_t ReadMemory(addr_t addr, void *buf, size_t size, lldb::SBError &error); + /// Read memory that may be in a non-default address space. + size_t ReadMemory(SBProcessAddress process_addr, void *buf, size_t size, + lldb::SBError &error); + + /// Resolve an address space name to its id, or LLDB_INVALID_ADDRESS_SPACE_ID. + lldb::addr_space_t GetAddressSpaceID(const char *name, lldb::SBError &error); + size_t WriteMemory(addr_t addr, const void *buf, size_t size, lldb::SBError &error); diff --git a/lldb/include/lldb/Host/common/NativeProcessProtocol.h b/lldb/include/lldb/Host/common/NativeProcessProtocol.h index 2bb9e9a2532ad..58c91bcd84a3b 100644 --- a/lldb/include/lldb/Host/common/NativeProcessProtocol.h +++ b/lldb/include/lldb/Host/common/NativeProcessProtocol.h @@ -14,6 +14,7 @@ #include "NativeWatchpointList.h" #include "lldb/Host/Host.h" #include "lldb/Host/MainLoop.h" +#include "lldb/Utility/AddressSpace.h" #include "lldb/Utility/ArchSpec.h" #include "lldb/Utility/Iterable.h" #include "lldb/Utility/ProcessAddress.h" @@ -97,6 +98,10 @@ class NativeProcessProtocol { virtual Status GetMemoryRegionInfo(lldb::addr_t load_addr, MemoryRegionInfo &range_info); + /// Served over the "jAddressSpacesInfo" packet. + virtual std::vector<AddressSpaceInfo> GetAddressSpaces() { return {}; } + + /// Plugins without address spaces should error on a non-default one. virtual Status ReadMemory(const ProcessAddress &addr, void *buf, size_t size, size_t &bytes_read) = 0; @@ -300,8 +305,9 @@ class NativeProcessProtocol { siginfo_read = (1u << 8), libraries = (1u << 9), accelerator_plugins = (1u << 10), + address_spaces = (1u << 11), - LLVM_MARK_AS_BITMASK_ENUM(accelerator_plugins) + LLVM_MARK_AS_BITMASK_ENUM(address_spaces) }; class Manager { diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h index 2eedf2948d4da..d74b71af6e61a 100644 --- a/lldb/include/lldb/Target/Process.h +++ b/lldb/include/lldb/Target/Process.h @@ -46,6 +46,7 @@ #include "lldb/Target/ThreadList.h" #include "lldb/Target/ThreadPlanStack.h" #include "lldb/Target/Trace.h" +#include "lldb/Utility/AddressSpace.h" #include "lldb/Utility/AddressableBits.h" #include "lldb/Utility/ArchSpec.h" #include "lldb/Utility/Args.h" @@ -2065,6 +2066,12 @@ class Process : public std::enable_shared_from_this<Process>, virtual Status GetMemoryRegions(lldb_private::MemoryRegionInfos ®ion_list); + llvm::Expected<AddressSpaceInfo> + GetAddressSpaceInfo(llvm::StringRef address_space_name); + + llvm::Expected<AddressSpaceInfo> + GetAddressSpaceInfo(lldb::addr_space_t address_space_id); + /// Get the number of watchpoints supported by this target. /// /// We may be able to determine the number of watchpoints available @@ -3523,6 +3530,9 @@ void PruneThreadPlans(); ThreadList m_extended_thread_list; ///< Constituent for extended threads that may be /// generated, cleared on natural stops + /// A list of address spaces for this process. Empty for single address space + /// processes. + std::vector<AddressSpaceInfo> m_address_spaces; lldb::RunDirection m_base_direction; ///< ThreadPlanBase run direction uint32_t m_extended_thread_stop_id; ///< The natural stop id when ///extended_thread_list was last updated diff --git a/lldb/include/lldb/Utility/StringExtractorGDBRemote.h b/lldb/include/lldb/Utility/StringExtractorGDBRemote.h index 0236891dfcde1..fec3c09f225d7 100644 --- a/lldb/include/lldb/Utility/StringExtractorGDBRemote.h +++ b/lldb/include/lldb/Utility/StringExtractorGDBRemote.h @@ -108,6 +108,7 @@ class StringExtractorGDBRemote : public StringExtractor { eServerPacketType_QThreadSuffixSupported, eServerPacketType_jThreadsInfo, + eServerPacketType_jAddressSpacesInfo, eServerPacketType_qsThreadInfo, eServerPacketType_qfThreadInfo, eServerPacketType_qGetPid, diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-server/gdbremote_testcase.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-server/gdbremote_testcase.py index 76b49c205795c..b88fced342218 100644 --- a/lldb/packages/Python/lldbsuite/test/tools/lldb-server/gdbremote_testcase.py +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-server/gdbremote_testcase.py @@ -945,6 +945,7 @@ def add_qSupported_packets(self, client_features=[]): "fork-events", "vfork-events", "memory-tagging", + "address-spaces", "qSaveCore", "native-signals", "QNonStop", diff --git a/lldb/source/API/SBAddress.cpp b/lldb/source/API/SBAddress.cpp index 5015b6d81d732..45b6910bf9039 100644 --- a/lldb/source/API/SBAddress.cpp +++ b/lldb/source/API/SBAddress.cpp @@ -11,9 +11,11 @@ #include "lldb/API/SBProcess.h" #include "lldb/API/SBSection.h" #include "lldb/API/SBStream.h" +#include "lldb/API/SBThread.h" #include "lldb/Core/Address.h" #include "lldb/Core/Module.h" #include "lldb/Symbol/LineEntry.h" +#include "lldb/Target/Process.h" #include "lldb/Target/Target.h" #include "lldb/Utility/Instrumentation.h" #include "lldb/Utility/StreamString.h" @@ -261,3 +263,41 @@ SBLineEntry SBAddress::GetLineEntry() { } return sb_line_entry; } + +SBProcessAddress::SBProcessAddress(const SBProcessAddress &rhs) + : m_opaque_up(new ProcessAddress(rhs.ref())) { + LLDB_INSTRUMENT_VA(this, rhs); +} + +SBProcessAddress::SBProcessAddress(lldb::addr_t load_addr) + : m_opaque_up(new ProcessAddress(load_addr)) { + LLDB_INSTRUMENT_VA(this, load_addr); +} + +SBProcessAddress::SBProcessAddress(lldb::addr_t addr, + lldb::addr_space_t address_space_id) + : m_opaque_up(new ProcessAddress(addr, address_space_id)) { + LLDB_INSTRUMENT_VA(this, addr, address_space_id); +} + +SBProcessAddress::SBProcessAddress(lldb::addr_t addr, + lldb::addr_space_t address_space_id, + lldb::SBThread thread) + : m_opaque_up( + new ProcessAddress(addr, address_space_id, thread.GetThreadID())) { + LLDB_INSTRUMENT_VA(this, addr, address_space_id, thread); +} + +SBProcessAddress::~SBProcessAddress() = default; + +ProcessAddress &SBProcessAddress::ref() { return *m_opaque_up; } + +const ProcessAddress &SBProcessAddress::ref() const { return *m_opaque_up; } + +const SBProcessAddress & +SBProcessAddress::operator=(const SBProcessAddress &rhs) { + LLDB_INSTRUMENT_VA(this, rhs); + if (this != &rhs) + m_opaque_up = clone(rhs.m_opaque_up); + return *this; +} diff --git a/lldb/source/API/SBProcess.cpp b/lldb/source/API/SBProcess.cpp index a7980251c9578..288acfad62b2b 100644 --- a/lldb/source/API/SBProcess.cpp +++ b/lldb/source/API/SBProcess.cpp @@ -27,12 +27,14 @@ #include "lldb/Target/SystemRuntime.h" #include "lldb/Target/Target.h" #include "lldb/Target/Thread.h" +#include "lldb/Utility/AddressSpace.h" #include "lldb/Utility/Args.h" #include "lldb/Utility/LLDBLog.h" #include "lldb/Utility/ProcessInfo.h" #include "lldb/Utility/State.h" #include "lldb/Utility/Stream.h" +#include "lldb/API/SBAddress.h" #include "lldb/API/SBBroadcaster.h" #include "lldb/API/SBCommandReturnObject.h" #include "lldb/API/SBDebugger.h" @@ -905,6 +907,60 @@ size_t SBProcess::ReadMemory(addr_t addr, void *dst, size_t dst_len, return bytes_read; } +size_t SBProcess::ReadMemory(SBProcessAddress process_addr, void *dst, + size_t dst_len, SBError &sb_error) { + LLDB_INSTRUMENT_VA(this, process_addr, dst, dst_len, sb_error); + + if (!dst) { + sb_error = Status::FromErrorStringWithFormat( + "no buffer provided to read %zu bytes into", dst_len); + return 0; + } + + ProcessSP process_sp(GetSP()); + if (!process_sp) { + sb_error = Status::FromErrorString("SBProcess is invalid"); + return 0; + } + + Process::StopLocker stop_locker; + if (!stop_locker.TryLock(&process_sp->GetRunLock())) { + sb_error = Status::FromErrorString("process is running"); + return 0; + } + + TargetAPIMutex api_lock = process_sp->GetTarget().GetAPIMutex(); + std::lock_guard<TargetAPIMutex> guard(api_lock); + return process_sp->ReadMemory(process_addr.ref(), dst, dst_len, + sb_error.ref()); +} + +lldb::addr_space_t SBProcess::GetAddressSpaceID(const char *name, + SBError &sb_error) { + LLDB_INSTRUMENT_VA(this, name, sb_error); + + ProcessSP process_sp(GetSP()); + if (!process_sp) { + sb_error = Status::FromErrorString("SBProcess is invalid"); + return LLDB_INVALID_ADDRESS_SPACE_ID; + } + + if (!name || !name[0]) { + sb_error = Status::FromErrorString("an address space name is required"); + return LLDB_INVALID_ADDRESS_SPACE_ID; + } + + TargetAPIMutex api_lock = process_sp->GetTarget().GetAPIMutex(); + std::lock_guard<TargetAPIMutex> guard(api_lock); + llvm::Expected<AddressSpaceInfo> info = process_sp->GetAddressSpaceInfo(name); + if (!info) { + sb_error = Status::FromError(info.takeError()); + return LLDB_INVALID_ADDRESS_SPACE_ID; + } + sb_error.Clear(); + return info->space_id; +} + size_t SBProcess::ReadCStringFromMemory(addr_t addr, void *buf, size_t size, lldb::SBError &sb_error) { LLDB_INSTRUMENT_VA(this, addr, buf, size, sb_error); diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp index 1397abf02d7d6..53665cd409739 100644 --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp @@ -392,6 +392,7 @@ void GDBRemoteCommunicationClient::ResetDiscoverableSettings(bool did_exec) { m_attach_or_wait_reply = eLazyBoolCalculate; m_avoid_g_packets = eLazyBoolCalculate; m_supports_multiprocess = eLazyBoolCalculate; + m_supports_address_spaces = false; m_supports_qSaveCore = eLazyBoolCalculate; m_supports_qXfer_auxv_read = eLazyBoolCalculate; m_supports_qXfer_libraries_read = eLazyBoolCalculate; @@ -460,6 +461,7 @@ void GDBRemoteCommunicationClient::GetRemoteQSupported() { m_supports_QPassSignals = eLazyBoolNo; m_supports_memory_tagging = eLazyBoolNo; m_supports_qSaveCore = eLazyBoolNo; + m_supports_address_spaces = false; m_uses_native_signals = eLazyBoolNo; m_x_packet_state.reset(); m_supports_reverse_continue = eLazyBoolNo; @@ -521,6 +523,8 @@ void GDBRemoteCommunicationClient::GetRemoteQSupported() { m_supports_memory_tagging = eLazyBoolYes; else if (x == "qSaveCore+") m_supports_qSaveCore = eLazyBoolYes; + else if (x == "address-spaces+") + m_supports_address_spaces = true; else if (x == "native-signals+") m_uses_native_signals = eLazyBoolYes; else if (x == "binary-upload+") @@ -1172,6 +1176,34 @@ GDBRemoteCommunicationClient::GetProcessStandaloneBinaries() { return m_binary_addresses; } +std::vector<AddressSpaceInfo> GDBRemoteCommunicationClient::GetAddressSpaces() { + if (!m_supports_address_spaces) + return {}; + + StringExtractorGDBRemote response; + response.SetResponseValidatorToJSON(); + if (SendPacketAndWaitForResponse("jAddressSpacesInfo", response) != + PacketResult::Success) + return {}; + + if (response.IsUnsupportedResponse() || response.IsErrorResponse()) { + m_supports_address_spaces = false; + return {}; + } + + llvm::Expected<std::vector<AddressSpaceInfo>> info = + llvm::json::parse<std::vector<AddressSpaceInfo>>(response.Peek(), + "AddressSpaceInfo"); + if (info) + return std::move(*info); + + Log *log = GetLog(GDBRLog::Process); + LLDB_LOG_ERROR(log, info.takeError(), + "malformed jAddressSpacesInfo response '{1}': {0}", + response.GetStringRef()); + return {}; +} + bool GDBRemoteCommunicationClient::GetGDBServerVersion() { if (m_qGDBServerVersion_is_valid == eLazyBoolCalculate) { m_gdb_server_name.clear(); diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h index cc16949eef000..dd612177ad45a 100644 --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h @@ -20,6 +20,7 @@ #include "lldb/Host/File.h" #include "lldb/Utility/AcceleratorGDBRemotePackets.h" +#include "lldb/Utility/AddressSpace.h" #include "lldb/Utility/AddressableBits.h" #include "lldb/Utility/ArchSpec.h" #include "lldb/Utility/GDBRemote.h" @@ -223,6 +224,12 @@ class GDBRemoteCommunicationClient : public GDBRemoteClientBase { std::vector<lldb::addr_t> GetProcessStandaloneBinaries(); + /// Empty if the server does not support "jAddressSpacesInfo". + std::vector<AddressSpaceInfo> GetAddressSpaces(); + + /// Whether the server advertised address-space support ("address-spaces+"). + bool GetAddressSpacesSupported() const { return m_supports_address_spaces; } + void GetRemoteQSupported(); bool GetVContSupported(llvm::StringRef flavor); @@ -611,6 +618,7 @@ class GDBRemoteCommunicationClient : public GDBRemoteClientBase { LazyBool m_supports_error_string_reply = eLazyBoolCalculate; LazyBool m_supports_multiprocess = eLazyBoolCalculate; LazyBool m_supports_memory_tagging = eLazyBoolCalculate; + bool m_supports_address_spaces = false; LazyBool m_supports_qSaveCore = eLazyBoolCalculate; LazyBool m_uses_native_signals = eLazyBoolCalculate; std::optional<xPacketState> m_x_packet_state; diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp index 7b691160bffed..38aeb1bb40903 100644 --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp @@ -164,6 +164,9 @@ void GDBRemoteCommunicationServerLLGS::RegisterPacketHandlers() { RegisterMemberFunctionHandler( StringExtractorGDBRemote::eServerPacketType_jThreadsInfo, &GDBRemoteCommunicationServerLLGS::Handle_jThreadsInfo); + RegisterMemberFunctionHandler( + StringExtractorGDBRemote::eServerPacketType_jAddressSpacesInfo, + &GDBRemoteCommunicationServerLLGS::Handle_jAddressSpacesInfo); RegisterMemberFunctionHandler( StringExtractorGDBRemote::eServerPacketType_qWatchpointSupportInfo, &GDBRemoteCommunicationServerLLGS::Handle_qWatchpointSupportInfo); @@ -2678,6 +2681,25 @@ GDBRemoteCommunicationServerLLGS::Handle_memory_read( return SendOKResponse(); } + // Optional "address_space:<hex-id>;" suffix, with "thread:<hex-tid>;" for a + // thread specific address space (see the "address-spaces" feature). + lldb::addr_space_t address_space = LLDB_DEFAULT_ADDRESS_SPACE_ID; + std::optional<lldb::tid_t> tid; + if (m_address_space_suffix_supported && packet.GetBytesLeft() > 0 && + packet.GetChar() == ';') { + llvm::StringRef name, value; + while (packet.GetNameColonValue(name, value)) { + if (name == "address_space" && value.getAsInteger(16, address_space)) + return SendIllFormedResponse(packet, "invalid address_space suffix"); + if (name == "thread") { + lldb::tid_t parsed_tid = LLDB_INVALID_THREAD_ID; + if (value.getAsInteger(16, parsed_tid)) + return SendIllFormedResponse(packet, "invalid thread suffix"); + tid = parsed_tid; + } + } + } + // Allocate the response buffer. std::string buf(byte_count, '\0'); if (buf.empty()) @@ -2686,11 +2708,13 @@ GDBRemoteCommunicationServerLLGS::Handle_memory_read( // Retrieve the process memory. size_t bytes_read = 0; Status error = m_current_process->ReadMemoryWithoutTrap( - read_addr, &buf[0], byte_count, bytes_read); - LLDB_LOG( - log, - "ReadMemoryWithoutTrap({0}) read {1} of {2} requested bytes (error: {3})", - read_addr, byte_count, bytes_read, error); + ProcessAddress(read_addr, address_space, tid), &buf[0], byte_count, + bytes_read); + LLDB_LOG(log, + "read {2} of {1} requested bytes at {0:x} in address_space {4} " + "thread {5} (error: {3})", + read_addr, byte_count, bytes_read, error, address_space, + tid ? *tid : LLDB_INVALID_THREAD_ID); if (bytes_read == 0) return SendErrorResponse(0x08); @@ -3924,6 +3948,28 @@ GDBRemoteCommunicationServerLLGS::Handle_jThreadsInfo( return SendPacketNoLock(escaped_response.GetString()); } +GDBRemoteCommunication::PacketResult +GDBRemoteCommunicationServerLLGS::Handle_jAddressSpacesInfo( + StringExtractorGDBRemote &packet) { + Log *log = GetLog(LLDBLog::Process); + + // Ensure we have a process. + if (!m_current_process || + (m_current_process->GetID() == LLDB_INVALID_PROCESS_ID)) { + LLDB_LOG(log, "failed, no process available"); + return SendErrorResponse(Status::FromErrorString("invalid process")); + } + + std::vector<AddressSpaceInfo> address_spaces = + m_current_process->GetAddressSpaces(); + if (address_spaces.empty()) + return SendUnimplementedResponse(packet.GetStringRef().data()); + + StreamGDBRemote response; + response.PutAsJSONArray(address_spaces, /*hex_ascii=*/false); + return SendPacketNoLock(response.GetString()); +} + GDBRemoteCommunication::PacketResult GDBRemoteCommunicationServerLLGS::Handle_qWatchpointSupportInfo( StringExtractorGDBRemote &packet) { @@ -4514,6 +4560,10 @@ std::vector<std::string> GDBRemoteCommunicationServerLLGS::HandleFeatures( ret.push_back("memory-tagging+"); if (bool(plugin_features & Extension::savecore)) ret.push_back("qSaveCore+"); + if (bool(plugin_features & Extension::address_spaces)) { + ret.push_back("address-spaces+"); + m_address_space_suffix_supported = true; + } if (!m_accelerator_plugins.empty()) ret.push_back("accelerator-plugins+"); diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h index 3c8afe699bbaf..f3f1bbc557244 100644 --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h @@ -137,6 +137,7 @@ class GDBRemoteCommunicationServerLLGS std::unordered_map<uint32_t, lldb::DataBufferSP> m_saved_registers_map; uint32_t m_next_saved_registers_id = 1; bool m_thread_suffix_supported = false; + bool m_address_space_suffix_supported = false; bool m_list_threads_in_stop_reply = false; bool m_non_stop = false; bool m_disabling_non_stop = false; @@ -268,6 +269,8 @@ class GDBRemoteCommunicationServerLLGS PacketResult Handle_jThreadsInfo(StringExtractorGDBRemote &packet); + PacketResult Handle_jAddressSpacesInfo(StringExtractorGDBRemote &packet); + PacketResult Handle_qWatchpointSupportInfo(StringExtractorGDBRemote &packet); PacketResult Handle_qFileLoadAddress(StringExtractorGDBRemote &packet); diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp index ff09740e637a3..bcce0b259d9f1 100644 --- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp @@ -1017,6 +1017,9 @@ Status ProcessGDBRemote::ConnectToDebugserver(llvm::StringRef connect_url) { m_gdb_comm.GetVAttachOrWaitSupported(); m_gdb_comm.EnableErrorStringInPacket(); + // Empty unless the server advertised "address-spaces+" in qSupported. + m_address_spaces = m_gdb_comm.GetAddressSpaces(); + // First dispatch any commands from the platform: auto handle_cmds = [&] (const Args &args) -> void { for (const Args::ArgEntry &entry : args) { @@ -2910,6 +2913,13 @@ size_t ProcessGDBRemote::DoReadMemory(const ProcessAddress &process_addr, using xPacketState = GDBRemoteCommunicationClient::xPacketState; lldb::addr_t addr = process_addr.GetValue(); + lldb::addr_space_t addr_space = process_addr.GetAddressSpace(); + if (addr_space != LLDB_DEFAULT_ADDRESS_SPACE_ID && + !m_gdb_comm.GetAddressSpacesSupported()) { + error = Status::FromErrorString("address spaces are not supported"); + return 0; + } + GetMaxMemorySize(); xPacketState x_state = m_gdb_comm.GetxPacketState(); @@ -2924,11 +2934,35 @@ size_t ProcessGDBRemote::DoReadMemory(const ProcessAddress &process_addr, size = max_memory_size; } - char packet[64]; - int packet_len; - packet_len = ::snprintf(packet, sizeof(packet), "%c%" PRIx64 ",%" PRIx64, - x_state != xPacketState::Unimplemented ? 'x' : 'm', - (uint64_t)addr, (uint64_t)size); + // A non-default address space rides on an "address_space:<hex-id>;" suffix, + // followed by "thread:<hex-tid>;" when that space is thread specific. + std::string suffix; + if (addr_space != LLDB_DEFAULT_ADDRESS_SPACE_ID) { + llvm::Expected<AddressSpaceInfo> info = GetAddressSpaceInfo(addr_space); + if (!info) { + error = Status::FromError(info.takeError()); + return 0; + } + suffix = + llvm::formatv(";address_space:{0};", llvm::utohexstr(addr_space, true)); + if (info->is_thread_specific) { + std::optional<lldb::tid_t> tid = process_addr.GetThreadID(); + if (!tid) { + error = Status::FromErrorStringWithFormat( + "address space \"%s\" is thread specific, but no thread was " + "specified", + info->name.c_str()); + return 0; + } + suffix += llvm::formatv("thread:{0};", llvm::utohexstr(*tid, true)); + } + } + + char packet[128]; + int packet_len = + ::snprintf(packet, sizeof(packet), "%c%" PRIx64 ",%" PRIx64 "%s", + x_state != xPacketState::Unimplemented ? 'x' : 'm', + (uint64_t)addr, (uint64_t)size, suffix.c_str()); assert(packet_len + 1 < (int)sizeof(packet)); UNUSED_IF_ASSERT_DISABLED(packet_len); StringExtractorGDBRemote response; diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp index ea5b021b47775..d68402f49caab 100644 --- a/lldb/source/Target/Process.cpp +++ b/lldb/source/Target/Process.cpp @@ -2082,11 +2082,23 @@ void Process::VerifyMemoryRead(addr_t addr, const void *cache_buf, size_t Process::ReadMemory(const ProcessAddress &process_addr, void *buf, size_t size, Status &error) { + error.Clear(); + + // Non-default address spaces bypass the flat memory cache. + if (!process_addr.IsInDefaultAddressSpace()) { + llvm::Expected<AddressSpaceInfo> info = + GetAddressSpaceInfo(process_addr.GetAddressSpace()); + if (!info) { + error = Status::FromError(info.takeError()); + return 0; + } + return DoReadMemory(process_addr, buf, size, error); + } + lldb::addr_t addr = process_addr.GetValue(); if (ABISP abi_sp = GetABI()) addr = abi_sp->FixAnyAddress(addr); - error.Clear(); if (GetDisableMemoryCache()) return ReadMemoryFromInferior(addr, buf, size, error); @@ -7144,3 +7156,43 @@ void Process::SetAddressableBitMasks(AddressableBits bit_masks) { SetHighmemDataAddressMask(high_addr_mask); } } + +llvm::Expected<AddressSpaceInfo> +Process::GetAddressSpaceInfo(llvm::StringRef address_space_name) { + if (m_address_spaces.empty()) + return llvm::createStringError("process doesn't support address spaces"); + + for (const AddressSpaceInfo &info : m_address_spaces) { + if (address_space_name == info.name) + return info; + } + + std::string names = llvm::join( + llvm::map_range(m_address_spaces, + [](const AddressSpaceInfo &info) { return info.name; }), + ", "); + return llvm::createStringError( + "invalid address space \"%s\", expected one of: %s", + address_space_name.str().c_str(), names.c_str()); +} + +llvm::Expected<AddressSpaceInfo> +Process::GetAddressSpaceInfo(lldb::addr_space_t address_space_id) { + if (m_address_spaces.empty()) + return llvm::createStringError("process doesn't support address spaces"); + + for (const AddressSpaceInfo &info : m_address_spaces) { + if (info.space_id == address_space_id) + return info; + } + + std::string ids = + llvm::join(llvm::map_range(m_address_spaces, + [](const AddressSpaceInfo &info) { + return std::to_string(info.space_id); + }), + ", "); + return llvm::createStringError("invalid address space id %" PRIu64 + ", expected one of: %s", + address_space_id, ids.c_str()); +} diff --git a/lldb/source/Utility/StringExtractorGDBRemote.cpp b/lldb/source/Utility/StringExtractorGDBRemote.cpp index 953a2cd3090d1..a015e7c3c5c8b 100644 --- a/lldb/source/Utility/StringExtractorGDBRemote.cpp +++ b/lldb/source/Utility/StringExtractorGDBRemote.cpp @@ -321,6 +321,8 @@ StringExtractorGDBRemote::GetServerPacketType() const { return eServerPacketType_jSignalsInfo; if (PACKET_MATCHES("jThreadsInfo")) return eServerPacketType_jThreadsInfo; + if (PACKET_MATCHES("jAddressSpacesInfo")) + return eServerPacketType_jAddressSpacesInfo; if (PACKET_MATCHES("jLLDBTraceSupported")) return eServerPacketType_jLLDBTraceSupported; diff --git a/lldb/test/API/functionalities/gdb_remote_client/TestAddressSpaceMemoryRead.py b/lldb/test/API/functionalities/gdb_remote_client/TestAddressSpaceMemoryRead.py new file mode 100644 index 0000000000000..1f4268bd26bdc --- /dev/null +++ b/lldb/test/API/functionalities/gdb_remote_client/TestAddressSpaceMemoryRead.py @@ -0,0 +1,123 @@ +import lldb +from lldbsuite.test.lldbtest import * +from lldbsuite.test.decorators import * +from lldbsuite.test.gdbclientutils import * +from lldbsuite.test.lldbgdbclient import GDBRemoteTestBase + + +class TestAddressSpaceMemoryRead(GDBRemoteTestBase): + """ + End-to-end test that the same numeric address read from two diff erent + address spaces returns diff erent bytes. The server advertises + "address-spaces+" in qSupported, reports the spaces via "jAddressSpacesInfo", + and reads memory with an optional "address_space:<hex-id>;" suffix on the + standard memory packet. + """ + + def test(self): + address_spaces_json = ( + '[{"name":"global","space_id":1,"is_thread_specific":false},' + '{"name":"local","space_id":26,"is_thread_specific":true}]' + ) + + class MyResponder(MockGDBServerResponder): + def qSupported(self, client_supported): + return "PacketSize=3fff;QStartNoAckMode+;address-spaces+" + + def qHostInfo(self): + return "ptrsize:8;endian:little;" + + def _bytes_for_space(self, space): + if space == 1: + return "aabbccdd" + if space == 26: + return "11223344" + return "E01" + + def __init__(self): + super().__init__() + self.reads = [] + + def _respond_impl(self, packet): + # The base dispatcher can't parse the "address_space:<hex-id>;" + # suffix, so handle suffixed reads here. + if packet and packet[0] in ("m", "x") and "address_space:" in packet: + self.reads.append(packet) + space = 0 + for field in packet[1:].split(";"): + key, _, value = field.partition(":") + if key == "address_space": + space = int(value, 16) + return self._bytes_for_space(space) + return super()._respond_impl(packet) + + def x(self, addr, length): + # Force the client onto the hex "m" read path. + return "" + + def other(self, packet): + if packet == "jAddressSpacesInfo": + return escape_binary(address_spaces_json) + return "" + + self.server.responder = MyResponder() + target = self.dbg.CreateTarget("") + process = self.connect(target) + + error = lldb.SBError() + + # Same numeric address, two spaces (global == id 1, local == id 26). + global_bytes = process.ReadMemory(lldb.SBProcessAddress(0x1000, 1), 4, error) + self.assertSuccess(error) + self.assertEqual(global_bytes, b"\xaa\xbb\xcc\xdd") + + # "local" is thread specific, so reading it needs a thread. + thread = process.GetThreadAtIndex(0) + self.assertTrue(thread.IsValid()) + local_bytes = process.ReadMemory( + lldb.SBProcessAddress(0x1000, 26, thread), 4, error + ) + self.assertSuccess(error) + self.assertEqual(local_bytes, b"\x11\x22\x33\x44") + + # Same address, diff erent address space, diff erent bytes. + self.assertNotEqual(global_bytes, local_bytes) + + # Ids go out in base 16, like the other numeric values in the packet. + self.assertEqual(len(self.server.responder.reads), 2) + self.assertIn("address_space:1;", self.server.responder.reads[0]) + self.assertIn("address_space:1a;", self.server.responder.reads[1]) + + # Only the thread specific read carries a "thread:" field, and it names + # the thread that was asked for. + self.assertNotIn("thread:", self.server.responder.reads[0]) + self.assertIn( + "thread:%x;" % thread.GetThreadID(), self.server.responder.reads[1] + ) + + # A thread specific space without a thread is an error, not a read. + process.ReadMemory(lldb.SBProcessAddress(0x1000, 26), 4, error) + self.assertTrue(error.Fail()) + self.assertIn("thread specific", error.GetCString()) + + # Address spaces can be resolved by name, and reading through the + # resolved id gives the same bytes as reading through the id directly. + global_id = process.GetAddressSpaceID("global", error) + self.assertSuccess(error) + self.assertEqual(global_id, 1) + self.assertEqual( + process.ReadMemory(lldb.SBProcessAddress(0x1000, global_id), 4, error), + global_bytes, + ) + self.assertSuccess(error) + + self.assertEqual(process.GetAddressSpaceID("local", error), 26) + self.assertSuccess(error) + + # An unknown name is an error rather than a silent default. + process.GetAddressSpaceID("nonexistent", error) + self.assertTrue(error.Fail()) + + # So is no name at all. + process.GetAddressSpaceID(None, error) + self.assertTrue(error.Fail()) diff --git a/lldb/test/API/tools/lldb-server/TestGdbRemoteAddressSpaces.py b/lldb/test/API/tools/lldb-server/TestGdbRemoteAddressSpaces.py new file mode 100644 index 0000000000000..219ae097914b5 --- /dev/null +++ b/lldb/test/API/tools/lldb-server/TestGdbRemoteAddressSpaces.py @@ -0,0 +1,28 @@ +import gdbremote_testcase +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * + + +class TestGdbRemoteAddressSpaces(gdbremote_testcase.GdbRemoteTestCaseBase): + def test_qSupported_no_address_spaces_by_default(self): + self.build() + self.set_inferior_startup_launch() + self.prep_debug_monitor_and_inferior() + self.add_qSupported_packets() + features = self.parse_qSupported_response(self.expect_gdbremote_sequence()) + # A process with no address spaces does not advertise "address-spaces+". + self.assertNotIn("address-spaces", features) + + def test_jAddressSpacesInfo_empty_by_default(self): + self.build() + self.set_inferior_startup_launch() + self.prep_debug_monitor_and_inferior() + + self.test_sequence.add_log_lines( + [ + "read packet: $jAddressSpacesInfo#00", + "send packet: $#00", + ], + True, + ) + self.expect_gdbremote_sequence() diff --git a/lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp b/lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp index 3ec212b1c205d..d29e04f00a529 100644 --- a/lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp +++ b/lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp @@ -11,7 +11,9 @@ #include "lldb/Host/ConnectionFileDescriptor.h" #include "lldb/Host/XML.h" #include "lldb/Target/MemoryRegionInfo.h" +#include "lldb/Utility/AddressSpace.h" #include "lldb/Utility/DataBuffer.h" +#include "lldb/Utility/GDBRemote.h" #include "lldb/Utility/StructuredData.h" #include "lldb/lldb-enumerations.h" #include "llvm/ADT/ArrayRef.h" @@ -191,6 +193,71 @@ TEST_F(GDBRemoteCommunicationClientTest, ReadRegister) { memcmp(buffer_sp->GetBytes(), all_registers, sizeof all_registers)); } +// Advertise "address-spaces+" so the client enables address-space packets. +static void EnableAddressSpaces(GDBRemoteCommunicationClient &client, + MockServer &server) { + std::future<void> result = + std::async(std::launch::async, [&] { client.GetRemoteQSupported(); }); + HandlePacket(server, testing::StartsWith("qSupported:"), + "PacketSize=3fff;address-spaces+"); + result.get(); +} + +TEST_F(GDBRemoteCommunicationClientTest, GetAddressSpaces) { + EnableAddressSpaces(client, server); + std::future<std::vector<AddressSpaceInfo>> result = + std::async(std::launch::async, [&] { return client.GetAddressSpaces(); }); + StreamGDBRemote escaped; + llvm::StringRef json = + R"([{"name":"global","space_id":1,"is_thread_specific":false},)" + R"({"name":"local","space_id":2,"is_thread_specific":true}])"; + escaped.PutEscapedBytes(json.data(), json.size()); + HandlePacket(server, "jAddressSpacesInfo", escaped.GetString()); + + std::vector<AddressSpaceInfo> spaces = result.get(); + ASSERT_EQ(spaces.size(), 2u); + EXPECT_EQ(spaces[0].name, "global"); + EXPECT_EQ(spaces[0].space_id, 1u); + EXPECT_FALSE(spaces[0].is_thread_specific); + EXPECT_EQ(spaces[1].name, "local"); + EXPECT_EQ(spaces[1].space_id, 2u); + EXPECT_TRUE(spaces[1].is_thread_specific); +} + +TEST_F(GDBRemoteCommunicationClientTest, GetAddressSpacesThreadSpecific) { + EnableAddressSpaces(client, server); + std::future<std::vector<AddressSpaceInfo>> result = + std::async(std::launch::async, [&] { return client.GetAddressSpaces(); }); + StreamGDBRemote escaped; + llvm::StringRef json = + R"([{"name":"private","space_id":7,"is_thread_specific":true}])"; + escaped.PutEscapedBytes(json.data(), json.size()); + HandlePacket(server, "jAddressSpacesInfo", escaped.GetString()); + + std::vector<AddressSpaceInfo> spaces = result.get(); + ASSERT_EQ(spaces.size(), 1u); + EXPECT_EQ(spaces[0].name, "private"); + EXPECT_EQ(spaces[0].space_id, 7u); + EXPECT_TRUE(spaces[0].is_thread_specific); +} + +TEST_F(GDBRemoteCommunicationClientTest, GetAddressSpacesNotSupported) { + // Without "address-spaces+" in qSupported the client never sends the packet. + EXPECT_TRUE(client.GetAddressSpaces().empty()); +} + +TEST_F(GDBRemoteCommunicationClientTest, GetAddressSpacesMalformed) { + EnableAddressSpaces(client, server); + std::future<std::vector<AddressSpaceInfo>> result = + std::async(std::launch::async, [&] { return client.GetAddressSpaces(); }); + // Missing required fields, so parsing fails and the client returns empty. + StreamGDBRemote escaped; + llvm::StringRef malformed = R"([{"name":"global"}])"; + escaped.PutEscapedBytes(malformed.data(), malformed.size()); + HandlePacket(server, "jAddressSpacesInfo", escaped.GetString()); + EXPECT_TRUE(result.get().empty()); +} + TEST_F(GDBRemoteCommunicationClientTest, SaveRestoreRegistersNoSuffix) { const lldb::tid_t tid = 0x47; uint32_t save_id; _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
