From 286326f834fcb4816c78472e36b4a69675694727 Mon Sep 17 00:00:00 2001 From: Tess Strickland Date: Mon, 25 Jul 2022 11:03:29 +0000 Subject: [PATCH] [vm/compiler] Add symbols for read-only data when requested. Symbols for non-clustered objects in the read-only data section are now added to the static symbol tables for unstripped snapshots and separate debugging information. In DEBUG mode, the name for a non-String read-only data object also includes the name of the parent object. TEST=vm/dart{,_2}/readonly_data_symbols Change-Id: I623b023138aeca0580bc76392882eac5686f8f50 Cq-Include-Trybots: luci.dart.try:vm-kernel-precomp-dwarf-linux-product-x64-try,vm-kernel-precomp-linux-product-x64-try,vm-kernel-precomp-linux-debug-x64-try,vm-kernel-precomp-linux-release-x64-try,vm-kernel-precomp-nnbd-linux-release-x64-try,vm-kernel-precomp-nnbd-mac-release-arm64-try Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/251104 Reviewed-by: Ryan Macnak Commit-Queue: Tess Strickland --- pkg/native_stack_traces/CHANGELOG.md | 4 + pkg/native_stack_traces/lib/elf.dart | 10 +- pkg/native_stack_traces/lib/src/elf.dart | 25 ++ .../vm/dart/readonly_data_symbols_test.dart | 115 +++++++++ .../vm/dart_2/readonly_data_symbols_test.dart | 117 +++++++++ runtime/vm/app_snapshot.cc | 15 +- runtime/vm/app_snapshot.h | 7 +- runtime/vm/image_snapshot.cc | 229 +++++++++++++----- runtime/vm/image_snapshot.h | 133 +++++++--- runtime/vm/type_testing_stubs.cc | 47 ++-- runtime/vm/type_testing_stubs.h | 8 +- 11 files changed, 581 insertions(+), 129 deletions(-) create mode 100644 runtime/tests/vm/dart/readonly_data_symbols_test.dart create mode 100644 runtime/tests/vm/dart_2/readonly_data_symbols_test.dart diff --git a/pkg/native_stack_traces/CHANGELOG.md b/pkg/native_stack_traces/CHANGELOG.md index a1b47ce3126..39f161231dd 100644 --- a/pkg/native_stack_traces/CHANGELOG.md +++ b/pkg/native_stack_traces/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.5.1 + +- Exported more ELF utilities for use in Dart tests. + ## 0.5.0 - Require Dart >= 2.17 (enhanced enum support) diff --git a/pkg/native_stack_traces/lib/elf.dart b/pkg/native_stack_traces/lib/elf.dart index 31272b134f8..7c6e4bbada0 100644 --- a/pkg/native_stack_traces/lib/elf.dart +++ b/pkg/native_stack_traces/lib/elf.dart @@ -8,4 +8,12 @@ export 'src/constants.dart' isolateSymbolName, vmDataSymbolName, vmSymbolName; -export 'src/elf.dart' show DynamicTable, DynamicTableTag, Elf, Section, Symbol; +export 'src/elf.dart' + show + DynamicTable, + DynamicTableTag, + Elf, + Section, + Symbol, + SymbolBinding, + SymbolType; diff --git a/pkg/native_stack_traces/lib/src/elf.dart b/pkg/native_stack_traces/lib/src/elf.dart index d8328ebf7b2..6b8bbbe7254 100644 --- a/pkg/native_stack_traces/lib/src/elf.dart +++ b/pkg/native_stack_traces/lib/src/elf.dart @@ -696,16 +696,19 @@ class StringTable extends Section implements DwarfContainerStringTable { } } +/// An enumeration of recognized symbol binding values used by the ELF format. enum SymbolBinding { STB_LOCAL, STB_GLOBAL, STB_WEAK, } +/// An enumeration of recognized symbol types used by the ELF format. enum SymbolType { STT_NOTYPE, STT_OBJECT, STT_FUNC, + STT_SECTION, } enum SymbolVisibility { @@ -1006,6 +1009,17 @@ class Elf implements DwarfContainer { return null; } + /// Returns an iterable of the symbols in the dynamic symbol table(s). + /// The ordering of the symbols is not guaranteed. + Iterable get dynamicSymbols sync* { + for (final section in namedSections('.dynsym')) { + final dynsym = section as SymbolTable; + for (final symbol in dynsym.values) { + yield symbol; + } + } + } + /// Reverse lookup of the static symbol that contains the given virtual /// address. Returns null if no static symbol matching the address is found. @override @@ -1028,6 +1042,17 @@ class Elf implements DwarfContainer { return bestSym; } + /// Returns an iterable of the symbols in the static symbol table(s). + /// The ordering of the symbols is not guaranteed. + Iterable get staticSymbols sync* { + for (final section in namedSections('.symtab')) { + final symtab = section as SymbolTable; + for (final symbol in symtab.values) { + yield symbol; + } + } + } + /// Creates an [Elf] from the data pointed to by [reader]. /// /// After succesful completion, the [endian] and [wordSize] fields of the diff --git a/runtime/tests/vm/dart/readonly_data_symbols_test.dart b/runtime/tests/vm/dart/readonly_data_symbols_test.dart new file mode 100644 index 00000000000..3ccad9b0513 --- /dev/null +++ b/runtime/tests/vm/dart/readonly_data_symbols_test.dart @@ -0,0 +1,115 @@ +// Copyright (c) 2022, 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. + +// This test checks that gen_snapshot outputs static symbols for read-only +// data objects. + +// OtherResources=use_save_debugging_info_flag_program.dart + +import "dart:async"; +import "dart:io"; + +import 'package:expect/expect.dart'; +import 'package:native_stack_traces/elf.dart'; +import 'package:path/path.dart' as path; + +import 'use_flag_test_helper.dart'; + +main(List args) async { + if (!isAOTRuntime) { + return; // Running in JIT: AOT binaries not available. + } + + if (Platform.isAndroid) { + return; // SDK tree and dart_bootstrap not available on the test device. + } + + // These are the tools we need to be available to run on a given platform: + if (!await testExecutable(genSnapshot)) { + throw "Cannot run test as $genSnapshot not available"; + } + if (!await testExecutable(aotRuntime)) { + throw "Cannot run test as $aotRuntime not available"; + } + if (!File(platformDill).existsSync()) { + throw "Cannot run test as $platformDill does not exist"; + } + + await withTempDir('readonly-symbols-flag-test', (String tempDir) async { + final cwDir = path.dirname(Platform.script.toFilePath()); + final script = + path.join(cwDir, 'use_save_debugging_info_flag_program.dart'); + final scriptDill = path.join(tempDir, 'flag_program.dill'); + + // Compile script to Kernel IR. + await run(genKernel, [ + '--aot', + '--platform=$platformDill', + '-o', + scriptDill, + script, + ]); + + final scriptSnapshot = path.join(tempDir, 'dwarf.so'); + final scriptDebuggingInfo = path.join(tempDir, 'debug_info.so'); + await run(genSnapshot, [ + '--dwarf-stack-traces-mode', + '--save-debugging-info=$scriptDebuggingInfo', + '--snapshot-kind=app-aot-elf', + '--elf=$scriptSnapshot', + scriptDill, + ]); + + checkElf(scriptSnapshot); + checkElf(scriptDebuggingInfo); + }); +} + +void checkElf(String filename) { + // Check that the static symbol table contains entries that are not in the + // dynamic symbol table, have STB_LOCAL binding, and are of type STT_OBJECT. + final elf = Elf.fromFile(filename); + Expect.isNotNull(elf); + final dynamicSymbols = elf!.dynamicSymbols.toList(); + for (final symbol in dynamicSymbols) { + // All symbol tables have an initial entry with zero-valued fields. + if (symbol.name == '') { + Expect.equals(SymbolBinding.STB_LOCAL, symbol.bind); + Expect.equals(SymbolType.STT_NOTYPE, symbol.type); + Expect.equals(0, symbol.value); + } else { + Expect.equals(SymbolBinding.STB_GLOBAL, symbol.bind); + Expect.equals(SymbolType.STT_OBJECT, symbol.type); + Expect.isTrue(symbol.name.startsWith('_kDart'), + 'unexpected symbol name ${symbol.name}'); + } + } + final onlyStaticSymbols = elf.staticSymbols + .where((s1) => !dynamicSymbols.any((s2) => s1.name == s2.name)); + Expect.isNotEmpty(onlyStaticSymbols, 'no static-only symbols'); + final objectSymbols = + onlyStaticSymbols.where((s) => s.type == SymbolType.STT_OBJECT); + Expect.isNotEmpty(objectSymbols, 'no static-only object symbols'); + for (final symbol in objectSymbols) { + // Currently we only write local object symbols. + Expect.equals(SymbolBinding.STB_LOCAL, symbol.bind); + // All object symbols are prefixed with the type of the C++ object. + final objectType = symbol.name.substring(0, symbol.name.indexOf('_')); + switch (objectType) { + // Used for entries in the non-clustered portion of the read-only data + // section that don't correspond to a specific Dart object. + case 'RawBytes': + // Currently the only types of objects written to the non-clustered + // portion of the read-only data section. + case 'OneByteString': + case 'TwoByteString': + case 'CodeSourceMap': + case 'PcDescriptors': + case 'CompressedStackMaps': + break; + default: + Expect.fail('unexpected object type $objectType'); + } + } +} diff --git a/runtime/tests/vm/dart_2/readonly_data_symbols_test.dart b/runtime/tests/vm/dart_2/readonly_data_symbols_test.dart new file mode 100644 index 00000000000..c6468409cc3 --- /dev/null +++ b/runtime/tests/vm/dart_2/readonly_data_symbols_test.dart @@ -0,0 +1,117 @@ +// Copyright (c) 2022, 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. + +// @dart = 2.9 + +// This test checks that gen_snapshot outputs static symbols for read-only data +// objects. + +// OtherResources=use_save_debugging_info_flag_program.dart + +import "dart:async"; +import "dart:io"; + +import 'package:expect/expect.dart'; +import 'package:native_stack_traces/elf.dart'; +import 'package:path/path.dart' as path; + +import 'use_flag_test_helper.dart'; + +main(List args) async { + if (!isAOTRuntime) { + return; // Running in JIT: AOT binaries not available. + } + + if (Platform.isAndroid) { + return; // SDK tree and dart_bootstrap not available on the test device. + } + + // These are the tools we need to be available to run on a given platform: + if (!await testExecutable(genSnapshot)) { + throw "Cannot run test as $genSnapshot not available"; + } + if (!await testExecutable(aotRuntime)) { + throw "Cannot run test as $aotRuntime not available"; + } + if (!File(platformDill).existsSync()) { + throw "Cannot run test as $platformDill does not exist"; + } + + await withTempDir('readonly-symbols-flag-test', (String tempDir) async { + final cwDir = path.dirname(Platform.script.toFilePath()); + final script = + path.join(cwDir, 'use_save_debugging_info_flag_program.dart'); + final scriptDill = path.join(tempDir, 'flag_program.dill'); + + // Compile script to Kernel IR. + await run(genKernel, [ + '--aot', + '--platform=$platformDill', + '-o', + scriptDill, + script, + ]); + + final scriptSnapshot = path.join(tempDir, 'dwarf.so'); + final scriptDebuggingInfo = path.join(tempDir, 'debug_info.so'); + await run(genSnapshot, [ + '--dwarf-stack-traces-mode', + '--save-debugging-info=$scriptDebuggingInfo', + '--snapshot-kind=app-aot-elf', + '--elf=$scriptSnapshot', + scriptDill, + ]); + + checkElf(scriptSnapshot); + checkElf(scriptDebuggingInfo); + }); +} + +void checkElf(String filename) { + // Check that the static symbol table contains entries that are not in the + // dynamic symbol table, have STB_LOCAL binding, and are of type STT_OBJECT. + final elf = Elf.fromFile(filename); + Expect.isNotNull(elf); + final dynamicSymbols = elf.dynamicSymbols.toList(); + for (final symbol in dynamicSymbols) { + // All symbol tables have an initial entry with zero-valued fields. + if (symbol.name == '') { + Expect.equals(SymbolBinding.STB_LOCAL, symbol.bind); + Expect.equals(SymbolType.STT_NOTYPE, symbol.type); + Expect.equals(0, symbol.value); + } else { + Expect.equals(SymbolBinding.STB_GLOBAL, symbol.bind); + Expect.equals(SymbolType.STT_OBJECT, symbol.type); + Expect.isTrue(symbol.name.startsWith('_kDart'), + 'unexpected symbol name ${symbol.name}'); + } + } + final onlyStaticSymbols = elf.staticSymbols + .where((s1) => !dynamicSymbols.any((s2) => s1.name == s2.name)); + Expect.isNotEmpty(onlyStaticSymbols, 'no static-only symbols'); + final objectSymbols = + onlyStaticSymbols.where((s) => s.type == SymbolType.STT_OBJECT); + Expect.isNotEmpty(objectSymbols, 'no static-only object symbols'); + for (final symbol in objectSymbols) { + // Currently we only write local object symbols. + Expect.equals(SymbolBinding.STB_LOCAL, symbol.bind); + // All object symbols are prefixed with the type of the C++ object. + final objectType = symbol.name.substring(0, symbol.name.indexOf('_')); + switch (objectType) { + // Used for entries in the non-clustered portion of the read-only data + // section that don't correspond to a specific Dart object. + case 'RawBytes': + // Currently the only types of objects written to the non-clustered + // portion of the read-only data section. + case 'OneByteString': + case 'TwoByteString': + case 'CodeSourceMap': + case 'PcDescriptors': + case 'CompressedStackMaps': + break; + default: + Expect.fail('unexpected object type $objectType'); + } + } +} diff --git a/runtime/vm/app_snapshot.cc b/runtime/vm/app_snapshot.cc index dcf35fd3eba..78cb5956e11 100644 --- a/runtime/vm/app_snapshot.cc +++ b/runtime/vm/app_snapshot.cc @@ -7285,7 +7285,11 @@ void Serializer::TraceDataOffset(uint32_t offset) { } uint32_t Serializer::GetDataOffset(ObjectPtr object) const { +#if defined(SNAPSHOT_BACKTRACE) + return image_writer_->GetDataOffsetFor(object, ParentOf(object)); +#else return image_writer_->GetDataOffsetFor(object); +#endif } intptr_t Serializer::GetDataSize() const { @@ -7390,7 +7394,16 @@ void Serializer::UnexpectedObject(ObjectPtr raw_object, const char* message) { } #if defined(SNAPSHOT_BACKTRACE) -ObjectPtr Serializer::ParentOf(const Object& object) { +ObjectPtr Serializer::ParentOf(ObjectPtr object) const { + for (intptr_t i = 0; i < parent_pairs_.length(); i += 2) { + if (parent_pairs_[i]->ptr() == object) { + return parent_pairs_[i + 1]->ptr(); + } + } + return Object::null(); +} + +ObjectPtr Serializer::ParentOf(const Object& object) const { for (intptr_t i = 0; i < parent_pairs_.length(); i += 2) { if (parent_pairs_[i]->ptr() == object.ptr()) { return parent_pairs_[i + 1]->ptr(); diff --git a/runtime/vm/app_snapshot.h b/runtime/vm/app_snapshot.h index 922c8acf94a..3445cb49633 100644 --- a/runtime/vm/app_snapshot.h +++ b/runtime/vm/app_snapshot.h @@ -19,10 +19,6 @@ #include "vm/snapshot.h" #include "vm/version.h" -#if defined(DEBUG) -#define SNAPSHOT_BACKTRACE -#endif - namespace dart { // For full snapshots, we use a clustered snapshot format that trades longer @@ -244,7 +240,8 @@ class Serializer : public ThreadStackResource { void UnexpectedObject(ObjectPtr object, const char* message); #if defined(SNAPSHOT_BACKTRACE) - ObjectPtr ParentOf(const Object& object); + ObjectPtr ParentOf(ObjectPtr object) const; + ObjectPtr ParentOf(const Object& object) const; #endif SerializationCluster* NewClusterForClass(intptr_t cid, bool is_canonical); diff --git a/runtime/vm/image_snapshot.cc b/runtime/vm/image_snapshot.cc index 811593104d9..508fb3e4917 100644 --- a/runtime/vm/image_snapshot.cc +++ b/runtime/vm/image_snapshot.cc @@ -179,6 +179,9 @@ ImageWriter::ImageWriter(Thread* t) next_text_offset_(0), objects_(), instructions_(), +#if defined(DART_PRECOMPILER) + namer_(t->zone()), +#endif image_type_(TagObjectTypeAsReadOnly(zone_, "Image")), instructions_section_type_( TagObjectTypeAsReadOnly(zone_, "InstructionsSection")), @@ -286,11 +289,20 @@ intptr_t ImageWriter::SizeInSnapshot(ObjectPtr raw_object) { } } +#if defined(SNAPSHOT_BACKTRACE) +uint32_t ImageWriter::GetDataOffsetFor(ObjectPtr raw_object, + ObjectPtr raw_parent) { +#else uint32_t ImageWriter::GetDataOffsetFor(ObjectPtr raw_object) { +#endif const intptr_t snap_size = SizeInSnapshot(raw_object); const intptr_t offset = next_data_offset_; next_data_offset_ += snap_size; +#if defined(SNAPSHOT_BACKTRACE) + objects_.Add(ObjectData(raw_object, raw_parent)); +#else objects_.Add(ObjectData(raw_object)); +#endif return offset; } @@ -455,8 +467,11 @@ void ImageWriter::Write(NonStreamingWriteStream* clustered_stream, bool vm) { heap->SetObjectId(data.insns_->ptr(), 0); } for (auto& data : objects_) { - if (data.is_object) { + if (data.is_object()) { data.obj = &Object::Handle(zone_, data.raw_obj); +#if defined(SNAPSHOT_BACKTRACE) + data.parent = &Object::Handle(zone_, data.raw_parent); +#endif } } @@ -464,11 +479,14 @@ void ImageWriter::Write(NonStreamingWriteStream* clustered_stream, bool vm) { // to string objects. String is used for simplicity as a bit container, // can't use TypedData because it has an internal pointer (data_) field. for (auto& data : objects_) { - if (!data.is_object) { + if (!data.is_object()) { const auto bytes = data.bytes; data.obj = &Object::Handle( zone_, OneByteString::New(bytes.buf, bytes.length, Heap::kOld)); - data.is_object = true; +#if defined(SNAPSHOT_BACKTRACE) + data.parent = &Object::null_object(); +#endif + data.set_is_object(true); String::Cast(*data.obj).Hash(); free(bytes.buf); } @@ -489,13 +507,8 @@ void ImageWriter::Write(NonStreamingWriteStream* clustered_stream, bool vm) { } void ImageWriter::WriteROData(NonStreamingWriteStream* stream, bool vm) { -#if defined(DART_PRECOMPILER) - const intptr_t start_position = stream->Position(); -#endif - stream->Align(ImageWriter::kRODataAlignment); - + ASSERT(Utils::IsAligned(stream->Position(), kRODataAlignment)); // Heap page starts here. - intptr_t section_start = stream->Position(); stream->WriteWord(next_data_offset_); // Data length. @@ -505,20 +518,20 @@ void ImageWriter::WriteROData(NonStreamingWriteStream* stream, bool vm) { ASSERT_EQUAL(stream->Position() - section_start, Image::kHeaderSize); #if defined(DART_PRECOMPILER) if (profile_writer_ != nullptr) { - const intptr_t end_position = stream->Position(); + // Attribute the Image header to the artificial root. profile_writer_->AttributeBytesTo( - V8SnapshotProfileWriter::kArtificialRootId, - end_position - start_position); + V8SnapshotProfileWriter::kArtificialRootId, Image::kHeaderSize); } #endif // Heap page objects start here. for (auto entry : objects_) { - ASSERT(entry.is_object); + ASSERT(entry.is_object()); const Object& obj = *entry.obj; #if defined(DART_PRECOMPILER) AutoTraceImage(obj, section_start, stream); + const char* object_name = namer_.SnapshotNameFor(entry); #endif auto const object_start = stream->Position(); @@ -568,6 +581,9 @@ void ImageWriter::WriteROData(NonStreamingWriteStream* stream, bool vm) { } stream->Align(compiler::target::ObjectAlignment::kObjectAlignment); ASSERT_EQUAL(stream->Position() - object_start, SizeInSnapshot(obj)); +#if defined(DART_PRECOMPILER) + AddDataSymbol(object_name, object_start, stream->Position() - object_start); +#endif } } @@ -772,7 +788,6 @@ void ImageWriter::WriteText(bool vm) { #if defined(DART_PRECOMPILER) PcDescriptors& descriptors = PcDescriptors::Handle(zone_); - SnapshotTextObjectNamer namer(zone_); #endif ASSERT(offset_space_ != IdSpace::kSnapshot); @@ -782,10 +797,7 @@ void ImageWriter::WriteText(bool vm) { ASSERT_EQUAL(data.text_offset_, text_offset); #if defined(DART_PRECOMPILER) - // We won't add trampolines as symbols, so their name need not be unique - // across different WriteText() calls. - const char* object_name = namer.SnapshotNameFor( - is_trampoline ? i : unique_symbol_counter_++, data); + const char* object_name = namer_.SnapshotNameFor(data); if (profile_writer_ != nullptr) { const V8SnapshotProfileWriter::ObjectId id(offset_space_, text_offset); @@ -1106,7 +1118,7 @@ void AssemblyImageWriter::Finalize() { } } -static void AddAssemblerIdentifier(ZoneTextBuffer* printer, const char* label) { +static void AddAssemblerIdentifier(BaseTextBuffer* printer, const char* label) { ASSERT(label[0] != '.'); if (label[0] == 'L' && printer->length() == 0) { // Assembler treats labels starting with `L` as local which can cause @@ -1159,47 +1171,89 @@ static void AddAssemblerIdentifier(ZoneTextBuffer* printer, const char* label) { } } -const char* SnapshotTextObjectNamer::SnapshotNameFor(intptr_t code_index, - const Code& code) { - ASSERT(!code.IsNull()); - owner_ = code.owner(); - if (owner_.IsNull()) { - insns_ = code.instructions(); - const char* name = StubCode::NameOfStub(insns_.EntryPoint()); - ASSERT(name != nullptr); - return OS::SCreate(zone_, "Stub_%s", name); - } - // The weak reference to the Code's owner should never have been removed via - // an intermediate serialization, since WSRs are only introduced during - // precompilation. - owner_ = WeakSerializationReference::Unwrap(owner_); - ASSERT(!owner_.IsNull()); - ZoneTextBuffer printer(zone_); - if (owner_.IsClass()) { - const char* name = Class::Cast(owner_).ScrubbedNameCString(); - printer.AddString("AllocationStub_"); - AddAssemblerIdentifier(&printer, name); - } else if (owner_.IsAbstractType()) { - const char* name = namer_.StubNameForType(AbstractType::Cast(owner_)); - printer.AddString(name); - } else if (owner_.IsFunction()) { - const char* name = Function::Cast(owner_).QualifiedScrubbedNameCString(); - AddAssemblerIdentifier(&printer, name); +void ImageWriter::SnapshotTextObjectNamer::AddNonUniqueNameFor( + BaseTextBuffer* buffer, + const Object& object) { + if (object.IsCode()) { + const Code& code = Code::Cast(object); + owner_ = WeakSerializationReference::Unwrap(code.owner()); + if (owner_.IsNull()) { + buffer->AddString("Stub_"); + insns_ = code.instructions(); + const char* name = StubCode::NameOfStub(insns_.EntryPoint()); + ASSERT(name != nullptr); + buffer->AddString(name); + } else { + if (owner_.IsClass()) { + buffer->AddString("AllocationStub_"); + } else if (!owner_.IsAbstractType() && !owner_.IsFunction()) { + // Double-check that we didn't get an invalid owner for the Code object. + UNREACHABLE(); + } + AddNonUniqueNameFor(buffer, owner_); + } + } else if (object.IsClass()) { + const char* name = Class::Cast(object).ScrubbedNameCString(); + AddAssemblerIdentifier(buffer, name); + } else if (object.IsAbstractType()) { + namer_.WriteStubNameForTypeTo(buffer, AbstractType::Cast(object)); + } else if (object.IsFunction()) { + const char* name = Function::Cast(object).QualifiedScrubbedNameCString(); + AddAssemblerIdentifier(buffer, name); + } else if (object.IsCompressedStackMaps()) { + buffer->AddString("CompressedStackMaps"); + } else if (object.IsPcDescriptors()) { + buffer->AddString("PcDescriptors"); + } else if (object.IsCodeSourceMap()) { + buffer->AddString("CodeSourceMap"); + } else if (object.IsString()) { + const String& str = String::Cast(object); + if (str.IsOneByteString()) { + buffer->AddString("OneByteString"); + } else if (str.IsTwoByteString()) { + buffer->AddString("TwoByteString"); + } } else { UNREACHABLE(); } +} - printer.Printf("_%" Pd, code_index); +const char* ImageWriter::SnapshotTextObjectNamer::SnapshotNameFor( + const InstructionsData& data) { + ZoneTextBuffer printer(zone_); + if (data.trampoline_bytes != nullptr) { + printer.AddString("Trampoline"); + } else { + AddNonUniqueNameFor(&printer, *data.code_); + } + printer.Printf("_%" Pd, nonce_++); return printer.buffer(); } -const char* SnapshotTextObjectNamer::SnapshotNameFor( - intptr_t index, - const ImageWriter::InstructionsData& data) { - if (data.trampoline_bytes != nullptr) { - return OS::SCreate(zone_, "Trampoline_%" Pd "", index); +const char* ImageWriter::SnapshotTextObjectNamer::SnapshotNameFor( + const ObjectData& data) { + ASSERT(data.is_object()); + ZoneTextBuffer printer(zone_); + if (data.is_original_object()) { + const Object& obj = *data.obj; + AddNonUniqueNameFor(&printer, obj); +#if defined(SNAPSHOT_BACKTRACE) + // It's less useful knowing the parent of a String than other read-only + // data objects, and this avoids us having to handle other classes + // in AddNonUniqueNameFor. + if (!obj.IsString()) { + const Object& parent = *data.parent; + if (!parent.IsNull()) { + printer.AddString("_"); + AddNonUniqueNameFor(&printer, parent); + } + } +#endif + } else { + printer.AddString("RawBytes"); } - return SnapshotNameFor(index, *data.code_); + printer.Printf("_%" Pd, nonce_++); + return printer.buffer(); } void AssemblyImageWriter::WriteBss(bool vm) { @@ -1215,12 +1269,35 @@ void AssemblyImageWriter::WriteBss(bool vm) { void AssemblyImageWriter::WriteROData(NonStreamingWriteStream* clustered_stream, bool vm) { - ImageWriter::WriteROData(clustered_stream, vm); if (!EnterSection(ProgramSection::Data, vm, ImageWriter::kRODataAlignment)) { return; } - WriteBytes(clustered_stream->buffer(), clustered_stream->bytes_written()); - ExitSection(ProgramSection::Data, vm, clustered_stream->bytes_written()); + // The clustered stream already has some data on it from the serializer, so + // make sure that the read-only objects start at the appropriate alignment + // within the stream, as we'll write the entire clustered stream to the + // assembly output (which was aligned in EnterSection). + const intptr_t start_position = clustered_stream->Position(); + clustered_stream->Align(ImageWriter::kRODataAlignment); + if (profile_writer_ != nullptr) { + // Attribute any padding needed to the artificial root. + const intptr_t padding = clustered_stream->Position() - start_position; + profile_writer_->AttributeBytesTo( + V8SnapshotProfileWriter::kArtificialRootId, padding); + } + // First write the read-only data objects to the clustered stream. + ImageWriter::WriteROData(clustered_stream, vm); + // Next, write the bytes of the clustered stream (along with any symbols + // if appropriate) to the assembly output. + const uint8_t* bytes = clustered_stream->buffer(); + const intptr_t len = clustered_stream->bytes_written(); + intptr_t last_position = 0; + for (const auto& symbol : *current_symbols_) { + WriteBytes(bytes + last_position, symbol.offset - last_position); + assembly_stream_->Printf("%s:\n", symbol.name); + last_position = symbol.offset; + } + WriteBytes(bytes + last_position, len - last_position); + ExitSection(ProgramSection::Data, vm, len); } bool AssemblyImageWriter::EnterSection(ProgramSection section, @@ -1240,10 +1317,14 @@ bool AssemblyImageWriter::EnterSection(ProgramSection section, global_symbol = true; break; case ProgramSection::Data: - if (debug_elf_ != nullptr) { - current_symbols_ = - new (zone_) ZoneGrowableArray(zone_, 0); - } + // We create a SymbolData array even if there is no debug_elf_ because we + // may be writing RO data symbols, and RO data is written in two steps: + // 1. Serializing the read-only data objects to the clustered stream + // 2. Writing the bytes of the clustered stream to the assembly output. + // Thus, we'll need to interleave the symbols with the cluster bytes + // during step 2. + current_symbols_ = + new (zone_) ZoneGrowableArray(zone_, 0); #if defined(DART_TARGET_OS_LINUX) || defined(DART_TARGET_OS_ANDROID) || \ defined(DART_TARGET_OS_FUCHSIA) assembly_stream_->WriteString(".section .rodata\n"); @@ -1381,6 +1462,12 @@ void AssemblyImageWriter::AddCodeSymbol(const Code& code, assembly_stream_->Printf("%s:\n", symbol); } +void AssemblyImageWriter::AddDataSymbol(const char* symbol, + intptr_t offset, + size_t size) { + current_symbols_->Add({symbol, elf::STT_OBJECT, offset, size}); +} + void AssemblyImageWriter::FrameUnwindPrologue() { // Creates DWARF's .debug_frame // CFI = Call frame information @@ -1504,11 +1591,22 @@ void BlobImageWriter::WriteBss(bool vm) { void BlobImageWriter::WriteROData(NonStreamingWriteStream* clustered_stream, bool vm) { - ImageWriter::WriteROData(clustered_stream, vm); - current_section_stream_ = clustered_stream; +#if defined(DART_PRECOMPILER) + const intptr_t start_position = clustered_stream->Position(); +#endif + current_section_stream_ = ASSERT_NOTNULL(clustered_stream); if (!EnterSection(ProgramSection::Data, vm, ImageWriter::kRODataAlignment)) { return; } +#if defined(DART_PRECOMPILER) + if (profile_writer_ != nullptr) { + // Attribute any padding needed to the artificial root. + const intptr_t padding = clustered_stream->Position() - start_position; + profile_writer_->AttributeBytesTo( + V8SnapshotProfileWriter::kArtificialRootId, padding); + } +#endif + ImageWriter::WriteROData(clustered_stream, vm); ExitSection(ProgramSection::Data, vm, clustered_stream->bytes_written()); } @@ -1520,7 +1618,6 @@ bool BlobImageWriter::EnterSection(ProgramSection section, ASSERT(current_relocations_ == nullptr); ASSERT(current_symbols_ == nullptr); #endif - // For now, we set current_section_stream_ in ::WriteData. ASSERT(section == ProgramSection::Data || current_section_stream_ == nullptr); ASSERT(current_section_symbol_ == nullptr); switch (section) { @@ -1535,6 +1632,8 @@ bool BlobImageWriter::EnterSection(ProgramSection section, #endif break; case ProgramSection::Data: + // The stream to use is passed into WriteROData and set there. + ASSERT(current_section_stream_ != nullptr); #if defined(DART_PRECOMPILER) current_relocations_ = new (zone_) ZoneGrowableArray(zone_, 0); @@ -1614,6 +1713,12 @@ void BlobImageWriter::AddCodeSymbol(const Code& code, debug_elf_->dwarf()->AddCode(code, symbol); } } + +void BlobImageWriter::AddDataSymbol(const char* symbol, + intptr_t offset, + size_t size) { + current_symbols_->Add({symbol, elf::STT_OBJECT, offset, size}); +} #endif // defined(DART_PRECOMPILER) #endif // !defined(DART_PRECOMPILED_RUNTIME) diff --git a/runtime/vm/image_snapshot.h b/runtime/vm/image_snapshot.h index 730b861e196..c1ce1513ec1 100644 --- a/runtime/vm/image_snapshot.h +++ b/runtime/vm/image_snapshot.h @@ -22,6 +22,10 @@ #include "vm/type_testing_stubs.h" #include "vm/v8_snapshot_writer.h" +#if defined(DEBUG) +#define SNAPSHOT_BACKTRACE +#endif + namespace dart { // Forward declarations. @@ -270,7 +274,11 @@ class ImageWriter : public ValueObject { offset_space_ == IdSpace::kIsolateText; } int32_t GetTextOffsetFor(InstructionsPtr instructions, CodePtr code); +#if defined(SNAPSHOT_BACKTRACE) + uint32_t GetDataOffsetFor(ObjectPtr raw_object, ObjectPtr raw_parent); +#else uint32_t GetDataOffsetFor(ObjectPtr raw_object); +#endif uint32_t AddBytesToData(uint8_t* bytes, intptr_t length); @@ -356,10 +364,27 @@ class ImageWriter : public ValueObject { }; struct ObjectData { - explicit ObjectData(ObjectPtr raw_obj) - : raw_obj(raw_obj), is_object(true) {} +#if defined(SNAPSHOT_BACKTRACE) + explicit ObjectData(ObjectPtr raw_obj, ObjectPtr raw_parent) + : raw_obj(raw_obj), + raw_parent(raw_parent), + flags(IsObjectField::encode(true) | + IsOriginalObjectField::encode(true)) {} ObjectData(uint8_t* buf, intptr_t length) - : bytes({buf, length}), is_object(false) {} + : bytes({buf, length}), + raw_parent(Object::null()), + flags(IsObjectField::encode(false) | + IsOriginalObjectField::encode(false)) {} +#else + explicit ObjectData(ObjectPtr raw_obj) + : raw_obj(raw_obj), + flags(IsObjectField::encode(true) | + IsOriginalObjectField::encode(true)) {} + ObjectData(uint8_t* buf, intptr_t length) + : bytes({buf, length}), + flags(IsObjectField::encode(false) | + IsOriginalObjectField::encode(false)) {} +#endif union { struct { @@ -369,7 +394,26 @@ class ImageWriter : public ValueObject { ObjectPtr raw_obj; const Object* obj; }; - bool is_object; +#if defined(SNAPSHOT_BACKTRACE) + union { + ObjectPtr raw_parent; + const Object* parent; + }; +#endif + uint8_t flags; + + bool is_object() const { return IsObjectField::decode(flags); } + bool is_original_object() const { + return IsOriginalObjectField::decode(flags); + } + + void set_is_object(bool value) { + flags = IsObjectField::update(value, flags); + } + + using IsObjectField = BitField; + using IsOriginalObjectField = + BitField; }; // Methods abstracting out the particulars of the underlying concrete writer. @@ -415,6 +459,10 @@ class ImageWriter : public ValueObject { virtual void AddCodeSymbol(const Code& code, const char* symbol, intptr_t section_offset) = 0; + // Creates a static symbol for a read-only data object when appropriate. + virtual void AddDataSymbol(const char* symbol, + intptr_t section_offset, + size_t size) = 0; // Overloaded convenience versions of the above virtual methods. @@ -442,6 +490,47 @@ class ImageWriter : public ValueObject { GrowableArray objects_; GrowableArray instructions_; +#if defined(DART_PRECOMPILER) + class SnapshotTextObjectNamer : ValueObject { + public: + explicit SnapshotTextObjectNamer(Zone* zone) + : zone_(ASSERT_NOTNULL(zone)), + owner_(Object::Handle(zone)), + string_(String::Handle(zone)), + insns_(Instructions::Handle(zone)), + store_(IsolateGroup::Current()->object_store()) {} + + const char* StubNameForType(const AbstractType& type) const; + + // Returns a unique assembly-safe name for text data to use in symbols. + // Assumes that code in the InstructionsData has been allocated a handle. + const char* SnapshotNameFor(const InstructionsData& data); + // Returns a unique assembly-safe name for read-only data to use in symbols. + // Assumes that the ObjectData has already been converted to object handles. + const char* SnapshotNameFor(const ObjectData& data); + + private: + // Returns a unique assembly-safe name for the given code or read-only + // data object for use in symbols. + const char* SnapshotNameFor(const Object& object); + // Adds a non-unique assembly-safe name for the given object to the given + // buffer. + void AddNonUniqueNameFor(BaseTextBuffer* buffer, const Object& object); + + Zone* const zone_; + Object& owner_; + String& string_; + Instructions& insns_; + ObjectStore* const store_; + TypeTestingStubNamer namer_; + intptr_t nonce_ = 0; + + DISALLOW_COPY_AND_ASSIGN(SnapshotTextObjectNamer); + }; + + SnapshotTextObjectNamer namer_; +#endif + IdSpace offset_space_ = IdSpace::kSnapshot; V8SnapshotProfileWriter* profile_writer_ = nullptr; const char* const image_type_; @@ -449,12 +538,8 @@ class ImageWriter : public ValueObject { const char* const instructions_type_; const char* const trampoline_type_; - // Used to make sure Code symbols are unique across text sections. - intptr_t unique_symbol_counter_ = 0; - template friend class TraceImageObjectScope; - friend class SnapshotTextObjectNamer; // For InstructionsData. private: static intptr_t SizeInSnapshotForBytes(intptr_t length); @@ -503,32 +588,6 @@ class TraceImageObjectScope : ValueObject { DISALLOW_COPY_AND_ASSIGN(TraceImageObjectScope); }; -class SnapshotTextObjectNamer : ValueObject { - public: - explicit SnapshotTextObjectNamer(Zone* zone) - : zone_(ASSERT_NOTNULL(zone)), - owner_(Object::Handle(zone)), - string_(String::Handle(zone)), - insns_(Instructions::Handle(zone)), - store_(IsolateGroup::Current()->object_store()) {} - - const char* StubNameForType(const AbstractType& type) const; - - const char* SnapshotNameFor(intptr_t code_index, const Code& code); - const char* SnapshotNameFor(intptr_t index, - const ImageWriter::InstructionsData& data); - - private: - Zone* const zone_; - Object& owner_; - String& string_; - Instructions& insns_; - ObjectStore* const store_; - TypeTestingStubNamer namer_; - - DISALLOW_COPY_AND_ASSIGN(SnapshotTextObjectNamer); -}; - class AssemblyImageWriter : public ImageWriter { public: AssemblyImageWriter(Thread* thread, @@ -563,6 +622,7 @@ class AssemblyImageWriter : public ImageWriter { virtual void AddCodeSymbol(const Code& code, const char* symbol, intptr_t offset); + virtual void AddDataSymbol(const char* symbol, intptr_t offset, size_t size); BaseWriteStream* const assembly_stream_; Dwarf* const assembly_dwarf_; @@ -571,8 +631,8 @@ class AssemblyImageWriter : public ImageWriter { // Used in Relocation to output "(.)" for relocations involving the current // section position and creating local symbols in AddCodeSymbol. const char* current_section_symbol_ = nullptr; - // Used for creating local symbols for code objects in the debugging info, - // if separately written. + // Used for creating local symbols for code and data objects in the + // debugging info, if separately written. ZoneGrowableArray* current_symbols_ = nullptr; DISALLOW_COPY_AND_ASSIGN(AssemblyImageWriter); @@ -616,6 +676,7 @@ class BlobImageWriter : public ImageWriter { virtual void AddCodeSymbol(const Code& code, const char* symbol, intptr_t offset); + virtual void AddDataSymbol(const char* symbol, intptr_t offset, size_t size); // Set on section entrance to a new array containing the relocations for the // current section. diff --git a/runtime/vm/type_testing_stubs.cc b/runtime/vm/type_testing_stubs.cc index 41ac9d1f1d9..a62a5e44aac 100644 --- a/runtime/vm/type_testing_stubs.cc +++ b/runtime/vm/type_testing_stubs.cc @@ -14,6 +14,7 @@ #include "vm/stub_code.h" #include "vm/timeline.h" #include "vm/type_testing_stubs.h" +#include "vm/zone_text_buffer.h" #if !defined(DART_PRECOMPILED_RUNTIME) #include "vm/compiler/backend/flow_graph_compiler.h" @@ -32,57 +33,60 @@ TypeTestingStubNamer::TypeTestingStubNamer() const char* TypeTestingStubNamer::StubNameForType( const AbstractType& type) const { - Zone* Z = Thread::Current()->zone(); - return OS::SCreate(Z, "TypeTestingStub_%s", StringifyType(type)); + ZoneTextBuffer buffer(Thread::Current()->zone()); + WriteStubNameForTypeTo(&buffer, type); + return buffer.buffer(); } -const char* TypeTestingStubNamer::StringifyType( +void TypeTestingStubNamer::WriteStubNameForTypeTo( + BaseTextBuffer* buffer, const AbstractType& type) const { + buffer->AddString("TypeTestingStub_"); + StringifyTypeTo(buffer, type); +} + +void TypeTestingStubNamer::StringifyTypeTo(BaseTextBuffer* buffer, + const AbstractType& type) const { NoSafepointScope no_safepoint; - Zone* Z = Thread::Current()->zone(); if (type.IsType()) { const intptr_t cid = Type::Cast(type).type_class_id(); ClassTable* class_table = IsolateGroup::Current()->class_table(); klass_ = class_table->At(cid); ASSERT(!klass_.IsNull()); - const char* curl = ""; lib_ = klass_.library(); if (!lib_.IsNull()) { string_ = lib_.url(); - curl = OS::SCreate(Z, "%s_", string_.ToCString()); + buffer->AddString(string_.ToCString()); } else { - static std::atomic counter = 0; - curl = OS::SCreate(Z, "nolib%" Pd "_", counter++); + buffer->Printf("nolib%" Pd "_", nonce_++); } - const char* concatenated = AssemblerSafeName( - OS::SCreate(Z, "%s_%s", curl, klass_.ScrubbedNameCString())); + buffer->AddString("_"); + buffer->AddString(klass_.ScrubbedNameCString()); const intptr_t type_parameters = klass_.NumTypeParameters(); - auto& type_arguments = TypeArguments::Handle(); - if (type.arguments() != TypeArguments::null() && type_parameters > 0) { + auto& type_arguments = TypeArguments::Handle(type.arguments()); + if (!type_arguments.IsNull() && type_parameters > 0) { type_arguments = type.arguments(); ASSERT(type_arguments.Length() >= type_parameters); const intptr_t length = type_arguments.Length(); for (intptr_t i = 0; i < type_parameters; ++i) { type_ = type_arguments.TypeAt(length - type_parameters + i); - concatenated = - OS::SCreate(Z, "%s__%s", concatenated, StringifyType(type_)); + buffer->AddString("__"); + StringifyTypeTo(buffer, type_); } } - - return concatenated; } else if (type.IsTypeParameter()) { - return AssemblerSafeName( - OS::SCreate(Z, "%s", TypeParameter::Cast(type).CanonicalNameCString())); + buffer->AddString(TypeParameter::Cast(type).CanonicalNameCString()); } else { - return AssemblerSafeName(OS::SCreate(Z, "%s", type.ToCString())); + buffer->AddString(type.ToCString()); } + MakeNameAssemblerSafe(buffer); } -const char* TypeTestingStubNamer::AssemblerSafeName(char* cname) { - char* cursor = cname; +void TypeTestingStubNamer::MakeNameAssemblerSafe(BaseTextBuffer* buffer) { + char* cursor = buffer->buffer(); while (*cursor != '\0') { char c = *cursor; if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || @@ -91,7 +95,6 @@ const char* TypeTestingStubNamer::AssemblerSafeName(char* cname) { } cursor++; } - return cname; } CodePtr TypeTestingStubGenerator::DefaultCodeForType( diff --git a/runtime/vm/type_testing_stubs.h b/runtime/vm/type_testing_stubs.h index 7807bc1b014..b8232152fa2 100644 --- a/runtime/vm/type_testing_stubs.h +++ b/runtime/vm/type_testing_stubs.h @@ -24,15 +24,19 @@ class TypeTestingStubNamer { // // (only during dart_boostrap). const char* StubNameForType(const AbstractType& type) const; + void WriteStubNameForTypeTo(BaseTextBuffer* buffer, + const AbstractType& type) const; private: - const char* StringifyType(const AbstractType& type) const; - static const char* AssemblerSafeName(char* cname); + void StringifyTypeTo(BaseTextBuffer* buffer, const AbstractType& type) const; + // Converts the contents of the buffer to an assembly-safe name. + static void MakeNameAssemblerSafe(BaseTextBuffer* buffer); Library& lib_; Class& klass_; AbstractType& type_; String& string_; + mutable intptr_t nonce_ = 0; }; class TypeTestingStubGenerator {