Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions PerfView.sln
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
13 changes: 13 additions & 0 deletions src/TraceEvent/Symbols/PortableSymbolModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,19 @@ public PortableSymbolModule(SymbolReader reader, Stream stream, string pdbFileNa
_metaData = _provider.GetMetadataReader();
}

/// <summary>
/// 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.
/// </summary>
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
Expand Down
65 changes: 65 additions & 0 deletions src/TraceEvent/Symbols/SymbolReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -711,6 +713,69 @@ public NativeSymbolModule OpenNativeSymbolFile(string pdbFileName)
return OpenSymbolFile(pdbFileName) as NativeSymbolModule;
}

/// <summary>
/// Attempts to open a portable PDB that is embedded inside the managed module image at
/// <paramref name="dllFilePath"/> (modules built with &lt;DebugType&gt;embedded&lt;/DebugType&gt;).
/// Returns a <see cref="ManagedSymbolModule"/> (a <c>PortableSymbolModule</c>) whose data comes
/// straight from the module's embedded debug directory, or null if <paramref name="dllFilePath"/>
/// 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
/// <see cref="OpenSymbolFile(string)"/>, which opens a standalone PDB file.
/// </summary>
/// <param name="dllFilePath">The path to the managed module (.dll/.exe) that may contain an embedded portable PDB.</param>
/// <returns>The symbol module for the embedded PDB, or null if none is present.</returns>
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);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">

<!--
Test fixture for SymbolReader embedded-portable-PDB resolution.

This assembly is intentionally built with <DebugType>embedded</DebugType> so that its
portable PDB lives inside the PE image (there is no standalone .pdb next to the DLL).
SymbolReaderTests opens the produced DLL via SymbolReader.OpenEmbeddedPortablePdb and
verifies that managed source lines resolve straight out of the embedded PDB.

Keep this assembly tiny and deterministic: EmbeddedTarget is a static class with a single
method whose metadata token the test resolves via reflection.
-->
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<AssemblyName>EmbeddedPdbTestApp</AssemblyName>
<RootNamespace>EmbeddedPdbTestApp</RootNamespace>
<!-- The whole point of this fixture: embed the portable PDB in the PE image. -->
<DebugType>embedded</DebugType>
<DebugSymbols>true</DebugSymbols>
<Deterministic>true</Deterministic>
<!-- Always build unoptimized, even when the solution is built Release. The tests assert an
exact source line for EmbeddedTarget.Add's first sequence point; with optimization on, the
compiler collapses the method body and that sequence point shifts (e.g. to the 'return'
line), which made the tests fail in Release. Pinning Optimize=false keeps the emitted
sequence points stable regardless of the build configuration. -->
<Optimize>false</Optimize>
<GenerateDocumentationFile>false</GenerateDocumentationFile>
<IsPackable>false</IsPackable>
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
</PropertyGroup>

<!-- Public-sign with the same key as the test assembly. The (strong-named) test assembly
references this fixture, and .NET Framework refuses to load an unsigned assembly from a
strong-named one, so this fixture must be strong-named too. -->
<PropertyGroup>
<SignAssembly>true</SignAssembly>
<PublicSign Condition="'$(SIGNING_BUILD)' != 'true'">true</PublicSign>
<DelaySign Condition="'$(SIGNING_BUILD)' == 'true'">true</DelaySign>
<AssemblyOriginatorKeyFile>..\..\..\MSFT.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
namespace EmbeddedPdbTestApp
{
/// <summary>
/// Minimal target type for the embedded-portable-PDB SymbolReader test. The test resolves the
/// metadata token of <see cref="Add"/> 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
/// <see cref="Add"/> (IL offset 0, the first statement). If you move the body below,
/// update the expected line number in the test.
/// </summary>
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;
}
}
}
Loading