https://github.com/satyajanga updated 
https://github.com/llvm/llvm-project/pull/214089

>From 57055d1f5b812f963a777f3f4f95c661bdfe040b Mon Sep 17 00:00:00 2001
From: satya janga <[email protected]>
Date: Tue, 4 Aug 2026 12:28:23 -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 several, 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.

- A new "jAddressSpacesInfo" packet returns the address spaces of the
  process as JSON. NativeProcessProtocol gains a virtual GetAddressSpaces()
  that defaults to empty, and the server only advertises the feature when
  a plugin provides one. Process resolves the list once, on demand.

- Address spaces are carried on the existing memory packets with an
  optional ";address_space:<id>;" suffix, negotiated with the
  "address-spaces+" qSupported feature. The suffix is only sent for a
  non-default address space, so address space unaware stubs and reads are
  unaffected.

- Process::ReadMemory(const ProcessAddress &) reads through the memory
  cache for the default address space, and otherwise validates the
  address space id and goes straight to the process plugin.

- Public API: SBProcessAddress, SBProcess::GetAddressSpaceID() and an
  SBProcess::ReadMemory() overload taking an SBProcessAddress.

Documented in docs/resources/lldbgdbremote.md.

Tests: GDBRemoteCommunicationClient round trip tests for the qSupported
negotiation and jAddressSpacesInfo (supported, unsupported, malformed);
an end to end MockGDBServer test that connects to a target exposing two
address spaces and verifies the same address reads back different bytes
from each; and lldb-server tests for the default server responses.
---
 lldb/bindings/python/python-typemaps.swig     |   6 +
 lldb/docs/resources/lldbgdbremote.md          |  80 ++++++++++++
 lldb/include/lldb/API/SBAddress.h             |  30 +++++
 lldb/include/lldb/API/SBDefines.h             |   1 +
 lldb/include/lldb/API/SBProcess.h             |   7 ++
 .../lldb/Host/common/NativeProcessProtocol.h  |   8 +-
 lldb/include/lldb/Target/Process.h            |   9 ++
 .../lldb/Utility/StringExtractorGDBRemote.h   |   1 +
 .../tools/lldb-server/gdbremote_testcase.py   |   1 +
 lldb/source/API/SBAddress.cpp                 |  40 ++++++
 lldb/source/API/SBProcess.cpp                 |  58 +++++++++
 .../GDBRemoteCommunicationClient.cpp          |  32 +++++
 .../gdb-remote/GDBRemoteCommunicationClient.h |   9 ++
 .../GDBRemoteCommunicationServerLLGS.cpp      |  60 ++++++++-
 .../GDBRemoteCommunicationServerLLGS.h        |   3 +
 .../Process/gdb-remote/ProcessGDBRemote.cpp   |  43 ++++++-
 lldb/source/Target/Process.cpp                |  59 ++++++++-
 .../Utility/StringExtractorGDBRemote.cpp      |   2 +
 .../TestAddressSpaceMemoryRead.py             | 119 ++++++++++++++++++
 .../lldb-server/TestGdbRemoteAddressSpaces.py |  28 +++++
 .../GDBRemoteCommunicationClientTest.cpp      |  67 ++++++++++
 21 files changed, 651 insertions(+), 12 deletions(-)
 create mode 100644 
lldb/test/API/functionalities/gdb_remote_client/TestAddressSpaceMemoryRead.py
 create mode 100644 
lldb/test/API/tools/lldb-server/TestGdbRemoteAddressSpaces.py

