| // 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 if (type.IsInt32x4Type()) { | |
| 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) { | |
| return nullptr; | |
| } | |
| } // namespace | |
| void SnapshotAnalyzer::DumpCode(const Code& code) { | |
| js_.PrintProperty("type", "Code"); | |
| const auto instruction_base = reinterpret_cast<uint64_t>(info_.snapshot_text); | |
| 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_.snapshot_text); | |
| 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 if (type.IsInt32x4Type()) { | |
| unboxed_type = "Int32x4"; | |
| } 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", nullptr); | |
| js_.PrintfProperty("vm_instructions", "%p", nullptr); | |
| js_.PrintfProperty("isolate_data", "%p", info_.snapshot_data); | |
| js_.PrintfProperty("isolate_instructions", "%p", info_.snapshot_text); | |
| 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 |