blob: cb7eb7f3d807a59d0a17d675b833a34b10e66c22 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/developer/debug/zxdb/expr/eval_context_impl.h"
#include "src/developer/debug/shared/message_loop.h"
#include "src/developer/debug/zxdb/common/err.h"
#include "src/developer/debug/zxdb/expr/builtin_types.h"
#include "src/developer/debug/zxdb/expr/expr_value.h"
#include "src/developer/debug/zxdb/expr/find_name.h"
#include "src/developer/debug/zxdb/expr/resolve_collection.h"
#include "src/developer/debug/zxdb/expr/resolve_ptr_ref.h"
#include "src/developer/debug/zxdb/symbols/code_block.h"
#include "src/developer/debug/zxdb/symbols/compile_unit.h"
#include "src/developer/debug/zxdb/symbols/data_member.h"
#include "src/developer/debug/zxdb/symbols/dwarf_expr_eval.h"
#include "src/developer/debug/zxdb/symbols/function.h"
#include "src/developer/debug/zxdb/symbols/identifier.h"
#include "src/developer/debug/zxdb/symbols/input_location.h"
#include "src/developer/debug/zxdb/symbols/location.h"
#include "src/developer/debug/zxdb/symbols/modified_type.h"
#include "src/developer/debug/zxdb/symbols/process_symbols.h"
#include "src/developer/debug/zxdb/symbols/symbol_data_provider.h"
#include "src/developer/debug/zxdb/symbols/variable.h"
#include "src/lib/fxl/logging.h"
#include "src/lib/fxl/strings/string_printf.h"
namespace zxdb {
namespace {
debug_ipc::RegisterID GetRegister(const ParsedIdentifier& ident) {
auto str = GetSingleComponentIdentifierName(ident);
if (!str)
return debug_ipc::RegisterID::kUnknown;
return debug_ipc::StringToRegisterID(*str);
}
} // namespace
// The data associated with one in-progress variable resolution. This must be
// heap allocated for each resolution operation since multiple operations can
// be pending.
struct EvalContextImpl::ResolutionState : public fxl::RefCountedThreadSafe<ResolutionState> {
DwarfExprEval dwarf_eval;
ValueCallback callback;
// Not necessarily a concrete type, this is the type of the result the user
// will see.
fxl::RefPtr<Type> type;
// The Variable or DataMember that generated the value. Used to execute the
// callback.
fxl::RefPtr<Symbol> symbol;
// This private stuff prevents refcounted mistakes.
private:
FRIEND_REF_COUNTED_THREAD_SAFE(ResolutionState);
FRIEND_MAKE_REF_COUNTED(ResolutionState);
explicit ResolutionState(ValueCallback cb, fxl::RefPtr<Type> t, fxl::RefPtr<Symbol> s)
: callback(std::move(cb)), type(std::move(t)), symbol(std::move(s)) {}
~ResolutionState() = default;
};
EvalContextImpl::EvalContextImpl(fxl::WeakPtr<const ProcessSymbols> process_symbols,
const SymbolContext& symbol_context,
fxl::RefPtr<SymbolDataProvider> data_provider,
fxl::RefPtr<CodeBlock> code_block)
: process_symbols_(std::move(process_symbols)),
symbol_context_(symbol_context),
data_provider_(data_provider),
block_(std::move(code_block)),
weak_factory_(this) {}
EvalContextImpl::EvalContextImpl(fxl::WeakPtr<const ProcessSymbols> process_symbols,
fxl::RefPtr<SymbolDataProvider> data_provider,
const Location& location)
: process_symbols_(std::move(process_symbols)),
symbol_context_(location.symbol_context()),
data_provider_(data_provider),
weak_factory_(this) {
if (!location.symbol())
return;
const CodeBlock* function = location.symbol().Get()->AsCodeBlock();
if (function) {
block_ =
RefPtrTo(function->GetMostSpecificChild(location.symbol_context(), location.address()));
// Extract the language for the code if possible.
if (const CompileUnit* unit = function->GetCompileUnit())
language_ = DwarfLangToExprLanguage(unit->language());
}
}
EvalContextImpl::~EvalContextImpl() = default;
ExprLanguage EvalContextImpl::GetLanguage() const { return language_; }
void EvalContextImpl::GetNamedValue(const ParsedIdentifier& identifier, ValueCallback cb) const {
if (FoundName found =
FindName(GetFindNameContext(), FindNameOptions(FindNameOptions::kAllKinds), identifier)) {
switch (found.kind()) {
case FoundName::kVariable:
case FoundName::kMemberVariable:
DoResolve(std::move(found), std::move(cb));
return;
case FoundName::kNamespace:
cb(Err("Can not evaluate a namespace."), nullptr);
return;
case FoundName::kTemplate:
cb(Err("Can not evaluate a template with no parameters."), nullptr);
return;
case FoundName::kType:
cb(Err("Can not evaluate a type."), nullptr);
return;
case FoundName::kFunction:
break; // Function pointers not supported yet.
case FoundName::kNone:
break; // Fall through to checking other stuff.
}
}
auto reg = GetRegister(identifier);
if (reg == debug_ipc::RegisterID::kUnknown ||
GetArchForRegisterID(reg) != data_provider_->GetArch())
return cb(Err("No variable '%s' found.", identifier.GetFullName().c_str()), nullptr);
// Fall back to matching registers when no symbol is found.
data_provider_->GetRegisterAsync(reg,
[cb = std::move(cb)](const Err& err, uint64_t value) mutable {
if (err.has_error())
cb(err, fxl::RefPtr<zxdb::Symbol>());
else
cb(ExprValue(value), fxl::RefPtr<zxdb::Symbol>());
});
}
void EvalContextImpl::GetVariableValue(fxl::RefPtr<Value> input_val, ValueCallback cb) const {
fxl::RefPtr<Variable> var;
if (input_val->is_external()) {
// Convert extern Variables and DataMembers to the actual variable memory.
if (Err err = ResolveExternValue(input_val, &var); err.has_error())
return cb(err, nullptr);
} else {
// Everything else should be a variable.
var = RefPtrTo(input_val->AsVariable());
FXL_DCHECK(var);
}
// Need to explicitly take a reference to the type.
fxl::RefPtr<Type> type = RefPtrTo(var->type().Get()->AsType());
if (!type)
return cb(Err("Missing type information."), var);
std::optional<uint64_t> ip;
data_provider_->GetRegister(debug_ipc::GetSpecialRegisterID(data_provider_->GetArch(),
debug_ipc::SpecialRegisterType::kIP),
&ip);
if (!ip) // The IP should never require an async call.
return cb(Err("No location available."), var);
const VariableLocation::Entry* loc_entry = var->location().EntryForIP(symbol_context_, *ip);
if (!loc_entry) {
// No DWARF location applies to the current instruction pointer.
const char* err_str;
if (var->location().is_null()) {
// With no locations, this variable has been completely optimized out.
err_str = "Optimized out.";
} else {
// There are locations but none of them match the current IP.
err_str = "Unavailable";
}
return cb(Err(ErrType::kOptimizedOut, err_str), var);
}
// Schedule the expression to be evaluated.
auto state = fxl::MakeRefCounted<ResolutionState>(std::move(cb), std::move(type), std::move(var));
state->dwarf_eval.Eval(data_provider_, symbol_context_, loc_entry->expression,
[state = std::move(state), weak_this = weak_factory_.GetWeakPtr()](
DwarfExprEval*, const Err& err) {
if (weak_this)
weak_this->OnDwarfEvalComplete(err, std::move(state));
// Prevent the DwarfExprEval from getting reentrantly deleted from
// within its own callback by posting a reference back to the message
// loop.
debug_ipc::MessageLoop::Current()->PostTask(
FROM_HERE, [state = std::move(state)]() {});
});
}
fxl::RefPtr<Type> EvalContextImpl::ResolveForwardDefinition(const Type* type) const {
Identifier ident = type->GetIdentifier();
if (ident.empty()) {
// Some things like modified types don't have real identifier names.
return RefPtrTo(type);
}
ParsedIdentifier parsed_ident = ToParsedIdentifier(ident);
// Search for the first match of a type definition. Note that "find_types" is not desirable here
// since we only want to resolve real definitions. Normally the index contains only definitions
// but if a module contains only declarations that module's index will list the symbol as a
// declaration which we don't want.
FindNameOptions opts(FindNameOptions::kNoKinds);
opts.find_type_defs = true;
opts.max_results = 1;
// The type names will always be fully qualified. Mark the identifier as
// such and only search the global context by clearing the code location.
parsed_ident.set_qualification(IdentifierQualification::kGlobal);
auto context = GetFindNameContext();
context.block = nullptr;
if (FoundName result = FindName(context, opts, parsed_ident)) {
FXL_DCHECK(result.type());
return result.type();
}
// Nothing found in the index.
return RefPtrTo(type);
}
fxl::RefPtr<Type> EvalContextImpl::GetConcreteType(const Type* type) const {
if (!type)
return fxl::RefPtr<Type>();
// Iteratively strip C-V qualifications, follow typedefs, and follow forward
// declarations.
fxl::RefPtr<Type> cur = RefPtrTo(type);
do {
// Follow forward declarations.
if (cur->is_declaration()) {
cur = ResolveForwardDefinition(cur.get());
if (cur->is_declaration())
break; // Declaration can't be resolved, give up.
}
// Strip C-V qualifiers and follow typedefs.
cur = RefPtrTo(cur->StripCVT());
} while (cur && cur->is_declaration());
return cur;
}
fxl::RefPtr<SymbolDataProvider> EvalContextImpl::GetDataProvider() { return data_provider_; }
NameLookupCallback EvalContextImpl::GetSymbolNameLookupCallback() {
// The contract for this function is that the callback must not be stored
// so the callback can reference |this|.
return [this](const ParsedIdentifier& ident, const FindNameOptions& opts) -> FoundName {
// Look up the symbols in the symbol table if possible.
FoundName result = FindName(GetFindNameContext(), opts, ident);
// Fall back on builtin types.
if (result.kind() == FoundName::kNone && opts.find_types) {
if (auto type = GetBuiltinType(language_, ident.GetFullName()))
return FoundName(std::move(type));
}
return result;
};
}
Location EvalContextImpl::GetLocationForAddress(uint64_t address) const {
if (!process_symbols_)
return Location(Location::State::kAddress, address); // Can't symbolize.
auto locations = process_symbols_->ResolveInputLocation(InputLocation(address));
// Given an exact address, ResolveInputLocation() should only return one result.
FXL_DCHECK(locations.size() == 1u);
return locations[0];
}
Err EvalContextImpl::ResolveExternValue(const fxl::RefPtr<Value>& input_value,
fxl::RefPtr<Variable>* resolved) const {
FXL_DCHECK(input_value->is_external());
FindNameOptions options(FindNameOptions::kNoKinds);
options.find_vars = true;
// Passing a null block in the FindNameContext will bypass searching the current scope and
// "this" object and instead only search global names. This is what we want since the extern
// Value name will be fully qualified.
FindNameContext context = GetFindNameContext();
context.block = nullptr;
FoundName found = FindName(context, options, ToParsedIdentifier(input_value->GetIdentifier()));
if (!found || !found.variable())
return Err("Extern variable '%s' not found.", input_value->GetFullName().c_str());
*resolved = std::move(found.variable_ref());
return Err();
}
void EvalContextImpl::DoResolve(FoundName found, ValueCallback cb) const {
if (found.kind() == FoundName::kVariable) {
// Simple variable resolution.
GetVariableValue(found.variable_ref(), std::move(cb));
return;
}
// Everything below here is an object variable resolution.
FXL_DCHECK(found.kind() == FoundName::kMemberVariable);
// Static ("external") data members don't require a "this" pointer.
if (found.member().data_member()->is_external())
return GetVariableValue(RefPtrTo(found.member().data_member()), std::move(cb));
// Get the value of of the |this| variable to resolve.
GetVariableValue(
found.object_ptr_ref(), [weak_this = weak_factory_.GetWeakPtr(), found, cb = std::move(cb)](
ErrOrValue value, fxl::RefPtr<Symbol> symbol) mutable {
if (!weak_this)
return; // Don't issue callbacks if we've been destroyed.
if (value.has_error()) // |this| not available, probably optimized out.
return cb(value, symbol);
// Got |this|, resolve |this-><DataMember>|.
ResolveMemberByPointer(fxl::RefPtr<EvalContextImpl>(weak_this.get()), value.value(),
found.member(),
[weak_this, found, cb = std::move(cb)](ErrOrValue value) mutable {
if (weak_this) {
// Only issue callbacks if we're still alive.
cb(std::move(value), found.member().data_member_ref());
}
});
});
}
void EvalContextImpl::OnDwarfEvalComplete(const Err& err,
fxl::RefPtr<ResolutionState> state) const {
if (err.has_error()) // Error decoding.
return state->callback(err, state->symbol);
uint64_t result_int = state->dwarf_eval.GetResult();
// The DWARF expression will produce either the address of the value or the
// value itself.
if (state->dwarf_eval.GetResultType() == DwarfExprEval::ResultType::kValue) {
// Get the concrete type since we need the byte size. But don't use this
// to actually construct the variable since it will strip "const" and
// stuff that the user will expect to see.
fxl::RefPtr<Type> concrete_type = GetConcreteType(state->type.get());
// The DWARF expression produced the exact value (it's not in memory).
uint32_t type_size = concrete_type->byte_size();
if (type_size > sizeof(uint64_t)) {
state->callback(Err(fxl::StringPrintf("Result size insufficient for type of size %u. "
"Please file a bug with a repro case.",
type_size)),
state->symbol);
return;
}
std::vector<uint8_t> data;
data.resize(type_size);
memcpy(&data[0], &result_int, type_size);
state->callback(ExprValue(state->type, std::move(data)), state->symbol);
} else {
// The DWARF result is a pointer to the value.
ResolvePointer(RefPtrTo(this), result_int, state->type,
[state, weak_this = weak_factory_.GetWeakPtr()](ErrOrValue value) {
if (weak_this)
state->callback(std::move(value), state->symbol);
});
}
}
FoundName EvalContextImpl::DoTargetSymbolsNameLookup(const ParsedIdentifier& ident) {
return FindName(GetFindNameContext(), FindNameOptions(FindNameOptions::kAllKinds), ident);
}
FindNameContext EvalContextImpl::GetFindNameContext() const {
return FindNameContext(process_symbols_.get(), symbol_context_, block_.get());
}
} // namespace zxdb