| // 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_array.h" | |
| #include "src/developer/debug/shared/logging/logging.h" | |
| #include "src/developer/debug/zxdb/common/err.h" | |
| #include "src/developer/debug/zxdb/expr/eval_context.h" | |
| #include "src/developer/debug/zxdb/expr/expr_value.h" | |
| #include "src/developer/debug/zxdb/expr/pretty_type.h" | |
| #include "src/developer/debug/zxdb/expr/pretty_type_manager.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/array_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/modified_type.h" | |
| #include "src/developer/debug/zxdb/symbols/symbol_data_provider.h" | |
| #include "src/developer/debug/zxdb/symbols/type.h" | |
| namespace zxdb { | |
| namespace { | |
| enum class ArrayKind { | |
| kError, | |
| kStatic, // int[4] | |
| kPointer, // int* | |
| }; | |
| struct ArrayInfo { | |
| ArrayKind kind = ArrayKind::kError; | |
| // Guaranteed for kind != kError. | |
| fxl::RefPtr<Type> original_value_type; // Use for error messages. | |
| fxl::RefPtr<Type> concrete_value_type; // Use to get the element count, etc. | |
| fxl::RefPtr<Type> erased_type; // Use to get the size of one element, if the type does | |
| // not match |concrete_value_type|. | |
| // Valid when kind == kStatic. | |
| fxl::RefPtr<ArrayType> static_type; | |
| }; | |
| // On success, the ArrayInfo will have kind != kError. | |
| ErrOr<ArrayInfo> ClassifyArray(const fxl::RefPtr<EvalContext>& eval_context, const ExprValue& array, | |
| const Type* erased_type) { | |
| if (!array.type()) | |
| return Err("No type information."); | |
| ArrayInfo info; | |
| info.erased_type = RefPtrTo(erased_type); | |
| fxl::RefPtr<Type> concrete = eval_context->GetConcreteType(array.type()); | |
| if (const ArrayType* array_type = concrete->As<ArrayType>()) { | |
| info.kind = ArrayKind::kStatic; | |
| info.static_type = RefPtrTo(array_type); | |
| info.original_value_type = RefPtrTo(array_type->value_type()); | |
| info.concrete_value_type = eval_context->GetConcreteType(info.original_value_type); | |
| if (!info.concrete_value_type) | |
| return Err("Bad type information for '%s'.", array.type()->GetFullName().c_str()); | |
| return info; | |
| } else if (const ModifiedType* modified_type = concrete->As<ModifiedType>()) { | |
| if (modified_type->tag() == DwarfTag::kPointerType) { | |
| info.kind = ArrayKind::kPointer; | |
| info.original_value_type = RefPtrTo(modified_type->modified().Get()->As<Type>()); | |
| info.concrete_value_type = eval_context->GetConcreteType(info.original_value_type); | |
| if (!info.concrete_value_type) | |
| return Err("Bad type information for '%s'.", array.type()->GetFullName().c_str()); | |
| return info; | |
| } | |
| } | |
| return Err("Not an array type."); | |
| } | |
| void ArrayFromPointer(const fxl::RefPtr<EvalContext>& eval_context, uint64_t begin_address, | |
| fxl::RefPtr<Type> element_type, size_t element_count, EvalCallback cb) { | |
| fxl::RefPtr<Type> concrete_element_type = eval_context->GetConcreteType(element_type); | |
| if (!concrete_element_type) | |
| return cb(Err("Bad type information.")); | |
| auto array_type = fxl::MakeRefCounted<ArrayType>(element_type, element_count); | |
| eval_context->GetDataProvider()->GetMemoryAsync( | |
| begin_address, array_type->byte_size(), | |
| [array_type, begin_address, cb = std::move(cb)](const Err& err, | |
| std::vector<uint8_t> data) mutable { | |
| if (err.has_error()) | |
| return cb(err); | |
| if (data.size() < array_type->byte_size()) | |
| return cb(Err("Array memory not valid.")); // A short read indicates invalid memory. | |
| FX_DCHECK(data.size() == array_type->byte_size()); | |
| cb(ExprValue(array_type, std::move(data), ExprValueSource(begin_address))); | |
| }); | |
| } | |
| // Handles the "int[4]" case. | |
| ErrOrValueVector ResolveStaticArray(const ExprValue& array, const ArrayInfo& info, | |
| size_t begin_index, size_t end_index) { | |
| if (array.data().size() < info.static_type->byte_size()) { | |
| return Err( | |
| "Array data (%zu bytes) is too small for the expected size " | |
| "(%u bytes).", | |
| array.data().size(), info.static_type->byte_size()); | |
| } | |
| uint32_t type_size = info.concrete_value_type->byte_size(); | |
| std::vector<ExprValue> result; | |
| result.reserve(end_index - begin_index); | |
| for (size_t i = begin_index; i < end_index; i++) { | |
| size_t begin_offset = i * type_size; | |
| if (begin_offset + type_size > array.data().size()) | |
| break; | |
| // Describe the source of this data. | |
| ExprValueSource source = array.source(); | |
| if (source.type() == ExprValueSource::Type::kMemory) { | |
| source = source.GetOffsetInto(begin_offset); | |
| } else if (source.type() == ExprValueSource::Type::kRegister) { | |
| // Vector register, compute the bit shifts for this subset. This assumes little-endian | |
| // so we can compute the bit shifts to write to the register from the left. | |
| source = ExprValueSource(source.register_id(), type_size * 8, | |
| source.bit_shift() + (begin_offset * 8)); | |
| } | |
| // else keep as original temporary/constant source. | |
| // Extract the array element data. | |
| std::optional<TaggedData> data = array.data().Extract(begin_offset, type_size); | |
| if (!data) | |
| return Err("Array data out of range."); | |
| result.emplace_back(info.original_value_type, std::move(*data), source); | |
| } | |
| return result; | |
| } | |
| // Handles the "Foo*" case. | |
| void ResolvePointerArray(const fxl::RefPtr<EvalContext>& eval_context, const ExprValue& array, | |
| const ArrayInfo& info, size_t begin_index, size_t end_index, | |
| fit::callback<void(ErrOrValueVector)> cb) { | |
| // The address is stored in the contents of the array value. | |
| auto pointer_value_or = ExtractPointerValue(array); | |
| if (pointer_value_or.has_error()) | |
| return cb(pointer_value_or.err()); | |
| TargetPointer base_address = pointer_value_or.value(); | |
| fxl::RefPtr<Type> type; | |
| if (info.erased_type) { | |
| type = info.erased_type; | |
| } else { | |
| type = info.concrete_value_type; | |
| } | |
| uint32_t type_size = type->byte_size(); | |
| TargetPointer begin_address = base_address + type_size * begin_index; | |
| TargetPointer end_address = base_address + type_size * end_index; | |
| eval_context->GetDataProvider()->GetMemoryAsync( | |
| begin_address, end_address - begin_address, | |
| [begin_address, type, count = end_index - begin_index, cb = std::move(cb)]( | |
| const Err& err, std::vector<uint8_t> data) mutable { | |
| if (err.has_error()) | |
| return cb(err); | |
| // Convert returned raw memory to ExprValues. | |
| std::vector<ExprValue> result; | |
| result.reserve(count); | |
| uint32_t type_size = type->byte_size(); | |
| for (size_t i = 0; i < count; i++) { | |
| size_t begin_offset = i * type_size; | |
| if (begin_offset + type_size > data.size()) | |
| break; // Ran out of data, leave remaining results uninitialized. | |
| std::vector<uint8_t> item_data(&data[begin_offset], &data[begin_offset + type_size]); | |
| result.emplace_back(type, std::move(item_data), | |
| ExprValueSource(begin_address + begin_offset)); | |
| } | |
| cb(std::move(result)); | |
| }); | |
| } | |
| // Backend for the single-item and async multiple item array resolution. | |
| void DoResolveArray(const fxl::RefPtr<EvalContext>& eval_context, const ExprValue& array, | |
| const ArrayInfo& info, size_t begin_index, size_t end_index, | |
| fit::callback<void(ErrOrValueVector)> cb) { | |
| switch (info.kind) { | |
| case ArrayKind::kStatic: | |
| cb(ResolveStaticArray(array, info, begin_index, end_index)); | |
| break; | |
| case ArrayKind::kPointer: | |
| ResolvePointerArray(eval_context, array, info, begin_index, end_index, std::move(cb)); | |
| break; | |
| default: | |
| FX_NOTREACHED(); | |
| break; | |
| } | |
| } | |
| } // namespace | |
| void ResolveArray(const fxl::RefPtr<EvalContext>& eval_context, const Type* erased_type, | |
| const ExprValue& array, size_t begin_index, size_t end_index, | |
| fit::callback<void(ErrOrValueVector)> cb) { | |
| auto info_or = ClassifyArray(eval_context, array, erased_type); | |
| if (info_or.has_error()) | |
| return cb(info_or.err()); | |
| DoResolveArray(eval_context, array, info_or.value(), begin_index, end_index, std::move(cb)); | |
| } | |
| void ResolveArrayItem(const fxl::RefPtr<EvalContext>& eval_context, const ExprValue& array, | |
| size_t index, EvalCallback cb) { | |
| auto info_or = ClassifyArray(eval_context, array, nullptr); | |
| if (info_or.ok()) { | |
| // Do regular array access. | |
| DoResolveArray(eval_context, array, info_or.value(), index, index + 1, | |
| [cb = std::move(cb)](ErrOrValueVector result) mutable { | |
| if (result.has_error()) { | |
| cb(result.err()); | |
| } else if (result.value().empty()) { | |
| // Short read. | |
| cb(Err("Invalid array index.")); | |
| } else { | |
| cb(std::move(result.value()[0])); // Should have only one value. | |
| } | |
| }); | |
| } else { | |
| // Not an array, check for pretty types that support array access. | |
| if (const PrettyType* pretty = eval_context->GetPrettyTypeManager().GetForType(array.type())) { | |
| if (auto array_access = pretty->GetArrayAccess()) | |
| return array_access(eval_context, array, index, std::move(cb)); | |
| } | |
| cb(Err("Can't resolve an array access on type '%s'.", | |
| array.type() ? array.type()->GetFullName().c_str() : "<Unknown>")); | |
| } | |
| } | |
| void CoerceArraySize(const fxl::RefPtr<EvalContext>& eval_context, const ExprValue& array, | |
| size_t new_size, EvalCallback cb) { | |
| auto info_or = ClassifyArray(eval_context, array, nullptr); | |
| if (info_or.has_error()) | |
| return cb(info_or.err()); | |
| ArrayInfo& info = info_or.value(); | |
| switch (info.kind) { | |
| case ArrayKind::kError: | |
| FX_NOTREACHED(); // Errors should be caught above. | |
| break; | |
| case ArrayKind::kStatic: { | |
| if (info.static_type->num_elts() && new_size <= *info.static_type->num_elts()) { | |
| // Shrinking a static array, can just extract the subrange. | |
| auto new_array_type = fxl::MakeRefCounted<ArrayType>(info.original_value_type, new_size); | |
| auto extracted = array.data().Extract(0, new_array_type->byte_size()); | |
| if (!extracted) | |
| return cb(Err("Array contains less data than expected.")); | |
| cb(ExprValue(std::move(new_array_type), std::move(*extracted), array.source())); | |
| } else { | |
| // Expanding a static array. This requires the memory be re-fetched. | |
| if (array.source().type() != ExprValueSource::Type::kMemory) | |
| return cb(Err("Can not expand array that is not in memory.")); | |
| ArrayFromPointer(eval_context, array.source().address(), info.original_value_type, new_size, | |
| std::move(cb)); | |
| } | |
| break; | |
| } | |
| case ArrayKind::kPointer: { | |
| // Fetch the memory to convert to an array. | |
| auto pointer_value_or = ExtractPointerValue(array); | |
| if (pointer_value_or.has_error()) | |
| return cb(pointer_value_or.err()); | |
| fxl::RefPtr<Type> type = nullptr; | |
| if (info.erased_type) { | |
| type = info.erased_type; | |
| } else { | |
| type = info.original_value_type; | |
| } | |
| ArrayFromPointer(eval_context, pointer_value_or.value(), type, new_size, std::move(cb)); | |
| break; | |
| } | |
| } | |
| } | |
| } // namespace zxdb |