| // 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/resolve_collection.h" | |
| #include "src/developer/debug/shared/message_loop.h" | |
| #include "src/developer/debug/zxdb/expr/async_dwarf_expr_eval.h" | |
| #include "src/developer/debug/zxdb/expr/bitfield.h" | |
| #include "src/developer/debug/zxdb/expr/eval_context.h" | |
| #include "src/developer/debug/zxdb/expr/expr_parser.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_base.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/symbols/arch.h" | |
| #include "src/developer/debug/zxdb/symbols/base_type.h" | |
| #include "src/developer/debug/zxdb/symbols/collection.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/inherited_from.h" | |
| #include "src/developer/debug/zxdb/symbols/modified_type.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 { | |
| // A wrapper around FindMember that issues errors rather than returning an optional. The base can be | |
| // null for the convenience of the caller. On error, the output FoundMember will be untouched. | |
| ErrOr<FoundMember> FindMemberWithErr(const fxl::RefPtr<EvalContext>& context, | |
| const Collection* base, const ParsedIdentifier& identifier) { | |
| if (!base) { | |
| return Err("Can't resolve '%s' on non-struct/class/union value.", | |
| identifier.GetFullName().c_str()); | |
| } | |
| FindNameOptions options(FindNameOptions::kNoKinds); | |
| options.find_vars = true; | |
| std::vector<FoundName> found; | |
| FindMember(context->GetFindNameContext(), options, base, identifier, nullptr, &found); | |
| if (!found.empty()) { | |
| FX_DCHECK(found[0].kind() == FoundName::kMemberVariable); | |
| return found[0].member(); | |
| } | |
| return Err("No member '%s' in %s '%s'.", identifier.GetFullName().c_str(), base->GetKindString(), | |
| base->GetFullName().c_str()); | |
| } | |
| // Variant of the above that extracts the collection type from the given base value. | |
| ErrOr<FoundMember> FindMemberWithErr(const fxl::RefPtr<EvalContext>& context, const ExprValue& base, | |
| const ParsedIdentifier& identifier) { | |
| fxl::RefPtr<Type> concrete_base = base.GetConcreteType(context.get()); | |
| if (!concrete_base) | |
| return Err("No type information for collection."); | |
| return FindMemberWithErr(context, concrete_base->AsCollection(), identifier); | |
| } | |
| Err GetErrorForInvalidMemberOf(const Collection* coll) { | |
| return Err("Invalid data member for %s '%s'.", coll->GetKindString(), | |
| coll->GetFullName().c_str()); | |
| } | |
| // Tries to describe the type of the value as best as possible when a member access is invalid. | |
| Err GetErrorForInvalidMemberOf(const ExprValue& value) { | |
| if (!value.type()) | |
| return Err("No type information."); | |
| if (const Collection* coll = value.type()->AsCollection()) | |
| return GetErrorForInvalidMemberOf(coll); | |
| // Something other than a collection is the base. | |
| return Err("Accessing a member of non-struct/class/union '%s'.", | |
| value.type()->GetFullName().c_str()); | |
| } | |
| // Validates the input member (it will null check) and extracts the type and size for the member. | |
| // The size is returned separately because the member_type might be a forward declaration and | |
| // we can't return a concrete type without breaking CV qualifiers. | |
| Err GetMemberType(const fxl::RefPtr<EvalContext>& context, const Collection* coll, | |
| const DataMember* member, fxl::RefPtr<Type>* member_type, uint32_t* member_size) { | |
| if (!member) | |
| return GetErrorForInvalidMemberOf(coll); | |
| *member_type = RefPtrTo(member->type().Get()->AsType()); | |
| if (!*member_type) { | |
| return Err("Bad type information for '%s.%s'.", coll->GetFullName().c_str(), | |
| member->GetAssignedName().c_str()); | |
| } | |
| return Err(); | |
| } | |
| // Backend for ResolveMemberByPointer variants that does the actual memory fetch. It's given a | |
| // concrete pointer and pointed-to type, along with a specific found member inside it. | |
| void DoResolveMemberByPointer(const fxl::RefPtr<EvalContext>& context, const ExprValue& base_ptr, | |
| const Collection* pointed_to_type, const FoundMember& member, | |
| EvalCallback cb) { | |
| if (Err err = base_ptr.EnsureSizeIs(kTargetPointerSize); err.has_error()) | |
| return cb(err); | |
| if (member.data_member()->is_bitfield()) { | |
| // The bitfield case is complicated. Get the full pointed-to collection value and then resolve | |
| // the member access using "." mode to re-use the non-pointer codepath. This avoids duplicating | |
| // the bitfield logic. (This is actually valid logic for every case but fetches unnecessary | |
| // memory which we avoid in the common case below). | |
| ResolvePointer(context, base_ptr, | |
| [context, member, cb = std::move(cb)](ErrOrValue value) mutable { | |
| if (value.has_error()) | |
| return cb(std::move(value)); | |
| cb(ResolveBitfieldMember(context, value.value(), member)); | |
| }); | |
| } else { | |
| // Common case for non-bitfield members. We can avoid fetching the entire structure (which can | |
| // be very large in some edge cases) and just get fetch the memory for the item we need. | |
| fxl::RefPtr<Type> member_type; | |
| uint32_t member_size = 0; | |
| Err err = | |
| GetMemberType(context, pointed_to_type, member.data_member(), &member_type, &member_size); | |
| if (err.has_error()) | |
| return cb(err); | |
| TargetPointer base_address = base_ptr.GetAs<TargetPointer>(); | |
| // TODO(bug 41503) handle virtual inheritance. | |
| if (auto opt_offset = member.GetDataMemberOffset()) { | |
| ResolvePointer(context, base_address + *opt_offset, std::move(member_type), std::move(cb)); | |
| } else { | |
| return cb(Err("Virtual inheritance is not supported yet (bug 41503).")); | |
| } | |
| } | |
| } | |
| // Implementation of ResolveMemberByPointer with a named member. This does everything except handle | |
| // conversion to base classes. | |
| void DoResolveMemberNameByPointer(const fxl::RefPtr<EvalContext>& context, | |
| const ExprValue& base_ptr, const ParsedIdentifier& identifier, | |
| fit::callback<void(ErrOrValue, const FoundMember&)> cb) { | |
| fxl::RefPtr<Collection> coll; | |
| if (Err err = GetConcretePointedToCollection(context, base_ptr.type(), &coll); err.has_error()) | |
| return cb(err, FoundMember()); | |
| ErrOr<FoundMember> found = FindMemberWithErr(context, coll.get(), identifier); | |
| if (found.has_error()) | |
| return cb(found.err(), FoundMember()); | |
| // Dispatch to low-level version now that the member is found by name. | |
| DoResolveMemberByPointer(context, base_ptr, coll.get(), found.value(), | |
| [cb = std::move(cb), found = found.value()](ErrOrValue value) mutable { | |
| cb(std::move(value), found); | |
| }); | |
| } | |
| // Extracts an embedded type inside of a base. This can be used for finding collection data members | |
| // and inherited classes, both of which consist of a type and an offset. | |
| ErrOrValue ExtractSubType(const fxl::RefPtr<EvalContext>& context, const ExprValue& base, | |
| fxl::RefPtr<Type> sub_type, uint32_t offset) { | |
| // Need a valid size for the inside type so it has to be concrete. | |
| auto concrete = context->GetConcreteType(sub_type.get()); | |
| uint32_t size = concrete->byte_size(); | |
| if (offset + size > base.data().size()) { | |
| return Err("Invalid data offset %" PRIu32 " in object of size %zu.", offset, | |
| base.data().size()); | |
| } | |
| std::vector<uint8_t> member_data(base.data().begin() + offset, | |
| base.data().begin() + (offset + size)); | |
| return ExprValue(std::move(sub_type), std::move(member_data), | |
| base.source().GetOffsetInto(offset)); | |
| } | |
| // This variant takes a precomputed offset of the data member in the base class. This is to support | |
| // the case where the data member is in a derived class (the derived class will have its own | |
| // offset). | |
| ErrOrValue DoResolveNonstaticMember(const fxl::RefPtr<EvalContext>& context, const ExprValue& base, | |
| const FoundMember& member) { | |
| // Bitfields get special handling. | |
| if (member.data_member()->is_bitfield()) | |
| return ResolveBitfieldMember(context, base, member); | |
| // Constant value members. | |
| if (member.data_member()->const_value().has_value()) | |
| return ResolveConstValue(context, member.data_member()); | |
| fxl::RefPtr<Type> concrete_type = base.GetConcreteType(context.get()); | |
| const Collection* coll = nullptr; | |
| if (!base.type() || !(coll = concrete_type->AsCollection())) | |
| return Err("Can't resolve data member on non-struct/class value."); | |
| fxl::RefPtr<Type> member_type; | |
| uint32_t member_size = 0; | |
| Err err = GetMemberType(context, coll, member.data_member(), &member_type, &member_size); | |
| if (err.has_error()) | |
| return err; | |
| // TODO(bug 41503) handle virtual inheritance. | |
| if (auto opt_offset = member.GetDataMemberOffset()) | |
| return ExtractSubType(context, base, std::move(member_type), *opt_offset); | |
| return Err("Virtual inheritance is not supported yet (bug 41503)."); | |
| } | |
| // As with DoResolveNonstaticMember, this takes a precomputed offset. It is asynchronous to handle | |
| // static data members that may require a memory fetch. | |
| void DoResolveMember(const fxl::RefPtr<EvalContext>& context, const ExprValue& base, | |
| const FoundMember& member, EvalCallback cb) { | |
| FX_DCHECK(member.data_member()); | |
| if (member.data_member()->is_external()) { | |
| // A forward-declared static member. In C++ static members can't be bitfields so we don't handle | |
| // them. | |
| return context->GetVariableValue(RefPtrTo(member.data_member()), std::move(cb)); | |
| } | |
| // Normal nonstatic resolution is synchronous. | |
| cb(DoResolveNonstaticMember(context, base, member)); | |
| } | |
| } // namespace | |
| void ResolveMember(const fxl::RefPtr<EvalContext>& context, const ExprValue& base, | |
| const FoundMember& member, EvalCallback cb) { | |
| if (member.is_null()) | |
| return cb(GetErrorForInvalidMemberOf(base)); | |
| DoResolveMember(context, base, member, std::move(cb)); | |
| } | |
| void ResolveMember(const fxl::RefPtr<EvalContext>& context, const ExprValue& base, | |
| const ParsedIdentifier& identifier, EvalCallback cb) { | |
| ErrOr<FoundMember> found = FindMemberWithErr(context, base, identifier); | |
| if (found.has_error()) | |
| return cb(found.err()); | |
| DoResolveMember(context, base, found.value(), std::move(cb)); | |
| } | |
| ErrOrValue ResolveNonstaticMember(const fxl::RefPtr<EvalContext>& context, const ExprValue& base, | |
| const FoundMember& member) { | |
| if (member.is_null()) | |
| return GetErrorForInvalidMemberOf(base); | |
| return DoResolveNonstaticMember(context, base, member); | |
| } | |
| ErrOrValue ResolveNonstaticMember(const fxl::RefPtr<EvalContext>& context, const ExprValue& base, | |
| const ParsedIdentifier& identifier) { | |
| ErrOr<FoundMember> found = FindMemberWithErr(context, base, identifier); | |
| if (found.has_error()) | |
| return found.err(); | |
| return DoResolveNonstaticMember(context, base, found.value()); | |
| } | |
| ErrOrValue ResolveNonstaticMember(const fxl::RefPtr<EvalContext>& context, const ExprValue& base, | |
| std::initializer_list<std::string> names) { | |
| ExprValue cur = base; | |
| for (const std::string& name : names) { | |
| ParsedIdentifier id; | |
| if (Err err = ExprParser::ParseIdentifier(name, &id); err.has_error()) | |
| return err; | |
| ErrOrValue result = ResolveNonstaticMember(context, cur, id); | |
| if (result.has_error()) | |
| return result.err(); | |
| cur = std::move(result.take_value()); | |
| } | |
| return cur; | |
| } | |
| void ResolveMemberByPointer(const fxl::RefPtr<EvalContext>& context, const ExprValue& base_ptr, | |
| const FoundMember& found_member, EvalCallback cb) { | |
| fxl::RefPtr<Collection> pointed_to; | |
| Err err = GetConcretePointedToCollection(context, base_ptr.type(), &pointed_to); | |
| if (err.has_error()) | |
| return cb(err); | |
| DoResolveMemberByPointer(context, base_ptr, pointed_to.get(), found_member, std::move(cb)); | |
| } | |
| void ResolveMemberByPointer(const fxl::RefPtr<EvalContext>& context, const ExprValue& base_ptr, | |
| const ParsedIdentifier& identifier, | |
| fit::callback<void(ErrOrValue, const FoundMember&)> cb) { | |
| if (context->ShouldPromoteToDerived()) { | |
| // Check to see if this is a reference to a base class that we can convert to a derived class. | |
| PromotePtrRefToDerived(context, PromoteToDerived::kPtrOnly, std::move(base_ptr), | |
| [context, identifier, cb = std::move(cb)](ErrOrValue result) mutable { | |
| if (result.has_error()) { | |
| cb(result, {}); | |
| } else { | |
| DoResolveMemberNameByPointer(context, result.take_value(), | |
| identifier, std::move(cb)); | |
| } | |
| }); | |
| } else { | |
| // No magic base-class resolution is required, just check the pointer. | |
| DoResolveMemberNameByPointer(context, base_ptr, identifier, std::move(cb)); | |
| } | |
| } | |
| void ResolveInherited(const fxl::RefPtr<EvalContext>& context, const ExprValue& value, | |
| const InheritancePath& path, fit::callback<void(ErrOrValue)> cb) { | |
| auto final_type = path.base_ref(); | |
| // Most cases have a constant offset and we can do that right away. | |
| if (auto opt_offset = path.BaseOffsetInDerived()) | |
| return cb(ExtractSubType(context, value, std::move(final_type), *opt_offset)); | |
| // Everything else is a DWARF expression which needs to be evaluated based on the pointers to the | |
| // data. This requires that the class have a memory address. | |
| if (value.source().type() != ExprValueSource::Type::kMemory || value.source().is_bitfield()) | |
| return cb(Err("Can't evaluate virtual inheritance on an object without a memory address.")); | |
| TargetPointer object_ptr = value.source().address(); | |
| ResolveInheritedPtr( | |
| context, object_ptr, path, | |
| [context, value, object_ptr, final_type = std::move(final_type), | |
| cb = std::move(cb)](ErrOr<TargetPointer> base_ptr) mutable { | |
| if (base_ptr.has_error()) | |
| return cb(base_ptr.err()); | |
| // The resolved data "should" be inside the original class since it's a base class so we | |
| // don't have to re-request the memory from the target. Extract that if possible. | |
| auto concrete = context->GetConcreteType(final_type.get()); | |
| uint32_t size = concrete->byte_size(); | |
| if (base_ptr.value() >= object_ptr && | |
| (base_ptr.value() - object_ptr + size <= value.data().size())) { | |
| return cb(ExtractSubType(context, value, final_type, base_ptr.value() - object_ptr)); | |
| } | |
| // The resulting pointer isn't inside the derived class. While the DWARF spec never | |
| // guarantees this is the case, in our languages it will have to be for objects to make | |
| // any sense. If we detect this, assume memory is corrupted rather than report a | |
| // likely-incorrect address. | |
| cb(Err("Virtual base class '%s' (size %u) resolved to an address 0x%" PRIx64 | |
| " outside of the derived class '%s' at 0x%" PRIx64 " (size %zu).", | |
| final_type->GetFullName().c_str(), size, base_ptr.value(), | |
| value.type()->GetFullName().c_str(), object_ptr, value.data().size())); | |
| }); | |
| } | |
| void ResolveInheritedPtr(const fxl::RefPtr<EvalContext>& context, TargetPointer derived, | |
| const InheritancePath& path, | |
| fit::function<void(ErrOr<TargetPointer>)> cb) { | |
| // In the common case there will be a constant offset. This will also handle the identity cases | |
| // (they will come up when this is called recursively) where there is no inheritance. | |
| if (auto opt_offset = path.BaseOffsetInDerived()) | |
| return cb(derived + *opt_offset); | |
| // Non-constant path, likely due to virtual inheritance. A path could have many steps, some of | |
| // which are constant offsets, and some of which are expressions for virtual inheritance. For | |
| // simplicity we do each step separately in a recursive manner. | |
| // Since an inheritance path includes both the base and derived class, there should be more than | |
| // one entry for there to be any inheritance. | |
| FX_DCHECK(path.path().size() > 1); | |
| InheritancePath remaining = path.SubPath(1); // Path left over after computing the first offset. | |
| // The first step of the inheritance chain is index 1's "from". | |
| const InheritedFrom* first_from = path.path()[1].from.get(); | |
| FX_DCHECK(first_from); | |
| switch (first_from->kind()) { | |
| case InheritedFrom::kConstant: { | |
| ResolveInheritedPtr(context, derived + first_from->offset(), remaining, std::move(cb)); | |
| return; | |
| } | |
| case InheritedFrom::kExpression: { | |
| // Run the expression (may be asynchronous). We don't use the AsyncDwarfExprEval because | |
| // that attempts to create an ExprValue from the result, which normally means dereferencing | |
| // the result of the expression as a pointer. We want the literal number resulting from | |
| // evaluating the expression. | |
| auto dwarf_eval = std::make_shared<DwarfExprEval>(); | |
| dwarf_eval->Push(derived); | |
| dwarf_eval->Eval( | |
| context->GetDataProvider(), first_from->GetSymbolContext(context->GetProcessSymbols()), | |
| first_from->location_expression(), | |
| [dwarf_eval, context, remaining = std::move(remaining), cb = std::move(cb)]( | |
| DwarfExprEval*, const Err& err) mutable { | |
| if (err.has_error()) { | |
| cb(err); | |
| } else { | |
| // Continue resolution on any remaining inheritance steps. | |
| ResolveInheritedPtr(context, static_cast<TargetPointer>(dwarf_eval->GetResult()), | |
| remaining, std::move(cb)); | |
| } | |
| // Prevent the DwarfExprEval from getting deleted from its own stack. | |
| debug_ipc::MessageLoop::Current()->PostTask(FROM_HERE, | |
| [dwarf_eval = std::move(dwarf_eval)]() {}); | |
| }); | |
| return; | |
| } | |
| } | |
| FX_NOTREACHED(); | |
| cb(Err("Internal error")); | |
| } | |
| ErrOrValue ResolveInherited(const fxl::RefPtr<EvalContext>& context, const ExprValue& value, | |
| const InheritedFrom* from) { | |
| const Type* from_type = from->from().Get()->AsType(); | |
| if (!from_type) | |
| return GetErrorForInvalidMemberOf(value); | |
| return ExtractSubType(context, value, RefPtrTo(from_type), from->offset()); | |
| } | |
| ErrOrValue ResolveInherited(const fxl::RefPtr<EvalContext>& context, const ExprValue& value, | |
| fxl::RefPtr<Type> base_type, uint64_t offset) { | |
| return ExtractSubType(context, value, std::move(base_type), offset); | |
| } | |
| Err GetConcretePointedToCollection(const fxl::RefPtr<EvalContext>& eval_context, const Type* input, | |
| fxl::RefPtr<Collection>* pointed_to) { | |
| fxl::RefPtr<Type> to_type; | |
| if (Err err = GetPointedToType(eval_context, input, &to_type); err.has_error()) | |
| return err; | |
| to_type = eval_context->GetConcreteType(to_type.get()); | |
| if (const Collection* collection = to_type->AsCollection()) { | |
| *pointed_to = fxl::RefPtr<Collection>(const_cast<Collection*>(collection)); | |
| return Err(); | |
| } | |
| return Err("Attempting to dereference a pointer to '%s' which is not a class, struct, or union.", | |
| to_type->GetFullName().c_str()); | |
| } | |
| } // namespace zxdb |