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

>From a6ba378aebe4317a5ac4ad0862c8569db6b88178 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          |  31 ++++
 lldb/include/lldb/API/SBAddress.h             |  46 ++++++
 lldb/include/lldb/API/SBDefines.h             |   1 +
 lldb/include/lldb/API/SBProcess.h             |  20 +++
 lldb/include/lldb/API/SBThread.h              |   1 +
 .../lldb/Host/common/NativeProcessProtocol.h  |  10 +-
 lldb/include/lldb/Target/Process.h            | 147 +++++++++++++++++
 lldb/include/lldb/Utility/AddressSpace.h      |  37 +++++
 .../lldb/Utility/StringExtractorGDBRemote.h   |   1 +
 lldb/include/lldb/lldb-forward.h              |   1 +
 lldb/source/API/SBAddress.cpp                 |  41 +++++
 lldb/source/API/SBProcess.cpp                 |  30 ++++
 .../GDBRemoteCommunicationClient.cpp          |  85 ++++++++++
 .../gdb-remote/GDBRemoteCommunicationClient.h |  16 ++
 .../GDBRemoteCommunicationServerLLGS.cpp      |  30 ++++
 .../GDBRemoteCommunicationServerLLGS.h        |   2 +
 .../Process/gdb-remote/ProcessGDBRemote.cpp   |   9 ++
 .../Process/gdb-remote/ProcessGDBRemote.h     |   4 +
 lldb/source/Target/Process.cpp                | 151 ++++++++++++++++++
 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 +
 26 files changed, 878 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..f43f1958aeca0 100644
