blob: a9c87a43a9d236b3ea163d0e3a855027a4fa088f [file] [log] [blame] [edit]
// 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