File: System\Reflection\PortableExecutable\PEHeaders.cs
Web Access
Project: src\src\libraries\System.Reflection.Metadata\src\System.Reflection.Metadata.csproj (System.Reflection.Metadata)
// 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.Immutable;
using System.IO;
using System.Reflection.Internal;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
 
namespace System.Reflection.PortableExecutable
{
    /// <summary>
    /// An object used to read PE (Portable Executable) and COFF (Common Object File Format) headers from a stream.
    /// </summary>
    public sealed class PEHeaders
    {
        private readonly CoffHeader _coffHeader;
        private readonly PEHeader? _peHeader;
        private readonly ImmutableArray<SectionHeader> _sectionHeaders;
        private readonly CorHeader? _corHeader;
        private readonly bool _isLoadedImage;
 
        private readonly int _metadataStartOffset = -1;
        private readonly int _metadataSize;
        private readonly int _coffHeaderStartOffset = -1;
        private readonly int _corHeaderStartOffset = -1;
        private readonly int _peHeaderStartOffset = -1;
 
        internal const ushort DosSignature = 0x5A4D;     // 'M' 'Z'
        internal const int PESignatureOffsetLocation = 0x3C;
        internal const uint PESignature = 0x00004550;    // PE00
        internal const int PESignatureSize = sizeof(uint);
 
        /// <summary>
        /// Reads PE headers from the current location in the stream.
        /// </summary>
        /// <param name="peStream">Stream containing PE image starting at the stream's current position and ending at the end of the stream.</param>
        /// <exception cref="BadImageFormatException">The data read from stream have invalid format.</exception>
        /// <exception cref="IOException">Error reading from the stream.</exception>
        /// <exception cref="ArgumentException">The stream doesn't support seek operations.</exception>
        /// <exception cref="ArgumentNullException"><paramref name="peStream"/> is null.</exception>
        public PEHeaders(Stream peStream)
           : this(peStream, 0)
        {
        }
 
        /// <summary>
        /// Reads PE headers from the current location in the stream.
        /// </summary>
        /// <param name="peStream">Stream containing PE image of the given size starting at its current position.</param>
        /// <param name="size">Size of the PE image.</param>
        /// <exception cref="BadImageFormatException">The data read from stream have invalid format.</exception>
        /// <exception cref="IOException">Error reading from the stream.</exception>
        /// <exception cref="ArgumentException">The stream doesn't support seek operations.</exception>
        /// <exception cref="ArgumentNullException"><paramref name="peStream"/> is null.</exception>
        /// <exception cref="ArgumentOutOfRangeException">Size is negative or extends past the end of the stream.</exception>
        public PEHeaders(Stream peStream, int size)
            : this(peStream, size, isLoadedImage: false)
        {
        }
 
        /// <summary>
        /// Reads PE headers from the current location in the stream.
        /// </summary>
        /// <param name="peStream">Stream containing PE image of the given size starting at its current position.</param>
        /// <param name="size">Size of the PE image.</param>
        /// <param name="isLoadedImage">True if the PE image has been loaded into memory by the OS loader.</param>
        /// <exception cref="BadImageFormatException">The data read from stream have invalid format.</exception>
        /// <exception cref="IOException">Error reading from the stream.</exception>
        /// <exception cref="ArgumentException">The stream doesn't support seek operations.</exception>
        /// <exception cref="ArgumentNullException"><paramref name="peStream"/> is null.</exception>
        /// <exception cref="ArgumentOutOfRangeException">Size is negative or extends past the end of the stream.</exception>
        public PEHeaders(Stream peStream, int size, bool isLoadedImage)
        {
            if (peStream is null)
            {
                Throw.ArgumentNull(nameof(peStream));
            }
 
            if (!peStream.CanRead || !peStream.CanSeek)
            {
                throw new ArgumentException(SR.StreamMustSupportReadAndSeek, nameof(peStream));
            }
 
            _isLoadedImage = isLoadedImage;
 
            int actualSize = StreamExtensions.GetAndValidateSize(peStream, size, nameof(peStream));
            var reader = new PEBinaryReader(peStream, actualSize);
 
            bool isCoffOnly;
            SkipDosHeader(ref reader, out isCoffOnly);
 
            _coffHeaderStartOffset = reader.CurrentOffset;
            _coffHeader = new CoffHeader(ref reader);
 
            if (!isCoffOnly)
            {
                _peHeaderStartOffset = reader.CurrentOffset;
                _peHeader = new PEHeader(ref reader);
            }
 
            _sectionHeaders = this.ReadSectionHeaders(ref reader);
 
            if (!isCoffOnly)
            {
                int offset;
                if (TryCalculateCorHeaderOffset(out offset))
                {
                    _corHeaderStartOffset = offset;
                    reader.Seek(offset);
                    _corHeader = new CorHeader(ref reader);
                }
            }
 
            CalculateMetadataLocation(actualSize, out _metadataStartOffset, out _metadataSize);
        }
 
