| // Copyright (c) 2026, 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/coff.h" |
| |
| #if defined(DART_PRECOMPILER) |
| |
| #include "include/dart_api.h" |
| #include "platform/unaligned.h" |
| #include "platform/utils.h" |
| #include "vm/codeview.h" |
| #include "vm/dwarf.h" |
| #include "vm/image_snapshot.h" |
| #include "vm/os.h" |
| |
| namespace dart { |
| |
| namespace { |
| |
| static constexpr intptr_t kCoffRelocationOverflowThreshold = 0xffff; |
| static constexpr uint16_t kCoffRelocationCountOverflowSentinel = 0xffff; |
| |
| bool UsesRelocationCountOverflow(intptr_t relocation_count) { |
| return relocation_count >= kCoffRelocationOverflowThreshold; |
| } |
| |
| uint16_t EncodedRelocationCount(intptr_t relocation_count) { |
| if (UsesRelocationCountOverflow(relocation_count)) { |
| return kCoffRelocationCountOverflowSentinel; |
| } |
| return static_cast<uint16_t>(relocation_count); |
| } |
| |
| intptr_t RelocationRecordCountOnDisk(intptr_t relocation_count) { |
| return relocation_count + |
| (UsesRelocationCountOverflow(relocation_count) ? 1 : 0); |
| } |
| |
| struct ParsedUnwindPrologue { |
| uint32_t stack_size = 0; |
| uint8_t prologue_size = 0; |
| }; |
| |
| struct FunctionUnwindRange { |
| uint32_t begin = 0; |
| uint32_t end = 0; |
| ParsedUnwindPrologue prologue; |
| }; |
| |
| struct SharedUnwindInfo { |
| ParsedUnwindPrologue prologue; |
| uint32_t xdata_offset = 0; |
| }; |
| |
| int CompareFunctionUnwindRange(const FunctionUnwindRange* a, |
| const FunctionUnwindRange* b) { |
| if (a->begin < b->begin) return -1; |
| if (a->begin > b->begin) return 1; |
| if (a->end < b->end) return -1; |
| if (a->end > b->end) return 1; |
| return 0; |
| } |
| |
| } // namespace |
| |
| // ----------------------------------------------------------------------------- |
| // Construction |
| // ----------------------------------------------------------------------------- |
| |
| CoffWriter::CoffWriter(Zone* zone, |
| BaseWriteStream* stream, |
| Type type, |
| Dwarf* dwarf) |
| : SharedObjectWriter(zone, stream, type, dwarf), |
| all_sections_(zone, 8), |
| label_to_symbol_index_(zone), |
| static_symbol_usage_count_(zone), |
| text_portions_(zone, 4) { |
| symbols_ = new (zone) ZoneGrowableArray<CoffSym>(zone, 32); |
| string_table_ = new (zone) ZoneGrowableArray<uint8_t>(zone, 128); |
| } |
| |
| const GrowableArray<const Code*>& CoffWriter::debug_codes() { |
| ASSERT(dwarf() != nullptr); |
| return dwarf()->codes(); |
| } |
| |
| const GrowableArray<const Script*>& CoffWriter::debug_scripts() { |
| ASSERT(dwarf() != nullptr); |
| return dwarf()->scripts(); |
| } |
| |
| const Trie<const char>* CoffWriter::debug_deobfuscation_trie() { |
| ASSERT(dwarf() != nullptr); |
| return dwarf()->deobfuscation_trie(); |
| } |
| |
| intptr_t CoffWriter::LookupDebugCodeLabel(const Code& code) { |
| ASSERT(dwarf() != nullptr); |
| return dwarf()->LookupCodeLabel(code); |
| } |
| |
| intptr_t CoffWriter::LookupDebugScript(const Script& script) { |
| ASSERT(dwarf() != nullptr); |
| return dwarf()->LookupScript(script); |
| } |
| |
| // ----------------------------------------------------------------------------- |
| // String table |
| // ----------------------------------------------------------------------------- |
| |
| uint32_t CoffWriter::InternString(const char* str) { |
| // COFF string table starts at offset 4 (the first 4 bytes hold the size). |
| const uint32_t offset = static_cast<uint32_t>(string_table_->length()) + 4; |
| const intptr_t len = strlen(str); |
| for (intptr_t i = 0; i <= len; i++) { // include trailing NUL |
| string_table_->Add(static_cast<uint8_t>(str[i])); |
| } |
| return offset; |
| } |
| |
| // ----------------------------------------------------------------------------- |
| // Section helpers |
| // ----------------------------------------------------------------------------- |
| |
| CoffWriter::Section* CoffWriter::GetOrCreateSection(SectionKind kind, |
| const char* name, |
| uint32_t characteristics, |
| intptr_t alignment_log2, |
| bool is_bss) { |
| if (by_kind_[kind] != nullptr) { |
| return by_kind_[kind]; |
| } |
| auto* const section = new (zone_) Section(zone_); |
| ASSERT(strlen(name) <= 8); |
| for (intptr_t i = 0; i < 8 && name[i] != '\0'; i++) { |
| section->name[i] = name[i]; |
| } |
| section->characteristics = characteristics; |
| section->alignment_log2 = alignment_log2; |
| section->is_bss = is_bss; |
| section->relocs = new (zone_) ZoneGrowableArray<Reloc>(zone_, 8); |
| by_kind_[kind] = section; |
| all_sections_.Add(section); |
| return section; |
| } |
| |
| void CoffWriter::PadToAlignment(Section* section, |
| intptr_t alignment, |
| uint8_t pad_byte) { |
| ASSERT(section != nullptr); |
| ASSERT(!section->is_bss); |
| ASSERT(Utils::IsPowerOfTwo(alignment)); |
| section->data.Align(alignment, /*offset=*/0, pad_byte); |
| } |
| |
| intptr_t CoffWriter::AppendToSection( |
| SectionKind kind, |
| const uint8_t* bytes, |
| intptr_t size, |
| const SharedObjectWriter::RelocationArray* relocations, |
| intptr_t owning_label) { |
| using R = SharedObjectWriter::Relocation; |
| Section* const section = by_kind_[kind]; |
| ASSERT(section != nullptr); |
| ASSERT(!section->is_bss); |
| const intptr_t start = section->data.Position(); |
| section->data.bytes(bytes, size); |
| if (relocations != nullptr) { |
| for (const auto& vm_reloc : *relocations) { |
| // Adjust section_offset to account for any data already in the section. |
| SharedObjectWriter::Relocation translated = vm_reloc; |
| translated.section_offset += start; |
| // The Dart image writer expresses many text relocations as |
| // `target_label - owning_label`, where `owning_label` is the headline |
| // symbol for the current AddText portion (always at offset `start` |
| // within the section). COFF cannot express `sym1 - sym2` directly, |
| // but this form is semantically a self-relative reloc once we shift |
| // the reference point from `owning_label` to the relocation site. |
| // |
| // Rewrite: source_label_addr + source_offset == reloc_addr + new_offset |
| // new_offset = (start + source_offset) - reloc_addr |
| // After this rewrite the standard kSelfRelative codepath handles it. |
| if (owning_label > 0 && translated.source_label == owning_label) { |
| translated.source_offset = |
| start + translated.source_offset - translated.section_offset; |
| translated.source_label = R::kSelfRelative; |
| } |
| // Same normalization for target_label == owning_label (rare: e.g. |
| // `(.) - other_label` form). |
| if (owning_label > 0 && translated.target_label == owning_label) { |
| translated.target_offset = |
| start + translated.target_offset - translated.section_offset; |
| translated.target_label = R::kSelfRelative; |
| } |
| TranslateAndApply(section, translated); |
| } |
| } |
| return start; |
| } |
| |
| // ----------------------------------------------------------------------------- |
| // Public API |
| // ----------------------------------------------------------------------------- |
| |
| void CoffWriter::AddText(const char* name, |
| intptr_t label, |
| const uint8_t* bytes, |
| intptr_t size, |
| const SharedObjectWriter::RelocationArray* relocations, |
| const SharedObjectWriter::SymbolDataArray* symbols) { |
| // First text portion creates the section. 64-byte alignment matches |
| // ImageWriter::kTextAlignment (= kObjectStartAlignment); the runtime's |
| // VerifyAlignment refuses to load a snapshot whose instructions image is |
| // not aligned to kObjectStartAlignment (64). |
| GetOrCreateSection(kSecText, ".text", |
| coff::PE_SCN_CNT_CODE | coff::PE_SCN_MEM_EXECUTE | |
| coff::PE_SCN_MEM_READ | coff::PE_SCN_ALIGN_64BYTES, |
| /*alignment_log2=*/6, /*is_bss=*/false); |
| |
| // Pad to 64-byte alignment before each text portion so _kDartSnapshotText |
| // lands on an object-start boundary. Use 0xCC (int3) as the padding byte so |
| // any accidental jump into the gap fails fast. |
| PadToAlignment(by_kind_[kSecText], kObjectStartAlignment, 0xCC); |
| |
| const intptr_t offset = AppendToSection(kSecText, bytes, size, relocations, |
| /*owning_label=*/label); |
| |
| TextPortion portion; |
| portion.symbol_name = name; |
| portion.label = label; |
| portion.offset_in_text = offset; |
| portion.size = size; |
| text_portions_.Add(portion); |
| |
| // The Code/Stub container symbol itself. This is the headline label |
| // (_kDartSnapshotText) that other code resolves against, so it has to be in |
| // the symbol table. |
| if (name != nullptr) { |
| AddExternalSymbol(name, label, kTextSectionNumber, /*value=*/offset); |
| } |
| |
| if (symbols != nullptr) { |
| for (const auto& s : *symbols) { |
| AddStaticSymbol(s.name, s.label, kTextSectionNumber, |
| /*value=*/offset + s.offset); |
| } |
| } |
| } |
| |
| void CoffWriter::AddROData( |
| const char* name, |
| intptr_t label, |
| const uint8_t* bytes, |
| intptr_t size, |
| const SharedObjectWriter::RelocationArray* relocations, |
| const SharedObjectWriter::SymbolDataArray* symbols) { |
| GetOrCreateSection(kSecRData, ".rdata", |
| coff::PE_SCN_CNT_INITIALIZED_DATA | coff::PE_SCN_MEM_READ | |
| coff::PE_SCN_ALIGN_64BYTES, |
| /*alignment_log2=*/6, /*is_bss=*/false); |
| |
| // Match ImageWriter::kRODataAlignment (= kObjectStartAlignment = 64) so |
| // each snapshot-data blob's headline symbol falls on an object-start |
| // boundary, satisfying ImageReader::VerifyAlignment. |
| PadToAlignment(by_kind_[kSecRData], kObjectStartAlignment, 0x00); |
| |
| const intptr_t offset = AppendToSection(kSecRData, bytes, size, relocations, |
| /*owning_label=*/label); |
| |
| if (name != nullptr) { |
| AddExternalSymbol(name, label, kRDataSectionNumber, /*value=*/offset); |
| } |
| if (symbols != nullptr) { |
| for (const auto& s : *symbols) { |
| AddStaticSymbol(s.name, s.label, kRDataSectionNumber, |
| /*value=*/offset + s.offset); |
| } |
| } |
| } |
| |
| // ----------------------------------------------------------------------------- |
| // Relocation translation |
| // ----------------------------------------------------------------------------- |
| |
| void CoffWriter::TranslateAndApply( |
| Section* section, |
| const SharedObjectWriter::Relocation& reloc) { |
| using R = SharedObjectWriter::Relocation; |
| |
| intptr_t target_label = reloc.target_label; |
| const intptr_t source_label = reloc.source_label; |
| const intptr_t target_offset = reloc.target_offset; |
| const intptr_t source_offset = reloc.source_offset; |
| const size_t size_in_bytes = reloc.size_in_bytes; |
| const intptr_t inline_offset = reloc.section_offset; |
| |
| ASSERT(size_in_bytes == 4 || size_in_bytes == 8); |
| |
| auto write_inline = [&](int64_t value) { |
| const intptr_t end = section->data.Position(); |
| ASSERT(inline_offset + static_cast<intptr_t>(size_in_bytes) <= end); |
| section->data.SetPosition(inline_offset); |
| section->data.bytes(&value, size_in_bytes); |
| section->data.SetPosition(end); |
| }; |
| |
| auto emit_reloc = [&](uint16_t type) { |
| Reloc r; |
| r.virtual_address = static_cast<uint32_t>(inline_offset); |
| r.symbol_label = target_label; |
| r.type = type; |
| section->relocs->Add(r); |
| }; |
| |
| // Both sides are constants (snapshot-absolute). Just bake the difference. |
| if (source_label == R::kSnapshotRelative && |
| target_label == R::kSnapshotRelative) { |
| write_inline(static_cast<int64_t>(target_offset - source_offset)); |
| return; |
| } |
| |
| // Self-relative both -- difference is independent of layout. |
| if (source_label == R::kSelfRelative && target_label == R::kSelfRelative) { |
| write_inline(static_cast<int64_t>(target_offset - source_offset)); |
| return; |
| } |
| |
| if (target_label == kBuildIdLabel && |
| !label_to_symbol_index_.HasKey(kBuildIdLabel)) { |
| write_inline(0); |
| return; |
| } |
| |
| // Relocation value is relocated address of the target plus an offset. |
| if (source_label == R::kSnapshotRelative) { |
| // Special-case kBuildIdLabel during stripped builds: emit the sentinel. |
| // (Image::kNoRelocatedAddress is private to Image; its value is 0.) |
| int64_t addend = static_cast<int64_t>(target_offset - source_offset); |
| if (target_label == R::kSelfRelative) { |
| target_label = kSelfSectionSymbolLabel; |
| addend += inline_offset; |
| } |
| ASSERT(target_label > 0 || target_label == kSelfSectionSymbolLabel); |
| write_inline(addend); |
| if (size_in_bytes == 8) { |
| emit_reloc(coff::PE_REL_AMD64_ADDR64); |
| } else { |
| emit_reloc(coff::PE_REL_AMD64_ADDR32); |
| } |
| return; |
| } |
| |
| // Relocation value is a relative offset to the target from the current |
| // address. |
| if (source_label == R::kSelfRelative && target_label > 0) { |
| // COFF REL32 computes: inline + sym - (reloc_addr + 4) |
| // Dart semantics: target_offset + sym - source_offset - reloc_addr |
| // So: inline = target_offset - source_offset + 4 |
| int64_t addend = static_cast<int64_t>(target_offset - source_offset + 4); |
| if (size_in_bytes == 8) { |
| // COFF x64 has no 64-bit PC-relative relocation. The linker patches the |
| // low 32 bits, so the encoded addend must fit in signed 32 bits and the |
| // upper 32 bits stay zero for snapshot-internal positive offsets. |
| RELEASE_ASSERT(Utils::IsInt(32, addend)); |
| addend &= 0xffffffff; |
| } |
| write_inline(addend); |
| emit_reloc(coff::PE_REL_AMD64_REL32); |
| return; |
| } |
| |
| // Less common: label - constant. We can still resolve if the label is |
| // self-relative (PC-relative) and target is a constant. Bake target_offset |
| // - source PC. But we don't know PC at write time, so emit relocation form. |
| if (source_label > 0 && target_label == R::kSnapshotRelative) { |
| // ADDR64(-source). COFF: inline + sym. Dart: target_offset - sym - |
| // source_offset. So: inline = target_offset - source_offset, then we |
| // need to negate the symbol contribution -- which COFF cannot do. |
| FATAL("CoffWriter: unsupported relocation form (label - snapshot)"); |
| } |
| |
| if (source_label > 0 && target_label > 0) { |
| FATAL("CoffWriter: unsupported relocation form (label - label)"); |
| } |
| |
| FATAL("CoffWriter: unhandled relocation (src=%" Pd ", tgt=%" Pd ")", |
| source_label, target_label); |
| } |
| |
| // ----------------------------------------------------------------------------- |
| // Symbol helpers |
| // ----------------------------------------------------------------------------- |
| |
| intptr_t CoffWriter::AddExternalSymbol(const char* name, |
| intptr_t label, |
| int16_t section_number, |
| int32_t value) { |
| CoffSym sym; |
| sym.name = name; |
| sym.value = value; |
| sym.section_number = section_number; // patched at AssignSectionNumbers |
| sym.type = coff::PE_SYM_TYPE_NULL; |
| sym.storage_class = coff::PE_SYM_CLASS_EXTERNAL; |
| sym.label = label; |
| symbols_->Add(sym); |
| const intptr_t index = symbols_->length(); // 1-based when read at write time |
| if (label != 0) { |
| label_to_symbol_index_.Insert(label, index); |
| } |
| return index; |
| } |
| |
| intptr_t CoffWriter::AddStaticSymbol(const char* name, |
| intptr_t label, |
| int16_t section_number, |
| int32_t value) { |
| CoffSym sym; |
| sym.name = DisambiguateStaticSymbolName(name); |
| sym.value = value; |
| sym.section_number = section_number; |
| sym.type = coff::PE_SYM_TYPE_NULL; |
| sym.storage_class = coff::PE_SYM_CLASS_STATIC; |
| sym.label = label; |
| symbols_->Add(sym); |
| const intptr_t index = symbols_->length(); |
| if (label != 0) { |
| label_to_symbol_index_.Insert(label, index); |
| } |
| return index; |
| } |
| |
| const char* CoffWriter::DisambiguateStaticSymbolName(const char* name) { |
| if (name == nullptr) { |
| return nullptr; |
| } |
| auto* const pair = static_symbol_usage_count_.Lookup(name); |
| if (pair == nullptr) { |
| static_symbol_usage_count_.Insert({name, 1}); |
| return name; |
| } |
| return OS::SCreate(zone_, "%s (#%" Pd ")", name, ++pair->value); |
| } |
| |
| intptr_t CoffWriter::SymbolIndexForLabel(intptr_t label) const { |
| ASSERT(label != 0); |
| ASSERT(label_to_symbol_index_.HasKey(label)); |
| return label_to_symbol_index_.Lookup(label); |
| } |
| |
| intptr_t CoffWriter::SymbolIndexForLabelOrZero(intptr_t label) { |
| if (label_to_symbol_index_.HasKey(label)) { |
| return label_to_symbol_index_.Lookup(label); |
| } |
| return 0; |
| } |
| |
| void CoffWriter::CreateSectionSymbol(Section* section) { |
| ASSERT(section->section_number != 0); |
| CoffSym sym; |
| sym.value = 0; |
| sym.section_number = static_cast<int16_t>(section->section_number); |
| sym.type = coff::PE_SYM_TYPE_NULL; |
| sym.storage_class = coff::PE_SYM_CLASS_STATIC; |
| sym.num_aux = 1; |
| sym.name = OS::SCreate(zone_, "%s", section->name); |
| // Auxiliary record holds section data. |
| sym.aux = new (zone_) ZoneGrowableArray<uint8_t>(zone_, 18); |
| coff::ImageAuxSymbolSection aux = {}; |
| aux.Length = section->is_bss |
| ? static_cast<uint32_t>(section->bss_size) |
| : static_cast<uint32_t>(section->data.bytes_written()); |
| aux.NumberOfRelocations = EncodedRelocationCount(section->relocs->length()); |
| aux.NumberOfLinenumbers = 0; |
| aux.CheckSum = 0; |
| aux.Number = static_cast<int16_t>(section->section_number); |
| aux.Selection = 0; |
| for (size_t i = 0; i < sizeof(aux); i++) { |
| sym.aux->Add(reinterpret_cast<uint8_t*>(&aux)[i]); |
| } |
| symbols_->Add(sym); |
| section->section_symbol = symbols_->length(); |
| } |
| |
| // ----------------------------------------------------------------------------- |
| // BSS sections (mirror ElfWriter::CreateBSS) |
| // ----------------------------------------------------------------------------- |
| |
| void CoffWriter::EmitBssSections() { |
| if (text_portions_.is_empty()) return; |
| |
| Section* const bss = GetOrCreateSection( |
| kSecBss, ".bss", |
| coff::PE_SCN_CNT_UNINITIALIZED_DATA | coff::PE_SCN_MEM_READ | |
| coff::PE_SCN_MEM_WRITE | coff::PE_SCN_ALIGN_16BYTES, |
| /*alignment_log2=*/4, /*is_bss=*/true); |
| AddStaticSymbol(kSnapshotBssAsmSymbol, kIsolateBssLabel, kBssSectionNumber, |
| /*value=*/0); |
| bss->bss_size = BSS::kIsolateGroupEntryCount * compiler::target::kWordSize; |
| } |
| |
| // ----------------------------------------------------------------------------- |
| // SEH .pdata / .xdata |
| // ----------------------------------------------------------------------------- |
| |
| void CoffWriter::EmitUnwindData() { |
| Section* const text = by_kind_[kSecText]; |
| if (text == nullptr) return; |
| |
| Section* const pdata = |
| GetOrCreateSection(kSecPdata, ".pdata", |
| coff::PE_SCN_CNT_INITIALIZED_DATA | |
| coff::PE_SCN_MEM_READ | coff::PE_SCN_ALIGN_4BYTES, |
| /*alignment_log2=*/2, /*is_bss=*/false); |
| Section* const xdata = |
| GetOrCreateSection(kSecXdata, ".xdata", |
| coff::PE_SCN_CNT_INITIALIZED_DATA | |
| coff::PE_SCN_MEM_READ | coff::PE_SCN_ALIGN_4BYTES, |
| /*alignment_log2=*/2, /*is_bss=*/false); |
| |
| auto read_u32 = [](const DebugInfoStream* in, intptr_t offset) -> uint32_t { |
| return LoadUnaligned( |
| reinterpret_cast<const uint32_t*>(in->buffer() + offset)); |
| }; |
| |
| auto parse_standard_prologue = [&](uint32_t begin, |
| ParsedUnwindPrologue* prologue) -> bool { |
| const intptr_t length = text->data.Position(); |
| if (begin + 4 > static_cast<uint32_t>(length)) return false; |
| if (text->data.LoadU1(begin) != 0x55 || // push rbp |
| text->data.LoadU1(begin + 1) != 0x48 || |
| text->data.LoadU1(begin + 2) != 0x89 || |
| text->data.LoadU1(begin + 3) != 0xe5) { // mov rbp, rsp |
| return false; |
| } |
| |
| uint32_t cursor = begin + 4; |
| uint32_t stack_size = 0; |
| if (cursor + 4 <= static_cast<uint32_t>(length) && |
| text->data.LoadU1(cursor) == 0x48 && |
| text->data.LoadU1(cursor + 1) == 0x83 && |
| text->data.LoadU1(cursor + 2) == 0xec) { |
| stack_size = text->data.LoadU1(cursor + 3); |
| cursor += 4; |
| } else if (cursor + 7 <= static_cast<uint32_t>(length) && |
| text->data.LoadU1(cursor) == 0x48 && |
| text->data.LoadU1(cursor + 1) == 0x81 && |
| text->data.LoadU1(cursor + 2) == 0xec) { |
| stack_size = read_u32(&text->data, cursor + 3); |
| cursor += 7; |
| } |
| |
| if ((stack_size % 8) != 0) return false; |
| prologue->stack_size = stack_size; |
| prologue->prologue_size = static_cast<uint8_t>(cursor - begin); |
| return true; |
| }; |
| |
| auto has_standard_epilogue = [&](uint32_t begin, uint32_t end) -> bool { |
| const uint32_t length = static_cast<uint32_t>(text->data.Position()); |
| end = Utils::Minimum<uint32_t>(end, length); |
| if (end <= begin) return false; |
| |
| for (uint32_t cursor = begin; cursor + 4 <= end; cursor++) { |
| if (text->data.LoadU1(cursor + 0) != 0x48 || |
| text->data.LoadU1(cursor + 1) != 0x89 || |
| text->data.LoadU1(cursor + 2) != 0xec || |
| text->data.LoadU1(cursor + 3) != 0x5d) { |
| continue; |
| } |
| if (cursor + 4 >= end) return false; |
| switch (text->data.LoadU1(cursor + 4)) { |
| case 0xc3: // ret |
| case 0xe9: // jmp rel32 |
| case 0xeb: // jmp rel8 |
| case 0xff: // jmp r/m64 |
| return true; |
| default: |
| break; |
| } |
| } |
| return false; |
| }; |
| |
| ZoneGrowableArray<FunctionUnwindRange> functions(zone_, 128); |
| auto add_function = [&](uint32_t begin, uint32_t size) { |
| if (size == 0 || begin >= static_cast<uint32_t>(text->data.Position())) { |
| return; |
| } |
| FunctionUnwindRange range; |
| range.begin = begin; |
| range.end = Utils::Minimum<uint32_t>( |
| begin + size, static_cast<uint32_t>(text->data.Position())); |
| if (range.end <= range.begin) return; |
| if (!parse_standard_prologue(range.begin, &range.prologue)) return; |
| if (!has_standard_epilogue(range.begin, range.end)) return; |
| for (intptr_t i = 0; i < functions.length(); i++) { |
| if (functions[i].begin == range.begin) return; |
| } |
| functions.Add(range); |
| }; |
| |
| if (dwarf() != nullptr && symbols_ != nullptr) { |
| const auto& codes = dwarf()->codes(); |
| for (intptr_t i = 0; i < codes.length(); i++) { |
| const Code& code = *(codes[i]); |
| const intptr_t label = dwarf()->LookupCodeLabel(code); |
| if (label <= 0 || !label_to_symbol_index_.HasKey(label)) continue; |
| const intptr_t sym_idx = label_to_symbol_index_.Lookup(label); |
| ASSERT(sym_idx > 0 && sym_idx <= symbols_->length()); |
| const auto& sym = (*symbols_)[sym_idx - 1]; |
| ASSERT(sym.section_number == kTextSectionNumber); |
| add_function(static_cast<uint32_t>(sym.value), |
| static_cast<uint32_t>(code.Size())); |
| } |
| } |
| |
| if (functions.length() == 0) return; |
| functions.Sort(CompareFunctionUnwindRange); |
| |
| auto append_unwind_code = [&](uint8_t code_offset, uint8_t unwind_op, |
| uint8_t op_info) { |
| xdata->data.u1(code_offset); |
| xdata->data.u1(static_cast<uint8_t>((op_info << 4) | (unwind_op & 0x0f))); |
| }; |
| |
| auto emit_unwind_info = |
| [&](const ParsedUnwindPrologue& prologue) -> uint32_t { |
| const uint32_t xdata_offset = static_cast<uint32_t>(xdata->data.Position()); |
| xdata->data.u1(coff::kUnwindVersion); |
| xdata->data.u1(prologue.prologue_size); |
| const intptr_t code_count_offset = xdata->data.Position(); |
| xdata->data.u1(0); // CountOfCodes, patched below. |
| xdata->data.u1(coff::kRegRbp); // FrameRegister=RBP. |
| |
| uint8_t code_count = 0; |
| if (prologue.stack_size > 0) { |
| const uint32_t stack_size_words = prologue.stack_size / 8; |
| if (Utils::IsUint(4, stack_size_words - 1)) { |
| append_unwind_code(prologue.prologue_size, coff::UWOP_ALLOC_SMALL, |
| static_cast<uint8_t>(stack_size_words - 1)); |
| code_count += 1; |
| } else if (Utils::IsUint(16, stack_size_words)) { |
| append_unwind_code(prologue.prologue_size, coff::UWOP_ALLOC_LARGE, 0); |
| xdata->data.u2(static_cast<uint16_t>(stack_size_words)); |
| code_count += 2; |
| } else { |
| append_unwind_code(prologue.prologue_size, coff::UWOP_ALLOC_LARGE, 1); |
| xdata->data.u4(prologue.stack_size); |
| code_count += 3; |
| } |
| } |
| append_unwind_code(4, coff::UWOP_SET_FPREG, 0); |
| code_count += 1; |
| append_unwind_code(1, coff::UWOP_PUSH_NONVOL, coff::kRegRbp); |
| code_count += 1; |
| |
| xdata->data.StoreU1(code_count_offset, code_count); |
| xdata->data.Align(4); |
| return xdata_offset; |
| }; |
| |
| ZoneGrowableArray<SharedUnwindInfo> shared_unwind_infos(zone_, 16); |
| auto unwind_info_for = [&](const ParsedUnwindPrologue& prologue) -> uint32_t { |
| for (intptr_t i = 0; i < shared_unwind_infos.length(); i++) { |
| const SharedUnwindInfo& shared = shared_unwind_infos[i]; |
| if (shared.prologue.stack_size == prologue.stack_size && |
| shared.prologue.prologue_size == prologue.prologue_size) { |
| return shared.xdata_offset; |
| } |
| } |
| |
| SharedUnwindInfo shared; |
| shared.prologue = prologue; |
| shared.xdata_offset = emit_unwind_info(prologue); |
| shared_unwind_infos.Add(shared); |
| return shared.xdata_offset; |
| }; |
| |
| auto u4_with_reloc = [](Section* section, intptr_t label, uint16_t type, |
| uint32_t value) { |
| const intptr_t offset = section->data.Position(); |
| section->relocs->Add({static_cast<uint32_t>(offset), label, type}); |
| section->data.u4(value); |
| }; |
| |
| for (intptr_t i = 0; i < functions.length(); i++) { |
| const FunctionUnwindRange& fn = functions[i]; |
| const uint32_t xdata_offset = unwind_info_for(fn.prologue); |
| u4_with_reloc(pdata, kTextSectionSymbolLabel, coff::PE_REL_AMD64_ADDR32NB, |
| fn.begin); |
| u4_with_reloc(pdata, kTextSectionSymbolLabel, coff::PE_REL_AMD64_ADDR32NB, |
| fn.end); |
| u4_with_reloc(pdata, kXdataSectionSymbolLabel, coff::PE_REL_AMD64_ADDR32NB, |
| xdata_offset); |
| } |
| } |
| |
| // ----------------------------------------------------------------------------- |
| // .drectve |
| // ----------------------------------------------------------------------------- |
| |
| void CoffWriter::EmitDrectve() { |
| if (type_ != Type::Snapshot) return; |
| Section* const dr = |
| GetOrCreateSection(kSecDrectve, ".drectve", |
| coff::PE_SCN_LNK_INFO | coff::PE_SCN_LNK_REMOVE | |
| coff::PE_SCN_ALIGN_1BYTES, |
| /*alignment_log2=*/0, /*is_bss=*/false); |
| |
| // Export the snapshot data symbols directly. GetProcAddress can resolve data |
| // exports, so no getter thunks are needed. |
| const char* const directives = " /EXPORT:" kSnapshotDataCSymbol |
| ",DATA" |
| " /EXPORT:" kSnapshotTextCSymbol ",DATA"; |
| const intptr_t len = strlen(directives); |
| dr->data.bytes(directives, len); |
| } |
| |
| // ----------------------------------------------------------------------------- |
| // CodeView (Phase 7) |
| // ----------------------------------------------------------------------------- |
| |
| void CoffWriter::EmitCodeView() { |
| if (dwarf_ == nullptr) return; |
| Section* const debug_s = GetOrCreateSection( |
| kSecDebugS, ".debug$S", |
| coff::PE_SCN_CNT_INITIALIZED_DATA | coff::PE_SCN_MEM_READ | |
| coff::PE_SCN_MEM_DISCARDABLE | coff::PE_SCN_ALIGN_1BYTES, |
| /*alignment_log2=*/0, /*is_bss=*/false); |
| Section* const debug_t = GetOrCreateSection( |
| kSecDebugT, ".debug$T", |
| coff::PE_SCN_CNT_INITIALIZED_DATA | coff::PE_SCN_MEM_READ | |
| coff::PE_SCN_MEM_DISCARDABLE | coff::PE_SCN_ALIGN_1BYTES, |
| /*alignment_log2=*/0, /*is_bss=*/false); |
| codeview_ = |
| new (zone_) CodeViewBuilder(zone_, this, &debug_s->data, &debug_t->data); |
| codeview_->Build(); |
| } |
| |
| // ----------------------------------------------------------------------------- |
| // Section number / section symbol assignment |
| // ----------------------------------------------------------------------------- |
| |
| void CoffWriter::AssignSectionNumbersAndSymbols() { |
| // Section numbers are 1-based. Patch all symbols that referenced a section |
| // via the negative-sentinel scheme. |
| for (intptr_t i = 0; i < all_sections_.length(); i++) { |
| all_sections_[i]->section_number = i + 1; |
| } |
| Section* text = by_kind_[kSecText]; |
| Section* rdata = by_kind_[kSecRData]; |
| Section* bss = by_kind_[kSecBss]; |
| |
| for (intptr_t i = 0; i < symbols_->length(); i++) { |
| auto& s = (*symbols_)[i]; |
| if (s.section_number == kTextSectionNumber && text != nullptr) { |
| s.section_number = static_cast<int16_t>(text->section_number); |
| } else if (s.section_number == kRDataSectionNumber && rdata != nullptr) { |
| s.section_number = static_cast<int16_t>(rdata->section_number); |
| } else if (s.section_number == kBssSectionNumber && bss != nullptr) { |
| s.section_number = static_cast<int16_t>(bss->section_number); |
| } |
| } |
| |
| // Create section symbols and append them to the symbol table. |
| for (intptr_t i = 0; i < all_sections_.length(); i++) { |
| CreateSectionSymbol(all_sections_[i]); |
| } |
| |
| // Resolve sentinel section labels used inside our own emitters by inserting |
| // them into the label map after section symbols are created. |
| if (by_kind_[kSecText] != nullptr) { |
| label_to_symbol_index_.Insert(kTextSectionSymbolLabel, |
| by_kind_[kSecText]->section_symbol); |
| } |
| if (by_kind_[kSecXdata] != nullptr) { |
| label_to_symbol_index_.Insert(kXdataSectionSymbolLabel, |
| by_kind_[kSecXdata]->section_symbol); |
| } |
| } |
| |
| // ----------------------------------------------------------------------------- |
| // Layout + write |
| // ----------------------------------------------------------------------------- |
| |
| void CoffWriter::ComputeLayoutAndWrite() { |
| // Compute file offsets: |
| // file_header |
| // section_headers[] |
| // per section: raw data (if !is_bss), then relocations |
| // symbol_table |
| // string_table |
| const intptr_t num_sections = all_sections_.length(); |
| intptr_t cursor = sizeof(coff::ImageFileHeader) + |
| num_sections * sizeof(coff::ImageSectionHeader); |
| |
| for (auto* s : all_sections_) { |
| if (!s->is_bss) { |
| // Section raw data alignment within the file is 4 (LLVM convention). |
| cursor = Utils::RoundUp(cursor, 4); |
| s->raw_data_offset = cursor; |
| cursor += s->data.bytes_written(); |
| } else { |
| s->raw_data_offset = 0; |
| } |
| if (s->relocs->length() > 0) { |
| cursor = Utils::RoundUp(cursor, 4); |
| s->reloc_offset = cursor; |
| cursor += RelocationRecordCountOnDisk(s->relocs->length()) * |
| sizeof(coff::ImageRelocation); |
| } else { |
| s->reloc_offset = 0; |
| } |
| } |
| |
| cursor = Utils::RoundUp(cursor, 4); |
| const intptr_t symtab_offset = cursor; |
| // Each symbol entry (and each aux entry) is 18 bytes. |
| intptr_t symbol_count = 0; |
| // parent1_to_disk0[i] gives the 0-based on-disk symbol-table index for the |
| // symbol stored at parent-only index i (0-based) in symbols_. Values tracked |
| // elsewhere (label_to_symbol_index_ entries, Section::section_symbol) |
| // are 1-based parent-only indices; convert via parent1_to_disk0[v - 1] when |
| // writing a SymbolTableIndex into a relocation. The two diverge whenever a |
| // symbol with num_aux>0 (i.e. our section symbols) precedes the lookup. |
| ZoneGrowableArray<uint32_t> parent1_to_disk0(zone_, symbols_->length()); |
| for (intptr_t i = 0; i < symbols_->length(); i++) { |
| parent1_to_disk0.Add(static_cast<uint32_t>(symbol_count)); |
| symbol_count += 1 + (*symbols_)[i].num_aux; |
| } |
| cursor += symbol_count * 18; |
| auto disk_index_of = [&](intptr_t parent1) -> uint32_t { |
| ASSERT(parent1 >= 1 && parent1 <= symbols_->length()); |
| return parent1_to_disk0[parent1 - 1]; |
| }; |
| // String table begins immediately after symbol table; size prefix is |
| // included in the size word itself. |
| |
| // --- Write file header --- |
| coff::ImageFileHeader fh = {}; |
| fh.Machine = coff::PE_FILE_MACHINE_AMD64; |
| fh.NumberOfSections = static_cast<uint16_t>(num_sections); |
| fh.TimeDateStamp = 0; |
| fh.PointerToSymbolTable = static_cast<uint32_t>(symtab_offset); |
| fh.NumberOfSymbols = static_cast<uint32_t>(symbol_count); |
| fh.SizeOfOptionalHeader = 0; |
| fh.Characteristics = coff::PE_FILE_LARGE_ADDRESS_AWARE; |
| unwrapped_stream_->WriteBytes(&fh, sizeof(fh)); |
| |
| // --- Write section headers --- |
| for (auto* s : all_sections_) { |
| coff::ImageSectionHeader sh = {}; |
| for (intptr_t i = 0; i < 8; i++) { |
| sh.Name[i] = s->name[i]; |
| } |
| sh.VirtualSize = 0; |
| sh.VirtualAddress = 0; |
| if (s->is_bss) { |
| sh.SizeOfRawData = static_cast<uint32_t>(s->bss_size); |
| sh.PointerToRawData = 0; |
| } else { |
| sh.SizeOfRawData = static_cast<uint32_t>(s->data.bytes_written()); |
| sh.PointerToRawData = static_cast<uint32_t>(s->raw_data_offset); |
| } |
| sh.PointerToRelocations = static_cast<uint32_t>(s->reloc_offset); |
| sh.PointerToLinenumbers = 0; |
| const bool has_relocation_overflow = |
| UsesRelocationCountOverflow(s->relocs->length()); |
| sh.NumberOfRelocations = EncodedRelocationCount(s->relocs->length()); |
| sh.NumberOfLinenumbers = 0; |
| sh.Characteristics = s->characteristics; |
| if (has_relocation_overflow) { |
| sh.Characteristics |= coff::PE_SCN_LNK_NRELOC_OVFL; |
| } |
| unwrapped_stream_->WriteBytes(&sh, sizeof(sh)); |
| } |
| |
| // --- Write each section's body + reloc table --- |
| intptr_t written = unwrapped_stream_->Position(); |
| for (auto* s : all_sections_) { |
| if (!s->is_bss) { |
| unwrapped_stream_->Align(4); |
| written = unwrapped_stream_->Position(); |
| ASSERT(written == s->raw_data_offset); |
| unwrapped_stream_->WriteBytes(s->data.buffer(), s->data.bytes_written()); |
| written += s->data.bytes_written(); |
| } |
| if (s->relocs->length() > 0) { |
| unwrapped_stream_->Align(4); |
| written = unwrapped_stream_->Position(); |
| ASSERT(written == s->reloc_offset); |
| if (UsesRelocationCountOverflow(s->relocs->length())) { |
| coff::ImageRelocation overflow = {}; |
| // The overflow count includes this synthetic relocation itself. COFF |
| // readers skip it and expose VirtualAddress - 1 real relocations. |
| ASSERT(s->relocs->length() <= static_cast<intptr_t>(kMaxUint32) - 1); |
| overflow.VirtualAddress = |
| static_cast<uint32_t>(s->relocs->length() + 1); |
| unwrapped_stream_->WriteBytes(&overflow, sizeof(overflow)); |
| written += sizeof(overflow); |
| } |
| for (const auto& r : *s->relocs) { |
| coff::ImageRelocation cr; |
| cr.VirtualAddress = r.virtual_address; |
| // Special sentinel: kSelfSectionSymbolLabel means "the section symbol |
| // for the section this relocation lives in". Used by snapshot- |
| // relative-to-self relocs that need to reference their own section |
| // without knowing its index at AppendToSection time. |
| if (r.symbol_label == kSelfSectionSymbolLabel) { |
| cr.SymbolTableIndex = disk_index_of(s->section_symbol); |
| } else { |
| cr.SymbolTableIndex = |
| disk_index_of(SymbolIndexForLabel(r.symbol_label)); |
| } |
| cr.Type = r.type; |
| unwrapped_stream_->WriteBytes(&cr, sizeof(cr)); |
| written += sizeof(cr); |
| } |
| } |
| } |
| |
| // Align before symbol table. |
| unwrapped_stream_->Align(4); |
| written = unwrapped_stream_->Position(); |
| ASSERT(written == symtab_offset); |
| |
| // --- Write symbol table --- |
| for (const auto& s : *symbols_) { |
| coff::ImageSymbol is = {}; |
| const char* name = s.name; |
| if (name == nullptr || strlen(name) <= 8) { |
| // Inline name in the 8-byte field. |
| memset(is.N.ShortName, 0, 8); |
| if (name != nullptr) { |
| for (intptr_t i = 0; i < 8 && name[i] != '\0'; i++) { |
| is.N.ShortName[i] = name[i]; |
| } |
| } |
| } else { |
| is.N.LongName.Zeroes = 0; |
| is.N.LongName.Offset = InternString(name); |
| } |
| is.Value = s.value; |
| is.SectionNumber = s.section_number; |
| is.Type = s.type; |
| is.StorageClass = s.storage_class; |
| is.NumberOfAuxSymbols = s.num_aux; |
| unwrapped_stream_->WriteBytes(&is, sizeof(is)); |
| written += sizeof(is); |
| if (s.aux != nullptr) { |
| ASSERT(s.aux->length() == 18 * s.num_aux); |
| unwrapped_stream_->WriteBytes(&(*s.aux)[0], s.aux->length()); |
| written += s.aux->length(); |
| } |
| } |
| |
| // --- Write string table --- |
| uint32_t str_table_size = static_cast<uint32_t>(string_table_->length()) + 4; |
| unwrapped_stream_->WriteFixed(str_table_size); |
| if (string_table_->length() > 0) { |
| unwrapped_stream_->WriteBytes(&(*string_table_)[0], |
| string_table_->length()); |
| } |
| } |
| |
| // ----------------------------------------------------------------------------- |
| // Finalize |
| // ----------------------------------------------------------------------------- |
| |
| void CoffWriter::Finalize() { |
| EmitBssSections(); |
| EmitUnwindData(); |
| EmitDrectve(); |
| EmitCodeView(); |
| |
| AssignSectionNumbersAndSymbols(); |
| |
| // If CodeView builder accumulated relocations that needed the .text section |
| // symbol, those got resolved when we inserted the section symbol into the |
| // label map. Nothing else to do here. |
| |
| ComputeLayoutAndWrite(); |
| } |
| |
| } // namespace dart |
| |
| #endif // DART_PRECOMPILER |