| // 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/zxdb/common/adapters.h" | |
| #include "src/developer/debug/zxdb/common/err.h" | |
| #include "src/developer/debug/zxdb/expr/async_dwarf_expr_eval.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_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_ipc::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_ipc::StringToRegisterID(ident.components()[0].name()); | |
| } | |
| // Try to convert the identifier string to a register name. | |
| auto str = GetSingleComponentIdentifierName(ident); | |
| if (!str) | |
| return debug_ipc::RegisterID::kUnknown; | |
| return debug_ipc::StringToRegisterID(*str); | |
| } | |
| Err GetUnavailableRegisterErr(RegisterID id) { | |
| return Err("Register %s unavailable in this context.", debug_ipc::RegisterIDToString(id)); | |
| } | |
| ErrOrValue RegisterDataToValue(RegisterID id, VectorRegisterFormat vector_fmt, | |
| containers::array_view<uint8_t> data) { | |
| if (ShouldFormatRegisterAsVector(id)) | |
| return VectorRegisterToValue(id, vector_fmt, std::vector<uint8_t>(data.begin(), data.end())); | |
| ExprValueSource source(id); | |
| if (data.size() <= sizeof(uint64_t)) { | |
| uint64_t int_value = 0; | |
| memcpy(&int_value, &data[0], data.size()); | |
| // Use the types defined by ExprValue for the unsigned number of the corresponding size. | |
| // Passing a null type will cause ExprValue to create one matching the input type. | |
| switch (data.size()) { | |
| case 1: | |
| return ExprValue(static_cast<uint8_t>(int_value), fxl::RefPtr<Type>(), source); | |
| case 2: | |
| return ExprValue(static_cast<uint16_t>(int_value), fxl::RefPtr<Type>(), source); | |
| case 4: | |
| return ExprValue(static_cast<uint32_t>(int_value), fxl::RefPtr<Type>(), source); | |
| case 8: | |
| return ExprValue(static_cast<uint64_t>(int_value), fxl::RefPtr<Type>(), source); | |
| } | |
| } | |
| // Large and/or weird sized registers. | |
| const char* type_name; | |
| if (data.size() == sizeof(uint128_t)) | |
| type_name = "uint128_t"; | |
| else | |
| type_name = "(register data)"; | |
| return ExprValue( | |
| fxl::MakeRefCounted<BaseType>(BaseType::kBaseTypeUnsigned, data.size(), type_name), | |
| std::vector<uint8_t>(data.begin(), data.end()), source); | |
| } | |
| } // namespace | |
| EvalContextImpl::EvalContextImpl(fxl::WeakPtr<const ProcessSymbols> process_symbols, | |
| fxl::RefPtr<SymbolDataProvider> data_provider, | |
| ExprLanguage language, fxl::RefPtr<CodeBlock> code_block) | |
| : process_symbols_(std::move(process_symbols)), | |
| data_provider_(data_provider), | |
| block_(std::move(code_block)), | |
| language_(language), | |
| weak_factory_(this) {} | |
| EvalContextImpl::EvalContextImpl(fxl::WeakPtr<const ProcessSymbols> process_symbols, | |
| fxl::RefPtr<SymbolDataProvider> data_provider, | |
| const Location& location, | |
| std::optional<ExprLanguage> force_language) | |
| : process_symbols_(std::move(process_symbols)), | |
| data_provider_(data_provider), | |
| weak_factory_(this) { | |
| const CodeBlock* function = nullptr; | |
| if (location.symbol()) | |
| function = location.symbol().Get()->AsCodeBlock(); | |
| 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 || 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. | |
| 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(reg, GetVectorRegisterFormat(), *opt_reg_data)); | |
| } else { | |
| data_provider_->GetRegisterAsync( | |
| reg, [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(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->AsVariable()); | |
| 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()->AsType()); | |
| if (!type) | |
| return cb(Err("Missing type information.")); | |
| std::optional<containers::array_view<uint8_t>> ip_data = | |
| data_provider_->GetRegister(debug_ipc::GetSpecialRegisterID( | |
| data_provider_->GetArch(), debug_ipc::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 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)); | |
| } | |
| // Schedule the expression to be evaluated. | |
| auto evaluator = fxl::MakeRefCounted<AsyncDwarfExprEval>(std::move(cb), std::move(type)); | |
| evaluator->Eval(RefPtrTo(this), symbol_context, loc_entry->expression); | |
| } | |
| 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 |