From 63638bffcfad293f787a177ff4a245ec080949ca Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 11 Sep 2025 17:03:02 -0700 Subject: [PATCH 01/71] 1st pass PEWriter based on CoffObjectWriter --- .../Compiler/ObjectWriter/CoffObjectWriter.cs | 35 +- .../Compiler/ObjectWriter/ObjectWriter.cs | 4 +- .../Compiler/ObjectWriter/PEObjectWriter.cs | 568 ++++++++++++++++++ .../ObjectWriter/PETargetExtensions.cs | 133 ++++ .../ILCompiler.Compiler.csproj | 2 + 5 files changed, 723 insertions(+), 19 deletions(-) create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/PEObjectWriter.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/PETargetExtensions.cs diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CoffObjectWriter.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CoffObjectWriter.cs index d6deb27606a4a1..7eaaa4d1fa851c 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CoffObjectWriter.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CoffObjectWriter.cs @@ -45,12 +45,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 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, string OriginalName); - 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(); @@ -162,7 +162,8 @@ private protected override void CreateSection(ObjectNodeSection section, string } } - _sections.Add(new SectionDefinition(sectionHeader, sectionStream, new List(), comdatName, symbolName)); + string originalName = sectionHeader.Name; + _sections.Add(new SectionDefinition(sectionHeader, sectionStream, new List(), comdatName, symbolName, originalName)); } protected internal override void UpdateSectionAlignment(int sectionIndex, int alignment) @@ -194,7 +195,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); @@ -530,7 +531,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)) @@ -672,7 +673,7 @@ private protected override void EmitDebugSections(IDictionary 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, - }; + ]; private const int RegularSize = sizeof(ushort) + // Machine @@ -731,7 +732,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 +758,7 @@ public void Write(FileStream stream) } } - private sealed class CoffSectionHeader + protected sealed class CoffSectionHeader { public string Name { get; set; } public uint VirtualSize { get; set; } @@ -886,7 +887,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; } @@ -1038,7 +1039,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); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/ObjectWriter.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/ObjectWriter.cs index 6db2bc20b01b25..2136fa82302ecd 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/ObjectWriter.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/ObjectWriter.cs @@ -19,7 +19,7 @@ namespace ILCompiler.ObjectWriter public abstract 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); + 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); private protected readonly NodeFactory _nodeFactory; @@ -34,7 +34,7 @@ private protected sealed record BlockToRelocate(int SectionIndex, long Offset, b // Standard sections private readonly Dictionary _sectionNameToSectionIndex = new(StringComparer.Ordinal); private readonly List _sectionIndexToData = new(); - private readonly List> _sectionIndexToRelocations = new(); + protected readonly List> _sectionIndexToRelocations = new(); // Symbol table private readonly Dictionary _definedSymbols = new(StringComparer.Ordinal); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/PEObjectWriter.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/PEObjectWriter.cs new file mode 100644 index 00000000000000..42fe40b9caf7ed --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/PEObjectWriter.cs @@ -0,0 +1,568 @@ +// 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.IO; +using System.Linq; +using System.Numerics; +using System.Reflection.PortableExecutable; +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 + { + // PE layout computed ahead of writing the file. These are populated by + // EmitSectionsAndLayout so that data directories (e.g. exception table) + // can be filled prior to writing the optional header. + private uint _peSizeOfHeaders; + private uint _peSizeOfImage; + private uint _peSizeOfCode; + private uint _peSizeOfInitializedData; + private uint _peSectionAlignment; + private uint _peFileAlignment; + private uint _pdataRva; + private uint _pdataSize; + // Grouping of object sections by base image section name. Populated by + // EmitSectionsAndLayout so EmitObjectFile / EmitSections can consume + // the grouping without recomputing it. + private List _groupOrder; + private Dictionary> _groupMap; + + public PEObjectWriter(NodeFactory factory, ObjectWritingOptions options) + : base(factory, options) + { + } + + private protected override void CreateSection(ObjectNodeSection section, string comdatName, string symbolName, Stream sectionStream) + { + // COMDAT sections are not supported in PE files + base.CreateSection(section, comdatName: null, symbolName, sectionStream); + } + + private struct PEOptionalHeader + { + public bool IsPE32Plus { get; private set; } + + // 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(Machine machine) + { + IsPE32Plus = machine is Machine.Amd64 or Machine.Arm64; + + // 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(FileStream 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); + + // 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); + WriteLittleEndian(stream, LoaderFlags); + WriteLittleEndian(stream, NumberOfRvaAndSizes); + + // Data directories start at offset 0x60 for PE32 + dataDirectories.WriteTo(stream, (int)NumberOfRvaAndSizes); + } + 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 Set(int index, uint virtualAddress, uint size) + { + if ((uint)index >= (uint)_entries.Length) + throw new ArgumentOutOfRangeException(nameof(index)); + _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 EmitSectionsAndLayout() + { + // Mirror layout logic previously performed inside EmitObjectFile so + // that we can record section virtual addresses and sizes prior to + // emitting the headers (so data directories can reference them). + // Precompute grouping first so image header counts and the + // header size calculation below reflect the final number of + // image sections (base names without '$' suffix). + _groupOrder = new List(); + _groupMap = new Dictionary>(StringComparer.Ordinal); + for (int i = 0; i < _sections.Count; i++) + { + var sec = _sections[i]; + string fullName = sec.Header.Name; + int dollar = fullName.IndexOf('$'); + string baseName = dollar >= 0 ? fullName.Substring(0, dollar) : fullName; + if (!_groupMap.TryGetValue(baseName, out var list)) + { + list = new List<(SectionDefinition, int)>(); + _groupMap[baseName] = list; + _groupOrder.Add(baseName); + } + list.Add((sec, i)); + } + + // Build merged section definitions and grouped symbolic relocations + var mergedSections = new List(); + var groupedSymbolicRelocs = new List>(); + + foreach (string baseName in _groupOrder) + { + var contributions = _groupMap[baseName] + .OrderBy(c => c.Section.Header.Name, StringComparer.Ordinal) + .ToList(); + + // Concatenate contribution streams + long totalSize = 0; + foreach (var (s, idx) in contributions) + totalSize += s.Stream.Length; + + var mergedStream = new MemoryStream((int)Math.Min(totalSize, int.MaxValue)); + + uint contributionOffset = 0u; + var groupSymbolic = new List(); + + foreach (var (s, origIdx) in contributions) + { + s.Stream.Position = 0; + s.Stream.CopyTo(mergedStream); + + // Adjust symbolic relocations from the original section into + // the group's address space + foreach (var symRel in _sectionIndexToRelocations[origIdx]) + { + groupSymbolic.Add(new SymbolicRelocation(symRel.Offset + contributionOffset, symRel.Type, symRel.SymbolName, symRel.Addend)); + } + + contributionOffset += (uint)s.Stream.Length; + } + + // Create a new header for the merged section using the first contribution as a template + var template = contributions[0].Section.Header; + var mergedHeader = new CoffSectionHeader + { + Name = baseName, + SectionCharacteristics = contributions.Aggregate((SectionCharacteristics)0, (acc, t) => acc | t.Section.Header.SectionCharacteristics) + }; + + mergedSections.Add(new SectionDefinition(mergedHeader, mergedStream, new List(), contributions[0].Section.ComdatName, contributions[0].Section.SymbolName, baseName)); + groupedSymbolicRelocs.Add(groupSymbolic); + } + + // Replace section lists with grouped versions for subsequent layout & relocation emission + _sections.Clear(); + _sections.AddRange(mergedSections); + _sectionIndexToRelocations.Clear(); + _sectionIndexToRelocations.AddRange(groupedSymbolicRelocs); + + ushort numberOfSections = (ushort)_groupOrder.Count; + bool isPE32Plus = _nodeFactory.Target.PointerSize == 8; + ushort sizeOfOptionalHeader = (ushort)(isPE32Plus ? 0xF0 : 0xE0); + + // Determine file and section alignment following the same rules as + // the image writer. + int fileAlignment = 0x200; + bool isWindowsOr32bit = _nodeFactory.Target.IsWindows || !isPE32Plus; + if (isWindowsOr32bit) + { + fileAlignment = 0x1000; + } + + uint sectionAlignment = (uint)PEHeaderConstants.SectionAlignment; + if (!isWindowsOr32bit) + { + sectionAlignment = (uint)fileAlignment; + } + + _peFileAlignment = (uint)fileAlignment; + _peSectionAlignment = sectionAlignment; + + // Compute headers size and align to file alignment + uint sizeOfHeadersUnaligned = (uint)(0x80 + 4 + 20 + sizeOfOptionalHeader + 40 * numberOfSections); + uint sizeOfHeaders = (uint)AlignmentHelper.AlignUp((int)sizeOfHeadersUnaligned, (int)fileAlignment); + + // Calculate layout for sections: raw file offsets and virtual addresses + uint pointerToRawData = sizeOfHeaders; + uint virtualAddress = (uint)AlignmentHelper.AlignUp((int)sizeOfHeaders, (int)sectionAlignment); + + uint sizeOfCode = 0; + uint sizeOfInitializedData = 0; + + for (int i = 0; i < _sections.Count; i++) + { + var s = _sections[i]; + var h = s.Header; + h.SizeOfRawData = (uint)s.Stream.Length; + uint rawAligned = h.SectionCharacteristics.HasFlag(SectionCharacteristics.ContainsUninitializedData) ? 0u : (uint)AlignmentHelper.AlignUp((int)h.SizeOfRawData, (int)fileAlignment); + + if (rawAligned != 0) + { + h.PointerToRawData = pointerToRawData; + pointerToRawData += rawAligned; + h.SizeOfRawData = rawAligned; + } + else + { + h.PointerToRawData = 0; + } + + h.VirtualAddress = virtualAddress; + h.VirtualSize = Math.Max(h.VirtualSize, h.SizeOfRawData); + virtualAddress += (uint)AlignmentHelper.AlignUp((int)h.VirtualSize, (int)sectionAlignment); + + if (h.SectionCharacteristics.HasFlag(SectionCharacteristics.ContainsCode)) + sizeOfCode += h.SizeOfRawData; + else if (!h.SectionCharacteristics.HasFlag(SectionCharacteristics.ContainsUninitializedData)) + sizeOfInitializedData += h.SizeOfRawData; + + if (h.Name == ".pdata") + { + _pdataRva = h.VirtualAddress; + _pdataSize = h.VirtualSize != 0 ? h.VirtualSize : h.SizeOfRawData; + } + } + + uint sizeOfImage = (uint)AlignmentHelper.AlignUp((int)virtualAddress, (int)sectionAlignment); + + _peSizeOfHeaders = sizeOfHeaders; + _peSizeOfImage = sizeOfImage; + _peSizeOfCode = sizeOfCode; + _peSizeOfInitializedData = sizeOfInitializedData; + } + + private protected override void EmitObjectFile(string objectFilePath) + { + using var stream = new FileStream(objectFilePath, FileMode.Create, FileAccess.Write); + + // Write a minimal DOS stub and set the e_lfanew to 0x80 where we'll + // place the PE headers. + var dos = new byte[0x80]; + "MZ"u8.CopyTo(dos); + BinaryPrimitives.WriteInt32LittleEndian(dos.AsSpan(0x3c), 0x80); + stream.Write(dos); + + ushort numberOfSections = (ushort)_groupOrder.Count; + bool isPE32Plus = _nodeFactory.Target.PointerSize == 8; + ushort sizeOfOptionalHeader = (ushort)(isPE32Plus ? 0xF0 : 0xE0); + + int fileAlignment = 0x200; + bool isWindowsOr32bit = _nodeFactory.Target.IsWindows || !isPE32Plus; + 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; + } + + 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 = (uint)fileAlignment; + } + + // Use layout computed in EmitSectionsAndLayout + uint sizeOfHeaders = _peSizeOfHeaders; + uint sizeOfCode = _peSizeOfCode; + uint sizeOfInitializedData = _peSizeOfInitializedData; + uint sizeOfImage = _peSizeOfImage; + + // Write PE Signature at e_lfanew (0x80) + stream.Position = 0x80; + stream.Write("PE\0\0"u8); + + Characteristics characteristics = Characteristics.ExecutableImage | Characteristics.Dll; + characteristics |= isPE32Plus ? Characteristics.LargeAddressAware : Characteristics.Bit32Machine; + + // COFF File Header (use the shared CoffHeader type) + var coffHeader = new CoffHeader + { + Machine = _machine, + NumberOfSections = (uint)numberOfSections, + TimeDateStamp = (uint)DateTimeOffset.UtcNow.ToUnixTimeSeconds(), + PointerToSymbolTable = 0, + NumberOfSymbols = 0, + SizeOfOptionalHeader = sizeOfOptionalHeader, + Characteristics = characteristics, + }; + + coffHeader.Write(stream); + + var peOptional = new PEOptionalHeader(_machine) + { + 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 = sectionAlignment; + peOptional.FileAlignment = (uint)fileAlignment; + + // 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 exception table with .pdata if present. + if (_pdataSize != 0) + { + dataDirs.Set((int)ImageDirectoryEntry.Exception, _pdataRva, _pdataSize); + } + peOptional.Write(stream, dataDirs); + + CoffStringTable stringTable = new(); + int sectionIndex = 0; + // Emit headers for each group (baseName order preserved) + foreach (string baseName in _groupOrder) + { + var contributions = _groupMap[baseName]; + // The header we will emit for this image section is based on + // the first contribution's header but needs to have the + // section name without the '$' suffix. + var groupHeader = contributions[0].Section.Header; + string savedName = groupHeader.Name; + groupHeader.Name = baseName; + groupHeader.Write(stream, stringTable); + + // Restore the name to avoid affecting any other logic that + // may rely on the original value stored in the SectionDefinition. + groupHeader.Name = savedName; + + sectionIndex++; + } + } + + 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/aot/ILCompiler.Compiler/Compiler/ObjectWriter/PETargetExtensions.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/PETargetExtensions.cs new file mode 100644 index 00000000000000..f00cac6af8b153 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/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 int 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/ILCompiler.Compiler.csproj b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj index 86886d93c2baed..329d02c804abdf 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj @@ -631,6 +631,8 @@ + + From e683065e36d0eaa3d771c7fa6e66d5d4cfd744ea Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 12 Sep 2025 11:22:38 -0700 Subject: [PATCH 02/71] Add export header support --- .../Compiler/ObjectWriter/PEObjectWriter.cs | 208 ++++++++++++++++++ 1 file changed, 208 insertions(+) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/PEObjectWriter.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/PEObjectWriter.cs index 42fe40b9caf7ed..cc60fa9c2fd533 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/PEObjectWriter.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/PEObjectWriter.cs @@ -4,6 +4,7 @@ using System; using System.Buffers.Binary; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Numerics; @@ -31,17 +32,33 @@ internal sealed class PEObjectWriter : CoffObjectWriter private uint _peFileAlignment; private uint _pdataRva; private uint _pdataSize; + + // Add export directory fields + private uint _exportRva; + private uint _exportSize; + // Grouping of object sections by base image section name. Populated by // EmitSectionsAndLayout so EmitObjectFile / EmitSections can consume // the grouping without recomputing it. private List _groupOrder; private Dictionary> _groupMap; + private HashSet _exportedSymbols = new(); + private readonly Dictionary _exportedSymbolDefinitions = new(StringComparer.Ordinal); + public PEObjectWriter(NodeFactory factory, ObjectWritingOptions options) : base(factory, options) { } + public void AddExportedSymbol(string symbol) + { + if (!string.IsNullOrEmpty(symbol)) + { + _exportedSymbols.Add(symbol); + } + } + private protected override void CreateSection(ObjectNodeSection section, string comdatName, string symbolName, Stream sectionStream) { // COMDAT sections are not supported in PE files @@ -272,6 +289,18 @@ private enum ImageDirectoryEntry Reserved = 15, } + private protected override void EmitSymbolTable(IDictionary definedSymbols, SortedSet undefinedSymbols) + { + foreach (var (symbolName, symbolDefinition) in definedSymbols) + { + if (_exportedSymbols.Contains(symbolName)) + { + _exportedSymbolDefinitions[symbolName] = symbolDefinition; + } + } + base.EmitSymbolTable(definedSymbols, undefinedSymbols); + } + private protected override void EmitSectionsAndLayout() { // Mirror layout logic previously performed inside EmitObjectFile so @@ -417,6 +446,45 @@ private protected override void EmitSectionsAndLayout() } } + // If we have exports, create an export data section (.edata) and place it after other sections. + if (_exportedSymbolDefinitions.Count > 0) + { + var origToMerged = new Dictionary(); + for (int mi = 0; mi < _groupOrder.Count; mi++) + { + foreach (var (sec, origIdx) in _groupMap[_groupOrder[mi]]) + origToMerged[origIdx] = mi; + } + + uint edataRva = virtualAddress; + var exportDir = new ExportDirectory(_exportedSymbolDefinitions, origToMerged, _sections, edataRva); + var edataSection = exportDir.Section; + var edataHeader = edataSection.Header; + + // Compute raw and virtual sizes and update pointers + edataHeader.SizeOfRawData = (uint)edataSection.Stream.Length; + uint edataRawAligned = (uint)AlignmentHelper.AlignUp((int)edataHeader.SizeOfRawData, (int)fileAlignment); + edataHeader.PointerToRawData = pointerToRawData; + pointerToRawData += edataRawAligned; + edataHeader.SizeOfRawData = edataRawAligned; + + edataHeader.VirtualAddress = virtualAddress; + edataHeader.VirtualSize = Math.Max(edataHeader.VirtualSize, (uint)edataSection.Stream.Length); + virtualAddress += (uint)AlignmentHelper.AlignUp((int)edataHeader.VirtualSize, (int)sectionAlignment); + + // Add to sections and bookkeeping + _sections.Add(edataSection); + _sectionIndexToRelocations.Add(new List()); + _groupOrder.Add(".edata"); + _groupMap[".edata"] = new List<(SectionDefinition, int)> { (edataSection, -1) }; + + sizeOfInitializedData += edataHeader.SizeOfRawData; + + // Set export directory fields for header + _exportRva = edataRva + (uint)exportDir.ExportDirectoryOffset; + _exportSize = (uint)exportDir.ExportDirectorySize; + } + uint sizeOfImage = (uint)AlignmentHelper.AlignUp((int)virtualAddress, (int)sectionAlignment); _peSizeOfHeaders = sizeOfHeaders; @@ -533,6 +601,11 @@ private protected override void EmitObjectFile(string objectFilePath) { dataDirs.Set((int)ImageDirectoryEntry.Exception, _pdataRva, _pdataSize); } + // Populate export table if present + if (_exportSize != 0) + { + dataDirs.Set((int)ImageDirectoryEntry.Export, _exportRva, _exportSize); + } peOptional.Write(stream, dataDirs); CoffStringTable stringTable = new(); @@ -555,6 +628,29 @@ private protected override void EmitObjectFile(string objectFilePath) sectionIndex++; } + + // Write section content and relocations + foreach (string baseName in _groupOrder) + { + foreach (var (section, _) in _groupMap[baseName]) + { + if (!section.Header.SectionCharacteristics.HasFlag(SectionCharacteristics.ContainsUninitializedData)) + { + Debug.Assert(stream.Position == section.Header.PointerToRawData); + section.Stream.Position = 0; + section.Stream.CopyTo(stream); + } + + // TODO: We don't want to support COFF relocations here. + if (section.Relocations.Count > 0) + { + foreach (var relocation in section.Relocations) + { + relocation.Write(stream); + } + } + } + } } private static unsafe void WriteLittleEndian(Stream stream, T value) @@ -564,5 +660,117 @@ private static unsafe void WriteLittleEndian(Stream stream, T value) value.WriteLittleEndian(buffer); stream.Write(buffer); } + + private sealed class ExportDirectory + { + public SectionDefinition Section { get; } + public int ExportDirectoryOffset { get; } + public int ExportDirectorySize { get; } + + public ExportDirectory(IDictionary exportedSymbolDefinitions, Dictionary origToMerged, List mergedSections, uint edataRva) + { + var exports = exportedSymbolDefinitions.Select(kv => (Name: kv.Key, Def: kv.Value)).OrderBy(e => e.Name, StringComparer.Ordinal).ToArray(); + + using var ms = new MemoryStream(); + + // Emit names + var nameRvas = new uint[exports.Length]; + for (int i = 0; i < exports.Length; i++) + { + nameRvas[i] = edataRva + (uint)ms.Position; + ms.Write(System.Text.Encoding.UTF8.GetBytes(exports[i].Name)); + ms.WriteByte(0); + } + + // DLL name + string dllName = "UNKNOWN"; + uint dllNameRva = edataRva + (uint)ms.Position; + ms.Write(System.Text.Encoding.UTF8.GetBytes(dllName)); + ms.WriteByte(0); + + // Align to 4 + while (ms.Position % 4 != 0) + ms.WriteByte(0); + + // Name pointer table + uint namePointerTableRva = edataRva + (uint)ms.Position; + int minOrdinal = 1; + int count = exports.Length; + int maxOrdinal = minOrdinal + count - 1; + var addressTable = new int[maxOrdinal - minOrdinal + 1]; + + for (int i = 0; i < exports.Length; i++) + { + // name RVA + WriteLittleEndian(ms, (int)nameRvas[i]); + + var def = exports[i].Def; + int origIdx = def.SectionIndex; + int mergedIdx = origToMerged.TryGetValue(origIdx, out var mi) ? mi : 0; + uint targetRva = mergedSections[mergedIdx].Header.VirtualAddress + (uint)def.Value; + addressTable[i] = (int)targetRva; + } + + // Ordinal table + uint ordinalTableRva = edataRva + (uint)ms.Position; + Span tmp2 = stackalloc byte[2]; + for (int i = 0; i < exports.Length; i++) + { + BinaryPrimitives.WriteUInt16LittleEndian(tmp2, (ushort)(i)); + ms.Write(tmp2); + } + + // Address table + while (ms.Position % 4 != 0) + ms.WriteByte(0); + + uint addressTableRva = edataRva + (uint)ms.Position; + Span tmp4a = stackalloc byte[4]; + for (int i = 0; i < addressTable.Length; i++) + { + WriteLittleEndian(ms, addressTable[i]); + } + + // Export directory table + while (ms.Position % 4 != 0) + ms.WriteByte(0); + uint exportDirectoryTableRva = edataRva + (uint)ms.Position; + + // +0x00: reserved + WriteLittleEndian(ms, 0); + // +0x04: time/date stamp + WriteLittleEndian(ms, 0); + // +0x08: major version + WriteLittleEndian(ms, 0); + // +0x0A: minor version + WriteLittleEndian(ms, 0); + // +0x0C: DLL name RVA + WriteLittleEndian(ms, (int)dllNameRva); + // +0x10: ordinal base + WriteLittleEndian(ms, minOrdinal); + // +0x14: number of entries in the address table + WriteLittleEndian(ms, addressTable.Length); + // +0x18: number of name pointers + WriteLittleEndian(ms, exports.Length); + // +0x1C: export address table RVA + WriteLittleEndian(ms, (int)addressTableRva); + // +0x20: name pointer RVA + WriteLittleEndian(ms, (int)namePointerTableRva); + // +0x24: ordinal table RVA + WriteLittleEndian(ms, (int)ordinalTableRva); + + int exportDirectorySize = (int)(ms.Position - exportDirectoryTableRva); + int exportDirectoryOffset = (int)exportDirectoryTableRva - (int)edataRva; + + var header = new CoffSectionHeader + { + Name = ".edata", + SectionCharacteristics = SectionCharacteristics.MemRead | SectionCharacteristics.ContainsInitializedData + }; + Section = new SectionDefinition(header, ms, new List(), null, null, ".edata"); + ExportDirectoryOffset = exportDirectoryOffset; + ExportDirectorySize = exportDirectorySize; + } + } } } From 485ce2d668dd71a897571c30144ec8a554f7211d Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 12 Sep 2025 14:02:26 -0700 Subject: [PATCH 03/71] Emit PE relocs instead of COFF relocs and streamline the exports directory code a little --- .../Compiler/ObjectWriter/PEObjectWriter.cs | 194 ++++++++++++++---- 1 file changed, 150 insertions(+), 44 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/PEObjectWriter.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/PEObjectWriter.cs index cc60fa9c2fd533..01fb846c87ea8a 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/PEObjectWriter.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/PEObjectWriter.cs @@ -33,18 +33,26 @@ internal sealed class PEObjectWriter : CoffObjectWriter private uint _pdataRva; private uint _pdataSize; - // Add export directory fields private uint _exportRva; private uint _exportSize; + // Base relocation (.reloc) bookkeeping + private readonly SortedDictionary> _baseRelocMap = new(); + private uint _baseRelocRva; + private uint _baseRelocSize; + + // Emitted Symbol Table info + private record PESymbol(string Name, uint Offset); + private readonly List _exportedPESymbols = new(); + // Grouping of object sections by base image section name. Populated by // EmitSectionsAndLayout so EmitObjectFile / EmitSections can consume // the grouping without recomputing it. - private List _groupOrder; - private Dictionary> _groupMap; + private readonly List _groupOrder = new(); + private readonly Dictionary> _groupMap = new Dictionary>(StringComparer.Ordinal); + private readonly Dictionary _origToMergedSectionIndex = new(); - private HashSet _exportedSymbols = new(); - private readonly Dictionary _exportedSymbolDefinitions = new(StringComparer.Ordinal); + private HashSet _exportedSymbolNames = new(); public PEObjectWriter(NodeFactory factory, ObjectWritingOptions options) : base(factory, options) @@ -55,7 +63,7 @@ public void AddExportedSymbol(string symbol) { if (!string.IsNullOrEmpty(symbol)) { - _exportedSymbols.Add(symbol); + _exportedSymbolNames.Add(symbol); } } @@ -291,14 +299,21 @@ private enum ImageDirectoryEntry private protected override void EmitSymbolTable(IDictionary definedSymbols, SortedSet undefinedSymbols) { + if (undefinedSymbols.Count > 0) + { + throw new NotSupportedException("PEObjectWriter does not support undefined symbols"); + } foreach (var (symbolName, symbolDefinition) in definedSymbols) { - if (_exportedSymbols.Contains(symbolName)) + if (!_exportedSymbolNames.Contains(symbolName)) { - _exportedSymbolDefinitions[symbolName] = symbolDefinition; + continue; } + int mergedIdx = _origToMergedSectionIndex.TryGetValue(symbolDefinition.SectionIndex, out var mi) ? mi : 0; + uint targetRva = _sections[mergedIdx].Header.VirtualAddress + (uint)symbolDefinition.Value; + PESymbol sym = new(symbolName, targetRva); + _exportedPESymbols.Add(sym); } - base.EmitSymbolTable(definedSymbols, undefinedSymbols); } private protected override void EmitSectionsAndLayout() @@ -309,8 +324,6 @@ private protected override void EmitSectionsAndLayout() // Precompute grouping first so image header counts and the // header size calculation below reflect the final number of // image sections (base names without '$' suffix). - _groupOrder = new List(); - _groupMap = new Dictionary>(StringComparer.Ordinal); for (int i = 0; i < _sections.Count; i++) { var sec = _sections[i]; @@ -324,6 +337,7 @@ private protected override void EmitSectionsAndLayout() _groupOrder.Add(baseName); } list.Add((sec, i)); + _origToMergedSectionIndex.Add(i, _groupOrder.IndexOf(baseName)); } // Build merged section definitions and grouped symbolic relocations @@ -447,17 +461,10 @@ private protected override void EmitSectionsAndLayout() } // If we have exports, create an export data section (.edata) and place it after other sections. - if (_exportedSymbolDefinitions.Count > 0) + if (_exportedSymbolNames.Count > 0) { - var origToMerged = new Dictionary(); - for (int mi = 0; mi < _groupOrder.Count; mi++) - { - foreach (var (sec, origIdx) in _groupMap[_groupOrder[mi]]) - origToMerged[origIdx] = mi; - } - uint edataRva = virtualAddress; - var exportDir = new ExportDirectory(_exportedSymbolDefinitions, origToMerged, _sections, edataRva); + var exportDir = new ExportDirectory(_exportedPESymbols, moduleName: "UNKNOWN", edataRva); var edataSection = exportDir.Section; var edataHeader = edataSection.Header; @@ -493,6 +500,115 @@ private protected override void EmitSectionsAndLayout() _peSizeOfInitializedData = sizeOfInitializedData; } + private protected override void EmitRelocations(int sectionIndex, List relocationList) + { + base.EmitRelocations(sectionIndex, relocationList); + + // 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). When this + // override is invoked for the last section, synthesize the + // .reloc section from the collected entries and append it to + // the image section list so that it will be emitted by + // EmitObjectFile. + + foreach (var symRel in relocationList) + { + RelocType fileRelocType = Relocation.GetFileRelocationType(symRel.Type); + if (fileRelocType == RelocType.IMAGE_REL_BASED_ABSOLUTE) + continue; + + uint targetRva = _sections[sectionIndex].Header.VirtualAddress + (uint)symRel.Offset; + uint pageRva = targetRva & ~0xFFFu; + ushort offsetInPage = (ushort)(targetRva & 0xFFFu); + ushort entry = (ushort)(((ushort)fileRelocType << 12) | offsetInPage); + + if (!_baseRelocMap.TryGetValue(pageRva, out var list)) + { + list = new List(); + _baseRelocMap.Add(pageRva, list); + } + list.Add(entry); + } + + // If this is the last section being processed, create the + // .reloc section from the accumulated relocation blocks. + if (sectionIndex == _sectionIndexToRelocations.Count - 1 && _baseRelocMap.Count > 0) + { + var ms = new MemoryStream(); + + foreach (var kv in _baseRelocMap) + { + uint pageRva = kv.Key; + List entries = kv.Value; + entries.Sort(); + + int entriesSize = entries.Count * 2; + int sizeOfBlock = 8 + entriesSize; + // Pad to 4-byte alignment as customary + if ((sizeOfBlock & 3) != 0) + sizeOfBlock += 2; + + byte[] headerBuf = new byte[8]; + BinaryPrimitives.WriteUInt32LittleEndian(headerBuf.AsSpan(0, 4), pageRva); + BinaryPrimitives.WriteUInt32LittleEndian(headerBuf.AsSpan(4, 4), (uint)sizeOfBlock); + ms.Write(headerBuf, 0, headerBuf.Length); + + // Emit entries + foreach (ushort e in entries) + { + byte[] w = new byte[2]; + BinaryPrimitives.WriteUInt16LittleEndian(w.AsSpan(), e); + ms.Write(w, 0, 2); + } + + // Ensure block is 4-byte aligned by padding a WORD if needed + if (((entriesSize) & 3) != 0) + { + byte[] pad = new byte[2]; + BinaryPrimitives.WriteUInt16LittleEndian(pad.AsSpan(), 0); + ms.Write(pad, 0, 2); + } + } + + // Create a new section for .reloc and compute its raw/virtual layout + var relocHeader = new CoffSectionHeader + { + Name = ".reloc", + SectionCharacteristics = SectionCharacteristics.MemRead | SectionCharacteristics.ContainsInitializedData + }; + + var relocSection = new SectionDefinition(relocHeader, ms, new List(), null, null, ".reloc"); + + // Compute pointer to raw data: find last used raw end and align + uint lastRawEnd = 0; + uint lastVEnd = 0; + foreach (var s in _sections) + { + if (s.Header.PointerToRawData != 0) + { + lastRawEnd = Math.Max(lastRawEnd, s.Header.PointerToRawData + s.Header.SizeOfRawData); + } + uint vEnd = s.Header.VirtualAddress + (uint)AlignmentHelper.AlignUp((int)s.Header.VirtualSize, (int)_peSectionAlignment); + lastVEnd = Math.Max(lastVEnd, vEnd); + } + + uint pointerToRawData = lastRawEnd == 0 ? (uint)AlignmentHelper.AlignUp((int)_peSizeOfHeaders, (int)_peFileAlignment) : lastRawEnd; + relocHeader.PointerToRawData = pointerToRawData; + relocHeader.SizeOfRawData = (uint)AlignmentHelper.AlignUp((int)ms.Length, (int)_peFileAlignment); + relocHeader.VirtualAddress = (uint)AlignmentHelper.AlignUp((int)lastVEnd, (int)_peSectionAlignment); + relocHeader.VirtualSize = (uint)ms.Length; + + _sections.Add(relocSection); + _sectionIndexToRelocations.Add(new List()); + _groupOrder.Add(".reloc"); + _groupMap[".reloc"] = new List<(SectionDefinition, int)> { (relocSection, -1) }; + + _baseRelocRva = relocHeader.VirtualAddress; + _baseRelocSize = (uint)ms.Length; + } + } + private protected override void EmitObjectFile(string objectFilePath) { using var stream = new FileStream(objectFilePath, FileMode.Create, FileAccess.Write); @@ -548,7 +664,7 @@ private protected override void EmitObjectFile(string objectFilePath) Characteristics characteristics = Characteristics.ExecutableImage | Characteristics.Dll; characteristics |= isPE32Plus ? Characteristics.LargeAddressAware : Characteristics.Bit32Machine; - // COFF File Header (use the shared CoffHeader type) + // COFF File Header var coffHeader = new CoffHeader { Machine = _machine, @@ -606,6 +722,11 @@ private protected override void EmitObjectFile(string objectFilePath) { dataDirs.Set((int)ImageDirectoryEntry.Export, _exportRva, _exportSize); } + // Populate base relocation directory if present + if (_baseRelocSize != 0) + { + dataDirs.Set((int)ImageDirectoryEntry.BaseRelocation, _baseRelocRva, _baseRelocSize); + } peOptional.Write(stream, dataDirs); CoffStringTable stringTable = new(); @@ -629,7 +750,7 @@ private protected override void EmitObjectFile(string objectFilePath) sectionIndex++; } - // Write section content and relocations + // Write section content foreach (string baseName in _groupOrder) { foreach (var (section, _) in _groupMap[baseName]) @@ -640,15 +761,6 @@ private protected override void EmitObjectFile(string objectFilePath) section.Stream.Position = 0; section.Stream.CopyTo(stream); } - - // TODO: We don't want to support COFF relocations here. - if (section.Relocations.Count > 0) - { - foreach (var relocation in section.Relocations) - { - relocation.Write(stream); - } - } } } } @@ -667,9 +779,9 @@ private sealed class ExportDirectory public int ExportDirectoryOffset { get; } public int ExportDirectorySize { get; } - public ExportDirectory(IDictionary exportedSymbolDefinitions, Dictionary origToMerged, List mergedSections, uint edataRva) + public ExportDirectory(IEnumerable exportedPESymbols, string moduleName, uint edataRva) { - var exports = exportedSymbolDefinitions.Select(kv => (Name: kv.Key, Def: kv.Value)).OrderBy(e => e.Name, StringComparer.Ordinal).ToArray(); + var exports = exportedPESymbols.OrderBy(e => e.Name, StringComparer.Ordinal).ToArray(); using var ms = new MemoryStream(); @@ -682,10 +794,9 @@ public ExportDirectory(IDictionary exportedSymbolDefin ms.WriteByte(0); } - // DLL name - string dllName = "UNKNOWN"; - uint dllNameRva = edataRva + (uint)ms.Position; - ms.Write(System.Text.Encoding.UTF8.GetBytes(dllName)); + // Module name + uint moduleNameRva = edataRva + (uint)ms.Position; + ms.Write(System.Text.Encoding.UTF8.GetBytes(moduleName)); ms.WriteByte(0); // Align to 4 @@ -703,12 +814,7 @@ public ExportDirectory(IDictionary exportedSymbolDefin { // name RVA WriteLittleEndian(ms, (int)nameRvas[i]); - - var def = exports[i].Def; - int origIdx = def.SectionIndex; - int mergedIdx = origToMerged.TryGetValue(origIdx, out var mi) ? mi : 0; - uint targetRva = mergedSections[mergedIdx].Header.VirtualAddress + (uint)def.Value; - addressTable[i] = (int)targetRva; + addressTable[i] = (int)exports[i].Offset; } // Ordinal table @@ -745,7 +851,7 @@ public ExportDirectory(IDictionary exportedSymbolDefin // +0x0A: minor version WriteLittleEndian(ms, 0); // +0x0C: DLL name RVA - WriteLittleEndian(ms, (int)dllNameRva); + WriteLittleEndian(ms, (int)moduleNameRva); // +0x10: ordinal base WriteLittleEndian(ms, minOrdinal); // +0x14: number of entries in the address table From 70db62486548911b1706e59d52e947cccc91bf49 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 12 Sep 2025 16:11:27 -0700 Subject: [PATCH 04/71] Move files to Common in prep for inclusion in cg2 --- .../CodeView/CodeViewFileTableBuilder.cs | 0 .../ObjectWriter/CodeView/CodeViewNative.cs | 0 .../CodeView/CodeViewSymbolsBuilder.cs | 0 .../CodeView/CodeViewTypesBuilder.cs | 0 .../Compiler/ObjectWriter/CoffObjectWriter.cs | 0 .../ObjectWriter/Dwarf/DwarfAbbrev.cs | 0 .../ObjectWriter/Dwarf/DwarfBuilder.cs | 0 .../ObjectWriter/Dwarf/DwarfCfiOpcode.cs | 0 .../Compiler/ObjectWriter/Dwarf/DwarfCie.cs | 0 .../ObjectWriter/Dwarf/DwarfEhFrame.cs | 0 .../Dwarf/DwarfExpressionBuilder.cs | 0 .../Compiler/ObjectWriter/Dwarf/DwarfFde.cs | 0 .../ObjectWriter/Dwarf/DwarfFileName.cs | 0 .../ObjectWriter/Dwarf/DwarfHelper.cs | 0 .../Compiler/ObjectWriter/Dwarf/DwarfInfo.cs | 0 .../ObjectWriter/Dwarf/DwarfInfoWriter.cs | 0 .../Dwarf/DwarfLineProgramTableWriter.cs | 0 .../Dwarf/DwarfLineSequenceWriter.cs | 0 .../ObjectWriter/Dwarf/DwarfNative.cs | 0 .../Eabi/EabiAttributesBuilder.cs | 0 .../Compiler/ObjectWriter/Eabi/EabiNative.cs | 0 .../ObjectWriter/Eabi/EabiUnwindConverter.cs | 0 .../Compiler/ObjectWriter/ElfNative.cs | 0 .../Compiler/ObjectWriter/ElfObjectWriter.cs | 0 .../Compiler/ObjectWriter/MachNative.cs | 0 .../Compiler/ObjectWriter/MachObjectWriter.cs | 0 .../Compiler/ObjectWriter/ObjectWriter.cs | 0 .../ObjectWriter/ObjectWritingOptions.cs | 0 .../Compiler/ObjectWriter/PEObjectWriter.cs | 0 .../ObjectWriter/PETargetExtensions.cs | 0 .../Compiler/ObjectWriter/SectionData.cs | 0 .../Compiler/ObjectWriter/SectionWriter.cs | 0 .../ObjectWriter/StringTableBuilder.cs | 0 .../Compiler/ObjectWriter/UnixObjectWriter.cs | 0 .../ILCompiler.Compiler.csproj | 68 +++++++++---------- 35 files changed, 34 insertions(+), 34 deletions(-) rename src/coreclr/tools/{aot/ILCompiler.Compiler => Common}/Compiler/ObjectWriter/CodeView/CodeViewFileTableBuilder.cs (100%) rename src/coreclr/tools/{aot/ILCompiler.Compiler => Common}/Compiler/ObjectWriter/CodeView/CodeViewNative.cs (100%) rename src/coreclr/tools/{aot/ILCompiler.Compiler => Common}/Compiler/ObjectWriter/CodeView/CodeViewSymbolsBuilder.cs (100%) rename src/coreclr/tools/{aot/ILCompiler.Compiler => Common}/Compiler/ObjectWriter/CodeView/CodeViewTypesBuilder.cs (100%) rename src/coreclr/tools/{aot/ILCompiler.Compiler => Common}/Compiler/ObjectWriter/CoffObjectWriter.cs (100%) rename src/coreclr/tools/{aot/ILCompiler.Compiler => Common}/Compiler/ObjectWriter/Dwarf/DwarfAbbrev.cs (100%) rename src/coreclr/tools/{aot/ILCompiler.Compiler => Common}/Compiler/ObjectWriter/Dwarf/DwarfBuilder.cs (100%) rename src/coreclr/tools/{aot/ILCompiler.Compiler => Common}/Compiler/ObjectWriter/Dwarf/DwarfCfiOpcode.cs (100%) rename src/coreclr/tools/{aot/ILCompiler.Compiler => Common}/Compiler/ObjectWriter/Dwarf/DwarfCie.cs (100%) rename src/coreclr/tools/{aot/ILCompiler.Compiler => Common}/Compiler/ObjectWriter/Dwarf/DwarfEhFrame.cs (100%) rename src/coreclr/tools/{aot/ILCompiler.Compiler => Common}/Compiler/ObjectWriter/Dwarf/DwarfExpressionBuilder.cs (100%) rename src/coreclr/tools/{aot/ILCompiler.Compiler => Common}/Compiler/ObjectWriter/Dwarf/DwarfFde.cs (100%) rename src/coreclr/tools/{aot/ILCompiler.Compiler => Common}/Compiler/ObjectWriter/Dwarf/DwarfFileName.cs (100%) rename src/coreclr/tools/{aot/ILCompiler.Compiler => Common}/Compiler/ObjectWriter/Dwarf/DwarfHelper.cs (100%) rename src/coreclr/tools/{aot/ILCompiler.Compiler => Common}/Compiler/ObjectWriter/Dwarf/DwarfInfo.cs (100%) rename src/coreclr/tools/{aot/ILCompiler.Compiler => Common}/Compiler/ObjectWriter/Dwarf/DwarfInfoWriter.cs (100%) rename src/coreclr/tools/{aot/ILCompiler.Compiler => Common}/Compiler/ObjectWriter/Dwarf/DwarfLineProgramTableWriter.cs (100%) rename src/coreclr/tools/{aot/ILCompiler.Compiler => Common}/Compiler/ObjectWriter/Dwarf/DwarfLineSequenceWriter.cs (100%) rename src/coreclr/tools/{aot/ILCompiler.Compiler => Common}/Compiler/ObjectWriter/Dwarf/DwarfNative.cs (100%) rename src/coreclr/tools/{aot/ILCompiler.Compiler => Common}/Compiler/ObjectWriter/Eabi/EabiAttributesBuilder.cs (100%) rename src/coreclr/tools/{aot/ILCompiler.Compiler => Common}/Compiler/ObjectWriter/Eabi/EabiNative.cs (100%) rename src/coreclr/tools/{aot/ILCompiler.Compiler => Common}/Compiler/ObjectWriter/Eabi/EabiUnwindConverter.cs (100%) rename src/coreclr/tools/{aot/ILCompiler.Compiler => Common}/Compiler/ObjectWriter/ElfNative.cs (100%) rename src/coreclr/tools/{aot/ILCompiler.Compiler => Common}/Compiler/ObjectWriter/ElfObjectWriter.cs (100%) rename src/coreclr/tools/{aot/ILCompiler.Compiler => Common}/Compiler/ObjectWriter/MachNative.cs (100%) rename src/coreclr/tools/{aot/ILCompiler.Compiler => Common}/Compiler/ObjectWriter/MachObjectWriter.cs (100%) rename src/coreclr/tools/{aot/ILCompiler.Compiler => Common}/Compiler/ObjectWriter/ObjectWriter.cs (100%) rename src/coreclr/tools/{aot/ILCompiler.Compiler => Common}/Compiler/ObjectWriter/ObjectWritingOptions.cs (100%) rename src/coreclr/tools/{aot/ILCompiler.Compiler => Common}/Compiler/ObjectWriter/PEObjectWriter.cs (100%) rename src/coreclr/tools/{aot/ILCompiler.Compiler => Common}/Compiler/ObjectWriter/PETargetExtensions.cs (100%) rename src/coreclr/tools/{aot/ILCompiler.Compiler => Common}/Compiler/ObjectWriter/SectionData.cs (100%) rename src/coreclr/tools/{aot/ILCompiler.Compiler => Common}/Compiler/ObjectWriter/SectionWriter.cs (100%) rename src/coreclr/tools/{aot/ILCompiler.Compiler => Common}/Compiler/ObjectWriter/StringTableBuilder.cs (100%) rename src/coreclr/tools/{aot/ILCompiler.Compiler => Common}/Compiler/ObjectWriter/UnixObjectWriter.cs (100%) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CodeView/CodeViewFileTableBuilder.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/CodeView/CodeViewFileTableBuilder.cs similarity index 100% rename from src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CodeView/CodeViewFileTableBuilder.cs rename to src/coreclr/tools/Common/Compiler/ObjectWriter/CodeView/CodeViewFileTableBuilder.cs diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CodeView/CodeViewNative.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/CodeView/CodeViewNative.cs similarity index 100% rename from src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CodeView/CodeViewNative.cs rename to src/coreclr/tools/Common/Compiler/ObjectWriter/CodeView/CodeViewNative.cs diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CodeView/CodeViewSymbolsBuilder.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/CodeView/CodeViewSymbolsBuilder.cs similarity index 100% rename from src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CodeView/CodeViewSymbolsBuilder.cs rename to src/coreclr/tools/Common/Compiler/ObjectWriter/CodeView/CodeViewSymbolsBuilder.cs diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CodeView/CodeViewTypesBuilder.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/CodeView/CodeViewTypesBuilder.cs similarity index 100% rename from src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CodeView/CodeViewTypesBuilder.cs rename to src/coreclr/tools/Common/Compiler/ObjectWriter/CodeView/CodeViewTypesBuilder.cs diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CoffObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs similarity index 100% rename from src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CoffObjectWriter.cs rename to src/coreclr/tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfAbbrev.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/Dwarf/DwarfAbbrev.cs similarity index 100% rename from src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfAbbrev.cs rename to src/coreclr/tools/Common/Compiler/ObjectWriter/Dwarf/DwarfAbbrev.cs diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfBuilder.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/Dwarf/DwarfBuilder.cs similarity index 100% rename from src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfBuilder.cs rename to src/coreclr/tools/Common/Compiler/ObjectWriter/Dwarf/DwarfBuilder.cs diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfCfiOpcode.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/Dwarf/DwarfCfiOpcode.cs similarity index 100% rename from src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfCfiOpcode.cs rename to src/coreclr/tools/Common/Compiler/ObjectWriter/Dwarf/DwarfCfiOpcode.cs diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfCie.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/Dwarf/DwarfCie.cs similarity index 100% rename from src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfCie.cs rename to src/coreclr/tools/Common/Compiler/ObjectWriter/Dwarf/DwarfCie.cs diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfEhFrame.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/Dwarf/DwarfEhFrame.cs similarity index 100% rename from src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfEhFrame.cs rename to src/coreclr/tools/Common/Compiler/ObjectWriter/Dwarf/DwarfEhFrame.cs diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfExpressionBuilder.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/Dwarf/DwarfExpressionBuilder.cs similarity index 100% rename from src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfExpressionBuilder.cs rename to src/coreclr/tools/Common/Compiler/ObjectWriter/Dwarf/DwarfExpressionBuilder.cs diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfFde.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/Dwarf/DwarfFde.cs similarity index 100% rename from src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfFde.cs rename to src/coreclr/tools/Common/Compiler/ObjectWriter/Dwarf/DwarfFde.cs diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfFileName.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/Dwarf/DwarfFileName.cs similarity index 100% rename from src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfFileName.cs rename to src/coreclr/tools/Common/Compiler/ObjectWriter/Dwarf/DwarfFileName.cs 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/Dwarf/DwarfInfo.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/Dwarf/DwarfInfo.cs similarity index 100% rename from src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfInfo.cs rename to src/coreclr/tools/Common/Compiler/ObjectWriter/Dwarf/DwarfInfo.cs diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfInfoWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/Dwarf/DwarfInfoWriter.cs similarity index 100% rename from src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfInfoWriter.cs rename to src/coreclr/tools/Common/Compiler/ObjectWriter/Dwarf/DwarfInfoWriter.cs diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfLineProgramTableWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/Dwarf/DwarfLineProgramTableWriter.cs similarity index 100% rename from src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfLineProgramTableWriter.cs rename to src/coreclr/tools/Common/Compiler/ObjectWriter/Dwarf/DwarfLineProgramTableWriter.cs diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfLineSequenceWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/Dwarf/DwarfLineSequenceWriter.cs similarity index 100% rename from src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfLineSequenceWriter.cs rename to src/coreclr/tools/Common/Compiler/ObjectWriter/Dwarf/DwarfLineSequenceWriter.cs diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfNative.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/Dwarf/DwarfNative.cs similarity index 100% rename from src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfNative.cs rename to src/coreclr/tools/Common/Compiler/ObjectWriter/Dwarf/DwarfNative.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/Eabi/EabiUnwindConverter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/Eabi/EabiUnwindConverter.cs similarity index 100% rename from src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Eabi/EabiUnwindConverter.cs rename to src/coreclr/tools/Common/Compiler/ObjectWriter/Eabi/EabiUnwindConverter.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 100% rename from src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/ElfObjectWriter.cs rename to src/coreclr/tools/Common/Compiler/ObjectWriter/ElfObjectWriter.cs 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 100% rename from src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/MachObjectWriter.cs rename to src/coreclr/tools/Common/Compiler/ObjectWriter/MachObjectWriter.cs diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/ObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs similarity index 100% rename from src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/ObjectWriter.cs rename to src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/ObjectWritingOptions.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWritingOptions.cs similarity index 100% rename from src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/ObjectWritingOptions.cs rename to src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWritingOptions.cs diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/PEObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs similarity index 100% rename from src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/PEObjectWriter.cs rename to src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/PETargetExtensions.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/PETargetExtensions.cs similarity index 100% rename from src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/PETargetExtensions.cs rename to src/coreclr/tools/Common/Compiler/ObjectWriter/PETargetExtensions.cs 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 100% rename from src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/StringTableBuilder.cs rename to src/coreclr/tools/Common/Compiler/ObjectWriter/StringTableBuilder.cs diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/UnixObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/UnixObjectWriter.cs similarity index 100% rename from src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/UnixObjectWriter.cs rename to src/coreclr/tools/Common/Compiler/ObjectWriter/UnixObjectWriter.cs diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj index 329d02c804abdf..3ee938b53c4467 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj @@ -325,6 +325,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -602,40 +636,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From 2d4d8f3da20b3307c8e5f376e1134efa995d4afc Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 12 Sep 2025 16:11:41 -0700 Subject: [PATCH 05/71] Seal record type --- .../tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs index 01fb846c87ea8a..327347a1b5b49b 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs @@ -42,7 +42,7 @@ internal sealed class PEObjectWriter : CoffObjectWriter private uint _baseRelocSize; // Emitted Symbol Table info - private record PESymbol(string Name, uint Offset); + private sealed record PESymbol(string Name, uint Offset); private readonly List _exportedPESymbols = new(); // Grouping of object sections by base image section name. Populated by From aadac540359207fb0b2141d3b5962772ea5799dd Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 15 Sep 2025 16:02:00 -0700 Subject: [PATCH 06/71] Split out Unwind/Debug info into nativeaot-specific files as we don't need real unwind/debug info for R2R in the same way that NAOT does --- .../DependencyAnalysis/IObjectDumper.cs | 0 .../DependencyAnalysis/ISymbolNode.cs | 7 + .../Compiler/ObjectWriter/CoffObjectWriter.cs | 213 +----------- .../Compiler/ObjectWriter/ElfObjectWriter.cs | 124 +------ .../Compiler/ObjectWriter/MachObjectWriter.cs | 252 +------------- .../Compiler/ObjectWriter/ObjectWriter.cs | 142 ++------ .../ObjectWriter/ObjectWritingOptions.cs | 1 + .../Compiler/ObjectWriter/UnixObjectWriter.cs | 305 +--------------- .../DependencyAnalysis/ModulesSectionNode.cs | 4 +- .../CodeView/CodeViewFileTableBuilder.cs | 0 .../ObjectWriter/CodeView/CodeViewNative.cs | 0 .../CodeView/CodeViewSymbolsBuilder.cs | 0 .../CodeView/CodeViewTypesBuilder.cs | 0 .../ObjectWriter/CoffObjectWriter.Aot.cs | 259 ++++++++++++++ .../ObjectWriter/Dwarf/DwarfAbbrev.cs | 0 .../ObjectWriter/Dwarf/DwarfBuilder.cs | 0 .../ObjectWriter/Dwarf/DwarfCfiOpcode.cs | 0 .../Compiler/ObjectWriter/Dwarf/DwarfCie.cs | 0 .../ObjectWriter/Dwarf/DwarfEhFrame.cs | 0 .../Dwarf/DwarfExpressionBuilder.cs | 0 .../Compiler/ObjectWriter/Dwarf/DwarfFde.cs | 0 .../ObjectWriter/Dwarf/DwarfFileName.cs | 0 .../Compiler/ObjectWriter/Dwarf/DwarfInfo.cs | 0 .../ObjectWriter/Dwarf/DwarfInfoWriter.cs | 0 .../Dwarf/DwarfLineProgramTableWriter.cs | 0 .../Dwarf/DwarfLineSequenceWriter.cs | 0 .../ObjectWriter/Dwarf/DwarfNative.cs | 0 .../ObjectWriter/Eabi/EabiUnwindConverter.cs | 0 .../ObjectWriter/ElfObjectWriter.Aot.cs | 160 +++++++++ .../ObjectWriter/MachObjectWriter.Aot.cs | 297 ++++++++++++++++ .../Compiler/ObjectWriter/ObjectWriter.Aot.cs | 151 ++++++++ .../ObjectWriter/UnixObjectWriter.Aot.cs | 327 ++++++++++++++++++ .../ILCompiler.Compiler.csproj | 43 ++- .../ReadyToRunCodegenNodeFactory.cs | 6 + .../ReadyToRunCompilationModuleGroupBase.cs | 2 + .../ILCompiler.ReadyToRun.csproj | 17 + .../Compiler/RyuJitCompilation.cs | 2 +- 37 files changed, 1296 insertions(+), 1016 deletions(-) rename src/coreclr/tools/{aot/ILCompiler.Compiler => Common}/Compiler/DependencyAnalysis/IObjectDumper.cs (100%) rename src/coreclr/tools/{Common => aot/ILCompiler.Compiler}/Compiler/ObjectWriter/CodeView/CodeViewFileTableBuilder.cs (100%) rename src/coreclr/tools/{Common => aot/ILCompiler.Compiler}/Compiler/ObjectWriter/CodeView/CodeViewNative.cs (100%) rename src/coreclr/tools/{Common => aot/ILCompiler.Compiler}/Compiler/ObjectWriter/CodeView/CodeViewSymbolsBuilder.cs (100%) rename src/coreclr/tools/{Common => aot/ILCompiler.Compiler}/Compiler/ObjectWriter/CodeView/CodeViewTypesBuilder.cs (100%) create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CoffObjectWriter.Aot.cs rename src/coreclr/tools/{Common => aot/ILCompiler.Compiler}/Compiler/ObjectWriter/Dwarf/DwarfAbbrev.cs (100%) rename src/coreclr/tools/{Common => aot/ILCompiler.Compiler}/Compiler/ObjectWriter/Dwarf/DwarfBuilder.cs (100%) rename src/coreclr/tools/{Common => aot/ILCompiler.Compiler}/Compiler/ObjectWriter/Dwarf/DwarfCfiOpcode.cs (100%) rename src/coreclr/tools/{Common => aot/ILCompiler.Compiler}/Compiler/ObjectWriter/Dwarf/DwarfCie.cs (100%) rename src/coreclr/tools/{Common => aot/ILCompiler.Compiler}/Compiler/ObjectWriter/Dwarf/DwarfEhFrame.cs (100%) rename src/coreclr/tools/{Common => aot/ILCompiler.Compiler}/Compiler/ObjectWriter/Dwarf/DwarfExpressionBuilder.cs (100%) rename src/coreclr/tools/{Common => aot/ILCompiler.Compiler}/Compiler/ObjectWriter/Dwarf/DwarfFde.cs (100%) rename src/coreclr/tools/{Common => aot/ILCompiler.Compiler}/Compiler/ObjectWriter/Dwarf/DwarfFileName.cs (100%) rename src/coreclr/tools/{Common => aot/ILCompiler.Compiler}/Compiler/ObjectWriter/Dwarf/DwarfInfo.cs (100%) rename src/coreclr/tools/{Common => aot/ILCompiler.Compiler}/Compiler/ObjectWriter/Dwarf/DwarfInfoWriter.cs (100%) rename src/coreclr/tools/{Common => aot/ILCompiler.Compiler}/Compiler/ObjectWriter/Dwarf/DwarfLineProgramTableWriter.cs (100%) rename src/coreclr/tools/{Common => aot/ILCompiler.Compiler}/Compiler/ObjectWriter/Dwarf/DwarfLineSequenceWriter.cs (100%) rename src/coreclr/tools/{Common => aot/ILCompiler.Compiler}/Compiler/ObjectWriter/Dwarf/DwarfNative.cs (100%) rename src/coreclr/tools/{Common => aot/ILCompiler.Compiler}/Compiler/ObjectWriter/Eabi/EabiUnwindConverter.cs (100%) create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/ElfObjectWriter.Aot.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/MachObjectWriter.Aot.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/ObjectWriter.Aot.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/UnixObjectWriter.Aot.cs 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..b29ad233a3a94e 100644 --- a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/ISymbolNode.cs +++ b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/ISymbolNode.cs @@ -73,6 +73,13 @@ public interface ISymbolDefinitionNode : ISymbolNode new int Offset { get; } } + /// + /// 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/ObjectWriter/CoffObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs index 7eaaa4d1fa851c..831f874549bed2 100644 --- a/src/coreclr/tools/Common/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,7 +44,7 @@ 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 class CoffObjectWriter : ObjectWriter + internal partial class CoffObjectWriter : ObjectWriter { protected sealed record SectionDefinition(CoffSectionHeader Header, Stream Stream, List Relocations, string ComdatName, string SymbolName, string OriginalName); @@ -58,17 +57,8 @@ protected sealed record SectionDefinition(CoffSectionHeader Header, Stream Strea 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); @@ -371,102 +361,6 @@ private protected override void EmitRelocations(int sectionIndex, List (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); - } - protected struct CoffHeader { public Machine Machine { get; set; } diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/ElfObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/ElfObjectWriter.cs index 640cced9dd6bf9..2bd08d5569ceb9 100644 --- a/src/coreclr/tools/Common/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); @@ -604,125 +601,6 @@ private protected override void EmitSectionsAndLayout() } } - 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); - } - } - } - private protected override void EmitObjectFile(string objectFilePath) { using var outputFileStream = new FileStream(objectFilePath, FileMode.Create); diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/MachObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/MachObjectWriter.cs index 4a1742c36b8f58..a7ca75e5034c54 100644 --- a/src/coreclr/tools/Common/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"); @@ -167,7 +163,9 @@ private void LayoutSections(ref uint fileOffset, out uint segmentFileSize, out u private protected override void EmitObjectFile(string objectFilePath) { +#if !READYTORUN _sections.Add(_compactUnwindSection); +#endif // Segment + sections uint loadCommandsCount = 1; @@ -419,7 +417,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 +533,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 +560,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 +704,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 diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs index 2136fa82302ecd..359de0dcf4c699 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs @@ -10,13 +10,12 @@ 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 class ObjectWriter + public abstract partial class ObjectWriter { private protected sealed record SymbolDefinition(int SectionIndex, long Value, int Size = 0, bool Global = false); protected sealed record SymbolicRelocation(long Offset, RelocType Type, string SymbolName, long Addend = 0); @@ -39,9 +38,6 @@ private protected sealed record BlockToRelocate(int SectionIndex, long Offset, b // Symbol table private readonly Dictionary _definedSymbols = new(StringComparer.Ordinal); - // Debugging - private UserDefinedTypeDescriptor _userDefinedTypeDescriptor; - private protected ObjectWriter(NodeFactory factory, ObjectWritingOptions options) { _nodeFactory = factory; @@ -127,8 +123,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; @@ -266,43 +261,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 CreateEhSections(); + partial void EmitDebugInfo(IReadOnlyCollection nodes, Logger logger); private SortedSet GetUndefinedSymbols() { @@ -318,24 +283,6 @@ 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) { // Pre-create some of the sections @@ -350,12 +297,9 @@ private void EmitObject(string objectFilePath, IReadOnlyCollection nodes, NodeFactory factory, ObjectWritingOptions options, IObjectDumper dumper, Logger logger) { var stopwatch = Stopwatch.StartNew(); diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWritingOptions.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWritingOptions.cs index a3b76e220cde2a..5a11bdc219e903 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWritingOptions.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWritingOptions.cs @@ -11,5 +11,6 @@ public enum ObjectWritingOptions GenerateDebugInfo = 0x01, ControlFlowGuard = 0x02, UseDwarf5 = 0x4, + GenerateUnwindInfo = 0x8, } } diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/UnixObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/UnixObjectWriter.cs index 0ae31be325e186..71ab6fbcacfdd2 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/UnixObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/UnixObjectWriter.cs @@ -8,7 +8,6 @@ using System.Buffers.Binary; using ILCompiler.DependencyAnalysis; using Internal.TypeSystem; -using Internal.TypeSystem.TypesDebugInfo; using Debug = System.Diagnostics.Debug; @@ -18,31 +17,13 @@ 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; - private DwarfCie _dwarfCie; - private DwarfEhFrame _dwarfEhFrame; - - 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); - 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); - private static readonly ObjectNodeSection DebugLocSection = new ObjectNodeSection(".debug_loc", SectionType.Debug); - private static readonly ObjectNodeSection DebugRangesSection = new ObjectNodeSection(".debug_ranges", SectionType.Debug); - 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) @@ -64,289 +45,5 @@ private protected override void CreateSection(ObjectNodeSection section, string _sections.Add(null); } } - - private protected virtual bool EmitCompactUnwinding(string startSymbolName, ulong length, string lsdaSymbolName, byte[] blob) => false; - - private protected virtual bool UseFrameNames => false; - - private protected void EmitLsda( - INodeWithCodeInfo nodeWithCodeInfo, - FrameInfo[] frameInfos, - int frameInfoIndex, - SectionWriter lsdaSectionWriter, - ref long mainLsdaOffset) - { - FrameInfo frameInfo = frameInfos[frameInfoIndex]; - FrameInfoFlags flags = frameInfo.Flags; - - if (frameInfoIndex != 0) - { - lsdaSectionWriter.WriteByte((byte)flags); - lsdaSectionWriter.WriteLittleEndian((int)(mainLsdaOffset - lsdaSectionWriter.Position)); - // Emit relative offset from the main function - lsdaSectionWriter.WriteLittleEndian((uint)(frameInfo.StartOffset - frameInfos[0].StartOffset)); - } - 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; - - mainLsdaOffset = lsdaSectionWriter.Position; - lsdaSectionWriter.WriteByte((byte)flags); - - if (associatedDataNode is not null) - { - string symbolName = GetMangledName(associatedDataNode); - lsdaSectionWriter.EmitSymbolReference(RelocType.IMAGE_REL_BASED_RELPTR32, symbolName, 0); - } - - if (ehInfo is not null) - { - string symbolName = GetMangledName(ehInfo); - lsdaSectionWriter.EmitSymbolReference(RelocType.IMAGE_REL_BASED_RELPTR32, symbolName, 0); - } - - if (nodeWithCodeInfo.GCInfo is not null) - { - lsdaSectionWriter.Write(nodeWithCodeInfo.GCInfo); - } - } - } - - private sealed class LsdaCache - { - private sealed class LsdaComparer : IEqualityComparer - { - public static readonly LsdaComparer Instance = new LsdaComparer(); - - public bool Equals(INodeWithCodeInfo x, INodeWithCodeInfo y) - { - Debug.Assert(IsCacheable(x)); - Debug.Assert(IsCacheable(y)); - ReadOnlySpan xGc = x.GCInfo; - ReadOnlySpan yGc = y.GCInfo; - if (!xGc.SequenceEqual(yGc)) - return false; - - ReadOnlySpan xFrames = x.FrameInfos; - ReadOnlySpan yFrames = y.FrameInfos; - return xFrames.SequenceEqual(yFrames); - } - - public int GetHashCode(INodeWithCodeInfo obj) - { - Debug.Assert(IsCacheable(obj)); - HashCode hash = default; - hash.AddBytes(obj.GCInfo); - foreach (FrameInfo f in obj.FrameInfos) - hash.Add(f.GetHashCode()); - return hash.ToHashCode(); - } - } - - private Dictionary _lsdas = new Dictionary(LsdaComparer.Instance); - - public static bool IsCacheable(INodeWithCodeInfo nodeWithCodeInfo) - => nodeWithCodeInfo.EHInfo == null && !MethodAssociatedDataNode.MethodHasAssociatedData((IMethodNode)nodeWithCodeInfo); - - public string[] FindCachedLsda(INodeWithCodeInfo nodeWithCodeInfo) - { - Debug.Assert(IsCacheable(nodeWithCodeInfo)); - return _lsdas.GetValueOrDefault(nodeWithCodeInfo); - } - - public void AddLsdaToCache(INodeWithCodeInfo nodeWithCodeInfo, string[] symbols) - { - Debug.Assert(IsCacheable(nodeWithCodeInfo)); - _lsdas.Add(nodeWithCodeInfo, symbols); - } - } - - private readonly LsdaCache _lsdaCache = new LsdaCache(); - - private protected override void EmitUnwindInfo( - SectionWriter sectionWriter, - INodeWithCodeInfo nodeWithCodeInfo, - string currentSymbolName) - { - if (nodeWithCodeInfo.FrameInfos is FrameInfo[] frameInfos && - nodeWithCodeInfo is ISymbolDefinitionNode) - { - bool useFrameNames = UseFrameNames; - SectionWriter lsdaSectionWriter; - - string[] newLsdaSymbols = null; - string[] emittedLsdaSymbols = null; - if (ShouldShareSymbol((ObjectNode)nodeWithCodeInfo)) - { - lsdaSectionWriter = GetOrCreateSection(LsdaSection, currentSymbolName, $"_lsda0{currentSymbolName}"); - } - else - { - lsdaSectionWriter = _lsdaSectionWriter; - if (LsdaCache.IsCacheable(nodeWithCodeInfo) && !useFrameNames) - { - emittedLsdaSymbols = _lsdaCache.FindCachedLsda(nodeWithCodeInfo); - if (emittedLsdaSymbols == null) - newLsdaSymbols = new string[frameInfos.Length]; - } - } - - long mainLsdaOffset = 0; - 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 lsdaSymbolName; - if (emittedLsdaSymbols != null) - { - lsdaSymbolName = emittedLsdaSymbols[i]; - } - else - { - lsdaSymbolName = $"_lsda{i}{currentSymbolName}"; - if (newLsdaSymbols != null) - newLsdaSymbols[i] = lsdaSymbolName; - lsdaSectionWriter.EmitSymbolDefinition(lsdaSymbolName); - EmitLsda(nodeWithCodeInfo, frameInfos, i, _lsdaSectionWriter, ref mainLsdaOffset); - } - - string framSymbolName = $"_fram{i}{currentSymbolName}"; - if (useFrameNames && start != 0) - { - sectionWriter.EmitSymbolDefinition(framSymbolName, start); - } - - string startSymbolName = useFrameNames && start != 0 ? framSymbolName : currentSymbolName; - ulong length = (ulong)(end - start); - if (!EmitCompactUnwinding(startSymbolName, length, lsdaSymbolName, blob)) - { - var fde = new DwarfFde( - _dwarfCie, - blob, - pcStartSymbolName: startSymbolName, - pcStartSymbolOffset: useFrameNames ? 0 : start, - pcLength: (ulong)(end - start), - lsdaSymbolName, - personalitySymbolName: null); - _dwarfEhFrame.AddFde(fde); - } - } - - if (newLsdaSymbols != null) - _lsdaCache.AddLsdaToCache(nodeWithCodeInfo, newLsdaSymbols); - } - } - - private protected override void EmitDebugFunctionInfo( - uint methodTypeIndex, - string methodName, - SymbolDefinition methodSymbol, - INodeWithDebugInfo debugNode, - bool hasSequencePoints) - { - DebugEHClauseInfo[] clauses = null; - - if (debugNode is INodeWithCodeInfo nodeWithCodeInfo) - { - clauses = nodeWithCodeInfo.DebugEHClauseInfos; - } - - if (_sections[methodSymbol.SectionIndex] is UnixSectionDefinition section) - { - _dwarfBuilder.EmitSubprogramInfo( - methodName, - section.SymbolName, - methodSymbol.Value, - methodSymbol.Size, - methodTypeIndex, - debugNode.GetDebugVars().Select(debugVar => (debugVar, GetVarTypeIndex(debugNode.IsStateMachineMoveNextMethod, debugVar))), - clauses ?? []); - - if (hasSequencePoints) - { - _dwarfBuilder.EmitLineInfo( - methodSymbol.SectionIndex, - section.SymbolName, - methodSymbol.Value, - debugNode.GetNativeSequencePoints()); - } - } - } - - private protected override void EmitDebugSections(IDictionary definedSymbols) - { - foreach (UnixSectionDefinition section in _sections) - { - if (section is not null) - { - _dwarfBuilder.EmitSectionInfo(section.SymbolName, (ulong)section.SectionStream.Length); - } - } - - SectionWriter infoSectionWriter = GetOrCreateSection(DebugInfoSection); - SectionWriter stringSectionWriter = GetOrCreateSection(DebugStringSection); - SectionWriter abbrevSectionWriter = GetOrCreateSection(DebugAbbrevSection); - SectionWriter locSectionWriter = GetOrCreateSection(DebugLocSection); - SectionWriter rangeSectionWriter = GetOrCreateSection(DebugRangesSection); - SectionWriter lineSectionWriter = GetOrCreateSection(DebugLineSection); - SectionWriter arangeSectionWriter = GetOrCreateSection(DebugARangesSection); - - _dwarfBuilder.Write( - infoSectionWriter, - stringSectionWriter, - abbrevSectionWriter, - locSectionWriter, - rangeSectionWriter, - lineSectionWriter, - arangeSectionWriter, - symbolName => - { - if (definedSymbols.TryGetValue(ExternCName(symbolName), out SymbolDefinition symbolDef) && - _sections[symbolDef.SectionIndex] is UnixSectionDefinition section) - { - return (section.SymbolName, symbolDef.Value); - } - return (null, 0); - }); - } - - private protected override void CreateEhSections() - { - SectionWriter ehFrameSectionWriter; - - // Create sections for exception handling - _lsdaSectionWriter = GetOrCreateSection(LsdaSection); - ehFrameSectionWriter = GetOrCreateSection(EhFrameSection); - _lsdaSectionWriter.EmitAlignment(8); - ehFrameSectionWriter.EmitAlignment(8); - _ehFrameSectionIndex = ehFrameSectionWriter.SectionIndex; - - // 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 override ITypesDebugInfoWriter CreateDebugInfoBuilder() - { - return _dwarfBuilder = new DwarfBuilder( - _nodeFactory.NameMangler, - _nodeFactory.Target, - _options.HasFlag(ObjectWritingOptions.UseDwarf5)); - } } } 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/Common/Compiler/ObjectWriter/CodeView/CodeViewFileTableBuilder.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CodeView/CodeViewFileTableBuilder.cs similarity index 100% rename from src/coreclr/tools/Common/Compiler/ObjectWriter/CodeView/CodeViewFileTableBuilder.cs rename to src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CodeView/CodeViewFileTableBuilder.cs diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/CodeView/CodeViewNative.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CodeView/CodeViewNative.cs similarity index 100% rename from src/coreclr/tools/Common/Compiler/ObjectWriter/CodeView/CodeViewNative.cs rename to src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CodeView/CodeViewNative.cs diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/CodeView/CodeViewSymbolsBuilder.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CodeView/CodeViewSymbolsBuilder.cs similarity index 100% rename from src/coreclr/tools/Common/Compiler/ObjectWriter/CodeView/CodeViewSymbolsBuilder.cs rename to src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CodeView/CodeViewSymbolsBuilder.cs diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/CodeView/CodeViewTypesBuilder.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CodeView/CodeViewTypesBuilder.cs similarity index 100% rename from src/coreclr/tools/Common/Compiler/ObjectWriter/CodeView/CodeViewTypesBuilder.cs rename to src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CodeView/CodeViewTypesBuilder.cs 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..b8d353e2e67e07 --- /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(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(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/Common/Compiler/ObjectWriter/Dwarf/DwarfAbbrev.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfAbbrev.cs similarity index 100% rename from src/coreclr/tools/Common/Compiler/ObjectWriter/Dwarf/DwarfAbbrev.cs rename to src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfAbbrev.cs diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/Dwarf/DwarfBuilder.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfBuilder.cs similarity index 100% rename from src/coreclr/tools/Common/Compiler/ObjectWriter/Dwarf/DwarfBuilder.cs rename to src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfBuilder.cs diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/Dwarf/DwarfCfiOpcode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfCfiOpcode.cs similarity index 100% rename from src/coreclr/tools/Common/Compiler/ObjectWriter/Dwarf/DwarfCfiOpcode.cs rename to src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfCfiOpcode.cs diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/Dwarf/DwarfCie.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfCie.cs similarity index 100% rename from src/coreclr/tools/Common/Compiler/ObjectWriter/Dwarf/DwarfCie.cs rename to src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfCie.cs diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/Dwarf/DwarfEhFrame.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfEhFrame.cs similarity index 100% rename from src/coreclr/tools/Common/Compiler/ObjectWriter/Dwarf/DwarfEhFrame.cs rename to src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfEhFrame.cs diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/Dwarf/DwarfExpressionBuilder.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfExpressionBuilder.cs similarity index 100% rename from src/coreclr/tools/Common/Compiler/ObjectWriter/Dwarf/DwarfExpressionBuilder.cs rename to src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfExpressionBuilder.cs diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/Dwarf/DwarfFde.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfFde.cs similarity index 100% rename from src/coreclr/tools/Common/Compiler/ObjectWriter/Dwarf/DwarfFde.cs rename to src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfFde.cs diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/Dwarf/DwarfFileName.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfFileName.cs similarity index 100% rename from src/coreclr/tools/Common/Compiler/ObjectWriter/Dwarf/DwarfFileName.cs rename to src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfFileName.cs diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/Dwarf/DwarfInfo.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfInfo.cs similarity index 100% rename from src/coreclr/tools/Common/Compiler/ObjectWriter/Dwarf/DwarfInfo.cs rename to src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfInfo.cs diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/Dwarf/DwarfInfoWriter.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfInfoWriter.cs similarity index 100% rename from src/coreclr/tools/Common/Compiler/ObjectWriter/Dwarf/DwarfInfoWriter.cs rename to src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfInfoWriter.cs diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/Dwarf/DwarfLineProgramTableWriter.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfLineProgramTableWriter.cs similarity index 100% rename from src/coreclr/tools/Common/Compiler/ObjectWriter/Dwarf/DwarfLineProgramTableWriter.cs rename to src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfLineProgramTableWriter.cs diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/Dwarf/DwarfLineSequenceWriter.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfLineSequenceWriter.cs similarity index 100% rename from src/coreclr/tools/Common/Compiler/ObjectWriter/Dwarf/DwarfLineSequenceWriter.cs rename to src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfLineSequenceWriter.cs diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/Dwarf/DwarfNative.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfNative.cs similarity index 100% rename from src/coreclr/tools/Common/Compiler/ObjectWriter/Dwarf/DwarfNative.cs rename to src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfNative.cs diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/Eabi/EabiUnwindConverter.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Eabi/EabiUnwindConverter.cs similarity index 100% rename from src/coreclr/tools/Common/Compiler/ObjectWriter/Eabi/EabiUnwindConverter.cs rename to src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Eabi/EabiUnwindConverter.cs 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.Aot.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/UnixObjectWriter.Aot.cs new file mode 100644 index 00000000000000..70923f97089317 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/UnixObjectWriter.Aot.cs @@ -0,0 +1,327 @@ +// 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 Internal.TypeSystem.TypesDebugInfo; + +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 + { + // Exception handling sections + private SectionWriter _lsdaSectionWriter; + private int _ehFrameSectionIndex; + private DwarfCie _dwarfCie; + private DwarfEhFrame _dwarfEhFrame; + + protected int EhFrameSectionIndex => _ehFrameSectionIndex; + + // 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); + private static readonly ObjectNodeSection DebugLocSection = new ObjectNodeSection(".debug_loc", SectionType.Debug); + private static readonly ObjectNodeSection DebugRangesSection = new ObjectNodeSection(".debug_ranges", SectionType.Debug); + private static readonly ObjectNodeSection DebugLineSection = new ObjectNodeSection(".debug_line", SectionType.Debug); + private static readonly ObjectNodeSection DebugARangesSection = new ObjectNodeSection(".debug_aranges", SectionType.Debug); + + private protected virtual bool UseFrameNames => false; + + private protected virtual bool EmitCompactUnwinding(string startSymbolName, ulong length, string lsdaSymbolName, byte[] blob) => false; + + private protected override void CreateEhSections() + { + SectionWriter ehFrameSectionWriter; + + // Create sections for exception handling + _lsdaSectionWriter = GetOrCreateSection(LsdaSection); + ehFrameSectionWriter = GetOrCreateSection(EhFrameSection); + _lsdaSectionWriter.EmitAlignment(8); + ehFrameSectionWriter.EmitAlignment(8); + _ehFrameSectionIndex = ehFrameSectionWriter.SectionIndex; + + // 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, + FrameInfo[] frameInfos, + int frameInfoIndex, + SectionWriter lsdaSectionWriter, + ref long mainLsdaOffset) + { + FrameInfo frameInfo = frameInfos[frameInfoIndex]; + FrameInfoFlags flags = frameInfo.Flags; + + if (frameInfoIndex != 0) + { + lsdaSectionWriter.WriteByte((byte)flags); + lsdaSectionWriter.WriteLittleEndian((int)(mainLsdaOffset - lsdaSectionWriter.Position)); + // Emit relative offset from the main function + lsdaSectionWriter.WriteLittleEndian((uint)(frameInfo.StartOffset - frameInfos[0].StartOffset)); + } + 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; + + mainLsdaOffset = lsdaSectionWriter.Position; + lsdaSectionWriter.WriteByte((byte)flags); + + if (associatedDataNode is not null) + { + string symbolName = GetMangledName(associatedDataNode); + lsdaSectionWriter.EmitSymbolReference(RelocType.IMAGE_REL_BASED_RELPTR32, symbolName, 0); + } + + if (ehInfo is not null) + { + string symbolName = GetMangledName(ehInfo); + lsdaSectionWriter.EmitSymbolReference(RelocType.IMAGE_REL_BASED_RELPTR32, symbolName, 0); + } + + if (nodeWithCodeInfo.GCInfo is not null) + { + lsdaSectionWriter.Write(nodeWithCodeInfo.GCInfo); + } + } + } + + private sealed class LsdaCache + { + private sealed class LsdaComparer : IEqualityComparer + { + public static readonly LsdaComparer Instance = new LsdaComparer(); + + public bool Equals(INodeWithCodeInfo x, INodeWithCodeInfo y) + { + Debug.Assert(IsCacheable(x)); + Debug.Assert(IsCacheable(y)); + ReadOnlySpan xGc = x.GCInfo; + ReadOnlySpan yGc = y.GCInfo; + if (!xGc.SequenceEqual(yGc)) + return false; + + ReadOnlySpan xFrames = x.FrameInfos; + ReadOnlySpan yFrames = y.FrameInfos; + return xFrames.SequenceEqual(yFrames); + } + + public int GetHashCode(INodeWithCodeInfo obj) + { + Debug.Assert(IsCacheable(obj)); + HashCode hash = default; + hash.AddBytes(obj.GCInfo); + foreach (FrameInfo f in obj.FrameInfos) + hash.Add(f.GetHashCode()); + return hash.ToHashCode(); + } + } + + private Dictionary _lsdas = new Dictionary(LsdaComparer.Instance); + + public static bool IsCacheable(INodeWithCodeInfo nodeWithCodeInfo) + => nodeWithCodeInfo.EHInfo == null && !MethodAssociatedDataNode.MethodHasAssociatedData((IMethodNode)nodeWithCodeInfo); + + public string[] FindCachedLsda(INodeWithCodeInfo nodeWithCodeInfo) + { + Debug.Assert(IsCacheable(nodeWithCodeInfo)); + return _lsdas.GetValueOrDefault(nodeWithCodeInfo); + } + + public void AddLsdaToCache(INodeWithCodeInfo nodeWithCodeInfo, string[] symbols) + { + Debug.Assert(IsCacheable(nodeWithCodeInfo)); + _lsdas.Add(nodeWithCodeInfo, symbols); + } + } + + private readonly LsdaCache _lsdaCache = new LsdaCache(); + + private protected override void EmitUnwindInfo( + SectionWriter sectionWriter, + INodeWithCodeInfo nodeWithCodeInfo, + string currentSymbolName) + { + if (nodeWithCodeInfo.FrameInfos is FrameInfo[] frameInfos && + nodeWithCodeInfo is ISymbolDefinitionNode) + { + bool useFrameNames = UseFrameNames; + SectionWriter lsdaSectionWriter; + + string[] newLsdaSymbols = null; + string[] emittedLsdaSymbols = null; + if (ShouldShareSymbol((ObjectNode)nodeWithCodeInfo)) + { + lsdaSectionWriter = GetOrCreateSection(LsdaSection, currentSymbolName, $"_lsda0{currentSymbolName}"); + } + else + { + lsdaSectionWriter = _lsdaSectionWriter; + if (LsdaCache.IsCacheable(nodeWithCodeInfo) && !useFrameNames) + { + emittedLsdaSymbols = _lsdaCache.FindCachedLsda(nodeWithCodeInfo); + if (emittedLsdaSymbols == null) + newLsdaSymbols = new string[frameInfos.Length]; + } + } + + long mainLsdaOffset = 0; + 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 lsdaSymbolName; + if (emittedLsdaSymbols != null) + { + lsdaSymbolName = emittedLsdaSymbols[i]; + } + else + { + lsdaSymbolName = $"_lsda{i}{currentSymbolName}"; + if (newLsdaSymbols != null) + newLsdaSymbols[i] = lsdaSymbolName; + lsdaSectionWriter.EmitSymbolDefinition(lsdaSymbolName); + EmitLsda(nodeWithCodeInfo, frameInfos, i, _lsdaSectionWriter, ref mainLsdaOffset); + } + + string framSymbolName = $"_fram{i}{currentSymbolName}"; + if (useFrameNames && start != 0) + { + sectionWriter.EmitSymbolDefinition(framSymbolName, start); + } + + string startSymbolName = useFrameNames && start != 0 ? framSymbolName : currentSymbolName; + ulong length = (ulong)(end - start); + if (!EmitCompactUnwinding(startSymbolName, length, lsdaSymbolName, blob)) + { + var fde = new DwarfFde( + _dwarfCie, + blob, + pcStartSymbolName: startSymbolName, + pcStartSymbolOffset: useFrameNames ? 0 : start, + pcLength: (ulong)(end - start), + lsdaSymbolName, + personalitySymbolName: null); + _dwarfEhFrame.AddFde(fde); + } + } + + if (newLsdaSymbols != null) + _lsdaCache.AddLsdaToCache(nodeWithCodeInfo, newLsdaSymbols); + } + } + + private protected override void EmitDebugFunctionInfo( + uint methodTypeIndex, + string methodName, + SymbolDefinition methodSymbol, + INodeWithDebugInfo debugNode, + bool hasSequencePoints) + { + DebugEHClauseInfo[] clauses = null; + + if (debugNode is INodeWithCodeInfo nodeWithCodeInfo) + { + clauses = nodeWithCodeInfo.DebugEHClauseInfos; + } + + if (_sections[methodSymbol.SectionIndex] is UnixSectionDefinition section) + { + _dwarfBuilder.EmitSubprogramInfo( + methodName, + section.SymbolName, + methodSymbol.Value, + methodSymbol.Size, + methodTypeIndex, + debugNode.GetDebugVars().Select(debugVar => (debugVar, GetVarTypeIndex(debugNode.IsStateMachineMoveNextMethod, debugVar))), + clauses ?? []); + + if (hasSequencePoints) + { + _dwarfBuilder.EmitLineInfo( + methodSymbol.SectionIndex, + section.SymbolName, + methodSymbol.Value, + debugNode.GetNativeSequencePoints()); + } + } + } + + private protected override void EmitDebugSections(IDictionary definedSymbols) + { + foreach (UnixSectionDefinition section in _sections) + { + if (section is not null) + { + _dwarfBuilder.EmitSectionInfo(section.SymbolName, (ulong)section.SectionStream.Length); + } + } + + SectionWriter infoSectionWriter = GetOrCreateSection(DebugInfoSection); + SectionWriter stringSectionWriter = GetOrCreateSection(DebugStringSection); + SectionWriter abbrevSectionWriter = GetOrCreateSection(DebugAbbrevSection); + SectionWriter locSectionWriter = GetOrCreateSection(DebugLocSection); + SectionWriter rangeSectionWriter = GetOrCreateSection(DebugRangesSection); + SectionWriter lineSectionWriter = GetOrCreateSection(DebugLineSection); + SectionWriter arangeSectionWriter = GetOrCreateSection(DebugARangesSection); + + _dwarfBuilder.Write( + infoSectionWriter, + stringSectionWriter, + abbrevSectionWriter, + locSectionWriter, + rangeSectionWriter, + lineSectionWriter, + arangeSectionWriter, + symbolName => + { + if (definedSymbols.TryGetValue(ExternCName(symbolName), out SymbolDefinition symbolDef) && + _sections[symbolDef.SectionIndex] is UnixSectionDefinition section) + { + return (section.SymbolName, symbolDef.Value); + } + return (null, 0); + }); + } + + private protected override ITypesDebugInfoWriter CreateDebugInfoBuilder() + { + return _dwarfBuilder = new DwarfBuilder( + _nodeFactory.NameMangler, + _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 3ee938b53c4467..b4beab48ab29c0 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj @@ -293,6 +293,7 @@ + @@ -325,27 +326,9 @@ - - - - - - - - - - - - - - - - - - @@ -540,7 +523,6 @@ - @@ -641,6 +623,29 @@ + + + + + + + + + + + + + + + + + + + + + + + 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..e4f614973d062f 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs @@ -1056,5 +1056,11 @@ public void DetectGenericCycles(TypeSystemEntity caller, TypeSystemEntity callee { _genericCycleDetector?.DetectCycle(caller, callee); } + + public string GetSymbolAlternateName(ISymbolNode node, out bool isHidden) + { + isHidden = false; + return null; + } } } 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..ecf6c6c18729ac 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,22 @@ + + + + + + + + + + + + + + + + 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; From 2e29838a7bd2256cae73b403a286157e7babd874 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 15 Sep 2025 16:07:00 -0700 Subject: [PATCH 07/71] Remove support for merging grouped sections. The only sections where we needed this are CodeView debug sections, which aren't supported in PE files --- .../Compiler/ObjectWriter/PEObjectWriter.cs | 140 +++--------------- 1 file changed, 24 insertions(+), 116 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs index 327347a1b5b49b..532033b66fdbc2 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs @@ -45,12 +45,9 @@ internal sealed class PEObjectWriter : CoffObjectWriter private sealed record PESymbol(string Name, uint Offset); private readonly List _exportedPESymbols = new(); - // Grouping of object sections by base image section name. Populated by - // EmitSectionsAndLayout so EmitObjectFile / EmitSections can consume - // the grouping without recomputing it. - private readonly List _groupOrder = new(); - private readonly Dictionary> _groupMap = new Dictionary>(StringComparer.Ordinal); - private readonly Dictionary _origToMergedSectionIndex = new(); + // Note: Sections are emitted in the order provided by _sections. + // Grouping/merging of sections by base name (suffixes like '$') has been + // removed to simplify layout and emission. private HashSet _exportedSymbolNames = new(); @@ -309,8 +306,13 @@ private protected override void EmitSymbolTable(IDictionary= _sections.Count) + continue; + + uint targetRva = _sections[sectionIdx].Header.VirtualAddress + (uint)symbolDefinition.Value; PESymbol sym = new(symbolName, targetRva); _exportedPESymbols.Add(sym); } @@ -318,87 +320,12 @@ private protected override void EmitSymbolTable(IDictionary= 0 ? fullName.Substring(0, dollar) : fullName; - if (!_groupMap.TryGetValue(baseName, out var list)) - { - list = new List<(SectionDefinition, int)>(); - _groupMap[baseName] = list; - _groupOrder.Add(baseName); - } - list.Add((sec, i)); - _origToMergedSectionIndex.Add(i, _groupOrder.IndexOf(baseName)); - } - - // Build merged section definitions and grouped symbolic relocations - var mergedSections = new List(); - var groupedSymbolicRelocs = new List>(); - - foreach (string baseName in _groupOrder) - { - var contributions = _groupMap[baseName] - .OrderBy(c => c.Section.Header.Name, StringComparer.Ordinal) - .ToList(); - - // Concatenate contribution streams - long totalSize = 0; - foreach (var (s, idx) in contributions) - totalSize += s.Stream.Length; - - var mergedStream = new MemoryStream((int)Math.Min(totalSize, int.MaxValue)); - - uint contributionOffset = 0u; - var groupSymbolic = new List(); - - foreach (var (s, origIdx) in contributions) - { - s.Stream.Position = 0; - s.Stream.CopyTo(mergedStream); - - // Adjust symbolic relocations from the original section into - // the group's address space - foreach (var symRel in _sectionIndexToRelocations[origIdx]) - { - groupSymbolic.Add(new SymbolicRelocation(symRel.Offset + contributionOffset, symRel.Type, symRel.SymbolName, symRel.Addend)); - } - - contributionOffset += (uint)s.Stream.Length; - } - - // Create a new header for the merged section using the first contribution as a template - var template = contributions[0].Section.Header; - var mergedHeader = new CoffSectionHeader - { - Name = baseName, - SectionCharacteristics = contributions.Aggregate((SectionCharacteristics)0, (acc, t) => acc | t.Section.Header.SectionCharacteristics) - }; - - mergedSections.Add(new SectionDefinition(mergedHeader, mergedStream, new List(), contributions[0].Section.ComdatName, contributions[0].Section.SymbolName, baseName)); - groupedSymbolicRelocs.Add(groupSymbolic); - } - - // Replace section lists with grouped versions for subsequent layout & relocation emission - _sections.Clear(); - _sections.AddRange(mergedSections); - _sectionIndexToRelocations.Clear(); - _sectionIndexToRelocations.AddRange(groupedSymbolicRelocs); - - ushort numberOfSections = (ushort)_groupOrder.Count; + // Compute layout for sections directly from the _sections list + // without merging or grouping by name suffixes. + ushort numberOfSections = (ushort)_sections.Count; bool isPE32Plus = _nodeFactory.Target.PointerSize == 8; ushort sizeOfOptionalHeader = (ushort)(isPE32Plus ? 0xF0 : 0xE0); - // Determine file and section alignment following the same rules as - // the image writer. int fileAlignment = 0x200; bool isWindowsOr32bit = _nodeFactory.Target.IsWindows || !isPE32Plus; if (isWindowsOr32bit) @@ -482,8 +409,6 @@ private protected override void EmitSectionsAndLayout() // Add to sections and bookkeeping _sections.Add(edataSection); _sectionIndexToRelocations.Add(new List()); - _groupOrder.Add(".edata"); - _groupMap[".edata"] = new List<(SectionDefinition, int)> { (edataSection, -1) }; sizeOfInitializedData += edataHeader.SizeOfRawData; @@ -601,8 +526,6 @@ private protected override void EmitRelocations(int sectionIndex, List()); - _groupOrder.Add(".reloc"); - _groupMap[".reloc"] = new List<(SectionDefinition, int)> { (relocSection, -1) }; _baseRelocRva = relocHeader.VirtualAddress; _baseRelocSize = (uint)ms.Length; @@ -620,7 +543,7 @@ private protected override void EmitObjectFile(string objectFilePath) BinaryPrimitives.WriteInt32LittleEndian(dos.AsSpan(0x3c), 0x80); stream.Write(dos); - ushort numberOfSections = (ushort)_groupOrder.Count; + ushort numberOfSections = (ushort)_sections.Count; bool isPE32Plus = _nodeFactory.Target.PointerSize == 8; ushort sizeOfOptionalHeader = (ushort)(isPE32Plus ? 0xF0 : 0xE0); @@ -730,37 +653,22 @@ private protected override void EmitObjectFile(string objectFilePath) peOptional.Write(stream, dataDirs); CoffStringTable stringTable = new(); - int sectionIndex = 0; - // Emit headers for each group (baseName order preserved) - foreach (string baseName in _groupOrder) + + // Emit headers for each section in the order they appear in _sections + for (int i = 0; i < _sections.Count; i++) { - var contributions = _groupMap[baseName]; - // The header we will emit for this image section is based on - // the first contribution's header but needs to have the - // section name without the '$' suffix. - var groupHeader = contributions[0].Section.Header; - string savedName = groupHeader.Name; - groupHeader.Name = baseName; - groupHeader.Write(stream, stringTable); - - // Restore the name to avoid affecting any other logic that - // may rely on the original value stored in the SectionDefinition. - groupHeader.Name = savedName; - - sectionIndex++; + var hdr = _sections[i].Header; + hdr.Write(stream, stringTable); } // Write section content - foreach (string baseName in _groupOrder) + foreach (var section in _sections) { - foreach (var (section, _) in _groupMap[baseName]) + if (!section.Header.SectionCharacteristics.HasFlag(SectionCharacteristics.ContainsUninitializedData)) { - if (!section.Header.SectionCharacteristics.HasFlag(SectionCharacteristics.ContainsUninitializedData)) - { - Debug.Assert(stream.Position == section.Header.PointerToRawData); - section.Stream.Position = 0; - section.Stream.CopyTo(stream); - } + Debug.Assert(stream.Position == section.Header.PointerToRawData); + section.Stream.Position = 0; + section.Stream.CopyTo(stream); } } } From 9d5aa009209097b511378b465d6f952b04e3a6f4 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 17 Sep 2025 13:36:22 -0700 Subject: [PATCH 08/71] Fixup expected sections for R2R to be coded into the nodes themselves. --- .../Compiler/DependencyAnalysis/ObjectNodeSection.cs | 5 +++++ .../tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs | 7 +++++++ .../DependencyAnalysis/ReadyToRun/CopiedCorHeaderNode.cs | 7 ++++++- .../DependencyAnalysis/ReadyToRun/DebugDirectoryNode.cs | 7 ++++++- .../ReadyToRun/RuntimeFunctionsGCInfoNode.cs | 7 ++++++- .../ReadyToRun/RuntimeFunctionsTableNode.cs | 8 ++++++++ .../DependencyAnalysis/ReadyToRun/Win32ResourcesNode.cs | 7 ++++++- 7 files changed, 44 insertions(+), 4 deletions(-) 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/ObjectWriter/PEObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs index 532033b66fdbc2..20467982d63d00 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs @@ -66,6 +66,13 @@ public void AddExportedSymbol(string symbol) private protected override void CreateSection(ObjectNodeSection section, string comdatName, string symbolName, Stream sectionStream) { + if (section == ObjectNodeSection.ManagedCodeWindowsContentSection) + { + // Put managed code in the .text section in the PE. + // Having executable code in other sections makes AVs and other tools unhappy. + base.CreateSection(ObjectNodeSection.TextSection, comdatName, symbolName, sectionStream); + return; + } // COMDAT sections are not supported in PE files base.CreateSection(section, comdatName: null, symbolName, sectionStream); } 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..3ec43b0010fea8 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,12 @@ public CopiedCorHeaderNode(EcmaModule sourceModule) _module = sourceModule; } - public override ObjectNodeSection GetSection(NodeFactory factory) => ObjectNodeSection.TextSection; + public override ObjectNodeSection GetSection(NodeFactory factory) + { + if (factory.Target.IsWindows) + return ObjectNodeSection.CorMetaSection; + return ObjectNodeSection.TextSection; + } public override bool IsShareable => false; 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..17a9ed64acb64c 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 @@ -52,7 +52,12 @@ public DebugDirectoryNode(EcmaModule sourceModule, string outputFileName, bool s } } - public override ObjectNodeSection GetSection(NodeFactory factory) => ObjectNodeSection.TextSection; + public override ObjectNodeSection GetSection(NodeFactory factory) + { + if (factory.Target.IsWindows) + return ObjectNodeSection.DebugDirectorySection; + return ObjectNodeSection.TextSection; + } public override bool IsShareable => false; 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..832d899c6e1655 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,12 @@ public RuntimeFunctionsGCInfoNode() public override int ClassCode => 316678892; - public override ObjectNodeSection GetSection(NodeFactory factory) => ObjectNodeSection.ReadOnlyDataSection; + public override ObjectNodeSection GetSection(NodeFactory factory) + { + if (factory.Target.IsWindows) + return ObjectNodeSection.XDataSection; + return ObjectNodeSection.ReadOnlyDataSection; + } 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..7918c70c0fdb03 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,14 @@ public RuntimeFunctionsTableNode(NodeFactory nodeFactory) _nodeFactory = nodeFactory; } + public override ObjectNodeSection GetSection(NodeFactory factory) + { + if (factory.Target.IsWindows) + return ObjectNodeSection.PDataSection; + else + return ObjectNodeSection.DataSection; + } + public override void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb) { sb.Append(nameMangler.CompilationUnitPrefix); 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..1770431aacabc6 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,12 @@ public Win32ResourcesNode(ResourceData resourceData) _size = -1; } - public override ObjectNodeSection GetSection(NodeFactory factory) => ObjectNodeSection.TextSection; + public override ObjectNodeSection GetSection(NodeFactory factory) + { + if (factory.Target.IsWindows) + return ObjectNodeSection.Win32ResourcesSection; + return ObjectNodeSection.ReadOnlyDataSection; + } public override bool IsShareable => false; From 1c18952fe5d51c311f04e9ed2a12a9f45c7c037a Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 17 Sep 2025 13:36:43 -0700 Subject: [PATCH 09/71] Add TODOs for determinism/checksums in the new PE writer --- .../tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs index 20467982d63d00..83c49c5d1425c8 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs @@ -599,7 +599,7 @@ private protected override void EmitObjectFile(string objectFilePath) { Machine = _machine, NumberOfSections = (uint)numberOfSections, - TimeDateStamp = (uint)DateTimeOffset.UtcNow.ToUnixTimeSeconds(), + TimeDateStamp = (uint)DateTimeOffset.UtcNow.ToUnixTimeSeconds(), // TODO: Make deterministic PointerToSymbolTable = 0, NumberOfSymbols = 0, SizeOfOptionalHeader = sizeOfOptionalHeader, @@ -678,6 +678,9 @@ private protected override void EmitObjectFile(string objectFilePath) section.Stream.CopyTo(stream); } } + + // TODO: calculate PE checksum + // TODO: calculate deterministic timestamp } private static unsafe void WriteLittleEndian(Stream stream, T value) From 7ba3359357c38768980740c78944d797c2b84e77 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 17 Sep 2025 16:11:50 -0700 Subject: [PATCH 10/71] Add support for symbol-size reloc --- .../tools/Common/Compiler/ObjectWriter/ObjectWriter.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs index 359de0dcf4c699..03ba883cf07caa 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs @@ -178,6 +178,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); From a09bade84749ab3c98e40332575f0ecbbac08070 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 17 Sep 2025 16:34:28 -0700 Subject: [PATCH 11/71] Add support for custom section alignment --- .../Compiler/ObjectWriter/CoffObjectWriter.cs | 9 +++--- .../Compiler/ObjectWriter/ElfObjectWriter.cs | 5 ++-- .../Compiler/ObjectWriter/MachObjectWriter.cs | 6 ++-- .../Compiler/ObjectWriter/ObjectWriter.cs | 6 ++-- .../ObjectWriter/ObjectWritingOptions.cs | 1 + .../Compiler/ObjectWriter/PEObjectWriter.cs | 15 +++++----- .../Compiler/ObjectWriter/UnixObjectWriter.cs | 2 +- .../CodeGen/ReadyToRunObjectWriter.cs | 28 ++++++++++++++++--- 8 files changed, 45 insertions(+), 27 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs index 831f874549bed2..38307c13c83bf2 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs @@ -75,7 +75,7 @@ public CoffObjectWriter(NodeFactory factory, ObjectWritingOptions options) }; } - 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 { @@ -114,8 +114,7 @@ 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 definingSectionIndex = isPrimary ? (uint)sectionIndex : ((CoffSymbol)_symbols[(int)_symbolNameToIndex[comdatName]]).SectionIndex; var auxRecord = new CoffSectionSymbol { @@ -133,7 +132,7 @@ private protected override void CreateSection(ObjectNodeSection section, string { Name = sectionHeader.Name, Value = 0, - SectionIndex = sectionIndex, + SectionIndex = (uint)sectionIndex, StorageClass = CoffSymbolClass.IMAGE_SYM_CLASS_STATIC, NumberOfAuxiliaryRecords = 1, }); @@ -146,7 +145,7 @@ private protected override void CreateSection(ObjectNodeSection section, string { Name = symbolName, Value = 0, - SectionIndex = sectionIndex, + SectionIndex = (uint)sectionIndex, StorageClass = isPrimary ? CoffSymbolClass.IMAGE_SYM_CLASS_EXTERNAL : CoffSymbolClass.IMAGE_SYM_CLASS_STATIC, }); } diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/ElfObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/ElfObjectWriter.cs index 2bd08d5569ceb9..4253398e98157f 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/ElfObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/ElfObjectWriter.cs @@ -69,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; @@ -175,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) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/MachObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/MachObjectWriter.cs index a7ca75e5034c54..8b0ef543000c6f 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/MachObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/MachObjectWriter.cs @@ -299,7 +299,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 { @@ -354,11 +354,9 @@ private protected override void CreateSection(ObjectNodeSection section, string Log2Alignment = 1, 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) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs index 03ba883cf07caa..c11faa1115ac1b 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs @@ -54,7 +54,7 @@ private protected ObjectWriter(NodeFactory factory, ObjectWritingOptions options }; } - 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); @@ -81,7 +81,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) @@ -291,7 +291,7 @@ private SortedSet GetUndefinedSymbols() return undefinedSymbolSet; } - private void EmitObject(string objectFilePath, IReadOnlyCollection nodes, IObjectDumper dumper, Logger logger) + public void EmitObject(string objectFilePath, IReadOnlyCollection nodes, IObjectDumper dumper, Logger logger) { // Pre-create some of the sections GetOrCreateSection(ObjectNodeSection.TextSection); diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWritingOptions.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWritingOptions.cs index 5a11bdc219e903..61276810208aca 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWritingOptions.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWritingOptions.cs @@ -8,6 +8,7 @@ namespace ILCompiler.ObjectWriter [Flags] public enum ObjectWritingOptions { + None = 0x00, GenerateDebugInfo = 0x01, ControlFlowGuard = 0x02, UseDwarf5 = 0x4, diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs index 83c49c5d1425c8..ea38ba6d06e392 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs @@ -44,6 +44,7 @@ internal sealed class PEObjectWriter : CoffObjectWriter // Emitted Symbol Table info private sealed record PESymbol(string Name, uint Offset); private readonly List _exportedPESymbols = new(); + private readonly int? _sectionAlignment; // Note: Sections are emitted in the order provided by _sections. // Grouping/merging of sections by base name (suffixes like '$') has been @@ -51,7 +52,7 @@ private sealed record PESymbol(string Name, uint Offset); private HashSet _exportedSymbolNames = new(); - public PEObjectWriter(NodeFactory factory, ObjectWritingOptions options) + public PEObjectWriter(NodeFactory factory, ObjectWritingOptions options, int? sectionAlignment = null) : base(factory, options) { } @@ -64,14 +65,14 @@ public void AddExportedSymbol(string symbol) } } - 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) { - if (section == ObjectNodeSection.ManagedCodeWindowsContentSection) + // COMDAT sections are not supported in PE files + base.CreateSection(section, comdatName: null, symbolName, sectionIndex, sectionStream); + + if (_sectionAlignment is { } alignment) { - // Put managed code in the .text section in the PE. - // Having executable code in other sections makes AVs and other tools unhappy. - base.CreateSection(ObjectNodeSection.TextSection, comdatName, symbolName, sectionStream); - return; + UpdateSectionAlignment(sectionIndex, alignment); } // COMDAT sections are not supported in PE files base.CreateSection(section, comdatName: null, symbolName, sectionStream); diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/UnixObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/UnixObjectWriter.cs index 71ab6fbcacfdd2..024ea5c53b34c3 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/UnixObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/UnixObjectWriter.cs @@ -30,7 +30,7 @@ protected UnixObjectWriter(NodeFactory factory, ObjectWritingOptions options) { } - 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) { if (section.Type != SectionType.Debug && section != LsdaSection && diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs index 9c579901fdb0d7..930df04624eca2 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs @@ -18,6 +18,7 @@ using Internal.TypeSystem; using Internal.TypeSystem.Ecma; using System.Security.Cryptography; +using ILCompiler.ObjectWriter; namespace ILCompiler.DependencyAnalysis { @@ -54,7 +55,7 @@ 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. @@ -139,7 +140,7 @@ public ReadyToRunObjectWriter( string objectFilePath, EcmaModule componentModule, IEnumerable inputFiles, - IEnumerable nodes, + IReadOnlyCollection nodes, NodeFactory factory, bool generateMapFile, bool generateMapCsvFile, @@ -190,6 +191,18 @@ public ReadyToRunObjectWriter( } } + public void WritePortableExecutableObject(Logger logger) + { + var stopwatch = Stopwatch.StartNew(); + + PEObjectWriter objectWriter = new(_nodeFactory, ObjectWritingOptions.None, _customPESectionAlignment == 0 ? null : _customPESectionAlignment); + objectWriter.EmitObject(_objectFilePath, _nodes, new NoopObjectDumper(), logger); + + stopwatch.Stop(); + if (logger.IsVerbose) + logger.LogMessage($"Done writing object file in {stopwatch.Elapsed}"); + } + public void EmitPortableExecutable() { bool succeeded = false; @@ -515,7 +528,7 @@ public static void EmitObject( string objectFilePath, EcmaModule componentModule, IEnumerable inputFiles, - IEnumerable nodes, + IReadOnlyCollection nodes, NodeFactory factory, bool generateMapFile, bool generateMapCsvFile, @@ -526,7 +539,8 @@ public static void EmitObject( int perfMapFormatVersion, bool generateProfileFile, CallChainProfile callChainProfile, - int customPESectionAlignment) + int customPESectionAlignment, + Logger logger) { Console.WriteLine($@"Emitting R2R PE file: {objectFilePath}"); ReadyToRunObjectWriter objectWriter = new ReadyToRunObjectWriter( @@ -547,5 +561,11 @@ public static void EmitObject( customPESectionAlignment); objectWriter.EmitPortableExecutable(); } + + private sealed class NoopObjectDumper : IObjectDumper + { + public void DumpObjectNode(NodeFactory factory, ObjectNode node, ObjectData objectData) { } + public void ReportFoldedNode(NodeFactory factory, ObjectNode originalNode, ISymbolNode targetNode) { } + } } } From 1620f1d7768989d4467e1064c9b0dcfd767eb9d0 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 17 Sep 2025 16:35:07 -0700 Subject: [PATCH 12/71] Fix sections for code to avoid AV issues --- .../DependencyAnalysis/ReadyToRun/MethodColdCodeNode.cs | 2 +- .../Compiler/DependencyAnalysis/ReadyToRun/MethodWithGCInfo.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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..b06819729f8e7f 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 @@ -22,7 +22,7 @@ public MethodColdCodeNode(MethodDesc owningMethod) public override ObjectNodeSection GetSection(NodeFactory factory) { - return factory.Target.IsWindows ? ObjectNodeSection.ManagedCodeWindowsContentSection : ObjectNodeSection.ManagedCodeUnixContentSection; + return factory.Target.IsWindows ? ObjectNodeSection.TextSection; } public override bool IsShareable => false; 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..bf0a6f361b6391 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 factory.Target.IsWindows ? ObjectNodeSection.TextSection; } public FrameInfo[] FrameInfos => _frameInfos; From 5cf1dea2285e7269e9083e5d9d4d4b0e844f8097 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 17 Sep 2025 16:35:33 -0700 Subject: [PATCH 13/71] Fix R2RCodegenCompilation -> R2RObjectWriter calls --- .../Compiler/ReadyToRunCodegenCompilation.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.cs index ab820af948a71c..b8ed4fe75c47fa 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.cs @@ -414,7 +414,8 @@ public override void Compile(string outputFile) perfMapFormatVersion: _perfMapFormatVersion, generateProfileFile: _generateProfileFile, callChainProfile: _profileData.CallChainProfile, - _customPESectionAlignment); + _customPESectionAlignment, + _logger); CompilationModuleGroup moduleGroup = _nodeFactory.CompilationModuleGroup; if (moduleGroup.IsCompositeBuildMode) @@ -509,7 +510,8 @@ private void RewriteComponentFile(string inputFile, string outputFile, string ow perfMapFormatVersion: _perfMapFormatVersion, generateProfileFile: false, _profileData.CallChainProfile, - customPESectionAlignment: 0); + customPESectionAlignment: 0, + _logger); } public override void WriteDependencyLog(string outputFileName) From 6ef1d40419f1175a387e885fe4ff72031f9b5e49 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 18 Sep 2025 10:49:14 -0700 Subject: [PATCH 14/71] Add missing alignment set --- src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs index ea38ba6d06e392..ae09cb0b259d32 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs @@ -55,6 +55,7 @@ private sealed record PESymbol(string Name, uint Offset); public PEObjectWriter(NodeFactory factory, ObjectWritingOptions options, int? sectionAlignment = null) : base(factory, options) { + _sectionAlignment = sectionAlignment; } public void AddExportedSymbol(string symbol) From 0cb628bc4f8f7dd9a7f9f41727bc9930fb1b2e8c Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 18 Sep 2025 14:25:35 -0700 Subject: [PATCH 15/71] Fix reloc handling and start moving over weird fixup paths to work on top of PEObjectWriter/ObjectWriter --- .../Compiler/DependencyAnalysis/Relocation.cs | 10 +- .../Compiler/ObjectWriter/CoffObjectWriter.cs | 11 +- .../Compiler/ObjectWriter/ElfObjectWriter.cs | 11 +- .../Compiler/ObjectWriter/MachObjectWriter.cs | 21 +- .../Compiler/ObjectWriter/ObjectWriter.cs | 10 +- .../Compiler/ObjectWriter/PEObjectWriter.cs | 300 +++++++++++------- .../ObjectWriter/StringTableBuilder.cs | 2 +- .../CodeGen/ReadyToRunObjectWriter.cs | 44 ++- .../ReadyToRun/DebugDirectoryNode.cs | 4 + .../ReadyToRun/RuntimeFunctionsTableNode.cs | 2 + .../ReadyToRunCodegenNodeFactory.cs | 3 +- .../Compiler/ReadyToRunCodegenCompilation.cs | 4 +- 12 files changed, 271 insertions(+), 151 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/Relocation.cs b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/Relocation.cs index 91991a6882ebd4..a7ab6873d8af29 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 @@ -560,6 +566,8 @@ 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, _ => throw new NotSupportedException(), }; diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs index 38307c13c83bf2..accbc91b01a73d 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs @@ -360,9 +360,8 @@ private protected override void EmitRelocations(int sectionIndex, List IsBigObj ? BigObjSize : RegularSize; - public void Write(FileStream stream) + public void Write(Stream stream) { if (!IsBigObj) { @@ -575,7 +574,7 @@ protected 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]; @@ -688,7 +687,7 @@ protected sealed class CoffRelocation sizeof(uint) + // SymbolTableIndex sizeof(ushort); // Type - public void Write(FileStream stream) + public void Write(Stream stream) { Span buffer = stackalloc byte[Size]; @@ -838,7 +837,7 @@ protected 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/Common/Compiler/ObjectWriter/ElfObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/ElfObjectWriter.cs index 4253398e98157f..442fc61c7b4fbf 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/ElfObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/ElfObjectWriter.cs @@ -600,9 +600,8 @@ private protected override void EmitSectionsAndLayout() } } - private protected override void EmitObjectFile(string objectFilePath) + private protected override void EmitObjectFile(Stream outputFileStream) { - using var outputFileStream = new FileStream(objectFilePath, FileMode.Create); switch (_machine) { case EM_386: @@ -615,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(); @@ -923,7 +922,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()]; @@ -981,7 +980,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()]; @@ -1017,7 +1016,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/Common/Compiler/ObjectWriter/MachObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/MachObjectWriter.cs index 8b0ef543000c6f..ef2fddeec53c57 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/MachObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/MachObjectWriter.cs @@ -161,7 +161,7 @@ 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); @@ -183,8 +183,6 @@ private protected override void EmitObjectFile(string objectFilePath) uint segmentFileOffset = fileOffset; LayoutSections(ref fileOffset, out uint segmentFileSize, out ulong segmentSize); - using var outputFileStream = new FileStream(objectFilePath, FileMode.Create); - MachHeader64 machHeader = new MachHeader64 { CpuType = _cpuType, @@ -354,6 +352,7 @@ private protected override void CreateSection(ObjectNodeSection section, string Log2Alignment = 1, Flags = flags, }; + _sections.Add(machSection); base.CreateSection(section, comdatName, symbolName ?? $"lsection{sectionIndex}", sectionIndex, sectionStream); @@ -720,7 +719,7 @@ private struct MachHeader64 public static int HeaderSize => 32; - public void Write(FileStream stream) + public void Write(Stream stream) { Span buffer = stackalloc byte[HeaderSize]; @@ -751,7 +750,7 @@ public struct MachSegment64Header public static int HeaderSize => 72; - public void Write(FileStream stream) + public void Write(Stream stream) { Span buffer = stackalloc byte[HeaderSize]; @@ -810,7 +809,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]; @@ -840,7 +839,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; @@ -862,7 +861,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); @@ -886,7 +885,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]; @@ -924,7 +923,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]; @@ -961,7 +960,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/Common/Compiler/ObjectWriter/ObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs index c11faa1115ac1b..d433543c03ef99 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs @@ -273,7 +273,7 @@ private protected virtual void EmitSectionsAndLayout() { } - private protected abstract void EmitObjectFile(string objectFilePath); + private protected abstract void EmitObjectFile(Stream outputFileStream); partial void EmitDebugInfo(IReadOnlyCollection nodes, Logger logger); @@ -291,7 +291,7 @@ private SortedSet GetUndefinedSymbols() return undefinedSymbolSet; } - public 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); @@ -473,7 +473,7 @@ public void EmitObject(string objectFilePath, IReadOnlyCollection _definedPESymbols = new(); private readonly List _exportedPESymbols = new(); - private readonly int? _sectionAlignment; - - // Note: Sections are emitted in the order provided by _sections. - // Grouping/merging of sections by base name (suffixes like '$') has been - // removed to simplify layout and emission. + private readonly int _sectionAlignment; private HashSet _exportedSymbolNames = new(); - public PEObjectWriter(NodeFactory factory, ObjectWritingOptions options, int? sectionAlignment = null) + /// + /// The offset of the .debug section in the emitted object file. + /// + public uint DebugSectionOffsetInStream { get; private set; } + + public PEObjectWriter(NodeFactory factory, ObjectWritingOptions options, int sectionAlignment) : base(factory, options) { _sectionAlignment = sectionAlignment; @@ -71,17 +78,15 @@ private protected override void CreateSection(ObjectNodeSection section, string // COMDAT sections are not supported in PE files base.CreateSection(section, comdatName: null, symbolName, sectionIndex, sectionStream); - if (_sectionAlignment is { } alignment) + if (_sectionAlignment != 0) { - UpdateSectionAlignment(sectionIndex, alignment); + UpdateSectionAlignment(sectionIndex, _sectionAlignment); } - // COMDAT sections are not supported in PE files - base.CreateSection(section, comdatName: null, symbolName, sectionStream); } private struct PEOptionalHeader { - public bool IsPE32Plus { get; private set; } + public bool IsPE32Plus { get; } // Standard fields public byte MajorLinkerVersion { get; set; } @@ -163,7 +168,7 @@ public PEOptionalHeader(Machine machine) NumberOfRvaAndSizes = 16u; } - public void Write(FileStream stream, OptionalHeaderDataDirectories dataDirectories) + 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. @@ -311,19 +316,18 @@ private protected override void EmitSymbolTable(IDictionary= _sections.Count) continue; uint targetRva = _sections[sectionIdx].Header.VirtualAddress + (uint)symbolDefinition.Value; PESymbol sym = new(symbolName, targetRva); - _exportedPESymbols.Add(sym); + _definedPESymbols.Add(symbolName, sym); + + if (_exportedSymbolNames.Contains(symbolName)) + { + _exportedPESymbols.Add(sym); + } } } @@ -369,6 +373,8 @@ private protected override void EmitSectionsAndLayout() h.SizeOfRawData = (uint)s.Stream.Length; uint rawAligned = h.SectionCharacteristics.HasFlag(SectionCharacteristics.ContainsUninitializedData) ? 0u : (uint)AlignmentHelper.AlignUp((int)h.SizeOfRawData, (int)fileAlignment); + uint offsetToStart = pointerToRawData; + if (rawAligned != 0) { h.PointerToRawData = pointerToRawData; @@ -394,6 +400,12 @@ private protected override void EmitSectionsAndLayout() _pdataRva = h.VirtualAddress; _pdataSize = h.VirtualSize != 0 ? h.VirtualSize : h.SizeOfRawData; } + else if (h.Name == ".debug") + { + _debugRva = h.VirtualAddress; + _debugSize = h.VirtualSize != 0 ? h.VirtualSize : h.SizeOfRawData; + DebugSectionOffsetInStream = offsetToStart; + } } // If we have exports, create an export data section (.edata) and place it after other sections. @@ -434,123 +446,178 @@ private protected override void EmitSectionsAndLayout() _peSizeOfInitializedData = sizeOfInitializedData; } - private protected override void EmitRelocations(int sectionIndex, List relocationList) + private protected override unsafe void EmitRelocations(int sectionIndex, List relocationList) { - base.EmitRelocations(sectionIndex, relocationList); + foreach (var reloc in relocationList) + { + switch (reloc.Type) + { + case RelocType.IMAGE_REL_BASED_HIGHLOW: + case RelocType.IMAGE_REL_BASED_DIR64: + case RelocType.IMAGE_REL_BASED_THUMB_MOV32: + case RelocType.IMAGE_REL_BASED_ABSOLUTE: + { + // 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). + RelocType fileRelocType = Relocation.GetFileRelocationType(reloc.Type); + uint targetRva = _sections[sectionIndex].Header.VirtualAddress + (uint)reloc.Offset; + uint pageRva = targetRva & ~0xFFFu; + ushort offsetInPage = (ushort)(targetRva & 0xFFFu); + ushort entry = (ushort)(((ushort)fileRelocType << 12) | offsetInPage); + + if (!_baseRelocMap.TryGetValue(pageRva, out var list)) + { + list = new List(); + _baseRelocMap.Add(pageRva, list); + } + list.Add(entry); + break; + } + case RelocType.IMAGE_REL_BASED_ADDR32NB: + fixed (byte* pData = GetRelocDataSpan(reloc)) + { + long rva = _sections[sectionIndex].Header.VirtualAddress + reloc.Offset; + Relocation.WriteValue(reloc.Type, (void*)pData, rva); + WriteRelocDataSpan(reloc, pData); + } + break; + case RelocType.IMAGE_REL_BASED_REL32: + case RelocType.IMAGE_REL_BASED_RELPTR32: + { + PESymbol definedSymbol = _definedPESymbols[reloc.SymbolName]; + fixed (byte* pData = GetRelocDataSpan(reloc)) + { + long adjustedAddend = reloc.Addend; - // 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). When this - // override is invoked for the last section, synthesize the - // .reloc section from the collected entries and append it to - // the image section list so that it will be emitted by - // EmitObjectFile. + adjustedAddend -= 4; - foreach (var symRel in relocationList) - { - RelocType fileRelocType = Relocation.GetFileRelocationType(symRel.Type); - if (fileRelocType == RelocType.IMAGE_REL_BASED_ABSOLUTE) - continue; + adjustedAddend += definedSymbol.Offset; + adjustedAddend += Relocation.ReadValue(reloc.Type, (void*)pData); + adjustedAddend -= reloc.Offset; + + Relocation.WriteValue(reloc.Type, (void*)pData, adjustedAddend); + WriteRelocDataSpan(reloc, pData); + } + break; + } + case RelocType.IMAGE_REL_FILE_ABSOLUTE: + fixed (byte* pData = GetRelocDataSpan(reloc)) + { + long rva = _sections[sectionIndex].Header.PointerToRawData + reloc.Offset; + Relocation.WriteValue(reloc.Type, (void*)pData, rva); + WriteRelocDataSpan(reloc, pData); + } + break; + default: + throw new NotSupportedException($"Unsupported relocation: {reloc.Type}"); + } - uint targetRva = _sections[sectionIndex].Header.VirtualAddress + (uint)symRel.Offset; - uint pageRva = targetRva & ~0xFFFu; - ushort offsetInPage = (ushort)(targetRva & 0xFFFu); - ushort entry = (ushort)(((ushort)fileRelocType << 12) | offsetInPage); + Span GetRelocDataSpan(SymbolicRelocation reloc) + { + Stream stream = _sections[sectionIndex].Stream; + stream.Position = reloc.Offset; + Span data = new byte[Relocation.GetSize(reloc.Type)]; + stream.ReadExactly(data); + return data; + } - if (!_baseRelocMap.TryGetValue(pageRva, out var list)) + void WriteRelocDataSpan(SymbolicRelocation reloc, byte* data) { - list = new List(); - _baseRelocMap.Add(pageRva, list); + Stream stream = _sections[sectionIndex].Stream; + stream.Position = reloc.Offset; + stream.Write(new Span(data, Relocation.GetSize(reloc.Type))); } - list.Add(entry); } + } - // If this is the last section being processed, create the - // .reloc section from the accumulated relocation blocks. - if (sectionIndex == _sectionIndexToRelocations.Count - 1 && _baseRelocMap.Count > 0) - { - var ms = new MemoryStream(); + private void AddRelocSection() + { + var ms = new MemoryStream(); - foreach (var kv in _baseRelocMap) + foreach (var kv in _baseRelocMap) + { + uint pageRva = kv.Key; + List entries = kv.Value; + entries.Sort(); + + int entriesSize = entries.Count * 2; + int sizeOfBlock = 8 + entriesSize; + // Pad to 4-byte alignment as customary + if ((sizeOfBlock & 3) != 0) + sizeOfBlock += 2; + + byte[] headerBuf = new byte[8]; + BinaryPrimitives.WriteUInt32LittleEndian(headerBuf.AsSpan(0, 4), pageRva); + BinaryPrimitives.WriteUInt32LittleEndian(headerBuf.AsSpan(4, 4), (uint)sizeOfBlock); + ms.Write(headerBuf, 0, headerBuf.Length); + + // Emit entries + foreach (ushort e in entries) { - uint pageRva = kv.Key; - List entries = kv.Value; - entries.Sort(); - - int entriesSize = entries.Count * 2; - int sizeOfBlock = 8 + entriesSize; - // Pad to 4-byte alignment as customary - if ((sizeOfBlock & 3) != 0) - sizeOfBlock += 2; - - byte[] headerBuf = new byte[8]; - BinaryPrimitives.WriteUInt32LittleEndian(headerBuf.AsSpan(0, 4), pageRva); - BinaryPrimitives.WriteUInt32LittleEndian(headerBuf.AsSpan(4, 4), (uint)sizeOfBlock); - ms.Write(headerBuf, 0, headerBuf.Length); - - // Emit entries - foreach (ushort e in entries) - { - byte[] w = new byte[2]; - BinaryPrimitives.WriteUInt16LittleEndian(w.AsSpan(), e); - ms.Write(w, 0, 2); - } - - // Ensure block is 4-byte aligned by padding a WORD if needed - if (((entriesSize) & 3) != 0) - { - byte[] pad = new byte[2]; - BinaryPrimitives.WriteUInt16LittleEndian(pad.AsSpan(), 0); - ms.Write(pad, 0, 2); - } + byte[] w = new byte[2]; + BinaryPrimitives.WriteUInt16LittleEndian(w.AsSpan(), e); + ms.Write(w, 0, 2); } - // Create a new section for .reloc and compute its raw/virtual layout - var relocHeader = new CoffSectionHeader + // Ensure block is 4-byte aligned by padding a WORD if needed + if (((entriesSize) & 3) != 0) { - Name = ".reloc", - SectionCharacteristics = SectionCharacteristics.MemRead | SectionCharacteristics.ContainsInitializedData - }; + byte[] pad = new byte[2]; + BinaryPrimitives.WriteUInt16LittleEndian(pad.AsSpan(), 0); + ms.Write(pad, 0, 2); + } + } - var relocSection = new SectionDefinition(relocHeader, ms, new List(), null, null, ".reloc"); + // Create a new section for .reloc and compute its raw/virtual layout + var relocHeader = new CoffSectionHeader + { + Name = ".reloc", + SectionCharacteristics = SectionCharacteristics.MemRead | SectionCharacteristics.ContainsInitializedData | SectionCharacteristics.MemDiscardable, + }; + + var relocSection = new SectionDefinition(relocHeader, ms, new List(), null, null, ".reloc"); - // Compute pointer to raw data: find last used raw end and align - uint lastRawEnd = 0; - uint lastVEnd = 0; - foreach (var s in _sections) + // Compute pointer to raw data: find last used raw end and align + uint lastRawEnd = 0; + uint lastVEnd = 0; + foreach (var s in _sections) + { + if (s.Header.PointerToRawData != 0) { - if (s.Header.PointerToRawData != 0) - { - lastRawEnd = Math.Max(lastRawEnd, s.Header.PointerToRawData + s.Header.SizeOfRawData); - } - uint vEnd = s.Header.VirtualAddress + (uint)AlignmentHelper.AlignUp((int)s.Header.VirtualSize, (int)_peSectionAlignment); - lastVEnd = Math.Max(lastVEnd, vEnd); + lastRawEnd = Math.Max(lastRawEnd, s.Header.PointerToRawData + s.Header.SizeOfRawData); } + uint vEnd = s.Header.VirtualAddress + (uint)AlignmentHelper.AlignUp((int)s.Header.VirtualSize, (int)_peSectionAlignment); + lastVEnd = Math.Max(lastVEnd, vEnd); + } - uint pointerToRawData = lastRawEnd == 0 ? (uint)AlignmentHelper.AlignUp((int)_peSizeOfHeaders, (int)_peFileAlignment) : lastRawEnd; - relocHeader.PointerToRawData = pointerToRawData; - relocHeader.SizeOfRawData = (uint)AlignmentHelper.AlignUp((int)ms.Length, (int)_peFileAlignment); - relocHeader.VirtualAddress = (uint)AlignmentHelper.AlignUp((int)lastVEnd, (int)_peSectionAlignment); - relocHeader.VirtualSize = (uint)ms.Length; + uint pointerToRawData = lastRawEnd == 0 ? (uint)AlignmentHelper.AlignUp((int)_peSizeOfHeaders, (int)_peFileAlignment) : lastRawEnd; + relocHeader.PointerToRawData = pointerToRawData; + relocHeader.SizeOfRawData = (uint)AlignmentHelper.AlignUp((int)ms.Length, (int)_peFileAlignment); + relocHeader.VirtualAddress = (uint)AlignmentHelper.AlignUp((int)lastVEnd, (int)_peSectionAlignment); + relocHeader.VirtualSize = (uint)ms.Length; - _sections.Add(relocSection); - _sectionIndexToRelocations.Add(new List()); + _sections.Add(relocSection); + _sectionIndexToRelocations.Add(new List()); - _baseRelocRva = relocHeader.VirtualAddress; - _baseRelocSize = (uint)ms.Length; - } + _baseRelocRva = relocHeader.VirtualAddress; + _baseRelocSize = (uint)ms.Length; } - private protected override void EmitObjectFile(string objectFilePath) + private protected override void EmitObjectFile(Stream outputFileStream) { - using var stream = new FileStream(objectFilePath, FileMode.Create, FileAccess.Write); + if (_baseRelocMap.Count > 0) + { + AddRelocSection(); + } // Write a minimal DOS stub and set the e_lfanew to 0x80 where we'll // place the PE headers. + // TODO: Add DOS execution program stub. var dos = new byte[0x80]; "MZ"u8.CopyTo(dos); BinaryPrimitives.WriteInt32LittleEndian(dos.AsSpan(0x3c), 0x80); - stream.Write(dos); + outputFileStream.Write(dos); ushort numberOfSections = (ushort)_sections.Count; bool isPE32Plus = _nodeFactory.Target.PointerSize == 8; @@ -590,12 +657,19 @@ private protected override void EmitObjectFile(string objectFilePath) uint sizeOfImage = _peSizeOfImage; // Write PE Signature at e_lfanew (0x80) - stream.Position = 0x80; - stream.Write("PE\0\0"u8); + outputFileStream.Position = 0x80; + outputFileStream.Write("PE\0\0"u8); 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 { @@ -608,7 +682,7 @@ private protected override void EmitObjectFile(string objectFilePath) Characteristics = characteristics, }; - coffHeader.Write(stream); + coffHeader.Write(outputFileStream); var peOptional = new PEOptionalHeader(_machine) { @@ -659,7 +733,7 @@ private protected override void EmitObjectFile(string objectFilePath) { dataDirs.Set((int)ImageDirectoryEntry.BaseRelocation, _baseRelocRva, _baseRelocSize); } - peOptional.Write(stream, dataDirs); + peOptional.Write(outputFileStream, dataDirs); CoffStringTable stringTable = new(); @@ -667,7 +741,7 @@ private protected override void EmitObjectFile(string objectFilePath) for (int i = 0; i < _sections.Count; i++) { var hdr = _sections[i].Header; - hdr.Write(stream, stringTable); + hdr.Write(outputFileStream, stringTable); } // Write section content @@ -675,9 +749,9 @@ private protected override void EmitObjectFile(string objectFilePath) { if (!section.Header.SectionCharacteristics.HasFlag(SectionCharacteristics.ContainsUninitializedData)) { - Debug.Assert(stream.Position == section.Header.PointerToRawData); + Debug.Assert(outputFileStream.Position == section.Header.PointerToRawData); section.Stream.Position = 0; - section.Stream.CopyTo(stream); + section.Stream.CopyTo(outputFileStream); } } diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/StringTableBuilder.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/StringTableBuilder.cs index 81f7315d225e4a..0c6d1831dd4eb8 100644 --- a/src/coreclr/tools/Common/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/CodeGen/ReadyToRunObjectWriter.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs index 930df04624eca2..1b1aea02c5347d 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs @@ -5,20 +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 ILCompiler.ObjectWriter; +using ObjectData = ILCompiler.DependencyAnalysis.ObjectNode.ObjectData; namespace ILCompiler.DependencyAnalysis { @@ -195,8 +194,39 @@ public void WritePortableExecutableObject(Logger logger) { var stopwatch = Stopwatch.StartNew(); - PEObjectWriter objectWriter = new(_nodeFactory, ObjectWritingOptions.None, _customPESectionAlignment == 0 ? null : _customPESectionAlignment); - objectWriter.EmitObject(_objectFilePath, _nodes, new NoopObjectDumper(), logger); + PEObjectWriter objectWriter = new(_nodeFactory, ObjectWritingOptions.None, _customPESectionAlignment); + using FileStream stream = new FileStream(_objectFilePath, FileMode.Create); + objectWriter.EmitObject(stream, _nodes, new NoopObjectDumper(), logger); + + if (_mapFileBuilder != null) + { + _mapFileBuilder.SetFileSize(stream.Length); + } + + if (_nodeFactory.DebugDirectoryNode.PdbEntry is {} nativeDebugDirectoryEntryNode) + { + Debug.Assert(_generatePdbFile); + // Compute hash of the output image and store that in the native DebugDirectory entry + using (var hashAlgorithm = SHA256.Create()) + { + stream.Seek(0, SeekOrigin.Begin); + byte[] hash = hashAlgorithm.ComputeHash(stream); + byte[] rsdsEntry = nativeDebugDirectoryEntryNode.GenerateRSDSEntryData(hash); + + stream.Seek(objectWriter.DebugSectionOffsetInStream, SeekOrigin.Begin); + stream.Write(rsdsEntry); + } + } + + if (_nodeFactory.DebugDirectoryNode.PerfMapEntry is { } perfMapDebugDirectoryEntryNode) + { + Debug.Assert(_generatePerfMapFile && _outputInfoBuilder is not null && _outputInfoBuilder.EnumerateInputAssemblies().Any()); + byte[] perfmapSig = PerfMapWriter.PerfMapV1SignatureHelper(_outputInfoBuilder.EnumerateInputAssemblies(), _nodeFactory.Target); + byte[] perfMapEntry = perfMapDebugDirectoryEntryNode.GeneratePerfMapEntryData(perfmapSig, _perfMapFormatVersion); + + stream.Seek(objectWriter.DebugSectionOffsetInStream, SeekOrigin.Begin); + stream.Write(perfMapEntry); + } stopwatch.Stop(); if (logger.IsVerbose) 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 17a9ed64acb64c..b9955811b30f54 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 @@ -74,6 +74,10 @@ public override ObjectNodeSection GetSection(NodeFactory factory) + (_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/RuntimeFunctionsTableNode.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/RuntimeFunctionsTableNode.cs index 7918c70c0fdb03..a23d4249c218cd 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 @@ -157,6 +157,8 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) return runtimeFunctionsBuilder.ToObjectData(); } + public override bool ShouldSkipEmittingObjectNode(NodeFactory factory) => factory.OptimizationFlags.IsComponentModule; + /// /// Returns the runtime functions table size and excludes the 4 byte sentinel entry at the end (used by /// the runtime in NativeUnwindInfoLookupTable::LookupUnwindInfoForMethod) so that it's not treated as 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 e4f614973d062f..e293a1bb89d537 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 diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.cs index b8ed4fe75c47fa..652fe4d24b8b72 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.cs @@ -460,6 +460,8 @@ 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); @@ -475,7 +477,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 From eb2554c439e6b7a25d02eaaa53f4ca1ca7d01972 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 19 Sep 2025 16:30:40 -0700 Subject: [PATCH 16/71] Fix typos --- .../DependencyAnalysis/ReadyToRun/MethodColdCodeNode.cs | 2 +- .../Compiler/DependencyAnalysis/ReadyToRun/MethodWithGCInfo.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 b06819729f8e7f..6fad4eac8ef2d9 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 @@ -22,7 +22,7 @@ public MethodColdCodeNode(MethodDesc owningMethod) public override ObjectNodeSection GetSection(NodeFactory factory) { - return factory.Target.IsWindows ? ObjectNodeSection.TextSection; + return ObjectNodeSection.TextSection; } public override bool IsShareable => false; 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 bf0a6f361b6391..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.TextSection; + return ObjectNodeSection.TextSection; } public FrameInfo[] FrameInfos => _frameInfos; From 89a797cd24c6511af446bd9bf32d42d0dd902eb7 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 19 Sep 2025 16:58:27 -0700 Subject: [PATCH 17/71] Remove object dumper as null is okay --- .../CodeGen/ReadyToRunObjectWriter.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs index 1b1aea02c5347d..0afee9dc9938e0 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs @@ -196,7 +196,7 @@ public void WritePortableExecutableObject(Logger logger) PEObjectWriter objectWriter = new(_nodeFactory, ObjectWritingOptions.None, _customPESectionAlignment); using FileStream stream = new FileStream(_objectFilePath, FileMode.Create); - objectWriter.EmitObject(stream, _nodes, new NoopObjectDumper(), logger); + objectWriter.EmitObject(stream, _nodes, dumper: null, logger); if (_mapFileBuilder != null) { @@ -591,11 +591,5 @@ public static void EmitObject( customPESectionAlignment); objectWriter.EmitPortableExecutable(); } - - private sealed class NoopObjectDumper : IObjectDumper - { - public void DumpObjectNode(NodeFactory factory, ObjectNode node, ObjectData objectData) { } - public void ReportFoldedNode(NodeFactory factory, ObjectNode originalNode, ISymbolNode targetNode) { } - } } } From 93bb15ab8ed4dabc97dd89e10df5e307880da6d8 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 19 Sep 2025 17:09:56 -0700 Subject: [PATCH 18/71] Fix how we include/exclude the runtime functions table for component modules --- .../ReadyToRun/RuntimeFunctionsTableNode.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) 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 a23d4249c218cd..66133bf6f136f4 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 @@ -25,7 +25,7 @@ public RuntimeFunctionsTableNode(NodeFactory nodeFactory) public override ObjectNodeSection GetSection(NodeFactory factory) { - if (factory.Target.IsWindows) + if (factory.Target.IsWindows && !factory.OptimizationFlags.IsComponentModule) return ObjectNodeSection.PDataSection; else return ObjectNodeSection.DataSection; @@ -157,8 +157,6 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) return runtimeFunctionsBuilder.ToObjectData(); } - public override bool ShouldSkipEmittingObjectNode(NodeFactory factory) => factory.OptimizationFlags.IsComponentModule; - /// /// Returns the runtime functions table size and excludes the 4 byte sentinel entry at the end (used by /// the runtime in NativeUnwindInfoLookupTable::LookupUnwindInfoForMethod) so that it's not treated as From 626e8fcfce5f1c4d5149356be05e9ad7e319b90f Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 19 Sep 2025 17:27:56 -0700 Subject: [PATCH 19/71] Put debug data in .text in the existing linker for now --- .../tools/aot/ILCompiler.ReadyToRun/ObjectWriter/R2RPEBuilder.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/R2RPEBuilder.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/R2RPEBuilder.cs index 738910d98e92e5..eb8870ad9bf575 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/R2RPEBuilder.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/R2RPEBuilder.cs @@ -211,6 +211,7 @@ public void AddObjectData(DependencyAnalysis.ObjectNode.ObjectData objectData, O switch (section.Type) { case SectionType.ReadOnly: + case SectionType.Debug: // We put ReadOnly data into the text section to limit the number of sections. case SectionType.Executable: targetSectionIndex = _textSectionIndex; From 23a755b89519cf861e216092e01699d45bad003b Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 25 Sep 2025 11:26:41 -0700 Subject: [PATCH 20/71] We don't have info about output format at this time, so always put the runtime functions data into .pdata --- .../ReadyToRun/RuntimeFunctionsTableNode.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) 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 66133bf6f136f4..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 @@ -25,10 +25,7 @@ public RuntimeFunctionsTableNode(NodeFactory nodeFactory) public override ObjectNodeSection GetSection(NodeFactory factory) { - if (factory.Target.IsWindows && !factory.OptimizationFlags.IsComponentModule) - return ObjectNodeSection.PDataSection; - else - return ObjectNodeSection.DataSection; + return ObjectNodeSection.PDataSection; } public override void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb) From 3e876011a449f3d95eb8c47d36dff0453a49da94 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 25 Sep 2025 11:26:59 -0700 Subject: [PATCH 21/71] Add suppression --- .../tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs index 766cf723353524..2711b699e4b253 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs @@ -34,7 +34,7 @@ internal sealed class PEObjectWriter : CoffObjectWriter private uint _pdataRva; private uint _pdataSize; - + private uint _debugRva; private uint _debugSize; @@ -420,7 +420,9 @@ private protected override void EmitSectionsAndLayout() edataHeader.SizeOfRawData = (uint)edataSection.Stream.Length; uint edataRawAligned = (uint)AlignmentHelper.AlignUp((int)edataHeader.SizeOfRawData, (int)fileAlignment); edataHeader.PointerToRawData = pointerToRawData; +#pragma warning disable IDE0059 // Unnecessary assignment. We don't want to remove this and forget it if we add more sections after .edata. pointerToRawData += edataRawAligned; +#pragma warning restore IDE0059 // Unnecessary assignment edataHeader.SizeOfRawData = edataRawAligned; edataHeader.VirtualAddress = virtualAddress; From 482d03564b5462859a98ef686f923c7ab6325161 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 25 Sep 2025 11:44:34 -0700 Subject: [PATCH 22/71] Root the delay load method thunks range node --- .../Compiler/DependencyAnalysis/ReadyToRun/HeaderNode.cs | 7 ------- .../DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs | 1 + 2 files changed, 1 insertion(+), 7 deletions(-) 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/ReadyToRunCodegenNodeFactory.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs index e293a1bb89d537..abe41e3c3c5056 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs @@ -700,6 +700,7 @@ 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"); Header.Add(Internal.Runtime.ReadyToRunSectionType.DelayLoadMethodCallThunks, DelayLoadMethodCallThunks, DelayLoadMethodCallThunks); ExceptionInfoLookupTableNode exceptionInfoLookupTableNode = new ExceptionInfoLookupTableNode(this); From fd585d9122c7f3ad9d93a623315f28f649b217d5 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 25 Sep 2025 12:26:05 -0700 Subject: [PATCH 23/71] Copy all metadata into the .cormeta section --- .../DependencyAnalysis/ReadyToRun/CopiedFieldRvaNode.cs | 7 ++++++- .../ReadyToRun/CopiedManagedResourcesNode.cs | 7 ++++++- .../ReadyToRun/CopiedMetadataBlobNode.cs | 7 ++++++- .../DependencyAnalysis/ReadyToRun/CopiedMethodILNode.cs | 7 ++++++- .../ReadyToRun/CopiedStrongNameSignatureNode.cs | 7 ++++++- 5 files changed, 30 insertions(+), 5 deletions(-) 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..de89d799abafe0 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,12 @@ public CopiedFieldRvaNode(EcmaModule module, int rva) _module = module; } - public override ObjectNodeSection GetSection(NodeFactory factory) => ObjectNodeSection.TextSection; + public override ObjectNodeSection GetSection(NodeFactory factory) + { + if (factory.Target.IsWindows) + return ObjectNodeSection.CorMetaSection; + return ObjectNodeSection.TextSection; + } 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..1f5dbc3970798d 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,12 @@ public CopiedManagedResourcesNode(EcmaModule module) _module = module; } - public override ObjectNodeSection GetSection(NodeFactory factory) => ObjectNodeSection.TextSection; + public override ObjectNodeSection GetSection(NodeFactory factory) + { + if (factory.Target.IsWindows) + return ObjectNodeSection.CorMetaSection; + return ObjectNodeSection.TextSection; + } 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..fd780e768c72a8 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,12 @@ public CopiedMetadataBlobNode(EcmaModule sourceModule) _sourceModule = sourceModule; } - public override ObjectNodeSection GetSection(NodeFactory factory) => ObjectNodeSection.TextSection; + public override ObjectNodeSection GetSection(NodeFactory factory) + { + if (factory.Target.IsWindows) + return ObjectNodeSection.CorMetaSection; + return ObjectNodeSection.TextSection; + } 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..54a395555b6d59 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,12 @@ public CopiedMethodILNode(EcmaMethod method) _method = (EcmaMethod)method.GetTypicalMethodDefinition(); } - public override ObjectNodeSection GetSection(NodeFactory factory) => ObjectNodeSection.TextSection; + public override ObjectNodeSection GetSection(NodeFactory factory) + { + if (factory.Target.IsWindows) + return ObjectNodeSection.CorMetaSection; + return ObjectNodeSection.TextSection; + } 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..0f69f821876289 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,12 @@ public CopiedStrongNameSignatureNode(EcmaModule module) _module = module; } - public override ObjectNodeSection GetSection(NodeFactory factory) => ObjectNodeSection.TextSection; + public override ObjectNodeSection GetSection(NodeFactory factory) + { + if (factory.Target.IsWindows) + return ObjectNodeSection.CorMetaSection; + return ObjectNodeSection.TextSection; + } public override bool IsShareable => false; From 871c1a1c153744cdf9af94649a95dc3505e1b37b Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 25 Sep 2025 12:27:07 -0700 Subject: [PATCH 24/71] Add support for a symbol that represents a range between two symbols to ObjectWriter and move R2R usage over to be based on that. --- .../DependencyAnalysis/ISymbolNode.cs | 17 +++++++ .../Compiler/ObjectWriter/ObjectWriter.cs | 47 +++++++++++++++++-- .../CodeGen/ReadyToRunObjectWriter.cs | 20 ++------ .../DelayLoadMethodCallThunkNodeRange.cs | 32 +++++++++++-- .../ReadyToRunCodegenNodeFactory.cs | 1 + 5 files changed, 93 insertions(+), 24 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/ISymbolNode.cs b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/ISymbolNode.cs index b29ad233a3a94e..fd6738a66f5684 100644 --- a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/ISymbolNode.cs +++ b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/ISymbolNode.cs @@ -73,6 +73,23 @@ 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 symbol that should not be shared with another symbol during writing of the object file. /// diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs index d433543c03ef99..e705e54f0ba903 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs @@ -10,8 +10,8 @@ using ILCompiler.DependencyAnalysis; using ILCompiler.DependencyAnalysisFramework; using Internal.TypeSystem; -using ObjectData = ILCompiler.DependencyAnalysis.ObjectNode.ObjectData; using static ILCompiler.DependencyAnalysis.RelocType; +using ObjectData = ILCompiler.DependencyAnalysis.ObjectNode.ObjectData; namespace ILCompiler.ObjectWriter { @@ -323,11 +323,17 @@ public void EmitObject(Stream outputFileStream, IReadOnlyCollection blocksToRelocate = new(); + List symbolRangeNodes = []; + List blocksToRelocate = []; 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) @@ -429,6 +435,39 @@ public void EmitObject(Stream outputFileStream, IReadOnlyCollection - public class DelayLoadMethodCallThunkNodeRange : DependencyNodeCore, ISymbolDefinitionNode + public class DelayLoadMethodCallThunkNodeRange : DependencyNodeCore, ISymbolRangeNode { + 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; + 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) => "DelayLoadMethodCallThunkNodeRange"; public void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb) { sb.Append(GetName(null)); } + + 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/ReadyToRunCodegenNodeFactory.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs index abe41e3c3c5056..113efe5a0d13cb 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs @@ -701,6 +701,7 @@ public void AttachToDependencyGraph(DependencyAnalyzerBase graph, I 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); From f1196a26c0ce703b8739fb041985029376fd7b32 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 25 Sep 2025 12:40:56 -0700 Subject: [PATCH 25/71] Write in the DOS program stub --- .../Compiler/ObjectWriter/PEObjectWriter.cs | 41 +++++++++++++------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs index 2711b699e4b253..38defe1cdbb5c9 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs @@ -22,6 +22,31 @@ namespace ILCompiler.ObjectWriter /// internal sealed class PEObjectWriter : CoffObjectWriter { + internal const int DosHeaderSize = 0x80; + + 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 + ]; + // PE layout computed ahead of writing the file. These are populated by // EmitSectionsAndLayout so that data directories (e.g. exception table) // can be filled prior to writing the optional header. @@ -356,7 +381,7 @@ private protected override void EmitSectionsAndLayout() _peSectionAlignment = sectionAlignment; // Compute headers size and align to file alignment - uint sizeOfHeadersUnaligned = (uint)(0x80 + 4 + 20 + sizeOfOptionalHeader + 40 * numberOfSections); + uint sizeOfHeadersUnaligned = (uint)(DosHeaderSize + 4 + 20 + sizeOfOptionalHeader + 40 * numberOfSections); uint sizeOfHeaders = (uint)AlignmentHelper.AlignUp((int)sizeOfHeadersUnaligned, (int)fileAlignment); // Calculate layout for sections: raw file offsets and virtual addresses @@ -613,13 +638,9 @@ private protected override void EmitObjectFile(Stream outputFileStream) AddRelocSection(); } - // Write a minimal DOS stub and set the e_lfanew to 0x80 where we'll - // place the PE headers. - // TODO: Add DOS execution program stub. - var dos = new byte[0x80]; - "MZ"u8.CopyTo(dos); - BinaryPrimitives.WriteInt32LittleEndian(dos.AsSpan(0x3c), 0x80); - outputFileStream.Write(dos); + outputFileStream.Write(DosHeader); + Debug.Assert(DosHeader.Length == DosHeaderSize); + outputFileStream.Write("PE\0\0"u8); ushort numberOfSections = (ushort)_sections.Count; bool isPE32Plus = _nodeFactory.Target.PointerSize == 8; @@ -658,10 +679,6 @@ private protected override void EmitObjectFile(Stream outputFileStream) uint sizeOfInitializedData = _peSizeOfInitializedData; uint sizeOfImage = _peSizeOfImage; - // Write PE Signature at e_lfanew (0x80) - outputFileStream.Position = 0x80; - outputFileStream.Write("PE\0\0"u8); - Characteristics characteristics = Characteristics.ExecutableImage | Characteristics.Dll; characteristics |= isPE32Plus ? Characteristics.LargeAddressAware : Characteristics.Bit32Machine; From f64f152db45a23ccade8c2d19ba67679e3ac826d Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 25 Sep 2025 13:03:22 -0700 Subject: [PATCH 26/71] Fix UsesSubsectionsViaSymbols to not assume that anything targeting Apple will use it (only Mach-O uses it) --- .../Common/Compiler/ObjectWriter/MachObjectWriter.cs | 2 ++ .../tools/Common/Compiler/ObjectWriter/ObjectWriter.cs | 9 ++++----- .../tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs | 1 - 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/MachObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/MachObjectWriter.cs index ef2fddeec53c57..dc1aae3b88dc67 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/MachObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/MachObjectWriter.cs @@ -92,6 +92,8 @@ 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 diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs index e705e54f0ba903..2f3ce1c01f50ac 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs @@ -24,7 +24,6 @@ private protected sealed record BlockToRelocate(int SectionIndex, long Offset, b private protected readonly NodeFactory _nodeFactory; private protected readonly ObjectWritingOptions _options; private readonly bool _isSingleFileCompilation; - private readonly bool _usesSubsectionsViaSymbols; private readonly Dictionary _mangledNameMap = new(); @@ -43,7 +42,6 @@ private protected ObjectWriter(NodeFactory factory, ObjectWritingOptions options _nodeFactory = factory; _options = options; _isSingleFileCompilation = _nodeFactory.CompilationModuleGroup.IsSingleFileCompilation; - _usesSubsectionsViaSymbols = factory.Target.IsApplePlatform; // Padding byte for code sections (NOP for x86/x64) _insPaddingByte = factory.Target.Architecture switch @@ -53,6 +51,7 @@ 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, int sectionIndex, Stream sectionStream); @@ -102,7 +101,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)); @@ -110,7 +109,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 @@ -137,7 +136,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) && diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs index 38defe1cdbb5c9..6d93e861c4baf2 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs @@ -11,7 +11,6 @@ using System.Reflection.PortableExecutable; using ILCompiler.DependencyAnalysis; using Internal.TypeSystem; -using static System.Runtime.InteropServices.JavaScript.JSType; namespace ILCompiler.ObjectWriter { From 1f6bbce8c6c39b66690692cadfbf6e29772686fd Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 25 Sep 2025 15:51:52 -0700 Subject: [PATCH 27/71] Remove R2R-specific types from OutputInfoBuilder so it (or a version of it) can be used from within the ObjectWriter when provided. --- .../CodeGen/ReadyToRunObjectWriter.cs | 54 ++++++++++++++- .../ObjectWriter/MapFileBuilder.cs | 29 +++++---- .../ObjectWriter/OutputInfoBuilder.cs | 65 ++++++++++--------- .../ObjectWriter/ProfileFileBuilder.cs | 7 +- .../ObjectWriter/R2RPEBuilder.cs | 4 +- .../ObjectWriter/SectionBuilder.cs | 10 +-- .../ObjectWriter/SymbolFileBuilder.cs | 1 + .../ObjectWriter/TypeString.cs | 2 +- 8 files changed, 115 insertions(+), 57 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs index 4688a8e45224a7..9ea5703be35aea 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs @@ -190,7 +190,7 @@ public ReadyToRunObjectWriter( } } - public void WritePortableExecutableObject(Logger logger) + public void EmitPortableExecutableUsingObjectWriter(Logger logger) { var stopwatch = Stopwatch.StartNew(); @@ -228,6 +228,58 @@ public void WritePortableExecutableObject(Logger logger) stream.Write(perfMapEntry); } + if (_outputInfoBuilder != null) + { + foreach (string inputFile in _inputFiles) + { + _outputInfoBuilder.AddInputModule(_nodeFactory.TypeSystemContext.GetModuleFromPath(inputFile)); + } + } + + if (_outputInfoBuilder != 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); + } + } + stopwatch.Stop(); if (logger.IsVerbose) logger.LogMessage($"Done writing object file in {stopwatch.Elapsed}"); 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/OutputInfoBuilder.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/OutputInfoBuilder.cs index 000f55a111b4cf..4379b0f36521e2 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/OutputInfoBuilder.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/OutputInfoBuilder.cs @@ -18,13 +18,13 @@ using ILCompiler.DependencyAnalysis.ReadyToRun; using ILCompiler.Diagnostics; -namespace ILCompiler.PEWriter +namespace ILCompiler.ObjectWriter { /// /// 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 { @@ -62,7 +62,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 @@ -93,7 +93,7 @@ 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) : base(sectionIndex, offset, name) @@ -101,33 +101,36 @@ public OutputSymbol(int sectionIndex, int offset, string name) } } + public sealed class OutputSection + { + public OutputSection(string name, int virtualAddress, int filePosition, int length) + { + Name = name; + VirtualAddress = virtualAddress; + FilePosition = filePosition; + Length = length; + } + + public string Name { get; } + public int VirtualAddress { get; } + public int FilePosition { get; } + public int 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 Dictionary _relocCounts; + private readonly List _inputModules = []; + private readonly List _nodes = []; + private readonly List _symbols = []; + private readonly List _sections = []; - public OutputInfoBuilder() - { - _inputModules = new List(); - _nodes = new List(); - _symbols = new List(); - _sections = new List
(); - - _nodeSymbolMap = new Dictionary(); - _methodSymbolMap = new Dictionary(); + private readonly Dictionary _nodeSymbolMap = []; + private readonly Dictionary _methodSymbolMap = []; - _relocCounts = new Dictionary(); - } + private readonly Dictionary _relocCounts = []; public void AddInputModule(EcmaModule module) { @@ -152,12 +155,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); } @@ -183,7 +186,7 @@ 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(); if (symbolMethodPair.Value.Method.GetTypicalMethodDefinition() is EcmaMethod ecmaMethod) @@ -193,8 +196,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 + node.Offset); methodInfo.HotLength = (uint)node.Length; methodInfo.ColdRVA = 0; methodInfo.ColdLength = 0; @@ -237,11 +240,11 @@ private string FormatMethodName(MethodDesc method, TypeNameFormatter typeNameFor } 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/aot/ILCompiler.ReadyToRun/ObjectWriter/ProfileFileBuilder.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/ProfileFileBuilder.cs index c35aac16c0c785..b3892887c63d20 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); } @@ -156,7 +157,7 @@ private void CalculateCallInfo() 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) @@ -166,7 +167,7 @@ private void CalculateCallInfo() 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; } _callInfo.Add(new CallInfo( diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/R2RPEBuilder.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/R2RPEBuilder.cs index eb8870ad9bf575..040e8cf49dafef 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/R2RPEBuilder.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/R2RPEBuilder.cs @@ -200,7 +200,7 @@ public void SetWin32Resources(ISymbolNode symbol, int resourcesSize) /// 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) + public void AddObjectData(DependencyAnalysis.ObjectNode.ObjectData objectData, ObjectNodeSection section, string name, ObjectWriter.OutputInfoBuilder outputInfoBuilder) { if (_written) { @@ -272,7 +272,7 @@ public void Write(Stream outputStream, int? timeDateStamp) /// Fill in map builder section table. ///
/// Object info builder to set up - public void AddSections(OutputInfoBuilder outputInfoBuilder) + public void AddSections(ObjectWriter.OutputInfoBuilder outputInfoBuilder) { _sectionBuilder.AddSections(outputInfoBuilder); } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/SectionBuilder.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/SectionBuilder.cs index f3ebd11aae868c..65891458a9e28e 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/SectionBuilder.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/SectionBuilder.cs @@ -446,7 +446,7 @@ private NameMangler GetNameMangler() /// 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) + public void AddObjectData(ObjectNode.ObjectData objectData, int sectionIndex, string name, ObjectWriter.OutputInfoBuilder outputInfoBuilder) { Section section = _sections[sectionIndex]; @@ -485,7 +485,7 @@ public void AddObjectData(ObjectNode.ObjectData objectData, int sectionIndex, st if (outputInfoBuilder != null) { - var node = new OutputNode(sectionIndex, alignedOffset, objectData.Data.Length, name); + var node = new ObjectWriter.OutputNode(sectionIndex, alignedOffset, objectData.Data.Length, name); outputInfoBuilder.AddNode(node, objectData.DefinedSymbols[0]); if (objectData.Relocs != null) { @@ -511,7 +511,7 @@ public void AddObjectData(ObjectNode.ObjectData objectData, int sectionIndex, st Utf8StringBuilder sb = new Utf8StringBuilder(); symbol.AppendMangledName(GetNameMangler(), sb); int sectionRelativeOffset = alignedOffset + symbol.Offset; - outputInfoBuilder.AddSymbol(new OutputSymbol(sectionIndex, sectionRelativeOffset, sb.ToString())); + outputInfoBuilder.AddSymbol(new ObjectWriter.OutputSymbol(sectionIndex, sectionRelativeOffset, sb.ToString())); } _symbolMap.Add(symbol, new SymbolTarget( sectionIndex: sectionIndex, @@ -559,11 +559,11 @@ public IEnumerable GetSections() return sectionList; } - public void AddSections(OutputInfoBuilder outputInfoBuilder) + public void AddSections(ObjectWriter.OutputInfoBuilder outputInfoBuilder) { foreach (Section section in _sections) { - outputInfoBuilder.AddSection(section); + outputInfoBuilder.AddSection(new ObjectWriter.OutputSection(section.Name, section.RVAWhenPlaced, section.FilePosWhenPlaced, section.Content.Count)); } } 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.ReadyToRun/ObjectWriter/TypeString.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/TypeString.cs index 6d40c859fc0788..ca2c1a43fedf4d 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/TypeString.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/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 From 25cda144c275e4929fc4c5ffa716b5dda5a985a0 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 25 Sep 2025 16:50:12 -0700 Subject: [PATCH 28/71] Integrate OutputInfoBuilder as an optional component of ObjectWriter --- .../Compiler/ObjectWriter/CoffObjectWriter.cs | 7 ++- .../Compiler/ObjectWriter/ElfObjectWriter.cs | 3 + .../Compiler/ObjectWriter/MachObjectWriter.cs | 8 ++- .../Compiler/ObjectWriter/ObjectWriter.cs | 48 ++++++++++++++- .../ObjectWriter/OutputInfoBuilder.cs | 8 +-- .../Compiler/ObjectWriter/PEObjectWriter.cs | 59 ++++++++----------- .../Compiler}/ObjectWriter/TypeString.cs | 0 .../CodeGen/ReadyToRunObjectWriter.cs | 55 +++++++++++------ .../ILCompiler.ReadyToRun.csproj | 2 + .../ObjectWriter/MapFileBuilder.cs | 4 +- 10 files changed, 127 insertions(+), 67 deletions(-) rename src/coreclr/tools/{aot/ILCompiler.ReadyToRun => Common/Compiler}/ObjectWriter/OutputInfoBuilder.cs (97%) rename src/coreclr/tools/{aot/ILCompiler.ReadyToRun => Common/Compiler}/ObjectWriter/TypeString.cs (100%) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs index accbc91b01a73d..58dd7b6135db2a 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs @@ -63,8 +63,8 @@ protected sealed record SectionDefinition(CoffSectionHeader Header, Stream Strea 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 { @@ -392,6 +392,9 @@ private protected override void EmitObjectFile(Stream outputFileStream) section.Header.PointerToRelocations = section.Relocations.Count > 0 ? dataOffset : 0; dataOffset += (uint)(section.Relocations.Count * CoffRelocation.Size); + // Record the section layout + _outputSectionLayout.Add(new OutputSection(section.Header.Name, section.Header.PointerToRawData, section.Header.VirtualAddress, section.Header.SizeOfRawData)); + sectionIndex++; } diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/ElfObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/ElfObjectWriter.cs index 442fc61c7b4fbf..d2c3b99efbd77b 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/ElfObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/ElfObjectWriter.cs @@ -658,6 +658,9 @@ private void EmitObjectFile(Stream 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; diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/MachObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/MachObjectWriter.cs index dc1aae3b88dc67..5b657172164831 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/MachObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/MachObjectWriter.cs @@ -99,7 +99,7 @@ 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. @@ -120,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; @@ -153,6 +153,8 @@ private void LayoutSections(ref uint fileOffset, out uint segmentFileSize, out u sectionIndex++; segmentSize = Math.Max(segmentSize, virtualAddress); + + _outputSectionLayout.Add(new OutputSection($"{section.SectionName}{section.SegmentName}", section.VirtualAddress, section.FileOffset, (ulong)section.Stream.Length)); } // ...and the relocation tables @@ -183,7 +185,7 @@ private protected override void EmitObjectFile(Stream outputFileStream) // 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); + LayoutSections(recordFinalLayout: true, ref fileOffset, out uint segmentFileSize, out ulong segmentSize); MachHeader64 machHeader = new MachHeader64 { diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs index 2f3ce1c01f50ac..225fe94bcffd78 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs @@ -7,9 +7,12 @@ 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 static ILCompiler.DependencyAnalysis.ObjectNode; using static ILCompiler.DependencyAnalysis.RelocType; using ObjectData = ILCompiler.DependencyAnalysis.ObjectNode.ObjectData; @@ -23,6 +26,7 @@ private protected sealed record BlockToRelocate(int SectionIndex, long Offset, b private protected readonly NodeFactory _nodeFactory; private protected readonly ObjectWritingOptions _options; + private protected readonly OutputInfoBuilder _outputInfoBuilder; private readonly bool _isSingleFileCompilation; private readonly Dictionary _mangledNameMap = new(); @@ -33,14 +37,17 @@ private protected sealed record BlockToRelocate(int SectionIndex, long Offset, b private readonly Dictionary _sectionNameToSectionIndex = new(StringComparer.Ordinal); private readonly List _sectionIndexToData = new(); protected readonly List> _sectionIndexToRelocations = new(); + private protected readonly List _outputSectionLayout = []; // Symbol table private readonly Dictionary _definedSymbols = new(StringComparer.Ordinal); - 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; // Padding byte for code sections (NOP for x86/x64) @@ -373,10 +380,17 @@ public void EmitObject(Stream outputFileStream, IReadOnlyCollection diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs index 6d93e861c4baf2..ef304a2dbe4770 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs @@ -56,19 +56,13 @@ internal sealed class PEObjectWriter : CoffObjectWriter private uint _peSectionAlignment; private uint _peFileAlignment; - private uint _pdataRva; - private uint _pdataSize; - - private uint _debugRva; - private uint _debugSize; - - private uint _exportRva; - private uint _exportSize; + private int _pdataSectionIndex; + private int _debugSectionIndex; + private int _exportSectionIndex; + private int _baseRelocSectionIndex; // Base relocation (.reloc) bookkeeping private readonly SortedDictionary> _baseRelocMap = new(); - private uint _baseRelocRva; - private uint _baseRelocSize; // Emitted Symbol Table info private sealed record PESymbol(string Name, uint Offset); @@ -78,13 +72,8 @@ private sealed record PESymbol(string Name, uint Offset); private HashSet _exportedSymbolNames = new(); - /// - /// The offset of the .debug section in the emitted object file. - /// - public uint DebugSectionOffsetInStream { get; private set; } - - public PEObjectWriter(NodeFactory factory, ObjectWritingOptions options, int sectionAlignment) - : base(factory, options) + public PEObjectWriter(NodeFactory factory, ObjectWritingOptions options, int sectionAlignment, OutputInfoBuilder outputInfoBuilder) + : base(factory, options, outputInfoBuilder) { _sectionAlignment = sectionAlignment; } @@ -419,16 +408,15 @@ private protected override void EmitSectionsAndLayout() else if (!h.SectionCharacteristics.HasFlag(SectionCharacteristics.ContainsUninitializedData)) sizeOfInitializedData += h.SizeOfRawData; + _outputSectionLayout.Add(new OutputSection(h.Name, h.VirtualAddress, h.PointerToRawData, h.SizeOfRawData)); + if (h.Name == ".pdata") { - _pdataRva = h.VirtualAddress; - _pdataSize = h.VirtualSize != 0 ? h.VirtualSize : h.SizeOfRawData; + _pdataSectionIndex = i; } else if (h.Name == ".debug") { - _debugRva = h.VirtualAddress; - _debugSize = h.VirtualSize != 0 ? h.VirtualSize : h.SizeOfRawData; - DebugSectionOffsetInStream = offsetToStart; + _debugSectionIndex = i; } } @@ -460,8 +448,7 @@ private protected override void EmitSectionsAndLayout() sizeOfInitializedData += edataHeader.SizeOfRawData; // Set export directory fields for header - _exportRva = edataRva + (uint)exportDir.ExportDirectoryOffset; - _exportSize = (uint)exportDir.ExportDirectorySize; + _exportSectionIndex = _sections.Count - 1; } uint sizeOfImage = (uint)AlignmentHelper.AlignUp((int)virtualAddress, (int)sectionAlignment); @@ -626,8 +613,8 @@ private void AddRelocSection() _sections.Add(relocSection); _sectionIndexToRelocations.Add(new List()); - _baseRelocRva = relocHeader.VirtualAddress; - _baseRelocSize = (uint)ms.Length; + _outputSectionLayout.Add(new OutputSection(relocHeader.Name, relocHeader.VirtualAddress, relocHeader.PointerToRawData, relocHeader.SizeOfRawData)); + _baseRelocSectionIndex = _sections.Count - 1; } private protected override void EmitObjectFile(Stream outputFileStream) @@ -736,20 +723,22 @@ private protected override void EmitObjectFile(Stream outputFileStream) // Entries are zeroed by default; callers may populate particular directories // before writing if needed. var dataDirs = new OptionalHeaderDataDirectories(); - // Populate exception table with .pdata if present. - if (_pdataSize != 0) + // Populate data directories if present. + if (_pdataSectionIndex != 0) + { + dataDirs.Set((int)ImageDirectoryEntry.Exception, (uint)_outputSectionLayout[_pdataSectionIndex].VirtualAddress, (uint)_outputSectionLayout[_pdataSectionIndex].Length); + } + if (_exportSectionIndex != 0) { - dataDirs.Set((int)ImageDirectoryEntry.Exception, _pdataRva, _pdataSize); + dataDirs.Set((int)ImageDirectoryEntry.Export, (uint)_outputSectionLayout[_exportSectionIndex].VirtualAddress, (uint)_outputSectionLayout[_exportSectionIndex].Length); } - // Populate export table if present - if (_exportSize != 0) + if (_baseRelocSectionIndex != 0) { - dataDirs.Set((int)ImageDirectoryEntry.Export, _exportRva, _exportSize); + dataDirs.Set((int)ImageDirectoryEntry.BaseRelocation, (uint)_outputSectionLayout[_baseRelocSectionIndex].VirtualAddress, (uint)_outputSectionLayout[_baseRelocSectionIndex].Length); } - // Populate base relocation directory if present - if (_baseRelocSize != 0) + if (_debugSectionIndex != 0) { - dataDirs.Set((int)ImageDirectoryEntry.BaseRelocation, _baseRelocRva, _baseRelocSize); + dataDirs.Set((int)ImageDirectoryEntry.Debug, (uint)_outputSectionLayout[_debugSectionIndex].VirtualAddress, (uint)_outputSectionLayout[_debugSectionIndex].Length); } peOptional.Write(outputFileStream, dataDirs); diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/TypeString.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/TypeString.cs similarity index 100% rename from src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/TypeString.cs rename to src/coreclr/tools/Common/Compiler/ObjectWriter/TypeString.cs diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs index 9ea5703be35aea..ac759b91967643 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs @@ -194,38 +194,57 @@ public void EmitPortableExecutableUsingObjectWriter(Logger logger) { var stopwatch = Stopwatch.StartNew(); - PEObjectWriter objectWriter = new(_nodeFactory, ObjectWritingOptions.None, _customPESectionAlignment); + PEObjectWriter objectWriter = new(_nodeFactory, ObjectWritingOptions.None, _customPESectionAlignment, _outputInfoBuilder); using FileStream stream = new FileStream(_objectFilePath, FileMode.Create); objectWriter.EmitObject(stream, _nodes, dumper: null, logger); + if (_outputInfoBuilder != null) + { + foreach (MethodWithGCInfo methodNode in _nodeFactory.EnumerateCompiledMethods()) + _outputInfoBuilder.AddMethod(methodNode, methodNode); + } + if (_mapFileBuilder != null) { _mapFileBuilder.SetFileSize(stream.Length); } - if (_nodeFactory.DebugDirectoryNode.PdbEntry is {} nativeDebugDirectoryEntryNode) + ulong debugOffset = 0; + foreach (OutputSection section in _outputInfoBuilder.Sections) { - Debug.Assert(_generatePdbFile); - // Compute hash of the output image and store that in the native DebugDirectory entry - using (var hashAlgorithm = SHA256.Create()) + if (section.Name == ".debug") { - stream.Seek(0, SeekOrigin.Begin); - byte[] hash = hashAlgorithm.ComputeHash(stream); - byte[] rsdsEntry = nativeDebugDirectoryEntryNode.GenerateRSDSEntryData(hash); - - stream.Seek(objectWriter.DebugSectionOffsetInStream, SeekOrigin.Begin); - stream.Write(rsdsEntry); + debugOffset = section.FilePosition; + break; } } - if (_nodeFactory.DebugDirectoryNode.PerfMapEntry is { } perfMapDebugDirectoryEntryNode) + if (debugOffset != 0) { - Debug.Assert(_generatePerfMapFile && _outputInfoBuilder is not null && _outputInfoBuilder.EnumerateInputAssemblies().Any()); - byte[] perfmapSig = PerfMapWriter.PerfMapV1SignatureHelper(_outputInfoBuilder.EnumerateInputAssemblies(), _nodeFactory.Target); - byte[] perfMapEntry = perfMapDebugDirectoryEntryNode.GeneratePerfMapEntryData(perfmapSig, _perfMapFormatVersion); + if (_nodeFactory.DebugDirectoryNode.PdbEntry is { } nativeDebugDirectoryEntryNode) + { + Debug.Assert(_generatePdbFile); + // Compute hash of the output image and store that in the native DebugDirectory entry + using (var hashAlgorithm = SHA256.Create()) + { + stream.Seek(0, SeekOrigin.Begin); + byte[] hash = hashAlgorithm.ComputeHash(stream); + byte[] rsdsEntry = nativeDebugDirectoryEntryNode.GenerateRSDSEntryData(hash); - stream.Seek(objectWriter.DebugSectionOffsetInStream, SeekOrigin.Begin); - stream.Write(perfMapEntry); + stream.Seek((long)debugOffset, SeekOrigin.Begin); + stream.Write(rsdsEntry); + } + } + + if (_nodeFactory.DebugDirectoryNode.PerfMapEntry is { } perfMapDebugDirectoryEntryNode) + { + Debug.Assert(_generatePerfMapFile && _outputInfoBuilder is not null && _outputInfoBuilder.EnumerateInputAssemblies().Any()); + byte[] perfmapSig = PerfMapWriter.PerfMapV1SignatureHelper(_outputInfoBuilder.EnumerateInputAssemblies(), _nodeFactory.Target); + byte[] perfMapEntry = perfMapDebugDirectoryEntryNode.GeneratePerfMapEntryData(perfmapSig, _perfMapFormatVersion); + + stream.Seek((long)debugOffset, SeekOrigin.Begin); + stream.Write(perfMapEntry); + } } if (_outputInfoBuilder != null) @@ -238,8 +257,6 @@ public void EmitPortableExecutableUsingObjectWriter(Logger logger) if (_outputInfoBuilder != null) { - r2rPeBuilder.AddSections(_outputInfoBuilder); - if (_generateMapFile) { string mapFileName = Path.ChangeExtension(_objectFilePath, ".map"); diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj index ecf6c6c18729ac..faee031f69bff8 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj @@ -143,6 +143,7 @@ + @@ -154,6 +155,7 @@ + diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/MapFileBuilder.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/MapFileBuilder.cs index 9d49bb86680145..67e28d773a20f9 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/MapFileBuilder.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/MapFileBuilder.cs @@ -267,7 +267,7 @@ private void WriteMapCsv(StreamWriter writer) // No more nodes or next symbol is below next node - emit symbol OutputSymbol symbol = _outputInfoBuilder.Symbols[symbolIndex++]; OutputSection section = _outputInfoBuilder.Sections[symbol.SectionIndex]; - writer.Write($"0x{symbol.Offset + section.VirtualAddress:X8},"); + writer.Write($"0x{(uint)symbol.Offset + section.VirtualAddress:X8},"); writer.Write(","); writer.Write(","); writer.Write($"{section.Name},"); @@ -280,7 +280,7 @@ private void WriteMapCsv(StreamWriter writer) OutputNode node = _outputInfoBuilder.Nodes[nodeIndex++]; OutputSection section = _outputInfoBuilder.Sections[node.SectionIndex]; - writer.Write($"0x{node.Offset + section.VirtualAddress:X8},"); + writer.Write($"0x{(uint)node.Offset + section.VirtualAddress:X8},"); writer.Write($"{node.Length},"); writer.Write($"{node.Relocations},"); writer.Write($"{section.Name},"); From 6698c91fd23460b7b0548c65a0b1bacfb9299a27 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 25 Sep 2025 16:53:59 -0700 Subject: [PATCH 29/71] Add try-finally --- .../CodeGen/ReadyToRunObjectWriter.cs | 173 ++++++++++-------- 1 file changed, 97 insertions(+), 76 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs index ac759b91967643..f20412d720f11d 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs @@ -192,114 +192,135 @@ public ReadyToRunObjectWriter( public void EmitPortableExecutableUsingObjectWriter(Logger logger) { - var stopwatch = Stopwatch.StartNew(); - - PEObjectWriter objectWriter = new(_nodeFactory, ObjectWritingOptions.None, _customPESectionAlignment, _outputInfoBuilder); - using FileStream stream = new FileStream(_objectFilePath, FileMode.Create); - objectWriter.EmitObject(stream, _nodes, dumper: null, logger); + bool succeeded = false; - if (_outputInfoBuilder != null) + try { - foreach (MethodWithGCInfo methodNode in _nodeFactory.EnumerateCompiledMethods()) - _outputInfoBuilder.AddMethod(methodNode, methodNode); - } + var stopwatch = Stopwatch.StartNew(); - if (_mapFileBuilder != null) - { - _mapFileBuilder.SetFileSize(stream.Length); - } + PEObjectWriter objectWriter = new(_nodeFactory, ObjectWritingOptions.None, _customPESectionAlignment, _outputInfoBuilder); + using FileStream stream = new FileStream(_objectFilePath, FileMode.Create); + objectWriter.EmitObject(stream, _nodes, dumper: null, logger); - ulong debugOffset = 0; - foreach (OutputSection section in _outputInfoBuilder.Sections) - { - if (section.Name == ".debug") + if (_outputInfoBuilder != null) { - debugOffset = section.FilePosition; - break; + foreach (MethodWithGCInfo methodNode in _nodeFactory.EnumerateCompiledMethods()) + _outputInfoBuilder.AddMethod(methodNode, methodNode); } - } - if (debugOffset != 0) - { - if (_nodeFactory.DebugDirectoryNode.PdbEntry is { } nativeDebugDirectoryEntryNode) + if (_mapFileBuilder != null) { - Debug.Assert(_generatePdbFile); - // Compute hash of the output image and store that in the native DebugDirectory entry - using (var hashAlgorithm = SHA256.Create()) - { - stream.Seek(0, SeekOrigin.Begin); - byte[] hash = hashAlgorithm.ComputeHash(stream); - byte[] rsdsEntry = nativeDebugDirectoryEntryNode.GenerateRSDSEntryData(hash); + _mapFileBuilder.SetFileSize(stream.Length); + } - stream.Seek((long)debugOffset, SeekOrigin.Begin); - stream.Write(rsdsEntry); + ulong debugOffset = 0; + foreach (OutputSection section in _outputInfoBuilder.Sections) + { + if (section.Name == ".debug") + { + debugOffset = section.FilePosition; + break; } } - if (_nodeFactory.DebugDirectoryNode.PerfMapEntry is { } perfMapDebugDirectoryEntryNode) + if (debugOffset != 0) { - Debug.Assert(_generatePerfMapFile && _outputInfoBuilder is not null && _outputInfoBuilder.EnumerateInputAssemblies().Any()); - byte[] perfmapSig = PerfMapWriter.PerfMapV1SignatureHelper(_outputInfoBuilder.EnumerateInputAssemblies(), _nodeFactory.Target); - byte[] perfMapEntry = perfMapDebugDirectoryEntryNode.GeneratePerfMapEntryData(perfmapSig, _perfMapFormatVersion); + if (_nodeFactory.DebugDirectoryNode.PdbEntry is { } nativeDebugDirectoryEntryNode) + { + Debug.Assert(_generatePdbFile); + // Compute hash of the output image and store that in the native DebugDirectory entry + using (var hashAlgorithm = SHA256.Create()) + { + stream.Seek(0, SeekOrigin.Begin); + byte[] hash = hashAlgorithm.ComputeHash(stream); + byte[] rsdsEntry = nativeDebugDirectoryEntryNode.GenerateRSDSEntryData(hash); - stream.Seek((long)debugOffset, SeekOrigin.Begin); - stream.Write(perfMapEntry); - } - } + stream.Seek((long)debugOffset, SeekOrigin.Begin); + stream.Write(rsdsEntry); + } + } - if (_outputInfoBuilder != null) - { - foreach (string inputFile in _inputFiles) - { - _outputInfoBuilder.AddInputModule(_nodeFactory.TypeSystemContext.GetModuleFromPath(inputFile)); - } - } + if (_nodeFactory.DebugDirectoryNode.PerfMapEntry is { } perfMapDebugDirectoryEntryNode) + { + Debug.Assert(_generatePerfMapFile && _outputInfoBuilder is not null && _outputInfoBuilder.EnumerateInputAssemblies().Any()); + byte[] perfmapSig = PerfMapWriter.PerfMapV1SignatureHelper(_outputInfoBuilder.EnumerateInputAssemblies(), _nodeFactory.Target); + byte[] perfMapEntry = perfMapDebugDirectoryEntryNode.GeneratePerfMapEntryData(perfmapSig, _perfMapFormatVersion); - if (_outputInfoBuilder != null) - { - if (_generateMapFile) - { - string mapFileName = Path.ChangeExtension(_objectFilePath, ".map"); - _mapFileBuilder.SaveMap(mapFileName); + stream.Seek((long)debugOffset, SeekOrigin.Begin); + stream.Write(perfMapEntry); + } } - if (_generateMapCsvFile) + if (_outputInfoBuilder != null) { - string nodeStatsCsvFileName = Path.ChangeExtension(_objectFilePath, ".nodestats.csv"); - string mapCsvFileName = Path.ChangeExtension(_objectFilePath, ".map.csv"); - _mapFileBuilder.SaveCsv(nodeStatsCsvFileName, mapCsvFileName); + foreach (string inputFile in _inputFiles) + { + _outputInfoBuilder.AddInputModule(_nodeFactory.TypeSystemContext.GetModuleFromPath(inputFile)); + } } - if (_generatePdbFile) + if (_outputInfoBuilder != null) { - string path = _pdbPath; - if (string.IsNullOrEmpty(path)) + if (_generateMapFile) { - path = Path.GetDirectoryName(_objectFilePath); + string mapFileName = Path.ChangeExtension(_objectFilePath, ".map"); + _mapFileBuilder.SaveMap(mapFileName); } - _symbolFileBuilder.SavePdb(path, _objectFilePath); - } - if (_generatePerfMapFile) - { - string path = _perfMapPath; - if (string.IsNullOrEmpty(path)) + if (_generateMapCsvFile) + { + string nodeStatsCsvFileName = Path.ChangeExtension(_objectFilePath, ".nodestats.csv"); + string mapCsvFileName = Path.ChangeExtension(_objectFilePath, ".map.csv"); + _mapFileBuilder.SaveCsv(nodeStatsCsvFileName, mapCsvFileName); + } + + if (_generatePdbFile) { - path = Path.GetDirectoryName(_objectFilePath); + 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); } - _symbolFileBuilder.SavePerfMap(path, _perfMapFormatVersion, _objectFilePath); } - if (_profileFileBuilder != null) + stopwatch.Stop(); + if (logger.IsVerbose) + logger.LogMessage($"Done writing object file in {stopwatch.Elapsed}"); + succeeded = true; + } + finally + { + if (!succeeded) { - string path = Path.ChangeExtension(_objectFilePath, ".profile"); - _profileFileBuilder.SaveProfile(path); + // If there was an exception while generating the OBJ file, make sure we don't leave the unfinished + // object file around. + try + { + File.Delete(_objectFilePath); + } + catch + { + } } } - - stopwatch.Stop(); - if (logger.IsVerbose) - logger.LogMessage($"Done writing object file in {stopwatch.Elapsed}"); } public void EmitPortableExecutable() From ed7e475fceefc3574fda9da5dd2cadbefe4d366d Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 26 Sep 2025 14:03:32 -0700 Subject: [PATCH 30/71] Add a command line option for format and get enough working to generate a file on disk in the new path. (Does it work? That's the next step) --- .../Compiler/DependencyAnalysis/Relocation.cs | 1 + .../ObjectWriter/OutputInfoBuilder.cs | 2 +- .../Compiler/ObjectWriter/PEObjectWriter.cs | 198 ++++++++++-------- .../CodeGen/ReadyToRunContainerFormat.cs | 15 ++ .../CodeGen/ReadyToRunObjectWriter.cs | 96 ++++----- .../ReadyToRun/DelegateCtorSignature.cs | 2 +- .../ReadyToRun/FieldFixupSignature.cs | 2 +- .../ReadyToRun/GenericLookupSignature.cs | 2 +- .../ReadyToRun/ILBodyFixupSignature.cs | 2 +- .../ManifestAssemblyMvidHeaderNode.cs | 2 +- .../ReadyToRun/MethodFixupSignature.cs | 2 +- .../ReadyToRun/NewArrayFixupSignature.cs | 2 +- .../ReadyToRun/NewObjectFixupSignature.cs | 2 +- .../ReadyToRun/ReadyToRunHelperSignature.cs | 2 +- ...eadyToRunInstructionSetSupportSignature.cs | 2 +- .../ReadyToRun/SignatureBuilder.cs | 4 +- .../ReadyToRun/StringImportSignature.cs | 2 +- .../ReadyToRun/TypeFixupSignature.cs | 2 +- .../VirtualResolutionFixupSignature.cs | 2 +- .../ReadyToRun/Win32ResourcesNode.cs | 1 + .../Compiler/ReadyToRunCodegenCompilation.cs | 8 +- .../ReadyToRunCodegenCompilationBuilder.cs | 10 +- .../ILCompiler.ReadyToRun.csproj | 7 +- .../ObjectWriter/MapFileBuilder.cs | 4 +- .../ObjectWriter/ProfileFileBuilder.cs | 14 +- .../ObjectWriter/SectionBuilder.cs | 2 +- .../aot/crossgen2/Crossgen2RootCommand.cs | 16 ++ src/coreclr/tools/aot/crossgen2/Program.cs | 1 + .../aot/crossgen2/Properties/Resources.resx | 3 + 29 files changed, 247 insertions(+), 161 deletions(-) create mode 100644 src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunContainerFormat.cs diff --git a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/Relocation.cs b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/Relocation.cs index a7ab6873d8af29..d918720f03b860 100644 --- a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/Relocation.cs +++ b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/Relocation.cs @@ -569,6 +569,7 @@ public static int GetSize(RelocType relocType) RelocType.IMAGE_REL_BASED_ADDR32NB => 4, RelocType.IMAGE_REL_BASED_REL32 => 4, RelocType.IMAGE_REL_BASED_RELPTR32 => 4, + RelocType.IMAGE_REL_FILE_ABSOLUTE => 4, _ => throw new NotSupportedException(), }; } diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/OutputInfoBuilder.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/OutputInfoBuilder.cs index 236a79df2f5009..d96ad2a32e146a 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/OutputInfoBuilder.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/OutputInfoBuilder.cs @@ -197,7 +197,7 @@ public IEnumerable EnumerateMethods() methodInfo.Name = FormatMethodName(symbolMethodPair.Value.Method, typeNameFormatter); OutputNode node = _nodeSymbolMap[symbolMethodPair.Key]; OutputSection section = _sections[node.SectionIndex]; - methodInfo.HotRVA = (uint)(section.VirtualAddress + node.Offset); + methodInfo.HotRVA = (uint)(section.VirtualAddress + (ulong)node.Offset); methodInfo.HotLength = (uint)node.Length; methodInfo.ColdRVA = 0; methodInfo.ColdLength = 0; diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs index ef304a2dbe4770..af377b5659cd5c 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs @@ -56,6 +56,9 @@ internal sealed class PEObjectWriter : CoffObjectWriter private uint _peSectionAlignment; private uint _peFileAlignment; + // Relocations that we can resolve at emit time (ie not file-based relocations). + private List> _resolvableRelocations = []; + private int _pdataSectionIndex; private int _debugSectionIndex; private int _exportSectionIndex; @@ -381,8 +384,9 @@ private protected override void EmitSectionsAndLayout() for (int i = 0; i < _sections.Count; i++) { - var s = _sections[i]; - var h = s.Header; + _resolvableRelocations.Add([]); + SectionDefinition s = _sections[i]; + CoffSectionHeader h = s.Header; h.SizeOfRawData = (uint)s.Stream.Length; uint rawAligned = h.SectionCharacteristics.HasFlag(SectionCharacteristics.ContainsUninitializedData) ? 0u : (uint)AlignmentHelper.AlignUp((int)h.SizeOfRawData, (int)fileAlignment); @@ -424,9 +428,9 @@ private protected override void EmitSectionsAndLayout() if (_exportedSymbolNames.Count > 0) { uint edataRva = virtualAddress; - var exportDir = new ExportDirectory(_exportedPESymbols, moduleName: "UNKNOWN", edataRva); - var edataSection = exportDir.Section; - var edataHeader = edataSection.Header; + ExportDirectory exportDir = new(_exportedPESymbols, moduleName: "UNKNOWN", edataRva); + SectionDefinition edataSection = exportDir.Section; + CoffSectionHeader edataHeader = edataSection.Header; // Compute raw and virtual sizes and update pointers edataHeader.SizeOfRawData = (uint)edataSection.Stream.Length; @@ -444,6 +448,7 @@ private protected override void EmitSectionsAndLayout() // Add to sections and bookkeeping _sections.Add(edataSection); _sectionIndexToRelocations.Add(new List()); + _resolvableRelocations.Add([]); sizeOfInitializedData += edataHeader.SizeOfRawData; @@ -463,83 +468,28 @@ private protected override unsafe void EmitRelocations(int sectionIndex, List - // list of (type<<12 | offsetInPage) WORD entries). - RelocType fileRelocType = Relocation.GetFileRelocationType(reloc.Type); - uint targetRva = _sections[sectionIndex].Header.VirtualAddress + (uint)reloc.Offset; - uint pageRva = targetRva & ~0xFFFu; - ushort offsetInPage = (ushort)(targetRva & 0xFFFu); - ushort entry = (ushort)(((ushort)fileRelocType << 12) | offsetInPage); - - if (!_baseRelocMap.TryGetValue(pageRva, out var list)) - { - list = new List(); - _baseRelocMap.Add(pageRva, list); - } - list.Add(entry); - break; - } - case RelocType.IMAGE_REL_BASED_ADDR32NB: - fixed (byte* pData = GetRelocDataSpan(reloc)) - { - long rva = _sections[sectionIndex].Header.VirtualAddress + reloc.Offset; - Relocation.WriteValue(reloc.Type, (void*)pData, rva); - WriteRelocDataSpan(reloc, pData); - } - break; - case RelocType.IMAGE_REL_BASED_REL32: - case RelocType.IMAGE_REL_BASED_RELPTR32: - { - PESymbol definedSymbol = _definedPESymbols[reloc.SymbolName]; - fixed (byte* pData = GetRelocDataSpan(reloc)) - { - long adjustedAddend = reloc.Addend; + RelocType fileRelocType = Relocation.GetFileRelocationType(reloc.Type); - adjustedAddend -= 4; - - adjustedAddend += definedSymbol.Offset; - adjustedAddend += Relocation.ReadValue(reloc.Type, (void*)pData); - adjustedAddend -= reloc.Offset; - - Relocation.WriteValue(reloc.Type, (void*)pData, adjustedAddend); - WriteRelocDataSpan(reloc, pData); - } - break; - } - case RelocType.IMAGE_REL_FILE_ABSOLUTE: - fixed (byte* pData = GetRelocDataSpan(reloc)) - { - long rva = _sections[sectionIndex].Header.PointerToRawData + reloc.Offset; - Relocation.WriteValue(reloc.Type, (void*)pData, rva); - WriteRelocDataSpan(reloc, pData); - } - break; - default: - throw new NotSupportedException($"Unsupported relocation: {reloc.Type}"); - } - - Span GetRelocDataSpan(SymbolicRelocation reloc) + if (fileRelocType != reloc.Type) { - Stream stream = _sections[sectionIndex].Stream; - stream.Position = reloc.Offset; - Span data = new byte[Relocation.GetSize(reloc.Type)]; - stream.ReadExactly(data); - return data; + _resolvableRelocations[sectionIndex].Add(reloc); } - - void WriteRelocDataSpan(SymbolicRelocation reloc, byte* data) + else { - Stream stream = _sections[sectionIndex].Stream; - stream.Position = reloc.Offset; - stream.Write(new Span(data, Relocation.GetSize(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 & ~0xFFFu; + ushort offsetInPage = (ushort)(targetRva & 0xFFFu); + ushort entry = (ushort)(((ushort)fileRelocType << 12) | offsetInPage); + + if (!_baseRelocMap.TryGetValue(pageRva, out var list)) + { + list = new List(); + _baseRelocMap.Add(pageRva, list); + } + list.Add(entry); } } } @@ -612,6 +562,7 @@ private void AddRelocSection() _sections.Add(relocSection); _sectionIndexToRelocations.Add(new List()); + _resolvableRelocations.Add([]); _outputSectionLayout.Add(new OutputSection(relocHeader.Name, relocHeader.VirtualAddress, relocHeader.PointerToRawData, relocHeader.SizeOfRawData)); _baseRelocSectionIndex = _sections.Count - 1; @@ -748,17 +699,37 @@ private protected override void EmitObjectFile(Stream outputFileStream) 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; + } hdr.Write(outputFileStream, stringTable); } // Write section content - foreach (var section in _sections) + for (int i = 0; i < _sections.Count; i++) { - if (!section.Header.SectionCharacteristics.HasFlag(SectionCharacteristics.ContainsUninitializedData)) + SectionDefinition section = _sections[i]; + if (section.Header.VirtualSize != 0 && !section.Header.SectionCharacteristics.HasFlag(SectionCharacteristics.ContainsUninitializedData)) { - Debug.Assert(outputFileStream.Position == section.Header.PointerToRawData); - section.Stream.Position = 0; - section.Stream.CopyTo(outputFileStream); + Debug.Assert(outputFileStream.Position <= section.Header.PointerToRawData); + outputFileStream.Position = section.Header.PointerToRawData; // Pad to alignment + if (_resolvableRelocations[i] is not []) + { + // 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); + ResolveNonImageBaseRelocations(i, _resolvableRelocations[i], stream); + stream.Position = 0; + section.Stream.CopyTo(outputFileStream); + } + else + { + section.Stream.Position = 0; + section.Stream.CopyTo(outputFileStream); + } } } @@ -766,6 +737,67 @@ private protected override void EmitObjectFile(Stream outputFileStream) // TODO: calculate deterministic timestamp } + private unsafe void ResolveNonImageBaseRelocations(int sectionIndex, List symbolicRelocations, MemoryStream stream) + { + foreach (SymbolicRelocation reloc in symbolicRelocations) + { + switch (reloc.Type) + { + case RelocType.IMAGE_REL_BASED_ADDR32NB: + fixed (byte* pData = GetRelocDataSpan(reloc)) + { + long rva = _sections[sectionIndex].Header.VirtualAddress + reloc.Offset; + Relocation.WriteValue(reloc.Type, pData, rva); + WriteRelocDataSpan(reloc, pData); + } + break; + case RelocType.IMAGE_REL_BASED_REL32: + case RelocType.IMAGE_REL_BASED_RELPTR32: + { + PESymbol definedSymbol = _definedPESymbols[reloc.SymbolName]; + fixed (byte* pData = GetRelocDataSpan(reloc)) + { + long adjustedAddend = reloc.Addend; + + adjustedAddend -= 4; + + adjustedAddend += definedSymbol.Offset; + adjustedAddend += Relocation.ReadValue(reloc.Type, pData); + adjustedAddend -= reloc.Offset; + + Relocation.WriteValue(reloc.Type, pData, adjustedAddend); + WriteRelocDataSpan(reloc, pData); + } + break; + } + case RelocType.IMAGE_REL_FILE_ABSOLUTE: + fixed (byte* pData = GetRelocDataSpan(reloc)) + { + long rva = _sections[sectionIndex].Header.PointerToRawData + reloc.Offset; + Relocation.WriteValue(reloc.Type, pData, rva); + WriteRelocDataSpan(reloc, pData); + } + break; + default: + throw new NotSupportedException($"Unsupported relocation: {reloc.Type}"); + } + + 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 { 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..491022ddcc5b94 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunContainerFormat.cs @@ -0,0 +1,15 @@ +// 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 + { + LegacyPE, + PE + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs index f20412d720f11d..0d7c4005694aaf 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs @@ -57,9 +57,9 @@ internal class ReadyToRunObjectWriter private readonly IReadOnlyCollection _nodes; /// - /// Set to non-null when the executable generator should output a map or symbol file. + /// Builder to collect information from executable generation for auxiliary files (map, profile, or symbol files). /// - private readonly OutputInfoBuilder _outputInfoBuilder; + private readonly OutputInfoBuilder _outputInfoBuilder = new(); /// /// Set to non-null when the executable generator should output a map file. @@ -171,8 +171,6 @@ public ReadyToRunObjectWriter( if (generateMap || generateSymbols || generateProfileFile) { - _outputInfoBuilder = new OutputInfoBuilder(); - if (generateMap) { _mapFileBuilder = new MapFileBuilder(_outputInfoBuilder); @@ -190,7 +188,7 @@ public ReadyToRunObjectWriter( } } - public void EmitPortableExecutableUsingObjectWriter(Logger logger) + public void EmitReadyToRunObjects(ReadyToRunContainerFormat format, Logger logger) { bool succeeded = false; @@ -198,15 +196,14 @@ public void EmitPortableExecutableUsingObjectWriter(Logger logger) { var stopwatch = Stopwatch.StartNew(); + Debug.Assert(format == ReadyToRunContainerFormat.PE); + PEObjectWriter objectWriter = new(_nodeFactory, ObjectWritingOptions.None, _customPESectionAlignment, _outputInfoBuilder); using FileStream stream = new FileStream(_objectFilePath, FileMode.Create); objectWriter.EmitObject(stream, _nodes, dumper: null, logger); - if (_outputInfoBuilder != null) - { - foreach (MethodWithGCInfo methodNode in _nodeFactory.EnumerateCompiledMethods()) - _outputInfoBuilder.AddMethod(methodNode, methodNode); - } + foreach (MethodWithGCInfo methodNode in _nodeFactory.EnumerateCompiledMethods()) + _outputInfoBuilder.AddMethod(methodNode, methodNode); if (_mapFileBuilder != null) { @@ -242,7 +239,7 @@ public void EmitPortableExecutableUsingObjectWriter(Logger logger) if (_nodeFactory.DebugDirectoryNode.PerfMapEntry is { } perfMapDebugDirectoryEntryNode) { - Debug.Assert(_generatePerfMapFile && _outputInfoBuilder is not null && _outputInfoBuilder.EnumerateInputAssemblies().Any()); + Debug.Assert(_generatePerfMapFile && _outputInfoBuilder.EnumerateInputAssemblies().Any()); byte[] perfmapSig = PerfMapWriter.PerfMapV1SignatureHelper(_outputInfoBuilder.EnumerateInputAssemblies(), _nodeFactory.Target); byte[] perfMapEntry = perfMapDebugDirectoryEntryNode.GeneratePerfMapEntryData(perfmapSig, _perfMapFormatVersion); @@ -251,54 +248,48 @@ public void EmitPortableExecutableUsingObjectWriter(Logger logger) } } - if (_outputInfoBuilder != null) + foreach (string inputFile in _inputFiles) { - foreach (string inputFile in _inputFiles) - { - _outputInfoBuilder.AddInputModule(_nodeFactory.TypeSystemContext.GetModuleFromPath(inputFile)); - } + _outputInfoBuilder.AddInputModule(_nodeFactory.TypeSystemContext.GetModuleFromPath(inputFile)); } - if (_outputInfoBuilder != null) + if (_generateMapFile) { - if (_generateMapFile) - { - string mapFileName = Path.ChangeExtension(_objectFilePath, ".map"); - _mapFileBuilder.SaveMap(mapFileName); - } + 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 (_generateMapCsvFile) + { + string nodeStatsCsvFileName = Path.ChangeExtension(_objectFilePath, ".nodestats.csv"); + string mapCsvFileName = Path.ChangeExtension(_objectFilePath, ".map.csv"); + _mapFileBuilder.SaveCsv(nodeStatsCsvFileName, mapCsvFileName); + } - if (_generatePdbFile) + if (_generatePdbFile) + { + string path = _pdbPath; + if (string.IsNullOrEmpty(path)) { - string path = _pdbPath; - if (string.IsNullOrEmpty(path)) - { - path = Path.GetDirectoryName(_objectFilePath); - } - _symbolFileBuilder.SavePdb(path, _objectFilePath); + path = Path.GetDirectoryName(_objectFilePath); } + _symbolFileBuilder.SavePdb(path, _objectFilePath); + } - if (_generatePerfMapFile) + if (_generatePerfMapFile) + { + string path = _perfMapPath; + if (string.IsNullOrEmpty(path)) { - string path = _perfMapPath; - if (string.IsNullOrEmpty(path)) - { - path = Path.GetDirectoryName(_objectFilePath); - } - _symbolFileBuilder.SavePerfMap(path, _perfMapFormatVersion, _objectFilePath); + path = Path.GetDirectoryName(_objectFilePath); } + _symbolFileBuilder.SavePerfMap(path, _perfMapFormatVersion, _objectFilePath); + } - if (_profileFileBuilder != null) - { - string path = Path.ChangeExtension(_objectFilePath, ".profile"); - _profileFileBuilder.SaveProfile(path); - } + if (_profileFileBuilder != null) + { + string path = Path.ChangeExtension(_objectFilePath, ".profile"); + _profileFileBuilder.SaveProfile(path); } stopwatch.Stop(); @@ -647,6 +638,7 @@ public static void EmitObject( int perfMapFormatVersion, bool generateProfileFile, CallChainProfile callChainProfile, + ReadyToRunContainerFormat format, int customPESectionAlignment, Logger logger) { @@ -667,7 +659,15 @@ public static void EmitObject( generateProfileFile: generateProfileFile, callChainProfile, customPESectionAlignment); - objectWriter.EmitPortableExecutable(); + + if (format == ReadyToRunContainerFormat.LegacyPE) + { + objectWriter.EmitPortableExecutable(); + } + else + { + objectWriter.EmitReadyToRunObjects(format, logger); + } } } } 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/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/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/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/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/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/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 1770431aacabc6..febf720f88f0c4 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 @@ -49,6 +49,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/ReadyToRunCodegenCompilation.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.cs index 652fe4d24b8b72..52488834d90cf0 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,6 +418,7 @@ public override void Compile(string outputFile) perfMapFormatVersion: _perfMapFormatVersion, generateProfileFile: _generateProfileFile, callChainProfile: _profileData.CallChainProfile, + _format, _customPESectionAlignment, _logger); CompilationModuleGroup moduleGroup = _nodeFactory.CompilationModuleGroup; @@ -512,6 +517,7 @@ private void RewriteComponentFile(string inputFile, string outputFile, string ow perfMapFormatVersion: _perfMapFormatVersion, generateProfileFile: false, _profileData.CallChainProfile, + ReadyToRunContainerFormat.LegacyPE, customPESectionAlignment: 0, _logger); } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilationBuilder.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilationBuilder.cs index 3d937f94df1994..7a6fb94559b5bc 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.LegacyPE; 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 @@ -344,7 +351,8 @@ public override ICompilation ToCompilation() _r2rMethodLayoutAlgorithm, _r2rFileLayoutAlgorithm, _customPESectionAlignment, - _verifyTypeAndFieldLayout); + _verifyTypeAndFieldLayout, + _format); } } } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj index faee031f69bff8..251c18451954fa 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj @@ -168,6 +168,7 @@ + @@ -293,10 +294,8 @@ - - @@ -357,4 +356,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 67e28d773a20f9..bd39ae696a95c9 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/MapFileBuilder.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/MapFileBuilder.cs @@ -225,7 +225,7 @@ private void WriteMap(StreamWriter writer) // No more nodes or next symbol is below next node - emit symbol OutputSymbol symbol = _outputInfoBuilder.Symbols[symbolIndex++]; OutputSection section = _outputInfoBuilder.Sections[symbol.SectionIndex]; - writer.Write($"0x{symbol.Offset + section.VirtualAddress:X8} | "); + writer.Write($"0x{(ulong)symbol.Offset + section.VirtualAddress:X8} | "); writer.Write(" | "); writer.Write(" | "); writer.Write($"{GetNameHead(section),-SectionNameHeadLength} | "); @@ -237,7 +237,7 @@ private void WriteMap(StreamWriter writer) OutputNode node = _outputInfoBuilder.Nodes[nodeIndex++]; OutputSection section = _outputInfoBuilder.Sections[node.SectionIndex]; - writer.Write($"0x{node.Offset + section.VirtualAddress:X8} | "); + writer.Write($"0x{(ulong)node.Offset + section.VirtualAddress:X8} | "); writer.Write($"0x{node.Length:X6} | "); writer.Write($"{node.Relocations,6} | "); writer.Write($"{GetNameHead(section),-SectionNameHeadLength} | "); diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/ProfileFileBuilder.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/ProfileFileBuilder.cs index b3892887c63d20..97c591f22f190a 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/ProfileFileBuilder.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/ProfileFileBuilder.cs @@ -153,32 +153,32 @@ 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].VirtualAddress + callerNode.Offset; + callerRVA = _outputInfoBuilder.Sections[callerNode.SectionIndex].VirtualAddress + (ulong)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].VirtualAddress + calleeNode.Offset; + calleeRVA = _outputInfoBuilder.Sections[calleeNode.SectionIndex].VirtualAddress + (ulong)calleeNode.Offset; } _callInfo.Add(new CallInfo( caller: kvpCallerCalleeCount.Key, callerNode: callerNode, - callerRVA: callerRVA, + callerRVA: (int)callerRVA, callee: kvpCalleeCount.Key, calleeNode: calleeNode, - calleeRVA: calleeRVA, + calleeRVA: (int)calleeRVA, callCount: kvpCalleeCount.Value, - callType: GetCallType(callerNode, callerRVA, calleeNode, calleeRVA))); + callType: GetCallType(callerNode, (int)callerRVA, calleeNode, (int)calleeRVA))); } } } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/SectionBuilder.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/SectionBuilder.cs index 65891458a9e28e..2f5c020d0483dc 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/SectionBuilder.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/SectionBuilder.cs @@ -563,7 +563,7 @@ public void AddSections(ObjectWriter.OutputInfoBuilder outputInfoBuilder) { foreach (Section section in _sections) { - outputInfoBuilder.AddSection(new ObjectWriter.OutputSection(section.Name, section.RVAWhenPlaced, section.FilePosWhenPlaced, section.Content.Count)); + outputInfoBuilder.AddSection(new ObjectWriter.OutputSection(section.Name, (ulong)section.RVAWhenPlaced, (ulong)section.FilePosWhenPlaced, (ulong)section.Content.Count)); } } diff --git a/src/coreclr/tools/aot/crossgen2/Crossgen2RootCommand.cs b/src/coreclr/tools/aot/crossgen2/Crossgen2RootCommand.cs index bcb792d2eb7eaa..5152381a7cbe95 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("--format") { 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,19 @@ private static FileLayoutAlgorithm MakeFileLayoutAlgorithm(ArgumentResult result }; } + private static ReadyToRunContainerFormat MakeOutputFormat(ArgumentResult result) + { + if (result.Tokens.Count == 0) + return ReadyToRunContainerFormat.LegacyPE; + + return result.Tokens[0].Value.ToLowerInvariant() switch + { + "legacype" => ReadyToRunContainerFormat.LegacyPE, + "pe" => ReadyToRunContainerFormat.PE, + _ => throw new CommandLineException(SR.InvalidFileLayout) + }; + } + #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..882c4bec226941 100644 --- a/src/coreclr/tools/aot/crossgen2/Properties/Resources.resx +++ b/src/coreclr/tools/aot/crossgen2/Properties/Resources.resx @@ -426,4 +426,7 @@ Enable support for cached interface dispatch + + Target output format for the ReadyToRun image + \ No newline at end of file From 2551c9b28e81a4ab4ec53dfdb5b116da3fb17be7 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 26 Sep 2025 14:31:21 -0700 Subject: [PATCH 31/71] Fix writing PE optional header --- .../Compiler/ObjectWriter/PEObjectWriter.cs | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs index af377b5659cd5c..092c8cb1efcc9c 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs @@ -233,6 +233,9 @@ public void Write(Stream stream, OptionalHeaderDataDirectories dataDirectories) WriteLittleEndian(stream, MajorSubsystemVersion); WriteLittleEndian(stream, MinorSubsystemVersion); + // Win32VersionValue + WriteLittleEndian(stream, 0); + // SizeOfImage WriteLittleEndian(stream, SizeOfImage); @@ -254,11 +257,6 @@ public void Write(Stream stream, OptionalHeaderDataDirectories dataDirectories) WriteLittleEndian(stream, (uint)SizeOfStackCommit); WriteLittleEndian(stream, (uint)SizeOfHeapReserve); WriteLittleEndian(stream, (uint)SizeOfHeapCommit); - WriteLittleEndian(stream, LoaderFlags); - WriteLittleEndian(stream, NumberOfRvaAndSizes); - - // Data directories start at offset 0x60 for PE32 - dataDirectories.WriteTo(stream, (int)NumberOfRvaAndSizes); } else { @@ -266,12 +264,13 @@ public void Write(Stream stream, OptionalHeaderDataDirectories dataDirectories) 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); } + + WriteLittleEndian(stream, LoaderFlags); + WriteLittleEndian(stream, NumberOfRvaAndSizes); + + // Data directories start after NumberOfRvaAndSizes for PE32+ + dataDirectories.WriteTo(stream, (int)NumberOfRvaAndSizes); } } @@ -629,7 +628,7 @@ private protected override void EmitObjectFile(Stream outputFileStream) // COFF File Header var coffHeader = new CoffHeader { - Machine = _machine, + Machine = machine, NumberOfSections = (uint)numberOfSections, TimeDateStamp = (uint)DateTimeOffset.UtcNow.ToUnixTimeSeconds(), // TODO: Make deterministic PointerToSymbolTable = 0, From 0f7e93b1c91cdf0856699e282c77b967a96ff28a Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 26 Sep 2025 15:03:47 -0700 Subject: [PATCH 32/71] Fix writing section contents and writing out the CLR header. --- .../Common/Compiler/ObjectWriter/PEObjectWriter.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs index 092c8cb1efcc9c..9702941ccc06e6 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs @@ -63,6 +63,7 @@ internal sealed class PEObjectWriter : CoffObjectWriter private int _debugSectionIndex; private int _exportSectionIndex; private int _baseRelocSectionIndex; + private int _corMetaSectionIndex; // Base relocation (.reloc) bookkeeping private readonly SortedDictionary> _baseRelocMap = new(); @@ -421,6 +422,10 @@ private protected override void EmitSectionsAndLayout() { _debugSectionIndex = i; } + else if (h.Name == ".cormeta") + { + _corMetaSectionIndex = i; + } } // If we have exports, create an export data section (.edata) and place it after other sections. @@ -690,6 +695,10 @@ private protected override void EmitObjectFile(Stream outputFileStream) { dataDirs.Set((int)ImageDirectoryEntry.Debug, (uint)_outputSectionLayout[_debugSectionIndex].VirtualAddress, (uint)_outputSectionLayout[_debugSectionIndex].Length); } + if (_corMetaSectionIndex != 0) + { + dataDirs.Set((int)ImageDirectoryEntry.CLRRuntimeHeader, (uint)_outputSectionLayout[_corMetaSectionIndex].VirtualAddress, (uint)_outputSectionLayout[_corMetaSectionIndex].Length); + } peOptional.Write(outputFileStream, dataDirs); CoffStringTable stringTable = new(); @@ -703,6 +712,9 @@ private protected override void EmitObjectFile(Stream outputFileStream) // 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); } @@ -722,7 +734,7 @@ private protected override void EmitObjectFile(Stream outputFileStream) section.Stream.CopyTo(stream); ResolveNonImageBaseRelocations(i, _resolvableRelocations[i], stream); stream.Position = 0; - section.Stream.CopyTo(outputFileStream); + stream.CopyTo(outputFileStream); } else { From 674c2aa972c7feb8d88c1f99ef7d8e3d9f2866ca Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 26 Sep 2025 15:18:18 -0700 Subject: [PATCH 33/71] Fix emitting symbol size for non-methods on R2R (needed for the "size" reloc) --- .../tools/Common/Compiler/ObjectWriter/ObjectWriter.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs index 225fe94bcffd78..80c46c8b97fb68 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs @@ -377,6 +377,11 @@ public void EmitObject(Stream outputFileStream, IReadOnlyCollection Date: Fri, 26 Sep 2025 15:29:11 -0700 Subject: [PATCH 34/71] Fix ilc build --- .../Common/Compiler/ObjectWriter/ObjectWriter.cs | 10 ++-------- .../Compiler/ObjectWriter/OutputInfoBuilder.cs | 14 ++++++-------- .../Common/Compiler/ObjectWriter/PEObjectWriter.cs | 2 -- .../ILCompiler.Compiler/ILCompiler.Compiler.csproj | 3 +++ src/coreclr/tools/aot/ilc.slnx | 6 ++++++ 5 files changed, 17 insertions(+), 18 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs index 80c46c8b97fb68..142a81acf46338 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs @@ -391,10 +391,7 @@ public void EmitObject(Stream outputFileStream, IReadOnlyCollection { - public readonly static Comparer Instance = new Comparer(); + public static readonly Comparer Instance = new Comparer(); public int Compare([AllowNull] OutputItem x, [AllowNull] OutputItem y) { @@ -184,11 +183,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) { - MethodInfo methodInfo = new MethodInfo(); + MethodInfo methodInfo = default; if (symbolMethodPair.Value.Method.GetTypicalMethodDefinition() is EcmaMethod ecmaMethod) { methodInfo.MethodToken = (uint)MetadataTokens.GetToken(ecmaMethod.Handle); @@ -215,18 +213,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) @@ -235,7 +233,7 @@ private string FormatMethodName(MethodDesc method, TypeNameFormatter typeNameFor } output.Append(typeNameFormatter.FormatName(method.Signature[paramIndex])); } - output.Append(")"); + output.Append(')'); return output.ToString(); } diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs index 9702941ccc06e6..42e02a7d094d86 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs @@ -390,8 +390,6 @@ private protected override void EmitSectionsAndLayout() h.SizeOfRawData = (uint)s.Stream.Length; uint rawAligned = h.SectionCharacteristics.HasFlag(SectionCharacteristics.ContainsUninitializedData) ? 0u : (uint)AlignmentHelper.AlignUp((int)h.SizeOfRawData, (int)fileAlignment); - uint offsetToStart = pointerToRawData; - if (rawAligned != 0) { h.PointerToRawData = pointerToRawData; diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj index b4beab48ab29c0..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 @@ + @@ -337,11 +338,13 @@ + + 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 @@ + + + + + + From 5631b076fff6f1c0c6d26beffdcb88bd89396d45 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 29 Sep 2025 17:15:57 -0700 Subject: [PATCH 35/71] Fix section layout to include section sizes for sections added after initial layout and exclude sections that are 0-sized from the section count. --- .../Compiler/ObjectWriter/CoffObjectWriter.cs | 2 +- .../Compiler/ObjectWriter/MachObjectWriter.cs | 5 +- .../Compiler/ObjectWriter/ObjectWriter.cs | 5 +- .../Compiler/ObjectWriter/PEObjectWriter.cs | 552 +++++++----------- .../ObjectWriter/PETargetExtensions.cs | 2 +- 5 files changed, 235 insertions(+), 331 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs index 58dd7b6135db2a..9da244852e8885 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs @@ -100,7 +100,7 @@ private protected override void CreateSection(ObjectNodeSection section, string } }; - if (section == DebugTypesSection) + if (section == DebugTypesSection || section == ObjectNodeSection.DebugDirectorySection) { sectionHeader.SectionCharacteristics = SectionCharacteristics.MemRead | SectionCharacteristics.ContainsInitializedData | diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/MachObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/MachObjectWriter.cs index 5b657172164831..fec3165308290d 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/MachObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/MachObjectWriter.cs @@ -154,7 +154,10 @@ private void LayoutSections(bool recordFinalLayout, ref uint fileOffset, out uin segmentSize = Math.Max(segmentSize, virtualAddress); - _outputSectionLayout.Add(new OutputSection($"{section.SectionName}{section.SegmentName}", section.VirtualAddress, section.FileOffset, (ulong)section.Stream.Length)); + if (recordFinalLayout) + { + _outputSectionLayout.Add(new OutputSection($"{section.SectionName}{section.SegmentName}", section.VirtualAddress, section.FileOffset, (ulong)section.Stream.Length)); + } } // ...and the relocation tables diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs index 142a81acf46338..24d8e8e1a20d88 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs @@ -22,7 +22,7 @@ public abstract partial class ObjectWriter { private protected sealed record SymbolDefinition(int SectionIndex, long Value, int Size = 0, bool Global = false); 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); + private sealed record BlockToRelocate(int SectionIndex, long Offset, byte[] Data, Relocation[] Relocations); private protected readonly NodeFactory _nodeFactory; private protected readonly ObjectWritingOptions _options; @@ -36,13 +36,12 @@ private protected sealed record BlockToRelocate(int SectionIndex, long Offset, b // Standard sections private readonly Dictionary _sectionNameToSectionIndex = new(StringComparer.Ordinal); private readonly List _sectionIndexToData = new(); - protected readonly List> _sectionIndexToRelocations = new(); + private readonly List> _sectionIndexToRelocations = new(); private protected readonly List _outputSectionLayout = []; // Symbol table private readonly Dictionary _definedSymbols = new(StringComparer.Ordinal); - private protected ObjectWriter(NodeFactory factory, ObjectWritingOptions options, OutputInfoBuilder outputInfoBuilder = null) { _nodeFactory = factory; diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs index 42e02a7d094d86..2b567b90de7fee 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs @@ -9,6 +9,7 @@ using System.Linq; using System.Numerics; using System.Reflection.PortableExecutable; +using System.Text; using ILCompiler.DependencyAnalysis; using Internal.TypeSystem; @@ -22,6 +23,7 @@ namespace ILCompiler.ObjectWriter internal sealed class PEObjectWriter : CoffObjectWriter { internal const int DosHeaderSize = 0x80; + private const int NoSectionIndex = -1; private static ReadOnlySpan DosHeader => // DosHeaderSize [ @@ -46,40 +48,32 @@ internal sealed class PEObjectWriter : CoffObjectWriter 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ]; - // PE layout computed ahead of writing the file. These are populated by - // EmitSectionsAndLayout so that data directories (e.g. exception table) - // can be filled prior to writing the optional header. - private uint _peSizeOfHeaders; - private uint _peSizeOfImage; - private uint _peSizeOfCode; - private uint _peSizeOfInitializedData; + 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 int _requestedSectionAlignment; // Relocations that we can resolve at emit time (ie not file-based relocations). - private List> _resolvableRelocations = []; + private Dictionary> _resolvableRelocations = []; - private int _pdataSectionIndex; - private int _debugSectionIndex; - private int _exportSectionIndex; - private int _baseRelocSectionIndex; - private int _corMetaSectionIndex; + private int _pdataSectionIndex = NoSectionIndex; + private int _debugSectionIndex = NoSectionIndex; + private int _exportSectionIndex = NoSectionIndex; + private int _baseRelocSectionIndex = NoSectionIndex; + private int _corMetaSectionIndex = NoSectionIndex; // Base relocation (.reloc) bookkeeping private readonly SortedDictionary> _baseRelocMap = new(); - - // Emitted Symbol Table info - private sealed record PESymbol(string Name, uint Offset); - private readonly Dictionary _definedPESymbols = new(); - private readonly List _exportedPESymbols = new(); - private readonly int _sectionAlignment; + private Dictionary _definedSymbols = []; private HashSet _exportedSymbolNames = new(); public PEObjectWriter(NodeFactory factory, ObjectWritingOptions options, int sectionAlignment, OutputInfoBuilder outputInfoBuilder) : base(factory, options, outputInfoBuilder) { - _sectionAlignment = sectionAlignment; + _requestedSectionAlignment = sectionAlignment; } public void AddExportedSymbol(string symbol) @@ -95,9 +89,9 @@ private protected override void CreateSection(ObjectNodeSection section, string // COMDAT sections are not supported in PE files base.CreateSection(section, comdatName: null, symbolName, sectionIndex, sectionStream); - if (_sectionAlignment != 0) + if (_requestedSectionAlignment != 0) { - UpdateSectionAlignment(sectionIndex, _sectionAlignment); + UpdateSectionAlignment(sectionIndex, _requestedSectionAlignment); } } @@ -284,11 +278,15 @@ public OptionalHeaderDataDirectories() _entries = new (uint, uint)[16]; } - public void Set(int index, uint virtualAddress, uint size) + public void SetIfNonEmpty(int index, uint virtualAddress, uint size) { if ((uint)index >= (uint)_entries.Length) throw new ArgumentOutOfRangeException(nameof(index)); - _entries[index] = (virtualAddress, size); + + if (size != 0) + { + _entries[index] = (virtualAddress, size); + } } public void WriteTo(Stream stream, int count) @@ -330,65 +328,99 @@ private protected override void EmitSymbolTable(IDictionary= _sections.Count) - continue; - - uint targetRva = _sections[sectionIdx].Header.VirtualAddress + (uint)symbolDefinition.Value; - PESymbol sym = new(symbolName, targetRva); - _definedPESymbols.Add(symbolName, sym); - if (_exportedSymbolNames.Contains(symbolName)) - { - _exportedPESymbols.Add(sym); - } - } + // Grab the defined symbols to resolve relocs during emit. + _definedSymbols = new Dictionary(definedSymbols); } private protected override void EmitSectionsAndLayout() { - // Compute layout for sections directly from the _sections list - // without merging or grouping by name suffixes. - ushort numberOfSections = (ushort)_sections.Count; - bool isPE32Plus = _nodeFactory.Target.PointerSize == 8; - ushort sizeOfOptionalHeader = (ushort)(isPE32Plus ? 0xF0 : 0xE0); + SectionWriter exportDirectory = GetOrCreateSection(ExportDirectorySection); + + EmitExportDirectory(exportDirectory); + + // Grab section indicies. + _pdataSectionIndex = GetOrCreateSection(ObjectNodeSection.PDataSection).SectionIndex; + _debugSectionIndex = GetOrCreateSection(ObjectNodeSection.DebugDirectorySection).SectionIndex; + _corMetaSectionIndex = GetOrCreateSection(ObjectNodeSection.CorMetaSection).SectionIndex; + _exportSectionIndex = exportDirectory.SectionIndex; - int fileAlignment = 0x200; - bool isWindowsOr32bit = _nodeFactory.Target.IsWindows || !isPE32Plus; + // 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) { - fileAlignment = 0x1000; + // 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) { - sectionAlignment = (uint)fileAlignment; + // 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; } - _peFileAlignment = (uint)fileAlignment; + _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); - uint sizeOfHeaders = (uint)AlignmentHelper.AlignUp((int)sizeOfHeadersUnaligned, (int)fileAlignment); + 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)sectionAlignment); + uint virtualAddress = (uint)AlignmentHelper.AlignUp((int)sizeOfHeaders, (int)_peSectionAlignment); - uint sizeOfCode = 0; - uint sizeOfInitializedData = 0; + sizeOfCode = 0; + sizeOfInitializedData = 0; - for (int i = 0; i < _sections.Count; i++) + foreach (SectionDefinition s in _sections) { - _resolvableRelocations.Add([]); - SectionDefinition s = _sections[i]; CoffSectionHeader h = s.Header; h.SizeOfRawData = (uint)s.Stream.Length; - uint rawAligned = h.SectionCharacteristics.HasFlag(SectionCharacteristics.ContainsUninitializedData) ? 0u : (uint)AlignmentHelper.AlignUp((int)h.SizeOfRawData, (int)fileAlignment); + uint rawAligned = h.SectionCharacteristics.HasFlag(SectionCharacteristics.ContainsUninitializedData) ? 0u : (uint)AlignmentHelper.AlignUp((int)h.SizeOfRawData, (int)_peFileAlignment); if (rawAligned != 0) { @@ -403,67 +435,20 @@ private protected override void EmitSectionsAndLayout() h.VirtualAddress = virtualAddress; h.VirtualSize = Math.Max(h.VirtualSize, h.SizeOfRawData); - virtualAddress += (uint)AlignmentHelper.AlignUp((int)h.VirtualSize, (int)sectionAlignment); + virtualAddress += (uint)AlignmentHelper.AlignUp((int)h.VirtualSize, (int)_peSectionAlignment); if (h.SectionCharacteristics.HasFlag(SectionCharacteristics.ContainsCode)) sizeOfCode += h.SizeOfRawData; else if (!h.SectionCharacteristics.HasFlag(SectionCharacteristics.ContainsUninitializedData)) sizeOfInitializedData += h.SizeOfRawData; - _outputSectionLayout.Add(new OutputSection(h.Name, h.VirtualAddress, h.PointerToRawData, h.SizeOfRawData)); - - if (h.Name == ".pdata") + if (recordFinalLayout) { - _pdataSectionIndex = i; + _outputSectionLayout.Add(new OutputSection(h.Name, h.VirtualAddress, h.PointerToRawData, h.SizeOfRawData)); } - else if (h.Name == ".debug") - { - _debugSectionIndex = i; - } - else if (h.Name == ".cormeta") - { - _corMetaSectionIndex = i; - } - } - - // If we have exports, create an export data section (.edata) and place it after other sections. - if (_exportedSymbolNames.Count > 0) - { - uint edataRva = virtualAddress; - ExportDirectory exportDir = new(_exportedPESymbols, moduleName: "UNKNOWN", edataRva); - SectionDefinition edataSection = exportDir.Section; - CoffSectionHeader edataHeader = edataSection.Header; - - // Compute raw and virtual sizes and update pointers - edataHeader.SizeOfRawData = (uint)edataSection.Stream.Length; - uint edataRawAligned = (uint)AlignmentHelper.AlignUp((int)edataHeader.SizeOfRawData, (int)fileAlignment); - edataHeader.PointerToRawData = pointerToRawData; -#pragma warning disable IDE0059 // Unnecessary assignment. We don't want to remove this and forget it if we add more sections after .edata. - pointerToRawData += edataRawAligned; -#pragma warning restore IDE0059 // Unnecessary assignment - edataHeader.SizeOfRawData = edataRawAligned; - - edataHeader.VirtualAddress = virtualAddress; - edataHeader.VirtualSize = Math.Max(edataHeader.VirtualSize, (uint)edataSection.Stream.Length); - virtualAddress += (uint)AlignmentHelper.AlignUp((int)edataHeader.VirtualSize, (int)sectionAlignment); - - // Add to sections and bookkeeping - _sections.Add(edataSection); - _sectionIndexToRelocations.Add(new List()); - _resolvableRelocations.Add([]); - - sizeOfInitializedData += edataHeader.SizeOfRawData; - - // Set export directory fields for header - _exportSectionIndex = _sections.Count - 1; } - uint sizeOfImage = (uint)AlignmentHelper.AlignUp((int)virtualAddress, (int)sectionAlignment); - - _peSizeOfHeaders = sizeOfHeaders; - _peSizeOfImage = sizeOfImage; - _peSizeOfCode = sizeOfCode; - _peSizeOfInitializedData = sizeOfInitializedData; + sizeOfImage = (uint)AlignmentHelper.AlignUp((int)virtualAddress, (int)_peSectionAlignment); } private protected override unsafe void EmitRelocations(int sectionIndex, List relocationList) @@ -474,7 +459,11 @@ private protected override unsafe void EmitRelocations(int sectionIndex, List resolvable)) + { + _resolvableRelocations[sectionIndex] = resolvable = []; + } + resolvable.Add(reloc); } else { @@ -482,8 +471,8 @@ private protected override unsafe void EmitRelocations(int sectionIndex, List // list of (type<<12 | offsetInPage) WORD entries). uint targetRva = _sections[sectionIndex].Header.VirtualAddress + (uint)reloc.Offset; - uint pageRva = targetRva & ~0xFFFu; - ushort offsetInPage = (ushort)(targetRva & 0xFFFu); + uint pageRva = targetRva & ~(PEHeaderConstants.SectionAlignment - 1); + ushort offsetInPage = (ushort)(targetRva & ~(PEHeaderConstants.SectionAlignment - 1)); ushort entry = (ushort)(((ushort)fileRelocType << 12) | offsetInPage); if (!_baseRelocMap.TryGetValue(pageRva, out var list)) @@ -496,9 +485,98 @@ private protected override unsafe void EmitRelocations(int sectionIndex, List exports = [.._exportedSymbolNames]; + + exports.Sort(StringComparer.Ordinal); + string moduleName = "UNKNOWN"; + const int minOrdinal = 1; + int count = exports.Count; + int maxOrdinal = minOrdinal + count - 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 ms = new MemoryStream(); + 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) { @@ -508,116 +586,44 @@ private void AddRelocSection() int entriesSize = entries.Count * 2; int sizeOfBlock = 8 + entriesSize; - // Pad to 4-byte alignment as customary - if ((sizeOfBlock & 3) != 0) - sizeOfBlock += 2; + sizeOfBlock = AlignmentHelper.AlignUp(sizeOfBlock, 4); - byte[] headerBuf = new byte[8]; - BinaryPrimitives.WriteUInt32LittleEndian(headerBuf.AsSpan(0, 4), pageRva); - BinaryPrimitives.WriteUInt32LittleEndian(headerBuf.AsSpan(4, 4), (uint)sizeOfBlock); - ms.Write(headerBuf, 0, headerBuf.Length); + writer.WriteLittleEndian(pageRva); + writer.WriteLittleEndian((uint)sizeOfBlock); // Emit entries foreach (ushort e in entries) { - byte[] w = new byte[2]; - BinaryPrimitives.WriteUInt16LittleEndian(w.AsSpan(), e); - ms.Write(w, 0, 2); - } - - // Ensure block is 4-byte aligned by padding a WORD if needed - if (((entriesSize) & 3) != 0) - { - byte[] pad = new byte[2]; - BinaryPrimitives.WriteUInt16LittleEndian(pad.AsSpan(), 0); - ms.Write(pad, 0, 2); + writer.WriteLittleEndian(e); } - } - - // Create a new section for .reloc and compute its raw/virtual layout - var relocHeader = new CoffSectionHeader - { - Name = ".reloc", - SectionCharacteristics = SectionCharacteristics.MemRead | SectionCharacteristics.ContainsInitializedData | SectionCharacteristics.MemDiscardable, - }; - var relocSection = new SectionDefinition(relocHeader, ms, new List(), null, null, ".reloc"); - - // Compute pointer to raw data: find last used raw end and align - uint lastRawEnd = 0; - uint lastVEnd = 0; - foreach (var s in _sections) - { - if (s.Header.PointerToRawData != 0) - { - lastRawEnd = Math.Max(lastRawEnd, s.Header.PointerToRawData + s.Header.SizeOfRawData); - } - uint vEnd = s.Header.VirtualAddress + (uint)AlignmentHelper.AlignUp((int)s.Header.VirtualSize, (int)_peSectionAlignment); - lastVEnd = Math.Max(lastVEnd, vEnd); + // Ensure block is 4-byte aligned + writer.EmitAlignment(4); } - uint pointerToRawData = lastRawEnd == 0 ? (uint)AlignmentHelper.AlignUp((int)_peSizeOfHeaders, (int)_peFileAlignment) : lastRawEnd; - relocHeader.PointerToRawData = pointerToRawData; - relocHeader.SizeOfRawData = (uint)AlignmentHelper.AlignUp((int)ms.Length, (int)_peFileAlignment); - relocHeader.VirtualAddress = (uint)AlignmentHelper.AlignUp((int)lastVEnd, (int)_peSectionAlignment); - relocHeader.VirtualSize = (uint)ms.Length; - - _sections.Add(relocSection); - _sectionIndexToRelocations.Add(new List()); - _resolvableRelocations.Add([]); + CoffSectionHeader relocHeader = _sections[_baseRelocSectionIndex].Header; - _outputSectionLayout.Add(new OutputSection(relocHeader.Name, relocHeader.VirtualAddress, relocHeader.PointerToRawData, relocHeader.SizeOfRawData)); - _baseRelocSectionIndex = _sections.Count - 1; + relocHeader.SectionCharacteristics |= SectionCharacteristics.MemDiscardable; } private protected override void EmitObjectFile(Stream outputFileStream) { if (_baseRelocMap.Count > 0) { - AddRelocSection(); + 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); - ushort numberOfSections = (ushort)_sections.Count; + bool isPE32Plus = _nodeFactory.Target.PointerSize == 8; ushort sizeOfOptionalHeader = (ushort)(isPE32Plus ? 0xF0 : 0xE0); - int fileAlignment = 0x200; - bool isWindowsOr32bit = _nodeFactory.Target.IsWindows || !isPE32Plus; - 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; - } - - 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 = (uint)fileAlignment; - } - - // Use layout computed in EmitSectionsAndLayout - uint sizeOfHeaders = _peSizeOfHeaders; - uint sizeOfCode = _peSizeOfCode; - uint sizeOfInitializedData = _peSizeOfInitializedData; - uint sizeOfImage = _peSizeOfImage; - Characteristics characteristics = Characteristics.ExecutableImage | Characteristics.Dll; characteristics |= isPE32Plus ? Characteristics.LargeAddressAware : Characteristics.Bit32Machine; @@ -669,33 +675,33 @@ private protected override void EmitObjectFile(Stream outputFileStream) } peOptional.DllCharacteristics = dllCharacteristics; - peOptional.SectionAlignment = sectionAlignment; - peOptional.FileAlignment = (uint)fileAlignment; + 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 (_pdataSectionIndex != 0) + if (_pdataSectionIndex != NoSectionIndex) { - dataDirs.Set((int)ImageDirectoryEntry.Exception, (uint)_outputSectionLayout[_pdataSectionIndex].VirtualAddress, (uint)_outputSectionLayout[_pdataSectionIndex].Length); + dataDirs.SetIfNonEmpty((int)ImageDirectoryEntry.Exception, (uint)_outputSectionLayout[_pdataSectionIndex].VirtualAddress, (uint)_outputSectionLayout[_pdataSectionIndex].Length); } - if (_exportSectionIndex != 0) + if (_exportSectionIndex != NoSectionIndex) { - dataDirs.Set((int)ImageDirectoryEntry.Export, (uint)_outputSectionLayout[_exportSectionIndex].VirtualAddress, (uint)_outputSectionLayout[_exportSectionIndex].Length); + dataDirs.SetIfNonEmpty((int)ImageDirectoryEntry.Export, (uint)_outputSectionLayout[_exportSectionIndex].VirtualAddress, (uint)_outputSectionLayout[_exportSectionIndex].Length); } - if (_baseRelocSectionIndex != 0) + if (_baseRelocSectionIndex != NoSectionIndex) { - dataDirs.Set((int)ImageDirectoryEntry.BaseRelocation, (uint)_outputSectionLayout[_baseRelocSectionIndex].VirtualAddress, (uint)_outputSectionLayout[_baseRelocSectionIndex].Length); + dataDirs.SetIfNonEmpty((int)ImageDirectoryEntry.BaseRelocation, (uint)_outputSectionLayout[_baseRelocSectionIndex].VirtualAddress, (uint)_outputSectionLayout[_baseRelocSectionIndex].Length); } - if (_debugSectionIndex != 0) + if (_debugSectionIndex != NoSectionIndex) { - dataDirs.Set((int)ImageDirectoryEntry.Debug, (uint)_outputSectionLayout[_debugSectionIndex].VirtualAddress, (uint)_outputSectionLayout[_debugSectionIndex].Length); + dataDirs.SetIfNonEmpty((int)ImageDirectoryEntry.Debug, (uint)_outputSectionLayout[_debugSectionIndex].VirtualAddress, (uint)_outputSectionLayout[_debugSectionIndex].Length); } - if (_corMetaSectionIndex != 0) + if (_corMetaSectionIndex != NoSectionIndex) { - dataDirs.Set((int)ImageDirectoryEntry.CLRRuntimeHeader, (uint)_outputSectionLayout[_corMetaSectionIndex].VirtualAddress, (uint)_outputSectionLayout[_corMetaSectionIndex].Length); + dataDirs.SetIfNonEmpty((int)ImageDirectoryEntry.CLRRuntimeHeader, (uint)_outputSectionLayout[_corMetaSectionIndex].VirtualAddress, (uint)_outputSectionLayout[_corMetaSectionIndex].Length); } peOptional.Write(outputFileStream, dataDirs); @@ -724,13 +730,13 @@ private protected override void EmitObjectFile(Stream outputFileStream) { Debug.Assert(outputFileStream.Position <= section.Header.PointerToRawData); outputFileStream.Position = section.Header.PointerToRawData; // Pad to alignment - if (_resolvableRelocations[i] is not []) + 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); - ResolveNonImageBaseRelocations(i, _resolvableRelocations[i], stream); + ResolveNonImageBaseRelocations(i, relocationsToResolve, stream); stream.Position = 0; stream.CopyTo(outputFileStream); } @@ -742,6 +748,10 @@ private protected override void EmitObjectFile(Stream 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); + // TODO: calculate PE checksum // TODO: calculate deterministic timestamp } @@ -750,35 +760,33 @@ private unsafe void ResolveNonImageBaseRelocations(int sectionIndex, List(Stream stream, T value) value.WriteLittleEndian(buffer); stream.Write(buffer); } - - private sealed class ExportDirectory - { - public SectionDefinition Section { get; } - public int ExportDirectoryOffset { get; } - public int ExportDirectorySize { get; } - - public ExportDirectory(IEnumerable exportedPESymbols, string moduleName, uint edataRva) - { - var exports = exportedPESymbols.OrderBy(e => e.Name, StringComparer.Ordinal).ToArray(); - - using var ms = new MemoryStream(); - - // Emit names - var nameRvas = new uint[exports.Length]; - for (int i = 0; i < exports.Length; i++) - { - nameRvas[i] = edataRva + (uint)ms.Position; - ms.Write(System.Text.Encoding.UTF8.GetBytes(exports[i].Name)); - ms.WriteByte(0); - } - - // Module name - uint moduleNameRva = edataRva + (uint)ms.Position; - ms.Write(System.Text.Encoding.UTF8.GetBytes(moduleName)); - ms.WriteByte(0); - - // Align to 4 - while (ms.Position % 4 != 0) - ms.WriteByte(0); - - // Name pointer table - uint namePointerTableRva = edataRva + (uint)ms.Position; - int minOrdinal = 1; - int count = exports.Length; - int maxOrdinal = minOrdinal + count - 1; - var addressTable = new int[maxOrdinal - minOrdinal + 1]; - - for (int i = 0; i < exports.Length; i++) - { - // name RVA - WriteLittleEndian(ms, (int)nameRvas[i]); - addressTable[i] = (int)exports[i].Offset; - } - - // Ordinal table - uint ordinalTableRva = edataRva + (uint)ms.Position; - Span tmp2 = stackalloc byte[2]; - for (int i = 0; i < exports.Length; i++) - { - BinaryPrimitives.WriteUInt16LittleEndian(tmp2, (ushort)(i)); - ms.Write(tmp2); - } - - // Address table - while (ms.Position % 4 != 0) - ms.WriteByte(0); - - uint addressTableRva = edataRva + (uint)ms.Position; - Span tmp4a = stackalloc byte[4]; - for (int i = 0; i < addressTable.Length; i++) - { - WriteLittleEndian(ms, addressTable[i]); - } - - // Export directory table - while (ms.Position % 4 != 0) - ms.WriteByte(0); - uint exportDirectoryTableRva = edataRva + (uint)ms.Position; - - // +0x00: reserved - WriteLittleEndian(ms, 0); - // +0x04: time/date stamp - WriteLittleEndian(ms, 0); - // +0x08: major version - WriteLittleEndian(ms, 0); - // +0x0A: minor version - WriteLittleEndian(ms, 0); - // +0x0C: DLL name RVA - WriteLittleEndian(ms, (int)moduleNameRva); - // +0x10: ordinal base - WriteLittleEndian(ms, minOrdinal); - // +0x14: number of entries in the address table - WriteLittleEndian(ms, addressTable.Length); - // +0x18: number of name pointers - WriteLittleEndian(ms, exports.Length); - // +0x1C: export address table RVA - WriteLittleEndian(ms, (int)addressTableRva); - // +0x20: name pointer RVA - WriteLittleEndian(ms, (int)namePointerTableRva); - // +0x24: ordinal table RVA - WriteLittleEndian(ms, (int)ordinalTableRva); - - int exportDirectorySize = (int)(ms.Position - exportDirectoryTableRva); - int exportDirectoryOffset = (int)exportDirectoryTableRva - (int)edataRva; - - var header = new CoffSectionHeader - { - Name = ".edata", - SectionCharacteristics = SectionCharacteristics.MemRead | SectionCharacteristics.ContainsInitializedData - }; - Section = new SectionDefinition(header, ms, new List(), null, null, ".edata"); - ExportDirectoryOffset = exportDirectoryOffset; - ExportDirectorySize = exportDirectorySize; - } - } } } diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/PETargetExtensions.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/PETargetExtensions.cs index f00cac6af8b153..12f992c29677b8 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/PETargetExtensions.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/PETargetExtensions.cs @@ -27,7 +27,7 @@ internal enum MachineOSOverride : ushort /// internal static class PEHeaderConstants { - public const int SectionAlignment = 0x1000; + public const uint SectionAlignment = 0x1000; public const byte MajorLinkerVersion = 11; public const byte MinorLinkerVersion = 0; From c357981a4dbc44e0e9774a3693e3fd0d8a55a208 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 30 Sep 2025 11:29:17 -0700 Subject: [PATCH 36/71] Emit cold method code nodes as part of the late phase. --- .../CodeGen/ReadyToRunObjectWriter.cs | 25 ------------------- .../ReadyToRun/MethodColdCodeNode.cs | 2 ++ 2 files changed, 2 insertions(+), 25 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs index 0d7c4005694aaf..6011e736dcadfa 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs @@ -361,20 +361,11 @@ public void EmitPortableExecutable() PerfMapDebugDirectoryEntryNode perfMapDebugDirectoryEntryNode = 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) @@ -413,22 +404,6 @@ public void EmitPortableExecutable() _outputInfoBuilder.AddMethod(methodNode, methodNode); } - // Emit cold method nodes to end of execution section. - foreach (ObjectNode node in methodColdCodeNodes) - { - ++nodeIndex; - - if (node == null) - { - continue; - } - - 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); 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 6fad4eac8ef2d9..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,6 +18,8 @@ public MethodColdCodeNode(MethodDesc owningMethod) _owningMethod = owningMethod; } + protected internal override int Phase => (int)ObjectNodePhase.Late; + public int Offset => 0; public override ObjectNodeSection GetSection(NodeFactory factory) From dde50fbc227bf35d9939773683f462c4adbcb6cd Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 30 Sep 2025 12:31:43 -0700 Subject: [PATCH 37/71] Don't include padding in recorded section layout --- .../tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs index 2b567b90de7fee..e9ae59c168bf85 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs @@ -444,7 +444,8 @@ private void LayoutSections(bool recordFinalLayout, out ushort numberOfSections, if (recordFinalLayout) { - _outputSectionLayout.Add(new OutputSection(h.Name, h.VirtualAddress, h.PointerToRawData, h.SizeOfRawData)); + // Use the stream length so we don't include any space that's appended just for alignment purposes. + _outputSectionLayout.Add(new OutputSection(h.Name, h.VirtualAddress, h.PointerToRawData, (uint)s.Stream.Length)); } } From 9ecea74ee86c2ea6f82e9cba2a64fa53e56bbaa4 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 30 Sep 2025 14:04:25 -0700 Subject: [PATCH 38/71] Fix absolute file relocs and recording symbol offsets in OutputInfoBuilder --- .../tools/Common/Compiler/ObjectWriter/ObjectWriter.cs | 6 +++--- .../Common/Compiler/ObjectWriter/OutputInfoBuilder.cs | 8 ++++---- .../tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs | 2 +- .../ILCompiler.ReadyToRun/ObjectWriter/MapFileBuilder.cs | 8 ++++---- .../ObjectWriter/ProfileFileBuilder.cs | 4 ++-- .../ILCompiler.ReadyToRun/ObjectWriter/SectionBuilder.cs | 4 ++-- 6 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs index 24d8e8e1a20d88..efd4f4ab48d819 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs @@ -390,7 +390,7 @@ public void EmitObject(Stream outputFileStream, IReadOnlyCollection /// 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; @@ -74,7 +74,7 @@ public sealed 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; @@ -94,7 +94,7 @@ public void AddRelocation() /// 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) { } diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs index e9ae59c168bf85..9b45a6753c9343 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs @@ -791,7 +791,7 @@ private unsafe void ResolveNonImageBaseRelocations(int sectionIndex, List kvpCalleeCount in kvpCallerCalleeCount.Value) @@ -167,7 +167,7 @@ private void CalculateCallInfo() if (_symbolMethodMap.TryGetValue(kvpCalleeCount.Key, out ISymbolDefinitionNode calleeSymbol) && _outputInfoBuilder.NodeSymbolMap.TryGetValue(calleeSymbol, out calleeNode)) { - calleeRVA = _outputInfoBuilder.Sections[calleeNode.SectionIndex].VirtualAddress + (ulong)calleeNode.Offset; + calleeRVA = _outputInfoBuilder.Sections[calleeNode.SectionIndex].VirtualAddress + calleeNode.Offset; } _callInfo.Add(new CallInfo( diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/SectionBuilder.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/SectionBuilder.cs index 2f5c020d0483dc..dfef14b9ea6f1c 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/SectionBuilder.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/SectionBuilder.cs @@ -485,7 +485,7 @@ public void AddObjectData(ObjectNode.ObjectData objectData, int sectionIndex, st if (outputInfoBuilder != null) { - var node = new ObjectWriter.OutputNode(sectionIndex, alignedOffset, objectData.Data.Length, name); + var node = new ObjectWriter.OutputNode(sectionIndex, (ulong)alignedOffset, objectData.Data.Length, name); outputInfoBuilder.AddNode(node, objectData.DefinedSymbols[0]); if (objectData.Relocs != null) { @@ -511,7 +511,7 @@ public void AddObjectData(ObjectNode.ObjectData objectData, int sectionIndex, st Utf8StringBuilder sb = new Utf8StringBuilder(); symbol.AppendMangledName(GetNameMangler(), sb); int sectionRelativeOffset = alignedOffset + symbol.Offset; - outputInfoBuilder.AddSymbol(new ObjectWriter.OutputSymbol(sectionIndex, sectionRelativeOffset, sb.ToString())); + outputInfoBuilder.AddSymbol(new ObjectWriter.OutputSymbol(sectionIndex, (ulong)sectionRelativeOffset, sb.ToString())); } _symbolMap.Add(symbol, new SymbolTarget( sectionIndex: sectionIndex, From a4cc2546acc9902147375957b942af614190185c Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 30 Sep 2025 14:36:46 -0700 Subject: [PATCH 39/71] Generate the perfmap debug entry during build. We have the information available at a point where we don't need to do back-patching (so don't). --- .../CodeGen/ReadyToRunObjectWriter.cs | 31 +++----------- .../ReadyToRun/DebugDirectoryEntryNode.cs | 41 +++++++++---------- .../ReadyToRun/DebugDirectoryNode.cs | 4 +- .../ReadyToRunCodegenCompilationBuilder.cs | 2 +- 4 files changed, 28 insertions(+), 50 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs index 6011e736dcadfa..87b30ffe5e3351 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs @@ -220,6 +220,11 @@ public void EmitReadyToRunObjects(ReadyToRunContainerFormat format, Logger logge } } + foreach (string inputFile in _inputFiles) + { + _outputInfoBuilder.AddInputModule(_nodeFactory.TypeSystemContext.GetModuleFromPath(inputFile)); + } + if (debugOffset != 0) { if (_nodeFactory.DebugDirectoryNode.PdbEntry is { } nativeDebugDirectoryEntryNode) @@ -236,21 +241,6 @@ public void EmitReadyToRunObjects(ReadyToRunContainerFormat format, Logger logge stream.Write(rsdsEntry); } } - - if (_nodeFactory.DebugDirectoryNode.PerfMapEntry is { } perfMapDebugDirectoryEntryNode) - { - Debug.Assert(_generatePerfMapFile && _outputInfoBuilder.EnumerateInputAssemblies().Any()); - byte[] perfmapSig = PerfMapWriter.PerfMapV1SignatureHelper(_outputInfoBuilder.EnumerateInputAssemblies(), _nodeFactory.Target); - byte[] perfMapEntry = perfMapDebugDirectoryEntryNode.GeneratePerfMapEntryData(perfmapSig, _perfMapFormatVersion); - - stream.Seek((long)debugOffset, SeekOrigin.Begin); - stream.Write(perfMapEntry); - } - } - - foreach (string inputFile in _inputFiles) - { - _outputInfoBuilder.AddInputModule(_nodeFactory.TypeSystemContext.GetModuleFromPath(inputFile)); } if (_generateMapFile) @@ -452,17 +442,6 @@ public void EmitPortableExecutable() 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); - } } if (_outputInfoBuilder != null) 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..fb9ca6de3101d1 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,8 @@ using Internal.TypeSystem.Ecma; using System.IO; using System.Collections.Immutable; +using System.Collections.Generic; +using ILCompiler.Diagnostics; namespace ILCompiler.DependencyAnalysis.ReadyToRun { @@ -77,13 +79,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 +101,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) 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 b9955811b30f54..74ee43a2850ddf 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,7 +48,7 @@ public DebugDirectoryNode(EcmaModule sourceModule, string outputFileName, bool s if (shouldGeneratePerfmap) { - _perfMapEntry = new PerfMapDebugDirectoryEntryNode(pdbNameRoot + ".ni.r2rmap"); + _perfMapEntry = new PerfMapDebugDirectoryEntryNode(pdbNameRoot + ".ni.r2rmap", perfMapFormatVersion); } } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilationBuilder.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilationBuilder.cs index 7a6fb94559b5bc..3b57a4e5dad675 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilationBuilder.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilationBuilder.cs @@ -234,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 From f1a3fae9596bb0b15e909b45a2e74a7b815be465 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 30 Sep 2025 15:46:58 -0700 Subject: [PATCH 40/71] Add a concept of a "checksum" reloc to enable a generalized system for embedding image checksums --- .../DependencyAnalysis/ISymbolNode.cs | 10 ++++ .../DependencyAnalysis/ObjectDataBuilder.cs | 9 +++ .../Compiler/DependencyAnalysis/Relocation.cs | 3 + .../Compiler/ObjectWriter/ObjectWriter.cs | 39 +++++++++++++ .../Compiler/ObjectWriter/PEObjectWriter.cs | 2 + .../CodeGen/ReadyToRunObjectWriter.cs | 28 --------- .../ReadyToRun/DebugDirectoryEntryNode.cs | 57 ++++++++++++++++++- .../Compiler/ReadyToRunCodegenCompilation.cs | 2 +- .../ObjectWriter/SectionBuilder.cs | 6 ++ 9 files changed, 124 insertions(+), 32 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/ISymbolNode.cs b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/ISymbolNode.cs index fd6738a66f5684..742e0bd9505f6b 100644 --- a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/ISymbolNode.cs +++ b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/ISymbolNode.cs @@ -90,6 +90,16 @@ public interface ISymbolRangeNode : ISymbolNode 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. /// 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/Relocation.cs b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/Relocation.cs index d918720f03b860..152da671c00971 100644 --- a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/Relocation.cs +++ b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/Relocation.cs @@ -69,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 diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs index efd4f4ab48d819..089d021eea9085 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs @@ -330,6 +330,7 @@ public void EmitObject(Stream outputFileStream, IReadOnlyCollection symbolRangeNodes = []; List blocksToRelocate = []; + List checksumRelocations = []; foreach (DependencyNode depNode in nodes) { if (depNode is ISymbolRangeNode symbolRange) @@ -503,6 +504,7 @@ public void EmitObject(Stream outputFileStream, IReadOnlyCollection checksumRelocationsBuilder = default; foreach (Relocation reloc in blockToRelocate.Relocations) { #if READYTORUN @@ -511,6 +513,14 @@ public void EmitObject(Stream outputFileStream, IReadOnlyCollection 0) + { + EmitChecksums(outputFileStream, checksumRelocations); + } + if (_outputInfoBuilder is not null) { foreach (var outputSection in _outputSectionLayout) @@ -556,6 +572,29 @@ public void EmitObject(Stream outputFileStream, IReadOnlyCollection checksumRelocations) + { + MemoryStream originalOutputStream = new(); + outputFileStream.Seek(0, SeekOrigin.Begin); + outputFileStream.CopyTo(originalOutputStream); + byte[] originalOutput = originalOutputStream.ToArray(); + + foreach (var block in checksumRelocations) + { + foreach (var reloc in block.Relocations) + { + 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(); diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs index 9b45a6753c9343..b57aa1a69cdd1b 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs @@ -445,6 +445,8 @@ private void LayoutSections(bool recordFinalLayout, out ushort numberOfSections, 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)); } } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs index 87b30ffe5e3351..c6f874fa55cb9a 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs @@ -210,39 +210,11 @@ public void EmitReadyToRunObjects(ReadyToRunContainerFormat format, Logger logge _mapFileBuilder.SetFileSize(stream.Length); } - ulong debugOffset = 0; - foreach (OutputSection section in _outputInfoBuilder.Sections) - { - if (section.Name == ".debug") - { - debugOffset = section.FilePosition; - break; - } - } - foreach (string inputFile in _inputFiles) { _outputInfoBuilder.AddInputModule(_nodeFactory.TypeSystemContext.GetModuleFromPath(inputFile)); } - if (debugOffset != 0) - { - if (_nodeFactory.DebugDirectoryNode.PdbEntry is { } nativeDebugDirectoryEntryNode) - { - Debug.Assert(_generatePdbFile); - // Compute hash of the output image and store that in the native DebugDirectory entry - using (var hashAlgorithm = SHA256.Create()) - { - stream.Seek(0, SeekOrigin.Begin); - byte[] hash = hashAlgorithm.ComputeHash(stream); - byte[] rsdsEntry = nativeDebugDirectoryEntryNode.GenerateRSDSEntryData(hash); - - stream.Seek((long)debugOffset, SeekOrigin.Begin); - stream.Write(rsdsEntry); - } - } - } - if (_generateMapFile) { string mapFileName = Path.ChangeExtension(_objectFilePath, ".map"); 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 fb9ca6de3101d1..6e30876dca6df5 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 @@ -14,6 +14,8 @@ using System.Collections.Immutable; using System.Collections.Generic; using ILCompiler.Diagnostics; +using ILCompiler.DependencyAnalysisFramework; +using System.Security.Cryptography; namespace ILCompiler.DependencyAnalysis.ReadyToRun { @@ -160,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) { @@ -174,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(); } @@ -222,6 +236,43 @@ 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); + 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/ReadyToRunCodegenCompilation.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.cs index 52488834d90cf0..1b99a8a46b263f 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.cs @@ -472,7 +472,7 @@ private void RewriteComponentFile(string inputFile, string outputFile, string ow 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, diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/SectionBuilder.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/SectionBuilder.cs index dfef14b9ea6f1c..f983d965b69285 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/SectionBuilder.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/SectionBuilder.cs @@ -916,6 +916,12 @@ public void RelocateOutputFile( { foreach (Relocation relocation in placedObjectData.Relocs) { + if (relocation.RelocType == RelocType.IMAGE_REL_FILE_CHECKSUM_CALLBACK) + { + // Checksums are handled manually for PEWriter. + continue; + } + // Process a single relocation int relocationRVA = section.RVAWhenPlaced + placedObjectData.Offset + relocation.Offset; int relocationFilePos = relocationRVA + rvaToFilePosDelta; From eb8829df678d41941f1232cbb7908ff8a1de762a Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 30 Sep 2025 16:54:49 -0700 Subject: [PATCH 41/71] Make outputInfoBuilder optional again as we don't need it for back-patching any more --- .../CodeGen/ReadyToRunObjectWriter.cs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs index c6f874fa55cb9a..02ced8d6c37455 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs @@ -57,9 +57,9 @@ internal class ReadyToRunObjectWriter private readonly IReadOnlyCollection _nodes; /// - /// Builder to collect information from executable generation for auxiliary files (map, profile, or symbol files). + /// Set to non-null when generating symbol info or profile info. /// - private readonly OutputInfoBuilder _outputInfoBuilder = new(); + private readonly OutputInfoBuilder _outputInfoBuilder; /// /// Set to non-null when the executable generator should output a map file. @@ -171,6 +171,7 @@ public ReadyToRunObjectWriter( if (generateMap || generateSymbols || generateProfileFile) { + _outputInfoBuilder = new OutputInfoBuilder(); if (generateMap) { _mapFileBuilder = new MapFileBuilder(_outputInfoBuilder); @@ -202,17 +203,23 @@ public void EmitReadyToRunObjects(ReadyToRunContainerFormat format, Logger logge using FileStream stream = new FileStream(_objectFilePath, FileMode.Create); objectWriter.EmitObject(stream, _nodes, dumper: null, logger); - foreach (MethodWithGCInfo methodNode in _nodeFactory.EnumerateCompiledMethods()) - _outputInfoBuilder.AddMethod(methodNode, methodNode); + if (_outputInfoBuilder is not null) + { + foreach (MethodWithGCInfo methodNode in _nodeFactory.EnumerateCompiledMethods()) + _outputInfoBuilder.AddMethod(methodNode, methodNode); + } if (_mapFileBuilder != null) { _mapFileBuilder.SetFileSize(stream.Length); } - foreach (string inputFile in _inputFiles) + if (_outputInfoBuilder is not null) { - _outputInfoBuilder.AddInputModule(_nodeFactory.TypeSystemContext.GetModuleFromPath(inputFile)); + foreach (string inputFile in _inputFiles) + { + _outputInfoBuilder.AddInputModule(_nodeFactory.TypeSystemContext.GetModuleFromPath(inputFile)); + } } if (_generateMapFile) From abffd27d3af5e98ed5808f484f78a599e800173e Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 30 Sep 2025 17:33:16 -0700 Subject: [PATCH 42/71] Handle COFF timestamp and exporting the RTR header for composite mode --- .../Compiler/ObjectWriter/CoffObjectWriter.cs | 5 ++++ .../Compiler/ObjectWriter/ObjectWriter.cs | 13 +++++--- .../Compiler/ObjectWriter/PEObjectWriter.cs | 30 +++++++++++++++---- .../CodeGen/ReadyToRunObjectWriter.cs | 20 ++++++++++++- .../ReadyToRunCodegenNodeFactory.cs | 4 +++ 5 files changed, 61 insertions(+), 11 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs index 9da244852e8885..61b29f6a151e3e 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs @@ -486,6 +486,11 @@ protected struct CoffHeader 0xAF, 0x20, 0xFA, 0xF6, 0x6A, 0xA4, 0xDC, 0xB8, ]; + public static int TimeDateStampOffset(bool bigObj) + { + return bigObj ? 8 : 4; + } + private const int RegularSize = sizeof(ushort) + // Machine sizeof(ushort) + // NumberOfSections diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs index 089d021eea9085..e3e693cd2ce5b7 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs @@ -23,6 +23,7 @@ public abstract partial class ObjectWriter private protected sealed record SymbolDefinition(int SectionIndex, long Value, int Size = 0, bool Global = false); 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; @@ -330,7 +331,7 @@ public void EmitObject(Stream outputFileStream, IReadOnlyCollection symbolRangeNodes = []; List blocksToRelocate = []; - List checksumRelocations = []; + List checksumRelocations = []; foreach (DependencyNode depNode in nodes) { if (depNode is ISymbolRangeNode symbolRange) @@ -536,7 +537,7 @@ public void EmitObject(Stream outputFileStream, IReadOnlyCollection checksumRelocations) + 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.Relocations) + foreach (var reloc in block.ChecksumRelocations) { IChecksumNode checksum = (IChecksumNode)reloc.Target; diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs index b57aa1a69cdd1b..589c36e083d5b5 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs @@ -8,7 +8,9 @@ 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; @@ -53,7 +55,9 @@ internal sealed class PEObjectWriter : CoffObjectWriter 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 = []; @@ -69,11 +73,14 @@ internal sealed class PEObjectWriter : CoffObjectWriter private Dictionary _definedSymbols = []; private HashSet _exportedSymbolNames = new(); + private long _coffHeaderOffset; - public PEObjectWriter(NodeFactory factory, ObjectWritingOptions options, int sectionAlignment, OutputInfoBuilder outputInfoBuilder) + 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) @@ -499,7 +506,7 @@ private void EmitExportDirectory(SectionWriter sectionWriter) List exports = [.._exportedSymbolNames]; exports.Sort(StringComparer.Ordinal); - string moduleName = "UNKNOWN"; + string moduleName = Path.GetFileName(_outputPath); const int minOrdinal = 1; int count = exports.Count; int maxOrdinal = minOrdinal + count - 1; @@ -623,7 +630,6 @@ private protected override void EmitObjectFile(Stream outputFileStream) Debug.Assert(DosHeader.Length == DosHeaderSize); outputFileStream.Write("PE\0\0"u8); - bool isPE32Plus = _nodeFactory.Target.PointerSize == 8; ushort sizeOfOptionalHeader = (ushort)(isPE32Plus ? 0xF0 : 0xE0); @@ -642,13 +648,15 @@ private protected override void EmitObjectFile(Stream outputFileStream) { Machine = machine, NumberOfSections = (uint)numberOfSections, - TimeDateStamp = (uint)DateTimeOffset.UtcNow.ToUnixTimeSeconds(), // TODO: Make deterministic + TimeDateStamp = (uint)(_coffTimestamp ?? 0), PointerToSymbolTable = 0, NumberOfSymbols = 0, SizeOfOptionalHeader = sizeOfOptionalHeader, Characteristics = characteristics, }; + _coffHeaderOffset = outputFileStream.Position; + coffHeader.Write(outputFileStream); var peOptional = new PEOptionalHeader(_machine) @@ -754,9 +762,19 @@ private protected override void EmitObjectFile(Stream 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); + } - // TODO: calculate PE checksum - // TODO: calculate deterministic timestamp + 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 ResolveNonImageBaseRelocations(int sectionIndex, List symbolicRelocations, MemoryStream stream) diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs index 02ced8d6c37455..19c018580869b4 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs @@ -199,7 +199,25 @@ public void EmitReadyToRunObjects(ReadyToRunContainerFormat format, Logger logge Debug.Assert(format == ReadyToRunContainerFormat.PE); - PEObjectWriter objectWriter = new(_nodeFactory, ObjectWritingOptions.None, _customPESectionAlignment, _outputInfoBuilder); + int? timeDateStamp; + + if (_nodeFactory.CompilationModuleGroup.IsCompositeBuildMode && _componentModule == null) + { + timeDateStamp = null; + } + else + { + PEReader inputPeReader = (_componentModule != null ? _componentModule.PEReader : _nodeFactory.CompilationModuleGroup.CompilationModuleSet.First().PEReader); + timeDateStamp = inputPeReader.PEHeaders.CoffHeader.TimeDateStamp; + } + + PEObjectWriter objectWriter = new(_nodeFactory, ObjectWritingOptions.None, _outputInfoBuilder, _objectFilePath, _customPESectionAlignment, timeDateStamp); + + if (_nodeFactory.CompilationModuleGroup.IsCompositeBuildMode && _componentModule == null) + { + objectWriter.AddExportedSymbol("RTR_HEADER"); + } + using FileStream stream = new FileStream(_objectFilePath, FileMode.Create); objectWriter.EmitObject(stream, _nodes, dumper: null, logger); 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 113efe5a0d13cb..6f55a9e35a14e7 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs @@ -1063,6 +1063,10 @@ public void DetectGenericCycles(TypeSystemEntity caller, TypeSystemEntity callee public string GetSymbolAlternateName(ISymbolNode node, out bool isHidden) { isHidden = false; + if (node == Header) + { + return "RTR_HEADER"; + } return null; } } From e1e5bdb4d4da4968f3636cda9180b4ee23951221 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 1 Oct 2025 10:24:23 -0700 Subject: [PATCH 43/71] Fix 32-bit relative relocs in PEObjectWriter --- .../tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs index 589c36e083d5b5..f08ca14126c242 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs @@ -797,14 +797,9 @@ private unsafe void ResolveNonImageBaseRelocations(int sectionIndex, List Date: Fri, 3 Oct 2025 13:24:41 -0700 Subject: [PATCH 44/71] Change format default --- .../tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs | 2 -- src/coreclr/tools/aot/crossgen2/Crossgen2RootCommand.cs | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs index f08ca14126c242..83c28635c057a6 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs @@ -508,8 +508,6 @@ private void EmitExportDirectory(SectionWriter sectionWriter) exports.Sort(StringComparer.Ordinal); string moduleName = Path.GetFileName(_outputPath); const int minOrdinal = 1; - int count = exports.Count; - int maxOrdinal = minOrdinal + count - 1; StringTableBuilder exportsStringTable = new(); diff --git a/src/coreclr/tools/aot/crossgen2/Crossgen2RootCommand.cs b/src/coreclr/tools/aot/crossgen2/Crossgen2RootCommand.cs index 5152381a7cbe95..44ed36469e2537 100644 --- a/src/coreclr/tools/aot/crossgen2/Crossgen2RootCommand.cs +++ b/src/coreclr/tools/aot/crossgen2/Crossgen2RootCommand.cs @@ -410,7 +410,7 @@ private static FileLayoutAlgorithm MakeFileLayoutAlgorithm(ArgumentResult result private static ReadyToRunContainerFormat MakeOutputFormat(ArgumentResult result) { if (result.Tokens.Count == 0) - return ReadyToRunContainerFormat.LegacyPE; + return ReadyToRunContainerFormat.PE; return result.Tokens[0].Value.ToLowerInvariant() switch { From df0c0fa43de6526a521af06ac8d3fe3f5b269394 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 3 Oct 2025 15:24:52 -0700 Subject: [PATCH 45/71] Port over the rest of the reloc types supported by the PEWriter infra (and correctly emit the imagebase-relative ones) --- .../Compiler/ObjectWriter/PEObjectWriter.cs | 110 ++++++++++++------ 1 file changed, 75 insertions(+), 35 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs index 83c28635c057a6..4ecf89bbc743b3 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs @@ -465,17 +465,12 @@ private protected override unsafe void EmitRelocations(int sectionIndex, List resolvable)) { - if (!_resolvableRelocations.TryGetValue(sectionIndex, out List resolvable)) - { - _resolvableRelocations[sectionIndex] = resolvable = []; - } - resolvable.Add(reloc); + _resolvableRelocations[sectionIndex] = resolvable = []; } - else + 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 -> @@ -483,7 +478,7 @@ private protected override unsafe void EmitRelocations(int sectionIndex, List symbolicRelocations, MemoryStream stream) + 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[definedSymbol.SectionIndex].Header.VirtualAddress + reloc.Offset)); uint symbolImageOffset = checked((uint)(_sections[definedSymbol.SectionIndex].Header.VirtualAddress + definedSymbol.Value)); - switch (reloc.Type) + + fixed (byte* pData = GetRelocDataSpan(reloc)) { - case RelocType.IMAGE_REL_BASED_ADDR32NB: - 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 + 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: { - Relocation.WriteValue(reloc.Type, pData, symbolImageOffset); - WriteRelocDataSpan(reloc, pData); + 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; } - break; - case RelocType.IMAGE_REL_BASED_REL32: - case RelocType.IMAGE_REL_BASED_RELPTR32: - fixed (byte* pData = GetRelocDataSpan(reloc)) + 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: { - long relocOffset = checked((uint)(_sections[definedSymbol.SectionIndex].Header.VirtualAddress + reloc.Offset)); - long addend = Relocation.ReadValue(reloc.Type, pData); - - Relocation.WriteValue(reloc.Type, pData, symbolImageOffset - relocOffset + addend); - WriteRelocDataSpan(reloc, pData); + if (addend != 0) + { + throw new NotSupportedException(); + } + long delta = (symbolImageOffset - (relocOffset & ~0xfff) + ((symbolImageOffset & 0x800) << 1)); + Relocation.WriteValue(reloc.Type, pData, delta); + break; } - break; - case RelocType.IMAGE_REL_FILE_ABSOLUTE: - fixed (byte* pData = GetRelocDataSpan(reloc)) + case RelocType.IMAGE_REL_BASED_LOONGARCH64_JIR: + case RelocType.IMAGE_REL_BASED_RISCV64_PC: { - long rva = _sections[definedSymbol.SectionIndex].Header.PointerToRawData + definedSymbol.Value; - Relocation.WriteValue(reloc.Type, pData, rva); - WriteRelocDataSpan(reloc, pData); + if (addend != 0) + { + throw new NotSupportedException(); + } + long delta = symbolImageOffset - relocOffset; + Relocation.WriteValue(reloc.Type, pData, delta); + break; } - break; - default: - throw new NotSupportedException($"Unsupported relocation: {reloc.Type}"); + default: + throw new NotSupportedException($"Unsupported relocation: {reloc.Type}"); + } + WriteRelocDataSpan(reloc, pData); } Span GetRelocDataSpan(SymbolicRelocation reloc) From 4119e557b9c27e1985347af35b427515e0c3612e Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 6 Oct 2025 10:43:36 -0700 Subject: [PATCH 46/71] Fix rel32 relocs to calculate from the end of the reloc (not the start) --- .../tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs index 4ecf89bbc743b3..f3112a89e0706f 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs @@ -775,7 +775,8 @@ private unsafe void ResolveRelocations(int sectionIndex, List Date: Mon, 6 Oct 2025 12:08:08 -0700 Subject: [PATCH 47/71] Fix calculating in-page RVA for the reloc map and use the new format for composite component images as well. --- .../tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs | 2 +- .../Compiler/ReadyToRunCodegenCompilation.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs index f3112a89e0706f..128e4fcc73b6a7 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs @@ -477,7 +477,7 @@ private protected override unsafe void EmitRelocations(int sectionIndex, List Date: Mon, 6 Oct 2025 14:47:01 -0700 Subject: [PATCH 48/71] Output the node type name correctly into the map file --- .../Compiler/ObjectWriter/ObjectWriter.cs | 22 ++++++++++++++++++- .../ObjectWriter/OutputInfoBuilder.cs | 7 +++--- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs index e3e693cd2ce5b7..4c4e4c3234be52 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs @@ -450,7 +450,7 @@ public void EmitObject(Stream outputFileStream, IReadOnlyCollection 0) + { + name = name.Substring(lastDot + 1); + } + + return name; + } + private void EmitChecksums(Stream outputFileStream, List checksumRelocations) { MemoryStream originalOutputStream = new(); diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/OutputInfoBuilder.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/OutputInfoBuilder.cs index e0371071d50a6a..f99e03b7ebdad0 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/OutputInfoBuilder.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/OutputInfoBuilder.cs @@ -9,14 +9,13 @@ using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; - +using ILCompiler.DependencyAnalysis; +using ILCompiler.DependencyAnalysisFramework; +using ILCompiler.Diagnostics; using Internal.JitInterface; using Internal.TypeSystem; using Internal.TypeSystem.Ecma; -using ILCompiler.DependencyAnalysis; -using ILCompiler.Diagnostics; - namespace ILCompiler.ObjectWriter { /// From 43a6c0d0c41ce6d87d86736025a0c49a57f3b595 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 6 Oct 2025 14:55:46 -0700 Subject: [PATCH 49/71] Provide large-enough spans for all R2R-used relocation types. --- .../Common/Compiler/DependencyAnalysis/Relocation.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/Relocation.cs b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/Relocation.cs index 152da671c00971..7cff8eef4b5ecb 100644 --- a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/Relocation.cs +++ b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/Relocation.cs @@ -573,6 +573,16 @@ public static int GetSize(RelocType relocType) 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(), }; } From 60a8f06b9588ef7e580af60c702f7c92e03af735 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 6 Oct 2025 15:51:39 -0700 Subject: [PATCH 50/71] Add support for all R2R arch targets to PEObjectWriter/CoffObjectWriter --- .../tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs index 61b29f6a151e3e..80347d3b695364 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs @@ -71,6 +71,9 @@ public CoffObjectWriter(NodeFactory factory, ObjectWritingOptions options, Outpu TargetArchitecture.X86 => Machine.I386, TargetArchitecture.X64 => Machine.Amd64, TargetArchitecture.ARM64 => Machine.Arm64, + TargetArchitecture.ARM => Machine.Arm, + TargetArchitecture.LoongArch64 => Machine.LoongArch64, + TargetArchitecture.RiscV64 => Machine.RiscV64, _ => throw new NotSupportedException("Unsupported architecture") }; } From 2a6187eee27c13ef57dff66222612db52101b9a7 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 6 Oct 2025 17:00:48 -0700 Subject: [PATCH 51/71] Change the ARM64 import thunks to only emit aligned relocs. --- .../Target_ARM64/ARM64Emitter.cs | 15 +++++- .../ReadyToRun/Target_ARM64/ImportThunk.cs | 50 ++++++++++++++----- 2 files changed, 51 insertions(+), 14 deletions(-) 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/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..00d7d6be1dfd9a 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,19 +11,43 @@ 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 { + int ISymbolNode.Offset + { + get + { + // 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. + if (_thunkKind == Kind.Eager) + return base.Offset; + return base.Offset + 8; + } + } + protected override void EmitCode(NodeFactory factory, ref ARM64Emitter instructionEncoder, bool relocsOnly) { + if (_thunkKind == Kind.Eager) + { + instructionEncoder.EmitJMP(_helperCell); + return; + } + + instructionEncoder.Builder.RequireInitialPointerAlignment(); + Debug.Assert(instructionEncoder.Builder.CountBytes == 0); switch (_thunkKind) { - case Kind.Eager: - break; - case Kind.DelayLoadHelper: case Kind.DelayLoadHelperWithExistingIndirectionCell: case Kind.VirtualStubDispatch: + + instructionEncoder.Builder.EmitReloc(factory.ModuleImport, RelocType.IMAGE_REL_BASED_DIR64); + + Debug.Assert(instructionEncoder.Builder.CountBytes == ((ISymbolNode)this).Offset); + // x11 contains indirection cell // Do nothing x11 contains our first param @@ -35,17 +59,21 @@ protected override void EmitCode(NodeFactory factory, ref ARM64Emitter instructi } // 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: + instructionEncoder.Builder.EmitReloc(factory.ModuleImport, RelocType.IMAGE_REL_BASED_DIR64); + + Debug.Assert(instructionEncoder.Builder.CountBytes == ((ISymbolNode)this).Offset); + // 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 +85,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); } } } From 80b10828836ecc965cdb965dc7faf14beca0a8ac Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 6 Oct 2025 22:23:40 -0700 Subject: [PATCH 52/71] Fix ImportThunk offset handling for non-arm64 --- .../ReadyToRun/ImportThunk.cs | 19 +++++++++++++++++++ .../ReadyToRun/Target_ARM64/ImportThunk.cs | 14 -------------- 2 files changed, 19 insertions(+), 14 deletions(-) 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..5ce93c069470dc 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,6 +62,23 @@ public ImportThunk(NodeFactory factory, ReadyToRunHelper helperId, ImportSection { _thunkKind = Kind.Eager; } + + if (_thunkKind != Kind.Eager && factory.Target.Architecture == Internal.TypeSystem.TargetArchitecture.ARM64) + { + // 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 + { + get + { + return base.Offset + _symbolOffset; + } } public override void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb) 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 00d7d6be1dfd9a..e0ed2e8458f14b 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 @@ -13,20 +13,6 @@ namespace ILCompiler.DependencyAnalysis.ReadyToRun /// public partial class ImportThunk : ISymbolNode { - int ISymbolNode.Offset - { - get - { - // 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. - if (_thunkKind == Kind.Eager) - return base.Offset; - return base.Offset + 8; - } - } - protected override void EmitCode(NodeFactory factory, ref ARM64Emitter instructionEncoder, bool relocsOnly) { if (_thunkKind == Kind.Eager) From 8b2825aa7e3ba3355d2d0a70bee2769a08735a54 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 7 Oct 2025 08:58:46 -0700 Subject: [PATCH 53/71] Always put CLR metadata into the cormeta section (that's how the PE object writer identifies it). --- .../DependencyAnalysis/ReadyToRun/CopiedCorHeaderNode.cs | 4 +--- .../DependencyAnalysis/ReadyToRun/CopiedFieldRvaNode.cs | 4 +--- .../ReadyToRun/CopiedManagedResourcesNode.cs | 4 +--- .../DependencyAnalysis/ReadyToRun/CopiedMetadataBlobNode.cs | 4 +--- .../DependencyAnalysis/ReadyToRun/CopiedMethodILNode.cs | 4 +--- .../ReadyToRun/CopiedStrongNameSignatureNode.cs | 4 +--- 6 files changed, 6 insertions(+), 18 deletions(-) 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 3ec43b0010fea8..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 @@ -24,9 +24,7 @@ public CopiedCorHeaderNode(EcmaModule sourceModule) public override ObjectNodeSection GetSection(NodeFactory factory) { - if (factory.Target.IsWindows) - return ObjectNodeSection.CorMetaSection; - return ObjectNodeSection.TextSection; + 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 de89d799abafe0..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 @@ -26,9 +26,7 @@ public CopiedFieldRvaNode(EcmaModule module, int rva) public override ObjectNodeSection GetSection(NodeFactory factory) { - if (factory.Target.IsWindows) - return ObjectNodeSection.CorMetaSection; - return ObjectNodeSection.TextSection; + 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 1f5dbc3970798d..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 @@ -20,9 +20,7 @@ public CopiedManagedResourcesNode(EcmaModule module) public override ObjectNodeSection GetSection(NodeFactory factory) { - if (factory.Target.IsWindows) - return ObjectNodeSection.CorMetaSection; - return ObjectNodeSection.TextSection; + 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 fd780e768c72a8..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 @@ -27,9 +27,7 @@ public CopiedMetadataBlobNode(EcmaModule sourceModule) public override ObjectNodeSection GetSection(NodeFactory factory) { - if (factory.Target.IsWindows) - return ObjectNodeSection.CorMetaSection; - return ObjectNodeSection.TextSection; + 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 54a395555b6d59..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 @@ -24,9 +24,7 @@ public CopiedMethodILNode(EcmaMethod method) public override ObjectNodeSection GetSection(NodeFactory factory) { - if (factory.Target.IsWindows) - return ObjectNodeSection.CorMetaSection; - return ObjectNodeSection.TextSection; + 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 0f69f821876289..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 @@ -23,9 +23,7 @@ public CopiedStrongNameSignatureNode(EcmaModule module) public override ObjectNodeSection GetSection(NodeFactory factory) { - if (factory.Target.IsWindows) - return ObjectNodeSection.CorMetaSection; - return ObjectNodeSection.TextSection; + return ObjectNodeSection.CorMetaSection; } public override bool IsShareable => false; From 6aabbaf7791dc67ba6b9567e2259cd88e537dd76 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 7 Oct 2025 09:56:39 -0700 Subject: [PATCH 54/71] Remove OriginalName (it's not used since we got rid of the grouped symbol support in PEObjectWriter) --- .../tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs index 80347d3b695364..a5454eb7a1caa0 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs @@ -46,7 +46,7 @@ namespace ILCompiler.ObjectWriter /// internal partial class CoffObjectWriter : ObjectWriter { - protected sealed record SectionDefinition(CoffSectionHeader Header, Stream Stream, List Relocations, string ComdatName, string SymbolName, string OriginalName); + protected sealed record SectionDefinition(CoffSectionHeader Header, Stream Stream, List Relocations, string ComdatName, string SymbolName); protected readonly Machine _machine; protected readonly List _sections = new(); @@ -154,8 +154,7 @@ private protected override void CreateSection(ObjectNodeSection section, string } } - string originalName = sectionHeader.Name; - _sections.Add(new SectionDefinition(sectionHeader, sectionStream, new List(), comdatName, symbolName, originalName)); + _sections.Add(new SectionDefinition(sectionHeader, sectionStream, new List(), comdatName, symbolName)); } protected internal override void UpdateSectionAlignment(int sectionIndex, int alignment) From aca38444fd064d5fe81cbcab21c1f8537dbae545 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 7 Oct 2025 13:20:56 -0700 Subject: [PATCH 55/71] COFF section indexes are 1-based. --- .../tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs index a5454eb7a1caa0..e7c78f1ba4c417 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs @@ -117,7 +117,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 definingSectionIndex = isPrimary ? (uint)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 { @@ -135,7 +136,7 @@ private protected override void CreateSection(ObjectNodeSection section, string { Name = sectionHeader.Name, Value = 0, - SectionIndex = (uint)sectionIndex, + SectionIndex = coffSectionIndex, StorageClass = CoffSymbolClass.IMAGE_SYM_CLASS_STATIC, NumberOfAuxiliaryRecords = 1, }); @@ -148,7 +149,7 @@ private protected override void CreateSection(ObjectNodeSection section, string { Name = symbolName, Value = 0, - SectionIndex = (uint)sectionIndex, + SectionIndex = coffSectionIndex, StorageClass = isPrimary ? CoffSymbolClass.IMAGE_SYM_CLASS_EXTERNAL : CoffSymbolClass.IMAGE_SYM_CLASS_STATIC, }); } From 02498511978b434ee22ff51edbbc406396b86a59 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 7 Oct 2025 14:24:37 -0700 Subject: [PATCH 56/71] Correctly account for custom PE alignment and add the non-Windows filepos/rva special case. --- .../Compiler/ObjectWriter/CoffObjectWriter.cs | 7 +++ .../Compiler/ObjectWriter/PEObjectWriter.cs | 53 +++++++++++++++++-- 2 files changed, 57 insertions(+), 3 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs index e7c78f1ba4c417..68a17eb5883b4c 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs @@ -172,6 +172,13 @@ protected internal override void UpdateSectionAlignment(int sectionIndex, int al } } + protected uint GetSectionAlignment(CoffSectionHeader header) + { + int currentAlignmentBits = (int)(header.SectionCharacteristics & SectionCharacteristics.AlignMask); + uint alignment = (uint)(1 << ((currentAlignmentBits >> 20) - 1)); + return alignment; + } + protected internal override unsafe void EmitRelocation( int sectionIndex, long offset, diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs index 128e4fcc73b6a7..822aebf82ee131 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs @@ -24,6 +24,15 @@ namespace ILCompiler.ObjectWriter /// 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. + /// + const int RVABitsToMatchFilePos = 16; internal const int DosHeaderSize = 0x80; private const int NoSectionIndex = -1; @@ -384,6 +393,12 @@ private protected override void EmitSectionsAndLayout() sectionAlignment = fileAlignment; } + if (_requestedSectionAlignment != 0) + { + fileAlignment = (uint)_requestedSectionAlignment; + sectionAlignment = (uint)_requestedSectionAlignment; + } + _peFileAlignment = fileAlignment; _peSectionAlignment = sectionAlignment; LayoutSections(recordFinalLayout: false, out _, out _, out _, out _, out _); @@ -423,11 +438,15 @@ private void LayoutSections(bool recordFinalLayout, out ushort numberOfSections, sizeOfCode = 0; sizeOfInitializedData = 0; + bool firstSection = true; foreach (SectionDefinition s in _sections) { CoffSectionHeader h = s.Header; h.SizeOfRawData = (uint)s.Stream.Length; - uint rawAligned = h.SectionCharacteristics.HasFlag(SectionCharacteristics.ContainsUninitializedData) ? 0u : (uint)AlignmentHelper.AlignUp((int)h.SizeOfRawData, (int)_peFileAlignment); + 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) { @@ -440,9 +459,36 @@ private void LayoutSections(bool recordFinalLayout, out ushort numberOfSections, 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 = Math.Max(h.VirtualSize, h.SizeOfRawData); - virtualAddress += (uint)AlignmentHelper.AlignUp((int)h.VirtualSize, (int)_peSectionAlignment); + h.VirtualSize = virtualSize; + + virtualAddress += virtualSize; if (h.SectionCharacteristics.HasFlag(SectionCharacteristics.ContainsCode)) sizeOfCode += h.SizeOfRawData; @@ -459,6 +505,7 @@ private void LayoutSections(bool recordFinalLayout, out ushort numberOfSections, } sizeOfImage = (uint)AlignmentHelper.AlignUp((int)virtualAddress, (int)_peSectionAlignment); + firstSection = false; } private protected override unsafe void EmitRelocations(int sectionIndex, List relocationList) From 564de709a0b2c43a25e1816a7ec9f986e94d9274 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 7 Oct 2025 14:30:54 -0700 Subject: [PATCH 57/71] Handle section with no alignment bit set --- .../Common/Compiler/ObjectWriter/CoffObjectWriter.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs index 68a17eb5883b4c..263c698f5d0214 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs @@ -174,8 +174,12 @@ protected internal override void UpdateSectionAlignment(int sectionIndex, int al protected uint GetSectionAlignment(CoffSectionHeader header) { - int currentAlignmentBits = (int)(header.SectionCharacteristics & SectionCharacteristics.AlignMask); - uint alignment = (uint)(1 << ((currentAlignmentBits >> 20) - 1)); + SectionCharacteristics alignmentFlag = (header.SectionCharacteristics & SectionCharacteristics.AlignMask); + if (alignmentFlag == 0) + { + return 1; + } + uint alignment = (uint)(1 << (((int)alignmentFlag >> 20) - 1)); return alignment; } From 178c2a6c008bf59f418de4fc5651fcc5c3553945 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 7 Oct 2025 15:19:21 -0700 Subject: [PATCH 58/71] Fix ILC build --- .../tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs | 2 +- .../tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs index 263c698f5d0214..c78898d585bac2 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs @@ -172,7 +172,7 @@ protected internal override void UpdateSectionAlignment(int sectionIndex, int al } } - protected uint GetSectionAlignment(CoffSectionHeader header) + protected static uint GetSectionAlignment(CoffSectionHeader header) { SectionCharacteristics alignmentFlag = (header.SectionCharacteristics & SectionCharacteristics.AlignMask); if (alignmentFlag == 0) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs index 822aebf82ee131..505d9f52bbef1f 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs @@ -32,7 +32,7 @@ internal sealed class PEObjectWriter : CoffObjectWriter /// (the number of bits corresponds to the page size) to be identical to the file offset, /// otherwise memory mapping of the file fails. /// - const int RVABitsToMatchFilePos = 16; + private const int RVABitsToMatchFilePos = 16; internal const int DosHeaderSize = 0x80; private const int NoSectionIndex = -1; @@ -502,10 +502,10 @@ private void LayoutSections(bool recordFinalLayout, out ushort numberOfSections, // 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); - firstSection = false; } private protected override unsafe void EmitRelocations(int sectionIndex, List relocationList) From f9d00336f61e957cc6f9462b4069e998fe421d86 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 8 Oct 2025 11:41:26 -0700 Subject: [PATCH 59/71] Align RiscV and LoongArch64 import thunk relocs --- .../Target_LoongArch64/LoongArch64Emitter.cs | 14 +++++ .../Target_RiscV64/RiscV64Emitter.cs | 13 +++++ .../ReadyToRun/ImportThunk.cs | 5 +- .../ReadyToRun/Target_ARM64/ImportThunk.cs | 28 +++++----- .../Target_LoongArch64/ImportThunk.cs | 44 ++++++++++------ .../ReadyToRun/Target_RiscV64/ImportThunk.cs | 51 +++++++++++++------ 6 files changed, 110 insertions(+), 45 deletions(-) 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.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ImportThunk.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ImportThunk.cs index 5ce93c069470dc..63c3310bcc37be 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 @@ -63,7 +63,10 @@ public ImportThunk(NodeFactory factory, ReadyToRunHelper helperId, ImportSection _thunkKind = Kind.Eager; } - if (_thunkKind != Kind.Eager && factory.Target.Architecture == Internal.TypeSystem.TargetArchitecture.ARM64) + 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. 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 e0ed2e8458f14b..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 @@ -24,25 +24,30 @@ protected override void EmitCode(NodeFactory factory, ref ARM64Emitter instructi 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) { case Kind.DelayLoadHelper: case Kind.DelayLoadHelperWithExistingIndirectionCell: case Kind.VirtualStubDispatch: - instructionEncoder.Builder.EmitReloc(factory.ModuleImport, RelocType.IMAGE_REL_BASED_DIR64); - - Debug.Assert(instructionEncoder.Builder.CountBytes == ((ISymbolNode)this).Offset); - // 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-0xc] @@ -53,9 +58,6 @@ protected override void EmitCode(NodeFactory factory, ref ARM64Emitter instructi break; case Kind.Lazy: - instructionEncoder.Builder.EmitReloc(factory.ModuleImport, RelocType.IMAGE_REL_BASED_DIR64); - - Debug.Assert(instructionEncoder.Builder.CountBytes == ((ISymbolNode)this).Offset); // Move Module* -> x1 // ldr x1, [PC-0x8] 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); } } } From 9646f566972f09dc2116ebbac12707cdfa68ac5b Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 8 Oct 2025 17:02:49 -0700 Subject: [PATCH 60/71] Fix machine kind for ARM to match R2R expectation --- .../tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs index c78898d585bac2..484d672fb646a3 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs @@ -71,7 +71,7 @@ public CoffObjectWriter(NodeFactory factory, ObjectWritingOptions options, Outpu TargetArchitecture.X86 => Machine.I386, TargetArchitecture.X64 => Machine.Amd64, TargetArchitecture.ARM64 => Machine.Arm64, - TargetArchitecture.ARM => Machine.Arm, + TargetArchitecture.ARM => Machine.ArmThumb2, TargetArchitecture.LoongArch64 => Machine.LoongArch64, TargetArchitecture.RiscV64 => Machine.RiscV64, _ => throw new NotSupportedException("Unsupported architecture") From d9c623c3366f4c4328c8aeea29db801a5af70f97 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 8 Oct 2025 17:07:23 -0700 Subject: [PATCH 61/71] Correctly use PE32+ for riscv and loongarch64 --- .../tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs index 505d9f52bbef1f..4a23205fca2dbf 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs @@ -148,9 +148,9 @@ private struct PEOptionalHeader public uint LoaderFlags { get; set; } public uint NumberOfRvaAndSizes { get; set; } - public PEOptionalHeader(Machine machine) + public PEOptionalHeader(bool isPe32Plus) { - IsPE32Plus = machine is Machine.Amd64 or Machine.Arm64; + IsPE32Plus = isPe32Plus; // Defaults taken from PETargetExtensions (PEHeaderConstants / PE32/PE64 constants) MajorLinkerVersion = PEHeaderConstants.MajorLinkerVersion; @@ -699,7 +699,7 @@ private protected override void EmitObjectFile(Stream outputFileStream) coffHeader.Write(outputFileStream); - var peOptional = new PEOptionalHeader(_machine) + var peOptional = new PEOptionalHeader(isPE32Plus) { SizeOfCode = sizeOfCode, SizeOfInitializedData = sizeOfInitializedData, From 5ab5fc3698ee1be54dac1b0459aae9feccc5ac70 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 9 Oct 2025 11:15:59 -0700 Subject: [PATCH 62/71] Add handling for multiple thumb bits in PEObjectWriter and unify thumb bit handling between how R2R nodes did it (manually per reloc with an abstraction) and how ObjectWriter did it (automatically depending on target node, no abstraction). --- .../Compiler/ObjectWriter/ObjectWriter.cs | 14 ++++---- .../Compiler/ObjectWriter/PEObjectWriter.cs | 34 ++++++++++++++++++- .../ReadyToRun/DelayLoadHelperImport.cs | 2 +- .../ReadyToRun/MethodGCInfoNode.cs | 2 +- .../ReadyToRun/RuntimeFunctionsTableNode.cs | 2 +- 5 files changed, 43 insertions(+), 11 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs index 4c4e4c3234be52..c8f0f455c0c931 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs @@ -157,8 +157,8 @@ 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. - 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; + long maskCodeDeltaOut = relocType is IMAGE_REL_BASED_THUMB_BRANCH24 or IMAGE_REL_BASED_THUMB_MOV32_PCREL ? _nodeFactory.Target.CodeDelta : 0; + long maskCodeDeltaIn = relocType is IMAGE_REL_BASED_THUMB_MOV32_PCREL ? _nodeFactory.Target.CodeDelta : 0; long adjustedAddend = addend; adjustedAddend -= relocType switch @@ -169,9 +169,9 @@ or IMAGE_REL_BASED_THUMB_BRANCH24 or IMAGE_REL_BASED_THUMB_MOV32_PCREL && _ => 0 }; - adjustedAddend += definedSymbol.Value & ~maskThumbBitOut; + adjustedAddend += definedSymbol.Value & ~maskCodeDeltaOut; adjustedAddend += Relocation.ReadValue(relocType, (void*)pData); - adjustedAddend |= definedSymbol.Value & maskThumbBitIn; + adjustedAddend |= definedSymbol.Value & maskCodeDeltaIn; adjustedAddend -= offset; if (relocType is IMAGE_REL_BASED_THUMB_BRANCH24 && !Relocation.FitsInThumb2BlRel24((int)adjustedAddend)) @@ -383,13 +383,13 @@ public void EmitObject(Stream outputFileStream, IReadOnlyCollection EncodeDataCore(NodeFactory factory) { bool isFilterFunclet = (frameInfo.Flags & FrameInfoFlags.Filter) != 0; ISymbolNode personalityRoutine = (isFilterFunclet ? factory.FilterFuncletPersonalityRoutine : factory.PersonalityRoutine); - yield return new GCInfoComponent(personalityRoutine, factory.Target.CodeDelta); + yield return new GCInfoComponent(personalityRoutine, 0); } if (frameInfoIndex == 0 && _methodNode.GCInfo != null) 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 66aaa88514b476..9caadbe04b2bc6 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 @@ -124,7 +124,7 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) symbol = method; } - runtimeFunctionsBuilder.EmitReloc(symbol, RelocType.IMAGE_REL_BASED_ADDR32NB, delta: frameInfo.StartOffset + _nodeFactory.Target.CodeDelta); + runtimeFunctionsBuilder.EmitReloc(symbol, RelocType.IMAGE_REL_BASED_ADDR32NB, delta: frameInfo.StartOffset); if (!relocsOnly && _nodeFactory.Target.Architecture == TargetArchitecture.X64) { // On Amd64, the 2nd word contains the EndOffset of the runtime function From e34d7d0708aa2c95f53789f3f47f1cd64ccf2e37 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 9 Oct 2025 20:45:58 +0000 Subject: [PATCH 63/71] Only add the thumb bit when it was there initially --- .../Compiler/DependencyAnalysis/Relocation.cs | 4 +++- .../Compiler/ObjectWriter/PEObjectWriter.cs | 19 ++++++++++--------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/Relocation.cs b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/Relocation.cs index 7cff8eef4b5ecb..c7863213522e2d 100644 --- a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/Relocation.cs +++ b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/Relocation.cs @@ -579,10 +579,12 @@ public static int GetSize(RelocType relocType) 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, + // This relocation edits values across 8 bytes, + // but it semantically represents a 12-byte instruction sequence. + RelocType.IMAGE_REL_BASED_THUMB_MOV32_PCREL => 12, _ => throw new NotSupportedException(), }; } diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs index 1ce12658dca566..01f568890edb46 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs @@ -840,13 +840,14 @@ private unsafe void ResolveRelocations(int sectionIndex, List Date: Thu, 9 Oct 2025 21:54:49 +0000 Subject: [PATCH 64/71] Add imageBase for non-pcrel reloc --- .../tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs index 01f568890edb46..e9e02b48e719be 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs @@ -844,7 +844,7 @@ private unsafe void ResolveRelocations(int sectionIndex, List Date: Thu, 9 Oct 2025 17:48:15 -0700 Subject: [PATCH 65/71] Set up resource directory so resources show up in the structure --- .../tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs index e9e02b48e719be..aad056f25d64a2 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/PEObjectWriter.cs @@ -76,6 +76,7 @@ internal sealed class PEObjectWriter : CoffObjectWriter 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(); @@ -359,6 +360,7 @@ private protected override void EmitSectionsAndLayout() _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 @@ -734,6 +736,10 @@ private protected override void EmitObjectFile(Stream outputFileStream) // 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); From 32ce05eb08ab5f2d3547b65b82ad30dc741cac06 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 13 Oct 2025 09:20:17 -0700 Subject: [PATCH 66/71] We should determine section by format (not exposed yet) instead of target platform --- .../DependencyAnalysis/ReadyToRun/DebugDirectoryNode.cs | 4 +--- .../ReadyToRun/RuntimeFunctionsGCInfoNode.cs | 4 +--- .../DependencyAnalysis/ReadyToRun/Win32ResourcesNode.cs | 4 +--- 3 files changed, 3 insertions(+), 9 deletions(-) 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 74ee43a2850ddf..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 @@ -54,9 +54,7 @@ public DebugDirectoryNode(EcmaModule sourceModule, string outputFileName, bool s public override ObjectNodeSection GetSection(NodeFactory factory) { - if (factory.Target.IsWindows) - return ObjectNodeSection.DebugDirectorySection; - return ObjectNodeSection.TextSection; + return ObjectNodeSection.DebugDirectorySection; } public override bool IsShareable => false; 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 832d899c6e1655..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 @@ -18,9 +18,7 @@ public RuntimeFunctionsGCInfoNode() public override ObjectNodeSection GetSection(NodeFactory factory) { - if (factory.Target.IsWindows) - return ObjectNodeSection.XDataSection; - return ObjectNodeSection.ReadOnlyDataSection; + return ObjectNodeSection.XDataSection; } public override bool StaticDependenciesAreComputed => true; 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 febf720f88f0c4..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 @@ -21,9 +21,7 @@ public Win32ResourcesNode(ResourceData resourceData) public override ObjectNodeSection GetSection(NodeFactory factory) { - if (factory.Target.IsWindows) - return ObjectNodeSection.Win32ResourcesSection; - return ObjectNodeSection.ReadOnlyDataSection; + return ObjectNodeSection.Win32ResourcesSection; } public override bool IsShareable => false; From fc7a2e8983187f067127419f581374b5a5484879 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 13 Oct 2025 16:31:18 +0000 Subject: [PATCH 67/71] Go back to original R2R handling for the thumb bit for R2R. We should move NAOT to the same plan eventually. --- .../Compiler/DependencyAnalysis/Relocation.cs | 4 +- .../Compiler/ObjectWriter/ObjectWriter.cs | 23 ++++++++---- .../Compiler/ObjectWriter/PEObjectWriter.cs | 37 +------------------ .../ReadyToRun/DelayLoadHelperImport.cs | 2 +- .../ReadyToRun/MethodGCInfoNode.cs | 2 +- .../ReadyToRun/RuntimeFunctionsTableNode.cs | 2 +- 6 files changed, 22 insertions(+), 48 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/Relocation.cs b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/Relocation.cs index c7863213522e2d..7cff8eef4b5ecb 100644 --- a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/Relocation.cs +++ b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/Relocation.cs @@ -579,12 +579,10 @@ public static int GetSize(RelocType relocType) 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, - // This relocation edits values across 8 bytes, - // but it semantically represents a 12-byte instruction sequence. - RelocType.IMAGE_REL_BASED_THUMB_MOV32_PCREL => 12, _ => throw new NotSupportedException(), }; } diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs index c8f0f455c0c931..593272ccd72ab2 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs @@ -157,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. - long maskCodeDeltaOut = relocType is IMAGE_REL_BASED_THUMB_BRANCH24 or IMAGE_REL_BASED_THUMB_MOV32_PCREL ? _nodeFactory.Target.CodeDelta : 0; - long maskCodeDeltaIn = relocType is IMAGE_REL_BASED_THUMB_MOV32_PCREL ? _nodeFactory.Target.CodeDelta : 0; + // + // 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 @@ -169,9 +176,9 @@ or IMAGE_REL_BASED_THUMB_BRANCH24 or IMAGE_REL_BASED_THUMB_MOV32_PCREL && _ => 0 }; - adjustedAddend += definedSymbol.Value & ~maskCodeDeltaOut; + adjustedAddend += definedSymbol.Value & ~maskThumbBitOut; adjustedAddend += Relocation.ReadValue(relocType, (void*)pData); - adjustedAddend |= definedSymbol.Value & maskCodeDeltaIn; + adjustedAddend |= definedSymbol.Value & maskThumbBitIn; adjustedAddend -= offset; if (relocType is IMAGE_REL_BASED_THUMB_BRANCH24 && !Relocation.FitsInThumb2BlRel24((int)adjustedAddend)) @@ -380,16 +387,18 @@ public void EmitObject(Stream outputFileStream, IReadOnlyCollection EncodeDataCore(NodeFactory factory) { bool isFilterFunclet = (frameInfo.Flags & FrameInfoFlags.Filter) != 0; ISymbolNode personalityRoutine = (isFilterFunclet ? factory.FilterFuncletPersonalityRoutine : factory.PersonalityRoutine); - yield return new GCInfoComponent(personalityRoutine, 0); + yield return new GCInfoComponent(personalityRoutine, factory.Target.CodeDelta); } if (frameInfoIndex == 0 && _methodNode.GCInfo != null) 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 9caadbe04b2bc6..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 @@ -124,7 +124,7 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) symbol = method; } - runtimeFunctionsBuilder.EmitReloc(symbol, RelocType.IMAGE_REL_BASED_ADDR32NB, delta: frameInfo.StartOffset); + runtimeFunctionsBuilder.EmitReloc(symbol, RelocType.IMAGE_REL_BASED_ADDR32NB, delta: frameInfo.StartOffset + _nodeFactory.Target.CodeDelta); if (!relocsOnly && _nodeFactory.Target.Architecture == TargetArchitecture.X64) { // On Amd64, the 2nd word contains the EndOffset of the runtime function From c28a7a68baa757f1926bddf26f7fbfcf8e6f7261 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 13 Oct 2025 13:27:57 -0700 Subject: [PATCH 68/71] Remove LegacyPE builder now that we're passing on Pri0 everywhere --- .../CodeGen/ReadyToRunContainerFormat.cs | 1 - .../CodeGen/ReadyToRunObjectWriter.cs | 300 +----- .../ReadyToRunCodegenCompilationBuilder.cs | 2 +- .../ILCompiler.ReadyToRun.csproj | 3 - .../ObjectWriter/R2RPEBuilder.cs | 699 ------------- .../ObjectWriter/RelocationHelper.cs | 549 ---------- .../ObjectWriter/SectionBuilder.cs | 971 ------------------ .../aot/crossgen2/Crossgen2RootCommand.cs | 3 +- 8 files changed, 3 insertions(+), 2525 deletions(-) delete mode 100644 src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/R2RPEBuilder.cs delete mode 100644 src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/RelocationHelper.cs delete mode 100644 src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/SectionBuilder.cs diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunContainerFormat.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunContainerFormat.cs index 491022ddcc5b94..403fc97e4c1cd6 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunContainerFormat.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunContainerFormat.cs @@ -9,7 +9,6 @@ namespace ILCompiler.DependencyAnalysis { public enum ReadyToRunContainerFormat { - LegacyPE, PE } } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs index 19c018580869b4..1b475b1cff7d62 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs @@ -117,24 +117,6 @@ 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, @@ -301,279 +283,6 @@ public void EmitReadyToRunObjects(ReadyToRunContainerFormat format, Logger logge } } - public void EmitPortableExecutable() - { - bool succeeded = false; - - try - { - Stopwatch stopwatch = new Stopwatch(); - stopwatch.Start(); - - 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) - { - getRuntimeFunctionsTable = GetRuntimeFunctionsTable; - } - R2RPEBuilder r2rPeBuilder = new R2RPEBuilder( - _nodeFactory.Target, - headerBuilder, - r2rHeaderExportSymbol, - Path.GetFileName(_objectFilePath), - getRuntimeFunctionsTable, - _customPESectionAlignment, - peIdProvider); - - NativeDebugDirectoryEntryNode nativeDebugDirectoryEntryNode = null; - PerfMapDebugDirectoryEntryNode perfMapDebugDirectoryEntryNode = null; - ObjectNode lastWrittenObjectNode = null; - - int nodeIndex = -1; - foreach (var depNode in _nodes) - { - ObjectNode node = depNode as ObjectNode; - - ++nodeIndex; - - if (node == null) - { - continue; - } - - if (node.ShouldSkipEmittingObjectNode(_nodeFactory)) - continue; - - ObjectData nodeContents = node.GetData(_nodeFactory); - - 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; - } - - string name = GetDependencyNodeName(depNode); - - EmitObjectData(r2rPeBuilder, nodeContents, nodeIndex, name, node.GetSection(_nodeFactory)); - lastWrittenObjectNode = node; - } - - if (_outputInfoBuilder != null) - { - foreach (MethodWithGCInfo methodNode in _nodeFactory.EnumerateCompiledMethods()) - _outputInfoBuilder.AddMethod(methodNode, methodNode); - } - - r2rPeBuilder.SetCorHeader(_nodeFactory.CopiedCorHeaderNode, _nodeFactory.CopiedCorHeaderNode.Size); - r2rPeBuilder.SetDebugDirectory(_nodeFactory.DebugDirectoryNode, _nodeFactory.DebugDirectoryNode.Size); - - ISymbolNode firstImportThunk = _nodeFactory.DelayLoadMethodCallThunks.StartNode(_nodeFactory); - if (firstImportThunk is not null) - { - r2rPeBuilder.AddSymbolForRange(_nodeFactory.DelayLoadMethodCallThunks, firstImportThunk, _nodeFactory.DelayLoadMethodCallThunks.EndNode(_nodeFactory)); - } - - - if (_nodeFactory.Win32ResourcesNode != null) - { - Debug.Assert(_nodeFactory.Win32ResourcesNode.Size != 0); - r2rPeBuilder.SetWin32Resources(_nodeFactory.Win32ResourcesNode, _nodeFactory.Win32ResourcesNode.Size); - } - - if (_outputInfoBuilder != null) - { - foreach (string inputFile in _inputFiles) - { - _outputInfoBuilder.AddInputModule(_nodeFactory.TypeSystemContext.GetModuleFromPath(inputFile)); - } - } - - using (var peStream = File.Create(_objectFilePath)) - { - r2rPeBuilder.Write(peStream, timeDateStamp); - - if (_mapFileBuilder != null) - { - _mapFileBuilder.SetFileSize(peStream.Length); - } - - if (nativeDebugDirectoryEntryNode is not null) - { - 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 (_outputInfoBuilder != 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); - } - } - - succeeded = true; - } - finally - { - if (!succeeded) - { - // If there was an exception while generating the OBJ file, make sure we don't leave the unfinished - // object file around. - try - { - File.Delete(_objectFilePath); - } - catch - { - } - } - } - } - - /// - /// 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, @@ -611,14 +320,7 @@ public static void EmitObject( callChainProfile, customPESectionAlignment); - if (format == ReadyToRunContainerFormat.LegacyPE) - { - objectWriter.EmitPortableExecutable(); - } - else - { - objectWriter.EmitReadyToRunObjects(format, logger); - } + objectWriter.EmitReadyToRunObjects(format, logger); } } } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilationBuilder.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilationBuilder.cs index 3b57a4e5dad675..17dede9e9adebe 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilationBuilder.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilationBuilder.cs @@ -45,7 +45,7 @@ public sealed class ReadyToRunCodegenCompilationBuilder : CompilationBuilder private NodeFactoryOptimizationFlags _nodeFactoryOptimizationFlags = new NodeFactoryOptimizationFlags(); private int _genericCycleDetectionDepthCutoff = -1; private int _genericCycleDetectionBreadthCutoff = -1; - private ReadyToRunContainerFormat _format = ReadyToRunContainerFormat.LegacyPE; + private ReadyToRunContainerFormat _format = ReadyToRunContainerFormat.PE; private string _jitPath; private string _outputFile; diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj index 251c18451954fa..bf9887611b02fc 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj @@ -303,9 +303,6 @@ - - - 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 040e8cf49dafef..00000000000000 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/R2RPEBuilder.cs +++ /dev/null @@ -1,699 +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, ObjectWriter.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: - case SectionType.Debug: - // 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(ObjectWriter.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 f983d965b69285..00000000000000 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/SectionBuilder.cs +++ /dev/null @@ -1,971 +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, ObjectWriter.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 ObjectWriter.OutputNode(sectionIndex, (ulong)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 ObjectWriter.OutputSymbol(sectionIndex, (ulong)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(ObjectWriter.OutputInfoBuilder outputInfoBuilder) - { - foreach (Section section in _sections) - { - outputInfoBuilder.AddSection(new ObjectWriter.OutputSection(section.Name, (ulong)section.RVAWhenPlaced, (ulong)section.FilePosWhenPlaced, (ulong)section.Content.Count)); - } - } - - /// - /// 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) - { - if (relocation.RelocType == RelocType.IMAGE_REL_FILE_CHECKSUM_CALLBACK) - { - // Checksums are handled manually for PEWriter. - continue; - } - - // 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/crossgen2/Crossgen2RootCommand.cs b/src/coreclr/tools/aot/crossgen2/Crossgen2RootCommand.cs index 44ed36469e2537..a63ad0aaf88c58 100644 --- a/src/coreclr/tools/aot/crossgen2/Crossgen2RootCommand.cs +++ b/src/coreclr/tools/aot/crossgen2/Crossgen2RootCommand.cs @@ -31,7 +31,7 @@ internal class Crossgen2RootCommand : RootCommand public Option OutputFilePath { get; } = new("--out", "-o") { Description = SR.OutputFilePath }; public Option OutputFormat { get; } = - new("--format") { CustomParser = MakeOutputFormat, DefaultValueFactory = MakeOutputFormat, Description = SR.OutputFormat, HelpName = "arg" }; + 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; } = @@ -414,7 +414,6 @@ private static ReadyToRunContainerFormat MakeOutputFormat(ArgumentResult result) return result.Tokens[0].Value.ToLowerInvariant() switch { - "legacype" => ReadyToRunContainerFormat.LegacyPE, "pe" => ReadyToRunContainerFormat.PE, _ => throw new CommandLineException(SR.InvalidFileLayout) }; From 72cbdd5ff570b51f557eb5c57bcee084c7536d55 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 13 Oct 2025 15:24:49 -0700 Subject: [PATCH 69/71] Handle empty symbol ranges. --- .../Compiler/ObjectWriter/ObjectWriter.cs | 21 +++++++++++++------ .../DelayLoadMethodCallThunkNodeRange.cs | 5 +++-- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs index 593272ccd72ab2..91b51c384919a9 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs @@ -484,6 +484,19 @@ public void EmitObject(Stream outputFileStream, IReadOnlyCollection public class DelayLoadMethodCallThunkNodeRange : DependencyNodeCore, ISymbolRangeNode { + private const string NodeName = "DelayLoadMethodCallThunkNodeRange"; private ImportThunk _startNode; private ImportThunk _endNode; @@ -28,11 +29,11 @@ public class DelayLoadMethodCallThunkNodeRange : DependencyNodeCore 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) => "DelayLoadMethodCallThunkNodeRange"; + 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) From 5a7e03b685181e3d1faa9bf21a06402a3d5e874e Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 13 Oct 2025 16:24:13 -0700 Subject: [PATCH 70/71] PR feedback --- .../Common/Compiler/ObjectWriter/CoffObjectWriter.cs | 2 -- .../Compiler/ObjectWriter/CoffObjectWriter.Aot.cs | 2 +- .../ReadyToRun/DebugDirectoryEntryNode.cs | 1 + .../DependencyAnalysis/ReadyToRun/ImportThunk.cs | 8 +------- .../ObjectWriter/ProfileFileBuilder.cs | 9 ++++++--- src/coreclr/tools/aot/crossgen2/Crossgen2RootCommand.cs | 2 +- .../tools/aot/crossgen2/Properties/Resources.resx | 3 +++ 7 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs index 484d672fb646a3..74e999317eec9e 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs @@ -57,8 +57,6 @@ protected sealed record SectionDefinition(CoffSectionHeader Header, Stream Strea private readonly Dictionary _sectionNumberToComdatAuxRecord = new(); private readonly HashSet _referencedMethods = new(); - 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); 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 index b8d353e2e67e07..9126b03f88c918 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CoffObjectWriter.Aot.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CoffObjectWriter.Aot.cs @@ -60,7 +60,7 @@ internal partial class CoffObjectWriter : ObjectWriter private protected override void CreateEhSections() { // Create .pdata - _pdataSectionWriter = GetOrCreateSection(PDataSection); + _pdataSectionWriter = GetOrCreateSection(ObjectNodeSection.PDataSection); } private protected override void EmitUnwindInfo( 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 6e30876dca6df5..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 @@ -244,6 +244,7 @@ private class RSDSChecksumNode : DependencyNodeCore, IChecksumNode 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); } 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 63c3310bcc37be..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 @@ -76,13 +76,7 @@ or Internal.TypeSystem.TargetArchitecture.LoongArch64 } } - int ISymbolNode.Offset - { - get - { - return base.Offset + _symbolOffset; - } - } + int ISymbolNode.Offset => base.Offset + _symbolOffset; public override void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb) { diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/ProfileFileBuilder.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/ProfileFileBuilder.cs index 02ff1ea4d221e0..ce1194efcbae4e 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/ProfileFileBuilder.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/ProfileFileBuilder.cs @@ -170,15 +170,18 @@ private void CalculateCallInfo() 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: (int)callerRVA, + callerRVA: callerRVA32Bit, callee: kvpCalleeCount.Key, calleeNode: calleeNode, - calleeRVA: (int)calleeRVA, + calleeRVA: calleeRVA32Bit, callCount: kvpCalleeCount.Value, - callType: GetCallType(callerNode, (int)callerRVA, calleeNode, (int)calleeRVA))); + callType: GetCallType(callerNode, callerRVA32Bit, calleeNode, calleeRVA32Bit))); } } } diff --git a/src/coreclr/tools/aot/crossgen2/Crossgen2RootCommand.cs b/src/coreclr/tools/aot/crossgen2/Crossgen2RootCommand.cs index a63ad0aaf88c58..dc07e5abc4c43f 100644 --- a/src/coreclr/tools/aot/crossgen2/Crossgen2RootCommand.cs +++ b/src/coreclr/tools/aot/crossgen2/Crossgen2RootCommand.cs @@ -415,7 +415,7 @@ private static ReadyToRunContainerFormat MakeOutputFormat(ArgumentResult result) return result.Tokens[0].Value.ToLowerInvariant() switch { "pe" => ReadyToRunContainerFormat.PE, - _ => throw new CommandLineException(SR.InvalidFileLayout) + _ => throw new CommandLineException(SR.InvalidOutputFormat) }; } diff --git a/src/coreclr/tools/aot/crossgen2/Properties/Resources.resx b/src/coreclr/tools/aot/crossgen2/Properties/Resources.resx index 882c4bec226941..faaa727911aac1 100644 --- a/src/coreclr/tools/aot/crossgen2/Properties/Resources.resx +++ b/src/coreclr/tools/aot/crossgen2/Properties/Resources.resx @@ -429,4 +429,7 @@ Target output format for the ReadyToRun image + + Format must be PE + \ No newline at end of file From 082b41474dee710dbee237d3e515f6e06131b227 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 13 Oct 2025 17:48:43 -0700 Subject: [PATCH 71/71] Fix PDataSection reference --- .../Compiler/ObjectWriter/CoffObjectWriter.Aot.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 9126b03f88c918..78f0dad8ed5284 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CoffObjectWriter.Aot.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/CoffObjectWriter.Aot.cs @@ -89,7 +89,7 @@ private protected override void EmitUnwindInfo( { // Produce an associative COMDAT symbol. xdataSectionWriter = GetOrCreateSection(ObjectNodeSection.XDataSection, currentSymbolName, unwindSymbolName); - pdataSectionWriter = GetOrCreateSection(PDataSection, currentSymbolName, null); + pdataSectionWriter = GetOrCreateSection(ObjectNodeSection.PDataSection, currentSymbolName, null); } else {