mirror of
https://github.com/dart-lang/sdk
synced 2024-09-16 00:29:48 +00:00
a3e84160ec
TEST=vm/dart/analyze_snapshot_binary_test Change-Id: I96ef5d8fc92292151444e52d189e6b2d10a99375 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/308303 Commit-Queue: Martin Kustermann <kustermann@google.com> Reviewed-by: Martin Kustermann <kustermann@google.com>
401 lines
13 KiB
C++
401 lines
13 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 <map>
|
|
#include <set>
|
|
#include <sstream>
|
|
|
|
#include "include/analyze_snapshot_api.h"
|
|
#include "vm/compiler/runtime_api.h"
|
|
#include "vm/dart_api_impl.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) {}
|
|
|
|
// 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 DumpFunction(const Function& function);
|
|
void DumpCode(const Code& code);
|
|
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_;
|
|
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");
|
|
|
|
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);
|
|
|
|
// To avoid depending on layout of VM internal classes we don't use
|
|
// js_.PrintProperty("fields", GetObjectId(heap, klass.fields());
|
|
// here and instead iterate and refer to them manually.
|
|
array = klass.fields();
|
|
if (!array.IsNull()) {
|
|
DumpArray(array, "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::DumpFunction(const Function& function) {
|
|
js_.PrintProperty("type", "Function");
|
|
js_.PrintProperty("name", function.ToCString());
|
|
|
|
js_.PrintProperty("signature",
|
|
String::Handle(function.InternalSignature()).ToCString());
|
|
|
|
js_.PrintProperty("code", GetObjectId(function.CurrentCode()));
|
|
if (function.IsClosureFunction()) {
|
|
js_.PrintProperty("parent_function",
|
|
GetObjectId(function.parent_function()));
|
|
}
|
|
}
|
|
|
|
void SnapshotAnalyzer::DumpCode(const Code& code) {
|
|
js_.PrintProperty("type", "Code");
|
|
const auto instruction_base =
|
|
reinterpret_cast<uint64_t>(info_.vm_isolate_instructions);
|
|
|
|
// 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());
|
|
// Invoking code.PayloadStart() for _kDartVmSnapshotInstructions
|
|
// when the tree has been shaken always returns 0
|
|
if (code_addr == 0) {
|
|
js_.PrintProperty64("offset", 0);
|
|
js_.PrintProperty64("size", 0);
|
|
js_.PrintProperty("section", "_kDartVmSnapshotInstructions");
|
|
} else {
|
|
js_.PrintProperty64("offset", code_addr - instruction_base);
|
|
js_.PrintProperty64("size", static_cast<uint64_t>(code.Size()));
|
|
js_.PrintProperty("section", "_kDartIsolateSnapshotInstructions");
|
|
}
|
|
}
|
|
|
|
void SnapshotAnalyzer::DumpField(const Field& field) {
|
|
js_.PrintProperty("type", "Field");
|
|
js_.PrintProperty("name", String::Handle(field.name()).ToCString());
|
|
js_.PrintProperty64("type_class", GetObjectId(field.type()));
|
|
if (field.is_static()) {
|
|
js_.PrintProperty("instance", GetObjectId(field.StaticValue()));
|
|
}
|
|
if (field.HasInitializerFunction()) {
|
|
js_.PrintProperty("initializer_function",
|
|
GetObjectId(field.InitializerFunction()));
|
|
}
|
|
}
|
|
|
|
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() {
|
|
Zone* zone = thread_->zone();
|
|
|
|
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());
|
|
}
|
|
}
|
|
|
|
// 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();
|
|
}
|
|
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();
|
|
}
|
|
|
|
// TODO(#47924): Add processing of the entires in the dispatch table.
|
|
// Below is an example skeleton
|
|
// void DumpDispatchTable(dart::Thread* thread) {
|
|
// auto dispatch = thread->isolate_group()->dispatch_table();
|
|
// auto length = dispatch->length();
|
|
// We must unbias the array entries so we don't crash on null access.
|
|
// auto entries = dispatch->ArrayOrigin() - DispatchTable::kOriginElement;
|
|
// for (intptr_t i = 0; i < length; i++) {
|
|
// OS::Print("0x%lx at %ld\n", entries[i], i);
|
|
// }
|
|
// }
|
|
|
|
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
|