From 696cbc2136c1c8a743497a297793b30cacc31100 Mon Sep 17 00:00:00 2001 From: "Andrew Crawley (US - DIAGNOSTICS)" Date: Tue, 26 Apr 2022 18:52:49 -0700 Subject: [PATCH] Allow user to provide ID when generating a PDB This commit adds a new parameter to PortablePdbWriter.WritePdb that allows the caller to specify the exact Guid and timestamp that should be used in the generated PDB. This will be useful for several scenarios that are interesting for the Visual Studio debugger's integration: 1. Generating a PDB for an assembly that was built without debug info. The PDB writer currently fails in this case, since the input assembly has no debug directory from which to extract the relevant info. The debugger can provide values that will allow us to load the generated PDB. 2. Generating a PDB for an assembly that has multiple debug directories. The PDB writer currently uses the first debug directory it finds, but this isn't necessarily the correct one. The debugger can provide the correct values. --- .../PdbGenerationTestRunner.cs | 47 ++++++++++++++++--- .../DebugInfo/PortablePdbWriter.cs | 14 ++++-- 2 files changed, 50 insertions(+), 11 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/PdbGenerationTestRunner.cs b/ICSharpCode.Decompiler.Tests/PdbGenerationTestRunner.cs index 35f1eede4..f55098e8e 100644 --- a/ICSharpCode.Decompiler.Tests/PdbGenerationTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/PdbGenerationTestRunner.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Reflection.Metadata; using System.Reflection.PortableExecutable; using System.Runtime.CompilerServices; using System.Text; @@ -46,18 +47,38 @@ namespace ICSharpCode.Decompiler.Tests TestGeneratePdb(); } + [Test] + public void CustomPdbId() + { + // Generate a PDB for an assembly using a randomly-generated ID, then validate that the PDB uses the specified ID + (string peFileName, string pdbFileName) = CompileTestCase(nameof(HelloWorld)); + + var moduleDefinition = new PEFile(peFileName); + var resolver = new UniversalAssemblyResolver(peFileName, false, moduleDefinition.Metadata.DetectTargetFrameworkId(), null, PEStreamOptions.PrefetchEntireImage); + var decompiler = new CSharpDecompiler(moduleDefinition, resolver, new DecompilerSettings()); + var expectedPdbId = new BlobContentId(Guid.NewGuid(), (uint)Random.Shared.Next()); + + using (FileStream pdbStream = File.Open(Path.Combine(TestCasePath, nameof(CustomPdbId) + ".pdb"), FileMode.OpenOrCreate, FileAccess.ReadWrite)) + { + pdbStream.SetLength(0); + PortablePdbWriter.WritePdb(moduleDefinition, decompiler, new DecompilerSettings(), pdbStream, noLogo: true, pdbId: expectedPdbId); + + pdbStream.Position = 0; + var metadataReader = MetadataReaderProvider.FromPortablePdbStream(pdbStream).GetMetadataReader(); + var generatedPdbId = new BlobContentId(metadataReader.DebugMetadataHeader.Id); + + Assert.AreEqual(expectedPdbId.Guid, generatedPdbId.Guid); + Assert.AreEqual(expectedPdbId.Stamp, generatedPdbId.Stamp); + } + } + private void TestGeneratePdb([CallerMemberName] string testName = null) { const PdbToXmlOptions options = PdbToXmlOptions.IncludeEmbeddedSources | PdbToXmlOptions.ThrowOnError | PdbToXmlOptions.IncludeTokens | PdbToXmlOptions.ResolveTokens | PdbToXmlOptions.IncludeMethodSpans; string xmlFile = Path.Combine(TestCasePath, testName + ".xml"); - string xmlContent = File.ReadAllText(xmlFile); - XDocument document = XDocument.Parse(xmlContent); - var files = document.Descendants("file").ToDictionary(f => f.Attribute("name").Value, f => f.Value); - Tester.CompileCSharpWithPdb(Path.Combine(TestCasePath, testName + ".expected"), files); + (string peFileName, string pdbFileName) = CompileTestCase(testName); - string peFileName = Path.Combine(TestCasePath, testName + ".expected.dll"); - string pdbFileName = Path.Combine(TestCasePath, testName + ".expected.pdb"); var moduleDefinition = new PEFile(peFileName); var resolver = new UniversalAssemblyResolver(peFileName, false, moduleDefinition.Metadata.DetectTargetFrameworkId(), null, PEStreamOptions.PrefetchEntireImage); var decompiler = new CSharpDecompiler(moduleDefinition, resolver, new DecompilerSettings()); @@ -87,6 +108,20 @@ namespace ICSharpCode.Decompiler.Tests Assert.AreEqual(Normalize(expectedFileName), Normalize(generatedFileName)); } + private (string peFileName, string pdbFileName) CompileTestCase(string testName) + { + string xmlFile = Path.Combine(TestCasePath, testName + ".xml"); + string xmlContent = File.ReadAllText(xmlFile); + XDocument document = XDocument.Parse(xmlContent); + var files = document.Descendants("file").ToDictionary(f => f.Attribute("name").Value, f => f.Value); + Tester.CompileCSharpWithPdb(Path.Combine(TestCasePath, testName + ".expected"), files); + + string peFileName = Path.Combine(TestCasePath, testName + ".expected.dll"); + string pdbFileName = Path.Combine(TestCasePath, testName + ".expected.pdb"); + + return (peFileName, pdbFileName); + } + private void ProcessXmlFile(string fileName) { var document = XDocument.Load(fileName); diff --git a/ICSharpCode.Decompiler/DebugInfo/PortablePdbWriter.cs b/ICSharpCode.Decompiler/DebugInfo/PortablePdbWriter.cs index ba010a45d..cc8e342bd 100644 --- a/ICSharpCode.Decompiler/DebugInfo/PortablePdbWriter.cs +++ b/ICSharpCode.Decompiler/DebugInfo/PortablePdbWriter.cs @@ -49,7 +49,7 @@ namespace ICSharpCode.Decompiler.DebugInfo return file.Reader.ReadDebugDirectory().Any(entry => entry.Type == DebugDirectoryEntryType.CodeView); } - public static void WritePdb(PEFile file, CSharpDecompiler decompiler, DecompilerSettings settings, Stream targetStream, bool noLogo = false) + public static void WritePdb(PEFile file, CSharpDecompiler decompiler, DecompilerSettings settings, Stream targetStream, bool noLogo = false, BlobContentId? pdbId = null) { MetadataBuilder metadata = new MetadataBuilder(); MetadataReader reader = file.Metadata; @@ -195,10 +195,14 @@ namespace ICSharpCode.Decompiler.DebugInfo metadata.AddCustomDebugInformation(row.Parent, row.Guid, row.Blob); } - var debugDir = file.Reader.ReadDebugDirectory().FirstOrDefault(dir => dir.Type == DebugDirectoryEntryType.CodeView); - var portable = file.Reader.ReadCodeViewDebugDirectoryData(debugDir); - var contentId = new BlobContentId(portable.Guid, debugDir.Stamp); - PortablePdbBuilder serializer = new PortablePdbBuilder(metadata, GetRowCounts(reader), entrypointHandle, blobs => contentId); + if (pdbId == null) + { + var debugDir = file.Reader.ReadDebugDirectory().FirstOrDefault(dir => dir.Type == DebugDirectoryEntryType.CodeView); + var portable = file.Reader.ReadCodeViewDebugDirectoryData(debugDir); + pdbId = new BlobContentId(portable.Guid, debugDir.Stamp); + } + + PortablePdbBuilder serializer = new PortablePdbBuilder(metadata, GetRowCounts(reader), entrypointHandle, blobs => pdbId.Value); BlobBuilder blobBuilder = new BlobBuilder(); serializer.Serialize(blobBuilder); blobBuilder.WriteContentTo(targetStream);