Skip to content

System.Reflection.Emit SetChecksum produces invalid pdb #110096

@tgjones

Description

@tgjones

Description

Calling ISymbolDocumentWriter.SetChecksum on a ISymbolDocumentWriter returned from ModuleBuilder.DefineDocument results in a pdb with a document table containing a document with a Hash field that is an invalid blob handle.

I've traced the problem to this line:

hash: hash == null ? default : _metadataBuilder.GetOrAddBlob(hash),

That line should refer to _pdbBuilder, not _metadataBuilder. Because it's _metadataBuilder, the blob handle actually points into the blob heap in the emitted .dll, not the .pdb.

I'll put up a PR with the fix.

Reproduction Steps

using System.Diagnostics.SymbolStore;
using System.IO;
using System.Linq;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
using System.Reflection.PortableExecutable;

namespace System.Reflection.Emit.Tests;

public class PortablePdbStandalonePdbTest
{
    private static readonly Guid HashAlgorithmSha256 = new Guid("8829d00f-11b8-4213-878b-770e8597ac16");
    private static readonly byte[] TestHash = Convert.FromHexString("06CBAB3A501306FDD9176A00A83E5BB92EA4D7863CFD666355743527CF99EDC6");

    public static void Main()
    {
        var pdbFile = Path.GetTempFileName();
        var file = Path.GetTempFileName();

        try
        {
            MetadataBuilder metadataBuilder = GenerateAssemblyAndMetadata(out var entryPoint, out BlobBuilder ilStream, out MetadataBuilder pdbMetadata);
            MethodDefinitionHandle entryPointHandle = MetadataTokens.MethodDefinitionHandle(entryPoint.MetadataToken);

            BlobBuilder portablePdbBlob = new BlobBuilder();
            PortablePdbBuilder pdbBuilder = new PortablePdbBuilder(pdbMetadata, metadataBuilder.GetRowCounts(), entryPointHandle);
            BlobContentId pdbContentId = pdbBuilder.Serialize(portablePdbBlob);
            using var pdbFileStream = new FileStream(pdbFile, FileMode.Create, FileAccess.Write);
            portablePdbBlob.WriteContentTo(pdbFileStream);
            pdbFileStream.Close();

            DebugDirectoryBuilder debugDirectoryBuilder = new DebugDirectoryBuilder();
            debugDirectoryBuilder.AddCodeViewEntry(pdbFile, pdbContentId, pdbBuilder.FormatVersion);

            ManagedPEBuilder peBuilder = new ManagedPEBuilder(
                header: new PEHeaderBuilder(imageCharacteristics: Characteristics.ExecutableImage),
                metadataRootBuilder: new MetadataRootBuilder(metadataBuilder),
                ilStream: ilStream,
                debugDirectoryBuilder: debugDirectoryBuilder,
                entryPoint: entryPointHandle);

            BlobBuilder peBlob = new BlobBuilder();
            peBuilder.Serialize(peBlob);
            using var assemblyFileStream = new FileStream(file, FileMode.Create, FileAccess.Write);
            peBlob.WriteContentTo(assemblyFileStream);
            assemblyFileStream.Close();

            using var fs = new FileStream(pdbFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
            using MetadataReaderProvider provider = MetadataReaderProvider.FromPortablePdbStream(fs);
            ValidatePDB(provider.GetMetadataReader());
        }
        finally
        {
            File.Delete(pdbFile);
            File.Delete(file);
        }
    }

    private static MetadataBuilder GenerateAssemblyAndMetadata(
        out MethodBuilder entryPoint, out BlobBuilder ilStream, out MetadataBuilder pdbMetadata)
    {
        PersistedAssemblyBuilder ab = new PersistedAssemblyBuilder(new AssemblyName("MyAssembly2"), typeof(object).Assembly);
        ModuleBuilder mb = ab.DefineDynamicModule("MyModule2");
        TypeBuilder tb = mb.DefineType("MyType", TypeAttributes.Public | TypeAttributes.Class);
        ISymbolDocumentWriter srcdoc = mb.DefineDocument("MySourceFile.cs", SymLanguageType.CSharp);
        srcdoc.SetCheckSum(HashAlgorithmSha256, TestHash);
        
        entryPoint = tb.DefineMethod("Mm", MethodAttributes.HideBySig | MethodAttributes.Public | MethodAttributes.Static);
        ILGenerator il2 = entryPoint.GetILGenerator();
        il2.MarkSequencePoint(srcdoc, 12, 0, 12, 37);
        il2.Emit(OpCodes.Ret);
        tb.CreateType();
        return ab.GenerateMetadata(out ilStream, out BlobBuilder _, out pdbMetadata);
    }

    private static void ValidatePDB(MetadataReader reader)
    {
        var docHandle = reader.Documents.Single();
        Document doc = reader.GetDocument(docHandle);
        
        if (reader.GetGuid(doc.HashAlgorithm) != HashAlgorithmSha256)
        {
            throw new Exception("Incorrect hash algorithm");
        }

        if (!reader.GetBlobBytes(doc.Hash).SequenceEqual(TestHash))
        {
            throw new Exception("Incorrect hash");
        }
    }
}

Expected behavior

I expect the repro above to succeed.

Actual behavior

It crashes while attempting to retrieve the blob bytes corresponding to the document's Hash blob handle:

Image

Regression?

No response

Known Workarounds

No response

Configuration

.NET 9.0, Windows x64

Other information

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions