partial void TryOpenEmbeddedPortablePdb(DebugDirectoryEntry embeddedPdbEntry, ref bool openedEmbeddedPdb, ref MetadataReaderProvider?provider, ref Exception?errorToReport)
        {
            provider = null;
            MetadataReaderProvider?candidate = null;

            try
            {
                candidate = ReadEmbeddedPortablePdbDebugDirectoryData(embeddedPdbEntry);

                // throws if headers are invalid:
                candidate.GetMetadataReader();

                provider          = candidate;
                openedEmbeddedPdb = true;
                return;
            }
            catch (Exception e) when(e is BadImageFormatException || e is IOException)
            {
                errorToReport     = errorToReport ?? e;
                openedEmbeddedPdb = false;
            }
            finally
            {
                if (candidate == null)
                {
                    candidate?.Dispose();
                }
            }
        }
Пример #2
0
        private bool TryOpenPortablePdbFile(string path, BlobContentId id, Func <string, Stream?> pdbFileStreamProvider, out MetadataReaderProvider?provider, ref Exception?errorToReport)
        {
            provider = null;
            MetadataReaderProvider?candidate = null;

            try
            {
                Stream?pdbStream;

                try
                {
                    pdbStream = pdbFileStreamProvider(path);
                }
                catch (FileNotFoundException)
                {
                    // Not an unexpected IO exception, continue witout reporting the error.
                    pdbStream = null;
                }

                if (pdbStream == null)
                {
                    return(false);
                }

                if (!pdbStream.CanRead || !pdbStream.CanSeek)
                {
                    throw new InvalidOperationException(SR.StreamMustSupportReadAndSeek);
                }

                candidate = MetadataReaderProvider.FromPortablePdbStream(pdbStream);

                // Validate that the PDB matches the assembly version
                if (new BlobContentId(candidate.GetMetadataReader().DebugMetadataHeader !.Id) != id)
                {
                    return(false);
                }

                provider = candidate;
                return(true);
            }
            catch (Exception e) when(e is BadImageFormatException || e is IOException)
            {
                errorToReport = errorToReport ?? e;
                return(false);
            }
            finally
            {
                if (provider == null)
                {
                    candidate?.Dispose();
                }
            }
        }
Пример #3
0
        private static CompilationDiff?ValidateFile(FileInfo file, BuildConstructor buildConstructor, string?thisCompilerVersion)
        {
            if (s_ignorePatterns.Any(r => r.IsMatch(file.FullName)))
            {
                s_logger.LogTrace($"Ignoring {file.FullName}");
                return(null);
            }

            MetadataReaderProvider?pdbReaderProvider = null;

            try
            {
                // Find the embedded pdb
                using var fileStream = file.OpenRead();
                using var peReader   = new PEReader(fileStream);

                var pdbOpened = peReader.TryOpenAssociatedPortablePdb(
                    peImagePath: file.FullName,
                    filePath => File.Exists(filePath) ? File.OpenRead(filePath) : null,
                    out pdbReaderProvider,
                    out var pdbPath);

                if (!pdbOpened || pdbReaderProvider is null)
                {
                    s_logger.LogError($"Could not find pdb for {file.FullName}");
                    return(null);
                }

                s_logger.LogInformation($"Compiling {file.FullName} with pdb {pdbPath ?? "[embedded]"}");

                var reader = pdbReaderProvider.GetMetadataReader();

                // TODO: Check compilation version using the PEReader

                var compilation = buildConstructor.CreateCompilation(reader, file.Name);
                return(CompilationDiff.Create(file, compilation));
            }
            catch (Exception e)
            {
                s_logger.LogError(e, file.FullName);
                return(CompilationDiff.Create(file, e));
            }
            finally
            {
                pdbReaderProvider?.Dispose();
            }
        }
        /// <summary>
        /// Returns the portable PDB reader for the assembly path
        /// </summary>
        /// <param name="assembly">Managed Assembly to be used as a cache key</param>
        /// <param name="assemblyPath">
        /// File path of the assembly or null if the module is dynamic (generated by Reflection.Emit).
        /// </param>
        /// <param name="loadedPeAddress">
        /// Loaded PE image address or zero if the module is dynamic (generated by Reflection.Emit).
        /// Dynamic modules have their PDBs (if any) generated to an in-memory stream
        /// (pointed to by <paramref name="inMemoryPdbAddress"/> and <paramref name="inMemoryPdbSize"/>).
        /// </param>
        /// <param name="loadedPeSize">loaded PE image size</param>
        /// <param name="inMemoryPdbAddress">in memory PDB address or zero</param>
        /// <param name="inMemoryPdbSize">in memory PDB size</param>
        /// <returns>reader</returns>
        /// <remarks>
        /// Accounts for unloadable and dynamic types by keying the cache on the managed Assembly. The
        /// underlying ConditionalWeakTable doesn't keep the assembly alive, so cached types will be
        /// correctly invalidated when the Assembly is unloaded by the GC.
        /// </remarks>
        private unsafe MetadataReader?TryGetReader(Assembly assembly, string assemblyPath, IntPtr loadedPeAddress, int loadedPeSize, IntPtr inMemoryPdbAddress, int inMemoryPdbSize)
        {
            if ((loadedPeAddress == IntPtr.Zero || assemblyPath == null) && inMemoryPdbAddress == IntPtr.Zero)
            {
                // Dynamic or in-memory module without symbols (they would be in-memory if they were available).
                return(null);
            }

            // The ConditionalWeakTable's GetValue + callback will atomically create the cache entry for us
            // so we are protected from multiple threads racing to get/create the same MetadataReaderProvider
            MetadataReaderProvider?provider = _metadataCache.GetValue(assembly, (assembly) =>
            {
                return((inMemoryPdbAddress != IntPtr.Zero) ?
                       TryOpenReaderForInMemoryPdb(inMemoryPdbAddress, inMemoryPdbSize) :
                       TryOpenReaderFromAssemblyFile(assemblyPath, loadedPeAddress, loadedPeSize));
            });

            // The reader has already been open, so this doesn't throw.
            return(provider?.GetMetadataReader());
        }
