Author: Igor Kudrin
Date: 2025-06-27T14:30:24-07:00
New Revision: 442f99d7698a4ca87ebb16cb22df610c45d4698a

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

LOG: [lldb] Fix evaluating expressions without JIT in an object context 
(#145599)

If a server does not support allocating memory in an inferior process or
when debugging a core file, evaluating an expression in the context of a
value object results in an error:

```
error: <lldb wrapper prefix>:43:1: use of undeclared identifier '$__lldb_class'
   43 | $__lldb_class::$__lldb_expr(void *$__lldb_arg)
      | ^
```

Such expressions require a live address to be stored in the value
object. However, `EntityResultVariable::Dematerialize()` only sets
`ret->m_live_sp` if JIT is available, even if the address points to the
process memory and no custom allocations were made. Similarly,
`EntityPersistentVariable::Dematerialize()` tries to deallocate memory
based on the same check, resulting in an error if the memory was not
previously allocated in `EntityPersistentVariable::Materialize()`.

As an unintended bonus, the patch also fixes a FIXME case in
`TestCxxChar8_t.py`.

Added: 
    lldb/test/API/functionalities/postmortem/elf-core/expr/TestExpr.py
    lldb/test/API/functionalities/postmortem/elf-core/expr/linux-x86_64.core
    lldb/test/API/functionalities/postmortem/elf-core/expr/linux-x86_64.out
    lldb/test/API/functionalities/postmortem/elf-core/expr/main.cpp

Modified: 
    lldb/include/lldb/Expression/IRMemoryMap.h
    lldb/source/Expression/IRMemoryMap.cpp
    lldb/source/Expression/Materializer.cpp
    lldb/test/API/lang/cpp/char8_t/TestCxxChar8_t.py

Removed: 
    


################################################################################
diff  --git a/lldb/include/lldb/Expression/IRMemoryMap.h 
b/lldb/include/lldb/Expression/IRMemoryMap.h
index acbffd1e40b94..58b95c56c1c36 100644
--- a/lldb/include/lldb/Expression/IRMemoryMap.h
+++ b/lldb/include/lldb/Expression/IRMemoryMap.h
@@ -51,10 +51,13 @@ class IRMemoryMap {
                                  ///only in the process.
   };
 
+  // If 'policy' is 'eAllocationPolicyMirror' but it is impossible to allocate
+  // memory in the process, 'eAllocationPolicyHostOnly' will be used instead.
+  // The actual policy is returned via 'used_policy'.
   llvm::Expected<lldb::addr_t> Malloc(size_t size, uint8_t alignment,
                                       uint32_t permissions,
-                                      AllocationPolicy policy,
-                                      bool zero_memory);
+                                      AllocationPolicy policy, bool 
zero_memory,
+                                      AllocationPolicy *used_policy = nullptr);
   void Leak(lldb::addr_t process_address, Status &error);
   void Free(lldb::addr_t process_address, Status &error);
 

diff  --git a/lldb/source/Expression/IRMemoryMap.cpp 
b/lldb/source/Expression/IRMemoryMap.cpp
index f500272cfb302..150699352a2e3 100644
--- a/lldb/source/Expression/IRMemoryMap.cpp
+++ b/lldb/source/Expression/IRMemoryMap.cpp
@@ -319,10 +319,10 @@ IRMemoryMap::Allocation::Allocation(lldb::addr_t 
process_alloc,
   }
 }
 
-llvm::Expected<lldb::addr_t> IRMemoryMap::Malloc(size_t size, uint8_t 
alignment,
-                                                 uint32_t permissions,
-                                                 AllocationPolicy policy,
-                                                 bool zero_memory) {
+llvm::Expected<lldb::addr_t>
+IRMemoryMap::Malloc(size_t size, uint8_t alignment, uint32_t permissions,
+                    AllocationPolicy policy, bool zero_memory,
+                    AllocationPolicy *used_policy) {
   lldb_private::Log *log(GetLog(LLDBLog::Expressions));
 
   lldb::ProcessSP process_sp;
@@ -454,6 +454,9 @@ llvm::Expected<lldb::addr_t> IRMemoryMap::Malloc(size_t 
size, uint8_t alignment,
               (uint64_t)permissions, policy_string, aligned_address);
   }
 
+  if (used_policy)
+    *used_policy = policy;
+
   return aligned_address;
 }
 

