Browse Source

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.
pull/2678/head
Andrew Crawley (US - DIAGNOSTICS) 3 years ago
parent
commit
696cbc2136
  1. 47
      ICSharpCode.Decompiler.Tests/PdbGenerationTestRunner.cs
  2. 14
      ICSharpCode.Decompiler/DebugInfo/PortablePdbWriter.cs

47
ICSharpCode.Decompiler.Tests/PdbGenerationTestRunner.cs

@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable; using System.Reflection.PortableExecutable;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Text; using System.Text;
@ -46,18 +47,38 @@ namespace ICSharpCode.Decompiler.Tests
TestGeneratePdb(); 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) private void TestGeneratePdb([CallerMemberName] string testName = null)
{ {
const PdbToXmlOptions options = PdbToXmlOptions.IncludeEmbeddedSources | PdbToXmlOptions.ThrowOnError | PdbToXmlOptions.IncludeTokens | PdbToXmlOptions.ResolveTokens | PdbToXmlOptions.IncludeMethodSpans; const PdbToXmlOptions options = PdbToXmlOptions.IncludeEmbeddedSources | PdbToXmlOptions.ThrowOnError | PdbToXmlOptions.IncludeTokens | PdbToXmlOptions.ResolveTokens | PdbToXmlOptions.IncludeMethodSpans;
string xmlFile = Path.Combine(TestCasePath, testName + ".xml"); string xmlFile = Path.Combine(TestCasePath, testName + ".xml");
string xmlContent = File.ReadAllText(xmlFile); (string peFileName, string pdbFileName) = CompileTestCase(testName);
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");
var moduleDefinition = new PEFile(peFileName); var moduleDefinition = new PEFile(peFileName);
var resolver = new UniversalAssemblyResolver(peFileName, false, moduleDefinition.Metadata.DetectTargetFrameworkId(), null, PEStreamOptions.PrefetchEntireImage); var resolver = new UniversalAssemblyResolver(peFileName, false, moduleDefinition.Metadata.DetectTargetFrameworkId(), null, PEStreamOptions.PrefetchEntireImage);
var decompiler = new CSharpDecompiler(moduleDefinition, resolver, new DecompilerSettings()); var decompiler = new CSharpDecompiler(moduleDefinition, resolver, new DecompilerSettings());
@ -87,6 +108,20 @@ namespace ICSharpCode.Decompiler.Tests
Assert.AreEqual(Normalize(expectedFileName), Normalize(generatedFileName)); 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) private void ProcessXmlFile(string fileName)
{ {
var document = XDocument.Load(fileName); var document = XDocument.Load(fileName);

14
ICSharpCode.Decompiler/DebugInfo/PortablePdbWriter.cs

@ -49,7 +49,7 @@ namespace ICSharpCode.Decompiler.DebugInfo
return file.Reader.ReadDebugDirectory().Any(entry => entry.Type == DebugDirectoryEntryType.CodeView); 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(); MetadataBuilder metadata = new MetadataBuilder();
MetadataReader reader = file.Metadata; MetadataReader reader = file.Metadata;
@ -195,10 +195,14 @@ namespace ICSharpCode.Decompiler.DebugInfo
metadata.AddCustomDebugInformation(row.Parent, row.Guid, row.Blob); metadata.AddCustomDebugInformation(row.Parent, row.Guid, row.Blob);
} }
var debugDir = file.Reader.ReadDebugDirectory().FirstOrDefault(dir => dir.Type == DebugDirectoryEntryType.CodeView); if (pdbId == null)
var portable = file.Reader.ReadCodeViewDebugDirectoryData(debugDir); {
var contentId = new BlobContentId(portable.Guid, debugDir.Stamp); var debugDir = file.Reader.ReadDebugDirectory().FirstOrDefault(dir => dir.Type == DebugDirectoryEntryType.CodeView);
PortablePdbBuilder serializer = new PortablePdbBuilder(metadata, GetRowCounts(reader), entrypointHandle, blobs => contentId); 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(); BlobBuilder blobBuilder = new BlobBuilder();
serializer.Serialize(blobBuilder); serializer.Serialize(blobBuilder);
blobBuilder.WriteContentTo(targetStream); blobBuilder.WriteContentTo(targetStream);

Loading…
Cancel
Save