[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`.
This commit is contained in:
@@ -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<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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
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,19 +336,7 @@ 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 &
|
||||
if (m_persistent_variable_sp->m_flags &
|
||||
ExpressionVariable::EVNeedsAllocation &&
|
||||
!(m_persistent_variable_sp->m_flags &
|
||||
ExpressionVariable::EVKeepInTarget)) {
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
Binary file not shown.
BIN
lldb/test/API/functionalities/postmortem/elf-core/expr/linux-x86_64.out
Executable file
BIN
lldb/test/API/functionalities/postmortem/elf-core/expr/linux-x86_64.out
Executable file
Binary file not shown.
@@ -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;
|
||||
}
|
||||
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user