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(); } } }
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(); } } }
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()); }
partial void TryOpenEmbeddedPortablePdb(DebugDirectoryEntry embeddedPdbEntry, ref bool openedEmbeddedPdb, ref MetadataReaderProvider?provider, ref Exception?errorToReport);
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); }
/// <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); }
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(); } }