Skip to content
44 changes: 42 additions & 2 deletions src/coreclr/tools/PdbChecker/MSDiaSymbolReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.PortableExecutable;
using System.Runtime.InteropServices;
using Dia2Lib;

Expand All @@ -32,7 +33,7 @@ void CreateInstance([MarshalAs(UnmanagedType.Interface)] object? aggregator,

private readonly List<string> _pdbSymbols;

public MSDiaSymbolReader(string pdbFile)
public MSDiaSymbolReader(string pdbFile, string? imageFile = null)
{
try
{
Expand All @@ -41,7 +42,24 @@ public MSDiaSymbolReader(string pdbFile)
diaClassFactory.CreateInstance(null, typeof(IDiaDataSource).GetTypeInfo().GUID, out object comObject);

_diaDataSource = (IDiaDataSource)comObject;
_diaDataSource.loadDataFromPdb(pdbFile);

if (imageFile is not null)
{
// Validate the PDB against the image's CodeView / RSDS identity. DIA throws if the
// PDB-info GUID / age don't match the image
(Guid imageGuid, uint imageAge) = ReadImageCodeViewIdentity(imageFile);
Console.WriteLine("Image file: {0}", imageFile);
Console.WriteLine("Image GUID: {0}", imageGuid);
Console.WriteLine("Image age: {0}", imageAge);
// sig is 0: R2R images use the RSDS (PDB 7.0) CodeView record, whose identity is the
// GUID + age only; the legacy NB10 32-bit signature does not apply.
_diaDataSource.loadAndValidateDataFromPdb(pdbFile, ref imageGuid, 0, imageAge);
}
else
{
_diaDataSource.loadDataFromPdb(pdbFile);
}

_diaDataSource.openSession(out _diaSession);

_pdbSymbols = new List<string>();
Expand All @@ -57,7 +75,10 @@ public MSDiaSymbolReader(string pdbFile)
}
}

IDiaSymbol globalScope = _diaSession.globalScope;
Console.WriteLine("PDB file: {0}", pdbFile);
Console.WriteLine("PDB GUID: {0}", globalScope.guid);
Console.WriteLine("PDB age: {0}", globalScope.age);
Console.WriteLine("Total symbols: {0}", symbolsTotal);
Console.WriteLine("Public symbols: {0}", _pdbSymbols.Count);
}
Expand All @@ -67,6 +88,25 @@ public MSDiaSymbolReader(string pdbFile)
}
}

/// <summary>
/// Read the CodeView / RSDS debug record identity (GUID and age) from a PE image.
/// </summary>
private static (Guid Guid, uint Age) ReadImageCodeViewIdentity(string imageFile)
{
using FileStream imageStream = File.OpenRead(imageFile);
using PEReader peReader = new PEReader(imageStream);
foreach (DebugDirectoryEntry entry in peReader.ReadDebugDirectory())
{
if (entry.Type == DebugDirectoryEntryType.CodeView)
{
CodeViewDebugDirectoryData codeViewData = peReader.ReadCodeViewDebugDirectoryData(entry);
return (codeViewData.Guid, (uint)codeViewData.Age);
}
}

throw new Exception($"Image file {imageFile} does not contain a CodeView (RSDS) debug directory entry");
}

public void DumpSymbols()
{
Console.WriteLine("PDB public symbol list:");
Expand Down
35 changes: 30 additions & 5 deletions src/coreclr/tools/PdbChecker/Program.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// 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 Dia2Lib;
class Program
{
Expand All @@ -26,12 +27,36 @@ private static void TryMain(string[] args)
DisplayUsage();
return;
}
MSDiaSymbolReader reader = new MSDiaSymbolReader(args[0]);

string pdbFile = args[0];
string? imageFile = null;
List<string> symbolNames = new List<string>();

for (int argIndex = 1; argIndex < args.Length; argIndex++)
{
string arg = args[argIndex];
if (arg.Equals("--image", StringComparison.OrdinalIgnoreCase))
{
if (++argIndex >= args.Length)
{
throw new Exception("Missing image file path after --image");
}
imageFile = args[argIndex];
}
else
{
symbolNames.Add(arg);
}
}

// When an image is provided, MSDiaSymbolReader validates that the PDB-info identity
// matches the image's CodeView / RSDS record, guarding against the zero-GUID native PDB bug.
MSDiaSymbolReader reader = new MSDiaSymbolReader(pdbFile, imageFile);

int matchedSymbols = 0;
int missingSymbols = 0;
for (int symbolArgIndex = 1; symbolArgIndex < args.Length; symbolArgIndex++)
foreach (string symbolName in symbolNames)
{
string symbolName = args[symbolArgIndex];
if (reader.ContainsSymbol(symbolName))
{
matchedSymbols++;
Expand All @@ -55,6 +80,6 @@ private static void TryMain(string[] args)

private static void DisplayUsage()
{
Console.WriteLine("Usage: PdbChecker <pdb file to check> { <symbol to check for existence in the PDB file> }");
Console.WriteLine("Usage: PdbChecker <pdb file to check> [--image <image file to match PDB identity>] { <symbol to check for existence in the PDB file> }");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -187,8 +187,15 @@ public void EmitReadyToRunObjects(ReadyToRunContainerFormat format, Logger logge
_ => throw new UnreachableException()
};

using FileStream stream = new FileStream(_objectFilePath, FileMode.Create);
objectWriter.EmitObject(stream, _nodes, dumper: null, logger);
long outputFileSize;
// Close and flush the output stream before generating symbol files (PDB / PerfMap),
// which read the finished image back from disk. Leaving the stream open here would
// let the PDB writer observe an incomplete file (e.g. a zeroed CodeView/RSDS GUID).
using (FileStream stream = new FileStream(_objectFilePath, FileMode.Create))
{
objectWriter.EmitObject(stream, _nodes, dumper: null, logger);
outputFileSize = stream.Length;
}

if (_outputInfoBuilder is not null)
{
Expand All @@ -198,7 +205,7 @@ public void EmitReadyToRunObjects(ReadyToRunContainerFormat format, Logger logge

if (_mapFileBuilder != null)
{
_mapFileBuilder.SetFileSize(stream.Length);
_mapFileBuilder.SetFileSize(outputFileSize);
}

if (_outputInfoBuilder is not null)
Expand Down
2 changes: 1 addition & 1 deletion src/tests/Common/CLRTest.CrossGen.targets
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,7 @@ if defined RunCrossGen2 (
if defined CrossGen2TestCheckPdb (
set __CheckPdbCommand=!__DotNet!
set __CheckPdbCommand=!__CheckPdbCommand! "!CORE_ROOT!\PdbChecker\PdbChecker.dll"
set __CheckPdbCommand=!__CheckPdbCommand! !__PdbFile! @(CheckPdbSymbol->'%22%(Identity)%22', ' ')
set __CheckPdbCommand=!__CheckPdbCommand! !__PdbFile! --image !__OutputFile! @(CheckPdbSymbol->'%22%(Identity)%22', ' ')
echo "!__CheckPdbCommand!"
call !__CheckPdbCommand!
if not !ERRORLEVEL!==0 (
Expand Down