Files
sdk/runtime/vm/image_snapshot.cc
T
Tess Strickland bc021bbc01 [vm] Output accompaning relocatable object for Mach-O snapshots.
In order to appropriately generate .dSYMs for a snapshot and allow
the strip tool to be used on the snapshot afterwards, add a mode that
not only outputs the snapshot (sans DWARF information) but also
an associated relocatable object file that contains the program's DWARF
information. That allows dsymutil to retrieve the DWARF information
from the relocatable object when run on the snapshot prior to stripping.

To specify that the relocatable object file should be output, use
the new --macho-object command line argument to gen_snapshot
to specify where the object file should be written.

This CL also adds an additional command line argument,
--macho-reduce-padding, which reduces the alignment used for segments
and the text/const sections in Mach-O outputs from 16KB to 64 bytes.
The larger padding is needed for some uses, like non-native loading
of Mach-O objects, but can be elided for other uses like Flutter builds.

TEST=vm/dart/use_dwarf_stack_traces_flag_test
     vm/dart/use_macho_reduce_padding_flag_test

Change-Id: I2bf4bacb70c41299b8b6fdb7635c2374acf7a07d
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/457420
Reviewed-by: Slava Egorov <vegorov@google.com>
Commit-Queue: Tess Strickland <sstrickl@google.com>
2025-11-19 07:45:52 -08:00

2141 lines
79 KiB
C++

