[llvm] 24045be - [lldb] Fix GetIndexOfChildWithName and GetChildMemberWithName on register sets (#212727)

via llvm-commits llvm-commits at lists.llvm.org
Mon Aug 17 02:41:45 PDT 2026


Author: David Spickett
Date: 2026-08-17T09:41:39Z
New Revision: 24045beb3d9bbc62cfeaa5b42b23376c0e2ff73a

URL: https://github.com/llvm/llvm-project/commit/24045beb3d9bbc62cfeaa5b42b23376c0e2ff73a
DIFF: https://github.com/llvm/llvm-project/commit/24045beb3d9bbc62cfeaa5b42b23376c0e2ff73a.diff

LOG: [lldb] Fix GetIndexOfChildWithName and GetChildMemberWithName on register sets (#212727)

And GetChildMemberWithName which had the same issue.

Fixes #211787.

Both of these methods were doing a lookup on the register info array as
a whole, rather than the subset of indexes into that array. That subset
of indexes is the "register set".

This lead to problems like this where index and name getters disagreed:
```
>>> lldb.frame.GetRegisters()[1].GetChildAtIndex(0)
(unsigned char __attribute__((ext_vector_type(16)))) v0 = (0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f,
 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f)
>>> lldb.frame.GetRegisters()[1].GetIndexOfChildWithName("v0")
63
```
GetChildAtIndex told us that v0 was at index 0, but looking up v0 by
name tells us it's at index 63. And it meant you could get a register
using ChildMemberWithName on a register set that did not contain the
register. For example get v0 (floating point) via the general purpose
register set (which does not contain v0).

To fix this I've changed both to look through only the register infos of
the registers within the set. This exposed some problems in existing
tests, which I've fixed and I've added a full test that checks the two
methods agree with each other.

That test is not checking the literal values (the integer values)
between registers because:
* Overlapping register names is very unlikely, so we are unlikely to
make a mistake with the values.
* Some registers have different types (floating point/vector and so on)
and I didn't want to complicate the test with N different comparison
functions.
* Keeping the test high level means it can run anywhere. Aside from the
alias check, which has to be target specific.

Added: 
    

Modified: 
    lldb/include/lldb/ValueObject/ValueObjectRegister.h
    lldb/source/ValueObject/ValueObjectRegister.cpp
    lldb/test/API/functionalities/postmortem/minidump-new/TestMiniDumpNew.py
    lldb/test/API/linux/aarch64/aarch32_compat/TestAArch64LinuxAArch32Compat.py
    lldb/test/API/python_api/value/TestValueAPI.py
    llvm/docs/ReleaseNotes.md

Removed: 
    


################################################################################
diff  --git a/lldb/include/lldb/ValueObject/ValueObjectRegister.h b/lldb/include/lldb/ValueObject/ValueObjectRegister.h
index 3db4b00bd1b15..57ee2eadce4fb 100644
--- a/lldb/include/lldb/ValueObject/ValueObjectRegister.h
+++ b/lldb/include/lldb/ValueObject/ValueObjectRegister.h
@@ -75,6 +75,9 @@ class ValueObjectRegisterSet : public ValueObject {
     return nullptr;
   }
 
+  std::optional<std::pair<size_t, const RegisterInfo *>>
+  LookupChildWithName(llvm::StringRef name);
+
   // For ValueObject only
   ValueObjectRegisterSet(const ValueObjectRegisterSet &) = delete;
   const ValueObjectRegisterSet &

diff  --git a/lldb/source/ValueObject/ValueObjectRegister.cpp b/lldb/source/ValueObject/ValueObjectRegister.cpp
index 0d6e54b39ac1d..4ddf1dfee64b0 100644
--- a/lldb/source/ValueObject/ValueObjectRegister.cpp
+++ b/lldb/source/ValueObject/ValueObjectRegister.cpp
@@ -125,28 +125,42 @@ ValueObject *ValueObjectRegisterSet::CreateChildAtIndex(size_t idx) {
   return nullptr;
 }
 
+std::optional<std::pair<size_t, const RegisterInfo *>>
+ValueObjectRegisterSet::LookupChildWithName(llvm::StringRef name) {
+  if (!m_reg_ctx_sp || !m_reg_set)
+    return {};
+
+  // See if the register exists at all in any set.
+  const RegisterInfo *reg_info = m_reg_ctx_sp->GetRegisterInfoByName(name);
+  if (!reg_info)
+    return {};
+
+  // See if this register is in this register set.
+  for (size_t i = 0; i < m_reg_set->num_registers; ++i) {
+    const RegisterInfo *contained_reg_info =
+        m_reg_ctx_sp->GetRegisterInfoAtIndex(m_reg_set->registers[i]);
+    if (contained_reg_info == reg_info)
+      return std::make_pair(i, reg_info);
+  }
+
+  return {};
+}
+
 lldb::ValueObjectSP
 ValueObjectRegisterSet::GetChildMemberWithName(llvm::StringRef name,
                                                bool can_create) {
-  ValueObject *valobj = nullptr;
-  if (m_reg_ctx_sp && m_reg_set) {
-    const RegisterInfo *reg_info = m_reg_ctx_sp->GetRegisterInfoByName(name);
-    if (reg_info != nullptr)
-      valobj = new ValueObjectRegister(*this, m_reg_ctx_sp, reg_info);
-  }
-  if (valobj)
-    return valobj->GetSP();
-  else
-    return ValueObjectSP();
+  if (auto maybe_child = LookupChildWithName(name))
+    return (new ValueObjectRegister(*this, m_reg_ctx_sp, maybe_child->second))
+        ->GetSP();
+
+  return {};
 }
 
 llvm::Expected<size_t>
 ValueObjectRegisterSet::GetIndexOfChildWithName(llvm::StringRef name) {
-  if (m_reg_ctx_sp && m_reg_set) {
-    const RegisterInfo *reg_info = m_reg_ctx_sp->GetRegisterInfoByName(name);
-    if (reg_info != nullptr)
-      return reg_info->kinds[eRegisterKindLLDB];
-  }
+  if (auto maybe_child = LookupChildWithName(name))
+    return maybe_child->first;
+
   return llvm::createStringErrorV("type has no child named '{0}'", name);
 }
 

diff  --git a/lldb/test/API/functionalities/postmortem/minidump-new/TestMiniDumpNew.py b/lldb/test/API/functionalities/postmortem/minidump-new/TestMiniDumpNew.py
index 4b7d24ef58e7e..36ca8c3d5754b 100644
--- a/lldb/test/API/functionalities/postmortem/minidump-new/TestMiniDumpNew.py
+++ b/lldb/test/API/functionalities/postmortem/minidump-new/TestMiniDumpNew.py
@@ -245,8 +245,8 @@ def test_arm64_registers(self):
             self.check_register_string_value(fpr, "d%i" % (i), d, lldb.eFormatHex)
             self.check_register_string_value(fpr, "s%i" % (i), s, lldb.eFormatHex)
             self.check_register_string_value(fpr, "h%i" % (i), h, lldb.eFormatHex)
-        self.check_register_unsigned(gpr, "fpsr", 0x55667788)
-        self.check_register_unsigned(gpr, "fpcr", 0x99AABBCC)
+        self.check_register_unsigned(fpr, "fpsr", 0x55667788)
+        self.check_register_unsigned(fpr, "fpcr", 0x99AABBCC)
 
     def verify_arm_registers(self, apple=False):
         """
@@ -265,7 +265,7 @@ def verify_arm_registers(self, apple=False):
         self.assertEqual(stop_description, "")
         registers = thread.GetFrameAtIndex(0).GetRegisters()
         # Verify the GPR registers are all correct
-        # Verify x0 - x31 register values
+        # Verify r0 - r15 register values
         gpr = registers.GetValueAtIndex(0)
         for i in range(1, 16):
             self.check_register_unsigned(gpr, "r%i" % (i), i + 1)
@@ -284,7 +284,7 @@ def verify_arm_registers(self, apple=False):
         # Verify the FPR registers are all correct
         fpr = registers.GetValueAtIndex(1)
         # Check d0 - d31
-        self.check_register_unsigned(gpr, "fpscr", 0x55667788AABBCCDD)
+        self.check_register_unsigned(fpr, "fpscr", 0x55667788AABBCCDD)
         for i in range(32):
             value = (i + 1) | (i + 1) << 8 | (i + 1) << 32 | (i + 1) << 48
             self.check_register_unsigned(fpr, "d%i" % (i), value)

diff  --git a/lldb/test/API/linux/aarch64/aarch32_compat/TestAArch64LinuxAArch32Compat.py b/lldb/test/API/linux/aarch64/aarch32_compat/TestAArch64LinuxAArch32Compat.py
index f104b8b7ed2af..b1d7c452abc79 100644
--- a/lldb/test/API/linux/aarch64/aarch32_compat/TestAArch64LinuxAArch32Compat.py
+++ b/lldb/test/API/linux/aarch64/aarch32_compat/TestAArch64LinuxAArch32Compat.py
@@ -92,23 +92,10 @@ def test_aarch32_compat(self):
 
         fpr = registers[1]
 
-        # FIXME: there is a bug with fpr register indexes where it seems to be
-        # counting the GPRs as part of itself:
-        # (Pdb) fpr.GetChildAtIndex(0)
-        # (float) s0 = 1.40129846E-45
-        # (Pdb) fpr.GetIndexOfChildWithName("s0")
-        # 17
-        # (Pdb) fpr.GetChildAtIndex(17)
-        # (float) s17 = 2.52233724E-44
-        #
-        # See https://github.com/llvm/llvm-project/issues/211787.
-        #
-        # So we will assume that index 0 is s0 and not go via name lookup for
-        # fpr.
-
-        expected_fpr = {}
+        # Check s0-s31.
         for n in range(32):
-            reg = fpr.GetChildAtIndex(n)
+            reg = fpr.GetChildMemberWithName(f"s{n}")
+            self.assertTrue(reg.IsValid())
             # We cannot call GetValueAsUnsigned on the value directly, as these
             # are floating point registers.
             error = lldb.SBError()

diff  --git a/lldb/test/API/python_api/value/TestValueAPI.py b/lldb/test/API/python_api/value/TestValueAPI.py
index 371a4b8a8a5ad..beb3a6fce65f7 100644
--- a/lldb/test/API/python_api/value/TestValueAPI.py
+++ b/lldb/test/API/python_api/value/TestValueAPI.py
@@ -281,3 +281,88 @@ def test(self):
         self.assertEqual(
             a_null_int_ptr.Dereference().GetLoadAddress(), lldb.LLDB_INVALID_ADDRESS
         )
+
+    @no_debug_info_test
+    def test_register(self):
+        """
+        Test SBValue APIs when the values are backed by registers.
+        """
+        d = {"EXE": self.exe_name}
+        self.build(dictionary=d)
+        self.setTearDownCleanup(dictionary=d)
+        exe = self.getBuildArtifact(self.exe_name)
+
+        target = self.dbg.CreateTarget(exe)
+        self.assertTrue(target, VALID_TARGET)
+
+        breakpoint = target.BreakpointCreateByLocation("main.c", self.line)
+        self.assertTrue(breakpoint, VALID_BREAKPOINT)
+
+        process = target.LaunchSimple(None, None, self.get_process_working_directory())
+        self.assertTrue(process, PROCESS_IS_VALID)
+
+        self.assertState(process.GetState(), lldb.eStateStopped)
+        thread = lldbutil.get_stopped_thread(process, lldb.eStopReasonBreakpoint)
+        self.assertTrue(
+            thread.IsValid(),
+            "There should be a thread stopped due to breakpoint condition",
+        )
+        frame = thread.GetFrameAtIndex(0)
+
+        register_sets = frame.GetRegisters()
+        for set_idx in range(register_sets.GetSize()):
+            reg_set = register_sets.GetValueAtIndex(set_idx)
+            num_registers = reg_set.GetNumChildren()
+
+            for child_idx in range(num_registers):
+                reg_value = reg_set.GetChildAtIndex(child_idx)
+                self.assertTrue(reg_value.IsValid())
+                reg_name = reg_value.GetName()
+
+                if (
+                    self.getArchitecture() in ["amd64", "i386", "x86_64"]
+                    and reg_name == "sp"
+                ):
+                    # x86 has "rsp", and "sp" which is a subset of "rsp". Then there is
+                    # the ABI name "sp", which LLDB resolves to "rsp", not to the
+                    # architectural register "sp".
+                    # See https://github.com/llvm/llvm-project/issues/212778.
+                    sp_with_name_index = reg_set.GetIndexOfChildWithName(reg_name)
+                    self.assertTrue(sp_with_name_index < num_registers)
+                    rsp_with_name_index = reg_set.GetIndexOfChildWithName("rsp")
+                    self.assertTrue(rsp_with_name_index < num_registers)
+                    self.assertEqual(sp_with_name_index, rsp_with_name_index)
+
+                    continue
+
+                # GetIndexOfChildWithName should return the same index.
+                child_with_name_index = reg_set.GetIndexOfChildWithName(reg_name)
+                self.assertTrue(child_with_name_index < num_registers)
+                self.assertEqual(child_idx, child_with_name_index)
+
+                # GetChildMemberWithName should return a value with a matching name.
+                child_member = reg_set.GetChildMemberWithName(reg_name)
+                self.assertTrue(child_member.IsValid())
+                self.assertEqual(reg_name, child_member.GetName())
+
+                # That lookup should be case insensitive.
+                child_member = reg_set.GetChildMemberWithName(reg_name.swapcase())
+                self.assertTrue(child_member.IsValid())
+                # Note that the value's name is the one lldb uses, not the
+                # 
diff erently cased one used to get it.
+                self.assertEqual(reg_name, child_member.GetName())
+
+        if self.isAArch64():
+            # Name lookup also checks register aliases and is case insensitive.
+            gpr = register_sets.GetValueAtIndex(0)
+
+            # x30 is the link register. LLDB has lr as the primary name and x30
+            # as the alias.
+            lr = gpr.GetChildMemberWithName("lR")
+            self.assertTrue(lr.IsValid())
+            self.assertEqual("lr", lr.GetName())
+
+            x30 = gpr.GetChildMemberWithName("X30")
+            self.assertTrue(x30.IsValid())
+            # Note that the SBValue's name is primary name not the alias.
+            self.assertEqual("lr", x30.GetName())

diff  --git a/llvm/docs/ReleaseNotes.md b/llvm/docs/ReleaseNotes.md
index f87f791a49e6f..f84093e3c1618 100644
--- a/llvm/docs/ReleaseNotes.md
+++ b/llvm/docs/ReleaseNotes.md
@@ -137,6 +137,17 @@ Makes programs 10x faster by doing Special New Thing.
 
 ### Changes to LLDB
 
+#### SBAPI
+
+* A [bug](https://github.com/llvm/llvm-project/issues/211787) involving SBValues
+  representing a register set was fixed. The methods `GetIndexOfChildWithName`
+  and `GetChildMemberWithName` were incorrectly looking up values in all
+  register sets. This meant that `GetIndexOfChildWithName` could return an index
+  greater than the size of the set, and that `GetChildMemberWithName` could
+  return values that were actually in a 
diff erent set. Both methods are now fixed
+  so that they are limited to the registers within the register set. Scripts
+  using these methods may have to be updated as a result.
+
 #### Windows
 
 * Python 3.11 or later is now required for building LLDB 24 on Windows.


        


More information about the llvm-commits mailing list