Пример #5
0
 partial void TryOpenEmbeddedPortablePdb(DebugDirectoryEntry embeddedPdbEntry, ref bool openedEmbeddedPdb, ref MetadataReaderProvider?provider, ref Exception?errorToReport);
Пример #6
0
        private bool TryOpenCodeViewPortablePdb(DebugDirectoryEntry codeViewEntry, string peImageDirectory, Func <string, Stream?> pdbFileStreamProvider, out MetadataReaderProvider?provider, out string?pdbPath, ref Exception?errorToReport)
        {
            pdbPath  = null;
            provider = null;

            CodeViewDebugDirectoryData data;

            try
            {
                data = ReadCodeViewDebugDirectoryData(codeViewEntry);
            }
            catch (Exception e) when(e is BadImageFormatException || e is IOException)
            {
                errorToReport = errorToReport ?? e;
                return(false);
            }

            var id = new BlobContentId(data.Guid, codeViewEntry.Stamp);

            // The interpretation os the path in the CodeView needs to be platform agnostic,
            // so that PDBs built on Windows work on Unix-like systems and vice versa.
            // System.IO.Path.GetFileName() on Unix-like systems doesn't treat '\' as a file name separator,
            // so we need a custom implementation. Also avoid throwing an exception if the path contains invalid characters,
            // they might not be invalid on the other platform. It's up to the FS APIs to deal with that when opening the stream.
            string collocatedPdbPath = PathUtilities.CombinePathWithRelativePath(peImageDirectory, PathUtilities.GetFileName(data.Path));

            if (TryOpenPortablePdbFile(collocatedPdbPath, id, pdbFileStreamProvider, out provider, ref errorToReport))
            {
                pdbPath = collocatedPdbPath;
                return(true);
            }

            return(false);
        }
