mirror of
https://github.com/intel/llvm.git
synced 2026-01-13 19:08:21 +08:00
When you run an expression and the result has a dynamic type that is
different from the expression's static result type, we print the result
variable using the dynamic type, but at present when you use the result
variable in an expression later on, we only give you access to the
static type. For instance:
```
(lldb) expr MakeADerivedReportABase()
(Derived *) $0 = 0x00000001007e93e0
(lldb) expr $0->method_from_derived()
^
error: no member named 'method_from_derived' in 'Base'
(lldb)
```
The static return type of that function is `Base *`, but we printed that
the result was a `Derived *` and then only used the `Base *` part of it
in subsequent expressions. That's not very helpful, and forces you to
guess and then cast the result types to their dynamic type in order to
be able to access the full type you were returned, which is
inconvenient.
This patch makes lldb retain the dynamic type of the result variable
(and ditto for persistent result variables).
It also adds more testing of expression result variables with various
types of dynamic values, to ensure we can access both the ivars and
methods of the type we print the result as.
382 lines
14 KiB
C++
382 lines
14 KiB
C++
//===-- LLVMUserExpression.cpp --------------------------------------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "lldb/Expression/LLVMUserExpression.h"
|
|
#include "lldb/Core/Module.h"
|
|
#include "lldb/Expression/DiagnosticManager.h"
|
|
#include "lldb/Expression/ExpressionVariable.h"
|
|
#include "lldb/Expression/IRExecutionUnit.h"
|
|
#include "lldb/Expression/IRInterpreter.h"
|
|
#include "lldb/Expression/Materializer.h"
|
|
#include "lldb/Host/HostInfo.h"
|
|
#include "lldb/Symbol/Block.h"
|
|
#include "lldb/Symbol/Function.h"
|
|
#include "lldb/Symbol/ObjectFile.h"
|
|
#include "lldb/Symbol/SymbolVendor.h"
|
|
#include "lldb/Symbol/Type.h"
|
|
#include "lldb/Symbol/VariableList.h"
|
|
#include "lldb/Target/ABI.h"
|
|
#include "lldb/Target/ExecutionContext.h"
|
|
#include "lldb/Target/Process.h"
|
|
#include "lldb/Target/StackFrame.h"
|
|
#include "lldb/Target/Target.h"
|
|
#include "lldb/Target/ThreadPlan.h"
|
|
#include "lldb/Target/ThreadPlanCallUserExpression.h"
|
|
#include "lldb/Utility/ConstString.h"
|
|
#include "lldb/Utility/ErrorMessages.h"
|
|
#include "lldb/Utility/LLDBLog.h"
|
|
#include "lldb/Utility/Log.h"
|
|
#include "lldb/Utility/StreamString.h"
|
|
#include "lldb/ValueObject/ValueObjectConstResult.h"
|
|
|
|
using namespace lldb;
|
|
using namespace lldb_private;
|
|
|
|
char LLVMUserExpression::ID;
|
|
|
|
LLVMUserExpression::LLVMUserExpression(ExecutionContextScope &exe_scope,
|
|
llvm::StringRef expr,
|
|
llvm::StringRef prefix,
|
|
SourceLanguage language,
|
|
ResultType desired_type,
|
|
const EvaluateExpressionOptions &options)
|
|
: UserExpression(exe_scope, expr, prefix, language, desired_type, options),
|
|
m_stack_frame_bottom(LLDB_INVALID_ADDRESS),
|
|
m_stack_frame_top(LLDB_INVALID_ADDRESS), m_allow_cxx(false),
|
|
m_allow_objc(false), m_transformed_text(), m_execution_unit_sp(),
|
|
m_materializer_up(), m_jit_module_wp(), m_target(nullptr),
|
|
m_can_interpret(false), m_materialized_address(LLDB_INVALID_ADDRESS) {}
|
|
|
|
LLVMUserExpression::~LLVMUserExpression() {
|
|
if (m_target) {
|
|
lldb::ModuleSP jit_module_sp(m_jit_module_wp.lock());
|
|
if (jit_module_sp)
|
|
m_target->GetImages().Remove(jit_module_sp);
|
|
}
|
|
}
|
|
|
|
lldb::ExpressionResults
|
|
LLVMUserExpression::DoExecute(DiagnosticManager &diagnostic_manager,
|
|
ExecutionContext &exe_ctx,
|
|
const EvaluateExpressionOptions &options,
|
|
lldb::UserExpressionSP &shared_ptr_to_me,
|
|
lldb::ExpressionVariableSP &result_sp) {
|
|
// The expression log is quite verbose, and if you're just tracking the
|
|
// execution of the expression, it's quite convenient to have these logs come
|
|
// out with the STEP log as well.
|
|
Log *log(GetLog(LLDBLog::Expressions | LLDBLog::Step));
|
|
|
|
if (m_jit_start_addr == LLDB_INVALID_ADDRESS && !m_can_interpret) {
|
|
diagnostic_manager.PutString(
|
|
lldb::eSeverityError,
|
|
"Expression can't be run, because there is no JIT compiled function");
|
|
return lldb::eExpressionSetupError;
|
|
}
|
|
|
|
lldb::addr_t struct_address = LLDB_INVALID_ADDRESS;
|
|
|
|
if (!PrepareToExecuteJITExpression(diagnostic_manager, exe_ctx,
|
|
struct_address)) {
|
|
diagnostic_manager.Printf(
|
|
lldb::eSeverityError,
|
|
"errored out in %s, couldn't PrepareToExecuteJITExpression",
|
|
__FUNCTION__);
|
|
return lldb::eExpressionSetupError;
|
|
}
|
|
|
|
lldb::addr_t function_stack_bottom = LLDB_INVALID_ADDRESS;
|
|
lldb::addr_t function_stack_top = LLDB_INVALID_ADDRESS;
|
|
|
|
if (m_can_interpret) {
|
|
llvm::Module *module = m_execution_unit_sp->GetModule();
|
|
llvm::Function *function = m_execution_unit_sp->GetFunction();
|
|
|
|
if (!module || !function) {
|
|
diagnostic_manager.PutString(
|
|
lldb::eSeverityError, "supposed to interpret, but nothing is there");
|
|
return lldb::eExpressionSetupError;
|
|
}
|
|
|
|
Status interpreter_error;
|
|
|
|
std::vector<lldb::addr_t> args;
|
|
|
|
if (!AddArguments(exe_ctx, args, struct_address, diagnostic_manager)) {
|
|
diagnostic_manager.Printf(lldb::eSeverityError,
|
|
"errored out in %s, couldn't AddArguments",
|
|
__FUNCTION__);
|
|
return lldb::eExpressionSetupError;
|
|
}
|
|
|
|
function_stack_bottom = m_stack_frame_bottom;
|
|
function_stack_top = m_stack_frame_top;
|
|
|
|
IRInterpreter::Interpret(*module, *function, args, *m_execution_unit_sp,
|
|
interpreter_error, function_stack_bottom,
|
|
function_stack_top, exe_ctx, options.GetTimeout());
|
|
|
|
if (!interpreter_error.Success()) {
|
|
diagnostic_manager.Printf(lldb::eSeverityError,
|
|
"supposed to interpret, but failed: %s",
|
|
interpreter_error.AsCString());
|
|
return lldb::eExpressionDiscarded;
|
|
}
|
|
} else {
|
|
if (!exe_ctx.HasThreadScope()) {
|
|
diagnostic_manager.Printf(lldb::eSeverityError,
|
|
"%s called with no thread selected",
|
|
__FUNCTION__);
|
|
return lldb::eExpressionSetupError;
|
|
}
|
|
|
|
// Store away the thread ID for error reporting, in case it exits
|
|
// during execution:
|
|
lldb::tid_t expr_thread_id = exe_ctx.GetThreadRef().GetID();
|
|
|
|
Address wrapper_address(m_jit_start_addr);
|
|
|
|
std::vector<lldb::addr_t> args;
|
|
|
|
if (!AddArguments(exe_ctx, args, struct_address, diagnostic_manager)) {
|
|
diagnostic_manager.Printf(lldb::eSeverityError,
|
|
"errored out in %s, couldn't AddArguments",
|
|
__FUNCTION__);
|
|
return lldb::eExpressionSetupError;
|
|
}
|
|
|
|
lldb::ThreadPlanSP call_plan_sp(new ThreadPlanCallUserExpression(
|
|
exe_ctx.GetThreadRef(), wrapper_address, args, options,
|
|
shared_ptr_to_me));
|
|
|
|
StreamString ss;
|
|
if (!call_plan_sp || !call_plan_sp->ValidatePlan(&ss)) {
|
|
diagnostic_manager.PutString(lldb::eSeverityError, ss.GetString());
|
|
return lldb::eExpressionSetupError;
|
|
}
|
|
|
|
ThreadPlanCallUserExpression *user_expression_plan =
|
|
static_cast<ThreadPlanCallUserExpression *>(call_plan_sp.get());
|
|
|
|
lldb::addr_t function_stack_pointer =
|
|
user_expression_plan->GetFunctionStackPointer();
|
|
|
|
function_stack_bottom = function_stack_pointer - HostInfo::GetPageSize();
|
|
function_stack_top = function_stack_pointer;
|
|
|
|
LLDB_LOGF(log,
|
|
"-- [UserExpression::Execute] Execution of expression begins --");
|
|
|
|
if (exe_ctx.GetProcessPtr())
|
|
exe_ctx.GetProcessPtr()->SetRunningUserExpression(true);
|
|
|
|
lldb::ExpressionResults execution_result =
|
|
exe_ctx.GetProcessRef().RunThreadPlan(exe_ctx, call_plan_sp, options,
|
|
diagnostic_manager);
|
|
|
|
if (exe_ctx.GetProcessPtr())
|
|
exe_ctx.GetProcessPtr()->SetRunningUserExpression(false);
|
|
|
|
LLDB_LOGF(log, "-- [UserExpression::Execute] Execution of expression "
|
|
"completed --");
|
|
|
|
if (execution_result == lldb::eExpressionInterrupted ||
|
|
execution_result == lldb::eExpressionHitBreakpoint) {
|
|
const char *error_desc = nullptr;
|
|
const char *explanation = execution_result == lldb::eExpressionInterrupted
|
|
? "was interrupted"
|
|
: "hit a breakpoint";
|
|
|
|
if (user_expression_plan) {
|
|
if (auto real_stop_info_sp = user_expression_plan->GetRealStopInfo())
|
|
error_desc = real_stop_info_sp->GetDescription();
|
|
}
|
|
|
|
if (error_desc)
|
|
diagnostic_manager.Printf(lldb::eSeverityError,
|
|
"Expression execution %s: %s.", explanation,
|
|
error_desc);
|
|
else
|
|
diagnostic_manager.Printf(lldb::eSeverityError,
|
|
"Expression execution %s.", explanation);
|
|
|
|
if ((execution_result == lldb::eExpressionInterrupted &&
|
|
options.DoesUnwindOnError()) ||
|
|
(execution_result == lldb::eExpressionHitBreakpoint &&
|
|
options.DoesIgnoreBreakpoints()))
|
|
diagnostic_manager.AppendMessageToDiagnostic(
|
|
"The process has been returned to the state before expression "
|
|
"evaluation.");
|
|
else {
|
|
if (execution_result == lldb::eExpressionHitBreakpoint)
|
|
user_expression_plan->TransferExpressionOwnership();
|
|
diagnostic_manager.AppendMessageToDiagnostic(
|
|
"The process has been left at the point where it was "
|
|
"interrupted, use \"thread return -x\" to return to the state "
|
|
"before expression evaluation.");
|
|
}
|
|
|
|
return execution_result;
|
|
}
|
|
|
|
if (execution_result == lldb::eExpressionStoppedForDebug) {
|
|
diagnostic_manager.PutString(
|
|
lldb::eSeverityInfo,
|
|
"Expression execution was halted at the first instruction of the "
|
|
"expression function because \"debug\" was requested.\n"
|
|
"Use \"thread return -x\" to return to the state before expression "
|
|
"evaluation.");
|
|
return execution_result;
|
|
}
|
|
|
|
if (execution_result == lldb::eExpressionThreadVanished) {
|
|
diagnostic_manager.Printf(lldb::eSeverityError,
|
|
"Couldn't execute expression: the thread on "
|
|
"which the expression was being run (0x%" PRIx64
|
|
") exited during its execution.",
|
|
expr_thread_id);
|
|
return execution_result;
|
|
}
|
|
|
|
if (execution_result != lldb::eExpressionCompleted) {
|
|
diagnostic_manager.Printf(lldb::eSeverityError,
|
|
"Couldn't execute expression: result was %s",
|
|
toString(execution_result).c_str());
|
|
return execution_result;
|
|
}
|
|
}
|
|
|
|
if (FinalizeJITExecution(diagnostic_manager, exe_ctx, result_sp,
|
|
function_stack_bottom, function_stack_top))
|
|
return lldb::eExpressionCompleted;
|
|
|
|
return lldb::eExpressionResultUnavailable;
|
|
}
|
|
|
|
bool LLVMUserExpression::FinalizeJITExecution(
|
|
DiagnosticManager &diagnostic_manager, ExecutionContext &exe_ctx,
|
|
lldb::ExpressionVariableSP &result, lldb::addr_t function_stack_bottom,
|
|
lldb::addr_t function_stack_top) {
|
|
Log *log = GetLog(LLDBLog::Expressions);
|
|
|
|
LLDB_LOGF(log, "-- [UserExpression::FinalizeJITExecution] Dematerializing "
|
|
"after execution --");
|
|
|
|
if (!m_dematerializer_sp) {
|
|
diagnostic_manager.Printf(lldb::eSeverityError,
|
|
"Couldn't apply expression side effects : no "
|
|
"dematerializer is present");
|
|
return false;
|
|
}
|
|
|
|
Status dematerialize_error;
|
|
|
|
m_dematerializer_sp->Dematerialize(dematerialize_error, function_stack_bottom,
|
|
function_stack_top);
|
|
|
|
if (!dematerialize_error.Success()) {
|
|
diagnostic_manager.Printf(lldb::eSeverityError,
|
|
"Couldn't apply expression side effects : %s",
|
|
dematerialize_error.AsCString("unknown error"));
|
|
return false;
|
|
}
|
|
|
|
result =
|
|
GetResultAfterDematerialization(exe_ctx.GetBestExecutionContextScope());
|
|
|
|
if (result) {
|
|
// TransferAddress also does the offset_to_top calculation, so record the
|
|
// dynamic option before we do that.
|
|
if (EvaluateExpressionOptions *options = GetOptions())
|
|
result->PreserveDynamicOption(options->GetUseDynamic());
|
|
result->TransferAddress();
|
|
}
|
|
|
|
m_dematerializer_sp.reset();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool LLVMUserExpression::PrepareToExecuteJITExpression(
|
|
DiagnosticManager &diagnostic_manager, ExecutionContext &exe_ctx,
|
|
lldb::addr_t &struct_address) {
|
|
lldb::TargetSP target;
|
|
lldb::ProcessSP process;
|
|
lldb::StackFrameSP frame;
|
|
|
|
if (!LockAndCheckContext(exe_ctx, target, process, frame)) {
|
|
diagnostic_manager.PutString(
|
|
lldb::eSeverityError,
|
|
"The context has changed before we could JIT the expression!");
|
|
return false;
|
|
}
|
|
|
|
if (m_jit_start_addr != LLDB_INVALID_ADDRESS || m_can_interpret) {
|
|
if (m_materialized_address == LLDB_INVALID_ADDRESS) {
|
|
IRMemoryMap::AllocationPolicy policy =
|
|
m_can_interpret ? IRMemoryMap::eAllocationPolicyHostOnly
|
|
: IRMemoryMap::eAllocationPolicyMirror;
|
|
|
|
const bool zero_memory = false;
|
|
if (auto address_or_error = m_execution_unit_sp->Malloc(
|
|
m_materializer_up->GetStructByteSize(),
|
|
m_materializer_up->GetStructAlignment(),
|
|
lldb::ePermissionsReadable | lldb::ePermissionsWritable, policy,
|
|
zero_memory)) {
|
|
m_materialized_address = *address_or_error;
|
|
} else {
|
|
diagnostic_manager.Printf(
|
|
lldb::eSeverityError,
|
|
"Couldn't allocate space for materialized struct: %s",
|
|
toString(address_or_error.takeError()).c_str());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
struct_address = m_materialized_address;
|
|
|
|
if (m_can_interpret && m_stack_frame_bottom == LLDB_INVALID_ADDRESS) {
|
|
size_t stack_frame_size = target->GetExprAllocSize();
|
|
if (stack_frame_size == 0) {
|
|
ABISP abi_sp;
|
|
if (process && (abi_sp = process->GetABI()))
|
|
stack_frame_size = abi_sp->GetStackFrameSize();
|
|
else
|
|
stack_frame_size = 512 * 1024;
|
|
}
|
|
|
|
const bool zero_memory = false;
|
|
if (auto address_or_error = m_execution_unit_sp->Malloc(
|
|
stack_frame_size, 8,
|
|
lldb::ePermissionsReadable | lldb::ePermissionsWritable,
|
|
IRMemoryMap::eAllocationPolicyHostOnly, zero_memory)) {
|
|
m_stack_frame_bottom = *address_or_error;
|
|
m_stack_frame_top = m_stack_frame_bottom + stack_frame_size;
|
|
} else {
|
|
diagnostic_manager.Printf(
|
|
lldb::eSeverityError,
|
|
"Couldn't allocate space for the stack frame: %s",
|
|
toString(address_or_error.takeError()).c_str());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
Status materialize_error;
|
|
|
|
m_dematerializer_sp = m_materializer_up->Materialize(
|
|
frame, *m_execution_unit_sp, struct_address, materialize_error);
|
|
|
|
if (!materialize_error.Success()) {
|
|
diagnostic_manager.Printf(lldb::eSeverityError,
|
|
"Couldn't materialize: %s",
|
|
materialize_error.AsCString());
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|