diff --git a/src/installer/managed/Microsoft.NET.HostModel/MachO/BinaryFormat/LoadCommand.cs b/src/coreclr/tools/Common/MachO/BinaryFormat/LoadCommand.cs similarity index 92% rename from src/installer/managed/Microsoft.NET.HostModel/MachO/BinaryFormat/LoadCommand.cs rename to src/coreclr/tools/Common/MachO/BinaryFormat/LoadCommand.cs index c2ff7988d2ea63..c0cebf73e569b9 100644 --- a/src/installer/managed/Microsoft.NET.HostModel/MachO/BinaryFormat/LoadCommand.cs +++ b/src/coreclr/tools/Common/MachO/BinaryFormat/LoadCommand.cs @@ -3,7 +3,11 @@ using System.Runtime.InteropServices; +#if HOST_MODEL namespace Microsoft.NET.HostModel.MachO; +#else +namespace ILCompiler.Reflection.ReadyToRun.MachO; +#endif /// /// The base structure for all load commands in a Mach-O binary. diff --git a/src/installer/managed/Microsoft.NET.HostModel/MachO/BinaryFormat/MachHeader.cs b/src/coreclr/tools/Common/MachO/BinaryFormat/MachHeader.cs similarity index 91% rename from src/installer/managed/Microsoft.NET.HostModel/MachO/BinaryFormat/MachHeader.cs rename to src/coreclr/tools/Common/MachO/BinaryFormat/MachHeader.cs index b2fd21a5eb1dd3..6f2d569e72d1ff 100644 --- a/src/installer/managed/Microsoft.NET.HostModel/MachO/BinaryFormat/MachHeader.cs +++ b/src/coreclr/tools/Common/MachO/BinaryFormat/MachHeader.cs @@ -3,7 +3,11 @@ using System.Runtime.InteropServices; +#if HOST_MODEL namespace Microsoft.NET.HostModel.MachO; +#else +namespace ILCompiler.Reflection.ReadyToRun.MachO; +#endif /// /// The Mach-O header is the first data in a Mach-O file. @@ -25,6 +29,7 @@ internal struct MachHeader public uint SizeOfCommands { get => _magic.ConvertValue(_sizeOfCommands); set => _sizeOfCommands = _magic.ConvertValue(value); } public bool Is64Bit => _magic is MachMagic.MachHeader64CurrentEndian or MachMagic.MachHeader64OppositeEndian; public MachFileType FileType => (MachFileType)_magic.ConvertValue((uint)_fileType); + public uint CpuType => _magic.ConvertValue(_cpuType); public uint ConvertValue(uint value) => _magic.ConvertValue(value); public ulong ConvertValue(ulong value) => _magic.ConvertValue(value); diff --git a/src/installer/managed/Microsoft.NET.HostModel/MachO/BinaryFormat/NameBuffer.cs b/src/coreclr/tools/Common/MachO/BinaryFormat/NameBuffer.cs similarity index 65% rename from src/installer/managed/Microsoft.NET.HostModel/MachO/BinaryFormat/NameBuffer.cs rename to src/coreclr/tools/Common/MachO/BinaryFormat/NameBuffer.cs index 37e188e9e9f721..fd8e1f2a1433f8 100644 --- a/src/installer/managed/Microsoft.NET.HostModel/MachO/BinaryFormat/NameBuffer.cs +++ b/src/coreclr/tools/Common/MachO/BinaryFormat/NameBuffer.cs @@ -4,7 +4,11 @@ using System; using System.Runtime.InteropServices; +#if HOST_MODEL namespace Microsoft.NET.HostModel.MachO; +#else +namespace ILCompiler.Reflection.ReadyToRun.MachO; +#endif /// /// A 16 byte buffer used to store names in Mach-O load commands. @@ -15,9 +19,11 @@ internal struct NameBuffer private ulong _nameLower; private ulong _nameUpper; + private const int BufferLength = 16; + private NameBuffer(ReadOnlySpan nameBytes) { - byte[] buffer = new byte[16]; + byte[] buffer = new byte[BufferLength]; nameBytes.CopyTo(buffer); if (BitConverter.IsLittleEndian) @@ -34,4 +40,19 @@ private NameBuffer(ReadOnlySpan nameBytes) public static NameBuffer __TEXT = new NameBuffer("__TEXT"u8); public static NameBuffer __LINKEDIT = new NameBuffer("__LINKEDIT"u8); + + public unsafe string GetString() + { + fixed (ulong* ptr = &_nameLower) + { + byte* bytePtr = (byte*)ptr; + int length = 0; + while (length < BufferLength && bytePtr[length] != 0) + { + length++; + } + + return System.Text.Encoding.UTF8.GetString(bytePtr, length); + } + } } diff --git a/src/installer/managed/Microsoft.NET.HostModel/MachO/BinaryFormat/Section64LoadCommand.cs b/src/coreclr/tools/Common/MachO/BinaryFormat/Section64LoadCommand.cs similarity index 79% rename from src/installer/managed/Microsoft.NET.HostModel/MachO/BinaryFormat/Section64LoadCommand.cs rename to src/coreclr/tools/Common/MachO/BinaryFormat/Section64LoadCommand.cs index f2cb79b243c9e4..c878507522d595 100644 --- a/src/installer/managed/Microsoft.NET.HostModel/MachO/BinaryFormat/Section64LoadCommand.cs +++ b/src/coreclr/tools/Common/MachO/BinaryFormat/Section64LoadCommand.cs @@ -3,7 +3,11 @@ using System.Runtime.InteropServices; +#if HOST_MODEL namespace Microsoft.NET.HostModel.MachO; +#else +namespace ILCompiler.Reflection.ReadyToRun.MachO; +#endif /// /// A load command that provides information about a section in a segment. @@ -25,5 +29,8 @@ internal readonly struct Section64LoadCommand private readonly uint _reserved2; private readonly uint _reserved3; + internal NameBuffer SectionName => _sectionName; + internal ulong GetVMAddress(MachHeader header) => header.ConvertValue(_address); + internal ulong GetSize(MachHeader header) => header.ConvertValue(_size); internal uint GetFileOffset(MachHeader header) => header.ConvertValue(_fileOffset); } diff --git a/src/installer/managed/Microsoft.NET.HostModel/MachO/BinaryFormat/Segment64LoadCommand.cs b/src/coreclr/tools/Common/MachO/BinaryFormat/Segment64LoadCommand.cs similarity index 90% rename from src/installer/managed/Microsoft.NET.HostModel/MachO/BinaryFormat/Segment64LoadCommand.cs rename to src/coreclr/tools/Common/MachO/BinaryFormat/Segment64LoadCommand.cs index e731508ab2e47f..14def55928a22a 100644 --- a/src/installer/managed/Microsoft.NET.HostModel/MachO/BinaryFormat/Segment64LoadCommand.cs +++ b/src/coreclr/tools/Common/MachO/BinaryFormat/Segment64LoadCommand.cs @@ -3,7 +3,11 @@ using System.Runtime.InteropServices; +#if HOST_MODEL namespace Microsoft.NET.HostModel.MachO; +#else +namespace ILCompiler.Reflection.ReadyToRun.MachO; +#endif /// /// The structure for the 64-bit segment load command in a Mach-O binary. @@ -29,6 +33,7 @@ internal struct Segment64LoadCommand public ulong GetFileOffset(MachHeader header) => header.ConvertValue(_fileOffset); public ulong GetFileSize(MachHeader header) => header.ConvertValue(_fileSize); public void SetFileSize(ulong value, MachHeader header) => _fileSize = header.ConvertValue(value); + public ulong GetVMAddress(MachHeader header) => header.ConvertValue(_address); public void SetVMSize(ulong value, MachHeader header) => _size = header.ConvertValue(value); public ulong GetVMSize(MachHeader header) => header.ConvertValue(_size); public uint GetSectionsCount(MachHeader header) => header.ConvertValue(_numberOfSections); diff --git a/src/installer/managed/Microsoft.NET.HostModel/MachO/BinaryFormat/SymbolTableLoadCommand.cs b/src/coreclr/tools/Common/MachO/BinaryFormat/SymbolTableLoadCommand.cs similarity index 95% rename from src/installer/managed/Microsoft.NET.HostModel/MachO/BinaryFormat/SymbolTableLoadCommand.cs rename to src/coreclr/tools/Common/MachO/BinaryFormat/SymbolTableLoadCommand.cs index 33c719db071945..e97294d0511099 100644 --- a/src/installer/managed/Microsoft.NET.HostModel/MachO/BinaryFormat/SymbolTableLoadCommand.cs +++ b/src/coreclr/tools/Common/MachO/BinaryFormat/SymbolTableLoadCommand.cs @@ -3,7 +3,11 @@ using System.Runtime.InteropServices; +#if HOST_MODEL namespace Microsoft.NET.HostModel.MachO; +#else +namespace ILCompiler.Reflection.ReadyToRun.MachO; +#endif /// /// A load command with info about the location of the symbol table and string table. diff --git a/src/installer/managed/Microsoft.NET.HostModel/MachO/Enums/MachFileType.cs b/src/coreclr/tools/Common/MachO/Enums/MachFileType.cs similarity index 75% rename from src/installer/managed/Microsoft.NET.HostModel/MachO/Enums/MachFileType.cs rename to src/coreclr/tools/Common/MachO/Enums/MachFileType.cs index 711b48597ab41b..01555e59fc3d69 100644 --- a/src/installer/managed/Microsoft.NET.HostModel/MachO/Enums/MachFileType.cs +++ b/src/coreclr/tools/Common/MachO/Enums/MachFileType.cs @@ -1,7 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +#if HOST_MODEL namespace Microsoft.NET.HostModel.MachO; +#else +namespace ILCompiler.Reflection.ReadyToRun.MachO; +#endif internal enum MachFileType : uint { diff --git a/src/installer/managed/Microsoft.NET.HostModel/MachO/Enums/MachLoadCommandType.cs b/src/coreclr/tools/Common/MachO/Enums/MachLoadCommandType.cs similarity index 86% rename from src/installer/managed/Microsoft.NET.HostModel/MachO/Enums/MachLoadCommandType.cs rename to src/coreclr/tools/Common/MachO/Enums/MachLoadCommandType.cs index b827f6a2f7e7ea..1418eb664c7308 100644 --- a/src/installer/managed/Microsoft.NET.HostModel/MachO/Enums/MachLoadCommandType.cs +++ b/src/coreclr/tools/Common/MachO/Enums/MachLoadCommandType.cs @@ -1,7 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +#if HOST_MODEL namespace Microsoft.NET.HostModel.MachO; +#else +namespace ILCompiler.Reflection.ReadyToRun.MachO; +#endif /// /// See https://github.com/apple-oss-distributions/cctools/blob/7a5450708479bbff61527d5e0c32a3f7b7e4c1d0/include/mach-o/loader.h#L282 for reference. diff --git a/src/installer/managed/Microsoft.NET.HostModel/MachO/Enums/MachMagic.cs b/src/coreclr/tools/Common/MachO/Enums/MachMagic.cs similarity index 89% rename from src/installer/managed/Microsoft.NET.HostModel/MachO/Enums/MachMagic.cs rename to src/coreclr/tools/Common/MachO/Enums/MachMagic.cs index f36766cf58e41c..56e123c8e3ca12 100644 --- a/src/installer/managed/Microsoft.NET.HostModel/MachO/Enums/MachMagic.cs +++ b/src/coreclr/tools/Common/MachO/Enums/MachMagic.cs @@ -1,7 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +#if HOST_MODEL namespace Microsoft.NET.HostModel.MachO; +#else +namespace ILCompiler.Reflection.ReadyToRun.MachO; +#endif /// /// See https://github.com/apple-oss-distributions/cctools/blob/7a5450708479bbff61527d5e0c32a3f7b7e4c1d0/tests/include/MachO/MachHeader.pm#L12 for definitions. diff --git a/src/installer/managed/Microsoft.NET.HostModel/MachO/MachMagicExtensions.cs b/src/coreclr/tools/Common/MachO/MachMagicExtensions.cs similarity index 94% rename from src/installer/managed/Microsoft.NET.HostModel/MachO/MachMagicExtensions.cs rename to src/coreclr/tools/Common/MachO/MachMagicExtensions.cs index 2235664e195f01..08fe1a9dcfa9a6 100644 --- a/src/installer/managed/Microsoft.NET.HostModel/MachO/MachMagicExtensions.cs +++ b/src/coreclr/tools/Common/MachO/MachMagicExtensions.cs @@ -4,7 +4,11 @@ using System.Buffers.Binary; using System.IO; +#if HOST_MODEL namespace Microsoft.NET.HostModel.MachO; +#else +namespace ILCompiler.Reflection.ReadyToRun.MachO; +#endif internal static class MachMagicExtensions { diff --git a/src/coreclr/tools/Common/MachO/MachObjectFile.cs b/src/coreclr/tools/Common/MachO/MachObjectFile.cs new file mode 100644 index 00000000000000..0ce744ed80ffe0 --- /dev/null +++ b/src/coreclr/tools/Common/MachO/MachObjectFile.cs @@ -0,0 +1,32 @@ +// 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.IO; + +#if HOST_MODEL +namespace Microsoft.NET.HostModel.MachO; +#else +namespace ILCompiler.Reflection.ReadyToRun.MachO; +#endif + +/// +/// A managed object containing relevant information for AdHoc signing a Mach-O file. +/// The object is created from a memory mapped file, and a signature can be calculated from the memory mapped file. +/// However, since a memory mapped file cannot be extended, the signature is written to a file stream. +/// +internal unsafe partial class MachObjectFile +{ + public static bool IsMachOImage(string filePath) + { + using (BinaryReader reader = new BinaryReader(File.OpenRead(filePath))) + { + if (reader.BaseStream.Length < 256) // Header size + { + return false; + } + uint magic = reader.ReadUInt32(); + return Enum.IsDefined(typeof(MachMagic), magic); + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunContainerFormat.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunContainerFormat.cs index 403fc97e4c1cd6..cf1221e59c680d 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunContainerFormat.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunContainerFormat.cs @@ -1,11 +1,7 @@ // 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 +namespace ILCompiler { public enum ReadyToRunContainerFormat { diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/IBinaryImageReader.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/IBinaryImageReader.cs new file mode 100644 index 00000000000000..54b20e0012d6b3 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/IBinaryImageReader.cs @@ -0,0 +1,75 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.IO; +using System.Reflection.PortableExecutable; + +namespace ILCompiler.Reflection.ReadyToRun +{ + /// + /// Interface for abstracting binary image reading across different formats (PE, MachO) + /// + public interface IBinaryImageReader + { + /// + /// Gets the machine type of the binary image + /// + Machine Machine { get; } + + /// + /// Gets the operating system of the binary image + /// + OperatingSystem OperatingSystem { get; } + + /// + /// Gets the image base address + /// + ulong ImageBase { get; } + + /// + /// Get the entire image content + /// + ImmutableArray GetEntireImage(); + + /// + /// Get the index in the image byte array corresponding to the RVA + /// + /// The relative virtual address + int GetOffset(int rva); + + /// + /// Try to get the ReadyToRun header RVA for this image. + /// + /// RVA of the ReadyToRun header if available, 0 when not + /// true when the reader represents a composite ReadyToRun image, false for regular R2R + /// true when the reader represents a ReadyToRun image (composite or regular), false otherwise + bool TryGetReadyToRunHeader(out int rva, out bool isComposite); + + /// + /// Creates standalone assembly metadata from the image's embedded metadata. + /// + /// Assembly metadata, or null if the image has no embedded metadata + IAssemblyMetadata GetStandaloneAssemblyMetadata(); + + /// + /// Creates manifest assembly metadata from the R2R manifest + /// + /// Manifest metadata reader + /// Manifest assembly metadata + IAssemblyMetadata GetManifestAssemblyMetadata(System.Reflection.Metadata.MetadataReader manifestReader); + + /// + /// Write out image information using the specified writer + /// + /// The writer to use + void DumpImageInformation(TextWriter writer); + + /// + /// Gets the sections (name and size) of the binary image + /// + Dictionary GetSections(); + + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ILCompiler.Reflection.ReadyToRun.csproj b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ILCompiler.Reflection.ReadyToRun.csproj index 6274faad0c9b0c..061cad8eb38812 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ILCompiler.Reflection.ReadyToRun.csproj +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ILCompiler.Reflection.ReadyToRun.csproj @@ -1,4 +1,4 @@ - + ILCompiler.Reflection.ReadyToRun 1.0.0.0 @@ -31,6 +31,8 @@ + + diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/MachO/BinaryFormat/NList64.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/MachO/BinaryFormat/NList64.cs new file mode 100644 index 00000000000000..c534ea8f16b590 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/MachO/BinaryFormat/NList64.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.InteropServices; + +namespace ILCompiler.Reflection.ReadyToRun.MachO; + +/// +/// Represents a 64-bit symbol table entry. +/// See https://github.com/apple-oss-distributions/cctools/blob/7a5450708479bbff61527d5e0c32a3f7b7e4c1d0/include/mach-o/nlist.h#L92 for reference. +/// +[StructLayout(LayoutKind.Sequential)] +internal readonly struct NList64 +{ + private readonly uint _stringTableIndex; + private readonly byte _type; + private readonly byte _section; + private readonly ushort _description; + private readonly ulong _value; + + public uint GetStringTableIndex(MachHeader header) => header.ConvertValue(_stringTableIndex); + public ulong GetValue(MachHeader header) => header.ConvertValue(_value); +} diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/MachO/MachOImageReader.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/MachO/MachOImageReader.cs new file mode 100644 index 00000000000000..1e65f55f0ce3e5 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/MachO/MachOImageReader.cs @@ -0,0 +1,300 @@ +// 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.Collections.Immutable; +using System.IO; +using System.Reflection.PortableExecutable; +using System.Runtime.CompilerServices; + +namespace ILCompiler.Reflection.ReadyToRun.MachO +{ + /// + /// Wrapper around Mach-O file that implements IBinaryImageReader + /// + public class MachOImageReader : IBinaryImageReader + { + private readonly byte[] _image; + private readonly MachHeader _header; + private int? _rtrHeaderRva; + + public Machine Machine { get; } + public OperatingSystem OperatingSystem => OperatingSystem.Apple; + public ulong ImageBase => 0; + + public MachOImageReader(byte[] image) + { + _image = image; + + // Read the MachO header + Read(0, out _header); + if (!_header.Is64Bit) + throw new BadImageFormatException("Only 64-bit Mach-O files are supported"); + + // Determine machine type from CPU type + Machine = GetMachineType(_header.CpuType); + } + + public ImmutableArray GetEntireImage() + => Unsafe.As>(ref Unsafe.AsRef(in _image)); + + public int GetOffset(int rva) + { + if (TryGetContainingSegment((ulong)rva, out Segment64LoadCommand segment)) + { + // Calculate file offset from segment base and RVA + ulong offsetWithinSegment = (ulong)rva - segment.GetVMAddress(_header); + ulong fileOffset = segment.GetFileOffset(_header) + offsetWithinSegment; + System.Diagnostics.Debug.Assert(fileOffset <= int.MaxValue); + return (int)fileOffset; + } + else + { + throw new BadImageFormatException("Failed to convert RVA to offset: " + rva); + } + } + + public bool TryGetReadyToRunHeader(out int rva, out bool isComposite) + { + if (!_rtrHeaderRva.HasValue) + { + // Look for RTR_HEADER symbol in the Mach-O symbol table + // Mach-O R2R images are always composite (no regular R2R format) + if (TryFindSymbol("RTR_HEADER", out ulong symbolValue)) + { + System.Diagnostics.Debug.Assert(symbolValue <= int.MaxValue); + _rtrHeaderRva = (int)symbolValue; + } + else + { + _rtrHeaderRva = 0; + } + } + + rva = _rtrHeaderRva.Value; + isComposite = rva != 0; // Mach-O R2R images are always composite + return rva != 0; + } + + public IAssemblyMetadata GetStandaloneAssemblyMetadata() => null; + + public IAssemblyMetadata GetManifestAssemblyMetadata(System.Reflection.Metadata.MetadataReader manifestReader) + => new ManifestAssemblyMetadata(manifestReader); + + public void DumpImageInformation(TextWriter writer) + { + writer.WriteLine($"FileType: {_header.FileType}"); + writer.WriteLine($"CpuType: 0x{_header.CpuType:X}"); + writer.WriteLine($"NumberOfCommands: {_header.NumberOfCommands}"); + writer.WriteLine($"SizeOfCommands: {_header.SizeOfCommands} byte(s)"); + + writer.WriteLine("Sections:"); + EnumerateSections((segmentName, section) => + { + string sectionName = section.SectionName.GetString(); + ulong vmAddr = section.GetVMAddress(_header); + ulong size = section.GetSize(_header); + writer.WriteLine($" {segmentName},{sectionName,-16} 0x{vmAddr:X8} - 0x{vmAddr + size:X8}"); + }); + } + + public Dictionary GetSections() + { + Dictionary sectionMap = []; + EnumerateSections((segmentName, section) => + { + string sectionName = section.SectionName.GetString(); + ulong size = section.GetSize(_header); + System.Diagnostics.Debug.Assert(size <= int.MaxValue); + sectionMap[$"{segmentName},{sectionName}"] = (int)size; + }); + return sectionMap; + } + + private unsafe void EnumerateSections(Action callback) + { + long commandsPtr = sizeof(MachHeader); + for (int i = 0; i < _header.NumberOfCommands; i++) + { + Read(commandsPtr, out LoadCommand loadCommand); + + if (loadCommand.GetCommandType(_header) == MachLoadCommandType.Segment64) + { + Read(commandsPtr, out Segment64LoadCommand segment); + uint sectionsCount = segment.GetSectionsCount(_header); + string segmentName = segment.Name.GetString(); + + // Sections come immediately after the segment load command + long sectionPtr = commandsPtr + sizeof(Segment64LoadCommand); + for (uint j = 0; j < sectionsCount; j++) + { + Read(sectionPtr, out Section64LoadCommand section); + callback(segmentName, section); + sectionPtr += sizeof(Section64LoadCommand); + } + } + + commandsPtr += loadCommand.GetCommandSize(_header); + } + } + + private static Machine GetMachineType(uint cpuType) + { + // https://github.com/apple-oss-distributions/xnu/blob/f6217f891ac0bb64f3d375211650a4c1ff8ca1ea/osfmk/mach/machine.h + const uint CPU_TYPE_ARM64 = 0x0100000C; + const uint CPU_TYPE_X86_64 = 0x01000007; + return cpuType switch + { + CPU_TYPE_ARM64 => Machine.Arm64, + CPU_TYPE_X86_64 => Machine.Amd64, + _ => throw new NotSupportedException($"Unsupported MachO CPU type: {cpuType:X8}") + }; + } + + /// + /// Finds a symbol in the symbol table by name. + /// + /// The name of the symbol to find (without leading underscore). + /// The value of the symbol if found. + /// True if the symbol was found, false otherwise. + private unsafe bool TryFindSymbol(string symbolName, out ulong symbolValue) + { + symbolValue = 0; + + // Find the symbol table load command + long commandsPtr = sizeof(MachHeader); + SymbolTableLoadCommand symtabCommand = default; + bool foundSymtab = false; + + for (int i = 0; i < _header.NumberOfCommands; i++) + { + Read(commandsPtr, out LoadCommand loadCommand); + + if (loadCommand.GetCommandType(_header) == MachLoadCommandType.SymbolTable) + { + Read(commandsPtr, out symtabCommand); + foundSymtab = true; + break; + } + + commandsPtr += loadCommand.GetCommandSize(_header); + } + + if (!foundSymtab || symtabCommand.IsDefault) + { + return false; + } + + uint symbolTableOffset = symtabCommand.GetSymbolTableOffset(_header); + uint symbolsCount = symtabCommand.GetSymbolsCount(_header); + uint stringTableOffset = symtabCommand.GetStringTableOffset(_header); + uint stringTableSize = symtabCommand.GetStringTableSize(_header); + + for (uint i = 0; i < symbolsCount; i++) + { + long symOffset = symbolTableOffset + (i * sizeof(NList64)); + + // Read the symbol table entry + Read(symOffset, out NList64 symbol); + + uint strIndex = symbol.GetStringTableIndex(_header); + if (strIndex >= stringTableSize) + { + continue; + } + + // Read symbol name from string table + string name = ReadCString(stringTableOffset + strIndex, stringTableSize - strIndex); + + // Symbol names in Mach-O can have a leading underscore + if (name == symbolName || (name.Length > 0 && name[0] == '_' && name.AsSpan(1).SequenceEqual(symbolName))) + { + symbolValue = symbol.GetValue(_header); + return true; + } + } + + return false; + } + + /// + /// Finds the segment that contains the specified virtual memory address. + /// + /// The virtual memory address to find. + /// The segment containing the VM address if found. + /// True if a containing segment was found, false otherwise. + private unsafe bool TryGetContainingSegment(ulong vmAddress, out Segment64LoadCommand segment) + { + segment = default; + + // Iterate through all load commands to find segments + long commandsPtr = sizeof(MachHeader); + + for (int i = 0; i < _header.NumberOfCommands; i++) + { + Read(commandsPtr, out LoadCommand loadCommand); + + if (loadCommand.GetCommandType(_header) == MachLoadCommandType.Segment64) + { + Read(commandsPtr, out Segment64LoadCommand seg); + + // Check if the VM address falls within this segment + ulong segmentVMAddr = seg.GetVMAddress(_header); + ulong segmentVMSize = seg.GetVMSize(_header); + if (vmAddress >= segmentVMAddr && vmAddress < segmentVMAddr + segmentVMSize) + { + segment = seg; + return true; + } + } + + commandsPtr += loadCommand.GetCommandSize(_header); + } + + return false; + } + + /// + /// Reads a null-terminated C string from the image. + /// + private string ReadCString(uint offset, uint maxLength) + { + if (offset < 0 || offset >= _image.Length) + { + throw new ArgumentOutOfRangeException(nameof(offset)); + } + + // Find the null terminator in the image array + int length; + long end = Math.Min(offset + maxLength, _image.Length); + for (length = 0; offset + length < end; length++) + { + if (_image[offset + length] == 0) + { + break; + } + } + + System.Diagnostics.Debug.Assert(offset <= int.MaxValue); + return System.Text.Encoding.UTF8.GetString(_image, (int)offset, length); + } + + public void Read(long offset, out T result) where T : unmanaged + { + unsafe + { + if (offset < 0 || offset + sizeof(T) > _image.Length) + { + throw new ArgumentOutOfRangeException(nameof(offset)); + } + + fixed (byte* ptr = &_image[offset]) + { + result = Unsafe.ReadUnaligned(ptr); + } + } + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ManifestAssemblyMetadata.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ManifestAssemblyMetadata.cs index a85237661fc0f9..e6c0032a9109cc 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ManifestAssemblyMetadata.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ManifestAssemblyMetadata.cs @@ -25,10 +25,15 @@ internal class ManifestAssemblyMetadata : IAssemblyMetadata /// private readonly MetadataReader _metadataReader; + public ManifestAssemblyMetadata(MetadataReader metadataReader) + { + _metadataReader = metadataReader; + } + public ManifestAssemblyMetadata(PEReader peReader, MetadataReader metadataReader) + : this(metadataReader) { _peReader = peReader; - _metadataReader = metadataReader; } public PEReader ImageReader => _peReader; diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/PEImageReader.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/PEImageReader.cs new file mode 100644 index 00000000000000..739b9022e22e6b --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/PEImageReader.cs @@ -0,0 +1,123 @@ +// 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.IO; +using System.Reflection.PortableExecutable; + +namespace ILCompiler.Reflection.ReadyToRun +{ + /// + /// Wrapper around PEReader that implements IBinaryImageReader + /// + public class PEImageReader : IBinaryImageReader + { + private readonly PEReader _peReader; + + public Machine Machine { get; } + public OperatingSystem OperatingSystem { get; } + public ulong ImageBase => _peReader.PEHeaders.PEHeader.ImageBase; + + public PEImageReader(PEReader peReader) + { + _peReader = peReader; + + // Extract machine and OS from PE header + // The OS is encoded in the machine type + uint rawMachine = (uint)_peReader.PEHeaders.CoffHeader.Machine; + OperatingSystem = OperatingSystem.Unknown; + + foreach (OperatingSystem os in System.Enum.GetValues(typeof(OperatingSystem))) + { + Machine candidateMachine = (Machine)(rawMachine ^ (uint)os); + if (System.Enum.IsDefined(typeof(Machine), candidateMachine)) + { + Machine = candidateMachine; + OperatingSystem = os; + break; + } + } + + if (OperatingSystem == OperatingSystem.Unknown) + { + throw new BadImageFormatException($"Invalid PE Machine type: {rawMachine}"); + } + } + + public ImmutableArray GetEntireImage() => _peReader.GetEntireImage().GetContent(); + + public int GetOffset(int rva) => _peReader.GetOffset(rva); + + public bool TryGetReadyToRunHeader(out int rva, out bool isComposite) + { + if ((_peReader.PEHeaders.CorHeader.Flags & CorFlags.ILLibrary) == 0) + { + // Composite R2R - check for RTR_HEADER export + if (_peReader.TryGetCompositeReadyToRunHeader(out rva)) + { + isComposite = true; + return true; + } + } + else + { + var r2rHeaderDirectory = _peReader.PEHeaders.CorHeader.ManagedNativeHeaderDirectory; + if (r2rHeaderDirectory.Size != 0) + { + rva = r2rHeaderDirectory.RelativeVirtualAddress; + isComposite = false; + return true; + } + } + + rva = 0; + isComposite = false; + return false; + } + + public IAssemblyMetadata GetStandaloneAssemblyMetadata() + => _peReader.HasMetadata ? new StandaloneAssemblyMetadata(_peReader) : null; + + public IAssemblyMetadata GetManifestAssemblyMetadata(System.Reflection.Metadata.MetadataReader manifestReader) + => new ManifestAssemblyMetadata(_peReader, manifestReader); + + public void DumpImageInformation(TextWriter writer) + { + writer.WriteLine($"MetadataSize: {_peReader.PEHeaders.MetadataSize} byte(s)"); + + if (_peReader.PEHeaders.PEHeader is PEHeader header) + { + writer.WriteLine($"SizeOfImage: {header.SizeOfImage} byte(s)"); + writer.WriteLine($"ImageBase: 0x{header.ImageBase:X}"); + writer.WriteLine($"FileAlignment: 0x{header.FileAlignment:X}"); + writer.WriteLine($"SectionAlignment: 0x{header.SectionAlignment:X}"); + } + else + { + writer.WriteLine("No PEHeader"); + } + + writer.WriteLine($"CorHeader.Flags: {_peReader.PEHeaders.CorHeader?.Flags}"); + + writer.WriteLine("Sections:"); + foreach (var section in _peReader.PEHeaders.SectionHeaders) + writer.WriteLine($" {section.Name} {section.VirtualAddress} - {(section.VirtualAddress + section.VirtualSize)}"); + + var exportTable = _peReader.GetExportTable(); + exportTable.DumpToConsoleError(); + } + + public Dictionary GetSections() + { + Dictionary sectionMap = []; + foreach (SectionHeader sectionHeader in _peReader.PEHeaders.SectionHeaders) + { + sectionMap.Add(sectionHeader.Name, sectionHeader.SizeOfRawData); + } + + return sectionMap; + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunMethod.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunMethod.cs index 10ab13a6e9eaad..b5c7b3b6e10436 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunMethod.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunMethod.cs @@ -403,6 +403,7 @@ public ReadyToRunMethod( MethodDefinition methodDef = ComponentReader.MetadataReader.GetMethodDefinition((MethodDefinitionHandle)MethodHandle); if (methodDef.RelativeVirtualAddress != 0) { + System.Diagnostics.Debug.Assert(ComponentReader.ImageReader != null, "Component should be a PE and have an associated PEReader"); MethodBodyBlock mbb = ComponentReader.ImageReader.GetMethodBody(methodDef.RelativeVirtualAddress); if (!mbb.LocalSignature.IsNil) { diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunReader.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunReader.cs index 3e5341c03f83bc..1b184a1a8b61f1 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunReader.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunReader.cs @@ -132,10 +132,10 @@ public sealed class ReadyToRunReader private string _compilerIdentifier; /// - /// Underlying PE image reader is used to access raw PE structures like header - /// or section list. + /// Underlying binary image reader is used to access raw structures like headers + /// and sections. This can be either a PE or MachO image. /// - public PEReader CompositeReader { get; private set; } + public IBinaryImageReader CompositeReader { get; private set; } /// /// Byte array containing the ReadyToRun image @@ -394,7 +394,7 @@ internal IAssemblyMetadata R2RManifestMetadata public ReadyToRunReader(IAssemblyResolver assemblyResolver, IAssemblyMetadata metadata, PEReader peReader, string filename) { _assemblyResolver = assemblyResolver; - CompositeReader = peReader; + CompositeReader = new PEImageReader(peReader); Filename = filename; Initialize(metadata); } @@ -410,7 +410,7 @@ public ReadyToRunReader(IAssemblyResolver assemblyResolver, IAssemblyMetadata me public ReadyToRunReader(IAssemblyResolver assemblyResolver, IAssemblyMetadata metadata, PEReader peReader, string filename, ReadOnlyMemory content) { _assemblyResolver = assemblyResolver; - CompositeReader = peReader; + CompositeReader = new PEImageReader(peReader); Filename = filename; Image = ConvertToArray(content); ImageReader = new NativeReader(new MemoryStream(Image)); @@ -421,7 +421,7 @@ public ReadyToRunReader(IAssemblyResolver assemblyResolver, IAssemblyMetadata me /// Minimally initializes the R2R reader. /// /// Assembly resolver - /// PE file name + /// Binary image file name public unsafe ReadyToRunReader(IAssemblyResolver assemblyResolver, string filename) { _assemblyResolver = assemblyResolver; @@ -433,8 +433,8 @@ public unsafe ReadyToRunReader(IAssemblyResolver assemblyResolver, string filena /// Minimally initializes the R2R reader. /// /// Assembly resolver - /// PE file name - /// PE image content + /// Binary image file name + /// Binary image content public unsafe ReadyToRunReader(IAssemblyResolver assemblyResolver, string filename, ReadOnlyMemory content) { _assemblyResolver = assemblyResolver; @@ -497,47 +497,37 @@ private unsafe void Initialize(IAssemblyMetadata metadata) ImageReader = new NativeReader(new MemoryStream(Image)); byte[] image = Image; ImagePin = new PinningReference(image); - CompositeReader = new PEReader(Unsafe.As>(ref image)); + if (MachO.MachObjectFile.IsMachOImage(Filename)) + { + CompositeReader = new MachO.MachOImageReader(image); + } + else + { + CompositeReader = new PEImageReader(new PEReader(Unsafe.As>(ref image))); + } } else { - ImmutableArray content = CompositeReader.GetEntireImage().GetContent(); + ImmutableArray content = CompositeReader.GetEntireImage(); Image = Unsafe.As, byte[]>(ref content); ImageReader = new NativeReader(new MemoryStream(Image)); ImagePin = new PinningReference(Image); } - if (metadata == null && CompositeReader.HasMetadata) + if (metadata == null) { - metadata = new StandaloneAssemblyMetadata(CompositeReader); + metadata = CompositeReader.GetStandaloneAssemblyMetadata(); } - if (metadata != null) + if (!CompositeReader.TryGetReadyToRunHeader(out _readyToRunHeaderRVA, out _composite)) { - if ((CompositeReader.PEHeaders.CorHeader.Flags & CorFlags.ILLibrary) == 0) - { - if (!TryLocateNativeReadyToRunHeader()) - { - DumpImageInformation(); - - throw new BadImageFormatException("The file is not a ReadyToRun image"); - } - - Debug.Assert(Composite); - } - else - { - _assemblyCache.Add(metadata); - - DirectoryEntry r2rHeaderDirectory = CompositeReader.PEHeaders.CorHeader.ManagedNativeHeaderDirectory; - _readyToRunHeaderRVA = r2rHeaderDirectory.RelativeVirtualAddress; - Debug.Assert(!Composite); - } - + throw new BadImageFormatException("The file is not a ReadyToRun image"); } - else if (!TryLocateNativeReadyToRunHeader()) + + Debug.Assert(metadata != null || _composite); + if (metadata != null && !_composite) { - throw new BadImageFormatException($"ECMA metadata / RTR_HEADER not found in file '{Filename}'"); + _assemblyCache.Add(metadata); } } catch (BadImageFormatException) @@ -554,26 +544,7 @@ internal void DumpImageInformation() { Console.Error.WriteLine($"Image file '{Filename}' information:"); Console.Error.WriteLine($"Size: {Image.Length} byte(s)"); - Console.Error.WriteLine($"MetadataSize: {CompositeReader.PEHeaders.MetadataSize} byte(s)"); - - if (CompositeReader.PEHeaders.PEHeader is PEHeader header) - { - Console.Error.WriteLine($"SizeOfImage: {header.SizeOfImage} byte(s)"); - Console.Error.WriteLine($"ImageBase: 0x{header.ImageBase:X}"); - Console.Error.WriteLine($"FileAlignment: 0x{header.FileAlignment:X}"); - Console.Error.WriteLine($"SectionAlignment: 0x{header.SectionAlignment:X}"); - } - else - Console.Error.WriteLine("No PEHeader"); - - Console.Error.WriteLine($"CorHeader.Flags: {CompositeReader.PEHeaders.CorHeader?.Flags}"); - - Console.Error.WriteLine("Sections:"); - foreach (var section in CompositeReader.PEHeaders.SectionHeaders) - Console.Error.WriteLine($" {section.Name} {section.VirtualAddress} - {(section.VirtualAddress + section.VirtualSize)}"); - - var exportTable = CompositeReader.GetExportTable(); - exportTable.DumpToConsoleError(); + CompositeReader.DumpImageInformation(Console.Error); } catch (Exception exc) { @@ -665,12 +636,7 @@ public IReadOnlyDictionary GetCustomMethodToRuntimeFu return customMethods; } - private bool TryLocateNativeReadyToRunHeader() - { - _composite = CompositeReader.TryGetCompositeReadyToRunHeader(out _readyToRunHeaderRVA); - return _composite; - } private IAssemblyMetadata GetSystemModuleMetadataReader() { @@ -702,21 +668,9 @@ private unsafe void EnsureHeader() { return; } - uint machine = (uint)CompositeReader.PEHeaders.CoffHeader.Machine; - _operatingSystem = OperatingSystem.Unknown; - foreach (OperatingSystem os in Enum.GetValues(typeof(OperatingSystem))) - { - _machine = (Machine)(machine ^ (uint)os); - if (Enum.IsDefined(typeof(Machine), _machine)) - { - _operatingSystem = os; - break; - } - } - if (_operatingSystem == OperatingSystem.Unknown) - { - throw new BadImageFormatException($"Invalid Machine: {machine}"); - } + + _machine = CompositeReader.Machine; + _operatingSystem = CompositeReader.OperatingSystem; switch (_machine) { @@ -738,7 +692,7 @@ private unsafe void EnsureHeader() throw new NotImplementedException(Machine.ToString()); } - _imageBase = CompositeReader.PEHeaders.PEHeader.ImageBase; + _imageBase = CompositeReader.ImageBase; // Initialize R2RHeader Debug.Assert(_readyToRunHeaderRVA != 0); @@ -797,7 +751,8 @@ private unsafe void EnsureManifestReferences() fixed (byte* image = Image) { _manifestReader = new MetadataReader(image + GetOffset(manifestMetadata.RelativeVirtualAddress), manifestMetadata.Size); - _manifestAssemblyMetadata = new ManifestAssemblyMetadata(CompositeReader, _manifestReader); + _manifestAssemblyMetadata = CompositeReader.GetManifestAssemblyMetadata(_manifestReader); + int assemblyRefCount = _manifestReader.GetTableRowCount(TableIndex.AssemblyRef); for (int assemblyRefIndex = 1; assemblyRefIndex <= assemblyRefCount; assemblyRefIndex++) { diff --git a/src/coreclr/tools/r2rdump/R2RDiff.cs b/src/coreclr/tools/r2rdump/R2RDiff.cs index 8e3edd189cf3a8..1101703941919f 100644 --- a/src/coreclr/tools/r2rdump/R2RDiff.cs +++ b/src/coreclr/tools/r2rdump/R2RDiff.cs @@ -133,7 +133,7 @@ private void DiffTitle() /// private void DiffPESections() { - ShowDiff(GetPESectionMap(_leftDumper.Reader), GetPESectionMap(_rightDumper.Reader), "PE sections"); + ShowDiff(GetImageSectionMap(_leftDumper.Reader), GetImageSectionMap(_rightDumper.Reader), "PE sections"); } /// @@ -312,20 +312,13 @@ private void ShowDiff(Dictionary leftObjects, Dictionary - /// Read the PE file section map for a given R2R image. + /// Get the sections for a given R2R image. /// /// R2R image to scan /// - private Dictionary GetPESectionMap(ReadyToRunReader reader) + private Dictionary GetImageSectionMap(ReadyToRunReader reader) { - Dictionary sectionMap = new Dictionary(); - - foreach (SectionHeader sectionHeader in reader.CompositeReader.PEHeaders.SectionHeaders) - { - sectionMap.Add(sectionHeader.Name, sectionHeader.SizeOfRawData); - } - - return sectionMap; + return reader.CompositeReader.GetSections(); } /// diff --git a/src/installer/managed/Microsoft.NET.HostModel/MachO/MachObjectFile.cs b/src/installer/managed/Microsoft.NET.HostModel/MachO/MachObjectFile.cs index 0129a3d00cbe94..407eeeaf022d3b 100644 --- a/src/installer/managed/Microsoft.NET.HostModel/MachO/MachObjectFile.cs +++ b/src/installer/managed/Microsoft.NET.HostModel/MachO/MachObjectFile.cs @@ -38,6 +38,8 @@ internal unsafe partial class MachObjectFile internal EmbeddedSignatureBlob? EmbeddedSignatureBlob => _codeSignatureBlob; + internal MachHeader Header => _header; + private MachObjectFile( MachHeader header, (LinkEditLoadCommand Command, long FileOffset) codeSignatureLC, @@ -191,19 +193,6 @@ or MachMagic.MachHeader64CurrentEndian or MachMagic.MachHeader64OppositeEndian or MachMagic.FatMagicCurrentEndian or MachMagic.FatMagicOppositeEndian; } - public static bool IsMachOImage(string filePath) - { - using (BinaryReader reader = new BinaryReader(File.OpenRead(filePath))) - { - if (reader.BaseStream.Length < 256) // Header size - { - return false; - } - uint magic = reader.ReadUInt32(); - return Enum.IsDefined(typeof(MachMagic), magic); - } - } - /// /// Removes the code signature load command and signature blob from the file if present. /// Returns true and sets to a non-null value if the file is a MachO file and the signature was removed. diff --git a/src/installer/managed/Microsoft.NET.HostModel/Microsoft.NET.HostModel.csproj b/src/installer/managed/Microsoft.NET.HostModel/Microsoft.NET.HostModel.csproj index af27dd32dcc9a8..c5eaaeb864f4a1 100644 --- a/src/installer/managed/Microsoft.NET.HostModel/Microsoft.NET.HostModel.csproj +++ b/src/installer/managed/Microsoft.NET.HostModel/Microsoft.NET.HostModel.csproj @@ -34,6 +34,7 @@ +