        /// <summary>
        /// Gets the offset (in bytes) from the start of the PE image to the start of the CLI metadata.
        /// or -1 if the image does not contain metadata.
        /// </summary>
        public int MetadataStartOffset
        {
            get { return _metadataStartOffset; }
        }
 
        /// <summary>
        /// Gets the size of the CLI metadata 0 if the image does not contain metadata.)
        /// </summary>
        public int MetadataSize
        {
            get { return _metadataSize; }
        }
 
        /// <summary>
        /// Gets the COFF header of the image.
        /// </summary>
        public CoffHeader CoffHeader
        {
            get { return _coffHeader; }
        }
 
        /// <summary>
        /// Gets the byte offset from the start of the PE image to the start of the COFF header.
        /// </summary>
        public int CoffHeaderStartOffset
        {
            get { return _coffHeaderStartOffset; }
        }
 
        /// <summary>
        /// Determines if the image is Coff only.
        /// </summary>
        public bool IsCoffOnly
        {
            get { return _peHeader == null; }
        }
 
        /// <summary>
        /// Gets the PE header of the image or null if the image is COFF only.
        /// </summary>
        public PEHeader? PEHeader
        {
            get { return _peHeader; }
        }
 
        /// <summary>
        /// Gets the byte offset from the start of the image to
        /// </summary>
        public int PEHeaderStartOffset
        {
            get { return _peHeaderStartOffset; }
        }
 
        /// <summary>
        /// Gets the PE section headers.
        /// </summary>
        public ImmutableArray<SectionHeader> SectionHeaders
        {
            get { return _sectionHeaders; }
        }
 
        /// <summary>
        /// Gets the CLI header or null if the image does not have one.
        /// </summary>
        public CorHeader? CorHeader
        {
            get { return _corHeader; }
        }
 
        /// <summary>
        /// Gets the byte offset from the start of the image to the COR header or -1 if the image does not have one.
        /// </summary>
        public int CorHeaderStartOffset
        {
            get { return _corHeaderStartOffset; }
        }
 
        /// <summary>
        /// Determines if the image represents a Windows console application.
        /// </summary>
        public bool IsConsoleApplication
        {
            get
            {
                return _peHeader != null && _peHeader.Subsystem == Subsystem.WindowsCui;
            }
        }
 
        /// <summary>
        /// Determines if the image represents a dynamically linked library.
        /// </summary>
        public bool IsDll
        {
            get
            {
                return (_coffHeader.Characteristics & Characteristics.Dll) != 0;
            }
        }
 
        /// <summary>
        /// Determines if the image represents an executable.
        /// </summary>
        public bool IsExe
        {
            get
            {
                return (_coffHeader.Characteristics & Characteristics.Dll) == 0;
            }
        }
 
        private bool TryCalculateCorHeaderOffset(out int startOffset)
        {
            if (!TryGetDirectoryOffset(_peHeader!.CorHeaderTableDirectory, out startOffset, canCrossSectionBoundary: false))
            {
                startOffset = -1;
                return false;
            }
 
            int length = _peHeader.CorHeaderTableDirectory.Size;
            if (length < COR20Constants.SizeOfCorHeader)
            {
                throw new BadImageFormatException(SR.InvalidCorHeaderSize);
            }
 
            return true;
        }
 
        private static void SkipDosHeader(ref PEBinaryReader reader, out bool isCOFFOnly)
        {
            // Look for DOS Signature "MZ"
            ushort dosSig = reader.ReadUInt16();
 
            if (dosSig != DosSignature)
            {
                // If image doesn't start with DOS signature, let's assume it is a
                // COFF (Common Object File Format), aka .OBJ file.
                // See CLiteWeightStgdbRW::FindObjMetaData in ndp\clr\src\MD\enc\peparse.cpp
 
                if (dosSig != 0 || reader.ReadUInt16() != 0xffff)
                {
                    isCOFFOnly = true;
                    reader.Seek(0);
                }
                else
                {
                    // Might need to handle other formats. Anonymous or LTCG objects, for example.
                    throw new BadImageFormatException(SR.UnknownFileFormat);
                }
            }
            else
            {
                isCOFFOnly = false;
            }
 
            if (!isCOFFOnly)
            {
                // Skip the DOS Header
                reader.Seek(PESignatureOffsetLocation);
 
                int ntHeaderOffset = reader.ReadInt32();
                reader.Seek(ntHeaderOffset);
 
                // Look for PESignature "PE\0\0"
                uint ntSignature = reader.ReadUInt32();
                if (ntSignature != PESignature)
                {
                    throw new BadImageFormatException(SR.InvalidPESignature);
                }
            }
        }
 
