| // Copyright (c) 2017, 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 "vm/image_snapshot.h" | |
| #include "include/dart_api.h" | |
| #include "platform/assert.h" | |
| #include "platform/elf.h" | |
| #include "platform/mach_o.h" | |
| #include "vm/bss_relocs.h" | |
| #include "vm/class_id.h" | |
| #include "vm/compiler/runtime_api.h" | |
| #include "vm/dwarf.h" | |
| #include "vm/elf.h" | |
| #include "vm/hash.h" | |
| #include "vm/hash_map.h" | |
| #include "vm/heap/heap.h" | |
| #include "vm/instructions.h" | |
| #include "vm/json_writer.h" | |
| #include "vm/object.h" | |
| #include "vm/object_store.h" | |
| #include "vm/program_visitor.h" | |
| #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/code_statistics.h" | |
| #endif // !defined(DART_PRECOMPILED_RUNTIME) | |
| namespace dart { | |
| #if defined(DART_PRECOMPILER) | |
| DEFINE_FLAG(bool, | |
| print_instruction_stats, | |
| false, | |
| "Print instruction statistics"); | |
| DEFINE_FLAG(charp, | |
| print_instructions_sizes_to, | |
| nullptr, | |
| "Print sizes of all instruction objects to the given file"); | |
| #endif | |
| const UntaggedInstructionsSection* Image::ExtraInfo(const uword raw_memory, | |
| const uword size) { | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| auto const raw_value = | |
| FieldValue(raw_memory, HeaderField::InstructionsSectionOffset); | |
| if (raw_value != kNoInstructionsSection) { | |
| ASSERT(raw_value >= kHeaderSize); | |
| ASSERT(raw_value <= size - InstructionsSection::HeaderSize()); | |
| auto const layout = reinterpret_cast<const UntaggedInstructionsSection*>( | |
| raw_memory + raw_value); | |
| // The instructions section is likely non-empty in bare instructions mode | |
| // (unless splitting into multiple outputs and there are no Code objects | |
| // in this particular output), but is guaranteed empty otherwise (the | |
| // instructions follow the InstructionsSection object instead). | |
| ASSERT(raw_value <= | |
| size - InstructionsSection::InstanceSize(layout->payload_length_)); | |
| return layout; | |
| } | |
| #endif | |
| return nullptr; | |
| } | |
| uword* Image::bss() const { | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| ASSERT(extra_info_ != nullptr); | |
| // There should always be a non-zero BSS offset. | |
| ASSERT(extra_info_->bss_offset_ != 0); | |
| // Returning a non-const uword* is safe because we're translating from | |
| // the start of the instructions (read-only) to the start of the BSS | |
| // (read-write). | |
| return reinterpret_cast<uword*>(raw_memory_ + extra_info_->bss_offset_); | |
| #else | |
| return nullptr; | |
| #endif | |
| } | |
| uword Image::instructions_relocated_address() const { | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| ASSERT(extra_info_ != nullptr); | |
| return extra_info_->instructions_relocated_address_; | |
| #else | |
| return kNoRelocatedAddress; | |
| #endif | |
| } | |
| const void* Image::build_id_start() const { | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| if (compiled_to_shared_object() && | |
| extra_info_->build_id_offset_ != kNoBuildId) { | |
| return reinterpret_cast<const void*>(raw_memory_ + | |
| extra_info_->build_id_offset_); | |
| } | |
| #endif | |
| return nullptr; | |
| } | |
| const uint8_t* Image::build_id() const { | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| auto* const start = build_id_start(); | |
| if (start == nullptr) return nullptr; | |
| if (compiled_to_elf()) { | |
| auto* const note = reinterpret_cast<const elf::Note*>(start); | |
| return note->data + note->name_size; | |
| } else if (compiled_to_macho()) { | |
| return reinterpret_cast<const mach_o::uuid_command*>(start)->uuid; | |
| } | |
| #endif | |
| return nullptr; | |
| } | |
| intptr_t Image::build_id_length() const { | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| ASSERT(extra_info_ != nullptr); | |
| auto* const start = build_id_start(); | |
| if (start == nullptr) return 0; | |
| if (compiled_to_elf()) { | |
| auto const note = reinterpret_cast<const elf::Note*>(start); | |
| return note->description_size; | |
| } else if (compiled_to_macho()) { | |
| return sizeof(mach_o::uuid_command::uuid); | |
| } | |
| #endif | |
| return 0; | |
| } | |
| bool Image::compiled_to_shared_object() const { | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| ASSERT(extra_info_ != nullptr); | |
| // Since assembly snapshots can't set up this field correctly, it is used | |
| // to detect direct-to-shared-object snapshots. | |
| return extra_info_->instructions_relocated_address_ != kNoRelocatedAddress; | |
| #else | |
| return false; | |
| #endif | |
| } | |
| const uint8_t* Image::shared_object_start() const { | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| if (compiled_to_shared_object()) { | |
| return reinterpret_cast<const uint8_t*>( | |
| raw_memory_ - extra_info_->instructions_relocated_address_); | |
| } | |
| #endif | |
| return nullptr; | |
| } | |
| bool Image::compiled_to_elf() const { | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| if (!compiled_to_shared_object()) return false; | |
| auto* const ident = | |
| reinterpret_cast<const elf::ElfHeader*>(shared_object_start())->ident; | |
| for (size_t i = 0; i < ARRAY_SIZE(elf::ELFMAG); ++i) { | |
| if (ident[i] != elf::ELFMAG[i]) return false; | |
| } | |
| return true; | |
| #else | |
| return false; | |
| #endif | |
| } | |
| bool Image::compiled_to_macho() const { | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| if (!compiled_to_shared_object()) return false; | |
| auto const magic = | |
| reinterpret_cast<const mach_o::mach_header*>(shared_object_start()) | |
| ->magic; | |
| return magic == mach_o::MH_MAGIC || magic == mach_o::MH_CIGAM || | |
| magic == mach_o::MH_MAGIC_64 || magic == mach_o::MH_CIGAM_64; | |
| #else | |
| return false; | |
| #endif | |
| } | |
| uword ObjectOffsetTrait::Hash(Key key) { | |
| ObjectPtr obj = key; | |
| ASSERT(!obj->IsSmi()); | |
| uword body = UntaggedObject::ToAddr(obj) + sizeof(UntaggedObject); | |
| uword end = UntaggedObject::ToAddr(obj) + obj->untag()->HeapSize(); | |
| uint32_t hash = obj->GetClassIdOfHeapObject(); | |
| // Don't include the header. Objects in the image are pre-marked, but objects | |
| // in the current isolate are not. | |
| for (uword cursor = body; cursor < end; cursor += sizeof(uint32_t)) { | |
| hash = CombineHashes(hash, *reinterpret_cast<uint32_t*>(cursor)); | |
| } | |
| return FinalizeHash(hash, 30); | |
| } | |
| bool ObjectOffsetTrait::IsKeyEqual(Pair pair, Key key) { | |
| ObjectPtr a = pair.object; | |
| ObjectPtr b = key; | |
| ASSERT(!a->IsSmi()); | |
| ASSERT(!b->IsSmi()); | |
| if (a->GetClassIdOfHeapObject() != b->GetClassIdOfHeapObject()) { | |
| return false; | |
| } | |
| intptr_t heap_size = a->untag()->HeapSize(); | |
| if (b->untag()->HeapSize() != heap_size) { | |
| return false; | |
| } | |
| // Don't include the header. Objects in the image are pre-marked, but objects | |
| // in the current isolate are not. | |
| uword body_a = UntaggedObject::ToAddr(a) + sizeof(UntaggedObject); | |
| uword body_b = UntaggedObject::ToAddr(b) + sizeof(UntaggedObject); | |
| uword body_size = heap_size - sizeof(UntaggedObject); | |
| return 0 == memcmp(reinterpret_cast<const void*>(body_a), | |
| reinterpret_cast<const void*>(body_b), body_size); | |
| } | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| #if defined(DART_PRECOMPILER) | |
| ImageWriter::ImageWriter(Thread* t, | |
| bool generates_assembly, | |
| bool needs_unique_names, | |
| const Trie<const char>* deobfuscation_trie) | |
| #else | |
| ImageWriter::ImageWriter(Thread* t, | |
| bool generates_assembly, | |
| bool needs_unique_names) | |
| #endif | |
| : thread_(ASSERT_NOTNULL(t)), | |
| zone_(t->zone()), | |
| next_data_offset_(0), | |
| next_text_offset_(0), | |
| objects_(), | |
| instructions_(), | |
| #if defined(DART_PRECOMPILER) | |
| namer_(t->zone(), | |
| deobfuscation_trie, | |
| /*for_assembly=*/generates_assembly, | |
| /*create_unique_names=*/needs_unique_names), | |
| #endif | |
| image_type_(TagObjectTypeAsReadOnly(zone_, "Image")), | |
| instructions_section_type_( | |
| TagObjectTypeAsReadOnly(zone_, "InstructionsSection")), | |
| instructions_type_(TagObjectTypeAsReadOnly(zone_, "Instructions")), | |
| trampoline_type_(TagObjectTypeAsReadOnly(zone_, "Trampoline")), | |
| padding_type_(TagObjectTypeAsReadOnly(zone_, "Padding")) { | |
| ResetOffsets(); | |
| } | |
| void ImageWriter::PrepareForSerialization( | |
| GrowableArray<ImageWriterCommand>* commands) { | |
| if (commands != nullptr) { | |
| const intptr_t initial_offset = next_text_offset_; | |
| for (auto& inst : *commands) { | |
| ASSERT((initial_offset + inst.expected_offset) == next_text_offset_); | |
| switch (inst.op) { | |
| case ImageWriterCommand::InsertInstructionOfCode: { | |
| Heap* const heap = thread_->heap(); | |
| CodePtr code = inst.insert_instruction_of_code.code; | |
| InstructionsPtr instructions = Code::InstructionsOf(code); | |
| const intptr_t offset = next_text_offset_; | |
| instructions_.Add(InstructionsData(instructions, code, offset)); | |
| next_text_offset_ += SizeInSnapshot(instructions); | |
| ASSERT(heap->GetObjectId(instructions) == 0); | |
| heap->SetObjectId(instructions, offset); | |
| break; | |
| } | |
| case ImageWriterCommand::InsertBytesOfTrampoline: { | |
| auto trampoline_bytes = inst.insert_trampoline_bytes.buffer; | |
| auto trampoline_length = inst.insert_trampoline_bytes.buffer_length; | |
| const intptr_t offset = next_text_offset_; | |
| instructions_.Add( | |
| InstructionsData(trampoline_bytes, trampoline_length, offset)); | |
| next_text_offset_ += trampoline_length; | |
| break; | |
| } | |
| case ImageWriterCommand::InsertPadding: { | |
| auto padding_length = inst.insert_padding.padding_length; | |
| const intptr_t offset = next_text_offset_; | |
| instructions_.Add(InstructionsData(nullptr, padding_length, offset)); | |
| next_text_offset_ += padding_length; | |
| break; | |
| } | |
| default: | |
| UNREACHABLE(); | |
| } | |
| } | |
| } | |
| } | |
| int32_t ImageWriter::GetTextOffsetFor(InstructionsPtr instructions, | |
| CodePtr code) { | |
| Heap* const heap = thread_->heap(); | |
| intptr_t offset = heap->GetObjectId(instructions); | |
| if (offset != 0) { | |
| return offset; | |
| } | |
| offset = next_text_offset_; | |
| heap->SetObjectId(instructions, offset); | |
| next_text_offset_ += SizeInSnapshot(instructions); | |
| instructions_.Add(InstructionsData(instructions, code, offset)); | |
| ASSERT(offset != 0); | |
| return offset; | |
| } | |
| intptr_t ImageWriter::SizeInSnapshotForBytes(intptr_t length) { | |
| // We are just going to write it out as a string. | |
| return compiler::target::String::InstanceSize( | |
| length * OneByteString::kBytesPerElement); | |
| } | |
| intptr_t ImageWriter::SizeInSnapshot(ObjectPtr raw_object) { | |
| const classid_t cid = raw_object->GetClassIdOfHeapObject(); | |
| switch (cid) { | |
| case kCompressedStackMapsCid: { | |
| auto raw_maps = CompressedStackMaps::RawCast(raw_object); | |
| return compiler::target::CompressedStackMaps::InstanceSize( | |
| CompressedStackMaps::PayloadSizeOf(raw_maps)); | |
| } | |
| case kCodeSourceMapCid: { | |
| auto raw_map = CodeSourceMap::RawCast(raw_object); | |
| return compiler::target::CodeSourceMap::InstanceSize( | |
| raw_map->untag()->length_); | |
| } | |
| case kPcDescriptorsCid: { | |
| auto raw_desc = PcDescriptors::RawCast(raw_object); | |
| return compiler::target::PcDescriptors::InstanceSize( | |
| raw_desc->untag()->length_); | |
| } | |
| case kInstructionsCid: { | |
| auto raw_insns = Instructions::RawCast(raw_object); | |
| return compiler::target::Instructions::InstanceSize( | |
| Instructions::Size(raw_insns)); | |
| } | |
| case kOneByteStringCid: { | |
| auto raw_str = String::RawCast(raw_object); | |
| return compiler::target::String::InstanceSize( | |
| String::LengthOf(raw_str) * OneByteString::kBytesPerElement); | |
| } | |
| case kTwoByteStringCid: { | |
| auto raw_str = String::RawCast(raw_object); | |
| return compiler::target::String::InstanceSize( | |
| String::LengthOf(raw_str) * TwoByteString::kBytesPerElement); | |
| } | |
| default: { | |
| const Class& clazz = Class::Handle(Object::Handle(raw_object).clazz()); | |
| FATAL("Unsupported class %s in rodata section.\n", clazz.ToCString()); | |
| return 0; | |
| } | |
| } | |
| } | |
| #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; | |
| } | |
| uint32_t ImageWriter::AddBytesToData(uint8_t* bytes, intptr_t length) { | |
| const intptr_t snap_size = SizeInSnapshotForBytes(length); | |
| const intptr_t offset = next_data_offset_; | |
| next_data_offset_ += snap_size; | |
| objects_.Add(ObjectData(bytes, length)); | |
| return offset; | |
| } | |
| intptr_t ImageWriter::GetTextObjectCount() const { | |
| return instructions_.length(); | |
| } | |
| void ImageWriter::GetTrampolineInfo(intptr_t* count, intptr_t* size) const { | |
| ASSERT(count != nullptr && size != nullptr); | |
| *count = 0; | |
| *size = 0; | |
| for (auto const& data : instructions_) { | |
| if (data.trampoline_length != 0) { | |
| *count += 1; | |
| *size += data.trampoline_length; | |
| } | |
| } | |
| } | |
| // Returns nullptr if there is no profile writer. | |
| const char* ImageWriter::ObjectTypeForProfile(const Object& object) const { | |
| if (profile_writer_ == nullptr) return nullptr; | |
| ASSERT(IsROSpace()); | |
| REUSABLE_CLASS_HANDLESCOPE(thread_); | |
| REUSABLE_STRING_HANDLESCOPE(thread_); | |
| Class& klass = thread_->ClassHandle(); | |
| String& name = thread_->StringHandle(); | |
| klass = object.clazz(); | |
| name = klass.UserVisibleName(); | |
| auto const name_str = name.ToCString(); | |
| return TagObjectTypeAsReadOnly(zone_, name_str); | |
| } | |
| const char* ImageWriter::TagObjectTypeAsReadOnly(Zone* zone, const char* type) { | |
| ASSERT(zone != nullptr && type != nullptr); | |
| return OS::SCreate(zone, "(RO) %s", type); | |
| } | |
| #if defined(DART_PRECOMPILER) | |
| void ImageWriter::DumpInstructionStats() { | |
| std::unique_ptr<CombinedCodeStatistics> instruction_stats( | |
| new CombinedCodeStatistics()); | |
| for (intptr_t i = 0; i < instructions_.length(); i++) { | |
| auto& data = instructions_[i]; | |
| CodeStatistics* stats = data.insns_->stats(); | |
| if (stats != nullptr) { | |
| stats->AppendTo(instruction_stats.get()); | |
| } | |
| } | |
| instruction_stats->DumpStatistics(); | |
| } | |
| void ImageWriter::DumpInstructionsSizes() { | |
| auto& cls = Class::Handle(zone_); | |
| auto& lib = Library::Handle(zone_); | |
| auto& owner = Object::Handle(zone_); | |
| auto& url = String::Handle(zone_); | |
| auto& name = String::Handle(zone_); | |
| intptr_t trampolines_total_size = 0; | |
| JSONWriter js; | |
| js.OpenArray(); | |
| for (intptr_t i = 0; i < instructions_.length(); i++) { | |
| auto& data = instructions_[i]; | |
| // We count alignment padding into trampolines for now. | |
| const bool is_trampoline = data.trampoline_length != 0; | |
| if (is_trampoline) { | |
| trampolines_total_size += data.trampoline_length; | |
| continue; | |
| } | |
| owner = WeakSerializationReference::Unwrap(data.code_->owner()); | |
| js.OpenObject(); | |
| if (owner.IsFunction() && | |
| (owner.ptr() != StubCode::UnknownDartCode().owner())) { | |
| cls = Function::Cast(owner).Owner(); | |
| name = cls.ScrubbedName(); | |
| lib = cls.library(); | |
| url = lib.url(); | |
| js.PrintPropertyStr("l", url); | |
| js.PrintPropertyStr("c", name); | |
| } else if (owner.IsClass()) { | |
| cls ^= owner.ptr(); | |
| name = cls.ScrubbedName(); | |
| lib = cls.library(); | |
| url = lib.url(); | |
| js.PrintPropertyStr("l", url); | |
| js.PrintPropertyStr("c", name); | |
| } | |
| js.PrintProperty("n", | |
| data.code_->QualifiedName( | |
| NameFormattingParams::DisambiguatedWithoutClassName( | |
| Object::kInternalName))); | |
| js.PrintProperty("s", SizeInSnapshot(data.insns_->ptr())); | |
| js.CloseObject(); | |
| } | |
| if (trampolines_total_size != 0) { | |
| js.OpenObject(); | |
| js.PrintProperty("n", "[Stub] Trampoline"); | |
| js.PrintProperty("s", trampolines_total_size); | |
| js.CloseObject(); | |
| } | |
| js.CloseArray(); | |
| auto file_open = Dart::file_open_callback(); | |
| auto file_write = Dart::file_write_callback(); | |
| auto file_close = Dart::file_close_callback(); | |
| if ((file_open == nullptr) || (file_write == nullptr) || | |
| (file_close == nullptr)) { | |
| OS::PrintErr("warning: Could not access file callbacks."); | |
| return; | |
| } | |
| const char* filename = FLAG_print_instructions_sizes_to; | |
| void* file = file_open(filename, /*write=*/true); | |
| if (file == nullptr) { | |
| OS::PrintErr("warning: Failed to write instruction sizes: %s\n", filename); | |
| return; | |
| } | |
| char* output = nullptr; | |
| intptr_t output_length = 0; | |
| js.Steal(&output, &output_length); | |
| file_write(output, output_length, file); | |
| free(output); | |
| file_close(file); | |
| } | |
| void ImageWriter::DumpStatistics() { | |
| if (FLAG_print_instruction_stats) { | |
| DumpInstructionStats(); | |
| } | |
| if (FLAG_print_instructions_sizes_to != nullptr) { | |
| DumpInstructionsSizes(); | |
| } | |
| } | |
| #endif | |
| void ImageWriter::Write(NonStreamingWriteStream* clustered_stream) { | |
| Heap* heap = thread_->heap(); | |
| TIMELINE_DURATION(thread_, Isolate, "WriteInstructions"); | |
| // Handlify collected raw pointers as building the names below | |
| // will allocate on the Dart heap. | |
| for (intptr_t i = 0; i < instructions_.length(); i++) { | |
| InstructionsData& data = instructions_[i]; | |
| if (data.trampoline_length != 0) continue; | |
| data.insns_ = &Instructions::Handle(zone_, data.raw_insns_); | |
| ASSERT(data.raw_code_ != nullptr); | |
| data.code_ = &Code::Handle(zone_, data.raw_code_); | |
| // Reset object id as an isolate snapshot after a VM snapshot will not use | |
| // the VM snapshot's text image. | |
| heap->SetObjectId(data.insns_->ptr(), 0); | |
| } | |
| for (auto& data : objects_) { | |
| 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 | |
| } | |
| } | |
| // Once we have everything handlified we are going to do convert raw bytes | |
| // 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()) { | |
| const auto bytes = data.bytes; | |
| data.obj = &Object::Handle( | |
| zone_, OneByteString::New(bytes.buf, bytes.length, Heap::kOld)); | |
| #if defined(SNAPSHOT_BACKTRACE) | |
| data.parent = &Object::null_object(); | |
| #endif | |
| data.set_is_object(true); | |
| String::Cast(*data.obj).Hash(); | |
| free(bytes.buf); | |
| } | |
| } | |
| WriteBss(); | |
| offset_space_ = IdSpace::kIsolateText; | |
| WriteText(); | |
| // Append the direct-mapped RO data objects after the clustered snapshot | |
| // and then for shared object and assembly outputs, add appropriate sections | |
| // with that combined data. | |
| offset_space_ = IdSpace::kIsolateData; | |
| WriteROData(clustered_stream); | |
| } | |
| void ImageWriter::WriteROData(NonStreamingWriteStream* stream) { | |
| ASSERT(Utils::IsAligned(stream->Position(), kRODataAlignment)); | |
| // Heap page starts here. | |
| intptr_t section_start = stream->Position(); | |
| stream->WriteWord(next_data_offset_); // Data length. | |
| stream->WriteWord(Image::kNoInstructionsSection); | |
| // Zero values for the rest of the Image object header bytes. | |
| stream->Align(Image::kHeaderSize); | |
| ASSERT_EQUAL(stream->Position() - section_start, Image::kHeaderSize); | |
| #if defined(DART_PRECOMPILER) | |
| if (profile_writer_ != nullptr) { | |
| // Attribute the Image header to the artificial root. | |
| profile_writer_->AttributeBytesTo( | |
| V8SnapshotProfileWriter::kArtificialRootId, Image::kHeaderSize); | |
| } | |
| #endif | |
| // Heap page objects start here. | |
| for (auto entry : objects_) { | |
| 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(); | |
| NoSafepointScope no_safepoint; | |
| // Write object header with the mark and read-only bits set. | |
| stream->WriteTargetWord(GetMarkedTags(obj)); | |
| if (obj.IsCompressedStackMaps()) { | |
| const CompressedStackMaps& map = CompressedStackMaps::Cast(obj); | |
| const intptr_t payload_size = map.payload_size(); | |
| stream->WriteFixed<uint32_t>( | |
| map.ptr()->untag()->payload()->flags_and_size()); | |
| stream->WriteBytes(map.ptr()->untag()->payload()->data(), payload_size); | |
| } else if (obj.IsCodeSourceMap()) { | |
| const CodeSourceMap& map = CodeSourceMap::Cast(obj); | |
| stream->WriteTargetWord(map.Length()); | |
| ASSERT_EQUAL(stream->Position() - object_start, | |
| compiler::target::CodeSourceMap::HeaderSize()); | |
| stream->WriteBytes(map.Data(), map.Length()); | |
| } else if (obj.IsPcDescriptors()) { | |
| const PcDescriptors& desc = PcDescriptors::Cast(obj); | |
| stream->WriteTargetWord(desc.Length()); | |
| ASSERT_EQUAL(stream->Position() - object_start, | |
| compiler::target::PcDescriptors::HeaderSize()); | |
| stream->WriteBytes(desc.ptr()->untag()->data(), desc.Length()); | |
| } else if (obj.IsString()) { | |
| const String& str = String::Cast(obj); | |
| RELEASE_ASSERT(String::GetCachedHash(str.ptr()) != 0); | |
| RELEASE_ASSERT(str.IsOneByteString() || str.IsTwoByteString()); | |
| #if !defined(HASH_IN_OBJECT_HEADER) | |
| stream->WriteTargetWord(static_cast<uword>(str.ptr()->untag()->hash())); | |
| #endif | |
| stream->WriteTargetWord(static_cast<uword>(str.ptr()->untag()->length())); | |
| ASSERT_EQUAL(stream->Position() - object_start, | |
| compiler::target::String::InstanceSize()); | |
| stream->WriteBytes( | |
| str.IsOneByteString() | |
| ? static_cast<const void*>(OneByteString::DataStart(str)) | |
| : static_cast<const void*>(TwoByteString::DataStart(str)), | |
| str.Length() * (str.IsOneByteString() | |
| ? OneByteString::kBytesPerElement | |
| : TwoByteString::kBytesPerElement)); | |
| } else { | |
| const Class& clazz = Class::Handle(obj.clazz()); | |
| FATAL("Unsupported class %s in rodata section.\n", clazz.ToCString()); | |
| } | |
| 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 | |
| } | |
| } | |
| static constexpr uword kReadOnlyGCBits = | |
| UntaggedObject::AlwaysSetBit::encode(true) | | |
| UntaggedObject::NotMarkedBit::encode(false) | | |
| UntaggedObject::OldAndNotRememberedBit::encode(true) | | |
| UntaggedObject::NewOrEvacuationCandidateBit::encode(false); | |
| uword ImageWriter::GetMarkedTags(classid_t cid, | |
| intptr_t size, | |
| bool is_canonical /* = false */, | |
| bool is_shallow_immutable /* = false */, | |
| bool is_deeply_immutable /* = false */) { | |
| // UntaggedObject::SizeTag expects a size divisible by kObjectAlignment and | |
| // checks this in debug mode, but the size on the target machine may not be | |
| // divisible by the host machine's object alignment if they differ. | |
| // | |
| // We define [adjusted_size] as [size] * m, where m is the host alignment | |
| // divided by the target alignment. This means [adjusted_size] encodes on the | |
| // host machine to the same bits that decode to [size] on the target machine. | |
| // That is, | |
| // [adjusted_size] / host align == | |
| // [size] * (host align / target align) / host align == | |
| // [size] / target align | |
| // | |
| // Since alignments are always powers of 2, we use shifts and logs. | |
| const intptr_t adjusted_size = | |
| size << (kObjectAlignmentLog2 - | |
| compiler::target::ObjectAlignment::kObjectAlignmentLog2); | |
| return kReadOnlyGCBits | UntaggedObject::ClassIdTag::encode(cid) | | |
| UntaggedObject::SizeTag::encode(adjusted_size) | | |
| UntaggedObject::CanonicalBit::encode(is_canonical) | | |
| UntaggedObject::ShallowImmutableBit::encode(is_shallow_immutable) | | |
| UntaggedObject::DeeplyImmutableBit::encode(is_deeply_immutable); | |
| } | |
| uword ImageWriter::GetMarkedTags(const Object& obj) { | |
| uword tags = GetMarkedTags(obj.ptr()->untag()->GetClassId(), | |
| SizeInSnapshot(obj), obj.IsCanonical(), | |
| obj.IsShallowImmutable(), obj.IsDeeplyImmutable()); | |
| #if defined(HASH_IN_OBJECT_HEADER) | |
| tags = UntaggedObject::HashTag::update(obj.ptr()->untag()->GetHeaderHash(), | |
| tags); | |
| #endif | |
| return tags; | |
| } | |
| const char* ImageWriter::SectionSymbol(ProgramSection section) { | |
| switch (section) { | |
| case ProgramSection::Text: | |
| return kSnapshotTextAsmSymbol; | |
| case ProgramSection::Data: | |
| return kSnapshotDataAsmSymbol; | |
| case ProgramSection::Bss: | |
| return kSnapshotBssAsmSymbol; | |
| case ProgramSection::BuildId: | |
| return kSnapshotBuildIdAsmSymbol; | |
| } | |
| UNREACHABLE(); | |
| return nullptr; | |
| } | |
| #if (defined(DART_TARGET_OS_MACOS) || defined(DART_TARGET_OS_MACOS_IOS)) && \ | |
| defined(TARGET_ARCH_ARM64) | |
| // When generating ARM64 Mach-O LLVM tends to generate Compact Unwind Info | |
| // (__unwind_info) rather than traditional DWARF unwinding information | |
| // (__eh_frame). | |
| // | |
| // Unfortunately when generating __unwind_info LLVM seems to only apply CFI | |
| // rules to the region between two non-local symbols that contains these CFI | |
| // directives. In other words given: | |
| // | |
| // Abc: | |
| // .cfi_startproc | |
| // .cfi_def_cfa x29, 16 | |
| // .cfi_offset x30, -8 | |
| // .cfi_offset x29, -16 | |
| // ;; ... | |
| // Xyz: | |
| // ;; ... | |
| // .cfi_endproc | |
| // | |
| // __unwind_info would specify proper unwinding information only for the region | |
| // between Abc and Xyz symbols. And the region Xyz onwards will have no | |
| // unwinding information. | |
| // | |
| // There also seems to be a difference in how unwinding information is | |
| // canonicalized and compressed: when building __unwind_info from CFI directives | |
| // LLVM will fold together similar entries, the same does not happen for | |
| // __eh_frame. This means that emitting CFI directives for each function would | |
| // balloon the size of __eh_frame. | |
| // | |
| // Hence to work around the problem of incorrect __unwind_info without | |
| // ballooning snapshot size when __eh_frame is generated we choose to emit CFI | |
| // directives per function specifically on ARM64 Mac OS X and iOS. | |
| // | |
| // See also |useCompactUnwind| method in LLVM (https://github.com/llvm/llvm-project/blob/b27430f9f46b88bcd54d992debc8d72e131e1bd0/llvm/lib/MC/MCObjectFileInfo.cpp#L28-L50) | |
| #define EMIT_UNWIND_DIRECTIVES_PER_FUNCTION 1 | |
| #endif | |
| void ImageWriter::WriteText() { | |
| const bool bare_instruction_payloads = FLAG_precompiled_mode; | |
| // Start snapshot at page boundary. | |
| intptr_t alignment_padding = 0; | |
| if (!EnterSection(ProgramSection::Text, ImageWriter::kTextAlignment, | |
| &alignment_padding)) { | |
| return; | |
| } | |
| intptr_t text_offset = 0; | |
| #if defined(DART_PRECOMPILER) | |
| // Parent used for later profile objects. Starts off as the Image. When | |
| // writing bare instructions payloads, this is later updated with the | |
| // InstructionsSection object which contains all the bare payloads. | |
| V8SnapshotProfileWriter::ObjectId parent_id(offset_space_, text_offset); | |
| #endif | |
| // This head also provides the gap to make the instructions snapshot | |
| // look like a Page. | |
| const intptr_t image_size = Utils::RoundUp( | |
| next_text_offset_, compiler::target::ObjectAlignment::kObjectAlignment); | |
| text_offset += WriteTargetWord(image_size); | |
| // Output the offset to the InstructionsSection object from the start of the | |
| // image, if any. | |
| text_offset += | |
| WriteTargetWord(FLAG_precompiled_mode ? Image::kHeaderSize | |
| : Image::kNoInstructionsSection); | |
| // Zero values for the rest of the Image object header bytes. | |
| text_offset += Align(Image::kHeaderSize, 0, text_offset); | |
| ASSERT_EQUAL(text_offset, Image::kHeaderSize); | |
| #if defined(DART_PRECOMPILER) | |
| const char* instructions_symbol = SectionSymbol(ProgramSection::Text); | |
| ASSERT(instructions_symbol != nullptr); | |
| intptr_t instructions_label = SectionLabel(ProgramSection::Text); | |
| ASSERT(instructions_label > 0); | |
| const char* bss_symbol = SectionSymbol(ProgramSection::Bss); | |
| ASSERT(bss_symbol != nullptr); | |
| intptr_t bss_label = SectionLabel(ProgramSection::Bss); | |
| ASSERT(bss_label > 0); | |
| if (profile_writer_ != nullptr) { | |
| profile_writer_->SetObjectTypeAndName(parent_id, image_type_, | |
| instructions_symbol); | |
| profile_writer_->AttributeBytesTo( | |
| parent_id, ImageWriter::kTextAlignment + alignment_padding); | |
| profile_writer_->AddRoot(parent_id); | |
| } | |
| if (FLAG_precompiled_mode) { | |
| const intptr_t section_header_length = | |
| compiler::target::InstructionsSection::HeaderSize(); | |
| // Calculated using next_text_offset_, which doesn't include post-payload | |
| // padding to object alignment. Note that if not in bare instructions mode, | |
| // the section has no contents, instead the instructions objects follow it. | |
| const intptr_t section_payload_length = | |
| bare_instruction_payloads | |
| ? next_text_offset_ - text_offset - section_header_length | |
| : 0; | |
| const intptr_t section_size = | |
| compiler::target::InstructionsSection::InstanceSize( | |
| section_payload_length); | |
| const V8SnapshotProfileWriter::ObjectId id(offset_space_, text_offset); | |
| if (profile_writer_ != nullptr) { | |
| profile_writer_->SetObjectTypeAndName(id, instructions_section_type_, | |
| instructions_symbol); | |
| profile_writer_->AttributeBytesTo(id, | |
| section_size - section_payload_length); | |
| const intptr_t element_offset = id.nonce() - parent_id.nonce(); | |
| profile_writer_->AttributeReferenceTo( | |
| parent_id, | |
| V8SnapshotProfileWriter::Reference::Element(element_offset), id); | |
| // Later objects will have the InstructionsSection as a parent if in | |
| // bare instructions mode, otherwise the image. | |
| if (bare_instruction_payloads) { | |
| parent_id = id; | |
| } | |
| } | |
| // Add the RawInstructionsSection header. | |
| text_offset += | |
| WriteTargetWord(GetMarkedTags(kInstructionsSectionCid, section_size)); | |
| // An InstructionsSection has five fields: | |
| // 1) The length of the payload. | |
| text_offset += WriteTargetWord(section_payload_length); | |
| // 2) The BSS offset from this section. | |
| text_offset += Relocation(text_offset, instructions_label, bss_label); | |
| // 3) The relocated address of the instructions. | |
| text_offset += RelocatedAddress(text_offset, instructions_label); | |
| // 4) The GNU build ID note offset from this section. | |
| text_offset += Relocation(text_offset, instructions_label, | |
| SectionLabel(ProgramSection::BuildId)); | |
| const intptr_t section_contents_alignment = | |
| bare_instruction_payloads | |
| ? InstructionsSection::kPayloadAlignment | |
| : compiler::target::ObjectAlignment::kObjectAlignment; | |
| const intptr_t alignment_offset = | |
| compiler::target::ObjectAlignment::kOldObjectAlignmentOffset; | |
| const intptr_t expected_size = | |
| bare_instruction_payloads | |
| ? compiler::target::InstructionsSection::HeaderSize() | |
| : compiler::target::InstructionsSection::InstanceSize(0); | |
| text_offset += | |
| Align(section_contents_alignment, alignment_offset, text_offset); | |
| ASSERT_EQUAL(text_offset - id.nonce(), expected_size); | |
| } | |
| #endif | |
| #if !defined(EMIT_UNWIND_DIRECTIVES_PER_FUNCTION) | |
| FrameUnwindPrologue(); | |
| #endif | |
| // We don't expect more than 64 bytes of padding. | |
| uint8_t padding_bytes[64]; | |
| memset(&padding_bytes[0], 0, sizeof(padding_bytes)); | |
| ASSERT(offset_space_ != IdSpace::kSnapshot); | |
| for (intptr_t i = 0; i < instructions_.length(); i++) { | |
| auto& data = instructions_[i]; | |
| const bool is_trampoline = data.trampoline_bytes != nullptr; | |
| const bool is_padding = | |
| data.trampoline_bytes == nullptr && data.trampoline_length != 0; | |
| ASSERT_EQUAL(data.text_offset_, text_offset); | |
| #if defined(DART_PRECOMPILER) | |
| const char* object_name = namer_.SnapshotNameFor(data); | |
| if (profile_writer_ != nullptr) { | |
| const V8SnapshotProfileWriter::ObjectId id(offset_space_, text_offset); | |
| auto const type = is_trampoline ? trampoline_type_ | |
| : is_padding ? padding_type_ | |
| : instructions_type_; | |
| const intptr_t size = (is_trampoline || is_padding) | |
| ? data.trampoline_length | |
| : SizeInSnapshot(data.insns_->ptr()); | |
| profile_writer_->SetObjectTypeAndName(id, type, object_name); | |
| profile_writer_->AttributeBytesTo(id, size); | |
| const intptr_t element_offset = id.nonce() - parent_id.nonce(); | |
| profile_writer_->AttributeReferenceTo( | |
| parent_id, | |
| V8SnapshotProfileWriter::Reference::Element(element_offset), id); | |
| } | |
| #endif | |
| if (is_trampoline) { | |
| text_offset += WriteBytes(data.trampoline_bytes, data.trampoline_length); | |
| delete[] data.trampoline_bytes; | |
| data.trampoline_bytes = nullptr; | |
| continue; | |
| } | |
| if (is_padding) { | |
| text_offset += WriteBytes(padding_bytes, data.trampoline_length); | |
| continue; | |
| } | |
| const intptr_t instr_start = text_offset; | |
| const auto& insns = *data.insns_; | |
| // 1. Write from the object start to the payload start. This includes the | |
| // object header and the fixed fields. Not written for AOT snapshots using | |
| // bare instructions. | |
| if (!bare_instruction_payloads) { | |
| NoSafepointScope no_safepoint; | |
| // Write Instructions with the mark and read-only bits set. | |
| text_offset += WriteTargetWord(GetMarkedTags(insns)); | |
| text_offset += WriteFixed(insns.untag()->size_and_flags_); | |
| text_offset += | |
| Align(compiler::target::Instructions::kNonBarePayloadAlignment, | |
| compiler::target::ObjectAlignment::kOldObjectAlignmentOffset, | |
| text_offset); | |
| } | |
| ASSERT_EQUAL(text_offset - instr_start, | |
| compiler::target::Instructions::HeaderSize()); | |
| #if defined(DART_PRECOMPILER) | |
| const auto& code = *data.code_; | |
| // 2. Add a symbol for the code at the entry point in precompiled snapshots. | |
| // Linux's perf uses these labels. | |
| AddCodeSymbol(code, object_name, text_offset); | |
| #endif | |
| #if defined(EMIT_UNWIND_DIRECTIVES_PER_FUNCTION) | |
| FrameUnwindPrologue(); | |
| #endif | |
| { | |
| NoSafepointScope no_safepoint; | |
| // 3. Write from the payload start to payload end. For AOT snapshots | |
| // with bare instructions, this is the only part serialized other than | |
| // any padding needed for alignment. | |
| auto const payload_start = | |
| reinterpret_cast<const uint8_t*>(insns.PayloadStart()); | |
| // Double-check the payload alignment, since we will load and write | |
| // target-sized words starting from that address. | |
| ASSERT(Utils::IsAligned(payload_start, compiler::target::kWordSize)); | |
| const uword payload_size = insns.Size(); | |
| text_offset += WriteBytes(payload_start, payload_size); | |
| } | |
| // 4. Add appropriate padding. Note we can't simply copy from the object | |
| // because the host object may have less alignment filler than the target | |
| // object in the cross-word case. | |
| const intptr_t alignment = | |
| bare_instruction_payloads | |
| ? compiler::target::Instructions::kBarePayloadAlignment | |
| : compiler::target::ObjectAlignment::kObjectAlignment; | |
| text_offset += AlignWithBreakInstructions(alignment, text_offset); | |
| ASSERT_EQUAL(text_offset - instr_start, SizeInSnapshot(insns.ptr())); | |
| #if defined(EMIT_UNWIND_DIRECTIVES_PER_FUNCTION) | |
| FrameUnwindEpilogue(); | |
| #endif | |
| } | |
| // Should be a no-op unless writing bare instruction payloads, in which case | |
| // we need to add post-payload padding for the InstructionsSection object. | |
| // Since this follows instructions, we'll use break instructions for padding. | |
| ASSERT(bare_instruction_payloads || | |
| Utils::IsAligned(text_offset, | |
| compiler::target::ObjectAlignment::kObjectAlignment)); | |
| text_offset += AlignWithBreakInstructions( | |
| compiler::target::ObjectAlignment::kObjectAlignment, text_offset); | |
| ASSERT_EQUAL(text_offset, image_size); | |
| #if !defined(EMIT_UNWIND_DIRECTIVES_PER_FUNCTION) | |
| FrameUnwindEpilogue(); | |
| #endif | |
| ExitSection(ProgramSection::Text, text_offset); | |
| } | |
| intptr_t ImageWriter::AlignWithBreakInstructions(intptr_t alignment, | |
| intptr_t offset) { | |
| intptr_t bytes_written = 0; | |
| uword remaining; | |
| for (remaining = Utils::RoundUp(offset, alignment) - offset; | |
| remaining >= compiler::target::kWordSize; | |
| remaining -= compiler::target::kWordSize) { | |
| bytes_written += WriteTargetWord(kBreakInstructionFiller); | |
| } | |
| // clang-format off | |
| #if defined(TARGET_ARCH_ARM) | |
| // All instructions are 4 bytes long on ARM architectures, so on 32-bit ARM | |
| // there won't be any padding. | |
| ASSERT_EQUAL(remaining, 0); | |
| #elif defined(TARGET_ARCH_ARM64) | |
| // All instructions are 4 bytes long on ARM architectures, so on 64-bit ARM | |
| // there is only 0 or 4 bytes of padding. | |
| if (remaining != 0) { | |
| ASSERT_EQUAL(remaining, 4); | |
| bytes_written += WriteBytes(&kBreakInstructionFiller, remaining); | |
| } | |
| #elif defined(TARGET_ARCH_X64) || defined(TARGET_ARCH_IA32) || \ | |
| defined(TARGET_ARCH_RISCV32) || defined(TARGET_ARCH_RISCV64) | |
| // The break instruction is a single byte, repeated to fill a word. | |
| bytes_written += WriteBytes(&kBreakInstructionFiller, remaining); | |
| #else | |
| #error Unexpected architecture. | |
| #endif | |
| // clang-format on | |
| ASSERT_EQUAL(bytes_written, Utils::RoundUp(offset, alignment) - offset); | |
| return bytes_written; | |
| } | |
| #if defined(DART_PRECOMPILER) | |
| // Indices are log2(size in bytes). | |
| static constexpr const char* kSizeDirectives[] = {".byte", ".2byte", ".long", | |
| ".quad"}; | |
| static constexpr const char* kWordDirective = | |
| kSizeDirectives[compiler::target::kWordSizeLog2]; | |
| class DwarfAssemblyStream : public DwarfWriteStream { | |
| public: | |
| explicit DwarfAssemblyStream(Zone* zone, | |
| BaseWriteStream* stream, | |
| const IntMap<const char*>& label_to_name) | |
| : zone_(ASSERT_NOTNULL(zone)), | |
| stream_(ASSERT_NOTNULL(stream)), | |
| label_to_name_(label_to_name) {} | |
| void sleb128(intptr_t value) override { | |
| stream_->Printf(".sleb128 %" Pd "\n", value); | |
| } | |
| void uleb128(uintptr_t value) override { | |
| stream_->Printf(".uleb128 %" Pd "\n", value); | |
| } | |
| void u1(uint8_t value) override { | |
| stream_->Printf("%s %u\n", kSizeDirectives[kInt8SizeLog2], value); | |
| } | |
| void u2(uint16_t value) override { | |
| stream_->Printf("%s %u\n", kSizeDirectives[kInt16SizeLog2], value); | |
| } | |
| void u4(uint32_t value) override { | |
| stream_->Printf("%s %" Pu32 "\n", kSizeDirectives[kInt32SizeLog2], value); | |
| } | |
| void u8(uint64_t value) override { | |
| stream_->Printf("%s %" Pu64 "\n", kSizeDirectives[kInt64SizeLog2], value); | |
| } | |
| void string(const char* cstr) override { // NOLINT | |
| stream_->WriteString(".string \""); // NOLINT | |
| while (char c = *cstr++) { | |
| if (c == '"') { | |
| stream_->WriteString("\\\""); | |
| } else if (c == '\\') { | |
| stream_->WriteString("\\\\"); | |
| } else if (c == '\n') { | |
| stream_->WriteString("\\n"); | |
| } else if (c == '\r') { | |
| stream_->WriteString("\\r"); | |
| } else { | |
| stream_->WriteByte(c); | |
| } | |
| } | |
| stream_->WriteString("\"\n"); | |
| } | |
| void WritePrefixedLength(const char* prefix, | |
| std::function<void()> body) override { | |
| ASSERT(prefix != nullptr); | |
| const char* const length_prefix_symbol = | |
| OS::SCreate(zone_, ".L%s_length_prefix", prefix); | |
| // Assignment to temp works around buggy Mac assembler. | |
| stream_->Printf("L%s_size = .L%s_end - .L%s_start\n", prefix, prefix, | |
| prefix); | |
| // We assume DWARF v2 currently, so all sizes are 32-bit. | |
| stream_->Printf("%s: %s L%s_size\n", length_prefix_symbol, | |
| kSizeDirectives[kInt32SizeLog2], prefix); | |
| // All sizes for DWARF sections measure the size of the section data _after_ | |
| // the size value. | |
| stream_->Printf(".L%s_start:\n", prefix); | |
| body(); | |
| stream_->Printf(".L%s_end:\n", prefix); | |
| } | |
| void OffsetFromSymbol(intptr_t label, | |
| intptr_t offset, | |
| size_t size = kAddressSize) override { | |
| const char* symbol = label_to_name_.Lookup(label); | |
| ASSERT(symbol != nullptr); | |
| if (offset == 0) { | |
| PrintNamedAddress(symbol, size); | |
| } else { | |
| PrintNamedAddressWithOffset(symbol, offset, size); | |
| } | |
| } | |
| // No-op, we'll be using labels. | |
| void InitializeAbstractOrigins(intptr_t size) override {} | |
| void RegisterAbstractOrigin(intptr_t index) override { | |
| // Label for DW_AT_abstract_origin references | |
| stream_->Printf("Lfunc%" Pd " = .-%s\n", index, kDebugInfoLabel); | |
| } | |
| void AbstractOrigin(intptr_t index) override { | |
| stream_->Printf("%s Lfunc%" Pd "\n", kSizeDirectives[kInt32SizeLog2], | |
| index); | |
| } | |
| // Methods for writing the assembly prologues for various DWARF sections. | |
| void AbbreviationsPrologue() { | |
| #if defined(DART_TARGET_OS_MACOS) || defined(DART_TARGET_OS_MACOS_IOS) | |
| stream_->WriteString(".section __DWARF,__debug_abbrev,regular,debug\n"); | |
| #elif defined(DART_TARGET_OS_LINUX) || defined(DART_TARGET_OS_ANDROID) || \ | |
| defined(DART_TARGET_OS_FUCHSIA) | |
| stream_->WriteString(".section .debug_abbrev,\"\"\n"); | |
| #else | |
| UNIMPLEMENTED(); | |
| #endif | |
| } | |
| void DebugInfoPrologue() { | |
| #if defined(DART_TARGET_OS_MACOS) || defined(DART_TARGET_OS_MACOS_IOS) | |
| stream_->WriteString(".section __DWARF,__debug_info,regular,debug\n"); | |
| #elif defined(DART_TARGET_OS_LINUX) || defined(DART_TARGET_OS_ANDROID) || \ | |
| defined(DART_TARGET_OS_FUCHSIA) | |
| stream_->WriteString(".section .debug_info,\"\"\n"); | |
| #else | |
| UNIMPLEMENTED(); | |
| #endif | |
| // Used to calculate abstract origin values. | |
| stream_->Printf("%s:\n", kDebugInfoLabel); | |
| } | |
| void LineNumberProgramPrologue() { | |
| #if defined(DART_TARGET_OS_MACOS) || defined(DART_TARGET_OS_MACOS_IOS) | |
| stream_->WriteString(".section __DWARF,__debug_line,regular,debug\n"); | |
| #elif defined(DART_TARGET_OS_LINUX) || defined(DART_TARGET_OS_ANDROID) || \ | |
| defined(DART_TARGET_OS_FUCHSIA) | |
| stream_->WriteString(".section .debug_line,\"\"\n"); | |
| #else | |
| UNIMPLEMENTED(); | |
| #endif | |
| } | |
| private: | |
| static constexpr const char* kDebugInfoLabel = ".Ldebug_info"; | |
| void PrintNamedAddress(const char* name, size_t size) { | |
| auto* const directive = kSizeDirectives[Utils::ShiftForPowerOfTwo(size)]; | |
| stream_->Printf("%s \"%s\"\n", directive, name); | |
| } | |
| void PrintNamedAddressWithOffset(const char* name, | |
| intptr_t offset, | |
| size_t size) { | |
| auto* const directive = kSizeDirectives[Utils::ShiftForPowerOfTwo(size)]; | |
| stream_->Printf("%s \"%s\" + %" Pd "\n", directive, name, offset); | |
| } | |
| Zone* const zone_; | |
| BaseWriteStream* const stream_; | |
| const IntMap<const char*>& label_to_name_; | |
| intptr_t temp_ = 0; | |
| DISALLOW_COPY_AND_ASSIGN(DwarfAssemblyStream); | |
| }; | |
| static inline Dwarf* AddDwarfIfUnstripped( | |
| Zone* zone, | |
| bool strip, | |
| SharedObjectWriter* writer, | |
| const Trie<const char>* deobfuscation_trie) { | |
| #if defined(DART_TARGET_OS_WINDOWS) | |
| // PE uses PDB instead of DWARF. | |
| return nullptr; | |
| #else | |
| if (!strip) { | |
| if (writer != nullptr) { | |
| // Reuse the existing DWARF object. | |
| ASSERT(writer->dwarf() != nullptr); | |
| return writer->dwarf(); | |
| } | |
| return new (zone) Dwarf(zone, deobfuscation_trie); | |
| } | |
| return nullptr; | |
| #endif | |
| } | |
| AssemblyImageWriter::AssemblyImageWriter( | |
| Thread* thread, | |
| BaseWriteStream* stream, | |
| const Trie<const char>* deobfuscation_trie, | |
| bool strip, | |
| SharedObjectWriter* debug_so) | |
| : ImageWriter(thread, | |
| /*generates_assembly=*/true, | |
| /*needs_unique_names=*/true, | |
| deobfuscation_trie), | |
| assembly_stream_(stream), | |
| assembly_dwarf_( | |
| AddDwarfIfUnstripped(zone_, strip, debug_so, deobfuscation_trie)), | |
| debug_so_(debug_so), | |
| label_to_symbol_name_(zone_) { | |
| // Set up the label mappings for the section symbols for use in relocations. | |
| for (intptr_t i = 0; i < kNumProgramSections; i++) { | |
| auto const section = static_cast<ProgramSection>(i); | |
| auto const isolate_name = SectionSymbol(section); | |
| auto const isolate_label = SectionLabel(section); | |
| label_to_symbol_name_.Insert(isolate_label, isolate_name); | |
| } | |
| } | |
| void AssemblyImageWriter::Finalize() { | |
| if (assembly_dwarf_ != nullptr) { | |
| DwarfAssemblyStream dwarf_stream(zone_, assembly_stream_, | |
| label_to_symbol_name_); | |
| dwarf_stream.AbbreviationsPrologue(); | |
| assembly_dwarf_->WriteAbbreviations(&dwarf_stream); | |
| dwarf_stream.DebugInfoPrologue(); | |
| assembly_dwarf_->WriteDebugInfo(&dwarf_stream); | |
| dwarf_stream.LineNumberProgramPrologue(); | |
| assembly_dwarf_->WriteLineNumberProgram(&dwarf_stream); | |
| } | |
| if (debug_so_ != nullptr) { | |
| TIMELINE_DURATION(thread_, Isolate, "FinalizeDebugInfo"); | |
| debug_so_->Finalize(); | |
| } | |
| #if defined(DART_TARGET_OS_LINUX) || defined(DART_TARGET_OS_ANDROID) || \ | |
| defined(DART_TARGET_OS_FUCHSIA) | |
| // Non-executable stack. | |
| #if defined(TARGET_ARCH_ARM) | |
| assembly_stream_->WriteString(".section .note.GNU-stack,\"\",%progbits\n"); | |
| #else | |
| assembly_stream_->WriteString(".section .note.GNU-stack,\"\",@progbits\n"); | |
| #endif | |
| // CFI support. | |
| #if defined(TARGET_ARCH_X64) | |
| assembly_stream_->WriteString(".section .note.gnu.property, \"a\"\n"); | |
| assembly_stream_->WriteString(".align 8\n"); | |
| assembly_stream_->WriteString(".long 4\n"); // name size | |
| assembly_stream_->WriteString(".long 16\n"); // desc size | |
| assembly_stream_->Printf( | |
| ".long %d\n", static_cast<int>(elf::NoteType::NT_GNU_PROPERTY_TYPE_0)); | |
| assembly_stream_->WriteString(".string \"GNU\"\n"); // name | |
| assembly_stream_->Printf(".long %d\n", GNU_PROPERTY_X86_FEATURE_1_AND); | |
| assembly_stream_->WriteString(".long 4\n"); | |
| if (FLAG_support_cfi) { | |
| assembly_stream_->Printf(".long %d\n", GNU_PROPERTY_X86_FEATURE_1_IBT); | |
| } else { | |
| assembly_stream_->Printf(".long 0\n"); | |
| } | |
| assembly_stream_->WriteString(".long 0\n"); | |
| #elif defined(TARGET_ARCH_ARM64) | |
| assembly_stream_->WriteString(".section .note.gnu.property, \"a\"\n"); | |
| assembly_stream_->WriteString(".align 3\n"); | |
| assembly_stream_->WriteString(".word 4\n"); // name size | |
| assembly_stream_->WriteString(".word 16\n"); // desc size | |
| assembly_stream_->Printf( | |
| ".long %d\n", static_cast<int>(elf::NoteType::NT_GNU_PROPERTY_TYPE_0)); | |
| assembly_stream_->WriteString(".string \"GNU\"\n"); // name | |
| assembly_stream_->Printf(".word %d\n", GNU_PROPERTY_AARCH64_FEATURE_1_AND); | |
| assembly_stream_->WriteString(".word 4\n"); | |
| if (FLAG_support_cfi) { | |
| assembly_stream_->Printf(".word %d\n", GNU_PROPERTY_AARCH64_FEATURE_1_BTI); | |
| } else { | |
| assembly_stream_->Printf(".word 0\n"); | |
| } | |
| assembly_stream_->WriteString(".word 0\n"); | |
| #endif | |
| #endif | |
| #if defined(DART_TARGET_OS_MACOS) && defined(TARGET_ARCH_ARM64E) | |
| // XCode clang generates this but Fuchsia clang does not. | |
| // assembly_stream_->WriteString(".ptrauth_abi_version 0\n"); | |
| #endif | |
| #if defined(DART_TARGET_OS_WINDOWS) | |
| // __declspec(dllexport) | |
| const char* const exported_symbols[] = { | |
| kSnapshotDataCSymbol, | |
| kSnapshotTextCSymbol, | |
| }; | |
| assembly_stream_->WriteString(".section .drectve,\"yni\"\n"); | |
| assembly_stream_->WriteString(".ascii \""); | |
| for (const char* exported_symbol : exported_symbols) { | |
| assembly_stream_->WriteString(" /EXPORT:"); | |
| assembly_stream_->WriteString(exported_symbol); | |
| } | |
| assembly_stream_->WriteString("\"\n"); | |
| #endif | |
| } | |
| void ImageWriter::SnapshotTextObjectNamer::AddNonUniqueNameFor( | |
| BaseTextBuffer* buffer, | |
| const Object& object) { | |
| if (object.IsCode()) { | |
| const Code& code = Code::Cast(object); | |
| if (code.IsStubCode()) { | |
| buffer->AddString("stub "); | |
| insns_ = code.instructions(); | |
| const char* name = StubCode::NameOfStub(insns_.EntryPoint()); | |
| ASSERT(name != nullptr); | |
| buffer->AddString(name); | |
| } else { | |
| if (code.IsAllocationStubCode()) { | |
| buffer->AddString("new "); | |
| } else if (code.IsTypeTestStubCode()) { | |
| buffer->AddString("assert type is "); | |
| } else { | |
| ASSERT(code.IsFunctionCode()); | |
| } | |
| owner_ = code.owner(); | |
| AddNonUniqueNameFor(buffer, owner_); | |
| } | |
| } else if (object.IsClass()) { | |
| const char* name = Class::Cast(object).UserVisibleNameCString(); | |
| const char* deobfuscated_name = | |
| ImageWriter::Deobfuscate(zone_, deobfuscation_trie_, name); | |
| buffer->AddString(deobfuscated_name); | |
| } else if (object.IsAbstractType()) { | |
| const AbstractType& type = AbstractType::Cast(object); | |
| if (deobfuscation_trie_ == nullptr) { | |
| // Print directly to the output buffer. | |
| type.PrintName(Object::kUserVisibleName, buffer); | |
| } else { | |
| // Use an intermediate buffer for deobfuscation purposes. | |
| ZoneTextBuffer temp_buffer(zone_); | |
| type.PrintName(Object::kUserVisibleName, &temp_buffer); | |
| const char* deobfuscated_name = ImageWriter::Deobfuscate( | |
| zone_, deobfuscation_trie_, temp_buffer.buffer()); | |
| buffer->AddString(deobfuscated_name); | |
| } | |
| } else if (object.IsFunction()) { | |
| const Function& func = Function::Cast(object); | |
| NameFormattingParams params( | |
| {Object::kUserVisibleName, Object::NameDisambiguation::kNo}); | |
| if (deobfuscation_trie_ == nullptr) { | |
| // Print directly to the output buffer. | |
| func.PrintName(params, buffer); | |
| } else { | |
| // Use an intermediate buffer for deobfuscation purposes. | |
| ZoneTextBuffer temp_buffer(zone_); | |
| func.PrintName(params, &temp_buffer); | |
| const char* deobfuscated_name = ImageWriter::Deobfuscate( | |
| zone_, deobfuscation_trie_, temp_buffer.buffer()); | |
| buffer->AddString(deobfuscated_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(); | |
| } | |
| } | |
| void ImageWriter::SnapshotTextObjectNamer::ModifyForAssembly( | |
| BaseTextBuffer* buffer) { | |
| if (buffer->buffer()[0] == 'L') { | |
| // Assembler treats labels starting with `L` as local which can cause | |
| // some issues down the line e.g. on Mac the linker might fail to encode | |
| // compact unwind information because multiple functions end up being | |
| // treated as a single function. See https://github.com/flutter/flutter/issues/102281. | |
| // | |
| // Avoid this by prepending an underscore. | |
| auto* const result = OS::SCreate(zone_, "_%s", buffer->buffer()); | |
| buffer->Clear(); | |
| buffer->AddString(result); | |
| } | |
| } | |
| void ImageWriter::SnapshotTextObjectNamer::EnsureUniqueName( | |
| BaseTextBuffer* buffer) { | |
| auto* const pair = usage_count_.Lookup(buffer->buffer()); | |
| if (pair == nullptr) { | |
| usage_count_.Insert({buffer->buffer(), 1}); | |
| } else { | |
| buffer->Printf(" (#%" Pd ")", ++pair->value); | |
| } | |
| } | |
| const char* ImageWriter::SnapshotTextObjectNamer::SnapshotNameFor( | |
| const InstructionsData& data) { | |
| ZoneTextBuffer printer(zone_); | |
| if (data.trampoline_bytes != nullptr) { | |
| printer.AddString("Trampoline"); | |
| } else if (data.trampoline_length != 0) { | |
| printer.AddString("Padding"); | |
| } else { | |
| AddNonUniqueNameFor(&printer, *data.code_); | |
| } | |
| if (for_assembly_) { | |
| ModifyForAssembly(&printer); | |
| } | |
| if (create_unique_names_) { | |
| EnsureUniqueName(&printer); | |
| } | |
| return printer.buffer(); | |
| } | |
| 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); | |
| printer.AddString(")"); | |
| } | |
| } | |
| #endif | |
| } else { | |
| printer.AddString("RawBytes"); | |
| } | |
| if (for_assembly_) { | |
| ModifyForAssembly(&printer); | |
| } | |
| if (create_unique_names_) { | |
| EnsureUniqueName(&printer); | |
| } | |
| return printer.buffer(); | |
| } | |
| Trie<const char>* ImageWriter::CreateReverseObfuscationTrie(Thread* thread) { | |
| auto* const zone = thread->zone(); | |
| auto* const map_array = thread->isolate_group()->obfuscation_map(); | |
| if (map_array == nullptr) return nullptr; | |
| Trie<const char>* trie = nullptr; | |
| for (intptr_t i = 0; map_array[i] != nullptr; i += 2) { | |
| auto const key = map_array[i]; | |
| auto const value = map_array[i + 1]; | |
| ASSERT(value != nullptr); | |
| // Don't include identity mappings. | |
| if (strcmp(key, value) == 0) continue; | |
| // Otherwise, any value in the obfuscation map should be a valid key. | |
| ASSERT(Trie<const char>::IsValidKey(value)); | |
| trie = Trie<const char>::AddString(zone, trie, value, key); | |
| } | |
| return trie; | |
| } | |
| const char* ImageWriter::Deobfuscate(Zone* zone, | |
| const Trie<const char>* trie, | |
| const char* cstr) { | |
| if (trie == nullptr) return cstr; | |
| TextBuffer buffer(256); | |
| // Used to avoid Zone-allocating strings if no deobfuscation was performed. | |
| bool changed = false; | |
| intptr_t i = 0; | |
| while (cstr[i] != '\0') { | |
| intptr_t offset; | |
| auto const value = trie->Lookup(cstr + i, &offset); | |
| if (offset == 0) { | |
| // The first character was an invalid key element (that isn't the null | |
| // terminator due to the while condition), copy it and skip to the next. | |
| buffer.AddChar(cstr[i++]); | |
| } else if (value != nullptr) { | |
| changed = true; | |
| buffer.AddString(value); | |
| } else { | |
| buffer.AddRaw(reinterpret_cast<const uint8_t*>(cstr + i), offset); | |
| } | |
| i += offset; | |
| } | |
| if (!changed) return cstr; | |
| return OS::SCreate(zone, "%s", buffer.buffer()); | |
| } | |
| void AssemblyImageWriter::WriteBss() { | |
| EnterSection(ProgramSection::Bss, ImageWriter::kBssAlignment); | |
| auto const entry_count = BSS::kIsolateGroupEntryCount; | |
| for (intptr_t i = 0; i < entry_count; i++) { | |
| // All bytes in the .bss section must be zero. | |
| WriteTargetWord(0); | |
| } | |
| ExitSection(ProgramSection::Bss, entry_count * compiler::target::kWordSize); | |
| } | |
| void AssemblyImageWriter::WriteROData( | |
| NonStreamingWriteStream* clustered_stream) { | |
| if (!EnterSection(ProgramSection::Data, ImageWriter::kRODataAlignment)) { | |
| return; | |
| } | |
| // 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); | |
| // 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); | |
| #if defined(DART_TARGET_OS_LINUX) || defined(DART_TARGET_OS_ANDROID) || \ | |
| defined(DART_TARGET_OS_FUCHSIA) | |
| // Output size and type of the read-only data symbol to the assembly stream. | |
| assembly_stream_->Printf(".size \"%s\", %zu\n", symbol.name, symbol.size); | |
| assembly_stream_->Printf(".type \"%s\", %%object\n", symbol.name); | |
| #elif defined(DART_TARGET_OS_MACOS) || defined(DART_TARGET_OS_MACOS_IOS) | |
| // MachO symbol tables don't include the size of the symbol, so don't bother | |
| // printing it to the assembly output. | |
| #elif defined(DART_TARGET_OS_WINDOWS) | |
| // Windows also doesn't have a .size directive. | |
| #else | |
| UNIMPLEMENTED(); | |
| #endif | |
| last_position = symbol.offset; | |
| } | |
| WriteBytes(bytes + last_position, len - last_position); | |
| ExitSection(ProgramSection::Data, len); | |
| } | |
| bool AssemblyImageWriter::EnterSection(ProgramSection section, | |
| intptr_t alignment, | |
| intptr_t* alignment_padding) { | |
| ASSERT(FLAG_precompiled_mode); | |
| ASSERT(current_symbols_ == nullptr); | |
| bool global_symbol = false; | |
| switch (section) { | |
| case ProgramSection::Text: | |
| if (debug_so_ != nullptr) { | |
| current_symbols_ = | |
| new (zone_) SharedObjectWriter::SymbolDataArray(zone_, 0); | |
| } | |
| assembly_stream_->WriteString(".text\n"); | |
| global_symbol = true; | |
| break; | |
| case ProgramSection::Data: | |
| // We create a SymbolData array even if there is no debug_so_ 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_) SharedObjectWriter::SymbolDataArray(zone_, 0); | |
| #if defined(DART_TARGET_OS_LINUX) || defined(DART_TARGET_OS_ANDROID) || \ | |
| defined(DART_TARGET_OS_FUCHSIA) || defined(DART_TARGET_OS_WINDOWS) | |
| assembly_stream_->WriteString(".section .rodata\n"); | |
| #elif defined(DART_TARGET_OS_MACOS) || defined(DART_TARGET_OS_MACOS_IOS) | |
| assembly_stream_->WriteString(".const\n"); | |
| #else | |
| UNIMPLEMENTED(); | |
| #endif | |
| global_symbol = true; | |
| break; | |
| case ProgramSection::Bss: | |
| assembly_stream_->WriteString(".bss\n"); | |
| break; | |
| case ProgramSection::BuildId: | |
| break; | |
| } | |
| current_section_label_ = SectionLabel(section); | |
| ASSERT(current_section_label_ > 0); | |
| if (global_symbol) { | |
| assembly_stream_->Printf(".globl %s\n", SectionSymbol(section)); | |
| } | |
| intptr_t padding = Align(alignment, 0, 0); | |
| if (alignment_padding != nullptr) { | |
| *alignment_padding = padding; | |
| } | |
| assembly_stream_->Printf("%s:\n", SectionSymbol(section)); | |
| return true; | |
| } | |
| static void AddSharedObjectSection( | |
| SharedObjectWriter* writer, | |
| ImageWriter::ProgramSection section, | |
| const char* symbol, | |
| intptr_t label, | |
| uint8_t* bytes, | |
| intptr_t size, | |
| SharedObjectWriter::SymbolDataArray* symbols, | |
| SharedObjectWriter::RelocationArray* relocations = nullptr) { | |
| if (writer == nullptr) return; | |
| switch (section) { | |
| case ImageWriter::ProgramSection::Text: | |
| writer->AddText(symbol, label, bytes, size, relocations, symbols); | |
| break; | |
| case ImageWriter::ProgramSection::Data: | |
| writer->AddROData(symbol, label, bytes, size, relocations, symbols); | |
| break; | |
| default: | |
| // Other sections are handled by the shared object writer internally. | |
| break; | |
| } | |
| } | |
| void AssemblyImageWriter::ExitSection(ProgramSection name, intptr_t size) { | |
| // We should still be in the same section as the last EnterSection. | |
| ASSERT_EQUAL(current_section_label_, SectionLabel(name)); | |
| #if defined(DART_TARGET_OS_LINUX) || defined(DART_TARGET_OS_ANDROID) || \ | |
| defined(DART_TARGET_OS_FUCHSIA) | |
| // Output the size of the section symbol to the assembly stream. | |
| assembly_stream_->Printf(".size %s, %zu\n", SectionSymbol(name), size); | |
| assembly_stream_->Printf(".type %s, %%object\n", SectionSymbol(name)); | |
| #elif defined(DART_TARGET_OS_MACOS) || defined(DART_TARGET_OS_MACOS_IOS) | |
| // MachO symbol tables don't include the size of the symbol, so don't bother | |
| // printing it to the assembly output. | |
| #elif defined(DART_TARGET_OS_WINDOWS) | |
| // Windows also doesn't have a .size directive. | |
| #else | |
| UNIMPLEMENTED(); | |
| #endif | |
| // We need to generate a text segment of the appropriate size in the shared | |
| // object writer for two reasons: | |
| // | |
| // * We need unique virtual addresses for each text section in the DWARF | |
| // file and that the virtual addresses for payloads within those sections | |
| // do not overlap. | |
| // | |
| // * Our tools for converting DWARF stack traces back to "normal" Dart | |
| // stack traces calculate an offset into the appropriate instructions | |
| // section, and then add that offset to the virtual address of the | |
| // corresponding segment to get the virtual address for the frame. | |
| // | |
| // Since we don't want to add the actual contents of the segment in the | |
| // separate debugging information, we pass nullptr for the bytes. | |
| AddSharedObjectSection(debug_so_, name, SectionSymbol(name), | |
| current_section_label_, /*bytes=*/nullptr, size, | |
| current_symbols_); | |
| current_section_label_ = 0; | |
| current_symbols_ = nullptr; | |
| } | |
| intptr_t AssemblyImageWriter::WriteTargetWord(word value) { | |
| ASSERT(Utils::BitLength(value) <= compiler::target::kBitsPerWord); | |
| // Padding is helpful for comparing the .S with --disassemble. | |
| assembly_stream_->Printf("%s 0x%.*" Px "\n", kWordDirective, | |
| static_cast<int>(2 * compiler::target::kWordSize), | |
| value); | |
| return compiler::target::kWordSize; | |
| } | |
| intptr_t AssemblyImageWriter::Relocation(intptr_t section_offset, | |
| intptr_t source_label, | |
| intptr_t source_offset, | |
| intptr_t target_label, | |
| intptr_t target_offset) { | |
| // TODO(dartbug.com/43274): Remove once we generate consistent build IDs | |
| // between assembly snapshots and their debugging information. | |
| if (target_label == SectionLabel(ProgramSection::BuildId)) { | |
| return WriteTargetWord(Image::kNoBuildId); | |
| } | |
| // All relocations are word-sized. | |
| assembly_stream_->Printf("%s ", kWordDirective); | |
| if (target_label == current_section_label_) { | |
| assembly_stream_->WriteString("(.)"); | |
| target_offset -= section_offset; | |
| } else { | |
| const char* target_symbol = label_to_symbol_name_.Lookup(target_label); | |
| ASSERT(target_symbol != nullptr); | |
| assembly_stream_->Printf("%s", target_symbol); | |
| } | |
| if (target_offset != 0) { | |
| assembly_stream_->Printf(" + %" Pd "", target_offset); | |
| } | |
| if (source_label == current_section_label_) { | |
| assembly_stream_->WriteString(" - (.)"); | |
| source_offset -= section_offset; | |
| } else { | |
| const char* source_symbol = label_to_symbol_name_.Lookup(source_label); | |
| ASSERT(source_symbol != nullptr); | |
| assembly_stream_->Printf(" - %s", source_symbol); | |
| } | |
| if (source_offset != 0) { | |
| assembly_stream_->Printf(" - %" Pd "", source_offset); | |
| } | |
| assembly_stream_->WriteString("\n"); | |
| return compiler::target::kWordSize; | |
| } | |
| void AssemblyImageWriter::AddCodeSymbol(const Code& code, | |
| const char* symbol, | |
| intptr_t offset) { | |
| auto const label = next_label_++; | |
| label_to_symbol_name_.Insert(label, symbol); | |
| if (assembly_dwarf_ != nullptr) { | |
| assembly_dwarf_->AddCode(code, label); | |
| } | |
| if (debug_so_ != nullptr) { | |
| current_symbols_->Add({symbol, | |
| SharedObjectWriter::SymbolData::Type::Function, | |
| offset, code.Size(), label}); | |
| debug_so_->dwarf()->AddCode(code, label); | |
| } | |
| assembly_stream_->Printf("\"%s\":\n", symbol); | |
| #if defined(DART_TARGET_OS_LINUX) || defined(DART_TARGET_OS_ANDROID) || \ | |
| defined(DART_TARGET_OS_FUCHSIA) | |
| // Output the size of the code symbol to the assembly stream. | |
| assembly_stream_->Printf(".size \"%s\", %zu\n", symbol, code.Size()); | |
| assembly_stream_->Printf(".type \"%s\", %%function\n", symbol); | |
| #elif defined(DART_TARGET_OS_MACOS) || defined(DART_TARGET_OS_MACOS_IOS) | |
| // MachO symbol tables don't include the size of the symbol, so don't bother | |
| // printing it to the assembly output. | |
| #elif defined(DART_TARGET_OS_WINDOWS) | |
| // Windows also doesn't have a .size directive. | |
| #else | |
| UNIMPLEMENTED(); | |
| #endif | |
| } | |
| void AssemblyImageWriter::AddDataSymbol(const char* symbol, | |
| intptr_t offset, | |
| size_t size) { | |
| if (!FLAG_add_readonly_data_symbols) return; | |
| auto const label = next_label_++; | |
| label_to_symbol_name_.Insert(label, symbol); | |
| if (debug_so_ != nullptr) { | |
| current_symbols_->Add({symbol, SharedObjectWriter::SymbolData::Type::Object, | |
| offset, size, label}); | |
| } | |
| } | |
| void AssemblyImageWriter::FrameUnwindPrologue() { | |
| // Creates DWARF's .debug_frame | |
| // CFI = Call frame information | |
| // CFA = Canonical frame address | |
| assembly_stream_->WriteString(".cfi_startproc\n"); | |
| // Below .cfi_def_cfa defines CFA as caller's SP, while .cfi_offset R, offs | |
| // tells unwinder that caller's value of register R is stored at address | |
| // CFA+offs. | |
| // In the code below we emit .cfi_offset directive in the specific order: | |
| // PC first then FP. This should not actually matter, but we discovered | |
| // that LLVM code responsible for emitting compact unwind information | |
| // expects this specific ordering of CFI directives. If we don't | |
| // follow the order then LLVM fails to emit compact unwind info and emits | |
| // __eh_frame instead which is very large. | |
| // See also https://github.com/llvm/llvm-project/issues/62574 and | |
| // https://github.com/flutter/flutter/issues/126004. | |
| #if defined(TARGET_ARCH_IA32) | |
| UNREACHABLE(); | |
| #elif defined(TARGET_ARCH_X64) | |
| assembly_stream_->WriteString(".cfi_def_cfa rbp, 16\n"); | |
| assembly_stream_->WriteString(".cfi_offset rip, -8\n"); | |
| assembly_stream_->WriteString(".cfi_offset rbp, -16\n"); | |
| #elif defined(TARGET_ARCH_ARM64) | |
| // If this changes, then MachOHeader::GenerateUnwindingInformation must | |
| // also change to generate the appropriate compact unwinding information. | |
| COMPILE_ASSERT(R29 == FP); | |
| COMPILE_ASSERT(R30 == LINK_REGISTER); | |
| assembly_stream_->WriteString(".cfi_def_cfa x29, 16\n"); | |
| assembly_stream_->WriteString(".cfi_offset x30, -8\n"); | |
| assembly_stream_->WriteString(".cfi_offset x29, -16\n"); | |
| #elif defined(TARGET_ARCH_ARM) | |
| #if defined(DART_TARGET_OS_MACOS) || defined(DART_TARGET_OS_MACOS_IOS) | |
| COMPILE_ASSERT(FP == R7); | |
| assembly_stream_->WriteString(".cfi_def_cfa r7, 8\n"); | |
| #else | |
| COMPILE_ASSERT(FP == R11); | |
| assembly_stream_->WriteString(".cfi_def_cfa r11, 8\n"); | |
| #endif | |
| assembly_stream_->WriteString(".cfi_offset lr, -4\n"); | |
| #if defined(DART_TARGET_OS_MACOS) || defined(DART_TARGET_OS_MACOS_IOS) | |
| COMPILE_ASSERT(FP == R7); | |
| assembly_stream_->WriteString(".cfi_offset r7, -8\n"); | |
| #else | |
| COMPILE_ASSERT(FP == R11); | |
| assembly_stream_->WriteString(".cfi_offset r11, -8\n"); | |
| #endif | |
| // libunwind on ARM may use .ARM.exidx instead of .debug_frame | |
| #if !defined(DART_TARGET_OS_MACOS) && !defined(DART_TARGET_OS_MACOS_IOS) | |
| COMPILE_ASSERT(FP == R11); | |
| assembly_stream_->WriteString(".fnstart\n"); | |
| assembly_stream_->WriteString(".save {r11, lr}\n"); | |
| assembly_stream_->WriteString(".setfp r11, sp, #0\n"); | |
| #endif | |
| #elif defined(TARGET_ARCH_RISCV32) | |
| assembly_stream_->WriteString(".cfi_def_cfa fp, 0\n"); | |
| assembly_stream_->WriteString(".cfi_offset ra, -4\n"); | |
| assembly_stream_->WriteString(".cfi_offset fp, -8\n"); | |
| #elif defined(TARGET_ARCH_RISCV64) | |
| assembly_stream_->WriteString(".cfi_def_cfa fp, 0\n"); | |
| assembly_stream_->WriteString(".cfi_offset ra, -8\n"); | |
| assembly_stream_->WriteString(".cfi_offset fp, -16\n"); | |
| #else | |
| #error Unexpected architecture. | |
| #endif | |
| } | |
| void AssemblyImageWriter::FrameUnwindEpilogue() { | |
| #if defined(TARGET_ARCH_ARM) | |
| #if !defined(DART_TARGET_OS_MACOS) && !defined(DART_TARGET_OS_MACOS_IOS) | |
| assembly_stream_->WriteString(".fnend\n"); | |
| #endif | |
| #endif | |
| assembly_stream_->WriteString(".cfi_endproc\n"); | |
| } | |
| intptr_t AssemblyImageWriter::WriteBytes(const void* bytes, intptr_t size) { | |
| ASSERT(size >= 0); | |
| auto const start = reinterpret_cast<const uint8_t*>(bytes); | |
| auto const end_of_words = | |
| start + Utils::RoundDown(size, compiler::target::kWordSize); | |
| for (auto cursor = reinterpret_cast<const compiler::target::word*>(start); | |
| cursor < reinterpret_cast<const compiler::target::word*>(end_of_words); | |
| cursor++) { | |
| WriteTargetWord(*cursor); | |
| } | |
| auto const end = start + size; | |
| if (end != end_of_words) { | |
| assembly_stream_->WriteString(kSizeDirectives[kInt8SizeLog2]); | |
| for (auto cursor = end_of_words; cursor < end; cursor++) { | |
| assembly_stream_->Printf("%s 0x%.2x", cursor != end_of_words ? "," : "", | |
| *cursor); | |
| } | |
| assembly_stream_->WriteString("\n"); | |
| } | |
| return size; | |
| } | |
| intptr_t AssemblyImageWriter::Align(intptr_t alignment, | |
| intptr_t offset, | |
| intptr_t position) { | |
| ASSERT(offset == 0); | |
| const intptr_t next_position = Utils::RoundUp(position, alignment); | |
| assembly_stream_->Printf(".balign %" Pd ", 0\n", alignment); | |
| return next_position - position; | |
| } | |
| #endif // defined(DART_PRECOMPILER) | |
| #if defined(DART_PRECOMPILER) | |
| BlobImageWriter::BlobImageWriter(Thread* thread, | |
| NonStreamingWriteStream* instructions, | |
| const Trie<const char>* deobfuscation_trie, | |
| SharedObjectWriter* debug_so, | |
| SharedObjectWriter* so, | |
| bool needs_unique_names) | |
| : ImageWriter(thread, | |
| /*generates_assembly=*/false, | |
| needs_unique_names, | |
| deobfuscation_trie), | |
| #else | |
| BlobImageWriter::BlobImageWriter(Thread* thread, | |
| NonStreamingWriteStream* instructions, | |
| SharedObjectWriter* debug_so, | |
| SharedObjectWriter* so, | |
| bool needs_unique_names) | |
| : ImageWriter(thread, /*generates_assembly=*/false, needs_unique_names), | |
| #endif | |
| instructions_(instructions), | |
| so_(so), | |
| debug_so_(debug_so) { | |
| #if defined(DART_PRECOMPILER) | |
| ASSERT_EQUAL(FLAG_precompiled_mode, so_ != nullptr); | |
| ASSERT(debug_so_ == nullptr || debug_so_->dwarf() != nullptr); | |
| #else | |
| RELEASE_ASSERT(so_ == nullptr); | |
| RELEASE_ASSERT(debug_so_ == nullptr); | |
| #endif | |
| } | |
| void BlobImageWriter::Finalize() { | |
| #if defined(DART_PRECOMPILER) | |
| if (so_ != nullptr) { | |
| TIMELINE_DURATION(thread_, Isolate, "FinalizeSharedObject"); | |
| so_->Finalize(); | |
| } | |
| if (debug_so_ != nullptr) { | |
| { | |
| TIMELINE_DURATION(thread_, Isolate, "FinalizeDebugInfo"); | |
| debug_so_->Finalize(); | |
| } | |
| if (so_ != nullptr) { | |
| so_->AssertConsistency(debug_so_); | |
| } | |
| } | |
| #endif | |
| } | |
| intptr_t BlobImageWriter::WriteBytes(const void* bytes, intptr_t size) { | |
| current_section_stream_->WriteBytes(bytes, size); | |
| return size; | |
| } | |
| void BlobImageWriter::WriteBss() { | |
| #if defined(DART_PRECOMPILER) | |
| // We don't actually write a BSS segment, it's created as part of the | |
| // Finalize() method of the shared object writer. | |
| #endif | |
| } | |
| void BlobImageWriter::WriteROData(NonStreamingWriteStream* 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, 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); | |
| ExitSection(ProgramSection::Data, clustered_stream->bytes_written()); | |
| } | |
| bool BlobImageWriter::EnterSection(ProgramSection section, | |
| intptr_t alignment, | |
| intptr_t* alignment_padding) { | |
| #if defined(DART_PRECOMPILER) | |
| ASSERT_EQUAL(so_ != nullptr, FLAG_precompiled_mode); | |
| ASSERT(current_relocations_ == nullptr); | |
| ASSERT(current_symbols_ == nullptr); | |
| #endif | |
| ASSERT(section == ProgramSection::Data || current_section_stream_ == nullptr); | |
| switch (section) { | |
| case ProgramSection::Text: | |
| current_section_stream_ = ASSERT_NOTNULL(instructions_); | |
| #if defined(DART_PRECOMPILER) | |
| if (so_ != nullptr || debug_so_ != nullptr) { | |
| current_relocations_ = | |
| new (zone_) SharedObjectWriter::RelocationArray(zone_, 0); | |
| current_symbols_ = | |
| new (zone_) SharedObjectWriter::SymbolDataArray(zone_, 0); | |
| } | |
| #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) | |
| if (so_ != nullptr || debug_so_ != nullptr) { | |
| current_relocations_ = | |
| new (zone_) SharedObjectWriter::RelocationArray(zone_, 0); | |
| current_symbols_ = | |
| new (zone_) SharedObjectWriter::SymbolDataArray(zone_, 0); | |
| } | |
| #endif | |
| break; | |
| case ProgramSection::Bss: | |
| // SharedObjectWriter::Finalize() creates the BSS section. | |
| return false; | |
| case ProgramSection::BuildId: | |
| // SharedObjectWriter::Finalize() creates the appropriate type of | |
| // build ID for the target format. | |
| return false; | |
| } | |
| intptr_t padding = current_section_stream_->Align(alignment); | |
| if (alignment_padding != nullptr) { | |
| *alignment_padding = padding; | |
| } | |
| return true; | |
| } | |
| void BlobImageWriter::ExitSection(ProgramSection name, intptr_t size) { | |
| #if defined(DART_PRECOMPILER) | |
| AddSharedObjectSection(so_, name, SectionSymbol(name), SectionLabel(name), | |
| current_section_stream_->buffer(), size, | |
| current_symbols_, current_relocations_); | |
| // We create the corresponding segment in the debugging information as well, | |
| // since it needs the contents to create the correct build ID. | |
| AddSharedObjectSection(debug_so_, name, SectionSymbol(name), | |
| SectionLabel(name), current_section_stream_->buffer(), | |
| size, current_symbols_, current_relocations_); | |
| current_relocations_ = nullptr; | |
| current_symbols_ = nullptr; | |
| #endif | |
| current_section_stream_ = nullptr; | |
| } | |
| intptr_t BlobImageWriter::WriteTargetWord(word value) { | |
| current_section_stream_->WriteTargetWord(value); | |
| return compiler::target::kWordSize; | |
| } | |
| intptr_t BlobImageWriter::Align(intptr_t alignment, | |
| intptr_t offset, | |
| intptr_t position) { | |
| const intptr_t stream_padding = | |
| current_section_stream_->Align(alignment, offset); | |
| // Double-check that the position has the same alignment. | |
| ASSERT_EQUAL(Utils::RoundUp(position, alignment, offset) - position, | |
| stream_padding); | |
| return stream_padding; | |
| } | |
| #if defined(DART_PRECOMPILER) | |
| intptr_t BlobImageWriter::Relocation(intptr_t section_offset, | |
| intptr_t source_label, | |
| intptr_t source_offset, | |
| intptr_t target_label, | |
| intptr_t target_offset) { | |
| ASSERT(FLAG_precompiled_mode); | |
| if (current_relocations_ != nullptr) { | |
| current_relocations_->Add({compiler::target::kWordSize, section_offset, | |
| source_label, source_offset, target_label, | |
| target_offset}); | |
| } | |
| // We write break instructions so it's easy to tell if a relocation doesn't | |
| // get replaced appropriately. | |
| return WriteTargetWord(kBreakInstructionFiller); | |
| } | |
| void BlobImageWriter::AddCodeSymbol(const Code& code, | |
| const char* symbol, | |
| intptr_t offset) { | |
| const intptr_t label = next_label_++; | |
| if (current_symbols_ != nullptr) { | |
| current_symbols_->Add({symbol, | |
| SharedObjectWriter::SymbolData::Type::Function, | |
| offset, code.Size(), label}); | |
| } | |
| if (so_ != nullptr && so_->dwarf() != nullptr) { | |
| so_->dwarf()->AddCode(code, label); | |
| } | |
| if (debug_so_ != nullptr) { | |
| debug_so_->dwarf()->AddCode(code, label); | |
| } | |
| } | |
| void BlobImageWriter::AddDataSymbol(const char* symbol, | |
| intptr_t offset, | |
| size_t size) { | |
| if (!FLAG_add_readonly_data_symbols) return; | |
| const intptr_t label = next_label_++; | |
| if (current_symbols_ != nullptr) { | |
| current_symbols_->Add({symbol, SharedObjectWriter::SymbolData::Type::Object, | |
| offset, size, label}); | |
| } | |
| } | |
| #endif // defined(DART_PRECOMPILER) | |
| #endif // !defined(DART_PRECOMPILED_RUNTIME) | |
| ImageReader::ImageReader(const uint8_t* data_image, | |
| const uint8_t* instructions_image) | |
| : data_image_(ASSERT_NOTNULL(data_image)), | |
| instructions_image_(ASSERT_NOTNULL(instructions_image)), | |
| instructions_image_authed_(Auth(instructions_image)) {} | |
| char* ImageReader::VerifyAlignment() const { | |
| // If this changes, bin_to_assembly.py and bin_to_coff.py must also change. | |
| COMPILE_ASSERT(kObjectStartAlignment == 64); | |
| if (!Utils::IsAligned(data_image_, kObjectStartAlignment) || | |
| !Utils::IsAligned(instructions_image_, kObjectStartAlignment)) { | |
| return Utils::StrDup("Snapshot is misaligned"); | |
| } | |
| return nullptr; | |
| } | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| uword ImageReader::GetBareInstructionsAt(uint32_t offset) const { | |
| ASSERT(Utils::IsAligned(offset, Instructions::kBarePayloadAlignment)); | |
| return reinterpret_cast<uword>(instructions_image_authed_) + offset; | |
| } | |
| uword ImageReader::GetBareInstructionsEnd() const { | |
| TextImage image(instructions_image_); | |
| return reinterpret_cast<uword>(image.object_start()) + image.object_size(); | |
| } | |
| #endif | |
| InstructionsPtr ImageReader::GetInstructionsAt(uint32_t offset) const { | |
| ASSERT(!FLAG_precompiled_mode); | |
| ASSERT(Utils::IsAligned(offset, kObjectAlignment)); | |
| ObjectPtr result = UntaggedObject::FromAddr( | |
| reinterpret_cast<uword>(instructions_image_authed_) + offset); | |
| ASSERT(result->IsInstructions()); | |
| ASSERT(result->untag()->IsMarked()); | |
| return Instructions::RawCast(result); | |
| } | |
| ObjectPtr ImageReader::GetObjectAt(uint32_t offset) const { | |
| ASSERT(Utils::IsAligned(offset, kObjectAlignment)); | |
| ObjectPtr result = | |
| UntaggedObject::FromAddr(reinterpret_cast<uword>(data_image_) + offset); | |
| ASSERT(result->untag()->IsMarked()); | |
| return result; | |
| } | |
| } // namespace dart |