Skip to content

Commit 442f99d

Browse files
authored
[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`.
1 parent 9f5061d commit 442f99d

File tree

8 files changed

+105
-36
lines changed

8 files changed

+105
-36
lines changed

lldb/include/lldb/Expression/IRMemoryMap.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,13 @@ class IRMemoryMap {
5151
///only in the process.
5252
};
5353

54+
// If 'policy' is 'eAllocationPolicyMirror' but it is impossible to allocate
55+
// memory in the process, 'eAllocationPolicyHostOnly' will be used instead.
56+
// The actual policy is returned via 'used_policy'.
5457
llvm::Expected<lldb::addr_t> Malloc(size_t size, uint8_t alignment,
5558
uint32_t permissions,
56-
AllocationPolicy policy,
57-
bool zero_memory);
59+
AllocationPolicy policy, bool zero_memory,
60+
AllocationPolicy *used_policy = nullptr);
5861
void Leak(lldb::addr_t process_address, Status &error);
5962
void Free(lldb::addr_t process_address, Status &error);
6063

lldb/source/Expression/IRMemoryMap.cpp

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -319,10 +319,10 @@ IRMemoryMap::Allocation::Allocation(lldb::addr_t process_alloc,
319319
}
320320
}
321321

322-
llvm::Expected<lldb::addr_t> IRMemoryMap::Malloc(size_t size, uint8_t alignment,
323-
uint32_t permissions,
324-
AllocationPolicy policy,
325-
bool zero_memory) {
322+
llvm::Expected<lldb::addr_t>
323+
IRMemoryMap::Malloc(size_t size, uint8_t alignment, uint32_t permissions,
324+
AllocationPolicy policy, bool zero_memory,
325+
AllocationPolicy *used_policy) {
326326
lldb_private::Log *log(GetLog(LLDBLog::Expressions));
327327

328328
lldb::ProcessSP process_sp;
@@ -454,6 +454,9 @@ llvm::Expected<lldb::addr_t> IRMemoryMap::Malloc(size_t size, uint8_t alignment,
454454
(uint64_t)permissions, policy_string, aligned_address);
455455
}
456456

457+
if (used_policy)
458+
*used_policy = policy;
459+
457460
return aligned_address;
458461
}
459462

lldb/source/Expression/Materializer.cpp

Lines changed: 25 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -75,11 +75,12 @@ class EntityPersistentVariable : public Materializer::Entity {
7575
// contents.
7676

7777
const bool zero_memory = false;
78+
IRMemoryMap::AllocationPolicy used_policy;
7879
auto address_or_error = map.Malloc(
7980
llvm::expectedToOptional(m_persistent_variable_sp->GetByteSize())
8081
.value_or(0),
8182
8, lldb::ePermissionsReadable | lldb::ePermissionsWritable,
82-
IRMemoryMap::eAllocationPolicyMirror, zero_memory);
83+
IRMemoryMap::eAllocationPolicyMirror, zero_memory, &used_policy);
8384
if (!address_or_error) {
8485
err = Status::FromErrorStringWithFormat(
8586
"couldn't allocate a memory area to store %s: %s",
@@ -101,14 +102,22 @@ class EntityPersistentVariable : public Materializer::Entity {
101102
m_persistent_variable_sp->GetName(), mem, eAddressTypeLoad,
102103
map.GetAddressByteSize());
103104

104-
// Clear the flag if the variable will never be deallocated.
105-
106105
if (m_persistent_variable_sp->m_flags &
107106
ExpressionVariable::EVKeepInTarget) {
108-
Status leak_error;
109-
map.Leak(mem, leak_error);
110-
m_persistent_variable_sp->m_flags &=
111-
~ExpressionVariable::EVNeedsAllocation;
107+
if (used_policy == IRMemoryMap::eAllocationPolicyMirror) {
108+
// Clear the flag if the variable will never be deallocated.
109+
Status leak_error;
110+
map.Leak(mem, leak_error);
111+
m_persistent_variable_sp->m_flags &=
112+
~ExpressionVariable::EVNeedsAllocation;
113+
} else {
114+
// If the variable cannot be kept in target, clear this flag...
115+
m_persistent_variable_sp->m_flags &=
116+
~ExpressionVariable::EVKeepInTarget;
117+
// ...and set the flag to copy the value during dematerialization.
118+
m_persistent_variable_sp->m_flags |=
119+
ExpressionVariable::EVNeedsFreezeDry;
120+
}
112121
}
113122

114123
// Write the contents of the variable to the area.
@@ -327,22 +336,10 @@ class EntityPersistentVariable : public Materializer::Entity {
327336
return;
328337
}
329338

330-
lldb::ProcessSP process_sp =
331-
map.GetBestExecutionContextScope()->CalculateProcess();
332-
if (!process_sp || !process_sp->CanJIT()) {
333-
// Allocations are not persistent so persistent variables cannot stay
334-
// materialized.
335-
336-
m_persistent_variable_sp->m_flags |=
337-
ExpressionVariable::EVNeedsAllocation;
338-
339-
DestroyAllocation(map, err);
340-
if (!err.Success())
341-
return;
342-
} else if (m_persistent_variable_sp->m_flags &
343-
ExpressionVariable::EVNeedsAllocation &&
344-
!(m_persistent_variable_sp->m_flags &
345-
ExpressionVariable::EVKeepInTarget)) {
339+
if (m_persistent_variable_sp->m_flags &
340+
ExpressionVariable::EVNeedsAllocation &&
341+
!(m_persistent_variable_sp->m_flags &
342+
ExpressionVariable::EVKeepInTarget)) {
346343
DestroyAllocation(map, err);
347344
if (!err.Success())
348345
return;
@@ -1082,9 +1079,8 @@ class EntityResultVariable : public Materializer::Entity {
10821079
m_delegate->DidDematerialize(ret);
10831080
}
10841081

1085-
bool can_persist =
1086-
(m_is_program_reference && process_sp && process_sp->CanJIT() &&
1087-
!(address >= frame_bottom && address < frame_top));
1082+
bool can_persist = m_is_program_reference &&
1083+
!(address >= frame_bottom && address < frame_top);
10881084

10891085
if (can_persist && m_keep_in_memory) {
10901086
ret->m_live_sp = ValueObjectConstResult::Create(exe_scope, m_type, name,
@@ -1114,7 +1110,9 @@ class EntityResultVariable : public Materializer::Entity {
11141110
map.Free(m_temporary_allocation, free_error);
11151111
}
11161112
} else {
1117-
ret->m_flags |= ExpressionVariable::EVIsLLDBAllocated;
1113+
ret->m_flags |= m_is_program_reference
1114+
? ExpressionVariable::EVIsProgramReference
1115+
: ExpressionVariable::EVIsLLDBAllocated;
11181116
}
11191117

11201118
m_temporary_allocation = LLDB_INVALID_ADDRESS;
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
"""
2+
Test evaluating expressions when debugging core file.
3+
"""
4+
5+
import lldb
6+
from lldbsuite.test.decorators import *
7+
from lldbsuite.test.lldbtest import *
8+
from lldbsuite.test import lldbutil
9+
10+
11+
@skipIfLLVMTargetMissing("X86")
12+
class CoreExprTestCase(TestBase):
13+
def setUp(self):
14+
TestBase.setUp(self)
15+
self.target = self.dbg.CreateTarget("linux-x86_64.out")
16+
self.process = self.target.LoadCore("linux-x86_64.core")
17+
self.assertTrue(self.process, PROCESS_IS_VALID)
18+
19+
def test_result_var(self):
20+
"""Test that the result variable can be used in subsequent expressions."""
21+
22+
self.expect_expr(
23+
"outer",
24+
result_type="Outer",
25+
result_children=[ValueCheck(name="inner", type="Inner")],
26+
)
27+
self.expect_expr(
28+
"$0.inner",
29+
result_type="Inner",
30+
result_children=[ValueCheck(name="val", type="int", value="5")],
31+
)
32+
self.expect_expr("$1.val", result_type="int", result_value="5")
33+
34+
def test_persist_var(self):
35+
"""Test that user-defined variables can be used in subsequent expressions."""
36+
37+
self.target.EvaluateExpression("int $my_int = 5")
38+
self.expect_expr("$my_int * 2", result_type="int", result_value="10")
39+
40+
def test_context_object(self):
41+
"""Test expression evaluation in context of an object."""
42+
43+
val_outer = self.expect_expr("outer", result_type="Outer")
44+
45+
val_inner = val_outer.EvaluateExpression("inner")
46+
self.assertTrue(val_inner.IsValid())
47+
self.assertEqual("Inner", val_inner.GetDisplayTypeName())
48+
49+
val_val = val_inner.EvaluateExpression("this->val")
50+
self.assertTrue(val_val.IsValid())
51+
self.assertEqual("int", val_val.GetDisplayTypeName())
52+
self.assertEqual(val_val.GetValueAsSigned(), 5)
Binary file not shown.
Binary file not shown.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
struct Inner {
2+
Inner(int val) : val(val) {}
3+
int val;
4+
};
5+
6+
struct Outer {
7+
Outer(int val) : inner(val) {}
8+
Inner inner;
9+
};
10+
11+
extern "C" void _start(void) {
12+
Outer outer(5);
13+
char *boom = (char *)0;
14+
*boom = 47;
15+
}

lldb/test/API/lang/cpp/char8_t/TestCxxChar8_t.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,7 @@ def test_without_process(self):
2424

2525
self.expect_expr("a", result_type="char8_t", result_summary="0x61 u8'a'")
2626
self.expect_expr("ab", result_type="const char8_t *", result_summary='u8"你好"')
27-
28-
# FIXME: This should work too.
29-
self.expect("expr abc", substrs=['u8"你好"'], matching=False)
27+
self.expect_expr("abc", result_type="char8_t[9]", result_summary='u8"你好"')
3028

3129
@skipIf(compiler="clang", compiler_version=["<", "7.0"])
3230
def test_with_process(self):

0 commit comments

Comments
 (0)