From 442f99d7698a4ca87ebb16cb22df610c45d4698a Mon Sep 17 00:00:00 2001 From: Igor Kudrin Date: Fri, 27 Jun 2025 14:30:24 -0700 Subject: [PATCH] [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: :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`. --- lldb/include/lldb/Expression/IRMemoryMap.h | 7 ++- lldb/source/Expression/IRMemoryMap.cpp | 11 ++-- lldb/source/Expression/Materializer.cpp | 52 +++++++++--------- .../postmortem/elf-core/expr/TestExpr.py | 52 ++++++++++++++++++ .../elf-core/expr/linux-x86_64.core | Bin 0 -> 40960 bytes .../postmortem/elf-core/expr/linux-x86_64.out | Bin 0 -> 10816 bytes .../postmortem/elf-core/expr/main.cpp | 15 +++++ .../API/lang/cpp/char8_t/TestCxxChar8_t.py | 4 +- 8 files changed, 105 insertions(+), 36 deletions(-) create mode 100644 lldb/test/API/functionalities/postmortem/elf-core/expr/TestExpr.py create mode 100644 lldb/test/API/functionalities/postmortem/elf-core/expr/linux-x86_64.core create mode 100755 lldb/test/API/functionalities/postmortem/elf-core/expr/linux-x86_64.out create mode 100644 lldb/test/API/functionalities/postmortem/elf-core/expr/main.cpp diff --git a/lldb/include/lldb/Expression/IRMemoryMap.h b/lldb/include/lldb/Expression/IRMemoryMap.h index acbffd1e40b9..58b95c56c1c3 100644 --- a/lldb/include/lldb/Expression/IRMemoryMap.h +++ b/lldb/include/lldb/Expression/IRMemoryMap.h @@ -51,10 +51,13 @@ public: ///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 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 f500272cfb30..150699352a2e 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 IRMemoryMap::Malloc(size_t size, uint8_t alignment, - uint32_t permissions, - AllocationPolicy policy, - bool zero_memory) { +llvm::Expected +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 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 96add56f9218..17ea1596806d 100644 --- a/lldb/source/Expression/Materializer.cpp +++ b/lldb/source/Expression/Materializer.cpp @@ -75,11 +75,12 @@ public: // 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 @@ public: 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 @@ public: 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 @@ public: 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 @@ public: 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 000000000000..dd03a0cc836a --- /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 0000000000000000000000000000000000000000..3bd2916c64e9ff9facc4626ab53d785c47c6dcdf GIT binary patch literal 40960 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..33e60f025d210798b0ca9425211c301eabe42da0 GIT binary patch literal 10816 zcmeHN&u<$=6n?YowViC7I87l*1I?zbQA(xF4^l{JXk8MwZbbtLN)@4&tsQR~OR*i< z>nKo4i-JH=K#;$H9yoA@OSy0Ytz1bOOF`*O*^)w^BdGCGi zdo#PAXKh8<%c&QRYm9*$8XN(h#giQJXMp4rl7TS{gMo7#S~xefRl=y&!O>~4wDj1- z2f+qKA_uXb^oyJYAid;KAK%Xs1S{zW!e14z^s?`z+g|Q|efvb8^qh;>1onkR?LnNp zod@A6j#5A=pcGIFCE$2e>Gv=F@df=serFd> z=^Lqax`Om-YW*wdOD}&JKX<*Qdkmn)U%9lgQoWmAy|l4fy?f^oB6HTP-O~Lw^}-fk za#I_tr|+&r&t2zrH7Ny@0!jg;fKosypcGIFC>u0y>XA%Dn>yHNO6MlUvTP=E3Yi2%EykIT5aumW*h(s<@ zSSA2MMkEBxFnFdzI09OP-bxT<9XmB@)rN3t01|)|Q%o!XT!tc?LxIKBh)uurs%CHnWoTYmKFG7T=O@4y=_W5^aW_D7VMk zyArKzYHy|8YsbrKfs<``qx(Ql95GE3Z>WtuZR!creAYaSsS(4<$u>%cF%8>HPf#+_ zd9)KCVqp6a22l#|25r{bwIiJD@JU2z90ESzADFmH$Yc0lZ7NO+H{mVBx^BYV0#fGQueVrHy;Z(&hUs~nbTYK^P=8nbTod?%ezfm z)3E(Np2I>WU$kcy7NE~7Ec6Y)%v`1n`C`#6!(wJ0BM(mU?8T~wm{ajGWe=QJ#t(8} zbTF0Y87J|q#EIEbsQ}(wzQP|m5>DQ@yft|XV&mF{(0L+0>4{q41AjngJvyGVB(LCv zqYrHu?I_w9+9|Ygv`MsSv{%twvFiwDZix!LLkCtsW`$vBhEmfb~}&F71*jB#lhW^%Ni-%J#| z@LFCjUYO5Uyo#)2$tlgwR$PSsm(hi*&>5+{VxV z`RP6ZOGigPIOxD*|AOrA75%-I&sqwSG3LK40qhXQv_GAz;J9k}8Lb~2#HAtq^MRNb z#Pp5j2fqbDjAfsM>EW!$sCKi*XI%4LZLa6iZ*M(D(=n29kovjkw}Rj{+{*&1-~X2AuRkwxFX=znW=-1i2hmUe4G9j}C$Wz( zsK=;=zqfqQmuPU*b19c+FUU)a{&fL1qo=bg&tHeY8_??_4!YrlpG^cojO+JrMQpqN L7196czx4kJjnh(U literal 0 HcmV?d00001 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 000000000000..32a9c63e9b71 --- /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 4eb5351eefc8..08f09b317b21 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 @@ class CxxChar8_tTestCase(TestBase): 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):