| // 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 |