560b35add1
TEST=vm/dart/analyze_snapshot_binary_test Change-Id: I6ea4630a3be7074c63910e1904381b04e37e6e90 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/431705 Reviewed-by: Slava Egorov <vegorov@google.com> Reviewed-by: Martin Kustermann <kustermann@google.com> Auto-Submit: Roy Tu <tur@google.com> Commit-Queue: Martin Kustermann <kustermann@google.com>
697 lines
24 KiB
C++
697 lines
24 KiB
C++
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
|
|
// for details. All rights reserved. Use of this source code is governed by a
|
|
// BSD-style license that can be found in the LICENSE file.
|
|
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <set>
|
|
#include <sstream>
|
|
#include <unordered_map>
|
|
#include <vector>
|
|
|
|
#include "include/analyze_snapshot_api.h"
|
|
#include "vm/compiler/runtime_api.h"
|
|
#include "vm/dart.h"
|
|
#include "vm/dart_api_impl.h"
|
|
#include "vm/globals.h"
|
|
#include "vm/json_writer.h"
|
|
#include "vm/object.h"
|
|
#include "vm/object_store.h"
|
|
#include "vm/thread.h"
|
|
|
|
namespace dart {
|
|
namespace snapshot_analyzer {
|
|
|
|
constexpr intptr_t kSnapshotAnalyzerVersion = 2;
|
|
constexpr intptr_t kStartIndex = 1;
|
|
|
|
class FieldVisitor : public ObjectPointerVisitor {
|
|
public:
|
|
explicit FieldVisitor(IsolateGroup* isolate_group)
|
|
: ObjectPointerVisitor(isolate_group) {}
|
|
|
|
void init(std::function<void(ObjectPtr)>* fun) { callback_ = fun; }
|
|
|
|
void VisitPointers(ObjectPtr* first, ObjectPtr* last) override {
|
|
for (ObjectPtr* current = first; current <= last; current++) {
|
|
(*callback_)(*current);
|
|
}
|
|
}
|
|
|
|
#if defined(DART_COMPRESSED_POINTERS)
|
|
void VisitCompressedPointers(uword heap_base,
|
|
CompressedObjectPtr* first,
|
|
CompressedObjectPtr* last) override {
|
|
for (CompressedObjectPtr* current = first; current <= last; current++) {
|
|
(*callback_)(current->Decompress(heap_base));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
private:
|
|
std::function<void(ObjectPtr object)>* callback_ = nullptr;
|
|
};
|
|
|
|
class SnapshotAnalyzer {
|
|
public:
|
|
explicit SnapshotAnalyzer(const Dart_SnapshotAnalyzerInformation& info)
|
|
: info_(info),
|
|
class_fields_(IsolateGroup::Current()->class_table()->NumCids()),
|
|
top_level_class_fields_(
|
|
IsolateGroup::Current()->class_table()->NumTopLevelCids()) {}
|
|
|
|
// Saves JSON format snapshot information in the output character buffer.
|
|
void DumpSnapshotInformation(char** buffer, intptr_t* buffer_length);
|
|
|
|
private:
|
|
void DumpLibrary(const Library& library);
|
|
void DumpArray(const Array& array, const char* name);
|
|
void DumpClass(const Class& klass);
|
|
void DumpClassInstanceSlots(const Class& klass,
|
|
const std::vector<const Field*>& fields);
|
|
void DumpFunction(const Function& function);
|
|
void DumpCode(const Code& code);
|
|
void DumpCode(uword start_pc, uword end_pc, const char* name);
|
|
void DumpField(const Field& field);
|
|
void DumpString(const String& string);
|
|
void DumpInstance(const Object& object);
|
|
void DumpType(const Type& type);
|
|
void DumpObjectPool(const ObjectPool& pool);
|
|
|
|
void DumpInterestingObjects();
|
|
void DumpMetadata();
|
|
|
|
intptr_t GetObjectId(ObjectPtr obj) { return heap_->GetObjectId(obj); }
|
|
|
|
const Dart_SnapshotAnalyzerInformation& info_;
|
|
std::vector<std::vector<const Field*>> class_fields_;
|
|
std::vector<std::vector<const Field*>> top_level_class_fields_;
|
|
std::unordered_map<uword, const char*> stub_names_;
|
|
|
|
JSONWriter js_;
|
|
Thread* thread_;
|
|
Heap* heap_;
|
|
};
|
|
|
|
void SnapshotAnalyzer::DumpLibrary(const Library& library) {
|
|
js_.PrintProperty("type", "Library");
|
|
js_.PrintProperty("url", String::Handle(library.url()).ToCString());
|
|
|
|
js_.PrintProperty("toplevel_class",
|
|
GetObjectId(Object::RawCast(library.toplevel_class())));
|
|
}
|
|
|
|
void SnapshotAnalyzer::DumpArray(const Array& array, const char* name) {
|
|
js_.OpenArray(name);
|
|
for (intptr_t i = 0; i < array.Length(); ++i) {
|
|
js_.PrintValue64(GetObjectId(array.At(i)));
|
|
}
|
|
js_.CloseArray();
|
|
}
|
|
|
|
void SnapshotAnalyzer::DumpClass(const Class& klass) {
|
|
js_.PrintProperty("type", "Class");
|
|
|
|
auto& class_fields =
|
|
klass.IsTopLevel() ? top_level_class_fields_ : class_fields_;
|
|
const auto& fields =
|
|
class_fields[klass.IsTopLevel()
|
|
? ClassTable::IndexFromTopLevelCid(klass.id())
|
|
: klass.id()];
|
|
|
|
js_.PrintProperty("class_id", klass.id());
|
|
js_.PrintProperty("name", String::Handle(klass.Name()).ToCString());
|
|
js_.PrintProperty("super_class", GetObjectId(klass.SuperClass()));
|
|
|
|
Zone* zone = thread_->zone();
|
|
Array& array = Array::Handle(zone);
|
|
|
|
// We use [fields] instead of [Class.fields()] as a [Field] object may not
|
|
// appear in [Class.fields()] but may still be available (e.g. for
|
|
// `LateInitizializationError`) so we can include all fields of the class we
|
|
// found.
|
|
js_.OpenArray("fields");
|
|
for (uintptr_t i = 0; i < fields.size(); ++i) {
|
|
js_.PrintValue64(GetObjectId(fields[i]->ptr()));
|
|
}
|
|
js_.CloseArray();
|
|
|
|
// Here we write information about every slot in an instance. Even if there's
|
|
// no corresponding [Field] object, we will write out an entry describing the
|
|
// slot (e.g. whether it's boxed or not, ...)
|
|
//
|
|
// So this information is always available, whereas the "fields" above only
|
|
// writes non-tree shaken [Field] obejcts.
|
|
if (!klass.IsTopLevel()) {
|
|
DumpClassInstanceSlots(klass, fields);
|
|
}
|
|
|
|
array = klass.functions();
|
|
if (!array.IsNull()) {
|
|
DumpArray(array, "functions");
|
|
}
|
|
|
|
array = klass.interfaces();
|
|
if (!array.IsNull()) {
|
|
DumpArray(array, "interfaces");
|
|
}
|
|
|
|
Library& library = Library::Handle(klass.library());
|
|
if (!library.IsNull()) {
|
|
js_.PrintProperty("library", GetObjectId(klass.library()));
|
|
}
|
|
}
|
|
|
|
void SnapshotAnalyzer::DumpClassInstanceSlots(
|
|
const Class& klass,
|
|
const std::vector<const Field*>& fields) {
|
|
const auto& super_class = Class::Handle(klass.SuperClass());
|
|
auto& field = Field::Handle();
|
|
auto& type = AbstractType::Handle();
|
|
auto class_table = thread_->isolate_group()->class_table();
|
|
|
|
const auto bitmap = class_table->GetUnboxedFieldsMapAt(klass.id());
|
|
|
|
const intptr_t start_offset = super_class.IsNull()
|
|
? Instance::NextFieldOffset()
|
|
: super_class.host_next_field_offset();
|
|
const intptr_t end_offset = klass.host_next_field_offset();
|
|
|
|
js_.OpenArray("instance_slots");
|
|
intptr_t offset = start_offset;
|
|
while (offset < end_offset) {
|
|
const bool is_reference = !bitmap.Get(offset / kCompressedWordSize);
|
|
|
|
js_.OpenObject();
|
|
js_.PrintProperty("offset", offset);
|
|
js_.PrintPropertyBool("is_reference", is_reference);
|
|
|
|
if (offset == klass.host_type_arguments_field_offset()) {
|
|
RELEASE_ASSERT(is_reference);
|
|
js_.PrintProperty("slot_type", "type_arguments_field");
|
|
js_.CloseObject();
|
|
offset += kCompressedWordSize;
|
|
continue;
|
|
}
|
|
|
|
// Try to see if the corresponding [Field] object was not tree shaken.
|
|
bool found = false;
|
|
for (uintptr_t i = 0; i < fields.size(); ++i) {
|
|
field = fields[i]->ptr();
|
|
if (field.is_static()) continue;
|
|
if (field.HostOffset() == offset) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (found) {
|
|
type = field.type();
|
|
|
|
intptr_t slots = 0;
|
|
if (field.is_unboxed()) {
|
|
if (type.IsDoubleType()) {
|
|
slots = sizeof(double) / kCompressedWordSize;
|
|
} else if (type.IsIntType()) {
|
|
slots = sizeof(int64_t) / kCompressedWordSize;
|
|
} else if (type.IsFloat32x4Type()) {
|
|
slots = sizeof(simd128_value_t) / kCompressedWordSize;
|
|
} else if (type.IsFloat64x2Type()) {
|
|
slots = sizeof(simd128_value_t) / kCompressedWordSize;
|
|
} else {
|
|
// Rare: Could be that the field type isn't telling us the unboxed
|
|
// type but field is still unboxed (e.g. `dynamic` field which TFA
|
|
// inferred to be of certain type).
|
|
//
|
|
// In this case we treat it as unknown field below (as we don't know
|
|
// it's size).
|
|
slots = -1;
|
|
}
|
|
} else {
|
|
slots = 1;
|
|
}
|
|
if (!field.is_unboxed() || slots > 0) {
|
|
js_.PrintProperty("slot_type", "instance_field");
|
|
js_.PrintProperty64("field", GetObjectId(field.ptr()));
|
|
js_.CloseObject();
|
|
offset += slots * kCompressedWordSize;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// This slot is either an unknown reference field or part of an unknown
|
|
// unboxed field (64-bit integer/double or 128-bit float32x4/float64x2).
|
|
// We cannot know the size or type of the unboxed field as the [Field]
|
|
// object was tree shaken.
|
|
js_.PrintProperty("slot_type", "unknown_slot");
|
|
js_.CloseObject();
|
|
|
|
offset += kCompressedWordSize;
|
|
}
|
|
js_.CloseArray();
|
|
}
|
|
|
|
void SnapshotAnalyzer::DumpFunction(const Function& function) {
|
|
js_.PrintProperty("type", "Function");
|
|
js_.PrintProperty("name", function.QualifiedScrubbedNameCString());
|
|
|
|
js_.PrintProperty("signature",
|
|
String::Handle(function.InternalSignature()).ToCString());
|
|
|
|
js_.PrintProperty("code", GetObjectId(function.CurrentCode()));
|
|
js_.PrintProperty("owner_class", GetObjectId(function.Owner()));
|
|
if (function.IsClosureFunction()) {
|
|
js_.PrintProperty("parent_function",
|
|
GetObjectId(function.parent_function()));
|
|
}
|
|
}
|
|
|
|
namespace {
|
|
// Try to identify stubs which were effectively copied into the isolate
|
|
// instructions section by comparing payloads.
|
|
const char* TryIdentifyIsolateSpecificStubCopy(ObjectStore* object_store,
|
|
const Code& code) {
|
|
#define MATCH(member, name) \
|
|
if (object_store->member() != Code::null() && \
|
|
StubCode::name().ptr() == object_store->member() && \
|
|
StubCode::name().Size() == code.Size() && \
|
|
memcmp(reinterpret_cast<void*>(code.PayloadStart()), \
|
|
reinterpret_cast<void*>(StubCode::name().PayloadStart()), \
|
|
code.Size()) == 0) { \
|
|
return "_iso_stub_" #name "Stub"; \
|
|
}
|
|
OBJECT_STORE_STUB_CODE_LIST(MATCH)
|
|
#undef MATCH
|
|
|
|
return nullptr;
|
|
}
|
|
} // namespace
|
|
|
|
void SnapshotAnalyzer::DumpCode(const Code& code) {
|
|
js_.PrintProperty("type", "Code");
|
|
const auto instruction_base =
|
|
reinterpret_cast<uint64_t>(info_.vm_isolate_instructions);
|
|
|
|
if (code.IsUnknownDartCode()) {
|
|
js_.PrintProperty64("offset", 0);
|
|
js_.PrintProperty64("size", 0);
|
|
js_.PrintProperty("name", "UnknownDartCode");
|
|
js_.PrintProperty("section", "_kDartVmSnapshotInstructions");
|
|
return;
|
|
}
|
|
|
|
// On different architectures the type of the underlying
|
|
// dart::uword can result in an unsigned long long vs unsigned long
|
|
// mismatch.
|
|
const auto code_addr = static_cast<uint64_t>(code.PayloadStart());
|
|
js_.PrintProperty64("offset", code_addr - instruction_base);
|
|
js_.PrintProperty64("size", static_cast<uint64_t>(code.Size()));
|
|
js_.PrintProperty("section", "_kDartIsolateSnapshotInstructions");
|
|
|
|
if (code.owner() != Object::null()) {
|
|
const auto& owner = Object::Handle(code.owner());
|
|
js_.PrintProperty("owner", GetObjectId(owner.ptr()));
|
|
if (owner.IsClass()) {
|
|
js_.PrintfProperty("name", "new %s",
|
|
Class::Cast(owner).ScrubbedNameCString());
|
|
js_.PrintPropertyBool("is_stub", true);
|
|
} else if (owner.IsAbstractType()) {
|
|
js_.PrintfProperty("name", "as %s",
|
|
AbstractType::Cast(owner).ScrubbedNameCString());
|
|
js_.PrintPropertyBool("is_stub", true);
|
|
} else if (owner.IsFunction()) {
|
|
js_.PrintProperty("name", Function::Cast(owner).UserVisibleNameCString());
|
|
} else if (owner.IsSmi()) {
|
|
// This is a class id of the class which owned the function.
|
|
// See Precompiler::DropFunctions.
|
|
const auto cid = Smi::Cast(owner).Value();
|
|
auto class_table = thread_->isolate_group()->class_table();
|
|
if (class_table->IsValidIndex(cid) &&
|
|
class_table->At(cid) != Class::null()) {
|
|
const auto& cls = Class::Handle(class_table->At(cid));
|
|
js_.PrintProperty("owner", GetObjectId(cls.ptr()));
|
|
js_.PrintfProperty("name", "unknown function of %s",
|
|
Class::Cast(cls).ScrubbedNameCString());
|
|
} else {
|
|
js_.PrintfProperty("name", "unknown function of class #%" Pd "", cid);
|
|
}
|
|
} else {
|
|
// Expected to handle all possibilities.
|
|
UNREACHABLE();
|
|
}
|
|
} else {
|
|
js_.PrintPropertyBool("is_stub", true);
|
|
|
|
const auto it = stub_names_.find(code.EntryPoint());
|
|
if (it != stub_names_.end()) {
|
|
js_.PrintProperty("name", it->second);
|
|
} else if (auto stub_name = TryIdentifyIsolateSpecificStubCopy(
|
|
thread_->isolate_group()->object_store(), code)) {
|
|
js_.PrintProperty("name", stub_name);
|
|
} else {
|
|
UNREACHABLE();
|
|
}
|
|
}
|
|
}
|
|
|
|
void SnapshotAnalyzer::DumpCode(uword start_pc,
|
|
uword end_pc,
|
|
const char* name) {
|
|
js_.PrintProperty("type", "Code");
|
|
const auto instruction_base =
|
|
reinterpret_cast<uint64_t>(info_.vm_isolate_instructions);
|
|
|
|
js_.PrintProperty64("offset",
|
|
static_cast<uint64_t>(start_pc) - instruction_base);
|
|
js_.PrintProperty64("size", static_cast<uint64_t>(end_pc - start_pc));
|
|
js_.PrintProperty("name", name);
|
|
js_.PrintProperty("section", "_kDartIsolateSnapshotInstructions");
|
|
}
|
|
|
|
void SnapshotAnalyzer::DumpField(const Field& field) {
|
|
const auto& name = String::Handle(field.name());
|
|
const auto& type = AbstractType::Handle(field.type());
|
|
|
|
js_.PrintProperty("type", "Field");
|
|
js_.PrintProperty("name", name.ToCString());
|
|
js_.PrintProperty64("type_class", GetObjectId(field.type()));
|
|
js_.PrintProperty("owner_class", GetObjectId(field.Owner()));
|
|
if (field.is_static()) {
|
|
js_.PrintProperty("instance", GetObjectId(field.StaticValue()));
|
|
js_.PrintProperty64("static_field_offset",
|
|
FieldTable::FieldOffsetFor(field.field_id()));
|
|
}
|
|
if (field.HasInitializerFunction()) {
|
|
js_.PrintProperty("initializer_function",
|
|
GetObjectId(field.InitializerFunction()));
|
|
}
|
|
|
|
js_.PrintPropertyBool("is_reference", !field.is_unboxed());
|
|
if (field.is_unboxed()) {
|
|
const char* unboxed_type = nullptr;
|
|
if (type.IsDoubleType()) {
|
|
unboxed_type = "double";
|
|
} else if (type.IsIntType()) {
|
|
unboxed_type = "int";
|
|
} else if (type.IsFloat32x4Type()) {
|
|
unboxed_type = "Float32x4";
|
|
} else if (type.IsFloat64x2Type()) {
|
|
unboxed_type = "Float64x2";
|
|
} else {
|
|
unboxed_type = "unknown";
|
|
}
|
|
js_.PrintProperty("unboxed_type", unboxed_type);
|
|
}
|
|
|
|
js_.OpenArray("flags");
|
|
if (field.is_final()) js_.PrintValue("final");
|
|
if (field.is_static()) {
|
|
js_.PrintValue("static");
|
|
if (field.is_shared()) js_.PrintValue("shared");
|
|
}
|
|
if (field.is_instance()) {
|
|
if (field.is_late()) js_.PrintValue("late");
|
|
}
|
|
js_.CloseArray();
|
|
}
|
|
|
|
void SnapshotAnalyzer::DumpString(const String& string) {
|
|
js_.PrintProperty("type", "String");
|
|
js_.PrintProperty("value", string.ToCString());
|
|
}
|
|
|
|
void SnapshotAnalyzer::DumpInstance(const Object& object) {
|
|
js_.PrintProperty("type", "Instance");
|
|
|
|
js_.PrintProperty("class", GetObjectId(object.clazz()));
|
|
|
|
FieldVisitor visitor(thread_->isolate_group());
|
|
// Two phase algorithm, first discover all relevant objects
|
|
// and assign ids, then write them out.
|
|
std::function<void(ObjectPtr)> print_reference = [&](ObjectPtr value) {
|
|
if (!value.IsHeapObject()) return;
|
|
intptr_t index = GetObjectId(value);
|
|
js_.PrintValue64(index);
|
|
};
|
|
visitor.init(&print_reference);
|
|
|
|
js_.OpenArray("references");
|
|
object.ptr().untag()->VisitPointers(&visitor);
|
|
js_.CloseArray();
|
|
}
|
|
|
|
void SnapshotAnalyzer::DumpType(const Type& type) {
|
|
js_.PrintProperty("type", "Type");
|
|
|
|
js_.PrintProperty("type_class", GetObjectId(type.type_class()));
|
|
|
|
const TypeArguments& arguments = TypeArguments::Handle(type.arguments());
|
|
js_.OpenArray("type_arguments");
|
|
for (intptr_t i = 0; i < arguments.Length(); ++i) {
|
|
js_.PrintValue64(GetObjectId(arguments.TypeAt(i)));
|
|
}
|
|
js_.CloseArray();
|
|
}
|
|
|
|
void SnapshotAnalyzer::DumpObjectPool(const ObjectPool& pool) {
|
|
js_.PrintProperty("type", "ObjectPool");
|
|
js_.OpenArray("references");
|
|
for (intptr_t i = 0; i < pool.Length(); ++i) {
|
|
if (pool.TypeAt(i) == ObjectPool::EntryType::kTaggedObject) {
|
|
// We write (index, offset, value) triplets.
|
|
js_.PrintValue64(i);
|
|
js_.PrintValue64(pool.OffsetFromIndex(i));
|
|
js_.PrintValue64(GetObjectId(pool.ObjectAt(i)));
|
|
}
|
|
}
|
|
js_.CloseArray();
|
|
}
|
|
|
|
void SnapshotAnalyzer::DumpInterestingObjects() {
|
|
// Collect stubs into stub_names to enable quick name lookup
|
|
StubCode::ForEachStub([&](const char* name, uword entry_point) {
|
|
stub_names_[entry_point] = name;
|
|
return true;
|
|
});
|
|
|
|
Zone* zone = thread_->zone();
|
|
auto class_table = thread_->isolate_group()->class_table();
|
|
class_table->NumCids();
|
|
|
|
heap_->ResetObjectIdTable();
|
|
std::vector<const Object*> discovered_objects;
|
|
Object& object = Object::Handle(zone);
|
|
{
|
|
NoSafepointScope ns(thread_);
|
|
|
|
FieldVisitor visitor(thread_->isolate_group());
|
|
std::function<void(ObjectPtr)> handle_object = [&](ObjectPtr value) {
|
|
if (!value.IsHeapObject()) return;
|
|
|
|
// Ensure we never handle an object more than once.
|
|
if (heap_->GetObjectId(value) != 0) return;
|
|
|
|
heap_->SetObjectId(value, kStartIndex + discovered_objects.size());
|
|
discovered_objects.push_back(&Object::Handle(zone, value));
|
|
|
|
// Ensure all references of this object are visited first.
|
|
value->untag()->VisitPointers(&visitor);
|
|
};
|
|
visitor.init(&handle_object);
|
|
|
|
// BEGIN Visit all things we are interested in.
|
|
|
|
// - All constants reachable via object pool
|
|
object = thread_->isolate_group()->object_store()->global_object_pool();
|
|
handle_object(object.ptr());
|
|
|
|
// - All libraries
|
|
object = thread_->isolate_group()->object_store()->libraries();
|
|
object = GrowableObjectArray::Cast(object).data();
|
|
object.ptr().untag()->VisitPointers(&visitor);
|
|
|
|
// - All classes
|
|
auto class_table = thread_->isolate_group()->class_table();
|
|
for (intptr_t cid = 0; cid < class_table->NumCids(); ++cid) {
|
|
if (!class_table->HasValidClassAt(cid)) continue;
|
|
object = class_table->At(cid);
|
|
handle_object(object.ptr());
|
|
}
|
|
|
|
// - All instructions tables
|
|
const auto& instruction_tables = GrowableObjectArray::Handle(
|
|
thread_->isolate_group()->object_store()->instructions_tables());
|
|
for (intptr_t i = 0; i < instruction_tables.Length(); i++) {
|
|
object = instruction_tables.At(i);
|
|
object = InstructionsTable::Cast(object).code_objects();
|
|
handle_object(object.ptr());
|
|
}
|
|
|
|
// - All VM stubs
|
|
for (intptr_t i = 0; i < StubCode::NumEntries(); i++) {
|
|
if (!StubCode::EntryAt(i).IsNull()) {
|
|
handle_object(StubCode::EntryAt(i).ptr());
|
|
}
|
|
}
|
|
|
|
// - Object store.
|
|
//
|
|
// This will include a bunch of stuff we don't care about
|
|
// but it will also capture things like isolate specific stubs and
|
|
// canonicalized types which themselves include references to stubs.
|
|
thread_->isolate_group()->object_store()->VisitObjectPointers(&visitor);
|
|
}
|
|
|
|
// Sometimes we have [Field] objects for fields but they are not available
|
|
// from [Class.fields] (e.g. late final fields where the slow path uses
|
|
// [Field] from object pool to throw a nice error).
|
|
//
|
|
// So we manually look for all [Field]s and associate them with classes
|
|
// instead of relying on the [Class.fields] array.
|
|
auto& owner = Class::Handle();
|
|
for (uintptr_t i = 0; i < discovered_objects.size(); ++i) {
|
|
const Object& object = *discovered_objects[i];
|
|
if (object.IsField()) {
|
|
const auto& field = Field::Cast(object);
|
|
owner = field.Owner();
|
|
auto& array =
|
|
owner.IsTopLevel() ? top_level_class_fields_ : class_fields_;
|
|
const intptr_t index = owner.IsTopLevel()
|
|
? ClassTable::IndexFromTopLevelCid(owner.id())
|
|
: owner.id();
|
|
array[index].push_back(&field);
|
|
}
|
|
}
|
|
|
|
// Print information about objects
|
|
js_.OpenArray("objects");
|
|
|
|
// The 0 object id is used in the VM's weak hashmap implementation
|
|
// to indicate no value.
|
|
js_.OpenObject();
|
|
js_.PrintProperty("type", "NoValue");
|
|
js_.CloseObject();
|
|
|
|
for (size_t id = 0; id < discovered_objects.size(); ++id) {
|
|
const auto* object = discovered_objects[id];
|
|
js_.OpenObject();
|
|
// TODO(balid): Remove this as it can be inferred from the array position.
|
|
// Used for manual debugging at the moment.
|
|
js_.PrintProperty64("id", id + kStartIndex);
|
|
// Order matters here, Strings are a subtype of Instance, for example.
|
|
if (object->IsNull()) {
|
|
js_.PrintProperty("type", "Null");
|
|
} else if (object->IsLibrary()) {
|
|
DumpLibrary(Library::Cast(*object));
|
|
} else if (object->IsObjectPool()) {
|
|
DumpObjectPool(ObjectPool::Cast(*object));
|
|
} else if (object->IsClass()) {
|
|
DumpClass(Class::Cast(*object));
|
|
} else if (object->IsFunction()) {
|
|
DumpFunction(Function::Cast(*object));
|
|
} else if (object->IsCode()) {
|
|
DumpCode(Code::Cast(*object));
|
|
} else if (object->IsField()) {
|
|
DumpField(Field::Cast(*object));
|
|
} else if (object->IsString()) {
|
|
DumpString(String::Cast(*object));
|
|
} else if (object->IsArray()) {
|
|
js_.PrintProperty("type", "Array");
|
|
const Array& array = Array::Handle(Array::RawCast(object->ptr()));
|
|
DumpArray(array, "elements");
|
|
} else if (object->IsType()) {
|
|
DumpType(Type::Cast(*object));
|
|
} else if (object->IsInstance()) {
|
|
DumpInstance(*object);
|
|
}
|
|
js_.CloseObject();
|
|
}
|
|
|
|
// Finally dump pseudo-Code objects for all entries in the instructions
|
|
// tables without code objects.
|
|
uint64_t pseudo_code_id = kStartIndex + discovered_objects.size();
|
|
const auto& instruction_tables = GrowableObjectArray::Handle(
|
|
thread_->isolate_group()->object_store()->instructions_tables());
|
|
auto& instructions_table = InstructionsTable::Handle();
|
|
for (intptr_t i = 0; i < instruction_tables.Length(); i++) {
|
|
instructions_table ^= instruction_tables.At(i);
|
|
for (intptr_t index = 0; index < instructions_table.FirstEntryWithCode();
|
|
index++) {
|
|
js_.OpenObject();
|
|
js_.PrintProperty64("id", pseudo_code_id);
|
|
DumpCode(instructions_table.EntryPointAt(index),
|
|
instructions_table.EntryPointAt(index + 1), "Unknown Code");
|
|
js_.CloseObject();
|
|
pseudo_code_id++;
|
|
}
|
|
}
|
|
|
|
js_.CloseArray();
|
|
}
|
|
|
|
void SnapshotAnalyzer::DumpMetadata() {
|
|
js_.OpenObject("metadata");
|
|
js_.OpenObject("offsets");
|
|
js_.OpenObject("thread");
|
|
// TODO(balid): Use `dart::compiler::target::` versions.
|
|
js_.PrintProperty("isolate", Thread::isolate_offset());
|
|
js_.PrintProperty("isolate_group", Thread::isolate_group_offset());
|
|
js_.PrintProperty("dispatch_table_array",
|
|
Thread::dispatch_table_array_offset());
|
|
js_.CloseObject();
|
|
js_.OpenObject("isolate_group");
|
|
js_.PrintProperty("class_table", IsolateGroup::class_table_offset());
|
|
js_.PrintProperty("cached_class_table",
|
|
IsolateGroup::cached_class_table_table_offset());
|
|
js_.PrintProperty("object_store_offset", IsolateGroup::object_store_offset());
|
|
js_.CloseObject();
|
|
js_.CloseObject();
|
|
js_.PrintProperty64("word_size", dart::compiler::target::kWordSize);
|
|
js_.PrintProperty64("compressed_word_size",
|
|
dart::compiler::target::kCompressedWordSize);
|
|
js_.PrintProperty64("analyzer_version", kSnapshotAnalyzerVersion);
|
|
js_.CloseObject();
|
|
}
|
|
|
|
void SnapshotAnalyzer::DumpSnapshotInformation(char** buffer,
|
|
intptr_t* buffer_length) {
|
|
thread_ = Thread::Current();
|
|
heap_ = thread_->isolate_group()->heap();
|
|
DARTSCOPE(thread_);
|
|
|
|
// Open empty object so output is valid/parsable JSON.
|
|
js_.OpenObject();
|
|
js_.OpenObject("snapshot_data");
|
|
// Base addresses of the snapshot data, useful to calculate relative offsets.
|
|
js_.PrintfProperty("vm_data", "%p", info_.vm_snapshot_data);
|
|
js_.PrintfProperty("vm_instructions", "%p", info_.vm_snapshot_instructions);
|
|
js_.PrintfProperty("isolate_data", "%p", info_.vm_isolate_data);
|
|
js_.PrintfProperty("isolate_instructions", "%p",
|
|
info_.vm_isolate_instructions);
|
|
js_.CloseObject();
|
|
|
|
{
|
|
// Debug builds assert that our thread has a lock before accessing
|
|
// vm internal fields.
|
|
SafepointReadRwLocker ml(thread_, thread_->isolate_group()->program_lock());
|
|
DumpInterestingObjects();
|
|
DumpMetadata();
|
|
}
|
|
|
|
// Close our empty object.
|
|
js_.CloseObject();
|
|
|
|
// Give ownership to caller.
|
|
js_.Steal(buffer, buffer_length);
|
|
}
|
|
|
|
void Dart_DumpSnapshotInformationAsJson(
|
|
const Dart_SnapshotAnalyzerInformation& info,
|
|
char** out,
|
|
intptr_t* out_len) {
|
|
SnapshotAnalyzer analyzer(info);
|
|
analyzer.DumpSnapshotInformation(out, out_len);
|
|
}
|
|
|
|
} // namespace snapshot_analyzer
|
|
} // namespace dart
|