[Lldb-commits] [lldb] Lldb address space apertures (PR #232588)

via lldb-commits lldb-commits at lists.llvm.org
Thu Oct 15 21:06:50 PDT 2026


https://github.com/halehri created https://github.com/llvm/llvm-project/pull/232588

In heterogeneous and non-flat target architectures (such as GPUs, DSPs, and accelerators), memory spaces can have distinct addressing schemes, non-byte pointer units, or memory mapped into specific apertures of a flat process address space.

This change extends `jAddressSpacesInfo` / `AddressSpaceInfo` with optional flat aperture metadata (`dwarf_address_class`, `flat_base`, `flat_size`, `pointer_unit`). When evaluating pointers or references qualified with a DWARF address class (added in #230555), `ValueObject` translates the target pointer value to and from the process load address via `Process::PointerToLoadAddress` and `Process::LoadAddressToPointer`. This enables LLDB value inspection, dereferencing, and address-of operations to work on multi-address-space targets while preserving identity mapping on flat architectures.

Testing:
- Added `AddressSpaceTest.ApertureRoundTrip` in `lldb/unittests/Utility/AddressSpaceTest.cpp`
- Added `MemoryTest.TestPointerToLoadAddressApertures` in `lldb/unittests/Target/MemoryTest.cpp`
Assisted-by: Gemini

>From 3d4a89a66ce74eebff930acc63b4fb102fe0150a Mon Sep 17 00:00:00 2001
From: Harshal Lehri <halehri at google.com>
Date: Fri, 9 Oct 2026 15:13:52 +0000
Subject: [PATCH 1/2] [lldb] Support DW_AT_address_class in DWARFASTParserClang

In architectures with multiple or non-flat address spaces (such as GPUs, DSPs, and accelerators), compilers emit the standard DWARF attribute DW_AT_address_class on pointer types to indicate which target memory space (e.g., local, constant, or device memory) the pointer targets. Currently, LLDB's DWARF parser ignores DW_AT_address_class, stripping the qualification and treating all pointers as default flat-memory pointers.

This change maps DW_AT_address_class to a Clang target address space qualifier on the pointee type in TypeSystemClang, preserving address space type fidelity for variable inspection, memory dereferencing, and expression evaluation while remaining a no-op for standard flat-memory architectures.

Testing:
- Added TestTypeSystemClang.TestAddressSpaceModifier in lldb/unittests/Symbol/TestTypeSystemClang.cpp
- Added DWARFASTParserClangTests.TestAddressClassPointerParsing in lldb/unittests/SymbolFile/DWARF/DWARFASTParserClangTests.cpp

Assisted-by: Gemini
---
 lldb/include/lldb/Symbol/CompilerType.h       |   3 +
 lldb/include/lldb/Symbol/TypeSystem.h         |   7 +
 .../SymbolFile/DWARF/DWARFASTParserClang.cpp  |  23 ++++
 .../SymbolFile/DWARF/DWARFASTParserClang.h    |   2 +
 .../TypeSystem/Clang/TypeSystemClang.cpp      |  24 ++++
 .../TypeSystem/Clang/TypeSystemClang.h        |   7 +
 lldb/source/Symbol/CompilerType.cpp           |   7 +
 lldb/unittests/Symbol/TestTypeSystemClang.cpp |  32 +++++
 .../DWARF/DWARFASTParserClangTests.cpp        | 120 ++++++++++++++++++
 9 files changed, 225 insertions(+)

diff --git a/lldb/include/lldb/Symbol/CompilerType.h b/lldb/include/lldb/Symbol/CompilerType.h
index 168946cf17475e..03f59ec04cba0d 100644
--- a/lldb/include/lldb/Symbol/CompilerType.h
+++ b/lldb/include/lldb/Symbol/CompilerType.h
@@ -206,6 +206,9 @@ class CompilerType {
 
   bool HasPointerAuthQualifier() const;
 
+  /// The target address space of this type, if it is not the default one.
+  std::optional<uint32_t> GetTargetAddressSpace() const;
+
   /// This is used when you don't care about the signedness of the integer.
   bool IsInteger() const;
 
diff --git a/lldb/include/lldb/Symbol/TypeSystem.h b/lldb/include/lldb/Symbol/TypeSystem.h
index 9ec0f13814975a..927b267c0176d3 100644
--- a/lldb/include/lldb/Symbol/TypeSystem.h
+++ b/lldb/include/lldb/Symbol/TypeSystem.h
@@ -207,6 +207,13 @@ class TypeSystem : public PluginInterface,
     return false;
   }
 
+  /// Returns the target address space (the DWARF address class) of \p type,
+  /// or std::nullopt if it lives in the default address space.
+  virtual std::optional<uint32_t>
+  GetTargetAddressSpace(lldb::opaque_compiler_type_t type) {
+    return std::nullopt;
+  }
+
   virtual bool CanPassInRegisters(const CompilerType &type) = 0;
 
   // TypeSystems can support more than one language
diff --git a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp
index b60f1d9e41958a..260de0e0b95a4e 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp
+++ b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp
@@ -469,6 +469,10 @@ ParsedDWARFTypeAttributes::ParsedDWARFTypeAttributes(const DWARFDIE &die) {
       decl.SetColumn(form_value.Unsigned());
       break;
 
+    case DW_AT_address_class:
+      address_class = form_value.Unsigned();
+      break;
+
     case DW_AT_declaration:
       is_forward_declaration = form_value.Boolean();
       break;
@@ -946,6 +950,25 @@ DWARFASTParserClang::ParseTypeModifier(const SymbolContext &sc,
     }
   }
 
+  // A pointer with DW_AT_address_class points into a target address space
+  // (e.g. a GPU, DSP, or accelerator memory). Record it as an address-space
+  // qualifier on the pointee so value objects can map the pointer to a process
+  // address.
+  if (!clang_type && tag == DW_TAG_pointer_type && attrs.address_class != 0 &&
+      attrs.type.IsValid()) {
+    if (Type *pointee_type =
+            dwarf->ResolveTypeUID(attrs.type.Reference(), true)) {
+      CompilerType pointee = m_ast.AddAddressSpaceModifier(
+          pointee_type->GetForwardCompilerType(), attrs.address_class);
+      if (pointee) {
+        clang_type = pointee.GetPointerType();
+        encoding_data_type = Type::eEncodingIsUID;
+        attrs.type.Clear();
+        resolve_state = Type::ResolveState::Full;
+      }
+    }
+  }
+
   return dwarf->MakeType(die.GetID(), attrs.name, attrs.byte_size, nullptr,
                          attrs.type.Reference().GetID(), encoding_data_type,
                          &attrs.decl, clang_type, resolve_state, payload);
diff --git a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.h b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.h
index 03c431c73fb6ff..b16c5e0e702285 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.h
+++ b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.h
@@ -571,6 +571,8 @@ struct ParsedDWARFTypeAttributes {
   uint32_t bit_stride = 0;
   uint32_t byte_stride = 0;
   uint32_t encoding = 0;
+  /// DW_AT_address_class of a pointer or reference type (0 = none).
+  uint32_t address_class = 0;
 
   ///< Indicates ref-qualifier of C++ member function if present.
   ///< Is RQ_None otherwise.
diff --git a/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp b/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp
index ee7415354bb479..eca8386f8a36b8 100644
--- a/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp
+++ b/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp
@@ -3750,6 +3750,30 @@ bool TypeSystemClang::HasPointerAuthQualifier(
   return GetCanonicalQualType(type).getPointerAuth().isPresent();
 }
 
+std::optional<uint32_t>
+TypeSystemClang::GetTargetAddressSpace(lldb::opaque_compiler_type_t type) {
+  if (!type)
+    return std::nullopt;
+  clang::LangAS as = GetCanonicalQualType(type).getAddressSpace();
+  if (!clang::isTargetAddressSpace(as))
+    return std::nullopt;
+  return clang::toTargetAddressSpace(as);
+}
+
+CompilerType TypeSystemClang::AddAddressSpaceModifier(const CompilerType &type,
+                                                      uint32_t address_space) {
+  if (!type || address_space == 0)
+    return type;
+  auto ts = type.GetTypeSystem<TypeSystemClang>();
+  if (!ts || ts.get() != this)
+    return type;
+  clang::QualType qual_type = ClangUtil::GetQualType(type);
+  if (qual_type.hasAddressSpace())
+    return type;
+  return GetType(getASTContext().getAddrSpaceQualType(
+      qual_type, clang::getLangASFromTargetAS(address_space)));
+}
+
 bool TypeSystemClang::CanPassInRegisters(const CompilerType &type) {
   if (auto *record_decl =
       TypeSystemClang::GetAsRecordDecl(type)) {
diff --git a/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.h b/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.h
index ef6d5a9c595b97..2abfe6989ff484 100644
--- a/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.h
+++ b/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.h
@@ -725,6 +725,13 @@ class TypeSystemClang : public TypeSystem {
 
   bool HasPointerAuthQualifier(lldb::opaque_compiler_type_t type) override;
 
+  std::optional<uint32_t>
+  GetTargetAddressSpace(lldb::opaque_compiler_type_t type) override;
+
+  /// Returns \p type qualified with target address space \p address_space.
+  CompilerType AddAddressSpaceModifier(const CompilerType &type,
+                                       uint32_t address_space);
+
   bool CanPassInRegisters(const CompilerType &type) override;
 
   bool SupportsLanguage(lldb::LanguageType language) override;
diff --git a/lldb/source/Symbol/CompilerType.cpp b/lldb/source/Symbol/CompilerType.cpp
index 6f20bfa89ed38c..0c25c9a2db95e7 100644
--- a/lldb/source/Symbol/CompilerType.cpp
+++ b/lldb/source/Symbol/CompilerType.cpp
@@ -325,6 +325,13 @@ bool CompilerType::HasPointerAuthQualifier() const {
   return false;
 }
 
+std::optional<uint32_t> CompilerType::GetTargetAddressSpace() const {
+  if (IsValid())
+    if (auto type_system_sp = GetTypeSystem())
+      return type_system_sp->GetTargetAddressSpace(m_type);
+  return std::nullopt;
+}
+
 bool CompilerType::IsPointerToScalarType() const {
   if (!IsValid())
     return false;
diff --git a/lldb/unittests/Symbol/TestTypeSystemClang.cpp b/lldb/unittests/Symbol/TestTypeSystemClang.cpp
index 314f8e2c97e199..60bde43decabb6 100644
--- a/lldb/unittests/Symbol/TestTypeSystemClang.cpp
+++ b/lldb/unittests/Symbol/TestTypeSystemClang.cpp
@@ -1554,3 +1554,35 @@ TEST_F(TestTypeSystemClang, TestIsMemberDataPointerType) {
   EXPECT_FALSE(int_type.IsMemberDataPointerType());
   EXPECT_FALSE(int_type.IsMemberFunctionPointerType());
 }
+
+TEST_F(TestTypeSystemClang, TestAddressSpaceModifier) {
+  CompilerType invalid_type;
+  EXPECT_EQ(invalid_type.GetTargetAddressSpace(), std::nullopt);
+  EXPECT_FALSE(m_ast->AddAddressSpaceModifier(invalid_type, 3).IsValid());
+
+  CompilerType int_type = m_ast->GetBasicType(lldb::eBasicTypeInt);
+  ASSERT_TRUE(int_type.IsValid());
+  EXPECT_EQ(int_type.GetTargetAddressSpace(), std::nullopt);
+
+  // Address space 0 is the default address space (no qualifier added).
+  CompilerType zero_as_type = m_ast->AddAddressSpaceModifier(int_type, 0);
+  EXPECT_EQ(zero_as_type.GetTargetAddressSpace(), std::nullopt);
+
+  // Qualifying with a non-zero target address space records it on the type.
+  CompilerType as3_int = m_ast->AddAddressSpaceModifier(int_type, 3);
+  ASSERT_TRUE(as3_int.IsValid());
+  EXPECT_EQ(as3_int.GetTargetAddressSpace(), std::optional<uint32_t>(3U));
+
+  // Re-qualifying an already address-space-qualified type preserves the
+  // existing address space.
+  CompilerType re_qualified = m_ast->AddAddressSpaceModifier(as3_int, 5);
+  EXPECT_EQ(re_qualified.GetTargetAddressSpace(), std::optional<uint32_t>(3U));
+
+  // A pointer to an address-space-qualified type carries the qualifier on
+  // the pointee, while the pointer itself lives in the default space.
+  CompilerType ptr_to_as3_int = as3_int.GetPointerType();
+  ASSERT_TRUE(ptr_to_as3_int.IsPointerType());
+  EXPECT_EQ(ptr_to_as3_int.GetTargetAddressSpace(), std::nullopt);
+  EXPECT_EQ(ptr_to_as3_int.GetPointeeType().GetTargetAddressSpace(),
+            std::optional<uint32_t>(3U));
+}
diff --git a/lldb/unittests/SymbolFile/DWARF/DWARFASTParserClangTests.cpp b/lldb/unittests/SymbolFile/DWARF/DWARFASTParserClangTests.cpp
index 729b47c7d08d08..f8578d7e229209 100644
--- a/lldb/unittests/SymbolFile/DWARF/DWARFASTParserClangTests.cpp
+++ b/lldb/unittests/SymbolFile/DWARF/DWARFASTParserClangTests.cpp
@@ -2245,3 +2245,123 @@ TEST_F(DWARFASTParserClangTests, TestRustVariantMember) {
   // 29352461300415899028694309177919734273
   ASSERT_EQ(f_name, "$variant$29352461300415899028694309177919734273");
 }
+
+TEST_F(DWARFASTParserClangTests, TestAddressClassPointerParsing) {
+  // Tests that DW_TAG_pointer_type with a non-zero DW_AT_address_class
+  // qualifies the pointee type with the corresponding target address space,
+  // while DW_AT_address_class == 0 or omitted leaves the pointee in the
+  // default address space.
+  const char *yamldata = R"(
+--- !ELF
+FileHeader:
+  Class:   ELFCLASS64
+  Data:    ELFDATA2LSB
+  Type:    ET_EXEC
+  Machine: EM_AARCH64
+DWARF:
+  debug_abbrev:
+    - ID:              0
+      Table:
+        - Code:            0x1
+          Tag:             DW_TAG_compile_unit
+          Children:        DW_CHILDREN_yes
+          Attributes:
+            - Attribute:       DW_AT_language
+              Form:            DW_FORM_data2
+        - Code:            0x2
+          Tag:             DW_TAG_base_type
+          Children:        DW_CHILDREN_no
+          Attributes:
+            - Attribute:       DW_AT_name
+              Form:            DW_FORM_string
+            - Attribute:       DW_AT_encoding
+              Form:            DW_FORM_data1
+            - Attribute:       DW_AT_byte_size
+              Form:            DW_FORM_data1
+        - Code:            0x3
+          Tag:             DW_TAG_pointer_type
+          Children:        DW_CHILDREN_no
+          Attributes:
+            - Attribute:       DW_AT_type
+              Form:            DW_FORM_ref4
+            - Attribute:       DW_AT_address_class
+              Form:            DW_FORM_data4
+        - Code:            0x4
+          Tag:             DW_TAG_pointer_type
+          Children:        DW_CHILDREN_no
+          Attributes:
+            - Attribute:       DW_AT_type
+              Form:            DW_FORM_ref4
+
+  debug_info:
+    - Version:  5
+      UnitType: DW_UT_compile
+      AddrSize: 8
+      Entries:
+        # 0x0c: DW_TAG_compile_unit (DW_LANG_C_plus_plus = 0x0004)
+        - AbbrCode: 0x1
+          Values:
+            - Value: 0x04
+        # 0x0f: DW_TAG_base_type ('int', DW_ATE_signed = 0x05, size = 4)
+        - AbbrCode: 0x2
+          Values:
+            - CStr:  int
+            - Value: 0x05
+            - Value: 0x04
+        # DW_TAG_pointer_type -> 0x0f with DW_AT_address_class = 3
+        - AbbrCode: 0x3
+          Values:
+            - Value: 0x0f
+            - Value: 0x03
+        # DW_TAG_pointer_type -> 0x0f with DW_AT_address_class = 0
+        - AbbrCode: 0x3
+          Values:
+            - Value: 0x0f
+            - Value: 0x00
+        # DW_TAG_pointer_type -> 0x0f without DW_AT_address_class
+        - AbbrCode: 0x4
+          Values:
+            - Value: 0x0f
+        - AbbrCode: 0x0
+)";
+  DWARFASTParserClangYAMLTester tester(yamldata);
+  DWARFDIE cu_die = tester.GetCUDIE();
+
+  DWARFDIE int_die = cu_die.GetFirstChild();
+  ASSERT_EQ(int_die.Tag(), DW_TAG_base_type);
+
+  DWARFDIE ptr_as3_die = int_die.GetSibling();
+  ASSERT_EQ(ptr_as3_die.Tag(), DW_TAG_pointer_type);
+
+  DWARFDIE ptr_as0_die = ptr_as3_die.GetSibling();
+  ASSERT_EQ(ptr_as0_die.Tag(), DW_TAG_pointer_type);
+
+  DWARFDIE ptr_no_as_die = ptr_as0_die.GetSibling();
+  ASSERT_EQ(ptr_no_as_die.Tag(), DW_TAG_pointer_type);
+
+  SymbolContext sc;
+  auto &parser = tester.GetParser();
+
+  lldb::TypeSP ptr_as3_type =
+      parser.ParseTypeFromDWARF(sc, ptr_as3_die, /*type_is_new_ptr=*/nullptr);
+  ASSERT_NE(ptr_as3_type, nullptr);
+  CompilerType ct_as3 = ptr_as3_type->GetForwardCompilerType();
+  ASSERT_TRUE(ct_as3.IsPointerType());
+  EXPECT_EQ(ct_as3.GetTargetAddressSpace(), std::nullopt);
+  EXPECT_EQ(ct_as3.GetPointeeType().GetTargetAddressSpace(),
+            std::optional<uint32_t>(3U));
+
+  lldb::TypeSP ptr_as0_type =
+      parser.ParseTypeFromDWARF(sc, ptr_as0_die, /*type_is_new_ptr=*/nullptr);
+  ASSERT_NE(ptr_as0_type, nullptr);
+  CompilerType ct_as0 = ptr_as0_type->GetForwardCompilerType();
+  ASSERT_TRUE(ct_as0.IsPointerType());
+  EXPECT_EQ(ct_as0.GetPointeeType().GetTargetAddressSpace(), std::nullopt);
+
+  lldb::TypeSP ptr_no_as_type =
+      parser.ParseTypeFromDWARF(sc, ptr_no_as_die, /*type_is_new_ptr=*/nullptr);
+  ASSERT_NE(ptr_no_as_type, nullptr);
+  CompilerType ct_no_as = ptr_no_as_type->GetForwardCompilerType();
+  ASSERT_TRUE(ct_no_as.IsPointerType());
+  EXPECT_EQ(ct_no_as.GetPointeeType().GetTargetAddressSpace(), std::nullopt);
+}

>From 1f3330d932eb8f4caa5c2b99cc15abecf658ada9 Mon Sep 17 00:00:00 2001
From: Harshal Lehri <halehri at google.com>
Date: Fri, 9 Oct 2026 21:24:01 +0000
Subject: [PATCH 2/2] [lldb] Map address-class pointers through
 jAddressSpacesInfo apertures

In heterogeneous and non-flat target architectures (such as GPUs, DSPs, and accelerators), memory spaces can have distinct addressing schemes, non-byte pointer units, or memory mapped into specific apertures of a flat process address space.

This change extends jAddressSpacesInfo / AddressSpaceInfo with optional flat aperture metadata (dwarf_address_class, flat_base, flat_size, pointer_unit). When evaluating pointers or references qualified with a DWARF address class, ValueObject translates the target pointer value to and from the process load address via Process::PointerToLoadAddress and Process::LoadAddressToPointer. This enables lldb value inspection, dereferencing, and address-of operations to work seamlessly on multi-address-space targets while preserving identity mapping on flat architectures.

Depends on #230555

Testing:
- Added AddressSpaceTest.ApertureRoundTrip in lldb/unittests/Utility/AddressSpaceTest.cpp
- Added MemoryTest.TestPointerToLoadAddressApertures in lldb/unittests/Target/MemoryTest.cpp

Assisted-by: Gemini
---
 lldb/include/lldb/Target/Process.h            | 13 ++++
 lldb/include/lldb/Utility/AddressSpace.h      | 13 ++++
 lldb/include/lldb/ValueObject/ValueObject.h   |  4 ++
 .../Language/CPlusPlus/CPlusPlusLanguage.cpp  |  7 ++-
 lldb/source/Target/Process.cpp                | 45 ++++++++++++++
 lldb/source/Utility/AddressSpace.cpp          | 20 +++++--
 lldb/source/ValueObject/ValueObject.cpp       | 38 +++++++++++-
 lldb/unittests/Target/MemoryTest.cpp          | 60 +++++++++++++++++++
 lldb/unittests/Utility/AddressSpaceTest.cpp   | 31 ++++++++++
 9 files changed, 223 insertions(+), 8 deletions(-)

diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h
index 226c58034dd655..aa765d95370a42 100644
--- a/lldb/include/lldb/Target/Process.h
+++ b/lldb/include/lldb/Target/Process.h
@@ -2034,6 +2034,19 @@ class Process : public std::enable_shared_from_this<Process>,
   llvm::Expected<AddressSpaceInfo>
   GetAddressSpaceInfo(lldb::addr_space_t address_space_id);
 
+  /// Maps \p pointer, a pointer value read from the target whose pointee is
+  /// in DWARF address class \p address_class (0 for none), to the address
+  /// lldb uses to access the pointee. This is the identity unless the server
+  /// published a flat aperture for that class (see AddressSpaceInfo).
+  lldb::addr_t PointerToLoadAddress(lldb::addr_t pointer,
+                                    uint32_t address_class);
+
+  /// Inverse of PointerToLoadAddress: returns the target pointer value for
+  /// \p address if it lies in the flat aperture of \p address_class, and
+  /// \p address unchanged otherwise.
+  lldb::addr_t LoadAddressToPointer(lldb::addr_t address,
+                                    uint32_t address_class);
+
   /// Get the number of watchpoints supported by this target.
   ///
   /// We may be able to determine the number of watchpoints available
diff --git a/lldb/include/lldb/Utility/AddressSpace.h b/lldb/include/lldb/Utility/AddressSpace.h
index f4ab9647dd10f6..ee20e84607fdd3 100644
--- a/lldb/include/lldb/Utility/AddressSpace.h
+++ b/lldb/include/lldb/Utility/AddressSpace.h
@@ -11,6 +11,7 @@
 
 #include "lldb/lldb-types.h"
 #include "llvm/Support/JSON.h"
+#include <optional>
 #include <string>
 #include <vector>
 
@@ -21,6 +22,18 @@ struct AddressSpaceInfo {
   std::string name;
   lldb::addr_space_t space_id = 0;
   bool is_thread_specific = false;
+
+  /// Optional flat aperture: pointers whose pointee has DWARF address class
+  /// \a dwarf_address_class hold a value in units of \a pointer_unit bytes
+  /// that maps to the process address flat_base + value * pointer_unit. This
+  /// lets value objects dereference pointers into memories that are not byte
+  /// addressed or not in the default address space (e.g. GPU, DSP, or accelerator).
+  /// Address class 0 (default / flat) has no aperture; 0 is the NULL pointer.
+  std::optional<uint32_t> dwarf_address_class;
+  std::optional<lldb::addr_t> flat_base;
+  /// Size in bytes of the flat aperture; 0 means unbounded.
+  uint64_t flat_size = 0;
+  uint64_t pointer_unit = 1;
 };
 
 bool fromJSON(const llvm::json::Value &value, AddressSpaceInfo &data,
diff --git a/lldb/include/lldb/ValueObject/ValueObject.h b/lldb/include/lldb/ValueObject/ValueObject.h
index c336213c84e25d..82ca483e31d4aa 100644
--- a/lldb/include/lldb/ValueObject/ValueObject.h
+++ b/lldb/include/lldb/ValueObject/ValueObject.h
@@ -588,6 +588,10 @@ class ValueObject {
 
   AddrAndType GetPointerValue();
 
+  /// Maps a pointer value of this (pointer or reference typed) object to the
+  /// process address of its pointee, honoring the pointee's address space.
+  AddrAndType MapPointerToLoadAddress(AddrAndType pointer);
+
   lldb::ValueObjectSP GetSyntheticChild(ConstString key) const;
 
   lldb::ValueObjectSP GetSyntheticArrayMember(size_t index, bool can_create);
diff --git a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
index ea8e94c116a111..0fcf1942de62d8 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
+++ b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
@@ -2372,7 +2372,12 @@ bool CPlusPlusLanguage::IsNilReference(ValueObject &valobj) {
     return false;
   bool canReadValue = true;
   bool isZero = valobj.GetValueAsUnsigned(0, &canReadValue) == 0;
-  return canReadValue && isZero;
+  // Address class 0 (default / flat) has no aperture; 0 is the NULL pointer.
+  // In an address space whose pointer value 0 maps to real memory (a flat
+  // aperture), a zero pointer is not a null reference.
+  if (!canReadValue || !isZero)
+    return false;
+  return valobj.GetPointerValue().address == 0;
 }
 
 bool CPlusPlusLanguage::IsSourceFile(llvm::StringRef file_path) const {
diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp
index c1343fa196f6e8..301ea56b0c5b12 100644
--- a/lldb/source/Target/Process.cpp
+++ b/lldb/source/Target/Process.cpp
@@ -7,6 +7,7 @@
 //===----------------------------------------------------------------------===//
 
 #include <atomic>
+#include <limits>
 #include <memory>
 #include <mutex>
 #include <optional>
@@ -7256,3 +7257,47 @@ Process::GetAddressSpaceInfo(lldb::addr_space_t address_space_id) {
                                  ", expected one of: %s",
                                  address_space_id, ids.c_str());
 }
+
+// Address class 0 (default / flat) has no aperture; 0 is the NULL pointer.
+static const AddressSpaceInfo *
+FindFlatAperture(const std::vector<AddressSpaceInfo> &address_spaces,
+                 uint32_t address_class) {
+  for (const AddressSpaceInfo &info : address_spaces) {
+    if (info.flat_base && info.pointer_unit != 0 &&
+        info.dwarf_address_class == address_class)
+      return &info;
+  }
+  return nullptr;
+}
+
+lldb::addr_t Process::PointerToLoadAddress(lldb::addr_t pointer,
+                                           uint32_t address_class) {
+  if (pointer == LLDB_INVALID_ADDRESS)
+    return pointer;
+  const AddressSpaceInfo *info =
+      FindFlatAperture(m_address_spaces, address_class);
+  if (!info)
+    return pointer;
+  // Bounds check against aperture size and overflow check.
+  if (info->flat_size != 0 &&
+      pointer > (info->flat_size - 1) / info->pointer_unit)
+    return LLDB_INVALID_ADDRESS;
+  if (pointer > std::numeric_limits<lldb::addr_t>::max() / info->pointer_unit)
+    return LLDB_INVALID_ADDRESS;
+  lldb::addr_t offset = pointer * info->pointer_unit;
+  if (std::numeric_limits<lldb::addr_t>::max() - *info->flat_base < offset)
+    return LLDB_INVALID_ADDRESS;
+  return *info->flat_base + offset;
+}
+
+lldb::addr_t Process::LoadAddressToPointer(lldb::addr_t address,
+                                           uint32_t address_class) {
+  if (address == LLDB_INVALID_ADDRESS)
+    return address;
+  const AddressSpaceInfo *info =
+      FindFlatAperture(m_address_spaces, address_class);
+  if (!info || address < *info->flat_base ||
+      (info->flat_size != 0 && address - *info->flat_base >= info->flat_size))
+    return address;
+  return (address - *info->flat_base) / info->pointer_unit;
+}
diff --git a/lldb/source/Utility/AddressSpace.cpp b/lldb/source/Utility/AddressSpace.cpp
index 38d7878e5abd15..bd6813bb18fee5 100644
--- a/lldb/source/Utility/AddressSpace.cpp
+++ b/lldb/source/Utility/AddressSpace.cpp
@@ -16,13 +16,25 @@ 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("space_id", data.space_id) &&
-         o.map("is_thread_specific", data.is_thread_specific);
+         o.map("is_thread_specific", data.is_thread_specific) &&
+         o.mapOptional("dwarf_address_class", data.dwarf_address_class) &&
+         o.mapOptional("flat_base", data.flat_base) &&
+         o.mapOptional("flat_size", data.flat_size) &&
+         o.mapOptional("pointer_unit", data.pointer_unit);
 }
 
 json::Value toJSON(const AddressSpaceInfo &data) {
-  return json::Value(Object{{"name", data.name},
-                            {"space_id", data.space_id},
-                            {"is_thread_specific", data.is_thread_specific}});
+  Object object{{"name", data.name},
+                {"space_id", data.space_id},
+                {"is_thread_specific", data.is_thread_specific}};
+  if (data.dwarf_address_class)
+    object["dwarf_address_class"] = *data.dwarf_address_class;
+  if (data.flat_base) {
+    object["flat_base"] = *data.flat_base;
+    object["flat_size"] = data.flat_size;
+    object["pointer_unit"] = data.pointer_unit;
+  }
+  return json::Value(std::move(object));
 }
 
 } // namespace lldb_private
diff --git a/lldb/source/ValueObject/ValueObject.cpp b/lldb/source/ValueObject/ValueObject.cpp
index 989ad64a9a65c0..297d855c698f0d 100644
--- a/lldb/source/ValueObject/ValueObject.cpp
+++ b/lldb/source/ValueObject/ValueObject.cpp
@@ -1704,6 +1704,29 @@ std::optional<addr_t> ValueObject::GetStrippedPointerValue(addr_t address) {
   return std::nullopt;
 }
 
+/// Returns the DWARF address class of the pointee of \p type, or 0.
+static uint32_t GetPointeeAddressClass(const CompilerType &type) {
+  CompilerType pointee;
+  if (!type.IsPointerOrReferenceType(&pointee))
+    return 0;
+  return pointee.GetTargetAddressSpace().value_or(0);
+}
+
+ValueObject::AddrAndType
+ValueObject::MapPointerToLoadAddress(AddrAndType pointer) {
+  if (pointer.address == LLDB_INVALID_ADDRESS ||
+      pointer.type != eAddressTypeLoad)
+    return pointer;
+  CompilerType type = GetCompilerType();
+  if (!type.IsPointerOrReferenceType())
+    return pointer;
+  ExecutionContext exe_ctx(GetExecutionContextRef());
+  if (Process *process = exe_ctx.GetProcessPtr())
+    pointer.address = process->PointerToLoadAddress(
+        pointer.address, GetPointeeAddressClass(type));
+  return pointer;
+}
+
 ValueObject::AddrAndType ValueObject::GetPointerValue() {
   if (!UpdateValueIfNeeded(false))
     return {};
@@ -1712,14 +1735,16 @@ ValueObject::AddrAndType ValueObject::GetPointerValue() {
   case Value::ValueType::Invalid:
     return {};
   case Value::ValueType::Scalar:
-    return {m_value.GetScalar().ULongLong(LLDB_INVALID_ADDRESS),
-            GetAddressTypeOfChildren()};
+    return MapPointerToLoadAddress(
+        {m_value.GetScalar().ULongLong(LLDB_INVALID_ADDRESS),
+         GetAddressTypeOfChildren()});
 
   case Value::ValueType::HostAddress:
   case Value::ValueType::LoadAddress:
   case Value::ValueType::FileAddress: {
     lldb::offset_t data_offset = 0;
-    return {m_data.GetAddress(&data_offset), GetAddressTypeOfChildren()};
+    return MapPointerToLoadAddress(
+        {m_data.GetAddress(&data_offset), GetAddressTypeOfChildren()});
   }
   }
 
@@ -2961,6 +2986,13 @@ ValueObjectSP ValueObject::AddressOf(Status &error) {
         name.append(m_name.AsCString(""));
         ExecutionContext exe_ctx(GetExecutionContextRef());
 
+        // The resulting pointer holds the target's pointer encoding of addr.
+        if (address_type == eAddressTypeLoad) {
+          if (Process *process = exe_ctx.GetProcessPtr())
+            addr = process->LoadAddressToPointer(
+                addr, GetCompilerType().GetTargetAddressSpace().value_or(0));
+        }
+
         lldb::DataBufferSP buffer(
             new lldb_private::DataBufferHeap(&addr, sizeof(lldb::addr_t)));
         m_addr_of_valobj_sp = ValueObjectConstResult::Create(
diff --git a/lldb/unittests/Target/MemoryTest.cpp b/lldb/unittests/Target/MemoryTest.cpp
index e24e75d47a0305..4fc131fc6f6215 100644
--- a/lldb/unittests/Target/MemoryTest.cpp
+++ b/lldb/unittests/Target/MemoryTest.cpp
@@ -129,6 +129,9 @@ class DummyProcess : public Process {
   MemoryCache &GetMemoryCache() { return m_memory_cache; }
   void SetMaxReadSize(size_t size) { m_bytes_left = size; }
   void SetFiller(int filler) { m_filler = filler; }
+  void SetAddressSpaces(std::vector<AddressSpaceInfo> spaces) {
+    m_address_spaces = std::move(spaces);
+  }
 };
 
 // A MemoryCache subclass that exposes the otherwise-protected caches so a
@@ -1869,3 +1872,60 @@ TEST_F(MemoryTest, TestReadMemoryClearsStaleError) {
             12u);
   EXPECT_TRUE(short_error.Fail());
 }
+
+TEST_F(MemoryTest, TestPointerToLoadAddressApertures) {
+  ArchSpec arch("x86_64-apple-macosx-");
+  Platform::SetHostPlatform(PlatformRemoteMacOSX::CreateInstance(true, &arch));
+
+  DebuggerSP debugger_sp = Debugger::CreateInstance();
+  ASSERT_TRUE(debugger_sp);
+
+  TargetSP target_sp = CreateTarget(debugger_sp, arch);
+  ASSERT_TRUE(target_sp);
+
+  ProcessSP process_sp = CreateProcess(target_sp);
+  ASSERT_TRUE(process_sp);
+
+  auto *dummy_process = static_cast<DummyProcess *>(process_sp.get());
+  dummy_process->SetAddressSpaces({
+      // Byte-addressed bounded aperture for DWARF address class 3.
+      {"local", 1, /*is_thread_specific=*/false, /*dwarf_address_class=*/3U,
+       /*flat_base=*/0x100000ULL, /*flat_size=*/0x1000ULL,
+       /*pointer_unit=*/1ULL},
+      // 4-byte word-addressed bounded aperture for DWARF address class 5.
+      {"vmem", 2, /*is_thread_specific=*/false, /*dwarf_address_class=*/5U,
+       /*flat_base=*/0x200000ULL, /*flat_size=*/0x400ULL,
+       /*pointer_unit=*/4ULL},
+  });
+
+  // Invalid address is preserved.
+  EXPECT_EQ(process_sp->PointerToLoadAddress(LLDB_INVALID_ADDRESS, 3),
+            LLDB_INVALID_ADDRESS);
+  EXPECT_EQ(process_sp->LoadAddressToPointer(LLDB_INVALID_ADDRESS, 3),
+            LLDB_INVALID_ADDRESS);
+
+  // Default address class (0) or unconfigured class is the identity mapping.
+  EXPECT_EQ(process_sp->PointerToLoadAddress(0x1234, 0), 0x1234ULL);
+  EXPECT_EQ(process_sp->PointerToLoadAddress(0x1234, 99), 0x1234ULL);
+  EXPECT_EQ(process_sp->LoadAddressToPointer(0x100020, 0), 0x100020ULL);
+
+  // Byte-addressed aperture (class 3: base 0x100000, size 0x1000, unit 1):
+  // Pointer value 0 maps to flat_base.
+  EXPECT_EQ(process_sp->PointerToLoadAddress(0, 3), 0x100000ULL);
+  EXPECT_EQ(process_sp->PointerToLoadAddress(0xfff, 3), 0x100fffULL);
+  // Out-of-bounds pointer returns LLDB_INVALID_ADDRESS.
+  EXPECT_EQ(process_sp->PointerToLoadAddress(0x1000, 3), LLDB_INVALID_ADDRESS);
+  // Inverse mapping inside and outside the aperture.
+  EXPECT_EQ(process_sp->LoadAddressToPointer(0x100000, 3), 0ULL);
+  EXPECT_EQ(process_sp->LoadAddressToPointer(0x100040, 3), 0x40ULL);
+  EXPECT_EQ(process_sp->LoadAddressToPointer(0x0fffff, 3), 0x0fffffULL);
+  EXPECT_EQ(process_sp->LoadAddressToPointer(0x101000, 3), 0x101000ULL);
+
+  // Word-addressed aperture (class 5: base 0x200000, size 0x400, unit 4):
+  EXPECT_EQ(process_sp->PointerToLoadAddress(10, 5), 0x200028ULL);
+  EXPECT_EQ(process_sp->PointerToLoadAddress(0xff, 5), 0x2003fcULL);
+  EXPECT_EQ(process_sp->PointerToLoadAddress(0x100, 5), LLDB_INVALID_ADDRESS);
+  EXPECT_EQ(process_sp->PointerToLoadAddress(UINT64_MAX, 5),
+            LLDB_INVALID_ADDRESS);
+  EXPECT_EQ(process_sp->LoadAddressToPointer(0x200028, 5), 10ULL);
+}
diff --git a/lldb/unittests/Utility/AddressSpaceTest.cpp b/lldb/unittests/Utility/AddressSpaceTest.cpp
index 56f65965e80c87..ad547d2702c5d3 100644
--- a/lldb/unittests/Utility/AddressSpaceTest.cpp
+++ b/lldb/unittests/Utility/AddressSpaceTest.cpp
@@ -54,3 +54,34 @@ TEST(AddressSpaceTest, MissingFieldFails) {
       R"({"name":"global"})", "AddressSpaceInfo");
   EXPECT_THAT_EXPECTED(parsed, llvm::Failed());
 }
+
+TEST(AddressSpaceTest, ApertureRoundTrip) {
+  AddressSpaceInfo info{"local",
+                        2,
+                        /*is_thread_specific=*/false,
+                        /*dwarf_address_class=*/3U,
+                        /*flat_base=*/0x100000ULL,
+                        /*flat_size=*/0x4000ULL,
+                        /*pointer_unit=*/4ULL};
+  llvm::Expected<AddressSpaceInfo> parsed = llvm::json::parse<AddressSpaceInfo>(
+      ToString(toJSON(info)), "AddressSpaceInfo");
+  ASSERT_THAT_EXPECTED(parsed, llvm::Succeeded());
+  EXPECT_EQ(parsed->name, "local");
+  EXPECT_EQ(parsed->space_id, 2u);
+  EXPECT_FALSE(parsed->is_thread_specific);
+  EXPECT_EQ(parsed->dwarf_address_class, std::optional<uint32_t>(3U));
+  EXPECT_EQ(parsed->flat_base, std::optional<lldb::addr_t>(0x100000ULL));
+  EXPECT_EQ(parsed->flat_size, 0x4000ULL);
+  EXPECT_EQ(parsed->pointer_unit, 4ULL);
+
+  // Default AddressSpaceInfo omits optional aperture fields.
+  AddressSpaceInfo plain{"global", 1, /*is_thread_specific=*/false};
+  llvm::Expected<AddressSpaceInfo> parsed_plain =
+      llvm::json::parse<AddressSpaceInfo>(ToString(toJSON(plain)),
+                                          "AddressSpaceInfo");
+  ASSERT_THAT_EXPECTED(parsed_plain, llvm::Succeeded());
+  EXPECT_EQ(parsed_plain->dwarf_address_class, std::nullopt);
+  EXPECT_EQ(parsed_plain->flat_base, std::nullopt);
+  EXPECT_EQ(parsed_plain->flat_size, 0ULL);
+  EXPECT_EQ(parsed_plain->pointer_unit, 1ULL);
+}



More information about the lldb-commits mailing list