You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Microsoft.Diagnostics.Tracing.TraceEvent's SymbolReader cannot resolve managed source lines for a module that ships its portable PDB embedded in the PE image (<DebugType>embedded</DebugType>). When no standalone .pdb exists next to the DLL, on the SymbolPath, or on a symbol server, TraceCodeAddress.GetSourceLine(SymbolReader) falls back to no source, even though the PDB bytes are present inside the on-disk module that the trace points at (the trace itself records the module's path and signature, not its bytes).
This is distinct from #1487 / #1699, which added embedded source-file support (reading source text out of a portable PDB that is already loaded). The embedded PDB-loading half of #1487 — materializing the portable PDB from the PE debug directory in the first place — was never implemented.
Repro
Build a small managed app with <DebugType>embedded</DebugType> (no standalone .pdb produced).
Capture a .nettrace (EventPipe) or .etl CPU trace of it.
Open via TraceLog, resolve a managed frame with TraceCodeAddress.GetSourceLine(symbolReader).
Expected: the frame resolves to file:line.
Actual: no source line, because SymbolReader.FindSymbolFilePath / TraceCodeAddresses.OpenPdbForModuleFile only probe for a standalone .pdb (next to the DLL, on the SymbolPath, or on a symbol server) and never inspect the module's debug directory for an EmbeddedPortablePdb entry. A code search of microsoft/perfview for EmbeddedPortablePdb returns zero hits; PEFile's debug-directory scan (GetPdbSignature / IMAGE_DEBUG_TYPE) only recognizes the CODEVIEW (RSDS) entry, not the EmbeddedPortablePdb entry (type 17).
Proposed change
Teach the trace symbol-resolution path to read an embedded portable PDB directly from the on-disk module before falling back to the standalone-file probe. The natural hook is TraceCodeAddresses.OpenPdbForModuleFile (a private method in TraceLog.cs), which has the module's FilePath and already gates on-disk reads with the existing TraceModuleUnchanged checksum check; SymbolReader.OpenSymbolFile only ever sees a PDB path, so it is not the right entry point on its own:
When resolving a managed module (and after TraceModuleUnchanged confirms the on-disk DLL matches the trace), inspect its PE debug directory for an EmbeddedPortablePdb entry. This requires extending PEFile's debug-directory scan to recognize the EmbeddedPortablePdb entry (type 17) — TraceEvent today only reads the separate CODEVIEW (RSDS) entry for PdbName / PdbSignature / PdbAge, so the embedded entry is not yet reachable.
If present, inflate the MPDB-framed (0x4244504D) deflate blob in memory and hand the resulting portable-PDB bytes to the existing PortableSymbolModule(SymbolReader, Stream) constructor, skipping the file-system / symbol-server probe. System.Reflection.Metadata (already referenced) exposes PEReader.ReadEmbeddedPortablePdbDebugDirectoryData, so the MPDB/deflate handling need not be hand-rolled.
Because the PDB is read from the same on-disk DLL the trace points at, no separate GUID/age matching against a different copy is needed — which avoids the mismatch class of bugs that affects any "extract the PDB from a separately-built copy of the DLL" workaround.
The portable-PDB reader is cross-platform, so this would enable embedded-PDB source resolution on non-Windows as well, unlike the native .pdb path.
The embedded-source support added in #1699 already demonstrates TraceEvent will read embedded data out of a portable PDB once it has one in hand; this issue asks for the step that gets that PDB in hand.
Workaround (for reference)
Consumers can re-materialize the embedded PDB to a temp standalone .pdb and append its directory to SymbolReader.SymbolPath, but this requires the same build of the DLL that produced the trace (GUID/age must match), which is fragile — e.g. BenchmarkDotNet builds an isolated copy with its own deterministic GUID and deletes it during artifact cleanup. An in-reader solution that reads the embedded PDB straight from the on-disk DLL removes the separate extract-and-match step whenever that DLL is still present. Note it does not rescue the case where the producing DLL has already been deleted (e.g. BenchmarkDotNet's artifact cleanup) — the embedded PDB lives in that same file — but it does eliminate the GUID/age mismatch risk for the common case where the module is on disk but ships no standalone PDB.
Context
Follows up #1487 (which asked "can it also find embedded pdb's?" — answered "would need to test / investigation needed" and then closed via Support embedded source files #1699 covering only embedded source).
Happy to prototype the in-memory embedded-PDB path and open a PR if the approach sounds reasonable.
Summary
Microsoft.Diagnostics.Tracing.TraceEvent'sSymbolReadercannot resolve managed source lines for a module that ships its portable PDB embedded in the PE image (<DebugType>embedded</DebugType>). When no standalone.pdbexists next to the DLL, on theSymbolPath, or on a symbol server,TraceCodeAddress.GetSourceLine(SymbolReader)falls back to no source, even though the PDB bytes are present inside the on-disk module that the trace points at (the trace itself records the module's path and signature, not its bytes).This is distinct from #1487 / #1699, which added embedded source-file support (reading source text out of a portable PDB that is already loaded). The embedded PDB-loading half of #1487 — materializing the portable PDB from the PE debug directory in the first place — was never implemented.
Repro
<DebugType>embedded</DebugType>(no standalone.pdbproduced)..nettrace(EventPipe) or.etlCPU trace of it.TraceLog, resolve a managed frame withTraceCodeAddress.GetSourceLine(symbolReader).Expected: the frame resolves to
file:line.Actual: no source line, because
SymbolReader.FindSymbolFilePath/TraceCodeAddresses.OpenPdbForModuleFileonly probe for a standalone.pdb(next to the DLL, on theSymbolPath, or on a symbol server) and never inspect the module's debug directory for anEmbeddedPortablePdbentry. A code search ofmicrosoft/perfviewforEmbeddedPortablePdbreturns zero hits;PEFile's debug-directory scan (GetPdbSignature/IMAGE_DEBUG_TYPE) only recognizes theCODEVIEW(RSDS) entry, not theEmbeddedPortablePdbentry (type 17).Proposed change
Teach the trace symbol-resolution path to read an embedded portable PDB directly from the on-disk module before falling back to the standalone-file probe. The natural hook is
TraceCodeAddresses.OpenPdbForModuleFile(a private method inTraceLog.cs), which has the module'sFilePathand already gates on-disk reads with the existingTraceModuleUnchangedchecksum check;SymbolReader.OpenSymbolFileonly ever sees a PDB path, so it is not the right entry point on its own:TraceModuleUnchangedconfirms the on-disk DLL matches the trace), inspect its PE debug directory for anEmbeddedPortablePdbentry. This requires extendingPEFile's debug-directory scan to recognize theEmbeddedPortablePdbentry (type 17) — TraceEvent today only reads the separateCODEVIEW(RSDS) entry forPdbName/PdbSignature/PdbAge, so the embedded entry is not yet reachable.MPDB-framed (0x4244504D) deflate blob in memory and hand the resulting portable-PDB bytes to the existingPortableSymbolModule(SymbolReader, Stream)constructor, skipping the file-system / symbol-server probe.System.Reflection.Metadata(already referenced) exposesPEReader.ReadEmbeddedPortablePdbDebugDirectoryData, so the MPDB/deflate handling need not be hand-rolled..pdbpath.The embedded-source support added in #1699 already demonstrates TraceEvent will read embedded data out of a portable PDB once it has one in hand; this issue asks for the step that gets that PDB in hand.
Workaround (for reference)
Consumers can re-materialize the embedded PDB to a temp standalone
.pdband append its directory toSymbolReader.SymbolPath, but this requires the same build of the DLL that produced the trace (GUID/age must match), which is fragile — e.g. BenchmarkDotNet builds an isolated copy with its own deterministic GUID and deletes it during artifact cleanup. An in-reader solution that reads the embedded PDB straight from the on-disk DLL removes the separate extract-and-match step whenever that DLL is still present. Note it does not rescue the case where the producing DLL has already been deleted (e.g. BenchmarkDotNet's artifact cleanup) — the embedded PDB lives in that same file — but it does eliminate the GUID/age mismatch risk for the common case where the module is on disk but ships no standalone PDB.Context