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 &region_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

Reply via email to