dart-sdk/runtime/vm/image_snapshot.cc
Vyacheslav Egorov dd2f556730 [vm] Reoder CFI directives in generated assembly.
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 https://github.com/llvm/llvm-project/issues/62574.

Fixes https://github.com/flutter/flutter/issues/126004.

Tested: manually via pkg/vm/tool/precompiler2 --build-assembly and objdump --unwind-info
Change-Id: Idb1f4f64afdaa7206bf43adf1685bb1f4086217f
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/301740
Commit-Queue: Slava Egorov <vegorov@google.com>
Reviewed-by: Zach Anderson <zra@google.com>
2023-05-05 18:39:09 +00:00

1856 lines
69 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 "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);
// For assembly snapshots, we need to retrieve this from the initialized BSS.
const uword address =
compiled_to_elf() ? extra_info_->instructions_relocated_address_
: bss()[BSS::RelocationIndex(
BSS::Relocation::InstructionsRelocatedAddress)];
ASSERT(address != kNoRelocatedAddress);
return address;
#else
return kNoRelocatedAddress;
#endif
}
const uint8_t* Image::build_id() const {
#if defined(DART_PRECOMPILED_RUNTIME)
ASSERT(extra_info_ != nullptr);
if (extra_info_->build_id_offset_ != kNoBuildId) {
auto const note = reinterpret_cast<elf::Note*>(
raw_memory_ + extra_info_->build_id_offset_);
return note->data + note->name_size;
}
#endif
return nullptr;
}
intptr_t Image::build_id_length() const {
#if defined(DART_PRECOMPILED_RUNTIME)
ASSERT(extra_info_ != nullptr);
if (extra_info_->build_id_offset_ != kNoBuildId) {
auto const note = reinterpret_cast<elf::Note*>(
raw_memory_ + extra_info_->build_id_offset_);
return note->description_size;
}
#endif
return 0;
}
bool Image::compiled_to_elf() const {
#if defined(DART_PRECOMPILED_RUNTIME)
ASSERT(extra_info_ != nullptr);
// Since assembly snapshots can't set up this field correctly (instead,
// it's initialized in BSS at snapshot load time), we use it to detect
// direct-to-ELF snapshots.
return extra_info_->instructions_relocated_address_ != kNoRelocatedAddress;
#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->GetClassId();
// 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->GetClassId() != b->GetClassId()) {
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)
ImageWriter::ImageWriter(Thread* t, bool generates_assembly)
: thread_(ASSERT_NOTNULL(t)),
zone_(t->zone()),
next_data_offset_(0),
next_text_offset_(0),
objects_(),
instructions_(),
#if defined(DART_PRECOMPILER)
namer_(t->zone(), /*for_assembly=*/generates_assembly),
#endif
image_type_(TagObjectTypeAsReadOnly(zone_, "Image")),
instructions_section_type_(
TagObjectTypeAsReadOnly(zone_, "InstructionsSection")),
instructions_type_(TagObjectTypeAsReadOnly(zone_, "Instructions")),
trampoline_type_(TagObjectTypeAsReadOnly(zone_, "Trampoline")) {
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;
}
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->GetClassId();
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];
const bool is_trampoline = data.code_ == nullptr;
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];
const bool is_trampoline = data.trampoline_bytes != nullptr;
if (is_trampoline) 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);
}
}
// Needs to happen before WriteText, as we add information about the
// BSSsection in the text section as an initial InstructionsSection object.
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 ELF 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::OldBit::encode(true) |
UntaggedObject::OldAndNotMarkedBit::encode(false) |
UntaggedObject::OldAndNotRememberedBit::encode(true) |
UntaggedObject::NewBit::encode(false);
uword ImageWriter::GetMarkedTags(classid_t cid,
intptr_t size,
bool is_canonical /* = 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);
}
uword ImageWriter::GetMarkedTags(const Object& obj) {
uword tags = GetMarkedTags(obj.ptr()->untag()->GetClassId(),
SizeInSnapshot(obj), obj.IsCanonical());
#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
? compiler::target::Instructions::kBarePayloadAlignment
: 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
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;
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_ : instructions_type_;
const intptr_t size = is_trampoline ? 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;
}
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);
}
#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
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) { stream_->Printf(".sleb128 %" Pd "\n", value); }
void uleb128(uintptr_t value) {
stream_->Printf(".uleb128 %" Pd "\n", value);
}
void u1(uint8_t value) {
stream_->Printf("%s %u\n", kSizeDirectives[kInt8SizeLog2], value);
}
void u2(uint16_t value) {
stream_->Printf("%s %u\n", kSizeDirectives[kInt16SizeLog2], value);
}
void u4(uint32_t value) {
stream_->Printf("%s %" Pu32 "\n", kSizeDirectives[kInt32SizeLog2], value);
}
void u8(uint64_t value) {
stream_->Printf("%s %" Pu64 "\n", kSizeDirectives[kInt64SizeLog2], value);
}
void string(const char* cstr) { // NOLINT
stream_->Printf(".string \"%s\"\n", cstr); // NOLINT
}
void WritePrefixedLength(const char* prefix, std::function<void()> body) {
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) {
const char* symbol = label_to_name_.Lookup(label);
ASSERT(symbol != nullptr);
if (offset == 0) {
PrintNamedAddress(symbol);
} else {
PrintNamedAddressWithOffset(symbol, offset);
}
}
// No-op, we'll be using labels.
void InitializeAbstractOrigins(intptr_t size) {}
void RegisterAbstractOrigin(intptr_t index) {
// Label for DW_AT_abstract_origin references
stream_->Printf(".Lfunc%" Pd ":\n", index);
}
void AbstractOrigin(intptr_t index) {
// Assignment to temp works around buggy Mac assembler.
stream_->Printf("Ltemp%" Pd " = .Lfunc%" Pd " - %s\n", temp_, index,
kDebugInfoLabel);
stream_->Printf("%s Ltemp%" Pd "\n", kSizeDirectives[kInt32SizeLog2],
temp_);
temp_++;
}
// 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) {
stream_->Printf("%s \"%s\"\n", kWordDirective, name);
}
void PrintNamedAddressWithOffset(const char* name, intptr_t offset) {
stream_->Printf("%s \"%s\" + %" Pd "\n", kWordDirective, 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, Elf* elf) {
if (!strip) {
if (elf != nullptr) {
// Reuse the existing DWARF object.
ASSERT(elf->dwarf() != nullptr);
return elf->dwarf();
}
return new (zone) Dwarf(zone);
}
return nullptr;
}
AssemblyImageWriter::AssemblyImageWriter(Thread* thread,
BaseWriteStream* stream,
bool strip,
Elf* debug_elf)
: ImageWriter(thread, /*generates_assembly=*/true),
assembly_stream_(stream),
assembly_dwarf_(AddDwarfIfUnstripped(zone_, strip, debug_elf)),
debug_elf_(debug_elf),
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_elf_ != nullptr) {
debug_elf_->Finalize();
}
#if defined(DART_TARGET_OS_LINUX) || defined(DART_TARGET_OS_ANDROID) || \
defined(DART_TARGET_OS_FUCHSIA)
// Non-executable stack.
assembly_stream_->WriteString(".section .note.GNU-stack,\"\"\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();
buffer->AddString(name);
} else if (object.IsAbstractType()) {
AbstractType::Cast(object).PrintName(Object::kUserVisibleName, buffer);
} else if (object.IsFunction()) {
const Function& func = Function::Cast(object);
func.PrintName({Object::kUserVisibleName, Object::NameDisambiguation::kNo},
buffer);
} 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);
}
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 {
AddNonUniqueNameFor(&printer, *data.code_);
}
if (for_assembly_) {
ModifyForAssembly(&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);
}
return printer.buffer();
}
void AssemblyImageWriter::WriteBss(bool vm) {
EnterSection(ProgramSection::Bss, vm, ImageWriter::kBssAlignment);
auto const entry_count = vm ? BSS::kVmEntryCount : BSS::kIsolateEntryCount;
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.
#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_elf_ != nullptr) {
current_symbols_ =
new (zone_) ZoneGrowableArray<Elf::SymbolData>(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_elf_ because we
// may be writing RO data symbols, and RO data is written in two steps:
// 1. Serializing the read-only data objects to the clustered stream
// 2. Writing the bytes of the clustered stream to the assembly output.
// Thus, we'll need to interleave the symbols with the cluster bytes
// during step 2.
current_symbols_ =
new (zone_) ZoneGrowableArray<Elf::SymbolData>(zone_, 0);
#if defined(DART_TARGET_OS_LINUX) || defined(DART_TARGET_OS_ANDROID) || \
defined(DART_TARGET_OS_FUCHSIA)
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 ElfAddSection(
Elf* elf,
ImageWriter::ProgramSection section,
const char* symbol,
intptr_t label,
uint8_t* bytes,
intptr_t size,
ZoneGrowableArray<Elf::SymbolData>* symbols,
ZoneGrowableArray<Elf::Relocation>* relocations = nullptr) {
if (elf == nullptr) return;
switch (section) {
case ImageWriter::ProgramSection::Text:
elf->AddText(symbol, label, bytes, size, relocations, symbols);
break;
case ImageWriter::ProgramSection::Data:
elf->AddROData(symbol, label, bytes, size, relocations, symbols);
break;
default:
// Other sections are handled by the Elf object 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.
#else
UNIMPLEMENTED();
#endif
// We need to generate a text segment of the appropriate size in the ELF
// 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, which
// creates an appropriate NOBITS section instead of PROGBITS.
ElfAddSection(debug_elf_, 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,
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_elf_ != nullptr) {
current_symbols_->Add({symbol, elf::STT_FUNC, offset, code.Size(), label});
debug_elf_->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.
#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);
current_symbols_->Add({symbol, elf::STT_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)
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)
BlobImageWriter::BlobImageWriter(Thread* thread,
NonStreamingWriteStream* vm_instructions,
NonStreamingWriteStream* isolate_instructions,
Elf* debug_elf,
Elf* elf)
: ImageWriter(thread, /*generates_assembly=*/false),
vm_instructions_(vm_instructions),
isolate_instructions_(isolate_instructions),
elf_(elf),
debug_elf_(debug_elf) {
#if defined(DART_PRECOMPILER)
ASSERT_EQUAL(FLAG_precompiled_mode, elf_ != nullptr);
ASSERT(debug_elf_ == nullptr || debug_elf_->dwarf() != nullptr);
#else
RELEASE_ASSERT(elf_ == nullptr);
#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
// Elf constructor.
#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(elf_ != 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)
current_relocations_ =
new (zone_) ZoneGrowableArray<Elf::Relocation>(zone_, 0);
current_symbols_ =
new (zone_) ZoneGrowableArray<Elf::SymbolData>(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)
current_relocations_ =
new (zone_) ZoneGrowableArray<Elf::Relocation>(zone_, 0);
current_symbols_ =
new (zone_) ZoneGrowableArray<Elf::SymbolData>(zone_, 0);
#endif
break;
case ProgramSection::Bss:
// The BSS section is pre-made in the Elf object for precompiled snapshots
// and unused otherwise, so there's no work that needs doing here.
return false;
case ProgramSection::BuildId:
// The GNU build ID is handled specially in the Elf object, and does not
// get used for non-precompiled snapshots.
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)
ElfAddSection(elf_, 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.
ElfAddSection(debug_elf_, 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);
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_++;
current_symbols_->Add({symbol, elf::STT_FUNC, offset, code.Size(), label});
if (elf_ != nullptr && elf_->dwarf() != nullptr) {
elf_->dwarf()->AddCode(code, label);
}
if (debug_elf_ != nullptr) {
debug_elf_->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_++;
current_symbols_->Add({symbol, elf::STT_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