--- a/lldb/docs/resources/lldbgdbremote.md
+++ b/lldb/docs/resources/lldbgdbremote.md
@@ -825,6 +825,37 @@ 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.
+
+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.
+
+The server replies with an unsupported (empty) response when the process has no
+address spaces, so the default flat-address-space case costs nothing.
+
+**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..804ff6fc529c4 100644
--- a/lldb/include/lldb/API/SBAddress.h
+++ b/lldb/include/lldb/API/SBAddress.h
@@ -130,6 +130,52 @@ class LLDB_API SBAddress {
 bool LLDB_API operator==(const SBAddress &lhs, const SBAddress &rhs);
 #endif
 
+/// A class that represents a specification for an address that can include an
+/// address space and other info needed to read or write to a memory address.
+///
+/// This object is uses to read and write to memory addresses that need more
+/// data to describe a location in memory.  For example, a memory address in a
+/// process can be described by a single load address, but a memory address in 
a
+/// GPU might require an address space identifier and possibly a thread for
+/// address spaces that are thread specific.
+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 and instance from a address and address space name.
+  SBAddressSpec(lldb::addr_t addr, const char *address_space);
+
+  /// Create and instance from a load address and address space that is thread
+  /// specific.
+  SBAddressSpec(lldb::addr_t addr, const char *address_space,
+                lldb::SBThread thread);
+
+  ~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/API/SBThread.h b/lldb/include/lldb/API/SBThread.h
index 97d3b838492fb..714933d2e903a 100644
--- a/lldb/include/lldb/API/SBThread.h
+++ b/lldb/include/lldb/API/SBThread.h
@@ -240,6 +240,7 @@ class LLDB_API SBThread {
   SBValue GetSiginfo();
 
 private:
+  friend class SBAddressSpec;
   friend class SBBreakpoint;
   friend class SBBreakpointLocation;
   friend class SBBreakpointCallbackBaton;
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..7cd4639f64977 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,127 @@ 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 structure powerful enough to represent any memory
+/// reads from any kind of address space. This struct needs to handle all of 
the
+/// arguments needed to read from:
+/// - A load address to read from default address space.
+/// - A load address + address space for reading from different address spaces.
+/// - A file address + module for a file specific address in a module.
+/// - lldb::addr_t + module + thread ID for TLS (Thread Local Storage)
+class AddressSpec {
+  /// Load address, file address or offset. If this optional has no value, then
+  /// a constructor was called that failed to resolve.
+  lldb::addr_t m_value;
+  /// The name of the address space to read from if the value is not empty.
+  std::optional<std::string> m_addr_space_name;
+  /// The address space integer if this has a value.
+  std::optional<uint64_t> m_addr_space_id;
+  /// A module might be required for thread local data for a module, or for
+  /// address spaces that are thread specific.
+  lldb::ModuleWP m_module_wp;
+  /// A thread is needed for thread local data accesses where the m_value is
+  /// an offset in the thread specific section for a module that is maintained
+  /// by the dynamic loader, or for address spaces that are thread specific.
+  lldb::ThreadWP m_thread_wp;
+
+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
+  /// idenentifier and an optional thread.
+  ///
+  /// 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 load_addr,
+                       std::optional<uint64_t> addr_space_id,
+                       lldb::ThreadSP thread_sp = {})
+      : m_value(load_addr), m_addr_space_id(addr_space_id),
+        m_thread_wp(thread_sp) {}
+
+  /// Construct an AddressSpec from an address and address space and an
+  /// optional thread.
+  ///
+  /// 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,
+              lldb::ThreadSP thread_sp = {})
+      : m_value(addr), m_addr_space_name(addr_space.str()),
+        m_thread_wp(thread_sp) {}
+
+  /// Construct an AddressSpec from a file address and its module.
+  AddressSpec(lldb::addr_t file_addr, lldb::ModuleSP module_sp)
+      : m_value(file_addr), m_module_wp(module_sp) {}
+
+  /// Construct and AddressSpec from an offset + module + thread ID which
+  /// represents a threaad local storage address.
+  AddressSpec(lldb::addr_t offset, lldb::ModuleSP module_sp,
+              lldb::ThreadSP thread_sp)
+      : m_value(offset), m_module_wp(module_sp), m_thread_wp(thread_sp) {}
+
+  /// 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());
+  }
+
+  /// See if this address spec can be converted a load adderess in the default
+  /// address space. The call will resolve any AddressSpec that doesn't have
+  /// an address space into a load address. If the address has an address 
space,
+  /// an error will be returned. Otherwise this method will resolve the address
+  /// or return an error detailing why the address failed to resolve.
+  ///
+  /// \param[in] process The process to use when resolving this address.
+  ///
+  /// \param[out] load_addr The resolved load address to fill in.
+  ///
+  /// \return
+  ///     An error object. If the error is in a success state, \a load_addr was
+  ///     successfully resolved. Otherwise the error states why the address
+  ///     couldn't be resolved.
+
+  llvm::Expected<lldb::addr_t>
+  ResolveAddressInDefaultAddressSpace(lldb_private::Process &process) const;
+
+  /// \return
+  ///     The address value, which can be a load address, file address, or
+  ///     offset 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;
+
+  // Return an error if this is module specific and the module has expired,
+  // otherwise return a ModuleSP, even if it is empty.
+  llvm::Expected<lldb::ModuleSP> GetModule() const;
+
+  // Return an error if this is thread specific and the thread has expired,
+  // otherwise return a ThreadSP, even if it is empty.
+  llvm::Expected<lldb::ThreadSP> GetThread() 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 +1741,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 +2170,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 a address space name.
+  llvm::Expected<AddressSpaceInfo>
+  GetAddressSpaceInfo(llvm::StringRef address_space_name);
+
+  /// Get the address space info from a 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 +3185,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 +3642,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..9334def085720 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,42 @@ 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);
+}
+
+SBAddressSpec::SBAddressSpec(lldb::addr_t addr, const char *address_space,
+                             lldb::SBThread thread)
+    : m_opaque_up(new AddressSpec(addr, address_space, thread.GetSP())) {
+  LLDB_INSTRUMENT_VA(this, addr, address_space, thread);
+}
+
+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..cf691d973132e 100644
--- a/lldb/source/API/SBProcess.cpp
+++ b/lldb/source/API/SBProcess.cpp
@@ -33,6 +33,7 @@
 #include "lldb/Utility/State.h"
 #include "lldb/Utility/Stream.h"
 
+#include "lldb/API/SBAddress.h"
 #include "lldb/API/SBBroadcaster.h"
 #include "lldb/API/SBCommandReturnObject.h"
 #include "lldb/API/SBDebugger.h"
@@ -906,6 +907,35 @@ size_t SBProcess::ReadMemory(addr_t addr, void *dst, 
size_t dst_len,
   return bytes_read;
 }
 
