diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/IObjectDumper.cs b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/IObjectDumper.cs similarity index 100% rename from src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/IObjectDumper.cs rename to src/coreclr/tools/Common/Compiler/DependencyAnalysis/IObjectDumper.cs diff --git a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/ISymbolNode.cs b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/ISymbolNode.cs index 336c0121502952..742e0bd9505f6b 100644 --- a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/ISymbolNode.cs +++ b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/ISymbolNode.cs @@ -73,6 +73,40 @@ public interface ISymbolDefinitionNode : ISymbolNode new int Offset { get; } } + /// + /// Represents a symbol that should encompass the full range between the start and end symbols specified. + /// + public interface ISymbolRangeNode : ISymbolNode + { + /// + /// Return a node that determines the start of the range. + /// This node will be used for linkage. + /// + ISymbolNode StartNode(NodeFactory factory); + + /// + /// Return a node that is used to determine the symbol size. + /// + ISymbolNode EndNode(NodeFactory factory); + } + + /// + /// Represents a node that generates a checksum for the resulting output blob. + /// + public interface IChecksumNode : ISymbolNode + { + int ChecksumSize { get; } + + void EmitChecksum(ReadOnlySpan outputBlob, Span checksumLocation); + } + + /// + /// Represents a symbol that should not be shared with another symbol during writing of the object file. + /// + public interface IUniqueSymbolNode : ISymbolNode + { + } + public static class ISymbolNodeExtensions { [ThreadStatic] diff --git a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/ObjectDataBuilder.cs b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/ObjectDataBuilder.cs index 53e4671b57a58f..484792d0497ba9 100644 --- a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/ObjectDataBuilder.cs +++ b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/ObjectDataBuilder.cs @@ -305,6 +305,10 @@ public void EmitReloc(ISymbolNode symbol, RelocType relocType, int delta = 0) // Do not vacate space for this kind of relocation, because // the space is embedded in the instruction. break; + + case RelocType.IMAGE_REL_FILE_CHECKSUM_CALLBACK: + EmitZeros(delta); + break; default: throw new NotImplementedException(); } @@ -315,6 +319,11 @@ public void EmitPointerReloc(ISymbolNode symbol, int delta = 0) EmitReloc(symbol, (_target.PointerSize == 8) ? RelocType.IMAGE_REL_BASED_DIR64 : RelocType.IMAGE_REL_BASED_HIGHLOW, delta); } + public void EmitChecksumReloc(IChecksumNode checksum) + { + EmitReloc(checksum, RelocType.IMAGE_REL_FILE_CHECKSUM_CALLBACK, checksum.ChecksumSize); + } + public ObjectNode.ObjectData ToObjectData() { #if DEBUG diff --git a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/ObjectNodeSection.cs b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/ObjectNodeSection.cs index 9444cdb58aadfb..a226d8f4af2e1f 100644 --- a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/ObjectNodeSection.cs +++ b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/ObjectNodeSection.cs @@ -51,5 +51,10 @@ public ObjectNodeSection(string name, SectionType type) : this(name, type, null) public static readonly ObjectNodeSection ModulesWindowsContentSection = new ObjectNodeSection(".modules$I", SectionType.ReadOnly); public static readonly ObjectNodeSection ModulesUnixContentSection = new ObjectNodeSection("__modules", SectionType.Writeable); + + public static readonly ObjectNodeSection DebugDirectorySection = new ObjectNodeSection("debug", SectionType.Debug); + public static readonly ObjectNodeSection CorMetaSection = new ObjectNodeSection("cormeta", SectionType.ReadOnly); + public static readonly ObjectNodeSection Win32ResourcesSection = new ObjectNodeSection("rsrc", SectionType.ReadOnly); + public static readonly ObjectNodeSection PDataSection = new ObjectNodeSection("pdata", SectionType.ReadOnly); } } diff --git a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/Relocation.cs b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/Relocation.cs index 91991a6882ebd4..7cff8eef4b5ecb 100644 --- a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/Relocation.cs +++ b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/Relocation.cs @@ -8,11 +8,16 @@ namespace ILCompiler.DependencyAnalysis { public enum RelocType { + // PE base relocation types. IMAGE_REL_BASED_ABSOLUTE = 0x00, // No relocation required - IMAGE_REL_BASED_ADDR32NB = 0x02, // The 32-bit address without an image base (RVA) IMAGE_REL_BASED_HIGHLOW = 0x03, // 32 bit address base IMAGE_REL_BASED_THUMB_MOV32 = 0x07, // Thumb2: based MOVW/MOVT IMAGE_REL_BASED_DIR64 = 0x0A, // 64 bit address base + + // COFF relocation types + IMAGE_REL_BASED_ADDR32NB = 0x0B, // The 32-bit address without an image base (RVA) + + // General relocation types IMAGE_REL_BASED_REL32 = 0x10, // 32-bit relative address from byte following reloc IMAGE_REL_BASED_THUMB_BRANCH24 = 0x13, // Thumb2: based B, BL IMAGE_REL_BASED_THUMB_MOV32_PCREL = 0x14, // Thumb2: based MOVW/MOVT @@ -24,6 +29,7 @@ public enum RelocType // This is a special NGEN-specific relocation type // for relative pointer (used to make NGen relocation // section smaller) + IMAGE_REL_SECTION = 0x79, // 16 bit section index containing target IMAGE_REL_BASED_ARM64_PAGEBASE_REL21 = 0x81, // ADRP @@ -63,9 +69,12 @@ public enum RelocType // // Relocations for R2R image production + // None of these are "real" relocations that map to an object file's relocation. + // All must be emulated by the object writer. // IMAGE_REL_SYMBOL_SIZE = 0x1000, // The size of data in the image represented by the target symbol node IMAGE_REL_FILE_ABSOLUTE = 0x1001, // 32 bit offset from beginning of image + IMAGE_REL_FILE_CHECKSUM_CALLBACK = 0x1002, // After the image has been emitted, call the IChecksumNode.EmitChecksum method on the target symbol to emit the checksum data. } public struct Relocation @@ -560,7 +569,20 @@ public static int GetSize(RelocType relocType) { RelocType.IMAGE_REL_BASED_DIR64 => 8, RelocType.IMAGE_REL_BASED_HIGHLOW => 4, + RelocType.IMAGE_REL_BASED_ADDR32NB => 4, + RelocType.IMAGE_REL_BASED_REL32 => 4, RelocType.IMAGE_REL_BASED_RELPTR32 => 4, + RelocType.IMAGE_REL_FILE_ABSOLUTE => 4, + // The relocation itself aren't these sizes, but their values + // are immediates in instructions within + // a span of this many bytes. + RelocType.IMAGE_REL_BASED_ARM64_PAGEBASE_REL21 => 4, + RelocType.IMAGE_REL_BASED_ARM64_PAGEOFFSET_12A => 4, + RelocType.IMAGE_REL_BASED_THUMB_MOV32 => 8, + RelocType.IMAGE_REL_BASED_THUMB_MOV32_PCREL => 8, + RelocType.IMAGE_REL_BASED_LOONGARCH64_PC => 16, + RelocType.IMAGE_REL_BASED_LOONGARCH64_JIR => 16, + RelocType.IMAGE_REL_BASED_RISCV64_PC => 16, _ => throw new NotSupportedException(), }; } diff --git a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/Target_ARM64/ARM64Emitter.cs b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/Target_ARM64/ARM64Emitter.cs index e7cbce99e5ea9c..d1bd8b917f1f82 100644 --- a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/Target_ARM64/ARM64Emitter.cs +++ b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/Target_ARM64/ARM64Emitter.cs @@ -150,6 +150,14 @@ public void EmitJMP(ISymbolNode symbol) { if (symbol.RepresentsIndirectionCell) { + Builder.RequireInitialPointerAlignment(); + + if (Builder.CountBytes % Builder.TargetPointerSize == 0) + { + // Emit a NOP instruction to align the 64-bit reloc below. + EmitNOP(); + } + // ldr x12, [PC+0xc] EmitLDR(Register.X12, 0xc); @@ -157,7 +165,7 @@ public void EmitJMP(ISymbolNode symbol) EmitLDR(Register.X12, Register.X12); // br x12 - Builder.EmitUInt(0xd61f0180); + EmitJMP(Register.X12); Builder.EmitReloc(symbol, RelocType.IMAGE_REL_BASED_DIR64); } @@ -215,6 +223,11 @@ public void EmitJNE(ISymbolNode symbol) EmitJMP(symbol); } + public void EmitNOP() + { + Builder.EmitUInt(0xD503201F); + } + private static bool InSignedByteRange(int i) { return i == (int)(sbyte)i; diff --git a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/Target_LoongArch64/LoongArch64Emitter.cs b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/Target_LoongArch64/LoongArch64Emitter.cs index 47f8f471a172a2..f5df5286d44785 100644 --- a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/Target_LoongArch64/LoongArch64Emitter.cs +++ b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/Target_LoongArch64/LoongArch64Emitter.cs @@ -107,6 +107,14 @@ public void EmitJMP(ISymbolNode symbol) { if (symbol.RepresentsIndirectionCell) { + Builder.RequireInitialPointerAlignment(); + + if (Builder.CountBytes % Builder.TargetPointerSize != 0) + { + // Emit a NOP instruction to align the 64-bit reloc below. + EmitNOP(); + } + // pcaddi R21, 0 EmitPCADDI(Register.R21); @@ -150,5 +158,11 @@ public void EmitDBAR() { Builder.EmitUInt(0x38720000); } + + // nop + public void EmitNOP() + { + Builder.EmitUInt(0x03400000); + } } } diff --git a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/Target_RiscV64/RiscV64Emitter.cs b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/Target_RiscV64/RiscV64Emitter.cs index 380163ed2fbf20..86f7528c7262aa 100644 --- a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/Target_RiscV64/RiscV64Emitter.cs +++ b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/Target_RiscV64/RiscV64Emitter.cs @@ -103,6 +103,14 @@ public void EmitJMP(ISymbolNode symbol) { if (symbol.RepresentsIndirectionCell) { + Builder.RequireInitialPointerAlignment(); + + if (Builder.CountBytes % Builder.TargetPointerSize != 0) + { + // Emit a NOP instruction to align the 64-bit reloc below. + EmitNOP(); + } + // auipc x29, 0 EmitPC(Register.X29); // ld x29,16(x29) @@ -137,5 +145,10 @@ public void EmitJMPIfZero(Register regSrc, ISymbolNode symbol) Builder.EmitUInt((uint)(0x00001063 | ((uint)regSrc << 15) | encodedOffset)); EmitJMP(symbol); } + + public void EmitNOP() + { + Builder.EmitUInt(0x00000013); + } } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CoffObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs similarity index 79% rename from src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CoffObjectWriter.cs rename to src/coreclr/tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs index d6deb27606a4a1..74e999317eec9e 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CoffObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs @@ -15,7 +15,6 @@ using ILCompiler.DependencyAnalysis; using ILCompiler.DependencyAnalysisFramework; using Internal.TypeSystem; -using Internal.TypeSystem.TypesDebugInfo; using static ILCompiler.DependencyAnalysis.RelocType; using static ILCompiler.ObjectWriter.CoffObjectWriter.CoffRelocationType; @@ -45,12 +44,12 @@ namespace ILCompiler.ObjectWriter /// the number of sections exceeds 2^16 the same file format is still used. /// The linker treats the CodeView relocations symbolically. /// - internal sealed class CoffObjectWriter : ObjectWriter + internal partial class CoffObjectWriter : ObjectWriter { - private sealed record SectionDefinition(CoffSectionHeader Header, Stream Stream, List Relocations, string ComdatName, string SymbolName); + protected sealed record SectionDefinition(CoffSectionHeader Header, Stream Stream, List Relocations, string ComdatName, string SymbolName); - private readonly Machine _machine; - private readonly List _sections = new(); + protected readonly Machine _machine; + protected readonly List _sections = new(); // Symbol table private readonly List _symbols = new(); @@ -58,34 +57,26 @@ private sealed record SectionDefinition(CoffSectionHeader Header, Stream Stream, private readonly Dictionary _sectionNumberToComdatAuxRecord = new(); private readonly HashSet _referencedMethods = new(); - // Exception handling - private SectionWriter _pdataSectionWriter; - - // Debugging - private SectionWriter _debugTypesSectionWriter; - private SectionWriter _debugSymbolSectionWriter; - private CodeViewFileTableBuilder _debugFileTableBuilder; - private CodeViewSymbolsBuilder _debugSymbolsBuilder; - private CodeViewTypesBuilder _debugTypesBuilder; - - private static readonly ObjectNodeSection PDataSection = new ObjectNodeSection("pdata", SectionType.ReadOnly); 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) - : base(factory, options) + 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, string comdatName, string symbolName, Stream sectionStream) + private protected override void CreateSection(ObjectNodeSection section, string comdatName, string symbolName, int sectionIndex, Stream sectionStream) { var sectionHeader = new CoffSectionHeader { @@ -110,7 +101,7 @@ private protected override void CreateSection(ObjectNodeSection section, string } }; - if (section == DebugTypesSection) + if (section == DebugTypesSection || section == ObjectNodeSection.DebugDirectorySection) { sectionHeader.SectionCharacteristics = SectionCharacteristics.MemRead | SectionCharacteristics.ContainsInitializedData | @@ -124,8 +115,8 @@ private protected override void CreateSection(ObjectNodeSection section, string // 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 sectionIndex = (uint)_sections.Count + 1u; - uint definingSectionIndex = isPrimary ? sectionIndex : ((CoffSymbol)_symbols[(int)_symbolNameToIndex[comdatName]]).SectionIndex; + 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 { @@ -143,7 +134,7 @@ private protected override void CreateSection(ObjectNodeSection section, string { Name = sectionHeader.Name, Value = 0, - SectionIndex = sectionIndex, + SectionIndex = coffSectionIndex, StorageClass = CoffSymbolClass.IMAGE_SYM_CLASS_STATIC, NumberOfAuxiliaryRecords = 1, }); @@ -156,7 +147,7 @@ private protected override void CreateSection(ObjectNodeSection section, string { Name = symbolName, Value = 0, - SectionIndex = sectionIndex, + SectionIndex = coffSectionIndex, StorageClass = isPrimary ? CoffSymbolClass.IMAGE_SYM_CLASS_EXTERNAL : CoffSymbolClass.IMAGE_SYM_CLASS_STATIC, }); } @@ -179,6 +170,17 @@ protected internal override void UpdateSectionAlignment(int sectionIndex, int al } } + 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, @@ -194,7 +196,7 @@ protected internal override unsafe void EmitRelocation( if (addend != 0) { - fixed (byte *pData = data) + fixed (byte* pData = data) { long inlineValue = Relocation.ReadValue(relocType, (void*)pData); Relocation.WriteValue(relocType, (void*)pData, inlineValue + addend); @@ -370,105 +372,8 @@ private protected override void EmitRelocations(int sectionIndex, List 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++; } @@ -530,7 +438,7 @@ private protected override void EmitObjectFile(string objectFilePath) sectionIndex++; } - // Writer section content and relocations + // Write section content and relocations foreach (SectionDefinition section in _sections) { if (!section.Header.SectionCharacteristics.HasFlag(SectionCharacteristics.ContainsUninitializedData)) @@ -569,110 +477,7 @@ private protected override void EmitObjectFile(string objectFilePath) stringTable.Write(outputFileStream); } - private protected override void CreateEhSections() - { - // Create .pdata - _pdataSectionWriter = GetOrCreateSection(PDataSection); - } - - private protected override ITypesDebugInfoWriter CreateDebugInfoBuilder() - { - _debugFileTableBuilder = new CodeViewFileTableBuilder(); - - _debugSymbolSectionWriter = GetOrCreateSection(DebugSymbolSection); - _debugSymbolSectionWriter.EmitAlignment(4); - _debugSymbolsBuilder = new CodeViewSymbolsBuilder( - _nodeFactory.Target.Architecture, - _debugSymbolSectionWriter); - - _debugTypesSectionWriter = GetOrCreateSection(DebugTypesSection); - _debugTypesSectionWriter.EmitAlignment(4); - _debugTypesBuilder = new CodeViewTypesBuilder( - _nodeFactory.NameMangler, _nodeFactory.Target.PointerSize, - _debugTypesSectionWriter); - return _debugTypesBuilder; - } - - private protected override void EmitDebugFunctionInfo( - uint methodTypeIndex, - string methodName, - SymbolDefinition methodSymbol, - INodeWithDebugInfo debugNode, - bool hasSequencePoints) - { - DebugEHClauseInfo[] clauses = null; - CodeViewSymbolsBuilder debugSymbolsBuilder; - - if (debugNode is INodeWithCodeInfo nodeWithCodeInfo) - { - clauses = nodeWithCodeInfo.DebugEHClauseInfos; - } - - if (ShouldShareSymbol((ObjectNode)debugNode)) - { - // If the method is emitted in COMDAT section then we need to create an - // associated COMDAT section for the debugging symbols. - var sectionWriter = GetOrCreateSection(DebugSymbolSection, methodName, null); - debugSymbolsBuilder = new CodeViewSymbolsBuilder(_nodeFactory.Target.Architecture, sectionWriter); - } - else - { - debugSymbolsBuilder = _debugSymbolsBuilder; - } - - debugSymbolsBuilder.EmitSubprogramInfo( - methodName, - methodSymbol.Size, - methodTypeIndex, - debugNode.GetDebugVars().Select(debugVar => (debugVar, GetVarTypeIndex(debugNode.IsStateMachineMoveNextMethod, debugVar))), - clauses ?? Array.Empty()); - - if (hasSequencePoints) - { - debugSymbolsBuilder.EmitLineInfo( - _debugFileTableBuilder, - methodName, - methodSymbol.Size, - debugNode.GetNativeSequencePoints()); - } - } - - private protected override void EmitDebugThunkInfo( - string methodName, - SymbolDefinition methodSymbol, - INodeWithDebugInfo debugNode) - { - if (!debugNode.GetNativeSequencePoints().Any()) - return; - - CodeViewSymbolsBuilder debugSymbolsBuilder; - - if (ShouldShareSymbol((ObjectNode)debugNode)) - { - // If the method is emitted in COMDAT section then we need to create an - // associated COMDAT section for the debugging symbols. - var sectionWriter = GetOrCreateSection(DebugSymbolSection, methodName, null); - debugSymbolsBuilder = new CodeViewSymbolsBuilder(_nodeFactory.Target.Architecture, sectionWriter); - } - else - { - debugSymbolsBuilder = _debugSymbolsBuilder; - } - - debugSymbolsBuilder.EmitLineInfo( - _debugFileTableBuilder, - methodName, - methodSymbol.Size, - debugNode.GetNativeSequencePoints()); - } - - private protected override void EmitDebugSections(IDictionary definedSymbols) - { - _debugSymbolsBuilder.WriteUserDefinedTypes(_debugTypesBuilder.UserDefinedTypes); - _debugFileTableBuilder.Write(_debugSymbolSectionWriter); - } - - private struct CoffHeader + protected struct CoffHeader { public Machine Machine { get; set; } public uint NumberOfSections { get; set; } @@ -680,18 +485,23 @@ private struct CoffHeader public uint PointerToSymbolTable { get; set; } public uint NumberOfSymbols { get; set; } public ushort SizeOfOptionalHeader { get; set; } - public ushort Characteristics { 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 bool IsBigObj => NumberOfSections > 65279; + public readonly bool IsBigObj => NumberOfSections > 65279; - private static ReadOnlySpan BigObjMagic => new byte[] - { + private static ReadOnlySpan 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 @@ -719,7 +529,7 @@ private struct CoffHeader public int Size => IsBigObj ? BigObjSize : RegularSize; - public void Write(FileStream stream) + public void Write(Stream stream) { if (!IsBigObj) { @@ -731,7 +541,7 @@ public void Write(FileStream stream) BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(8), PointerToSymbolTable); BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(12), NumberOfSymbols); BinaryPrimitives.WriteUInt16LittleEndian(buffer.Slice(16), SizeOfOptionalHeader); - BinaryPrimitives.WriteUInt16LittleEndian(buffer.Slice(18), Characteristics); + BinaryPrimitives.WriteUInt16LittleEndian(buffer.Slice(18), (ushort)Characteristics); stream.Write(buffer); } @@ -757,7 +567,7 @@ public void Write(FileStream stream) } } - private sealed class CoffSectionHeader + protected sealed class CoffSectionHeader { public string Name { get; set; } public uint VirtualSize { get; set; } @@ -784,7 +594,7 @@ private sealed class CoffSectionHeader sizeof(ushort) + // NumberOfLineNumbers sizeof(uint); // SectionCharacteristics - public void Write(FileStream stream, CoffStringTable stringTable) + public void Write(Stream stream, CoffStringTable stringTable) { Span buffer = stackalloc byte[Size]; @@ -886,7 +696,7 @@ internal enum CoffRelocationType IMAGE_REL_ARM64_REL32 = 17, } - private sealed class CoffRelocation + protected sealed class CoffRelocation { public uint VirtualAddress { get; set; } public uint SymbolTableIndex { get; set; } @@ -897,7 +707,7 @@ private sealed class CoffRelocation sizeof(uint) + // SymbolTableIndex sizeof(ushort); // Type - public void Write(FileStream stream) + public void Write(Stream stream) { Span buffer = stackalloc byte[Size]; @@ -1038,7 +848,7 @@ public override void Write(Stream stream, CoffStringTable stringTable, bool isBi } } - private sealed class CoffStringTable : StringTableBuilder + protected sealed class CoffStringTable : StringTableBuilder { public new uint Size => (uint)(base.Size + 4); @@ -1047,7 +857,7 @@ private sealed class CoffStringTable : StringTableBuilder return base.GetStringOffset(text) + 4; } - public new void Write(FileStream stream) + public new void Write(Stream stream) { Span stringTableSize = stackalloc byte[4]; BinaryPrimitives.WriteUInt32LittleEndian(stringTableSize, Size); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfHelper.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/Dwarf/DwarfHelper.cs similarity index 100% rename from src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfHelper.cs rename to src/coreclr/tools/Common/Compiler/ObjectWriter/Dwarf/DwarfHelper.cs diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Eabi/EabiAttributesBuilder.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/Eabi/EabiAttributesBuilder.cs similarity index 100% rename from src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Eabi/EabiAttributesBuilder.cs rename to src/coreclr/tools/Common/Compiler/ObjectWriter/Eabi/EabiAttributesBuilder.cs diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Eabi/EabiNative.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/Eabi/EabiNative.cs similarity index 100% rename from src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Eabi/EabiNative.cs rename to src/coreclr/tools/Common/Compiler/ObjectWriter/Eabi/EabiNative.cs diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/ElfNative.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/ElfNative.cs similarity index 100% rename from src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/ElfNative.cs rename to src/coreclr/tools/Common/Compiler/ObjectWriter/ElfNative.cs diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/ElfObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/ElfObjectWriter.cs similarity index 87% rename from src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/ElfObjectWriter.cs rename to src/coreclr/tools/Common/Compiler/ObjectWriter/ElfObjectWriter.cs index 640cced9dd6bf9..d2c3b99efbd77b 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/ElfObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/ElfObjectWriter.cs @@ -32,7 +32,7 @@ namespace ILCompiler.ObjectWriter /// to accomodate the section indexes that don't fit within the regular /// section number field. /// - internal sealed class ElfObjectWriter : UnixObjectWriter + internal sealed partial class ElfObjectWriter : UnixObjectWriter { private readonly bool _useInlineRelocationAddends; private readonly ushort _machine; @@ -45,9 +45,6 @@ internal sealed class ElfObjectWriter : UnixObjectWriter // Symbol table private readonly Dictionary _symbolNameToIndex = new(); - private Dictionary _armUnwindSections; - private static readonly ObjectNodeSection ArmUnwindIndexSection = new ObjectNodeSection(".ARM.exidx", SectionType.UnwindData); - private static readonly ObjectNodeSection ArmUnwindTableSection = new ObjectNodeSection(".ARM.extab", SectionType.ReadOnly); private static readonly ObjectNodeSection ArmAttributesSection = new ObjectNodeSection(".ARM.attributes", SectionType.ReadOnly); private static readonly ObjectNodeSection ArmTextThunkSection = new ObjectNodeSection(".text.thunks", SectionType.Executable); private static readonly ObjectNodeSection CommentSection = new ObjectNodeSection(".comment", SectionType.ReadOnly); @@ -72,12 +69,11 @@ public ElfObjectWriter(NodeFactory factory, ObjectWritingOptions options) _symbols.Add(new ElfSymbol {}); } - private protected override void CreateSection(ObjectNodeSection section, string comdatName, string symbolName, Stream sectionStream) + private protected override void CreateSection(ObjectNodeSection section, string comdatName, string symbolName, int sectionIndex, Stream sectionStream) { string sectionName = section.Name == "rdata" ? ".rodata" : (section.Name.StartsWith('_') || section.Name.StartsWith('.') ? section.Name : "." + section.Name); - int sectionIndex = _sections.Count; uint type = 0; uint flags = 0; ElfSectionDefinition groupSection = null; @@ -178,7 +174,7 @@ private protected override void CreateSection(ObjectNodeSection section, string }); } - base.CreateSection(section, comdatName, symbolName ?? sectionName, sectionStream); + base.CreateSection(section, comdatName, symbolName ?? sectionName, sectionIndex, sectionStream); } protected internal override void UpdateSectionAlignment(int sectionIndex, int alignment) @@ -604,128 +600,8 @@ private protected override void EmitSectionsAndLayout() } } - private protected override void CreateEhSections() + private protected override void EmitObjectFile(Stream outputFileStream) { - // ARM creates the EHABI sections lazily in EmitUnwindInfo - if (_machine is not EM_ARM) - { - base.CreateEhSections(); - } - } - - private protected override void EmitUnwindInfo( - SectionWriter sectionWriter, - INodeWithCodeInfo nodeWithCodeInfo, - string currentSymbolName) - { - if (_machine is not EM_ARM) - { - base.EmitUnwindInfo(sectionWriter, nodeWithCodeInfo, currentSymbolName); - return; - } - - if (nodeWithCodeInfo.FrameInfos is FrameInfo[] frameInfos && - nodeWithCodeInfo is ISymbolDefinitionNode) - { - SectionWriter exidxSectionWriter; - SectionWriter extabSectionWriter; - - if (ShouldShareSymbol((ObjectNode)nodeWithCodeInfo)) - { - exidxSectionWriter = GetOrCreateSection(ArmUnwindIndexSection, currentSymbolName, $"_unwind0{currentSymbolName}"); - extabSectionWriter = GetOrCreateSection(ArmUnwindTableSection, currentSymbolName, $"_extab0{currentSymbolName}"); - _sections[exidxSectionWriter.SectionIndex].LinkSection = _sections[sectionWriter.SectionIndex]; - } - else - { - _armUnwindSections ??= new(); - if (_armUnwindSections.TryGetValue(sectionWriter.SectionIndex, out var unwindSections)) - { - exidxSectionWriter = unwindSections.ExidxSectionWriter; - extabSectionWriter = unwindSections.ExtabSectionWriter; - } - else - { - string sectionName = _sections[sectionWriter.SectionIndex].Name; - exidxSectionWriter = GetOrCreateSection(new ObjectNodeSection($"{ArmUnwindIndexSection.Name}{sectionName}", ArmUnwindIndexSection.Type)); - extabSectionWriter = GetOrCreateSection(new ObjectNodeSection($"{ArmUnwindTableSection.Name}{sectionName}", ArmUnwindTableSection.Type)); - _sections[exidxSectionWriter.SectionIndex].LinkSection = _sections[sectionWriter.SectionIndex]; - _armUnwindSections.Add(sectionWriter.SectionIndex, (exidxSectionWriter, extabSectionWriter)); - } - } - - long mainLsdaOffset = 0; - Span unwindWord = stackalloc byte[4]; - for (int i = 0; i < frameInfos.Length; i++) - { - FrameInfo frameInfo = frameInfos[i]; - int start = frameInfo.StartOffset; - int end = frameInfo.EndOffset; - byte[] blob = frameInfo.BlobData; - - string framSymbolName = $"_fram{i}{currentSymbolName}"; - string extabSymbolName = $"_extab{i}{currentSymbolName}"; - - sectionWriter.EmitSymbolDefinition(framSymbolName, start); - - // Emit the index info - exidxSectionWriter.EmitSymbolReference(IMAGE_REL_ARM_PREL31, framSymbolName); - exidxSectionWriter.EmitSymbolReference(IMAGE_REL_ARM_PREL31, extabSymbolName); - - Span armUnwindInfo = EabiUnwindConverter.ConvertCFIToEabi(blob); - string personalitySymbolName; - - if (armUnwindInfo.Length <= 3) - { - personalitySymbolName = "__aeabi_unwind_cpp_pr0"; - unwindWord[3] = 0x80; - unwindWord[2] = (byte)(armUnwindInfo.Length > 0 ? armUnwindInfo[0] : 0xB0); - unwindWord[1] = (byte)(armUnwindInfo.Length > 1 ? armUnwindInfo[1] : 0xB0); - unwindWord[0] = (byte)(armUnwindInfo.Length > 2 ? armUnwindInfo[2] : 0xB0); - armUnwindInfo = Span.Empty; - } - else - { - Debug.Assert(armUnwindInfo.Length <= 1024); - personalitySymbolName = "__aeabi_unwind_cpp_pr1"; - unwindWord[3] = 0x81; - unwindWord[2] = (byte)(((armUnwindInfo.Length - 2) + 3) / 4); - unwindWord[1] = armUnwindInfo[0]; - unwindWord[0] = armUnwindInfo[1]; - armUnwindInfo = armUnwindInfo.Slice(2); - } - - extabSectionWriter.EmitAlignment(4); - extabSectionWriter.EmitSymbolDefinition(extabSymbolName); - - // ARM EHABI requires emitting a dummy relocation to the personality routine - // to tell the linker to preserve it. - extabSectionWriter.EmitRelocation(0, unwindWord, IMAGE_REL_BASED_ABSOLUTE, personalitySymbolName, 0); - - // Emit the unwinding code. First word specifies the personality routine, - // format and first few bytes of the unwind code. For longer unwind codes - // the other words follow. They are padded with the "finish" instruction - // (0xB0). - extabSectionWriter.Write(unwindWord); - while (armUnwindInfo.Length > 0) - { - unwindWord[3] = (byte)(armUnwindInfo.Length > 0 ? armUnwindInfo[0] : 0xB0); - unwindWord[2] = (byte)(armUnwindInfo.Length > 1 ? armUnwindInfo[1] : 0xB0); - unwindWord[1] = (byte)(armUnwindInfo.Length > 2 ? armUnwindInfo[2] : 0xB0); - unwindWord[0] = (byte)(armUnwindInfo.Length > 3 ? armUnwindInfo[3] : 0xB0); - extabSectionWriter.Write(unwindWord); - armUnwindInfo = armUnwindInfo.Length > 3 ? armUnwindInfo.Slice(4) : Span.Empty; - } - - // Emit our LSDA info directly into the exception table - EmitLsda(nodeWithCodeInfo, frameInfos, i, extabSectionWriter, ref mainLsdaOffset); - } - } - } - - private protected override void EmitObjectFile(string objectFilePath) - { - using var outputFileStream = new FileStream(objectFilePath, FileMode.Create); switch (_machine) { case EM_386: @@ -738,7 +614,7 @@ private protected override void EmitObjectFile(string objectFilePath) } } - private void EmitObjectFile(FileStream outputFileStream) + private void EmitObjectFile(Stream outputFileStream) where TSize : struct, IBinaryInteger { ElfStringTable _stringTable = new(); @@ -782,6 +658,9 @@ private void EmitObjectFile(FileStream outputFileStream) section.SectionHeader.Offset = currentOffset; section.SectionHeader.Size = (ulong)section.Stream.Length; + // Record the section layout + _outputSectionLayout.Add(new OutputSection(section.Name, currentOffset, currentOffset, (ulong)section.Stream.Length)); + if (section.SectionHeader.Type != SHT_NOBITS) { currentOffset += (ulong)section.Stream.Length; @@ -1046,7 +925,7 @@ public static int GetSize() sizeof(ushort); // String table index } - public void Write(FileStream stream) + public void Write(Stream stream) where TSize : struct, IBinaryInteger { Span buffer = stackalloc byte[GetSize()]; @@ -1104,7 +983,7 @@ public static int GetSize() default(TSize).GetByteCount(); // Entry size } - public void Write(FileStream stream) + public void Write(Stream stream) where TSize : struct, IBinaryInteger { Span buffer = stackalloc byte[GetSize()]; @@ -1140,7 +1019,7 @@ public static int GetSize() return typeof(TSize) == typeof(uint) ? 16 : 24; } - public void Write(FileStream stream, ElfStringTable stringTable) + public void Write(Stream stream, ElfStringTable stringTable) where TSize : struct, IBinaryInteger { Span buffer = stackalloc byte[GetSize()]; diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/MachNative.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/MachNative.cs similarity index 100% rename from src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/MachNative.cs rename to src/coreclr/tools/Common/Compiler/ObjectWriter/MachNative.cs diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/MachObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/MachObjectWriter.cs similarity index 79% rename from src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/MachObjectWriter.cs rename to src/coreclr/tools/Common/Compiler/ObjectWriter/MachObjectWriter.cs index 4a1742c36b8f58..fec3165308290d 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/MachObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/MachObjectWriter.cs @@ -54,21 +54,13 @@ namespace ILCompiler.ObjectWriter /// The Apple linker is extremely picky in which relocation types are allowed /// inside the DWARF sections, both for debugging and exception handling. /// - internal sealed class MachObjectWriter : UnixObjectWriter + internal sealed partial class MachObjectWriter : UnixObjectWriter { - private sealed record CompactUnwindCode(string PcStartSymbolName, uint PcLength, uint Code, string LsdaSymbolName = null, string PersonalitySymbolName = null); - private readonly TargetOS _targetOS; private readonly uint _cpuType; private readonly uint _cpuSubType; private readonly List _sections = new(); - // Exception handling sections - private MachSection _compactUnwindSection; - private MemoryStream _compactUnwindStream; - private readonly List _compactUnwindCodes = new(); - private readonly uint _compactUnwindDwarfCode; - // Symbol table private readonly Dictionary _symbolNameToIndex = new(); private readonly List _symbolTable = new(); @@ -82,12 +74,16 @@ public MachObjectWriter(NodeFactory factory, ObjectWritingOptions options) case TargetArchitecture.ARM64: _cpuType = CPU_TYPE_ARM64; _cpuSubType = CPU_SUBTYPE_ARM64_ALL; +#if !READYTORUN _compactUnwindDwarfCode = 0x3_00_00_00u; +#endif break; case TargetArchitecture.X64: _cpuType = CPU_TYPE_X86_64; _cpuSubType = CPU_SUBTYPE_X86_64_ALL; +#if !READYTORUN _compactUnwindDwarfCode = 0x4_00_00_00u; +#endif break; default: throw new NotSupportedException("Unsupported architecture"); @@ -96,12 +92,14 @@ public MachObjectWriter(NodeFactory factory, ObjectWritingOptions options) _targetOS = factory.Target.OperatingSystem; } + private protected override bool UsesSubsectionsViaSymbols => true; + private protected override void EmitSectionsAndLayout() { // Layout sections. At this point we don't really care if the file offsets are correct // but we need to compute the virtual addresses to populate the symbol table. uint fileOffset = 0; - LayoutSections(ref fileOffset, out _, out _); + LayoutSections(recordFinalLayout: false, ref fileOffset, out _, out _); // Generate section base symbols. The section symbols are used for PC relative relocations // to subtract the base of the section, and in DWARF to emit section relative relocations. @@ -122,7 +120,7 @@ private protected override void EmitSectionsAndLayout() } } - private void LayoutSections(ref uint fileOffset, out uint segmentFileSize, out ulong segmentSize) + private void LayoutSections(bool recordFinalLayout, ref uint fileOffset, out uint segmentFileSize, out ulong segmentSize) { ulong virtualAddress = 0; byte sectionIndex = 1; @@ -155,6 +153,11 @@ private void LayoutSections(ref uint fileOffset, out uint segmentFileSize, out u sectionIndex++; segmentSize = Math.Max(segmentSize, virtualAddress); + + if (recordFinalLayout) + { + _outputSectionLayout.Add(new OutputSection($"{section.SectionName}{section.SegmentName}", section.VirtualAddress, section.FileOffset, (ulong)section.Stream.Length)); + } } // ...and the relocation tables @@ -165,9 +168,11 @@ private void LayoutSections(ref uint fileOffset, out uint segmentFileSize, out u } } - private protected override void EmitObjectFile(string objectFilePath) + private protected override void EmitObjectFile(Stream outputFileStream) { +#if !READYTORUN _sections.Add(_compactUnwindSection); +#endif // Segment + sections uint loadCommandsCount = 1; @@ -183,9 +188,7 @@ private protected override void EmitObjectFile(string objectFilePath) // so re-run the layout and this time calculate with the correct file offsets. uint fileOffset = (uint)MachHeader64.HeaderSize + loadCommandsSize; uint segmentFileOffset = fileOffset; - LayoutSections(ref fileOffset, out uint segmentFileSize, out ulong segmentSize); - - using var outputFileStream = new FileStream(objectFilePath, FileMode.Create); + LayoutSections(recordFinalLayout: true, ref fileOffset, out uint segmentFileSize, out ulong segmentSize); MachHeader64 machHeader = new MachHeader64 { @@ -301,7 +304,7 @@ private protected override void EmitObjectFile(string objectFilePath) stringTable.Write(outputFileStream); } - private protected override void CreateSection(ObjectNodeSection section, string comdatName, string symbolName, Stream sectionStream) + private protected override void CreateSection(ObjectNodeSection section, string comdatName, string symbolName, int sectionIndex, Stream sectionStream) { string segmentName = section.Name switch { @@ -357,10 +360,9 @@ private protected override void CreateSection(ObjectNodeSection section, string Flags = flags, }; - int sectionIndex = _sections.Count; _sections.Add(machSection); - base.CreateSection(section, comdatName, symbolName ?? $"lsection{sectionIndex}", sectionStream); + base.CreateSection(section, comdatName, symbolName ?? $"lsection{sectionIndex}", sectionIndex, sectionStream); } protected internal override void UpdateSectionAlignment(int sectionIndex, int alignment) @@ -419,7 +421,7 @@ protected internal override unsafe void EmitRelocation( break; case IMAGE_REL_BASED_RELPTR32: - if (_cpuType == CPU_TYPE_ARM64 || sectionIndex == EhFrameSectionIndex) + if (_cpuType == CPU_TYPE_ARM64 || IsEhFrameSection(sectionIndex)) { // On ARM64 we need to represent PC relative relocations as // subtraction and the PC offset is baked into the addend. @@ -535,6 +537,10 @@ private protected override void EmitRelocations(int sectionIndex, List false; +#endif + private void EmitRelocationsX64(int sectionIndex, List relocationList) { ICollection sectionRelocations = _sections[sectionIndex].Relocations; @@ -558,7 +564,7 @@ private void EmitRelocationsX64(int sectionIndex, List reloc IsPCRelative = false, }); } - else if (symbolicRelocation.Type == IMAGE_REL_BASED_RELPTR32 && sectionIndex == EhFrameSectionIndex) + else if (symbolicRelocation.Type == IMAGE_REL_BASED_RELPTR32 && IsEhFrameSection(sectionIndex)) { sectionRelocations.Add( new MachRelocation @@ -702,236 +708,10 @@ private void EmitRelocationsArm64(int sectionIndex, List rel } } - private void EmitCompactUnwindTable(IDictionary definedSymbols) - { - _compactUnwindStream = new MemoryStream(32 * _compactUnwindCodes.Count); - // Preset the size of the compact unwind section which is not generated yet - _compactUnwindStream.SetLength(32 * _compactUnwindCodes.Count); - - _compactUnwindSection = new MachSection("__LD", "__compact_unwind", _compactUnwindStream) - { - Log2Alignment = 3, - Flags = S_REGULAR | S_ATTR_DEBUG, - }; - - IList symbols = _symbolTable; - Span tempBuffer = stackalloc byte[8]; - foreach (var cu in _compactUnwindCodes) - { - EmitCompactUnwindSymbol(cu.PcStartSymbolName); - BinaryPrimitives.WriteUInt32LittleEndian(tempBuffer, cu.PcLength); - BinaryPrimitives.WriteUInt32LittleEndian(tempBuffer.Slice(4), cu.Code); - _compactUnwindStream.Write(tempBuffer); - EmitCompactUnwindSymbol(cu.PersonalitySymbolName); - EmitCompactUnwindSymbol(cu.LsdaSymbolName); - } - - void EmitCompactUnwindSymbol(string symbolName) - { - Span tempBuffer = stackalloc byte[8]; - if (symbolName is not null) - { - SymbolDefinition symbol = definedSymbols[symbolName]; - MachSection section = _sections[symbol.SectionIndex]; - BinaryPrimitives.WriteUInt64LittleEndian(tempBuffer, section.VirtualAddress + (ulong)symbol.Value); - _compactUnwindSection.Relocations.Add( - new MachRelocation - { - Address = (int)_compactUnwindStream.Position, - SymbolOrSectionIndex = (byte)(1 + symbol.SectionIndex), // 1-based - Length = 8, - RelocationType = ARM64_RELOC_UNSIGNED, - IsExternal = false, - IsPCRelative = false, - } - ); - } - _compactUnwindStream.Write(tempBuffer); - } - } + partial void EmitCompactUnwindTable(IDictionary definedSymbols); private protected override string ExternCName(string name) => "_" + name; - private static uint GetArm64CompactUnwindCode(byte[] blobData) - { - if (blobData == null || blobData.Length == 0) - { - return UNWIND_ARM64_MODE_FRAMELESS; - } - - Debug.Assert(blobData.Length % 8 == 0); - - short spReg = -1; - - int codeOffset = 0; - short cfaRegister = spReg; - int cfaOffset = 0; - int spOffset = 0; - - const int REG_DWARF_X19 = 19; - const int REG_DWARF_X30 = 30; - const int REG_DWARF_FP = 29; - const int REG_DWARF_D8 = 72; - const int REG_DWARF_D15 = 79; - const int REG_IDX_X19 = 0; - const int REG_IDX_X28 = 9; - const int REG_IDX_FP = 10; - const int REG_IDX_LR = 11; - const int REG_IDX_D8 = 12; - const int REG_IDX_D15 = 19; - Span registerOffset = stackalloc int[20]; - - registerOffset.Fill(int.MinValue); - - // First process all the CFI codes to figure out the layout of X19-X28, FP, LR, and - // D8-D15 on the stack. - int offset = 0; - while (offset < blobData.Length) - { - codeOffset = Math.Max(codeOffset, blobData[offset++]); - CFI_OPCODE opcode = (CFI_OPCODE)blobData[offset++]; - short dwarfReg = BinaryPrimitives.ReadInt16LittleEndian(blobData.AsSpan(offset)); - offset += sizeof(short); - int cfiOffset = BinaryPrimitives.ReadInt32LittleEndian(blobData.AsSpan(offset)); - offset += sizeof(int); - - switch (opcode) - { - case CFI_OPCODE.CFI_DEF_CFA_REGISTER: - cfaRegister = dwarfReg; - - if (spOffset != 0) - { - for (int i = 0; i < registerOffset.Length; i++) - if (registerOffset[i] != int.MinValue) - registerOffset[i] -= spOffset; - - cfaOffset += spOffset; - spOffset = 0; - } - - break; - - case CFI_OPCODE.CFI_REL_OFFSET: - Debug.Assert(cfaRegister == spReg); - if (dwarfReg >= REG_DWARF_X19 && dwarfReg <= REG_DWARF_X30) // X19 - X28, FP, LR - { - registerOffset[dwarfReg - REG_DWARF_X19 + REG_IDX_X19] = cfiOffset; - } - else if (dwarfReg >= REG_DWARF_D8 && dwarfReg <= REG_DWARF_D15) // D8 - D15 - { - registerOffset[dwarfReg - REG_DWARF_D8 + REG_IDX_D8] = cfiOffset; - } - else - { - // We cannot represent this register in the compact unwinding format, - // fallback to DWARF immediately. - return UNWIND_ARM64_MODE_DWARF; - } - break; - - case CFI_OPCODE.CFI_ADJUST_CFA_OFFSET: - if (cfaRegister != spReg) - { - cfaOffset += cfiOffset; - } - else - { - spOffset += cfiOffset; - - for (int i = 0; i < registerOffset.Length; i++) - if (registerOffset[i] != int.MinValue) - registerOffset[i] += cfiOffset; - } - break; - } - } - - uint unwindCode; - int nextOffset; - - if (cfaRegister == REG_DWARF_FP && - cfaOffset == 16 && - registerOffset[REG_IDX_FP] == -16 && - registerOffset[REG_IDX_LR] == -8) - { - // Frame format - FP/LR are saved on the top. SP is restored to FP+16 - unwindCode = UNWIND_ARM64_MODE_FRAME; - nextOffset = -24; - } - else if (cfaRegister == -1 && spOffset <= 65520 && - registerOffset[REG_IDX_FP] == int.MinValue && registerOffset[REG_IDX_LR] == int.MinValue) - { - // Frameless format - FP/LR are not saved, SP must fit within the representable range - uint encodedSpOffset = (uint)(spOffset / 16) << 12; - unwindCode = UNWIND_ARM64_MODE_FRAMELESS | encodedSpOffset; - nextOffset = spOffset - 8; - } - else - { - return UNWIND_ARM64_MODE_DWARF; - } - - // Check that the integer register pairs are in the right order and mark - // a flag for each successive pair that is present. - for (int i = REG_IDX_X19; i < REG_IDX_X28; i += 2) - { - if (registerOffset[i] == int.MinValue) - { - if (registerOffset[i + 1] != int.MinValue) - return UNWIND_ARM64_MODE_DWARF; - } - else if (registerOffset[i] == nextOffset) - { - if (registerOffset[i + 1] != nextOffset - 8) - return UNWIND_ARM64_MODE_DWARF; - nextOffset -= 16; - unwindCode |= UNWIND_ARM64_FRAME_X19_X20_PAIR << (i >> 1); - } - } - - // Check that the floating point register pairs are in the right order and mark - // a flag for each successive pair that is present. - for (int i = REG_IDX_D8; i < REG_IDX_D15; i += 2) - { - if (registerOffset[i] == int.MinValue) - { - if (registerOffset[i + 1] != int.MinValue) - return UNWIND_ARM64_MODE_DWARF; - } - else if (registerOffset[i] == nextOffset) - { - if (registerOffset[i + 1] != nextOffset - 8) - return UNWIND_ARM64_MODE_DWARF; - nextOffset -= 16; - unwindCode |= UNWIND_ARM64_FRAME_D8_D9_PAIR << (i >> 1); - } - } - - return unwindCode; - } - - private protected override bool EmitCompactUnwinding(string startSymbolName, ulong length, string lsdaSymbolName, byte[] blob) - { - uint encoding = _compactUnwindDwarfCode; - - if (_cpuType == CPU_TYPE_ARM64) - { - encoding = GetArm64CompactUnwindCode(blob); - } - - _compactUnwindCodes.Add(new CompactUnwindCode( - PcStartSymbolName: startSymbolName, - PcLength: (uint)length, - Code: encoding | (encoding != _compactUnwindDwarfCode && lsdaSymbolName is not null ? 0x40000000u : 0), // UNWIND_HAS_LSDA - LsdaSymbolName: encoding != _compactUnwindDwarfCode ? lsdaSymbolName : null - )); - - return encoding != _compactUnwindDwarfCode; - } - - private protected override bool UseFrameNames => true; - private static bool IsSectionSymbolName(string symbolName) => symbolName.StartsWith('l'); private struct MachHeader64 @@ -946,7 +726,7 @@ private struct MachHeader64 public static int HeaderSize => 32; - public void Write(FileStream stream) + public void Write(Stream stream) { Span buffer = stackalloc byte[HeaderSize]; @@ -977,7 +757,7 @@ public struct MachSegment64Header public static int HeaderSize => 72; - public void Write(FileStream stream) + public void Write(Stream stream) { Span buffer = stackalloc byte[HeaderSize]; @@ -1036,7 +816,7 @@ public MachSection(string segmentName, string sectionName, Stream stream) this.relocationCollection = null; } - public void WriteHeader(FileStream stream) + public void WriteHeader(Stream stream) { Span buffer = stackalloc byte[HeaderSize]; @@ -1066,7 +846,7 @@ private sealed class MachRelocation public byte Length { get; init; } public byte RelocationType { get; init; } - public void Write(FileStream stream) + public void Write(Stream stream) { Span relocationBuffer = stackalloc byte[8]; uint info = SymbolOrSectionIndex; @@ -1088,7 +868,7 @@ private sealed class MachSymbol public ushort Descriptor { get; init; } public ulong Value { get; init; } - public void Write(FileStream stream, MachStringTable stringTable) + public void Write(Stream stream, MachStringTable stringTable) { Span buffer = stackalloc byte[16]; uint nameIndex = stringTable.GetStringOffset(Name); @@ -1112,7 +892,7 @@ private sealed class MachSymbolTableCommandHeader public static int HeaderSize => 24; - public void Write(FileStream stream) + public void Write(Stream stream) { Span buffer = stackalloc byte[HeaderSize]; @@ -1150,7 +930,7 @@ private sealed class MachDynamicLinkEditSymbolTable public static int HeaderSize => 80; - public void Write(FileStream stream) + public void Write(Stream stream) { Span buffer = stackalloc byte[HeaderSize]; @@ -1187,7 +967,7 @@ private struct MachBuildVersionCommandHeader public static int HeaderSize => 24; - public void Write(FileStream stream) + public void Write(Stream stream) { Span buffer = stackalloc byte[HeaderSize]; diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/ObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs similarity index 67% rename from src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/ObjectWriter.cs rename to src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs index 6db2bc20b01b25..91b51c384919a9 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/ObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs @@ -7,25 +7,28 @@ using System.Diagnostics; using System.IO; using System.Linq; +using System.Xml.Linq; using ILCompiler.DependencyAnalysis; using ILCompiler.DependencyAnalysisFramework; +using Internal.Text; using Internal.TypeSystem; -using Internal.TypeSystem.TypesDebugInfo; -using ObjectData = ILCompiler.DependencyAnalysis.ObjectNode.ObjectData; +using static ILCompiler.DependencyAnalysis.ObjectNode; using static ILCompiler.DependencyAnalysis.RelocType; +using ObjectData = ILCompiler.DependencyAnalysis.ObjectNode.ObjectData; namespace ILCompiler.ObjectWriter { - public abstract class ObjectWriter + public abstract partial class ObjectWriter { private protected sealed record SymbolDefinition(int SectionIndex, long Value, int Size = 0, bool Global = false); - private protected sealed record SymbolicRelocation(long Offset, RelocType Type, string SymbolName, long Addend = 0); - private protected sealed record BlockToRelocate(int SectionIndex, long Offset, byte[] Data, Relocation[] Relocations); + protected sealed record SymbolicRelocation(long Offset, RelocType Type, string SymbolName, long Addend = 0); + private sealed record BlockToRelocate(int SectionIndex, long Offset, byte[] Data, Relocation[] Relocations); + private protected sealed record ChecksumsToCalculate(int SectionIndex, long Offset, Relocation[] ChecksumRelocations); private protected readonly NodeFactory _nodeFactory; private protected readonly ObjectWritingOptions _options; + private protected readonly OutputInfoBuilder _outputInfoBuilder; private readonly bool _isSingleFileCompilation; - private readonly bool _usesSubsectionsViaSymbols; private readonly Dictionary _mangledNameMap = new(); @@ -35,19 +38,17 @@ private protected sealed record BlockToRelocate(int SectionIndex, long Offset, b private readonly Dictionary _sectionNameToSectionIndex = new(StringComparer.Ordinal); private readonly List _sectionIndexToData = new(); private readonly List> _sectionIndexToRelocations = new(); + private protected readonly List _outputSectionLayout = []; // Symbol table private readonly Dictionary _definedSymbols = new(StringComparer.Ordinal); - // Debugging - private UserDefinedTypeDescriptor _userDefinedTypeDescriptor; - - private protected ObjectWriter(NodeFactory factory, ObjectWritingOptions options) + private protected ObjectWriter(NodeFactory factory, ObjectWritingOptions options, OutputInfoBuilder outputInfoBuilder = null) { _nodeFactory = factory; _options = options; + _outputInfoBuilder = outputInfoBuilder; _isSingleFileCompilation = _nodeFactory.CompilationModuleGroup.IsSingleFileCompilation; - _usesSubsectionsViaSymbols = factory.Target.IsApplePlatform; // Padding byte for code sections (NOP for x86/x64) _insPaddingByte = factory.Target.Architecture switch @@ -57,8 +58,9 @@ private protected ObjectWriter(NodeFactory factory, ObjectWritingOptions options _ => 0 }; } + private protected virtual bool UsesSubsectionsViaSymbols => false; - private protected abstract void CreateSection(ObjectNodeSection section, string comdatName, string symbolName, Stream sectionStream); + private protected abstract void CreateSection(ObjectNodeSection section, string comdatName, string symbolName, int sectionIndex, Stream sectionStream); protected internal abstract void UpdateSectionAlignment(int sectionIndex, int alignment); @@ -85,7 +87,7 @@ private protected SectionWriter GetOrCreateSection(ObjectNodeSection section, st { sectionData = new SectionData(section.Type == SectionType.Executable ? _insPaddingByte : (byte)0); sectionIndex = _sectionIndexToData.Count; - CreateSection(section, comdatName, symbolName, sectionData.GetReadStream()); + CreateSection(section, comdatName, symbolName, sectionIndex, sectionData.GetReadStream()); _sectionIndexToData.Add(sectionData); _sectionIndexToRelocations.Add(new()); if (comdatName is null) @@ -106,7 +108,7 @@ private protected SectionWriter GetOrCreateSection(ObjectNodeSection section, st private protected bool ShouldShareSymbol(ObjectNode node) { - if (_usesSubsectionsViaSymbols) + if (UsesSubsectionsViaSymbols) return false; return ShouldShareSymbol(node, node.GetSection(_nodeFactory)); @@ -114,7 +116,7 @@ private protected bool ShouldShareSymbol(ObjectNode node) private protected bool ShouldShareSymbol(ObjectNode node, ObjectNodeSection section) { - if (_usesSubsectionsViaSymbols) + if (UsesSubsectionsViaSymbols) return false; // Foldable sections are always COMDATs @@ -127,8 +129,7 @@ private protected bool ShouldShareSymbol(ObjectNode node, ObjectNodeSection sect if (node is not ISymbolNode) return false; - // These intentionally clash with one another, but are merged with linker directives so should not be COMDAT folded - if (node is ModulesSectionNode) + if (node is IUniqueSymbolNode) return false; return true; @@ -142,7 +143,7 @@ private unsafe void EmitOrResolveRelocation( string symbolName, long addend) { - if (!_usesSubsectionsViaSymbols && + if (!UsesSubsectionsViaSymbols && relocType is IMAGE_REL_BASED_REL32 or IMAGE_REL_BASED_RELPTR32 or IMAGE_REL_BASED_ARM64_BRANCH26 or IMAGE_REL_BASED_THUMB_BRANCH24 or IMAGE_REL_BASED_THUMB_MOV32_PCREL && _definedSymbols.TryGetValue(symbolName, out SymbolDefinition definedSymbol) && @@ -156,8 +157,15 @@ or IMAGE_REL_BASED_THUMB_BRANCH24 or IMAGE_REL_BASED_THUMB_MOV32_PCREL && // and R_ARM_THM_MOVW_PREL_NC relocations using the formula ((S + A) | T) – P. // The thumb bit is thus supposed to be only added once. // For R_ARM_THM_JUMP24 the thumb bit cannot be encoded, so mask it out. + // + // R2R doesn't use add the thumb bit to the symbol value, so we don't need to do this here. +#if !READYTORUN long maskThumbBitOut = relocType is IMAGE_REL_BASED_THUMB_BRANCH24 or IMAGE_REL_BASED_THUMB_MOV32_PCREL ? 1 : 0; long maskThumbBitIn = relocType is IMAGE_REL_BASED_THUMB_MOV32_PCREL ? 1 : 0; +#else + long maskThumbBitOut = 0; + long maskThumbBitIn = 0; +#endif long adjustedAddend = addend; adjustedAddend -= relocType switch @@ -183,6 +191,14 @@ or IMAGE_REL_BASED_THUMB_BRANCH24 or IMAGE_REL_BASED_THUMB_MOV32_PCREL && } } } + else if (relocType is IMAGE_REL_SYMBOL_SIZE && + _definedSymbols.TryGetValue(symbolName, out definedSymbol)) + { + fixed (byte* pData = data) + { + Relocation.WriteValue(relocType, (void*)pData, definedSymbol.Size); + } + } else { EmitRelocation(sectionIndex, offset, data, relocType, symbolName, addend); @@ -266,43 +282,13 @@ private protected string GetMangledName(ISymbolNode symbolNode) return symbolName; } - private protected abstract void EmitUnwindInfo( - SectionWriter sectionWriter, - INodeWithCodeInfo nodeWithCodeInfo, - string currentSymbolName); - - private protected uint GetVarTypeIndex(bool isStateMachineMoveNextMethod, DebugVarInfoMetadata debugVar) - { - uint typeIndex; - try - { - if (isStateMachineMoveNextMethod && debugVar.DebugVarInfo.VarNumber == 0) - { - typeIndex = _userDefinedTypeDescriptor.GetStateMachineThisVariableTypeIndex(debugVar.Type); - // FIXME - // varName = "locals"; - } - else - { - typeIndex = _userDefinedTypeDescriptor.GetVariableTypeIndex(debugVar.Type); - } - } - catch (TypeSystemException) - { - typeIndex = 0; // T_NOTYPE - // FIXME - // Debug.Fail(); - } - return typeIndex; - } - private protected virtual void EmitSectionsAndLayout() { } - private protected abstract void EmitObjectFile(string objectFilePath); + private protected abstract void EmitObjectFile(Stream outputFileStream); - private protected abstract void CreateEhSections(); + partial void EmitDebugInfo(IReadOnlyCollection nodes, Logger logger); private SortedSet GetUndefinedSymbols() { @@ -318,25 +304,7 @@ private SortedSet GetUndefinedSymbols() return undefinedSymbolSet; } - private protected abstract ITypesDebugInfoWriter CreateDebugInfoBuilder(); - - private protected abstract void EmitDebugFunctionInfo( - uint methodTypeIndex, - string methodName, - SymbolDefinition methodSymbol, - INodeWithDebugInfo debugNode, - bool hasSequencePoints); - - private protected virtual void EmitDebugThunkInfo( - string methodName, - SymbolDefinition methodSymbol, - INodeWithDebugInfo debugNode) - { - } - - private protected abstract void EmitDebugSections(IDictionary definedSymbols); - - private void EmitObject(string objectFilePath, IReadOnlyCollection nodes, IObjectDumper dumper, Logger logger) + public void EmitObject(Stream outputFileStream, IReadOnlyCollection nodes, IObjectDumper dumper, Logger logger) { // Pre-create some of the sections GetOrCreateSection(ObjectNodeSection.TextSection); @@ -350,12 +318,9 @@ private void EmitObject(string objectFilePath, IReadOnlyCollection blocksToRelocate = new(); + List symbolRangeNodes = []; + List blocksToRelocate = []; + List checksumRelocations = []; foreach (DependencyNode depNode in nodes) { - ObjectNode node = depNode as ObjectNode; - if (node is null) + if (depNode is ISymbolRangeNode symbolRange) + { + symbolRangeNodes.Add(symbolRange); + continue; + } + + if (depNode is not ObjectNode node) continue; if (logger.IsVerbose) @@ -385,12 +357,15 @@ private void EmitObject(string objectFilePath, IReadOnlyCollection checksumRelocationsBuilder = default; foreach (Relocation reloc in blockToRelocate.Relocations) { +#if READYTORUN + ISymbolNode relocTarget = reloc.Target; +#else ISymbolNode relocTarget = _nodeFactory.ObjectInterner.GetDeduplicatedSymbol(_nodeFactory, reloc.Target); +#endif + + if (reloc.RelocType == RelocType.IMAGE_REL_FILE_CHECKSUM_CALLBACK) + { + // Checksum relocations don't get emitted into the image. + // We manually proces them after we do all other object emission. + checksumRelocationsBuilder.Add(reloc); + continue; + } string relocSymbolName = GetMangledName(relocTarget); @@ -490,15 +550,12 @@ private void EmitObject(string objectFilePath, IReadOnlyCollection relocationList in _sectionIndexToRelocations) + { + EmitRelocations(relocSectionIndex, relocationList); + relocSectionIndex++; + } - // Ensure any allocated MethodTables have debug info - if (node is ConstructedEETypeNode methodTable) - { - _userDefinedTypeDescriptor.GetTypeIndex(methodTable.Type, needsCompleteType: true); - } + EmitObjectFile(outputFileStream); - if (node is INodeWithDebugInfo debugNode and ISymbolDefinitionNode symbolDefinitionNode) - { - string methodName = GetMangledName(symbolDefinitionNode); - if (_definedSymbols.TryGetValue(methodName, out var methodSymbol)) - { - if (node is IMethodNode methodNode) - { - bool hasSequencePoints = debugNode.GetNativeSequencePoints().Any(); - uint methodTypeIndex = hasSequencePoints ? _userDefinedTypeDescriptor.GetMethodFunctionIdTypeIndex(methodNode.Method) : 0; - EmitDebugFunctionInfo(methodTypeIndex, methodName, methodSymbol, debugNode, hasSequencePoints); - } - else - { - EmitDebugThunkInfo(methodName, methodSymbol, debugNode); - } - } - } - } + if (checksumRelocations.Count > 0) + { + EmitChecksums(outputFileStream, checksumRelocations); + } - // Ensure all fields associated with generated static bases have debug info - foreach (MetadataType typeWithStaticBase in _nodeFactory.MetadataManager.GetTypesWithStaticBases()) + if (_outputInfoBuilder is not null) + { + foreach (var outputSection in _outputSectionLayout) { - _userDefinedTypeDescriptor.GetTypeIndex(typeWithStaticBase, needsCompleteType: true); + _outputInfoBuilder.AddSection(outputSection); } + } + } - EmitDebugSections(_definedSymbols); + private static string GetNodeTypeName(Type nodeType) + { + string name = nodeType.ToString(); + int firstGeneric = name.IndexOf('['); + + if (firstGeneric < 0) + { + firstGeneric = name.Length; } - EmitSymbolTable(_definedSymbols, GetUndefinedSymbols()); + int lastDot = name.LastIndexOf('.', firstGeneric - 1, firstGeneric); - int relocSectionIndex = 0; - foreach (List relocationList in _sectionIndexToRelocations) + if (lastDot > 0) { - EmitRelocations(relocSectionIndex, relocationList); - relocSectionIndex++; + name = name.Substring(lastDot + 1); } - EmitObjectFile(objectFilePath); + return name; } + private void EmitChecksums(Stream outputFileStream, List checksumRelocations) + { + MemoryStream originalOutputStream = new(); + outputFileStream.Seek(0, SeekOrigin.Begin); + outputFileStream.CopyTo(originalOutputStream); + byte[] originalOutput = originalOutputStream.ToArray(); + EmitChecksumsForObject(outputFileStream, checksumRelocations, originalOutput); + } + + private protected virtual void EmitChecksumsForObject(Stream outputFileStream, List checksumRelocations, ReadOnlySpan originalOutput) + { + foreach (var block in checksumRelocations) + { + foreach (var reloc in block.ChecksumRelocations) + { + IChecksumNode checksum = (IChecksumNode)reloc.Target; + + byte[] checksumValue = new byte[checksum.ChecksumSize]; + checksum.EmitChecksum(originalOutput, checksumValue); + + var checksumOffset = (long)_outputSectionLayout[block.SectionIndex].FilePosition + block.Offset + reloc.Offset; + outputFileStream.Seek(checksumOffset, SeekOrigin.Begin); + outputFileStream.Write(checksumValue); + } + } + } + + partial void HandleControlFlowForRelocation(ISymbolNode relocTarget, string relocSymbolName); + + partial void PrepareForUnwindInfo(); + + partial void EmitUnwindInfoForNode(ObjectNode node, string currentSymbolName, SectionWriter sectionWriter); + public static void EmitObject(string objectFilePath, IReadOnlyCollection nodes, NodeFactory factory, ObjectWritingOptions options, IObjectDumper dumper, Logger logger) { var stopwatch = Stopwatch.StartNew(); @@ -578,7 +652,9 @@ public static void EmitObject(string objectFilePath, IReadOnlyCollection /// Base class for symbols and nodes in the output file implements common logic /// for section / offset ordering. /// - public class OutputItem + public abstract class OutputItem { public class Comparer : IComparer { - public readonly static Comparer Instance = new Comparer(); + public static readonly Comparer Instance = new Comparer(); public int Compare([AllowNull] OutputItem x, [AllowNull] OutputItem y) { @@ -44,14 +42,14 @@ public int Compare([AllowNull] OutputItem x, [AllowNull] OutputItem y) /// /// Offset relative to section beginning /// - public readonly int Offset; + public readonly ulong Offset; /// /// Item name /// public readonly string Name; - public OutputItem(int sectionIndex, int offset, string name) + public OutputItem(int sectionIndex, ulong offset, string name) { SectionIndex = sectionIndex; Offset = offset; @@ -62,7 +60,7 @@ public OutputItem(int sectionIndex, int offset, string name) /// /// This class represents a single node (contiguous block of data) in the output R2R PE file. /// - public class OutputNode : OutputItem + public sealed class OutputNode : OutputItem { /// /// Node length (number of bytes). This doesn't include any external alignment @@ -75,7 +73,7 @@ public class OutputNode : OutputItem /// public int Relocations { get; private set; } - public OutputNode(int sectionIndex, int offset, int length, string name) + public OutputNode(int sectionIndex, ulong offset, int length, string name) : base(sectionIndex, offset, name) { Length = length; @@ -93,41 +91,44 @@ public void AddRelocation() /// node beginnings (most nodes have a "start symbol" representing the beginning /// of the node). /// - public class OutputSymbol : OutputItem + public sealed class OutputSymbol : OutputItem { - public OutputSymbol(int sectionIndex, int offset, string name) + public OutputSymbol(int sectionIndex, ulong offset, string name) : base(sectionIndex, offset, name) { } } + public sealed class OutputSection + { + public OutputSection(string name, ulong virtualAddress, ulong filePosition, ulong length) + { + Name = name; + VirtualAddress = virtualAddress; + FilePosition = filePosition; + Length = length; + } + + public string Name { get; } + public ulong VirtualAddress { get; } + public ulong FilePosition { get; } + public ulong Length { get; } + } + /// /// Common class used to collect information to use when emitting map files and symbol files. /// public class OutputInfoBuilder { - private readonly List _inputModules; - private readonly List _nodes; - private readonly List _symbols; - private readonly List
_sections; - - private readonly Dictionary _nodeSymbolMap; - private readonly Dictionary _methodSymbolMap; + private readonly List _inputModules = []; + private readonly List _nodes = []; + private readonly List _symbols = []; + private readonly List _sections = []; - private readonly Dictionary _relocCounts; + private readonly Dictionary _nodeSymbolMap = []; + private readonly Dictionary _methodSymbolMap = []; - public OutputInfoBuilder() - { - _inputModules = new List(); - _nodes = new List(); - _symbols = new List(); - _sections = new List
(); - - _nodeSymbolMap = new Dictionary(); - _methodSymbolMap = new Dictionary(); - - _relocCounts = new Dictionary(); - } + private readonly Dictionary _relocCounts = []; public void AddInputModule(EcmaModule module) { @@ -152,12 +153,12 @@ public void AddSymbol(OutputSymbol symbol) _symbols.Add(symbol); } - public void AddSection(Section section) + public void AddSection(OutputSection section) { _sections.Add(section); } - public void AddMethod(MethodWithGCInfo method, ISymbolDefinitionNode symbol) + public void AddMethod(IMethodNode method, ISymbolDefinitionNode symbol) { _methodSymbolMap.Add(symbol, method); } @@ -181,11 +182,10 @@ public bool FindSymbol(OutputItem item, out int index) public IEnumerable EnumerateMethods() { - DebugNameFormatter nameFormatter = new DebugNameFormatter(); TypeNameFormatter typeNameFormatter = TypeString.Instance; - foreach (KeyValuePair symbolMethodPair in _methodSymbolMap) + foreach (KeyValuePair symbolMethodPair in _methodSymbolMap) { - MethodInfo methodInfo = new MethodInfo(); + MethodInfo methodInfo = default; if (symbolMethodPair.Value.Method.GetTypicalMethodDefinition() is EcmaMethod ecmaMethod) { methodInfo.MethodToken = (uint)MetadataTokens.GetToken(ecmaMethod.Handle); @@ -193,8 +193,8 @@ public IEnumerable EnumerateMethods() } methodInfo.Name = FormatMethodName(symbolMethodPair.Value.Method, typeNameFormatter); OutputNode node = _nodeSymbolMap[symbolMethodPair.Key]; - Section section = _sections[node.SectionIndex]; - methodInfo.HotRVA = (uint)(section.RVAWhenPlaced + node.Offset); + OutputSection section = _sections[node.SectionIndex]; + methodInfo.HotRVA = (uint)(section.VirtualAddress + (ulong)node.Offset); methodInfo.HotLength = (uint)node.Length; methodInfo.ColdRVA = 0; methodInfo.ColdLength = 0; @@ -212,18 +212,18 @@ public IEnumerable EnumerateInputAssemblies() } } - private string FormatMethodName(MethodDesc method, TypeNameFormatter typeNameFormatter) + private static string FormatMethodName(MethodDesc method, TypeNameFormatter typeNameFormatter) { StringBuilder output = new StringBuilder(); if (!method.Signature.ReturnType.IsVoid) { output.Append(typeNameFormatter.FormatName(method.Signature.ReturnType)); - output.Append(" "); + output.Append(' '); } output.Append(typeNameFormatter.FormatName(method.OwningType)); output.Append("::"); output.Append(method.GetName()); - output.Append("("); + output.Append('('); for (int paramIndex = 0; paramIndex < method.Signature.Length; paramIndex++) { if (paramIndex != 0) @@ -232,16 +232,16 @@ private string FormatMethodName(MethodDesc method, TypeNameFormatter typeNameFor } output.Append(typeNameFormatter.FormatName(method.Signature[paramIndex])); } - output.Append(")"); + output.Append(')'); return output.ToString(); } public IReadOnlyList Nodes => _nodes; - public IReadOnlyList
Sections => _sections; + public IReadOnlyList Sections => _sections; public IReadOnlyList Symbols => _symbols; public IReadOnlyDictionary NodeSymbolMap => _nodeSymbolMap; - public IReadOnlyDictionary MethodSymbolMap => _methodSymbolMap; + public IReadOnlyDictionary MethodSymbolMap => _methodSymbolMap; public IReadOnlyDictionary RelocCounts => _relocCounts; } diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs new file mode 100644 index 00000000000000..6e28b753d2ce88 --- /dev/null +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs @@ -0,0 +1,934 @@ +// 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.Binary; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Numerics; +using System.Reflection.Metadata; +using System.Reflection.PortableExecutable; +using System.Security.Cryptography; +using System.Text; +using ILCompiler.DependencyAnalysis; +using Internal.TypeSystem; + +namespace ILCompiler.ObjectWriter +{ + /// + /// PE image file writer built on top of the COFF object writer infrastructure. + /// This writer reuses sections prepared by the base class and emits a minimal + /// PE/PE+ image containing those sections. + /// + internal sealed class PEObjectWriter : CoffObjectWriter + { + /// + /// Number of low-order RVA bits that must match file position on Linux. + /// + /// + /// This is because the CoreCLR runtime on Linux requires the 12-16 low-order bits of section RVAs + /// (the number of bits corresponds to the page size) to be identical to the file offset, + /// otherwise memory mapping of the file fails. + /// + private const int RVABitsToMatchFilePos = 16; + internal const int DosHeaderSize = 0x80; + private const int NoSectionIndex = -1; + + private static ReadOnlySpan DosHeader => // DosHeaderSize + [ + 0x4d, 0x5a, 0x90, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, + 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + + 0x80, 0x00, 0x00, 0x00, // NT Header offset (0x80 == DosHeader.Length) + + 0x0e, 0x1f, 0xba, 0x0e, 0x00, 0xb4, 0x09, 0xcd, + 0x21, 0xb8, 0x01, 0x4c, 0xcd, 0x21, 0x54, 0x68, + 0x69, 0x73, 0x20, 0x70, 0x72, 0x6f, 0x67, 0x72, + 0x61, 0x6d, 0x20, 0x63, 0x61, 0x6e, 0x6e, 0x6f, + 0x74, 0x20, 0x62, 0x65, 0x20, 0x72, 0x75, 0x6e, + 0x20, 0x69, 0x6e, 0x20, 0x44, 0x4f, 0x53, 0x20, + 0x6d, 0x6f, 0x64, 0x65, 0x2e, 0x0d, 0x0d, 0x0a, + 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + ]; + + private static ObjectNodeSection ExportDirectorySection = new ObjectNodeSection("edata", SectionType.ReadOnly); + private static ObjectNodeSection BaseRelocSection = new ObjectNodeSection("reloc", SectionType.ReadOnly); + + private uint _peSectionAlignment; + private uint _peFileAlignment; + private readonly string _outputPath; + private readonly int _requestedSectionAlignment; + private readonly int? _coffTimestamp; + + // Relocations that we can resolve at emit time (ie not file-based relocations). + private Dictionary> _resolvableRelocations = []; + + private int _pdataSectionIndex = NoSectionIndex; + private int _debugSectionIndex = NoSectionIndex; + private int _exportSectionIndex = NoSectionIndex; + private int _baseRelocSectionIndex = NoSectionIndex; + private int _corMetaSectionIndex = NoSectionIndex; + private int _rsrcSectionIndex = NoSectionIndex; + + // Base relocation (.reloc) bookkeeping + private readonly SortedDictionary> _baseRelocMap = new(); + private Dictionary _definedSymbols = []; + + private HashSet _exportedSymbolNames = new(); + private long _coffHeaderOffset; + + public PEObjectWriter(NodeFactory factory, ObjectWritingOptions options, OutputInfoBuilder outputInfoBuilder, string outputPath, int sectionAlignment, int? coffTimestamp) + : base(factory, options, outputInfoBuilder) + { + _outputPath = outputPath; + _requestedSectionAlignment = sectionAlignment; + _coffTimestamp = coffTimestamp; + } + + public void AddExportedSymbol(string symbol) + { + if (!string.IsNullOrEmpty(symbol)) + { + _exportedSymbolNames.Add(symbol); + } + } + + private protected override void CreateSection(ObjectNodeSection section, string comdatName, string symbolName, int sectionIndex, Stream sectionStream) + { + // COMDAT sections are not supported in PE files + base.CreateSection(section, comdatName: null, symbolName, sectionIndex, sectionStream); + + if (_requestedSectionAlignment != 0) + { + UpdateSectionAlignment(sectionIndex, _requestedSectionAlignment); + } + } + + private struct PEOptionalHeader + { + public bool IsPE32Plus { get; } + + // Standard fields + public byte MajorLinkerVersion { get; set; } + public byte MinorLinkerVersion { get; set; } + public uint SizeOfCode { get; set; } + public uint SizeOfInitializedData { get; set; } + public uint SizeOfUninitializedData { get; set; } + public uint AddressOfEntryPoint { get; set; } + public uint BaseOfCode { get; set; } + public uint BaseOfData { get; set; } + + // Windows-specific fields + public ulong ImageBase { get; set; } + public uint SectionAlignment { get; set; } + public uint FileAlignment { get; set; } + public ushort MajorOperatingSystemVersion { get; set; } + public ushort MinorOperatingSystemVersion { get; set; } + public ushort MajorImageVersion { get; set; } + public ushort MinorImageVersion { get; set; } + public ushort MajorSubsystemVersion { get; set; } + public ushort MinorSubsystemVersion { get; set; } + public Subsystem Subsystem { get; set; } + public DllCharacteristics DllCharacteristics { get; set; } + public uint Win32VersionValue { get; set; } + public uint SizeOfImage { get; set; } + public uint SizeOfHeaders { get; set; } + public uint CheckSum { get; set; } + public ulong SizeOfStackReserve { get; set; } + public ulong SizeOfStackCommit { get; set; } + public ulong SizeOfHeapReserve { get; set; } + public ulong SizeOfHeapCommit { get; set; } + public uint LoaderFlags { get; set; } + public uint NumberOfRvaAndSizes { get; set; } + + public PEOptionalHeader(bool isPe32Plus) + { + IsPE32Plus = isPe32Plus; + + // Defaults taken from PETargetExtensions (PEHeaderConstants / PE32/PE64 constants) + MajorLinkerVersion = PEHeaderConstants.MajorLinkerVersion; + MinorLinkerVersion = PEHeaderConstants.MinorLinkerVersion; + + SizeOfCode = 0; + SizeOfInitializedData = 0; + SizeOfUninitializedData = 0; + AddressOfEntryPoint = 0; + BaseOfCode = 0; + BaseOfData = 0; + + ImageBase = IsPE32Plus ? PE64HeaderConstants.ExeImageBase : PE32HeaderConstants.ImageBase; + // Use PETargetExtensions defaults for alignments and versions + SectionAlignment = (uint)PEHeaderConstants.SectionAlignment; + FileAlignment = 0x200; + + MajorOperatingSystemVersion = PEHeaderConstants.MajorOperatingSystemVersion; + MinorOperatingSystemVersion = PEHeaderConstants.MinorOperatingSystemVersion; + + MajorImageVersion = PEHeaderConstants.MajorImageVersion; + MinorImageVersion = PEHeaderConstants.MinorImageVersion; + + MajorSubsystemVersion = PEHeaderConstants.MajorSubsystemVersion; + MinorSubsystemVersion = PEHeaderConstants.MinorSubsystemVersion; + + Subsystem = Subsystem.WindowsCui; + DllCharacteristics = DllCharacteristics.DynamicBase | DllCharacteristics.NxCompatible | DllCharacteristics.NoSeh | DllCharacteristics.TerminalServerAware; + + Win32VersionValue = 0; + SizeOfImage = 0; + SizeOfHeaders = 0; + CheckSum = 0; + + // Use PE32/PE64 per-target defaults for stack/heap + SizeOfStackReserve = IsPE32Plus ? PE64HeaderConstants.SizeOfStackReserve : PE32HeaderConstants.SizeOfStackReserve; + SizeOfStackCommit = IsPE32Plus ? PE64HeaderConstants.SizeOfStackCommit : PE32HeaderConstants.SizeOfStackCommit; + SizeOfHeapReserve = IsPE32Plus ? PE64HeaderConstants.SizeOfHeapReserve : PE32HeaderConstants.SizeOfHeapReserve; + SizeOfHeapCommit = IsPE32Plus ? PE64HeaderConstants.SizeOfHeapCommit : PE32HeaderConstants.SizeOfHeapCommit; + + LoaderFlags = 0; + NumberOfRvaAndSizes = 16u; + } + + public void Write(Stream stream, OptionalHeaderDataDirectories dataDirectories) + { + // Write the optional header fields directly to the stream in the + // correct order for PE32 / PE32+ without allocating a giant buffer. + + // Magic + WriteLittleEndian(stream, (ushort)(IsPE32Plus ? 0x20b : 0x10b)); + + // Linker versions + stream.WriteByte(MajorLinkerVersion); + stream.WriteByte(MinorLinkerVersion); + + // SizeOfCode + WriteLittleEndian(stream, SizeOfCode); + + // SizeOfInitializedData + WriteLittleEndian(stream, SizeOfInitializedData); + + // SizeOfUninitializedData + WriteLittleEndian(stream, SizeOfUninitializedData); + + // AddressOfEntryPoint + WriteLittleEndian(stream, AddressOfEntryPoint); + + // BaseOfCode + WriteLittleEndian(stream, BaseOfCode); + + if (!IsPE32Plus) + { + WriteLittleEndian(stream, BaseOfData); + WriteLittleEndian(stream, (uint)ImageBase); + } + else + { + WriteLittleEndian(stream, ImageBase); + } + + // SectionAlignment and FileAlignment + WriteLittleEndian(stream, SectionAlignment); + WriteLittleEndian(stream, FileAlignment); + + // Versioning + WriteLittleEndian(stream, MajorOperatingSystemVersion); + WriteLittleEndian(stream, MinorOperatingSystemVersion); + WriteLittleEndian(stream, MajorImageVersion); + WriteLittleEndian(stream, MinorImageVersion); + WriteLittleEndian(stream, MajorSubsystemVersion); + WriteLittleEndian(stream, MinorSubsystemVersion); + + // Win32VersionValue + WriteLittleEndian(stream, 0); + + // SizeOfImage + WriteLittleEndian(stream, SizeOfImage); + + // SizeOfHeaders + WriteLittleEndian(stream, SizeOfHeaders); + + // CheckSum + WriteLittleEndian(stream, CheckSum); + + // Subsystem + WriteLittleEndian(stream, (ushort)Subsystem); + + // DllCharacteristics + WriteLittleEndian(stream, (ushort)DllCharacteristics); + + if (!IsPE32Plus) + { + WriteLittleEndian(stream, (uint)SizeOfStackReserve); + WriteLittleEndian(stream, (uint)SizeOfStackCommit); + WriteLittleEndian(stream, (uint)SizeOfHeapReserve); + WriteLittleEndian(stream, (uint)SizeOfHeapCommit); + } + else + { + WriteLittleEndian(stream, SizeOfStackReserve); + WriteLittleEndian(stream, SizeOfStackCommit); + WriteLittleEndian(stream, SizeOfHeapReserve); + WriteLittleEndian(stream, SizeOfHeapCommit); + } + + WriteLittleEndian(stream, LoaderFlags); + WriteLittleEndian(stream, NumberOfRvaAndSizes); + + // Data directories start after NumberOfRvaAndSizes for PE32+ + dataDirectories.WriteTo(stream, (int)NumberOfRvaAndSizes); + } + } + + private sealed class OptionalHeaderDataDirectories + { + private readonly (uint VirtualAddress, uint Size)[] _entries; + + public OptionalHeaderDataDirectories() + { + _entries = new (uint, uint)[16]; + } + + public void SetIfNonEmpty(int index, uint virtualAddress, uint size) + { + if ((uint)index >= (uint)_entries.Length) + throw new ArgumentOutOfRangeException(nameof(index)); + + if (size != 0) + { + _entries[index] = (virtualAddress, size); + } + } + + public void WriteTo(Stream stream, int count) + { + int max = Math.Min(count, _entries.Length); + for (int i = 0; i < max; i++) + { + WriteLittleEndian(stream, _entries[i].VirtualAddress); + WriteLittleEndian(stream, _entries[i].Size); + } + } + } + + // Named image directory indices for the PE optional header. This avoids + // using magic numbers when populating data directories. + private enum ImageDirectoryEntry + { + Export = 0, + Import = 1, + Resource = 2, + Exception = 3, + Security = 4, + BaseRelocation = 5, + Debug = 6, + Architecture = 7, + GlobalPtr = 8, + TLS = 9, + LoadConfig = 10, + BoundImport = 11, + IAT = 12, + DelayImport = 13, + CLRRuntimeHeader = 14, + Reserved = 15, + } + + private protected override void EmitSymbolTable(IDictionary definedSymbols, SortedSet undefinedSymbols) + { + if (undefinedSymbols.Count > 0) + { + throw new NotSupportedException("PEObjectWriter does not support undefined symbols"); + } + + // Grab the defined symbols to resolve relocs during emit. + _definedSymbols = new Dictionary(definedSymbols); + } + + private protected override void EmitSectionsAndLayout() + { + SectionWriter exportDirectory = GetOrCreateSection(ExportDirectorySection); + + EmitExportDirectory(exportDirectory); + + // Grab section indicies. + _pdataSectionIndex = GetOrCreateSection(ObjectNodeSection.PDataSection).SectionIndex; + _debugSectionIndex = GetOrCreateSection(ObjectNodeSection.DebugDirectorySection).SectionIndex; + _corMetaSectionIndex = GetOrCreateSection(ObjectNodeSection.CorMetaSection).SectionIndex; + _rsrcSectionIndex = GetOrCreateSection(ObjectNodeSection.Win32ResourcesSection).SectionIndex; + _exportSectionIndex = exportDirectory.SectionIndex; + + // Create the reloc section last. We write page offsets into it based on the virtual addresses of other sections + // and we write it after the initial layout. Therefore, we need to have it after all other sections that it may reference, + // as we can't move the emitted values later. + _baseRelocSectionIndex = GetOrCreateSection(BaseRelocSection).SectionIndex; + + uint fileAlignment = 0x200; + bool isWindowsOr32bit = _nodeFactory.Target.IsWindows || _nodeFactory.Target.PointerSize != 8; + if (isWindowsOr32bit) + { + // To minimize wasted VA space on 32-bit systems (regardless of OS), + // align file to page boundaries (presumed to be 4K) + // + // On Windows we use 4K file alignment (regardless of ptr size), + // per requirements of memory mapping API (MapViewOfFile3, et al). + // The alternative could be using the same approach as on Unix, but that would result in PEs + // incompatible with OS loader. While that is not a problem on Unix, we do not want that on Windows. + fileAlignment = PEHeaderConstants.SectionAlignment; + } + + uint sectionAlignment = (uint)PEHeaderConstants.SectionAlignment; + if (!isWindowsOr32bit) + { + // On 64bit Linux, we must match the bottom 12 bits of section RVA's to their file offsets. For this reason + // we need the same alignment for both. + // + // In addition to that we specify section RVAs to be at least 64K apart, which is > page on most systems. + // It ensures that the sections will not overlap when mapped from a singlefile bundle, which introduces a sub-page skew. + // + // Such format would not be accepted by OS loader on Windows, but it is not a problem on Unix. + sectionAlignment = fileAlignment; + } + + if (_requestedSectionAlignment != 0) + { + fileAlignment = (uint)_requestedSectionAlignment; + sectionAlignment = (uint)_requestedSectionAlignment; + } + + _peFileAlignment = fileAlignment; + _peSectionAlignment = sectionAlignment; + LayoutSections(recordFinalLayout: false, out _, out _, out _, out _, out _); + } + + private void LayoutSections(bool recordFinalLayout, out ushort numberOfSections, out uint sizeOfHeaders, out uint sizeOfImage, out uint sizeOfInitializedData, out uint sizeOfCode) + { + bool isPE32Plus = _nodeFactory.Target.PointerSize == 8; + ushort sizeOfOptionalHeader = (ushort)(isPE32Plus ? 0xF0 : 0xE0); + + numberOfSections = (ushort)_sections.Count; + + ushort numNonEmptySections = 0; + foreach (SectionDefinition section in _sections) + { + // Only count sections with data or that contain uninitialized data + if (section.Header.SectionCharacteristics.HasFlag(SectionCharacteristics.ContainsUninitializedData)) + { + numNonEmptySections++; + } + else if (section.Stream.Length != 0) + { + numNonEmptySections++; + } + } + + numberOfSections = numNonEmptySections; + + // Compute headers size and align to file alignment + uint sizeOfHeadersUnaligned = (uint)(DosHeaderSize + 4 + 20 + sizeOfOptionalHeader + 40 * numberOfSections); + sizeOfHeaders = (uint)AlignmentHelper.AlignUp((int)sizeOfHeadersUnaligned, (int)_peFileAlignment); + + // Calculate layout for sections: raw file offsets and virtual addresses + uint pointerToRawData = sizeOfHeaders; + uint virtualAddress = (uint)AlignmentHelper.AlignUp((int)sizeOfHeaders, (int)_peSectionAlignment); + + sizeOfCode = 0; + sizeOfInitializedData = 0; + + bool firstSection = true; + foreach (SectionDefinition s in _sections) + { + CoffSectionHeader h = s.Header; + h.SizeOfRawData = (uint)s.Stream.Length; + uint requestedAlignment = GetSectionAlignment(h); + uint rawAligned = h.SectionCharacteristics.HasFlag(SectionCharacteristics.ContainsUninitializedData) + ? 0u + : (uint)AlignmentHelper.AlignUp((int)h.SizeOfRawData, (int)uint.Max(requestedAlignment, _peFileAlignment)); + + if (rawAligned != 0) + { + h.PointerToRawData = pointerToRawData; + pointerToRawData += rawAligned; + h.SizeOfRawData = rawAligned; + } + else + { + h.PointerToRawData = 0; + } + + uint sectionAlignment = uint.Max(requestedAlignment, _peSectionAlignment); + if (!_nodeFactory.Target.IsWindows) + { + const int RVAAlign = 1 << RVABitsToMatchFilePos; + if (!firstSection) + { + // when assembly is stored in a singlefile bundle, an additional skew is introduced + // as the streams inside the bundle are not necessarily page aligned as we do not + // know the actual page size on the target system. + // We may need one page gap of unused VA space before the next section starts. + // We will assume the page size is <= RVAAlign + virtualAddress += RVAAlign; + } + + virtualAddress = (uint)AlignmentHelper.AlignUp((int)virtualAddress, RVAAlign); + + uint rvaAdjust = (h.PointerToRawData - virtualAddress) & (RVAAlign - 1); + virtualAddress += rvaAdjust; + } + else + { + virtualAddress = (uint)AlignmentHelper.AlignUp((int)virtualAddress, (int)sectionAlignment); + } + + uint virtualSize = (uint)AlignmentHelper.AlignUp((int)h.SizeOfRawData, (int)_peSectionAlignment); + + h.VirtualAddress = virtualAddress; + h.VirtualSize = virtualSize; + + virtualAddress += virtualSize; + + if (h.SectionCharacteristics.HasFlag(SectionCharacteristics.ContainsCode)) + sizeOfCode += h.SizeOfRawData; + else if (!h.SectionCharacteristics.HasFlag(SectionCharacteristics.ContainsUninitializedData)) + sizeOfInitializedData += h.SizeOfRawData; + + if (recordFinalLayout) + { + // Use the stream length so we don't include any space that's appended just for alignment purposes. + // To ensure that we match the section indexes in _sections, we don't skip empty sections here + // even though we omit them in EmitObjectFile. + _outputSectionLayout.Add(new OutputSection(h.Name, h.VirtualAddress, h.PointerToRawData, (uint)s.Stream.Length)); + } + firstSection = false; + } + + sizeOfImage = (uint)AlignmentHelper.AlignUp((int)virtualAddress, (int)_peSectionAlignment); + } + + private protected override unsafe void EmitRelocations(int sectionIndex, List relocationList) + { + foreach (var reloc in relocationList) + { + if (!_resolvableRelocations.TryGetValue(sectionIndex, out List resolvable)) + { + _resolvableRelocations[sectionIndex] = resolvable = []; + } + resolvable.Add(reloc); + if (Relocation.GetFileRelocationType(reloc.Type) == reloc.Type) + { + // Gather file-level relocations that need to go into the .reloc + // section. We collect entries grouped by 4KB page (page RVA -> + // list of (type<<12 | offsetInPage) WORD entries). + uint targetRva = _sections[sectionIndex].Header.VirtualAddress + (uint)reloc.Offset; + uint pageRva = targetRva & ~(PEHeaderConstants.SectionAlignment - 1); + ushort offsetInPage = (ushort)(targetRva & (PEHeaderConstants.SectionAlignment - 1)); + ushort entry = (ushort)(((ushort)reloc.Type << 12) | offsetInPage); + + if (!_baseRelocMap.TryGetValue(pageRva, out var list)) + { + list = new List(); + _baseRelocMap.Add(pageRva, list); + } + list.Add(entry); + } + } + } + + private void EmitExportDirectory(SectionWriter sectionWriter) + { + if (_exportedSymbolNames.Count == 0) + { + // No exports to emit. + return; + } + + List exports = [.._exportedSymbolNames]; + + exports.Sort(StringComparer.Ordinal); + string moduleName = Path.GetFileName(_outputPath); + const int minOrdinal = 1; + + StringTableBuilder exportsStringTable = new(); + + exportsStringTable.ReserveString(moduleName); + foreach (var exportName in exports) + { + exportsStringTable.ReserveString(exportName); + } + + string exportsStringTableSymbol = GenerateSymbolNameForReloc("exportsStringTable"); + string addressTableSymbol = GenerateSymbolNameForReloc("addressTable"); + string namePointerTableSymbol = GenerateSymbolNameForReloc("namePointerTable"); + string ordinalPointerTableSymbol = GenerateSymbolNameForReloc("ordinalPointerTable"); + + Debug.Assert(sectionWriter.Position == 0); + + // +0x00: reserved + sectionWriter.WriteLittleEndian(0); + // +0x04: time/date stamp + sectionWriter.WriteLittleEndian(0); + // +0x08: major version + sectionWriter.WriteLittleEndian(0); + // +0x0A: minor version + sectionWriter.WriteLittleEndian(0); + // +0x0C: DLL name RVA + sectionWriter.EmitSymbolReference(RelocType.IMAGE_REL_BASED_ADDR32NB, exportsStringTableSymbol, exportsStringTable.GetStringOffset(moduleName)); + // +0x10: ordinal base + sectionWriter.WriteLittleEndian(minOrdinal); + // +0x14: number of entries in the address table + sectionWriter.WriteLittleEndian(exports.Count); + // +0x18: number of name pointers + sectionWriter.WriteLittleEndian(exports.Count); + // +0x1C: export address table RVA + sectionWriter.EmitSymbolReference(RelocType.IMAGE_REL_BASED_ADDR32NB, addressTableSymbol); + // +0x20: name pointer RVA + sectionWriter.EmitSymbolReference(RelocType.IMAGE_REL_BASED_ADDR32NB, namePointerTableSymbol); + // +0x24: ordinal table RVA + sectionWriter.EmitSymbolReference(RelocType.IMAGE_REL_BASED_ADDR32NB, ordinalPointerTableSymbol); + + + sectionWriter.EmitAlignment(4); + sectionWriter.EmitSymbolDefinition(addressTableSymbol); + foreach (var exportName in exports) + { + sectionWriter.EmitSymbolReference(RelocType.IMAGE_REL_BASED_ADDR32NB, exportName); + } + + sectionWriter.EmitAlignment(4); + sectionWriter.EmitSymbolDefinition(namePointerTableSymbol); + + foreach (var exportName in exports) + { + sectionWriter.EmitSymbolReference(RelocType.IMAGE_REL_BASED_ADDR32NB, exportsStringTableSymbol, exportsStringTable.GetStringOffset(exportName)); + } + + sectionWriter.EmitAlignment(4); + sectionWriter.EmitSymbolDefinition(ordinalPointerTableSymbol); + for (int i = 0; i < exports.Count; i++) + { + sectionWriter.WriteLittleEndian(checked((ushort)i)); + } + + sectionWriter.EmitSymbolDefinition(exportsStringTableSymbol); + MemoryStream ms = new(); + exportsStringTable.Write(ms); + sectionWriter.Write(ms.ToArray()); + + string GenerateSymbolNameForReloc(string name) + { + return $"{_nodeFactory.NameMangler.CompilationUnitPrefix}__ExportDirectory__{name}"; + } + } + + private void EmitRelocSectionData() + { + var writer = GetOrCreateSection(BaseRelocSection); + Debug.Assert(writer.SectionIndex == _sections.Count - 1, "The .reloc section must be the last section we emit."); + + foreach (var kv in _baseRelocMap) + { + uint pageRva = kv.Key; + List entries = kv.Value; + entries.Sort(); + + int entriesSize = entries.Count * 2; + int sizeOfBlock = 8 + entriesSize; + sizeOfBlock = AlignmentHelper.AlignUp(sizeOfBlock, 4); + + writer.WriteLittleEndian(pageRva); + writer.WriteLittleEndian((uint)sizeOfBlock); + + // Emit entries + foreach (ushort e in entries) + { + writer.WriteLittleEndian(e); + } + + // Ensure block is 4-byte aligned + writer.EmitAlignment(4); + } + + CoffSectionHeader relocHeader = _sections[_baseRelocSectionIndex].Header; + + relocHeader.SectionCharacteristics |= SectionCharacteristics.MemDiscardable; + } + + private protected override void EmitObjectFile(Stream outputFileStream) + { + if (_baseRelocMap.Count > 0) + { + EmitRelocSectionData(); + } + + // redo layout in case we made any additions during inital layout. + LayoutSections(recordFinalLayout: true, out ushort numberOfSections, out uint sizeOfHeaders, out uint sizeOfImage, out uint sizeOfInitializedData, out uint sizeOfCode); + + outputFileStream.Write(DosHeader); + Debug.Assert(DosHeader.Length == DosHeaderSize); + outputFileStream.Write("PE\0\0"u8); + + bool isPE32Plus = _nodeFactory.Target.PointerSize == 8; + ushort sizeOfOptionalHeader = (ushort)(isPE32Plus ? 0xF0 : 0xE0); + + Characteristics characteristics = Characteristics.ExecutableImage | Characteristics.Dll; + characteristics |= isPE32Plus ? Characteristics.LargeAddressAware : Characteristics.Bit32Machine; + + Machine machine = _machine; +#if READYTORUN + // On R2R, we encode the target OS into the machine bits to ensure we don't try running + // linux or mac R2R code on Windows, or vice versa. + machine = (Machine) ((ushort)machine ^ (ushort)_nodeFactory.Target.MachineOSOverrideFromTarget()); +#endif + + // COFF File Header + var coffHeader = new CoffHeader + { + Machine = machine, + NumberOfSections = (uint)numberOfSections, + TimeDateStamp = (uint)(_coffTimestamp ?? 0), + PointerToSymbolTable = 0, + NumberOfSymbols = 0, + SizeOfOptionalHeader = sizeOfOptionalHeader, + Characteristics = characteristics, + }; + + _coffHeaderOffset = outputFileStream.Position; + + coffHeader.Write(outputFileStream); + + var peOptional = new PEOptionalHeader(isPE32Plus) + { + SizeOfCode = sizeOfCode, + SizeOfInitializedData = sizeOfInitializedData, + AddressOfEntryPoint = 0u, + BaseOfCode = 0u, + BaseOfData = 0u, + SizeOfImage = sizeOfImage, + SizeOfHeaders = sizeOfHeaders, + NumberOfRvaAndSizes = 16u, + }; + + // Set DLL characteristics similar to PEHeaderProvider.Create + DllCharacteristics dllCharacteristics = + DllCharacteristics.DynamicBase | + DllCharacteristics.NxCompatible | + DllCharacteristics.TerminalServerAware; + if (isPE32Plus) + { + dllCharacteristics |= DllCharacteristics.HighEntropyVirtualAddressSpace; + } + else + { + dllCharacteristics |= DllCharacteristics.NoSeh; + } + + peOptional.DllCharacteristics = dllCharacteristics; + peOptional.SectionAlignment = _peSectionAlignment; + peOptional.FileAlignment = _peFileAlignment; + + // Create data directories object and pass it to the optional header writer. + // Entries are zeroed by default; callers may populate particular directories + // before writing if needed. + var dataDirs = new OptionalHeaderDataDirectories(); + // Populate data directories if present. + if (_rsrcSectionIndex != NoSectionIndex) + { + dataDirs.SetIfNonEmpty((int)ImageDirectoryEntry.Resource, (uint)_outputSectionLayout[_rsrcSectionIndex].VirtualAddress, (uint)_outputSectionLayout[_rsrcSectionIndex].Length); + } + if (_pdataSectionIndex != NoSectionIndex) + { + dataDirs.SetIfNonEmpty((int)ImageDirectoryEntry.Exception, (uint)_outputSectionLayout[_pdataSectionIndex].VirtualAddress, (uint)_outputSectionLayout[_pdataSectionIndex].Length); + } + if (_exportSectionIndex != NoSectionIndex) + { + dataDirs.SetIfNonEmpty((int)ImageDirectoryEntry.Export, (uint)_outputSectionLayout[_exportSectionIndex].VirtualAddress, (uint)_outputSectionLayout[_exportSectionIndex].Length); + } + if (_baseRelocSectionIndex != NoSectionIndex) + { + dataDirs.SetIfNonEmpty((int)ImageDirectoryEntry.BaseRelocation, (uint)_outputSectionLayout[_baseRelocSectionIndex].VirtualAddress, (uint)_outputSectionLayout[_baseRelocSectionIndex].Length); + } + if (_debugSectionIndex != NoSectionIndex) + { + dataDirs.SetIfNonEmpty((int)ImageDirectoryEntry.Debug, (uint)_outputSectionLayout[_debugSectionIndex].VirtualAddress, (uint)_outputSectionLayout[_debugSectionIndex].Length); + } + if (_corMetaSectionIndex != NoSectionIndex) + { + dataDirs.SetIfNonEmpty((int)ImageDirectoryEntry.CLRRuntimeHeader, (uint)_outputSectionLayout[_corMetaSectionIndex].VirtualAddress, (uint)_outputSectionLayout[_corMetaSectionIndex].Length); + } + peOptional.Write(outputFileStream, dataDirs); + + CoffStringTable stringTable = new(); + + // Emit headers for each section in the order they appear in _sections + for (int i = 0; i < _sections.Count; i++) + { + var hdr = _sections[i].Header; + if (hdr.VirtualSize == 0 && !hdr.SectionCharacteristics.HasFlag(SectionCharacteristics.ContainsUninitializedData)) + { + // Don't emit empty sections into the binary + continue; + } + + // The alignment flags aren't valid in PE image files, only in COFF object files. + hdr.SectionCharacteristics &= ~SectionCharacteristics.AlignMask; + hdr.Write(outputFileStream, stringTable); + } + + // Write section content + for (int i = 0; i < _sections.Count; i++) + { + SectionDefinition section = _sections[i]; + if (section.Header.VirtualSize != 0 && !section.Header.SectionCharacteristics.HasFlag(SectionCharacteristics.ContainsUninitializedData)) + { + Debug.Assert(outputFileStream.Position <= section.Header.PointerToRawData); + outputFileStream.Position = section.Header.PointerToRawData; // Pad to alignment + if (_resolvableRelocations.TryGetValue(i, out List relocationsToResolve)) + { + // Resolve all relocations we can't represent in a PE as we write out the data. + MemoryStream stream = new((int)section.Stream.Length); + section.Stream.Position = 0; + section.Stream.CopyTo(stream); + ResolveRelocations(i, relocationsToResolve, (long)peOptional.ImageBase, stream); + stream.Position = 0; + stream.CopyTo(outputFileStream); + } + else + { + section.Stream.Position = 0; + section.Stream.CopyTo(outputFileStream); + } + } + } + + // Align the output file size with the image (including trailing padding for section and file alignment). + Debug.Assert(outputFileStream.Position <= sizeOfImage); + outputFileStream.SetLength(sizeOfImage); + } + + private protected override void EmitChecksumsForObject(Stream outputFileStream, List checksumRelocations, ReadOnlySpan originalOutput) + { + base.EmitChecksumsForObject(outputFileStream, checksumRelocations, originalOutput); + + if (_coffTimestamp is null) + { + // If we were not provided a deterministic timestamp, generate one from a hash of the content. + outputFileStream.Seek(_coffHeaderOffset + CoffHeader.TimeDateStampOffset(bigObj: false), SeekOrigin.Begin); + using BinaryWriter writer = new(outputFileStream, Encoding.UTF8, leaveOpen: true); + writer.Write(BlobContentId.FromHash(SHA256.HashData(originalOutput)).Stamp); + } + } + + private unsafe void ResolveRelocations(int sectionIndex, List symbolicRelocations, long imageBase, MemoryStream stream) + { + foreach (SymbolicRelocation reloc in symbolicRelocations) + { + SymbolDefinition definedSymbol = _definedSymbols[reloc.SymbolName]; + uint relocOffset = checked((uint)(_sections[sectionIndex].Header.VirtualAddress + reloc.Offset)); + uint relocLength = (uint)Relocation.GetSize(reloc.Type); + uint symbolImageOffset = checked((uint)(_sections[definedSymbol.SectionIndex].Header.VirtualAddress + definedSymbol.Value)); + + fixed (byte* pData = GetRelocDataSpan(reloc)) + { + long addend = Relocation.ReadValue(reloc.Type, pData); + switch (reloc.Type) + { + case RelocType.IMAGE_REL_BASED_ABSOLUTE: + // No action required + break; + + case RelocType.IMAGE_REL_BASED_THUMB_MOV32: + case RelocType.IMAGE_REL_BASED_DIR64: + case RelocType.IMAGE_REL_BASED_HIGHLOW: + // Write the ImageBase-relative value to be relocated at load time. + Relocation.WriteValue(reloc.Type, pData, symbolImageOffset + imageBase + addend); + break; + case RelocType.IMAGE_REL_BASED_ADDR32NB: + Relocation.WriteValue(reloc.Type, pData, symbolImageOffset + addend); + break; + case RelocType.IMAGE_REL_BASED_REL32: + case RelocType.IMAGE_REL_BASED_RELPTR32: + Relocation.WriteValue(reloc.Type, pData, symbolImageOffset - (relocOffset + relocLength) + addend); + break; + case RelocType.IMAGE_REL_FILE_ABSOLUTE: + long fileOffset = _sections[definedSymbol.SectionIndex].Header.PointerToRawData + definedSymbol.Value; + Relocation.WriteValue(reloc.Type, pData, fileOffset + addend); + break; + case RelocType.IMAGE_REL_BASED_THUMB_MOV32_PCREL: + const uint offsetCorrection = 12; + Relocation.WriteValue(reloc.Type, pData, symbolImageOffset - (relocOffset + offsetCorrection) + addend); + break; + case RelocType.IMAGE_REL_BASED_ARM64_PAGEBASE_REL21: + { + if (addend != 0) + { + throw new NotSupportedException(); + } + int sourcePageRVA = (int)(relocOffset & ~0xFFF); + long delta = (symbolImageOffset - sourcePageRVA >> 12) & 0x1f_ffff; + Relocation.WriteValue(reloc.Type, pData, delta); + break; + } + case RelocType.IMAGE_REL_BASED_ARM64_PAGEOFFSET_12A: + if (addend != 0) + { + throw new NotSupportedException(); + } + Relocation.WriteValue(reloc.Type, pData, symbolImageOffset & 0xfff); + break; + case RelocType.IMAGE_REL_BASED_LOONGARCH64_PC: + { + if (addend != 0) + { + throw new NotSupportedException(); + } + long delta = (symbolImageOffset - (relocOffset & ~0xfff) + ((symbolImageOffset & 0x800) << 1)); + Relocation.WriteValue(reloc.Type, pData, delta); + break; + } + case RelocType.IMAGE_REL_BASED_LOONGARCH64_JIR: + case RelocType.IMAGE_REL_BASED_RISCV64_PC: + { + if (addend != 0) + { + throw new NotSupportedException(); + } + long delta = symbolImageOffset - relocOffset; + Relocation.WriteValue(reloc.Type, pData, delta); + break; + } + default: + throw new NotSupportedException($"Unsupported relocation: {reloc.Type}"); + } + WriteRelocDataSpan(reloc, pData); + } + + Span GetRelocDataSpan(SymbolicRelocation reloc) + { + stream.Position = reloc.Offset; + Span data = new byte[Relocation.GetSize(reloc.Type)]; + stream.ReadExactly(data); + return data; + } + + void WriteRelocDataSpan(SymbolicRelocation reloc, byte* data) + { + stream.Position = reloc.Offset; + stream.Write(new Span(data, Relocation.GetSize(reloc.Type))); + } + } + } + + private static unsafe void WriteLittleEndian(Stream stream, T value) + where T : IBinaryInteger + { + Span buffer = stackalloc byte[sizeof(T)]; + value.WriteLittleEndian(buffer); + stream.Write(buffer); + } + } +} diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/PETargetExtensions.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/PETargetExtensions.cs new file mode 100644 index 00000000000000..12f992c29677b8 --- /dev/null +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/PETargetExtensions.cs @@ -0,0 +1,133 @@ +// 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.Reflection.PortableExecutable; + +using Internal.TypeSystem; + +namespace ILCompiler.ObjectWriter +{ + /// + /// Per-OS machine overrides. Corresponds to CoreCLR constants + /// IMAGE_FILE_MACHINE_NATIVE_OS_OVERRIDE. + /// + internal enum MachineOSOverride : ushort + { + Windows = 0, + Linux = 0x7B79, + Apple = 0x4644, + FreeBSD = 0xADC4, + NetBSD = 0x1993, + SunOS = 0x1992, + } + + /// + /// Constants for emission of Windows PE file mostly copied from CoreCLR pewriter.cpp. + /// + internal static class PEHeaderConstants + { + public const uint SectionAlignment = 0x1000; + + public const byte MajorLinkerVersion = 11; + public const byte MinorLinkerVersion = 0; + + public const byte MajorOperatingSystemVersion = 4; + public const byte MinorOperatingSystemVersion = 0; + + public const ushort MajorImageVersion = 0; + public const ushort MinorImageVersion = 0; + + public const ushort MajorSubsystemVersion = 4; + public const ushort MinorSubsystemVersion = 0; + } + + internal static class PE32HeaderConstants + { + public const uint ImageBase = 0x0040_0000; + + public const uint SizeOfStackReserve = 0x100000; + public const uint SizeOfStackCommit = 0x1000; + public const uint SizeOfHeapReserve = 0x100000; + public const uint SizeOfHeapCommit = 0x1000; + } + + internal static class PE64HeaderConstants + { + // Default base addresses used by Roslyn + public const ulong ExeImageBase = 0x1_4000_0000; + public const ulong DllImageBase = 0x1_8000_0000; + + public const ulong SizeOfStackReserve = 0x400000; + public const ulong SizeOfStackCommit = 0x4000; + public const ulong SizeOfHeapReserve = 0x100000; + public const ulong SizeOfHeapCommit = 0x2000; + } + + internal static class TargetExtensions + { + /// + /// Calculate machine ID based on compilation target architecture. + /// + /// Compilation target environment specification + /// + public static Machine MachineFromTarget(this TargetDetails target) + { + switch (target.Architecture) + { + case Internal.TypeSystem.TargetArchitecture.X64: + return Machine.Amd64; + + case Internal.TypeSystem.TargetArchitecture.X86: + return Machine.I386; + + case Internal.TypeSystem.TargetArchitecture.ARM64: + return Machine.Arm64; + + case Internal.TypeSystem.TargetArchitecture.ARM: + return Machine.ArmThumb2; + + case Internal.TypeSystem.TargetArchitecture.LoongArch64: + return Machine.LoongArch64; + + case Internal.TypeSystem.TargetArchitecture.RiscV64: + return Machine.RiscV64; + + default: + throw new NotImplementedException(target.Architecture.ToString()); + } + } + + /// + /// Determine OS machine override for the target operating system. + /// + public static MachineOSOverride MachineOSOverrideFromTarget(this TargetDetails target) + { + switch (target.OperatingSystem) + { + case TargetOS.Windows: + return MachineOSOverride.Windows; + + case TargetOS.Linux: + return MachineOSOverride.Linux; + + case TargetOS.OSX: + case TargetOS.MacCatalyst: + case TargetOS.iOS: + case TargetOS.iOSSimulator: + case TargetOS.tvOS: + case TargetOS.tvOSSimulator: + return MachineOSOverride.Apple; + + case TargetOS.FreeBSD: + return MachineOSOverride.FreeBSD; + + case TargetOS.NetBSD: + return MachineOSOverride.NetBSD; + + default: + throw new NotImplementedException(target.OperatingSystem.ToString()); + } + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/SectionData.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/SectionData.cs similarity index 100% rename from src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/SectionData.cs rename to src/coreclr/tools/Common/Compiler/ObjectWriter/SectionData.cs diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/SectionWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/SectionWriter.cs similarity index 100% rename from src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/SectionWriter.cs rename to src/coreclr/tools/Common/Compiler/ObjectWriter/SectionWriter.cs diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/StringTableBuilder.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/StringTableBuilder.cs similarity index 99% rename from src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/StringTableBuilder.cs rename to src/coreclr/tools/Common/Compiler/ObjectWriter/StringTableBuilder.cs index 81f7315d225e4a..0c6d1831dd4eb8 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/StringTableBuilder.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/StringTableBuilder.cs @@ -17,7 +17,7 @@ internal class StringTableBuilder private readonly SortedSet _reservedStrings = new(StringComparer.Ordinal); private Dictionary _stringToOffset = new(StringComparer.Ordinal); - public void Write(FileStream stream) + public void Write(Stream stream) { _stream.Position = 0; _stream.CopyTo(stream); diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/TypeString.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/TypeString.cs similarity index 98% rename from src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/TypeString.cs rename to src/coreclr/tools/Common/Compiler/ObjectWriter/TypeString.cs index 6d40c859fc0788..ca2c1a43fedf4d 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/TypeString.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/TypeString.cs @@ -5,7 +5,7 @@ using Internal.TypeSystem; -namespace ILCompiler.PEWriter +namespace ILCompiler.ObjectWriter { // This is a very rough equivalent of typestring.cpp in the CLR with only // basic formatting options. Only used to format type names for map diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/UnixObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/UnixObjectWriter.cs new file mode 100644 index 00000000000000..024ea5c53b34c3 --- /dev/null +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/UnixObjectWriter.cs @@ -0,0 +1,49 @@ +// 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.Collections.Generic; +using System.IO; +using System.Linq; +using System.Buffers.Binary; +using ILCompiler.DependencyAnalysis; +using Internal.TypeSystem; + +using Debug = System.Diagnostics.Debug; + +namespace ILCompiler.ObjectWriter +{ + /// + /// Base implementation for ELF and Mach-O object file format writers. Implements + /// the common code for DWARF debugging and exception handling information. + /// + internal abstract partial class UnixObjectWriter : ObjectWriter + { + private sealed record UnixSectionDefinition(string SymbolName, Stream SectionStream); + private readonly List _sections = new(); + + private static readonly ObjectNodeSection LsdaSection = new ObjectNodeSection(".dotnet_eh_table", SectionType.ReadOnly); + private static readonly ObjectNodeSection EhFrameSection = new ObjectNodeSection(".eh_frame", SectionType.UnwindData); + + protected UnixObjectWriter(NodeFactory factory, ObjectWritingOptions options) + : base(factory, options) + { + } + + private protected override void CreateSection(ObjectNodeSection section, string comdatName, string symbolName, int sectionIndex, Stream sectionStream) + { + if (section.Type != SectionType.Debug && + section != LsdaSection && + section != EhFrameSection && + (comdatName is null || Equals(comdatName, symbolName))) + { + // Record code and data sections that can be referenced from debugging information + _sections.Add(new UnixSectionDefinition(symbolName, sectionStream)); + } + else + { + _sections.Add(null); + } + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ModulesSectionNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ModulesSectionNode.cs index 3bc379eea93a22..881b21ea1ff0be 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ModulesSectionNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ModulesSectionNode.cs @@ -5,12 +5,14 @@ namespace ILCompiler.DependencyAnalysis { - public class ModulesSectionNode : ObjectNode, ISymbolDefinitionNode + public class ModulesSectionNode : ObjectNode, ISymbolDefinitionNode, IUniqueSymbolNode { // Each compilation unit produces one module. When all compilation units are linked // together in multifile mode, the runtime needs to get list of modules present // in the final binary. This list is created via a special .modules section that // contains list of pointers to all module headers. + // + // These intentionally clash with one another, but are merged with linker directives so should not be COMDAT folded public override ObjectNodeSection GetSection(NodeFactory factory) { diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CoffObjectWriter.Aot.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CoffObjectWriter.Aot.cs new file mode 100644 index 00000000000000..78f0dad8ed5284 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CoffObjectWriter.Aot.cs @@ -0,0 +1,259 @@ +// 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.TypeSystem; +using Internal.TypeSystem.TypesDebugInfo; +using static ILCompiler.DependencyAnalysis.RelocType; +using static ILCompiler.ObjectWriter.CoffObjectWriter.CoffRelocationType; + +namespace ILCompiler.ObjectWriter +{ + /// + /// COFF object file format writer for Windows targets. + /// + /// + /// 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. + /// + internal partial class CoffObjectWriter : ObjectWriter + { + // Debugging + private SectionWriter _debugTypesSectionWriter; + private SectionWriter _debugSymbolSectionWriter; + private CodeViewFileTableBuilder _debugFileTableBuilder; + private CodeViewSymbolsBuilder _debugSymbolsBuilder; + private CodeViewTypesBuilder _debugTypesBuilder; + + // Exception handling + private SectionWriter _pdataSectionWriter; + + private protected override void CreateEhSections() + { + // Create .pdata + _pdataSectionWriter = GetOrCreateSection(ObjectNodeSection.PDataSection); + } + + private protected override void EmitUnwindInfo( + SectionWriter sectionWriter, + INodeWithCodeInfo nodeWithCodeInfo, + string currentSymbolName) + { + if (nodeWithCodeInfo.FrameInfos is FrameInfo[] frameInfos && + nodeWithCodeInfo is ISymbolDefinitionNode) + { + SectionWriter xdataSectionWriter; + SectionWriter pdataSectionWriter; + bool shareSymbol = ShouldShareSymbol((ObjectNode)nodeWithCodeInfo); + + for (int i = 0; i < frameInfos.Length; i++) + { + FrameInfo frameInfo = frameInfos[i]; + + int start = frameInfo.StartOffset; + int end = frameInfo.EndOffset; + byte[] blob = frameInfo.BlobData; + + string unwindSymbolName = $"_unwind{i}{currentSymbolName}"; + + if (shareSymbol) + { + // Produce an associative COMDAT symbol. + xdataSectionWriter = GetOrCreateSection(ObjectNodeSection.XDataSection, currentSymbolName, unwindSymbolName); + pdataSectionWriter = GetOrCreateSection(ObjectNodeSection.PDataSection, currentSymbolName, null); + } + else + { + // Produce a COMDAT section for each unwind symbol and let linker + // do the deduplication across the ones with identical content. + xdataSectionWriter = GetOrCreateSection(ObjectNodeSection.XDataSection, unwindSymbolName, unwindSymbolName); + pdataSectionWriter = _pdataSectionWriter; + } + + // Need to emit the UNWIND_INFO at 4-byte alignment to ensure that the + // pointer has the lower two bits in .pdata section set to zero. On ARM64 + // non-zero bits would mean a compact encoding. + xdataSectionWriter.EmitAlignment(4); + + xdataSectionWriter.EmitSymbolDefinition(unwindSymbolName); + + // Emit UNWIND_INFO + xdataSectionWriter.Write(blob); + + FrameInfoFlags flags = frameInfo.Flags; + + if (i != 0) + { + xdataSectionWriter.WriteByte((byte)flags); + } + else + { + MethodExceptionHandlingInfoNode ehInfo = nodeWithCodeInfo.EHInfo; + ISymbolNode associatedDataNode = nodeWithCodeInfo.GetAssociatedDataNode(_nodeFactory) as ISymbolNode; + + flags |= ehInfo is not null ? FrameInfoFlags.HasEHInfo : 0; + flags |= associatedDataNode is not null ? FrameInfoFlags.HasAssociatedData : 0; + + xdataSectionWriter.WriteByte((byte)flags); + + if (associatedDataNode is not null) + { + xdataSectionWriter.EmitSymbolReference( + IMAGE_REL_BASED_ADDR32NB, + GetMangledName(associatedDataNode)); + } + + if (ehInfo is not null) + { + xdataSectionWriter.EmitSymbolReference( + IMAGE_REL_BASED_ADDR32NB, + GetMangledName(ehInfo)); + } + + if (nodeWithCodeInfo.GCInfo is not null) + { + xdataSectionWriter.Write(nodeWithCodeInfo.GCInfo); + } + } + + // Emit RUNTIME_FUNCTION + pdataSectionWriter.EmitAlignment(4); + pdataSectionWriter.EmitSymbolReference(IMAGE_REL_BASED_ADDR32NB, currentSymbolName, start); + // Only x86/x64 has the End symbol + if (_machine is Machine.I386 or Machine.Amd64) + { + pdataSectionWriter.EmitSymbolReference(IMAGE_REL_BASED_ADDR32NB, currentSymbolName, end); + } + // Unwind info pointer + pdataSectionWriter.EmitSymbolReference(IMAGE_REL_BASED_ADDR32NB, unwindSymbolName, 0); + } + } + } + + private protected override ITypesDebugInfoWriter CreateDebugInfoBuilder() + { + _debugFileTableBuilder = new CodeViewFileTableBuilder(); + + _debugSymbolSectionWriter = GetOrCreateSection(DebugSymbolSection); + _debugSymbolSectionWriter.EmitAlignment(4); + _debugSymbolsBuilder = new CodeViewSymbolsBuilder( + _nodeFactory.Target.Architecture, + _debugSymbolSectionWriter); + + _debugTypesSectionWriter = GetOrCreateSection(DebugTypesSection); + _debugTypesSectionWriter.EmitAlignment(4); + _debugTypesBuilder = new CodeViewTypesBuilder( + _nodeFactory.NameMangler, _nodeFactory.Target.PointerSize, + _debugTypesSectionWriter); + return _debugTypesBuilder; + } + + private protected override void EmitDebugFunctionInfo( + uint methodTypeIndex, + string methodName, + SymbolDefinition methodSymbol, + INodeWithDebugInfo debugNode, + bool hasSequencePoints) + { + DebugEHClauseInfo[] clauses = null; + CodeViewSymbolsBuilder debugSymbolsBuilder; + + if (debugNode is INodeWithCodeInfo nodeWithCodeInfo) + { + clauses = nodeWithCodeInfo.DebugEHClauseInfos; + } + + if (ShouldShareSymbol((ObjectNode)debugNode)) + { + // If the method is emitted in COMDAT section then we need to create an + // associated COMDAT section for the debugging symbols. + var sectionWriter = GetOrCreateSection(DebugSymbolSection, methodName, null); + debugSymbolsBuilder = new CodeViewSymbolsBuilder(_nodeFactory.Target.Architecture, sectionWriter); + } + else + { + debugSymbolsBuilder = _debugSymbolsBuilder; + } + + debugSymbolsBuilder.EmitSubprogramInfo( + methodName, + methodSymbol.Size, + methodTypeIndex, + debugNode.GetDebugVars().Select(debugVar => (debugVar, GetVarTypeIndex(debugNode.IsStateMachineMoveNextMethod, debugVar))), + clauses ?? Array.Empty()); + + if (hasSequencePoints) + { + debugSymbolsBuilder.EmitLineInfo( + _debugFileTableBuilder, + methodName, + methodSymbol.Size, + debugNode.GetNativeSequencePoints()); + } + } + + private protected override void EmitDebugThunkInfo( + string methodName, + SymbolDefinition methodSymbol, + INodeWithDebugInfo debugNode) + { + if (!debugNode.GetNativeSequencePoints().Any()) + return; + + CodeViewSymbolsBuilder debugSymbolsBuilder; + + if (ShouldShareSymbol((ObjectNode)debugNode)) + { + // If the method is emitted in COMDAT section then we need to create an + // associated COMDAT section for the debugging symbols. + var sectionWriter = GetOrCreateSection(DebugSymbolSection, methodName, null); + debugSymbolsBuilder = new CodeViewSymbolsBuilder(_nodeFactory.Target.Architecture, sectionWriter); + } + else + { + debugSymbolsBuilder = _debugSymbolsBuilder; + } + + debugSymbolsBuilder.EmitLineInfo( + _debugFileTableBuilder, + methodName, + methodSymbol.Size, + debugNode.GetNativeSequencePoints()); + } + + private protected override void EmitDebugSections(IDictionary definedSymbols) + { + _debugSymbolsBuilder.WriteUserDefinedTypes(_debugTypesBuilder.UserDefinedTypes); + _debugFileTableBuilder.Write(_debugSymbolSectionWriter); + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/ElfObjectWriter.Aot.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/ElfObjectWriter.Aot.cs new file mode 100644 index 00000000000000..9652211fa0ddc2 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/ElfObjectWriter.Aot.cs @@ -0,0 +1,160 @@ +// 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.Collections.Generic; +using System.IO; +using System.Diagnostics; +using System.Buffers.Binary; +using System.Numerics; +using System.Reflection; +using ILCompiler.DependencyAnalysis; +using ILCompiler.DependencyAnalysisFramework; +using Internal.TypeSystem; +using static ILCompiler.DependencyAnalysis.RelocType; +using static ILCompiler.ObjectWriter.EabiNative; +using static ILCompiler.ObjectWriter.ElfNative; + +namespace ILCompiler.ObjectWriter +{ + /// + /// ELF object file format writer for Linux/Unix targets. + /// + /// + /// ELF object format is described by the official specification hosted + /// at https://refspecs.linuxfoundation.org/elf/elf.pdf. Different + /// architectures specify the details in the ABI specification. + /// + /// Like COFF there are several quirks related to large number of sections + /// (> 65279). Some of the fields in the ELF file header are moved to the + /// first (NULL) section header. The symbol table that is normally a single + /// section in the file is extended with a second .symtab_shndx section + /// to accomodate the section indexes that don't fit within the regular + /// section number field. + /// + internal sealed partial class ElfObjectWriter : UnixObjectWriter + { + private Dictionary _armUnwindSections; + private static readonly ObjectNodeSection ArmUnwindIndexSection = new ObjectNodeSection(".ARM.exidx", SectionType.UnwindData); + private static readonly ObjectNodeSection ArmUnwindTableSection = new ObjectNodeSection(".ARM.extab", SectionType.ReadOnly); + + private protected override void CreateEhSections() + { + // ARM creates the EHABI sections lazily in EmitUnwindInfo + if (_machine is not EM_ARM) + { + base.CreateEhSections(); + } + } + + private protected override void EmitUnwindInfo( + SectionWriter sectionWriter, + INodeWithCodeInfo nodeWithCodeInfo, + string currentSymbolName) + { + if (_machine is not EM_ARM) + { + base.EmitUnwindInfo(sectionWriter, nodeWithCodeInfo, currentSymbolName); + return; + } + + if (nodeWithCodeInfo.FrameInfos is FrameInfo[] frameInfos && + nodeWithCodeInfo is ISymbolDefinitionNode) + { + SectionWriter exidxSectionWriter; + SectionWriter extabSectionWriter; + + if (ShouldShareSymbol((ObjectNode)nodeWithCodeInfo)) + { + exidxSectionWriter = GetOrCreateSection(ArmUnwindIndexSection, currentSymbolName, $"_unwind0{currentSymbolName}"); + extabSectionWriter = GetOrCreateSection(ArmUnwindTableSection, currentSymbolName, $"_extab0{currentSymbolName}"); + _sections[exidxSectionWriter.SectionIndex].LinkSection = _sections[sectionWriter.SectionIndex]; + } + else + { + _armUnwindSections ??= new(); + if (_armUnwindSections.TryGetValue(sectionWriter.SectionIndex, out var unwindSections)) + { + exidxSectionWriter = unwindSections.ExidxSectionWriter; + extabSectionWriter = unwindSections.ExtabSectionWriter; + } + else + { + string sectionName = _sections[sectionWriter.SectionIndex].Name; + exidxSectionWriter = GetOrCreateSection(new ObjectNodeSection($"{ArmUnwindIndexSection.Name}{sectionName}", ArmUnwindIndexSection.Type)); + extabSectionWriter = GetOrCreateSection(new ObjectNodeSection($"{ArmUnwindTableSection.Name}{sectionName}", ArmUnwindTableSection.Type)); + _sections[exidxSectionWriter.SectionIndex].LinkSection = _sections[sectionWriter.SectionIndex]; + _armUnwindSections.Add(sectionWriter.SectionIndex, (exidxSectionWriter, extabSectionWriter)); + } + } + + long mainLsdaOffset = 0; + Span unwindWord = stackalloc byte[4]; + for (int i = 0; i < frameInfos.Length; i++) + { + FrameInfo frameInfo = frameInfos[i]; + int start = frameInfo.StartOffset; + int end = frameInfo.EndOffset; + byte[] blob = frameInfo.BlobData; + + string framSymbolName = $"_fram{i}{currentSymbolName}"; + string extabSymbolName = $"_extab{i}{currentSymbolName}"; + + sectionWriter.EmitSymbolDefinition(framSymbolName, start); + + // Emit the index info + exidxSectionWriter.EmitSymbolReference(IMAGE_REL_ARM_PREL31, framSymbolName); + exidxSectionWriter.EmitSymbolReference(IMAGE_REL_ARM_PREL31, extabSymbolName); + + Span armUnwindInfo = EabiUnwindConverter.ConvertCFIToEabi(blob); + string personalitySymbolName; + + if (armUnwindInfo.Length <= 3) + { + personalitySymbolName = "__aeabi_unwind_cpp_pr0"; + unwindWord[3] = 0x80; + unwindWord[2] = (byte)(armUnwindInfo.Length > 0 ? armUnwindInfo[0] : 0xB0); + unwindWord[1] = (byte)(armUnwindInfo.Length > 1 ? armUnwindInfo[1] : 0xB0); + unwindWord[0] = (byte)(armUnwindInfo.Length > 2 ? armUnwindInfo[2] : 0xB0); + armUnwindInfo = Span.Empty; + } + else + { + Debug.Assert(armUnwindInfo.Length <= 1024); + personalitySymbolName = "__aeabi_unwind_cpp_pr1"; + unwindWord[3] = 0x81; + unwindWord[2] = (byte)(((armUnwindInfo.Length - 2) + 3) / 4); + unwindWord[1] = armUnwindInfo[0]; + unwindWord[0] = armUnwindInfo[1]; + armUnwindInfo = armUnwindInfo.Slice(2); + } + + extabSectionWriter.EmitAlignment(4); + extabSectionWriter.EmitSymbolDefinition(extabSymbolName); + + // ARM EHABI requires emitting a dummy relocation to the personality routine + // to tell the linker to preserve it. + extabSectionWriter.EmitRelocation(0, unwindWord, IMAGE_REL_BASED_ABSOLUTE, personalitySymbolName, 0); + + // Emit the unwinding code. First word specifies the personality routine, + // format and first few bytes of the unwind code. For longer unwind codes + // the other words follow. They are padded with the "finish" instruction + // (0xB0). + extabSectionWriter.Write(unwindWord); + while (armUnwindInfo.Length > 0) + { + unwindWord[3] = (byte)(armUnwindInfo.Length > 0 ? armUnwindInfo[0] : 0xB0); + unwindWord[2] = (byte)(armUnwindInfo.Length > 1 ? armUnwindInfo[1] : 0xB0); + unwindWord[1] = (byte)(armUnwindInfo.Length > 2 ? armUnwindInfo[2] : 0xB0); + unwindWord[0] = (byte)(armUnwindInfo.Length > 3 ? armUnwindInfo[3] : 0xB0); + extabSectionWriter.Write(unwindWord); + armUnwindInfo = armUnwindInfo.Length > 3 ? armUnwindInfo.Slice(4) : Span.Empty; + } + + // Emit our LSDA info directly into the exception table + EmitLsda(nodeWithCodeInfo, frameInfos, i, extabSectionWriter, ref mainLsdaOffset); + } + } + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/MachObjectWriter.Aot.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/MachObjectWriter.Aot.cs new file mode 100644 index 00000000000000..76cdb395b8228f --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/MachObjectWriter.Aot.cs @@ -0,0 +1,297 @@ +// 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.Binary; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Numerics; +using System.Text; +using ILCompiler.DependencyAnalysis; +using ILCompiler.DependencyAnalysisFramework; +using Internal.TypeSystem; +using static ILCompiler.DependencyAnalysis.RelocType; +using static ILCompiler.ObjectWriter.MachNative; + +namespace ILCompiler.ObjectWriter +{ + /// + /// Mach-O object file format writer for Apple macOS and iOS-like targets. + /// + /// + /// Old version of the Mach-O file format specification is mirrored at + /// https://github.com/aidansteele/osx-abi-macho-file-format-reference. + /// + /// There are some notable differences when compared to ELF or COFF: + /// - The maximum number of sections in object file is limited to 255. + /// - Sections are subdivided by their symbols and treated by the + /// linker as subsections (often referred to as atoms by the linker). + /// + /// The consequences of these design decisions is the COMDAT sections are + /// modeled in entirely different way. Dead code elimination works on the + /// atom level, so relative relocations within the same section have to be + /// preserved. + /// + /// Debug information uses the standard DWARF format. It is, however, not + /// linked into the intermediate executable files. Instead the linker creates + /// a map between the final executable and the object files. Debuggers like + /// lldb then use this map to read the debug information from the object + /// file directly. As a consequence the DWARF information is not generated + /// with relocations for the DWARF sections themselves since it's never + /// needed. + /// + /// While Mach-O uses the DWARF exception handling information for unwind + /// tables it also supports a compact representation for common prolog types. + /// Unofficial reference of the format can be found at + /// https://faultlore.com/blah/compact-unwinding/. It's necessary to emit + /// at least the stub entries pointing to the DWARF information but due + /// to limits in the linked file format it's advisable to use the compact + /// encoding whenever possible. + /// + /// The Apple linker is extremely picky in which relocation types are allowed + /// inside the DWARF sections, both for debugging and exception handling. + /// + internal sealed partial class MachObjectWriter : UnixObjectWriter + { + private sealed record CompactUnwindCode(string PcStartSymbolName, uint PcLength, uint Code, string LsdaSymbolName = null, string PersonalitySymbolName = null); + + // Exception handling sections + private MachSection _compactUnwindSection; + private MemoryStream _compactUnwindStream; + private readonly List _compactUnwindCodes = new(); + private readonly uint _compactUnwindDwarfCode; + + private bool IsEhFrameSection(int sectionIndex) => sectionIndex == EhFrameSectionIndex; + + partial void EmitCompactUnwindTable(IDictionary definedSymbols) + { + _compactUnwindStream = new MemoryStream(32 * _compactUnwindCodes.Count); + // Preset the size of the compact unwind section which is not generated yet + _compactUnwindStream.SetLength(32 * _compactUnwindCodes.Count); + + _compactUnwindSection = new MachSection("__LD", "__compact_unwind", _compactUnwindStream) + { + Log2Alignment = 3, + Flags = S_REGULAR | S_ATTR_DEBUG, + }; + + IList symbols = _symbolTable; + Span tempBuffer = stackalloc byte[8]; + foreach (var cu in _compactUnwindCodes) + { + EmitCompactUnwindSymbol(cu.PcStartSymbolName); + BinaryPrimitives.WriteUInt32LittleEndian(tempBuffer, cu.PcLength); + BinaryPrimitives.WriteUInt32LittleEndian(tempBuffer.Slice(4), cu.Code); + _compactUnwindStream.Write(tempBuffer); + EmitCompactUnwindSymbol(cu.PersonalitySymbolName); + EmitCompactUnwindSymbol(cu.LsdaSymbolName); + } + + void EmitCompactUnwindSymbol(string symbolName) + { + Span tempBuffer = stackalloc byte[8]; + if (symbolName is not null) + { + SymbolDefinition symbol = definedSymbols[symbolName]; + MachSection section = _sections[symbol.SectionIndex]; + BinaryPrimitives.WriteUInt64LittleEndian(tempBuffer, section.VirtualAddress + (ulong)symbol.Value); + _compactUnwindSection.Relocations.Add( + new MachRelocation + { + Address = (int)_compactUnwindStream.Position, + SymbolOrSectionIndex = (byte)(1 + symbol.SectionIndex), // 1-based + Length = 8, + RelocationType = ARM64_RELOC_UNSIGNED, + IsExternal = false, + IsPCRelative = false, + } + ); + } + _compactUnwindStream.Write(tempBuffer); + } + } + + private static uint GetArm64CompactUnwindCode(byte[] blobData) + { + if (blobData == null || blobData.Length == 0) + { + return UNWIND_ARM64_MODE_FRAMELESS; + } + + Debug.Assert(blobData.Length % 8 == 0); + + short spReg = -1; + + int codeOffset = 0; + short cfaRegister = spReg; + int cfaOffset = 0; + int spOffset = 0; + + const int REG_DWARF_X19 = 19; + const int REG_DWARF_X30 = 30; + const int REG_DWARF_FP = 29; + const int REG_DWARF_D8 = 72; + const int REG_DWARF_D15 = 79; + const int REG_IDX_X19 = 0; + const int REG_IDX_X28 = 9; + const int REG_IDX_FP = 10; + const int REG_IDX_LR = 11; + const int REG_IDX_D8 = 12; + const int REG_IDX_D15 = 19; + Span registerOffset = stackalloc int[20]; + + registerOffset.Fill(int.MinValue); + + // First process all the CFI codes to figure out the layout of X19-X28, FP, LR, and + // D8-D15 on the stack. + int offset = 0; + while (offset < blobData.Length) + { + codeOffset = Math.Max(codeOffset, blobData[offset++]); + CFI_OPCODE opcode = (CFI_OPCODE)blobData[offset++]; + short dwarfReg = BinaryPrimitives.ReadInt16LittleEndian(blobData.AsSpan(offset)); + offset += sizeof(short); + int cfiOffset = BinaryPrimitives.ReadInt32LittleEndian(blobData.AsSpan(offset)); + offset += sizeof(int); + + switch (opcode) + { + case CFI_OPCODE.CFI_DEF_CFA_REGISTER: + cfaRegister = dwarfReg; + + if (spOffset != 0) + { + for (int i = 0; i < registerOffset.Length; i++) + if (registerOffset[i] != int.MinValue) + registerOffset[i] -= spOffset; + + cfaOffset += spOffset; + spOffset = 0; + } + + break; + + case CFI_OPCODE.CFI_REL_OFFSET: + Debug.Assert(cfaRegister == spReg); + if (dwarfReg >= REG_DWARF_X19 && dwarfReg <= REG_DWARF_X30) // X19 - X28, FP, LR + { + registerOffset[dwarfReg - REG_DWARF_X19 + REG_IDX_X19] = cfiOffset; + } + else if (dwarfReg >= REG_DWARF_D8 && dwarfReg <= REG_DWARF_D15) // D8 - D15 + { + registerOffset[dwarfReg - REG_DWARF_D8 + REG_IDX_D8] = cfiOffset; + } + else + { + // We cannot represent this register in the compact unwinding format, + // fallback to DWARF immediately. + return UNWIND_ARM64_MODE_DWARF; + } + break; + + case CFI_OPCODE.CFI_ADJUST_CFA_OFFSET: + if (cfaRegister != spReg) + { + cfaOffset += cfiOffset; + } + else + { + spOffset += cfiOffset; + + for (int i = 0; i < registerOffset.Length; i++) + if (registerOffset[i] != int.MinValue) + registerOffset[i] += cfiOffset; + } + break; + } + } + + uint unwindCode; + int nextOffset; + + if (cfaRegister == REG_DWARF_FP && + cfaOffset == 16 && + registerOffset[REG_IDX_FP] == -16 && + registerOffset[REG_IDX_LR] == -8) + { + // Frame format - FP/LR are saved on the top. SP is restored to FP+16 + unwindCode = UNWIND_ARM64_MODE_FRAME; + nextOffset = -24; + } + else if (cfaRegister == -1 && spOffset <= 65520 && + registerOffset[REG_IDX_FP] == int.MinValue && registerOffset[REG_IDX_LR] == int.MinValue) + { + // Frameless format - FP/LR are not saved, SP must fit within the representable range + uint encodedSpOffset = (uint)(spOffset / 16) << 12; + unwindCode = UNWIND_ARM64_MODE_FRAMELESS | encodedSpOffset; + nextOffset = spOffset - 8; + } + else + { + return UNWIND_ARM64_MODE_DWARF; + } + + // Check that the integer register pairs are in the right order and mark + // a flag for each successive pair that is present. + for (int i = REG_IDX_X19; i < REG_IDX_X28; i += 2) + { + if (registerOffset[i] == int.MinValue) + { + if (registerOffset[i + 1] != int.MinValue) + return UNWIND_ARM64_MODE_DWARF; + } + else if (registerOffset[i] == nextOffset) + { + if (registerOffset[i + 1] != nextOffset - 8) + return UNWIND_ARM64_MODE_DWARF; + nextOffset -= 16; + unwindCode |= UNWIND_ARM64_FRAME_X19_X20_PAIR << (i >> 1); + } + } + + // Check that the floating point register pairs are in the right order and mark + // a flag for each successive pair that is present. + for (int i = REG_IDX_D8; i < REG_IDX_D15; i += 2) + { + if (registerOffset[i] == int.MinValue) + { + if (registerOffset[i + 1] != int.MinValue) + return UNWIND_ARM64_MODE_DWARF; + } + else if (registerOffset[i] == nextOffset) + { + if (registerOffset[i + 1] != nextOffset - 8) + return UNWIND_ARM64_MODE_DWARF; + nextOffset -= 16; + unwindCode |= UNWIND_ARM64_FRAME_D8_D9_PAIR << (i >> 1); + } + } + + return unwindCode; + } + + private protected override bool EmitCompactUnwinding(string startSymbolName, ulong length, string lsdaSymbolName, byte[] blob) + { + uint encoding = _compactUnwindDwarfCode; + + if (_cpuType == CPU_TYPE_ARM64) + { + encoding = GetArm64CompactUnwindCode(blob); + } + + _compactUnwindCodes.Add(new CompactUnwindCode( + PcStartSymbolName: startSymbolName, + PcLength: (uint)length, + Code: encoding | (encoding != _compactUnwindDwarfCode && lsdaSymbolName is not null ? 0x40000000u : 0), // UNWIND_HAS_LSDA + LsdaSymbolName: encoding != _compactUnwindDwarfCode ? lsdaSymbolName : null + )); + + return encoding != _compactUnwindDwarfCode; + } + + private protected override bool UseFrameNames => true; + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/ObjectWriter.Aot.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/ObjectWriter.Aot.cs new file mode 100644 index 00000000000000..03717aa881eef5 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/ObjectWriter.Aot.cs @@ -0,0 +1,151 @@ +// 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.Binary; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using ILCompiler.DependencyAnalysis; +using ILCompiler.DependencyAnalysisFramework; +using Internal.TypeSystem; +using Internal.TypeSystem.TypesDebugInfo; +using ObjectData = ILCompiler.DependencyAnalysis.ObjectNode.ObjectData; +using static ILCompiler.DependencyAnalysis.RelocType; + +namespace ILCompiler.ObjectWriter +{ + public abstract partial class ObjectWriter + { + // Debugging + private UserDefinedTypeDescriptor _userDefinedTypeDescriptor; + + private protected abstract void EmitUnwindInfo( + SectionWriter sectionWriter, + INodeWithCodeInfo nodeWithCodeInfo, + string currentSymbolName); + + private protected uint GetVarTypeIndex(bool isStateMachineMoveNextMethod, DebugVarInfoMetadata debugVar) + { + uint typeIndex; + try + { + if (isStateMachineMoveNextMethod && debugVar.DebugVarInfo.VarNumber == 0) + { + typeIndex = _userDefinedTypeDescriptor.GetStateMachineThisVariableTypeIndex(debugVar.Type); + // FIXME + // varName = "locals"; + } + else + { + typeIndex = _userDefinedTypeDescriptor.GetVariableTypeIndex(debugVar.Type); + } + } + catch (TypeSystemException) + { + typeIndex = 0; // T_NOTYPE + // FIXME + // Debug.Fail(); + } + return typeIndex; + } + + private protected abstract ITypesDebugInfoWriter CreateDebugInfoBuilder(); + + private protected abstract void EmitDebugFunctionInfo( + uint methodTypeIndex, + string methodName, + SymbolDefinition methodSymbol, + INodeWithDebugInfo debugNode, + bool hasSequencePoints); + + private protected virtual void EmitDebugThunkInfo( + string methodName, + SymbolDefinition methodSymbol, + INodeWithDebugInfo debugNode) + { + } + + private protected abstract void EmitDebugSections(IDictionary definedSymbols); + + partial void EmitDebugInfo(IReadOnlyCollection nodes, Logger logger) + { + if (logger.IsVerbose) + logger.LogMessage($"Emitting debug information"); + + _userDefinedTypeDescriptor = new UserDefinedTypeDescriptor(CreateDebugInfoBuilder(), _nodeFactory); + + foreach (DependencyNode depNode in nodes) + { + ObjectNode node = depNode as ObjectNode; + if (node is null || node.ShouldSkipEmittingObjectNode(_nodeFactory)) + { + continue; + } + + ISymbolNode symbolNode = node as ISymbolNode; + ISymbolNode deduplicatedSymbolNode = _nodeFactory.ObjectInterner.GetDeduplicatedSymbol(_nodeFactory, symbolNode); + if (deduplicatedSymbolNode != symbolNode) + { + continue; + } + + // Ensure any allocated MethodTables have debug info + if (node is ConstructedEETypeNode methodTable) + { + _userDefinedTypeDescriptor.GetTypeIndex(methodTable.Type, needsCompleteType: true); + } + + if (node is INodeWithDebugInfo debugNode and ISymbolDefinitionNode symbolDefinitionNode) + { + string methodName = GetMangledName(symbolDefinitionNode); + if (_definedSymbols.TryGetValue(methodName, out var methodSymbol)) + { + if (node is IMethodNode methodNode) + { + bool hasSequencePoints = debugNode.GetNativeSequencePoints().Any(); + uint methodTypeIndex = hasSequencePoints ? _userDefinedTypeDescriptor.GetMethodFunctionIdTypeIndex(methodNode.Method) : 0; + EmitDebugFunctionInfo(methodTypeIndex, methodName, methodSymbol, debugNode, hasSequencePoints); + } + else + { + EmitDebugThunkInfo(methodName, methodSymbol, debugNode); + } + } + } + } + + // Ensure all fields associated with generated static bases have debug info + foreach (MetadataType typeWithStaticBase in _nodeFactory.MetadataManager.GetTypesWithStaticBases()) + { + _userDefinedTypeDescriptor.GetTypeIndex(typeWithStaticBase, needsCompleteType: true); + } + + EmitDebugSections(_definedSymbols); + } + + private protected abstract void CreateEhSections(); + + partial void PrepareForUnwindInfo() => CreateEhSections(); + + partial void EmitUnwindInfoForNode(ObjectNode node, string currentSymbolName, SectionWriter sectionWriter) + { + if (node is INodeWithCodeInfo nodeWithCodeInfo) + { + EmitUnwindInfo(sectionWriter, nodeWithCodeInfo, currentSymbolName); + } + } + + partial void HandleControlFlowForRelocation(ISymbolNode relocTarget, string relocSymbolName) + { + if (relocTarget is IMethodNode or AssemblyStubNode or AddressTakenExternFunctionSymbolNode) + { + // For now consider all method symbols address taken. + // We could restrict this in the future to those that are referenced from + // reflection tables, EH tables, were actually address taken in code, or are referenced from vtables. + EmitReferencedMethod(relocSymbolName); + } + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/UnixObjectWriter.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/UnixObjectWriter.Aot.cs similarity index 91% rename from src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/UnixObjectWriter.cs rename to src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/UnixObjectWriter.Aot.cs index 0ae31be325e186..70923f97089317 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/UnixObjectWriter.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/UnixObjectWriter.Aot.cs @@ -18,14 +18,8 @@ namespace ILCompiler.ObjectWriter /// Base implementation for ELF and Mach-O object file format writers. Implements /// the common code for DWARF debugging and exception handling information. /// - internal abstract class UnixObjectWriter : ObjectWriter + internal abstract partial class UnixObjectWriter : ObjectWriter { - private sealed record UnixSectionDefinition(string SymbolName, Stream SectionStream); - - // Debugging - private DwarfBuilder _dwarfBuilder; - private readonly List _sections = new(); - // Exception handling sections private SectionWriter _lsdaSectionWriter; private int _ehFrameSectionIndex; @@ -34,8 +28,9 @@ private sealed record UnixSectionDefinition(string SymbolName, Stream SectionStr protected int EhFrameSectionIndex => _ehFrameSectionIndex; - private static readonly ObjectNodeSection LsdaSection = new ObjectNodeSection(".dotnet_eh_table", SectionType.ReadOnly); - private static readonly ObjectNodeSection EhFrameSection = new ObjectNodeSection(".eh_frame", SectionType.UnwindData); + // Debugging + private DwarfBuilder _dwarfBuilder; + private static readonly ObjectNodeSection DebugInfoSection = new ObjectNodeSection(".debug_info", SectionType.Debug); private static readonly ObjectNodeSection DebugStringSection = new ObjectNodeSection(".debug_str", SectionType.Debug); private static readonly ObjectNodeSection DebugAbbrevSection = new ObjectNodeSection(".debug_abbrev", SectionType.Debug); @@ -44,30 +39,32 @@ private sealed record UnixSectionDefinition(string SymbolName, Stream SectionStr private static readonly ObjectNodeSection DebugLineSection = new ObjectNodeSection(".debug_line", SectionType.Debug); private static readonly ObjectNodeSection DebugARangesSection = new ObjectNodeSection(".debug_aranges", SectionType.Debug); - protected UnixObjectWriter(NodeFactory factory, ObjectWritingOptions options) - : base(factory, options) - { - } + private protected virtual bool UseFrameNames => false; + + private protected virtual bool EmitCompactUnwinding(string startSymbolName, ulong length, string lsdaSymbolName, byte[] blob) => false; - private protected override void CreateSection(ObjectNodeSection section, string comdatName, string symbolName, Stream sectionStream) + private protected override void CreateEhSections() { - if (section.Type != SectionType.Debug && - section != LsdaSection && - section != EhFrameSection && - (comdatName is null || Equals(comdatName, symbolName))) - { - // Record code and data sections that can be referenced from debugging information - _sections.Add(new UnixSectionDefinition(symbolName, sectionStream)); - } - else - { - _sections.Add(null); - } - } + SectionWriter ehFrameSectionWriter; - private protected virtual bool EmitCompactUnwinding(string startSymbolName, ulong length, string lsdaSymbolName, byte[] blob) => false; + // Create sections for exception handling + _lsdaSectionWriter = GetOrCreateSection(LsdaSection); + ehFrameSectionWriter = GetOrCreateSection(EhFrameSection); + _lsdaSectionWriter.EmitAlignment(8); + ehFrameSectionWriter.EmitAlignment(8); + _ehFrameSectionIndex = ehFrameSectionWriter.SectionIndex; - private protected virtual bool UseFrameNames => false; + // We always use the same CIE in DWARF EH frames, so create and emit it now + bool is64Bit = _nodeFactory.Target.Architecture switch + { + TargetArchitecture.X86 => false, + TargetArchitecture.ARM => false, + _ => true + }; + _dwarfCie = new DwarfCie(_nodeFactory.Target.Architecture); + _dwarfEhFrame = new DwarfEhFrame(ehFrameSectionWriter, is64Bit); + _dwarfEhFrame.AddCie(_dwarfCie); + } private protected void EmitLsda( INodeWithCodeInfo nodeWithCodeInfo, @@ -318,29 +315,6 @@ private protected override void EmitDebugSections(IDictionary false, - TargetArchitecture.ARM => false, - _ => true - }; - _dwarfCie = new DwarfCie(_nodeFactory.Target.Architecture); - _dwarfEhFrame = new DwarfEhFrame(ehFrameSectionWriter, is64Bit); - _dwarfEhFrame.AddCie(_dwarfCie); - } - private protected override ITypesDebugInfoWriter CreateDebugInfoBuilder() { return _dwarfBuilder = new DwarfBuilder( @@ -348,5 +322,6 @@ private protected override ITypesDebugInfoWriter CreateDebugInfoBuilder() _nodeFactory.Target, _options.HasFlag(ObjectWritingOptions.UseDwarf5)); } + } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj index 86886d93c2baed..c1a26ce1951dc7 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj @@ -22,6 +22,7 @@ + @@ -293,6 +294,7 @@ + @@ -325,6 +327,24 @@ + + + + + + + + + + + + + + + + + + @@ -506,7 +526,6 @@ - @@ -602,6 +621,15 @@ + + + + + + + + + @@ -610,35 +638,17 @@ - - - - - - - - - - - - - - - - - - - - - - - + + + + + diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunContainerFormat.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunContainerFormat.cs new file mode 100644 index 00000000000000..403fc97e4c1cd6 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunContainerFormat.cs @@ -0,0 +1,14 @@ +// 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.Collections.Generic; +using System.Text; + +namespace ILCompiler.DependencyAnalysis +{ + public enum ReadyToRunContainerFormat + { + PE + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs index 9c579901fdb0d7..1b475b1cff7d62 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs @@ -5,19 +5,19 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.IO.Pipes; using System.Linq; using System.Reflection.Metadata; using System.Reflection.PortableExecutable; - +using System.Security.Cryptography; using ILCompiler.DependencyAnalysis.ReadyToRun; using ILCompiler.DependencyAnalysisFramework; using ILCompiler.Diagnostics; +using ILCompiler.ObjectWriter; using ILCompiler.PEWriter; -using ObjectData = ILCompiler.DependencyAnalysis.ObjectNode.ObjectData; - using Internal.TypeSystem; using Internal.TypeSystem.Ecma; -using System.Security.Cryptography; +using ObjectData = ILCompiler.DependencyAnalysis.ObjectNode.ObjectData; namespace ILCompiler.DependencyAnalysis { @@ -54,10 +54,10 @@ internal class ReadyToRunObjectWriter /// /// Nodes to emit into the output executable as collected by the dependency analysis. /// - private readonly IEnumerable _nodes; + private readonly IReadOnlyCollection _nodes; /// - /// Set to non-null when the executable generator should output a map or symbol file. + /// Set to non-null when generating symbol info or profile info. /// private readonly OutputInfoBuilder _outputInfoBuilder; @@ -117,29 +117,11 @@ internal class ReadyToRunObjectWriter /// private readonly int _customPESectionAlignment; -#if DEBUG - private struct NodeInfo - { - public readonly ISymbolNode Node; - public readonly int NodeIndex; - public readonly int SymbolIndex; - - public NodeInfo(ISymbolNode node, int nodeIndex, int symbolIndex) - { - Node = node; - NodeIndex = nodeIndex; - SymbolIndex = symbolIndex; - } - } - - Dictionary _previouslyWrittenNodeNames = new Dictionary(); -#endif - public ReadyToRunObjectWriter( string objectFilePath, EcmaModule componentModule, IEnumerable inputFiles, - IEnumerable nodes, + IReadOnlyCollection nodes, NodeFactory factory, bool generateMapFile, bool generateMapCsvFile, @@ -172,7 +154,6 @@ public ReadyToRunObjectWriter( if (generateMap || generateSymbols || generateProfileFile) { _outputInfoBuilder = new OutputInfoBuilder(); - if (generateMap) { _mapFileBuilder = new MapFileBuilder(_outputInfoBuilder); @@ -190,238 +171,99 @@ public ReadyToRunObjectWriter( } } - public void EmitPortableExecutable() + public void EmitReadyToRunObjects(ReadyToRunContainerFormat format, Logger logger) { bool succeeded = false; try { - Stopwatch stopwatch = new Stopwatch(); - stopwatch.Start(); + var stopwatch = Stopwatch.StartNew(); + + Debug.Assert(format == ReadyToRunContainerFormat.PE); - PEHeaderBuilder headerBuilder; int? timeDateStamp; - ISymbolNode r2rHeaderExportSymbol; - Func, BlobContentId> peIdProvider = null; if (_nodeFactory.CompilationModuleGroup.IsCompositeBuildMode && _componentModule == null) { - headerBuilder = PEHeaderProvider.Create(Subsystem.Unknown, _nodeFactory.Target, _nodeFactory.ImageBase); - peIdProvider = new Func, BlobContentId>(content => BlobContentId.FromHash(CryptographicHashProvider.ComputeSourceHash(content))); timeDateStamp = null; - r2rHeaderExportSymbol = _nodeFactory.Header; } else { PEReader inputPeReader = (_componentModule != null ? _componentModule.PEReader : _nodeFactory.CompilationModuleGroup.CompilationModuleSet.First().PEReader); - headerBuilder = PEHeaderProvider.Create(inputPeReader.PEHeaders.PEHeader.Subsystem, _nodeFactory.Target, _nodeFactory.ImageBase); timeDateStamp = inputPeReader.PEHeaders.CoffHeader.TimeDateStamp; - r2rHeaderExportSymbol = null; } - Func getRuntimeFunctionsTable = null; - if (_componentModule == null) + PEObjectWriter objectWriter = new(_nodeFactory, ObjectWritingOptions.None, _outputInfoBuilder, _objectFilePath, _customPESectionAlignment, timeDateStamp); + + if (_nodeFactory.CompilationModuleGroup.IsCompositeBuildMode && _componentModule == null) { - getRuntimeFunctionsTable = GetRuntimeFunctionsTable; + objectWriter.AddExportedSymbol("RTR_HEADER"); } - R2RPEBuilder r2rPeBuilder = new R2RPEBuilder( - _nodeFactory.Target, - headerBuilder, - r2rHeaderExportSymbol, - Path.GetFileName(_objectFilePath), - getRuntimeFunctionsTable, - _customPESectionAlignment, - peIdProvider); - - NativeDebugDirectoryEntryNode nativeDebugDirectoryEntryNode = null; - PerfMapDebugDirectoryEntryNode perfMapDebugDirectoryEntryNode = null; - ISymbolDefinitionNode firstImportThunk = null; - ISymbolDefinitionNode lastImportThunk = null; - ObjectNode lastWrittenObjectNode = null; - - // Save cold method nodes here, and emit them to execution section last. - List methodColdCodeNodes = new List(); - - int nodeIndex = -1; - foreach (var depNode in _nodes) - { - ObjectNode node = depNode as ObjectNode; - - if (node is MethodColdCodeNode) - { - methodColdCodeNodes.Add(node); - continue; - } - - ++nodeIndex; - - if (node == null) - { - continue; - } - - if (node.ShouldSkipEmittingObjectNode(_nodeFactory)) - continue; - ObjectData nodeContents = node.GetData(_nodeFactory); + using FileStream stream = new FileStream(_objectFilePath, FileMode.Create); + objectWriter.EmitObject(stream, _nodes, dumper: null, logger); - if (node is NativeDebugDirectoryEntryNode nddeNode) - { - // There should be only one NativeDebugDirectoryEntry. - Debug.Assert(nativeDebugDirectoryEntryNode == null); - nativeDebugDirectoryEntryNode = nddeNode; - } - - if (node is PerfMapDebugDirectoryEntryNode pmdeNode) - { - // There should be only one PerfMapDebugDirectoryEntryNode. - Debug.Assert(perfMapDebugDirectoryEntryNode is null); - perfMapDebugDirectoryEntryNode = pmdeNode; - } - - if (node is ImportThunk importThunkNode) - { - Debug.Assert(firstImportThunk == null || lastWrittenObjectNode is ImportThunk, - "All the import thunks must be in single contiguous run"); - - if (firstImportThunk == null) - { - firstImportThunk = importThunkNode; - } - lastImportThunk = importThunkNode; - } - - string name = GetDependencyNodeName(depNode); - - EmitObjectData(r2rPeBuilder, nodeContents, nodeIndex, name, node.GetSection(_nodeFactory)); - lastWrittenObjectNode = node; - } - - if (_outputInfoBuilder != null) + if (_outputInfoBuilder is not null) { foreach (MethodWithGCInfo methodNode in _nodeFactory.EnumerateCompiledMethods()) _outputInfoBuilder.AddMethod(methodNode, methodNode); } - // Emit cold method nodes to end of execution section. - foreach (ObjectNode node in methodColdCodeNodes) + if (_mapFileBuilder != null) { - ++nodeIndex; + _mapFileBuilder.SetFileSize(stream.Length); + } - if (node == null) + if (_outputInfoBuilder is not null) + { + foreach (string inputFile in _inputFiles) { - continue; + _outputInfoBuilder.AddInputModule(_nodeFactory.TypeSystemContext.GetModuleFromPath(inputFile)); } - - ObjectData nodeContents = node.GetData(_nodeFactory); - string name = GetDependencyNodeName(node); - - EmitObjectData(r2rPeBuilder, nodeContents, nodeIndex, name, node.GetSection(_nodeFactory)); } - r2rPeBuilder.SetCorHeader(_nodeFactory.CopiedCorHeaderNode, _nodeFactory.CopiedCorHeaderNode.Size); - r2rPeBuilder.SetDebugDirectory(_nodeFactory.DebugDirectoryNode, _nodeFactory.DebugDirectoryNode.Size); - if (firstImportThunk != null) + if (_generateMapFile) { - r2rPeBuilder.AddSymbolForRange(_nodeFactory.DelayLoadMethodCallThunks, firstImportThunk, lastImportThunk); + string mapFileName = Path.ChangeExtension(_objectFilePath, ".map"); + _mapFileBuilder.SaveMap(mapFileName); } - - if (_nodeFactory.Win32ResourcesNode != null) + if (_generateMapCsvFile) { - Debug.Assert(_nodeFactory.Win32ResourcesNode.Size != 0); - r2rPeBuilder.SetWin32Resources(_nodeFactory.Win32ResourcesNode, _nodeFactory.Win32ResourcesNode.Size); + string nodeStatsCsvFileName = Path.ChangeExtension(_objectFilePath, ".nodestats.csv"); + string mapCsvFileName = Path.ChangeExtension(_objectFilePath, ".map.csv"); + _mapFileBuilder.SaveCsv(nodeStatsCsvFileName, mapCsvFileName); } - if (_outputInfoBuilder != null) + if (_generatePdbFile) { - foreach (string inputFile in _inputFiles) + string path = _pdbPath; + if (string.IsNullOrEmpty(path)) { - _outputInfoBuilder.AddInputModule(_nodeFactory.TypeSystemContext.GetModuleFromPath(inputFile)); + path = Path.GetDirectoryName(_objectFilePath); } + _symbolFileBuilder.SavePdb(path, _objectFilePath); } - using (var peStream = File.Create(_objectFilePath)) + if (_generatePerfMapFile) { - r2rPeBuilder.Write(peStream, timeDateStamp); - - if (_mapFileBuilder != null) - { - _mapFileBuilder.SetFileSize(peStream.Length); - } - - if (nativeDebugDirectoryEntryNode is not null) + string path = _perfMapPath; + if (string.IsNullOrEmpty(path)) { - Debug.Assert(_generatePdbFile); - // Compute hash of the output image and store that in the native DebugDirectory entry - using (var hashAlgorithm = SHA256.Create()) - { - peStream.Seek(0, SeekOrigin.Begin); - byte[] hash = hashAlgorithm.ComputeHash(peStream); - byte[] rsdsEntry = nativeDebugDirectoryEntryNode.GenerateRSDSEntryData(hash); - - int offsetToUpdate = r2rPeBuilder.GetSymbolFilePosition(nativeDebugDirectoryEntryNode); - peStream.Seek(offsetToUpdate, SeekOrigin.Begin); - peStream.Write(rsdsEntry); - } - } - - if (perfMapDebugDirectoryEntryNode is not null) - { - Debug.Assert(_generatePerfMapFile && _outputInfoBuilder is not null && _outputInfoBuilder.EnumerateInputAssemblies().Any()); - byte[] perfmapSig = PerfMapWriter.PerfMapV1SignatureHelper(_outputInfoBuilder.EnumerateInputAssemblies(), _nodeFactory.Target); - byte[] perfMapEntry = perfMapDebugDirectoryEntryNode.GeneratePerfMapEntryData(perfmapSig, _perfMapFormatVersion); - - int offsetToUpdate = r2rPeBuilder.GetSymbolFilePosition(perfMapDebugDirectoryEntryNode); - peStream.Seek(offsetToUpdate, SeekOrigin.Begin); - peStream.Write(perfMapEntry); + path = Path.GetDirectoryName(_objectFilePath); } + _symbolFileBuilder.SavePerfMap(path, _perfMapFormatVersion, _objectFilePath); } - if (_outputInfoBuilder != null) + if (_profileFileBuilder != null) { - r2rPeBuilder.AddSections(_outputInfoBuilder); - - if (_generateMapFile) - { - string mapFileName = Path.ChangeExtension(_objectFilePath, ".map"); - _mapFileBuilder.SaveMap(mapFileName); - } - - if (_generateMapCsvFile) - { - string nodeStatsCsvFileName = Path.ChangeExtension(_objectFilePath, ".nodestats.csv"); - string mapCsvFileName = Path.ChangeExtension(_objectFilePath, ".map.csv"); - _mapFileBuilder.SaveCsv(nodeStatsCsvFileName, mapCsvFileName); - } - - if (_generatePdbFile) - { - string path = _pdbPath; - if (string.IsNullOrEmpty(path)) - { - path = Path.GetDirectoryName(_objectFilePath); - } - _symbolFileBuilder.SavePdb(path, _objectFilePath); - } - - if (_generatePerfMapFile) - { - string path = _perfMapPath; - if (string.IsNullOrEmpty(path)) - { - path = Path.GetDirectoryName(_objectFilePath); - } - _symbolFileBuilder.SavePerfMap(path, _perfMapFormatVersion, _objectFilePath); - } - - if (_profileFileBuilder != null) - { - string path = Path.ChangeExtension(_objectFilePath, ".profile"); - _profileFileBuilder.SaveProfile(path); - } + string path = Path.ChangeExtension(_objectFilePath, ".profile"); + _profileFileBuilder.SaveProfile(path); } + stopwatch.Stop(); + if (logger.IsVerbose) + logger.LogMessage($"Done writing object file in {stopwatch.Elapsed}"); succeeded = true; } finally @@ -441,81 +283,11 @@ public void EmitPortableExecutable() } } - /// - /// Helper method to generate the name of a given DependencyNode. Returns null if a name is not needed. - /// A name is needed only when the executable generator should output a map file. - /// - /// The DependencyNode to return a name for, if one is needed. - private string GetDependencyNodeName(DependencyNode depNode) - { - if (_mapFileBuilder == null) - { - return null; - } - - string name = depNode.GetType().ToString(); - int firstGeneric = name.IndexOf('['); - - if (firstGeneric < 0) - { - firstGeneric = name.Length; - } - - int lastDot = name.LastIndexOf('.', firstGeneric - 1, firstGeneric); - - if (lastDot > 0) - { - name = name.Substring(lastDot + 1); - } - - return name; - } - - /// - /// Update the PE header directories by setting up the exception directory to point to the runtime functions table. - /// This is needed for RtlLookupFunctionEntry / RtlLookupFunctionTable to work. - /// - /// PE header directory builder can be used to override RVA's / sizes of any of the directories - private RuntimeFunctionsTableNode GetRuntimeFunctionsTable() => _nodeFactory.RuntimeFunctionsTable; - - /// - /// Emit a single ObjectData into the proper section of the output R2R PE executable. - /// - /// R2R PE builder to output object data to - /// ObjectData blob to emit - /// Logical index of the emitted node for diagnostic purposes - /// Textual representation of the ObjecData blob in the map file - /// Section to emit the blob into - private void EmitObjectData(R2RPEBuilder r2rPeBuilder, ObjectData data, int nodeIndex, string name, ObjectNodeSection section) - { -#if DEBUG - for (int symbolIndex = 0; symbolIndex < data.DefinedSymbols.Length; symbolIndex++) - { - ISymbolNode definedSymbol = data.DefinedSymbols[symbolIndex]; - NodeInfo alreadyWrittenSymbol; - string symbolName = definedSymbol.GetMangledName(_nodeFactory.NameMangler); - if (_previouslyWrittenNodeNames.TryGetValue(symbolName, out alreadyWrittenSymbol)) - { - Console.WriteLine($@"Duplicate symbol - 1st occurrence: [{alreadyWrittenSymbol.NodeIndex}:{alreadyWrittenSymbol.SymbolIndex}], {alreadyWrittenSymbol.Node.GetMangledName(_nodeFactory.NameMangler)}"); - Console.WriteLine($@"Duplicate symbol - 2nd occurrence: [{nodeIndex}:{symbolIndex}], {definedSymbol.GetMangledName(_nodeFactory.NameMangler)}"); - Debug.Fail("Duplicate node name emitted to file", - $"Symbol {definedSymbol.GetMangledName(_nodeFactory.NameMangler)} has already been written to the output object file {_objectFilePath} with symbol {alreadyWrittenSymbol}"); - } - else - { - _previouslyWrittenNodeNames.Add(symbolName, new NodeInfo(definedSymbol, nodeIndex, symbolIndex)); - } - } -#endif - - r2rPeBuilder.AddObjectData(data, section, name, _outputInfoBuilder); - } - public static void EmitObject( string objectFilePath, EcmaModule componentModule, IEnumerable inputFiles, - IEnumerable nodes, + IReadOnlyCollection nodes, NodeFactory factory, bool generateMapFile, bool generateMapCsvFile, @@ -526,7 +298,9 @@ public static void EmitObject( int perfMapFormatVersion, bool generateProfileFile, CallChainProfile callChainProfile, - int customPESectionAlignment) + ReadyToRunContainerFormat format, + int customPESectionAlignment, + Logger logger) { Console.WriteLine($@"Emitting R2R PE file: {objectFilePath}"); ReadyToRunObjectWriter objectWriter = new ReadyToRunObjectWriter( @@ -545,7 +319,8 @@ public static void EmitObject( generateProfileFile: generateProfileFile, callChainProfile, customPESectionAlignment); - objectWriter.EmitPortableExecutable(); + + objectWriter.EmitReadyToRunObjects(format, logger); } } } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/CopiedCorHeaderNode.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/CopiedCorHeaderNode.cs index 38e12cbc072ebd..aa31162f89cd63 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/CopiedCorHeaderNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/CopiedCorHeaderNode.cs @@ -22,7 +22,10 @@ public CopiedCorHeaderNode(EcmaModule sourceModule) _module = sourceModule; } - public override ObjectNodeSection GetSection(NodeFactory factory) => ObjectNodeSection.TextSection; + public override ObjectNodeSection GetSection(NodeFactory factory) + { + return ObjectNodeSection.CorMetaSection; + } public override bool IsShareable => false; diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/CopiedFieldRvaNode.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/CopiedFieldRvaNode.cs index e73c321649509f..533b4fb293fe37 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/CopiedFieldRvaNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/CopiedFieldRvaNode.cs @@ -24,7 +24,10 @@ public CopiedFieldRvaNode(EcmaModule module, int rva) _module = module; } - public override ObjectNodeSection GetSection(NodeFactory factory) => ObjectNodeSection.TextSection; + public override ObjectNodeSection GetSection(NodeFactory factory) + { + return ObjectNodeSection.CorMetaSection; + } public override bool IsShareable => false; diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/CopiedManagedResourcesNode.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/CopiedManagedResourcesNode.cs index 8e015d97178e37..0756da31a76cb1 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/CopiedManagedResourcesNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/CopiedManagedResourcesNode.cs @@ -18,7 +18,10 @@ public CopiedManagedResourcesNode(EcmaModule module) _module = module; } - public override ObjectNodeSection GetSection(NodeFactory factory) => ObjectNodeSection.TextSection; + public override ObjectNodeSection GetSection(NodeFactory factory) + { + return ObjectNodeSection.CorMetaSection; + } public override bool IsShareable => false; diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/CopiedMetadataBlobNode.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/CopiedMetadataBlobNode.cs index 913ed4ab9e3408..d296b15d8762fb 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/CopiedMetadataBlobNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/CopiedMetadataBlobNode.cs @@ -25,7 +25,10 @@ public CopiedMetadataBlobNode(EcmaModule sourceModule) _sourceModule = sourceModule; } - public override ObjectNodeSection GetSection(NodeFactory factory) => ObjectNodeSection.TextSection; + public override ObjectNodeSection GetSection(NodeFactory factory) + { + return ObjectNodeSection.CorMetaSection; + } public override bool IsShareable => false; diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/CopiedMethodILNode.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/CopiedMethodILNode.cs index 2b1ad266ae8762..7c16ce08710a68 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/CopiedMethodILNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/CopiedMethodILNode.cs @@ -22,7 +22,10 @@ public CopiedMethodILNode(EcmaMethod method) _method = (EcmaMethod)method.GetTypicalMethodDefinition(); } - public override ObjectNodeSection GetSection(NodeFactory factory) => ObjectNodeSection.TextSection; + public override ObjectNodeSection GetSection(NodeFactory factory) + { + return ObjectNodeSection.CorMetaSection; + } public override bool IsShareable => false; diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/CopiedStrongNameSignatureNode.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/CopiedStrongNameSignatureNode.cs index dd5b03b87dba5d..8398909174018a 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/CopiedStrongNameSignatureNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/CopiedStrongNameSignatureNode.cs @@ -21,7 +21,10 @@ public CopiedStrongNameSignatureNode(EcmaModule module) _module = module; } - public override ObjectNodeSection GetSection(NodeFactory factory) => ObjectNodeSection.TextSection; + public override ObjectNodeSection GetSection(NodeFactory factory) + { + return ObjectNodeSection.CorMetaSection; + } public override bool IsShareable => false; diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/DebugDirectoryEntryNode.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/DebugDirectoryEntryNode.cs index e665d3c0cc74c3..4d0f410033d574 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/DebugDirectoryEntryNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/DebugDirectoryEntryNode.cs @@ -12,6 +12,10 @@ using Internal.TypeSystem.Ecma; using System.IO; using System.Collections.Immutable; +using System.Collections.Generic; +using ILCompiler.Diagnostics; +using ILCompiler.DependencyAnalysisFramework; +using System.Security.Cryptography; namespace ILCompiler.DependencyAnalysis.ReadyToRun { @@ -77,13 +81,15 @@ public class PerfMapDebugDirectoryEntryNode : DebugDirectoryEntryNode public unsafe int Size => PerfMapEntrySize; - public PerfMapDebugDirectoryEntryNode(string entryName) + public PerfMapDebugDirectoryEntryNode(string entryName, int perfMapFormatVersion) : base(null) { _entryName = entryName; + _perfMapFormatVersion = perfMapFormatVersion; } - private string _entryName; + private readonly string _entryName; + private readonly int _perfMapFormatVersion; public override void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb) { @@ -97,30 +103,25 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) builder.RequireInitialPointerAlignment(); builder.AddSymbol(this); - // Emit empty entry. This will be filled with data after the output image is emitted - builder.EmitZeros(PerfMapEntrySize); + List assemblies = []; + foreach (string inputPath in factory.TypeSystemContext.InputFilePaths.Values) + { + EcmaModule module = factory.TypeSystemContext.GetModuleFromPath(inputPath); + assemblies.Add(new AssemblyInfo(module.Assembly.GetName().Name, module.MetadataReader.GetGuid(module.MetadataReader.GetModuleDefinition().Mvid))); + } - return builder.ToObjectData(); - } + byte[] signature = PerfMapWriter.PerfMapV1SignatureHelper(assemblies, factory.Target); - public byte[] GeneratePerfMapEntryData(byte[] signature, int version) - { - Debug.Assert(SignatureSize == signature.Length); - MemoryStream perfmapEntry = new MemoryStream(PerfMapEntrySize); + builder.EmitUInt(PerfMapMagic); + builder.EmitBytes(signature); + builder.EmitInt(_perfMapFormatVersion); - using (BinaryWriter writer = new BinaryWriter(perfmapEntry)) - { - writer.Write(PerfMapMagic); - writer.Write(signature); - writer.Write(version); + builder.EmitBytes(Encoding.UTF8.GetBytes(_entryName)); + builder.EmitByte(0); - byte[] perfmapNameBytes = Encoding.UTF8.GetBytes(_entryName); - writer.Write(perfmapNameBytes); - writer.Write(0); // Null terminator + Debug.Assert(builder.CountBytes <= PerfMapEntrySize); - Debug.Assert(perfmapEntry.Length <= PerfMapEntrySize); - return perfmapEntry.ToArray(); - } + return builder.ToObjectData(); } internal void EmitHeader(ref ObjectDataBuilder builder) @@ -161,7 +162,8 @@ public NativeDebugDirectoryEntryNode(string pdbName) _pdbName = pdbName; } - private string _pdbName; + private readonly string _pdbName; + private readonly RSDSChecksumNode _checksumNode = new(); public override void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb) { @@ -175,8 +177,19 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) builder.RequireInitialPointerAlignment(); builder.AddSymbol(this); - // Emit empty entry. This will be filled with data after the output image is emitted - builder.EmitZeros(RSDSSize); + builder.EmitUInt(RsdsMagic); + + builder.EmitChecksumReloc(_checksumNode); + + // Age + builder.EmitInt(1); + + string pdbFileName = _pdbName; + byte[] pdbFileNameBytes = Encoding.UTF8.GetBytes(pdbFileName); + builder.EmitBytes(pdbFileNameBytes); + builder.EmitByte(0); // Null terminator + + Debug.Assert(builder.CountBytes <= RSDSSize); return builder.ToObjectData(); } @@ -223,6 +236,44 @@ internal void EmitHeader(ref ObjectDataBuilder builder, uint stamp, ushort major builder.EmitReloc(this, RelocType.IMAGE_REL_BASED_ADDR32NB); builder.EmitReloc(this, RelocType.IMAGE_REL_FILE_ABSOLUTE); } + + private class RSDSChecksumNode : DependencyNodeCore, IChecksumNode + { + public int ChecksumSize => 16; + + public void EmitChecksum(ReadOnlySpan outputBlob, Span checksumLocation) + { + Debug.Assert(checksumLocation.Length == ChecksumSize); + // Take the first 16 bytes of the SHA256 hash as the RSDS checksum. + SHA256.HashData(outputBlob)[0..ChecksumSize].CopyTo(checksumLocation); + } + + public override bool InterestingForDynamicDependencyAnalysis => false; + + public override bool HasDynamicDependencies => false; + + public override bool HasConditionalStaticDependencies => false; + + public override bool StaticDependenciesAreComputed => true; + + public int Offset => 0; + + public bool RepresentsIndirectionCell => false; + + public void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb) + { + sb.Append(nameMangler.CompilationUnitPrefix); + sb.Append($"__RSDSChecksum"); + } + + public override IEnumerable GetConditionalStaticDependencies(NodeFactory context) => []; + public override IEnumerable GetStaticDependencies(NodeFactory context) => []; + public override IEnumerable SearchDynamicDependencies(List> markedNodes, int firstNode, NodeFactory context) => []; + protected override string GetName(NodeFactory context) + { + return "RSDSChecksum"; + } + } } public class CopiedDebugDirectoryEntryNode : DebugDirectoryEntryNode diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/DebugDirectoryNode.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/DebugDirectoryNode.cs index d903db2524c0bf..7c304148cfee81 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/DebugDirectoryNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/DebugDirectoryNode.cs @@ -31,7 +31,7 @@ public class DebugDirectoryNode : ObjectNode, ISymbolDefinitionNode private bool _insertDeterministicEntry; - public DebugDirectoryNode(EcmaModule sourceModule, string outputFileName, bool shouldAddNiPdb, bool shouldGeneratePerfmap) + public DebugDirectoryNode(EcmaModule sourceModule, string outputFileName, bool shouldAddNiPdb, bool shouldGeneratePerfmap, int perfMapFormatVersion) { _module = sourceModule; _insertDeterministicEntry = sourceModule == null; // Mark module as deterministic if generating composite image @@ -48,11 +48,14 @@ public DebugDirectoryNode(EcmaModule sourceModule, string outputFileName, bool s if (shouldGeneratePerfmap) { - _perfMapEntry = new PerfMapDebugDirectoryEntryNode(pdbNameRoot + ".ni.r2rmap"); + _perfMapEntry = new PerfMapDebugDirectoryEntryNode(pdbNameRoot + ".ni.r2rmap", perfMapFormatVersion); } } - public override ObjectNodeSection GetSection(NodeFactory factory) => ObjectNodeSection.TextSection; + public override ObjectNodeSection GetSection(NodeFactory factory) + { + return ObjectNodeSection.DebugDirectorySection; + } public override bool IsShareable => false; @@ -69,6 +72,10 @@ public DebugDirectoryNode(EcmaModule sourceModule, string outputFileName, bool s + (_perfMapEntry is not null ? 1 : 0) + (_insertDeterministicEntry ? 1 : 0)) * ImageDebugDirectorySize; + public NativeDebugDirectoryEntryNode PdbEntry => _nativeEntry; + + public PerfMapDebugDirectoryEntryNode PerfMapEntry => _perfMapEntry; + public void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb) { sb.Append(nameMangler.CompilationUnitPrefix); diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/DelayLoadMethodCallThunkNodeRange.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/DelayLoadMethodCallThunkNodeRange.cs index e450f816b666d8..dc2108d714d25d 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/DelayLoadMethodCallThunkNodeRange.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/DelayLoadMethodCallThunkNodeRange.cs @@ -14,22 +14,47 @@ namespace ILCompiler.DependencyAnalysis.ReadyToRun /// They are emitted in a contiguous run of object nodes. This symbol is used in the object writer to represent the range /// of bytes containing all the thunks. /// - public class DelayLoadMethodCallThunkNodeRange : DependencyNodeCore, ISymbolDefinitionNode + public class DelayLoadMethodCallThunkNodeRange : DependencyNodeCore, ISymbolRangeNode { + private const string NodeName = "DelayLoadMethodCallThunkNodeRange"; + private ImportThunk _startNode; + private ImportThunk _endNode; + public override bool InterestingForDynamicDependencyAnalysis => false; public override bool HasDynamicDependencies => false; public override bool HasConditionalStaticDependencies => false; public override bool StaticDependenciesAreComputed => true; public int Offset => 0; public bool RepresentsIndirectionCell => false; - public override IEnumerable GetConditionalStaticDependencies(NodeFactory context) => null; - public override IEnumerable GetStaticDependencies(NodeFactory context) => null; - public override IEnumerable SearchDynamicDependencies(List> markedNodes, int firstNode, NodeFactory context) => null; - protected override string GetName(NodeFactory context) => "DelayLoadMethodCallThunkNodeRange"; + public override IEnumerable GetConditionalStaticDependencies(NodeFactory context) => []; + public override IEnumerable GetStaticDependencies(NodeFactory context) => []; + public override IEnumerable SearchDynamicDependencies(List> markedNodes, int firstNode, NodeFactory context) => []; + protected override string GetName(NodeFactory context) => NodeName; public void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb) { - sb.Append(GetName(null)); + sb.Append($"__{NodeName}"); + } + + public void OnNodeMarked(DependencyNodeCore node) + { + if (node is ImportThunk thunk) + { + if (_startNode is null + || CompilerComparer.Instance.Compare(thunk, _startNode) <= 0) + { + _startNode = thunk; + } + + if (_endNode is null + || CompilerComparer.Instance.Compare(thunk, _endNode) > 0) + { + _endNode = thunk; + } + } } + + public ISymbolNode StartNode(NodeFactory factory) => _startNode; + public ISymbolNode EndNode(NodeFactory factory) => _endNode; } } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/DelegateCtorSignature.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/DelegateCtorSignature.cs index 8771ce9809056d..4fce8fa99bb579 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/DelegateCtorSignature.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/DelegateCtorSignature.cs @@ -35,7 +35,7 @@ public DelegateCtorSignature( public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) { - ObjectDataSignatureBuilder builder = new ObjectDataSignatureBuilder(); + ObjectDataSignatureBuilder builder = new ObjectDataSignatureBuilder(factory, relocsOnly); builder.AddSymbol(this); if (!relocsOnly) diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/FieldFixupSignature.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/FieldFixupSignature.cs index 93c5569c5b01ea..654267876d9006 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/FieldFixupSignature.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/FieldFixupSignature.cs @@ -32,7 +32,7 @@ public FieldFixupSignature(ReadyToRunFixupKind fixupKind, FieldWithToken fieldWi public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) { - ObjectDataSignatureBuilder dataBuilder = new ObjectDataSignatureBuilder(); + ObjectDataSignatureBuilder dataBuilder = new ObjectDataSignatureBuilder(factory, relocsOnly); if (!relocsOnly) { diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/GenericLookupSignature.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/GenericLookupSignature.cs index a50daa232ff56f..e97d8cd835b2f2 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/GenericLookupSignature.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/GenericLookupSignature.cs @@ -93,7 +93,7 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) throw new NotImplementedException(); } - ObjectDataSignatureBuilder dataBuilder = new ObjectDataSignatureBuilder(); + ObjectDataSignatureBuilder dataBuilder = new ObjectDataSignatureBuilder(factory, relocsOnly); dataBuilder.AddSymbol(this); SignatureContext innerContext = dataBuilder.EmitFixup(factory, fixupToEmit, targetModule, factory.SignatureContext); diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/HeaderNode.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/HeaderNode.cs index f010b4fd0934f0..702e69f6192c42 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/HeaderNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/HeaderNode.cs @@ -178,13 +178,6 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) if (!relocsOnly && item.Node is ObjectNode on && on.ShouldSkipEmittingObjectNode(factory)) continue; - // Unmarked nodes are not part of the graph - if (!item.Node.Marked && !(item.Node is ObjectNode)) - { - Debug.Assert(item.Node is DelayLoadMethodCallThunkNodeRange); - continue; - } - builder.EmitInt((int)item.Id); builder.EmitReloc(item.StartSymbol, RelocType.IMAGE_REL_BASED_ADDR32NB); diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ILBodyFixupSignature.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ILBodyFixupSignature.cs index 3bca9f037e3daf..45b39e8de77d72 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ILBodyFixupSignature.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ILBodyFixupSignature.cs @@ -52,7 +52,7 @@ private ModuleToken GetModuleToken(NodeFactory factory) public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) { - ObjectDataSignatureBuilder dataBuilder = new ObjectDataSignatureBuilder(); + ObjectDataSignatureBuilder dataBuilder = new ObjectDataSignatureBuilder(factory, relocsOnly); if (!relocsOnly) { diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ImportThunk.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ImportThunk.cs index 25a381cf0b80c9..27390868bed540 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ImportThunk.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ImportThunk.cs @@ -28,6 +28,8 @@ enum Kind private readonly ImportSectionNode _containingImportSection; + private readonly int _symbolOffset = 0; + /// /// Import thunks are used to call a runtime-provided helper which fixes up an indirection cell in a particular /// import section. Optionally they may also contain a relocation for a specific indirection cell to fix up. @@ -60,8 +62,22 @@ public ImportThunk(NodeFactory factory, ReadyToRunHelper helperId, ImportSection { _thunkKind = Kind.Eager; } + + if (_thunkKind != Kind.Eager + && factory.Target.Architecture is Internal.TypeSystem.TargetArchitecture.ARM64 + or Internal.TypeSystem.TargetArchitecture.LoongArch64 + or Internal.TypeSystem.TargetArchitecture.RiscV64) + { + // We stuff the reloc to the module import pointer before the start of the thunk + // to ensure alignment. + // The thunk itself starts immediately after the reloc. + // We don't need this for an Eager thunk. + _symbolOffset = 8; + } } + int ISymbolNode.Offset => base.Offset + _symbolOffset; + public override void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb) { sb.Append("DelayLoadHelper->"u8); diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ManifestAssemblyMvidHeaderNode.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ManifestAssemblyMvidHeaderNode.cs index cfed82c3126870..20240ee78f3ee2 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ManifestAssemblyMvidHeaderNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ManifestAssemblyMvidHeaderNode.cs @@ -56,7 +56,7 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) } byte[] manifestAssemblyMvidTable = _manifestNode.GetManifestAssemblyMvidTableData(); - return new ObjectData(manifestAssemblyMvidTable, Array.Empty(), alignment: 0, new ISymbolDefinitionNode[] { this }); + return new ObjectData(manifestAssemblyMvidTable, Array.Empty(), alignment: 1, new ISymbolDefinitionNode[] { this }); } } } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/MethodColdCodeNode.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/MethodColdCodeNode.cs index 5e8bffc5e6e211..d9d593d176a363 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/MethodColdCodeNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/MethodColdCodeNode.cs @@ -18,11 +18,13 @@ public MethodColdCodeNode(MethodDesc owningMethod) _owningMethod = owningMethod; } + protected internal override int Phase => (int)ObjectNodePhase.Late; + public int Offset => 0; public override ObjectNodeSection GetSection(NodeFactory factory) { - return factory.Target.IsWindows ? ObjectNodeSection.ManagedCodeWindowsContentSection : ObjectNodeSection.ManagedCodeUnixContentSection; + return ObjectNodeSection.TextSection; } public override bool IsShareable => false; diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/MethodFixupSignature.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/MethodFixupSignature.cs index e6a761d9dd7e17..c3f3324e0c6ba4 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/MethodFixupSignature.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/MethodFixupSignature.cs @@ -83,7 +83,7 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) return new ObjectData(data: Array.Empty(), relocs: null, alignment: 0, definedSymbols: null); } - ObjectDataSignatureBuilder dataBuilder = new ObjectDataSignatureBuilder(); + ObjectDataSignatureBuilder dataBuilder = new ObjectDataSignatureBuilder(factory, relocsOnly); dataBuilder.AddSymbol(this); // Optimize some of the fixups into a more compact form diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/MethodWithGCInfo.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/MethodWithGCInfo.cs index 2d13944555485c..4b5f00c661c678 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/MethodWithGCInfo.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/MethodWithGCInfo.cs @@ -302,7 +302,7 @@ protected override string GetName(NodeFactory factory) public override ObjectNodeSection GetSection(NodeFactory factory) { - return factory.Target.IsWindows ? ObjectNodeSection.ManagedCodeWindowsContentSection : ObjectNodeSection.ManagedCodeUnixContentSection; + return ObjectNodeSection.TextSection; } public FrameInfo[] FrameInfos => _frameInfos; diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/NewArrayFixupSignature.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/NewArrayFixupSignature.cs index d6bbd506a9bbd3..1846f5f0a6259d 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/NewArrayFixupSignature.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/NewArrayFixupSignature.cs @@ -24,7 +24,7 @@ public NewArrayFixupSignature(ArrayType arrayType) public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) { - ObjectDataSignatureBuilder dataBuilder = new ObjectDataSignatureBuilder(); + ObjectDataSignatureBuilder dataBuilder = new ObjectDataSignatureBuilder(factory, relocsOnly); if (!relocsOnly) { diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/NewObjectFixupSignature.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/NewObjectFixupSignature.cs index 7ca4bd3d8b552f..da94117cb46234 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/NewObjectFixupSignature.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/NewObjectFixupSignature.cs @@ -24,7 +24,7 @@ public NewObjectFixupSignature(TypeDesc typeDesc) public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) { - ObjectDataSignatureBuilder dataBuilder = new ObjectDataSignatureBuilder(); + ObjectDataSignatureBuilder dataBuilder = new ObjectDataSignatureBuilder(factory, relocsOnly); if (!relocsOnly) { diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ReadyToRunHelperSignature.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ReadyToRunHelperSignature.cs index e42a112ee2015d..fa4b6a7e217f2d 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ReadyToRunHelperSignature.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ReadyToRunHelperSignature.cs @@ -23,7 +23,7 @@ public ReadyToRunHelperSignature(ReadyToRunHelper helper) public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) { - ObjectDataSignatureBuilder builder = new ObjectDataSignatureBuilder(); + ObjectDataSignatureBuilder builder = new ObjectDataSignatureBuilder(factory, relocsOnly); builder.AddSymbol(this); builder.EmitByte((byte)ReadyToRunFixupKind.Helper); builder.EmitUInt((uint)_helperID); diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ReadyToRunInstructionSetSupportSignature.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ReadyToRunInstructionSetSupportSignature.cs index 44b0000d91df3e..633ac9585b34d7 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ReadyToRunInstructionSetSupportSignature.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ReadyToRunInstructionSetSupportSignature.cs @@ -74,7 +74,7 @@ private ReadyToRunInstructionSet InstructionSetFromString(string instructionSetS public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) { - ObjectDataSignatureBuilder builder = new ObjectDataSignatureBuilder(); + ObjectDataSignatureBuilder builder = new ObjectDataSignatureBuilder(factory, relocsOnly); builder.AddSymbol(this); string[] supportedAndUnsupportedSplit = _instructionSetsSupport.Split(','); diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/RuntimeFunctionsGCInfoNode.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/RuntimeFunctionsGCInfoNode.cs index d65a01c827a303..bf2aa3bd0622e7 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/RuntimeFunctionsGCInfoNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/RuntimeFunctionsGCInfoNode.cs @@ -16,7 +16,10 @@ public RuntimeFunctionsGCInfoNode() public override int ClassCode => 316678892; - public override ObjectNodeSection GetSection(NodeFactory factory) => ObjectNodeSection.ReadOnlyDataSection; + public override ObjectNodeSection GetSection(NodeFactory factory) + { + return ObjectNodeSection.XDataSection; + } public override bool StaticDependenciesAreComputed => true; diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/RuntimeFunctionsTableNode.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/RuntimeFunctionsTableNode.cs index 7c2b07e338f94c..66aaa88514b476 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/RuntimeFunctionsTableNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/RuntimeFunctionsTableNode.cs @@ -23,6 +23,11 @@ public RuntimeFunctionsTableNode(NodeFactory nodeFactory) _nodeFactory = nodeFactory; } + public override ObjectNodeSection GetSection(NodeFactory factory) + { + return ObjectNodeSection.PDataSection; + } + public override void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb) { sb.Append(nameMangler.CompilationUnitPrefix); diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/SignatureBuilder.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/SignatureBuilder.cs index 1fb549a6f1afa0..2c4ca7cd004702 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/SignatureBuilder.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/SignatureBuilder.cs @@ -567,9 +567,9 @@ public class ObjectDataSignatureBuilder : SignatureBuilder { private ObjectDataBuilder _builder; - public ObjectDataSignatureBuilder() + public ObjectDataSignatureBuilder(NodeFactory factory, bool relocsOnly) { - _builder = new ObjectDataBuilder(); + _builder = new ObjectDataBuilder(factory, relocsOnly); } public void AddSymbol(ISymbolDefinitionNode symbol) diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/StringImportSignature.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/StringImportSignature.cs index 76e1e864d74d9b..91f212eb08271b 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/StringImportSignature.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/StringImportSignature.cs @@ -19,7 +19,7 @@ public StringImportSignature(ModuleToken token) public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) { - ObjectDataSignatureBuilder dataBuilder = new ObjectDataSignatureBuilder(); + ObjectDataSignatureBuilder dataBuilder = new ObjectDataSignatureBuilder(factory, relocsOnly); if (!relocsOnly) { diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/Target_ARM64/ImportThunk.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/Target_ARM64/ImportThunk.cs index ba619129a989bb..2e3827d7d0b765 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/Target_ARM64/ImportThunk.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/Target_ARM64/ImportThunk.cs @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; - +using System.Diagnostics; using ILCompiler.DependencyAnalysis.ARM64; namespace ILCompiler.DependencyAnalysis.ReadyToRun @@ -11,41 +11,57 @@ namespace ILCompiler.DependencyAnalysis.ReadyToRun /// This node emits a thunk calling DelayLoad_Helper with a given instance signature /// to populate its indirection cell. /// - public partial class ImportThunk + public partial class ImportThunk : ISymbolNode { protected override void EmitCode(NodeFactory factory, ref ARM64Emitter instructionEncoder, bool relocsOnly) { + if (_thunkKind == Kind.Eager) + { + instructionEncoder.EmitJMP(_helperCell); + return; + } - switch (_thunkKind) + instructionEncoder.Builder.RequireInitialPointerAlignment(); + Debug.Assert(instructionEncoder.Builder.CountBytes == 0); + + instructionEncoder.Builder.EmitReloc(factory.ModuleImport, RelocType.IMAGE_REL_BASED_DIR64); + + Debug.Assert(instructionEncoder.Builder.CountBytes == ((ISymbolNode)this).Offset); + + if (relocsOnly) { - case Kind.Eager: - break; + // When doing relocs only, we don't need to generate the actual instructions + // as they will be ignored. Just emit the jump so we record the dependency. + instructionEncoder.EmitJMP(_helperCell); + return; + } + switch (_thunkKind) + { case Kind.DelayLoadHelper: case Kind.DelayLoadHelperWithExistingIndirectionCell: case Kind.VirtualStubDispatch: + // x11 contains indirection cell // Do nothing x11 contains our first param - if (!relocsOnly) - { - // movz x9, #index - int index = _containingImportSection.IndexFromBeginningOfArray; - instructionEncoder.EmitMOV(Register.X9, checked((ushort)index)); - } + // movz x9, #index + int index = _containingImportSection.IndexFromBeginningOfArray; + instructionEncoder.EmitMOV(Register.X9, checked((ushort)index)); // Move Module* -> x10 - // ldr x10, [PC+0x1c] - instructionEncoder.EmitLDR(Register.X10, 0x1c); + // ldr x10, [PC-0xc] + instructionEncoder.EmitLDR(Register.X10, -0xc); // ldr x10, [x10] instructionEncoder.EmitLDR(Register.X10, Register.X10); break; case Kind.Lazy: + // Move Module* -> x1 - // ldr x1, [PC+0x1c] - instructionEncoder.EmitLDR(Register.X1, 0x1c); + // ldr x1, [PC-0x8] + instructionEncoder.EmitLDR(Register.X1, -0x8); // ldr x1, [x1] instructionEncoder.EmitLDR(Register.X1, Register.X1); @@ -57,10 +73,6 @@ protected override void EmitCode(NodeFactory factory, ref ARM64Emitter instructi // branch to helper instructionEncoder.EmitJMP(_helperCell); - - // Emit relocation for the Module* load above - if (_thunkKind != Kind.Eager) - instructionEncoder.Builder.EmitReloc(factory.ModuleImport, RelocType.IMAGE_REL_BASED_DIR64); } } } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/Target_LoongArch64/ImportThunk.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/Target_LoongArch64/ImportThunk.cs index 4a3d47000a1bba..0efca97f939375 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/Target_LoongArch64/ImportThunk.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/Target_LoongArch64/ImportThunk.cs @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; - +using System.Diagnostics; using ILCompiler.DependencyAnalysis.LoongArch64; namespace ILCompiler.DependencyAnalysis.ReadyToRun @@ -15,12 +15,30 @@ public partial class ImportThunk { protected override void EmitCode(NodeFactory factory, ref LoongArch64Emitter instructionEncoder, bool relocsOnly) { + if (_thunkKind == Kind.Eager) + { + // branch to helper + instructionEncoder.EmitJMP(_helperCell); + return; + } - switch (_thunkKind) + instructionEncoder.Builder.RequireInitialPointerAlignment(); + Debug.Assert(instructionEncoder.Builder.CountBytes == 0); + + instructionEncoder.Builder.EmitReloc(factory.ModuleImport, RelocType.IMAGE_REL_BASED_DIR64); + + Debug.Assert(instructionEncoder.Builder.CountBytes == ((ISymbolNode)this).Offset); + + if (relocsOnly) { - case Kind.Eager: - break; + // When doing relocs only, we don't need to generate the actual instructions + // as they will be ignored. Just emit the jump so we record the dependency. + instructionEncoder.EmitJMP(_helperCell); + return; + } + switch (_thunkKind) + { case Kind.DelayLoadHelper: case Kind.VirtualStubDispatch: case Kind.DelayLoadHelperWithExistingIndirectionCell: @@ -28,18 +46,16 @@ protected override void EmitCode(NodeFactory factory, ref LoongArch64Emitter ins // T8 contains indirection cell // Do nothing T8=R20 contains our first param - if (!relocsOnly) - { - // ori T0=R12, R0, #index - int index = _containingImportSection.IndexFromBeginningOfArray; - instructionEncoder.EmitMOV(Register.R12, checked((ushort)index)); - } + // ori T0=R12, R0, #index + int index = _containingImportSection.IndexFromBeginningOfArray; + instructionEncoder.EmitMOV(Register.R12, checked((ushort)index)); + + int offset = -instructionEncoder.Builder.CountBytes; // get pc // pcaddi T1=R13, 0 instructionEncoder.EmitPCADDI(Register.R13); - int offset = _helperCell.RepresentsIndirectionCell ? 0x24 : 0x14; // load Module* -> T1 instructionEncoder.EmitLD(Register.R13, Register.R13, offset); @@ -50,11 +66,11 @@ protected override void EmitCode(NodeFactory factory, ref LoongArch64Emitter ins case Kind.Lazy: { + int offset = -instructionEncoder.Builder.CountBytes; // get pc // pcaddi R5, 0 instructionEncoder.EmitPCADDI(Register.R5); - int offset = _helperCell.RepresentsIndirectionCell ? 0x24 : 0x14; // load Module* -> R5=A1 instructionEncoder.EmitLD(Register.R5, Register.R5, offset); @@ -69,10 +85,6 @@ protected override void EmitCode(NodeFactory factory, ref LoongArch64Emitter ins // branch to helper instructionEncoder.EmitJMP(_helperCell); - - // Emit relocation for the Module* load above - if (_thunkKind != Kind.Eager) - instructionEncoder.Builder.EmitReloc(factory.ModuleImport, RelocType.IMAGE_REL_BASED_DIR64); } } } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/Target_RiscV64/ImportThunk.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/Target_RiscV64/ImportThunk.cs index 9db82c334f1d6c..d4342dd4b3f42a 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/Target_RiscV64/ImportThunk.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/Target_RiscV64/ImportThunk.cs @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; - +using System.Diagnostics; using ILCompiler.DependencyAnalysis.RiscV64; namespace ILCompiler.DependencyAnalysis.ReadyToRun @@ -15,6 +15,26 @@ public partial class ImportThunk { protected override void EmitCode(NodeFactory factory, ref RiscV64Emitter instructionEncoder, bool relocsOnly) { + if (_thunkKind == Kind.Eager) + { + instructionEncoder.EmitJMP(_helperCell); + return; + } + + instructionEncoder.Builder.RequireInitialPointerAlignment(); + Debug.Assert(instructionEncoder.Builder.CountBytes == 0); + + instructionEncoder.Builder.EmitReloc(factory.ModuleImport, RelocType.IMAGE_REL_BASED_DIR64); + + Debug.Assert(instructionEncoder.Builder.CountBytes == ((ISymbolNode)this).Offset); + + if (relocsOnly) + { + // When doing relocs only, we don't need to generate the actual instructions + // as they will be ignored. Just emit the jump so we record the dependency. + instructionEncoder.EmitJMP(_helperCell); + return; + } switch (_thunkKind) { @@ -23,46 +43,47 @@ protected override void EmitCode(NodeFactory factory, ref RiscV64Emitter instruc case Kind.DelayLoadHelper: case Kind.VirtualStubDispatch: + { // t5 contains indirection cell // Do nothing t5 contains our first param - if (!relocsOnly) - { - // li t0, #index - int index = _containingImportSection.IndexFromBeginningOfArray; - instructionEncoder.EmitLI(Register.X5, index); - } + + // li t0, #index + int index = _containingImportSection.IndexFromBeginningOfArray; + instructionEncoder.EmitLI(Register.X5, index); + + int offset = -instructionEncoder.Builder.CountBytes; + // get pc // auipc t1, 0 instructionEncoder.EmitPC(Register.X6); // load Module* -> t1 - instructionEncoder.EmitLD(Register.X6, Register.X6, 0x24); + instructionEncoder.EmitLD(Register.X6, Register.X6, offset); // ld t1, t1, 0 instructionEncoder.EmitLD(Register.X6, Register.X6, 0); break; - + } case Kind.Lazy: + { + int offset = -instructionEncoder.Builder.CountBytes; + // get pc instructionEncoder.EmitPC(Register.X11); // load Module* -> a1 - instructionEncoder.EmitLD(Register.X11, Register.X11, 0x24); + instructionEncoder.EmitLD(Register.X11, Register.X11, offset); // ld a1, a1, 0 instructionEncoder.EmitLD(Register.X11, Register.X11, 0); break; - + } default: throw new NotImplementedException(); } // branch to helper instructionEncoder.EmitJMP(_helperCell); - - // Emit relocation for the Module* load above - if (_thunkKind != Kind.Eager) - instructionEncoder.Builder.EmitReloc(factory.ModuleImport, RelocType.IMAGE_REL_BASED_DIR64); } } } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TypeFixupSignature.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TypeFixupSignature.cs index afd5b384d912d4..4a45d024afcf89 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TypeFixupSignature.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TypeFixupSignature.cs @@ -32,7 +32,7 @@ public TypeFixupSignature(ReadyToRunFixupKind fixupKind, TypeDesc typeDesc) public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) { - ObjectDataSignatureBuilder dataBuilder = new ObjectDataSignatureBuilder(); + ObjectDataSignatureBuilder dataBuilder = new ObjectDataSignatureBuilder(factory, relocsOnly); if (!relocsOnly) { diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/VirtualResolutionFixupSignature.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/VirtualResolutionFixupSignature.cs index e3f1cef834f64f..4d946fce7a2e51 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/VirtualResolutionFixupSignature.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/VirtualResolutionFixupSignature.cs @@ -41,7 +41,7 @@ public VirtualResolutionFixupSignature(ReadyToRunFixupKind fixupKind, MethodWith public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) { - ObjectDataSignatureBuilder dataBuilder = new ObjectDataSignatureBuilder(); + ObjectDataSignatureBuilder dataBuilder = new ObjectDataSignatureBuilder(factory, relocsOnly); if (!relocsOnly) { diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/Win32ResourcesNode.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/Win32ResourcesNode.cs index b062478a786035..343a860dddd9f4 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/Win32ResourcesNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/Win32ResourcesNode.cs @@ -19,7 +19,10 @@ public Win32ResourcesNode(ResourceData resourceData) _size = -1; } - public override ObjectNodeSection GetSection(NodeFactory factory) => ObjectNodeSection.TextSection; + public override ObjectNodeSection GetSection(NodeFactory factory) + { + return ObjectNodeSection.Win32ResourcesSection; + } public override bool IsShareable => false; @@ -44,6 +47,7 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) private ObjectData GetDataInternal() { ObjectDataBuilder builder = new ObjectDataBuilder(); + builder.RequireInitialAlignment(1); builder.AddSymbol(this); _resourceData.WriteResources(this, ref builder); _size = builder.CountBytes; diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs index e644439b2bec67..6f55a9e35a14e7 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs @@ -52,13 +52,14 @@ public enum TypeValidationRule SkipTypeValidation } - public sealed class NodeFactoryOptimizationFlags + public struct NodeFactoryOptimizationFlags { public bool OptimizeAsyncMethods; public TypeValidationRule TypeValidation; public int DeterminismStress; public bool PrintReproArgs; public bool EnableCachedInterfaceDispatchSupport; + public bool IsComponentModule; } // To make the code future compatible to the composite R2R story @@ -699,6 +700,8 @@ public void AttachToDependencyGraph(DependencyAnalyzerBase graph, I graph.AddRoot(RuntimeFunctionsGCInfo, "GC info is always generated"); DelayLoadMethodCallThunks = new DelayLoadMethodCallThunkNodeRange(); + graph.AddRoot(DelayLoadMethodCallThunks, "DelayLoadMethodCallThunks header entry is always generated"); + graph.NewMarkedNode += DelayLoadMethodCallThunks.OnNodeMarked; Header.Add(Internal.Runtime.ReadyToRunSectionType.DelayLoadMethodCallThunks, DelayLoadMethodCallThunks, DelayLoadMethodCallThunks); ExceptionInfoLookupTableNode exceptionInfoLookupTableNode = new ExceptionInfoLookupTableNode(this); @@ -1056,5 +1059,15 @@ public void DetectGenericCycles(TypeSystemEntity caller, TypeSystemEntity callee { _genericCycleDetector?.DetectCycle(caller, callee); } + + public string GetSymbolAlternateName(ISymbolNode node, out bool isHidden) + { + isHidden = false; + if (node == Header) + { + return "RTR_HEADER"; + } + return null; + } } } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.cs index ab820af948a71c..e7e4039f95071f 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.cs @@ -305,6 +305,8 @@ public sealed class ReadyToRunCodegenCompilation : Compilation public ReadyToRunSymbolNodeFactory SymbolNodeFactory { get; } public ReadyToRunCompilationModuleGroupBase CompilationModuleGroup { get; } private readonly int _customPESectionAlignment; + private readonly ReadyToRunContainerFormat _format; + /// /// Determining whether a type's layout is fixed is a little expensive and the question can be asked many times /// for the same type during compilation so preserve the computed value. @@ -337,7 +339,8 @@ internal ReadyToRunCodegenCompilation( MethodLayoutAlgorithm methodLayoutAlgorithm, FileLayoutAlgorithm fileLayoutAlgorithm, int customPESectionAlignment, - bool verifyTypeAndFieldLayout) + bool verifyTypeAndFieldLayout, + ReadyToRunContainerFormat format) : base( dependencyGraph, nodeFactory, @@ -361,6 +364,7 @@ internal ReadyToRunCodegenCompilation( _perfMapFormatVersion = perfMapFormatVersion; _generateProfileFile = generateProfileFile; _customPESectionAlignment = customPESectionAlignment; + _format = format; SymbolNodeFactory = new ReadyToRunSymbolNodeFactory(nodeFactory, verifyTypeAndFieldLayout); if (nodeFactory.InstrumentationDataTable != null) nodeFactory.InstrumentationDataTable.Initialize(SymbolNodeFactory); @@ -414,7 +418,9 @@ public override void Compile(string outputFile) perfMapFormatVersion: _perfMapFormatVersion, generateProfileFile: _generateProfileFile, callChainProfile: _profileData.CallChainProfile, - _customPESectionAlignment); + _format, + _customPESectionAlignment, + _logger); CompilationModuleGroup moduleGroup = _nodeFactory.CompilationModuleGroup; if (moduleGroup.IsCompositeBuildMode) @@ -459,12 +465,14 @@ private void RewriteComponentFile(string inputFile, string outputFile, string ow flags |= ReadyToRunFlags.READYTORUN_FLAG_SkipTypeValidation; } + NodeFactoryOptimizationFlags optimizationFlags = _nodeFactory.OptimizationFlags with { IsComponentModule = true }; + flags |= _nodeFactory.CompilationModuleGroup.GetReadyToRunFlags() & ReadyToRunFlags.READYTORUN_FLAG_MultiModuleVersionBubble; CopiedCorHeaderNode copiedCorHeader = new CopiedCorHeaderNode(inputModule); // Re-written components shouldn't have any additional diagnostic information - only information about the forwards. // Even with all of this, we might be modifying the image in a silly manner - adding a directory when if didn't have one. - DebugDirectoryNode debugDirectory = new DebugDirectoryNode(inputModule, outputFile, shouldAddNiPdb: false, shouldGeneratePerfmap: false); + DebugDirectoryNode debugDirectory = new DebugDirectoryNode(inputModule, outputFile, shouldAddNiPdb: false, shouldGeneratePerfmap: false, perfMapFormatVersion: 0); NodeFactory componentFactory = new NodeFactory( _nodeFactory.TypeSystemContext, _nodeFactory.CompilationModuleGroup, @@ -474,7 +482,7 @@ private void RewriteComponentFile(string inputFile, string outputFile, string ow debugDirectory, win32Resources: new Win32Resources.ResourceData(inputModule), flags, - _nodeFactory.OptimizationFlags, + optimizationFlags, _nodeFactory.ImageBase, automaticTypeValidation ? inputModule : null, genericCycleDepthCutoff: -1, // We don't need generic cycle detection when rewriting component assemblies @@ -509,7 +517,9 @@ private void RewriteComponentFile(string inputFile, string outputFile, string ow perfMapFormatVersion: _perfMapFormatVersion, generateProfileFile: false, _profileData.CallChainProfile, - customPESectionAlignment: 0); + ReadyToRunContainerFormat.PE, + customPESectionAlignment: 0, + _logger); } public override void WriteDependencyLog(string outputFileName) diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilationBuilder.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilationBuilder.cs index 3d937f94df1994..17dede9e9adebe 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilationBuilder.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilationBuilder.cs @@ -45,6 +45,7 @@ public sealed class ReadyToRunCodegenCompilationBuilder : CompilationBuilder private NodeFactoryOptimizationFlags _nodeFactoryOptimizationFlags = new NodeFactoryOptimizationFlags(); private int _genericCycleDetectionDepthCutoff = -1; private int _genericCycleDetectionBreadthCutoff = -1; + private ReadyToRunContainerFormat _format = ReadyToRunContainerFormat.PE; private string _jitPath; private string _outputFile; @@ -220,6 +221,12 @@ public ReadyToRunCodegenCompilationBuilder UseGenericCycleDetection(int depthCut return this; } + public ReadyToRunCodegenCompilationBuilder UseContainerFormat(ReadyToRunContainerFormat format) + { + _format = format; + return this; + } + public override ICompilation ToCompilation() { // TODO: only copy COR headers for single-assembly build and for composite build with embedded MSIL @@ -227,7 +234,7 @@ public override ICompilation ToCompilation() EcmaModule singleModule = _compilationGroup.IsCompositeBuildMode ? null : inputModules.First(); CopiedCorHeaderNode corHeaderNode = new CopiedCorHeaderNode(singleModule); // TODO: proper support for multiple input files - DebugDirectoryNode debugDirectoryNode = new DebugDirectoryNode(singleModule, _outputFile, _generatePdbFile, _generatePerfMapFile); + DebugDirectoryNode debugDirectoryNode = new DebugDirectoryNode(singleModule, _outputFile, _generatePdbFile, _generatePerfMapFile, _perfMapFormatVersion); // Produce a ResourceData where the IBC PROFILE_DATA entry has been filtered out // TODO: proper support for multiple input files @@ -344,7 +351,8 @@ public override ICompilation ToCompilation() _r2rMethodLayoutAlgorithm, _r2rFileLayoutAlgorithm, _customPESectionAlignment, - _verifyTypeAndFieldLayout); + _verifyTypeAndFieldLayout, + _format); } } } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCompilationModuleGroupBase.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCompilationModuleGroupBase.cs index a2ea17ccc111b8..5e86bc9a6eed1d 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCompilationModuleGroupBase.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCompilationModuleGroupBase.cs @@ -931,5 +931,7 @@ public virtual void ApplyProfileGuidedOptimizationData(ProfileDataManager profil { _profileData = profileGuidedCompileRestriction; } + + public bool IsSingleFileCompilation => true; } } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj index 3251ff2660ebe3..bf9887611b02fc 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj @@ -75,6 +75,7 @@ + @@ -137,6 +138,24 @@ + + + + + + + + + + + + + + + + + + @@ -149,6 +168,7 @@ + @@ -274,10 +294,8 @@ - - @@ -285,9 +303,6 @@ - - - @@ -338,4 +353,8 @@ JitInterface\UnboxingMethodDesc.cs + + + + diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/MapFileBuilder.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/MapFileBuilder.cs index 726ae5fe079e6d..9d49bb86680145 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/MapFileBuilder.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/MapFileBuilder.cs @@ -18,6 +18,7 @@ using ILCompiler.DependencyAnalysis; using ILCompiler.DependencyAnalysis.ReadyToRun; using ILCompiler.Diagnostics; +using ILCompiler.ObjectWriter; namespace ILCompiler.PEWriter { @@ -197,12 +198,12 @@ private void WriteSections(StreamWriter writer) WriteTitle(writer, "INDEX | FILEOFFSET | RVA | END_RVA | LENGTH | NAME"); for (int sectionIndex = 0; sectionIndex < _outputInfoBuilder.Sections.Count; sectionIndex++) { - Section section = _outputInfoBuilder.Sections[sectionIndex]; + OutputSection section = _outputInfoBuilder.Sections[sectionIndex]; writer.Write($"{sectionIndex,5} | "); - writer.Write($"0x{section.FilePosWhenPlaced:X8} | "); - writer.Write($"0x{section.RVAWhenPlaced:X8} | "); - writer.Write($"0x{(section.RVAWhenPlaced + section.Content.Count):X8} | "); - writer.Write($"0x{section.Content.Count:X8} | "); + writer.Write($"0x{section.FilePosition:X8} | "); + writer.Write($"0x{section.VirtualAddress:X8} | "); + writer.Write($"0x{(section.VirtualAddress + section.Length):X8} | "); + writer.Write($"0x{section.Length:X8} | "); writer.WriteLine(section.Name); } } @@ -223,8 +224,8 @@ private void WriteMap(StreamWriter writer) { // No more nodes or next symbol is below next node - emit symbol OutputSymbol symbol = _outputInfoBuilder.Symbols[symbolIndex++]; - Section section = _outputInfoBuilder.Sections[symbol.SectionIndex]; - writer.Write($"0x{symbol.Offset + section.RVAWhenPlaced:X8} | "); + OutputSection section = _outputInfoBuilder.Sections[symbol.SectionIndex]; + writer.Write($"0x{symbol.Offset + section.VirtualAddress:X8} | "); writer.Write(" | "); writer.Write(" | "); writer.Write($"{GetNameHead(section),-SectionNameHeadLength} | "); @@ -234,9 +235,9 @@ private void WriteMap(StreamWriter writer) { // Emit node and optionally symbol OutputNode node = _outputInfoBuilder.Nodes[nodeIndex++]; - Section section = _outputInfoBuilder.Sections[node.SectionIndex]; + OutputSection section = _outputInfoBuilder.Sections[node.SectionIndex]; - writer.Write($"0x{node.Offset + section.RVAWhenPlaced:X8} | "); + writer.Write($"0x{node.Offset + section.VirtualAddress:X8} | "); writer.Write($"0x{node.Length:X6} | "); writer.Write($"{node.Relocations,6} | "); writer.Write($"{GetNameHead(section),-SectionNameHeadLength} | "); @@ -265,8 +266,8 @@ private void WriteMapCsv(StreamWriter writer) { // No more nodes or next symbol is below next node - emit symbol OutputSymbol symbol = _outputInfoBuilder.Symbols[symbolIndex++]; - Section section = _outputInfoBuilder.Sections[symbol.SectionIndex]; - writer.Write($"0x{symbol.Offset + section.RVAWhenPlaced:X8},"); + OutputSection section = _outputInfoBuilder.Sections[symbol.SectionIndex]; + writer.Write($"0x{symbol.Offset + section.VirtualAddress:X8},"); writer.Write(","); writer.Write(","); writer.Write($"{section.Name},"); @@ -277,9 +278,9 @@ private void WriteMapCsv(StreamWriter writer) { // Emit node and optionally symbol OutputNode node = _outputInfoBuilder.Nodes[nodeIndex++]; - Section section = _outputInfoBuilder.Sections[node.SectionIndex]; + OutputSection section = _outputInfoBuilder.Sections[node.SectionIndex]; - writer.Write($"0x{node.Offset + section.RVAWhenPlaced:X8},"); + writer.Write($"0x{node.Offset + section.VirtualAddress:X8},"); writer.Write($"{node.Length},"); writer.Write($"{node.Relocations},"); writer.Write($"{section.Name},"); @@ -294,7 +295,7 @@ private void WriteMapCsv(StreamWriter writer) } } - private static string GetNameHead(Section section) + private static string GetNameHead(OutputSection section) { string sectionNameHead = section.Name; if (sectionNameHead.Length > SectionNameHeadLength) diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/ProfileFileBuilder.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/ProfileFileBuilder.cs index c35aac16c0c785..ce1194efcbae4e 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/ProfileFileBuilder.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/ProfileFileBuilder.cs @@ -12,6 +12,7 @@ using ILCompiler.DependencyAnalysis; using ILCompiler.DependencyAnalysis.ReadyToRun; +using ILCompiler.ObjectWriter; namespace ILCompiler.PEWriter { @@ -132,7 +133,7 @@ private void CalculateSymbolMethodMap() return; } _symbolMethodMap = new Dictionary(); - foreach (KeyValuePair kvpSymbolMethod in _outputInfoBuilder.MethodSymbolMap) + foreach (KeyValuePair kvpSymbolMethod in _outputInfoBuilder.MethodSymbolMap) { _symbolMethodMap.Add(kvpSymbolMethod.Value.Method, kvpSymbolMethod.Key); } @@ -152,32 +153,35 @@ private void CalculateCallInfo() foreach (KeyValuePair> kvpCallerCalleeCount in _callChainProfile.ResolvedProfileData) { OutputNode callerNode = null; - int callerRVA = 0; + ulong callerRVA = 0; if (_symbolMethodMap.TryGetValue(kvpCallerCalleeCount.Key, out ISymbolDefinitionNode callerSymbol) && _outputInfoBuilder.NodeSymbolMap.TryGetValue(callerSymbol, out callerNode)) { - callerRVA = _outputInfoBuilder.Sections[callerNode.SectionIndex].RVAWhenPlaced + callerNode.Offset; + callerRVA = _outputInfoBuilder.Sections[callerNode.SectionIndex].VirtualAddress + callerNode.Offset; } foreach (KeyValuePair kvpCalleeCount in kvpCallerCalleeCount.Value) { OutputNode calleeNode = null; - int calleeRVA = 0; + ulong calleeRVA = 0; if (_symbolMethodMap.TryGetValue(kvpCalleeCount.Key, out ISymbolDefinitionNode calleeSymbol) && _outputInfoBuilder.NodeSymbolMap.TryGetValue(calleeSymbol, out calleeNode)) { - calleeRVA = _outputInfoBuilder.Sections[calleeNode.SectionIndex].RVAWhenPlaced + calleeNode.Offset; + calleeRVA = _outputInfoBuilder.Sections[calleeNode.SectionIndex].VirtualAddress + calleeNode.Offset; } + int callerRVA32Bit = checked((int)callerRVA); + int calleeRVA32Bit = checked((int)calleeRVA); + _callInfo.Add(new CallInfo( caller: kvpCallerCalleeCount.Key, callerNode: callerNode, - callerRVA: callerRVA, + callerRVA: callerRVA32Bit, callee: kvpCalleeCount.Key, calleeNode: calleeNode, - calleeRVA: calleeRVA, + calleeRVA: calleeRVA32Bit, callCount: kvpCalleeCount.Value, - callType: GetCallType(callerNode, callerRVA, calleeNode, calleeRVA))); + callType: GetCallType(callerNode, callerRVA32Bit, calleeNode, calleeRVA32Bit))); } } } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/R2RPEBuilder.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/R2RPEBuilder.cs deleted file mode 100644 index 738910d98e92e5..00000000000000 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/R2RPEBuilder.cs +++ /dev/null @@ -1,698 +0,0 @@ -// 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.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics; -using System.IO; -using System.Reflection.Metadata; -using System.Reflection.PortableExecutable; - -using ILCompiler.DependencyAnalysis; -using ILCompiler.DependencyAnalysis.ReadyToRun; - -using Internal.TypeSystem; - -namespace ILCompiler.PEWriter -{ - /// - /// Ready-to-run PE builder combines copying the input MSIL PE executable with managed - /// metadata and IL and adding new code and data representing the R2R JITted code and - /// additional runtime structures (R2R header and tables). - /// - public sealed class R2RPEBuilder : PEBuilder - { - /// - /// Number of low-order RVA bits that must match file position on Linux. - /// - const int RVABitsToMatchFilePos = 16; - - /// - /// Name of the text section. - /// - public const string TextSectionName = ".text"; - - /// - /// Name of the relocation section. - /// - public const string RelocSectionName = ".reloc"; - - /// - /// Name of the writeable data section. - /// - public const string DataSectionName = ".data"; - - /// - /// Name of the export data section. - /// - public const string ExportDataSectionName = ".edata"; - - /// - /// Compilation target OS and architecture specification. - /// - private TargetDetails _target; - - /// - /// Callback to retrieve the runtime function table which needs setting to the - /// ExceptionTable PE directory entry. - /// - private Func _getRuntimeFunctionsTable; - - private class SerializedSectionData - { - /// - /// Name of the section - /// - public string Name; - - /// - /// Logical section start RVAs. When emitting R2R PE executables for Linux, we must - /// align RVA's so that their 'RVABitsToMatchFilePos' lowest-order bits match the - /// file position (otherwise memory mapping of the file fails and CoreCLR silently - /// switches over to runtime JIT). PEBuilder doesn't support this today so that we - /// must store the RVA's and post-process the produced PE by patching the section - /// headers in the PE header. - /// - public int RVA; - - /// - /// Pointers to the location of the raw data. Needed to allow phyical file alignment - /// beyond 4KB. PEBuilder doesn't support this today so that we - /// must store the RVA's and post-process the produced PE by patching the section - /// headers in the PE header. - /// - public int PointerToRawData; - - /// - /// Maximum of virtual and physical size for each section. - /// - public int RawSize; - - /// - /// Whether or not the section has been serialized - if the RVA, pointer to raw data, - /// and size have been set. - /// - public bool IsSerialized; - } - - /// - /// List of possible sections to emit into the output R2R executable in the order in which - /// they are expected to be serialized. Data (aside from name) is set during serialization. - /// - private readonly SerializedSectionData[] _sectionData; - - /// - /// R2R PE section builder & relocator. - /// - private readonly SectionBuilder _sectionBuilder; - - /// - /// Zero-based index of the CPAOT-generated text section - /// - private readonly int _textSectionIndex; - - /// - /// Zero-based index of the CPAOT-generated read-write data section - /// - private readonly int _dataSectionIndex; - - /// - /// True after Write has been called; it's not possible to add further object data items past that point. - /// - private bool _written; - - /// - /// If non-null, the PE file will be laid out such that it can naturally be mapped with a higher alignment than 4KB - /// This is used to support loading via large pages on Linux - /// - private readonly int _customPESectionAlignment; - - /// - /// Constructor initializes the various control structures and combines the section list. - /// - /// Target environment specifier - /// PE file header builder - /// Callback to retrieve the runtime functions table - public R2RPEBuilder( - TargetDetails target, - PEHeaderBuilder peHeaderBuilder, - ISymbolNode r2rHeaderExportSymbol, - string outputFileSimpleName, - Func getRuntimeFunctionsTable, - int customPESectionAlignment, - Func, BlobContentId> deterministicIdProvider) - : base(peHeaderBuilder, deterministicIdProvider: deterministicIdProvider) - { - _target = target; - _getRuntimeFunctionsTable = getRuntimeFunctionsTable; - - _sectionBuilder = new SectionBuilder(target); - - _textSectionIndex = _sectionBuilder.AddSection(TextSectionName, SectionCharacteristics.ContainsCode | SectionCharacteristics.MemExecute | SectionCharacteristics.MemRead, 512); - _dataSectionIndex = _sectionBuilder.AddSection(DataSectionName, SectionCharacteristics.ContainsInitializedData | SectionCharacteristics.MemWrite | SectionCharacteristics.MemRead, 512); - - _customPESectionAlignment = customPESectionAlignment; - - if (r2rHeaderExportSymbol != null) - { - _sectionBuilder.AddSection(R2RPEBuilder.ExportDataSectionName, SectionCharacteristics.ContainsInitializedData | SectionCharacteristics.MemRead, 512); - _sectionBuilder.AddExportSymbol("RTR_HEADER", 1, r2rHeaderExportSymbol); - _sectionBuilder.SetDllNameForExportDirectoryTable(outputFileSimpleName); - } - - // Always inject the relocation section to the end of section list - _sectionBuilder.AddSection( - R2RPEBuilder.RelocSectionName, - SectionCharacteristics.ContainsInitializedData | - SectionCharacteristics.MemRead | - SectionCharacteristics.MemDiscardable, - PEHeaderConstants.SectionAlignment); - - List sectionData = new List(); - foreach (SectionInfo sectionInfo in _sectionBuilder.GetSections()) - { - sectionData.Add(new SerializedSectionData() { Name = sectionInfo.SectionName }); - } - - _sectionData = sectionData.ToArray(); - } - - public void SetCorHeader(ISymbolNode symbol, int headerSize) - { - _sectionBuilder.SetCorHeader(symbol, headerSize); - } - - public void SetDebugDirectory(ISymbolNode symbol, int size) - { - _sectionBuilder.SetDebugDirectory(symbol, size); - } - - public void SetWin32Resources(ISymbolNode symbol, int resourcesSize) - { - _sectionBuilder.SetWin32Resources(symbol, resourcesSize); - } - - /// - /// Emit a single object data item into the output R2R PE file using the section builder. - /// - /// Object data to emit - /// Target section - /// Textual name of the object data for diagnostic purposese - /// Optional output info builder to output the data item to - public void AddObjectData(DependencyAnalysis.ObjectNode.ObjectData objectData, ObjectNodeSection section, string name, OutputInfoBuilder outputInfoBuilder) - { - if (_written) - { - throw new InternalCompilerErrorException("Inconsistent upstream behavior - AddObjectData mustn't be called after Write"); - } - - int targetSectionIndex; - switch (section.Type) - { - case SectionType.ReadOnly: - // We put ReadOnly data into the text section to limit the number of sections. - case SectionType.Executable: - targetSectionIndex = _textSectionIndex; - break; - - case SectionType.Writeable: - targetSectionIndex = _dataSectionIndex; - break; - - default: - throw new NotImplementedException(); - } - - _sectionBuilder.AddObjectData(objectData, targetSectionIndex, name, outputInfoBuilder); - } - - /// - /// Add a symbol to the symbol map which defines the area of the binary between the two emitted symbols. - /// This allows relocations (both position and size) to regions of the image. Both nodes must be in the - /// same section and firstNode must be emitted before secondNode. - /// - public void AddSymbolForRange(ISymbolNode symbol, ISymbolNode firstNode, ISymbolNode secondNode) - { - _sectionBuilder.AddSymbolForRange(symbol, firstNode, secondNode); - } - - public int GetSymbolFilePosition(ISymbolNode symbol) - { - return _sectionBuilder.GetSymbolFilePosition(symbol); - } - - /// - /// Emit built sections into the R2R PE file. - /// - /// Output stream for the final R2R PE file - /// Timestamp to set in the PE header of the output R2R executable - public void Write(Stream outputStream, int? timeDateStamp) - { - BlobBuilder outputPeFile = new BlobBuilder(); - Serialize(outputPeFile); - - _sectionBuilder.RelocateOutputFile(outputPeFile, Header.ImageBase, outputStream); - - UpdateSectionRVAs(outputStream); - - if (_customPESectionAlignment != 0) - SetPEHeaderSectionAlignment(outputStream, _customPESectionAlignment); - - ApplyMachineOSOverride(outputStream); - - if (timeDateStamp.HasValue) - SetPEHeaderTimeStamp(outputStream, timeDateStamp.Value); - - _written = true; - } - - /// - /// Fill in map builder section table. - /// - /// Object info builder to set up - public void AddSections(OutputInfoBuilder outputInfoBuilder) - { - _sectionBuilder.AddSections(outputInfoBuilder); - } - - /// - /// PE header constants copied from System.Reflection.Metadata where they are - /// sadly mostly internal or private. - /// - const int DosHeaderSize = 0x80; - const int PESignatureSize = sizeof(uint); - - const int COFFHeaderSize = - sizeof(short) + // Machine - sizeof(short) + // NumberOfSections - sizeof(int) + // TimeDateStamp: - sizeof(int) + // PointerToSymbolTable - sizeof(int) + // NumberOfSymbols - sizeof(short) + // SizeOfOptionalHeader: - sizeof(ushort); // Characteristics - - const int OffsetOfSectionAlign = - sizeof(short) + // Magic - sizeof(byte) + // MajorLinkerVersion - sizeof(byte) + // MinorLinkerVersion - sizeof(int) + // SizeOfCode - sizeof(int) + // SizeOfInitializedData - sizeof(int) + // SizeOfUninitializedData - sizeof(int) + // AddressOfEntryPoint - sizeof(int) + // BaseOfCode - sizeof(long); // PE32: BaseOfData (int), ImageBase (int) - // PE32+: ImageBase (long) - const int OffsetOfChecksum = OffsetOfSectionAlign + - sizeof(int) + // SectionAlignment - sizeof(int) + // FileAlignment - sizeof(short) + // MajorOperatingSystemVersion - sizeof(short) + // MinorOperatingSystemVersion - sizeof(short) + // MajorImageVersion - sizeof(short) + // MinorImageVersion - sizeof(short) + // MajorSubsystemVersion - sizeof(short) + // MinorSubsystemVersion - sizeof(int) + // Win32VersionValue - sizeof(int) + // SizeOfImage - sizeof(int); // SizeOfHeaders - - const int OffsetOfSizeOfImage = OffsetOfChecksum - 2 * sizeof(int); // SizeOfHeaders, SizeOfImage - - const int SectionHeaderNameSize = 8; - const int SectionHeaderVirtualSize = SectionHeaderNameSize; // VirtualSize follows - const int SectionHeaderRVAOffset = SectionHeaderVirtualSize + sizeof(int); // RVA Offset follows VirtualSize + 4 bytes VirtualSize - const int SectionHeaderSizeOfRawData = SectionHeaderRVAOffset + sizeof(int); // SizeOfRawData follows RVA - const int SectionHeaderPointerToRawDataOffset = SectionHeaderSizeOfRawData + sizeof(int); // PointerToRawData immediately follows the SizeOfRawData - - const int SectionHeaderSize = - SectionHeaderNameSize + - sizeof(int) + // VirtualSize - sizeof(int) + // VirtualAddress - sizeof(int) + // SizeOfRawData - sizeof(int) + // PointerToRawData - sizeof(int) + // PointerToRelocations - sizeof(int) + // PointerToLineNumbers - sizeof(short) + // NumberOfRelocations - sizeof(short) + // NumberOfLineNumbers - sizeof(int); // SectionCharacteristics - - /// - /// On Linux, we must patch the section headers. This is because the CoreCLR runtime on Linux - /// requires the 12-16 low-order bits of section RVAs (the number of bits corresponds to the page - /// size) to be identical to the file offset, otherwise memory mapping of the file fails. - /// Sadly PEBuilder in System.Reflection.Metadata doesn't support this so we must post-process - /// the EXE by patching section headers with the correct RVA's. To reduce code variations - /// we're performing the same transformation on Windows where it is a no-op. - /// - /// - private void UpdateSectionRVAs(Stream outputStream) - { - int peHeaderSize = - OffsetOfChecksum + - sizeof(int) + // Checksum - sizeof(short) + // Subsystem - sizeof(short) + // DllCharacteristics - 4 * _target.PointerSize + // SizeOfStackReserve, SizeOfStackCommit, SizeOfHeapReserve, SizeOfHeapCommit - sizeof(int) + // LoaderFlags - sizeof(int) + // NumberOfRvaAndSizes - 16 * sizeof(long); // directory entries - - int sectionHeaderOffset = DosHeaderSize + PESignatureSize + COFFHeaderSize + peHeaderSize; - int sectionCount = _sectionData.Length; - for (int sectionIndex = 0; sectionIndex < sectionCount; sectionIndex++) - { - SerializedSectionData section = _sectionData[sectionIndex]; - if (!section.IsSerialized) - continue; - - if (_customPESectionAlignment != 0) - { - // When _customPESectionAlignment is set, the physical and virtual sizes are the same - byte[] sizeBytes = BitConverter.GetBytes(section.RawSize); - Debug.Assert(sizeBytes.Length == sizeof(int)); - - // Update VirtualSize - { - outputStream.Seek(sectionHeaderOffset + SectionHeaderSize * sectionIndex + SectionHeaderVirtualSize, SeekOrigin.Begin); - outputStream.Write(sizeBytes, 0, sizeBytes.Length); - } - // Update SizeOfRawData - { - outputStream.Seek(sectionHeaderOffset + SectionHeaderSize * sectionIndex + SectionHeaderSizeOfRawData, SeekOrigin.Begin); - outputStream.Write(sizeBytes, 0, sizeBytes.Length); - } - } - - // Update RVAs - { - outputStream.Seek(sectionHeaderOffset + SectionHeaderSize * sectionIndex + SectionHeaderRVAOffset, SeekOrigin.Begin); - byte[] rvaBytes = BitConverter.GetBytes(section.RVA); - Debug.Assert(rvaBytes.Length == sizeof(int)); - outputStream.Write(rvaBytes, 0, rvaBytes.Length); - } - - // Update pointer to raw data - { - outputStream.Seek(sectionHeaderOffset + SectionHeaderSize * sectionIndex + SectionHeaderPointerToRawDataOffset, SeekOrigin.Begin); - byte[] rawDataBytesBytes = BitConverter.GetBytes(section.PointerToRawData); - Debug.Assert(rawDataBytesBytes.Length == sizeof(int)); - outputStream.Write(rawDataBytesBytes, 0, rawDataBytesBytes.Length); - } - } - - // Patch SizeOfImage to point past the end of the last section - SerializedSectionData lastSection = null; - for (int i = sectionCount - 1; i >= 0; i--) - { - if (_sectionData[i].IsSerialized) - { - lastSection = _sectionData[i]; - break; - } - } - Debug.Assert(lastSection != null); - outputStream.Seek(DosHeaderSize + PESignatureSize + COFFHeaderSize + OffsetOfSizeOfImage, SeekOrigin.Begin); - int sizeOfImage = AlignmentHelper.AlignUp(lastSection.RVA + lastSection.RawSize, Header.SectionAlignment); - byte[] sizeOfImageBytes = BitConverter.GetBytes(sizeOfImage); - Debug.Assert(sizeOfImageBytes.Length == sizeof(int)); - outputStream.Write(sizeOfImageBytes, 0, sizeOfImageBytes.Length); - } - - /// - /// Set PE header section alignment, for alignments not supported by the System.Reflection.Metadata - /// - /// Output stream representing the R2R PE executable - /// Timestamp to set in the R2R PE header - private void SetPEHeaderSectionAlignment(Stream outputStream, int customAlignment) - { - outputStream.Seek(DosHeaderSize + PESignatureSize + COFFHeaderSize + OffsetOfSectionAlign, SeekOrigin.Begin); - byte[] alignBytes = BitConverter.GetBytes(customAlignment); - Debug.Assert(alignBytes.Length == sizeof(int)); - outputStream.Write(alignBytes, 0, alignBytes.Length); - } - - /// - /// TODO: System.Reflection.Metadata doesn't currently support OS machine overrides. - /// We cannot directly pass the xor-ed target machine to PEHeaderBuilder because it - /// may incorrectly detect 32-bitness and emit wrong OptionalHeader.Magic. Therefore - /// we create the executable using the raw Machine ID and apply the override as the - /// last operation before closing the file. - /// - /// Output stream representing the R2R PE executable - private void ApplyMachineOSOverride(Stream outputStream) - { - byte[] patchedTargetMachine = BitConverter.GetBytes( - (ushort)unchecked((ushort)Header.Machine ^ (ushort)_target.MachineOSOverrideFromTarget())); - Debug.Assert(patchedTargetMachine.Length == sizeof(ushort)); - - outputStream.Seek(DosHeaderSize + PESignatureSize, SeekOrigin.Begin); - outputStream.Write(patchedTargetMachine, 0, patchedTargetMachine.Length); - } - - /// - /// Set PE header timestamp in the output R2R image to a given value. - /// - /// Output stream representing the R2R PE executable - /// Timestamp to set in the R2R PE header - private void SetPEHeaderTimeStamp(Stream outputStream, int timeDateStamp) - { - byte[] patchedTimestamp = BitConverter.GetBytes(timeDateStamp); - int seekSize = - DosHeaderSize + - PESignatureSize + - sizeof(short) + // Machine - sizeof(short); // NumberOfSections - - outputStream.Seek(seekSize, SeekOrigin.Begin); - outputStream.Write(patchedTimestamp, 0, patchedTimestamp.Length); - } - - /// - /// Copy all directory entries and the address of entry point, relocating them along the way. - /// - protected override PEDirectoriesBuilder GetDirectories() - { - PEDirectoriesBuilder builder = new PEDirectoriesBuilder(); - - _sectionBuilder.UpdateDirectories(builder); - - if (_getRuntimeFunctionsTable != null) - { - RuntimeFunctionsTableNode runtimeFunctionsTable = _getRuntimeFunctionsTable(); - if (runtimeFunctionsTable.TableSizeExcludingSentinel != 0) - { - builder.ExceptionTable = new DirectoryEntry( - relativeVirtualAddress: _sectionBuilder.GetSymbolRVA(runtimeFunctionsTable), - size: runtimeFunctionsTable.TableSizeExcludingSentinel); - } - } - - return builder; - } - - /// - /// Provide an array of sections for the PEBuilder to use. - /// - protected override ImmutableArray
CreateSections() - { - ImmutableArray
.Builder sectionListBuilder = ImmutableArray.CreateBuilder
(); - foreach (SectionInfo sectionInfo in _sectionBuilder.GetSections()) - { - // Only include sections that have content. - if (!_sectionBuilder.HasContent(sectionInfo.SectionName)) - continue; - - sectionListBuilder.Add(new Section(sectionInfo.SectionName, sectionInfo.Characteristics)); - } - - return sectionListBuilder.ToImmutable(); - } - - /// - /// Output the section with a given name. - /// - /// Section name - /// RVA and file location where the section will be put - /// Blob builder representing the section data - protected override BlobBuilder SerializeSection(string name, SectionLocation location) - { - BlobBuilder sectionDataBuilder = null; - int sectionStartRva = location.RelativeVirtualAddress; - - int outputSectionIndex = _sectionData.Length - 1; - while (outputSectionIndex >= 0 && _sectionData[outputSectionIndex].Name != name) - { - outputSectionIndex--; - } - - if (outputSectionIndex < 0) - throw new ArgumentException($"Unknown section name: '{name}'", nameof(name)); - - Debug.Assert(_sectionBuilder.HasContent(name)); - SerializedSectionData outputSection = _sectionData[outputSectionIndex]; - SerializedSectionData previousSection = null; - for (int i = outputSectionIndex - 1; i >= 0; i--) - { - if (_sectionData[i].IsSerialized) - { - previousSection = _sectionData[i]; - break; - } - } - - int injectedPadding = 0; - if (_customPESectionAlignment != 0) - { - if (previousSection is not null) - { - sectionStartRva = Math.Max(sectionStartRva, previousSection.RVA + previousSection.RawSize); - } - - int newSectionStartRva = AlignmentHelper.AlignUp(sectionStartRva, _customPESectionAlignment); - int newSectionPointerToRawData = AlignmentHelper.AlignUp(location.PointerToRawData, _customPESectionAlignment); - if (newSectionPointerToRawData > location.PointerToRawData) - { - sectionDataBuilder = new BlobBuilder(); - injectedPadding = newSectionPointerToRawData - location.PointerToRawData; - sectionDataBuilder.WriteBytes(1, injectedPadding); - } - sectionStartRva = newSectionStartRva; - location = new SectionLocation(sectionStartRva, newSectionPointerToRawData); - } - - if (!_target.IsWindows) - { - const int RVAAlign = 1 << RVABitsToMatchFilePos; - if (previousSection is not null) - { - sectionStartRva = Math.Max(sectionStartRva, previousSection.RVA + previousSection.RawSize); - - // when assembly is stored in a singlefile bundle, an additional skew is introduced - // as the streams inside the bundle are not necessarily page aligned as we do not - // know the actual page size on the target system. - // We may need one page gap of unused VA space before the next section starts. - // We will assume the page size is <= RVAAlign - sectionStartRva += RVAAlign; - } - - sectionStartRva = AlignmentHelper.AlignUp(sectionStartRva, RVAAlign); - - int rvaAdjust = (location.PointerToRawData - sectionStartRva) & (RVAAlign - 1); - sectionStartRva += rvaAdjust; - location = new SectionLocation(sectionStartRva, location.PointerToRawData); - } - - outputSection.RVA = sectionStartRva; - outputSection.PointerToRawData = location.PointerToRawData; - - BlobBuilder extraData = _sectionBuilder.SerializeSection(name, location); - Debug.Assert(extraData != null); - if (sectionDataBuilder == null) - { - // See above - there's a bug due to which LinkSuffix to an empty BlobBuilder screws up the blob content. - sectionDataBuilder = extraData; - } - else - { - sectionDataBuilder.LinkSuffix(extraData); - } - - int sectionRawSize = sectionDataBuilder.Count - injectedPadding; - - if (_customPESectionAlignment != 0) - { - // Align the end of the section to the padding offset - int count = AlignmentHelper.AlignUp(sectionRawSize, _customPESectionAlignment); - sectionDataBuilder.WriteBytes(0, count - sectionRawSize); - sectionRawSize = count; - } - - outputSection.RawSize = sectionRawSize; - outputSection.IsSerialized = true; - - return sectionDataBuilder; - } - } - - /// - /// Simple helper for filling in PE header information. - /// - static class PEHeaderProvider - { - /// - /// Fill in PE header information into a PEHeaderBuilder used by PEBuilder. - /// - /// Targeting subsystem - /// Target architecture to set in the header - public static PEHeaderBuilder Create(Subsystem subsystem, TargetDetails target, ulong imageBase) - { - bool is64BitTarget = target.PointerSize == sizeof(long); - - Characteristics imageCharacteristics = Characteristics.ExecutableImage | Characteristics.Dll; - imageCharacteristics |= is64BitTarget ? Characteristics.LargeAddressAware : Characteristics.Bit32Machine; - - int fileAlignment = 0x200; - bool isWindowsOr32bit = target.IsWindows || !is64BitTarget; - if (isWindowsOr32bit) - { - // To minimize wasted VA space on 32-bit systems (regardless of OS), - // align file to page boundaries (presumed to be 4K) - // - // On Windows we use 4K file alignment (regardless of ptr size), - // per requirements of memory mapping API (MapViewOfFile3, et al). - // The alternative could be using the same approach as on Unix, but that would result in PEs - // incompatible with OS loader. While that is not a problem on Unix, we do not want that on Windows. - fileAlignment = 0x1000; - } - - int sectionAlignment = 0x1000; - if (!isWindowsOr32bit) - { - // On 64bit Linux, we must match the bottom 12 bits of section RVA's to their file offsets. For this reason - // we need the same alignment for both. - // - // In addition to that we specify section RVAs to be at least 64K apart, which is > page on most systems. - // It ensures that the sections will not overlap when mapped from a singlefile bundle, which introduces a sub-page skew. - // - // Such format would not be accepted by OS loader on Windows, but it is not a problem on Unix. - sectionAlignment = fileAlignment; - } - - // Without NxCompatible the PE executable cannot execute on Windows ARM64 - DllCharacteristics dllCharacteristics = - DllCharacteristics.DynamicBase | - DllCharacteristics.NxCompatible | - DllCharacteristics.TerminalServerAware; - - if (is64BitTarget) - { - dllCharacteristics |= DllCharacteristics.HighEntropyVirtualAddressSpace; - } - else - { - dllCharacteristics |= DllCharacteristics.NoSeh; - } - - return new PEHeaderBuilder( - machine: target.MachineFromTarget(), - sectionAlignment: sectionAlignment, - fileAlignment: fileAlignment, - imageBase: imageBase, - majorLinkerVersion: PEHeaderConstants.MajorLinkerVersion, - minorLinkerVersion: PEHeaderConstants.MinorLinkerVersion, - majorOperatingSystemVersion: PEHeaderConstants.MajorOperatingSystemVersion, - minorOperatingSystemVersion: PEHeaderConstants.MinorOperatingSystemVersion, - majorImageVersion: PEHeaderConstants.MajorImageVersion, - minorImageVersion: PEHeaderConstants.MinorImageVersion, - majorSubsystemVersion: PEHeaderConstants.MajorSubsystemVersion, - minorSubsystemVersion: PEHeaderConstants.MinorSubsystemVersion, - subsystem: subsystem, - dllCharacteristics: dllCharacteristics, - imageCharacteristics: imageCharacteristics, - sizeOfStackReserve: (is64BitTarget ? PE64HeaderConstants.SizeOfStackReserve : PE32HeaderConstants.SizeOfStackReserve), - sizeOfStackCommit: (is64BitTarget ? PE64HeaderConstants.SizeOfStackCommit : PE32HeaderConstants.SizeOfStackCommit), - sizeOfHeapReserve: (is64BitTarget ? PE64HeaderConstants.SizeOfHeapReserve : PE32HeaderConstants.SizeOfHeapReserve), - sizeOfHeapCommit: (is64BitTarget ? PE64HeaderConstants.SizeOfHeapCommit : PE32HeaderConstants.SizeOfHeapCommit)); - } - } -} diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/RelocationHelper.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/RelocationHelper.cs deleted file mode 100644 index 6a2501482ab786..00000000000000 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/RelocationHelper.cs +++ /dev/null @@ -1,549 +0,0 @@ -// 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.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Reflection.Metadata; -using System.Reflection.Metadata.Ecma335; -using System.Reflection.PortableExecutable; -using System.Runtime.CompilerServices; - -using ILCompiler.DependencyAnalysis; - -namespace ILCompiler.PEWriter -{ - /// - /// Helper used to copy the produced PE file from a BlobBuilder to an output stream, - /// applying relocations along the way. It's mostly a linear copier that occasionally stops, - /// patches a few bytes and then continues. - /// - class RelocationHelper - { - /// - /// Maximum number of bytes to process for any relocation type. - /// - const int LongestRelocationBytes = 8; - - /// - /// Enumerator of blobs within the blob builder. - /// - private BlobBuilder.Blobs _peFileBlobs; - - /// - /// Blob length is used at the end to verify that the relocated file hasn't changed length. - /// - private int _peFileLength; - - /// - /// Backing array for the ArraySegment of the current blob. - /// - private byte[] _currentBlob; - - /// - /// Current offset within the active blob. - /// - private int _blobOffset; - - /// - /// Remaining number of bytes unprocessed in the active blob. - /// - private int _remainingLength; - - /// - /// Preferred image load address is needed to properly fix up absolute relocation types. - /// - private ulong _defaultImageBase; - - /// - /// Output stream to receive the relocated file - /// - private Stream _outputStream; - - /// - /// Current position in the output file - /// - private int _outputFilePos; - - /// - /// Buffer to hold data for the currently processed relocation. - /// - private byte[] _relocationBuffer = new byte[LongestRelocationBytes]; - - /// - /// Relocation helper stores the output stream and initializes the PE blob builder enumerator. - /// - /// Output stream for the relocated PE file - /// PE file blob builder - public RelocationHelper(Stream outputStream, ulong defaultImageBase, BlobBuilder peFileBuilder) - { - _outputStream = outputStream; - _outputFilePos = 0; - - _defaultImageBase = defaultImageBase; - - _peFileLength = peFileBuilder.Count; - _peFileBlobs = peFileBuilder.GetBlobs(); - FetchNextBlob(); - } - - /// - /// Copy data from the PE file builder to the output stream, stopping at given file position. - /// - /// Output PE file position to stop at - public void CopyToFilePosition(int filePos) - { - CopyBytesToOutput(filePos - _outputFilePos); - } - - /// - /// Advance output position in case of external writes to the output stream. - /// - /// Number of bytes advance output by - public void AdvanceOutputPos(int delta) - { - _outputFilePos += delta; - } - - /// - /// Copy all unprocessed data (after the last relocation) into the output file - /// without any further modifications. - /// - public void CopyRestOfFile() - { - do - { - CopyBytesToOutput(_remainingLength); - } - while (TryFetchNextBlob()); - - if (_outputFilePos != _peFileLength) - { - // Input / output PE file length mismatch - internal error in the relocator - throw new BadImageFormatException(); - } - } - - /// - /// Process a single relocation by copying the required number of bytes into a - /// buffer, applying the relocation and writing it to the output file. - /// - /// Relocation type to process - /// RVA representing the address to relocate - /// RVA representing the relocation target - public void ProcessRelocation(RelocType relocationType, int sourceRVA, int targetRVA, int filePosWhenPlaced) - { - int relocationLength = 0; - long delta = 0; - - switch (relocationType) - { - case RelocType.IMAGE_REL_BASED_ABSOLUTE: - // No relocation - return; - - case RelocType.IMAGE_REL_BASED_HIGHLOW: - { - relocationLength = 4; - delta = unchecked(targetRVA + (int)_defaultImageBase); - break; - } - - case RelocType.IMAGE_REL_BASED_ADDR32NB: - case RelocType.IMAGE_REL_SYMBOL_SIZE: - { - relocationLength = 4; - delta = targetRVA; - break; - } - - case RelocType.IMAGE_REL_BASED_REL32: - { - relocationLength = 4; - delta = targetRVA - sourceRVA - 4; - break; - } - - case RelocType.IMAGE_REL_BASED_DIR64: - { - relocationLength = 8; - delta = unchecked(targetRVA + (long)_defaultImageBase); - break; - } - - case RelocType.IMAGE_REL_BASED_THUMB_MOV32: - { - relocationLength = 8; - delta = unchecked(targetRVA + (int)_defaultImageBase); - break; - } - - case RelocType.IMAGE_REL_BASED_THUMB_MOV32_PCREL: - { - relocationLength = 8; - const uint offsetCorrection = 12; - delta = unchecked(targetRVA - (sourceRVA + offsetCorrection)); - break; - } - - case RelocType.IMAGE_REL_BASED_THUMB_BRANCH24: - { - relocationLength = 4; - delta = targetRVA - sourceRVA - 4; - break; - } - - case RelocType.IMAGE_REL_BASED_ARM64_PAGEBASE_REL21: - { - relocationLength = 4; - int sourcePageRVA = sourceRVA & ~0xfff; - // Page delta always fits in 21 bits as long as we use 4-byte RVAs - delta = ((targetRVA - sourcePageRVA) >> 12) & 0x1f_ffff; - break; - } - - case RelocType.IMAGE_REL_BASED_ARM64_PAGEOFFSET_12A: - { - relocationLength = 4; - delta = targetRVA & 0xfff; - break; - } - - case RelocType.IMAGE_REL_FILE_ABSOLUTE: - { - relocationLength = 4; - delta = filePosWhenPlaced; - break; - } - - case RelocType.IMAGE_REL_BASED_LOONGARCH64_PC: - { - relocationLength = 8; - delta = (int)(targetRVA - (sourceRVA & ~0xfff) + ((targetRVA & 0x800) << 1)); - break; - } - case RelocType.IMAGE_REL_BASED_LOONGARCH64_JIR: - { - relocationLength = 8; - delta = targetRVA - sourceRVA; - break; - } - - case RelocType.IMAGE_REL_BASED_RISCV64_PC: - { - relocationLength = 8; - delta = targetRVA - sourceRVA; - break; - } - - default: - throw new NotSupportedException(); - } - - if (relocationLength > 0) - { - CopyBytesToBuffer(_relocationBuffer, relocationLength); - unsafe - { - fixed (byte *bufferContent = _relocationBuffer) - { - long value = Relocation.ReadValue(relocationType, bufferContent); - // Supporting non-zero values for ARM64 would require refactoring this function - if (((relocationType == RelocType.IMAGE_REL_BASED_ARM64_PAGEBASE_REL21) || - (relocationType == RelocType.IMAGE_REL_BASED_ARM64_PAGEOFFSET_12A) || - (relocationType == RelocType.IMAGE_REL_BASED_LOONGARCH64_PC) || - (relocationType == RelocType.IMAGE_REL_BASED_LOONGARCH64_JIR) || - (relocationType == RelocType.IMAGE_REL_BASED_RISCV64_PC) - ) && (value != 0)) - { - throw new NotSupportedException(); - } - - Relocation.WriteValue(relocationType, bufferContent, unchecked(value + delta)); - } - } - - // Write the relocated bytes to the output file - _outputStream.Write(_relocationBuffer, 0, relocationLength); - _outputFilePos += relocationLength; - } - } - - /// - /// Read next blob from the PE blob builder. Throw exception of no more data is available - /// (indicates an inconsistent PE file). - /// - private void FetchNextBlob() - { - if (!TryFetchNextBlob()) - { - throw new BadImageFormatException(); - } - } - - /// - /// Try to fetch next blob from the PE blob builder, return false on EOF. - /// - /// True when another blob was successfully fetched, false on EOF - private bool TryFetchNextBlob() - { - if (!_peFileBlobs.MoveNext()) - { - return false; - } - - ArraySegment blobContent = _peFileBlobs.Current.GetBytes(); - _currentBlob = blobContent.Array; - _blobOffset = blobContent.Offset; - _remainingLength = blobContent.Count; - return true; - } - - /// - /// Copy a given number of bytes from the PE blob builder to the output stream. - /// - /// Number of bytes to copy - private void CopyBytesToOutput(int length) - { - Debug.Assert(length >= 0); - - while (length > 0) - { - if (_remainingLength == 0) - { - FetchNextBlob(); - } - - int part = Math.Min(length, _remainingLength); - _outputStream.Write(_currentBlob, _blobOffset, part); - _outputFilePos += part; - _blobOffset += part; - _remainingLength -= part; - length -= part; - } - } - - /// - /// Copy bytes from the PE blob builder to the given byte buffer. - /// - /// Buffer to fill in from the blob builder - /// Number of bytes to copy to the buffer - public void CopyBytesToBuffer(byte[] buffer, int count) - { - int offset = 0; - while (offset < count) - { - if (_remainingLength == 0) - { - FetchNextBlob(); - } - - int part = Math.Min(count - offset, _remainingLength); - Array.Copy( - sourceArray: _currentBlob, - sourceIndex: _blobOffset, - destinationArray: buffer, - destinationIndex: offset, - length: part); - - _blobOffset += part; - _remainingLength -= part; - offset += part; - } - } - - /// - /// Extract the 24-bit rel offset from bl instruction - /// - /// Byte buffer containing the instruction to analyze - /// Offset of the instruction within the buffer - private static unsafe int GetThumb2BlRel24(byte[] bytes, int offset) - { - uint opcode0 = BitConverter.ToUInt16(bytes, offset + 0); - uint opcode1 = BitConverter.ToUInt16(bytes, offset + 2); - - uint s = opcode0 >> 10; - uint j2 = opcode1 >> 11; - uint j1 = opcode1 >> 13; - - uint ret = - ((s << 24) & 0x1000000) | - (((j1 ^ s ^ 1) << 23) & 0x0800000) | - (((j2 ^ s ^ 1) << 22) & 0x0400000) | - ((opcode0 << 12) & 0x03FF000) | - ((opcode1 << 1) & 0x0000FFE); - - // Sign-extend and return - return (int)((ret << 7) >> 7); - } - - /// - /// Patch a MOVW / MOVT Thumb2 instruction by updating its 16-bit immediate operand to imm16. - /// - /// Immediate 16-bit operand to inject into the instruction - /// Byte array containing the instruction to patch - /// Offset of the MOVW / MOVT instruction - private static void PutThumb2Imm16(ushort imm16, byte[] bytes, int offset) - { - const ushort Mask1 = 0xf000; - const ushort Val1 = (Mask1 >> 12); - const ushort Mask2 = 0x0800; - const ushort Val2 = (Mask2 >> 1); - const ushort Mask3 = 0x0700; - const ushort Val3 = (Mask3 << 4); - const ushort Mask4 = 0x00ff; - const ushort Val4 = (Mask4 << 0); - const ushort Val = Val1 | Val2 | Val3 | Val4; - - ushort opcode0 = BitConverter.ToUInt16(bytes, offset); - ushort opcode1 = BitConverter.ToUInt16(bytes, offset + 2); - - opcode0 &= unchecked((ushort)~Val); - opcode0 |= unchecked((ushort)(((imm16 & Mask1) >> 12) | ((imm16 & Mask2) >> 1) | ((imm16 & Mask3) << 4) | ((imm16 & Mask4) << 0))); - - WriteUInt16(opcode0, bytes, offset); - WriteUInt16(opcode1, bytes, offset + 2); - } - - /// - /// Decode the 32-bit immediate operand from a MOVW / MOVT instruction pair (8 bytes total). - /// - /// Byte array containing the 8-byte sequence MOVW - MOVT - private static int GetThumb2Mov32(byte[] bytes) - { - Debug.Assert(((uint)BitConverter.ToUInt16(bytes, 0) & 0xFBF0) == 0xF240); - Debug.Assert(((uint)BitConverter.ToUInt16(bytes, 4) & 0xFBF0) == 0xF2C0); - - return (int)GetThumb2Imm16(bytes, 0) + ((int)(GetThumb2Imm16(bytes, 4) << 16)); - } - - /// - /// Decode the 16-bit immediate operand from a MOVW / MOVT instruction. - /// - private static ushort GetThumb2Imm16(byte[] bytes, int offset) - { - uint opcode0 = BitConverter.ToUInt16(bytes, offset); - uint opcode1 = BitConverter.ToUInt16(bytes, offset + 2); - uint result = - ((opcode0 << 12) & 0xf000) | - ((opcode0 << 1) & 0x0800) | - ((opcode1 >> 4) & 0x0700) | - ((opcode1 >> 0) & 0x00ff); - return (ushort)result; - } - - /// - /// Returns whether the offset fits into bl instruction - /// - /// Immediate operand to check. - private static bool FitsInThumb2BlRel24(int imm24) - { - return ((imm24 << 7) >> 7) == imm24; - } - - /// - /// Deposit the 24-bit rel offset into bl instruction - /// - /// Immediate operand to inject into the instruction - /// Byte buffer containing the BL instruction to patch - /// Offset of the instruction within the buffer - private static void PutThumb2BlRel24(int imm24, byte[] bytes, int offset) - { - // Verify that we got a valid offset - Debug.Assert(FitsInThumb2BlRel24(imm24)); - - // Ensure that the ThumbBit is not set on the offset - // as it cannot be encoded. - Debug.Assert((imm24 & 1/*THUMB_CODE*/) == 0); - - ushort opcode0 = BitConverter.ToUInt16(bytes, 0); - ushort opcode1 = BitConverter.ToUInt16(bytes, 2); - opcode0 &= 0xF800; - opcode1 &= 0xD000; - - uint s = (unchecked((uint)imm24) & 0x1000000) >> 24; - uint j1 = ((unchecked((uint)imm24) & 0x0800000) >> 23) ^ s ^ 1; - uint j2 = ((unchecked((uint)imm24) & 0x0400000) >> 22) ^ s ^ 1; - - opcode0 |= (ushort)(((unchecked((uint)imm24) & 0x03FF000) >> 12) | (s << 10)); - opcode1 |= (ushort)(((unchecked((uint)imm24) & 0x0000FFE) >> 1) | (j1 << 13) | (j2 << 11)); - - WriteUInt16(opcode0, bytes, offset + 0); - WriteUInt16(opcode1, bytes, offset + 2); - - Debug.Assert(GetThumb2BlRel24(bytes, 0) == imm24); - } - - /// - /// Helper to write 16-bit value to a byte array. - /// - /// Value to write to the byte array - /// Target byte array - /// Offset in the array - static void WriteUInt16(ushort value, byte[] bytes, int offset) - { - bytes[offset + 0] = unchecked((byte)value); - bytes[offset + 1] = (byte)(value >> 8); - } - - /// - /// Helper to write 32-bit value to a byte array. - /// - /// Value to write to the byte array - /// Target byte array - /// Offset in the array - static void WriteUInt32(uint value, byte[] bytes, int offset) - { - bytes[offset + 0] = unchecked((byte)(value >> 0)); - bytes[offset + 1] = unchecked((byte)(value >> 8)); - bytes[offset + 2] = unchecked((byte)(value >> 16)); - bytes[offset + 3] = unchecked((byte)(value >> 24)); - } - - /// - /// We use the same byte encoding for signed and unsigned 32-bit values - /// so this method just forwards to WriteUInt32. - /// - /// Value to write to the byte array - /// Target byte array - /// Offset in the array - static void WriteInt32(int value, byte[] bytes, int offset) - { - WriteUInt32(unchecked((uint)value), bytes, offset); - } - - /// - /// Helper to write 64-bit value to a byte array. - /// - /// Value to write to the byte array - /// Target byte array - /// Offset in the array - static void WriteUInt64(ulong value, byte[] bytes, int offset) - { - bytes[offset + 0] = unchecked((byte)(value >> 0)); - bytes[offset + 1] = unchecked((byte)(value >> 8)); - bytes[offset + 2] = unchecked((byte)(value >> 16)); - bytes[offset + 3] = unchecked((byte)(value >> 24)); - bytes[offset + 4] = unchecked((byte)(value >> 32)); - bytes[offset + 5] = unchecked((byte)(value >> 40)); - bytes[offset + 6] = unchecked((byte)(value >> 48)); - bytes[offset + 7] = unchecked((byte)(value >> 56)); - } - - /// - /// We use the same byte encoding for signed and unsigned 64-bit values - /// so this method just forwards to WriteUInt64. - /// - /// Value to write to the byte array - /// Target byte array - /// Offset in the array - static void WriteInt64(long value, byte[] bytes, int offset) - { - WriteUInt64(unchecked((ulong)value), bytes, offset); - } - } -} diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/SectionBuilder.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/SectionBuilder.cs deleted file mode 100644 index f3ebd11aae868c..00000000000000 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/SectionBuilder.cs +++ /dev/null @@ -1,965 +0,0 @@ -// 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.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Reflection.Metadata; -using System.Reflection.PortableExecutable; - -using ILCompiler.DependencyAnalysis; - -using Internal.Text; -using Internal.TypeSystem; - -namespace ILCompiler.PEWriter -{ - /// - /// For a given symbol, this structure represents its target section and offset - /// within the containing section. - /// - public struct SymbolTarget - { - /// - /// Index of the section holding the symbol target. - /// - public readonly int SectionIndex; - - /// - /// Offset of the symbol within the section. - /// - public readonly int Offset; - - public readonly int Size; - - /// - /// Initialize symbol target with section and offset. - /// - /// Section index where the symbol target resides - /// Offset of the target within the section - public SymbolTarget(int sectionIndex, int offset, int size) - { - SectionIndex = sectionIndex; - Offset = offset; - Size = size; - } - } - - /// - /// After placing an ObjectData within a section, we use this helper structure to record - /// its relocation information for the final relocation pass. - /// - public struct PlacedObjectData - { - /// - /// Offset of the ObjectData block within the section - /// - public readonly int Offset; - - /// - /// Object data representing an array of relocations to fix up. - /// - public readonly ObjectNode.ObjectData Data; - - /// - /// Array of relocations that need fixing up within the block. - /// - public Relocation[] Relocs => Data.Relocs; - - /// - /// Initialize the list of relocations for a given object data item within the section. - /// - /// Offset within the section - /// Object data block containing the list of relocations to fix up - public PlacedObjectData(int offset, ObjectNode.ObjectData data) - { - Offset = offset; - Data = data; - } - } - - public struct SectionInfo - { - public readonly string SectionName; - public readonly SectionCharacteristics Characteristics; - - public SectionInfo(string sectionName, SectionCharacteristics characteristics) - { - SectionName = sectionName; - Characteristics = characteristics; - } - } - - /// - /// Section represents a contiguous area of code or data with the same characteristics. - /// - public class Section - { - /// - /// Index within the internal section table used by the section builder - /// - public readonly int Index; - - /// - /// Section name - /// - public readonly string Name; - - /// - /// Section characteristics - /// - public readonly SectionCharacteristics Characteristics; - - /// - /// Alignment to apply when combining multiple builder sections into a single - /// physical output section (typically when combining hot and cold code into - /// the output code section). - /// - public readonly int Alignment; - - /// - /// Blob builder representing the section content. - /// - public readonly BlobBuilder Content; - - /// - /// All blocks requiring relocation resolution within the section - /// - public readonly List PlacedObjectDataToRelocate; - - /// - /// RVA gets filled in during section serialization. - /// - public int RVAWhenPlaced; - - /// - /// Output file position gets filled in during section serialization. - /// - public int FilePosWhenPlaced; - - /// - /// Construct a new session object. - /// - /// Zero-based section index - /// Section name - /// Section characteristics - /// Alignment for combining multiple logical sections - public Section(int index, string name, SectionCharacteristics characteristics, int alignment) - { - Index = index; - Name = name; - Characteristics = characteristics; - Alignment = alignment; - Content = new BlobBuilder(); - PlacedObjectDataToRelocate = new List(); - RVAWhenPlaced = 0; - FilePosWhenPlaced = 0; - } - } - - /// - /// This class represents a single export symbol in the PE file. - /// - public class ExportSymbol - { - /// - /// Symbol identifier - /// - public readonly string Name; - - /// - /// When placed into the export section, RVA of the symbol name gets updated. - /// - public int NameRVAWhenPlaced; - - /// - /// Export symbol ordinal - /// - public readonly int Ordinal; - - /// - /// Symbol to export - /// - public readonly ISymbolNode Symbol; - - /// - /// Construct the export symbol instance filling in its arguments - /// - /// Export symbol identifier - /// Ordinal ID of the export symbol - /// Symbol to export - public ExportSymbol(string name, int ordinal, ISymbolNode symbol) - { - Name = name; - Ordinal = ordinal; - Symbol = symbol; - } - } - - /// - /// Section builder is capable of accumulating blocks, using them to lay out sections - /// and relocate the produced executable according to the block relocation information. - /// - public class SectionBuilder - { - /// - /// Target OS / architecture. - /// - TargetDetails _target; - - /// - /// Map from symbols to their target sections and offsets. - /// - Dictionary _symbolMap; - - /// - /// List of sections defined in the builder - /// - List
_sections; - - /// - /// Symbols to export from the PE file. - /// - List _exportSymbols; - - /// - /// Optional symbol representing an entrypoint override. - /// - ISymbolNode _entryPointSymbol; - - /// - /// Export directory entry when available. - /// - DirectoryEntry _exportDirectoryEntry; - - /// - /// Directory entry representing the extra relocation records. - /// - DirectoryEntry _relocationDirectoryEntry; - - /// - /// Symbol representing the ready-to-run COR (MSIL) header table. - /// Only present in single-file R2R executables. Composite R2R - /// executables don't have a COR header and locate the ReadyToRun - /// header directly using the well-known export symbol RTR_HEADER. - /// - ISymbolNode _corHeaderSymbol; - - /// - /// Size of the ready-to-run header table in bytes. - /// - int _corHeaderSize; - - /// - /// Symbol representing the debug directory. - /// - ISymbolNode _debugDirectorySymbol; - - /// - /// Size of the debug directory in bytes. - /// - int _debugDirectorySize; - - /// - /// Symbol representing the start of the win32 resources - /// - ISymbolNode _win32ResourcesSymbol; - - /// - /// Size of the win32 resources - /// - int _win32ResourcesSize; - - /// - /// Padding 4-byte sequence to use in code section. Typically corresponds - /// to some interrupt to be thrown at "invalid" IP addresses. - /// - uint _codePadding; - - /// - /// For PE files with exports, this is the "DLL name" string to store in the export directory table. - /// - string _dllNameForExportDirectoryTable; - - /// - /// Construct an empty section builder without any sections or blocks. - /// - public SectionBuilder(TargetDetails target) - { - _target = target; - _symbolMap = new Dictionary(); - _sections = new List
(); - _exportSymbols = new List(); - _entryPointSymbol = null; - _exportDirectoryEntry = default(DirectoryEntry); - _relocationDirectoryEntry = default(DirectoryEntry); - - switch (_target.Architecture) - { - case TargetArchitecture.X86: - case TargetArchitecture.X64: - // 4 times INT 3 (or debugger break) - _codePadding = 0xCCCCCCCCu; - break; - - case TargetArchitecture.ARM: - // 2 times undefined instruction used as debugger break - _codePadding = (_target.IsWindows ? 0xDEFEDEFEu : 0xDE01DE01u); - break; - - case TargetArchitecture.ARM64: - _codePadding = 0xD43E0000u; - break; - - case TargetArchitecture.LoongArch64: - _codePadding = 0x002A0005u; - break; - - case TargetArchitecture.RiscV64: - _codePadding = 0x00100073u; - break; - - default: - throw new NotImplementedException(); - } - } - - /// - /// Add a new section. Section names must be unique. - /// - /// Section name - /// Section characteristics - /// - /// Alignment for composing multiple builder sections into one physical output section - /// - /// Zero-based index of the added section - public int AddSection(string name, SectionCharacteristics characteristics, int alignment) - { - int sectionIndex = _sections.Count; - _sections.Add(new Section(sectionIndex, name, characteristics, alignment)); - return sectionIndex; - } - - /// - /// Try to look up a pre-existing section in the builder; returns null if not found. - /// - public Section FindSection(string name) - { - return _sections.FirstOrDefault((sec) => sec.Name == name); - } - - /// - /// Look up RVA for a given symbol. This assumes the section have already been placed. - /// - /// Symbol to look up - /// RVA of the symbol - public int GetSymbolRVA(ISymbolNode symbol) - { - SymbolTarget symbolTarget = _symbolMap[symbol]; - Section section = _sections[symbolTarget.SectionIndex]; - Debug.Assert(section.RVAWhenPlaced != 0); - return section.RVAWhenPlaced + symbolTarget.Offset; - } - - /// - /// Look up final file position for a given symbol. This assumes the section have already been placed. - /// - /// Symbol to look up - /// File position of the symbol, from the beginning of the emitted image - public int GetSymbolFilePosition(ISymbolNode symbol) - { - SymbolTarget symbolTarget = _symbolMap[symbol]; - Section section = _sections[symbolTarget.SectionIndex]; - Debug.Assert(section.RVAWhenPlaced != 0); - return section.FilePosWhenPlaced + symbolTarget.Offset; - } - - /// - /// Attach an export symbol to the output PE file. - /// - /// Export symbol identifier - /// Ordinal ID of the export symbol - /// Symbol to export - public void AddExportSymbol(string name, int ordinal, ISymbolNode symbol) - { - _exportSymbols.Add(new ExportSymbol( - name: name, - ordinal: ordinal, - symbol: symbol)); - } - - /// - /// Record DLL name to emit in the export directory table. - /// - /// DLL name to emit - public void SetDllNameForExportDirectoryTable(string dllName) - { - _dllNameForExportDirectoryTable = dllName; - } - - /// - /// Override entry point for the app. - /// - /// Symbol representing the new entry point - public void SetEntryPoint(ISymbolNode symbol) - { - _entryPointSymbol = symbol; - } - - public void SetCorHeader(ISymbolNode symbol, int headerSize) - { - _corHeaderSymbol = symbol; - _corHeaderSize = headerSize; - } - - public void SetDebugDirectory(ISymbolNode symbol, int size) - { - _debugDirectorySymbol = symbol; - _debugDirectorySize = size; - } - - public void SetWin32Resources(ISymbolNode symbol, int resourcesSize) - { - _win32ResourcesSymbol = symbol; - _win32ResourcesSize = resourcesSize; - } - - private NativeAotNameMangler _nameMangler; - - private NameMangler GetNameMangler() - { - if (_nameMangler == null) - { - // TODO-REFACTOR: why do we have two name manglers? - _nameMangler = new NativeAotNameMangler(); - _nameMangler.CompilationUnitPrefix = ""; - } - return _nameMangler; - } - - /// - /// Add an ObjectData block to a given section. - /// - /// Block to add - /// Section index - /// Node name to emit in the map file - /// Optional output info to collect (used for creating maps and symbols) - public void AddObjectData(ObjectNode.ObjectData objectData, int sectionIndex, string name, OutputInfoBuilder outputInfoBuilder) - { - Section section = _sections[sectionIndex]; - - // Calculate alignment padding - apparently ObjectDataBuilder can produce an alignment of 0 - int alignedOffset = section.Content.Count; - if (objectData.Alignment > 1) - { - alignedOffset = (section.Content.Count + objectData.Alignment - 1) & -objectData.Alignment; - int padding = alignedOffset - section.Content.Count; - if (padding > 0) - { - if ((section.Characteristics & SectionCharacteristics.ContainsCode) != 0) - { - uint cp = _codePadding; - while (padding >= sizeof(uint)) - { - section.Content.WriteUInt32(cp); - padding -= sizeof(uint); - } - if (padding >= 2) - { - section.Content.WriteUInt16(unchecked((ushort)cp)); - cp >>= 16; - } - if ((padding & 1) != 0) - { - section.Content.WriteByte(unchecked((byte)cp)); - } - } - else - { - section.Content.WriteBytes(0, padding); - } - } - } - - if (outputInfoBuilder != null) - { - var node = new OutputNode(sectionIndex, alignedOffset, objectData.Data.Length, name); - outputInfoBuilder.AddNode(node, objectData.DefinedSymbols[0]); - if (objectData.Relocs != null) - { - foreach (Relocation reloc in objectData.Relocs) - { - RelocType fileReloc = Relocation.GetFileRelocationType(reloc.RelocType); - if (fileReloc != RelocType.IMAGE_REL_BASED_ABSOLUTE) - { - outputInfoBuilder.AddRelocation(node, fileReloc); - } - } - } - } - - section.Content.WriteBytes(objectData.Data); - - if (objectData.DefinedSymbols != null) - { - foreach (ISymbolDefinitionNode symbol in objectData.DefinedSymbols) - { - if (outputInfoBuilder != null) - { - Utf8StringBuilder sb = new Utf8StringBuilder(); - symbol.AppendMangledName(GetNameMangler(), sb); - int sectionRelativeOffset = alignedOffset + symbol.Offset; - outputInfoBuilder.AddSymbol(new OutputSymbol(sectionIndex, sectionRelativeOffset, sb.ToString())); - } - _symbolMap.Add(symbol, new SymbolTarget( - sectionIndex: sectionIndex, - offset: alignedOffset + symbol.Offset, - size: objectData.Data.Length)); - } - } - - if (objectData.Relocs != null && objectData.Relocs.Length != 0) - { - section.PlacedObjectDataToRelocate.Add(new PlacedObjectData(alignedOffset, objectData)); - } - } - - public void AddSymbolForRange(ISymbolNode symbol, ISymbolNode firstNode, ISymbolNode secondNode) - { - SymbolTarget firstSymbolTarget = _symbolMap[firstNode]; - SymbolTarget secondSymbolTarget = _symbolMap[secondNode]; - Debug.Assert(firstSymbolTarget.SectionIndex == secondSymbolTarget.SectionIndex); - Debug.Assert(firstSymbolTarget.Offset <= secondSymbolTarget.Offset); - - _symbolMap.Add(symbol, new SymbolTarget( - sectionIndex: firstSymbolTarget.SectionIndex, - offset: firstSymbolTarget.Offset, - size: secondSymbolTarget.Offset - firstSymbolTarget.Offset + secondSymbolTarget.Size - )); - } - - /// - /// Get the list of sections that need to be emitted to the output PE file. - /// We filter out name duplicates as we'll end up merging builder sections with the same name - /// into a single output physical section. - /// - public IEnumerable GetSections() - { - List sectionList = new List(); - foreach (Section section in _sections) - { - if (!sectionList.Any((sc) => sc.SectionName == section.Name)) - { - sectionList.Add(new SectionInfo(section.Name, section.Characteristics)); - } - } - - return sectionList; - } - - public void AddSections(OutputInfoBuilder outputInfoBuilder) - { - foreach (Section section in _sections) - { - outputInfoBuilder.AddSection(section); - } - } - - /// - /// Traverse blocks within a single section and use them to calculate final layout - /// of the given section. - /// - /// Section to serialize - /// Logical section address within the output PE file - /// - public BlobBuilder SerializeSection(string name, SectionLocation sectionLocation) - { - if (name == R2RPEBuilder.RelocSectionName) - { - return SerializeRelocationSection(sectionLocation); - } - - if (name == R2RPEBuilder.ExportDataSectionName) - { - return SerializeExportSection(sectionLocation); - } - - BlobBuilder serializedSection = null; - - // Locate logical section index by name - foreach (Section section in _sections.Where((sec) => sec.Name == name)) - { - // Calculate alignment padding - int alignedRVA = (sectionLocation.RelativeVirtualAddress + section.Alignment - 1) & -section.Alignment; - int padding = alignedRVA - sectionLocation.RelativeVirtualAddress; - if (padding > 0) - { - if (serializedSection == null) - { - serializedSection = new BlobBuilder(); - } - serializedSection.WriteBytes(0, padding); - sectionLocation = new SectionLocation( - sectionLocation.RelativeVirtualAddress + padding, - sectionLocation.PointerToRawData + padding); - } - - // Place the section - section.RVAWhenPlaced = sectionLocation.RelativeVirtualAddress; - section.FilePosWhenPlaced = sectionLocation.PointerToRawData; - - if (section.Content.Count != 0) - { - sectionLocation = new SectionLocation( - sectionLocation.RelativeVirtualAddress + section.Content.Count, - sectionLocation.PointerToRawData + section.Content.Count); - - if (serializedSection == null) - { - serializedSection = section.Content; - } - else - { - serializedSection.LinkSuffix(section.Content); - } - } - } - - return serializedSection; - } - - /// - /// Emit the .reloc section based on file relocation information in the individual blocks. - /// We rely on the fact that the .reloc section is emitted last so that, by the time - /// it's getting serialized, all other sections that may contain relocations have already - /// been laid out. - /// - private BlobBuilder SerializeRelocationSection(SectionLocation sectionLocation) - { - // There are 12 bits for the relative offset - const int RelocationTypeShift = 12; - const int MaxRelativeOffsetInBlock = (1 << RelocationTypeShift) - 1; - - // Even though the format doesn't dictate it, it seems customary - // to align the base RVA's on 4K boundaries. - const int BaseRVAAlignment = 1 << RelocationTypeShift; - - BlobBuilder builder = new BlobBuilder(); - int baseRVA = 0; - List offsetsAndTypes = null; - - Section relocSection = FindSection(R2RPEBuilder.RelocSectionName); - if (relocSection != null) - { - relocSection.FilePosWhenPlaced = sectionLocation.PointerToRawData; - relocSection.RVAWhenPlaced = sectionLocation.RelativeVirtualAddress; - builder = relocSection.Content; - } - - // Traverse relocations in all sections in their RVA order - // By now, all "normal" sections with relocations should already have been laid out - foreach (Section section in _sections.OrderBy((sec) => sec.RVAWhenPlaced)) - { - foreach (PlacedObjectData placedObjectData in section.PlacedObjectDataToRelocate) - { - for (int relocIndex = 0; relocIndex < placedObjectData.Relocs.Length; relocIndex++) - { - RelocType relocType = placedObjectData.Relocs[relocIndex].RelocType; - RelocType fileRelocType = Relocation.GetFileRelocationType(relocType); - if (fileRelocType != RelocType.IMAGE_REL_BASED_ABSOLUTE) - { - int relocationRVA = section.RVAWhenPlaced + placedObjectData.Offset + placedObjectData.Relocs[relocIndex].Offset; - if (offsetsAndTypes != null && relocationRVA - baseRVA > MaxRelativeOffsetInBlock) - { - // Need to flush relocation block as the current RVA is too far from base RVA - FlushRelocationBlock(builder, baseRVA, offsetsAndTypes); - offsetsAndTypes = null; - } - if (offsetsAndTypes == null) - { - // Create new relocation block - baseRVA = relocationRVA & -BaseRVAAlignment; - offsetsAndTypes = new List(); - } - ushort offsetAndType = (ushort)(((ushort)fileRelocType << RelocationTypeShift) | (relocationRVA - baseRVA)); - offsetsAndTypes.Add(offsetAndType); - } - } - } - } - - if (offsetsAndTypes != null) - { - FlushRelocationBlock(builder, baseRVA, offsetsAndTypes); - } - - if (builder.Count != 0) - { - _relocationDirectoryEntry = new DirectoryEntry(sectionLocation.RelativeVirtualAddress, builder.Count); - } - - return builder; - } - - /// - /// Serialize a block of relocations into the .reloc section. - /// - /// Output blob builder to receive the serialized relocation block - /// Base RVA of the relocation block - /// 16-bit entries encoding offset relative to the base RVA (low 12 bits) and relocation type (top 4 bite) - private static void FlushRelocationBlock(BlobBuilder builder, int baseRVA, List offsetsAndTypes) - { - // Ensure blocks are 4-byte aligned. This is required by kernel memory manager - // on Windows 8.1 and earlier. - if ((offsetsAndTypes.Count & 1) == 1) - { - offsetsAndTypes.Add(0); - } - - // First, emit the block header: 4 bytes starting RVA, - builder.WriteInt32(baseRVA); - // followed by the total block size comprising this header - // and following 16-bit entries. - builder.WriteInt32(4 + 4 + 2 * offsetsAndTypes.Count); - // Now serialize out the entries - foreach (ushort offsetAndType in offsetsAndTypes) - { - builder.WriteUInt16(offsetAndType); - } - } - - /// - /// Serialize the export symbol table into the export section. - /// - /// RVA and file location of the .edata section - private BlobBuilder SerializeExportSection(SectionLocation sectionLocation) - { - _exportSymbols.MergeSort((es1, es2) => StringComparer.Ordinal.Compare(es1.Name, es2.Name)); - - BlobBuilder builder = new BlobBuilder(); - - int minOrdinal = int.MaxValue; - int maxOrdinal = int.MinValue; - - // First, emit the name table and store the name RVA's for the individual export symbols - // Also, record the ordinal range. - foreach (ExportSymbol symbol in _exportSymbols) - { - symbol.NameRVAWhenPlaced = sectionLocation.RelativeVirtualAddress + builder.Count; - builder.WriteUTF8(symbol.Name); - builder.WriteByte(0); - - if (symbol.Ordinal < minOrdinal) - { - minOrdinal = symbol.Ordinal; - } - if (symbol.Ordinal > maxOrdinal) - { - maxOrdinal = symbol.Ordinal; - } - } - - // Emit the DLL name - int dllNameRVA = sectionLocation.RelativeVirtualAddress + builder.Count; - builder.WriteUTF8(_dllNameForExportDirectoryTable); - builder.WriteByte(0); - - int[] addressTable = new int[maxOrdinal - minOrdinal + 1]; - - // Emit the name pointer table; it should be alphabetically sorted. - // Also, we can now fill in the export address table as we've detected its size - // in the previous pass. - builder.Align(4); - int namePointerTableRVA = sectionLocation.RelativeVirtualAddress + builder.Count; - foreach (ExportSymbol symbol in _exportSymbols) - { - builder.WriteInt32(symbol.NameRVAWhenPlaced); - SymbolTarget symbolTarget = _symbolMap[symbol.Symbol]; - Section symbolSection = _sections[symbolTarget.SectionIndex]; - Debug.Assert(symbolSection.RVAWhenPlaced != 0); - addressTable[symbol.Ordinal - minOrdinal] = symbolSection.RVAWhenPlaced + symbolTarget.Offset; - } - - // Emit the ordinal table - int ordinalTableRVA = sectionLocation.RelativeVirtualAddress + builder.Count; - foreach (ExportSymbol symbol in _exportSymbols) - { - builder.WriteUInt16((ushort)(symbol.Ordinal - minOrdinal)); - } - - // Emit the address table - builder.Align(4); - int addressTableRVA = sectionLocation.RelativeVirtualAddress + builder.Count; - foreach (int addressTableEntry in addressTable) - { - builder.WriteInt32(addressTableEntry); - } - - // Emit the export directory table - builder.Align(4); - int exportDirectoryTableRVA = sectionLocation.RelativeVirtualAddress + builder.Count; - // +0x00: reserved - builder.WriteInt32(0); - // +0x04: TODO: time/date stamp - builder.WriteInt32(0); - // +0x08: major version - builder.WriteInt16(0); - // +0x0A: minor version - builder.WriteInt16(0); - // +0x0C: DLL name RVA - builder.WriteInt32(dllNameRVA); - // +0x10: ordinal base - builder.WriteInt32(minOrdinal); - // +0x14: number of entries in the address table - builder.WriteInt32(addressTable.Length); - // +0x18: number of name pointers - builder.WriteInt32(_exportSymbols.Count); - // +0x1C: export address table RVA - builder.WriteInt32(addressTableRVA); - // +0x20: name pointer RVV - builder.WriteInt32(namePointerTableRVA); - // +0x24: ordinal table RVA - builder.WriteInt32(ordinalTableRVA); - int exportDirectorySize = sectionLocation.RelativeVirtualAddress + builder.Count - exportDirectoryTableRVA; - - _exportDirectoryEntry = new DirectoryEntry(relativeVirtualAddress: exportDirectoryTableRVA, size: exportDirectorySize); - - return builder; - } - - /// - /// Update the PE file directories. Currently this is used to update the export symbol table - /// when export symbols have been added to the section builder. - /// - /// PE directory builder to update - public void UpdateDirectories(PEDirectoriesBuilder directoriesBuilder) - { - if (_corHeaderSymbol != null) - { - SymbolTarget symbolTarget = _symbolMap[_corHeaderSymbol]; - Section section = _sections[symbolTarget.SectionIndex]; - Debug.Assert(section.RVAWhenPlaced != 0); - directoriesBuilder.CorHeaderTable = new DirectoryEntry(section.RVAWhenPlaced + symbolTarget.Offset, _corHeaderSize); - } - - if (_win32ResourcesSymbol != null) - { - SymbolTarget symbolTarget = _symbolMap[_win32ResourcesSymbol]; - Section section = _sections[symbolTarget.SectionIndex]; - Debug.Assert(section.RVAWhenPlaced != 0); - - // Windows has a bug in its resource processing logic that occurs when - // 1. A PE file is loaded as a data file - // 2. The resource data found in the resources has an RVA which has a magnitude greater than the size of the section which holds the resources - // 3. The offset of the start of the resource data from the start of the section is not zero. - // - // As it is impossible to effect condition 1 in the compiler, and changing condition 2 would require bloating the virtual size of the sections, - // instead require that the resource data is located at offset 0 within the section. - // We achieve that by sorting the Win32ResourcesNode as the first node. - Debug.Assert(symbolTarget.Offset == 0); - directoriesBuilder.ResourceTable = new DirectoryEntry(section.RVAWhenPlaced + symbolTarget.Offset, _win32ResourcesSize); - } - - if (_exportDirectoryEntry.Size != 0) - { - directoriesBuilder.ExportTable = _exportDirectoryEntry; - } - - int relocationTableRVA = directoriesBuilder.BaseRelocationTable.RelativeVirtualAddress; - if (relocationTableRVA == 0) - { - relocationTableRVA = _relocationDirectoryEntry.RelativeVirtualAddress; - } - directoriesBuilder.BaseRelocationTable = new DirectoryEntry( - relocationTableRVA, - directoriesBuilder.BaseRelocationTable.Size + _relocationDirectoryEntry.Size); - - if (_entryPointSymbol != null) - { - SymbolTarget symbolTarget = _symbolMap[_entryPointSymbol]; - Section section = _sections[symbolTarget.SectionIndex]; - Debug.Assert(section.RVAWhenPlaced != 0); - directoriesBuilder.AddressOfEntryPoint = section.RVAWhenPlaced + symbolTarget.Offset; - } - - if (_debugDirectorySymbol != null) - { - SymbolTarget symbolTarget = _symbolMap[_debugDirectorySymbol]; - Section section = _sections[symbolTarget.SectionIndex]; - Debug.Assert(section.RVAWhenPlaced != 0); - directoriesBuilder.DebugTable = new DirectoryEntry(section.RVAWhenPlaced + symbolTarget.Offset, _debugDirectorySize); - } - } - - /// - /// Relocate the produced PE file and output the result into a given stream. - /// - /// Blob builder representing the complete PE file - /// Default load address for the image - /// COR header - /// File position of the COR header - /// Stream to receive the relocated PE file - public void RelocateOutputFile( - BlobBuilder peFile, - ulong defaultImageBase, - Stream outputStream) - { - RelocationHelper relocationHelper = new RelocationHelper(outputStream, defaultImageBase, peFile); - - // Traverse relocations in all sections in their RVA order - foreach (Section section in _sections.OrderBy((sec) => sec.RVAWhenPlaced)) - { - int rvaToFilePosDelta = section.FilePosWhenPlaced - section.RVAWhenPlaced; - foreach (PlacedObjectData placedObjectData in section.PlacedObjectDataToRelocate) - { - foreach (Relocation relocation in placedObjectData.Relocs) - { - // Process a single relocation - int relocationRVA = section.RVAWhenPlaced + placedObjectData.Offset + relocation.Offset; - int relocationFilePos = relocationRVA + rvaToFilePosDelta; - - // Flush parts of PE file before the relocation to the output stream - relocationHelper.CopyToFilePosition(relocationFilePos); - - // Look up relocation target - SymbolTarget relocationTarget = _symbolMap[relocation.Target]; - Section targetSection = _sections[relocationTarget.SectionIndex]; - int targetRVA = targetSection.RVAWhenPlaced + relocationTarget.Offset; - int filePosWhenPlaced = targetSection.FilePosWhenPlaced + relocationTarget.Offset; - - // If relocating to a node's size, switch out the target RVA with data length - if (relocation.RelocType == RelocType.IMAGE_REL_SYMBOL_SIZE) - { - targetRVA = relocationTarget.Size; - } - - // Apply the relocation - relocationHelper.ProcessRelocation(relocation.RelocType, relocationRVA, targetRVA, filePosWhenPlaced); - } - } - } - - // Flush remaining PE file blocks after the last relocation - relocationHelper.CopyRestOfFile(); - } - - internal bool HasContent(string sectionName) - { - if (sectionName == R2RPEBuilder.ExportDataSectionName) - return _exportSymbols.Count > 0 && _dllNameForExportDirectoryTable != null; - - if (sectionName == R2RPEBuilder.RelocSectionName) - { - return _sections.Any( - s => s.PlacedObjectDataToRelocate.Any( - d => d.Relocs.Any( - r => Relocation.GetFileRelocationType(r.RelocType) != RelocType.IMAGE_REL_BASED_ABSOLUTE))); - } - - Section section = FindSection(sectionName); - return section != null && section.Content.Count > 0; - } - } -} diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/SymbolFileBuilder.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/SymbolFileBuilder.cs index 3065ae37e16896..80feb9812767b6 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/SymbolFileBuilder.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/SymbolFileBuilder.cs @@ -10,6 +10,7 @@ using Internal.TypeSystem; using ILCompiler.Diagnostics; +using ILCompiler.ObjectWriter; namespace ILCompiler.PEWriter { diff --git a/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/RyuJitCompilation.cs b/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/RyuJitCompilation.cs index 98b214e2b4785a..039b50d699aede 100644 --- a/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/RyuJitCompilation.cs +++ b/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/RyuJitCompilation.cs @@ -106,7 +106,7 @@ protected override void CompileInternal(string outputFile, ObjectDumper dumper) NodeFactory.SetMarkingComplete(); - ObjectWritingOptions options = default; + ObjectWritingOptions options = ObjectWritingOptions.GenerateUnwindInfo; if ((_compilationOptions & RyuJitCompilationOptions.UseDwarf5) != 0) options |= ObjectWritingOptions.UseDwarf5; diff --git a/src/coreclr/tools/aot/crossgen2/Crossgen2RootCommand.cs b/src/coreclr/tools/aot/crossgen2/Crossgen2RootCommand.cs index bcb792d2eb7eaa..dc07e5abc4c43f 100644 --- a/src/coreclr/tools/aot/crossgen2/Crossgen2RootCommand.cs +++ b/src/coreclr/tools/aot/crossgen2/Crossgen2RootCommand.cs @@ -30,6 +30,8 @@ internal class Crossgen2RootCommand : RootCommand new("--mibc", "-m") { DefaultValueFactory = _ => Array.Empty(), Description = SR.MibcFiles }; public Option OutputFilePath { get; } = new("--out", "-o") { Description = SR.OutputFilePath }; + public Option OutputFormat { get; } = + new("--obj-format", "-f") { CustomParser = MakeOutputFormat, DefaultValueFactory = MakeOutputFormat, Description = SR.OutputFormat, HelpName = "arg" }; public Option CompositeRootPath { get; } = new("--compositerootpath", "--crp") { Description = SR.CompositeRootPath }; public Option Optimize { get; } = @@ -161,6 +163,7 @@ public Crossgen2RootCommand(string[] args) : base(SR.Crossgen2BannerText) Options.Add(MaxVectorTBitWidth); Options.Add(MibcFilePaths); Options.Add(OutputFilePath); + Options.Add(OutputFormat); Options.Add(CompositeRootPath); Options.Add(Optimize); Options.Add(OptimizeDisabled); @@ -404,6 +407,18 @@ private static FileLayoutAlgorithm MakeFileLayoutAlgorithm(ArgumentResult result }; } + private static ReadyToRunContainerFormat MakeOutputFormat(ArgumentResult result) + { + if (result.Tokens.Count == 0) + return ReadyToRunContainerFormat.PE; + + return result.Tokens[0].Value.ToLowerInvariant() switch + { + "pe" => ReadyToRunContainerFormat.PE, + _ => throw new CommandLineException(SR.InvalidOutputFormat) + }; + } + #if DEBUG private static bool DumpReproArguments(CodeGenerationFailedException ex) { diff --git a/src/coreclr/tools/aot/crossgen2/Program.cs b/src/coreclr/tools/aot/crossgen2/Program.cs index 90aabdbc349c31..231f2f637ee50e 100644 --- a/src/coreclr/tools/aot/crossgen2/Program.cs +++ b/src/coreclr/tools/aot/crossgen2/Program.cs @@ -616,6 +616,7 @@ private void RunSingleCompilation(Dictionary inFilePaths, Instru .UseHotColdSplitting(Get(_command.HotColdSplitting)) .GenerateOutputFile(outFile) .UseImageBase(_imageBase) + .UseContainerFormat(Get(_command.OutputFormat)) .UseILProvider(ilProvider) .UseBackendOptions(Get(_command.CodegenOptions)) .UseLogger(logger) diff --git a/src/coreclr/tools/aot/crossgen2/Properties/Resources.resx b/src/coreclr/tools/aot/crossgen2/Properties/Resources.resx index 0803acdcfe812d..faaa727911aac1 100644 --- a/src/coreclr/tools/aot/crossgen2/Properties/Resources.resx +++ b/src/coreclr/tools/aot/crossgen2/Properties/Resources.resx @@ -426,4 +426,10 @@ Enable support for cached interface dispatch + + Target output format for the ReadyToRun image + + + Format must be PE + \ No newline at end of file diff --git a/src/coreclr/tools/aot/ilc.slnx b/src/coreclr/tools/aot/ilc.slnx index 49b7198de62088..ba4d00a6f349f9 100644 --- a/src/coreclr/tools/aot/ilc.slnx +++ b/src/coreclr/tools/aot/ilc.slnx @@ -34,6 +34,12 @@ + + + + + +