// 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()) {
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, bool vm) {
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(vm);
offset_space_ = vm ? IdSpace::kVmText : IdSpace::kIsolateText;
WriteText(vm);
// 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_ = vm ? IdSpace::kVmData : IdSpace::kIsolateData;
WriteROData(clustered_stream, vm);
}
void ImageWriter::WriteROData(NonStreamingWriteStream* stream, bool vm) {
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_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::ImmutableBit::encode(is_immutable);
}
uword ImageWriter::GetMarkedTags(const Object& obj) {
uword tags =
GetMarkedTags(obj.ptr()->untag()->GetClassId(), SizeInSnapshot(obj),
obj.IsCanonical(), obj.IsImmutable());
#if defined(HASH_IN_OBJECT_HEADER)
tags = UntaggedObject::HashTag::update(obj.ptr()->untag()->GetHeaderHash(),
tags);
#endif
return tags;
}
const char* ImageWriter::SectionSymbol(ProgramSection section, bool vm) {
switch (section) {
case ProgramSection::Text:
return vm ? kVmSnapshotInstructionsAsmSymbol
: kIsolateSnapshotInstructionsAsmSymbol;
case ProgramSection::Data:
return vm ? kVmSnapshotDataAsmSymbol : kIsolateSnapshotDataAsmSymbol;
case ProgramSection::Bss:
return vm ? kVmSnapshotBssAsmSymbol : kIsolateSnapshotBssAsmSymbol;
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(bool vm) {
const bool bare_instruction_payloads = FLAG_precompiled_mode;
// Start snapshot at page boundary.
intptr_t alignment_padding = 0;
if (!EnterSection(ProgramSection::Text, vm, 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, vm);
ASSERT(instructions_symbol != nullptr);
intptr_t instructions_label = SectionLabel(ProgramSection::Text, vm);
ASSERT(instructions_label > 0);
const char* bss_symbol = SectionSymbol(ProgramSection::Bss, vm);
ASSERT(bss_symbol != nullptr);
intptr_t bss_label = SectionLabel(ProgramSection::Bss, vm);
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, vm));
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
#if defined(DART_PRECOMPILER)
PcDescriptors& descriptors = PcDescriptors::Handle(zone_);
#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();
auto const payload_end = payload_start + payload_size;
auto cursor = payload_start;
#if defined(DART_PRECOMPILER)
descriptors = code.pc_descriptors();
PcDescriptors::Iterator iterator(
descriptors, /*kind_mask=*/UntaggedPcDescriptors::kBSSRelocation);
while (iterator.MoveNext()) {
// We only generate BSS relocations in the precompiler.
ASSERT(FLAG_precompiled_mode);
auto const next_reloc_offset = iterator.PcOffset();
auto const next_reloc_address = payload_start + next_reloc_offset;
// We only generate BSS relocations that are target word-sized and at
// target word-aligned offsets in the payload. Double-check this.
ASSERT(
Utils::IsAligned(next_reloc_address, compiler::target::kWordSize));
text_offset += WriteBytes(cursor, next_reloc_address - cursor);
// The instruction stream at the relocation position holds the target
// offset into the BSS section.
const auto target_offset =
*reinterpret_cast<const compiler::target::word*>(
next_reloc_address);
text_offset += Relocation(text_offset, instructions_label, text_offset,
bss_label, target_offset);
cursor = next_reloc_address + compiler::target::kWordSize;
}
#endif
text_offset += WriteBytes(cursor, payload_end - cursor);
}
// 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, vm, 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 vm_name = SectionSymbol(section, /*vm=*/true);
auto const vm_label = SectionLabel(section, /*vm=*/true);
label_to_symbol_name_.Insert(vm_label, vm_name);
auto const isolate_name = SectionSymbol(section, /*vm=*/false);
auto const isolate_label = SectionLabel(section, /*vm=*/false);
if (vm_label != isolate_label) {
label_to_symbol_name_.Insert(isolate_label, isolate_name);
} else {
// Make sure the names also match.
ASSERT_EQUAL(strcmp(vm_name, isolate_name), 0);
}
}
}
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
#endif
#if defined(DART_TARGET_OS_WINDOWS)
// __declspec(dllexport)
const char* const exported_symbols[] = {
kVmSnapshotDataCSymbol,
kVmSnapshotInstructionsCSymbol,
kIsolateSnapshotDataCSymbol,
kIsolateSnapshotInstructionsCSymbol,
};
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(bool vm) {
EnterSection(ProgramSection::Bss, vm, ImageWriter::kBssAlignment);
auto const entry_count =
vm ? BSS::kVmEntryCount : 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, vm,
entry_count * compiler::target::kWordSize);
}
void AssemblyImageWriter::WriteROData(NonStreamingWriteStream* clustered_stream,
bool vm) {
if (!EnterSection(ProgramSection::Data, vm, 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, 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);
#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, vm, len);
}
bool AssemblyImageWriter::EnterSection(ProgramSection section,
bool vm,
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, vm);
ASSERT(current_section_label_ > 0);
if (global_symbol) {
assembly_stream_->Printf(".globl %s\n", SectionSymbol(section, vm));
}
intptr_t padding = Align(alignment, 0, 0);
if (alignment_padding != nullptr) {
*alignment_padding = padding;
}
assembly_stream_->Printf("%s:\n", SectionSymbol(section, vm));
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,
bool vm,
intptr_t size) {
// We should still be in the same section as the last EnterSection.
ASSERT_EQUAL(current_section_label_, SectionLabel(name, vm));
#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, vm), size);
assembly_stream_->Printf(".type %s, %%object\n", SectionSymbol(name, vm));
#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, vm),
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, /*vm=*/false)) {
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* vm_instructions,
NonStreamingWriteStream* isolate_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* vm_instructions,
NonStreamingWriteStream* isolate_instructions,
SharedObjectWriter* debug_so,
SharedObjectWriter* so,
bool needs_unique_names)
: ImageWriter(thread, /*generates_assembly=*/false, needs_unique_names),
#endif
vm_instructions_(vm_instructions),
isolate_instructions_(isolate_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(bool vm) {
#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,
bool vm) {
#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());
}
bool BlobImageWriter::EnterSection(ProgramSection section,
bool vm,
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(vm ? vm_instructions_ : isolate_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, bool vm, intptr_t size) {
#if defined(DART_PRECOMPILER)
AddSharedObjectSection(so_, name, SectionSymbol(name, vm),
SectionLabel(name, vm),
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, vm),
SectionLabel(name, vm),
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)) {}
ApiErrorPtr ImageReader::VerifyAlignment() const {
if (!Utils::IsAligned(data_image_, kObjectStartAlignment) ||
!Utils::IsAligned(instructions_image_, kObjectStartAlignment)) {
return ApiError::New(
String::Handle(String::New("Snapshot is misaligned", Heap::kOld)),
Heap::kOld);
}
return ApiError::null();
}
#if defined(DART_PRECOMPILED_RUNTIME)
uword ImageReader::GetBareInstructionsAt(uint32_t offset) const {
ASSERT(Utils::IsAligned(offset, Instructions::kBarePayloadAlignment));
return reinterpret_cast<uword>(instructions_image_) + offset;
}
uword ImageReader::GetBareInstructionsEnd() const {
Image 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_) + 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