// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Buffers; using System.Buffers.Binary; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Numerics; using System.Reflection.PortableExecutable; using System.Runtime.InteropServices; using System.Text; using ILCompiler.DependencyAnalysis; using ILCompiler.DependencyAnalysisFramework; using Internal.Text; using Internal.TypeSystem; using static ILCompiler.DependencyAnalysis.RelocType; using static ILCompiler.ObjectWriter.CoffObjectWriter.CoffRelocationType; namespace ILCompiler.ObjectWriter { /// <summary> /// COFF object file format writer for Windows targets. /// </summary> /// <remarks> /// The PE/COFF object format is described in the official specifciation at /// https://learn.microsoft.com/windows/win32/debug/pe-format. However, /// numerous extensions are missing in the specification. The most notable /// ones are listed below. /// /// Object files with more than 65279 sections use an extended big object /// file format that is recognized by the Microsoft linker. Many of the /// internal file structures are different. The code below denotes it by /// "BigObj" in parameters and variables. /// /// Section names longer than 8 bytes need to be written indirectly in the /// string table. The PE/COFF specification describes the /NNNNNNN syntax /// for referencing them. However, if the string table gets big enough the /// syntax no longer works. There's an undocumented //BBBBBB syntax where /// base64 offset is used instead. /// /// CodeView debugging format uses 16-bit section index relocations. Once /// the number of sections exceeds 2^16 the same file format is still used. /// The linker treats the CodeView relocations symbolically. /// </remarks> internal partial class CoffObjectWriter : ObjectWriter { protected sealed record SectionDefinition(CoffSectionHeader Header, Stream Stream, List<CoffRelocation> Relocations, Utf8String ComdatName, Utf8String SymbolName); protected readonly Machine _machine; protected readonly List<SectionDefinition> _sections = new(); // Symbol table private readonly List<CoffSymbolRecord> _symbols = new(); private readonly Dictionary<Utf8String, uint> _symbolNameToIndex = new(); private readonly Dictionary<int, CoffSectionSymbol> _sectionNumberToComdatAuxRecord = new(); private readonly HashSet<Utf8String> _referencedMethods = new(); private static readonly ObjectNodeSection GfidsSection = new ObjectNodeSection(".gfids$y", SectionType.ReadOnly); private static readonly ObjectNodeSection DebugTypesSection = new ObjectNodeSection(".debug$T", SectionType.ReadOnly); private static readonly ObjectNodeSection DebugSymbolSection = new ObjectNodeSection(".debug$S", SectionType.ReadOnly); public CoffObjectWriter(NodeFactory factory, ObjectWritingOptions options, OutputInfoBuilder outputInfoBuilder = null) : base(factory, options, outputInfoBuilder) { _machine = factory.Target.Architecture switch { TargetArchitecture.X86 => Machine.I386, TargetArchitecture.X64 => Machine.Amd64, TargetArchitecture.ARM64 => Machine.Arm64, TargetArchitecture.ARM => Machine.ArmThumb2, TargetArchitecture.LoongArch64 => Machine.LoongArch64, TargetArchitecture.RiscV64 => Machine.RiscV64, _ => throw new NotSupportedException("Unsupported architecture") }; } private protected override void CreateSection(ObjectNodeSection section, Utf8String comdatName, Utf8String symbolName, int sectionIndex, Stream sectionStream) { var sectionHeader = new CoffSectionHeader { Name = section == ObjectNodeSection.TLSSection ? ".tls$" : section == ObjectNodeSection.HydrationTargetSection ? "hydrated" : (section.Name.StartsWith('.') ? section.Name : "." + section.Name), SectionCharacteristics = section.Type switch { SectionType.ReadOnly => SectionCharacteristics.MemRead | SectionCharacteristics.ContainsInitializedData, SectionType.UnwindData => SectionCharacteristics.MemRead | SectionCharacteristics.ContainsInitializedData, SectionType.Writeable => SectionCharacteristics.MemRead | SectionCharacteristics.MemWrite | SectionCharacteristics.ContainsInitializedData, SectionType.Executable => SectionCharacteristics.MemRead | SectionCharacteristics.MemExecute | SectionCharacteristics.ContainsCode, SectionType.Uninitialized => SectionCharacteristics.MemRead | SectionCharacteristics.MemWrite | SectionCharacteristics.ContainsUninitializedData, _ => 0 } }; if (section == DebugTypesSection) { sectionHeader.SectionCharacteristics = SectionCharacteristics.MemRead | SectionCharacteristics.ContainsInitializedData | SectionCharacteristics.MemDiscardable; } if (!comdatName.IsNull) { sectionHeader.SectionCharacteristics |= SectionCharacteristics.LinkerComdat; // We find the defining section of the COMDAT symbol. That one is marked // as "ANY" selection type. All the other ones are marked as associated. bool isPrimary = Equals(comdatName, symbolName); uint coffSectionIndex = (uint)sectionIndex + 1u; // COFF section index is 1-based uint definingSectionIndex = isPrimary ? coffSectionIndex : ((CoffSymbol)_symbols[(int)_symbolNameToIndex[comdatName]]).SectionIndex; var auxRecord = new CoffSectionSymbol { // SizeOfRawData, NumberOfRelocations, NumberOfLineNumbers // CheckSum will be filled later in EmitObjectFile Number = definingSectionIndex, Selection = isPrimary ? CoffComdatSelect.IMAGE_COMDAT_SELECT_ANY : CoffComdatSelect.IMAGE_COMDAT_SELECT_ASSOCIATIVE, }; _sectionNumberToComdatAuxRecord[_sections.Count] = auxRecord; _symbols.Add(new CoffSymbol { Name = new Utf8String(sectionHeader.Name), Value = 0, SectionIndex = coffSectionIndex, StorageClass = CoffSymbolClass.IMAGE_SYM_CLASS_STATIC, NumberOfAuxiliaryRecords = 1, }); _symbols.Add(auxRecord); if (!symbolName.IsNull) { _symbolNameToIndex.Add(symbolName, (uint)_symbols.Count); _symbols.Add(new CoffSymbol { Name = symbolName, Value = 0, SectionIndex = coffSectionIndex, StorageClass = isPrimary ? CoffSymbolClass.IMAGE_SYM_CLASS_EXTERNAL : CoffSymbolClass.IMAGE_SYM_CLASS_STATIC, }); } } _sections.Add(new SectionDefinition(sectionHeader, sectionStream, new List<CoffRelocation>(), comdatName, symbolName)); } protected internal override void UpdateSectionAlignment(int sectionIndex, int alignment) { Debug.Assert(alignment > 0 && BitOperations.IsPow2((uint)alignment)); int minimumAlignment = (BitOperations.Log2((uint)alignment) + 1) << 20; int currentAlignment = (int)(_sections[sectionIndex].Header.SectionCharacteristics & SectionCharacteristics.AlignMask); if (currentAlignment < minimumAlignment) { _sections[sectionIndex].Header.SectionCharacteristics = (_sections[sectionIndex].Header.SectionCharacteristics & ~SectionCharacteristics.AlignMask) | (SectionCharacteristics)minimumAlignment; } } protected static uint GetSectionAlignment(CoffSectionHeader header) { SectionCharacteristics alignmentFlag = (header.SectionCharacteristics & SectionCharacteristics.AlignMask); if (alignmentFlag == 0) { return 1; } uint alignment = (uint)(1 << (((int)alignmentFlag >> 20) - 1)); return alignment; } protected internal override unsafe void EmitRelocation( int sectionIndex, long offset, Span<byte> data, RelocType relocType, Utf8String symbolName, long addend) { if (relocType is IMAGE_REL_BASED_RELPTR32) { addend += 4; } if (addend != 0) { fixed (byte* pData = data) { long inlineValue = Relocation.ReadValue(relocType, (void*)pData); Relocation.WriteValue(relocType, (void*)pData, inlineValue + addend); } } base.EmitRelocation(sectionIndex, offset, data, relocType, symbolName, 0); } private protected override void EmitReferencedMethod(Utf8String symbolName) { _referencedMethods.Add(symbolName); } private protected override void EmitSymbolTable( IDictionary<Utf8String, SymbolDefinition> definedSymbols, SortedSet<Utf8String> undefinedSymbols) { Feat00Flags feat00Flags = _machine is Machine.I386 ? Feat00Flags.SafeSEH : 0; foreach (var (symbolName, symbolDefinition) in definedSymbols) { if (_symbolNameToIndex.TryGetValue(symbolName, out uint symbolIndex)) { // Update value for COMDAT symbols ((CoffSymbol)_symbols[(int)symbolIndex]).Value = (uint)symbolDefinition.Value; } else { _symbolNameToIndex.Add(symbolName, (uint)_symbols.Count); _symbols.Add(new CoffSymbol { Name = symbolName, Value = (uint)symbolDefinition.Value, SectionIndex = (uint)(1 + symbolDefinition.SectionIndex), StorageClass = CoffSymbolClass.IMAGE_SYM_CLASS_EXTERNAL, }); } } foreach (var symbolName in undefinedSymbols) { _symbolNameToIndex.Add(symbolName, (uint)_symbols.Count); _symbols.Add(new CoffSymbol { Name = symbolName, StorageClass = CoffSymbolClass.IMAGE_SYM_CLASS_EXTERNAL, }); } if (_options.HasFlag(ObjectWritingOptions.ControlFlowGuard)) { // Create section with control flow guard symbols SectionWriter gfidsSectionWriter = GetOrCreateSection(GfidsSection); foreach (var symbolName in _referencedMethods) { gfidsSectionWriter.WriteLittleEndian<uint>(_symbolNameToIndex[symbolName]); } feat00Flags |= Feat00Flags.ControlFlowGuard; } if (feat00Flags != 0) { // Emit the feat.00 symbol that controls various linker behaviors _symbols.Add(new CoffSymbol { Name = new Utf8String("@feat.00"u8), StorageClass = CoffSymbolClass.IMAGE_SYM_CLASS_STATIC, SectionIndex = uint.MaxValue, // IMAGE_SYM_ABSOLUTE Value = (uint)feat00Flags, }); } } private protected override void EmitRelocations(int sectionIndex, List<SymbolicRelocation> relocationList) { CoffSectionHeader sectionHeader = _sections[sectionIndex].Header; List<CoffRelocation> coffRelocations = _sections[sectionIndex].Relocations; if (relocationList.Count > 0) { if (relocationList.Count <= ushort.MaxValue) { sectionHeader.NumberOfRelocations = (ushort)relocationList.Count; } else { // Write an overflow relocation with the real count of relocations sectionHeader.NumberOfRelocations = ushort.MaxValue; sectionHeader.SectionCharacteristics |= SectionCharacteristics.LinkerNRelocOvfl; coffRelocations.Add(new CoffRelocation { VirtualAddress = (uint)(relocationList.Count + 1) }); } switch (_machine) { case Machine.I386: foreach (var relocation in relocationList) { coffRelocations.Add(new CoffRelocation { VirtualAddress = (uint)relocation.Offset, SymbolTableIndex = _symbolNameToIndex[relocation.SymbolName], Type = relocation.Type switch { IMAGE_REL_BASED_ABSOLUTE => IMAGE_REL_I386_DIR32NB, IMAGE_REL_BASED_ADDR32NB => IMAGE_REL_I386_DIR32NB, IMAGE_REL_BASED_HIGHLOW => IMAGE_REL_I386_DIR32, IMAGE_REL_BASED_REL32 => IMAGE_REL_I386_REL32, IMAGE_REL_BASED_RELPTR32 => IMAGE_REL_I386_REL32, IMAGE_REL_SECREL => IMAGE_REL_I386_SECREL, IMAGE_REL_SECTION => IMAGE_REL_I386_SECTION, _ => throw new NotSupportedException($"Unsupported relocation: {relocation.Type}") }, }); } break; case Machine.Amd64: foreach (var relocation in relocationList) { coffRelocations.Add(new CoffRelocation { VirtualAddress = (uint)relocation.Offset, SymbolTableIndex = _symbolNameToIndex[relocation.SymbolName], Type = relocation.Type switch { IMAGE_REL_BASED_ABSOLUTE => IMAGE_REL_AMD64_ADDR32NB, IMAGE_REL_BASED_ADDR32NB => IMAGE_REL_AMD64_ADDR32NB, IMAGE_REL_BASED_HIGHLOW => IMAGE_REL_AMD64_ADDR32, IMAGE_REL_BASED_DIR64 => IMAGE_REL_AMD64_ADDR64, IMAGE_REL_BASED_REL32 => IMAGE_REL_AMD64_REL32, IMAGE_REL_BASED_RELPTR32 => IMAGE_REL_AMD64_REL32, IMAGE_REL_SECREL => IMAGE_REL_AMD64_SECREL, IMAGE_REL_SECTION => IMAGE_REL_AMD64_SECTION, _ => throw new NotSupportedException($"Unsupported relocation: {relocation.Type}") }, }); } break; case Machine.Arm64: foreach (var relocation in relocationList) { coffRelocations.Add(new CoffRelocation { VirtualAddress = (uint)relocation.Offset, SymbolTableIndex = _symbolNameToIndex[relocation.SymbolName], Type = relocation.Type switch { IMAGE_REL_BASED_ABSOLUTE => IMAGE_REL_ARM64_ADDR32NB, IMAGE_REL_BASED_ADDR32NB => IMAGE_REL_ARM64_ADDR32NB, IMAGE_REL_BASED_HIGHLOW => IMAGE_REL_ARM64_ADDR32, IMAGE_REL_BASED_DIR64 => IMAGE_REL_ARM64_ADDR64, IMAGE_REL_BASED_REL32 => IMAGE_REL_ARM64_REL32, IMAGE_REL_BASED_RELPTR32 => IMAGE_REL_ARM64_REL32, IMAGE_REL_BASED_ARM64_BRANCH26 => IMAGE_REL_ARM64_BRANCH26, IMAGE_REL_BASED_ARM64_PAGEBASE_REL21 => IMAGE_REL_ARM64_PAGEBASE_REL21, IMAGE_REL_BASED_ARM64_PAGEOFFSET_12A => IMAGE_REL_ARM64_PAGEOFFSET_12A, IMAGE_REL_BASED_ARM64_PAGEOFFSET_12L => IMAGE_REL_ARM64_PAGEOFFSET_12L, IMAGE_REL_ARM64_TLS_SECREL_HIGH12A => IMAGE_REL_ARM64_SECREL_HIGH12A, IMAGE_REL_ARM64_TLS_SECREL_LOW12A => IMAGE_REL_ARM64_SECREL_LOW12A, IMAGE_REL_SECREL => IMAGE_REL_ARM64_SECREL, IMAGE_REL_SECTION => IMAGE_REL_ARM64_SECTION, _ => throw new NotSupportedException($"Unsupported relocation: {relocation.Type}") }, }); } break; default: throw new NotSupportedException("Unsupported architecture"); } } } private protected override void EmitObjectFile(Stream outputFileStream) { var stringTable = new CoffStringTable(); var coffHeader = new CoffHeader { Machine = _machine, NumberOfSections = (uint)_sections.Count, NumberOfSymbols = (uint)_symbols.Count, }; // Calculate size of section data and assign offsets uint dataOffset = (uint)(coffHeader.Size + _sections.Count * CoffSectionHeader.Size); int sectionIndex = 0; foreach (SectionDefinition section in _sections) { section.Header.SizeOfRawData = (uint)section.Stream.Length; // Section content if (section.Header.SectionCharacteristics.HasFlag(SectionCharacteristics.ContainsUninitializedData)) { section.Header.PointerToRawData = 0; } else { section.Header.PointerToRawData = dataOffset; dataOffset += section.Header.SizeOfRawData; } // Section relocations section.Header.PointerToRelocations = section.Relocations.Count > 0 ? dataOffset : 0; dataOffset += (uint)(section.Relocations.Count * CoffRelocation.Size); // Record the section layout _outputSectionLayout.Add(new OutputSection(section.Header.Name, section.Header.PointerToRawData, section.Header.VirtualAddress, section.Header.SizeOfRawData)); sectionIndex++; } coffHeader.PointerToSymbolTable = dataOffset; // Write COFF header coffHeader.Write(outputFileStream); // Write COFF section headers sectionIndex = 0; foreach (SectionDefinition section in _sections) { section.Header.Write(outputFileStream, stringTable); // Relocation code below assumes that addresses are 0-indexed Debug.Assert(section.Header.VirtualAddress == 0); // Update COMDAT section symbol if (_sectionNumberToComdatAuxRecord.TryGetValue(sectionIndex, out var auxRecord)) { auxRecord.SizeOfRawData = section.Header.SizeOfRawData; auxRecord.NumberOfRelocations = section.Header.NumberOfRelocations; auxRecord.NumberOfLineNumbers = section.Header.NumberOfLineNumbers; section.Stream.Position = 0; auxRecord.CheckSum = JamCrc32.CalculateChecksum(section.Stream); } sectionIndex++; } // Write section content and relocations foreach (SectionDefinition section in _sections) { if (!section.Header.SectionCharacteristics.HasFlag(SectionCharacteristics.ContainsUninitializedData)) { Debug.Assert(outputFileStream.Position == section.Header.PointerToRawData); section.Stream.Position = 0; section.Stream.CopyTo(outputFileStream); } if (section.Relocations.Count > 0) { foreach (var relocation in section.Relocations) { relocation.Write(outputFileStream); } } } // Optimize the string table foreach (var coffSymbolRecord in _symbols) { if (coffSymbolRecord is CoffSymbol coffSymbol) { stringTable.ReserveString(coffSymbol.Name); } } // Write symbol table Debug.Assert(outputFileStream.Position == coffHeader.PointerToSymbolTable); foreach (var coffSymbolRecord in _symbols) { coffSymbolRecord.Write(outputFileStream, stringTable, coffHeader.IsBigObj); } // Write string table stringTable.Write(outputFileStream); } protected struct CoffHeader { public Machine Machine { get; set; } public uint NumberOfSections { get; set; } public uint TimeDateStamp { get; set; } public uint PointerToSymbolTable { get; set; } public uint NumberOfSymbols { get; set; } public ushort SizeOfOptionalHeader { get; set; } public Characteristics Characteristics { get; set; } // Maximum number of section that can be handled Microsoft linker // before it bails out. We automatically switch to big object file // layout after that. public readonly bool IsBigObj => NumberOfSections > 65279; private static ReadOnlySpan<byte> BigObjMagic => [ 0xC7, 0xA1, 0xBA, 0xD1, 0xEE, 0xBA, 0xA9, 0x4B, 0xAF, 0x20, 0xFA, 0xF6, 0x6A, 0xA4, 0xDC, 0xB8, ]; public static int TimeDateStampOffset(bool bigObj) { return bigObj ? 8 : 4; } private const int RegularSize = sizeof(ushort) + // Machine sizeof(ushort) + // NumberOfSections sizeof(uint) + // TimeDateStamp sizeof(uint) + // PointerToSymbolTable sizeof(uint) + // NumberOfSymbols sizeof(ushort) + // SizeOfOptionalHeader sizeof(ushort); // Characteristics private const int BigObjSize = sizeof(ushort) + // Signature 1 (Machine = Unknown) sizeof(ushort) + // Signature 2 (NumberOfSections = 0xFFFF) sizeof(ushort) + // Version (2) sizeof(ushort) + // Machine sizeof(uint) + // TimeDateStamp 16 + // BigObjMagic sizeof(uint) + // Reserved1 sizeof(uint) + // Reserved2 sizeof(uint) + // Reserved3 sizeof(uint) + // Reserved4 sizeof(uint) + // NumberOfSections sizeof(uint) + // PointerToSymbolTable sizeof(uint); // NumberOfSymbols public int Size => IsBigObj ? BigObjSize : RegularSize; public void Write(Stream stream) { if (!IsBigObj) { Span<byte> buffer = stackalloc byte[RegularSize]; BinaryPrimitives.WriteInt16LittleEndian(buffer, (short)Machine); BinaryPrimitives.WriteUInt16LittleEndian(buffer.Slice(2), (ushort)NumberOfSections); BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(4), TimeDateStamp); BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(8), PointerToSymbolTable); BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(12), NumberOfSymbols); BinaryPrimitives.WriteUInt16LittleEndian(buffer.Slice(16), SizeOfOptionalHeader); BinaryPrimitives.WriteUInt16LittleEndian(buffer.Slice(18), (ushort)Characteristics); stream.Write(buffer); } else { Span<byte> buffer = stackalloc byte[BigObjSize]; buffer.Clear(); BinaryPrimitives.WriteUInt16LittleEndian(buffer.Slice(2), 0xFFFF); BinaryPrimitives.WriteUInt16LittleEndian(buffer.Slice(4), 2); BinaryPrimitives.WriteInt16LittleEndian(buffer.Slice(6), (short)Machine); BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(8), TimeDateStamp); BigObjMagic.CopyTo(buffer.Slice(12)); BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(44), NumberOfSections); BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(48), PointerToSymbolTable); BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(52), NumberOfSymbols); Debug.Assert(SizeOfOptionalHeader == 0); Debug.Assert(Characteristics == 0); stream.Write(buffer); } } } protected sealed class CoffSectionHeader { public string Name { get; set; } public uint VirtualSize { get; set; } public uint VirtualAddress { get; set; } public uint SizeOfRawData { get; set; } public uint PointerToRawData { get; set; } public uint PointerToRelocations { get; set; } public uint PointerToLineNumbers { get; set; } public ushort NumberOfRelocations { get; set; } public ushort NumberOfLineNumbers { get; set; } public SectionCharacteristics SectionCharacteristics { get; set; } private const int NameSize = 8; public const int Size = NameSize + // Name size sizeof(uint) + // VirtualSize sizeof(uint) + // VirtualAddress sizeof(uint) + // SizeOfRawData sizeof(uint) + // PointerToRawData sizeof(uint) + // PointerToRelocations sizeof(uint) + // PointerToLineNumbers sizeof(ushort) + // NumberOfRelocations sizeof(ushort) + // NumberOfLineNumbers sizeof(uint); // SectionCharacteristics public void Write(Stream stream, CoffStringTable stringTable) { Span<byte> buffer = stackalloc byte[Size]; var nameBytes = Encoding.UTF8.GetByteCount(Name); if (nameBytes <= NameSize) { Encoding.UTF8.GetBytes(Name, buffer); if (nameBytes < NameSize) { buffer.Slice(nameBytes, 8 - nameBytes).Clear(); } } else { buffer.Clear(); buffer[0] = (byte)'/'; uint offset = stringTable.GetStringOffset(new Utf8String(Name)); if (offset <= 9999999) { Span<char> charBuffer = stackalloc char[16]; int charsWritten; offset.TryFormat(charBuffer, out charsWritten); for (int i = 0; i < charsWritten; i++) { buffer[1 + i] = (byte)charBuffer[i]; } } else { ReadOnlySpan<byte> s_base64Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"u8; // Maximum expressible offset is 64^6 which is less than uint.MaxValue buffer[1] = (byte)'/'; for (int i = 0; i < 6; i++) { buffer[7 - i] = s_base64Alphabet[(int)(offset % 64)]; offset /= 64; } } } BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(NameSize), VirtualSize); BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(NameSize + 4), VirtualAddress); BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(NameSize + 8), SizeOfRawData); BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(NameSize + 12), PointerToRawData); BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(NameSize + 16), PointerToRelocations); BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(NameSize + 20), PointerToLineNumbers); BinaryPrimitives.WriteUInt16LittleEndian(buffer.Slice(NameSize + 24), NumberOfRelocations); BinaryPrimitives.WriteUInt16LittleEndian(buffer.Slice(NameSize + 26), NumberOfLineNumbers); BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(NameSize + 28), (uint)SectionCharacteristics); stream.Write(buffer); } } internal enum CoffRelocationType { IMAGE_REL_I386_ABSOLUTE = 0, IMAGE_REL_I386_DIR32 = 6, IMAGE_REL_I386_DIR32NB = 7, IMAGE_REL_I386_SECTION = 10, IMAGE_REL_I386_SECREL = 11, IMAGE_REL_I386_REL32 = 20, IMAGE_REL_AMD64_ABSOLUTE = 0, IMAGE_REL_AMD64_ADDR64 = 1, IMAGE_REL_AMD64_ADDR32 = 2, IMAGE_REL_AMD64_ADDR32NB = 3, IMAGE_REL_AMD64_REL32 = 4, IMAGE_REL_AMD64_REL32_1 = 5, IMAGE_REL_AMD64_REL32_2 = 6, IMAGE_REL_AMD64_REL32_3 = 7, IMAGE_REL_AMD64_REL32_4 = 8, IMAGE_REL_AMD64_REL32_5 = 9, IMAGE_REL_AMD64_SECTION = 10, IMAGE_REL_AMD64_SECREL = 11, IMAGE_REL_AMD64_SECREL7 = 12, IMAGE_REL_AMD64_TOKEN = 13, IMAGE_REL_AMD64_SREL32 = 14, IMAGE_REL_AMD64_PAIR = 15, IMAGE_REL_AMD64_SSPAN32 = 16, IMAGE_REL_ARM64_ABSOLUTE = 0, IMAGE_REL_ARM64_ADDR32 = 1, IMAGE_REL_ARM64_ADDR32NB = 2, IMAGE_REL_ARM64_BRANCH26 = 3, IMAGE_REL_ARM64_PAGEBASE_REL21 = 4, IMAGE_REL_ARM64_REL21 = 5, IMAGE_REL_ARM64_PAGEOFFSET_12A = 6, IMAGE_REL_ARM64_PAGEOFFSET_12L = 7, IMAGE_REL_ARM64_SECREL = 8, IMAGE_REL_ARM64_SECREL_LOW12A = 9, IMAGE_REL_ARM64_SECREL_HIGH12A = 10, IMAGE_REL_ARM64_SECREL_LOW12L = 11, IMAGE_REL_ARM64_TOKEN = 12, IMAGE_REL_ARM64_SECTION = 13, IMAGE_REL_ARM64_ADDR64 = 14, IMAGE_REL_ARM64_BRANCH19 = 15, IMAGE_REL_ARM64_BRANCH14 = 16, IMAGE_REL_ARM64_REL32 = 17, } protected sealed class CoffRelocation { public uint VirtualAddress { get; set; } public uint SymbolTableIndex { get; set; } public CoffRelocationType Type { get; set; } public const int Size = sizeof(uint) + // VirtualAddress sizeof(uint) + // SymbolTableIndex sizeof(ushort); // Type public void Write(Stream stream) { Span<byte> buffer = stackalloc byte[Size]; BinaryPrimitives.WriteUInt32LittleEndian(buffer, VirtualAddress); BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(4), SymbolTableIndex); BinaryPrimitives.WriteUInt16LittleEndian(buffer.Slice(8), (ushort)Type); stream.Write(buffer); } } private abstract class CoffSymbolRecord { public abstract void Write(Stream stream, CoffStringTable stringTable, bool isBigObj); } private enum CoffSymbolClass : byte { IMAGE_SYM_CLASS_EXTERNAL = 2, IMAGE_SYM_CLASS_STATIC = 3, IMAGE_SYM_CLASS_LABEL = 6, } private sealed class CoffSymbol : CoffSymbolRecord { public Utf8String Name { get; set; } public uint Value { get; set; } public uint SectionIndex { get; set; } public ushort Type { get; set; } public CoffSymbolClass StorageClass { get; set; } public byte NumberOfAuxiliaryRecords { get; set; } private const int NameSize = 8; private const int RegularSize = NameSize + // Name size sizeof(uint) + // Value sizeof(ushort) + // Section index sizeof(ushort) + // Type sizeof(byte) + // Storage class sizeof(byte); // Auxiliary symbol count private const int BigObjSize = NameSize + // Name size sizeof(uint) + // Value sizeof(uint) + // Section index sizeof(ushort) + // Type sizeof(byte) + // Storage class sizeof(byte); // Auxiliary symbol count public override void Write(Stream stream, CoffStringTable stringTable, bool isBigObj) { Span<byte> buffer = stackalloc byte[isBigObj ? BigObjSize : RegularSize]; if (Name.Length <= NameSize) { Name.AsSpan().CopyTo(buffer); if (Name.Length < NameSize) { buffer.Slice(Name.Length, 8 - Name.Length).Clear(); } } else { BinaryPrimitives.WriteUInt32LittleEndian(buffer, 0); BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(4, 4), stringTable.GetStringOffset(Name)); } BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(NameSize), Value); int sliceIndex; if (isBigObj) { BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(NameSize + 4), SectionIndex); sliceIndex = NameSize + 8; } else { Debug.Assert(SectionIndex == uint.MaxValue || SectionIndex < ushort.MaxValue); BinaryPrimitives.WriteUInt16LittleEndian(buffer.Slice(NameSize + 4), (ushort)SectionIndex); sliceIndex = NameSize + 6; } BinaryPrimitives.WriteUInt16LittleEndian(buffer.Slice(sliceIndex), Type); buffer[sliceIndex + 2] = (byte)StorageClass; buffer[sliceIndex + 3] = NumberOfAuxiliaryRecords; stream.Write(buffer); } } private enum CoffComdatSelect { IMAGE_COMDAT_SELECT_NODUPLICATES = 1, IMAGE_COMDAT_SELECT_ANY = 2, IMAGE_COMDAT_SELECT_SAME_SIZE = 3, IMAGE_COMDAT_SELECT_EXACT_MATCH = 4, IMAGE_COMDAT_SELECT_ASSOCIATIVE = 5, IMAGE_COMDAT_SELECT_LARGEST = 6, } private sealed class CoffSectionSymbol : CoffSymbolRecord { public uint SizeOfRawData { get; set; } public ushort NumberOfRelocations { get; set; } public ushort NumberOfLineNumbers { get; set; } public uint CheckSum { get; set; } public uint Number { get; set; } public CoffComdatSelect Selection { get; set; } private const int RegularSize = sizeof(uint) + // SizeOfRawData sizeof(ushort) + // NumberOfRelocations sizeof(ushort) + // NumberOfLineNumbers sizeof(uint) + // CheckSum sizeof(ushort) + // Number sizeof(byte) + // Selection 3; // Reserved private const int BigObjSize = RegularSize + 2; public override void Write(Stream stream, CoffStringTable stringTable, bool isBigObj) { Span<byte> buffer = stackalloc byte[isBigObj ? BigObjSize : RegularSize]; buffer.Clear(); BinaryPrimitives.WriteUInt32LittleEndian(buffer, SizeOfRawData); BinaryPrimitives.WriteUInt16LittleEndian(buffer.Slice(4), NumberOfRelocations); BinaryPrimitives.WriteUInt16LittleEndian(buffer.Slice(6), NumberOfLineNumbers); BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(8), CheckSum); BinaryPrimitives.WriteUInt16LittleEndian(buffer.Slice(12), (ushort)Number); buffer[14] = (byte)Selection; if (isBigObj) { BinaryPrimitives.WriteUInt16LittleEndian(buffer.Slice(16), (ushort)(Number >> 16)); } stream.Write(buffer); } } protected sealed class CoffStringTable : StringTableBuilder { public new uint Size => (uint)(base.Size + 4); public new uint GetStringOffset(Utf8String text) { return base.GetStringOffset(text) + 4; } public new void Write(Stream stream) { Span<byte> stringTableSize = stackalloc byte[4]; BinaryPrimitives.WriteUInt32LittleEndian(stringTableSize, Size); stream.Write(stringTableSize); base.Write(stream); } } /// <summary> /// Checksum algorithm used for COMDAT sections. This is similar to standard /// CRC32 but starts with 0 as initial value instead of ~0. /// </summary> private static class JamCrc32 { public static uint CalculateChecksum(Stream stream) { // NOTE: // This can be generated by Crc32ReflectedTable.Generate(0xEDB88320u); // We embed the pre-generated version since it's small. ReadOnlySpan<uint> table = [ 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D ]; uint crc = 0; Span<byte> buffer = stackalloc byte[4096]; while (stream.Position < stream.Length) { int length = stream.Read(buffer); for (int i = 0; i < length; i++) { crc = table[(byte)(crc ^ buffer[i])] ^ (crc >> 8); } } return crc; } } private enum Feat00Flags : uint { SafeSEH = 1, StackGuard = 0x100, SoftwareDevelopmentLifecycle = 0x200, ControlFlowGuard = 0x800, ExceptionContinuationMetadata = 0x4000, } } } |