diff  --git a/lldb/source/Expression/Materializer.cpp 
b/lldb/source/Expression/Materializer.cpp
index 96add56f92180..17ea1596806d0 100644
--- a/lldb/source/Expression/Materializer.cpp
+++ b/lldb/source/Expression/Materializer.cpp
@@ -75,11 +75,12 @@ class EntityPersistentVariable : public 
Materializer::Entity {
     // contents.
 
     const bool zero_memory = false;
+    IRMemoryMap::AllocationPolicy used_policy;
     auto address_or_error = map.Malloc(
         llvm::expectedToOptional(m_persistent_variable_sp->GetByteSize())
             .value_or(0),
         8, lldb::ePermissionsReadable | lldb::ePermissionsWritable,
-        IRMemoryMap::eAllocationPolicyMirror, zero_memory);
+        IRMemoryMap::eAllocationPolicyMirror, zero_memory, &used_policy);
     if (!address_or_error) {
       err = Status::FromErrorStringWithFormat(
           "couldn't allocate a memory area to store %s: %s",
@@ -101,14 +102,22 @@ class EntityPersistentVariable : public 
Materializer::Entity {
         m_persistent_variable_sp->GetName(), mem, eAddressTypeLoad,
         map.GetAddressByteSize());
 
-    // Clear the flag if the variable will never be deallocated.
-
     if (m_persistent_variable_sp->m_flags &
         ExpressionVariable::EVKeepInTarget) {
-      Status leak_error;
-      map.Leak(mem, leak_error);
-      m_persistent_variable_sp->m_flags &=
-          ~ExpressionVariable::EVNeedsAllocation;
+      if (used_policy == IRMemoryMap::eAllocationPolicyMirror) {
+        // Clear the flag if the variable will never be deallocated.
+        Status leak_error;
+        map.Leak(mem, leak_error);
+        m_persistent_variable_sp->m_flags &=
+            ~ExpressionVariable::EVNeedsAllocation;
+      } else {
+        // If the variable cannot be kept in target, clear this flag...
+        m_persistent_variable_sp->m_flags &=
+            ~ExpressionVariable::EVKeepInTarget;
+        // ...and set the flag to copy the value during dematerialization.
+        m_persistent_variable_sp->m_flags |=
+            ExpressionVariable::EVNeedsFreezeDry;
+      }
     }
 
     // Write the contents of the variable to the area.
@@ -327,22 +336,10 @@ class EntityPersistentVariable : public 
Materializer::Entity {
       return;
     }
 
-    lldb::ProcessSP process_sp =
-        map.GetBestExecutionContextScope()->CalculateProcess();
-    if (!process_sp || !process_sp->CanJIT()) {
-      // Allocations are not persistent so persistent variables cannot stay
-      // materialized.
-
-      m_persistent_variable_sp->m_flags |=
-          ExpressionVariable::EVNeedsAllocation;
-
-      DestroyAllocation(map, err);
-      if (!err.Success())
-        return;
-    } else if (m_persistent_variable_sp->m_flags &
-                   ExpressionVariable::EVNeedsAllocation &&
-               !(m_persistent_variable_sp->m_flags &
-                 ExpressionVariable::EVKeepInTarget)) {
+    if (m_persistent_variable_sp->m_flags &
+            ExpressionVariable::EVNeedsAllocation &&
+        !(m_persistent_variable_sp->m_flags &
+          ExpressionVariable::EVKeepInTarget)) {
       DestroyAllocation(map, err);
       if (!err.Success())
         return;
@@ -1082,9 +1079,8 @@ class EntityResultVariable : public Materializer::Entity {
       m_delegate->DidDematerialize(ret);
     }
 
-    bool can_persist =
-        (m_is_program_reference && process_sp && process_sp->CanJIT() &&
-         !(address >= frame_bottom && address < frame_top));
+    bool can_persist = m_is_program_reference &&
+                       !(address >= frame_bottom && address < frame_top);
 
     if (can_persist && m_keep_in_memory) {
       ret->m_live_sp = ValueObjectConstResult::Create(exe_scope, m_type, name,
@@ -1114,7 +1110,9 @@ class EntityResultVariable : public Materializer::Entity {
         map.Free(m_temporary_allocation, free_error);
       }
     } else {
-      ret->m_flags |= ExpressionVariable::EVIsLLDBAllocated;
+      ret->m_flags |= m_is_program_reference
+                          ? ExpressionVariable::EVIsProgramReference
+                          : ExpressionVariable::EVIsLLDBAllocated;
     }
 
     m_temporary_allocation = LLDB_INVALID_ADDRESS;

diff  --git 
a/lldb/test/API/functionalities/postmortem/elf-core/expr/TestExpr.py 
b/lldb/test/API/functionalities/postmortem/elf-core/expr/TestExpr.py
new file mode 100644
index 0000000000000..dd03a0cc836a7
--- /dev/null
+++ b/lldb/test/API/functionalities/postmortem/elf-core/expr/TestExpr.py
@@ -0,0 +1,52 @@
+"""
+Test evaluating expressions when debugging core file.
+"""
+
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+@skipIfLLVMTargetMissing("X86")
+class CoreExprTestCase(TestBase):
+    def setUp(self):
+        TestBase.setUp(self)
+        self.target = self.dbg.CreateTarget("linux-x86_64.out")
+        self.process = self.target.LoadCore("linux-x86_64.core")
+        self.assertTrue(self.process, PROCESS_IS_VALID)
+
+    def test_result_var(self):
+        """Test that the result variable can be used in subsequent 
expressions."""
+
+        self.expect_expr(
+            "outer",
+            result_type="Outer",
+            result_children=[ValueCheck(name="inner", type="Inner")],
+        )
+        self.expect_expr(
+            "$0.inner",
+            result_type="Inner",
+            result_children=[ValueCheck(name="val", type="int", value="5")],
+        )
+        self.expect_expr("$1.val", result_type="int", result_value="5")
+
+    def test_persist_var(self):
+        """Test that user-defined variables can be used in subsequent 
expressions."""
+
+        self.target.EvaluateExpression("int $my_int = 5")
+        self.expect_expr("$my_int * 2", result_type="int", result_value="10")
+
+    def test_context_object(self):
+        """Test expression evaluation in context of an object."""
+
+        val_outer = self.expect_expr("outer", result_type="Outer")
+
+        val_inner = val_outer.EvaluateExpression("inner")
+        self.assertTrue(val_inner.IsValid())
+        self.assertEqual("Inner", val_inner.GetDisplayTypeName())
+
+        val_val = val_inner.EvaluateExpression("this->val")
+        self.assertTrue(val_val.IsValid())
+        self.assertEqual("int", val_val.GetDisplayTypeName())
+        self.assertEqual(val_val.GetValueAsSigned(), 5)

diff  --git 
a/lldb/test/API/functionalities/postmortem/elf-core/expr/linux-x86_64.core 
b/lldb/test/API/functionalities/postmortem/elf-core/expr/linux-x86_64.core
new file mode 100644
index 0000000000000..3bd2916c64e9f
Binary files /dev/null and 
b/lldb/test/API/functionalities/postmortem/elf-core/expr/linux-x86_64.core 
diff er

diff  --git 
a/lldb/test/API/functionalities/postmortem/elf-core/expr/linux-x86_64.out 
b/lldb/test/API/functionalities/postmortem/elf-core/expr/linux-x86_64.out
new file mode 100755
index 0000000000000..33e60f025d210
Binary files /dev/null and 
b/lldb/test/API/functionalities/postmortem/elf-core/expr/linux-x86_64.out 
diff er

diff  --git a/lldb/test/API/functionalities/postmortem/elf-core/expr/main.cpp 
b/lldb/test/API/functionalities/postmortem/elf-core/expr/main.cpp
new file mode 100644
index 0000000000000..32a9c63e9b716
--- /dev/null
+++ b/lldb/test/API/functionalities/postmortem/elf-core/expr/main.cpp
@@ -0,0 +1,15 @@
+struct Inner {
+  Inner(int val) : val(val) {}
+  int val;
+};
+
+struct Outer {
+  Outer(int val) : inner(val) {}
+  Inner inner;
+};
+
+extern "C" void _start(void) {
+  Outer outer(5);
+  char *boom = (char *)0;
+  *boom = 47;
+}

diff  --git a/lldb/test/API/lang/cpp/char8_t/TestCxxChar8_t.py 
b/lldb/test/API/lang/cpp/char8_t/TestCxxChar8_t.py
index 4eb5351eefc82..08f09b317b217 100644
--- a/lldb/test/API/lang/cpp/char8_t/TestCxxChar8_t.py
+++ b/lldb/test/API/lang/cpp/char8_t/TestCxxChar8_t.py
@@ -24,9 +24,7 @@ def test_without_process(self):
 
         self.expect_expr("a", result_type="char8_t", result_summary="0x61 
u8'a'")
         self.expect_expr("ab", result_type="const char8_t *", 
result_summary='u8"你好"')
-
-        # FIXME: This should work too.
-        self.expect("expr abc", substrs=['u8"你好"'], matching=False)
+        self.expect_expr("abc", result_type="char8_t[9]", 
result_summary='u8"你好"')
 
     @skipIf(compiler="clang", compiler_version=["<", "7.0"])
     def test_with_process(self):


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

Reply via email to