| // 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 |