| // 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. | |
| #ifndef RUNTIME_VM_COFF_H_ | |
| #define RUNTIME_VM_COFF_H_ | |
| #include "platform/globals.h" | |
| #if defined(DART_PRECOMPILER) | |
| #include "platform/coff.h" | |
| #include "vm/allocation.h" | |
| #include "vm/datastream.h" | |
| #include "vm/debug_info_stream.h" | |
| #include "vm/growable_array.h" | |
| #include "vm/hash_map.h" | |
| #include "vm/so_writer.h" | |
| #include "vm/zone.h" | |
| namespace dart { | |
| class CodeViewBuilder; | |
| template <typename T> | |
| class Trie; | |
| // Emits a PE/COFF .obj file consumed by MSVC link.exe to build a | |
| // Windows DLL. This is the third subclass of SharedObjectWriter, alongside | |
| // ElfWriter and MachOWriter, and short-circuits the historical | |
| // gen_snapshot -> .S -> llvm-mc -> .obj pipeline on Windows. | |
| // | |
| // Scope: x86-64 only, .obj output only. The linker still produces the .dll | |
| // and (with /DEBUG) the .pdb. | |
| class CoffWriter : public SharedObjectWriter { | |
| public: | |
| CoffWriter(Zone* zone, | |
| BaseWriteStream* stream, | |
| Type type, | |
| Dwarf* dwarf = nullptr); | |
| static constexpr intptr_t kPageSize = 4096; | |
| intptr_t page_size() const override { return kPageSize; } | |
| Output output() const override { return Output::Coff; } | |
| void AddText(const char* name, | |
| intptr_t label, | |
| const uint8_t* bytes, | |
| intptr_t size, | |
| const SharedObjectWriter::RelocationArray* relocations, | |
| const SharedObjectWriter::SymbolDataArray* symbols) override; | |
| void AddROData(const char* name, | |
| intptr_t label, | |
| const uint8_t* bytes, | |
| intptr_t size, | |
| const SharedObjectWriter::RelocationArray* relocations, | |
| const SharedObjectWriter::SymbolDataArray* symbols) override; | |
| void Finalize() override; | |
| const GrowableArray<const Code*>& debug_codes(); | |
| const GrowableArray<const Script*>& debug_scripts(); | |
| const Trie<const char>* debug_deobfuscation_trie(); | |
| intptr_t LookupDebugCodeLabel(const Code& code); | |
| intptr_t LookupDebugScript(const Script& script); | |
| void AssertConsistency(const SharedObjectWriter* debug) const override { | |
| if (debug->AsCoffWriter() == nullptr) { | |
| FATAL("Expected both snapshot and debug to be COFF"); | |
| } | |
| } | |
| const CoffWriter* AsCoffWriter() const override { return this; } | |
| // Sentinel `symbol_label` value used inside Reloc to mean "the section | |
| // symbol of the section this relocation lives in". Resolved at write time | |
| // by looking up `section->section_symbol` directly, since the section that | |
| // owns the relocation isn't known until the symbol-table layout phase. | |
| // Negative enough to never collide with VM-internal labels (>0) or the | |
| // section-symbol labels below. | |
| static constexpr intptr_t kSelfSectionSymbolLabel = -1000; | |
| // Section index slots (0 means "not present"). Resolved during Finalize. | |
| enum SectionKind { | |
| kSecText = 0, | |
| kSecRData, | |
| kSecBss, | |
| kSecDrectve, | |
| kSecPdata, | |
| kSecXdata, | |
| kSecDebugS, | |
| kSecDebugT, | |
| kNumSections, | |
| }; | |
| private: | |
| static constexpr int16_t kTextSectionNumber = -1; | |
| static constexpr int16_t kRDataSectionNumber = -2; | |
| static constexpr int16_t kBssSectionNumber = -3; | |
| static constexpr intptr_t kTextSectionSymbolLabel = -10; | |
| static constexpr intptr_t kXdataSectionSymbolLabel = -11; | |
| // --------------------------------------------------------------------------- | |
| // Internal data model | |
| // --------------------------------------------------------------------------- | |
| struct Reloc { | |
| uint32_t virtual_address; // byte offset within section | |
| intptr_t symbol_label; // resolved to symbol index at finalize | |
| uint16_t type; // PE_REL_AMD64_* | |
| }; | |
| struct Section : public ZoneObject { | |
| explicit Section(Zone* zone) | |
| : data_stream(zone, 1024), data(zone, &data_stream) {} | |
| char name[9] = {0}; | |
| uint32_t characteristics = 0; | |
| intptr_t alignment_log2 = 0; // 0 = 1, 1 = 2, ..., 4 = 16 byte | |
| bool is_bss = false; // no PointerToRawData when true | |
| // Raw bytes for the section (unused for .bss). | |
| ZoneWriteStream data_stream; | |
| DebugInfoStream data; | |
| // BSS section size when is_bss=true. | |
| intptr_t bss_size = 0; | |
| // Relocations to emit in the section's reloc table. | |
| ZoneGrowableArray<Reloc>* relocs = nullptr; | |
| // Computed during finalize: | |
| intptr_t section_number = 0; // 1-based | |
| intptr_t section_symbol = 0; // 1-based symbol-table index of section sym | |
| intptr_t raw_data_offset = 0; | |
| intptr_t reloc_offset = 0; | |
| }; | |
| // One PE_SYMBOL entry (plus optional auxiliary follow-up entries). | |
| struct CoffSym { | |
| const char* name = nullptr; // may be > 8 chars; goes to string table | |
| int32_t value = 0; | |
| // PE section number. Before AssignSectionNumbersAndSymbols, this can hold | |
| // one of the k*SectionNumber sentinels above. | |
| int16_t section_number = coff::PE_SYM_UNDEFINED; | |
| uint16_t type = 0; | |
| uint8_t storage_class = 0; | |
| uint8_t num_aux = 0; // count of aux entries that follow | |
| intptr_t label = 0; // VM-internal label, 0 if none | |
| // Auxiliary entries that immediately follow this symbol. Each is 18 bytes | |
| // (sizeof(PE_SYMBOL)). Stored as raw byte arrays. | |
| ZoneGrowableArray<uint8_t>* aux = nullptr; | |
| }; | |
| // --------------------------------------------------------------------------- | |
| // Section helpers | |
| // --------------------------------------------------------------------------- | |
| Section* GetOrCreateSection(SectionKind kind, | |
| const char* name, | |
| uint32_t characteristics, | |
| intptr_t alignment_log2, | |
| bool is_bss); | |
| // Pads the section out to the next `alignment`-byte boundary using | |
| // `pad_byte` (0xCC for .text so accidental fall-through traps, 0x00 for | |
| // data sections). | |
| void PadToAlignment(Section* section, intptr_t alignment, uint8_t pad_byte); | |
| // Adds bytes + relocations to the given section, returning the byte offset at | |
| // which the new content starts (which becomes the value of any symbol added). | |
| // `owning_label` is the headline label for this batch. Positive | |
| // `source_label` values in `relocs` that match it are treated as | |
| // self-relative, since they refer to the start of the bytes we just wrote. | |
| intptr_t AppendToSection(SectionKind kind, | |
| const uint8_t* bytes, | |
| intptr_t size, | |
| const SharedObjectWriter::RelocationArray* relocs, | |
| intptr_t owning_label = 0); | |
| // Translate one VM relocation into one COFF relocation entry, applying the | |
| // inline addend at `inline_offset` within `section_data`. | |
| void TranslateAndApply(Section* section, | |
| const SharedObjectWriter::Relocation& vm_reloc); | |
| // --------------------------------------------------------------------------- | |
| // Symbol helpers | |
| // --------------------------------------------------------------------------- | |
| // Create the canonical section symbol (PE_SYM_CLASS_STATIC with an aux | |
| // section record) for `section`. | |
| void CreateSectionSymbol(Section* section); | |
| // Add an external symbol bound to a label. Returns the assigned symbol | |
| // table index (1-based). | |
| intptr_t AddExternalSymbol(const char* name, | |
| intptr_t label, | |
| int16_t section_number, | |
| int32_t value); | |
| // Add a static (file-local) symbol for a label or named offset. | |
| intptr_t AddStaticSymbol(const char* name, | |
| intptr_t label, | |
| int16_t section_number, | |
| int32_t value); | |
| const char* DisambiguateStaticSymbolName(const char* name); | |
| // Look up the symbol-table index for `label`. FATAL if not present. | |
| intptr_t SymbolIndexForLabel(intptr_t label) const; | |
| // Either look up the symbol index for `label`, or synthesize an undefined | |
| // external symbol if the label is unknown (used for build-id during stripped | |
| // builds). Returns 0 to indicate "no relocation, treat as constant". | |
| intptr_t SymbolIndexForLabelOrZero(intptr_t label); | |
| // --------------------------------------------------------------------------- | |
| // Finalize sub-phases | |
| // --------------------------------------------------------------------------- | |
| void EmitBssSections(); | |
| void EmitUnwindData(); | |
| void EmitDrectve(); | |
| void EmitCodeView(); | |
| void AssignSectionNumbersAndSymbols(); | |
| void ComputeLayoutAndWrite(); | |
| // --------------------------------------------------------------------------- | |
| // String table helper. Returns the (1-based, offset-from-start-of-table) | |
| // string offset. Strings of <=8 bytes go inline in the symbol entry by the | |
| // caller instead. | |
| uint32_t InternString(const char* str); | |
| // --------------------------------------------------------------------------- | |
| // Storage | |
| // --------------------------------------------------------------------------- | |
| ZoneGrowableArray<Section*> all_sections_; // creation order | |
| Section* by_kind_[kNumSections] = {}; | |
| ZoneGrowableArray<CoffSym>* symbols_ = nullptr; | |
| IntMap<intptr_t> label_to_symbol_index_; // label -> 1-based index | |
| CStringIntMap static_symbol_usage_count_; | |
| // String table accumulator. First 4 bytes are the size, leading byte cannot | |
| // be referenced (offsets start at 4). We store the payload only; the size | |
| // is prepended at write time. | |
| ZoneGrowableArray<uint8_t>* string_table_ = nullptr; | |
| // Snapshot text section labels we discover via AddText, used to size the | |
| // .pdata block and emit unwind data. | |
| struct TextPortion { | |
| const char* symbol_name; | |
| intptr_t label; | |
| intptr_t offset_in_text; | |
| intptr_t size; | |
| }; | |
| ZoneGrowableArray<TextPortion> text_portions_; | |
| // Tracks where each appended .text portion lives so the unwind data | |
| // generator can reference it by section-relative offset. These are filled | |
| // in by AppendToSection for .text. | |
| CodeViewBuilder* codeview_ = nullptr; | |
| friend class CodeViewBuilder; | |
| DISALLOW_COPY_AND_ASSIGN(CoffWriter); | |
| }; | |
| } // namespace dart | |
| #endif // DART_PRECOMPILER | |
| #endif // RUNTIME_VM_COFF_H_ |