diff --git a/eng/Subsets.props b/eng/Subsets.props
index 2ed013d619c209..d693e2a58f9ff0 100644
--- a/eng/Subsets.props
+++ b/eng/Subsets.props
@@ -338,6 +338,7 @@
$(CoreClrProjectRoot)tools\dotnet-pgo\dotnet-pgo.csproj;
$(CoreClrProjectRoot)tools\aot\ILCompiler\repro\repro.csproj;
$(CoreClrProjectRoot)tools\r2rtest\R2RTest.csproj;
+ $(CoreClrProjectRoot)tools\PdbChecker\PdbChecker.csproj;
$(CoreClrProjectRoot)tools\AssemblyChecker\AssemblyChecker.csproj" Category="clr" Condition="'$(DotNetBuildFromSource)' != 'true'"/>
diff --git a/src/coreclr/tools/PdbChecker/MSDiaSymbolReader.cs b/src/coreclr/tools/PdbChecker/MSDiaSymbolReader.cs
new file mode 100644
index 00000000000000..ddea0a5ef01743
--- /dev/null
+++ b/src/coreclr/tools/PdbChecker/MSDiaSymbolReader.cs
@@ -0,0 +1,81 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.InteropServices;
+using Dia2Lib;
+
+class MSDiaSymbolReader
+{
+ [return: MarshalAs(UnmanagedType.Interface)]
+ [DllImport("msdia140.dll", CharSet = CharSet.Unicode, ExactSpelling = true, PreserveSig = false)]
+ private static extern object DllGetClassObject(
+ [In] in Guid rclsid,
+ [In] in Guid riid);
+
+ [ComImport, ComVisible(false), Guid("00000001-0000-0000-C000-000000000046"),
+ InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+ private interface IClassFactory
+ {
+ void CreateInstance([MarshalAs(UnmanagedType.Interface)] object? aggregator,
+ [In] in Guid refiid,
+ [MarshalAs(UnmanagedType.Interface)] out object createdObject);
+ void LockServer(bool incrementRefCount);
+ }
+
+ private readonly IDiaDataSource _diaDataSource;
+ private readonly IDiaSession _diaSession;
+
+ private readonly List _pdbSymbols;
+
+ public MSDiaSymbolReader(string pdbFile)
+ {
+ try
+ {
+ var dia140SourceClassGuid = new Guid("{e6756135-1e65-4d17-8576-610761398c3c}");
+ IClassFactory diaClassFactory = (IClassFactory)DllGetClassObject(dia140SourceClassGuid, typeof(IClassFactory).GetTypeInfo().GUID);
+ diaClassFactory.CreateInstance(null, typeof(IDiaDataSource).GetTypeInfo().GUID, out object comObject);
+
+ _diaDataSource = (IDiaDataSource)comObject;
+ _diaDataSource.loadDataFromPdb(pdbFile);
+ _diaDataSource.openSession(out _diaSession);
+
+ _pdbSymbols = new List();
+
+ _diaSession.getSymbolsByAddr(out IDiaEnumSymbolsByAddr symbolEnum);
+ int symbolsTotal = 0;
+ for (IDiaSymbol symbol = symbolEnum.symbolByRVA(0); symbol != null; symbolEnum.Next(1, out symbol, out uint fetched))
+ {
+ symbolsTotal++;
+ if (symbol.symTag == (uint)SymTagEnum.SymTagFunction || symbol.symTag == (uint)SymTagEnum.SymTagPublicSymbol)
+ {
+ _pdbSymbols.Add(symbol.name);
+ }
+ }
+
+ Console.WriteLine("PDB file: {0}", pdbFile);
+ Console.WriteLine("Total symbols: {0}", symbolsTotal);
+ Console.WriteLine("Public symbols: {0}", _pdbSymbols.Count);
+ }
+ catch (Exception ex)
+ {
+ throw new Exception($"Error opening PDB file {pdbFile}", ex);
+ }
+ }
+
+ public void DumpSymbols()
+ {
+ Console.WriteLine("PDB public symbol list:");
+ foreach (string symbol in _pdbSymbols.OrderBy(s => s))
+ {
+ Console.WriteLine(symbol);
+ }
+ Console.WriteLine("End of PDB public symbol list");
+ }
+
+ public bool ContainsSymbol(string symbolName) => _pdbSymbols.Any(s => s.Contains(symbolName));
+}
diff --git a/src/coreclr/tools/PdbChecker/PdbChecker.csproj b/src/coreclr/tools/PdbChecker/PdbChecker.csproj
new file mode 100644
index 00000000000000..fe2ade9844ceee
--- /dev/null
+++ b/src/coreclr/tools/PdbChecker/PdbChecker.csproj
@@ -0,0 +1,28 @@
+
+
+
+ PdbChecker
+ Exe
+ $(NetCoreAppToolCurrent)
+ AnyCPU
+ false
+ true
+ true
+ enable
+ $(RuntimeBinDir)\PdbChecker
+ false
+
+
+
+
+
+
+
+
+ $(TargetArchitecture)
+ amd64
+
+
+
+
+
diff --git a/src/coreclr/tools/PdbChecker/PdbChecker.sln b/src/coreclr/tools/PdbChecker/PdbChecker.sln
new file mode 100644
index 00000000000000..3ba7e9ae4c3370
--- /dev/null
+++ b/src/coreclr/tools/PdbChecker/PdbChecker.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.3.32708.82
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PdbChecker", "PdbChecker.csproj", "{6247A503-5387-4BE1-ACA3-027CADA30CA9}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {6247A503-5387-4BE1-ACA3-027CADA30CA9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6247A503-5387-4BE1-ACA3-027CADA30CA9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6247A503-5387-4BE1-ACA3-027CADA30CA9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6247A503-5387-4BE1-ACA3-027CADA30CA9}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {4033231C-763B-4C57-BA35-7C1AC007AD0E}
+ EndGlobalSection
+EndGlobal
diff --git a/src/coreclr/tools/PdbChecker/Program.cs b/src/coreclr/tools/PdbChecker/Program.cs
new file mode 100644
index 00000000000000..41f4209ce648dc
--- /dev/null
+++ b/src/coreclr/tools/PdbChecker/Program.cs
@@ -0,0 +1,60 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using Dia2Lib;
+class Program
+{
+ public static int Main(string[] args)
+ {
+ try
+ {
+ TryMain(args);
+ return 0;
+ }
+ catch (Exception ex)
+ {
+ Console.Error.WriteLine("Fatal error: {0}", ex);
+ return 1;
+ }
+ }
+
+ private static void TryMain(string[] args)
+ {
+ if (args.Length == 0)
+ {
+ DisplayUsage();
+ return;
+ }
+ MSDiaSymbolReader reader = new MSDiaSymbolReader(args[0]);
+ int matchedSymbols = 0;
+ int missingSymbols = 0;
+ for (int symbolArgIndex = 1; symbolArgIndex < args.Length; symbolArgIndex++)
+ {
+ string symbolName = args[symbolArgIndex];
+ if (reader.ContainsSymbol(symbolName))
+ {
+ matchedSymbols++;
+ }
+ else
+ {
+ missingSymbols++;
+ Console.Error.WriteLine("Missing symbol: {0}", symbolName);
+ }
+ }
+ if (missingSymbols > 0)
+ {
+ reader.DumpSymbols();
+ throw new Exception($"{missingSymbols} missing symbols ({matchedSymbols} symbols matched)");
+ }
+ if (matchedSymbols > 0)
+ {
+ Console.WriteLine("Matched all {0} symbols", matchedSymbols);
+ }
+ }
+
+ private static void DisplayUsage()
+ {
+ Console.WriteLine("Usage: PdbChecker { }");
+ }
+}
diff --git a/src/tests/Common/CLRTest.CrossGen.targets b/src/tests/Common/CLRTest.CrossGen.targets
index c71df61f92e998..ff8f875df7449a 100644
--- a/src/tests/Common/CLRTest.CrossGen.targets
+++ b/src/tests/Common/CLRTest.CrossGen.targets
@@ -189,8 +189,12 @@ if /i "$(AlwaysUseCrossGen2)" == "true" (
REM CrossGen2 Script
if defined RunCrossGen2 (
set ExtraCrossGen2Args=!ExtraCrossGen2Args! $(CrossGen2TestExtraArguments)
+ set CrossGen2TestCheckPdb=$(CrossGen2TestCheckPdb)
+ set __CreatePdb=$(__CreatePdb)
if defined LargeVersionBubble ( set ExtraCrossGen2Args=!ExtraCrossGen2Args! --inputbubble)
+ if defined CrossGen2TestCheckPdb ( set __CreatePdb=1)
+
set CrossGen2Status=0
set R2RDumpStatus=0
set compilationDoneFlagFile=!ScriptPath!IL-CG2\done
@@ -214,6 +218,7 @@ if defined RunCrossGen2 (
if defined CompositeBuildMode (
set ExtraCrossGen2Args=!ExtraCrossGen2Args! --composite
set __OutputFile=!scriptPath!\composite-r2r.dll
+ set __PdbFile=!scriptPath!\composite-r2r.ni.pdb
rem In composite mode, treat all dll's in the test folder as rooting inputs
set __InputFile=!scriptPath!IL-CG2\*.dll
call :CompileOneFileCrossgen2
@@ -222,6 +227,7 @@ if defined RunCrossGen2 (
set ExtraCrossGen2Args=!ExtraCrossGen2Args! -r:!scriptPath!IL-CG2\*.dll
for %%I in (!scriptPath!IL-CG2\*.dll) do (
set __OutputFile=!scriptPath!%%~nI.dll
+ set __PdbFile=!scriptPath!%%~nI.ni.pdb
set __InputFile=%%I
call :CompileOneFileCrossgen2
IF NOT !CrossGen2Status!==0 (
@@ -238,13 +244,14 @@ if defined RunCrossGen2 (
set __ResponseFile=!__OutputFile!.rsp
del /Q !__ResponseFile! 2>nul
- set __Command=!_DebuggerFullPath!
REM Tests run locally need __TestDotNetCmd (set by runtest.py) or a compatible 5.0 dotnet runtime in the path
if defined __TestDotNetCmd (
- set __Command=!__Command! "!__TestDotNetCmd!"
+ set __DotNet="!__TestDotNetCmd!"
) else (
- set __Command=!__Command! "dotnet"
+ set __DotNet="dotnet"
)
+ set __Command=!_DebuggerFullPath!
+ set __Command=!__Command! !__DotNet!
set __R2RDumpCommand=!__Command! "!CORE_ROOT!\r2rdump\r2rdump.dll"
set __R2RDumpCommand=!__R2RDumpCommand! --header --sc --in !__OutputFile! --out !__OutputFile!.r2rdump --val
set __Command=!__Command! "!CORE_ROOT!\crossgen2\crossgen2.dll"
@@ -269,7 +276,7 @@ if defined RunCrossGen2 (
echo -r:!CORE_ROOT!\mscorlib.dll>>!__ResponseFile!
echo -r:!CORE_ROOT!\netstandard.dll>>!__ResponseFile!
- if not "$(__CreatePdb)" == "" (
+ if defined __CreatePdb (
echo --pdb>>!__ResponseFile!
)
@@ -303,6 +310,21 @@ if defined RunCrossGen2 (
endlocal & set "R2RDumpStatus=%R2RDumpStatus%" &set "CrossGen2Status=%CrossGen2Status%"
echo %time%
+
+ if !CrossGen2Status!==0 (
+ if defined CrossGen2TestCheckPdb (
+ set __CheckPdbCommand=!__DotNet!
+ set __CheckPdbCommand=!__CheckPdbCommand! "!CORE_ROOT!\PdbChecker\PdbChecker.dll"
+ set __CheckPdbCommand=!__CheckPdbCommand! !__PdbFile! @(CheckPdbSymbol->'%22%(Identity)%22', ' ')
+ echo "!__CheckPdbCommand!"
+ call !__CheckPdbCommand!
+ if not !ERRORLEVEL!==0 (
+ echo PDB check failed for file !__PdbFile! >2
+ set CrossGen2Status=42
+ )
+ )
+ )
+
Exit /b 0
:DoneCrossgen2Operations
diff --git a/src/tests/Common/Directory.Build.targets b/src/tests/Common/Directory.Build.targets
index 74e2cc211c5329..faa9d09b22a96a 100644
--- a/src/tests/Common/Directory.Build.targets
+++ b/src/tests/Common/Directory.Build.targets
@@ -108,6 +108,9 @@
True
+
+
+
True
diff --git a/src/tests/readytorun/crossgen2/crossgen2smoke.csproj b/src/tests/readytorun/crossgen2/crossgen2smoke.csproj
index c233cd8dcbd044..dfefd2c243fb15 100644
--- a/src/tests/readytorun/crossgen2/crossgen2smoke.csproj
+++ b/src/tests/readytorun/crossgen2/crossgen2smoke.csproj
@@ -5,6 +5,13 @@
true
+ true
+
+
+
+
+
+