        private ImmutableArray<SectionHeader> ReadSectionHeaders(ref PEBinaryReader reader)
        {
            int numberOfSections = _coffHeader.NumberOfSections;
            if (numberOfSections < 0)
            {
                throw new BadImageFormatException(SR.InvalidNumberOfSections);
            }
 
            var builder = ImmutableArray.CreateBuilder<SectionHeader>(numberOfSections);
 
            for (int i = 0; i < numberOfSections; i++)
            {
                builder.Add(new SectionHeader(ref reader));
            }
 
            return builder.MoveToImmutable();
        }
 
        /// <summary>
        /// Gets the offset (in bytes) from the start of the image to the given directory data.
        /// </summary>
        /// <param name="directory">PE directory entry</param>
        /// <param name="offset">Offset from the start of the image to the given directory data</param>
        /// <returns>True if the directory data is found, false otherwise.</returns>
        public bool TryGetDirectoryOffset(DirectoryEntry directory, out int offset)
        {
            return TryGetDirectoryOffset(directory, out offset, canCrossSectionBoundary: true);
        }
 
        internal bool TryGetDirectoryOffset(DirectoryEntry directory, out int offset, bool canCrossSectionBoundary)
        {
            int sectionIndex = GetContainingSectionIndex(directory.RelativeVirtualAddress);
            if (sectionIndex < 0)
            {
                offset = -1;
                return false;
            }
 
            int relativeOffset = directory.RelativeVirtualAddress - _sectionHeaders[sectionIndex].VirtualAddress;
            if (!canCrossSectionBoundary && directory.Size > _sectionHeaders[sectionIndex].VirtualSize - relativeOffset)
            {
                throw new BadImageFormatException(SR.SectionTooSmall);
            }
 
            offset = _isLoadedImage ? directory.RelativeVirtualAddress : _sectionHeaders[sectionIndex].PointerToRawData + relativeOffset;
            return true;
        }
 
        /// <summary>
        /// Searches sections of the PE image for the one that contains specified Relative Virtual Address.
        /// </summary>
        /// <param name="relativeVirtualAddress">Address.</param>
        /// <returns>
        /// Index of the section that contains <paramref name="relativeVirtualAddress"/>,
        /// or -1 if there is none.
        /// </returns>
        public int GetContainingSectionIndex(int relativeVirtualAddress)
        {
            for (int i = 0; i < _sectionHeaders.Length; i++)
            {
                if (_sectionHeaders[i].VirtualAddress <= relativeVirtualAddress &&
                    relativeVirtualAddress < _sectionHeaders[i].VirtualAddress + _sectionHeaders[i].VirtualSize)
                {
                    return i;
                }
            }
 
            return -1;
        }
 
        internal int IndexOfSection(string name)
        {
            for (int i = 0; i < SectionHeaders.Length; i++)
            {
                if (SectionHeaders[i].Name.Equals(name, StringComparison.Ordinal))
                {
                    return i;
                }
            }
 
            return -1;
        }
 
        private void CalculateMetadataLocation(long peImageSize, out int start, out int size)
        {
            if (IsCoffOnly)
            {
                int cormeta = IndexOfSection(".cormeta");
                if (cormeta == -1)
                {
                    start = -1;
                    size = 0;
                    return;
                }
 
                if (_isLoadedImage)
                {
                    start = SectionHeaders[cormeta].VirtualAddress;
                    size = SectionHeaders[cormeta].VirtualSize;
                }
                else
                {
                    start = SectionHeaders[cormeta].PointerToRawData;
                    size = SectionHeaders[cormeta].SizeOfRawData;
                }
            }
            else if (_corHeader == null)
            {
                start = 0;
                size = 0;
                return;
            }
            else
            {
                if (!TryGetDirectoryOffset(_corHeader.MetadataDirectory, out start, canCrossSectionBoundary: false))
                {
                    throw new BadImageFormatException(SR.MissingDataDirectory);
                }
 
                size = _corHeader.MetadataDirectory.Size;
            }
 
            if (start < 0 ||
                start >= peImageSize ||
                size <= 0 ||
                start > peImageSize - size)
            {
                throw new BadImageFormatException(SR.InvalidMetadataSectionSpan);
            }
        }
    }
}