[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