< Summary

Line coverage
0%
Covered lines: 0
Uncovered lines: 464
Coverable lines: 464
Total lines: 1027
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 164
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Reflection.Metadata/src/System/Reflection/PortableExecutable/PEReader.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;
 6using System.Diagnostics.CodeAnalysis;
 7using System.IO;
 8using System.Reflection.Internal;
 9using System.Reflection.Metadata;
 10using System.Runtime.ExceptionServices;
 11using System.Runtime.InteropServices;
 12using System.Threading;
 13using ImmutableArrayExtensions = System.Linq.ImmutableArrayExtensions;
 14
 15namespace System.Reflection.PortableExecutable
 16{
 17    /// <summary>
 18    /// Portable Executable format reader.
 19    /// </summary>
 20    /// <remarks>
 21    /// The implementation is thread-safe, that is multiple threads can read data from the reader in parallel.
 22    /// Disposal of the reader is not thread-safe (see <see cref="Dispose"/>).
 23    /// </remarks>
 24    public sealed partial class PEReader : IDisposable
 25    {
 26        /// <summary>
 27        /// True if the PE image has been loaded into memory by the OS loader.
 28        /// </summary>
 029        public bool IsLoadedImage { get; }
 30
 31        // May be null in the event that the entire image is not
 32        // deemed necessary and we have been instructed to read
 33        // the image contents without being lazy.
 34        //
 35        // _lazyPEHeaders are not null in that case.
 36        private MemoryBlockProvider? _peImage;
 37
 38        // If we read the data from the image lazily (peImage != null) we defer reading the PE headers.
 39        private PEHeaders? _lazyPEHeaders;
 40
 41        private AbstractMemoryBlock? _lazyMetadataBlock;
 42        private AbstractMemoryBlock? _lazyImageBlock;
 43        private AbstractMemoryBlock?[]? _lazyPESectionBlocks;
 44
 45        /// <summary>
 46        /// Creates a Portable Executable reader over a PE image stored in memory.
 47        /// </summary>
 48        /// <param name="peImage">Pointer to the start of the PE image.</param>
 49        /// <param name="size">The size of the PE image.</param>
 50        /// <exception cref="ArgumentNullException"><paramref name="peImage"/> is <see cref="IntPtr.Zero"/>.</exception>
 51        /// <exception cref="ArgumentOutOfRangeException"><paramref name="size"/> is negative.</exception>
 52        /// <remarks>
 53        /// The memory is owned by the caller and not released on disposal of the <see cref="PEReader"/>.
 54        /// The caller is responsible for keeping the memory alive and unmodified throughout the lifetime of the <see cr
 55        /// The content of the image is not read during the construction of the <see cref="PEReader"/>
 56        /// </remarks>
 57        public unsafe PEReader(byte* peImage, int size)
 058            : this(peImage, size, isLoadedImage: false)
 059        {
 060        }
 61
 62        /// <summary>
 63        /// Creates a Portable Executable reader over a PE image stored in memory.
 64        /// </summary>
 65        /// <param name="peImage">Pointer to the start of the PE image.</param>
 66        /// <param name="size">The size of the PE image.</param>
 67        /// <param name="isLoadedImage">True if the PE image has been loaded into memory by the OS loader.</param>
 68        /// <exception cref="ArgumentNullException"><paramref name="peImage"/> is <see cref="IntPtr.Zero"/>.</exception>
 69        /// <exception cref="ArgumentOutOfRangeException"><paramref name="size"/> is negative.</exception>
 70        /// <remarks>
 71        /// The memory is owned by the caller and not released on disposal of the <see cref="PEReader"/>.
 72        /// The caller is responsible for keeping the memory alive and unmodified throughout the lifetime of the <see cr
 73        /// The content of the image is not read during the construction of the <see cref="PEReader"/>
 74        /// </remarks>
 075        public unsafe PEReader(byte* peImage, int size, bool isLoadedImage)
 076        {
 077            if (peImage is null)
 078            {
 079                Throw.ArgumentNull(nameof(peImage));
 80            }
 81
 082            if (size < 0)
 083            {
 084                throw new ArgumentOutOfRangeException(nameof(size));
 85            }
 86
 087            _peImage = new ExternalMemoryBlockProvider(peImage, size);
 088            IsLoadedImage = isLoadedImage;
 089        }
 90
 91        /// <summary>
 92        /// Creates a Portable Executable reader over a PE image stored in a stream.
 93        /// </summary>
 94        /// <param name="peStream">PE image stream.</param>
 95        /// <exception cref="ArgumentNullException"><paramref name="peStream"/> is null.</exception>
 96        /// <remarks>
 97        /// Ownership of the stream is transferred to the <see cref="PEReader"/> upon successful validation of construct
 98        /// disposed by the <see cref="PEReader"/> and the caller must not manipulate it.
 99        /// </remarks>
 100        public PEReader(Stream peStream)
 0101            : this(peStream, PEStreamOptions.Default)
 0102        {
 0103        }
 104
 105        /// <summary>
 106        /// Creates a Portable Executable reader over a PE image stored in a stream beginning at its current position an
 107        /// </summary>
 108        /// <param name="peStream">PE image stream.</param>
 109        /// <param name="options">
 110        /// Options specifying how sections of the PE image are read from the stream.
 111        ///
 112        /// Unless <see cref="PEStreamOptions.LeaveOpen"/> is specified, ownership of the stream is transferred to the <
 113        /// upon successful argument validation. It will be disposed by the <see cref="PEReader"/> and the caller must n
 114        ///
 115        /// Unless <see cref="PEStreamOptions.PrefetchMetadata"/> or <see cref="PEStreamOptions.PrefetchEntireImage"/> i
 116        /// is read from the stream during the construction of the <see cref="PEReader"/>. Furthermore, the stream must 
 117        /// by caller while the <see cref="PEReader"/> is alive and undisposed.
 118        ///
 119        /// If <see cref="PEStreamOptions.PrefetchMetadata"/> or <see cref="PEStreamOptions.PrefetchEntireImage"/>, the 
 120        /// will have read all of the data requested during construction. As such, if <see cref="PEStreamOptions.LeaveOp
 121        /// specified, the caller retains full ownership of the stream and is assured that it will not be manipulated by
 122        /// after construction.
 123        /// </param>
 124        /// <exception cref="ArgumentNullException"><paramref name="peStream"/> is null.</exception>
 125        /// <exception cref="ArgumentOutOfRangeException"><paramref name="options"/> has an invalid value.</exception>
 126        /// <exception cref="IOException">Error reading from the stream (only when prefetching data).</exception>
 127        /// <exception cref="BadImageFormatException"><see cref="PEStreamOptions.PrefetchMetadata"/> is specified and th
 128        public PEReader(Stream peStream, PEStreamOptions options)
 0129            : this(peStream, options, 0)
 0130        {
 0131        }
 132
 133        /// <summary>
 134        /// Creates a Portable Executable reader over a PE image of the given size beginning at the stream's current pos
 135        /// </summary>
 136        /// <param name="peStream">PE image stream.</param>
 137        /// <param name="size">PE image size.</param>
 138        /// <param name="options">
 139        /// Options specifying how sections of the PE image are read from the stream.
 140        ///
 141        /// Unless <see cref="PEStreamOptions.LeaveOpen"/> is specified, ownership of the stream is transferred to the <
 142        /// upon successful argument validation. It will be disposed by the <see cref="PEReader"/> and the caller must n
 143        ///
 144        /// Unless <see cref="PEStreamOptions.PrefetchMetadata"/> or <see cref="PEStreamOptions.PrefetchEntireImage"/> i
 145        /// is read from the stream during the construction of the <see cref="PEReader"/>. Furthermore, the stream must 
 146        /// by caller while the <see cref="PEReader"/> is alive and undisposed.
 147        ///
 148        /// If <see cref="PEStreamOptions.PrefetchMetadata"/> or <see cref="PEStreamOptions.PrefetchEntireImage"/>, the 
 149        /// will have read all of the data requested during construction. As such, if <see cref="PEStreamOptions.LeaveOp
 150        /// specified, the caller retains full ownership of the stream and is assured that it will not be manipulated by
 151        /// after construction.
 152        /// </param>
 153        /// <exception cref="ArgumentOutOfRangeException">Size is negative or extends past the end of the stream.</excep
 154        /// <exception cref="IOException">Error reading from the stream (only when prefetching data).</exception>
 155        /// <exception cref="BadImageFormatException"><see cref="PEStreamOptions.PrefetchMetadata"/> is specified and th
 0156        public unsafe PEReader(Stream peStream, PEStreamOptions options, int size)
 0157        {
 0158            if (peStream is null)
 0159            {
 0160                Throw.ArgumentNull(nameof(peStream));
 161            }
 162
 0163            if (!peStream.CanRead || !peStream.CanSeek)
 0164            {
 0165                throw new ArgumentException(SR.StreamMustSupportReadAndSeek, nameof(peStream));
 166            }
 167
 0168            if (!options.IsValid())
 0169            {
 0170                throw new ArgumentOutOfRangeException(nameof(options));
 171            }
 172
 0173            IsLoadedImage = (options & PEStreamOptions.IsLoadedImage) != 0;
 174
 0175            long start = peStream.Position;
 0176            int actualSize = StreamExtensions.GetAndValidateSize(peStream, size, nameof(peStream));
 177
 0178            bool closeStream = true;
 179            try
 0180            {
 0181                if ((options & (PEStreamOptions.PrefetchMetadata | PEStreamOptions.PrefetchEntireImage)) == 0)
 0182                {
 0183                    _peImage = new StreamMemoryBlockProvider(peStream, start, actualSize, (options & PEStreamOptions.Lea
 0184                    closeStream = false;
 0185                }
 186                else
 0187                {
 188                    // Read in the entire image or metadata blob:
 0189                    if ((options & PEStreamOptions.PrefetchEntireImage) != 0)
 0190                    {
 0191                        var imageBlock = StreamMemoryBlockProvider.ReadMemoryBlockNoLock(peStream, start, actualSize);
 0192                        _lazyImageBlock = imageBlock;
 0193                        _peImage = new ExternalMemoryBlockProvider(imageBlock.Pointer, imageBlock.Size);
 194
 195                        // if the caller asked for metadata initialize the PE headers (calculates metadata offset):
 0196                        if ((options & PEStreamOptions.PrefetchMetadata) != 0)
 0197                        {
 0198                            _lazyPEHeaders = new PEHeaders(imageBlock.GetStream(), imageBlock.Size, IsLoadedImage);
 0199                        }
 0200                    }
 201                    else
 0202                    {
 203                        // The peImage is left null, but the lazyMetadataBlock is initialized up front.
 0204                        _lazyPEHeaders = new PEHeaders(peStream, actualSize, IsLoadedImage);
 205
 0206                        if (_lazyPEHeaders.MetadataStartOffset != -1)
 0207                        {
 0208                            _lazyMetadataBlock = StreamMemoryBlockProvider.ReadMemoryBlockNoLock(peStream, start + _lazy
 0209                        }
 0210                    }
 211                    // We read all we need, the stream is going to be closed.
 0212                }
 0213            }
 214            finally
 0215            {
 0216                if (closeStream && (options & PEStreamOptions.LeaveOpen) == 0)
 0217                {
 0218                    peStream.Dispose();
 0219                }
 0220            }
 0221        }
 222
 223        /// <summary>
 224        /// Creates a Portable Executable reader over a PE image stored in a byte array.
 225        /// </summary>
 226        /// <param name="peImage">PE image.</param>
 227        /// <remarks>
 228        /// The content of the image is not read during the construction of the <see cref="PEReader"/>
 229        /// </remarks>
 230        /// <exception cref="ArgumentNullException"><paramref name="peImage"/> is null.</exception>
 0231        public PEReader(ImmutableArray<byte> peImage)
 0232        {
 0233            if (peImage.IsDefault)
 0234            {
 0235                Throw.ArgumentNull(nameof(peImage));
 236            }
 237
 0238            _peImage = new ByteArrayMemoryProvider(peImage);
 0239        }
 240
 241        /// <summary>
 242        /// Disposes all memory allocated by the reader.
 243        /// </summary>
 244        /// <remarks>
 245        /// <see cref="Dispose"/>  can be called multiple times (but not in parallel).
 246        /// It is not safe to call <see cref="Dispose"/> in parallel with any other operation on the <see cref="PEReader
 247        /// or reading from <see cref="PEMemoryBlock"/>s retrieved from the reader.
 248        /// </remarks>
 249        public void Dispose()
 0250        {
 0251            _lazyPEHeaders = null;
 252
 0253            _peImage?.Dispose();
 0254            _peImage = null;
 255
 0256            _lazyImageBlock?.Dispose();
 0257            _lazyImageBlock = null;
 258
 0259            _lazyMetadataBlock?.Dispose();
 0260            _lazyMetadataBlock = null;
 261
 0262            var peSectionBlocks = _lazyPESectionBlocks;
 0263            if (peSectionBlocks != null)
 0264            {
 0265                foreach (var block in peSectionBlocks)
 0266                {
 0267                    block?.Dispose();
 0268                }
 269
 0270                _lazyPESectionBlocks = null;
 0271            }
 0272        }
 273
 274        private MemoryBlockProvider GetPEImage()
 0275        {
 0276            var peImage = _peImage;
 0277            if (peImage == null)
 0278            {
 0279                if (_lazyPEHeaders == null)
 0280                {
 0281                    Throw.PEReaderDisposed();
 282                }
 283
 0284                Throw.InvalidOperation_PEImageNotAvailable();
 285            }
 286
 0287            return peImage;
 0288        }
 289
 290        /// <summary>
 291        /// Gets the PE headers.
 292        /// </summary>
 293        /// <exception cref="BadImageFormatException">The headers contain invalid data.</exception>
 294        /// <exception cref="IOException">Error reading from the stream.</exception>
 295        public PEHeaders PEHeaders
 296        {
 297            get
 0298            {
 0299                if (_lazyPEHeaders == null)
 0300                {
 0301                    InitializePEHeaders();
 0302                }
 303
 0304                return _lazyPEHeaders;
 0305            }
 306        }
 307
 308        /// <exception cref="IOException">Error reading from the stream.</exception>
 309        [MemberNotNull(nameof(_lazyPEHeaders))]
 310        private void InitializePEHeaders()
 0311        {
 0312            MemoryBlockProvider peImage = GetPEImage();
 313
 314            PEHeaders headers;
 315            // If the PE image is backed by a stream, use that to read the headers.
 0316            if (peImage.TryGetUnderlyingStream(out Stream? stream, out long imageStart, out int imageSize, out object? s
 0317            {
 0318                lock (streamGuard)
 0319                {
 0320                    Debug.Assert(imageStart >= 0 && imageStart <= stream.Length);
 0321                    stream.Seek(imageStart, SeekOrigin.Begin);
 0322                    headers = new PEHeaders(stream, imageSize, IsLoadedImage);
 0323                }
 0324            }
 325            // Otherwise, get the memory block and wrap it in a stream.
 326            else
 0327            {
 328                // No need to acquire any lock here; GetStream() creates a new stream.
 0329                AbstractMemoryBlock memoryBlock = peImage.GetMemoryBlock();
 0330                headers = new PEHeaders(memoryBlock.GetStream(), memoryBlock.Size, IsLoadedImage);
 0331            }
 332
 0333            Interlocked.CompareExchange(ref _lazyPEHeaders, headers, null);
 0334        }
 335
 336        /// <summary>
 337        /// Returns a view of the entire image as a pointer and length.
 338        /// </summary>
 339        /// <exception cref="InvalidOperationException">PE image not available.</exception>
 340        private AbstractMemoryBlock GetEntireImageBlock()
 0341        {
 0342            if (_lazyImageBlock == null)
 0343            {
 0344                var newBlock = GetPEImage().GetMemoryBlock();
 0345                if (Interlocked.CompareExchange(ref _lazyImageBlock, newBlock, null) != null)
 0346                {
 347                    // another thread created the block already, we need to dispose ours:
 0348                    newBlock.Dispose();
 0349                }
 0350            }
 351
 0352            return _lazyImageBlock;
 0353        }
 354
 355        /// <exception cref="IOException">IO error while reading from the underlying stream.</exception>
 356        /// <exception cref="InvalidOperationException">PE image doesn't have metadata.</exception>
 357        private AbstractMemoryBlock GetMetadataBlock()
 0358        {
 0359            if (!HasMetadata)
 0360            {
 0361                throw new InvalidOperationException(SR.PEImageDoesNotHaveMetadata);
 362            }
 363
 0364            if (_lazyMetadataBlock == null)
 0365            {
 0366                var newBlock = GetPEImage().GetMemoryBlock(PEHeaders.MetadataStartOffset, PEHeaders.MetadataSize);
 0367                if (Interlocked.CompareExchange(ref _lazyMetadataBlock, newBlock, null) != null)
 0368                {
 369                    // another thread created the block already, we need to dispose ours:
 0370                    newBlock.Dispose();
 0371                }
 0372            }
 373
 0374            return _lazyMetadataBlock;
 0375        }
 376
 377        /// <exception cref="IOException">IO error while reading from the underlying stream.</exception>
 378        /// <exception cref="InvalidOperationException">PE image not available.</exception>
 379        private AbstractMemoryBlock GetPESectionBlock(int index)
 0380        {
 0381            Debug.Assert(index >= 0 && index < PEHeaders.SectionHeaders.Length);
 382
 0383            var peImage = GetPEImage();
 384
 0385            if (_lazyPESectionBlocks == null)
 0386            {
 0387                Interlocked.CompareExchange(ref _lazyPESectionBlocks, new AbstractMemoryBlock[PEHeaders.SectionHeaders.L
 0388            }
 389
 0390            AbstractMemoryBlock? existingBlock = Volatile.Read(ref _lazyPESectionBlocks[index]);
 0391            if (existingBlock != null)
 0392            {
 0393                return existingBlock;
 394            }
 395
 396            AbstractMemoryBlock newBlock;
 0397            if (IsLoadedImage)
 0398            {
 0399                newBlock = peImage.GetMemoryBlock(
 0400                    PEHeaders.SectionHeaders[index].VirtualAddress,
 0401                    PEHeaders.SectionHeaders[index].VirtualSize);
 0402            }
 403            else
 0404            {
 405                // Virtual size can be smaller than size in the image
 406                // since the size in the image is aligned.
 407                // Trim the alignment.
 408                //
 409                // Virtual size can also be larger than size in the image.
 410                // When loaded sizeInImage bytes are mapped from the image
 411                // and the rest of the bytes are zeroed out.
 412                // Only return data stored in the image.
 413
 0414                int size = Math.Min(
 0415                    PEHeaders.SectionHeaders[index].VirtualSize,
 0416                    PEHeaders.SectionHeaders[index].SizeOfRawData);
 417
 0418                newBlock = peImage.GetMemoryBlock(PEHeaders.SectionHeaders[index].PointerToRawData, size);
 0419            }
 420
 0421            if (Interlocked.CompareExchange(ref _lazyPESectionBlocks[index], newBlock, null) != null)
 0422            {
 423                // another thread created the block already, we need to dispose ours:
 0424                newBlock.Dispose();
 0425            }
 426
 0427            return _lazyPESectionBlocks[index]!;
 0428        }
 429
 430        /// <summary>
 431        /// Return true if the reader can access the entire PE image.
 432        /// </summary>
 433        /// <remarks>
 434        /// Returns false if the <see cref="PEReader"/> is constructed from a stream and only part of it is prefetched i
 435        /// </remarks>
 0436        public bool IsEntireImageAvailable => _lazyImageBlock != null || _peImage != null;
 437
 438        /// <summary>
 439        /// Gets a pointer to and size of the PE image if available (<see cref="IsEntireImageAvailable"/>).
 440        /// </summary>
 441        /// <exception cref="InvalidOperationException">The entire PE image is not available.</exception>
 442        public PEMemoryBlock GetEntireImage()
 0443        {
 0444            return new PEMemoryBlock(GetEntireImageBlock());
 0445        }
 446
 447        /// <summary>
 448        /// Returns true if the PE image contains CLI metadata.
 449        /// </summary>
 450        /// <exception cref="BadImageFormatException">The PE headers contain invalid data.</exception>
 451        /// <exception cref="IOException">Error reading from the underlying stream.</exception>
 452        public bool HasMetadata
 453        {
 0454            get { return PEHeaders.MetadataSize > 0; }
 455        }
 456
 457        /// <summary>
 458        /// Loads PE section that contains CLI metadata.
 459        /// </summary>
 460        /// <exception cref="InvalidOperationException">The PE image doesn't contain metadata (<see cref="HasMetadata"/>
 461        /// <exception cref="BadImageFormatException">The PE headers contain invalid data.</exception>
 462        /// <exception cref="IOException">IO error while reading from the underlying stream.</exception>
 463        public PEMemoryBlock GetMetadata()
 0464        {
 0465            return new PEMemoryBlock(GetMetadataBlock());
 0466        }
 467
 468        /// <summary>
 469        /// Loads PE section that contains the specified <paramref name="relativeVirtualAddress"/> into memory
 470        /// and returns a memory block that starts at <paramref name="relativeVirtualAddress"/> and ends at the end of t
 471        /// </summary>
 472        /// <param name="relativeVirtualAddress">Relative Virtual Address of the data to read.</param>
 473        /// <returns>
 474        /// An empty block if <paramref name="relativeVirtualAddress"/> doesn't represent a location in any of the PE se
 475        /// </returns>
 476        /// <exception cref="BadImageFormatException">The PE headers contain invalid data.</exception>
 477        /// <exception cref="IOException">IO error while reading from the underlying stream.</exception>
 478        /// <exception cref="InvalidOperationException">PE image not available.</exception>
 479        /// <exception cref="ArgumentOutOfRangeException"><paramref name="relativeVirtualAddress"/> is negative.</except
 480        public PEMemoryBlock GetSectionData(int relativeVirtualAddress)
 0481        {
 0482            if (relativeVirtualAddress < 0)
 0483            {
 0484                Throw.ArgumentOutOfRange(nameof(relativeVirtualAddress));
 485            }
 486
 0487            int sectionIndex = PEHeaders.GetContainingSectionIndex(relativeVirtualAddress);
 0488            if (sectionIndex < 0)
 0489            {
 0490                return default(PEMemoryBlock);
 491            }
 492
 0493            var block = GetPESectionBlock(sectionIndex);
 494
 0495            int relativeOffset = relativeVirtualAddress - PEHeaders.SectionHeaders[sectionIndex].VirtualAddress;
 0496            if (relativeOffset > block.Size)
 0497            {
 0498                return default(PEMemoryBlock);
 499            }
 500
 0501            return new PEMemoryBlock(block, relativeOffset);
 0502        }
 503
 504        /// <summary>
 505        /// Loads PE section of the specified name into memory and returns a memory block that spans the section.
 506        /// </summary>
 507        /// <param name="sectionName">Name of the section.</param>
 508        /// <returns>
 509        /// An empty block if no section of the given <paramref name="sectionName"/> exists in this PE image.
 510        /// </returns>
 511        /// <exception cref="ArgumentNullException"><paramref name="sectionName"/> is null.</exception>
 512        /// <exception cref="InvalidOperationException">PE image not available.</exception>
 513        public PEMemoryBlock GetSectionData(string sectionName)
 0514        {
 0515            if (sectionName is null)
 0516            {
 0517                Throw.ArgumentNull(nameof(sectionName));
 518            }
 519
 0520            int sectionIndex = PEHeaders.IndexOfSection(sectionName);
 0521            if (sectionIndex < 0)
 0522            {
 0523                return default(PEMemoryBlock);
 524            }
 525
 0526            return new PEMemoryBlock(GetPESectionBlock(sectionIndex));
 0527        }
 528
 529        /// <summary>
 530        /// Reads all Debug Directory table entries.
 531        /// </summary>
 532        /// <exception cref="BadImageFormatException">Bad format of the entry.</exception>
 533        /// <exception cref="IOException">IO error while reading from the underlying stream.</exception>
 534        /// <exception cref="InvalidOperationException">PE image not available.</exception>
 535        public ImmutableArray<DebugDirectoryEntry> ReadDebugDirectory()
 0536        {
 0537            Debug.Assert(PEHeaders.PEHeader != null);
 538
 0539            var debugDirectory = PEHeaders.PEHeader.DebugTableDirectory;
 0540            if (debugDirectory.Size == 0)
 0541            {
 0542                return ImmutableArray<DebugDirectoryEntry>.Empty;
 543            }
 544
 545            int position;
 0546            if (!PEHeaders.TryGetDirectoryOffset(debugDirectory, out position))
 0547            {
 0548                throw new BadImageFormatException(SR.InvalidDirectoryRVA);
 549            }
 550
 0551            if (debugDirectory.Size % DebugDirectoryEntry.Size != 0)
 0552            {
 0553                throw new BadImageFormatException(SR.InvalidDirectorySize);
 554            }
 555
 0556            using (AbstractMemoryBlock block = GetPEImage().GetMemoryBlock(position, debugDirectory.Size))
 0557            {
 0558                return ReadDebugDirectoryEntries(block.GetReader());
 559            }
 0560        }
 561
 562        internal static ImmutableArray<DebugDirectoryEntry> ReadDebugDirectoryEntries(BlobReader reader)
 0563        {
 0564            int entryCount = reader.Length / DebugDirectoryEntry.Size;
 0565            var builder = ImmutableArray.CreateBuilder<DebugDirectoryEntry>(entryCount);
 0566            for (int i = 0; i < entryCount; i++)
 0567            {
 568                // Reserved, must be zero.
 0569                int characteristics = reader.ReadInt32();
 0570                if (characteristics != 0)
 0571                {
 0572                    throw new BadImageFormatException(SR.InvalidDebugDirectoryEntryCharacteristics);
 573                }
 574
 0575                uint stamp = reader.ReadUInt32();
 0576                ushort majorVersion = reader.ReadUInt16();
 0577                ushort minorVersion = reader.ReadUInt16();
 578
 0579                var type = (DebugDirectoryEntryType)reader.ReadInt32();
 580
 0581                int dataSize = reader.ReadInt32();
 0582                int dataRva = reader.ReadInt32();
 0583                int dataPointer = reader.ReadInt32();
 584
 0585                builder.Add(new DebugDirectoryEntry(stamp, majorVersion, minorVersion, type, dataSize, dataRva, dataPoin
 0586            }
 587
 0588            return builder.MoveToImmutable();
 0589        }
 590
 591        private AbstractMemoryBlock GetDebugDirectoryEntryDataBlock(DebugDirectoryEntry entry)
 0592        {
 0593            int dataOffset = IsLoadedImage ? entry.DataRelativeVirtualAddress : entry.DataPointer;
 0594            return GetPEImage().GetMemoryBlock(dataOffset, entry.DataSize);
 0595        }
 596
 597        /// <summary>
 598        /// Reads the data pointed to by the specified Debug Directory entry and interprets them as CodeView.
 599        /// </summary>
 600        /// <exception cref="ArgumentException"><paramref name="entry"/> is not a CodeView entry.</exception>
 601        /// <exception cref="BadImageFormatException">Bad format of the data.</exception>
 602        /// <exception cref="IOException">IO error while reading from the underlying stream.</exception>
 603        /// <exception cref="InvalidOperationException">PE image not available.</exception>
 604        public CodeViewDebugDirectoryData ReadCodeViewDebugDirectoryData(DebugDirectoryEntry entry)
 0605        {
 0606            if (entry.Type != DebugDirectoryEntryType.CodeView)
 0607            {
 0608                Throw.InvalidArgument(SR.Format(SR.UnexpectedDebugDirectoryType, nameof(DebugDirectoryEntryType.CodeView
 609            }
 610
 0611            using (var block = GetDebugDirectoryEntryDataBlock(entry))
 0612            {
 0613                return DecodeCodeViewDebugDirectoryData(block);
 614            }
 0615        }
 616
 617        // internal for testing
 618        internal static CodeViewDebugDirectoryData DecodeCodeViewDebugDirectoryData(AbstractMemoryBlock block)
 0619        {
 0620            var reader = block.GetReader();
 621
 0622            if (reader.ReadByte() != (byte)'R' ||
 0623                reader.ReadByte() != (byte)'S' ||
 0624                reader.ReadByte() != (byte)'D' ||
 0625                reader.ReadByte() != (byte)'S')
 0626            {
 0627                throw new BadImageFormatException(SR.UnexpectedCodeViewDataSignature);
 628            }
 629
 0630            Guid guid = reader.ReadGuid();
 0631            int age = reader.ReadInt32();
 0632            string path = reader.ReadUtf8NullTerminated();
 633
 0634            return new CodeViewDebugDirectoryData(guid, age, path);
 0635        }
 636
 637        /// <summary>
 638        /// Reads the data pointed to by the specified Debug Directory entry and interprets them as PDB Checksum entry.
 639        /// </summary>
 640        /// <exception cref="ArgumentException"><paramref name="entry"/> is not a PDB Checksum entry.</exception>
 641        /// <exception cref="BadImageFormatException">Bad format of the data.</exception>
 642        /// <exception cref="IOException">IO error while reading from the underlying stream.</exception>
 643        /// <exception cref="InvalidOperationException">PE image not available.</exception>
 644        public PdbChecksumDebugDirectoryData ReadPdbChecksumDebugDirectoryData(DebugDirectoryEntry entry)
 0645        {
 0646            if (entry.Type != DebugDirectoryEntryType.PdbChecksum)
 0647            {
 0648                Throw.InvalidArgument(SR.Format(SR.UnexpectedDebugDirectoryType, nameof(DebugDirectoryEntryType.PdbCheck
 649            }
 650
 0651            using (var block = GetDebugDirectoryEntryDataBlock(entry))
 0652            {
 0653                return DecodePdbChecksumDebugDirectoryData(block);
 654            }
 0655        }
 656
 657        // internal for testing
 658        internal static PdbChecksumDebugDirectoryData DecodePdbChecksumDebugDirectoryData(AbstractMemoryBlock block)
 0659        {
 0660            var reader = block.GetReader();
 661
 0662            var algorithmName = reader.ReadUtf8NullTerminated();
 0663            byte[]? checksum = reader.ReadBytes(reader.RemainingBytes);
 0664            if (algorithmName.Length == 0 || checksum.Length == 0)
 0665            {
 0666                throw new BadImageFormatException(SR.InvalidPdbChecksumDataFormat);
 667            }
 668
 0669            return new PdbChecksumDebugDirectoryData(
 0670                algorithmName,
 0671                ImmutableCollectionsMarshal.AsImmutableArray(checksum));
 0672        }
 673
 674        /// <summary>
 675        /// Opens a Portable PDB associated with this PE image.
 676        /// </summary>
 677        /// <param name="peImagePath">
 678        /// The path to the PE image. The path is used to locate the PDB file located in the directory containing the PE
 679        /// </param>
 680        /// <param name="pdbFileStreamProvider">
 681        /// If specified, called to open a <see cref="Stream"/> for a given file path.
 682        /// The provider is expected to either return a readable and seekable <see cref="Stream"/>,
 683        /// or <c>null</c> if the target file doesn't exist or should be ignored for some reason.
 684        ///
 685        /// The provider shall throw <see cref="IOException"/> if it fails to open the file due to an unexpected IO erro
 686        /// </param>
 687        /// <param name="pdbReaderProvider">
 688        /// If successful, a new instance of <see cref="MetadataReaderProvider"/> to be used to read the Portable PDB,.
 689        /// </param>
 690        /// <param name="pdbPath">
 691        /// If successful and the PDB is found in a file, the path to the file. Returns <c>null</c> if the PDB is embedd
 692        /// </param>
 693        /// <returns>
 694        /// True if the PE image has a PDB associated with it and the PDB has been successfully opened.
 695        /// </returns>
 696        /// <remarks>
 697        /// Implements a simple PDB file lookup based on the content of the PE image Debug Directory.
 698        /// A sophisticated tool might need to follow up with additional lookup on search paths or symbol server.
 699        ///
 700        /// The method looks the PDB up in the following steps in the listed order:
 701        /// 1) Check for a matching PDB file of the name found in the CodeView entry in the directory containing the PE 
 702        /// 2) Check for a PDB embedded in the PE image itself.
 703        ///
 704        /// The first PDB that matches the information specified in the Debug Directory is returned.
 705        /// </remarks>
 706        /// <exception cref="ArgumentNullException"><paramref name="peImagePath"/> or <paramref name="pdbFileStreamProvi
 707        /// <exception cref="InvalidOperationException">The stream returned from <paramref name="pdbFileStreamProvider"/
 708        /// <exception cref="BadImageFormatException">No matching PDB file is found due to an error: The PE image or the
 709        /// <exception cref="IOException">No matching PDB file is found due to an error: An IO error occurred while read
 710        public bool TryOpenAssociatedPortablePdb(string peImagePath, Func<string, Stream?> pdbFileStreamProvider, out Me
 0711        {
 0712            if (peImagePath is null)
 0713            {
 0714                Throw.ArgumentNull(nameof(peImagePath));
 715            }
 0716            if (pdbFileStreamProvider is null)
 0717            {
 0718                Throw.ArgumentNull(nameof(pdbFileStreamProvider));
 719            }
 720
 0721            pdbReaderProvider = null;
 0722            pdbPath = null;
 723
 724            string? peImageDirectory;
 725            try
 0726            {
 0727                peImageDirectory = Path.GetDirectoryName(peImagePath);
 0728            }
 0729            catch (Exception e)
 0730            {
 0731                throw new ArgumentException(e.Message, nameof(peImagePath), e);
 732            }
 733
 0734            Exception? errorToReport = null;
 0735            var entries = ReadDebugDirectory();
 736
 737            // First try .pdb file specified in CodeView data (we prefer .pdb file on disk over embedded PDB
 738            // since embedded PDB needs decompression which is less efficient than memory-mapping the file).
 0739            var codeViewEntry = ImmutableArrayExtensions.FirstOrDefault(entries, e => e.IsPortableCodeView);
 0740            if (codeViewEntry.DataSize != 0 &&
 0741                TryOpenCodeViewPortablePdb(codeViewEntry, peImageDirectory!, pdbFileStreamProvider, out pdbReaderProvide
 0742            {
 0743                return true;
 744            }
 745
 746            // if it failed try Embedded Portable PDB (if available):
 0747            var embeddedPdbEntry = ImmutableArrayExtensions.FirstOrDefault(entries, e => e.Type == DebugDirectoryEntryTy
 0748            if (embeddedPdbEntry.DataSize != 0)
 0749            {
 0750                bool openedEmbeddedPdb = false;
 0751                pdbReaderProvider = null;
 0752                TryOpenEmbeddedPortablePdb(embeddedPdbEntry, ref openedEmbeddedPdb, ref pdbReaderProvider, ref errorToRe
 0753                if (openedEmbeddedPdb)
 0754                    return true;
 0755            }
 756
 757            // Report any metadata and IO errors. PDB might exist but we couldn't read some metadata.
 758            // The caller might chose to ignore the failure or report it to the user.
 0759            if (errorToReport != null)
 0760            {
 0761                Debug.Assert(errorToReport is BadImageFormatException || errorToReport is IOException);
 0762                ExceptionDispatchInfo.Capture(errorToReport).Throw();
 763            }
 764
 0765            return false;
 0766        }
 767
 768        private bool TryOpenCodeViewPortablePdb(DebugDirectoryEntry codeViewEntry, string peImageDirectory, Func<string,
 0769        {
 0770            pdbPath = null;
 0771            provider = null;
 772
 773            CodeViewDebugDirectoryData data;
 774
 775            try
 0776            {
 0777                data = ReadCodeViewDebugDirectoryData(codeViewEntry);
 0778            }
 0779            catch (Exception e) when (e is BadImageFormatException || e is IOException)
 0780            {
 0781                errorToReport ??= e;
 0782                return false;
 783            }
 784
 0785            var id = new BlobContentId(data.Guid, codeViewEntry.Stamp);
 786
 787            // The interpretation os the path in the CodeView needs to be platform agnostic,
 788            // so that PDBs built on Windows work on Unix-like systems and vice versa.
 789            // System.IO.Path.GetFileName() on Unix-like systems doesn't treat '\' as a file name separator,
 790            // so we need a custom implementation. Also avoid throwing an exception if the path contains invalid charact
 791            // they might not be invalid on the other platform. It's up to the FS APIs to deal with that when opening th
 0792            string collocatedPdbPath = PathUtilities.CombinePathWithRelativePath(peImageDirectory, PathUtilities.GetFile
 793
 0794            if (TryOpenPortablePdbFile(collocatedPdbPath, id, pdbFileStreamProvider, out provider, ref errorToReport))
 0795            {
 0796                pdbPath = collocatedPdbPath;
 0797                return true;
 798            }
 799
 0800            return false;
 0801        }
 802
 803        private static bool TryOpenPortablePdbFile(string path, BlobContentId id, Func<string, Stream?> pdbFileStreamPro
 0804        {
 0805            provider = null;
 0806            MetadataReaderProvider? candidate = null;
 807
 808            try
 0809            {
 810                Stream? pdbStream;
 811
 812                try
 0813                {
 0814                    pdbStream = pdbFileStreamProvider(path);
 0815                }
 0816                catch (FileNotFoundException)
 0817                {
 818                    // Not an unexpected IO exception, continue witout reporting the error.
 0819                    pdbStream = null;
 0820                }
 821
 0822                if (pdbStream == null)
 0823                {
 0824                    return false;
 825                }
 826
 0827                if (!pdbStream.CanRead || !pdbStream.CanSeek)
 0828                {
 0829                    throw new InvalidOperationException(SR.StreamMustSupportReadAndSeek);
 830                }
 831
 0832                candidate = MetadataReaderProvider.FromPortablePdbStream(pdbStream);
 833
 834                // Validate that the PDB matches the assembly version
 0835                if (new BlobContentId(candidate.GetMetadataReader().DebugMetadataHeader!.Id) != id)
 0836                {
 0837                    return false;
 838                }
 839
 0840                provider = candidate;
 0841                return true;
 842            }
 0843            catch (Exception e) when (e is BadImageFormatException || e is IOException)
 0844            {
 0845                errorToReport ??= e;
 0846                return false;
 847            }
 848            finally
 0849            {
 0850                if (provider == null)
 0851                {
 0852                    candidate?.Dispose();
 0853                }
 0854            }
 0855        }
 856
 857        partial void TryOpenEmbeddedPortablePdb(DebugDirectoryEntry embeddedPdbEntry, ref bool openedEmbeddedPdb, ref Me
 858    }
 859}
 860

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Reflection.Metadata/src/System/Reflection/PortableExecutable/PEReader.EmbeddedPortablePdb.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;
 6using System.IO;
 7using System.IO.Compression;
 8using System.Reflection.Internal;
 9using System.Reflection.Metadata;
 10using System.Runtime.ExceptionServices;
 11using System.Threading;
 12
 13namespace System.Reflection.PortableExecutable
 14{
 15    /// <summary>
 16    /// Portable Executable format reader.
 17    /// </summary>
 18    /// <remarks>
 19    /// The implementation is thread-safe, that is multiple threads can read data from the reader in parallel.
 20    /// Disposal of the reader is not thread-safe (see <see cref="Dispose"/>).
 21    /// </remarks>
 22    public sealed partial class PEReader : IDisposable
 23    {
 24        /// <summary>
 25        /// Reads the data pointed to by the specified Debug Directory entry and interprets them as Embedded Portable PD
 26        /// </summary>
 27        /// <returns>
 28        /// Provider of a metadata reader reading the embedded Portable PDB image.
 29        /// Dispose to release resources allocated for the embedded PDB.
 30        /// </returns>
 31        /// <exception cref="ArgumentException"><paramref name="entry"/> is not a <see cref="DebugDirectoryEntryType.Emb
 32        /// <exception cref="BadImageFormatException">Bad format of the data.</exception>
 33        /// <exception cref="InvalidOperationException">PE image not available.</exception>
 34        public MetadataReaderProvider ReadEmbeddedPortablePdbDebugDirectoryData(DebugDirectoryEntry entry)
 035        {
 036            if (entry.Type != DebugDirectoryEntryType.EmbeddedPortablePdb)
 037            {
 038                Throw.InvalidArgument(SR.Format(SR.UnexpectedDebugDirectoryType, nameof(DebugDirectoryEntryType.Embedded
 39            }
 40
 041            ValidateEmbeddedPortablePdbVersion(entry);
 42
 043            using var block = GetDebugDirectoryEntryDataBlock(entry);
 044            return new MetadataReaderProvider(DecodeEmbeddedPortablePdbDebugDirectoryData(block));
 045        }
 46
 47        // internal for testing
 48        internal static void ValidateEmbeddedPortablePdbVersion(DebugDirectoryEntry entry)
 049        {
 50            // Major version encodes the version of Portable PDB format itself.
 51            // Minor version encodes the version of Embedded Portable PDB blob.
 52            // Accept any version of Portable PDB >= 1.0,
 53            // but only accept version 1.* of the Embedded Portable PDB blob.
 54            // Any breaking change in the format should rev major version of the embedded blob.
 055            ushort formatVersion = entry.MajorVersion;
 056            if (formatVersion < PortablePdbVersions.MinFormatVersion)
 057            {
 058                throw new BadImageFormatException(SR.Format(SR.UnsupportedFormatVersion, PortablePdbVersions.Format(form
 59            }
 60
 061            ushort embeddedBlobVersion = entry.MinorVersion;
 062            if (embeddedBlobVersion != PortablePdbVersions.DefaultEmbeddedVersion)
 063            {
 064                throw new BadImageFormatException(SR.Format(SR.UnsupportedFormatVersion, PortablePdbVersions.Format(embe
 65            }
 066        }
 67
 68        // internal for testing
 69        internal static unsafe NativeHeapMemoryBlock DecodeEmbeddedPortablePdbDebugDirectoryData(AbstractMemoryBlock blo
 070        {
 71            NativeHeapMemoryBlock? decompressed;
 72
 073            var headerReader = block.GetReader();
 074            if (headerReader.ReadUInt32() != PortablePdbVersions.DebugDirectoryEmbeddedSignature)
 075            {
 076                throw new BadImageFormatException(SR.UnexpectedEmbeddedPortablePdbDataSignature);
 77            }
 78
 079            int decompressedSize = headerReader.ReadInt32();
 80
 81            try
 082            {
 083                decompressed = new NativeHeapMemoryBlock(decompressedSize);
 084            }
 085            catch (Exception e)
 086            {
 087                throw new BadImageFormatException(SR.DataTooBig, e);
 88            }
 89
 090            bool success = false;
 91            try
 092            {
 093                var compressed = new UnmanagedMemoryStream(headerReader.CurrentPointer, headerReader.RemainingBytes);
 094                using var deflate = new DeflateStream(compressed, CompressionMode.Decompress, leaveOpen: true);
 95
 096                if (decompressedSize > 0)
 097                {
 98                    int actualLength;
 99
 100                    try
 0101                    {
 0102                        actualLength = deflate.ReadAtLeast(new Span<byte>(decompressed.Pointer, decompressed.Size), deco
 0103                    }
 0104                    catch (Exception e)
 0105                    {
 0106                        throw new BadImageFormatException(e.Message, e);
 107                    }
 108
 0109                    if (actualLength != decompressed.Size)
 0110                    {
 0111                        throw new BadImageFormatException(SR.SizeMismatch);
 112                    }
 0113                }
 114
 115                // Check that there is no more compressed data left,
 116                // in case the decompressed size specified in the header is smaller
 117                // than the actual decompressed size of the data.
 0118                if (deflate.ReadByte() != -1)
 0119                {
 0120                    throw new BadImageFormatException(SR.SizeMismatch);
 121                }
 122
 0123                success = true;
 0124            }
 125            finally
 0126            {
 0127                if (!success)
 0128                {
 0129                    decompressed.Dispose();
 0130                }
 0131            }
 132
 0133            return decompressed;
 0134        }
 135
 136        partial void TryOpenEmbeddedPortablePdb(DebugDirectoryEntry embeddedPdbEntry, ref bool openedEmbeddedPdb, ref Me
 0137        {
 0138            provider = null;
 0139            MetadataReaderProvider? candidate = null;
 140
 141            try
 0142            {
 0143                candidate = ReadEmbeddedPortablePdbDebugDirectoryData(embeddedPdbEntry);
 144
 145                // throws if headers are invalid:
 0146                candidate.GetMetadataReader();
 147
 0148                provider = candidate;
 0149                openedEmbeddedPdb = true;
 0150                return;
 151            }
 0152            catch (Exception e) when (e is BadImageFormatException || e is IOException)
 0153            {
 0154                errorToReport ??= e;
 0155                openedEmbeddedPdb = false;
 0156            }
 157            finally
 0158            {
 0159                if (provider == null)
 0160                {
 0161                    candidate?.Dispose();
 0162                }
 0163            }
 0164        }
 165    }
 166}
 167

Methods/Properties

IsLoadedImage()
.ctor(System.Byte*,System.Int32)
.ctor(System.Byte*,System.Int32,System.Boolean)
.ctor(System.IO.Stream)
.ctor(System.IO.Stream,System.Reflection.PortableExecutable.PEStreamOptions)
.ctor(System.IO.Stream,System.Reflection.PortableExecutable.PEStreamOptions,System.Int32)
.ctor(System.Collections.Immutable.ImmutableArray`1<System.Byte>)
Dispose()
GetPEImage()
PEHeaders()
InitializePEHeaders()
GetEntireImageBlock()
GetMetadataBlock()
GetPESectionBlock(System.Int32)
IsEntireImageAvailable()
GetEntireImage()
HasMetadata()
GetMetadata()
GetSectionData(System.Int32)
GetSectionData(System.String)
ReadDebugDirectory()
ReadDebugDirectoryEntries(System.Reflection.Metadata.BlobReader)
GetDebugDirectoryEntryDataBlock(System.Reflection.PortableExecutable.DebugDirectoryEntry)
ReadCodeViewDebugDirectoryData(System.Reflection.PortableExecutable.DebugDirectoryEntry)
DecodeCodeViewDebugDirectoryData(System.Reflection.Internal.AbstractMemoryBlock)
ReadPdbChecksumDebugDirectoryData(System.Reflection.PortableExecutable.DebugDirectoryEntry)
DecodePdbChecksumDebugDirectoryData(System.Reflection.Internal.AbstractMemoryBlock)
TryOpenAssociatedPortablePdb(System.String,System.Func`2<System.String,System.IO.Stream>,System.Reflection.Metadata.MetadataReaderProvider&,System.String&)
TryOpenCodeViewPortablePdb(System.Reflection.PortableExecutable.DebugDirectoryEntry,System.String,System.Func`2<System.String,System.IO.Stream>,System.Reflection.Metadata.MetadataReaderProvider&,System.String&,System.Exception&)
TryOpenPortablePdbFile(System.String,System.Reflection.Metadata.BlobContentId,System.Func`2<System.String,System.IO.Stream>,System.Reflection.Metadata.MetadataReaderProvider&,System.Exception&)
ReadEmbeddedPortablePdbDebugDirectoryData(System.Reflection.PortableExecutable.DebugDirectoryEntry)
ValidateEmbeddedPortablePdbVersion(System.Reflection.PortableExecutable.DebugDirectoryEntry)
DecodeEmbeddedPortablePdbDebugDirectoryData(System.Reflection.Internal.AbstractMemoryBlock)
TryOpenEmbeddedPortablePdb(System.Reflection.PortableExecutable.DebugDirectoryEntry,System.Boolean&,System.Reflection.Metadata.MetadataReaderProvider&,System.Exception&)