Пример #7
0
        /// <summary>
        /// Opens a Portable PDB associated with this PE image.
        /// </summary>
        /// <param name="peImagePath">
        /// The path to the PE image. The path is used to locate the PDB file located in the directory containing the PE file.
        /// </param>
        /// <param name="pdbFileStreamProvider">
        /// If specified, called to open a <see cref="Stream"/> for a given file path.
        /// The provider is expected to either return a readable and seekable <see cref="Stream"/>,
        /// or <c>null</c> if the target file doesn't exist or should be ignored for some reason.
        ///
        /// The provider shall throw <see cref="IOException"/> if it fails to open the file due to an unexpected IO error.
        /// </param>
        /// <param name="pdbReaderProvider">
        /// If successful, a new instance of <see cref="MetadataReaderProvider"/> to be used to read the Portable PDB,.
        /// </param>
        /// <param name="pdbPath">
        /// If successful and the PDB is found in a file, the path to the file. Returns <c>null</c> if the PDB is embedded in the PE image itself.
        /// </param>
        /// <returns>
        /// True if the PE image has a PDB associated with it and the PDB has been successfully opened.
        /// </returns>
        /// <remarks>
        /// Implements a simple PDB file lookup based on the content of the PE image Debug Directory.
        /// A sophisticated tool might need to follow up with additional lookup on search paths or symbol server.
        ///
        /// The method looks the PDB up in the following steps in the listed order:
        /// 1) Check for a matching PDB file of the name found in the CodeView entry in the directory containing the PE file (the directory of <paramref name="peImagePath"/>).
        /// 2) Check for a PDB embedded in the PE image itself.
        ///
        /// The first PDB that matches the information specified in the Debug Directory is returned.
        /// </remarks>
        /// <exception cref="ArgumentNullException"><paramref name="peImagePath"/> or <paramref name="pdbFileStreamProvider"/> is null.</exception>
        /// <exception cref="InvalidOperationException">The stream returned from <paramref name="pdbFileStreamProvider"/> doesn't support read and seek operations.</exception>
        /// <exception cref="BadImageFormatException">No matching PDB file is found due to an error: The PE image or the PDB is invalid.</exception>
        /// <exception cref="IOException">No matching PDB file is found due to an error: An IO error occurred while reading the PE image or the PDB.</exception>
        public bool TryOpenAssociatedPortablePdb(string peImagePath, Func <string, Stream?> pdbFileStreamProvider, out MetadataReaderProvider?pdbReaderProvider, out string?pdbPath)
        {
            if (peImagePath == null)
            {
                Throw.ArgumentNull(nameof(peImagePath));
            }

            if (pdbFileStreamProvider == null)
            {
                Throw.ArgumentNull(nameof(pdbFileStreamProvider));
            }

            pdbReaderProvider = null;
            pdbPath           = null;

            string?peImageDirectory;

            try
            {
                peImageDirectory = Path.GetDirectoryName(peImagePath);
            }
            catch (Exception e)
            {
                throw new ArgumentException(e.Message, nameof(peImagePath));
            }

            Exception?errorToReport = null;
            var       entries       = ReadDebugDirectory();

            // First try .pdb file specified in CodeView data (we prefer .pdb file on disk over embedded PDB
            // since embedded PDB needs decompression which is less efficient than memory-mapping the file).
            var codeViewEntry = entries.FirstOrDefault(e => e.IsPortableCodeView);

            if (codeViewEntry.DataSize != 0 &&
                TryOpenCodeViewPortablePdb(codeViewEntry, peImageDirectory !, pdbFileStreamProvider, out pdbReaderProvider, out pdbPath, ref errorToReport))
            {
                return(true);
            }

            // if it failed try Embedded Portable PDB (if available):
            var embeddedPdbEntry = entries.FirstOrDefault(e => e.Type == DebugDirectoryEntryType.EmbeddedPortablePdb);

            if (embeddedPdbEntry.DataSize != 0)
            {
                bool openedEmbeddedPdb = false;
                pdbReaderProvider = null;
                TryOpenEmbeddedPortablePdb(embeddedPdbEntry, ref openedEmbeddedPdb, ref pdbReaderProvider, ref errorToReport);
                if (openedEmbeddedPdb)
                {
                    return(true);
                }
            }

            // Report any metadata and IO errors. PDB might exist but we couldn't read some metadata.
            // The caller might chose to ignore the failure or report it to the user.
            if (errorToReport != null)
            {
                Debug.Assert(errorToReport is BadImageFormatException || errorToReport is IOException);
                ExceptionDispatchInfo.Capture(errorToReport).Throw();
            }

            return(false);
        }
Пример #8
0
        private static CompilationDiff?ValidateFile(FileInfo originalBinary, BuildConstructor buildConstructor, ILogger logger, Options options)
        {
            if (s_ignorePatterns.Any(r => r.IsMatch(originalBinary.FullName)))
            {
                logger.LogTrace($"Ignoring {originalBinary.FullName}");
                return(null);
            }

            MetadataReaderProvider?pdbReaderProvider = null;

            try
            {
                // Find the embedded pdb
                using var originalBinaryStream = originalBinary.OpenRead();
                using var originalPeReader     = new PEReader(originalBinaryStream);

                var pdbOpened = originalPeReader.TryOpenAssociatedPortablePdb(
                    peImagePath: originalBinary.FullName,
                    filePath => File.Exists(filePath) ? File.OpenRead(filePath) : null,
                    out pdbReaderProvider,
                    out var pdbPath);

                if (!pdbOpened || pdbReaderProvider is null)
                {
                    logger.LogError($"Could not find pdb for {originalBinary.FullName}");
                    return(null);
                }

                using var _ = logger.BeginScope($"Verifying {originalBinary.FullName} with pdb {pdbPath ?? "[embedded]"}");

                var pdbReader     = pdbReaderProvider.GetMetadataReader();
                var optionsReader = new CompilationOptionsReader(logger, pdbReader, originalPeReader);

                var compilation = buildConstructor.CreateCompilation(
                    optionsReader,
                    Path.GetFileNameWithoutExtension(originalBinary.Name));

                var compilationDiff = CompilationDiff.Create(originalBinary, optionsReader, compilation, getDebugEntryPoint(), logger, options);
                return(compilationDiff);

                IMethodSymbol?getDebugEntryPoint()
                {
                    if (optionsReader.GetMainTypeName() is { } mainTypeName&&
                        optionsReader.GetMainMethodName() is { } mainMethodName)
                    {
                        var typeSymbol = compilation.GetTypeByMetadataName(mainTypeName);
                        if (typeSymbol is object)
                        {
                            var methodSymbols = typeSymbol
                                                .GetMembers(mainMethodName)
                                                .OfType <IMethodSymbol>();
                            return(methodSymbols.FirstOrDefault());
                        }
                    }

                    return(null);
                }
            }
            finally
            {
                pdbReaderProvider?.Dispose();
            }
        }