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

>From f458798f8db59821a24839a03e9e51e352d41e15 Mon Sep 17 00:00:00 2001
From: satya janga <[email protected]>
Date: Sun, 28 Jun 2026 12:59:16 -0700
Subject: [PATCH] [lldb] Add generic address space support

Add a generic mechanism for a process to report the address spaces it
exposes and to read memory from a specific one. Most processes have a
single flat address space, but some (such as GPUs) have multiple address
spaces where the same numeric address refers to different storage
depending on the address space (for example global, local, private or
generic memory).

Everything lives in the generic layers; there is no plugin-specific code.

- Utility/AddressSpace.{h,cpp}: AddressSpaceInfo {name, value,
  is_thread_specific} with JSON serialization.
- New "jAddressSpacesInfo" gdb-remote packet, gated by the
  "address-spaces+" qSupported feature (NativeProcessProtocol gains an
  Extension::address_spaces capability advertised by LLGS, and a virtual
  GetAddressSpaces() that defaults to empty). The client queries it via
  GetAddressSpaces() only when the feature was advertised. Process caches
  the result (Process::GetAddressSpaces()); ProcessGDBRemote populates it
  on connect.
- AddressSpec describes an address plus an optional address space (by name
  or id) and optional module/thread, resolving to a load address when no
  address space is set. Process::ReadMemory(const AddressSpec&) reads
  through the default address space when possible, otherwise resolves the
  AddressSpaceInfo and calls DoReadMemory(); ProcessGDBRemote reads from
  the address space via the new "qMemRead:addr:..;space:..;length:..;"
  packet.
- Public API: SBAddressSpec and SBProcess::ReadMemoryFromSpec().
- Documented in docs/resources/lldbgdbremote.md.

Tests: AddressSpaceInfo JSON round-trip unit tests; GDBRemoteCommunication
Client round-trip tests (advertised / not-advertised / malformed); and an
end-to-end MockGDBServer test that connects lldb to a target exposing two
address spaces and verifies the same address reads back different bytes
from each.
---
 lldb/docs/resources/lldbgdbremote.md          |  35 ++++++
 lldb/include/lldb/API/SBAddress.h             |  38 +++++++
 lldb/include/lldb/API/SBDefines.h             |   1 +
 lldb/include/lldb/API/SBProcess.h             |  20 ++++
 .../lldb/Host/common/NativeProcessProtocol.h  |  10 +-
 lldb/include/lldb/Target/Process.h            | 104 ++++++++++++++++++
 lldb/include/lldb/Utility/AddressSpace.h      |  37 +++++++
 .../lldb/Utility/StringExtractorGDBRemote.h   |   1 +
 lldb/include/lldb/lldb-forward.h              |   1 +
 lldb/source/API/SBAddress.cpp                 |  35 ++++++
 lldb/source/API/SBProcess.cpp                 |  28 +++++
 .../GDBRemoteCommunicationClient.cpp          |  77 +++++++++++++
 .../gdb-remote/GDBRemoteCommunicationClient.h |  14 +++
 .../GDBRemoteCommunicationServerLLGS.cpp      |  27 +++++
 .../GDBRemoteCommunicationServerLLGS.h        |   2 +
 .../Process/gdb-remote/ProcessGDBRemote.cpp   |   9 ++
 .../Process/gdb-remote/ProcessGDBRemote.h     |   4 +
 lldb/source/Target/Process.cpp                |  88 +++++++++++++++
 lldb/source/Utility/AddressSpace.cpp          |  28 +++++
 lldb/source/Utility/CMakeLists.txt            |   1 +
 .../Utility/StringExtractorGDBRemote.cpp      |   2 +
 .../TestAddressSpaceMemoryRead.py             |  74 +++++++++++++
 .../GDBRemoteCommunicationClientTest.cpp      |  55 +++++++++
 lldb/unittests/Utility/AddressSpaceTest.cpp   |  55 +++++++++
 lldb/unittests/Utility/CMakeLists.txt         |   1 +
 25 files changed, 746 insertions(+), 1 deletion(-)
 create mode 100644 lldb/include/lldb/Utility/AddressSpace.h
 create mode 100644 lldb/source/Utility/AddressSpace.cpp
 create mode 100644 
lldb/test/API/functionalities/gdb_remote_client/TestAddressSpaceMemoryRead.py
 create mode 100644 lldb/unittests/Utility/AddressSpaceTest.cpp

