blob: 292f2869f639b0ca39be05e2d3d2e7b44b12ef1b [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_PLATFORM_COFF_H_
#define RUNTIME_PLATFORM_COFF_H_
#include "platform/globals.h"
#if defined(DART_PRECOMPILER)
namespace dart {
namespace coff {
#pragma pack(push, 1)
// -----------------------------------------------------------------------------
// PE/COFF file format constants and structures
//
// Reference: Microsoft PE/COFF specification (winnt.h definitions). This
// header contains the minimum subset required by the COFF object writer used
// by gen_snapshot.
// -----------------------------------------------------------------------------
// File header machine values (we only support x64 here).
static constexpr uint16_t PE_FILE_MACHINE_UNKNOWN = 0x0000;
static constexpr uint16_t PE_FILE_MACHINE_AMD64 = 0x8664;
// PE_FILE_HEADER.Characteristics flags.
static constexpr uint16_t PE_FILE_RELOCS_STRIPPED = 0x0001;
static constexpr uint16_t PE_FILE_EXECUTABLE_IMAGE = 0x0002;
static constexpr uint16_t PE_FILE_LINE_NUMS_STRIPPED = 0x0004;
static constexpr uint16_t PE_FILE_LARGE_ADDRESS_AWARE = 0x0020;
struct ImageFileHeader {
uint16_t Machine;
uint16_t NumberOfSections;
uint32_t TimeDateStamp;
uint32_t PointerToSymbolTable;
uint32_t NumberOfSymbols;
uint16_t SizeOfOptionalHeader;
uint16_t Characteristics;
};
// PE_SECTION_HEADER.Characteristics flags (subset we use).
static constexpr uint32_t PE_SCN_CNT_CODE = 0x00000020;
static constexpr uint32_t PE_SCN_CNT_INITIALIZED_DATA = 0x00000040;
static constexpr uint32_t PE_SCN_CNT_UNINITIALIZED_DATA = 0x00000080;
static constexpr uint32_t PE_SCN_LNK_INFO = 0x00000200;
static constexpr uint32_t PE_SCN_LNK_REMOVE = 0x00000800;
static constexpr uint32_t PE_SCN_LNK_COMDAT = 0x00001000;
static constexpr uint32_t PE_SCN_LNK_NRELOC_OVFL = 0x01000000;
static constexpr uint32_t PE_SCN_ALIGN_1BYTES = 0x00100000;
static constexpr uint32_t PE_SCN_ALIGN_2BYTES = 0x00200000;
static constexpr uint32_t PE_SCN_ALIGN_4BYTES = 0x00300000;
static constexpr uint32_t PE_SCN_ALIGN_8BYTES = 0x00400000;
static constexpr uint32_t PE_SCN_ALIGN_16BYTES = 0x00500000;
static constexpr uint32_t PE_SCN_ALIGN_32BYTES = 0x00600000;
static constexpr uint32_t PE_SCN_ALIGN_64BYTES = 0x00700000;
static constexpr uint32_t PE_SCN_ALIGN_MASK = 0x00F00000;
static constexpr uint32_t PE_SCN_MEM_DISCARDABLE = 0x02000000;
static constexpr uint32_t PE_SCN_MEM_NOT_CACHED = 0x04000000;
static constexpr uint32_t PE_SCN_MEM_NOT_PAGED = 0x08000000;
static constexpr uint32_t PE_SCN_MEM_SHARED = 0x10000000;
static constexpr uint32_t PE_SCN_MEM_EXECUTE = 0x20000000;
static constexpr uint32_t PE_SCN_MEM_READ = 0x40000000;
static constexpr uint32_t PE_SCN_MEM_WRITE = 0x80000000;
struct ImageSectionHeader {
char Name[8];
uint32_t VirtualSize;
uint32_t VirtualAddress;
uint32_t SizeOfRawData;
uint32_t PointerToRawData;
uint32_t PointerToRelocations;
uint32_t PointerToLinenumbers;
uint16_t NumberOfRelocations;
uint16_t NumberOfLinenumbers;
uint32_t Characteristics;
};
// Symbol storage classes.
static constexpr uint8_t PE_SYM_CLASS_NULL = 0;
static constexpr uint8_t PE_SYM_CLASS_EXTERNAL = 2;
static constexpr uint8_t PE_SYM_CLASS_STATIC = 3;
static constexpr uint8_t PE_SYM_CLASS_LABEL = 6;
static constexpr uint8_t PE_SYM_CLASS_FUNCTION = 101;
static constexpr uint8_t PE_SYM_CLASS_FILE = 103;
static constexpr uint8_t PE_SYM_CLASS_SECTION = 104;
// Symbol special section numbers.
static constexpr int16_t PE_SYM_UNDEFINED = 0;
static constexpr int16_t PE_SYM_ABSOLUTE = -1;
static constexpr int16_t PE_SYM_DEBUG = -2;
// Symbol base types.
static constexpr uint16_t PE_SYM_TYPE_NULL = 0x0000;
// Microsoft tooling expects function symbols to have DT_FUNCTION in the high
// byte of the Type field, regardless of base type. The full value commonly
// emitted is 0x20.
static constexpr uint16_t PE_SYM_DTYPE_FUNCTION = 0x0020;
struct ImageSymbol {
union {
char ShortName[8];
struct {
uint32_t Zeroes; // 0 means use string table offset.
uint32_t Offset; // Offset into string table.
} LongName;
} N;
int32_t Value;
int16_t SectionNumber;
uint16_t Type;
uint8_t StorageClass;
uint8_t NumberOfAuxSymbols;
};
// Auxiliary symbol record for PE_SYM_CLASS_STATIC section symbols.
struct ImageAuxSymbolSection {
uint32_t Length;
uint16_t NumberOfRelocations;
uint16_t NumberOfLinenumbers;
uint32_t CheckSum;
int16_t Number;
uint8_t Selection;
uint8_t Reserved[3];
};
// Auxiliary symbol record for PE_SYM_CLASS_EXTERNAL function symbols.
struct ImageAuxSymbolFuncDef {
uint32_t TagIndex;
uint32_t TotalSize;
uint32_t PointerToLinenumber;
uint32_t PointerToNextFunction;
uint16_t Unused;
};
// Relocation record.
struct ImageRelocation {
uint32_t VirtualAddress;
uint32_t SymbolTableIndex;
uint16_t Type;
};
// x64 relocation types we use.
static constexpr uint16_t PE_REL_AMD64_ABSOLUTE = 0x0000;
static constexpr uint16_t PE_REL_AMD64_ADDR64 = 0x0001;
static constexpr uint16_t PE_REL_AMD64_ADDR32 = 0x0002;
static constexpr uint16_t PE_REL_AMD64_ADDR32NB = 0x0003;
static constexpr uint16_t PE_REL_AMD64_REL32 = 0x0004;
static constexpr uint16_t PE_REL_AMD64_REL32_1 = 0x0005;
static constexpr uint16_t PE_REL_AMD64_REL32_2 = 0x0006;
static constexpr uint16_t PE_REL_AMD64_REL32_3 = 0x0007;
static constexpr uint16_t PE_REL_AMD64_REL32_4 = 0x0008;
static constexpr uint16_t PE_REL_AMD64_REL32_5 = 0x0009;
static constexpr uint16_t PE_REL_AMD64_SECTION = 0x000A;
static constexpr uint16_t PE_REL_AMD64_SECREL = 0x000B;
// -----------------------------------------------------------------------------
// SEH / unwind info (x64)
//
// Locally redeclared to avoid pulling in runtime/platform/unwinding_records.h,
// which is built for the JIT and not appropriate for inclusion from the
// snapshot writer.
// -----------------------------------------------------------------------------
static constexpr uint8_t kUnwindVersion = 1;
// UNWIND_OP_CODES (subset).
enum UnwindOpCode : uint8_t {
UWOP_PUSH_NONVOL = 0,
UWOP_ALLOC_LARGE = 1,
UWOP_ALLOC_SMALL = 2,
UWOP_SET_FPREG = 3,
UWOP_SAVE_NONVOL = 4,
UWOP_SAVE_NONVOL_FAR = 5,
UWOP_SAVE_XMM128 = 8,
UWOP_SAVE_XMM128_FAR = 9,
UWOP_PUSH_MACHFRAME = 10,
};
// Register encodings for unwind ops.
static constexpr uint8_t kRegRax = 0;
static constexpr uint8_t kRegRcx = 1;
static constexpr uint8_t kRegRdx = 2;
static constexpr uint8_t kRegRbx = 3;
static constexpr uint8_t kRegRsp = 4;
static constexpr uint8_t kRegRbp = 5;
static constexpr uint8_t kRegRsi = 6;
static constexpr uint8_t kRegRdi = 7;
struct UnwindCode {
uint8_t CodeOffset;
uint8_t UnwindOp : 4;
uint8_t OpInfo : 4;
};
struct UnwindInfoHeader {
uint8_t Version : 3;
uint8_t Flags : 5;
uint8_t SizeOfProlog;
uint8_t CountOfCodes;
uint8_t FrameRegister : 4;
uint8_t FrameOffset : 4;
};
struct RuntimeFunction {
uint32_t BeginAddress;
uint32_t EndAddress;
uint32_t UnwindData;
};
// -----------------------------------------------------------------------------
// CodeView debug records (MSVC PDB-compatible).
//
// Reference: Microsoft cvinfo.h, LLVM include/llvm/DebugInfo/CodeView. We
// declare only what the writer emits.
// -----------------------------------------------------------------------------
// .debug$S leading signature.
static constexpr uint32_t CV_SIGNATURE_C13 = 4;
// Debug subsection kinds.
static constexpr uint32_t DEBUG_S_SYMBOLS = 0xF1;
static constexpr uint32_t DEBUG_S_LINES = 0xF2;
static constexpr uint32_t DEBUG_S_STRINGTABLE = 0xF3;
static constexpr uint32_t DEBUG_S_FILECHKSMS = 0xF4;
static constexpr uint32_t DEBUG_S_INLINEELINES = 0xF6;
// File checksum kinds.
static constexpr uint8_t CHKSUM_TYPE_NONE = 0;
static constexpr uint8_t CHKSUM_TYPE_MD5 = 1;
static constexpr uint8_t CHKSUM_TYPE_SHA1 = 2;
static constexpr uint8_t CHKSUM_TYPE_SHA256 = 3;
// Symbol record kinds (subset).
static constexpr uint16_t S_END = 0x0006;
static constexpr uint16_t S_FRAMEPROC = 0x1012;
static constexpr uint16_t S_OBJNAME = 0x1101;
static constexpr uint16_t S_BLOCK32 = 0x1103;
static constexpr uint16_t S_COMPILE2 = 0x1116;
static constexpr uint16_t S_GPROC32 = 0x1110;
static constexpr uint16_t S_LPROC32 = 0x110F;
static constexpr uint16_t S_COMPILE3 = 0x113C;
static constexpr uint16_t S_GPROC32_ID = 0x1147;
static constexpr uint16_t S_LPROC32_ID = 0x1146;
static constexpr uint16_t S_PROC_ID_END = 0x114F;
// Type record kinds (subset).
static constexpr uint16_t LF_POINTER = 0x1002;
static constexpr uint16_t LF_PROCEDURE = 0x1008;
static constexpr uint16_t LF_ARGLIST = 0x1201;
static constexpr uint16_t LF_FUNC_ID = 0x1601;
static constexpr uint16_t LF_STRING_ID = 0x1605;
// Primitive type indices used by us.
static constexpr uint32_t T_VOID = 0x0003;
static constexpr uint32_t T_NOTYPE = 0x0000;
// Calling conventions.
static constexpr uint8_t CV_CALL_NEAR_C = 0x00;
// Language for S_COMPILE3 (we use CV_CFL_C as a closest reasonable proxy for
// Dart-AOT generated code).
static constexpr uint8_t CV_CFL_C = 0x00;
static constexpr uint8_t CV_CFL_CXX = 0x01;
static constexpr uint8_t CV_CFL_RUST = 0x15;
// Machine for S_COMPILE3.
static constexpr uint16_t CV_CFL_X64 = 0xD0;
// Subsection (kind/size) header. Each subsection in .debug$S starts with this
// followed by `length` bytes of payload, aligned to 4 on completion.
struct DebugSubsectionHeader {
uint32_t Kind; // DEBUG_S_*
uint32_t Length; // size of payload (not including header)
};
// Per-function block at the start of a DEBUG_S_LINES payload.
struct CvLineSectionHeader {
uint32_t Offset; // SECREL relocation target
uint16_t Section; // SECTION relocation target (1-based section index)
uint16_t Flags; // 0 (no columns) or 1 (with columns)
uint32_t CodeSize; // length of code described by this block
};
// Header for a contiguous line block within a function (one per source file).
struct CvLineBlockHeader {
uint32_t NameIndex; // offset into file checksum subsection
uint32_t NumLines;
uint32_t BlockSize; // total bytes including this header
};
// Single line entry. `Flags` packs:
// bits 0-23: line start
// bits 24-30: delta line end
// bit 31: is statement
struct CvLine {
static constexpr uint32_t kLineMask = 0x00ffffffu;
static constexpr uint32_t kIsStatementBit = 1u << 31;
uint32_t Offset; // byte offset from function start
uint32_t Flags;
};
// File checksum entry header in a DEBUG_S_FILECHKSMS subsection.
struct CvFileChecksumHeader {
uint32_t NameOffset; // offset into DEBUG_S_STRINGTABLE
uint8_t ChecksumSize;
uint8_t ChecksumKind; // CHKSUM_TYPE_*
// Followed by `ChecksumSize` bytes, padded to 4-byte alignment.
};
// S_GPROC32_ID record (followed by null-terminated name).
struct ProcSym32 {
uint16_t RecordLength; // size of record excluding the length field itself
uint16_t RecordKind; // S_GPROC32_ID
uint32_t Parent; // 0
uint32_t End; // patched to point at matching S_PROC_ID_END
uint32_t Next; // 0
uint32_t CodeSize;
uint32_t DebugStart; // 0
uint32_t DebugEnd; // 0
uint32_t FunctionType; // LF_FUNC_ID index
uint32_t CodeOffset; // patched by SECREL relocation
uint16_t Segment; // patched by SECTION relocation
uint8_t Flags;
// Followed by name (NUL-terminated).
};
// S_FRAMEPROC record.
struct FrameProcSym {
uint16_t RecordLength;
uint16_t RecordKind; // S_FRAMEPROC
uint32_t FrameSize; // total frame
uint32_t PaddingSize;
uint32_t OffsetToPadding;
uint32_t BytesOfCalleeSavedRegisters;
uint32_t OffsetOfExceptionHandler;
uint16_t SectionOfExceptionHandler;
uint32_t Flags;
};
// S_COMPILE3 record (followed by null-terminated compiler name).
struct Compile3Sym {
uint16_t RecordLength;
uint16_t RecordKind; // S_COMPILE3
uint32_t Flags; // low byte = language, high bytes = misc flags
uint16_t Machine; // CV_CFL_X64
uint16_t FrontendMajor;
uint16_t FrontendMinor;
uint16_t FrontendBuild;
uint16_t FrontendQFE;
uint16_t BackendMajor;
uint16_t BackendMinor;
uint16_t BackendBuild;
uint16_t BackendQFE;
// Followed by VersionString (NUL-terminated).
};
// S_OBJNAME record (followed by null-terminated name).
struct ObjNameSym {
uint16_t RecordLength;
uint16_t RecordKind; // S_OBJNAME
uint32_t Signature; // 0
// Followed by name (NUL-terminated).
};
// LF_ARGLIST type record body.
struct ArgListType {
uint16_t RecordLength;
uint16_t RecordKind; // LF_ARGLIST
uint32_t Count; // followed by `Count` uint32_t argument type indices
};
// LF_PROCEDURE type record.
struct ProcedureType {
uint16_t RecordLength;
uint16_t RecordKind; // LF_PROCEDURE
uint32_t ReturnType;
uint8_t CallType; // CV_CALL_NEAR_C
uint8_t FuncAttrs; // 0
uint16_t ParameterCount;
uint32_t ArgListType;
};
// LF_FUNC_ID type record (followed by NUL-terminated name).
struct FuncIdType {
uint16_t RecordLength;
uint16_t RecordKind; // LF_FUNC_ID
uint32_t ParentScope; // 0
uint32_t FunctionType;
// Followed by Name (NUL-terminated).
};
// LF_STRING_ID type record (followed by NUL-terminated string).
struct StringIdType {
uint16_t RecordLength;
uint16_t RecordKind; // LF_STRING_ID
uint32_t Substring; // 0
// Followed by String (NUL-terminated).
};
#pragma pack(pop)
// -----------------------------------------------------------------------------
// Compile-time size assertions
// -----------------------------------------------------------------------------
static_assert(sizeof(ImageFileHeader) == 20, "PE/COFF file header layout");
static_assert(sizeof(ImageSectionHeader) == 40,
"PE/COFF section header layout");
static_assert(sizeof(ImageSymbol) == 18, "PE/COFF symbol layout");
static_assert(sizeof(ImageAuxSymbolSection) == 18, "Aux section symbol layout");
static_assert(sizeof(ImageAuxSymbolFuncDef) == 18, "Aux funcdef layout");
static_assert(sizeof(ImageRelocation) == 10, "PE/COFF relocation layout");
static_assert(sizeof(UnwindInfoHeader) == 4, "Unwind info header layout");
static_assert(sizeof(UnwindCode) == 2, "Unwind code layout");
static_assert(sizeof(RuntimeFunction) == 12, "RUNTIME_FUNCTION layout");
static_assert(sizeof(DebugSubsectionHeader) == 8, "CodeView subsection header");
static_assert(sizeof(CvLineSectionHeader) == 12,
"CodeView line section header");
static_assert(sizeof(CvLineBlockHeader) == 12, "CodeView line block header");
static_assert(sizeof(CvLine) == 8, "CodeView line entry");
static_assert(sizeof(CvFileChecksumHeader) == 6, "CodeView file checksum");
static_assert(sizeof(ProcSym32) == 39, "CodeView S_GPROC32_ID prefix");
// FrameProcSym is 30 bytes when packed-1 (2+2+4+4+4+4+4+2+4). Microsoft's
// natural-aligned layout in cvinfo.h ends up at 32 bytes; the on-wire CodeView
// record is the packed 30-byte form, which is what gets emitted by our byte-
// stream helpers in codeview.cc.
static_assert(sizeof(FrameProcSym) == 30, "CodeView S_FRAMEPROC layout");
static_assert(sizeof(Compile3Sym) == 26, "CodeView S_COMPILE3 prefix");
static_assert(sizeof(ObjNameSym) == 8, "CodeView S_OBJNAME prefix");
static_assert(sizeof(ArgListType) == 8, "CodeView LF_ARGLIST prefix");
// ProcedureType: 2-byte RecordLength + 14-byte LF_PROCEDURE body = 16 bytes.
static_assert(sizeof(ProcedureType) == 16, "CodeView LF_PROCEDURE layout");
static_assert(sizeof(FuncIdType) == 12, "CodeView LF_FUNC_ID prefix");
static_assert(sizeof(StringIdType) == 8, "CodeView LF_STRING_ID prefix");
} // namespace coff
} // namespace dart
#endif // DART_PRECOMPILER
#endif // RUNTIME_PLATFORM_COFF_H_