[Lldb-commits] [lldb] [LLDB] Expose and display GDB remote register union values (PR #224147)
Bar Soloveychik via lldb-commits
lldb-commits at lists.llvm.org
Sun Oct 18 07:07:39 PDT 2026
https://github.com/barsolo2000 updated https://github.com/llvm/llvm-project/pull/224147
>From 07998625fe901227268a88ec2c59c1ce1bf51950 Mon Sep 17 00:00:00 2001
From: Bar Soloveychik <barsolo at fb.com>
Date: Tue, 8 Sep 2026 08:43:31 -0700
Subject: [PATCH 01/12] [lldb][7/7] Expose and display GDB remote register
union values
---
.../source/Commands/CommandObjectRegister.cpp | 137 +++++++-
lldb/source/Core/DumpRegisterValue.cpp | 3 +-
lldb/source/Core/Value.cpp | 7 +-
.../RegisterTypeBuilder/CMakeLists.txt | 1 +
.../RegisterTypeBuilderClang.cpp | 177 ++++++++--
.../RegisterTypeBuilderClang.h | 18 +-
.../ValueObject/ValueObjectRegister.cpp | 15 +-
.../gdb_remote_client/TestXMLRegisterUnion.py | 327 ++++++++++++++++++
.../Target/RegisterTypeBuilderClangTest.cpp | 160 +++++++++
9 files changed, 792 insertions(+), 53 deletions(-)
diff --git a/lldb/source/Commands/CommandObjectRegister.cpp b/lldb/source/Commands/CommandObjectRegister.cpp
index 192cef6a2a53b6..02717c82262777 100644
--- a/lldb/source/Commands/CommandObjectRegister.cpp
+++ b/lldb/source/Commands/CommandObjectRegister.cpp
@@ -10,6 +10,7 @@
#include "lldb/Core/Debugger.h"
#include "lldb/Core/DumpRegisterInfo.h"
#include "lldb/Core/DumpRegisterValue.h"
+#include "lldb/DataFormatters/DumpValueObjectOptions.h"
#include "lldb/Host/OptionParser.h"
#include "lldb/Interpreter/CommandInterpreter.h"
#include "lldb/Interpreter/CommandOptionArgumentTable.h"
@@ -23,11 +24,16 @@
#include "lldb/Target/Process.h"
#include "lldb/Target/RegisterContext.h"
#include "lldb/Target/SectionLoadList.h"
+#include "lldb/Target/StackFrame.h"
#include "lldb/Target/Thread.h"
#include "lldb/Utility/Args.h"
#include "lldb/Utility/DataExtractor.h"
+#include "lldb/Utility/RegisterType.h"
#include "lldb/Utility/RegisterValue.h"
+#include "lldb/Utility/StreamString.h"
+#include "lldb/ValueObject/ValueObjectRegister.h"
#include "llvm/Support/Errno.h"
+#include "llvm/Support/Error.h"
using namespace lldb;
using namespace lldb_private;
@@ -41,6 +47,68 @@ static size_t GetNameSize(const RegisterInfo *reg_info, bool use_primary_name) {
return reg_name ? strlen(reg_name) : 0;
}
+static std::pair<const RegisterInfo *, llvm::StringRef>
+FindRegisterWithExpressionPath(RegisterContext ®_ctx, llvm::StringRef name) {
+ if (const RegisterInfo *reg_info = reg_ctx.GetRegisterInfoByName(name))
+ return {reg_info, {}};
+
+ size_t search_end = name.size();
+ while (search_end) {
+ size_t split = name.take_front(search_end).find_last_of(".[");
+ if (split == llvm::StringRef::npos)
+ break;
+ if (const RegisterInfo *reg_info =
+ reg_ctx.GetRegisterInfoByName(name.take_front(split)))
+ return {reg_info, name.drop_front(split)};
+ search_end = split;
+ }
+ return {nullptr, {}};
+}
+
+static ValueObjectSP
+GetRegisterValueForExpressionPath(ValueObjectSP value,
+ llvm::StringRef expression_path) {
+ while (!expression_path.empty()) {
+ if (expression_path.consume_front(".")) {
+ size_t separator = expression_path.find_first_of(".[");
+ llvm::StringRef member = separator == llvm::StringRef::npos
+ ? expression_path
+ : expression_path.take_front(separator);
+ if (member.empty())
+ return {};
+ value = value->GetChildMemberWithName(member);
+ expression_path = separator == llvm::StringRef::npos
+ ? llvm::StringRef()
+ : expression_path.drop_front(separator);
+ } else if (expression_path.consume_front("[")) {
+ size_t closing_bracket = expression_path.find(']');
+ if (closing_bracket == llvm::StringRef::npos)
+ return {};
+
+ uint32_t index = 0;
+ if (expression_path.take_front(closing_bracket).getAsInteger(10, index))
+ return {};
+
+ llvm::Expected<uint32_t> num_children = value->GetNumChildren();
+ if (!num_children) {
+ llvm::consumeError(num_children.takeError());
+ return {};
+ }
+ if (index >= *num_children)
+ return {};
+
+ value = value->GetChildAtIndex(index);
+ expression_path = expression_path.drop_front(closing_bracket + 1);
+ } else {
+ return {};
+ }
+
+ if (!value)
+ return {};
+ }
+ return value;
+}
+
static size_t ComputeLongestRegisterName(RegisterContext *reg_ctx,
const RegisterSet ®_set,
bool use_primary_name,
@@ -78,8 +146,9 @@ static size_t ComputeLongestRegisterName(Args &command,
llvm::StringRef arg_str = entry.ref();
arg_str.consume_front("$");
- if (const RegisterInfo *reg_info =
- reg_ctx->GetRegisterInfoByName(arg_str)) {
+ const RegisterInfo *reg_info =
+ FindRegisterWithExpressionPath(*reg_ctx, arg_str).first;
+ if (reg_info) {
name_right_align_at = std::max(name_right_align_at,
GetNameSize(reg_info, use_primary_name));
}
@@ -161,6 +230,61 @@ class CommandObjectRegisterRead : public CommandObjectParsed {
return true;
}
+ bool DumpRegisterField(const ExecutionContext &exe_ctx, Stream &strm,
+ const RegisterInfo ®_info,
+ llvm::StringRef expression_path,
+ size_t reg_name_right_align_at,
+ CommandReturnObject &result) {
+ if (!llvm::isa_and_present<RegisterTypeUnion, RegisterTypeVector>(
+ reg_info.register_type)) {
+ result.AppendErrorWithFormat(
+ "Register '%s' does not have a structured type", reg_info.name);
+ return false;
+ }
+
+ StackFrame *frame = exe_ctx.GetFramePtr();
+ if (!frame)
+ return false;
+ lldb::RegisterContextSP reg_ctx_sp = frame->GetRegisterContextSP();
+ ValueObjectSP register_value =
+ ValueObjectRegister::Create(frame, reg_ctx_sp, ®_info);
+ ValueObjectSP child =
+ GetRegisterValueForExpressionPath(register_value, expression_path);
+ if (!child) {
+ llvm::StringRef error_path = expression_path;
+ error_path.consume_front(".");
+ result.AppendErrorWithFormat("No field path '%s' in register '%s'",
+ error_path.str().c_str(), reg_info.name);
+ return false;
+ }
+
+ DumpValueObjectOptions options(*child);
+ options.SetHideRootType(true)
+ .SetHideRootName(true)
+ .SetHideName(true)
+ .SetAllowOnelinerMode(true);
+ if (m_format_options.GetFormatValue().OptionWasSet())
+ options.SetFormat(m_format_options.GetFormatValue().GetCurrentValue());
+
+ StreamString value_stream;
+ if (llvm::Error error = child->Dump(value_stream, options)) {
+ result.AppendError(toString(std::move(error)));
+ return false;
+ }
+
+ llvm::StringRef value = value_stream.GetString();
+ value.consume_back("\n");
+ const char *register_name =
+ m_command_options.alternate_name && reg_info.alt_name
+ ? reg_info.alt_name
+ : reg_info.name;
+ strm.Indent();
+ strm.Printf("%*s%s = ", static_cast<int>(reg_name_right_align_at),
+ register_name, expression_path.str().c_str());
+ strm << value << '\n';
+ return true;
+ }
+
bool DumpRegisterSet(const ExecutionContext &exe_ctx, Stream &strm,
RegisterContext *reg_ctx, size_t set_idx,
bool primitive_only = false) {
@@ -264,14 +388,19 @@ class CommandObjectRegisterRead : public CommandObjectParsed {
auto arg_str = entry.ref();
arg_str.consume_front("$");
- if (const RegisterInfo *reg_info =
- reg_ctx->GetRegisterInfoByName(arg_str)) {
+ auto [reg_info, expression_path] =
+ FindRegisterWithExpressionPath(*reg_ctx, arg_str);
+
+ if (reg_info && expression_path.empty()) {
// If they have asked for a specific format don't obscure that by
// printing a structured value afterwards.
bool print_type = !m_format_options.GetFormatValue().OptionWasSet();
if (!DumpRegister(m_exe_ctx, strm, *reg_ctx, *reg_info, print_type,
reg_name_right_align_at))
strm.Printf("%-12s = error: unavailable\n", reg_info->name);
+ } else if (reg_info) {
+ DumpRegisterField(m_exe_ctx, strm, *reg_info, expression_path,
+ reg_name_right_align_at, result);
} else {
result.AppendErrorWithFormat("Invalid register name '%s'",
arg_str.str().c_str());
diff --git a/lldb/source/Core/DumpRegisterValue.cpp b/lldb/source/Core/DumpRegisterValue.cpp
index dff81f9bdbe900..84b1e03dd4576c 100644
--- a/lldb/source/Core/DumpRegisterValue.cpp
+++ b/lldb/source/Core/DumpRegisterValue.cpp
@@ -143,7 +143,8 @@ void lldb_private::DumpRegisterValue(const RegisterValue ®_val, Stream &s,
const RegisterTypeFlags *flags_type =
llvm::dyn_cast_if_present<RegisterTypeFlags>(reg_info.register_type);
if (!flags_type &&
- !llvm::isa_and_present<RegisterTypeVector>(reg_info.register_type))
+ !llvm::isa_and_present<RegisterTypeVector, RegisterTypeUnion>(
+ reg_info.register_type))
return;
if (flags_type && reg_info.byte_size != 4 && reg_info.byte_size != 8)
return;
diff --git a/lldb/source/Core/Value.cpp b/lldb/source/Core/Value.cpp
index 0a7610e62ed4ab..11d313ccd9e9b3 100644
--- a/lldb/source/Core/Value.cpp
+++ b/lldb/source/Core/Value.cpp
@@ -26,6 +26,7 @@
#include "lldb/Utility/FileSpec.h"
#include "lldb/Utility/LLDBLog.h"
#include "lldb/Utility/Log.h"
+#include "lldb/Utility/RegisterType.h"
#include "lldb/Utility/State.h"
#include "lldb/Utility/Stream.h"
#include "lldb/lldb-defines.h"
@@ -493,10 +494,10 @@ Status Value::GetValueAsData(ExecutionContext *exe_ctx, DataExtractor &data,
if (exe_ctx) {
if (Target *target = exe_ctx->GetTargetPtr()) {
ByteOrder byte_order = target->GetArchitecture().GetByteOrder();
- // ValueObjectRegister stores typed vectors in target byte order and
- // all other register buffers in host byte order.
+ // ValueObjectRegister stores structured register types in target byte
+ // order and all other register buffers in host byte order.
if (m_context_type == ContextType::RegisterInfo &&
- !llvm::isa_and_present<RegisterTypeVector>(
+ !llvm::isa_and_present<RegisterTypeVector, RegisterTypeUnion>(
GetRegisterInfo()->register_type))
byte_order = endian::InlHostByteOrder();
data.SetByteOrder(byte_order);
diff --git a/lldb/source/Plugins/RegisterTypeBuilder/CMakeLists.txt b/lldb/source/Plugins/RegisterTypeBuilder/CMakeLists.txt
index 7411b24c86cbdb..c70e61ea0aa225 100644
--- a/lldb/source/Plugins/RegisterTypeBuilder/CMakeLists.txt
+++ b/lldb/source/Plugins/RegisterTypeBuilder/CMakeLists.txt
@@ -7,5 +7,6 @@ add_lldb_library(lldbPluginRegisterTypeBuilderClang PLUGIN
Support
LINK_LIBS
lldbCore
+ lldbDataFormatters
lldbTarget
)
diff --git a/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.cpp b/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.cpp
index 893c1b27ade509..07464d552a318d 100644
--- a/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.cpp
+++ b/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.cpp
@@ -10,11 +10,15 @@
#include "RegisterTypeBuilderClang.h"
#include "lldb/Core/PluginManager.h"
+#include "lldb/DataFormatters/DataVisualization.h"
+#include "lldb/DataFormatters/TypeSummary.h"
#include "lldb/Utility/RegisterType.h"
#include "lldb/lldb-enumerations.h"
#include "llvm/ADT/bit.h"
#include "llvm/Support/ErrorHandling.h"
+#include <algorithm>
+
using namespace lldb_private;
LLDB_PLUGIN_DEFINE(RegisterTypeBuilderClang)
@@ -152,36 +156,15 @@ RegisterTypeBuilderClang::BuildVectorType(const RegisterTypeVector *vector_type,
if (auto type = GetExistingCompilerType(vector_type, expected_byte_size))
return *type;
- std::optional<uint64_t> element_size =
- vector_type->GetElementType()->GetByteSize();
- if (!element_size) {
- if (expected_byte_size % vector_type->GetCount())
- return {};
- element_size = expected_byte_size / vector_type->GetCount();
- }
- if (*element_size > UINT32_MAX ||
- expected_byte_size % vector_type->GetCount() ||
- *element_size != expected_byte_size / vector_type->GetCount())
+ std::optional<uint32_t> element_size =
+ GetTargetByteSize(vector_type->GetElementType(), type_system);
+ if (!element_size || *element_size > UINT32_MAX / vector_type->GetCount() ||
+ *element_size * vector_type->GetCount() != expected_byte_size)
return {};
- CompilerType element_type;
const RegisterType *element_register_type = vector_type->GetElementType();
- switch (element_register_type->getKind()) {
- case RegisterType::eRegisterTypeKindBuiltin:
- element_type =
- BuildBuiltinType(llvm::cast<RegisterTypeBuiltin>(element_register_type),
- *element_size, type_system);
- break;
- case RegisterType::eRegisterTypeKindVector:
- element_type =
- BuildVectorType(llvm::cast<RegisterTypeVector>(element_register_type),
- *element_size, type_system);
- break;
- case RegisterType::eRegisterTypeKindEnum:
- case RegisterType::eRegisterTypeKindFlags:
- case RegisterType::eRegisterTypeKindUnion:
- return {};
- }
+ CompilerType element_type =
+ BuildType(element_register_type, *element_size, type_system);
if (!element_type.IsValid())
return {};
@@ -217,6 +200,129 @@ RegisterTypeBuilderClang::BuildVectorType(const RegisterTypeVector *vector_type,
return compiler_type;
}
+CompilerType
+RegisterTypeBuilderClang::BuildUnionType(const RegisterTypeUnion *union_type,
+ uint32_t expected_byte_size,
+ lldb::TypeSystemClangSP type_system) {
+ if (!expected_byte_size)
+ return {};
+ if (auto type = GetExistingCompilerType(union_type, expected_byte_size))
+ return *type;
+
+ std::vector<std::pair<llvm::StringRef, CompilerType>> fields;
+ fields.reserve(union_type->GetFields().size());
+ for (const RegisterTypeUnion::Field &field : union_type->GetFields()) {
+ std::optional<uint32_t> field_size =
+ GetTargetByteSize(field.GetType(), type_system);
+ if (!field_size || *field_size > expected_byte_size)
+ return {};
+
+ CompilerType field_type =
+ BuildType(field.GetType(), *field_size, type_system);
+ if (!field_type.IsValid())
+ return {};
+ fields.emplace_back(field.GetName(), field_type);
+ }
+
+ std::string type_name = "__lldb_register_union_" +
+ std::to_string(union_type->GetUID()) + "_" +
+ std::to_string(expected_byte_size);
+ CompilerType compiler_type = type_system->CreateRecordType(
+ nullptr, OptionalClangModuleID(), type_name,
+ llvm::to_underlying(clang::TagTypeKind::Union), lldb::eLanguageTypeC);
+ type_system->StartTagDeclarationDefinition(compiler_type);
+ for (const auto &[field_name, field_type] : fields)
+ type_system->AddFieldToRecordType(compiler_type, field_name, field_type,
+ /*bitfield_bit_size=*/0);
+ type_system->CompleteTagDeclarationDefinition(compiler_type);
+ type_system->SetIsPacked(compiler_type);
+
+ if (llvm::expectedToOptional(compiler_type.GetByteSize(nullptr)) !=
+ expected_byte_size)
+ return {};
+
+ TypeSummaryImpl::Flags summary_flags;
+ summary_flags.SetShowMembersOneLiner(true);
+ auto summary_sp = std::make_shared<StringSummaryFormat>(summary_flags, "");
+ lldb::TypeCategoryImplSP category_sp;
+ DataVisualization::Categories::GetCategory(ConstString("default"),
+ category_sp);
+ if (category_sp)
+ category_sp->AddTypeSummary(type_name, lldb::eFormatterMatchExact,
+ summary_sp);
+
+ m_type_cache.try_emplace(
+ std::make_pair(union_type->GetUID(), expected_byte_size), compiler_type);
+ return compiler_type;
+}
+
+CompilerType
+RegisterTypeBuilderClang::BuildType(const RegisterType *register_type,
+ uint32_t expected_byte_size,
+ lldb::TypeSystemClangSP type_system) {
+ switch (register_type->getKind()) {
+ case RegisterType::eRegisterTypeKindBuiltin:
+ return BuildBuiltinType(llvm::cast<RegisterTypeBuiltin>(register_type),
+ expected_byte_size, type_system);
+ case RegisterType::eRegisterTypeKindVector:
+ return BuildVectorType(llvm::cast<RegisterTypeVector>(register_type),
+ expected_byte_size, type_system);
+ case RegisterType::eRegisterTypeKindUnion:
+ return BuildUnionType(llvm::cast<RegisterTypeUnion>(register_type),
+ expected_byte_size, type_system);
+ case RegisterType::eRegisterTypeKindEnum:
+ case RegisterType::eRegisterTypeKindFlags:
+ return {};
+ }
+}
+
+std::optional<uint32_t> RegisterTypeBuilderClang::GetTargetByteSize(
+ const RegisterType *register_type, lldb::TypeSystemClangSP type_system) {
+ if (std::optional<uint64_t> fixed_size = register_type->GetByteSize()) {
+ if (*fixed_size <= UINT32_MAX)
+ return static_cast<uint32_t>(*fixed_size);
+ return std::nullopt;
+ }
+
+ switch (register_type->getKind()) {
+ case RegisterType::eRegisterTypeKindBuiltin: {
+ const auto *builtin_type = llvm::cast<RegisterTypeBuiltin>(register_type);
+ if (builtin_type->GetID() != "data_ptr" &&
+ builtin_type->GetID() != "code_ptr")
+ return std::nullopt;
+ std::optional<uint64_t> pointer_size = llvm::expectedToOptional(
+ type_system->GetType(type_system->getASTContext().VoidPtrTy)
+ .GetByteSize(nullptr));
+ if (!pointer_size || *pointer_size > UINT32_MAX)
+ return std::nullopt;
+ return static_cast<uint32_t>(*pointer_size);
+ }
+ case RegisterType::eRegisterTypeKindVector: {
+ const auto *vector_type = llvm::cast<RegisterTypeVector>(register_type);
+ std::optional<uint32_t> element_size =
+ GetTargetByteSize(vector_type->GetElementType(), type_system);
+ if (!element_size || *element_size > UINT32_MAX / vector_type->GetCount())
+ return std::nullopt;
+ return *element_size * vector_type->GetCount();
+ }
+ case RegisterType::eRegisterTypeKindUnion: {
+ uint32_t byte_size = 0;
+ for (const RegisterTypeUnion::Field &field :
+ llvm::cast<RegisterTypeUnion>(register_type)->GetFields()) {
+ std::optional<uint32_t> field_size =
+ GetTargetByteSize(field.GetType(), type_system);
+ if (!field_size)
+ return std::nullopt;
+ byte_size = std::max(byte_size, *field_size);
+ }
+ return byte_size;
+ }
+ case RegisterType::eRegisterTypeKindEnum:
+ case RegisterType::eRegisterTypeKindFlags:
+ return std::nullopt;
+ }
+}
+
CompilerType
RegisterTypeBuilderClang::GetRegisterType(const RegisterInfo ®_info) {
lldb::TypeSystemClangSP type_system =
@@ -237,9 +343,7 @@ RegisterTypeBuilderClang::GetRegisterType(const RegisterInfo ®_info) {
switch (reg_info.register_type->getKind()) {
case RegisterType::eRegisterTypeKindBuiltin:
- return BuildBuiltinType(
- llvm::cast<RegisterTypeBuiltin>(reg_info.register_type),
- reg_info.byte_size, type_system);
+ return BuildType(reg_info.register_type, reg_info.byte_size, type_system);
case RegisterType::eRegisterTypeKindFlags:
return BuildFlagsType(
llvm::dyn_cast<RegisterTypeFlags>(reg_info.register_type),
@@ -249,11 +353,14 @@ RegisterTypeBuilderClang::GetRegisterType(const RegisterInfo ®_info) {
llvm::dyn_cast<RegisterTypeEnum>(reg_info.register_type),
reg_info.byte_size, type_system);
case RegisterType::eRegisterTypeKindVector:
- return BuildVectorType(
- llvm::cast<RegisterTypeVector>(reg_info.register_type),
- reg_info.byte_size, type_system);
- case RegisterType::eRegisterTypeKindUnion:
- return {};
+ return BuildType(reg_info.register_type, reg_info.byte_size, type_system);
+ case RegisterType::eRegisterTypeKindUnion: {
+ std::optional<uint32_t> byte_size =
+ GetTargetByteSize(reg_info.register_type, type_system);
+ if (!byte_size || *byte_size > reg_info.byte_size)
+ return {};
+ return BuildType(reg_info.register_type, *byte_size, type_system);
+ }
}
llvm_unreachable("Unhandled RegisterType kind");
}
diff --git a/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.h b/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.h
index ab069b56422251..a8c005d77252f3 100644
--- a/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.h
+++ b/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.h
@@ -49,15 +49,27 @@ class RegisterTypeBuilderClang : public RegisterTypeBuilder {
uint32_t expected_byte_size,
lldb::TypeSystemClangSP type_system);
+ CompilerType BuildUnionType(const RegisterTypeUnion *union_type,
+ uint32_t expected_byte_size,
+ lldb::TypeSystemClangSP type_system);
+
+ CompilerType BuildType(const RegisterType *register_type,
+ uint32_t expected_byte_size,
+ lldb::TypeSystemClangSP type_system);
+
+ std::optional<uint32_t>
+ GetTargetByteSize(const RegisterType *register_type,
+ lldb::TypeSystemClangSP type_system);
+
Target &m_target;
// A cache of previously created types. We do not cache by element ID because
// IDs are not unique across xml <feature> elements and this class does not
// know anything about features.
//
- // The key contains the process-wide UID of the type and the size of the
- // register we made it for. Some types (enums for example) use the register
- // size in their type and must be rebuilt for a different size.
+ // The key contains the process-wide UID of the type and the byte size used
+ // to build it. Some types (enums for example) use the register size, while a
+ // union can use its natural size inside a larger register or nested type.
//
// 8 is chosen because types are only made when needed, and most lldb commands
// do not need them.
diff --git a/lldb/source/ValueObject/ValueObjectRegister.cpp b/lldb/source/ValueObject/ValueObjectRegister.cpp
index 23f4f3deb5c87a..71be7ab993681d 100644
--- a/lldb/source/ValueObject/ValueObjectRegister.cpp
+++ b/lldb/source/ValueObject/ValueObjectRegister.cpp
@@ -229,8 +229,8 @@ CompilerType ValueObjectRegister::GetCompilerTypeImpl() {
if (!target)
return {};
- if (llvm::isa_and_present<RegisterTypeBuiltin, RegisterTypeVector>(
- m_reg_info.register_type)) {
+ if (llvm::isa_and_present<RegisterTypeBuiltin, RegisterTypeVector,
+ RegisterTypeUnion>(m_reg_info.register_type)) {
m_compiler_type = target->GetRegisterType(m_reg_info);
if (m_compiler_type.IsValid())
return m_compiler_type;
@@ -284,12 +284,13 @@ bool ValueObjectRegister::UpdateValue() {
RegisterValue m_old_reg_value(m_reg_value);
if (m_reg_ctx_sp->ReadRegister(&m_reg_info, m_reg_value)) {
Target *target = exe_ctx.GetTargetPtr();
- const bool has_vector_type =
- llvm::isa_and_present<RegisterTypeVector>(m_reg_info.register_type);
- // Scalar registers remain in host byte order, while CompilerType vector
- // children need target-order bytes to interpret their layout.
+ const bool has_structured_type =
+ llvm::isa_and_present<RegisterTypeVector, RegisterTypeUnion>(
+ m_reg_info.register_type);
+ // Scalar registers remain in host byte order, while CompilerType
+ // children need target-order bytes to interpret structured layouts.
const bool got_data =
- has_vector_type && target
+ has_structured_type && target
? m_reg_value.GetData(m_data, m_reg_info.byte_size,
target->GetArchitecture().GetByteOrder())
: m_reg_value.GetData(m_data);
diff --git a/lldb/test/API/functionalities/gdb_remote_client/TestXMLRegisterUnion.py b/lldb/test/API/functionalities/gdb_remote_client/TestXMLRegisterUnion.py
index 3303d57d4504a8..19826ba8ef2912 100644
--- a/lldb/test/API/functionalities/gdb_remote_client/TestXMLRegisterUnion.py
+++ b/lldb/test/API/functionalities/gdb_remote_client/TestXMLRegisterUnion.py
@@ -274,3 +274,330 @@ def test_union_ids_are_scoped_to_included_feature(self):
self.assert_union_info("first", 8, ["first (uint64, 8 bytes)"])
self.assert_union_info("second", 4, ["second (uint32, 4 bytes)"])
self.assert_no_union_info("unresolved")
+
+ @skipIfXmlSupportMissing
+ @skipIfRemote
+ def test_direct_union_sb_api(self):
+ process = self.setup_register_test(
+ """\
+ <vector id="v4f" type="ieee_single" count="4"/>
+ <vector id="v2d" type="ieee_double" count="2"/>
+ <union id="views">
+ <field name="f32" type="ieee_single"/>
+ <field name="f64" type="ieee_double"/>
+ <field name="floats" type="v4f"/>
+ <field name="doubles" type="v2d"/>
+ <field name="raw" type="uint128"/>
+ </union>
+ <reg name="u0" regnum="0" bitsize="128" type="views"/>
+ <reg name="pc" bitsize="64"/>""",
+ "0000c03f000020400000604000009040" + "00" * 8,
+ )
+
+ frame = process.GetThreadAtIndex(0).GetFrameAtIndex(0)
+ union = frame.FindRegister("u0")
+ self.assertTrue(union.IsValid())
+ self.assertTrue(union.GetType().IsValid())
+ self.assertEqual(union.GetByteSize(), 16)
+ self.assertEqual(union.GetNumChildren(), 5)
+ self.assertEqual(
+ [union.GetChildAtIndex(i).GetName() for i in range(5)],
+ ["f32", "f64", "floats", "doubles", "raw"],
+ )
+ self.assertAlmostEqual(
+ union.GetChildMemberWithName("f32").GetData().float[0], 1.5
+ )
+ self.assertAlmostEqual(
+ union.GetValueForExpressionPath(".floats[2]").GetData().float[0],
+ 3.5,
+ )
+
+ @skipIfXmlSupportMissing
+ @skipIfRemote
+ def test_union_smaller_than_register_sb_api(self):
+ process = self.setup_register_test(
+ """\
+ <union id="small">
+ <field name="value" type="uint32"/>
+ </union>
+ <reg name="u0" regnum="0" bitsize="128" type="small"/>
+ <reg name="pc" bitsize="64"/>""",
+ "2a000000" + "00" * 20,
+ )
+
+ union = process.GetThreadAtIndex(0).GetFrameAtIndex(0).FindRegister("u0")
+ self.assertEqual(union.GetByteSize(), 16)
+ self.assertEqual(union.GetType().GetByteSize(), 4)
+ self.assertEqual(union.GetChildMemberWithName("value").GetValueAsUnsigned(), 42)
+
+ @skipIfXmlSupportMissing
+ @skipIfRemote
+ def test_nested_union_and_vector_sb_api(self):
+ process = self.setup_register_test(
+ """\
+ <vector id="v4f" type="ieee_single" count="4"/>
+ <union id="float_views">
+ <field name="scalar" type="ieee_single"/>
+ <field name="lanes" type="v4f"/>
+ </union>
+ <union id="nested">
+ <field name="f32_view" type="float_views"/>
+ <field name="raw" type="uint128"/>
+ </union>
+ <reg name="n0" regnum="0" bitsize="128" type="nested"/>
+ <reg name="pc" bitsize="64"/>""",
+ "0000c03f000020400000604000009040" + "00" * 8,
+ )
+
+ frame = process.GetThreadAtIndex(0).GetFrameAtIndex(0)
+ union = frame.FindRegister("n0")
+ self.assertEqual(union.GetNumChildren(), 2)
+ lane = union.GetValueForExpressionPath(".f32_view.lanes[3]")
+ self.assertTrue(lane.IsValid())
+ self.assertAlmostEqual(lane.GetData().float[0], 4.5)
+
+ @skipIfXmlSupportMissing
+ @skipIfRemote
+ def test_byte_view_and_vector_of_unions_sb_api(self):
+ process = self.setup_register_test(
+ """\
+ <vector id="c32" type="uint8" count="32"/>
+ <union id="bytes_view">
+ <field name="c" type="c32"/>
+ </union>
+ <union id="scalar32_views">
+ <field name="f32" type="ieee_single"/>
+ <field name="u32" type="uint32"/>
+ </union>
+ <vector id="v4views" type="scalar32_views" count="4"/>
+ <reg name="b0" regnum="0" bitsize="256" type="bytes_view"/>
+ <reg name="vu0" regnum="1" bitsize="128" type="v4views"/>
+ <reg name="pc" bitsize="64"/>""",
+ bytes(range(32)).hex() + "0000c03f000020400000604000009040" + "00" * 8,
+ )
+
+ frame = process.GetThreadAtIndex(0).GetFrameAtIndex(0)
+ byte_view = frame.FindRegister("b0").GetChildMemberWithName("c")
+ self.assertEqual(byte_view.GetNumChildren(), 32)
+ self.assertEqual(byte_view.GetChildAtIndex(16).GetValueAsUnsigned(), 16)
+
+ vector = frame.FindRegister("vu0")
+ self.assertEqual(vector.GetNumChildren(), 4)
+ third = vector.GetChildAtIndex(2)
+ self.assertEqual(
+ [third.GetChildAtIndex(i).GetName() for i in range(2)],
+ ["f32", "u32"],
+ )
+ self.assertAlmostEqual(
+ third.GetChildMemberWithName("f32").GetData().float[0], 3.5
+ )
+ self.expect("register read vu0[2].f32", substrs=["vu0[2].f32 = 3.5"])
+
+ @skipIfXmlSupportMissing
+ @skipIfRemote
+ def test_target_sized_union_vector_sb_api(self):
+ process = self.setup_register_test(
+ """\
+ <union id="pointer_view">
+ <field name="pointer" type="data_ptr"/>
+ </union>
+ <vector id="v2p" type="pointer_view" count="2"/>
+ <reg name="v0" regnum="0" bitsize="128" type="v2p"/>
+ <reg name="pc" bitsize="64"/>""",
+ "34120000000000007856000000000000" + "00" * 8,
+ )
+
+ vector = process.GetThreadAtIndex(0).GetFrameAtIndex(0).FindRegister("v0")
+ self.assertEqual(vector.GetNumChildren(), 2)
+ self.assertEqual(
+ vector.GetChildAtIndex(0)
+ .GetChildMemberWithName("pointer")
+ .GetValueAsUnsigned(),
+ 0x1234,
+ )
+ self.assertEqual(
+ vector.GetChildAtIndex(1)
+ .GetChildMemberWithName("pointer")
+ .GetValueAsUnsigned(),
+ 0x5678,
+ )
+
+ @skipIfXmlSupportMissing
+ @skipIfRemote
+ @skipIfLLVMTargetMissing("SystemZ")
+ def test_big_endian_union_sb_api(self):
+ process = self.setup_multidoc_test(
+ {
+ "target.xml": dedent(
+ """\
+ <?xml version="1.0"?>
+ <target version="1.0">
+ <architecture>s390x</architecture>
+ <feature name="test.register.unions">
+ <vector id="v2f" type="ieee_single" count="2"/>
+ <union id="views">
+ <field name="scalar" type="ieee_single"/>
+ <field name="lanes" type="v2f"/>
+ </union>
+ <reg name="u0" regnum="0" bitsize="64" type="views"/>
+ <union id="small">
+ <field name="value" type="uint32"/>
+ </union>
+ <reg name="small" regnum="1" bitsize="128" type="small"/>
+ <reg name="pswa" regnum="2" bitsize="64"/>
+ </feature>
+ </target>"""
+ )
+ },
+ "3fc0000040200000" + "0000002a" + "00" * 20,
+ )
+
+ frame = process.GetThreadAtIndex(0).GetFrameAtIndex(0)
+ union = frame.FindRegister("u0")
+ self.assertAlmostEqual(
+ union.GetChildMemberWithName("scalar").GetData().float[0], 1.5
+ )
+ lanes = union.GetChildMemberWithName("lanes")
+ self.assertEqual(
+ [lanes.GetChildAtIndex(i).GetData().float[0] for i in range(2)],
+ [1.5, 2.5],
+ )
+ ull = process.GetTarget().GetBasicType(lldb.eBasicTypeUnsignedLongLong)
+ self.assertEqual(union.Cast(ull).GetValueAsUnsigned(), 0x3FC0000040200000)
+
+ small = frame.FindRegister("small")
+ self.assertEqual(small.GetByteSize(), 16)
+ self.assertEqual(small.GetType().GetByteSize(), 4)
+ self.assertEqual(small.GetChildMemberWithName("value").GetValueAsUnsigned(), 42)
+
+ @skipIfXmlSupportMissing
+ @skipIfRemote
+ def test_union_cli_summary_and_member_paths(self):
+ process = self.setup_register_test(
+ """\
+ <vector id="v4f" type="ieee_single" count="4"/>
+ <union id="views">
+ <field name="f32" type="ieee_single"/>
+ <field name="f64" type="ieee_double"/>
+ <field name="u64" type="uint64"/>
+ </union>
+ <union id="vector_views">
+ <field name="floats" type="v4f"/>
+ <field name="raw" type="uint128"/>
+ </union>
+ <union id="nested">
+ <field name="view" type="vector_views"/>
+ <field name="raw" type="uint128"/>
+ </union>
+ <reg name="u0" altname="alt_u0" regnum="0" bitsize="64" type="views"/>
+ <reg name="u1" regnum="1" bitsize="128" type="vector_views"/>
+ <reg name="n0" regnum="2" bitsize="128" type="nested"/>
+ <reg name="pc" bitsize="64"/>""",
+ "0000c03fffffffff" + "0000c03f000020400000604000009040" * 2 + "00" * 8,
+ )
+
+ self.expect(
+ "register read u0",
+ patterns=[
+ r"u0 = 0xffffffff3fc00000\r?\n"
+ r" = \(f32 = 1\.5, f64 = NaN, "
+ r"u64 = 18446744070484131840\)"
+ ],
+ )
+ self.expect("register read u0.f32", substrs=["u0.f32 = 1.5"])
+ self.expect("register read -A alt_u0.f32", substrs=["alt_u0.f32 = 1.5"])
+ self.expect("register read $u0.f64", substrs=["u0.f64 = NaN"])
+ self.expect("register read u1.floats[2]", substrs=["u1.floats[2] = 3.5"])
+ self.expect(
+ "register read n0.view.floats[3]",
+ substrs=["n0.view.floats[3] = 4.5"],
+ )
+
+ union = process.GetThreadAtIndex(0).GetFrameAtIndex(0).FindRegister("u0")
+ self.assertEqual(
+ union.GetSummary(),
+ "(f32 = 1.5, f64 = NaN, u64 = 18446744070484131840)",
+ )
+
+ @skipIfXmlSupportMissing
+ @skipIfRemote
+ def test_exact_dotted_register_name_takes_precedence(self):
+ self.setup_register_test(
+ """\
+ <union id="views">
+ <field name="f32" type="ieee_single"/>
+ <field name="raw" type="uint64"/>
+ </union>
+ <reg name="u0" regnum="0" bitsize="64" type="views"/>
+ <reg name="u0.f32" regnum="1" bitsize="32"/>
+ <reg name="u0.view" regnum="2" bitsize="64" type="views"/>
+ <reg name="pc" bitsize="64"/>""",
+ "0000c03f00000000" + "2a000000" + "0000c03f00000000" + "00" * 8,
+ )
+
+ self.expect("register read u0.f32", substrs=["u0.f32 = 0x0000002a"])
+ self.expect("register read u0.view.f32", substrs=["u0.view.f32 = 1.5"])
+
+ @skipIfXmlSupportMissing
+ @skipIfRemote
+ def test_invalid_union_member_paths(self):
+ self.setup_register_test(
+ """\
+ <union id="views">
+ <field name="f32" type="ieee_single"/>
+ <field name="raw" type="uint64"/>
+ </union>
+ <vector id="v2views" type="views" count="2"/>
+ <reg name="u0" regnum="0" bitsize="64" type="views"/>
+ <reg name="v0" regnum="1" bitsize="128" type="v2views"/>
+ <reg name="pc" regnum="2" bitsize="64"/>""",
+ "00" * 32,
+ )
+
+ self.expect("register read u0.missing", error=True, substrs=["No field path"])
+ self.expect("register read u0.", error=True, substrs=["No field path"])
+ self.expect("register read u0..f32", error=True, substrs=["No field path"])
+ self.expect(
+ "register read v0[9].f32",
+ error=True,
+ substrs=["No field path '[9].f32'"],
+ )
+ self.expect(
+ "register read pc.field",
+ error=True,
+ substrs=["does not have a structured type"],
+ )
+
+ @skipIfXmlSupportMissing
+ @skipIfRemote
+ @skipIfLLVMTargetMissing("SystemZ")
+ def test_big_endian_union_cli(self):
+ self.setup_multidoc_test(
+ {
+ "target.xml": dedent(
+ """\
+ <?xml version="1.0"?>
+ <target version="1.0">
+ <architecture>s390x</architecture>
+ <feature name="test.register.unions">
+ <vector id="v2f" type="ieee_single" count="2"/>
+ <union id="views">
+ <field name="scalar" type="ieee_single"/>
+ <field name="lanes" type="v2f"/>
+ </union>
+ <reg name="u0" regnum="0" bitsize="64" type="views"/>
+ <reg name="pswa" regnum="1" bitsize="64"/>
+ </feature>
+ </target>"""
+ )
+ },
+ "3fc0000040200000" + "00" * 8,
+ )
+
+ self.expect(
+ "register read u0",
+ patterns=[
+ r"u0 = 0x3fc0000040200000\r?\n"
+ r" = \(scalar = 1\.5, lanes = \(1\.5, 2\.5\)\)"
+ ],
+ )
diff --git a/lldb/unittests/Target/RegisterTypeBuilderClangTest.cpp b/lldb/unittests/Target/RegisterTypeBuilderClangTest.cpp
index 8cfb177fe625d9..88fa9f97152731 100644
--- a/lldb/unittests/Target/RegisterTypeBuilderClangTest.cpp
+++ b/lldb/unittests/Target/RegisterTypeBuilderClangTest.cpp
@@ -347,4 +347,164 @@ TEST_F(RegisterTypeBuilderClangTest, RejectsVectorSizeMismatch) {
EXPECT_FALSE(builder.GetRegisterType(MakeRegisterInfo(pointer_vector, 15)));
}
+TEST_F(RegisterTypeBuilderClangTest, BuildsPackedUnion) {
+ Target &target = m_debugger_sp->GetDummyTarget();
+ RegisterTypeBuiltin float_type("ieee_single", eEncodingIEEE754, eFormatFloat,
+ 4);
+ RegisterTypeBuiltin double_type("ieee_double", eEncodingIEEE754, eFormatFloat,
+ 8);
+ RegisterTypeUnion union_type("views",
+ {RegisterTypeUnion::Field("f32", &float_type),
+ RegisterTypeUnion::Field("f64", &double_type)});
+ RegisterTypeBuilderClang builder(target);
+
+ CompilerType type = builder.GetRegisterType(MakeRegisterInfo(union_type, 8));
+
+ ASSERT_TRUE(type);
+ EXPECT_EQ(type.GetTypeClass(), eTypeClassUnion);
+ EXPECT_EQ(llvm::expectedToOptional(type.GetByteSize(nullptr)), 8u);
+ EXPECT_EQ(type.GetNumFields(), 2u);
+ for (size_t index = 0; index < 2; ++index) {
+ std::string name;
+ uint64_t bit_offset = UINT64_MAX;
+ ASSERT_TRUE(
+ type.GetFieldAtIndex(index, name, &bit_offset, nullptr, nullptr));
+ EXPECT_EQ(bit_offset, 0u);
+ }
+}
+
+TEST_F(RegisterTypeBuilderClangTest, AllowsUnionSmallerThanRegister) {
+ Target &target = m_debugger_sp->GetDummyTarget();
+ RegisterTypeBuiltin uint32_type("uint32", eEncodingUint, eFormatHex, 4);
+ RegisterTypeUnion union_type(
+ "small", {RegisterTypeUnion::Field("value", &uint32_type)});
+ RegisterTypeBuilderClang builder(target);
+
+ CompilerType type = builder.GetRegisterType(MakeRegisterInfo(union_type, 16));
+
+ ASSERT_TRUE(type);
+ EXPECT_EQ(llvm::expectedToOptional(type.GetByteSize(nullptr)), 4u);
+}
+
+TEST_F(RegisterTypeBuilderClangTest, BuildsOddSizedUnion) {
+ Target &target = m_debugger_sp->GetDummyTarget();
+ RegisterTypeBuiltin byte_type("uint8", eEncodingUint, eFormatHex, 1);
+ RegisterTypeVector bytes_type("v3u8", &byte_type, 3);
+ RegisterTypeBuiltin uint16_type("uint16", eEncodingUint, eFormatHex, 2);
+ RegisterTypeUnion union_type(
+ "odd", {RegisterTypeUnion::Field("bytes", &bytes_type),
+ RegisterTypeUnion::Field("word", &uint16_type)});
+ RegisterTypeBuilderClang builder(target);
+
+ CompilerType type = builder.GetRegisterType(MakeRegisterInfo(union_type, 3));
+
+ ASSERT_TRUE(type);
+ EXPECT_EQ(llvm::expectedToOptional(type.GetByteSize(nullptr)), 3u);
+ EXPECT_EQ(type.GetNumFields(), 2u);
+}
+
+TEST_F(RegisterTypeBuilderClangTest, BuildsNestedUnionAndVectorMembers) {
+ Target &target = m_debugger_sp->GetDummyTarget();
+ RegisterTypeBuiltin float_type("ieee_single", eEncodingIEEE754, eFormatFloat,
+ 4);
+ RegisterTypeVector vector_type("v4f", &float_type, 4);
+ RegisterTypeUnion inner_type(
+ "inner", {RegisterTypeUnion::Field("scalar", &float_type),
+ RegisterTypeUnion::Field("lanes", &vector_type)});
+ RegisterTypeUnion outer_type("outer",
+ {RegisterTypeUnion::Field("view", &inner_type),
+ RegisterTypeUnion::Field("raw", &vector_type)});
+ RegisterTypeBuilderClang builder(target);
+
+ CompilerType type = builder.GetRegisterType(MakeRegisterInfo(outer_type, 16));
+
+ ASSERT_TRUE(type);
+ EXPECT_EQ(llvm::expectedToOptional(type.GetByteSize(nullptr)), 16u);
+ EXPECT_EQ(type.GetNumFields(), 2u);
+ std::string name;
+ CompilerType inner = type.GetFieldAtIndex(0, name, nullptr, nullptr, nullptr);
+ ASSERT_TRUE(inner);
+ EXPECT_EQ(name, "view");
+ EXPECT_EQ(inner.GetNumFields(), 2u);
+}
+
+TEST_F(RegisterTypeBuilderClangTest, BuildsVectorOfUnions) {
+ Target &target = m_debugger_sp->GetDummyTarget();
+ RegisterTypeBuiltin float_type("ieee_single", eEncodingIEEE754, eFormatFloat,
+ 4);
+ RegisterTypeBuiltin uint32_type("uint32", eEncodingUint, eFormatHex, 4);
+ RegisterTypeUnion element_type(
+ "views", {RegisterTypeUnion::Field("f32", &float_type),
+ RegisterTypeUnion::Field("u32", &uint32_type)});
+ RegisterTypeVector vector_type("v4views", &element_type, 4);
+ RegisterTypeBuilderClang builder(target);
+
+ CompilerType type =
+ builder.GetRegisterType(MakeRegisterInfo(vector_type, 16));
+
+ ASSERT_TRUE(type);
+ EXPECT_EQ(llvm::expectedToOptional(type.GetByteSize(nullptr)), 16u);
+ EXPECT_NE(type.GetTypeInfo() & eTypeIsArray, 0u);
+ EXPECT_EQ(llvm::expectedToOptional(type.GetNumChildren(true, nullptr)), 4u);
+}
+
+TEST_F(RegisterTypeBuilderClangTest, BuildsVectorOfTargetSizedUnions) {
+ Target &target = m_debugger_sp->GetDummyTarget();
+ lldb::TypeSystemClangSP type_system =
+ ScratchTypeSystemClang::GetForTarget(target);
+ ASSERT_TRUE(type_system);
+ std::optional<uint64_t> pointer_size = llvm::expectedToOptional(
+ type_system->GetType(type_system->getASTContext().VoidPtrTy)
+ .GetByteSize(nullptr));
+ ASSERT_TRUE(pointer_size);
+
+ RegisterTypeBuiltin pointer_type("data_ptr", eEncodingUint,
+ eFormatAddressInfo, std::nullopt);
+ RegisterTypeUnion element_type(
+ "pointer_view", {RegisterTypeUnion::Field("pointer", &pointer_type)});
+ RegisterTypeVector vector_type("pointer_views", &element_type, 2);
+ RegisterTypeBuilderClang builder(target);
+
+ CompilerType type = builder.GetRegisterType(
+ MakeRegisterInfo(vector_type, static_cast<uint32_t>(*pointer_size * 2)));
+
+ ASSERT_TRUE(type);
+ EXPECT_EQ(llvm::expectedToOptional(type.GetByteSize(nullptr)),
+ *pointer_size * 2);
+ EXPECT_EQ(llvm::expectedToOptional(type.GetNumChildren(true, nullptr)), 2u);
+}
+
+TEST_F(RegisterTypeBuilderClangTest, BuildsTargetSizedUnionMember) {
+ Target &target = m_debugger_sp->GetDummyTarget();
+ lldb::TypeSystemClangSP type_system =
+ ScratchTypeSystemClang::GetForTarget(target);
+ ASSERT_TRUE(type_system);
+ std::optional<uint64_t> pointer_size = llvm::expectedToOptional(
+ type_system->GetType(type_system->getASTContext().VoidPtrTy)
+ .GetByteSize(nullptr));
+ ASSERT_TRUE(pointer_size);
+
+ RegisterTypeBuiltin pointer_type("data_ptr", eEncodingUint,
+ eFormatAddressInfo, std::nullopt);
+ RegisterTypeUnion union_type(
+ "pointer_view", {RegisterTypeUnion::Field("pointer", &pointer_type)});
+ RegisterTypeBuilderClang builder(target);
+
+ CompilerType type = builder.GetRegisterType(
+ MakeRegisterInfo(union_type, static_cast<uint32_t>(*pointer_size)));
+
+ ASSERT_TRUE(type);
+ EXPECT_EQ(llvm::expectedToOptional(type.GetByteSize(nullptr)), pointer_size);
+}
+
+TEST_F(RegisterTypeBuilderClangTest, RejectsOversizedUnion) {
+ Target &target = m_debugger_sp->GetDummyTarget();
+ RegisterTypeBuiltin uint64_type("uint64", eEncodingUint, eFormatHex, 8);
+ RegisterTypeUnion union_type(
+ "wide", {RegisterTypeUnion::Field("value", &uint64_type)});
+ RegisterTypeBuilderClang builder(target);
+
+ EXPECT_FALSE(builder.GetRegisterType(MakeRegisterInfo(union_type, 4)));
+}
+
} // namespace
>From 9e7a7f2c8afcb38c63799a92dc0b09bbef231d9b Mon Sep 17 00:00:00 2001
From: Bar Soloveychik <barsolo at fb.com>
Date: Wed, 16 Sep 2026 13:24:59 -0700
Subject: [PATCH 02/12] [lldb][7/7] Tighten register union value handling
---
.../source/Commands/CommandObjectRegister.cpp | 85 ++++++++++---------
lldb/source/DataFormatters/FormatManager.cpp | 8 ++
.../RegisterTypeBuilder/CMakeLists.txt | 1 -
.../RegisterTypeBuilderClang.cpp | 12 ---
.../gdb_remote_client/TestXMLRegisterUnion.py | 40 ++++++---
.../Target/RegisterTypeBuilderClangTest.cpp | 3 +-
6 files changed, 81 insertions(+), 68 deletions(-)
diff --git a/lldb/source/Commands/CommandObjectRegister.cpp b/lldb/source/Commands/CommandObjectRegister.cpp
index 02717c82262777..792aebce5067cc 100644
--- a/lldb/source/Commands/CommandObjectRegister.cpp
+++ b/lldb/source/Commands/CommandObjectRegister.cpp
@@ -65,48 +65,41 @@ FindRegisterWithExpressionPath(RegisterContext ®_ctx, llvm::StringRef name) {
return {nullptr, {}};
}
-static ValueObjectSP
-GetRegisterValueForExpressionPath(ValueObjectSP value,
- llvm::StringRef expression_path) {
- while (!expression_path.empty()) {
- if (expression_path.consume_front(".")) {
- size_t separator = expression_path.find_first_of(".[");
- llvm::StringRef member = separator == llvm::StringRef::npos
- ? expression_path
- : expression_path.take_front(separator);
- if (member.empty())
- return {};
- value = value->GetChildMemberWithName(member);
- expression_path = separator == llvm::StringRef::npos
- ? llvm::StringRef()
- : expression_path.drop_front(separator);
- } else if (expression_path.consume_front("[")) {
- size_t closing_bracket = expression_path.find(']');
- if (closing_bracket == llvm::StringRef::npos)
- return {};
-
- uint32_t index = 0;
- if (expression_path.take_front(closing_bracket).getAsInteger(10, index))
- return {};
-
- llvm::Expected<uint32_t> num_children = value->GetNumChildren();
- if (!num_children) {
- llvm::consumeError(num_children.takeError());
- return {};
- }
- if (index >= *num_children)
- return {};
+static bool HasValidRegisterIndexes(ValueObjectSP value,
+ llvm::StringRef expression_path) {
+ size_t search_from = 0;
+ while (true) {
+ size_t open_bracket = expression_path.find('[', search_from);
+ if (open_bracket == llvm::StringRef::npos)
+ return true;
+
+ size_t close_bracket = expression_path.find(']', open_bracket + 1);
+ if (close_bracket == llvm::StringRef::npos)
+ return false;
- value = value->GetChildAtIndex(index);
- expression_path = expression_path.drop_front(closing_bracket + 1);
- } else {
- return {};
+ uint32_t index;
+ if (expression_path.slice(open_bracket + 1, close_bracket)
+ .getAsInteger(0, index))
+ return false;
+
+ llvm::StringRef parent_path = expression_path.take_front(open_bracket);
+ ValueObjectSP parent = parent_path.empty()
+ ? value
+ : value->GetValueForExpressionPath(parent_path);
+ if (!parent)
+ return false;
+
+ llvm::Expected<uint32_t> num_children = parent->GetNumChildren();
+ if (!num_children) {
+ llvm::consumeError(num_children.takeError());
+ return false;
}
+ if (index >= *num_children)
+ return false;
- if (!value)
- return {};
+ search_from = close_bracket + 1;
}
- return value;
+ return true;
}
static size_t ComputeLongestRegisterName(RegisterContext *reg_ctx,
@@ -248,9 +241,19 @@ class CommandObjectRegisterRead : public CommandObjectParsed {
lldb::RegisterContextSP reg_ctx_sp = frame->GetRegisterContextSP();
ValueObjectSP register_value =
ValueObjectRegister::Create(frame, reg_ctx_sp, ®_info);
- ValueObjectSP child =
- GetRegisterValueForExpressionPath(register_value, expression_path);
- if (!child) {
+ ValueObject::ExpressionPathScanEndReason reason =
+ ValueObject::eExpressionPathScanEndReasonUnknown;
+ ValueObject::ExpressionPathEndResultType result_type =
+ ValueObject::eExpressionPathEndResultTypeInvalid;
+ ValueObjectSP child;
+ // Generic expression paths permit out-of-bounds synthetic array members,
+ // but register paths must stay within the bytes supplied by the target.
+ if (HasValidRegisterIndexes(register_value, expression_path))
+ child = register_value->GetValueForExpressionPath(expression_path,
+ &reason, &result_type);
+ if (!child ||
+ reason != ValueObject::eExpressionPathScanEndReasonEndOfString ||
+ result_type != ValueObject::eExpressionPathEndResultTypePlain) {
llvm::StringRef error_path = expression_path;
error_path.consume_front(".");
result.AppendErrorWithFormat("No field path '%s' in register '%s'",
diff --git a/lldb/source/DataFormatters/FormatManager.cpp b/lldb/source/DataFormatters/FormatManager.cpp
index e9f0e51e34149d..f1485d1e8a4075 100644
--- a/lldb/source/DataFormatters/FormatManager.cpp
+++ b/lldb/source/DataFormatters/FormatManager.cpp
@@ -763,6 +763,14 @@ void FormatManager::LoadSystemFormatters() {
sys_category_sp->AddTypeSummary("OSType", eFormatterMatchExact,
ostype_summary);
+ TypeSummaryImpl::Flags register_union_flags;
+ register_union_flags.SetShowMembersOneLiner(true);
+ // One rule covers all generated register union types without accumulating a
+ // formatter entry for every target description LLDB reads.
+ AddStringSummary(sys_category_sp, "",
+ R"(^__lldb_register_union_[0-9]+_[0-9]+$)",
+ register_union_flags, true);
+
TypeFormatImpl::Flags fourchar_flags;
fourchar_flags.SetCascades(true).SetSkipPointers(true).SetSkipReferences(
true);
diff --git a/lldb/source/Plugins/RegisterTypeBuilder/CMakeLists.txt b/lldb/source/Plugins/RegisterTypeBuilder/CMakeLists.txt
index c70e61ea0aa225..7411b24c86cbdb 100644
--- a/lldb/source/Plugins/RegisterTypeBuilder/CMakeLists.txt
+++ b/lldb/source/Plugins/RegisterTypeBuilder/CMakeLists.txt
@@ -7,6 +7,5 @@ add_lldb_library(lldbPluginRegisterTypeBuilderClang PLUGIN
Support
LINK_LIBS
lldbCore
- lldbDataFormatters
lldbTarget
)
diff --git a/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.cpp b/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.cpp
index 07464d552a318d..33eeb7f16850df 100644
--- a/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.cpp
+++ b/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.cpp
@@ -10,8 +10,6 @@
#include "RegisterTypeBuilderClang.h"
#include "lldb/Core/PluginManager.h"
-#include "lldb/DataFormatters/DataVisualization.h"
-#include "lldb/DataFormatters/TypeSummary.h"
#include "lldb/Utility/RegisterType.h"
#include "lldb/lldb-enumerations.h"
#include "llvm/ADT/bit.h"
@@ -241,16 +239,6 @@ RegisterTypeBuilderClang::BuildUnionType(const RegisterTypeUnion *union_type,
expected_byte_size)
return {};
- TypeSummaryImpl::Flags summary_flags;
- summary_flags.SetShowMembersOneLiner(true);
- auto summary_sp = std::make_shared<StringSummaryFormat>(summary_flags, "");
- lldb::TypeCategoryImplSP category_sp;
- DataVisualization::Categories::GetCategory(ConstString("default"),
- category_sp);
- if (category_sp)
- category_sp->AddTypeSummary(type_name, lldb::eFormatterMatchExact,
- summary_sp);
-
m_type_cache.try_emplace(
std::make_pair(union_type->GetUID(), expected_byte_size), compiler_type);
return compiler_type;
diff --git a/lldb/test/API/functionalities/gdb_remote_client/TestXMLRegisterUnion.py b/lldb/test/API/functionalities/gdb_remote_client/TestXMLRegisterUnion.py
index 19826ba8ef2912..be5c15b06c7a4d 100644
--- a/lldb/test/API/functionalities/gdb_remote_client/TestXMLRegisterUnion.py
+++ b/lldb/test/API/functionalities/gdb_remote_client/TestXMLRegisterUnion.py
@@ -496,15 +496,29 @@ def test_union_cli_summary_and_member_paths(self):
"0000c03fffffffff" + "0000c03f000020400000604000009040" * 2 + "00" * 8,
)
+ union = process.GetThreadAtIndex(0).GetFrameAtIndex(0).FindRegister("u0")
+ self.assertEqual(
+ union.GetSummary(),
+ "(f32 = 1.5, f64 = NaN, u64 = 18446744070484131840)",
+ )
+
self.expect(
"register read u0",
- patterns=[
- r"u0 = 0xffffffff3fc00000\r?\n"
- r" = \(f32 = 1\.5, f64 = NaN, "
- r"u64 = 18446744070484131840\)"
+ substrs=[
+ "u0 = 0xffffffff3fc00000",
+ " = (f32 = 1.5, f64 = NaN, u64 = 18446744070484131840)",
],
)
self.expect("register read u0.f32", substrs=["u0.f32 = 1.5"])
+ self.expect(
+ "register read u0.u64 --format X",
+ substrs=["u0.u64 = 0xFFFFFFFF3FC00000"],
+ )
+ self.expect(
+ "register read u0 --format X",
+ substrs=["u0 = 0xFFFFFFFF3FC00000"],
+ )
+ self.expect("register read u0 --format X", matching=False, substrs=["f32 ="])
self.expect("register read -A alt_u0.f32", substrs=["alt_u0.f32 = 1.5"])
self.expect("register read $u0.f64", substrs=["u0.f64 = NaN"])
self.expect("register read u1.floats[2]", substrs=["u1.floats[2] = 3.5"])
@@ -513,12 +527,6 @@ def test_union_cli_summary_and_member_paths(self):
substrs=["n0.view.floats[3] = 4.5"],
)
- union = process.GetThreadAtIndex(0).GetFrameAtIndex(0).FindRegister("u0")
- self.assertEqual(
- union.GetSummary(),
- "(f32 = 1.5, f64 = NaN, u64 = 18446744070484131840)",
- )
-
@skipIfXmlSupportMissing
@skipIfRemote
def test_exact_dotted_register_name_takes_precedence(self):
@@ -557,6 +565,12 @@ def test_invalid_union_member_paths(self):
self.expect("register read u0.missing", error=True, substrs=["No field path"])
self.expect("register read u0.", error=True, substrs=["No field path"])
self.expect("register read u0..f32", error=True, substrs=["No field path"])
+ for path in ["u0[0]", "v0[", "v0[0", "v0[]", "v0[x]", "v0[0]junk"]:
+ self.expect(
+ "register read " + path,
+ error=True,
+ substrs=["No field path"],
+ )
self.expect(
"register read v0[9].f32",
error=True,
@@ -596,8 +610,8 @@ def test_big_endian_union_cli(self):
self.expect(
"register read u0",
- patterns=[
- r"u0 = 0x3fc0000040200000\r?\n"
- r" = \(scalar = 1\.5, lanes = \(1\.5, 2\.5\)\)"
+ substrs=[
+ "u0 = 0x3fc0000040200000",
+ " = (scalar = 1.5, lanes = (1.5, 2.5))",
],
)
diff --git a/lldb/unittests/Target/RegisterTypeBuilderClangTest.cpp b/lldb/unittests/Target/RegisterTypeBuilderClangTest.cpp
index 88fa9f97152731..9bec2179bba446 100644
--- a/lldb/unittests/Target/RegisterTypeBuilderClangTest.cpp
+++ b/lldb/unittests/Target/RegisterTypeBuilderClangTest.cpp
@@ -364,6 +364,7 @@ TEST_F(RegisterTypeBuilderClangTest, BuildsPackedUnion) {
EXPECT_EQ(type.GetTypeClass(), eTypeClassUnion);
EXPECT_EQ(llvm::expectedToOptional(type.GetByteSize(nullptr)), 8u);
EXPECT_EQ(type.GetNumFields(), 2u);
+ EXPECT_EQ(type, builder.GetRegisterType(MakeRegisterInfo(union_type, 8)));
for (size_t index = 0; index < 2; ++index) {
std::string name;
uint64_t bit_offset = UINT64_MAX;
@@ -444,7 +445,7 @@ TEST_F(RegisterTypeBuilderClangTest, BuildsVectorOfUnions) {
ASSERT_TRUE(type);
EXPECT_EQ(llvm::expectedToOptional(type.GetByteSize(nullptr)), 16u);
- EXPECT_NE(type.GetTypeInfo() & eTypeIsArray, 0u);
+ EXPECT_TRUE(type.IsArrayType());
EXPECT_EQ(llvm::expectedToOptional(type.GetNumChildren(true, nullptr)), 4u);
}
>From 7577358a6d3695e0ad90a535c31e5c8a74a6ac5b Mon Sep 17 00:00:00 2001
From: Bar Soloveychik <barsolo at fb.com>
Date: Wed, 23 Sep 2026 11:33:55 -0700
Subject: [PATCH 03/12] Fixing greg's comments
---
lldb/include/lldb/Utility/RegisterType.h | 16 +++++-
.../source/Commands/CommandObjectRegister.cpp | 52 ++++++++++---------
lldb/source/Core/DumpRegisterValue.cpp | 3 +-
lldb/source/Core/Value.cpp | 2 +-
lldb/source/DataFormatters/FormatManager.cpp | 5 +-
.../RegisterTypeBuilderClang.cpp | 2 +
lldb/source/Utility/RegisterType.cpp | 8 ++-
.../ValueObject/ValueObjectRegister.cpp | 6 +--
.../gdb_remote_client/TestXMLRegisterUnion.py | 44 ++++++++++------
lldb/unittests/Utility/RegisterTypeTest.cpp | 12 +++++
10 files changed, 98 insertions(+), 52 deletions(-)
diff --git a/lldb/include/lldb/Utility/RegisterType.h b/lldb/include/lldb/Utility/RegisterType.h
index ae77da554dec38..3d271befcdf2d0 100644
--- a/lldb/include/lldb/Utility/RegisterType.h
+++ b/lldb/include/lldb/Utility/RegisterType.h
@@ -81,6 +81,18 @@ class RegisterType {
std::vector<const RegisterType *> m_dependencies;
};
+/// A register type whose value is interpreted as a structured layout.
+class RegisterTypeComposite : public RegisterType {
+public:
+ static bool classof(const RegisterType *type) {
+ return type->getKind() == eRegisterTypeKindVector ||
+ type->getKind() == eRegisterTypeKindUnion;
+ }
+
+protected:
+ RegisterTypeComposite(RegisterTypeKind kind, std::string id);
+};
+
/// A predefined GDB target-description type. Builtin types are referenced by
/// name and are not emitted as XML definitions.
class RegisterTypeBuiltin : public RegisterType {
@@ -109,7 +121,7 @@ class RegisterTypeBuiltin : public RegisterType {
};
/// A GDB target-description vector type. The element type must outlive it.
-class RegisterTypeVector : public RegisterType {
+class RegisterTypeVector : public RegisterTypeComposite {
public:
RegisterTypeVector(std::string id, const RegisterType *element_type,
uint32_t count);
@@ -135,7 +147,7 @@ class RegisterTypeVector : public RegisterType {
/// A GDB target-description union type. Each field is an alternative view of
/// the same register data. Referenced field types must outlive the union.
-class RegisterTypeUnion : public RegisterType {
+class RegisterTypeUnion : public RegisterTypeComposite {
public:
class Field {
public:
diff --git a/lldb/source/Commands/CommandObjectRegister.cpp b/lldb/source/Commands/CommandObjectRegister.cpp
index 792aebce5067cc..9f42e873eaca23 100644
--- a/lldb/source/Commands/CommandObjectRegister.cpp
+++ b/lldb/source/Commands/CommandObjectRegister.cpp
@@ -47,6 +47,8 @@ static size_t GetNameSize(const RegisterInfo *reg_info, bool use_primary_name) {
return reg_name ? strlen(reg_name) : 0;
}
+// Return the exact register and an empty path, the longest register-name prefix
+// and its remaining path, or {nullptr, {}} when no register matches.
static std::pair<const RegisterInfo *, llvm::StringRef>
FindRegisterWithExpressionPath(RegisterContext ®_ctx, llvm::StringRef name) {
if (const RegisterInfo *reg_info = reg_ctx.GetRegisterInfoByName(name))
@@ -65,41 +67,54 @@ FindRegisterWithExpressionPath(RegisterContext ®_ctx, llvm::StringRef name) {
return {nullptr, {}};
}
-static bool HasValidRegisterIndexes(ValueObjectSP value,
- llvm::StringRef expression_path) {
+static ValueObjectSP
+ResolveRegisterExpressionPath(ValueObjectSP value,
+ llvm::StringRef expression_path) {
+ // Generic expression paths permit out-of-bounds synthetic array members,
+ // but register paths must stay within the bytes supplied by the target.
size_t search_from = 0;
while (true) {
size_t open_bracket = expression_path.find('[', search_from);
if (open_bracket == llvm::StringRef::npos)
- return true;
+ break;
size_t close_bracket = expression_path.find(']', open_bracket + 1);
if (close_bracket == llvm::StringRef::npos)
- return false;
+ return {};
uint32_t index;
if (expression_path.slice(open_bracket + 1, close_bracket)
.getAsInteger(0, index))
- return false;
+ return {};
llvm::StringRef parent_path = expression_path.take_front(open_bracket);
ValueObjectSP parent = parent_path.empty()
? value
: value->GetValueForExpressionPath(parent_path);
if (!parent)
- return false;
+ return {};
llvm::Expected<uint32_t> num_children = parent->GetNumChildren();
if (!num_children) {
llvm::consumeError(num_children.takeError());
- return false;
+ return {};
}
if (index >= *num_children)
- return false;
+ return {};
search_from = close_bracket + 1;
}
- return true;
+
+ ValueObject::ExpressionPathScanEndReason reason =
+ ValueObject::eExpressionPathScanEndReasonUnknown;
+ ValueObject::ExpressionPathEndResultType result_type =
+ ValueObject::eExpressionPathEndResultTypeInvalid;
+ ValueObjectSP child =
+ value->GetValueForExpressionPath(expression_path, &reason, &result_type);
+ if (reason != ValueObject::eExpressionPathScanEndReasonEndOfString ||
+ result_type != ValueObject::eExpressionPathEndResultTypePlain)
+ return {};
+ return child;
}
static size_t ComputeLongestRegisterName(RegisterContext *reg_ctx,
@@ -228,8 +243,7 @@ class CommandObjectRegisterRead : public CommandObjectParsed {
llvm::StringRef expression_path,
size_t reg_name_right_align_at,
CommandReturnObject &result) {
- if (!llvm::isa_and_present<RegisterTypeUnion, RegisterTypeVector>(
- reg_info.register_type)) {
+ if (!llvm::isa_and_present<RegisterTypeComposite>(reg_info.register_type)) {
result.AppendErrorWithFormat(
"Register '%s' does not have a structured type", reg_info.name);
return false;
@@ -241,19 +255,9 @@ class CommandObjectRegisterRead : public CommandObjectParsed {
lldb::RegisterContextSP reg_ctx_sp = frame->GetRegisterContextSP();
ValueObjectSP register_value =
ValueObjectRegister::Create(frame, reg_ctx_sp, ®_info);
- ValueObject::ExpressionPathScanEndReason reason =
- ValueObject::eExpressionPathScanEndReasonUnknown;
- ValueObject::ExpressionPathEndResultType result_type =
- ValueObject::eExpressionPathEndResultTypeInvalid;
- ValueObjectSP child;
- // Generic expression paths permit out-of-bounds synthetic array members,
- // but register paths must stay within the bytes supplied by the target.
- if (HasValidRegisterIndexes(register_value, expression_path))
- child = register_value->GetValueForExpressionPath(expression_path,
- &reason, &result_type);
- if (!child ||
- reason != ValueObject::eExpressionPathScanEndReasonEndOfString ||
- result_type != ValueObject::eExpressionPathEndResultTypePlain) {
+ ValueObjectSP child =
+ ResolveRegisterExpressionPath(register_value, expression_path);
+ if (!child) {
llvm::StringRef error_path = expression_path;
error_path.consume_front(".");
result.AppendErrorWithFormat("No field path '%s' in register '%s'",
diff --git a/lldb/source/Core/DumpRegisterValue.cpp b/lldb/source/Core/DumpRegisterValue.cpp
index 84b1e03dd4576c..9c4b679b70970e 100644
--- a/lldb/source/Core/DumpRegisterValue.cpp
+++ b/lldb/source/Core/DumpRegisterValue.cpp
@@ -143,8 +143,7 @@ void lldb_private::DumpRegisterValue(const RegisterValue ®_val, Stream &s,
const RegisterTypeFlags *flags_type =
llvm::dyn_cast_if_present<RegisterTypeFlags>(reg_info.register_type);
if (!flags_type &&
- !llvm::isa_and_present<RegisterTypeVector, RegisterTypeUnion>(
- reg_info.register_type))
+ !llvm::isa_and_present<RegisterTypeComposite>(reg_info.register_type))
return;
if (flags_type && reg_info.byte_size != 4 && reg_info.byte_size != 8)
return;
diff --git a/lldb/source/Core/Value.cpp b/lldb/source/Core/Value.cpp
index 11d313ccd9e9b3..eec39e183446fa 100644
--- a/lldb/source/Core/Value.cpp
+++ b/lldb/source/Core/Value.cpp
@@ -497,7 +497,7 @@ Status Value::GetValueAsData(ExecutionContext *exe_ctx, DataExtractor &data,
// ValueObjectRegister stores structured register types in target byte
// order and all other register buffers in host byte order.
if (m_context_type == ContextType::RegisterInfo &&
- !llvm::isa_and_present<RegisterTypeVector, RegisterTypeUnion>(
+ !llvm::isa_and_present<RegisterTypeComposite>(
GetRegisterInfo()->register_type))
byte_order = endian::InlHostByteOrder();
data.SetByteOrder(byte_order);
diff --git a/lldb/source/DataFormatters/FormatManager.cpp b/lldb/source/DataFormatters/FormatManager.cpp
index f1485d1e8a4075..237ca0347acb3a 100644
--- a/lldb/source/DataFormatters/FormatManager.cpp
+++ b/lldb/source/DataFormatters/FormatManager.cpp
@@ -765,8 +765,9 @@ void FormatManager::LoadSystemFormatters() {
TypeSummaryImpl::Flags register_union_flags;
register_union_flags.SetShowMembersOneLiner(true);
- // One rule covers all generated register union types without accumulating a
- // formatter entry for every target description LLDB reads.
+ // RegisterTypeBuilderClang gives synthesized union types this prefix. One
+ // rule covers all of them without accumulating a formatter entry for every
+ // target description LLDB reads.
AddStringSummary(sys_category_sp, "",
R"(^__lldb_register_union_[0-9]+_[0-9]+$)",
register_union_flags, true);
diff --git a/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.cpp b/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.cpp
index 33eeb7f16850df..2e74c1a57ec7c9 100644
--- a/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.cpp
+++ b/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.cpp
@@ -307,6 +307,8 @@ std::optional<uint32_t> RegisterTypeBuilderClang::GetTargetByteSize(
}
case RegisterType::eRegisterTypeKindEnum:
case RegisterType::eRegisterTypeKindFlags:
+ // Enum and flags use dedicated top-level builders. They are not supported
+ // when recursively building vector or union members.
return std::nullopt;
}
}
diff --git a/lldb/source/Utility/RegisterType.cpp b/lldb/source/Utility/RegisterType.cpp
index 84131da1254083..7adcec38318016 100644
--- a/lldb/source/Utility/RegisterType.cpp
+++ b/lldb/source/Utility/RegisterType.cpp
@@ -28,6 +28,10 @@ RegisterType::RegisterType(RegisterTypeKind kind, std::string id)
: m_kind(kind), m_id(std::move(id)),
m_uid(g_next_register_type_uid.fetch_add(1, std::memory_order_relaxed)) {}
+RegisterTypeComposite::RegisterTypeComposite(RegisterTypeKind kind,
+ std::string id)
+ : RegisterType(kind, std::move(id)) {}
+
void RegisterType::ToXML(Stream &strm,
std::unordered_set<std::string> &previously_emitted,
const RegisterType *user) const {
@@ -65,7 +69,7 @@ void RegisterTypeBuiltin::ToXMLElement(Stream &, const RegisterType *) const {}
RegisterTypeVector::RegisterTypeVector(std::string id,
const RegisterType *element_type,
uint32_t count)
- : RegisterType(eRegisterTypeKindVector, std::move(id)),
+ : RegisterTypeComposite(eRegisterTypeKindVector, std::move(id)),
m_element_type(element_type), m_count(count) {
assert(m_element_type && "Vector element type cannot be null");
assert(m_count && "Vector element count cannot be zero");
@@ -117,7 +121,7 @@ RegisterTypeUnion::Field::Field(std::string name, const RegisterType *type)
}
RegisterTypeUnion::RegisterTypeUnion(std::string id, std::vector<Field> fields)
- : RegisterType(eRegisterTypeKindUnion, std::move(id)),
+ : RegisterTypeComposite(eRegisterTypeKindUnion, std::move(id)),
m_fields(std::move(fields)) {
assert(!m_fields.empty() && "Union must have at least one field");
diff --git a/lldb/source/ValueObject/ValueObjectRegister.cpp b/lldb/source/ValueObject/ValueObjectRegister.cpp
index 71be7ab993681d..497aabfb950ddd 100644
--- a/lldb/source/ValueObject/ValueObjectRegister.cpp
+++ b/lldb/source/ValueObject/ValueObjectRegister.cpp
@@ -229,8 +229,8 @@ CompilerType ValueObjectRegister::GetCompilerTypeImpl() {
if (!target)
return {};
- if (llvm::isa_and_present<RegisterTypeBuiltin, RegisterTypeVector,
- RegisterTypeUnion>(m_reg_info.register_type)) {
+ if (llvm::isa_and_present<RegisterTypeBuiltin, RegisterTypeComposite>(
+ m_reg_info.register_type)) {
m_compiler_type = target->GetRegisterType(m_reg_info);
if (m_compiler_type.IsValid())
return m_compiler_type;
@@ -285,7 +285,7 @@ bool ValueObjectRegister::UpdateValue() {
if (m_reg_ctx_sp->ReadRegister(&m_reg_info, m_reg_value)) {
Target *target = exe_ctx.GetTargetPtr();
const bool has_structured_type =
- llvm::isa_and_present<RegisterTypeVector, RegisterTypeUnion>(
+ llvm::isa_and_present<RegisterTypeComposite>(
m_reg_info.register_type);
// Scalar registers remain in host byte order, while CompilerType
// children need target-order bytes to interpret structured layouts.
diff --git a/lldb/test/API/functionalities/gdb_remote_client/TestXMLRegisterUnion.py b/lldb/test/API/functionalities/gdb_remote_client/TestXMLRegisterUnion.py
index be5c15b06c7a4d..af15fec036b7f3 100644
--- a/lldb/test/API/functionalities/gdb_remote_client/TestXMLRegisterUnion.py
+++ b/lldb/test/API/functionalities/gdb_remote_client/TestXMLRegisterUnion.py
@@ -281,12 +281,9 @@ def test_direct_union_sb_api(self):
process = self.setup_register_test(
"""\
<vector id="v4f" type="ieee_single" count="4"/>
- <vector id="v2d" type="ieee_double" count="2"/>
<union id="views">
- <field name="f32" type="ieee_single"/>
- <field name="f64" type="ieee_double"/>
- <field name="floats" type="v4f"/>
- <field name="doubles" type="v2d"/>
+ <field name="scalar" type="ieee_single"/>
+ <field name="lanes" type="v4f"/>
<field name="raw" type="uint128"/>
</union>
<reg name="u0" regnum="0" bitsize="128" type="views"/>
@@ -298,17 +295,20 @@ def test_direct_union_sb_api(self):
union = frame.FindRegister("u0")
self.assertTrue(union.IsValid())
self.assertTrue(union.GetType().IsValid())
+ self.assertRegex(
+ union.GetType().GetName(), r"^__lldb_register_union_[0-9]+_16$"
+ )
self.assertEqual(union.GetByteSize(), 16)
- self.assertEqual(union.GetNumChildren(), 5)
+ self.assertEqual(union.GetNumChildren(), 3)
self.assertEqual(
- [union.GetChildAtIndex(i).GetName() for i in range(5)],
- ["f32", "f64", "floats", "doubles", "raw"],
+ [union.GetChildAtIndex(i).GetName() for i in range(3)],
+ ["scalar", "lanes", "raw"],
)
self.assertAlmostEqual(
- union.GetChildMemberWithName("f32").GetData().float[0], 1.5
+ union.GetChildMemberWithName("scalar").GetData().float[0], 1.5
)
self.assertAlmostEqual(
- union.GetValueForExpressionPath(".floats[2]").GetData().float[0],
+ union.GetValueForExpressionPath(".lanes[2]").GetData().float[0],
3.5,
)
@@ -326,6 +326,7 @@ def test_union_smaller_than_register_sb_api(self):
)
union = process.GetThreadAtIndex(0).GetFrameAtIndex(0).FindRegister("u0")
+ self.assertRegex(union.GetType().GetName(), r"^__lldb_register_union_[0-9]+_4$")
self.assertEqual(union.GetByteSize(), 16)
self.assertEqual(union.GetType().GetByteSize(), 4)
self.assertEqual(union.GetChildMemberWithName("value").GetValueAsUnsigned(), 42)
@@ -562,14 +563,25 @@ def test_invalid_union_member_paths(self):
"00" * 32,
)
- self.expect("register read u0.missing", error=True, substrs=["No field path"])
- self.expect("register read u0.", error=True, substrs=["No field path"])
- self.expect("register read u0..f32", error=True, substrs=["No field path"])
- for path in ["u0[0]", "v0[", "v0[0", "v0[]", "v0[x]", "v0[0]junk"]:
+ invalid_paths = {
+ "u0.missing": "No field path 'missing' in register 'u0'",
+ "u0.": "No field path '' in register 'u0'",
+ "u0..f32": "No field path '.f32' in register 'u0'",
+ "u0[0]": "No field path '[0]' in register 'u0'",
+ "v0[": "No field path '[' in register 'v0'",
+ "v0[0": "No field path '[0' in register 'v0'",
+ "v0[]": "No field path '[]' in register 'v0'",
+ "v0[x]": "No field path '[x]' in register 'v0'",
+ "v0[-1]": "No field path '[-1]' in register 'v0'",
+ "v0[4294967296]": "No field path '[4294967296]' in register 'v0'",
+ "v0[0]junk": "No field path '[0]junk' in register 'v0'",
+ "v0[0].missing": "No field path '[0].missing' in register 'v0'",
+ }
+ for path, diagnostic in invalid_paths.items():
self.expect(
"register read " + path,
error=True,
- substrs=["No field path"],
+ substrs=[diagnostic],
)
self.expect(
"register read v0[9].f32",
@@ -579,7 +591,7 @@ def test_invalid_union_member_paths(self):
self.expect(
"register read pc.field",
error=True,
- substrs=["does not have a structured type"],
+ substrs=["Register 'pc' does not have a structured type"],
)
@skipIfXmlSupportMissing
diff --git a/lldb/unittests/Utility/RegisterTypeTest.cpp b/lldb/unittests/Utility/RegisterTypeTest.cpp
index 3d50e6b7404c75..10bc4151222e5d 100644
--- a/lldb/unittests/Utility/RegisterTypeTest.cpp
+++ b/lldb/unittests/Utility/RegisterTypeTest.cpp
@@ -631,6 +631,18 @@ TEST(RegisterTypeBuiltinTest, DoesNotSerialize) {
EXPECT_TRUE(strm.GetString().empty());
}
+TEST(RegisterTypeCompositeTest, TypeIdentification) {
+ RegisterTypeBuiltin builtin_type("ieee_single", eEncodingIEEE754,
+ eFormatFloat, 4);
+ RegisterTypeVector vector_type("v4f", &builtin_type, 4);
+ RegisterTypeUnion union_type(
+ "views", {RegisterTypeUnion::Field("scalar", &builtin_type)});
+
+ EXPECT_FALSE(llvm::isa<RegisterTypeComposite>(&builtin_type));
+ EXPECT_TRUE(llvm::isa<RegisterTypeComposite>(&vector_type));
+ EXPECT_TRUE(llvm::isa<RegisterTypeComposite>(&union_type));
+}
+
TEST(RegisterTypeVectorTest, ConstructionAndXML) {
RegisterTypeBuiltin element_type("ieee_single", eEncodingIEEE754,
eFormatFloat, 4);
>From 393066ab846446801c032c4e78dcdbe84ec8bbca Mon Sep 17 00:00:00 2001
From: Bar Soloveychik <barsolo at fb.com>
Date: Wed, 23 Sep 2026 11:44:26 -0700
Subject: [PATCH 04/12] [lldb][NFC] Address register union builder review
feedback
---
.../RegisterTypeBuilderClang.cpp | 17 +++++++++++------
.../RegisterTypeBuilderClang.h | 6 ++++--
2 files changed, 15 insertions(+), 8 deletions(-)
diff --git a/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.cpp b/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.cpp
index 2e74c1a57ec7c9..2f79e7d9a42d4c 100644
--- a/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.cpp
+++ b/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.cpp
@@ -155,7 +155,7 @@ RegisterTypeBuilderClang::BuildVectorType(const RegisterTypeVector *vector_type,
return *type;
std::optional<uint32_t> element_size =
- GetTargetByteSize(vector_type->GetElementType(), type_system);
+ GetExpectedByteSize(vector_type->GetElementType(), type_system);
if (!element_size || *element_size > UINT32_MAX / vector_type->GetCount() ||
*element_size * vector_type->GetCount() != expected_byte_size)
return {};
@@ -209,9 +209,11 @@ RegisterTypeBuilderClang::BuildUnionType(const RegisterTypeUnion *union_type,
std::vector<std::pair<llvm::StringRef, CompilerType>> fields;
fields.reserve(union_type->GetFields().size());
+ // XML parsing restricts union fields to builtins, vectors, and unions.
+ // BuildType recursively constructs the vector and union cases.
for (const RegisterTypeUnion::Field &field : union_type->GetFields()) {
std::optional<uint32_t> field_size =
- GetTargetByteSize(field.GetType(), type_system);
+ GetExpectedByteSize(field.GetType(), type_system);
if (!field_size || *field_size > expected_byte_size)
return {};
@@ -222,6 +224,9 @@ RegisterTypeBuilderClang::BuildUnionType(const RegisterTypeUnion *union_type,
fields.emplace_back(field.GetName(), field_type);
}
+ // XML type IDs are scoped to a feature and need not be unique or valid
+ // Clang identifiers. Keep the ID on RegisterTypeUnion for XML lookup and use
+ // the process-wide UID and instantiated size for a unique scratch AST tag.
std::string type_name = "__lldb_register_union_" +
std::to_string(union_type->GetUID()) + "_" +
std::to_string(expected_byte_size);
@@ -264,7 +269,7 @@ RegisterTypeBuilderClang::BuildType(const RegisterType *register_type,
}
}
-std::optional<uint32_t> RegisterTypeBuilderClang::GetTargetByteSize(
+std::optional<uint32_t> RegisterTypeBuilderClang::GetExpectedByteSize(
const RegisterType *register_type, lldb::TypeSystemClangSP type_system) {
if (std::optional<uint64_t> fixed_size = register_type->GetByteSize()) {
if (*fixed_size <= UINT32_MAX)
@@ -288,7 +293,7 @@ std::optional<uint32_t> RegisterTypeBuilderClang::GetTargetByteSize(
case RegisterType::eRegisterTypeKindVector: {
const auto *vector_type = llvm::cast<RegisterTypeVector>(register_type);
std::optional<uint32_t> element_size =
- GetTargetByteSize(vector_type->GetElementType(), type_system);
+ GetExpectedByteSize(vector_type->GetElementType(), type_system);
if (!element_size || *element_size > UINT32_MAX / vector_type->GetCount())
return std::nullopt;
return *element_size * vector_type->GetCount();
@@ -298,7 +303,7 @@ std::optional<uint32_t> RegisterTypeBuilderClang::GetTargetByteSize(
for (const RegisterTypeUnion::Field &field :
llvm::cast<RegisterTypeUnion>(register_type)->GetFields()) {
std::optional<uint32_t> field_size =
- GetTargetByteSize(field.GetType(), type_system);
+ GetExpectedByteSize(field.GetType(), type_system);
if (!field_size)
return std::nullopt;
byte_size = std::max(byte_size, *field_size);
@@ -346,7 +351,7 @@ RegisterTypeBuilderClang::GetRegisterType(const RegisterInfo ®_info) {
return BuildType(reg_info.register_type, reg_info.byte_size, type_system);
case RegisterType::eRegisterTypeKindUnion: {
std::optional<uint32_t> byte_size =
- GetTargetByteSize(reg_info.register_type, type_system);
+ GetExpectedByteSize(reg_info.register_type, type_system);
if (!byte_size || *byte_size > reg_info.byte_size)
return {};
return BuildType(reg_info.register_type, *byte_size, type_system);
diff --git a/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.h b/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.h
index a8c005d77252f3..128da85ad4eccc 100644
--- a/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.h
+++ b/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.h
@@ -57,9 +57,11 @@ class RegisterTypeBuilderClang : public RegisterTypeBuilder {
uint32_t expected_byte_size,
lldb::TypeSystemClangSP type_system);
+ /// Return the target-resolved byte size of \p register_type. Builders verify
+ /// the resulting Clang type has this size.
std::optional<uint32_t>
- GetTargetByteSize(const RegisterType *register_type,
- lldb::TypeSystemClangSP type_system);
+ GetExpectedByteSize(const RegisterType *register_type,
+ lldb::TypeSystemClangSP type_system);
Target &m_target;
>From a3514bc30853dd38a1b9e7e447de2eebecb93017 Mon Sep 17 00:00:00 2001
From: Bar Soloveychik <barsolo at fb.com>
Date: Fri, 25 Sep 2026 11:25:24 -0700
Subject: [PATCH 05/12] Use shared expression path resolution for registers
---
.../source/Commands/CommandObjectRegister.cpp | 237 +++++++-----------
lldb/source/DataFormatters/FormatManager.cpp | 9 -
lldb/source/Target/StackFrame.cpp | 113 +++++++--
lldb/source/ValueObject/DILEval.cpp | 51 +++-
lldb/source/ValueObject/ValueObject.cpp | 44 +++-
.../gdb_remote_client/TestXMLRegisterUnion.py | 209 +++++++++++++--
6 files changed, 465 insertions(+), 198 deletions(-)
diff --git a/lldb/source/Commands/CommandObjectRegister.cpp b/lldb/source/Commands/CommandObjectRegister.cpp
index 9f42e873eaca23..cb9b26c3507bcc 100644
--- a/lldb/source/Commands/CommandObjectRegister.cpp
+++ b/lldb/source/Commands/CommandObjectRegister.cpp
@@ -28,13 +28,15 @@
#include "lldb/Target/Thread.h"
#include "lldb/Utility/Args.h"
#include "lldb/Utility/DataExtractor.h"
-#include "lldb/Utility/RegisterType.h"
#include "lldb/Utility/RegisterValue.h"
#include "lldb/Utility/StreamString.h"
-#include "lldb/ValueObject/ValueObjectRegister.h"
+#include "lldb/ValueObject/ValueObject.h"
#include "llvm/Support/Errno.h"
#include "llvm/Support/Error.h"
+#include <string>
+#include <vector>
+
using namespace lldb;
using namespace lldb_private;
@@ -47,76 +49,6 @@ static size_t GetNameSize(const RegisterInfo *reg_info, bool use_primary_name) {
return reg_name ? strlen(reg_name) : 0;
}
-// Return the exact register and an empty path, the longest register-name prefix
-// and its remaining path, or {nullptr, {}} when no register matches.
-static std::pair<const RegisterInfo *, llvm::StringRef>
-FindRegisterWithExpressionPath(RegisterContext ®_ctx, llvm::StringRef name) {
- if (const RegisterInfo *reg_info = reg_ctx.GetRegisterInfoByName(name))
- return {reg_info, {}};
-
- size_t search_end = name.size();
- while (search_end) {
- size_t split = name.take_front(search_end).find_last_of(".[");
- if (split == llvm::StringRef::npos)
- break;
- if (const RegisterInfo *reg_info =
- reg_ctx.GetRegisterInfoByName(name.take_front(split)))
- return {reg_info, name.drop_front(split)};
- search_end = split;
- }
- return {nullptr, {}};
-}
-
-static ValueObjectSP
-ResolveRegisterExpressionPath(ValueObjectSP value,
- llvm::StringRef expression_path) {
- // Generic expression paths permit out-of-bounds synthetic array members,
- // but register paths must stay within the bytes supplied by the target.
- size_t search_from = 0;
- while (true) {
- size_t open_bracket = expression_path.find('[', search_from);
- if (open_bracket == llvm::StringRef::npos)
- break;
-
- size_t close_bracket = expression_path.find(']', open_bracket + 1);
- if (close_bracket == llvm::StringRef::npos)
- return {};
-
- uint32_t index;
- if (expression_path.slice(open_bracket + 1, close_bracket)
- .getAsInteger(0, index))
- return {};
-
- llvm::StringRef parent_path = expression_path.take_front(open_bracket);
- ValueObjectSP parent = parent_path.empty()
- ? value
- : value->GetValueForExpressionPath(parent_path);
- if (!parent)
- return {};
-
- llvm::Expected<uint32_t> num_children = parent->GetNumChildren();
- if (!num_children) {
- llvm::consumeError(num_children.takeError());
- return {};
- }
- if (index >= *num_children)
- return {};
-
- search_from = close_bracket + 1;
- }
-
- ValueObject::ExpressionPathScanEndReason reason =
- ValueObject::eExpressionPathScanEndReasonUnknown;
- ValueObject::ExpressionPathEndResultType result_type =
- ValueObject::eExpressionPathEndResultTypeInvalid;
- ValueObjectSP child =
- value->GetValueForExpressionPath(expression_path, &reason, &result_type);
- if (reason != ValueObject::eExpressionPathScanEndReasonEndOfString ||
- result_type != ValueObject::eExpressionPathEndResultTypePlain)
- return {};
- return child;
-}
-
static size_t ComputeLongestRegisterName(RegisterContext *reg_ctx,
const RegisterSet ®_set,
bool use_primary_name,
@@ -140,31 +72,6 @@ static size_t ComputeLongestRegisterName(RegisterContext *reg_ctx,
return name_right_align_at;
}
-// We expect that [command] only contains register names to be printed.
-static size_t ComputeLongestRegisterName(Args &command,
- RegisterContext *reg_ctx,
- bool use_primary_name) {
- size_t name_right_align_at = 0;
-
- // Loop through all the arguments to find the longest register name.
- for (auto &entry : command) {
- // In most LLDB commands we accept '$<register>' as well as '<register>'
- // for example '$rbx' for 'rbx'. However internally the name does not have
- // '$'.
- llvm::StringRef arg_str = entry.ref();
- arg_str.consume_front("$");
-
- const RegisterInfo *reg_info =
- FindRegisterWithExpressionPath(*reg_ctx, arg_str).first;
- if (reg_info) {
- name_right_align_at = std::max(name_right_align_at,
- GetNameSize(reg_info, use_primary_name));
- }
- }
-
- return name_right_align_at;
-}
-
class CommandObjectRegisterRead : public CommandObjectParsed {
public:
CommandObjectRegisterRead(CommandInterpreter &interpreter)
@@ -238,34 +145,12 @@ class CommandObjectRegisterRead : public CommandObjectParsed {
return true;
}
- bool DumpRegisterField(const ExecutionContext &exe_ctx, Stream &strm,
- const RegisterInfo ®_info,
+ bool DumpRegisterField(Stream &strm, const RegisterInfo ®_info,
+ const ValueObjectSP &value_sp,
llvm::StringRef expression_path,
size_t reg_name_right_align_at,
CommandReturnObject &result) {
- if (!llvm::isa_and_present<RegisterTypeComposite>(reg_info.register_type)) {
- result.AppendErrorWithFormat(
- "Register '%s' does not have a structured type", reg_info.name);
- return false;
- }
-
- StackFrame *frame = exe_ctx.GetFramePtr();
- if (!frame)
- return false;
- lldb::RegisterContextSP reg_ctx_sp = frame->GetRegisterContextSP();
- ValueObjectSP register_value =
- ValueObjectRegister::Create(frame, reg_ctx_sp, ®_info);
- ValueObjectSP child =
- ResolveRegisterExpressionPath(register_value, expression_path);
- if (!child) {
- llvm::StringRef error_path = expression_path;
- error_path.consume_front(".");
- result.AppendErrorWithFormat("No field path '%s' in register '%s'",
- error_path.str().c_str(), reg_info.name);
- return false;
- }
-
- DumpValueObjectOptions options(*child);
+ DumpValueObjectOptions options(*value_sp);
options.SetHideRootType(true)
.SetHideRootName(true)
.SetHideName(true)
@@ -274,7 +159,7 @@ class CommandObjectRegisterRead : public CommandObjectParsed {
options.SetFormat(m_format_options.GetFormatValue().GetCurrentValue());
StreamString value_stream;
- if (llvm::Error error = child->Dump(value_stream, options)) {
+ if (llvm::Error error = value_sp->Dump(value_stream, options)) {
result.AppendError(toString(std::move(error)));
return false;
}
@@ -382,35 +267,95 @@ class CommandObjectRegisterRead : public CommandObjectParsed {
result.AppendError("the --set <set> option can't be used when "
"registers names are supplied as arguments\n");
} else {
- int reg_name_right_align_at = ComputeLongestRegisterName(
- command, reg_ctx, !m_command_options.alternate_name);
+ struct RegisterArgument {
+ const RegisterInfo *reg_info = nullptr;
+ ValueObjectSP value_sp;
+ std::string expression_path;
+ std::string error;
+ };
+
+ StackFrame *frame = m_exe_ctx.GetFramePtr();
+ if (!frame) {
+ result.AppendError("no frame");
+ return;
+ }
+ std::vector<RegisterArgument> register_arguments;
+ register_arguments.reserve(command.GetArgumentCount());
+ size_t reg_name_right_align_at = 0;
+
+ for (auto &entry : command) {
+ llvm::StringRef expression = entry.ref();
+ expression.consume_front("$");
+
+ RegisterArgument argument;
+ argument.reg_info = reg_ctx->GetRegisterInfoByName(expression);
+ if (!argument.reg_info) {
+ if (expression.find_first_of(".[") == llvm::StringRef::npos &&
+ !expression.contains("->")) {
+ argument.error =
+ "Invalid register name '" + expression.str() + "'";
+ } else {
+ std::string variable_path = "$" + expression.str();
+ VariableSP variable_sp;
+ Status error;
+ argument.value_sp = frame->GetValueForVariableExpressionPath(
+ variable_path, eNoDynamicValues,
+ StackFrame::eExpressionPathOptionCheckPtrVsMember,
+ variable_sp, error, eDILModeLegacy);
+ if (error.Fail() || !argument.value_sp) {
+ argument.error = error.AsCString("invalid register path");
+ } else if (ValueObject *root = argument.value_sp->GetRoot()) {
+ argument.reg_info = reg_ctx->GetRegisterInfoByName(
+ root->GetName().GetStringRef());
+ }
+ }
+ }
+
+ if (!argument.reg_info) {
+ if (argument.error.empty())
+ argument.error =
+ "Invalid register name '" + expression.str() + "'";
+ } else {
+ if (argument.value_sp) {
+ StreamString expression_stream;
+ argument.value_sp->GetExpressionPath(expression_stream);
+ llvm::StringRef expression_path = expression_stream.GetString();
+ if (!expression_path.consume_front("$") ||
+ !expression_path.consume_front(argument.reg_info->name)) {
+ argument.error = "unable to reconstruct register path '" +
+ expression.str() + "'";
+ } else {
+ argument.expression_path = expression_path.str();
+ }
+ }
+ reg_name_right_align_at =
+ std::max(reg_name_right_align_at,
+ GetNameSize(argument.reg_info,
+ !m_command_options.alternate_name));
+ }
+ register_arguments.push_back(std::move(argument));
+ }
+
// Extra ident to be consistent with register sets dumping.
strm.IndentMore();
- for (auto &entry : command) {
- // in most LLDB commands we accept $rbx as the name for register RBX
- // - and here we would reject it and non-existant. we should be more
- // consistent towards the user and allow them to say reg read $rbx -
- // internally, however, we should be strict and not allow ourselves
- // to call our registers $rbx in our own API
- auto arg_str = entry.ref();
- arg_str.consume_front("$");
-
- auto [reg_info, expression_path] =
- FindRegisterWithExpressionPath(*reg_ctx, arg_str);
-
- if (reg_info && expression_path.empty()) {
+ for (const RegisterArgument &argument : register_arguments) {
+ if (!argument.error.empty()) {
+ result.AppendError(argument.error);
+ continue;
+ }
+
+ if (!argument.value_sp) {
// If they have asked for a specific format don't obscure that by
// printing a structured value afterwards.
bool print_type = !m_format_options.GetFormatValue().OptionWasSet();
- if (!DumpRegister(m_exe_ctx, strm, *reg_ctx, *reg_info, print_type,
- reg_name_right_align_at))
- strm.Printf("%-12s = error: unavailable\n", reg_info->name);
- } else if (reg_info) {
- DumpRegisterField(m_exe_ctx, strm, *reg_info, expression_path,
- reg_name_right_align_at, result);
+ if (!DumpRegister(m_exe_ctx, strm, *reg_ctx, *argument.reg_info,
+ print_type, reg_name_right_align_at))
+ strm.Printf("%-12s = error: unavailable\n",
+ argument.reg_info->name);
} else {
- result.AppendErrorWithFormat("Invalid register name '%s'",
- arg_str.str().c_str());
+ DumpRegisterField(strm, *argument.reg_info, argument.value_sp,
+ argument.expression_path, reg_name_right_align_at,
+ result);
}
}
strm.IndentLess();
diff --git a/lldb/source/DataFormatters/FormatManager.cpp b/lldb/source/DataFormatters/FormatManager.cpp
index 237ca0347acb3a..e9f0e51e34149d 100644
--- a/lldb/source/DataFormatters/FormatManager.cpp
+++ b/lldb/source/DataFormatters/FormatManager.cpp
@@ -763,15 +763,6 @@ void FormatManager::LoadSystemFormatters() {
sys_category_sp->AddTypeSummary("OSType", eFormatterMatchExact,
ostype_summary);
- TypeSummaryImpl::Flags register_union_flags;
- register_union_flags.SetShowMembersOneLiner(true);
- // RegisterTypeBuilderClang gives synthesized union types this prefix. One
- // rule covers all of them without accumulating a formatter entry for every
- // target description LLDB reads.
- AddStringSummary(sys_category_sp, "",
- R"(^__lldb_register_union_[0-9]+_[0-9]+$)",
- register_union_flags, true);
-
TypeFormatImpl::Flags fourchar_flags;
fourchar_flags.SetCascades(true).SetSkipPointers(true).SetSkipReferences(
true);
diff --git a/lldb/source/Target/StackFrame.cpp b/lldb/source/Target/StackFrame.cpp
index 7289b44ae73c85..1badffead07072 100644
--- a/lldb/source/Target/StackFrame.cpp
+++ b/lldb/source/Target/StackFrame.cpp
@@ -36,6 +36,7 @@
#include "lldb/ValueObject/DILParser.h"
#include "lldb/ValueObject/ValueObjectConstResult.h"
#include "lldb/ValueObject/ValueObjectMemory.h"
+#include "lldb/ValueObject/ValueObjectRegister.h"
#include "lldb/ValueObject/ValueObjectVariable.h"
#include "lldb/lldb-enumerations.h"
@@ -48,6 +49,33 @@ using namespace lldb_private;
// LLVM RTTI support.
char StackFrame::ID;
+static std::pair<const RegisterInfo *, llvm::StringRef>
+FindRegisterInExpressionPath(RegisterContext ®_ctx,
+ llvm::StringRef expression_path) {
+ // Register names may contain periods. Prefer an exact name, then the
+ // longest name that leaves a member or index expression as the suffix.
+ if (const RegisterInfo *reg_info =
+ reg_ctx.GetRegisterInfoByName(expression_path))
+ return {reg_info, {}};
+
+ size_t search_end = expression_path.size();
+ while (search_end) {
+ llvm::StringRef prefix = expression_path.take_front(search_end);
+ size_t split = prefix.find_last_of(".[");
+ size_t arrow = prefix.rfind("->");
+ if (arrow != llvm::StringRef::npos &&
+ (split == llvm::StringRef::npos || arrow > split))
+ split = arrow;
+ if (split == llvm::StringRef::npos)
+ break;
+ if (const RegisterInfo *reg_info =
+ reg_ctx.GetRegisterInfoByName(expression_path.take_front(split)))
+ return {reg_info, expression_path.drop_front(split)};
+ search_end = split;
+ }
+ return {nullptr, {}};
+}
+
// The first bits in the flags are reserved for the SymbolContext::Scope bits
// so we know if we have tried to look up information in our internal symbol
// context (m_sc) already.
@@ -616,14 +644,6 @@ ValueObjectSP StackFrame::LegacyGetValueForVariableExpressionPath(
bool deref = false;
bool address_of = false;
ValueObjectSP valobj_sp;
- const bool get_file_globals = true;
- // When looking up a variable for an expression, we need only consider the
- // variables that are in scope.
- VariableListSP var_list_sp(GetInScopeVariableList(get_file_globals));
- VariableList *variable_list = var_list_sp.get();
-
- if (!variable_list)
- return ValueObjectSP();
// If first character is a '*', then show pointer contents
std::string var_expr_storage;
@@ -635,20 +655,54 @@ ValueObjectSP StackFrame::LegacyGetValueForVariableExpressionPath(
var_expr = var_expr.drop_front(); // Skip the '&'
}
+ bool is_register_path = var_expr.consume_front("$");
+ if (is_register_path)
+ var_sp.reset();
size_t separator_idx = var_expr.find_first_of(".-[=+~|&^%#@!/?,<>{}");
StreamString var_expr_path_strm;
ConstString name_const_string(var_expr.substr(0, separator_idx));
+ VariableListSP var_list_sp;
+ VariableList *variable_list = nullptr;
+
+ if (is_register_path) {
+ RegisterContextSP reg_ctx_sp = GetRegisterContextSP();
+ auto [reg_info, register_path] =
+ reg_ctx_sp
+ ? FindRegisterInExpressionPath(*reg_ctx_sp, var_expr)
+ : std::pair<const RegisterInfo *, llvm::StringRef>{nullptr, {}};
+ if (!reg_info) {
+ error = Status::FromErrorStringWithFormat(
+ "no register named '%s' found in this frame",
+ name_const_string.GetCString());
+ return ValueObjectSP();
+ }
+ valobj_sp = ValueObjectRegister::Create(this, reg_ctx_sp, reg_info);
+ if (!valobj_sp) {
+ error = Status::FromErrorStringWithFormat(
+ "unable to read register '%s' in this frame", reg_info->name);
+ return {};
+ }
+ name_const_string = ConstString(reg_info->name);
+ var_expr = register_path;
+ } else {
+ const bool get_file_globals = true;
+ // When looking up a variable for an expression, we need only consider the
+ // variables that are in scope.
+ var_list_sp = GetInScopeVariableList(get_file_globals);
+ variable_list = var_list_sp.get();
+ if (!variable_list)
+ return ValueObjectSP();
- var_sp = variable_list->FindVariable(name_const_string, false);
+ var_sp = variable_list->FindVariable(name_const_string, false);
+ if (var_sp)
+ var_expr = var_expr.drop_front(name_const_string.GetLength());
+ }
bool synthetically_added_instance_object = false;
- if (var_sp) {
- var_expr = var_expr.drop_front(name_const_string.GetLength());
- }
-
- if (!var_sp && (options & eExpressionPathOptionsAllowDirectIVarAccess)) {
+ if (!valobj_sp && !var_sp &&
+ (options & eExpressionPathOptionsAllowDirectIVarAccess)) {
// Check for direct ivars access which helps us with implicit access to
// ivars using "this" or "self".
GetSymbolContext(eSymbolContextFunction | eSymbolContextBlock);
@@ -671,7 +725,8 @@ ValueObjectSP StackFrame::LegacyGetValueForVariableExpressionPath(
}
}
- if (!var_sp && (options & eExpressionPathOptionsInspectAnonymousUnions)) {
+ if (!valobj_sp && !var_sp &&
+ (options & eExpressionPathOptionsInspectAnonymousUnions)) {
// Check if any anonymous unions are there which contain a variable with
// the name we need
for (const VariableSP &variable_sp : *variable_list) {
@@ -863,6 +918,18 @@ ValueObjectSP StackFrame::LegacyGetValueForVariableExpressionPath(
if (index_expr.empty()) {
// The entire index expression was a single integer.
+ if (valobj_sp->GetValueType() == eValueTypeRegister &&
+ !valobj_sp->IsPointerType() &&
+ (child_index < 0 ||
+ static_cast<unsigned long>(child_index) > UINT32_MAX)) {
+ valobj_sp->GetExpressionPath(var_expr_path_strm);
+ error = Status::FromErrorStringWithFormat(
+ "array index %ld is not valid for \"(%s) %s\"", child_index,
+ valobj_sp->GetTypeName().AsCString("<invalid type>"),
+ var_expr_path_strm.GetData());
+ return ValueObjectSP();
+ }
+
if (valobj_sp->GetCompilerType().IsPointerToScalarType() && deref) {
// what we have is *ptr[low]. the most similar C++ syntax is to deref
// ptr and extract bit low out of it. reading array item low would be
@@ -963,7 +1030,8 @@ ValueObjectSP StackFrame::LegacyGetValueForVariableExpressionPath(
}
}
} else if (valobj_sp->GetCompilerType().IsArrayType(
- nullptr, nullptr, &is_incomplete_array)) {
+ nullptr, nullptr, &is_incomplete_array) ||
+ valobj_sp->GetCompilerType().IsVectorType()) {
// Pass false to dynamic_value here so we can tell the difference
// between no dynamic value and no member of this type...
child_valobj_sp = valobj_sp->GetChildAtIndex(child_index);
@@ -1062,6 +1130,19 @@ ValueObjectSP StackFrame::LegacyGetValueForVariableExpressionPath(
final_index = temp;
}
+ if (valobj_sp->GetValueType() == eValueTypeRegister &&
+ !valobj_sp->IsPointerType() &&
+ (child_index < 0 || final_index < 0 ||
+ static_cast<unsigned long>(child_index) > UINT32_MAX ||
+ static_cast<unsigned long>(final_index) > UINT32_MAX)) {
+ valobj_sp->GetExpressionPath(var_expr_path_strm);
+ error = Status::FromErrorStringWithFormat(
+ "bitfield range %ld-%ld is not valid for \"(%s) %s\"", child_index,
+ final_index, valobj_sp->GetTypeName().AsCString("<invalid type>"),
+ var_expr_path_strm.GetData());
+ return {};
+ }
+
if (valobj_sp->GetCompilerType().IsPointerToScalarType() && deref) {
// what we have is *ptr[low-high]. the most similar C++ syntax is to
// deref ptr and extract bits low thru high out of it. reading array
diff --git a/lldb/source/ValueObject/DILEval.cpp b/lldb/source/ValueObject/DILEval.cpp
index bf28dc29bb8e0a..5f8d3b90489d89 100644
--- a/lldb/source/ValueObject/DILEval.cpp
+++ b/lldb/source/ValueObject/DILEval.cpp
@@ -23,6 +23,7 @@
#include "llvm/Support/ErrorExtras.h"
#include "llvm/Support/FormatAdapters.h"
#include <memory>
+#include <optional>
namespace lldb_private::dil {
@@ -82,6 +83,30 @@ llvm::Expected<lldb::TypeSystemSP> Interpreter::GetTypeSystemFromCU() {
return symbol_context->module_sp->GetTypeSystemForLanguage(language);
}
+// DIL normally treats '.' as member access, but register names may contain
+// periods. Reconstruct the candidate so an exact register name takes
+// precedence.
+static std::optional<std::string> GetRegisterPathName(const ASTNode &node) {
+ if (node.GetKind() == NodeKind::eIdentifierNode) {
+ std::string name_storage =
+ static_cast<const IdentifierNode &>(node).GetName();
+ llvm::StringRef name = name_storage;
+ if (name.consume_front("$"))
+ return name.str();
+ return std::nullopt;
+ }
+
+ if (node.GetKind() != NodeKind::eMemberOfNode)
+ return std::nullopt;
+ const auto &member = static_cast<const MemberOfNode &>(node);
+ if (member.GetIsArrow())
+ return std::nullopt;
+ std::optional<std::string> base_name = GetRegisterPathName(member.GetBase());
+ if (!base_name)
+ return std::nullopt;
+ return *base_name + "." + member.GetFieldName().str();
+}
+
llvm::Expected<lldb::ValueObjectSP>
Interpreter::UnaryConversion(lldb::ValueObjectSP valobj, uint32_t location) {
if (!valobj)
@@ -1493,6 +1518,18 @@ Interpreter::Visit(const BinaryOpNode &node) {
llvm::Expected<lldb::ValueObjectSP>
Interpreter::Visit(const MemberOfNode &node) {
+ if (std::optional<std::string> register_name = GetRegisterPathName(node)) {
+ auto frame_sp = m_exe_ctx.GetFrameSP();
+ lldb::RegisterContextSP reg_ctx_sp =
+ frame_sp ? frame_sp->GetRegisterContext() : nullptr;
+ if (reg_ctx_sp) {
+ if (const RegisterInfo *reg_info =
+ reg_ctx_sp->GetRegisterInfoByName(*register_name))
+ return ValueObjectRegister::Create(
+ m_exe_ctx.GetBestExecutionContextScope(), reg_ctx_sp, reg_info);
+ }
+ }
+
auto base_or_err = Evaluate(node.GetBase());
if (!base_or_err)
return base_or_err;
@@ -1610,6 +1647,17 @@ Interpreter::Visit(const ArraySubscriptNode &node) {
CompilerType base_type = base->GetCompilerType().GetNonReferenceType();
base->GetExpressionPath(var_expr_path_strm);
+ if (base->GetValueType() == lldb::eValueTypeRegister &&
+ !base_type.IsPointerType() &&
+ child_idx > std::numeric_limits<uint32_t>::max()) {
+ std::string err_msg = llvm::formatv(
+ "array index {0} is not valid for \"({1}) {2}\"", child_idx,
+ base->GetTypeName().AsCString("<invalid type>"),
+ var_expr_path_strm.GetData());
+ return llvm::make_error<DILDiagnosticError>(m_expr, std::move(err_msg),
+ node.GetLocation());
+ }
+
bool is_incomplete_array = false;
if (base_type.IsPointerType()) {
bool is_objc_pointer = true;
@@ -1674,7 +1722,8 @@ Interpreter::Visit(const ArraySubscriptNode &node) {
return llvm::make_error<DILDiagnosticError>(m_expr, std::move(err_msg),
node.GetLocation());
}
- } else if (base_type.IsArrayType(nullptr, nullptr, &is_incomplete_array)) {
+ } else if (base_type.IsArrayType(nullptr, nullptr, &is_incomplete_array) ||
+ base_type.IsVectorType()) {
child_valobj_sp = base->GetChildAtIndex(child_idx);
if (!child_valobj_sp && (is_incomplete_array || m_use_synthetic))
child_valobj_sp = base->GetSyntheticArrayMember(child_idx, true);
diff --git a/lldb/source/ValueObject/ValueObject.cpp b/lldb/source/ValueObject/ValueObject.cpp
index f7f6824a3820ad..c7ff3d1fff5d79 100644
--- a/lldb/source/ValueObject/ValueObject.cpp
+++ b/lldb/source/ValueObject/ValueObject.cpp
@@ -1898,6 +1898,14 @@ bool ValueObject::IsUninitializedReference() {
ValueObjectSP ValueObject::GetSyntheticArrayMember(size_t index,
bool can_create) {
+ // Register values use exact-size host buffers. Do not create array children
+ // that would read beyond the bytes supplied by the register context.
+ uint64_t array_size = 0;
+ if (GetValueType() == eValueTypeRegister &&
+ GetCompilerType().IsArrayType(nullptr, &array_size) &&
+ index >= array_size)
+ return {};
+
ValueObjectSP synthetic_child_sp;
if (IsPointerType() || IsArrayType()) {
std::string index_str = llvm::formatv("[{0}]", index);
@@ -1925,6 +1933,16 @@ ValueObjectSP ValueObject::GetSyntheticArrayMember(size_t index,
ValueObjectSP ValueObject::GetSyntheticBitFieldChild(uint32_t from, uint32_t to,
bool can_create) {
+ if (from > to)
+ return {};
+ // Register children must stay within the exact bytes supplied by the
+ // register context.
+ if (GetValueType() == eValueTypeRegister) {
+ std::optional<uint64_t> byte_size = llvm::expectedToOptional(GetByteSize());
+ if (!byte_size || to / 8 >= *byte_size)
+ return {};
+ }
+
ValueObjectSP synthetic_child_sp;
if (IsScalarType()) {
std::string index_str = llvm::formatv("[{0}-{1}]", from, to);
@@ -2250,7 +2268,8 @@ void ValueObject::GetExpressionPath(Stream &s,
if (non_base_class_parent_type_info & eTypeIsPointer) {
s.PutCString("->");
} else if ((non_base_class_parent_type_info & eTypeHasChildren) &&
- !(non_base_class_parent_type_info & eTypeIsArray)) {
+ !(non_base_class_parent_type_info &
+ (eTypeIsArray | eTypeIsVector))) {
s.PutChar('.');
}
}
@@ -2523,6 +2542,13 @@ ValueObjectSP ValueObject::GetValueForExpressionPath_Impl(
return ValueObjectSP();
}
}
+ if (temp_expression.size() == 1) {
+ *reason_to_stop =
+ ValueObject::eExpressionPathScanEndReasonUnexpectedSymbol;
+ *final_result = ValueObject::eExpressionPathEndResultTypeInvalid;
+ return nullptr;
+ }
+
if (temp_expression[1] ==
']') // if this is an unbounded range it only works for arrays
{
@@ -2570,6 +2596,14 @@ ValueObjectSP ValueObject::GetValueForExpressionPath_Impl(
*final_result = ValueObject::eExpressionPathEndResultTypeInvalid;
return nullptr;
}
+ if (root->GetValueType() == eValueTypeRegister &&
+ !root_compiler_type_info.Test(eTypeIsPointer) &&
+ index > UINT32_MAX) {
+ *reason_to_stop =
+ ValueObject::eExpressionPathScanEndReasonNoSuchChild;
+ *final_result = ValueObject::eExpressionPathEndResultTypeInvalid;
+ return nullptr;
+ }
// from here on we do have a valid index
if (root_compiler_type_info.Test(eTypeIsArray)) {
@@ -2731,6 +2765,14 @@ ValueObjectSP ValueObject::GetValueForExpressionPath_Impl(
if (low_index > high_index) // swap indices if required
std::swap(low_index, high_index);
+ if (root->GetValueType() == eValueTypeRegister &&
+ high_index > UINT32_MAX) {
+ *reason_to_stop =
+ ValueObject::eExpressionPathScanEndReasonNoSuchChild;
+ *final_result = ValueObject::eExpressionPathEndResultTypeInvalid;
+ return nullptr;
+ }
+
if (root_compiler_type_info.Test(
eTypeIsScalar)) // expansion only works for scalars
{
diff --git a/lldb/test/API/functionalities/gdb_remote_client/TestXMLRegisterUnion.py b/lldb/test/API/functionalities/gdb_remote_client/TestXMLRegisterUnion.py
index af15fec036b7f3..9b299ff4d51ca3 100644
--- a/lldb/test/API/functionalities/gdb_remote_client/TestXMLRegisterUnion.py
+++ b/lldb/test/API/functionalities/gdb_remote_client/TestXMLRegisterUnion.py
@@ -473,7 +473,7 @@ def test_big_endian_union_sb_api(self):
@skipIfXmlSupportMissing
@skipIfRemote
- def test_union_cli_summary_and_member_paths(self):
+ def test_union_cli_and_member_paths(self):
process = self.setup_register_test(
"""\
<vector id="v4f" type="ieee_single" count="4"/>
@@ -490,18 +490,25 @@ def test_union_cli_summary_and_member_paths(self):
<field name="view" type="vector_views"/>
<field name="raw" type="uint128"/>
</union>
+ <union id="index_view">
+ <field name="value" type="uint64"/>
+ </union>
<reg name="u0" altname="alt_u0" regnum="0" bitsize="64" type="views"/>
<reg name="u1" regnum="1" bitsize="128" type="vector_views"/>
<reg name="n0" regnum="2" bitsize="128" type="nested"/>
+ <reg name="idx" regnum="3" bitsize="64" type="index_view"/>
<reg name="pc" bitsize="64"/>""",
- "0000c03fffffffff" + "0000c03f000020400000604000009040" * 2 + "00" * 8,
+ "0000c03fffffffff"
+ + "0000c03f000020400000604000009040" * 2
+ + "0200000000000000"
+ + "00" * 8,
)
union = process.GetThreadAtIndex(0).GetFrameAtIndex(0).FindRegister("u0")
- self.assertEqual(
- union.GetSummary(),
- "(f32 = 1.5, f64 = NaN, u64 = 18446744070484131840)",
- )
+ self.assertIsNone(union.GetSummary())
+ description = lldb.SBStream()
+ self.assertTrue(union.GetDescription(description))
+ self.assertIn("f32 = 1.5", description.GetData())
self.expect(
"register read u0",
@@ -511,6 +518,7 @@ def test_union_cli_summary_and_member_paths(self):
],
)
self.expect("register read u0.f32", substrs=["u0.f32 = 1.5"])
+ self.expect("register read U0.f32", substrs=["u0.f32 = 1.5"])
self.expect(
"register read u0.u64 --format X",
substrs=["u0.u64 = 0xFFFFFFFF3FC00000"],
@@ -523,15 +531,60 @@ def test_union_cli_summary_and_member_paths(self):
self.expect("register read -A alt_u0.f32", substrs=["alt_u0.f32 = 1.5"])
self.expect("register read $u0.f64", substrs=["u0.f64 = NaN"])
self.expect("register read u1.floats[2]", substrs=["u1.floats[2] = 3.5"])
+ self.expect(
+ "register read u1.floats[$idx.value]",
+ substrs=["u1.floats[2] = 3.5"],
+ )
+ self.expect(
+ "register read u0 u1.floats[2]",
+ substrs=["u0 = 0xffffffff3fc00000", "u1.floats[2] = 3.5"],
+ )
self.expect(
"register read n0.view.floats[3]",
substrs=["n0.view.floats[3] = 4.5"],
)
+ frame = process.GetThreadAtIndex(0).GetFrameAtIndex(0)
+ lane = frame.FindRegister("u1").GetValueForExpressionPath(".floats[2]")
+ expression_path = lldb.SBStream()
+ self.assertTrue(lane.GetExpressionPath(expression_path))
+ self.assertEqual(expression_path.GetData(), "$u1.floats[2]")
+ self.assertEqual(lane.GetValueType(), lldb.eValueTypeRegister)
+
+ resolved_lane = frame.GetValueForVariablePath("$u1.floats[2]")
+ self.assertTrue(resolved_lane.IsValid())
+ self.assertEqual(resolved_lane.GetValue(), "3.5")
+ self.assertEqual(resolved_lane.GetValueType(), lldb.eValueTypeRegister)
+
+ self.expect(
+ "v $u0",
+ substrs=[
+ "u0 = (f32 = 1.5",
+ "f64 = NaN",
+ "u64 = 18446744070484131840)",
+ ],
+ )
+ self.expect("v $u0.f32", substrs=["(float) $u0.f32 = 1.5"])
+ self.expect("v $u1.floats[2]", substrs=["(float) $u1.floats[2] = 3.5"])
+ self.expect(
+ "v $u1.floats[$idx.value]",
+ substrs=["(float) $u1.floats[$idx.value] = 3.5"],
+ )
+
+ self.runCmd("settings set target.experimental.use-DIL false")
+ try:
+ self.expect("register read u1.floats[2]", substrs=["u1.floats[2] = 3.5"])
+ self.expect("v $u1.floats[2]", substrs=["(float) $u1.floats[2] = 3.5"])
+ resolved_lane = frame.GetValueForVariablePath("$u1.floats[2]")
+ self.assertTrue(resolved_lane.IsValid())
+ self.assertEqual(resolved_lane.GetValue(), "3.5")
+ finally:
+ self.runCmd("settings set target.experimental.use-DIL true")
+
@skipIfXmlSupportMissing
@skipIfRemote
def test_exact_dotted_register_name_takes_precedence(self):
- self.setup_register_test(
+ process = self.setup_register_test(
"""\
<union id="views">
<field name="f32" type="ieee_single"/>
@@ -546,36 +599,68 @@ def test_exact_dotted_register_name_takes_precedence(self):
self.expect("register read u0.f32", substrs=["u0.f32 = 0x0000002a"])
self.expect("register read u0.view.f32", substrs=["u0.view.f32 = 1.5"])
+ self.expect("v $u0.f32", substrs=["u0.f32 = 0x0000002a"])
+ self.expect("v $u0.view.f32", substrs=["(float) $u0.view.f32 = 1.5"])
+
+ frame = process.GetThreadAtIndex(0).GetFrameAtIndex(0)
+ self.assertEqual(
+ frame.GetValueForVariablePath("$u0.f32").GetValue(), "0x0000002a"
+ )
+ self.assertEqual(
+ frame.GetValueForVariablePath("$u0.view.f32").GetValue(), "1.5"
+ )
+
+ self.runCmd("settings set target.experimental.use-DIL false")
+ try:
+ self.expect("register read u0.f32", substrs=["u0.f32 = 0x0000002a"])
+ self.expect("register read u0.view.f32", substrs=["u0.view.f32 = 1.5"])
+ self.expect("v $u0.view.f32", substrs=["(float) $u0.view.f32 = 1.5"])
+ self.assertEqual(
+ frame.GetValueForVariablePath("$u0.f32").GetValue(),
+ "0x0000002a",
+ )
+ self.assertEqual(
+ frame.GetValueForVariablePath("$u0.view.f32").GetValue(),
+ "1.5",
+ )
+ finally:
+ self.runCmd("settings set target.experimental.use-DIL true")
@skipIfXmlSupportMissing
@skipIfRemote
def test_invalid_union_member_paths(self):
- self.setup_register_test(
+ process = self.setup_register_test(
"""\
<union id="views">
<field name="f32" type="ieee_single"/>
<field name="raw" type="uint64"/>
</union>
<vector id="v2views" type="views" count="2"/>
+ <vector id="v2f" type="ieee_single" count="2"/>
+ <union id="vector_view">
+ <field name="elements" type="v2views"/>
+ </union>
<reg name="u0" regnum="0" bitsize="64" type="views"/>
<reg name="v0" regnum="1" bitsize="128" type="v2views"/>
- <reg name="pc" regnum="2" bitsize="64"/>""",
- "00" * 32,
+ <reg name="n0" regnum="2" bitsize="128" type="vector_view"/>
+ <reg name="vf" regnum="3" bitsize="64" type="v2f"/>
+ <reg name="pc" regnum="4" bitsize="64"/>""",
+ "0000000001000000" + "00" * 48,
)
invalid_paths = {
- "u0.missing": "No field path 'missing' in register 'u0'",
- "u0.": "No field path '' in register 'u0'",
- "u0..f32": "No field path '.f32' in register 'u0'",
- "u0[0]": "No field path '[0]' in register 'u0'",
- "v0[": "No field path '[' in register 'v0'",
- "v0[0": "No field path '[0' in register 'v0'",
- "v0[]": "No field path '[]' in register 'v0'",
- "v0[x]": "No field path '[x]' in register 'v0'",
- "v0[-1]": "No field path '[-1]' in register 'v0'",
- "v0[4294967296]": "No field path '[4294967296]' in register 'v0'",
- "v0[0]junk": "No field path '[0]junk' in register 'v0'",
- "v0[0].missing": "No field path '[0].missing' in register 'v0'",
+ "u0.missing": '"missing" is not a member',
+ "u0.": '"" is not a member',
+ "u0..f32": '"" is not a member',
+ "u0[0]": "is not an array type",
+ "v0[": "Unexpected token",
+ "v0[0": "expected 'r_square'",
+ "v0[]": "Unexpected token",
+ "v0[x]": "use of undeclared identifier 'x'",
+ "v0[-1]": "is not allowed in DIL legacy mode",
+ "v0[4294967296]": "array index 4294967296 is not valid",
+ "v0[0]junk": "expected 'eof'",
+ "v0[0].missing": '"missing" is not a member',
}
for path, diagnostic in invalid_paths.items():
self.expect(
@@ -586,13 +671,85 @@ def test_invalid_union_member_paths(self):
self.expect(
"register read v0[9].f32",
error=True,
- substrs=["No field path '[9].f32'"],
+ substrs=["array index 9 is not valid"],
)
self.expect(
"register read pc.field",
error=True,
- substrs=["Register 'pc' does not have a structured type"],
+ substrs=['"field" is not a member'],
+ )
+ self.expect(
+ "register read pc->field",
+ error=True,
+ substrs=["member reference type"],
)
+ self.expect(
+ "register read does_not_exist",
+ error=True,
+ substrs=["Invalid register name 'does_not_exist'"],
+ )
+
+ frame = process.GetThreadAtIndex(0).GetFrameAtIndex(0)
+ vector = frame.FindRegister("v0")
+ self.assertTrue(vector.GetType().IsArrayType())
+ self.assertEqual(vector.GetValueType(), lldb.eValueTypeRegister)
+ self.assertEqual(
+ vector.GetChildAtIndex(0).GetValueType(), lldb.eValueTypeRegister
+ )
+ self.assertTrue(
+ vector.GetChildAtIndex(1, lldb.eNoDynamicValues, True).IsValid()
+ )
+ self.assertFalse(
+ vector.GetChildAtIndex(9, lldb.eNoDynamicValues, True).IsValid()
+ )
+ self.assertFalse(vector.GetValueForExpressionPath("[9].f32").IsValid())
+ self.assertFalse(vector.GetValueForExpressionPath("[4294967296]").IsValid())
+
+ scalar = frame.FindRegister("u0").GetChildMemberWithName("raw")
+ self.assertTrue(scalar.GetValueForExpressionPath("[63]").IsValid())
+ self.assertFalse(scalar.GetValueForExpressionPath("[64]").IsValid())
+ self.assertFalse(scalar.GetValueForExpressionPath("[64-64]").IsValid())
+ for command in ["register read u0.raw[64]", "v $u0.raw[64]"]:
+ self.expect(command, error=True, substrs=["bitfield range 64"])
+
+ nested_vector = frame.FindRegister("n0").GetChildMemberWithName("elements")
+ self.assertEqual(nested_vector.GetValueType(), lldb.eValueTypeRegister)
+ self.assertFalse(nested_vector.GetValueForExpressionPath("[9].f32").IsValid())
+ native_vector = frame.FindRegister("vf")
+ self.assertTrue(native_vector.GetType().IsVectorType())
+ self.assertEqual(native_vector.GetValueType(), lldb.eValueTypeRegister)
+ self.assertEqual(
+ native_vector.GetChildAtIndex(0).GetValueType(),
+ lldb.eValueTypeRegister,
+ )
+ self.expect(
+ "v $v0[$u0.raw].f32",
+ error=True,
+ substrs=["array index 4294967296 is not valid"],
+ )
+ self.expect(
+ "v $vf[$u0.raw]",
+ error=True,
+ substrs=["array index 4294967296 is not valid"],
+ )
+
+ self.runCmd("settings set target.experimental.use-DIL false")
+ try:
+ for command in [
+ "register read v0[9].f32",
+ "register read v0[4294967296]",
+ "v $v0[9].f32",
+ "v $v0[4294967296]",
+ ]:
+ self.expect(command, error=True, substrs=["array index"])
+ for command in [
+ "register read u0.raw[64]",
+ "register read u0.raw[64-64]",
+ "v $u0.raw[64]",
+ ]:
+ self.expect(command, error=True, substrs=["bitfield range 64"])
+ finally:
+ self.runCmd("settings set target.experimental.use-DIL true")
@skipIfXmlSupportMissing
@skipIfRemote
@@ -624,6 +781,8 @@ def test_big_endian_union_cli(self):
"register read u0",
substrs=[
"u0 = 0x3fc0000040200000",
- " = (scalar = 1.5, lanes = (1.5, 2.5))",
+ " = {",
+ "scalar = 1.5",
+ "lanes = ([0] = 1.5, [1] = 2.5)",
],
)
>From 12ec357e761dfba6098ae5c4636e18833a0ecad0 Mon Sep 17 00:00:00 2001
From: Bar Soloveychik <barsolo at fb.com>
Date: Mon, 5 Oct 2026 13:29:14 -0700
Subject: [PATCH 06/12] address greg's comment
---
.../source/Commands/CommandObjectRegister.cpp | 52 +++++++++----------
1 file changed, 26 insertions(+), 26 deletions(-)
diff --git a/lldb/source/Commands/CommandObjectRegister.cpp b/lldb/source/Commands/CommandObjectRegister.cpp
index cb9b26c3507bcc..1be96256aa1a15 100644
--- a/lldb/source/Commands/CommandObjectRegister.cpp
+++ b/lldb/source/Commands/CommandObjectRegister.cpp
@@ -288,27 +288,23 @@ class CommandObjectRegisterRead : public CommandObjectParsed {
expression.consume_front("$");
RegisterArgument argument;
- argument.reg_info = reg_ctx->GetRegisterInfoByName(expression);
- if (!argument.reg_info) {
+ std::string variable_path = "$" + expression.str();
+ VariableSP variable_sp;
+ Status error;
+ argument.value_sp = frame->GetValueForVariableExpressionPath(
+ variable_path, eNoDynamicValues,
+ StackFrame::eExpressionPathOptionCheckPtrVsMember, variable_sp,
+ error, eDILModeLegacy);
+ if (error.Fail() || !argument.value_sp) {
if (expression.find_first_of(".[") == llvm::StringRef::npos &&
- !expression.contains("->")) {
+ !expression.contains("->"))
argument.error =
"Invalid register name '" + expression.str() + "'";
- } else {
- std::string variable_path = "$" + expression.str();
- VariableSP variable_sp;
- Status error;
- argument.value_sp = frame->GetValueForVariableExpressionPath(
- variable_path, eNoDynamicValues,
- StackFrame::eExpressionPathOptionCheckPtrVsMember,
- variable_sp, error, eDILModeLegacy);
- if (error.Fail() || !argument.value_sp) {
- argument.error = error.AsCString("invalid register path");
- } else if (ValueObject *root = argument.value_sp->GetRoot()) {
- argument.reg_info = reg_ctx->GetRegisterInfoByName(
- root->GetName().GetStringRef());
- }
- }
+ else
+ argument.error = error.AsCString("invalid register path");
+ } else if (ValueObject *root = argument.value_sp->GetRoot()) {
+ argument.reg_info =
+ reg_ctx->GetRegisterInfoByName(root->GetName().GetStringRef());
}
if (!argument.reg_info) {
@@ -317,15 +313,19 @@ class CommandObjectRegisterRead : public CommandObjectParsed {
"Invalid register name '" + expression.str() + "'";
} else {
if (argument.value_sp) {
- StreamString expression_stream;
- argument.value_sp->GetExpressionPath(expression_stream);
- llvm::StringRef expression_path = expression_stream.GetString();
- if (!expression_path.consume_front("$") ||
- !expression_path.consume_front(argument.reg_info->name)) {
- argument.error = "unable to reconstruct register path '" +
- expression.str() + "'";
+ if (argument.value_sp.get() == argument.value_sp->GetRoot()) {
+ argument.value_sp.reset();
} else {
- argument.expression_path = expression_path.str();
+ StreamString expression_stream;
+ argument.value_sp->GetExpressionPath(expression_stream);
+ llvm::StringRef expression_path = expression_stream.GetString();
+ if (!expression_path.consume_front("$") ||
+ !expression_path.consume_front(argument.reg_info->name)) {
+ argument.error = "unable to reconstruct register path '" +
+ expression.str() + "'";
+ } else {
+ argument.expression_path = expression_path.str();
+ }
}
}
reg_name_right_align_at =
>From 0e7e34b37fa597bb2a4c999a96a61085a24e4560 Mon Sep 17 00:00:00 2001
From: Bar Soloveychik <barsolo at fb.com>
Date: Tue, 6 Oct 2026 11:28:34 -0700
Subject: [PATCH 07/12] adding greg's suggestions
---
.../source/Commands/CommandObjectRegister.cpp | 57 +++++++++----------
.../gdb_remote_client/TestXMLRegisterUnion.py | 2 +-
2 files changed, 28 insertions(+), 31 deletions(-)
diff --git a/lldb/source/Commands/CommandObjectRegister.cpp b/lldb/source/Commands/CommandObjectRegister.cpp
index 1be96256aa1a15..9060fea199f4b5 100644
--- a/lldb/source/Commands/CommandObjectRegister.cpp
+++ b/lldb/source/Commands/CommandObjectRegister.cpp
@@ -147,7 +147,6 @@ class CommandObjectRegisterRead : public CommandObjectParsed {
bool DumpRegisterField(Stream &strm, const RegisterInfo ®_info,
const ValueObjectSP &value_sp,
- llvm::StringRef expression_path,
size_t reg_name_right_align_at,
CommandReturnObject &result) {
DumpValueObjectOptions options(*value_sp);
@@ -170,9 +169,21 @@ class CommandObjectRegisterRead : public CommandObjectParsed {
m_command_options.alternate_name && reg_info.alt_name
? reg_info.alt_name
: reg_info.name;
+
+ StreamString expression_stream;
+ value_sp->GetExpressionPath(expression_stream);
+ llvm::StringRef expression = expression_stream.GetString();
+ expression.consume_front("$");
+ llvm::StringRef expression_path = expression;
+
strm.Indent();
- strm.Printf("%*s%s = ", static_cast<int>(reg_name_right_align_at),
- register_name, expression_path.str().c_str());
+ // Keep the resolved suffix while allowing an alternate register name.
+ if (expression_path.consume_front(reg_info.name))
+ strm.Printf("%*s%s = ", static_cast<int>(reg_name_right_align_at),
+ register_name, expression_path.str().c_str());
+ else
+ strm.Printf("%*s = ", static_cast<int>(reg_name_right_align_at),
+ expression.str().c_str());
strm << value << '\n';
return true;
}
@@ -270,7 +281,6 @@ class CommandObjectRegisterRead : public CommandObjectParsed {
struct RegisterArgument {
const RegisterInfo *reg_info = nullptr;
ValueObjectSP value_sp;
- std::string expression_path;
std::string error;
};
@@ -295,14 +305,12 @@ class CommandObjectRegisterRead : public CommandObjectParsed {
variable_path, eNoDynamicValues,
StackFrame::eExpressionPathOptionCheckPtrVsMember, variable_sp,
error, eDILModeLegacy);
- if (error.Fail() || !argument.value_sp) {
- if (expression.find_first_of(".[") == llvm::StringRef::npos &&
- !expression.contains("->"))
- argument.error =
- "Invalid register name '" + expression.str() + "'";
- else
- argument.error = error.AsCString("invalid register path");
- } else if (ValueObject *root = argument.value_sp->GetRoot()) {
+ if (error.Fail())
+ argument.error = error.AsCString();
+ else if (!argument.value_sp)
+ argument.error =
+ "Invalid register expression '" + expression.str() + "'";
+ else if (ValueObject *root = argument.value_sp->GetRoot()) {
argument.reg_info =
reg_ctx->GetRegisterInfoByName(root->GetName().GetStringRef());
}
@@ -310,23 +318,13 @@ class CommandObjectRegisterRead : public CommandObjectParsed {
if (!argument.reg_info) {
if (argument.error.empty())
argument.error =
- "Invalid register name '" + expression.str() + "'";
+ "Invalid register expression '" + expression.str() + "'";
} else {
- if (argument.value_sp) {
- if (argument.value_sp.get() == argument.value_sp->GetRoot()) {
- argument.value_sp.reset();
- } else {
- StreamString expression_stream;
- argument.value_sp->GetExpressionPath(expression_stream);
- llvm::StringRef expression_path = expression_stream.GetString();
- if (!expression_path.consume_front("$") ||
- !expression_path.consume_front(argument.reg_info->name)) {
- argument.error = "unable to reconstruct register path '" +
- expression.str() + "'";
- } else {
- argument.expression_path = expression_path.str();
- }
- }
+ if (argument.value_sp &&
+ argument.value_sp.get() == argument.value_sp->GetRoot()) {
+ // Whole registers use DumpRegister below. Only descendants use
+ // DumpRegisterField.
+ argument.value_sp.reset();
}
reg_name_right_align_at =
std::max(reg_name_right_align_at,
@@ -354,8 +352,7 @@ class CommandObjectRegisterRead : public CommandObjectParsed {
argument.reg_info->name);
} else {
DumpRegisterField(strm, *argument.reg_info, argument.value_sp,
- argument.expression_path, reg_name_right_align_at,
- result);
+ reg_name_right_align_at, result);
}
}
strm.IndentLess();
diff --git a/lldb/test/API/functionalities/gdb_remote_client/TestXMLRegisterUnion.py b/lldb/test/API/functionalities/gdb_remote_client/TestXMLRegisterUnion.py
index 9b299ff4d51ca3..e8314f70a35d51 100644
--- a/lldb/test/API/functionalities/gdb_remote_client/TestXMLRegisterUnion.py
+++ b/lldb/test/API/functionalities/gdb_remote_client/TestXMLRegisterUnion.py
@@ -686,7 +686,7 @@ def test_invalid_union_member_paths(self):
self.expect(
"register read does_not_exist",
error=True,
- substrs=["Invalid register name 'does_not_exist'"],
+ substrs=["use of undeclared identifier '$does_not_exist'"],
)
frame = process.GetThreadAtIndex(0).GetFrameAtIndex(0)
>From 251b7ae5edf4e54070e74c1ea12a57e4bc6ae432 Mon Sep 17 00:00:00 2001
From: Bar Soloveychik <barsolo at fb.com>
Date: Tue, 6 Oct 2026 15:34:31 -0700
Subject: [PATCH 08/12] Update register error expectations
---
.../register/register_command/TestRegisters.py | 4 ++--
.../gdb_remote_client/TestGDBServerTargetXML.py | 8 +++++---
.../gdb_remote_client/TestXMLRegisterUnion.py | 10 ++++++++++
3 files changed, 17 insertions(+), 5 deletions(-)
diff --git a/lldb/test/API/commands/register/register_command/TestRegisters.py b/lldb/test/API/commands/register/register_command/TestRegisters.py
index b77a5e8794a15d..8c16cd4d748f80 100644
--- a/lldb/test/API/commands/register/register_command/TestRegisters.py
+++ b/lldb/test/API/commands/register/register_command/TestRegisters.py
@@ -472,7 +472,7 @@ def fp_register_write(self):
else:
self.expect(
"register read ymm0",
- substrs=["Invalid register name 'ymm0'"],
+ substrs=["use of undeclared identifier '$ymm0'"],
error=True,
)
@@ -493,7 +493,7 @@ def fp_register_write(self):
else:
self.expect(
"register read bnd0",
- substrs=["Invalid register name 'bnd0'"],
+ substrs=["use of undeclared identifier '$bnd0'"],
error=True,
)
diff --git a/lldb/test/API/functionalities/gdb_remote_client/TestGDBServerTargetXML.py b/lldb/test/API/functionalities/gdb_remote_client/TestGDBServerTargetXML.py
index 42d4a1f2c419c8..72408af5b25794 100644
--- a/lldb/test/API/functionalities/gdb_remote_client/TestGDBServerTargetXML.py
+++ b/lldb/test/API/functionalities/gdb_remote_client/TestGDBServerTargetXML.py
@@ -881,7 +881,9 @@ def haltReason(self):
self.match("register read rdx", ["rdx = 0x1817161514131211"])
# edx should not be added
self.match(
- "register read edx", ["error: Invalid register name 'edx'"], error=True
+ "register read edx",
+ [r"use of undeclared identifier '\$edx'"],
+ error=True,
)
@skipIfXmlSupportMissing
@@ -954,7 +956,7 @@ def haltReason(self):
self.match("register read ecx", ["ecx = 0x14131211"])
# dx should not be added
self.match(
- "register read cx", ["error: Invalid register name 'cx'"], error=True
+ "register read cx", [r"use of undeclared identifier '\$cx'"], error=True
)
@skipIfXmlSupportMissing
@@ -1049,5 +1051,5 @@ def haltReason(self):
self.match("register read x1", ["x1 = 0x1817161514131211"])
# w1 should not be added
self.match(
- "register read w1", ["error: Invalid register name 'w1'"], error=True
+ "register read w1", [r"use of undeclared identifier '\$w1'"], error=True
)
diff --git a/lldb/test/API/functionalities/gdb_remote_client/TestXMLRegisterUnion.py b/lldb/test/API/functionalities/gdb_remote_client/TestXMLRegisterUnion.py
index e8314f70a35d51..61c06ec217410c 100644
--- a/lldb/test/API/functionalities/gdb_remote_client/TestXMLRegisterUnion.py
+++ b/lldb/test/API/functionalities/gdb_remote_client/TestXMLRegisterUnion.py
@@ -689,6 +689,16 @@ def test_invalid_union_member_paths(self):
substrs=["use of undeclared identifier '$does_not_exist'"],
)
+ self.runCmd("settings set target.experimental.use-DIL false")
+ try:
+ self.expect(
+ "register read does_not_exist",
+ error=True,
+ substrs=["no register named 'does_not_exist' found in this frame"],
+ )
+ finally:
+ self.runCmd("settings set target.experimental.use-DIL true")
+
frame = process.GetThreadAtIndex(0).GetFrameAtIndex(0)
vector = frame.FindRegister("v0")
self.assertTrue(vector.GetType().IsArrayType())
>From 53dfd1d22ed1d38512817d58bc8e52e7245b9597 Mon Sep 17 00:00:00 2001
From: Bar Soloveychik <barsolo at fb.com>
Date: Wed, 7 Oct 2026 16:04:04 -0700
Subject: [PATCH 09/12] fixed issues that greg flagged
---
.../source/Commands/CommandObjectRegister.cpp | 51 ++++++++-----------
.../Process/scripted/ScriptedFrame.cpp | 29 +++++++++--
lldb/source/Target/StackFrame.cpp | 12 ++---
.../register_command/TestRegisters.py | 8 +++
...rameProviderWrappedFrameRegisterContext.py | 6 +++
.../frame_provider.py | 4 ++
6 files changed, 71 insertions(+), 39 deletions(-)
diff --git a/lldb/source/Commands/CommandObjectRegister.cpp b/lldb/source/Commands/CommandObjectRegister.cpp
index 9060fea199f4b5..91d2ebc7774931 100644
--- a/lldb/source/Commands/CommandObjectRegister.cpp
+++ b/lldb/source/Commands/CommandObjectRegister.cpp
@@ -29,6 +29,7 @@
#include "lldb/Utility/Args.h"
#include "lldb/Utility/DataExtractor.h"
#include "lldb/Utility/RegisterValue.h"
+#include "lldb/Utility/Status.h"
#include "lldb/Utility/StreamString.h"
#include "lldb/ValueObject/ValueObject.h"
#include "llvm/Support/Errno.h"
@@ -145,7 +146,7 @@ class CommandObjectRegisterRead : public CommandObjectParsed {
return true;
}
- bool DumpRegisterField(Stream &strm, const RegisterInfo ®_info,
+ void DumpRegisterField(Stream &strm, const RegisterInfo ®_info,
const ValueObjectSP &value_sp,
size_t reg_name_right_align_at,
CommandReturnObject &result) {
@@ -160,7 +161,7 @@ class CommandObjectRegisterRead : public CommandObjectParsed {
StreamString value_stream;
if (llvm::Error error = value_sp->Dump(value_stream, options)) {
result.AppendError(toString(std::move(error)));
- return false;
+ return;
}
llvm::StringRef value = value_stream.GetString();
@@ -185,7 +186,6 @@ class CommandObjectRegisterRead : public CommandObjectParsed {
strm.Printf("%*s = ", static_cast<int>(reg_name_right_align_at),
expression.str().c_str());
strm << value << '\n';
- return true;
}
bool DumpRegisterSet(const ExecutionContext &exe_ctx, Stream &strm,
@@ -279,9 +279,10 @@ class CommandObjectRegisterRead : public CommandObjectParsed {
"registers names are supplied as arguments\n");
} else {
struct RegisterArgument {
+ std::string expression;
const RegisterInfo *reg_info = nullptr;
ValueObjectSP value_sp;
- std::string error;
+ Status error;
};
StackFrame *frame = m_exe_ctx.GetFramePtr();
@@ -298,51 +299,41 @@ class CommandObjectRegisterRead : public CommandObjectParsed {
expression.consume_front("$");
RegisterArgument argument;
- std::string variable_path = "$" + expression.str();
+ argument.expression = expression.str();
+ std::string variable_path = "$" + argument.expression;
VariableSP variable_sp;
- Status error;
argument.value_sp = frame->GetValueForVariableExpressionPath(
variable_path, eNoDynamicValues,
StackFrame::eExpressionPathOptionCheckPtrVsMember, variable_sp,
- error, eDILModeLegacy);
- if (error.Fail())
- argument.error = error.AsCString();
- else if (!argument.value_sp)
- argument.error =
- "Invalid register expression '" + expression.str() + "'";
- else if (ValueObject *root = argument.value_sp->GetRoot()) {
- argument.reg_info =
- reg_ctx->GetRegisterInfoByName(root->GetName().GetStringRef());
+ argument.error, eDILModeLegacy);
+ if (argument.error.Success()) {
+ ValueObject *root = argument.value_sp->GetRoot();
+ if (root && root->GetValueType() == eValueTypeRegister)
+ argument.reg_info = reg_ctx->GetRegisterInfoByName(
+ root->GetName().GetStringRef());
}
- if (!argument.reg_info) {
- if (argument.error.empty())
- argument.error =
- "Invalid register expression '" + expression.str() + "'";
- } else {
- if (argument.value_sp &&
- argument.value_sp.get() == argument.value_sp->GetRoot()) {
- // Whole registers use DumpRegister below. Only descendants use
- // DumpRegisterField.
- argument.value_sp.reset();
- }
+ if (argument.error.Success() && !argument.reg_info)
+ argument.error = Status::FromErrorStringWithFormat(
+ "Invalid register expression '%s'",
+ argument.expression.c_str());
+ else if (argument.error.Success())
reg_name_right_align_at =
std::max(reg_name_right_align_at,
GetNameSize(argument.reg_info,
!m_command_options.alternate_name));
- }
register_arguments.push_back(std::move(argument));
}
// Extra ident to be consistent with register sets dumping.
strm.IndentMore();
for (const RegisterArgument &argument : register_arguments) {
- if (!argument.error.empty()) {
- result.AppendError(argument.error);
+ if (argument.error.Fail()) {
+ result.AppendError(argument.error.AsCString());
continue;
}
- if (!argument.value_sp) {
+ if (argument.value_sp.get() == argument.value_sp->GetRoot()) {
// If they have asked for a specific format don't obscure that by
// printing a structured value afterwards.
bool print_type = !m_format_options.GetFormatValue().OptionWasSet();
diff --git a/lldb/source/Plugins/Process/scripted/ScriptedFrame.cpp b/lldb/source/Plugins/Process/scripted/ScriptedFrame.cpp
index 1207aa7eef538c..afaaffdda6be54 100644
--- a/lldb/source/Plugins/Process/scripted/ScriptedFrame.cpp
+++ b/lldb/source/Plugins/Process/scripted/ScriptedFrame.cpp
@@ -353,10 +353,33 @@ lldb::ValueObjectSP ScriptedFrame::GetValueForVariableExpressionPath(
// can't do that!
// FIXME: We should make it possible for the frame implementation to create
// Variable objects.
- (void)var_sp;
// Otherwise, delegate to the scripted frame interface pointer.
- return m_scripted_frame_interface_sp->GetValueObjectForVariableExpression(
- var_expr, options, error);
+ error.Clear();
+ ValueObjectSP value_sp =
+ m_scripted_frame_interface_sp->GetValueObjectForVariableExpression(
+ var_expr, options, error);
+ if (value_sp || !var_expr.starts_with("$"))
+ return value_sp;
+
+ VariableSP fallback_var_sp;
+ Status fallback_error;
+ ValueObjectSP fallback_value_sp =
+ StackFrame::GetValueForVariableExpressionPath(var_expr, use_dynamic,
+ options, fallback_var_sp,
+ fallback_error, mode);
+
+ // If the provider declined without an error, use the standard result. If it
+ // reported an error, only a real register from this frame takes precedence.
+ ValueObject *fallback_root =
+ fallback_value_sp ? fallback_value_sp->GetRoot() : nullptr;
+ if (error.Success() ||
+ (fallback_error.Success() && fallback_root &&
+ fallback_root->GetValueType() == eValueTypeRegister)) {
+ var_sp = std::move(fallback_var_sp);
+ error = std::move(fallback_error);
+ return fallback_value_sp;
+ }
+ return {};
}
llvm::Expected<lldb::ThreadPlanSP>
diff --git a/lldb/source/Target/StackFrame.cpp b/lldb/source/Target/StackFrame.cpp
index 1badffead07072..83a76496793e3e 100644
--- a/lldb/source/Target/StackFrame.cpp
+++ b/lldb/source/Target/StackFrame.cpp
@@ -576,6 +576,8 @@ StackFrame::GetInScopeVariableList(bool get_file_globals,
ValueObjectSP StackFrame::GetValueForVariableExpressionPath(
llvm::StringRef var_expr, DynamicValueType use_dynamic, uint32_t options,
VariableSP &var_sp, Status &error, lldb::DILMode mode) {
+ error.Clear();
+
ExecutionContext exe_ctx;
CalculateExecutionContext(exe_ctx);
bool use_DIL = exe_ctx.GetTargetRef().GetUseDIL(&exe_ctx);
@@ -627,8 +629,11 @@ ValueObjectSP StackFrame::LegacyGetValueForVariableExpressionPath(
VariableSP &var_sp, Status &error) {
llvm::StringRef original_var_expr = var_expr;
// We can't fetch variable information for a history stack frame.
- if (IsHistorical())
+ if (IsHistorical()) {
+ error = Status::FromErrorString(
+ "cannot resolve a variable path in a historical frame");
return ValueObjectSP();
+ }
if (var_expr.empty()) {
error = Status::FromErrorStringWithFormatv("invalid variable path '{0}'",
@@ -678,11 +683,6 @@ ValueObjectSP StackFrame::LegacyGetValueForVariableExpressionPath(
return ValueObjectSP();
}
valobj_sp = ValueObjectRegister::Create(this, reg_ctx_sp, reg_info);
- if (!valobj_sp) {
- error = Status::FromErrorStringWithFormat(
- "unable to read register '%s' in this frame", reg_info->name);
- return {};
- }
name_const_string = ConstString(reg_info->name);
var_expr = register_path;
} else {
diff --git a/lldb/test/API/commands/register/register_command/TestRegisters.py b/lldb/test/API/commands/register/register_command/TestRegisters.py
index 8c16cd4d748f80..6ab449ce4d4173 100644
--- a/lldb/test/API/commands/register/register_command/TestRegisters.py
+++ b/lldb/test/API/commands/register/register_command/TestRegisters.py
@@ -124,6 +124,14 @@ def test_register_expressions(self):
if self.getArchitecture() in ["amd64", "x86_64"]:
self.expect("expr -- ($rax & 0xffffffff) == $eax", substrs=["true"])
+ self.runCmd("expr -- int $not_a_register = 42")
+ self.expect("expr -- $not_a_register", substrs=[" = 42"])
+ self.expect(
+ "register read not_a_register",
+ substrs=["Invalid register expression 'not_a_register'"],
+ error=True,
+ )
+
@skipIfiOSSimulator
@skipIf(archs=no_match(["amd64", "x86_64"]))
@expectedFailureWindowsAndNoLLDBServer(bugnumber="llvm.org/pr37683")
diff --git a/lldb/test/API/functionalities/scripted_frame_provider/wrapped_frame_register_context/TestFrameProviderWrappedFrameRegisterContext.py b/lldb/test/API/functionalities/scripted_frame_provider/wrapped_frame_register_context/TestFrameProviderWrappedFrameRegisterContext.py
index f2f31e8e1b0fd1..3dbe21bbff85bb 100644
--- a/lldb/test/API/functionalities/scripted_frame_provider/wrapped_frame_register_context/TestFrameProviderWrappedFrameRegisterContext.py
+++ b/lldb/test/API/functionalities/scripted_frame_provider/wrapped_frame_register_context/TestFrameProviderWrappedFrameRegisterContext.py
@@ -33,3 +33,9 @@ def test_backtrace_with_forwarded_register_context(self):
# which reads registers through the scripted frame's register context.
# Checking the values, not just for a crash, proves the lookup worked.
self.expect("bt", substrs=["compute(a=3, b=4)", "main"])
+
+ frame = process.GetSelectedThread().GetFrameAtIndex(0)
+ register = frame.GetRegisters().GetValueAtIndex(0).GetChildAtIndex(0)
+ register_name = register.GetName()
+ self.assertTrue(frame.GetValueForVariablePath("$" + register_name).IsValid())
+ self.expect("register read " + register_name, substrs=[register_name + " ="])
diff --git a/lldb/test/API/functionalities/scripted_frame_provider/wrapped_frame_register_context/frame_provider.py b/lldb/test/API/functionalities/scripted_frame_provider/wrapped_frame_register_context/frame_provider.py
index 103a870bfcb86d..13774220b29327 100644
--- a/lldb/test/API/functionalities/scripted_frame_provider/wrapped_frame_register_context/frame_provider.py
+++ b/lldb/test/API/functionalities/scripted_frame_provider/wrapped_frame_register_context/frame_provider.py
@@ -78,6 +78,10 @@ def read(entry):
return struct.pack(struct_format, *struct_data)
+ def get_value_for_variable_expression(self, expr, options, error):
+ error.SetErrorString(f"no scripted value for {expr}")
+ return None
+
class WrapVariablesProvider(ScriptedFrameProvider):
@staticmethod
>From 156c5240113071dc7fd535167edbe4e1b052489e Mon Sep 17 00:00:00 2001
From: Bar Soloveychik <barsolo at fb.com>
Date: Thu, 8 Oct 2026 13:56:24 -0700
Subject: [PATCH 10/12] Preserve $
---
lldb/source/Commands/CommandObjectRegister.cpp | 8 ++++----
.../commands/register/register_command/TestRegisters.py | 5 +++++
2 files changed, 9 insertions(+), 4 deletions(-)
diff --git a/lldb/source/Commands/CommandObjectRegister.cpp b/lldb/source/Commands/CommandObjectRegister.cpp
index 91d2ebc7774931..142c0715953edf 100644
--- a/lldb/source/Commands/CommandObjectRegister.cpp
+++ b/lldb/source/Commands/CommandObjectRegister.cpp
@@ -295,12 +295,12 @@ class CommandObjectRegisterRead : public CommandObjectParsed {
size_t reg_name_right_align_at = 0;
for (auto &entry : command) {
+ RegisterArgument argument;
+ argument.expression = entry.ref().str();
+
llvm::StringRef expression = entry.ref();
expression.consume_front("$");
-
- RegisterArgument argument;
- argument.expression = expression.str();
- std::string variable_path = "$" + argument.expression;
+ std::string variable_path = "$" + expression.str();
VariableSP variable_sp;
argument.value_sp = frame->GetValueForVariableExpressionPath(
variable_path, eNoDynamicValues,
diff --git a/lldb/test/API/commands/register/register_command/TestRegisters.py b/lldb/test/API/commands/register/register_command/TestRegisters.py
index 6ab449ce4d4173..7bd11f5e84f844 100644
--- a/lldb/test/API/commands/register/register_command/TestRegisters.py
+++ b/lldb/test/API/commands/register/register_command/TestRegisters.py
@@ -131,6 +131,11 @@ def test_register_expressions(self):
substrs=["Invalid register expression 'not_a_register'"],
error=True,
)
+ self.expect(
+ "register read $not_a_register",
+ substrs=["Invalid register expression '$not_a_register'"],
+ error=True,
+ )
@skipIfiOSSimulator
@skipIf(archs=no_match(["amd64", "x86_64"]))
>From 7cacbd00f283fdf6d6335abe307a59ba9cc78a87 Mon Sep 17 00:00:00 2001
From: Bar Soloveychik <barsolo at fb.com>
Date: Sun, 18 Oct 2026 02:18:45 -0700
Subject: [PATCH 11/12] fixed David comments
---
lldb/source/Commands/CommandObjectRegister.cpp | 7 +++++++
.../RegisterTypeBuilderClang.cpp | 16 ++++++++--------
.../RegisterTypeBuilderClang.h | 2 +-
.../register/register_command/TestRegisters.py | 4 ++--
.../gdb_remote_client/TestGDBServerTargetXML.py | 10 +++++++---
.../gdb_remote_client/TestXMLRegisterUnion.py | 4 ++--
6 files changed, 27 insertions(+), 16 deletions(-)
diff --git a/lldb/source/Commands/CommandObjectRegister.cpp b/lldb/source/Commands/CommandObjectRegister.cpp
index 142c0715953edf..bf3d41e2ed3861 100644
--- a/lldb/source/Commands/CommandObjectRegister.cpp
+++ b/lldb/source/Commands/CommandObjectRegister.cpp
@@ -306,6 +306,13 @@ class CommandObjectRegisterRead : public CommandObjectParsed {
variable_path, eNoDynamicValues,
StackFrame::eExpressionPathOptionCheckPtrVsMember, variable_sp,
argument.error, eDILModeLegacy);
+ if (argument.error.Fail() &&
+ expression.find_first_of(".[") == llvm::StringRef::npos &&
+ !expression.contains("->")) {
+ argument.error = Status::FromErrorStringWithFormat(
+ "Invalid register name or identifier '%s'",
+ argument.expression.c_str());
+ }
if (argument.error.Success()) {
ValueObject *root = argument.value_sp->GetRoot();
if (root && root->GetValueType() == eValueTypeRegister)
diff --git a/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.cpp b/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.cpp
index 2f79e7d9a42d4c..2b2c78bfb98a07 100644
--- a/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.cpp
+++ b/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.cpp
@@ -200,8 +200,13 @@ RegisterTypeBuilderClang::BuildVectorType(const RegisterTypeVector *vector_type,
CompilerType
RegisterTypeBuilderClang::BuildUnionType(const RegisterTypeUnion *union_type,
- uint32_t expected_byte_size,
+ uint32_t available_byte_size,
lldb::TypeSystemClangSP type_system) {
+ std::optional<uint32_t> byte_size =
+ GetExpectedByteSize(union_type, type_system);
+ if (!byte_size || *byte_size > available_byte_size)
+ return {};
+ uint32_t expected_byte_size = *byte_size;
if (!expected_byte_size)
return {};
if (auto type = GetExistingCompilerType(union_type, expected_byte_size))
@@ -349,13 +354,8 @@ RegisterTypeBuilderClang::GetRegisterType(const RegisterInfo ®_info) {
reg_info.byte_size, type_system);
case RegisterType::eRegisterTypeKindVector:
return BuildType(reg_info.register_type, reg_info.byte_size, type_system);
- case RegisterType::eRegisterTypeKindUnion: {
- std::optional<uint32_t> byte_size =
- GetExpectedByteSize(reg_info.register_type, type_system);
- if (!byte_size || *byte_size > reg_info.byte_size)
- return {};
- return BuildType(reg_info.register_type, *byte_size, type_system);
- }
+ case RegisterType::eRegisterTypeKindUnion:
+ return BuildType(reg_info.register_type, reg_info.byte_size, type_system);
}
llvm_unreachable("Unhandled RegisterType kind");
}
diff --git a/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.h b/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.h
index 128da85ad4eccc..77f1584f6c98d7 100644
--- a/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.h
+++ b/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.h
@@ -50,7 +50,7 @@ class RegisterTypeBuilderClang : public RegisterTypeBuilder {
lldb::TypeSystemClangSP type_system);
CompilerType BuildUnionType(const RegisterTypeUnion *union_type,
- uint32_t expected_byte_size,
+ uint32_t available_byte_size,
lldb::TypeSystemClangSP type_system);
CompilerType BuildType(const RegisterType *register_type,
diff --git a/lldb/test/API/commands/register/register_command/TestRegisters.py b/lldb/test/API/commands/register/register_command/TestRegisters.py
index 7bd11f5e84f844..80341310ef45be 100644
--- a/lldb/test/API/commands/register/register_command/TestRegisters.py
+++ b/lldb/test/API/commands/register/register_command/TestRegisters.py
@@ -485,7 +485,7 @@ def fp_register_write(self):
else:
self.expect(
"register read ymm0",
- substrs=["use of undeclared identifier '$ymm0'"],
+ substrs=["Invalid register name or identifier 'ymm0'"],
error=True,
)
@@ -506,7 +506,7 @@ def fp_register_write(self):
else:
self.expect(
"register read bnd0",
- substrs=["use of undeclared identifier '$bnd0'"],
+ substrs=["Invalid register name or identifier 'bnd0'"],
error=True,
)
diff --git a/lldb/test/API/functionalities/gdb_remote_client/TestGDBServerTargetXML.py b/lldb/test/API/functionalities/gdb_remote_client/TestGDBServerTargetXML.py
index 72408af5b25794..c970b893e8ef12 100644
--- a/lldb/test/API/functionalities/gdb_remote_client/TestGDBServerTargetXML.py
+++ b/lldb/test/API/functionalities/gdb_remote_client/TestGDBServerTargetXML.py
@@ -882,7 +882,7 @@ def haltReason(self):
# edx should not be added
self.match(
"register read edx",
- [r"use of undeclared identifier '\$edx'"],
+ ["Invalid register name or identifier 'edx'"],
error=True,
)
@@ -956,7 +956,9 @@ def haltReason(self):
self.match("register read ecx", ["ecx = 0x14131211"])
# dx should not be added
self.match(
- "register read cx", [r"use of undeclared identifier '\$cx'"], error=True
+ "register read cx",
+ ["Invalid register name or identifier 'cx'"],
+ error=True,
)
@skipIfXmlSupportMissing
@@ -1051,5 +1053,7 @@ def haltReason(self):
self.match("register read x1", ["x1 = 0x1817161514131211"])
# w1 should not be added
self.match(
- "register read w1", [r"use of undeclared identifier '\$w1'"], error=True
+ "register read w1",
+ ["Invalid register name or identifier 'w1'"],
+ error=True,
)
diff --git a/lldb/test/API/functionalities/gdb_remote_client/TestXMLRegisterUnion.py b/lldb/test/API/functionalities/gdb_remote_client/TestXMLRegisterUnion.py
index 61c06ec217410c..36571f054b60dc 100644
--- a/lldb/test/API/functionalities/gdb_remote_client/TestXMLRegisterUnion.py
+++ b/lldb/test/API/functionalities/gdb_remote_client/TestXMLRegisterUnion.py
@@ -686,7 +686,7 @@ def test_invalid_union_member_paths(self):
self.expect(
"register read does_not_exist",
error=True,
- substrs=["use of undeclared identifier '$does_not_exist'"],
+ substrs=["Invalid register name or identifier 'does_not_exist'"],
)
self.runCmd("settings set target.experimental.use-DIL false")
@@ -694,7 +694,7 @@ def test_invalid_union_member_paths(self):
self.expect(
"register read does_not_exist",
error=True,
- substrs=["no register named 'does_not_exist' found in this frame"],
+ substrs=["Invalid register name or identifier 'does_not_exist'"],
)
finally:
self.runCmd("settings set target.experimental.use-DIL true")
>From 4191b715abeb466f387c0f37e5888f9c21b02be4 Mon Sep 17 00:00:00 2001
From: Bar Soloveychik <barsolo at fb.com>
Date: Sun, 18 Oct 2026 07:07:01 -0700
Subject: [PATCH 12/12] Fix register expression paths without compile units
---
lldb/source/ValueObject/DILEval.cpp | 34 ++++++++++++++++++++---------
1 file changed, 24 insertions(+), 10 deletions(-)
diff --git a/lldb/source/ValueObject/DILEval.cpp b/lldb/source/ValueObject/DILEval.cpp
index 5f8d3b90489d89..97df5bf7c34958 100644
--- a/lldb/source/ValueObject/DILEval.cpp
+++ b/lldb/source/ValueObject/DILEval.cpp
@@ -14,6 +14,7 @@
#include "lldb/Symbol/VariableList.h"
#include "lldb/Target/ExecutionContext.h"
#include "lldb/Target/RegisterContext.h"
+#include "lldb/Target/Target.h"
#include "lldb/Utility/LLDBLog.h"
#include "lldb/ValueObject/DILAST.h"
#include "lldb/ValueObject/DILParser.h"
@@ -74,13 +75,24 @@ llvm::Expected<lldb::LanguageType> Interpreter::GetSourceLanguageFromCU() {
}
llvm::Expected<lldb::TypeSystemSP> Interpreter::GetTypeSystemFromCU() {
- llvm::Expected<SymbolContext> symbol_context =
- GetSymbolContexFromFrame(m_exe_ctx);
- if (!symbol_context)
- return symbol_context.takeError();
+ lldb::LanguageType language = lldb::eLanguageTypeUnknown;
+ if (auto frame_sp = m_exe_ctx.GetFrameSP()) {
+ SymbolContext symbol_context =
+ frame_sp->GetSymbolContext(lldb::eSymbolContextCompUnit);
+ if (symbol_context.comp_unit) {
+ language = symbol_context.comp_unit->GetLanguage();
+ symbol_context = frame_sp->GetSymbolContext(lldb::eSymbolContextModule);
+ if (symbol_context.module_sp)
+ return symbol_context.module_sp->GetTypeSystemForLanguage(language);
+ }
+ }
- lldb::LanguageType language = symbol_context->comp_unit->GetLanguage();
- return symbol_context->module_sp->GetTypeSystemForLanguage(language);
+ lldb::TargetSP target_sp = m_exe_ctx.GetTargetSP();
+ if (!target_sp)
+ return llvm::createStringError("no target for frame");
+ if (language == lldb::eLanguageTypeUnknown)
+ language = target_sp->GetLanguage().AsLanguageType();
+ return target_sp->GetScratchTypeSystemForLanguage(language);
}
// DIL normally treats '.' as member access, but register names may contain
@@ -565,10 +577,12 @@ Interpreter::Visit(const IdentifierNode &node) {
if (!identifier && node.GetName()[0] == '$') {
auto language = GetSourceLanguageFromCU();
- if (!language)
- return language.takeError();
- identifier =
- LookupPersistentIdentifier(node.GetName(), m_exe_ctx, language.get());
+ if (language)
+ identifier =
+ LookupPersistentIdentifier(node.GetName(), m_exe_ctx, language.get());
+ else
+ // Fall through to the unknown identifier diagnostic without a CU.
+ llvm::consumeError(language.takeError());
}
if (!identifier && node.GetName() == "nullptr") {
More information about the lldb-commits
mailing list