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 b652563a9583d3..f010b4fd0934f0 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 @@ -129,13 +129,10 @@ public void Add(ReadyToRunSectionType id, DependencyNodeCore node, public override bool StaticDependenciesAreComputed => true; - public override ObjectNodeSection GetSection(NodeFactory factory) - { - if (factory.Target.IsWindows) - return ObjectNodeSection.ReadOnlyDataSection; - else - return ObjectNodeSection.DataSection; - } + // For R2R, we can put the header in the read-only section on non-Windows as well. Since we emit a PE image + // and do our own mapping, we don't need it to be writeable for the OS loader to handle absolute pointer relocs. + // Our R2R PE images group read-only data into the .text section, so this doesn't result in more work to map. + public override ObjectNodeSection GetSection(NodeFactory factory) => ObjectNodeSection.ReadOnlyDataSection; public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) { diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/R2RPEBuilder.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/R2RPEBuilder.cs index 672df8e84b5da4..7a8aa110e7ff5d 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/R2RPEBuilder.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/R2RPEBuilder.cs @@ -21,7 +21,7 @@ namespace ILCompiler.PEWriter /// metadata and IL and adding new code and data representing the R2R JITted code and /// additional runtime structures (R2R header and tables). /// - public class R2RPEBuilder : PEBuilder + public sealed class R2RPEBuilder : PEBuilder { /// /// Number of low-order RVA bits that must match file position on Linux. @@ -73,7 +73,7 @@ public SectionRVADelta(int startRVA, int endRVA, int deltaRVA) /// Name of the initialized data section. /// public const string SDataSectionName = ".sdata"; - + /// /// Name of the relocation section. /// @@ -94,11 +94,6 @@ public SectionRVADelta(int startRVA, int endRVA, int deltaRVA) /// private TargetDetails _target; - /// - /// Complete list of sections to emit into the output R2R executable. - /// - private ImmutableArray
_sections; - /// /// Callback to retrieve the runtime function table which needs setting to the /// ExceptionTable PE directory entry. @@ -112,28 +107,48 @@ public SectionRVADelta(int startRVA, int endRVA, int deltaRVA) /// private List _sectionRvaDeltas; - /// - /// 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. - /// - private int[] _sectionRVAs; + private class SerializedSectionData + { + /// + /// Name of the section + /// + public string Name; - /// - /// 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. - /// - private int[] _sectionPointerToRawData; + /// + /// 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; + } /// - /// Maximum of virtual and physical size for each section. + /// 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 int[] _sectionRawSizes; + private readonly SerializedSectionData[] _sectionData; /// /// R2R PE section builder & relocator. @@ -206,18 +221,13 @@ public R2RPEBuilder( PEHeaderConstants.SectionAlignment); } - ImmutableArray
.Builder sectionListBuilder = ImmutableArray.CreateBuilder
(); + List sectionData = new List(); foreach (SectionInfo sectionInfo in _sectionBuilder.GetSections()) { - ILCompiler.PEWriter.Section builderSection = _sectionBuilder.FindSection(sectionInfo.SectionName); - Debug.Assert(builderSection != null); - sectionListBuilder.Add(new Section(builderSection.Name, builderSection.Characteristics)); + sectionData.Add(new SerializedSectionData() { Name = sectionInfo.SectionName }); } - _sections = sectionListBuilder.ToImmutableArray(); - _sectionRVAs = new int[_sections.Length]; - _sectionPointerToRawData = new int[_sections.Length]; - _sectionRawSizes = new int[_sections.Length]; + _sectionData = sectionData.ToArray(); } public void SetCorHeader(ISymbolNode symbol, int headerSize) @@ -400,13 +410,17 @@ private void UpdateSectionRVAs(Stream outputStream) 16 * sizeof(long); // directory entries int sectionHeaderOffset = DosHeaderSize + PESignatureSize + COFFHeaderSize + peHeaderSize; - int sectionCount = _sectionRVAs.Length; + 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(_sectionRawSizes[sectionIndex]); + byte[] sizeBytes = BitConverter.GetBytes(section.RawSize); Debug.Assert(sizeBytes.Length == sizeof(int)); // Update VirtualSize @@ -424,7 +438,7 @@ private void UpdateSectionRVAs(Stream outputStream) // Update RVAs { outputStream.Seek(sectionHeaderOffset + SectionHeaderSize * sectionIndex + SectionHeaderRVAOffset, SeekOrigin.Begin); - byte[] rvaBytes = BitConverter.GetBytes(_sectionRVAs[sectionIndex]); + byte[] rvaBytes = BitConverter.GetBytes(section.RVA); Debug.Assert(rvaBytes.Length == sizeof(int)); outputStream.Write(rvaBytes, 0, rvaBytes.Length); } @@ -432,15 +446,25 @@ private void UpdateSectionRVAs(Stream outputStream) // Update pointer to raw data { outputStream.Seek(sectionHeaderOffset + SectionHeaderSize * sectionIndex + SectionHeaderPointerToRawDataOffset, SeekOrigin.Begin); - byte[] rawDataBytesBytes = BitConverter.GetBytes(_sectionPointerToRawData[sectionIndex]); + 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(_sectionRVAs[sectionCount - 1] + _sectionRawSizes[sectionCount - 1], Header.SectionAlignment); + 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); @@ -557,14 +581,21 @@ private int RelocateRVA(int rva) ///
protected override ImmutableArray
CreateSections() { - return _sections; + 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. For sections existent in the source MSIL PE file - /// (.text, optionally .rsrc and .reloc), we first copy the content of the input MSIL PE file - /// and then call the section serialization callback to emit the extra content after the input - /// section content. + /// Output the section with a given name. /// /// Section name /// RVA and file location where the section will be put @@ -574,18 +605,33 @@ protected override BlobBuilder SerializeSection(string name, SectionLocation loc BlobBuilder sectionDataBuilder = null; int sectionStartRva = location.RelativeVirtualAddress; - int outputSectionIndex = _sections.Length - 1; - while (outputSectionIndex >= 0 && _sections[outputSectionIndex].Name != name) + 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 (outputSectionIndex > 0) + if (previousSection is not null) { - sectionStartRva = Math.Max(sectionStartRva, _sectionRVAs[outputSectionIndex - 1] + _sectionRawSizes[outputSectionIndex - 1]); + sectionStartRva = Math.Max(sectionStartRva, previousSection.RVA + previousSection.RawSize); } int newSectionStartRva = AlignmentHelper.AlignUp(sectionStartRva, _customPESectionAlignment); @@ -603,13 +649,13 @@ protected override BlobBuilder SerializeSection(string name, SectionLocation loc if (!_target.IsWindows) { const int RVAAlign = 1 << RVABitsToMatchFilePos; - if (outputSectionIndex > 0) + if (previousSection is not null) { - sectionStartRva = Math.Max(sectionStartRva, _sectionRVAs[outputSectionIndex - 1] + _sectionRawSizes[outputSectionIndex - 1]); + 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. + // 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; @@ -622,36 +668,19 @@ protected override BlobBuilder SerializeSection(string name, SectionLocation loc location = new SectionLocation(sectionStartRva, location.PointerToRawData); } - if (outputSectionIndex >= 0) - { - _sectionRVAs[outputSectionIndex] = sectionStartRva; - _sectionPointerToRawData[outputSectionIndex] = location.PointerToRawData; - } + outputSection.RVA = sectionStartRva; + outputSection.PointerToRawData = location.PointerToRawData; BlobBuilder extraData = _sectionBuilder.SerializeSection(name, location); - if (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); - } - } - - // Make sure the section has at least 1 byte, otherwise the PE emitter goes mad, - // messes up the section map and corrups the output executable. + Debug.Assert(extraData != null); if (sectionDataBuilder == null) { - sectionDataBuilder = new BlobBuilder(); + // See above - there's a bug due to which LinkSuffix to an empty BlobBuilder screws up the blob content. + sectionDataBuilder = extraData; } - - if (sectionDataBuilder.Count == 0) + else { - sectionDataBuilder.WriteByte(0); + sectionDataBuilder.LinkSuffix(extraData); } int sectionRawSize = sectionDataBuilder.Count - injectedPadding; @@ -664,15 +693,13 @@ protected override BlobBuilder SerializeSection(string name, SectionLocation loc sectionRawSize = count; } - if (outputSectionIndex >= 0) - { - _sectionRawSizes[outputSectionIndex] = sectionRawSize; - } + outputSection.RawSize = sectionRawSize; + outputSection.IsSerialized = true; return sectionDataBuilder; } } - + /// /// Simple helper for filling in PE header information. /// diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/SectionBuilder.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/SectionBuilder.cs index e10348e562e1e0..f3ebd11aae868c 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/SectionBuilder.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/SectionBuilder.cs @@ -944,5 +944,22 @@ public void RelocateOutputFile( // 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/vm/peimagelayout.cpp b/src/coreclr/vm/peimagelayout.cpp index 533d3537f090ea..de28670f4df80f 100644 --- a/src/coreclr/vm/peimagelayout.cpp +++ b/src/coreclr/vm/peimagelayout.cpp @@ -106,7 +106,9 @@ PEImageLayout* PEImageLayout::LoadConverted(PEImage* pOwner, bool disableMapping _ASSERTE(!pOwner->IsFile() || !pFlat->HasReadyToRunHeader() || disableMapping); #endif - if ((pFlat->HasReadyToRunHeader() && AllowR2RForImage(pOwner)) + // If the image is R2R with native code (that is, not a component assembly of composite R2R) or has writeable sections, + // we need to actually load/map it into virtual addresses + if ((pFlat->HasReadyToRunHeader() && !pFlat->IsComponentAssembly() && AllowR2RForImage(pOwner)) || pFlat->HasWriteableSections()) { return new ConvertedImageLayout(pFlat, disableMapping); diff --git a/src/coreclr/vm/readytoruninfo.cpp b/src/coreclr/vm/readytoruninfo.cpp index 8ae5bd42a43d0c..53b7db308aa794 100644 --- a/src/coreclr/vm/readytoruninfo.cpp +++ b/src/coreclr/vm/readytoruninfo.cpp @@ -515,12 +515,37 @@ static bool AcquireImage(Module * pModule, PEImageLayout * pLayout, READYTORUN_H static NativeImage *AcquireCompositeImage(Module * pModule, PEImageLayout * pLayout, READYTORUN_HEADER *pHeader) { READYTORUN_SECTION * pSections = (READYTORUN_SECTION*)(pHeader + 1); - LPCUTF8 ownerCompositeExecutableName = NULL; + DWORD virtualAddress = UINT32_MAX; for (DWORD i = 0; i < pHeader->CoreHeader.NumberOfSections; i++) { if (pSections[i].Type == ReadyToRunSectionType::OwnerCompositeExecutable) { - ownerCompositeExecutableName = (LPCUTF8)pLayout->GetBase() + pSections[i].Section.VirtualAddress; + virtualAddress = pSections[i].Section.VirtualAddress; + break; + } + } + + if (virtualAddress == UINT32_MAX) + return NULL; + + LPCUTF8 ownerCompositeExecutableName = NULL; + if (pLayout->IsMapped()) + { + ownerCompositeExecutableName = (LPCUTF8)pLayout->GetBase() + virtualAddress; + } + else + { + // Flat layout - find the data corresponding to the owner composite executable name + int numSections = pLayout->GetNumberOfSections(); + IMAGE_SECTION_HEADER* sectionHeaders = pLayout->FindFirstSection(); + for (int i = 0; i < numSections; i++) + { + IMAGE_SECTION_HEADER& header = sectionHeaders[i]; + if (header.VirtualAddress > virtualAddress || header.VirtualAddress + header.SizeOfRawData < virtualAddress) + continue; + + DWORD offset = virtualAddress - header.VirtualAddress; + ownerCompositeExecutableName = (LPCUTF8)pLayout->GetBase() + header.PointerToRawData + offset; break; } } @@ -585,7 +610,8 @@ PTR_ReadyToRunInfo ReadyToRunInfo::Initialize(Module * pModule, AllocMemTracker } // The file must have been loaded using LoadLibrary - if (!pLayout->IsRelocated()) + bool isComponentAssembly = pLayout->IsComponentAssembly(); + if (!isComponentAssembly && !pLayout->IsRelocated()) { DoLog("Ready to Run disabled - module not loaded for execution"); return NULL; @@ -601,7 +627,7 @@ PTR_ReadyToRunInfo ReadyToRunInfo::Initialize(Module * pModule, AllocMemTracker } NativeImage *nativeImage = NULL; - if (pHeader->CoreHeader.Flags & READYTORUN_FLAG_COMPONENT) + if (isComponentAssembly) { nativeImage = AcquireCompositeImage(pModule, pLayout, pHeader); if (nativeImage == NULL)