[Lldb-commits] [lldb] [lldb] Add generic address space support (PR #206370)
satyanarayana reddy janga via lldb-commits
lldb-commits at lists.llvm.org
Thu Jul 30 10:47:45 PDT 2026
https://github.com/satyajanga updated https://github.com/llvm/llvm-project/pull/206370
>From 8f9a204edb3f6e7b5891983e33c873d7eb2c02c9 Mon Sep 17 00:00:00 2001
From: satya janga <satyajanga at fb.com>
Date: Thu, 30 Jul 2026 09:29:20 -0700
Subject: [PATCH] [lldb] Add generic address space support
Add a generic mechanism for a process to report the address spaces it
exposes and to read memory from a specific one. Most processes have a
single flat address space, but some (such as GPUs) have multiple address
spaces where the same numeric address refers to different storage
depending on the address space (for example global, local, private or
generic memory).
Everything lives in the generic layers; there is no plugin-specific code.
- Utility/AddressSpace.{h,cpp}: AddressSpaceInfo {name, value} with JSON
serialization.
- New "jAddressSpacesInfo" gdb-remote packet returning the process's
address spaces as JSON. NativeProcessProtocol gains a virtual
GetAddressSpaces() that defaults to empty. Process caches the result
(Process::GetAddressSpaces()); ProcessGDBRemote populates it on connect.
- Address spaces are carried on the existing memory packets via an optional
";address_space:<id>;" key-value suffix, negotiated with the
"QAddressSpaceSuffixSupported" packet (modelled on QThreadSuffixSupported).
The suffix is only sent for a non-default space, so address-space-unaware
stubs and reads are unaffected. The server routes suffixed reads through a
new virtual NativeProcessProtocol::ReadMemoryInAddressSpace() (default
unsupported).
- ProcessAddress describes an address plus an address space id (0 = the
default space). The id is canonical; names are resolved through the
process. Process::ReadMemory(const ProcessAddress&) reads through the
default space when possible, otherwise resolves the AddressSpaceInfo and
calls DoReadMemory(). An optional LLDB_ENABLE_ADDRESS_SPACE_CHECKS CMake
flag adds assertions that catch misuse.
- Public API: SBProcessAddress and SBProcess::ReadMemoryFromProcessAddress().
- Documented in docs/resources/lldbgdbremote.md.
Tests: AddressSpaceInfo JSON round-trip unit tests; GDBRemoteCommunication
Client round-trip tests (QAddressSpaceSuffixSupported negotiation and
jAddressSpacesInfo supported / not-supported / malformed); an end-to-end
MockGDBServer test that connects lldb to a target exposing two address
spaces and verifies the same address reads back different bytes from each;
and lldb-server packet tests for the default server responses (empty
jAddressSpacesInfo, error on a non-default address space read).
---
lldb/docs/resources/lldbgdbremote.md | 81 ++++++++++++++++++
lldb/include/lldb/API/SBAddress.h | 27 ++++++
lldb/include/lldb/API/SBDefines.h | 1 +
lldb/include/lldb/API/SBProcess.h | 5 ++
.../lldb/Host/common/NativeProcessProtocol.h | 15 ++++
lldb/include/lldb/Target/Process.h | 64 ++++++++++++++
lldb/include/lldb/Utility/AddressSpace.h | 33 +++++++
.../lldb/Utility/StringExtractorGDBRemote.h | 2 +
lldb/include/lldb/lldb-forward.h | 1 +
lldb/source/API/SBAddress.cpp | 31 +++++++
lldb/source/API/SBProcess.cpp | 30 +++++++
.../GDBRemoteCommunicationClient.cpp | 43 ++++++++++
.../gdb-remote/GDBRemoteCommunicationClient.h | 11 +++
.../GDBRemoteCommunicationServerLLGS.cpp | 64 ++++++++++++--
.../GDBRemoteCommunicationServerLLGS.h | 6 ++
.../Process/gdb-remote/ProcessGDBRemote.cpp | 46 ++++++++--
.../Process/gdb-remote/ProcessGDBRemote.h | 11 +++
lldb/source/Target/Process.cpp | 85 +++++++++++++++++++
lldb/source/Utility/AddressSpace.cpp | 25 ++++++
lldb/source/Utility/CMakeLists.txt | 1 +
.../Utility/StringExtractorGDBRemote.cpp | 7 ++
.../TestAddressSpaceMemoryRead.py | 77 +++++++++++++++++
.../lldb-server/TestGdbRemoteAddressSpaces.py | 64 ++++++++++++++
.../GDBRemoteCommunicationClientTest.cpp | 39 +++++++++
lldb/unittests/Utility/AddressSpaceTest.cpp | 53 ++++++++++++
lldb/unittests/Utility/CMakeLists.txt | 1 +
26 files changed, 811 insertions(+), 12 deletions(-)
create mode 100644 lldb/include/lldb/Utility/AddressSpace.h
create mode 100644 lldb/source/Utility/AddressSpace.cpp
create mode 100644 lldb/test/API/functionalities/gdb_remote_client/TestAddressSpaceMemoryRead.py
create mode 100644 lldb/test/API/tools/lldb-server/TestGdbRemoteAddressSpaces.py
create mode 100644 lldb/unittests/Utility/AddressSpaceTest.cpp
diff --git a/lldb/docs/resources/lldbgdbremote.md b/lldb/docs/resources/lldbgdbremote.md
index 93090c19c5ec0..b2af5bec0d12c 100644
--- a/lldb/docs/resources/lldbgdbremote.md
+++ b/lldb/docs/resources/lldbgdbremote.md
@@ -822,6 +822,83 @@ 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 different
+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 is only sent after the client has negotiated address-space suffix
+support via `QAddressSpaceSuffixSupported`.
+
+The response is a JSON array of dictionaries, one per address space:
+```
+ [
+ {"name":"global","value":1},
+ {"name":"local","value":2}
+ ]
+```
+
+Each dictionary has the following keys:
+
+* `name`: the human readable name of the address space.
+* `value`: the integer identifier of the address space.
+
+If a server that supports the address-space suffix has no address spaces to
+report, it replies with an unsupported (empty) response.
+
+**Priority To Implement:** Low
+
+Only needed for targets that expose more than one address space.
+
+## QAddressSpaceSuffixSupported
+
+Negotiate support for the optional address-space suffix on memory packets.
+
+Some processes (such as GPUs) expose multiple address spaces where the same
+numeric address refers to different storage (see `jAddressSpacesInfo`). To read
+from a specific address space, the client appends an optional
+`;address_space:<id>;` key-value suffix to the existing memory packets rather
+than introducing a dedicated packet. This mirrors the thread suffix negotiated
+by `QThreadSuffixSupported`.
+
+The client sends `QAddressSpaceSuffixSupported`; a server that understands the
+suffix replies `OK`. The suffix is only ever sent for a non-default address
+space, so address-space-unaware stubs and reads are unaffected.
+
+```
+send packet: $QAddressSpaceSuffixSupported
+read packet: $OK
+```
+
+### The `address_space` suffix
+
+When the suffix has been negotiated, a memory packet may carry a trailing
+`;address_space:<id>;` field, where `<id>` is the base-16 address space id
+reported by `jAddressSpacesInfo`. An id of `0`, or the absence of the suffix,
+means the default address space and behaves exactly as before.
+
+```
+send packet: $x1000,4;address_space:2;
+read packet: $<binary encoding of the 4 bytes at 0x1000 in address space 2>
+```
+
+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:** Low
+
+Only needed for targets that expose more than one address space.
+
## MultiMemRead
Read memory from multiple memory ranges.
@@ -2717,6 +2794,10 @@ xADDRESS,LENGTH
where both `ADDRESS` and `LENGTH` are big-endian base 16 values.
+The `x` packet may also carry an optional `;address_space:<id>;` suffix to read
+from a non-default address space; see
+[QAddressSpaceSuffixSupported](#qaddressspacesuffixsupported).
+
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..c474d7e8b6fdb 100644
--- a/lldb/include/lldb/API/SBAddress.h
+++ b/lldb/include/lldb/API/SBAddress.h
@@ -130,6 +130,33 @@ class LLDB_API SBAddress {
bool LLDB_API operator==(const SBAddress &lhs, const SBAddress &rhs);
#endif
+/// A memory address that can name a non-default address space (see
+/// lldb_private::ProcessAddress).
+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, uint64_t address_space_id);
+
+ ~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..99bf6070824e6 100644
--- a/lldb/include/lldb/API/SBProcess.h
+++ b/lldb/include/lldb/API/SBProcess.h
@@ -199,6 +199,11 @@ class LLDB_API SBProcess {
size_t ReadMemory(addr_t addr, void *buf, size_t size, lldb::SBError &error);
+ /// Read memory described by an SBProcessAddress (which may name a non-default
+ /// address space). Returns the number of bytes read into \a buf.
+ size_t ReadMemoryFromProcessAddress(SBProcessAddress process_addr, void *buf,
+ size_t size, 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 435185a38f3f9..ccb3114939800 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/Status.h"
@@ -96,6 +97,20 @@ class NativeProcessProtocol {
virtual Status GetMemoryRegionInfo(lldb::addr_t load_addr,
MemoryRegionInfo &range_info);
+ /// The address spaces this process exposes (empty by default). Served over
+ /// the "jAddressSpacesInfo" packet.
+ virtual std::vector<AddressSpaceInfo> GetAddressSpaces() { return {}; }
+
+ /// Read memory from the address space with the given id (see
+ /// GetAddressSpaces()). Unsupported by default.
+ virtual Status ReadMemoryInAddressSpace(lldb::addr_t addr,
+ uint64_t address_space, void *buf,
+ size_t size, size_t &bytes_read) {
+ bytes_read = 0;
+ return Status::FromErrorString(
+ "reading from an address space is not supported");
+ }
+
virtual Status ReadMemory(lldb::addr_t addr, void *buf, size_t size,
size_t &bytes_read) = 0;
diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h
index 9162158a277d9..21b77db9bcda7 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"
@@ -350,6 +351,42 @@ inline bool operator!=(const ProcessModID &lhs, const ProcessModID &rhs) {
return (!lhs.StopIDEqual(rhs) || !lhs.MemoryIDEqual(rhs));
}
+/// \class ProcessAddress Process.h "lldb/Target/Process.h"
+/// A live address that may name a non-default address space.
+///
+/// The address space is a numeric id reported by the process (see
+/// Process::GetAddressSpaces); id 0 is the default (flat) address space, so a
+/// ProcessAddress with no space behaves like a plain lldb::addr_t.
+class ProcessAddress {
+ lldb::addr_t m_value;
+ uint64_t m_addr_space = 0;
+
+public:
+ /// Implicit so existing lldb::addr_t call sites keep working.
+ ProcessAddress(lldb::addr_t load_addr) : m_value(load_addr) {}
+
+ /// \a addr_space is a numeric id (0 for the default space); resolve a name
+ /// via Process::GetAddressSpaceInfo.
+ ProcessAddress(lldb::addr_t addr, uint64_t addr_space)
+ : m_value(addr), m_addr_space(addr_space) {}
+
+ bool IsInDefaultAddressSpace() const { return m_addr_space == 0; }
+
+ /// Resolve to a load address, or an error if this names a non-default address
+ /// space (which must be read via Process::DoReadMemory).
+ llvm::Expected<lldb::addr_t>
+ ResolveAddressInDefaultAddressSpace(lldb_private::Process &process) const;
+
+ lldb::addr_t GetValue() const { return m_value; }
+
+ uint64_t GetAddressSpace() const { return m_addr_space; }
+
+ /// Resolve this address's space to the process's AddressSpaceInfo, or an
+ /// error if it is the default space or the process does not expose it.
+ llvm::Expected<AddressSpaceInfo>
+ GetAddressSpaceInfo(lldb_private::Process &process) const;
+};
+
/// \class Process Process.h "lldb/Target/Process.h"
/// A plug-in interface definition class for debugging a process.
class Process : public std::enable_shared_from_this<Process>,
@@ -1632,6 +1669,9 @@ class Process : public std::enable_shared_from_this<Process>,
virtual size_t ReadMemory(lldb::addr_t vm_addr, void *buf, size_t size,
Status &error);
+ virtual size_t ReadMemory(const ProcessAddress &process_addr, void *buf,
+ size_t size, Status &error);
+
/// Read from multiple memory ranges and write the results into buffer.
///
/// \param[in] ranges
@@ -2058,6 +2098,17 @@ class Process : public std::enable_shared_from_this<Process>,
virtual Status
GetMemoryRegions(lldb_private::MemoryRegionInfos ®ion_list);
+ /// The address spaces this process exposes; empty for single-space processes.
+ llvm::ArrayRef<AddressSpaceInfo> GetAddressSpaces() const {
+ return m_address_spaces;
+ }
+
+ llvm::Expected<AddressSpaceInfo>
+ GetAddressSpaceInfo(llvm::StringRef address_space_name);
+
+ llvm::Expected<AddressSpaceInfo>
+ GetAddressSpaceInfo(uint64_t address_space_id);
+
/// Get the number of watchpoints supported by this target.
///
/// We may be able to determine the number of watchpoints available
@@ -3054,6 +3105,14 @@ void PruneThreadPlans();
virtual size_t DoReadMemory(lldb::addr_t vm_addr, void *buf, size_t size,
Status &error) = 0;
+ virtual size_t DoReadMemory(const ProcessAddress &process_addr,
+ const AddressSpaceInfo &info, void *buf,
+ size_t size, Status &error);
+
+ /// Populate m_address_spaces from the process plugin, at most once. Default
+ /// does nothing.
+ virtual void DoResolveAddressSpaces() {}
+
/// Reads each range individually via ReadMemoryFromInferior, bypassing the
/// memory cache. Subclasses may override it to batch the reads more
/// efficiently.
@@ -3516,6 +3575,11 @@ void PruneThreadPlans();
ThreadList
m_extended_thread_list; ///< Constituent for extended threads that may be
/// generated, cleared on natural stops
+ std::vector<AddressSpaceInfo>
+ m_address_spaces; ///< Address spaces reported by the process plugin,
+ /// empty for single-address-space processes.
+ bool m_address_spaces_resolved = false; ///< Whether DoResolveAddressSpaces()
+ /// has run.
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/AddressSpace.h b/lldb/include/lldb/Utility/AddressSpace.h
new file mode 100644
index 0000000000000..ff6c1c31e0324
--- /dev/null
+++ b/lldb/include/lldb/Utility/AddressSpace.h
@@ -0,0 +1,33 @@
+//===-- AddressSpace.h ----------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_UTILITY_ADDRESSSPACE_H
+#define LLDB_UTILITY_ADDRESSSPACE_H
+
+#include "lldb/lldb-types.h"
+#include "llvm/Support/JSON.h"
+#include <string>
+#include <vector>
+
+namespace lldb_private {
+
+/// A single address space reported by a process (see the "jAddressSpacesInfo"
+/// packet in docs/resources/lldbgdbremote.md).
+struct AddressSpaceInfo {
+ std::string name;
+ uint64_t value = 0;
+};
+
+bool fromJSON(const llvm::json::Value &value, AddressSpaceInfo &data,
+ llvm::json::Path path);
+
+llvm::json::Value toJSON(const AddressSpaceInfo &data);
+
+} // namespace lldb_private
+
+#endif // LLDB_UTILITY_ADDRESSSPACE_H
diff --git a/lldb/include/lldb/Utility/StringExtractorGDBRemote.h b/lldb/include/lldb/Utility/StringExtractorGDBRemote.h
index 624a2febe857e..f186f6f7cd875 100644
--- a/lldb/include/lldb/Utility/StringExtractorGDBRemote.h
+++ b/lldb/include/lldb/Utility/StringExtractorGDBRemote.h
@@ -106,8 +106,10 @@ class StringExtractorGDBRemote : public StringExtractor {
eServerPacketType_QSetEnableAsyncProfiling,
eServerPacketType_QSyncThreadState,
eServerPacketType_QThreadSuffixSupported,
+ eServerPacketType_QAddressSpaceSuffixSupported,
eServerPacketType_jThreadsInfo,
+ eServerPacketType_jAddressSpacesInfo,
eServerPacketType_qsThreadInfo,
eServerPacketType_qfThreadInfo,
eServerPacketType_qGetPid,
diff --git a/lldb/include/lldb/lldb-forward.h b/lldb/include/lldb/lldb-forward.h
index fceb94a8a3e7d..00e3e8973ec4e 100644
--- a/lldb/include/lldb/lldb-forward.h
+++ b/lldb/include/lldb/lldb-forward.h
@@ -22,6 +22,7 @@ class AddressRange;
class AddressRanges;
class AddressRangeList;
class AddressResolver;
+class ProcessAddress;
class ArchSpec;
class Architecture;
class Args;
diff --git a/lldb/source/API/SBAddress.cpp b/lldb/source/API/SBAddress.cpp
index 78acc2e34564d..484165a66eefc 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"
@@ -260,3 +262,32 @@ 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() = default;
+
+SBProcessAddress::SBProcessAddress(lldb::addr_t load_addr)
+ : m_opaque_up(new ProcessAddress(load_addr)) {
+ LLDB_INSTRUMENT_VA(this);
+}
+
+SBProcessAddress::SBProcessAddress(lldb::addr_t addr, uint64_t address_space_id)
+ : m_opaque_up(new ProcessAddress(addr, address_space_id)) {
+ LLDB_INSTRUMENT_VA(this, addr, address_space_id);
+}
+
+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 08e39f754cf85..c4cd5ea5b745a 100644
--- a/lldb/source/API/SBProcess.cpp
+++ b/lldb/source/API/SBProcess.cpp
@@ -33,6 +33,7 @@
#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"
@@ -906,6 +907,35 @@ size_t SBProcess::ReadMemory(addr_t addr, void *dst, size_t dst_len,
return bytes_read;
}
+size_t SBProcess::ReadMemoryFromProcessAddress(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;
+ }
+
+ std::lock_guard<std::recursive_mutex> guard(
+ process_sp->GetTarget().GetAPIMutex());
+ return process_sp->ReadMemory(process_addr.ref(), dst, dst_len,
+ sb_error.ref());
+}
+
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 e208c16649832..932d164d0d344 100644
--- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
+++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
@@ -384,6 +384,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_space_suffix = eLazyBoolCalculate;
m_supports_qSaveCore = eLazyBoolCalculate;
m_supports_qXfer_auxv_read = eLazyBoolCalculate;
m_supports_qXfer_libraries_read = eLazyBoolCalculate;
@@ -1160,6 +1161,48 @@ GDBRemoteCommunicationClient::GetProcessStandaloneBinaries() {
return m_binary_addresses;
}
+std::vector<AddressSpaceInfo> GDBRemoteCommunicationClient::GetAddressSpaces() {
+ if (!GetAddressSpaceSuffixSupported())
+ return {};
+
+ StringExtractorGDBRemote response;
+ response.SetResponseValidatorToJSON();
+ if (SendPacketAndWaitForResponse("jAddressSpacesInfo", response) !=
+ PacketResult::Success)
+ return {};
+
+ if (response.IsUnsupportedResponse() || response.IsErrorResponse()) {
+ m_supports_address_space_suffix = eLazyBoolNo;
+ return {};
+ }
+
+ llvm::Expected<std::vector<AddressSpaceInfo>> info =
+ llvm::json::parse<std::vector<AddressSpaceInfo>>(response.Peek(),
+ "AddressSpaceInfo");
+ if (info)
+ return std::move(*info);
+
+ // Log the full response and parse error rather than surfacing it.
+ Log *log = GetLog(GDBRLog::Process);
+ LLDB_LOG_ERROR(log, info.takeError(),
+ "malformed jAddressSpacesInfo response '{1}': {0}",
+ response.GetStringRef());
+ return {};
+}
+
+bool GDBRemoteCommunicationClient::GetAddressSpaceSuffixSupported() {
+ if (m_supports_address_space_suffix == eLazyBoolCalculate) {
+ StringExtractorGDBRemote response;
+ m_supports_address_space_suffix = eLazyBoolNo;
+ if (SendPacketAndWaitForResponse("QAddressSpaceSuffixSupported",
+ response) == PacketResult::Success) {
+ if (response.IsOKResponse())
+ m_supports_address_space_suffix = eLazyBoolYes;
+ }
+ }
+ return m_supports_address_space_suffix == eLazyBoolYes;
+}
+
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 3a0a34f840c21..e3db873dac427 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"
@@ -34,6 +35,7 @@
#include "llvm/Support/VersionTuple.h"
namespace lldb_private {
+
namespace process_gdb_remote {
/// The offsets used by the target when relocating the executable. Decoded from
@@ -223,6 +225,14 @@ class GDBRemoteCommunicationClient : public GDBRemoteClientBase {
std::vector<lldb::addr_t> GetProcessStandaloneBinaries();
+ /// Query the process for its address spaces via "jAddressSpacesInfo"; empty
+ /// if unsupported.
+ std::vector<AddressSpaceInfo> GetAddressSpaces();
+
+ /// Whether the server supports the ";address_space:<id>;" suffix on memory
+ /// packets. Mirrors GetThreadSuffixSupported().
+ bool GetAddressSpaceSuffixSupported();
+
void GetRemoteQSupported();
bool GetVContSupported(llvm::StringRef flavor);
@@ -606,6 +616,7 @@ class GDBRemoteCommunicationClient : public GDBRemoteClientBase {
LazyBool m_supports_error_string_reply = eLazyBoolCalculate;
LazyBool m_supports_multiprocess = eLazyBoolCalculate;
LazyBool m_supports_memory_tagging = eLazyBoolCalculate;
+ LazyBool m_supports_address_space_suffix = eLazyBoolCalculate;
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 4f11cf8c5475e..9bbcbc8a87e37 100644
--- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp
+++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp
@@ -162,6 +162,12 @@ void GDBRemoteCommunicationServerLLGS::RegisterPacketHandlers() {
RegisterMemberFunctionHandler(
StringExtractorGDBRemote::eServerPacketType_jThreadsInfo,
&GDBRemoteCommunicationServerLLGS::Handle_jThreadsInfo);
+ RegisterMemberFunctionHandler(
+ StringExtractorGDBRemote::eServerPacketType_jAddressSpacesInfo,
+ &GDBRemoteCommunicationServerLLGS::Handle_jAddressSpacesInfo);
+ RegisterMemberFunctionHandler(
+ StringExtractorGDBRemote::eServerPacketType_QAddressSpaceSuffixSupported,
+ &GDBRemoteCommunicationServerLLGS::Handle_QAddressSpaceSuffixSupported);
RegisterMemberFunctionHandler(
StringExtractorGDBRemote::eServerPacketType_qWatchpointSupportInfo,
&GDBRemoteCommunicationServerLLGS::Handle_qWatchpointSupportInfo);
@@ -2669,6 +2675,17 @@ GDBRemoteCommunicationServerLLGS::Handle_memory_read(
return SendOKResponse();
}
+ // Optional ";address_space:<hex>;" suffix (see QAddressSpaceSuffixSupported).
+ uint64_t address_space = 0;
+ 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");
+ }
+ }
+
// Allocate the response buffer.
std::string buf(byte_count, '\0');
if (buf.empty())
@@ -2676,12 +2693,17 @@ 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);
+ Status error;
+ if (address_space != 0)
+ error = m_current_process->ReadMemoryInAddressSpace(
+ read_addr, address_space, &buf[0], byte_count, bytes_read);
+ else
+ error = m_current_process->ReadMemoryWithoutTrap(read_addr, &buf[0],
+ byte_count, bytes_read);
+ LLDB_LOG(log,
+ "read {1} of {2} requested bytes at {0:x} in address_space {4} "
+ "(error: {3})",
+ read_addr, byte_count, bytes_read, error, address_space);
if (bytes_read == 0)
return SendErrorResponse(0x08);
@@ -3913,6 +3935,35 @@ 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_QAddressSpaceSuffixSupported(
+ StringExtractorGDBRemote &packet) {
+ m_address_space_suffix_supported = true;
+ return SendOKResponse();
+}
+
GDBRemoteCommunication::PacketResult
GDBRemoteCommunicationServerLLGS::Handle_qWatchpointSupportInfo(
StringExtractorGDBRemote &packet) {
@@ -4477,6 +4528,7 @@ std::vector<std::string> GDBRemoteCommunicationServerLLGS::HandleFeatures(
GDBRemoteCommunicationServerCommon::HandleFeatures(client_features);
ret.insert(ret.end(), {
"QThreadSuffixSupported+",
+ "QAddressSpaceSuffixSupported+",
"QListThreadsInStopReply+",
"qXfer:features:read+",
"QNonStop+",
diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h
index e5b4c9ec0bed0..60767950dea6f 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,11 @@ class GDBRemoteCommunicationServerLLGS
PacketResult Handle_jThreadsInfo(StringExtractorGDBRemote &packet);
+ PacketResult Handle_jAddressSpacesInfo(StringExtractorGDBRemote &packet);
+
+ PacketResult
+ Handle_QAddressSpaceSuffixSupported(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 724e7f2e71bd8..5638b7b35208a 100644
--- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
+++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
@@ -1216,6 +1216,13 @@ void ProcessGDBRemote::LoadStubBinaries() {
}
}
+void ProcessGDBRemote::DoResolveAddressSpaces() {
+ if (m_address_spaces_resolved)
+ return;
+ m_address_spaces_resolved = true;
+ m_address_spaces = m_gdb_comm.GetAddressSpaces();
+}
+
void ProcessGDBRemote::MaybeLoadExecutableModule() {
ModuleSP module_sp = GetTarget().GetExecutableModule();
if (!module_sp)
@@ -2902,8 +2909,10 @@ void ProcessGDBRemote::WillPublicStop() {
}
// Process Memory
-size_t ProcessGDBRemote::DoReadMemory(addr_t addr, void *buf, size_t size,
- Status &error) {
+size_t ProcessGDBRemote::DoReadMemoryWithAddressSpace(addr_t addr,
+ uint64_t addr_space,
+ void *buf, size_t size,
+ Status &error) {
using xPacketState = GDBRemoteCommunicationClient::xPacketState;
GetMaxMemorySize();
@@ -2920,11 +2929,20 @@ size_t ProcessGDBRemote::DoReadMemory(addr_t addr, void *buf, size_t size,
size = max_memory_size;
}
- char packet[64];
+ // A non-default address space rides on an optional ";address_space:<id>;"
+ // suffix on the standard m/x packet (see QAddressSpaceSuffixSupported).
+ char packet[128];
int packet_len;
- packet_len = ::snprintf(packet, sizeof(packet), "%c%" PRIx64 ",%" PRIx64,
- x_state != xPacketState::Unimplemented ? 'x' : 'm',
- (uint64_t)addr, (uint64_t)size);
+ if (addr_space != 0)
+ packet_len =
+ ::snprintf(packet, sizeof(packet),
+ "%c%" PRIx64 ",%" PRIx64 ";address_space:%" PRIx64 ";",
+ x_state != xPacketState::Unimplemented ? 'x' : 'm',
+ (uint64_t)addr, (uint64_t)size, addr_space);
+ else
+ packet_len = ::snprintf(packet, sizeof(packet), "%c%" PRIx64 ",%" PRIx64,
+ x_state != xPacketState::Unimplemented ? 'x' : 'm',
+ (uint64_t)addr, (uint64_t)size);
assert(packet_len + 1 < (int)sizeof(packet));
UNUSED_IF_ASSERT_DISABLED(packet_len);
StringExtractorGDBRemote response;
@@ -2973,6 +2991,22 @@ size_t ProcessGDBRemote::DoReadMemory(addr_t addr, void *buf, size_t size,
return 0;
}
+size_t ProcessGDBRemote::DoReadMemory(addr_t addr, void *buf, size_t size,
+ Status &error) {
+ return DoReadMemoryWithAddressSpace(addr, /*addr_space=*/0, buf, size, error);
+}
+
+size_t ProcessGDBRemote::DoReadMemory(const ProcessAddress &process_addr,
+ const AddressSpaceInfo &info, void *buf,
+ size_t size, Status &error) {
+ if (!m_gdb_comm.GetAddressSpaceSuffixSupported()) {
+ error = Status::FromErrorString("address spaces are not supported");
+ return 0;
+ }
+ return DoReadMemoryWithAddressSpace(process_addr.GetValue(), info.value, buf,
+ size, error);
+}
+
/// Returns the number of ranges that is safe to request using MultiMemRead
/// while respecting max_packet_size.
static uint64_t ComputeNumRangesMultiMemRead(
diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
index ca75899bc5cbf..93cb2ccb97ed0 100644
--- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
+++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
@@ -140,6 +140,17 @@ class ProcessGDBRemote : public Process,
size_t DoReadMemory(lldb::addr_t addr, void *buf, size_t size,
Status &error) override;
+ size_t DoReadMemory(const ProcessAddress &process_addr,
+ const AddressSpaceInfo &info, void *buf, size_t size,
+ Status &error) override;
+
+ /// Shared m/x read implementation; appends an ";address_space:<id>;" suffix
+ /// when \a addr_space is non-zero.
+ size_t DoReadMemoryWithAddressSpace(lldb::addr_t addr, uint64_t addr_space,
+ void *buf, size_t size, Status &error);
+
+ void DoResolveAddressSpaces() override;
+
/// Override of DoReadMemoryRanges that uses MultiMemRead to perform this
/// operation in a single packet.
llvm::SmallVector<llvm::MutableArrayRef<uint8_t>>
diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp
index 256ce12abc1ef..ad80708515e72 100644
--- a/lldb/source/Target/Process.cpp
+++ b/lldb/source/Target/Process.cpp
@@ -2031,6 +2031,34 @@ Status Process::DisableSoftwareBreakpoint(BreakpointSite *bp_site) {
return error;
}
+size_t Process::ReadMemory(const ProcessAddress &process_addr, void *buf,
+ size_t size, Status &error) {
+ error.Clear();
+ if (process_addr.IsInDefaultAddressSpace()) {
+ llvm::Expected<lldb::addr_t> load_addr =
+ process_addr.ResolveAddressInDefaultAddressSpace(*this);
+ if (load_addr)
+ return ReadMemory(*load_addr, buf, size, error);
+ error = Status::FromError(load_addr.takeError());
+ return 0;
+ }
+ llvm::Expected<AddressSpaceInfo> info =
+ process_addr.GetAddressSpaceInfo(*this);
+
+ if (info)
+ return DoReadMemory(process_addr, *info, buf, size, error);
+ error = Status::FromError(info.takeError());
+ return 0;
+}
+
+size_t Process::DoReadMemory(const ProcessAddress &process_addr,
+ const AddressSpaceInfo &info, void *buf,
+ size_t size, Status &error) {
+ error =
+ Status::FromErrorString("ProcessAddress memory reading is not supported");
+ return 0;
+}
+
// Uncomment to verify memory caching works after making changes to caching
// code
//#define VERIFY_MEMORY_READS
@@ -7103,3 +7131,60 @@ void Process::SetAddressableBitMasks(AddressableBits bit_masks) {
SetHighmemDataAddressMask(high_addr_mask);
}
}
+
+llvm::Expected<AddressSpaceInfo>
+ProcessAddress::GetAddressSpaceInfo(lldb_private::Process &process) const {
+ if (IsInDefaultAddressSpace())
+ return llvm::createStringError("address is in the default address space");
+ return process.GetAddressSpaceInfo(m_addr_space);
+}
+
+llvm::Expected<lldb::addr_t>
+ProcessAddress::ResolveAddressInDefaultAddressSpace(
+ lldb_private::Process &) const {
+ if (!IsInDefaultAddressSpace()) {
+ assert(false && "non-default ProcessAddress used as a plain load address");
+ return llvm::createStringError(
+ "address is not in the default address space");
+ }
+ return m_value;
+}
+
+llvm::Expected<AddressSpaceInfo>
+Process::GetAddressSpaceInfo(llvm::StringRef address_space_name) {
+ DoResolveAddressSpaces();
+ if (m_address_spaces.empty())
+ return llvm::createStringError("process doesn't support address spaces");
+
+ for (const auto &address_space_info : m_address_spaces) {
+ if (address_space_info.name == address_space_name.str())
+ return address_space_info;
+ }
+
+ std::string error_str("invalid address space \"");
+ error_str.append(address_space_name.str());
+ error_str.append("\", address space must be one of:");
+ bool first = true;
+ for (const auto &addr_space_info : m_address_spaces) {
+ if (!first)
+ error_str.append(",");
+ error_str.append(" \"");
+ error_str.append(addr_space_info.name);
+ error_str.append("\"");
+ first = false;
+ }
+ return llvm::createStringError(error_str.c_str());
+}
+
+llvm::Expected<AddressSpaceInfo>
+Process::GetAddressSpaceInfo(uint64_t address_space_id) {
+ DoResolveAddressSpaces();
+ if (m_address_spaces.empty())
+ return llvm::createStringError("process doesn't support address spaces");
+
+ for (const auto &address_space_info : m_address_spaces) {
+ if (address_space_info.value == address_space_id)
+ return address_space_info;
+ }
+ return llvm::createStringError("invalid address space id");
+}
diff --git a/lldb/source/Utility/AddressSpace.cpp b/lldb/source/Utility/AddressSpace.cpp
new file mode 100644
index 0000000000000..44b4a3932e880
--- /dev/null
+++ b/lldb/source/Utility/AddressSpace.cpp
@@ -0,0 +1,25 @@
+//===-- AddressSpace.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/Utility/AddressSpace.h"
+
+using namespace llvm;
+using namespace llvm::json;
+
+namespace lldb_private {
+
+bool fromJSON(const json::Value &value, AddressSpaceInfo &data, Path path) {
+ ObjectMapper o(value, path);
+ return o && o.map("name", data.name) && o.map("value", data.value);
+}
+
+json::Value toJSON(const AddressSpaceInfo &data) {
+ return json::Value(Object{{"name", data.name}, {"value", data.value}});
+}
+
+} // namespace lldb_private
diff --git a/lldb/source/Utility/CMakeLists.txt b/lldb/source/Utility/CMakeLists.txt
index c0d0a42367b26..616a59b4ff3ca 100644
--- a/lldb/source/Utility/CMakeLists.txt
+++ b/lldb/source/Utility/CMakeLists.txt
@@ -25,6 +25,7 @@ endif()
add_lldb_library(lldbUtility NO_INTERNAL_DEPENDENCIES
AddressableBits.cpp
+ AddressSpace.cpp
ArchSpec.cpp
Args.cpp
Baton.cpp
diff --git a/lldb/source/Utility/StringExtractorGDBRemote.cpp b/lldb/source/Utility/StringExtractorGDBRemote.cpp
index 6fc3b63e02dd1..f6ed88a252d71 100644
--- a/lldb/source/Utility/StringExtractorGDBRemote.cpp
+++ b/lldb/source/Utility/StringExtractorGDBRemote.cpp
@@ -138,6 +138,11 @@ StringExtractorGDBRemote::GetServerPacketType() const {
return eServerPacketType_QSyncThreadState;
break;
+ case 'A':
+ if (PACKET_MATCHES("QAddressSpaceSuffixSupported"))
+ return eServerPacketType_QAddressSpaceSuffixSupported;
+ break;
+
case 'L':
if (PACKET_STARTS_WITH("QLaunchArch:"))
return eServerPacketType_QLaunchArch;
@@ -321,6 +326,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..20c52b2e2ff35
--- /dev/null
+++ b/lldb/test/API/functionalities/gdb_remote_client/TestAddressSpaceMemoryRead.py
@@ -0,0 +1,77 @@
+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 different
+ address spaces returns different bytes. The client negotiates
+ "QAddressSpaceSuffixSupported", discovers the spaces via "jAddressSpacesInfo",
+ and reads memory with an optional ";address_space:<id>;" suffix on the
+ standard memory packet.
+ """
+
+ def test(self):
+ address_spaces_json = '[{"name":"global","value":1},{"name":"local","value":2}]'
+
+ class MyResponder(MockGDBServerResponder):
+ def qSupported(self, client_supported):
+ return "PacketSize=3fff;QStartNoAckMode+;QAddressSpaceSuffixSupported+"
+
+ def qHostInfo(self):
+ return "ptrsize:8;endian:little;"
+
+ def _bytes_for_space(self, space):
+ if space == 1:
+ return "aabbccdd"
+ if space == 2:
+ return "11223344"
+ return "E01"
+
+ def _respond_impl(self, packet):
+ # The base dispatcher can't parse the ";address_space:<id>;"
+ # suffix, so handle suffixed reads here.
+ if packet and packet[0] in ("m", "x") and "address_space:" in 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 == "QAddressSpaceSuffixSupported":
+ return "OK"
+ 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 2).
+ global_bytes = process.ReadMemoryFromProcessAddress(
+ lldb.SBProcessAddress(0x1000, 1), 4, error
+ )
+ self.assertSuccess(error)
+ self.assertEqual(global_bytes, b"\xaa\xbb\xcc\xdd")
+
+ local_bytes = process.ReadMemoryFromProcessAddress(
+ lldb.SBProcessAddress(0x1000, 2), 4, error
+ )
+ self.assertSuccess(error)
+ self.assertEqual(local_bytes, b"\x11\x22\x33\x44")
+
+ # Same address, different address space, different bytes.
+ self.assertNotEqual(global_bytes, local_bytes)
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..73411b786dc45
--- /dev/null
+++ b/lldb/test/API/tools/lldb-server/TestGdbRemoteAddressSpaces.py
@@ -0,0 +1,64 @@
+"""
+Server-side tests for the address-space gdb-remote packets.
+
+A normal (non-accelerator) process exposes no address spaces, so lldb-server's
+default handlers reply accordingly: "jAddressSpacesInfo" is unsupported (empty
+response) and a read from a non-default address space errors. Processes that
+expose address spaces (such as GPUs) override GetAddressSpaces() and
+ReadMemoryInAddressSpace().
+"""
+
+import gdbremote_testcase
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+
+
+class TestGdbRemoteAddressSpaces(gdbremote_testcase.GdbRemoteTestCaseBase):
+ 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()
+
+ def test_QAddressSpaceSuffixSupported(self):
+ self.build()
+ self.set_inferior_startup_launch()
+ self.prep_debug_monitor_and_inferior()
+
+ self.test_sequence.add_log_lines(
+ [
+ "read packet: $QAddressSpaceSuffixSupported#00",
+ "send packet: $OK#00",
+ ],
+ True,
+ )
+ self.expect_gdbremote_sequence()
+
+ def test_address_space_read_errors_by_default(self):
+ self.build()
+ self.set_inferior_startup_launch()
+ self.prep_debug_monitor_and_inferior()
+
+ # After negotiating the suffix, a read from a non-default address space
+ # errors because the default ReadMemoryInAddressSpace() is unsupported.
+ self.test_sequence.add_log_lines(
+ [
+ "read packet: $QAddressSpaceSuffixSupported#00",
+ "send packet: $OK#00",
+ "read packet: $x1000,4;address_space:1;#00",
+ {
+ "direction": "send",
+ "regex": r"^\$E[0-9a-fA-F]+",
+ },
+ ],
+ 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..277349e8dbead 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,43 @@ TEST_F(GDBRemoteCommunicationClientTest, ReadRegister) {
memcmp(buffer_sp->GetBytes(), all_registers, sizeof all_registers));
}
+TEST_F(GDBRemoteCommunicationClientTest, GetAddressSpaces) {
+ std::future<std::vector<AddressSpaceInfo>> result =
+ std::async(std::launch::async, [&] { return client.GetAddressSpaces(); });
+ HandlePacket(server, "QAddressSpaceSuffixSupported", "OK");
+ StreamGDBRemote escaped;
+ llvm::StringRef json = R"([{"name":"global","value":1},)"
+ R"({"name":"local","value":2}])";
+ 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].value, 1u);
+ EXPECT_EQ(spaces[1].name, "local");
+ EXPECT_EQ(spaces[1].value, 2u);
+}
+
+TEST_F(GDBRemoteCommunicationClientTest, GetAddressSpacesNotSupported) {
+ std::future<std::vector<AddressSpaceInfo>> result =
+ std::async(std::launch::async, [&] { return client.GetAddressSpaces(); });
+ HandlePacket(server, "QAddressSpaceSuffixSupported", "");
+ EXPECT_TRUE(result.get().empty());
+}
+
+TEST_F(GDBRemoteCommunicationClientTest, GetAddressSpacesMalformed) {
+ std::future<std::vector<AddressSpaceInfo>> result =
+ std::async(std::launch::async, [&] { return client.GetAddressSpaces(); });
+ HandlePacket(server, "QAddressSpaceSuffixSupported", "OK");
+ // 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;
diff --git a/lldb/unittests/Utility/AddressSpaceTest.cpp b/lldb/unittests/Utility/AddressSpaceTest.cpp
new file mode 100644
index 0000000000000..71904e4c66128
--- /dev/null
+++ b/lldb/unittests/Utility/AddressSpaceTest.cpp
@@ -0,0 +1,53 @@
+//===-- AddressSpaceTest.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/Utility/AddressSpace.h"
+#include "llvm/Support/JSON.h"
+#include "llvm/Testing/Support/Error.h"
+#include "gtest/gtest.h"
+
+using namespace lldb_private;
+
+static std::string ToString(const llvm::json::Value &value) {
+ return llvm::formatv("{0}", value).str();
+}
+
+TEST(AddressSpaceTest, RoundTrip) {
+ AddressSpaceInfo info{"global", 1};
+ llvm::Expected<AddressSpaceInfo> parsed = llvm::json::parse<AddressSpaceInfo>(
+ ToString(toJSON(info)), "AddressSpaceInfo");
+ ASSERT_THAT_EXPECTED(parsed, llvm::Succeeded());
+ EXPECT_EQ(parsed->name, "global");
+ EXPECT_EQ(parsed->value, 1u);
+}
+
+TEST(AddressSpaceTest, ArrayRoundTrip) {
+ std::vector<AddressSpaceInfo> spaces = {
+ {"global", 1},
+ {"local", 2},
+ {"private", 3},
+ };
+ llvm::json::Array array;
+ for (const AddressSpaceInfo &space : spaces)
+ array.push_back(toJSON(space));
+
+ llvm::Expected<std::vector<AddressSpaceInfo>> parsed =
+ llvm::json::parse<std::vector<AddressSpaceInfo>>(
+ ToString(llvm::json::Value(std::move(array))), "AddressSpaceInfo");
+ ASSERT_THAT_EXPECTED(parsed, llvm::Succeeded());
+ ASSERT_EQ(parsed->size(), 3u);
+ EXPECT_EQ((*parsed)[1].name, "local");
+ EXPECT_EQ((*parsed)[1].value, 2u);
+}
+
+TEST(AddressSpaceTest, MissingFieldFails) {
+ // "value" is required.
+ llvm::Expected<AddressSpaceInfo> parsed = llvm::json::parse<AddressSpaceInfo>(
+ R"({"name":"global"})", "AddressSpaceInfo");
+ EXPECT_THAT_EXPECTED(parsed, llvm::Failed());
+}
diff --git a/lldb/unittests/Utility/CMakeLists.txt b/lldb/unittests/Utility/CMakeLists.txt
index ed159748838b5..e46a1774f020d 100644
--- a/lldb/unittests/Utility/CMakeLists.txt
+++ b/lldb/unittests/Utility/CMakeLists.txt
@@ -1,5 +1,6 @@
add_lldb_unittest(UtilityTests
AcceleratorGDBRemotePacketsTest.cpp
+ AddressSpaceTest.cpp
AnsiTerminalTest.cpp
ArgsTest.cpp
OptionsWithRawTest.cpp
More information about the lldb-commits
mailing list