diff --git a/PerfView.sln b/PerfView.sln
index 3da566c16..2007aade6 100644
--- a/PerfView.sln
+++ b/PerfView.sln
@@ -91,6 +91,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TraceParserGen.Tests", "src
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TraceEvent.Benchmarks", "src\TraceEvent\TraceEvent.Benchmarks\TraceEvent.Benchmarks.csproj", "{F2FBDEF0-4C45-44B2-8B92-9C5763BD2E69}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TraceEvent", "TraceEvent", "{F2AE6042-2485-6774-F42B-98E120E28306}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TraceEvent.Tests", "TraceEvent.Tests", "{282B72FF-D1CF-1C67-705A-D384E1729A5D}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EmbeddedPdbTestApp", "src\TraceEvent\TraceEvent.Tests\EmbeddedPdbTestApp\EmbeddedPdbTestApp.csproj", "{1CE6D3C2-5E27-4296-89FD-5177387F0B15}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -445,10 +453,27 @@ Global
{F2FBDEF0-4C45-44B2-8B92-9C5763BD2E69}.Release|x64.Build.0 = Release|Any CPU
{F2FBDEF0-4C45-44B2-8B92-9C5763BD2E69}.Release|x86.ActiveCfg = Release|Any CPU
{F2FBDEF0-4C45-44B2-8B92-9C5763BD2E69}.Release|x86.Build.0 = Release|Any CPU
+ {1CE6D3C2-5E27-4296-89FD-5177387F0B15}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1CE6D3C2-5E27-4296-89FD-5177387F0B15}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1CE6D3C2-5E27-4296-89FD-5177387F0B15}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {1CE6D3C2-5E27-4296-89FD-5177387F0B15}.Debug|x64.Build.0 = Debug|Any CPU
+ {1CE6D3C2-5E27-4296-89FD-5177387F0B15}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {1CE6D3C2-5E27-4296-89FD-5177387F0B15}.Debug|x86.Build.0 = Debug|Any CPU
+ {1CE6D3C2-5E27-4296-89FD-5177387F0B15}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1CE6D3C2-5E27-4296-89FD-5177387F0B15}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1CE6D3C2-5E27-4296-89FD-5177387F0B15}.Release|x64.ActiveCfg = Release|Any CPU
+ {1CE6D3C2-5E27-4296-89FD-5177387F0B15}.Release|x64.Build.0 = Release|Any CPU
+ {1CE6D3C2-5E27-4296-89FD-5177387F0B15}.Release|x86.ActiveCfg = Release|Any CPU
+ {1CE6D3C2-5E27-4296-89FD-5177387F0B15}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {F2AE6042-2485-6774-F42B-98E120E28306} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
+ {282B72FF-D1CF-1C67-705A-D384E1729A5D} = {F2AE6042-2485-6774-F42B-98E120E28306}
+ {1CE6D3C2-5E27-4296-89FD-5177387F0B15} = {282B72FF-D1CF-1C67-705A-D384E1729A5D}
+ EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {9F85A2A3-E0DF-4826-9BBA-4DFFA0F17150}
EndGlobalSection
diff --git a/src/TraceEvent/Symbols/PortableSymbolModule.cs b/src/TraceEvent/Symbols/PortableSymbolModule.cs
index 09e54e0f9..30f8a8184 100644
--- a/src/TraceEvent/Symbols/PortableSymbolModule.cs
+++ b/src/TraceEvent/Symbols/PortableSymbolModule.cs
@@ -19,6 +19,19 @@ public PortableSymbolModule(SymbolReader reader, Stream stream, string pdbFileNa
_metaData = _provider.GetMetadataReader();
}
+ ///
+ /// Creates a PortableSymbolModule from a MetadataReaderProvider that has already been
+ /// opened (e.g. an embedded portable PDB read out of a PE image via
+ /// PEReader.ReadEmbeddedPortablePdbDebugDirectoryData). The provider owns its own backing
+ /// memory, so it remains valid after the PEReader it came from has been disposed. This
+ /// PortableSymbolModule takes ownership of 'provider' and disposes it when disposed.
+ ///
+ internal PortableSymbolModule(SymbolReader reader, MetadataReaderProvider provider, string pdbFileName = "") : base(reader, pdbFileName)
+ {
+ _provider = provider;
+ _metaData = _provider.GetMetadataReader();
+ }
+
public void Dispose() => _provider.Dispose();
public override Guid PdbGuid
diff --git a/src/TraceEvent/Symbols/SymbolReader.cs b/src/TraceEvent/Symbols/SymbolReader.cs
index b7ea16039..bd7ea5796 100644
--- a/src/TraceEvent/Symbols/SymbolReader.cs
+++ b/src/TraceEvent/Symbols/SymbolReader.cs
@@ -4,6 +4,8 @@
using System.IO;
using System.Linq;
using System.Net.Http;
+using System.Reflection.Metadata;
+using System.Reflection.PortableExecutable;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
@@ -711,6 +713,69 @@ public NativeSymbolModule OpenNativeSymbolFile(string pdbFileName)
return OpenSymbolFile(pdbFileName) as NativeSymbolModule;
}
+ ///
+ /// Attempts to open a portable PDB that is embedded inside the managed module image at
+ /// (modules built with <DebugType>embedded</DebugType>).
+ /// Returns a (a PortableSymbolModule) whose data comes
+ /// straight from the module's embedded debug directory, or null if
+ /// does not exist, is not a PE file, or has no embedded portable PDB.
+ ///
+ /// Because the PDB bytes are read from the same on-disk module, no separate GUID/age matching
+ /// against a standalone PDB file is needed. This is the embedded-PDB analog of
+ /// , which opens a standalone PDB file.
+ ///
+ /// The path to the managed module (.dll/.exe) that may contain an embedded portable PDB.
+ /// The symbol module for the embedded PDB, or null if none is present.
+ public ManagedSymbolModule OpenEmbeddedPortablePdb(string dllFilePath)
+ {
+ // Suffix the key so it cannot collide with the standalone-PDB cache entries (which are
+ // keyed by PDB file path), even when an embedded and a standalone PDB share the same module.
+ string cacheKey = dllFilePath + "|EmbeddedPdb";
+ if (m_symbolModuleCache.TryGet(cacheKey, out ManagedSymbolModule ret))
+ {
+ return ret;
+ }
+
+ try
+ {
+ if (!File.Exists(dllFilePath))
+ {
+ m_log.WriteLine("OpenEmbeddedPortablePdb: {0} does not exist.", dllFilePath);
+ return null;
+ }
+
+ using (FileStream peStream = File.Open(dllFilePath, FileMode.Open, FileAccess.Read, FileShare.Read))
+ using (PEReader peReader = new PEReader(peStream))
+ {
+ foreach (DebugDirectoryEntry entry in peReader.ReadDebugDirectory())
+ {
+ if (entry.Type == DebugDirectoryEntryType.EmbeddedPortablePdb)
+ {
+ // The returned provider owns its own (decompressed) backing memory, so it
+ // remains valid after the PEReader and FileStream are disposed below.
+ MetadataReaderProvider provider = peReader.ReadEmbeddedPortablePdbDebugDirectoryData(entry);
+ ret = new PortableSymbolModule(this, provider, dllFilePath);
+ m_log.WriteLine("OpenEmbeddedPortablePdb: Found embedded portable PDB in {0}", dllFilePath);
+ break;
+ }
+ }
+
+ if (ret == null)
+ {
+ m_log.WriteLine("OpenEmbeddedPortablePdb: {0} has no embedded portable PDB.", dllFilePath);
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ m_log.WriteLine("OpenEmbeddedPortablePdb: Failure reading {0}: {1}", dllFilePath, e.Message);
+ ret = null;
+ }
+
+ m_symbolModuleCache.Add(cacheKey, ret);
+ return ret;
+ }
+
internal R2RPerfMapSymbolModule OpenR2RPerfMapSymbolFile(string filePath, uint loadedLayoutTextOffset)
{
return new R2RPerfMapSymbolModule(filePath, loadedLayoutTextOffset);
diff --git a/src/TraceEvent/TraceEvent.Tests/EmbeddedPdbTestApp/EmbeddedPdbTestApp.csproj b/src/TraceEvent/TraceEvent.Tests/EmbeddedPdbTestApp/EmbeddedPdbTestApp.csproj
new file mode 100644
index 000000000..6a7f498f5
--- /dev/null
+++ b/src/TraceEvent/TraceEvent.Tests/EmbeddedPdbTestApp/EmbeddedPdbTestApp.csproj
@@ -0,0 +1,44 @@
+
+
+
+
+
+ netstandard2.0
+ EmbeddedPdbTestApp
+ EmbeddedPdbTestApp
+
+ embedded
+ true
+ true
+
+ false
+ false
+ false
+ true
+
+
+
+
+ true
+ true
+ true
+ ..\..\..\MSFT.snk
+
+
+
diff --git a/src/TraceEvent/TraceEvent.Tests/EmbeddedPdbTestApp/EmbeddedTarget.cs b/src/TraceEvent/TraceEvent.Tests/EmbeddedPdbTestApp/EmbeddedTarget.cs
new file mode 100644
index 000000000..48d929691
--- /dev/null
+++ b/src/TraceEvent/TraceEvent.Tests/EmbeddedPdbTestApp/EmbeddedTarget.cs
@@ -0,0 +1,20 @@
+namespace EmbeddedPdbTestApp
+{
+ ///
+ /// Minimal target type for the embedded-portable-PDB SymbolReader test. The test resolves the
+ /// metadata token of via reflection (do not rely on a fixed token, since the
+ /// netstandard2.0 build synthesizes attribute-type constructors ahead of it in the MethodDef table).
+ ///
+ /// IMPORTANT: SymbolReaderTests asserts the source line of the first sequence point of
+ /// (IL offset 0, the first statement). If you move the body below,
+ /// update the expected line number in the test.
+ ///
+ public static class EmbeddedTarget
+ {
+ public static int Add(int a, int b)
+ {
+ int sum = a + b; // first sequence point (IL offset 0) is on this line
+ return sum;
+ }
+ }
+}
diff --git a/src/TraceEvent/TraceEvent.Tests/Symbols/SymbolReaderTests.cs b/src/TraceEvent/TraceEvent.Tests/Symbols/SymbolReaderTests.cs
index bb0eec08b..21e4e9eb4 100644
--- a/src/TraceEvent/TraceEvent.Tests/Symbols/SymbolReaderTests.cs
+++ b/src/TraceEvent/TraceEvent.Tests/Symbols/SymbolReaderTests.cs
@@ -1,4 +1,8 @@
+using FastSerialization;
using Microsoft.Diagnostics.Symbols;
+using Microsoft.Diagnostics.Tracing;
+using Microsoft.Diagnostics.Tracing.Etlx;
+using Microsoft.Diagnostics.Tracing.EventPipe;
using PerfView.TestUtilities;
using System;
using System.Collections.Generic;
@@ -135,6 +139,145 @@ public void PortablePdbHasValidSourceInfo()
}
}
+ [Fact]
+ public void EmbeddedPortablePdbResolvesSourceLine()
+ {
+ string dllPath = EmbeddedPdbTestAppPath;
+
+ ManagedSymbolModule pdbFile = _symbolReader.OpenEmbeddedPortablePdb(dllPath);
+ using (pdbFile as IDisposable)
+ {
+ Assert.NotNull(pdbFile);
+ Assert.IsType(pdbFile);
+
+ // Resolve the metadata token for EmbeddedTarget.Add from the referenced fixture assembly
+ // rather than hard-coding it: on netstandard2.0 the compiler synthesizes attribute types
+ // (e.g. EmbeddedAttribute, RefSafetyRulesAttribute) whose constructors occupy the first
+ // MethodDef rows, so Add is not token 0x06000001.
+ uint addToken = (uint)typeof(EmbeddedPdbTestApp.EmbeddedTarget)
+ .GetMethod(nameof(EmbeddedPdbTestApp.EmbeddedTarget.Add)).MetadataToken;
+
+ // Resolve the source location of the method's first sequence point (the one covering
+ // IL offset 0).
+ SourceLocation sourceLocation = pdbFile.SourceLocationForManagedCode(addToken, ilOffset: 0);
+ Assert.NotNull(sourceLocation);
+
+ SourceFile sourceFile = sourceLocation.SourceFile;
+ Assert.NotNull(sourceFile);
+ Assert.EndsWith("EmbeddedTarget.cs", sourceFile.BuildTimeFilePath, StringComparison.OrdinalIgnoreCase);
+
+ // The first sequence point of Add() (IL offset 0) maps to its first statement,
+ // "int sum = a + b;", on this line of EmbeddedTarget.cs. If the fixture source moves,
+ // update this expectation.
+ Assert.Equal(15, sourceLocation.LineNumber);
+ }
+ }
+
+ [Fact]
+ public void EmbeddedPortablePdbReadableAfterPEReaderDisposed()
+ {
+ // OpenEmbeddedPortablePdb disposes the PEReader/FileStream it used before returning, so a
+ // successful source-line lookup here proves the MetadataReaderProvider owns its own backing
+ // memory and survives that disposal. (Note: the SymbolReader still owns the returned module;
+ // disposing the SymbolReader itself would clear its cache and dispose this module.)
+ uint addToken = (uint)typeof(EmbeddedPdbTestApp.EmbeddedTarget)
+ .GetMethod(nameof(EmbeddedPdbTestApp.EmbeddedTarget.Add)).MetadataToken;
+
+ ManagedSymbolModule pdbFile = _symbolReader.OpenEmbeddedPortablePdb(EmbeddedPdbTestAppPath);
+ using (pdbFile as IDisposable)
+ {
+ Assert.NotNull(pdbFile);
+ SourceLocation sourceLocation = pdbFile.SourceLocationForManagedCode(addToken, ilOffset: 0);
+ Assert.NotNull(sourceLocation);
+ Assert.NotNull(sourceLocation.SourceFile);
+ }
+ }
+
+ [Fact]
+ public void OpenEmbeddedPortablePdbCachesResult()
+ {
+ // A second open of the same module must return the cached instance (the embedded-PDB cache
+ // key cannot collide with a standalone-PDB path). Do not dispose between calls: the
+ // SymbolReader owns the cached module's lifetime.
+ ManagedSymbolModule first = _symbolReader.OpenEmbeddedPortablePdb(EmbeddedPdbTestAppPath);
+ ManagedSymbolModule second = _symbolReader.OpenEmbeddedPortablePdb(EmbeddedPdbTestAppPath);
+
+ Assert.NotNull(first);
+ Assert.Same(first, second);
+ }
+
+ [Fact]
+ public void OpenEmbeddedPortablePdbReturnsNullWhenNotEmbedded()
+ {
+ // The test assembly itself is built with a standalone (portable) PDB, not an embedded one,
+ // so there is no EmbeddedPortablePdb debug-directory entry to read.
+ string dllWithoutEmbeddedPdb = typeof(SymbolReaderTests).Assembly.Location;
+ Assert.True(File.Exists(dllWithoutEmbeddedPdb));
+
+ ManagedSymbolModule pdbFile = _symbolReader.OpenEmbeddedPortablePdb(dllWithoutEmbeddedPdb);
+ Assert.Null(pdbFile);
+ }
+
+ [Fact]
+ public void OpenEmbeddedPortablePdbReturnsNullForMissingFile()
+ {
+ string missing = Path.Combine(Path.GetTempPath(), "DoesNotExist_" + Guid.NewGuid().ToString("N") + ".dll");
+ ManagedSymbolModule pdbFile = _symbolReader.OpenEmbeddedPortablePdb(missing);
+ Assert.Null(pdbFile);
+ }
+
+ [Fact]
+ public void TraceLogOpenPdbForModuleFileResolvesEmbeddedPdb()
+ {
+ // Exercises the trace symbol-resolution path end-to-end: TraceCodeAddresses.OpenPdbForModuleFile,
+ // given a managed module that (a) carries no PDB identity in the trace, (b) whose on-disk copy
+ // matches the trace (checksum + timestamp), and (c) has no standalone PDB next to it, must fall
+ // back to the module's embedded portable PDB. This is the only consumer of
+ // SymbolReader.OpenEmbeddedPortablePdb that the SymbolReaderTests above do not reach directly.
+ string dllPath = EmbeddedPdbTestAppPath;
+
+ // Read the real PE checksum/timestamp from the fixture DLL so the TraceModuleUnchanged gate
+ // inside OpenPdbForModuleFile passes regardless of how/when the fixture was (re)built.
+ int imageChecksum;
+ int timeDateStamp;
+ using (var peFile = new PEFile.PEFile(dllPath))
+ {
+ imageChecksum = (int)peFile.Header.CheckSum;
+ timeDateStamp = peFile.Header.TimeDateStampSec;
+ }
+
+ uint addToken = (uint)typeof(EmbeddedPdbTestApp.EmbeddedTarget)
+ .GetMethod(nameof(EmbeddedPdbTestApp.EmbeddedTarget.Add)).MetadataToken;
+
+ using (TraceLog traceLog = CreateEmptyInMemoryTraceLog())
+ {
+ // Build a managed TraceModuleFile that points at the fixture DLL on disk. The internal
+ // constructor lower-cases the path, so restore the real-cased path afterwards because
+ // File.Exists (inside TraceModuleUnchanged) is case-sensitive on non-Windows.
+ TraceModuleFile moduleFile = new TraceModuleFile(dllPath, 0x10000, (ModuleFileIndex)0);
+ moduleFile.fileName = dllPath;
+ moduleFile.imageChecksum = imageChecksum;
+ moduleFile.timeDateStamp = timeDateStamp;
+ // Leave symbolInfo null so PdbSignature == Guid.Empty: with no recorded PDB identity,
+ // OpenPdbForModuleFile is forced down the on-disk-match -> embedded-PDB branch.
+
+ ManagedSymbolModule pdbFile = traceLog.CodeAddresses.OpenPdbForModuleFile(_symbolReader, moduleFile);
+ using (pdbFile as IDisposable)
+ {
+ Assert.NotNull(pdbFile);
+ Assert.IsType(pdbFile);
+
+ // OpenPdbForModuleFile stamps ExePath with the module it resolved symbols for.
+ Assert.Equal(dllPath, pdbFile.ExePath);
+
+ SourceLocation sourceLocation = pdbFile.SourceLocationForManagedCode(addToken, ilOffset: 0);
+ Assert.NotNull(sourceLocation);
+ Assert.EndsWith("EmbeddedTarget.cs", sourceLocation.SourceFile.BuildTimeFilePath, StringComparison.OrdinalIgnoreCase);
+ Assert.Equal(15, sourceLocation.LineNumber);
+ }
+ }
+ }
+
[Fact]
public void SourceLinkUrlsAreEscaped()
{
@@ -1402,6 +1545,45 @@ protected void PrepareTestData()
protected string UnzippedSymbolReaderTestInputDir => Path.Combine(UnZippedDataDir, SymbolReaderTestInput);
+ ///
+ /// Path to the EmbeddedPdbTestApp fixture DLL (built with <DebugType>embedded</DebugType>)
+ /// that the TraceEvent.Tests project copies next to the test assembly.
+ ///
+ protected static string EmbeddedPdbTestAppPath
+ {
+ get
+ {
+ string path = Path.Combine(AppContext.BaseDirectory, "EmbeddedPdbTestApp.dll");
+ Assert.True(File.Exists(path), "EmbeddedPdbTestApp.dll was not found next to the test assembly: " + path);
+ return path;
+ }
+ }
+
+ ///
+ /// Builds a minimal, valid, empty entirely in memory (no ETW capture, so
+ /// it works cross-platform). Callers can then hand-populate s and
+ /// drive symbol-resolution APIs directly.
+ ///
+ private static TraceLog CreateEmptyInMemoryTraceLog()
+ {
+ // Generate a minimal in-memory nettrace stream with just enough metadata to be valid.
+ var writer = new EventPipeWriterV6();
+ writer.WriteHeaders();
+ writer.WriteMetadataBlock(new EventMetadata(1, "Microsoft-Windows-DotNETRuntime", "EventSource", 0));
+ writer.WriteThreadBlock(w => w.WriteThreadEntry(1, 0, 0));
+ writer.WriteEventBlock(w => w.WriteEventBlob(1, 1, 1, new byte[0]));
+ writer.WriteEndBlock();
+
+ using MemoryStream nettraceStream = new MemoryStream(writer.ToArray());
+ TraceEventDispatcher eventSource = new EventPipeEventSource(nettraceStream);
+
+ // Convert to in-memory ETLX and open it as a TraceLog (the caller owns disposal).
+ MemoryStream etlxStream = new MemoryStream();
+ TraceLog.CreateFromEventPipeEventSources(eventSource, new IOStreamStreamWriter(etlxStream, SerializationSettings.Default, leaveOpen: true), null);
+ etlxStream.Position = 0;
+ return new TraceLog(etlxStream);
+ }
+
///
/// A handler for the in that
/// can be used by unit tests to intercept requests to symbol server (for PDB
diff --git a/src/TraceEvent/TraceEvent.Tests/TraceEvent.Tests.csproj b/src/TraceEvent/TraceEvent.Tests/TraceEvent.Tests.csproj
index 0ffb5c455..589f6b8e9 100644
--- a/src/TraceEvent/TraceEvent.Tests/TraceEvent.Tests.csproj
+++ b/src/TraceEvent/TraceEvent.Tests/TraceEvent.Tests.csproj
@@ -32,6 +32,19 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/src/TraceEvent/TraceLog.cs b/src/TraceEvent/TraceLog.cs
index f397c03bb..dfbb35c97 100644
--- a/src/TraceEvent/TraceLog.cs
+++ b/src/TraceEvent/TraceLog.cs
@@ -9111,7 +9111,7 @@ private void LookupSymbolsForModule(SymbolReader reader, TraceModuleFile moduleF
///
/// Look up the SymbolModule (open PDB) for a given moduleFile. Will generate NGEN pdbs as needed.
///
- private unsafe ManagedSymbolModule OpenPdbForModuleFile(SymbolReader symReader, TraceModuleFile moduleFile)
+ internal unsafe ManagedSymbolModule OpenPdbForModuleFile(SymbolReader symReader, TraceModuleFile moduleFile)
{
string pdbFileName = null;
// If we have a signature, use it
@@ -9131,6 +9131,21 @@ private unsafe ManagedSymbolModule OpenPdbForModuleFile(SymbolReader symReader,
if (TraceModuleUnchanged(moduleFile, symReader.m_log))
{
pdbFileName = symReader.FindSymbolFilePathForModule(moduleFile.FilePath);
+
+ // No standalone PDB found, but the on-disk module matches the trace. See if the
+ // module carries an embedded portable PDB (embedded) and use
+ // it directly. Because the bytes come from the matched-on-disk module, no GUID/age
+ // re-validation is required, so we return the module immediately.
+ if (pdbFileName == null)
+ {
+ ManagedSymbolModule embeddedSymbolModule = symReader.OpenEmbeddedPortablePdb(moduleFile.FilePath);
+ if (embeddedSymbolModule != null)
+ {
+ embeddedSymbolModule.ExePath = moduleFile.FilePath;
+ symReader.m_log.WriteLine("Opened embedded portable PDB for {0}", moduleFile.FilePath);
+ return embeddedSymbolModule;
+ }
+ }
}
}