[torque][tools] Define layout of DescriptorArray for postmortem tools
This change defines a way that v8_debug_helper can describe object fields which are packed structs, and uses it for the "descriptors" field in DescriptorArray. In more detail: - debug-helper.h (the public interface for v8_debug_helper) adds a size and an optional list of struct properties to ObjectProperty. - debug-helper-internal.h mirrors those changes to the internal class hierarchy which maintains proper unique_ptr ownership. - In src/torque/class-debug-reader-generator.cc, - Some existing logic is moved into smaller functions. - New logic is added to generate the field list for structs. Example output is included in a comment above the function GenerateGetPropsChunkForField. Bug: v8:9376 Change-Id: I531acac039ccb42050641448a4cbaec26186a7bc Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1894362 Commit-Queue: Seth Brenith <seth.brenith@microsoft.com> Reviewed-by: Tobias Tebbi <tebbi@chromium.org> Cr-Commit-Position: refs/heads/master@{#65079}
This commit is contained in:
parent
c968607e12
commit
6b11b700d7
@ -26,6 +26,330 @@ const d::ClassList kObjectClassList {
|
||||
)";
|
||||
|
||||
namespace {
|
||||
enum TypeStorage {
|
||||
kAsStoredInHeap,
|
||||
kUncompressed,
|
||||
};
|
||||
|
||||
// A convenient way to keep track of several different ways that we might need
|
||||
// to represent a field's type in the generated C++.
|
||||
class DebugFieldType {
|
||||
public:
|
||||
explicit DebugFieldType(const Field& field) : field_(field) {}
|
||||
|
||||
bool IsTagged() const {
|
||||
return field_.name_and_type.type->IsSubtypeOf(TypeOracle::GetTaggedType());
|
||||
}
|
||||
|
||||
// Returns the type that should be used for this field's value within code
|
||||
// that is compiled as part of the debug helper library. In particular, this
|
||||
// simplifies any tagged type to a plain uintptr_t because the debug helper
|
||||
// compiles without most of the V8 runtime code.
|
||||
std::string GetValueType(TypeStorage storage) const {
|
||||
if (IsTagged()) {
|
||||
return storage == kAsStoredInHeap ? "i::Tagged_t" : "uintptr_t";
|
||||
}
|
||||
// Note that we need constexpr names to resolve correctly in the global
|
||||
// namespace, because we're passing them as strings to a debugging
|
||||
// extension. We can verify this during build of the debug helper, because
|
||||
// we use this type for a local variable below, and generate this code in
|
||||
// a disjoint namespace. However, we can't emit a useful error at this
|
||||
// point. Instead we'll emit a comment that might be helpful.
|
||||
return GetOriginalType(storage) +
|
||||
" /*Failing? Ensure constexpr type name is fully qualified and "
|
||||
"necessary #includes are in debug-helper-internal.h*/";
|
||||
}
|
||||
|
||||
// Returns the type that should be used to represent a field's type to
|
||||
// debugging tools that have full V8 symbols. The types returned from this
|
||||
// method are fully qualified and may refer to object types that are not
|
||||
// included in the compilation of the debug helper library.
|
||||
std::string GetOriginalType(TypeStorage storage) const {
|
||||
if (field_.name_and_type.type->IsStructType()) {
|
||||
// There's no meaningful type we could use here, because the V8 symbols
|
||||
// don't have any definition of a C++ struct matching this struct type.
|
||||
return "";
|
||||
}
|
||||
if (IsTagged()) {
|
||||
if (storage == kAsStoredInHeap && COMPRESS_POINTERS_BOOL) {
|
||||
return "v8::internal::TaggedValue";
|
||||
}
|
||||
base::Optional<const ClassType*> field_class_type =
|
||||
field_.name_and_type.type->ClassSupertype();
|
||||
return "v8::internal::" +
|
||||
(field_class_type.has_value()
|
||||
? (*field_class_type)->GetGeneratedTNodeTypeName()
|
||||
: "Object");
|
||||
}
|
||||
const Type* constexpr_version =
|
||||
field_.name_and_type.type->ConstexprVersion();
|
||||
if (constexpr_version == nullptr) {
|
||||
Error("Type '", field_.name_and_type.type->ToString(),
|
||||
"' requires a constexpr representation")
|
||||
.Position(field_.pos);
|
||||
return "";
|
||||
}
|
||||
return constexpr_version->GetGeneratedTypeName();
|
||||
}
|
||||
|
||||
// Returns the field's size in bytes.
|
||||
size_t GetSize() const {
|
||||
size_t size = 0;
|
||||
std::tie(size, std::ignore) = field_.GetFieldSizeInformation();
|
||||
return size;
|
||||
}
|
||||
|
||||
// Returns the name of the function for getting this field's address.
|
||||
std::string GetAddressGetter() {
|
||||
return "Get" + CamelifyString(field_.name_and_type.name) + "Address";
|
||||
}
|
||||
|
||||
private:
|
||||
const Field& field_;
|
||||
};
|
||||
|
||||
// Emits a function to get the address of a field within a class, based on the
|
||||
// member variable {address_}, which is a tagged pointer. Example
|
||||
// implementation:
|
||||
//
|
||||
// uintptr_t TqFixedArray::GetObjectsAddress() const {
|
||||
// return address_ - i::kHeapObjectTag + 16;
|
||||
// }
|
||||
void GenerateFieldAddressAccessor(const Field& field,
|
||||
const std::string& class_name,
|
||||
std::ostream& h_contents,
|
||||
std::ostream& cc_contents) {
|
||||
DebugFieldType debug_field_type(field);
|
||||
|
||||
const std::string address_getter = debug_field_type.GetAddressGetter();
|
||||
|
||||
h_contents << " uintptr_t " << address_getter << "() const;\n";
|
||||
cc_contents << "\nuintptr_t Tq" << class_name << "::" << address_getter
|
||||
<< "() const {\n";
|
||||
cc_contents << " return address_ - i::kHeapObjectTag + " << field.offset
|
||||
<< ";\n";
|
||||
cc_contents << "}\n";
|
||||
}
|
||||
|
||||
// Emits a function to get the value of a field, or the value from an indexed
|
||||
// position within an array field, based on the member variable {address_},
|
||||
// which is a tagged pointer, and the parameter {accessor}, a function pointer
|
||||
// that allows for fetching memory from the debuggee. The returned result
|
||||
// includes both a "validity", indicating whether the memory could be fetched,
|
||||
// and the fetched value. If the field contains tagged data, then these
|
||||
// functions call EnsureDecompressed to expand compressed data. Example:
|
||||
//
|
||||
// Value<uintptr_t> TqMap::GetPrototypeValue(d::MemoryAccessor accessor) const {
|
||||
// i::Tagged_t value{};
|
||||
// d::MemoryAccessResult validity = accessor(
|
||||
// GetPrototypeAddress(),
|
||||
// reinterpret_cast<uint8_t*>(&value),
|
||||
// sizeof(value));
|
||||
// return {validity, EnsureDecompressed(value, address_)};
|
||||
// }
|
||||
//
|
||||
// For array fields, an offset parameter is included. Example:
|
||||
//
|
||||
// Value<uintptr_t> TqFixedArray::GetObjectsValue(d::MemoryAccessor accessor,
|
||||
// size_t offset) const {
|
||||
// i::Tagged_t value{};
|
||||
// d::MemoryAccessResult validity = accessor(
|
||||
// GetObjectsAddress() + offset * sizeof(value),
|
||||
// reinterpret_cast<uint8_t*>(&value),
|
||||
// sizeof(value));
|
||||
// return {validity, EnsureDecompressed(value, address_)};
|
||||
// }
|
||||
void GenerateFieldValueAccessor(const Field& field,
|
||||
const std::string& class_name,
|
||||
std::ostream& h_contents,
|
||||
std::ostream& cc_contents) {
|
||||
// Currently not implemented for struct fields.
|
||||
if (field.name_and_type.type->IsStructType()) return;
|
||||
|
||||
DebugFieldType debug_field_type(field);
|
||||
|
||||
const std::string address_getter = debug_field_type.GetAddressGetter();
|
||||
const std::string field_getter =
|
||||
"Get" + CamelifyString(field.name_and_type.name) + "Value";
|
||||
|
||||
std::string index_param;
|
||||
std::string index_offset;
|
||||
if (field.index) {
|
||||
index_param = ", size_t offset";
|
||||
index_offset = " + offset * sizeof(value)";
|
||||
}
|
||||
|
||||
if (!field.name_and_type.type->IsStructType()) {
|
||||
std::string field_value_type = debug_field_type.GetValueType(kUncompressed);
|
||||
h_contents << " Value<" << field_value_type << "> " << field_getter
|
||||
<< "(d::MemoryAccessor accessor " << index_param << ") const;\n";
|
||||
cc_contents << "\nValue<" << field_value_type << "> Tq" << class_name
|
||||
<< "::" << field_getter << "(d::MemoryAccessor accessor"
|
||||
<< index_param << ") const {\n";
|
||||
cc_contents << " " << debug_field_type.GetValueType(kAsStoredInHeap)
|
||||
<< " value{};\n";
|
||||
cc_contents << " d::MemoryAccessResult validity = accessor("
|
||||
<< address_getter << "()" << index_offset
|
||||
<< ", reinterpret_cast<uint8_t*>(&value), sizeof(value));\n";
|
||||
cc_contents << " return {validity, "
|
||||
<< (debug_field_type.IsTagged()
|
||||
? "EnsureDecompressed(value, address_)"
|
||||
: "value")
|
||||
<< "};\n";
|
||||
cc_contents << "}\n";
|
||||
}
|
||||
}
|
||||
|
||||
// Emits a portion of the member function GetProperties that is responsible for
|
||||
// adding data about the current field to a result vector called "result".
|
||||
// Example output:
|
||||
//
|
||||
// result.push_back(std::make_unique<ObjectProperty>(
|
||||
// "prototype", // Field name
|
||||
// "v8::internal::HeapObject", // Field type
|
||||
// "v8::internal::HeapObject", // Decompressed type
|
||||
// GetPrototypeAddress(), // Field address
|
||||
// 1, // Number of values
|
||||
// 8, // Size of value
|
||||
// std::vector<std::unique_ptr<StructProperty>>(), // Struct fields
|
||||
// d::PropertyKind::kSingle)); // Field kind
|
||||
//
|
||||
// In builds with pointer compression enabled, the field type for tagged values
|
||||
// is "v8::internal::TaggedValue" (a four-byte class) and the decompressed type
|
||||
// is a normal Object subclass that describes the expanded eight-byte type.
|
||||
//
|
||||
// If the field is an array, then its length is fetched from the debuggee. This
|
||||
// could fail if the debuggee has incomplete memory, so the "validity" from that
|
||||
// fetch is used to determine the result PropertyKind, which will say whether
|
||||
// the array's length is known.
|
||||
//
|
||||
// If the field's type is a struct, then a local variable is created and filled
|
||||
// with descriptions of each of the struct's fields. The type and decompressed
|
||||
// type in the ObjectProperty are set to the empty string, to indicate to the
|
||||
// caller that the struct fields vector should be used instead.
|
||||
//
|
||||
// The following example is an array of structs, so it uses both of the optional
|
||||
// components described above:
|
||||
//
|
||||
// std::vector<std::unique_ptr<StructProperty>> descriptors_struct_field_list;
|
||||
// descriptors_struct_field_list.push_back(std::make_unique<StructProperty>(
|
||||
// "key", // Struct field name
|
||||
// "v8::internal::PrimitiveHeapObject", // Struct field type
|
||||
// "v8::internal::PrimitiveHeapObject", // Struct field decompressed type
|
||||
// 0)); // Offset within struct data
|
||||
// // The line above is repeated for other struct fields. Omitted here.
|
||||
// Value<uint16_t> indexed_field_count =
|
||||
// GetNumberOfAllDescriptorsValue(accessor); // Fetch the array length.
|
||||
// result.push_back(std::make_unique<ObjectProperty>(
|
||||
// "descriptors", // Field name
|
||||
// "", // Field type
|
||||
// "", // Decompressed type
|
||||
// GetDescriptorsAddress(), // Field address
|
||||
// indexed_field_count.value, // Number of values
|
||||
// 24, // Size of value
|
||||
// std::move(descriptors_struct_field_list), // Struct fields
|
||||
// GetArrayKind(indexed_field_count.validity))); // Field kind
|
||||
void GenerateGetPropsChunkForField(const Field& field,
|
||||
std::ostream& get_props_impl) {
|
||||
DebugFieldType debug_field_type(field);
|
||||
|
||||
// If the current field is a struct, create a vector describing its fields.
|
||||
std::string struct_field_list =
|
||||
"std::vector<std::unique_ptr<StructProperty>>()";
|
||||
if (const StructType* field_struct_type =
|
||||
StructType::DynamicCast(field.name_and_type.type)) {
|
||||
struct_field_list = field.name_and_type.name + "_struct_field_list";
|
||||
get_props_impl << " std::vector<std::unique_ptr<StructProperty>> "
|
||||
<< struct_field_list << ";\n";
|
||||
for (const Field& struct_field : field_struct_type->fields()) {
|
||||
DebugFieldType struct_field_type(struct_field);
|
||||
get_props_impl << " " << struct_field_list
|
||||
<< ".push_back(std::make_unique<StructProperty>(\""
|
||||
<< struct_field.name_and_type.name << "\", \""
|
||||
<< struct_field_type.GetOriginalType(kAsStoredInHeap)
|
||||
<< "\", \""
|
||||
<< struct_field_type.GetOriginalType(kUncompressed)
|
||||
<< "\", " << struct_field.offset << "));\n";
|
||||
}
|
||||
struct_field_list = "std::move(" + struct_field_list + ")";
|
||||
}
|
||||
|
||||
// The number of values and property kind for non-indexed properties:
|
||||
std::string count_value = "1";
|
||||
std::string property_kind = "d::PropertyKind::kSingle";
|
||||
|
||||
// If the field is indexed, emit a fetch of the array length, and change
|
||||
// count_value and property_kind to be the correct values for an array.
|
||||
if (field.index) {
|
||||
const Type* index_type = field.index->type;
|
||||
std::string index_type_name;
|
||||
if (index_type == TypeOracle::GetSmiType()) {
|
||||
index_type_name = "uintptr_t";
|
||||
count_value =
|
||||
"i::PlatformSmiTagging::SmiToInt(indexed_field_count.value)";
|
||||
} else if (!index_type->IsSubtypeOf(TypeOracle::GetTaggedType())) {
|
||||
const Type* constexpr_index = index_type->ConstexprVersion();
|
||||
if (constexpr_index == nullptr) {
|
||||
Error("Type '", index_type->ToString(),
|
||||
"' requires a constexpr representation");
|
||||
return;
|
||||
}
|
||||
index_type_name = constexpr_index->GetGeneratedTypeName();
|
||||
count_value = "indexed_field_count.value";
|
||||
} else {
|
||||
Error("Unsupported index type: ", index_type);
|
||||
return;
|
||||
}
|
||||
get_props_impl << " Value<" << index_type_name
|
||||
<< "> indexed_field_count = Get"
|
||||
<< CamelifyString(field.index->name) << "Value(accessor);\n";
|
||||
property_kind = "GetArrayKind(indexed_field_count.validity)";
|
||||
}
|
||||
|
||||
get_props_impl << " result.push_back(std::make_unique<ObjectProperty>(\""
|
||||
<< field.name_and_type.name << "\", \""
|
||||
<< debug_field_type.GetOriginalType(kAsStoredInHeap)
|
||||
<< "\", \"" << debug_field_type.GetOriginalType(kUncompressed)
|
||||
<< "\", " << debug_field_type.GetAddressGetter() << "(), "
|
||||
<< count_value << ", " << debug_field_type.GetSize() << ", "
|
||||
<< struct_field_list << ", " << property_kind << "));\n";
|
||||
}
|
||||
|
||||
// For any Torque-defined class Foo, this function generates a class TqFoo which
|
||||
// allows for convenient inspection of objects of type Foo in a crash dump or
|
||||
// time travel session (where we can't just run the object printer). The
|
||||
// generated class looks something like this:
|
||||
//
|
||||
// class TqFoo : public TqParentOfFoo {
|
||||
// public:
|
||||
// // {address} is an uncompressed tagged pointer.
|
||||
// inline TqFoo(uintptr_t address) : TqParentOfFoo(address) {}
|
||||
//
|
||||
// // Creates and returns a list of this object's properties.
|
||||
// std::vector<std::unique_ptr<ObjectProperty>> GetProperties(
|
||||
// d::MemoryAccessor accessor) const override;
|
||||
//
|
||||
// // Returns the name of this class, "v8::internal::Foo".
|
||||
// const char* GetName() const override;
|
||||
//
|
||||
// // Visitor pattern; implementation just calls visitor->VisitFoo(this).
|
||||
// void Visit(TqObjectVisitor* visitor) const override;
|
||||
//
|
||||
// // Returns whether Foo is a superclass of the other object's type.
|
||||
// bool IsSuperclassOf(const TqObject* other) const override;
|
||||
//
|
||||
// // Field accessors omitted here (see other comments above).
|
||||
// };
|
||||
//
|
||||
// Four output streams are written:
|
||||
//
|
||||
// h_contents: A header file which gets the class definition above.
|
||||
// cc_contents: A cc file which gets implementations of that class's members.
|
||||
// visitor: A stream that is accumulating the definition of the class
|
||||
// TqObjectVisitor. Each class Foo gets its own virtual method
|
||||
// VisitFoo in TqObjectVisitor.
|
||||
// class_names: A stream that is accumulating a list of strings including fully-
|
||||
// qualified names for every Torque-defined class type.
|
||||
void GenerateClassDebugReader(const ClassType& type, std::ostream& h_contents,
|
||||
std::ostream& cc_contents, std::ostream& visitor,
|
||||
std::ostream& class_names,
|
||||
@ -70,6 +394,9 @@ void GenerateClassDebugReader(const ClassType& type, std::ostream& h_contents,
|
||||
<< name << "*>(other) != nullptr;\n";
|
||||
cc_contents << "}\n";
|
||||
|
||||
// By default, the visitor method for this class just calls the visitor method
|
||||
// for this class's parent. This allows custom visitors to only override a few
|
||||
// classes they care about without needing to know about the entire hierarchy.
|
||||
visitor << " virtual void Visit" << name << "(const Tq" << name
|
||||
<< "* object) {\n";
|
||||
visitor << " Visit" << super_name << "(object);\n";
|
||||
@ -80,123 +407,20 @@ void GenerateClassDebugReader(const ClassType& type, std::ostream& h_contents,
|
||||
std::stringstream get_props_impl;
|
||||
|
||||
for (const Field& field : type.fields()) {
|
||||
const Type* field_type = field.name_and_type.type;
|
||||
if (field_type == TypeOracle::GetVoidType()) continue;
|
||||
if (field_type->IsStructType()) continue; // Not yet supported.
|
||||
const std::string& field_name = field.name_and_type.name;
|
||||
bool is_field_tagged = field_type->IsSubtypeOf(TypeOracle::GetTaggedType());
|
||||
base::Optional<const ClassType*> field_class_type =
|
||||
field_type->ClassSupertype();
|
||||
size_t field_size = 0;
|
||||
std::string field_size_string;
|
||||
std::tie(field_size, field_size_string) = field.GetFieldSizeInformation();
|
||||
|
||||
std::string field_value_type;
|
||||
std::string field_value_type_compressed;
|
||||
std::string field_cc_type;
|
||||
std::string field_cc_type_compressed;
|
||||
if (is_field_tagged) {
|
||||
field_value_type = "uintptr_t";
|
||||
field_value_type_compressed = "i::Tagged_t";
|
||||
field_cc_type = "v8::internal::" +
|
||||
(field_class_type.has_value()
|
||||
? (*field_class_type)->GetGeneratedTNodeTypeName()
|
||||
: "Object");
|
||||
field_cc_type_compressed =
|
||||
COMPRESS_POINTERS_BOOL ? "v8::internal::TaggedValue" : field_cc_type;
|
||||
} else {
|
||||
const Type* constexpr_version = field_type->ConstexprVersion();
|
||||
if (constexpr_version == nullptr) {
|
||||
Error("Type '", field_type->ToString(),
|
||||
"' requires a constexpr representation");
|
||||
continue;
|
||||
}
|
||||
field_cc_type = constexpr_version->GetGeneratedTypeName();
|
||||
field_cc_type_compressed = field_cc_type;
|
||||
// Note that we need constexpr names to resolve correctly in the global
|
||||
// namespace, because we're passing them as strings to a debugging
|
||||
// extension. We can verify this during build of the debug helper, because
|
||||
// we use this type for a local variable below, and generate this code in
|
||||
// a disjoint namespace. However, we can't emit a useful error at this
|
||||
// point. Instead we'll emit a comment that might be helpful.
|
||||
field_value_type =
|
||||
field_cc_type +
|
||||
" /*Failing? Ensure constexpr type name is fully qualified and "
|
||||
"necessary #includes are in debug-helper-internal.h*/";
|
||||
field_value_type_compressed = field_value_type;
|
||||
}
|
||||
|
||||
const std::string field_getter =
|
||||
"Get" + CamelifyString(field_name) + "Value";
|
||||
const std::string address_getter =
|
||||
"Get" + CamelifyString(field_name) + "Address";
|
||||
|
||||
std::string indexed_field_info;
|
||||
std::string index_param;
|
||||
std::string index_offset;
|
||||
if (field.index) {
|
||||
const Type* index_type = field.index->type;
|
||||
std::string index_type_name;
|
||||
std::string index_value;
|
||||
if (index_type == TypeOracle::GetSmiType()) {
|
||||
index_type_name = "uintptr_t";
|
||||
index_value =
|
||||
"i::PlatformSmiTagging::SmiToInt(indexed_field_count.value)";
|
||||
} else if (!index_type->IsSubtypeOf(TypeOracle::GetTaggedType())) {
|
||||
const Type* constexpr_index = index_type->ConstexprVersion();
|
||||
if (constexpr_index == nullptr) {
|
||||
Error("Type '", index_type->ToString(),
|
||||
"' requires a constexpr representation");
|
||||
continue;
|
||||
}
|
||||
index_type_name = constexpr_index->GetGeneratedTypeName();
|
||||
index_value = "indexed_field_count.value";
|
||||
} else {
|
||||
Error("Unsupported index type: ", index_type);
|
||||
continue;
|
||||
}
|
||||
get_props_impl << " Value<" << index_type_name
|
||||
<< "> indexed_field_count = Get"
|
||||
<< CamelifyString(field.index->name)
|
||||
<< "Value(accessor);\n";
|
||||
indexed_field_info =
|
||||
", " + index_value + ", GetArrayKind(indexed_field_count.validity)";
|
||||
index_param = ", size_t offset";
|
||||
index_offset = " + offset * sizeof(value)";
|
||||
}
|
||||
get_props_impl << " result.push_back(std::make_unique<ObjectProperty>(\""
|
||||
<< field_name << "\", \"" << field_cc_type_compressed
|
||||
<< "\", \"" << field_cc_type << "\", " << address_getter
|
||||
<< "()" << indexed_field_info << "));\n";
|
||||
|
||||
h_contents << " uintptr_t " << address_getter << "() const;\n";
|
||||
h_contents << " Value<" << field_value_type << "> " << field_getter
|
||||
<< "(d::MemoryAccessor accessor " << index_param << ") const;\n";
|
||||
cc_contents << "\nuintptr_t Tq" << name << "::" << address_getter
|
||||
<< "() const {\n";
|
||||
cc_contents << " return address_ - i::kHeapObjectTag + " << field.offset
|
||||
<< ";\n";
|
||||
cc_contents << "}\n";
|
||||
cc_contents << "\nValue<" << field_value_type << "> Tq" << name
|
||||
<< "::" << field_getter << "(d::MemoryAccessor accessor"
|
||||
<< index_param << ") const {\n";
|
||||
cc_contents << " " << field_value_type_compressed << " value{};\n";
|
||||
cc_contents << " d::MemoryAccessResult validity = accessor("
|
||||
<< address_getter << "()" << index_offset
|
||||
<< ", reinterpret_cast<uint8_t*>(&value), sizeof(value));\n";
|
||||
cc_contents << " return {validity, "
|
||||
<< (is_field_tagged ? "EnsureDecompressed(value, address_)"
|
||||
: "value")
|
||||
<< "};\n";
|
||||
cc_contents << "}\n";
|
||||
if (field.name_and_type.type == TypeOracle::GetVoidType()) continue;
|
||||
GenerateFieldAddressAccessor(field, name, h_contents, cc_contents);
|
||||
GenerateFieldValueAccessor(field, name, h_contents, cc_contents);
|
||||
GenerateGetPropsChunkForField(field, get_props_impl);
|
||||
}
|
||||
|
||||
h_contents << "};\n";
|
||||
|
||||
cc_contents << "\nstd::vector<std::unique_ptr<ObjectProperty>> Tq" << name
|
||||
<< "::GetProperties(d::MemoryAccessor accessor) const {\n";
|
||||
// Start by getting the fields from the parent class.
|
||||
cc_contents << " std::vector<std::unique_ptr<ObjectProperty>> result = Tq"
|
||||
<< super_name << "::GetProperties(accessor);\n";
|
||||
// Then add the fields from this class.
|
||||
cc_contents << get_props_impl.str();
|
||||
cc_contents << " return result;\n";
|
||||
cc_contents << "}\n";
|
||||
|
@ -43,16 +43,21 @@ d::MemoryAccessResult ReadMemory(uintptr_t address, uint8_t* destination,
|
||||
return d::MemoryAccessResult::kOk;
|
||||
}
|
||||
|
||||
void CheckPropBase(const d::PropertyBase& property, const char* expected_type,
|
||||
const char* expected_name) {
|
||||
CHECK(property.type == std::string("v8::internal::TaggedValue") ||
|
||||
property.type == std::string(expected_type));
|
||||
CHECK(property.decompressed_type == std::string(expected_type));
|
||||
CHECK(property.name == std::string(expected_name));
|
||||
}
|
||||
|
||||
void CheckProp(const d::ObjectProperty& property, const char* expected_type,
|
||||
const char* expected_name,
|
||||
d::PropertyKind expected_kind = d::PropertyKind::kSingle,
|
||||
size_t expected_num_values = 1) {
|
||||
CheckPropBase(property, expected_type, expected_name);
|
||||
CHECK_EQ(property.num_values, expected_num_values);
|
||||
CHECK(property.type == std::string("v8::internal::TaggedValue") ||
|
||||
property.type == std::string(expected_type));
|
||||
CHECK(property.decompressed_type == std::string(expected_type));
|
||||
CHECK(property.kind == expected_kind);
|
||||
CHECK(property.name == std::string(expected_name));
|
||||
}
|
||||
|
||||
template <typename TValue>
|
||||
@ -66,6 +71,24 @@ bool StartsWith(std::string full_string, std::string prefix) {
|
||||
return full_string.substr(0, prefix.size()) == prefix;
|
||||
}
|
||||
|
||||
void CheckStructProp(const d::StructProperty& property,
|
||||
const char* expected_type, const char* expected_name,
|
||||
size_t expected_offset) {
|
||||
CheckPropBase(property, expected_type, expected_name);
|
||||
CHECK_EQ(property.offset, expected_offset);
|
||||
}
|
||||
|
||||
template <typename TValue>
|
||||
TValue ReadProp(const d::ObjectPropertiesResult& props, std::string name) {
|
||||
for (size_t i = 0; i < props.num_properties; ++i) {
|
||||
if (name == props.properties[i]->name) {
|
||||
return *reinterpret_cast<TValue*>(props.properties[i]->address);
|
||||
}
|
||||
}
|
||||
CHECK_WITH_MSG(false, ("property '" + name + "' not found").c_str());
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(GetObjectProperties) {
|
||||
@ -254,6 +277,39 @@ TEST(GetObjectProperties) {
|
||||
o = v8::Utils::OpenHandle(*v);
|
||||
props = d::GetObjectProperties(o->ptr(), &ReadMemory, heap_addresses);
|
||||
CHECK(std::string(props->brief).substr(79, 7) == std::string("aa...\" "));
|
||||
|
||||
// Verify the result for a heap object field which is itself a struct: the
|
||||
// "descriptors" field on a DescriptorArray.
|
||||
// First we need to construct an object and get its map's descriptor array.
|
||||
v = CompileRun("({a: 1, b: 2})");
|
||||
o = v8::Utils::OpenHandle(*v);
|
||||
props = d::GetObjectProperties(o->ptr(), &ReadMemory, heap_addresses);
|
||||
props = d::GetObjectProperties(ReadProp<i::Tagged_t>(*props, "map"),
|
||||
&ReadMemory, heap_addresses);
|
||||
props = d::GetObjectProperties(
|
||||
ReadProp<i::Tagged_t>(*props, "instance_descriptors"), &ReadMemory,
|
||||
heap_addresses);
|
||||
// It should have at least two descriptors (possibly plus slack).
|
||||
CheckProp(*props->properties[1], "uint16_t", "number_of_all_descriptors");
|
||||
uint16_t number_of_all_descriptors =
|
||||
*reinterpret_cast<uint16_t*>(props->properties[1]->address);
|
||||
CHECK_GE(number_of_all_descriptors, 2);
|
||||
// The "descriptors" property should describe the struct layout for each
|
||||
// element in the array.
|
||||
const d::ObjectProperty& descriptors = *props->properties[6];
|
||||
// No C++ type is reported directly because there may not be an actual C++
|
||||
// struct with this layout, hence the empty string in this check.
|
||||
CheckProp(descriptors, /*type=*/"", "descriptors",
|
||||
d::PropertyKind::kArrayOfKnownSize, number_of_all_descriptors);
|
||||
CHECK_EQ(descriptors.size, 3 * i::kTaggedSize);
|
||||
CHECK_EQ(descriptors.num_struct_fields, 3);
|
||||
CheckStructProp(*descriptors.struct_fields[0],
|
||||
"v8::internal::PrimitiveHeapObject", "key",
|
||||
0 * i::kTaggedSize);
|
||||
CheckStructProp(*descriptors.struct_fields[1], "v8::internal::Object",
|
||||
"details", 1 * i::kTaggedSize);
|
||||
CheckStructProp(*descriptors.struct_fields[2], "v8::internal::Object",
|
||||
"value", 2 * i::kTaggedSize);
|
||||
}
|
||||
|
||||
TEST(ListObjectClasses) {
|
||||
|
@ -28,39 +28,85 @@ struct Value {
|
||||
TValue value;
|
||||
};
|
||||
|
||||
// Internal version of API class v8::debug_helper::ObjectProperty.
|
||||
class ObjectProperty {
|
||||
// Internal version of API class v8::debug_helper::PropertyBase.
|
||||
class PropertyBase {
|
||||
public:
|
||||
inline ObjectProperty(std::string name, std::string type,
|
||||
std::string decompressed_type, uintptr_t address,
|
||||
size_t num_values = 1,
|
||||
d::PropertyKind kind = d::PropertyKind::kSingle)
|
||||
: name_(name),
|
||||
type_(type),
|
||||
decompressed_type_(decompressed_type),
|
||||
address_(address),
|
||||
num_values_(num_values),
|
||||
kind_(kind) {}
|
||||
|
||||
inline d::ObjectProperty* GetPublicView() {
|
||||
public_view_.name = name_.c_str();
|
||||
public_view_.type = type_.c_str();
|
||||
public_view_.decompressed_type = decompressed_type_.c_str();
|
||||
public_view_.address = address_;
|
||||
public_view_.num_values = num_values_;
|
||||
public_view_.kind = kind_;
|
||||
return &public_view_;
|
||||
PropertyBase(std::string name, std::string type,
|
||||
std::string decompressed_type)
|
||||
: name_(name), type_(type), decompressed_type_(decompressed_type) {}
|
||||
void SetFieldsOnPublicView(d::PropertyBase* public_view) {
|
||||
public_view->name = name_.c_str();
|
||||
public_view->type = type_.c_str();
|
||||
public_view->decompressed_type = decompressed_type_.c_str();
|
||||
}
|
||||
|
||||
private:
|
||||
std::string name_;
|
||||
std::string type_;
|
||||
std::string decompressed_type_;
|
||||
};
|
||||
|
||||
// Internal version of API class v8::debug_helper::StructProperty.
|
||||
class StructProperty : public PropertyBase {
|
||||
public:
|
||||
StructProperty(std::string name, std::string type,
|
||||
std::string decompressed_type, size_t offset)
|
||||
: PropertyBase(std::move(name), std::move(type),
|
||||
std::move(decompressed_type)),
|
||||
offset_(offset) {}
|
||||
|
||||
d::StructProperty* GetPublicView() {
|
||||
PropertyBase::SetFieldsOnPublicView(&public_view_);
|
||||
public_view_.offset = offset_;
|
||||
return &public_view_;
|
||||
}
|
||||
|
||||
private:
|
||||
size_t offset_;
|
||||
|
||||
d::StructProperty public_view_;
|
||||
};
|
||||
|
||||
// Internal version of API class v8::debug_helper::ObjectProperty.
|
||||
class ObjectProperty : public PropertyBase {
|
||||
public:
|
||||
ObjectProperty(std::string name, std::string type,
|
||||
std::string decompressed_type, uintptr_t address,
|
||||
size_t num_values, size_t size,
|
||||
std::vector<std::unique_ptr<StructProperty>> struct_fields,
|
||||
d::PropertyKind kind)
|
||||
: PropertyBase(std::move(name), std::move(type),
|
||||
std::move(decompressed_type)),
|
||||
address_(address),
|
||||
num_values_(num_values),
|
||||
size_(size),
|
||||
struct_fields_(std::move(struct_fields)),
|
||||
kind_(kind) {}
|
||||
|
||||
d::ObjectProperty* GetPublicView() {
|
||||
PropertyBase::SetFieldsOnPublicView(&public_view_);
|
||||
public_view_.address = address_;
|
||||
public_view_.num_values = num_values_;
|
||||
public_view_.size = size_;
|
||||
public_view_.num_struct_fields = struct_fields_.size();
|
||||
struct_fields_raw_.clear();
|
||||
for (const auto& property : struct_fields_) {
|
||||
struct_fields_raw_.push_back(property->GetPublicView());
|
||||
}
|
||||
public_view_.struct_fields = struct_fields_raw_.data();
|
||||
public_view_.kind = kind_;
|
||||
return &public_view_;
|
||||
}
|
||||
|
||||
private:
|
||||
uintptr_t address_;
|
||||
size_t num_values_;
|
||||
size_t size_;
|
||||
std::vector<std::unique_ptr<StructProperty>> struct_fields_;
|
||||
d::PropertyKind kind_;
|
||||
|
||||
d::ObjectProperty public_view_;
|
||||
std::vector<d::StructProperty*> struct_fields_raw_;
|
||||
};
|
||||
|
||||
class ObjectPropertiesResult;
|
||||
@ -84,9 +130,9 @@ class ObjectPropertiesResult {
|
||||
guessed_types_ = std::move(guessed_types);
|
||||
}
|
||||
|
||||
inline void Prepend(const char* prefix) { brief_ = prefix + brief_; }
|
||||
void Prepend(const char* prefix) { brief_ = prefix + brief_; }
|
||||
|
||||
inline d::ObjectPropertiesResult* GetPublicView() {
|
||||
d::ObjectPropertiesResult* GetPublicView() {
|
||||
public_view_.type_check_result = type_check_result_;
|
||||
public_view_.brief = brief_.c_str();
|
||||
public_view_.type = type_.c_str();
|
||||
@ -124,7 +170,7 @@ class TqObjectVisitor;
|
||||
// Subclasses for specific object types are generated by the Torque compiler.
|
||||
class TqObject {
|
||||
public:
|
||||
inline TqObject(uintptr_t address) : address_(address) {}
|
||||
TqObject(uintptr_t address) : address_(address) {}
|
||||
virtual ~TqObject() = default;
|
||||
virtual std::vector<std::unique_ptr<ObjectProperty>> GetProperties(
|
||||
d::MemoryAccessor accessor) const;
|
||||
@ -137,7 +183,7 @@ class TqObject {
|
||||
};
|
||||
|
||||
// In ptr-compr builds, returns whether the address looks like a compressed
|
||||
// pointer (sign-extended from 32 bits). Otherwise returns false because no
|
||||
// pointer (zero-extended from 32 bits). Otherwise returns false because no
|
||||
// pointers can be compressed.
|
||||
bool IsPointerCompressed(uintptr_t address);
|
||||
|
||||
|
@ -66,10 +66,12 @@ enum class PropertyKind {
|
||||
kArrayOfUnknownSizeDueToValidButInaccessibleMemory,
|
||||
};
|
||||
|
||||
struct ObjectProperty {
|
||||
struct PropertyBase {
|
||||
const char* name;
|
||||
|
||||
// Statically-determined type, such as from .tq definition.
|
||||
// Statically-determined type, such as from .tq definition. Can be an empty
|
||||
// string if this property is itself a Torque-defined struct; in that case use
|
||||
// |struct_fields| instead.
|
||||
const char* type;
|
||||
|
||||
// In some cases, |type| may be a simple type representing a compressed
|
||||
@ -79,7 +81,14 @@ struct ObjectProperty {
|
||||
// to pass the |decompressed_type| value as the type_hint on a subsequent call
|
||||
// to GetObjectProperties.
|
||||
const char* decompressed_type;
|
||||
};
|
||||
|
||||
struct StructProperty : public PropertyBase {
|
||||
// The offset from the beginning of the struct to this field.
|
||||
size_t offset;
|
||||
};
|
||||
|
||||
struct ObjectProperty : public PropertyBase {
|
||||
// The address where the property value can be found in the debuggee's address
|
||||
// space, or the address of the first value for an array.
|
||||
uintptr_t address;
|
||||
@ -90,6 +99,18 @@ struct ObjectProperty {
|
||||
// versus num_values=1 and kind=kArrayOfKnownSize (one-element array).
|
||||
size_t num_values;
|
||||
|
||||
// The number of bytes occupied by a single instance of the value type for
|
||||
// this property. This can also be used as the array stride because arrays are
|
||||
// tightly packed like in C.
|
||||
size_t size;
|
||||
|
||||
// If |type| is nullptr, then this property does not correspond directly to
|
||||
// any C++ type. Instead, the property is a struct made up of several pieces
|
||||
// of data packed together. In that case, the |struct_fields| array contains
|
||||
// the struct fields.
|
||||
size_t num_struct_fields;
|
||||
StructProperty** struct_fields;
|
||||
|
||||
PropertyKind kind;
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user