blob: edc939b7f1a240eab9c98e324aea06df537fccf2 [file] [edit]
// 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_