blob: 723b4a59f7aef1a4036c7ebc74f8beec830c42b5 [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/resolve_collection.h"
#include "src/developer/debug/shared/message_loop.h"
#include "src/developer/debug/zxdb/expr/bitfield.h"
#include "src/developer/debug/zxdb/expr/eval_context.h"
#include "src/developer/debug/zxdb/expr/eval_dwarf_expr.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<Collection> base_type = context->GetConcreteTypeAs<Collection>(base.type());
if (!base_type)
return Err("No type information for collection.");
return FindMemberWithErr(context, base_type.get(), 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()->As<Collection>())
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()->As<Type>());
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();
std::optional<TaggedData> extracted = base.data().Extract(offset, size);
if (!extracted) {
return Err("Invalid data offset %" PRIu32 " in object of size %zu.", offset,
base.data().size());
}
return ExprValue(std::move(sub_type), std::move(*extracted), 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<Collection> coll = context->GetConcreteTypeAs<Collection>(base.type());
if (!coll)
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.get(), 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: {
// This callback is executed once the DWARF expression has produced the result. It converts
// the result to the base class pointer and continues evaluating.
auto on_expr_complete = [context, remaining = std::move(remaining), cb = std::move(cb)](
DwarfExprEval& eval, const Err& err) mutable {
if (err.has_error()) {
cb(err);
} else {
// Continue resolution on any remaining inheritance steps.
if (eval.GetResultType() == DwarfExprEval::ResultType::kData)
return cb(Err("Unexpected result type from DWARF expression."));
DwarfStackEntry result = eval.GetResult();
if (!result.TreatAsUnsigned())
return cb(Err("Unexpected result type from DWARF expression."));
ResolveInheritedPtr(context, static_cast<TargetPointer>(result.unsigned_value()),
remaining, std::move(cb));
}
};
// The expression is evaluated by pushing the derived pointer on the evaluation stack and
// executing the DWARF expression to get the resulting base class pointer.
auto async_eval = fxl::MakeRefCounted<AsyncDwarfExprEval>(std::move(on_expr_complete));
async_eval->dwarf_eval().Push(DwarfStackEntry(derived));
async_eval->Eval(context->GetDataProvider(),
first_from->GetSymbolContext(context->GetProcessSymbols()),
first_from->location_expression());
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()->As<Type>();
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 ((*pointed_to = eval_context->GetConcreteTypeAs<Collection>(to_type.get())))
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