From a28d5a7551a5bdf7056aaceb31c668b0d78e3778 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Tue, 10 Sep 2019 16:18:02 -0700 Subject: [PATCH] Resource writing for crossgen2 (dotnet/coreclr#26639) - Refactor the win32 resource reading code to use more natural managed data structures - Build a Win32 resource emitter on top of refactored data structures - Replace resource section copy logic with new node to generate win32 resources Commit migrated from https://github.com/dotnet/coreclr/commit/17e4bdb9dab45240c85815edd6bf656bf36b1e3d --- .../DependencyAnalysis/ObjectDataBuilder.cs | 19 ++ .../CodeGen/ReadyToRunObjectWriter.cs | 6 + .../ReadyToRun/Win32ResourcesNode.cs | 61 ++++++ .../ReadyToRunCodegenNodeFactory.cs | 11 +- .../ReadyToRunCodegenCompilationBuilder.cs | 22 +- .../ILCompiler.ReadyToRun.csproj | 1 + .../ObjectWriter/R2RPEBuilder.cs | 238 +-------------------- .../ObjectWriter/SectionBuilder.cs | 24 +++ .../Win32Resources/ResourceData.Reader.cs | 12 +- .../ResourceData.ResourcesDataModel.cs | 68 ++---- .../ResourceData.UpdateResourceDataModel.cs | 202 +++-------------- .../Win32Resources/ResourceData.Win32Structs.cs | 39 ++++ .../Win32Resources/ResourceData.cs | 114 +++++++++- 13 files changed, 341 insertions(+), 476 deletions(-) create mode 100644 src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/Win32ResourcesNode.cs diff --git a/src/coreclr/src/tools/crossgen2/Common/Compiler/DependencyAnalysis/ObjectDataBuilder.cs b/src/coreclr/src/tools/crossgen2/Common/Compiler/DependencyAnalysis/ObjectDataBuilder.cs index d56b5dd..6699d06 100644 --- a/src/coreclr/src/tools/crossgen2/Common/Compiler/DependencyAnalysis/ObjectDataBuilder.cs +++ b/src/coreclr/src/tools/crossgen2/Common/Compiler/DependencyAnalysis/ObjectDataBuilder.cs @@ -256,6 +256,15 @@ namespace ILCompiler.DependencyAnalysis _data[offset + 3] = (byte)((emit >> 24) & 0xFF); } + public void EmitUInt(Reservation reservation, uint emit) + { + int offset = ReturnReservationTicket(reservation); + _data[offset] = (byte)(emit & 0xFF); + _data[offset + 1] = (byte)((emit >> 8) & 0xFF); + _data[offset + 2] = (byte)((emit >> 16) & 0xFF); + _data[offset + 3] = (byte)((emit >> 24) & 0xFF); + } + public void EmitReloc(ISymbolNode symbol, RelocType relocType, int delta = 0) { #if DEBUG @@ -321,5 +330,15 @@ namespace ILCompiler.DependencyAnalysis { _definedSymbols.Add(node); } + + public void PadAlignment(int align) + { + Debug.Assert((align == 2) || (align == 4) || (align == 8) || (align == 16)); + int misalignment = _data.Count & (align - 1); + if (misalignment != 0) + { + EmitZeros(align - misalignment); + } + } } } diff --git a/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs index 8fc5442..0474411 100644 --- a/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs +++ b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs @@ -114,6 +114,12 @@ namespace ILCompiler.DependencyAnalysis r2rPeBuilder.SetCorHeader(_nodeFactory.CopiedCorHeaderNode, _nodeFactory.CopiedCorHeaderNode.Size); + if (_nodeFactory.Win32ResourcesNode != null) + { + Debug.Assert(_nodeFactory.Win32ResourcesNode.Size != 0); + r2rPeBuilder.SetWin32Resources(_nodeFactory.Win32ResourcesNode, _nodeFactory.Win32ResourcesNode.Size); + } + using (var peStream = File.Create(_objectFilePath)) { r2rPeBuilder.Write(peStream); diff --git a/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/Win32ResourcesNode.cs b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/Win32ResourcesNode.cs new file mode 100644 index 0000000..7536f59 --- /dev/null +++ b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/Win32ResourcesNode.cs @@ -0,0 +1,61 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; + +using Internal.NativeFormat; +using Internal.Text; +using ILCompiler.Win32Resources; +using Internal.TypeSystem; +using Internal.TypeSystem.Ecma; + +namespace ILCompiler.DependencyAnalysis.ReadyToRun +{ + public class Win32ResourcesNode : ObjectNode, ISymbolDefinitionNode + { + private ResourceData _resourceData; + private int _size; + + public Win32ResourcesNode(ResourceData resourceData) + { + _resourceData = resourceData; + } + + public override ObjectNodeSection Section => ObjectNodeSection.TextSection; + + public override bool IsShareable => false; + + public override int ClassCode => 315358339; + + public override bool StaticDependenciesAreComputed => true; + + public int Offset => 0; + + public void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb) + { + sb.Append("____Win32Resources"); + } + + public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) + { + ObjectDataBuilder builder = new ObjectDataBuilder(); + builder.AddSymbol(this); + _resourceData.WriteResources(this, ref builder); + _size = builder.CountBytes; + return builder.ToObjectData(); + } + + protected override string GetName(NodeFactory context) + { + return "____Win32Resources"; + } + + public int Size => _size; + } +} diff --git a/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs index 8c0cc4f..f0c048c 100644 --- a/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs +++ b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs @@ -15,6 +15,7 @@ using Internal.JitInterface; using Internal.TypeSystem; using Internal.Text; using Internal.TypeSystem.Ecma; +using ILCompiler.Win32Resources; namespace ILCompiler.DependencyAnalysis { @@ -214,7 +215,8 @@ namespace ILCompiler.DependencyAnalysis NameMangler nameMangler, ModuleTokenResolver moduleTokenResolver, SignatureContext signatureContext, - CopiedCorHeaderNode corHeaderNode) + CopiedCorHeaderNode corHeaderNode, + ResourceData win32Resources) : base(context, compilationModuleGroup, nameMangler, @@ -225,6 +227,8 @@ namespace ILCompiler.DependencyAnalysis Resolver = moduleTokenResolver; InputModuleContext = signatureContext; CopiedCorHeaderNode = corHeaderNode; + if (!win32Resources.IsEmpty) + Win32ResourcesNode = new Win32ResourcesNode(win32Resources); } public SignatureContext InputModuleContext; @@ -233,6 +237,8 @@ namespace ILCompiler.DependencyAnalysis public CopiedCorHeaderNode CopiedCorHeaderNode; + public Win32ResourcesNode Win32ResourcesNode; + public HeaderNode Header; public RuntimeFunctionsTableNode RuntimeFunctionsTable; @@ -557,6 +563,9 @@ namespace ILCompiler.DependencyAnalysis graph.AddRoot(Header, "ReadyToRunHeader is always generated"); graph.AddRoot(CopiedCorHeaderNode, "MSIL COR header is always generated"); + if (Win32ResourcesNode != null) + graph.AddRoot(Win32ResourcesNode, "Win32 Resources are placed if not empty"); + MetadataManager.AttachToDependencyGraph(graph); } diff --git a/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilationBuilder.cs b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilationBuilder.cs index ff61006..89c86d1 100644 --- a/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilationBuilder.cs +++ b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilationBuilder.cs @@ -8,7 +8,7 @@ using System.Collections.Generic; using ILCompiler.DependencyAnalysis; using ILCompiler.DependencyAnalysis.ReadyToRun; using ILCompiler.DependencyAnalysisFramework; - +using ILCompiler.Win32Resources; using Internal.IL; using Internal.JitInterface; using Internal.TypeSystem; @@ -81,13 +81,31 @@ namespace ILCompiler SignatureContext signatureContext = new SignatureContext(_inputModule, moduleTokenResolver); CopiedCorHeaderNode corHeaderNode = new CopiedCorHeaderNode(_inputModule); + // Produce a ResourceData where the IBC PROFILE_DATA entry has been filtered out + ResourceData win32Resources = new ResourceData(_inputModule, (object type, object name, ushort language) => + { + if (!(type is string) || !(name is string)) + return true; + if (language != 0) + return true; + + string typeString = (string)type; + string nameString = (string)name; + + if ((typeString == "IBC") && (nameString == "PROFILE_DATA")) + return false; + + return true; + }); + ReadyToRunCodegenNodeFactory factory = new ReadyToRunCodegenNodeFactory( _context, _compilationGroup, _nameMangler, moduleTokenResolver, signatureContext, - corHeaderNode); + corHeaderNode, + win32Resources); DependencyAnalyzerBase graph = CreateDependencyGraph(factory); diff --git a/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj index a04a1d6..73baa78 100644 --- a/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj +++ b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj @@ -157,6 +157,7 @@ + diff --git a/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/ObjectWriter/R2RPEBuilder.cs b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/ObjectWriter/R2RPEBuilder.cs index 5eaa351..394d877 100644 --- a/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/ObjectWriter/R2RPEBuilder.cs +++ b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/ObjectWriter/R2RPEBuilder.cs @@ -7,10 +7,7 @@ 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 ILCompiler.DependencyAnalysis; @@ -77,11 +74,6 @@ namespace ILCompiler.PEWriter /// Name of the initialized data section. /// public const string SDataSectionName = ".sdata"; - - /// - /// Name of the resource section. - /// - public const string RsrcSectionName = ".rsrc"; /// /// Name of the relocation section. @@ -196,14 +188,6 @@ namespace ILCompiler.PEWriter _customSections.Add(section.SectionName); } - foreach (SectionHeader sectionHeader in peReader.PEHeaders.SectionHeaders) - { - if (_sectionBuilder.FindSection(sectionHeader.Name) == null) - { - _sectionBuilder.AddSection(sectionHeader.Name, sectionHeader.SectionCharacteristics, peReader.PEHeaders.PEHeader.SectionAlignment); - } - } - if (_sectionBuilder.FindSection(R2RPEBuilder.RelocSectionName) == null) { // Always inject the relocation section to the end of section list @@ -233,6 +217,11 @@ namespace ILCompiler.PEWriter _sectionBuilder.SetCorHeader(symbol, headerSize); } + 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. /// @@ -407,8 +396,6 @@ namespace ILCompiler.PEWriter protected override PEDirectoriesBuilder GetDirectories() { PEDirectoriesBuilder builder = new PEDirectoriesBuilder(); - builder.CorHeaderTable = RelocateDirectoryEntry(_peReader.PEHeaders.PEHeader.CorHeaderTableDirectory); - builder.ResourceTable = RelocateDirectoryEntry(_peReader.PEHeaders.PEHeader.ResourceTableDirectory); _sectionBuilder.UpdateDirectories(builder); @@ -502,57 +489,6 @@ namespace ILCompiler.PEWriter _sectionRVAs[outputSectionIndex] = sectionStartRva; } - int inputSectionIndex = _peReader.PEHeaders.SectionHeaders.Count() - 1; - while (inputSectionIndex >= 0 && _peReader.PEHeaders.SectionHeaders[inputSectionIndex].Name != name) - { - inputSectionIndex--; - } - if (inputSectionIndex >= 0) - { - SectionHeader sectionHeader = _peReader.PEHeaders.SectionHeaders[inputSectionIndex]; - int sectionOffset = (_peReader.IsLoadedImage ? sectionHeader.VirtualAddress : sectionHeader.PointerToRawData); - int rvaDelta = location.RelativeVirtualAddress - sectionHeader.VirtualAddress; - - _sectionRvaDeltas.Add(new SectionRVADelta( - startRVA: sectionHeader.VirtualAddress, - endRVA: sectionHeader.VirtualAddress + Math.Max(sectionHeader.VirtualSize, sectionHeader.SizeOfRawData), - deltaRVA: rvaDelta)); - - - int bytesToRead = Math.Min(sectionHeader.SizeOfRawData, sectionHeader.VirtualSize); - BlobReader inputSectionReader = _peReader.GetEntireImage().GetReader(sectionOffset, bytesToRead); - - if (name == RsrcSectionName) - { - // There seems to be a bug in BlobBuilder - when we LinkSuffix to an empty blob builder, - // the blob data goes out of sync and WriteContentTo outputs garbage. - sectionDataBuilder = PEResourceHelper.Relocate(inputSectionReader, rvaDelta); - } - - int alignedSize = sectionHeader.VirtualSize; - - // When custom section data is present, align the section size to 4K to prevent - // pre-generated MSIL relocations from tampering with native relocations. - if (_customSections.Contains(name)) - { - alignedSize = (alignedSize + 0xFFF) & ~0xFFF; - } - - if (sectionDataBuilder != null) - { - if (alignedSize > bytesToRead) - { - // If the number of bytes read from the source PE file is less than the virtual size, - // zero pad to the end of virtual size before emitting extra section data - sectionDataBuilder.WriteBytes(0, alignedSize - bytesToRead); - } - - location = new SectionLocation( - location.RelativeVirtualAddress + sectionDataBuilder.Count, - location.PointerToRawData + sectionDataBuilder.Count); - } - } - BlobBuilder extraData = _sectionBuilder.SerializeSection(name, location); if (extraData != null) { @@ -587,170 +523,6 @@ namespace ILCompiler.PEWriter return sectionDataBuilder; } } - - /// - /// When copying PE contents we may need to move the resource section, however its internal - /// ResourceDataEntry records hold RVA's so they need to be relocated. Thankfully the resource - /// data model is very simple so that we just traverse the structure using offset constants. - /// - unsafe sealed class PEResourceHelper - { - /// - /// Field offsets in the resource directory table. - /// - private static class DirectoryTable - { - public const int Characteristics = 0x0; - public const int TimeDateStamp = 0x04; - public const int MajorVersion = 0x08; - public const int MinorVersion = 0x0A; - public const int NumberOfNameEntries = 0x0C; - public const int NumberOfIDEntries = 0x0E; - public const int Size = 0x10; - } - - /// - /// Field offsets in the resource directory entry. - /// - private static class DirectoryEntry - { - public const int NameOffsetOrID = 0x0; - public const int DataOrSubdirectoryOffset = 0x4; - public const int Size = 0x8; - } - - /// - /// When the 4-byte value at the offset DirectoryEntry.DataOrSubdirectoryOffset - /// has 31-st bit set, it's a subdirectory table entry; when it's clear, it's a - /// resource data entry. - /// - private const int EntryOffsetIsSubdirectory = unchecked((int)0x80000000u); - - /// - /// Field offsets in the resource data entry. - /// - private static class DataEntry - { - public const int RVA = 0x0; - public const int Size = 0x4; - public const int Codepage = 0x8; - public const int Reserved = 0xC; - } - - /// - /// Blob reader representing the input resource section. - /// - private BlobReader _reader; - - /// - /// This BlobBuilder holds the relocated resource section after the ctor finishes. - /// - private BlobBuilder _builder; - - /// - /// Relocation delta (the difference between input and output RVA of the resource section). - /// - private int _delta; - - /// - /// Offsets within the resource section representing RVA's in the resource data entries - /// that need relocating. - /// - private List _offsetsOfRvasToRelocate; - - /// - /// Public API receives the input resource section reader and the relocation delta - /// and returns a blob builder representing the relocated resource section. - /// - /// Blob reader representing the input resource section - /// Relocation delta to apply (value to add to RVA's) - public static BlobBuilder Relocate(BlobReader reader, int delta) - { - return new PEResourceHelper(reader, delta)._builder; - } - - /// - /// Private constructor first traverses the internal graph of resource tables - /// and collects offsets to RVA's that need relocation; after that we sort the list of - /// offsets and do a linear copying pass patching the RVA cells with the updated values. - /// - /// Blob reader representing the input resource section - /// Relocation delta to apply (value to add to RVA's) - private PEResourceHelper(BlobReader reader, int delta) - { - _reader = reader; - _builder = new BlobBuilder(); - _delta = delta; - - _offsetsOfRvasToRelocate = new List(); - - TraverseDirectoryTable(tableOffset: 0); - - _offsetsOfRvasToRelocate.Sort(); - int currentOffset = 0; - - _reader.Reset(); - foreach (int offsetOfRvaToRelocate in _offsetsOfRvasToRelocate) - { - int bytesToCopy = offsetOfRvaToRelocate - currentOffset; - Debug.Assert(bytesToCopy >= 0); - if (bytesToCopy > 0) - { - _builder.WriteBytes(_reader.CurrentPointer, bytesToCopy); - _reader.Offset += bytesToCopy; - currentOffset += bytesToCopy; - } - int rva = _reader.ReadInt32(); - _builder.WriteInt32(rva + delta); - currentOffset += sizeof(int); - } - if (_reader.RemainingBytes > 0) - { - _builder.WriteBytes(_reader.CurrentPointer, _reader.RemainingBytes); - } - } - - /// - /// Traverse a single directory table at a given offset within the resource section. - /// Please note the method might end up calling itself recursively through the call graph - /// TraverseDirectoryTable -> TraverseDirectoryEntry -> TraverseDirectoryTable. - /// Maximum depth is equal to depth of the table graph - today resources use 3. - /// - /// Offset of the resource directory table within the resource section - private void TraverseDirectoryTable(int tableOffset) - { - _reader.Offset = tableOffset + DirectoryTable.NumberOfNameEntries; - int numberOfNameEntries = _reader.ReadInt16(); - int numberOfIDEntries = _reader.ReadInt16(); - int totalEntries = numberOfNameEntries + numberOfIDEntries; - for (int entryIndex = 0; entryIndex < totalEntries; entryIndex++) - { - TraverseDirectoryEntry(tableOffset + DirectoryTable.Size + entryIndex * DirectoryEntry.Size); - } - } - - /// - /// Traverse a single directory entry (name- and ID-based directory entries are processed - /// the same way as we're not really interested in the entry identifier, just in the - /// data / table pointers. - /// - /// Offset of the resource directory entry within the resource section - private void TraverseDirectoryEntry(int entryOffset) - { - _reader.Offset = entryOffset + DirectoryEntry.DataOrSubdirectoryOffset; - int dataOrSubdirectoryOffset = _reader.ReadInt32(); - if ((dataOrSubdirectoryOffset & EntryOffsetIsSubdirectory) != 0) - { - // subdirectory offset - TraverseDirectoryTable(dataOrSubdirectoryOffset & ~EntryOffsetIsSubdirectory); - } - else - { - // data entry offset - _offsetsOfRvasToRelocate.Add(dataOrSubdirectoryOffset + DataEntry.RVA); - } - } - } /// /// Simple helper for copying the various global values in the PE header. diff --git a/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/ObjectWriter/SectionBuilder.cs b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/ObjectWriter/SectionBuilder.cs index 92ed56f..c2c4fbe 100644 --- a/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/ObjectWriter/SectionBuilder.cs +++ b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/ObjectWriter/SectionBuilder.cs @@ -252,6 +252,16 @@ namespace ILCompiler.PEWriter int _corHeaderSize; /// + /// 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. /// @@ -372,6 +382,12 @@ namespace ILCompiler.PEWriter _corHeaderSize = headerSize; } + public void SetWin32Resources(ISymbolNode symbol, int resourcesSize) + { + _win32ResourcesSymbol = symbol; + _win32ResourcesSize = resourcesSize; + } + private CoreRTNameMangler _nameMangler; private NameMangler GetNameMangler() @@ -739,6 +755,14 @@ namespace ILCompiler.PEWriter 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); + directoriesBuilder.ResourceTable = new DirectoryEntry(section.RVAWhenPlaced + symbolTarget.Offset, _win32ResourcesSize); + } + if (_exportDirectoryEntry.Size != 0) { directoriesBuilder.ExportTable = _exportDirectoryEntry; diff --git a/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Win32Resources/ResourceData.Reader.cs b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Win32Resources/ResourceData.Reader.cs index e6cf0c3..b9b3353 100644 --- a/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Win32Resources/ResourceData.Reader.cs +++ b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Win32Resources/ResourceData.Reader.cs @@ -4,14 +4,14 @@ using System; using System.Reflection.Metadata; +using System.Reflection.PortableExecutable; namespace ILCompiler.Win32Resources { public unsafe partial class ResourceData { - private void ReadResourceData() + private void ReadResourceData(BlobReader resourceReader, PEReader peFile, Func resourceFilter) { - BlobReader resourceReader = _resourceDataBlob; DoResourceDirectoryRead(resourceReader, 0, ProcessOuterResource); return; @@ -43,9 +43,15 @@ namespace ILCompiler.Win32Resources IMAGE_RESOURCE_DATA_ENTRY resourceData = new IMAGE_RESOURCE_DATA_ENTRY(ref resourceReader); // The actual resource data offset is relative to the start address of the file - BlobReader resourceDataBlob = _peFile.GetSectionData(checked((int)resourceData.OffsetToData)).GetReader(0, checked((int)resourceData.Size)); + BlobReader resourceDataBlob = peFile.GetSectionData(checked((int)resourceData.OffsetToData)).GetReader(0, checked((int)resourceData.Size)); byte[] data = resourceDataBlob.ReadBytes((int)resourceData.Size); + if (resourceFilter != null) + { + // If the filter returns false, don't add this resource to the model + if (!resourceFilter(typeName, name, (ushort)languageName)) + return; + } AddResource(typeName, name, (ushort)languageName, data); } } diff --git a/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Win32Resources/ResourceData.ResourcesDataModel.cs b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Win32Resources/ResourceData.ResourcesDataModel.cs index f842a66..266e4e2 100644 --- a/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Win32Resources/ResourceData.ResourcesDataModel.cs +++ b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Win32Resources/ResourceData.ResourcesDataModel.cs @@ -2,81 +2,37 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; namespace ILCompiler.Win32Resources { public unsafe partial class ResourceData { - private readonly List _resTypeHeadID = new List(); - private readonly List _resTypeHeadName = new List(); + private readonly SortedDictionary _resTypeHeadID = new SortedDictionary(); + private readonly SortedDictionary _resTypeHeadName = new SortedDictionary(StringComparer.Ordinal); - private class OrdinalName + private class ResLanguage { - public OrdinalName(ushort ordinal) { Ordinal = ordinal; } - public readonly ushort Ordinal; - } - - private interface IUnderlyingName - { - T Name { get; } - } + public ResLanguage(byte[] data) + { + DataEntry = data; + } - private class ResName - { public uint DataSize => (uint)DataEntry.Length; public byte[] DataEntry; - public ushort NumberOfLanguages; - public ushort LanguageId; } - private class ResName_Name : ResName, IUnderlyingName + private class ResName { - public ResName_Name(string name) - { - Name = name; - } - - public string Name { get; } - } - - private class ResName_Ordinal : ResName, IUnderlyingName - { - public ResName_Ordinal(ushort name) - { - Name = new OrdinalName(name); - } - - public OrdinalName Name; - ushort IUnderlyingName.Name => Name.Ordinal; + public SortedDictionary Languages = new SortedDictionary(); } private class ResType { - public List NameHeadName = new List(); - public List NameHeadID = new List(); - } - - private class ResType_Ordinal : ResType, IUnderlyingName - { - public ResType_Ordinal(ushort type) - { - Type = new OrdinalName(type); - } - - public OrdinalName Type; - ushort IUnderlyingName.Name => Type.Ordinal; + public SortedDictionary NameHeadName = new SortedDictionary(StringComparer.Ordinal); + public SortedDictionary NameHeadID = new SortedDictionary(); } - private class ResType_Name : ResType, IUnderlyingName - { - public ResType_Name(string type) - { - Type = type; - } - - public string Type { get; set; } - string IUnderlyingName.Name => Type; - } } } diff --git a/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Win32Resources/ResourceData.UpdateResourceDataModel.cs b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Win32Resources/ResourceData.UpdateResourceDataModel.cs index 8272811..37f80ab 100644 --- a/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Win32Resources/ResourceData.UpdateResourceDataModel.cs +++ b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Win32Resources/ResourceData.UpdateResourceDataModel.cs @@ -11,161 +11,45 @@ namespace ILCompiler.Win32Resources { private void AddResource(object type, object name, ushort language, byte[] data) { - ResType resType = null; - // Allocate new object in case it is needed. - ResType newResType; - int newIndex; + ResType resType; - IList typeList; - bool updateExisting; if (type is ushort) { - ResType_Ordinal newOrdinalType = new ResType_Ordinal((ushort)type); - newResType = newOrdinalType; - typeList = _resTypeHeadID; - - newIndex = GetIndexOfFirstItemMatchingInListOrInsertionPoint(typeList, (ushort left, ushort right) => (int)left - (int)right, (ushort)type, out updateExisting); - } - else - { - ResType_Name newStringType = new ResType_Name((string)type); - newResType = newStringType; - typeList = _resTypeHeadName; - - newIndex = GetIndexOfFirstItemMatchingInListOrInsertionPoint(typeList, string.CompareOrdinal, (string)type, out updateExisting); - } - - if (updateExisting) - { - resType = (ResType)typeList[newIndex]; - } - else - { - // This is a new type - if (newIndex == -1) - typeList.Add(newResType); - else - typeList.Insert(newIndex, newResType); - - resType = newResType; - } - - Type resNameType; - IList nameList; - int nameIndex; - - if (name is ushort) - { - nameList = resType.NameHeadID; - resNameType = typeof(ResName_Ordinal); - nameIndex = GetIndexOfFirstItemMatchingInListOrInsertionPoint(nameList, (ushort left, ushort right) => (int)left - (int)right, (ushort)name, out updateExisting); - } - else - { - nameList = resType.NameHeadName; - resNameType = typeof(ResName_Name); - nameIndex = GetIndexOfFirstItemMatchingInListOrInsertionPoint(nameList, string.CompareOrdinal, (string)name, out updateExisting); - } - - if (updateExisting) - { - // We have at least 1 language with the same type/name. Insert/delete from language list - ResName resName = (ResName)nameList[nameIndex]; - int newNumberOfLanguages = (int)resName.NumberOfLanguages + (data != null ? 1 : -1); - - int newIndexForNewOrUpdatedNameWithMatchingLanguage = GetIndexOfFirstItemMatchingInListOrInsertionPoint(nameList, nameIndex, - resName.NumberOfLanguages, (object o) => ((ResName)o).LanguageId, (ushort left, ushort right) => (int)left - (int)right, language, out bool exactLanguageExists); - - if (exactLanguageExists) + if (!_resTypeHeadID.TryGetValue((ushort)type, out resType)) { - if (data == null) - { - // delete item - nameList.RemoveAt(newIndexForNewOrUpdatedNameWithMatchingLanguage); - - if (newNumberOfLanguages > 0) - { - // if another name is still present, update the number of languages counter - resName = (ResName)nameList[nameIndex]; - resName.NumberOfLanguages = (ushort)newNumberOfLanguages; - } - - if ((resType.NameHeadID.Count == 0) && (resType.NameHeadName.Count == 0)) - { - /* type list completely empty? */ - typeList.Remove(resType); - } - } - else - { - // Resource file has two copies of same resource... ignore second copy - return; - } - } - else - { - // Insert a new name at the new spot - AddNewName(nameList, resNameType, newIndexForNewOrUpdatedNameWithMatchingLanguage, name, language, data); - // Update the NumberOfLanguages for the language list - resName = (ResName)nameList[nameIndex]; - resName.NumberOfLanguages = (ushort)newNumberOfLanguages; + resType = new ResType(); + _resTypeHeadID[(ushort)type] = resType; } } else { - // This is a new name in a new language list - if (data == null) + if (!_resTypeHeadName.TryGetValue((string)type, out resType)) { - // Can't delete new name - throw new ArgumentException(); + resType = new ResType(); + _resTypeHeadName[(string)type] = resType; } - - AddNewName(nameList, resNameType, nameIndex, name, language, data); } - } - private static int GetIndexOfFirstItemMatchingInListOrInsertionPoint(IList list, Func compareFunction, T comparand, out bool exists) - { - return GetIndexOfFirstItemMatchingInListOrInsertionPoint(list, 0, list.Count, (object o) => ((IUnderlyingName)o).Name, compareFunction, comparand, out exists); - } + ResName resName; - private static int GetIndexOfFirstItemMatchingInListOrInsertionPoint(IList list, int start, int count, Func getComparandFromListElem, Func compareFunction, T comparand, out bool exists) - { - int i = start; - for (; i < (start + count); i++) + if (name is ushort) { - int iCompare = compareFunction(comparand, getComparandFromListElem(list[i])); - if (iCompare == 0) - { - exists = true; - return i; - } - else if (iCompare < 0) + if (!resType.NameHeadID.TryGetValue((ushort)name, out resName)) { - exists = false; - return i; + resName = new ResName(); + resType.NameHeadID[(ushort)name] = resName; } } - - exists = false; - if ((start + count) < list.Count) + else { - return start + count; + if (!resType.NameHeadName.TryGetValue((string)name, out resName)) + { + resName = new ResName(); + resType.NameHeadName[(string)name] = resName; + } } - return -1; - } - private void AddNewName(IList list, Type resNameType, int insertPoint, object name, ushort language, byte[] data) - { - ResName newResName = (ResName)Activator.CreateInstance(resNameType, name); - newResName.LanguageId = language; - newResName.NumberOfLanguages = 1; - newResName.DataEntry = data; - - if (insertPoint == -1) - list.Add(newResName); - else - list.Insert(insertPoint, newResName); + resName.Languages[language] = new ResLanguage(data); } private byte[] FindResourceInternal(object name, object type, ushort language) @@ -174,58 +58,34 @@ namespace ILCompiler.Win32Resources if (type is ushort) { - foreach (ResType_Ordinal candidate in _resTypeHeadID) - { - if (candidate.Type.Ordinal == (ushort)type) - { - resType = candidate; - break; - } - } + _resTypeHeadID.TryGetValue((ushort)type, out resType); } if (type is string) { - foreach (ResType_Name candidate in _resTypeHeadName) - { - if (candidate.Type == (string)type) - { - resType = candidate; - break; - } - } + _resTypeHeadName.TryGetValue((string)type, out resType); } if (resType == null) return null; + ResName resName = null; + if (name is ushort) { - foreach (ResName_Ordinal candidate in resType.NameHeadID) - { - if (candidate.Name.Ordinal != (ushort)type) - continue; - - if (candidate.LanguageId != language) - continue; - - return (byte[])candidate.DataEntry.Clone(); - } + resType.NameHeadID.TryGetValue((ushort)name, out resName); } if (name is string) { - foreach (ResName_Name candidate in resType.NameHeadName) - { - if (candidate.Name != (string)name) - continue; + resType.NameHeadName.TryGetValue((string)name, out resName); + } - if (candidate.LanguageId != language) - continue; + if (resName == null) + return null; - return (byte[])candidate.DataEntry.Clone(); - } - } + if (!resName.Languages.TryGetValue(language, out ResLanguage resLanguage)) + return null; - return null; + return (byte[])resLanguage.DataEntry.Clone(); } } } diff --git a/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Win32Resources/ResourceData.Win32Structs.cs b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Win32Resources/ResourceData.Win32Structs.cs index a5421f1..74aa6d1 100644 --- a/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Win32Resources/ResourceData.Win32Structs.cs +++ b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Win32Resources/ResourceData.Win32Structs.cs @@ -2,9 +2,12 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Collections.Generic; using System.Runtime.InteropServices; using System.Reflection.Metadata; +using ILCompiler.DependencyAnalysis; + namespace ILCompiler.Win32Resources { public unsafe partial class ResourceData @@ -21,6 +24,16 @@ namespace ILCompiler.Win32Resources NumberOfIdEntries = blobReader.ReadUInt16(); } + public static void Write(ref ObjectDataBuilder builder, ushort namedEntries, ushort idEntries) + { + builder.EmitUInt(0); // Characteristics + builder.EmitUInt(0); // TimeDateStamp + builder.EmitUShort(4); // MajorVersion + builder.EmitUShort(0); // MinorVersion + builder.EmitUShort(namedEntries); + builder.EmitUShort(idEntries); + } + public readonly uint Characteristics; public readonly uint TimeDateStamp; public readonly ushort MajorVersion; @@ -37,6 +50,24 @@ namespace ILCompiler.Win32Resources OffsetToData = blobReader.ReadUInt32(); } + public static ObjectDataBuilder.Reservation Write(ref ObjectDataBuilder dataBuilder, string name, IDictionary> nameTable) + { + List relatedNameReferences; + if (!nameTable.TryGetValue(name, out relatedNameReferences)) + { + relatedNameReferences = new List(); + nameTable[name] = relatedNameReferences; + } + relatedNameReferences.Add(dataBuilder.ReserveInt()); + return dataBuilder.ReserveInt(); + } + + public static ObjectDataBuilder.Reservation Write(ref ObjectDataBuilder dataBuilder, ushort id) + { + dataBuilder.EmitInt(id); + return dataBuilder.ReserveInt(); + } + public readonly uint Name; public readonly uint OffsetToData; } @@ -52,6 +83,14 @@ namespace ILCompiler.Win32Resources Reserved = blobReader.ReadUInt32(); } + public static void Write(ref ObjectDataBuilder dataBuilder, ISymbolNode node, int offsetFromSymbol, int sizeOfData) + { + dataBuilder.EmitReloc(node, RelocType.IMAGE_REL_BASED_ADDR32NB, offsetFromSymbol); + dataBuilder.EmitInt(sizeOfData); + dataBuilder.EmitInt(1252); // CODEPAGE = DEFAULT_CODEPAGE + dataBuilder.EmitInt(0); // RESERVED + } + public uint OffsetToData; public uint Size; private uint CodePage; diff --git a/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Win32Resources/ResourceData.cs b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Win32Resources/ResourceData.cs index 9c6d342..daa10dd 100644 --- a/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Win32Resources/ResourceData.cs +++ b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Win32Resources/ResourceData.cs @@ -2,10 +2,15 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using Internal.TypeSystem.Ecma; +using System; +using System.Collections.Generic; +using System.Diagnostics; using System.Reflection.Metadata; using System.Reflection.PortableExecutable; +using ILCompiler.DependencyAnalysis; +using Internal.TypeSystem.Ecma; + namespace ILCompiler.Win32Resources { /// @@ -14,23 +19,20 @@ namespace ILCompiler.Win32Resources /// public unsafe partial class ResourceData { - BlobReader _resourceDataBlob; - PEReader _peFile; - /// /// Initialize a ResourceData instance from a PE file /// /// - public ResourceData(EcmaModule ecmaModule) + public ResourceData(EcmaModule ecmaModule, Func resourceFilter = null) { - var ecmaData = ecmaModule.PEReader.GetEntireImage().GetContent(); - _peFile = ecmaModule.PEReader; + System.Collections.Immutable.ImmutableArray ecmaData = ecmaModule.PEReader.GetEntireImage().GetContent(); + PEReader peFile = ecmaModule.PEReader; - DirectoryEntry resourceDirectory = _peFile.PEHeaders.PEHeader.ResourceTableDirectory; + DirectoryEntry resourceDirectory = peFile.PEHeaders.PEHeader.ResourceTableDirectory; if (resourceDirectory.Size != 0) { - _resourceDataBlob = ecmaModule.PEReader.GetSectionData(resourceDirectory.RelativeVirtualAddress).GetReader(0, resourceDirectory.Size); - ReadResourceData(); + BlobReader resourceDataBlob = ecmaModule.PEReader.GetSectionData(resourceDirectory.RelativeVirtualAddress).GetReader(0, resourceDirectory.Size); + ReadResourceData(resourceDataBlob, peFile, resourceFilter); } } @@ -65,5 +67,97 @@ namespace ILCompiler.Win32Resources { return FindResourceInternal(name, type, language); } + + public bool IsEmpty + { + get + { + if (_resTypeHeadID.Count > 0) + return false; + + if (_resTypeHeadName.Count > 0) + return false; + + return true; + } + } + + public void WriteResources(ISymbolNode nodeAssociatedWithDataBuilder, ref ObjectDataBuilder dataBuilder) + { + Debug.Assert(dataBuilder.CountBytes == 0); + + SortedDictionary> nameTable = new SortedDictionary>(); + Dictionary dataEntryTable = new Dictionary(); + List> resTypes = new List>(); + List> resNames = new List>(); + List> resLanguages = new List>(); + + IMAGE_RESOURCE_DIRECTORY.Write(ref dataBuilder, checked((ushort)_resTypeHeadName.Count), checked((ushort)_resTypeHeadID.Count)); + foreach (KeyValuePair res in _resTypeHeadName) + { + resTypes.Add(new Tuple(res.Value, IMAGE_RESOURCE_DIRECTORY_ENTRY.Write(ref dataBuilder, res.Key, nameTable))); + } + foreach (KeyValuePair res in _resTypeHeadID) + { + resTypes.Add(new Tuple(res.Value, IMAGE_RESOURCE_DIRECTORY_ENTRY.Write(ref dataBuilder, res.Key))); + } + + foreach (Tuple type in resTypes) + { + dataBuilder.EmitUInt(type.Item2, (uint)dataBuilder.CountBytes | 0x80000000); + IMAGE_RESOURCE_DIRECTORY.Write(ref dataBuilder, checked((ushort)type.Item1.NameHeadName.Count), checked((ushort)type.Item1.NameHeadID.Count)); + + foreach (KeyValuePair res in type.Item1.NameHeadName) + { + resNames.Add(new Tuple(res.Value, IMAGE_RESOURCE_DIRECTORY_ENTRY.Write(ref dataBuilder, res.Key, nameTable))); + } + foreach (KeyValuePair res in type.Item1.NameHeadID) + { + resNames.Add(new Tuple(res.Value, IMAGE_RESOURCE_DIRECTORY_ENTRY.Write(ref dataBuilder, res.Key))); + } + } + + foreach (Tuple type in resNames) + { + dataBuilder.EmitUInt(type.Item2, (uint)dataBuilder.CountBytes | 0x80000000); + IMAGE_RESOURCE_DIRECTORY.Write(ref dataBuilder, 0, checked((ushort)type.Item1.Languages.Count)); + foreach (KeyValuePair res in type.Item1.Languages) + { + resLanguages.Add(new Tuple(res.Value, IMAGE_RESOURCE_DIRECTORY_ENTRY.Write(ref dataBuilder, res.Key))); + } + } + + // Emit name table + dataBuilder.PadAlignment(2); // name table is 2 byte aligned + foreach (KeyValuePair> name in nameTable) + { + foreach (ObjectDataBuilder.Reservation reservation in name.Value) + { + dataBuilder.EmitUInt(reservation, (uint)dataBuilder.CountBytes | 0x80000000); + } + + dataBuilder.EmitUShort(checked((ushort)name.Key.Length)); + foreach (char c in name.Key) + { + dataBuilder.EmitUShort((ushort)c); + } + } + + // Emit byte arrays of resource data, capture the offsets + foreach (Tuple language in resLanguages) + { + dataBuilder.PadAlignment(4); // Data in resource files is 4 byte aligned + dataEntryTable.Add(language.Item1, dataBuilder.CountBytes); + dataBuilder.EmitBytes(language.Item1.DataEntry); + } + + dataBuilder.PadAlignment(4); // resource data entries are 4 byte aligned + foreach (Tuple language in resLanguages) + { + dataBuilder.EmitInt(language.Item2, dataBuilder.CountBytes); + IMAGE_RESOURCE_DATA_ENTRY.Write(ref dataBuilder, nodeAssociatedWithDataBuilder, dataEntryTable[language.Item1], language.Item1.DataEntry.Length); + } + dataBuilder.PadAlignment(4); // resource data entries are 4 byte aligned + } } } -- 2.7.4