+size_t SBProcess::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;
+  }
+
+  size_t bytes_read = 0;
+  ProcessSP process_sp(GetSP());
+  if (process_sp) {
+    Process::StopLocker stop_locker;
+    if (stop_locker.TryLock(&process_sp->GetRunLock())) {
+      std::lock_guard<std::recursive_mutex> guard(
+          process_sp->GetTarget().GetAPIMutex());
+      bytes_read =
+          process_sp->ReadMemory(addr_spec.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;
+}
+
 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..9f0fd3ec7c513 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,87 @@ 
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(';');
+  if (info.is_thread_specific) {
+    if (llvm::Expected<lldb::ThreadSP> thread = addr_spec.GetThread()) {
+      packet.PutCString("tid:");
+      packet.PutHex64((*thread)->GetID());
+      packet.PutChar(';');
+    } else {
+      error = Status::FromError(thread.takeError());
+    }
+  }
+
+  StringExtractorGDBRemote response;
+  if (SendPacketAndWaitForResponse(packet.GetString(), response) ==
+      PacketResult::Success) {
+    if (response.IsUnsupportedResponse()) {
+      m_supports_address_spaces = false;
+      error = Status::FromErrorString("address spaces are not supported");
+    } else if (response.IsErrorResponse()) {
+      error = response.GetStatus();
+    } else {
+      return response.GetHexBytes(
+          llvm::MutableArrayRef<uint8_t>((uint8_t *)buf, size), '\xdd');
+    }
+  } else {
+    error = Status::FromErrorString("failed to send qMemRead packet");
+  }
+  return 0;
+}
+
 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..8cc1790118e67 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,13 @@
 #include "llvm/Support/VersionTuple.h"
 
 namespace lldb_private {
+
+class AddressSpec;
+
 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 +229,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 +621,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..fd561236ea819 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,31 @@ 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_LOGF(log,
+              "GDBRemoteCommunicationServerLLGS::%s failed, no process "
+              "available",
+              __FUNCTION__);
+    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 +4524,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..287730ada2a51 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,121 @@ void Process::SetAddressableBitMasks(AddressableBits 
bit_masks) {
     SetHighmemDataAddressMask(high_addr_mask);
   }
 }
+
+// Check if a weak_ptr was never initialized.
+// This is useful to distinguish between a weak_ptr that was never initialized
+// and a weak_ptr that was initialized and then expired.
+// 
https://stackoverflow.com/questions/45507041/how-to-check-if-weak-ptr-is-empty-non-assigned
+template <typename T>
+static bool weak_ptr_is_uninitialized(std::weak_ptr<T> const &weak) {
+  using wt = std::weak_ptr<T>;
+  return !weak.owner_before(wt{}) && !wt{}.owner_before(weak);
+}
+
+llvm::Expected<lldb::ModuleSP> AddressSpec::GetModule() const {
+  if (m_module_wp.expired() && !weak_ptr_is_uninitialized(m_module_wp))
+    return llvm::createStringError("module has expired");
+  return m_module_wp.lock();
+}
+
+llvm::Expected<lldb::ThreadSP> AddressSpec::GetThread() const {
+  if (m_thread_wp.expired() && !weak_ptr_is_uninitialized(m_thread_wp))
+    return llvm::createStringError("module has expired");
+  return m_thread_wp.lock();
+}
+
+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 &process) const {
+  if (!IsInDefaultAddressSpace())
+    return llvm::createStringError("address is not in the default address "
+                                   "space");
+  // This object holds a weak pointer to a module. We need to make sure the
+  // module hasn't been destroyed.
+  auto exp_module = GetModule();
+  if (!exp_module)
+    return exp_module.takeError();
+  ModuleSP module_sp = *exp_module;
+  if (module_sp) {
+    // This object holds a weak pointer to a thread and we need to make sure
+    // thread is still around.
+    auto exp_thread = GetThread();
+    if (!exp_thread)
+      return exp_thread.takeError();
+    ThreadSP thread_sp = *exp_thread;
+    if (thread_sp) {
+      // We have thread local storage address specification. Try to resolve the
+      // TLS address via the dynamic loader.
+      DynamicLoader *dyld = process.GetDynamicLoader();
+      if (dyld) {
+        addr_t load_addr =
+            dyld->GetThreadLocalData(module_sp, thread_sp, m_value);
+        if (load_addr != LLDB_INVALID_ADDRESS)
+          return load_addr;
+        return llvm::createStringError(
+            "dynamic loader was unable to resolve the thread local address");
+      }
+      return llvm::createStringError(
+          "no dynamic loader, unable to resolve the thread local address");
+    } else {
+      // m_value is a file address within a module.
+      Address so_addr;
+      if (module_sp->ResolveFileAddress(m_value, so_addr)) {
+        addr_t load_addr = so_addr.GetLoadAddress(&process.GetTarget());
+        if (load_addr != LLDB_INVALID_ADDRESS)
+          return load_addr;
+        return llvm::createStringError(
+            "section for module file address is not loaded in the target");
+      }
+      return llvm::createStringError(
+          "unable to resolve file address in module");
+    }
+  }
+  // We just have a plain load address already
+  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