blob: 703860c487376d2b83c1c7e1c6ff682b581897bb [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 <lib/syslog/cpp/macros.h>
#include "src/developer/debug/shared/message_loop.h"
#include "src/developer/debug/shared/register_info.h"
#include "src/developer/debug/zxdb/common/adapters.h"
#include "src/developer/debug/zxdb/common/err.h"
#include "src/developer/debug/zxdb/expr/builtin_types.h"
#include "src/developer/debug/zxdb/expr/eval_dwarf_expr.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_const_value.h"
#include "src/developer/debug/zxdb/expr/resolve_ptr_ref.h"
#include "src/developer/debug/zxdb/expr/resolve_type.h"
#include "src/developer/debug/zxdb/symbols/array_type.h"
#include "src/developer/debug/zxdb/symbols/base_type.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/strings/string_printf.h"
namespace zxdb {
namespace {
using debug::RegisterID;
RegisterID GetRegisterID(const ParsedIdentifier& ident) {
// Check for explicit register identifier annotation.
if (ident.components().size() == 1 &&
ident.components()[0].special() == SpecialIdentifier::kRegister) {
return debug::StringToRegisterID(ident.components()[0].name());
}
// Try to convert the identifier string to a register name.
auto str = GetSingleComponentIdentifierName(ident);
if (!str)
return debug::RegisterID::kUnknown;
return debug::StringToRegisterID(*str);
}
Err GetUnavailableRegisterErr(RegisterID id) {
return Err("Register %s unavailable in this context.", debug::RegisterIDToString(id));
}
ErrOrValue RegisterDataToValue(ExprLanguage lang, RegisterID id, VectorRegisterFormat vector_fmt,
containers::array_view<uint8_t> data) {
const debug::RegisterInfo* info = debug::InfoForRegister(id);
if (!info)
return Err("Unknown register");
ExprValueSource source(id);
switch (info->format) {
case debug::RegisterFormat::kGeneral:
case debug::RegisterFormat::kSpecial: {
return ExprValue(GetBuiltinUnsignedType(lang, data.size()),
std::vector<uint8_t>(data.begin(), data.end()), source);
}
case debug::RegisterFormat::kFloat: {
return ExprValue(GetBuiltinFloatType(lang, data.size()),
std::vector<uint8_t>(data.begin(), data.end()), source);
}
case debug::RegisterFormat::kVector: {
return VectorRegisterToValue(id, vector_fmt, std::vector<uint8_t>(data.begin(), data.end()));
}
case debug::RegisterFormat::kVoidAddress: {
// A void* is a pointer to no type.
return ExprValue(fxl::MakeRefCounted<ModifiedType>(DwarfTag::kPointerType, LazySymbol()),
std::vector<uint8_t>(data.begin(), data.end()), source);
}
case debug::RegisterFormat::kWordAddress: {
auto word_ptr_type = fxl::MakeRefCounted<ModifiedType>(DwarfTag::kPointerType,
GetBuiltinUnsignedType(lang, 8));
return ExprValue(word_ptr_type, std::vector<uint8_t>(data.begin(), data.end()), source);
}
}
return Err("Unknown register type");
}
} // namespace
EvalContextImpl::EvalContextImpl(std::shared_ptr<Abi> abi,
fxl::WeakPtr<const ProcessSymbols> process_symbols,
fxl::RefPtr<SymbolDataProvider> data_provider,
ExprLanguage language, fxl::RefPtr<CodeBlock> code_block)
: abi_(std::move(abi)),
process_symbols_(std::move(process_symbols)),
data_provider_(data_provider),
block_(std::move(code_block)),
language_(language),
weak_factory_(this) {}
EvalContextImpl::EvalContextImpl(std::shared_ptr<Abi> abi,
fxl::WeakPtr<const ProcessSymbols> process_symbols,
fxl::RefPtr<SymbolDataProvider> data_provider,
const Location& location,
std::optional<ExprLanguage> force_language)
: abi_(std::move(abi)),
process_symbols_(std::move(process_symbols)),
data_provider_(data_provider),
weak_factory_(this) {
const CodeBlock* function = nullptr;
if (location.symbol())
function = location.symbol().Get()->As<CodeBlock>();
if (function) {
block_ =
RefPtrTo(function->GetMostSpecificChild(location.symbol_context(), location.address()));
}
if (force_language) {
language_ = *force_language;
} else if (function) {
// Extract the language for the code if possible.
if (auto unit = function->GetCompileUnit())
language_ = DwarfLangToExprLanguage(unit->language());
}
}
EvalContextImpl::~EvalContextImpl() = default;
ExprLanguage EvalContextImpl::GetLanguage() const { return language_; }
FindNameContext EvalContextImpl::GetFindNameContext() const {
// The symbol context for the current location is passed to the FindNameContext to prioritize
// the current module's values when searching for variables. If relative, this will be ignored.
SymbolContext symbol_context = SymbolContext::ForRelativeAddresses();
if (block_ && process_symbols_)
symbol_context = block_->GetSymbolContext(process_symbols_.get());
return FindNameContext(process_symbols_.get(), symbol_context, block_.get());
}
void EvalContextImpl::GetNamedValue(const ParsedIdentifier& identifier, EvalCallback 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."));
return;
case FoundName::kTemplate:
cb(Err("Can not evaluate a template with no parameters."));
return;
case FoundName::kType:
cb(Err("Can not evaluate a type."));
return;
case FoundName::kFunction:
case FoundName::kOtherSymbol:
break; // Function pointers not supported yet.
case FoundName::kNone:
break; // Fall through to checking other stuff.
}
}
auto reg = GetRegisterID(identifier);
if (reg == RegisterID::kUnknown || debug::GetArchForRegisterID(reg) != data_provider_->GetArch())
return cb(Err("No variable '%s' found.", identifier.GetFullName().c_str()));
// Fall back to matching registers when no symbol is found. The data_provider is in charge
// of extracting the bits for non-canonical sub registers (like "ah" and "al" on x86) so we
// can pass the register enums through directly.
if (std::optional<containers::array_view<uint8_t>> opt_reg_data =
data_provider_->GetRegister(reg)) {
// Available synchronously.
if (opt_reg_data->empty())
cb(GetUnavailableRegisterErr(reg));
else
cb(RegisterDataToValue(language_, reg, GetVectorRegisterFormat(), *opt_reg_data));
} else {
data_provider_->GetRegisterAsync(
reg, [lang = language_, reg, vector_fmt = GetVectorRegisterFormat(), cb = std::move(cb)](
const Err& err, std::vector<uint8_t> value) mutable {
if (err.has_error()) {
cb(err);
} else if (value.empty()) {
cb(GetUnavailableRegisterErr(reg));
} else {
cb(RegisterDataToValue(lang, reg, vector_fmt, value));
}
});
}
}
void EvalContextImpl::GetVariableValue(fxl::RefPtr<Value> input_val, EvalCallback cb) const {
// Handle const values.
if (input_val->const_value().has_value())
return cb(ResolveConstValue(RefPtrTo(this), input_val.get()));
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);
} else {
// Everything else should be a variable.
var = RefPtrTo(input_val->As<Variable>());
FX_DCHECK(var);
}
SymbolContext symbol_context = var->GetSymbolContext(process_symbols_.get());
// Need to explicitly take a reference to the type.
fxl::RefPtr<Type> type = RefPtrTo(var->type().Get()->As<Type>());
if (!type)
return cb(Err("Missing type information."));
std::optional<containers::array_view<uint8_t>> ip_data = data_provider_->GetRegister(
debug::GetSpecialRegisterID(data_provider_->GetArch(), debug::SpecialRegisterType::kIP));
TargetPointer ip;
if (!ip_data || ip_data->size() != sizeof(ip)) // The IP should never require an async call.
return cb(Err("No location available."));
memcpy(&ip, &(*ip_data)[0], ip_data->size());
const DwarfExpr* loc_expr = var->location().ExprForIP(symbol_context, ip);
if (!loc_expr) {
// 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));
}
// Schedule the expression to be evaluated.
DwarfExprToValue(RefPtrTo(this), symbol_context, *loc_expr, std::move(type), std::move(cb));
}
const ProcessSymbols* EvalContextImpl::GetProcessSymbols() const {
if (!process_symbols_)
return nullptr;
return process_symbols_.get();
}
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.
FX_DCHECK(locations.size() == 1u);
return locations[0];
}
Err EvalContextImpl::ResolveExternValue(const fxl::RefPtr<Value>& input_value,
fxl::RefPtr<Variable>* resolved) const {
FX_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 = found.variable_ref();
return Err();
}
void EvalContextImpl::DoResolve(FoundName found, EvalCallback 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.
FX_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) 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);
// Got |this|, resolve |this-><DataMember>|.
//
// Here we do not support automatically converting a base class pointer to a derived class if
// we can. First, that's more difficult to implement because it requires asynchronously
// computing the derived class based on |this|'s vtable pointer. Second, it's not linguistically
// in scope and it could be surprising, especially if it shadows another value. The user can
// always do "this->foo" to expicitly request the conversion if enabled.
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));
}
});
});
}
FoundName EvalContextImpl::DoTargetSymbolsNameLookup(const ParsedIdentifier& ident) {
return FindName(GetFindNameContext(), FindNameOptions(FindNameOptions::kAllKinds), ident);
}
} // namespace zxdb