Author: David Spickett
Date: 2025-08-13T14:58:29+01:00
New Revision: b3396c5e96f366ba85daf5f6404f18eb8a467aea

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

LOG: [lldb] Account for registers being host endian when casting values 
(#150011)

Fixes https://github.com/llvm/llvm-project/issues/135707

Follow up to https://github.com/llvm/llvm-project/pull/148836
which fixed some of this issue but not all of it. 

Our Value/ValueObject system does not store the endian directly
in the values. Instead it assumes that the endian of the result
of a cast can be assumed to be the target's endian, or the host
but only as a fallback. It assumes the place it is copying from
is also that endian.

This breaks down when you have register values. These are always
host endian and continue to be when cast. Casting them to big 
endian when on a little endian host breaks certain calls like
GetValueAsUnsigned.

To fix this, check the context of the value. If it has a register
context, always treat it as host endian and make the result host
endian.

I had an alternative where I passed an "is_register" flag into
all calls to this, but it felt like a layering violation and changed
many more lines.

This solution isn't much more robust, but it works for all the test
cases I know of. Perhaps you can create a register value without
a RegisterInfo backing it, but I don't know of a way myself.

For testing, I had to add a minimal program file for each arch
so that there is a type system to support the casting. This is
generated from YAML since we only need the machine and endian
to be set.

Added: 
    

Modified: 
    lldb/source/Core/Value.cpp
    lldb/test/API/commands/expression/TestRegisterExpressionEndian.py

Removed: 
    


################################################################################
diff  --git a/lldb/source/Core/Value.cpp b/lldb/source/Core/Value.cpp
index 028f0587c5790..86327e3a57334 100644
--- a/lldb/source/Core/Value.cpp
+++ b/lldb/source/Core/Value.cpp
@@ -488,9 +488,11 @@ Status Value::GetValueAsData(ExecutionContext *exe_ctx, 
DataExtractor &data,
     address = m_value.ULongLong(LLDB_INVALID_ADDRESS);
     address_type = eAddressTypeHost;
     if (exe_ctx) {
-      Target *target = exe_ctx->GetTargetPtr();
-      if (target) {
-        data.SetByteOrder(target->GetArchitecture().GetByteOrder());
+      if (Target *target = exe_ctx->GetTargetPtr()) {
+        // Registers are always stored in host endian.
+        data.SetByteOrder(m_context_type == ContextType::RegisterInfo
+                              ? endian::InlHostByteOrder()
+                              : target->GetArchitecture().GetByteOrder());
         
data.SetAddressByteSize(target->GetArchitecture().GetAddressByteSize());
         break;
       }

diff  --git a/lldb/test/API/commands/expression/TestRegisterExpressionEndian.py 
b/lldb/test/API/commands/expression/TestRegisterExpressionEndian.py
index 66e38df3a9696..d6de8731385b6 100644
--- a/lldb/test/API/commands/expression/TestRegisterExpressionEndian.py
+++ b/lldb/test/API/commands/expression/TestRegisterExpressionEndian.py
@@ -40,9 +40,15 @@ def readRegisters(self):
 
 class TestXMLRegisterFlags(GDBRemoteTestBase):
     def do_endian_test(self, endian):
-        architecture, pc_reg_name = {
-            Endian.BIG: ("s390x", "pswa"),
-            Endian.LITTLE: ("aarch64", "pc"),
+        architecture, pc_reg_name, yaml_file, data, machine = {
+            Endian.BIG: ("s390x", "pswa", "s390x.yaml", "ELFDATA2MSB", 
"EM_S390"),
+            Endian.LITTLE: (
+                "aarch64",
+                "pc",
+                "aarch64.yaml",
+                "ELFDATA2LSB",
+                "EM_AARCH64",
+            ),
         }[endian]
 
         self.server.responder = Responder(
@@ -58,14 +64,35 @@ def do_endian_test(self, endian):
             ),
             endian,
         )
-        target = self.dbg.CreateTarget("")
+
+        # We need to have a program file, so that we have a full type system,
+        # so that we can do the casts later.
+        obj_path = self.getBuildArtifact("main.o")
+        yaml_path = self.getBuildArtifact(yaml_file)
+        with open(yaml_path, "w") as f:
+            f.write(
+                dedent(
+                    f"""\
+                --- !ELF
+                FileHeader:
+                  Class:    ELFCLASS64
+                  Data:     {data}
+                  Type:     ET_REL
+                  Machine:  {machine}
+                ...
+                """
+                )
+            )
+        self.yaml2obj(yaml_path, obj_path)
+        target = self.dbg.CreateTarget(obj_path)
+
         process = self.connect(target)
         lldbutil.expect_state_changes(
             self, self.dbg.GetListener(), process, [lldb.eStateStopped]
         )
 
         # If expressions convert register values into target endian, the
-        # result of register read and expr should be the same.
+        # result of register read, expr and casts should be the same.
         pc_value = "0x0000000000001234"
         self.expect(
             "register read pc",
@@ -73,14 +100,29 @@ def do_endian_test(self, endian):
         )
         self.expect("expr --format hex -- $pc", substrs=[pc_value])
 
+        pc = (
+            process.thread[0]
+            .frame[0]
+            .GetRegisters()
+            .GetValueAtIndex(0)
+            .GetChildMemberWithName("pc")
+        )
+        ull = target.FindTypes("unsigned long long").GetTypeAtIndex(0)
+        pc_ull = pc.Cast(ull)
+
+        self.assertEqual(pc.GetValue(), pc_ull.GetValue())
+        self.assertEqual(pc.GetValueAsAddress(), pc_ull.GetValueAsAddress())
+        self.assertEqual(pc.GetValueAsSigned(), pc_ull.GetValueAsSigned())
+        self.assertEqual(pc.GetValueAsUnsigned(), pc_ull.GetValueAsUnsigned())
+
     @skipIfXmlSupportMissing
     @skipIfRemote
+    @skipIfLLVMTargetMissing("AArch64")
     def test_little_endian_target(self):
         self.do_endian_test(Endian.LITTLE)
 
     @skipIfXmlSupportMissing
     @skipIfRemote
-    # Unlike AArch64, we do need the backend present for this test to work.
     @skipIfLLVMTargetMissing("SystemZ")
     def test_big_endian_target(self):
         self.do_endian_test(Endian.BIG)


        
_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to