diff --git a/lldb/bindings/python/python-typemaps.swig 
b/lldb/bindings/python/python-typemaps.swig
index d148bc27ad413..df1ffe190c37e 100644
--- a/lldb/bindings/python/python-typemaps.swig
+++ b/lldb/bindings/python/python-typemaps.swig
@@ -343,6 +343,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 93090c19c5ec0..464c045a769e5 100644
--- a/lldb/docs/resources/lldbgdbremote.md
+++ b/lldb/docs/resources/lldbgdbremote.md
@@ -822,6 +822,81 @@ 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 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:<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:<id>;` key-value suffix to the existing memory packets rather
+than introducing a dedicated packet, where `<id>` is the 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, so
+address-space-unaware stubs and reads are unaffected.
+
+```
+send packet: $x1000,4;address_space:2;
+read packet: $<binary encoding of the 4 bytes at 0x1000 in address space 2>
+```
+
+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:2;thread:1a2b;
+read packet: $<binary encoding of the 4 bytes at 0x1000 in address space 2 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.
@@ -2717,6 +2792,11 @@ 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, 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 67206c4b55b79..afe62e2a52497 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;
 
@@ -298,8 +303,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 c260a4204d2f7..55c0379548c9b 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"
@@ -2059,6 +2060,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
@@ -3517,6 +3524,8 @@ 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; ///< Empty for single address space processes.
   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 624a2febe857e..a16dfcfe1601a 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 99f278c3186d1..0e067e68a3fef 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
@@ -932,6 +932,7 @@ def add_qSupported_packets(self, client_features=[]):
         "PacketSize",
         "QStartNoAckMode",
         "QThreadSuffixSupported",
+        "address-spaces",
         "QListThreadsInStopReply",
         "qXfer:auxv:read",
         "qXfer:libraries:read",
diff --git a/lldb/source/API/SBAddress.cpp b/lldb/source/API/SBAddress.cpp
index 5015b6d81d732..1ba06ceabd818 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() = 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,
+                                   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);
+}
+
+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 0984daa0e92b9..d7101f27d654e 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,62 @@ 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;
+  }
+
+  size_t bytes_read = 0;
+  ProcessSP process_sp(GetSP());
+
+  if (process_sp) {
+    Process::StopLocker stop_locker;
+    if (stop_locker.TryLock(&process_sp->GetRunLock())) {
+      TargetAPIMutex api_lock = process_sp->GetTarget().GetAPIMutex();
+      std::lock_guard<TargetAPIMutex> guard(api_lock);
+      bytes_read = process_sp->ReadMemory(process_addr.ref(), dst, dst_len,
+                                          sb_error.ref());
+    } else {
+      sb_error = Status::FromErrorString("process is running");
+    }
+  } else {
+    sb_error = Status::FromErrorString("SBProcess is invalid");
+  }
+
+  return bytes_read;
+}
+
+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 b440869f25984..279f0510bfd31 100644
--- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
+++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
@@ -386,6 +386,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;
@@ -453,6 +454,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;
@@ -513,6 +515,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+")
@@ -1162,6 +1166,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 3a0a34f840c21..c64f85f6696be 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,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() { return m_supports_address_spaces; }
+
   void GetRemoteQSupported();
 
   bool GetVContSupported(llvm::StringRef flavor);
@@ -606,6 +614,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 2e824282079b4..087c57543d1bf 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);
@@ -2671,6 +2674,25 @@ GDBRemoteCommunicationServerLLGS::Handle_memory_read(
     return SendOKResponse();
   }
 
+  // Optional ";address_space:<id>;" suffix, with ";thread:<hex-tid>;" for a
+  // thread specific address space (see the "address-spaces" feature).
+  lldb::addr_space_t address_space = 0;
+  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(0, 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())
@@ -2679,11 +2701,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 {1} of {2} 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);
 
@@ -3915,6 +3939,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) {
@@ -4502,6 +4548,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 e5b4c9ec0bed0..d4d48d41bb5a8 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 d8005ebfcea72..668bcfff32f30 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,34 @@ 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:<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};", addr_space);
+    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 1d7e413603c82..bfbed1cfd67b6 100644
--- a/lldb/source/Target/Process.cpp
+++ b/lldb/source/Target/Process.cpp
@@ -2037,11 +2037,23 @@ Status 
Process::DisableSoftwareBreakpoint(BreakpointSite *bp_site) {
 
 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()) {
 #if defined(VERIFY_MEMORY_READS)
     // Memory caching is enabled, with debug verification
@@ -7105,3 +7117,48 @@ 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 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(lldb::addr_space_t address_space_id) {
+  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.space_id == address_space_id)
+      return address_space_info;
+  }
+
+  std::string error_str("invalid address space id, valid ids are:");
+  bool first = true;
+  for (const auto &addr_space_info : m_address_spaces) {
+    error_str.append(first ? " " : ", ");
+    error_str.append(std::to_string(addr_space_info.space_id));
+    first = false;
+  }
+  return llvm::createStringError(error_str.c_str());
+}
diff --git a/lldb/source/Utility/StringExtractorGDBRemote.cpp 
b/lldb/source/Utility/StringExtractorGDBRemote.cpp
index 6fc3b63e02dd1..20237aec70c50 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..ae553b65fea09
--- /dev/null
+++ 
b/lldb/test/API/functionalities/gdb_remote_client/TestAddressSpaceMemoryRead.py
@@ -0,0 +1,119 @@
+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 server advertises
+    "address-spaces+" in qSupported, reports 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","space_id":1,"is_thread_specific":false},'
+            '{"name":"local","space_id":2,"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 == 2:
+                    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:<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 2).
+        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, 2, thread), 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)
+
+        # Only the thread specific read carries a "thread:" field, and it names
+        # the thread that was asked for.
+        self.assertEqual(len(self.server.responder.reads), 2)
+        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, 2), 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), 2)
+        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