diff --git a/lldb/docs/resources/lldbgdbremote.md 
b/lldb/docs/resources/lldbgdbremote.md
index 577a5ad31674f..3c55dc740cab5 100644
--- a/lldb/docs/resources/lldbgdbremote.md
+++ b/lldb/docs/resources/lldbgdbremote.md
@@ -825,6 +825,41 @@ 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`. A server
+only advertises `address-spaces+` when its process exposes address spaces, so
+clients never send this packet to a process with a single flat address space.
+
+The response is a JSON array of dictionaries, one per address space:
+```
+    [
+      {"name":"global","value":1,"is_thread_specific":false},
+      {"name":"local","value":2,"is_thread_specific":true}
+    ]
+```
+
+Each dictionary has the following keys:
+
+* `name`: the human readable name of the address space.
+* `value`: the integer identifier of the address space.
+* `is_thread_specific`: `true` if the same address-space value resolves to
+  different storage for different threads.
+
+If a server that advertised `address-spaces+` has no address spaces to report,
+it replies with an unsupported (empty) response.
+
+**Priority To Implement:** Low
+
+Only needed for targets that expose more than one address space.
+
 ### MultiMemRead
 
 Read memory from multiple memory ranges.
diff --git a/lldb/include/lldb/API/SBAddress.h 
b/lldb/include/lldb/API/SBAddress.h
index 430dad4862dbf..adfed4ccf9e24 100644
--- a/lldb/include/lldb/API/SBAddress.h
+++ b/lldb/include/lldb/API/SBAddress.h
@@ -130,6 +130,44 @@ class LLDB_API SBAddress {
 bool LLDB_API operator==(const SBAddress &lhs, const SBAddress &rhs);
 #endif
 
+/// A specification for a memory address that can include an address space.
+///
+/// A plain load address fully describes a location in most processes, but some
+/// (such as GPUs) need an address space identifier -- and possibly a thread 
for
+/// address spaces that are thread specific -- to describe a location in 
memory.
+class LLDB_API SBAddressSpec {
+public:
+  /// Create an invalid address spec.
+  SBAddressSpec();
+
+  /// Copy constructor.
+  SBAddressSpec(const SBAddressSpec &rhs);
+
+  /// Create from a load address.
+  ///
+  /// This represents a load address in memory and is equivalent to calling the
+  /// ReadMemory(...) methods that take a single lldb::addr_t value.
+  SBAddressSpec(lldb::addr_t load_addr);
+
+  /// Create an instance from an address and address space name.
+  SBAddressSpec(lldb::addr_t addr, const char *address_space);
+
+  ~SBAddressSpec();
+
+  /// Assignment operator.
+  const lldb::SBAddressSpec &operator=(const lldb::SBAddressSpec &rhs);
+
+protected:
+  friend class SBProcess;
+
+  lldb_private::AddressSpec &ref();
+
+  const lldb_private::AddressSpec &ref() const;
+
+private:
+  std::unique_ptr<lldb_private::AddressSpec> 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 5fcc685050c0b..39c2ccd2a3dac 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 SBAddressSpec;
 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..81031c14102e5 100644
--- a/lldb/include/lldb/API/SBProcess.h
+++ b/lldb/include/lldb/API/SBProcess.h
@@ -199,6 +199,26 @@ class LLDB_API SBProcess {
 
   size_t ReadMemory(addr_t addr, void *buf, size_t size, lldb::SBError &error);
 
+  /// Read memory from an address space.
+  ///
+  /// \param [in] addr_spec
+  ///   An address space specification that describes the memory to read from.
+  ///
+  /// \param [in] buf
+  ///   A pointer to a buffer to place the memory that is read.
+  ///
+  /// \param [in] size
+  ///   The number of bytes to read from memory.
+  ///
+  /// \param [out] error
+  ///   An error object that gets modified to indicate the success or failure
+  ///   of the memory read.
+  ///
+  /// \return
+  ///   The number of bytes that were successfully read into \a buf.
+  size_t ReadMemoryFromSpec(SBAddressSpec addr_spec, void *buf, size_t size,
+                            lldb::SBError &error);
+
   size_t WriteMemory(addr_t addr, const void *buf, size_t size,
                      lldb::SBError &error);
 
diff --git a/lldb/include/lldb/Host/common/NativeProcessProtocol.h 
b/lldb/include/lldb/Host/common/NativeProcessProtocol.h
index 435185a38f3f9..e463af72b969c 100644
--- a/lldb/include/lldb/Host/common/NativeProcessProtocol.h
+++ b/lldb/include/lldb/Host/common/NativeProcessProtocol.h
@@ -14,6 +14,7 @@
 #include "NativeWatchpointList.h"
 #include "lldb/Host/Host.h"
 #include "lldb/Host/MainLoop.h"
+#include "lldb/Utility/AddressSpace.h"
 #include "lldb/Utility/ArchSpec.h"
 #include "lldb/Utility/Iterable.h"
 #include "lldb/Utility/Status.h"
@@ -96,6 +97,12 @@ class NativeProcessProtocol {
   virtual Status GetMemoryRegionInfo(lldb::addr_t load_addr,
                                      MemoryRegionInfo &range_info);
 
+  /// Return the address spaces this process exposes. The default
+  /// implementation returns an empty list; processes that have multiple
+  /// address spaces (such as GPUs) override this. Served over the
+  /// "jAddressSpacesInfo" packet.
+  virtual std::vector<AddressSpaceInfo> GetAddressSpaces() { return {}; }
+
   virtual Status ReadMemory(lldb::addr_t addr, void *buf, size_t size,
                             size_t &bytes_read) = 0;
 
@@ -297,8 +304,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 8432c326d3281..345f4dc045cac 100644
--- a/lldb/include/lldb/Target/Process.h
+++ b/lldb/include/lldb/Target/Process.h
@@ -45,6 +45,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"
@@ -348,6 +349,84 @@ inline bool operator!=(const ProcessModID &lhs, const 
ProcessModID &rhs) {
   return (!lhs.StopIDEqual(rhs) || !lhs.MemoryIDEqual(rhs));
 }
 
+/// \class AddressSpec Process.h "lldb/Target/Process.h"
+/// An address specification able to describe a memory location in any kind of
+/// address space. It handles the arguments needed to read from:
+/// - A load address in the default address space.
+/// - A load address + address space for non-default address spaces.
+class AddressSpec {
+  /// Load address, or an offset within an address space.
+  lldb::addr_t m_value;
+  /// The name of the address space to read from, if set.
+  std::optional<std::string> m_addr_space_name;
+  /// The address space integer identifier, if set.
+  std::optional<uint64_t> m_addr_space_id;
+
+public:
+  /// Construct an AddressSpec from a load address.
+  explicit AddressSpec(lldb::addr_t load_addr) : m_value(load_addr) {}
+
+  /// Construct an AddressSpec from an address and address space integer
+  /// identifier.
+  ///
+  /// This method should be used by clients that parse debug info or runtime
+  /// information that contains the address space integer identifier in the
+  /// serialized format.
+  explicit AddressSpec(lldb::addr_t addr, std::optional<uint64_t> 
addr_space_id)
+      : m_value(addr), m_addr_space_id(addr_space_id) {}
+
+  /// Construct an AddressSpec from an address and address space name.
+  ///
+  /// This method should be used by users when specifying options or address
+  /// spaces by name as the users should not know about the numbering schemes
+  /// for address spaces.
+  AddressSpec(lldb::addr_t addr, llvm::StringRef addr_space)
+      : m_value(addr), m_addr_space_name(addr_space.str()) {}
+
+  /// Check if this address specification describes an address that is mapped
+  /// into the default address space. The default address space indicates we
+  /// should read memory from the process using the old APIs that take a
+  /// lldb::addr_t as a load address with no address space.
+  bool IsInDefaultAddressSpace() const {
+    // If we have an address space, then this address can't be in the default
+    // address space.
+    return !(m_addr_space_name.has_value() || m_addr_space_id.has_value());
+  }
+
+  /// Resolve this spec to a load address in the default address space.
+  ///
+  /// \param[in] process The process to use when resolving this address.
+  ///
+  /// \return
+  ///     The resolved load address, or an error if the spec names an address
+  ///     space (which must be read via DoReadMemory) or cannot be resolved.
+  llvm::Expected<lldb::addr_t>
+  ResolveAddressInDefaultAddressSpace(lldb_private::Process &process) const;
+
+  /// \return
+  ///     The address value, a load address or an offset within an address
+  ///     space depending on how this AddressSpec was constructed.
+  uint64_t GetValue() const { return m_value; }
+
+  /// \return
+  ///     A StringRef to the address space name if it was specified via the
+  ///     string-based constructor, or an empty StringRef if no name is set.
+  llvm::StringRef GetSpaceName() const {
+    if (m_addr_space_name.has_value())
+      return *m_addr_space_name;
+    return llvm::StringRef();
+  }
+
+  /// \return
+  ///     An optional containing the address space ID if this AddressSpec was
+  ///     constructed with a numeric address space identifier, or std::nullopt
+  ///     if no numeric ID is set.
+  std::optional<uint64_t> GetSpaceId() const { return m_addr_space_id; }
+
+  llvm::Expected<AddressSpaceInfo>
+  GetAddressSpaceInfo(lldb_private::Process &process) const;
+};
+
 /// \class Process Process.h "lldb/Target/Process.h"
 /// A plug-in interface definition class for debugging a process.
 class Process : public std::enable_shared_from_this<Process>,
@@ -1619,6 +1698,9 @@ class Process : public 
std::enable_shared_from_this<Process>,
   virtual size_t ReadMemory(lldb::addr_t vm_addr, void *buf, size_t size,
                             Status &error);
 
+  virtual size_t ReadMemory(const AddressSpec &addr_spec, void *buf,
+                            size_t size, Status &error);
+
   /// Read from multiple memory ranges and write the results into buffer.
   ///
   /// \param[in] ranges
@@ -2045,6 +2127,21 @@ class Process : public 
std::enable_shared_from_this<Process>,
   virtual Status
   GetMemoryRegions(lldb_private::MemoryRegionInfos &region_list);
 
+  /// Get the address spaces this process exposes (for example a GPU's global,
+  /// local, private or generic memory). Empty for processes with a single
+  /// address space. Populated from the process plugin when it connects.
+  const std::vector<AddressSpaceInfo> &GetAddressSpaces() const {
+    return m_address_spaces;
+  }
+
+  /// Get the address space info from an address space name.
+  llvm::Expected<AddressSpaceInfo>
+  GetAddressSpaceInfo(llvm::StringRef address_space_name);
+
+  /// Get the address space info from an address space integer identifier.
+  llvm::Expected<AddressSpaceInfo>
+  GetAddressSpaceInfo(uint64_t address_space_id);
+
   /// Get the number of watchpoints supported by this target.
   ///
   /// We may be able to determine the number of watchpoints available
@@ -3045,6 +3142,10 @@ void PruneThreadPlans();
   virtual size_t DoReadMemory(lldb::addr_t vm_addr, void *buf, size_t size,
                               Status &error) = 0;
 
+  virtual size_t DoReadMemory(const AddressSpec &addr_spec,
+                              const AddressSpaceInfo &info, void *buf,
+                              size_t size, Status &error);
+
   /// Reads each range individually via ReadMemoryFromInferior, bypassing the
   /// memory cache. Subclasses may override it to batch the reads more
   /// efficiently.
@@ -3498,6 +3599,9 @@ void PruneThreadPlans();
   ThreadList
       m_extended_thread_list; ///< Constituent for extended threads that may be
                               /// generated, cleared on natural stops
+  std::vector<AddressSpaceInfo>
+      m_address_spaces; ///< Address spaces reported by the process plugin,
+                        /// empty for single-address-space processes.
   lldb::RunDirection m_base_direction; ///< ThreadPlanBase run direction
   uint32_t m_extended_thread_stop_id; ///< The natural stop id when
                                       ///extended_thread_list was last updated
diff --git a/lldb/include/lldb/Utility/AddressSpace.h 
b/lldb/include/lldb/Utility/AddressSpace.h
new file mode 100644
index 0000000000000..adee7aef3e8db
--- /dev/null
+++ b/lldb/include/lldb/Utility/AddressSpace.h
@@ -0,0 +1,37 @@
+//===-- AddressSpace.h 
----------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM 
Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_UTILITY_ADDRESSSPACE_H
+#define LLDB_UTILITY_ADDRESSSPACE_H
+
+#include "lldb/lldb-types.h"
+#include "llvm/Support/JSON.h"
+#include <string>
+#include <vector>
+
+namespace lldb_private {
+
+/// Describes a single address space reported by a process. Processes such as
+/// GPUs can have multiple address spaces (for example global, local, private 
or
+/// generic memory) where the same numeric address refers to different storage
+/// depending on the address space. See the "jAddressSpacesInfo" packet in
+/// docs/resources/lldbgdbremote.md for the wire format.
+struct AddressSpaceInfo {
+  std::string name;        ///< The name of the address space.
+  uint64_t value;          ///< The integer identifier of the address space.
+  bool is_thread_specific; ///< True if the address space is thread specific.
+};
+
+bool fromJSON(const llvm::json::Value &value, AddressSpaceInfo &data,
+              llvm::json::Path path);
+
+llvm::json::Value toJSON(const AddressSpaceInfo &data);
+
+} // namespace lldb_private
+
+#endif // LLDB_UTILITY_ADDRESSSPACE_H
diff --git a/lldb/include/lldb/Utility/StringExtractorGDBRemote.h 
b/lldb/include/lldb/Utility/StringExtractorGDBRemote.h
index 624a2febe857e..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/include/lldb/lldb-forward.h b/lldb/include/lldb/lldb-forward.h
index 67888f7d32ed1..296ebd0a98afe 100644
--- a/lldb/include/lldb/lldb-forward.h
+++ b/lldb/include/lldb/lldb-forward.h
@@ -22,6 +22,7 @@ class AddressRange;
 class AddressRanges;
 class AddressRangeList;
 class AddressResolver;
+class AddressSpec;
 class ArchSpec;
 class Architecture;
 class Args;
diff --git a/lldb/source/API/SBAddress.cpp b/lldb/source/API/SBAddress.cpp
index 78acc2e34564d..ed67f34e638d3 100644
--- a/lldb/source/API/SBAddress.cpp
+++ b/lldb/source/API/SBAddress.cpp
@@ -11,9 +11,11 @@
 #include "lldb/API/SBProcess.h"
 #include "lldb/API/SBSection.h"
 #include "lldb/API/SBStream.h"
+#include "lldb/API/SBThread.h"
 #include "lldb/Core/Address.h"
 #include "lldb/Core/Module.h"
 #include "lldb/Symbol/LineEntry.h"
+#include "lldb/Target/Process.h"
 #include "lldb/Target/Target.h"
 #include "lldb/Utility/Instrumentation.h"
 #include "lldb/Utility/StreamString.h"
@@ -260,3 +262,36 @@ SBLineEntry SBAddress::GetLineEntry() {
   }
   return sb_line_entry;
 }
+
+SBAddressSpec::SBAddressSpec()
+    : m_opaque_up(new AddressSpec(LLDB_INVALID_ADDRESS)) {
+  LLDB_INSTRUMENT_VA(this);
+}
+
+SBAddressSpec::SBAddressSpec(const SBAddressSpec &rhs)
+    : m_opaque_up(new AddressSpec(rhs.ref())) {
+  LLDB_INSTRUMENT_VA(this, rhs);
+}
+
+SBAddressSpec::~SBAddressSpec() = default;
+
+SBAddressSpec::SBAddressSpec(lldb::addr_t load_addr)
+    : m_opaque_up(new AddressSpec(load_addr)) {
+  LLDB_INSTRUMENT_VA(this);
+}
+
+SBAddressSpec::SBAddressSpec(lldb::addr_t addr, const char *address_space)
+    : m_opaque_up(new AddressSpec(addr, address_space)) {
+  LLDB_INSTRUMENT_VA(this, addr, address_space);
+}
+
+AddressSpec &SBAddressSpec::ref() { return *m_opaque_up; }
+
+const AddressSpec &SBAddressSpec::ref() const { return *m_opaque_up; }
+
+const SBAddressSpec &SBAddressSpec::operator=(const SBAddressSpec &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 79ab659c70581..0f8be11acd7ea 100644
--- a/lldb/source/API/SBProcess.cpp
+++ b/lldb/source/API/SBProcess.cpp
@@ -33,6 +33,7 @@
 #include "lldb/Utility/State.h"
 #include "lldb/Utility/Stream.h"
 
+#include "lldb/API/SBAddress.h"
 #include "lldb/API/SBBroadcaster.h"
 #include "lldb/API/SBCommandReturnObject.h"
 #include "lldb/API/SBDebugger.h"
@@ -906,6 +907,33 @@ size_t SBProcess::ReadMemory(addr_t addr, void *dst, 
size_t dst_len,
   return bytes_read;
 }
 
+size_t SBProcess::ReadMemoryFromSpec(SBAddressSpec addr_spec, void *dst,
+                                     size_t dst_len, SBError &sb_error) {
+  LLDB_INSTRUMENT_VA(this, addr_spec, dst, dst_len, sb_error);
+
+  if (!dst) {
+    sb_error = Status::FromErrorStringWithFormat(
+        "no buffer provided to read %zu bytes into", dst_len);
+    return 0;
+  }
+
+  ProcessSP process_sp(GetSP());
+  if (!process_sp) {
+    sb_error = Status::FromErrorString("SBProcess is invalid");
+    return 0;
+  }
+
+  Process::StopLocker stop_locker;
+  if (!stop_locker.TryLock(&process_sp->GetRunLock())) {
+    sb_error = Status::FromErrorString("process is running");
+    return 0;
+  }
+
+  std::lock_guard<std::recursive_mutex> guard(
+      process_sp->GetTarget().GetAPIMutex());
+  return process_sp->ReadMemory(addr_spec.ref(), dst, dst_len, sb_error.ref());
+}
+
 size_t SBProcess::ReadCStringFromMemory(addr_t addr, void *buf, size_t size,
                                         lldb::SBError &sb_error) {
   LLDB_INSTRUMENT_VA(this, addr, buf, size, sb_error);
diff --git 
a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp 
b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
index 698bf5f7049cf..dec2903fb47a6 100644
--- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
+++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
@@ -384,6 +384,7 @@ void 
GDBRemoteCommunicationClient::ResetDiscoverableSettings(bool did_exec) {
     m_attach_or_wait_reply = eLazyBoolCalculate;
     m_avoid_g_packets = eLazyBoolCalculate;
     m_supports_multiprocess = eLazyBoolCalculate;
+    m_supports_address_spaces = false;
     m_supports_qSaveCore = eLazyBoolCalculate;
     m_supports_qXfer_auxv_read = eLazyBoolCalculate;
     m_supports_qXfer_libraries_read = eLazyBoolCalculate;
@@ -451,6 +452,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;
@@ -509,6 +511,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+")
@@ -1158,6 +1162,79 @@ 
GDBRemoteCommunicationClient::GetProcessStandaloneBinaries() {
   return m_binary_addresses;
 }
 
+std::vector<AddressSpaceInfo> GDBRemoteCommunicationClient::GetAddressSpaces() 
{
+  // Address space support is advertised via the "address-spaces+" qSupported
+  // feature; don't send the packet if the server didn't advertise it.
+  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);
+
+  // A bare JSON parse error is meaningless on its own, so log both the full
+  // response and the parse error rather than surfacing it to the user.
+  Log *log = GetLog(GDBRLog::Process);
+  LLDB_LOG_ERROR(log, info.takeError(),
+                 "malformed jAddressSpacesInfo response '{1}': {0}",
+                 response.GetStringRef());
+  return {};
+}
+
+size_t GDBRemoteCommunicationClient::ReadMemory(ProcessGDBRemote *process,
+                                                const AddressSpec &addr_spec,
+                                                const AddressSpaceInfo &info,
+                                                void *buf, size_t size,
+                                                Status &error) {
+  // Make sure this packet is supported.
+  if (!m_supports_address_spaces) {
+    error = Status::FromErrorString("address spaces are not supported");
+    return 0;
+  }
+  StreamString packet;
+  packet.PutCString("qMemRead:");
+  packet.PutCString("addr:");
+  packet.PutHex64(addr_spec.GetValue());
+  packet.PutChar(';');
+  packet.PutCString("space:");
+  packet.PutHex64(info.value);
+  packet.PutChar(';');
+  packet.PutCString("length:");
+  packet.PutHex64(size);
+  packet.PutChar(';');
+
+  StringExtractorGDBRemote response;
+  if (SendPacketAndWaitForResponse(packet.GetString(), response) !=
+      PacketResult::Success) {
+    error = Status::FromErrorString("failed to send qMemRead packet");
+    return 0;
+  }
+  if (response.IsUnsupportedResponse()) {
+    m_supports_address_spaces = false;
+    error = Status::FromErrorString("address spaces are not supported");
+    return 0;
+  }
+  if (response.IsErrorResponse()) {
+    error = response.GetStatus();
+    return 0;
+  }
+  return response.GetHexBytes(
+      llvm::MutableArrayRef<uint8_t>((uint8_t *)buf, size), '\xdd');
+}
+
 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..2a855314ec236 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,8 +35,11 @@
 #include "llvm/Support/VersionTuple.h"
 
 namespace lldb_private {
+
 namespace process_gdb_remote {
 
+class ProcessGDBRemote;
+
 /// The offsets used by the target when relocating the executable. Decoded from
 /// qOffsets packet response.
 struct QOffsets {
@@ -223,6 +227,15 @@ class GDBRemoteCommunicationClient : public 
GDBRemoteClientBase {
 
   std::vector<lldb::addr_t> GetProcessStandaloneBinaries();
 
+  /// Query the process for the address spaces it exposes via the
+  /// "jAddressSpacesInfo" packet. Returns an empty list if the process does
+  /// not support address spaces.
+  std::vector<AddressSpaceInfo> GetAddressSpaces();
+
+  size_t ReadMemory(ProcessGDBRemote *process, const AddressSpec &addr_spec,
+                    const AddressSpaceInfo &info, void *buf, size_t size,
+                    Status &error);
+
   void GetRemoteQSupported();
 
   bool GetVContSupported(llvm::StringRef flavor);
@@ -606,6 +619,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 f265fcb71be4e..8865eb14c867e 100644
--- 
a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp
+++ 
b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp
@@ -162,6 +162,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);
@@ -3909,6 +3912,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) {
@@ -4496,6 +4521,8 @@ 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+");
   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..eaef6100445a8 100644
--- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h
+++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h
@@ -268,6 +268,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 a986d0350bf57..584812b4f895c 100644
--- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
+++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
@@ -1192,6 +1192,9 @@ void ProcessGDBRemote::LoadStubBinaries() {
           allow_memory_image_last_resort);
     }
   }
+
+  // Cache the address spaces the remote exposes (empty for most processes).
+  m_address_spaces = m_gdb_comm.GetAddressSpaces();
 }
 
 void ProcessGDBRemote::MaybeLoadExecutableModule() {
@@ -2939,6 +2942,12 @@ size_t ProcessGDBRemote::DoReadMemory(addr_t addr, void 
*buf, size_t size,
   return 0;
 }
 
+size_t ProcessGDBRemote::DoReadMemory(const AddressSpec &addr_spec,
+                                      const AddressSpaceInfo &info, void *buf,
+                                      size_t size, Status &error) {
+  return m_gdb_comm.ReadMemory(this, addr_spec, info, buf, size, error);
+}
+
 /// Returns the number of ranges that is safe to request using MultiMemRead
 /// while respecting max_packet_size.
 static uint64_t ComputeNumRangesMultiMemRead(
diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h 
b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
index 0a3386082c388..e1a9fb97f3c53 100644
--- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
+++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
@@ -139,6 +139,10 @@ class ProcessGDBRemote : public Process,
   size_t DoReadMemory(lldb::addr_t addr, void *buf, size_t size,
                       Status &error) override;
 
+  size_t DoReadMemory(const AddressSpec &addr_spec,
+                      const AddressSpaceInfo &info, void *buf, size_t size,
+                      Status &error) override;
+
   /// Override of DoReadMemoryRanges that uses MultiMemRead to perform this
   /// operation in a single packet.
   llvm::SmallVector<llvm::MutableArrayRef<uint8_t>>
diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp
index 6e20703f65a45..7a6c854f445ae 100644
--- a/lldb/source/Target/Process.cpp
+++ b/lldb/source/Target/Process.cpp
@@ -2022,6 +2022,39 @@ Status Process::DisableSoftwareBreakpoint(BreakpointSite 
*bp_site) {
   return error;
 }
 
+size_t Process::ReadMemory(const AddressSpec &addr_spec, void *buf, size_t 
size,
+                           Status &error) {
+  error.Clear();
+  if (addr_spec.IsInDefaultAddressSpace()) {
+    llvm::Expected<lldb::addr_t> load_addr =
+        addr_spec.ResolveAddressInDefaultAddressSpace(*this);
+    if (load_addr) {
+      // We were able to resolve the address to an address in the default
+      // address space. Just call our standard read memory method which goes
+      // through memory caching and ABI address fixing.
+      return ReadMemory(*load_addr, buf, size, error);
+    }
+    error = Status::FromError(load_addr.takeError());
+    return 0;
+  }
+  // We have an address that can't be resolved in the default address space, so
+  // we need to call the overload that knows how to read from an address space.
+  llvm::Expected<AddressSpaceInfo> info = addr_spec.GetAddressSpaceInfo(*this);
+
+  if (info)
+    return DoReadMemory(addr_spec, *info, buf, size, error);
+  error = Status::FromError(info.takeError());
+  return 0;
+}
+
+size_t Process::DoReadMemory(const AddressSpec &addr_spec,
+                             const AddressSpaceInfo &info, void *buf,
+                             size_t size, Status &error) {
+  error =
+      Status::FromErrorString("AddressSpec memory reading is not supported");
+  return 0;
+}
+
 // Uncomment to verify memory caching works after making changes to caching
 // code
 //#define VERIFY_MEMORY_READS
@@ -7093,3 +7126,58 @@ void Process::SetAddressableBitMasks(AddressableBits 
bit_masks) {
     SetHighmemDataAddressMask(high_addr_mask);
   }
 }
+
+llvm::Expected<AddressSpaceInfo>
+AddressSpec::GetAddressSpaceInfo(lldb_private::Process &process) const {
+  if (m_addr_space_id.has_value())
+    return process.GetAddressSpaceInfo(*m_addr_space_id);
+  if (m_addr_space_name.has_value())
+    return process.GetAddressSpaceInfo(GetSpaceName());
+  return llvm::createStringError("AddressSpec has no address space info");
+}
+
+llvm::Expected<lldb::addr_t> AddressSpec::ResolveAddressInDefaultAddressSpace(
+    lldb_private::Process &) const {
+  if (!IsInDefaultAddressSpace())
+    return llvm::createStringError(
+        "address is not in the default address space");
+  // We just have a plain load address.
+  return m_value;
+}
+
+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(uint64_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.value == address_space_id)
+      return address_space_info;
+  }
+  return llvm::createStringError("invalid address space id");
+}
diff --git a/lldb/source/Utility/AddressSpace.cpp 
b/lldb/source/Utility/AddressSpace.cpp
new file mode 100644
index 0000000000000..c118259706510
--- /dev/null
+++ b/lldb/source/Utility/AddressSpace.cpp
@@ -0,0 +1,28 @@
+//===-- AddressSpace.cpp 
--------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM 
Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "lldb/Utility/AddressSpace.h"
+
+using namespace llvm;
+using namespace llvm::json;
+
+namespace lldb_private {
+
+bool fromJSON(const json::Value &value, AddressSpaceInfo &data, Path path) {
+  ObjectMapper o(value, path);
+  return o && o.map("name", data.name) && o.map("value", data.value) &&
+         o.map("is_thread_specific", data.is_thread_specific);
+}
+
+json::Value toJSON(const AddressSpaceInfo &data) {
+  return json::Value(Object{{"name", data.name},
+                            {"value", data.value},
+                            {"is_thread_specific", data.is_thread_specific}});
+}
+
+} // namespace lldb_private
diff --git a/lldb/source/Utility/CMakeLists.txt 
b/lldb/source/Utility/CMakeLists.txt
index e6d918c1ce9c7..f6000b9c330fb 100644
--- a/lldb/source/Utility/CMakeLists.txt
+++ b/lldb/source/Utility/CMakeLists.txt
@@ -25,6 +25,7 @@ endif()
 
 add_lldb_library(lldbUtility NO_INTERNAL_DEPENDENCIES
   AddressableBits.cpp
+  AddressSpace.cpp
   ArchSpec.cpp
   Args.cpp
   Baton.cpp
diff --git a/lldb/source/Utility/StringExtractorGDBRemote.cpp 
b/lldb/source/Utility/StringExtractorGDBRemote.cpp
index 6fc3b63e02dd1..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..b6fc3e50a831f
--- /dev/null
+++ 
b/lldb/test/API/functionalities/gdb_remote_client/TestAddressSpaceMemoryRead.py
@@ -0,0 +1,74 @@
+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 mock server advertises two
+    address spaces via "jAddressSpacesInfo" and answers "qMemRead" packets
+    with different bytes depending on the requested address space.
+    """
+
+    def test(self):
+        # JSON describing the two address spaces the mock process exposes. The
+        # response goes over the gdb-remote channel, so the "}" characters need
+        # to be escaped just like a real server would.
+        address_spaces_json = (
+            '[{"name":"global","value":1,"is_thread_specific":false},'
+            '{"name":"local","value":2,"is_thread_specific":false}]'
+        )
+
+        class MyResponder(MockGDBServerResponder):
+            def qSupported(self, client_supported):
+                # Advertise address space support so the client will query it.
+                return "PacketSize=3fff;QStartNoAckMode+;address-spaces+"
+
+            def qHostInfo(self):
+                return "ptrsize:8;endian:little;"
+
+            def other(self, packet):
+                if packet == "jAddressSpacesInfo":
+                    return escape_binary(address_spaces_json)
+                if packet.startswith("qMemRead:"):
+                    # Parse the "key:value;" fields out of the packet body.
+                    fields = {}
+                    for field in packet[len("qMemRead:") :].split(";"):
+                        if not field:
+                            continue
+                        key, _, value = field.partition(":")
+                        fields[key] = value
+                    space = int(fields["space"], 16)
+                    if space == 1:
+                        return "aabbccdd"
+                    if space == 2:
+                        return "11223344"
+                    return "E01"
+                return ""
+
+        self.server.responder = MyResponder()
+        target = self.dbg.CreateTarget("")
+        process = self.connect(target)
+
+        error = lldb.SBError()
+
+        # Read the same numeric address (0x1000) from two different address
+        # spaces and verify that the bytes differ and match the values the
+        # server returned for each space.
+        global_bytes = process.ReadMemoryFromSpec(
+            lldb.SBAddressSpec(0x1000, "global"), 4, error
+        )
+        self.assertSuccess(error)
+        self.assertEqual(global_bytes, b"\xaa\xbb\xcc\xdd")
+
+        local_bytes = process.ReadMemoryFromSpec(
+            lldb.SBAddressSpec(0x1000, "local"), 4, error
+        )
+        self.assertSuccess(error)
+        self.assertEqual(local_bytes, b"\x11\x22\x33\x44")
+
+        # Same address, different address space, different bytes.
+        self.assertNotEqual(global_bytes, local_bytes)
diff --git 
a/lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp 
b/lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp
index 3ec212b1c205d..04f51f484e761 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"
@@ -53,6 +55,17 @@ void HandlePacket(MockServer &server,
   ASSERT_EQ(PacketResult::Success, server.SendPacket(response));
 }
 
+// Run the qSupported handshake and have the server advertise "address-spaces+"
+// so that subsequent address-space packets are enabled on the client.
+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();
+}
+
 uint8_t all_registers[] = {'@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
                            'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O'};
 std::string all_registers_hex = "404142434445464748494a4b4c4d4e4f";
@@ -191,6 +204,48 @@ TEST_F(GDBRemoteCommunicationClientTest, ReadRegister) {
             memcmp(buffer_sp->GetBytes(), all_registers, sizeof 
all_registers));
 }
 
+TEST_F(GDBRemoteCommunicationClientTest, GetAddressSpaces) {
+  EnableAddressSpaces(client, server);
+  std::future<std::vector<AddressSpaceInfo>> result =
+      std::async(std::launch::async, [&] { return client.GetAddressSpaces(); 
});
+  // JSON responses are sent with the gdb-remote escaping applied (the same way
+  // the server's StreamGDBRemote::PutAsJSONArray does), so '{' and '}' survive
+  // the round trip.
+  StreamGDBRemote escaped;
+  llvm::StringRef json =
+      R"([{"name":"global","value":1,"is_thread_specific":false},)"
+      R"({"name":"local","value":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].value, 1u);
+  EXPECT_FALSE(spaces[0].is_thread_specific);
+  EXPECT_EQ(spaces[1].name, "local");
+  EXPECT_EQ(spaces[1].value, 2u);
+  EXPECT_TRUE(spaces[1].is_thread_specific);
+}
+
+TEST_F(GDBRemoteCommunicationClientTest, GetAddressSpacesNotAdvertised) {
+  // 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(); 
});
+  // Valid-looking JSON array, but the objects are missing required fields, so
+  // parsing fails and the client returns an empty list rather than crashing.
+  StreamGDBRemote escaped;
+  llvm::StringRef bad = R"([{"name":"global"}])";
+  escaped.PutEscapedBytes(bad.data(), bad.size());
+  HandlePacket(server, "jAddressSpacesInfo", escaped.GetString());
+  EXPECT_TRUE(result.get().empty());
+}
+
 TEST_F(GDBRemoteCommunicationClientTest, SaveRestoreRegistersNoSuffix) {
   const lldb::tid_t tid = 0x47;
   uint32_t save_id;
diff --git a/lldb/unittests/Utility/AddressSpaceTest.cpp 
b/lldb/unittests/Utility/AddressSpaceTest.cpp
new file mode 100644
index 0000000000000..7a4cf04cc8d2b
--- /dev/null
+++ b/lldb/unittests/Utility/AddressSpaceTest.cpp
@@ -0,0 +1,55 @@
+//===-- AddressSpaceTest.cpp 
----------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM 
Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "lldb/Utility/AddressSpace.h"
+#include "llvm/Support/JSON.h"
+#include "llvm/Testing/Support/Error.h"
+#include "gtest/gtest.h"
+
+using namespace lldb_private;
+
+static std::string ToString(const llvm::json::Value &value) {
+  return llvm::formatv("{0}", value).str();
+}
+
+TEST(AddressSpaceTest, RoundTrip) {
+  AddressSpaceInfo info{"global", 1, /*is_thread_specific=*/false};
+  llvm::Expected<AddressSpaceInfo> parsed = 
llvm::json::parse<AddressSpaceInfo>(
+      ToString(toJSON(info)), "AddressSpaceInfo");
+  ASSERT_THAT_EXPECTED(parsed, llvm::Succeeded());
+  EXPECT_EQ(parsed->name, "global");
+  EXPECT_EQ(parsed->value, 1u);
+  EXPECT_FALSE(parsed->is_thread_specific);
+}
+
+TEST(AddressSpaceTest, ArrayRoundTrip) {
+  std::vector<AddressSpaceInfo> spaces = {
+      {"global", 1, false},
+      {"local", 2, true},
+      {"private", 3, true},
+  };
+  llvm::json::Array array;
+  for (const AddressSpaceInfo &space : spaces)
+    array.push_back(toJSON(space));
+
+  llvm::Expected<std::vector<AddressSpaceInfo>> parsed =
+      llvm::json::parse<std::vector<AddressSpaceInfo>>(
+          ToString(llvm::json::Value(std::move(array))), "AddressSpaceInfo");
+  ASSERT_THAT_EXPECTED(parsed, llvm::Succeeded());
+  ASSERT_EQ(parsed->size(), 3u);
+  EXPECT_EQ((*parsed)[1].name, "local");
+  EXPECT_EQ((*parsed)[1].value, 2u);
+  EXPECT_TRUE((*parsed)[1].is_thread_specific);
+}
+
+TEST(AddressSpaceTest, MissingFieldFails) {
+  // "is_thread_specific" is required.
+  llvm::Expected<AddressSpaceInfo> parsed = 
llvm::json::parse<AddressSpaceInfo>(
+      R"({"name":"global","value":1})", "AddressSpaceInfo");
+  EXPECT_THAT_EXPECTED(parsed, llvm::Failed());
+}
diff --git a/lldb/unittests/Utility/CMakeLists.txt 
b/lldb/unittests/Utility/CMakeLists.txt
index ed159748838b5..e46a1774f020d 100644
--- a/lldb/unittests/Utility/CMakeLists.txt
+++ b/lldb/unittests/Utility/CMakeLists.txt
@@ -1,5 +1,6 @@
 add_lldb_unittest(UtilityTests
   AcceleratorGDBRemotePacketsTest.cpp
+  AddressSpaceTest.cpp
   AnsiTerminalTest.cpp
   ArgsTest.cpp
   OptionsWithRawTest.cpp

_______________________________________________
lldb-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to