< Summary

Line coverage
0%
Covered lines: 0
Uncovered lines: 182
Coverable lines: 182
Total lines: 430
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 72
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.ctor(...)0%16160%
.ctor(...)100%110%
.ctor(...)100%110%
TryCalculateCorHeaderOffset(...)0%440%
SkipDosHeader(...)0%10100%
ReadSectionHeaders(...)0%660%
TryGetDirectoryOffset(...)100%110%
TryGetDirectoryOffset(...)0%880%
GetContainingSectionIndex(...)0%660%
IndexOfSection(...)0%440%
CalculateMetadataLocation(...)0%16160%

File(s)

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Reflection.Metadata/src/System/Reflection/PortableExecutable/PEHeaders.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Collections.Immutable;
 5using System.Diagnostics.CodeAnalysis;
 6using System.IO;
 7using System.Reflection.Internal;
 8using System.Reflection.Metadata;
 9using System.Reflection.Metadata.Ecma335;
 10
 11namespace System.Reflection.PortableExecutable
 12{
 13    /// <summary>
 14    /// An object used to read PE (Portable Executable) and COFF (Common Object File Format) headers from a stream.
 15    /// </summary>
 16    public sealed class PEHeaders
 17    {
 18        private readonly CoffHeader _coffHeader;
 19        private readonly PEHeader? _peHeader;
 20        private readonly ImmutableArray<SectionHeader> _sectionHeaders;
 21        private readonly CorHeader? _corHeader;
 22        private readonly bool _isLoadedImage;
 23
 024        private readonly int _metadataStartOffset = -1;
 25        private readonly int _metadataSize;
 026        private readonly int _coffHeaderStartOffset = -1;
 027        private readonly int _corHeaderStartOffset = -1;
 028        private readonly int _peHeaderStartOffset = -1;
 29
 30        internal const ushort DosSignature = 0x5A4D;     // 'M' 'Z'
 31        internal const int PESignatureOffsetLocation = 0x3C;
 32        internal const uint PESignature = 0x00004550;    // PE00
 33        internal const int PESignatureSize = sizeof(uint);
 34
 35        /// <summary>
 36        /// Reads PE headers from the current location in the stream.
 37        /// </summary>
 38        /// <param name="peStream">Stream containing PE image starting at the stream's current position and ending at th
 39        /// <exception cref="BadImageFormatException">The data read from stream have invalid format.</exception>
 40        /// <exception cref="IOException">Error reading from the stream.</exception>
 41        /// <exception cref="ArgumentException">The stream doesn't support seek operations.</exception>
 42        /// <exception cref="ArgumentNullException"><paramref name="peStream"/> is null.</exception>
 43        public PEHeaders(Stream peStream)
 044           : this(peStream, 0)
 045        {
 046        }
 47
 48        /// <summary>
 49        /// Reads PE headers from the current location in the stream.
 50        /// </summary>
 51        /// <param name="peStream">Stream containing PE image of the given size starting at its current position.</param
 52        /// <param name="size">Size of the PE image.</param>
 53        /// <exception cref="BadImageFormatException">The data read from stream have invalid format.</exception>
 54        /// <exception cref="IOException">Error reading from the stream.</exception>
 55        /// <exception cref="ArgumentException">The stream doesn't support seek operations.</exception>
 56        /// <exception cref="ArgumentNullException"><paramref name="peStream"/> is null.</exception>
 57        /// <exception cref="ArgumentOutOfRangeException">Size is negative or extends past the end of the stream.</excep
 58        public PEHeaders(Stream peStream, int size)
 059            : this(peStream, size, isLoadedImage: false)
 060        {
 061        }
 62
 63        /// <summary>
 64        /// Reads PE headers from the current location in the stream.
 65        /// </summary>
 66        /// <param name="peStream">Stream containing PE image of the given size starting at its current position.</param
 67        /// <param name="size">Size of the PE image.</param>
 68        /// <param name="isLoadedImage">True if the PE image has been loaded into memory by the OS loader.</param>
 69        /// <exception cref="BadImageFormatException">The data read from stream have invalid format.</exception>
 70        /// <exception cref="IOException">Error reading from the stream.</exception>
 71        /// <exception cref="ArgumentException">The stream doesn't support seek operations.</exception>
 72        /// <exception cref="ArgumentNullException"><paramref name="peStream"/> is null.</exception>
 73        /// <exception cref="ArgumentOutOfRangeException">Size is negative or extends past the end of the stream.</excep
 074        public PEHeaders(Stream peStream, int size, bool isLoadedImage)
 075        {
 076            if (peStream is null)
 077            {
 078                Throw.ArgumentNull(nameof(peStream));
 79            }
 80
 081            if (!peStream.CanRead || !peStream.CanSeek)
 082            {
 083                throw new ArgumentException(SR.StreamMustSupportReadAndSeek, nameof(peStream));
 84            }
 85
 086            _isLoadedImage = isLoadedImage;
 87
 088            int actualSize = StreamExtensions.GetAndValidateSize(peStream, size, nameof(peStream));
 089            var reader = new PEBinaryReader(peStream, actualSize);
 90
 091            SkipDosHeader(ref reader, out bool isCoffOnly);
 92
 093            if (isCoffOnly && isLoadedImage)
 094            {
 95                // Only images can be loaded.
 096                throw new BadImageFormatException(SR.InvalidPESignature);
 97            }
 98
 099            _coffHeaderStartOffset = reader.Offset;
 0100            _coffHeader = new CoffHeader(ref reader);
 101
 0102            if (isCoffOnly)
 0103            {
 104                // In COFF files the size of the optional header must be zero.
 0105                if (_coffHeader.SizeOfOptionalHeader != 0)
 0106                {
 0107                    throw new BadImageFormatException(SR.UnknownFileFormat);
 108                }
 0109            }
 110            else
 0111            {
 0112                _peHeaderStartOffset = reader.Offset;
 0113                _peHeader = new PEHeader(ref reader);
 0114            }
 115
 0116            _sectionHeaders = ReadSectionHeaders(ref reader, actualSize);
 117
 0118            if (!isCoffOnly)
 0119            {
 0120                if (TryCalculateCorHeaderOffset(out int offset))
 0121                {
 0122                    _corHeaderStartOffset = offset;
 0123                    reader.Offset = offset;
 0124                    _corHeader = new CorHeader(ref reader);
 0125                }
 0126            }
 127
 0128            CalculateMetadataLocation(actualSize, out _metadataStartOffset, out _metadataSize);
 0129        }
 130
 131        /// <summary>
 132        /// Gets the offset (in bytes) from the start of the PE image to the start of the CLI metadata.
 133        /// or -1 if the image does not contain metadata.
 134        /// </summary>
 135        public int MetadataStartOffset
 136        {
 0137            get { return _metadataStartOffset; }
 138        }
 139
 140        /// <summary>
 141        /// Gets the size of the CLI metadata 0 if the image does not contain metadata.)
 142        /// </summary>
 143        public int MetadataSize
 144        {
 0145            get { return _metadataSize; }
 146        }
 147
 148        /// <summary>
 149        /// Gets the COFF header of the image.
 150        /// </summary>
 151        public CoffHeader CoffHeader
 152        {
 0153            get { return _coffHeader; }
 154        }
 155
 156        /// <summary>
 157        /// Gets the byte offset from the start of the PE image to the start of the COFF header.
 158        /// </summary>
 159        public int CoffHeaderStartOffset
 160        {
 0161            get { return _coffHeaderStartOffset; }
 162        }
 163
 164        /// <summary>
 165        /// Determines if the image is Coff only.
 166        /// </summary>
 167        public bool IsCoffOnly
 168        {
 0169            get { return _peHeader == null; }
 170        }
 171
 172        /// <summary>
 173        /// Gets the PE header of the image or null if the image is COFF only.
 174        /// </summary>
 175        public PEHeader? PEHeader
 176        {
 0177            get { return _peHeader; }
 178        }
 179
 180        /// <summary>
 181        /// Gets the byte offset from the start of the image to
 182        /// </summary>
 183        public int PEHeaderStartOffset
 184        {
 0185            get { return _peHeaderStartOffset; }
 186        }
 187
 188        /// <summary>
 189        /// Gets the PE section headers.
 190        /// </summary>
 191        public ImmutableArray<SectionHeader> SectionHeaders
 192        {
 0193            get { return _sectionHeaders; }
 194        }
 195
 196        /// <summary>
 197        /// Gets the CLI header or null if the image does not have one.
 198        /// </summary>
 199        public CorHeader? CorHeader
 200        {
 0201            get { return _corHeader; }
 202        }
 203
 204        /// <summary>
 205        /// Gets the byte offset from the start of the image to the COR header or -1 if the image does not have one.
 206        /// </summary>
 207        public int CorHeaderStartOffset
 208        {
 0209            get { return _corHeaderStartOffset; }
 210        }
 211
 212        /// <summary>
 213        /// Determines if the image represents a Windows console application.
 214        /// </summary>
 215        public bool IsConsoleApplication
 216        {
 217            get
 0218            {
 0219                return _peHeader != null && _peHeader.Subsystem == Subsystem.WindowsCui;
 0220            }
 221        }
 222
 223        /// <summary>
 224        /// Determines if the image represents a dynamically linked library.
 225        /// </summary>
 226        public bool IsDll
 227        {
 228            get
 0229            {
 0230                return (_coffHeader.Characteristics & Characteristics.Dll) != 0;
 0231            }
 232        }
 233
 234        /// <summary>
 235        /// Determines if the image represents an executable.
 236        /// </summary>
 237        public bool IsExe
 238        {
 239            get
 0240            {
 0241                return (_coffHeader.Characteristics & Characteristics.Dll) == 0;
 0242            }
 243        }
 244
 245        private bool TryCalculateCorHeaderOffset(out int startOffset)
 0246        {
 0247            if (!TryGetDirectoryOffset(_peHeader!.CorHeaderTableDirectory, out startOffset, canCrossSectionBoundary: fal
 0248            {
 0249                startOffset = -1;
 0250                return false;
 251            }
 252
 0253            int length = _peHeader.CorHeaderTableDirectory.Size;
 0254            if (length < COR20Constants.SizeOfCorHeader)
 0255            {
 0256                throw new BadImageFormatException(SR.InvalidCorHeaderSize);
 257            }
 258
 0259            return true;
 0260        }
 261
 262        private static void SkipDosHeader(ref PEBinaryReader reader, out bool isCOFFOnly)
 0263        {
 264            // Look for DOS Signature "MZ"
 0265            ushort dosSig = reader.ReadUInt16();
 266
 0267            if (dosSig != DosSignature)
 0268            {
 269                // If image doesn't start with DOS signature, let's assume it is a
 270                // COFF (Common Object File Format), aka .OBJ file.
 271                // See CLiteWeightStgdbRW::FindObjMetaData in ndp\clr\src\MD\enc\peparse.cpp
 272
 0273                if (dosSig != 0 || reader.ReadUInt16() != 0xffff)
 0274                {
 0275                    isCOFFOnly = true;
 0276                    reader.Offset = 0;
 0277                }
 278                else
 0279                {
 280                    // Might need to handle other formats. Anonymous or LTCG objects, for example.
 0281                    throw new BadImageFormatException(SR.UnknownFileFormat);
 282                }
 0283            }
 284            else
 0285            {
 0286                isCOFFOnly = false;
 0287            }
 288
 0289            if (!isCOFFOnly)
 0290            {
 291                // Skip the DOS Header
 0292                reader.Offset = PESignatureOffsetLocation;
 293
 0294                int ntHeaderOffset = reader.ReadInt32();
 0295                reader.Offset = ntHeaderOffset;
 296
 297                // Look for PESignature "PE\0\0"
 0298                uint ntSignature = reader.ReadUInt32();
 0299                if (ntSignature != PESignature)
 0300                {
 0301                    throw new BadImageFormatException(SR.InvalidPESignature);
 302                }
 0303            }
 0304        }
 305
 306        private ImmutableArray<SectionHeader> ReadSectionHeaders(ref PEBinaryReader reader, int size)
 0307        {
 0308            int numberOfSections = _coffHeader.NumberOfSections;
 0309            if (numberOfSections < 0 || numberOfSections * SectionHeader.Size > size - reader.Offset)
 0310            {
 0311                throw new BadImageFormatException(SR.InvalidNumberOfSections);
 312            }
 313
 0314            var builder = ImmutableArray.CreateBuilder<SectionHeader>(numberOfSections);
 315
 0316            for (int i = 0; i < numberOfSections; i++)
 0317            {
 0318                builder.Add(new SectionHeader(ref reader));
 0319            }
 320
 0321            return builder.MoveToImmutable();
 0322        }
 323
 324        /// <summary>
 325        /// Gets the offset (in bytes) from the start of the image to the given directory data.
 326        /// </summary>
 327        /// <param name="directory">PE directory entry</param>
 328        /// <param name="offset">Offset from the start of the image to the given directory data</param>
 329        /// <returns>True if the directory data is found, false otherwise.</returns>
 330        public bool TryGetDirectoryOffset(DirectoryEntry directory, out int offset)
 0331        {
 0332            return TryGetDirectoryOffset(directory, out offset, canCrossSectionBoundary: true);
 0333        }
 334
 335        internal bool TryGetDirectoryOffset(DirectoryEntry directory, out int offset, bool canCrossSectionBoundary)
 0336        {
 0337            int sectionIndex = GetContainingSectionIndex(directory.RelativeVirtualAddress);
 0338            if (sectionIndex < 0)
 0339            {
 0340                offset = -1;
 0341                return false;
 342            }
 343
 0344            int relativeOffset = directory.RelativeVirtualAddress - _sectionHeaders[sectionIndex].VirtualAddress;
 0345            if (!canCrossSectionBoundary && directory.Size > _sectionHeaders[sectionIndex].VirtualSize - relativeOffset)
 0346            {
 0347                throw new BadImageFormatException(SR.SectionTooSmall);
 348            }
 349
 0350            offset = _isLoadedImage ? directory.RelativeVirtualAddress : _sectionHeaders[sectionIndex].PointerToRawData 
 0351            return true;
 0352        }
 353
 354        /// <summary>
 355        /// Searches sections of the PE image for the one that contains specified Relative Virtual Address.
 356        /// </summary>
 357        /// <param name="relativeVirtualAddress">Address.</param>
 358        /// <returns>
 359        /// Index of the section that contains <paramref name="relativeVirtualAddress"/>,
 360        /// or -1 if there is none.
 361        /// </returns>
 362        public int GetContainingSectionIndex(int relativeVirtualAddress)
 0363        {
 0364            for (int i = 0; i < _sectionHeaders.Length; i++)
 0365            {
 0366                if (_sectionHeaders[i].VirtualAddress <= relativeVirtualAddress &&
 0367                    relativeVirtualAddress < _sectionHeaders[i].VirtualAddress + _sectionHeaders[i].VirtualSize)
 0368                {
 0369                    return i;
 370                }
 0371            }
 372
 0373            return -1;
 0374        }
 375
 376        internal int IndexOfSection(string name)
 0377        {
 0378            for (int i = 0; i < SectionHeaders.Length; i++)
 0379            {
 0380                if (SectionHeaders[i].Name.Equals(name, StringComparison.Ordinal))
 0381                {
 0382                    return i;
 383                }
 0384            }
 385
 0386            return -1;
 0387        }
 388
 389        private void CalculateMetadataLocation(long peImageSize, out int start, out int size)
 0390        {
 0391            if (IsCoffOnly)
 0392            {
 0393                int cormeta = IndexOfSection(".cormeta");
 0394                if (cormeta == -1)
 0395                {
 0396                    start = -1;
 0397                    size = 0;
 0398                    return;
 399                }
 400
 0401                start = SectionHeaders[cormeta].PointerToRawData;
 0402                size = SectionHeaders[cormeta].SizeOfRawData;
 0403            }
 0404            else if (_corHeader == null)
 0405            {
 0406                start = 0;
 0407                size = 0;
 0408                return;
 409            }
 410            else
 0411            {
 0412                if (!TryGetDirectoryOffset(_corHeader.MetadataDirectory, out start, canCrossSectionBoundary: false))
 0413                {
 0414                    throw new BadImageFormatException(SR.MissingDataDirectory);
 415                }
 416
 0417                size = _corHeader.MetadataDirectory.Size;
 0418            }
 419
 0420            if (start < 0 ||
 0421                start >= peImageSize ||
 0422                size <= 0 ||
 0423                start > peImageSize - size)
 0424            {
 0425                throw new BadImageFormatException(SR.InvalidMetadataSectionSpan);
 426            }
 0427        }
 428    }
 429}
 430