diff --git a/src/Microsoft.TestPlatform.ObjectModel/Navigation/DiaSession.cs b/src/Microsoft.TestPlatform.ObjectModel/Navigation/DiaSession.cs index 249cf364c1..8ea8c96aa6 100644 --- a/src/Microsoft.TestPlatform.ObjectModel/Navigation/DiaSession.cs +++ b/src/Microsoft.TestPlatform.ObjectModel/Navigation/DiaSession.cs @@ -101,12 +101,21 @@ private static ISymbolReader GetSymbolReader(string binaryPath) // For remote scenario, also look for pdb in current directory, (esp for UWP) // The alternate search path should be an input from Adapters, but since it is not so currently adding a HACK pdbFilePath = !File.Exists(pdbFilePath) ? Path.Combine(Directory.GetCurrentDirectory(), Path.GetFileName(pdbFilePath)) : pdbFilePath; - using var stream = new FileHelper().GetStream(pdbFilePath, FileMode.Open, FileAccess.Read); - if (PortablePdbReader.IsPortable(stream)) + + if (File.Exists(pdbFilePath)) { + using var stream = new FileHelper().GetStream(pdbFilePath, FileMode.Open, FileAccess.Read); + if (PortablePdbReader.IsPortable(stream)) + { + return new PortableSymbolReader(); + } + + return new FullSymbolReader(); + } + else + { + // If we cannot find the pdb file, it might be embedded in the dll. return new PortableSymbolReader(); } - - return new FullSymbolReader(); } } diff --git a/src/Microsoft.TestPlatform.ObjectModel/Navigation/PortablePdbReader.cs b/src/Microsoft.TestPlatform.ObjectModel/Navigation/PortablePdbReader.cs index e6cc268fb3..479a207de6 100644 --- a/src/Microsoft.TestPlatform.ObjectModel/Navigation/PortablePdbReader.cs +++ b/src/Microsoft.TestPlatform.ObjectModel/Navigation/PortablePdbReader.cs @@ -54,6 +54,17 @@ public PortablePdbReader(Stream stream) _reader = _provider.GetMetadataReader(); } + /// + /// Reads the pdb using a provided metadata reader, when the pdb is embedded in the dll, or found by + /// path that is in the dll metadata. + /// + /// + public PortablePdbReader(MetadataReaderProvider metadataReaderProvider!!) + { + _provider = metadataReaderProvider; + _reader = _provider.GetMetadataReader(); + } + /// /// Dispose Metadata reader /// diff --git a/src/Microsoft.TestPlatform.ObjectModel/Navigation/PortableSymbolReader.cs b/src/Microsoft.TestPlatform.ObjectModel/Navigation/PortableSymbolReader.cs index 4573197de0..2f7c437282 100644 --- a/src/Microsoft.TestPlatform.ObjectModel/Navigation/PortableSymbolReader.cs +++ b/src/Microsoft.TestPlatform.ObjectModel/Navigation/PortableSymbolReader.cs @@ -5,6 +5,8 @@ using System.Collections.Generic; using System.IO; using System.Reflection; +using System.Reflection.Metadata; +using System.Reflection.PortableExecutable; using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions; using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers; @@ -91,7 +93,10 @@ private void PopulateCacheForTypeAndMethodSymbols(string binaryPath) try { var pdbFilePath = Path.ChangeExtension(binaryPath, ".pdb"); - using var pdbReader = new PortablePdbReader(new FileHelper().GetStream(pdbFilePath, FileMode.Open, FileAccess.Read)); + using PortablePdbReader pdbReader = File.Exists(pdbFilePath) + ? CreatePortablePdbReaderFromExistingPdbFile(pdbFilePath) + : CreatePortablePdbReaderFromPEData(binaryPath); + // At this point, the assembly should be already loaded into the load context. We query for a reference to // find the types and cache the symbol information. Let the loader follow default lookup order instead of // forcing load from a specific path. @@ -148,4 +153,36 @@ private void PopulateCacheForTypeAndMethodSymbols(string binaryPath) throw; } } + + /// + /// Reads the pdb data from the dlls itself, either by loading the referenced pdb file, or by reading + /// embedded pdb from the dll itself. + /// + /// + /// + /// + private static PortablePdbReader CreatePortablePdbReaderFromPEData(string binaryPath) + { + using var dllStream = new FileStream(binaryPath, FileMode.Open, FileAccess.Read); + using var peReader = new PEReader(dllStream); + + var hasPdb = peReader.TryOpenAssociatedPortablePdb(binaryPath, pdbPath => new FileStream(pdbPath, FileMode.Open, FileAccess.Read), out MetadataReaderProvider mp, pdbPath: out _); + + // The out parameters don't give additional info about the pdbFile in case it is not found. So we have few reasons to fail: + if (!hasPdb) + { + throw new InvalidOperationException($"Cannot find portable .PDB file for {binaryPath}. This can have multiple reasons:" + + "\n- The dll was built with portable and the pdb file is missing (it was deleted, or not moved together with the dll)." + + "\n- The dll was built with embedded and there is some unknown error reading the metadata from the dll." + + "\n- The sll was built with none and the pdb file was never even emitted during build." + + "\n- Additionally if your dll is built with full, see FullPdbReader instead."); + } + + return new PortablePdbReader(mp); + } + + private static PortablePdbReader CreatePortablePdbReaderFromExistingPdbFile(string pdbFilePath) + { + return new PortablePdbReader(new FileHelper().GetStream(pdbFilePath, FileMode.Open, FileAccess.Read)); + } }