diff --git a/src/coreclr/tools/PdbChecker/MSDiaSymbolReader.cs b/src/coreclr/tools/PdbChecker/MSDiaSymbolReader.cs index ddea0a5ef01743..29c525622afb19 100644 --- a/src/coreclr/tools/PdbChecker/MSDiaSymbolReader.cs +++ b/src/coreclr/tools/PdbChecker/MSDiaSymbolReader.cs @@ -6,6 +6,7 @@ using System.IO; using System.Linq; using System.Reflection; +using System.Reflection.PortableExecutable; using System.Runtime.InteropServices; using Dia2Lib; @@ -32,7 +33,7 @@ void CreateInstance([MarshalAs(UnmanagedType.Interface)] object? aggregator, private readonly List _pdbSymbols; - public MSDiaSymbolReader(string pdbFile) + public MSDiaSymbolReader(string pdbFile, string? imageFile = null) { try { @@ -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(); @@ -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); } @@ -67,6 +88,25 @@ public MSDiaSymbolReader(string pdbFile) } } + /// + /// Read the CodeView / RSDS debug record identity (GUID and age) from a PE image. + /// + 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:"); diff --git a/src/coreclr/tools/PdbChecker/Program.cs b/src/coreclr/tools/PdbChecker/Program.cs index 41f4209ce648dc..a61565d17cd6d2 100644 --- a/src/coreclr/tools/PdbChecker/Program.cs +++ b/src/coreclr/tools/PdbChecker/Program.cs @@ -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 { @@ -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 symbolNames = new List(); + + 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++; @@ -55,6 +80,6 @@ private static void TryMain(string[] args) private static void DisplayUsage() { - Console.WriteLine("Usage: PdbChecker { }"); + Console.WriteLine("Usage: PdbChecker [--image ] { }"); } } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs index c83fce292bf3f0..7f8156d59159a1 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs @@ -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) { @@ -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) diff --git a/src/tests/Common/CLRTest.CrossGen.targets b/src/tests/Common/CLRTest.CrossGen.targets index 8da1a085b5ab0c..98ef6dca7685df 100644 --- a/src/tests/Common/CLRTest.CrossGen.targets +++ b/src/tests/Common/CLRTest.CrossGen.targets @@ -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 (