Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

System.Reflection.Emit SetChecksum produces invalid pdb #110096

Open
tgjones opened this issue Nov 22, 2024 · 1 comment · May be fixed by #110097
Open

System.Reflection.Emit SetChecksum produces invalid pdb #110096

tgjones opened this issue Nov 22, 2024 · 1 comment · May be fixed by #110097
Labels
area-System.Reflection.Emit in-pr There is an active PR which will close this issue when it is merged untriaged New issue has not been triaged by the area owner

Comments

@tgjones
Copy link

tgjones commented Nov 22, 2024

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

@dotnet-issue-labeler dotnet-issue-labeler bot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Nov 22, 2024
@dotnet-policy-service dotnet-policy-service bot added the untriaged New issue has not been triaged by the area owner label Nov 22, 2024
@dotnet-policy-service dotnet-policy-service bot added the in-pr There is an active PR which will close this issue when it is merged label Nov 22, 2024
@tgjones tgjones changed the title System.Reflection.Emit.SymbolDocumentWriter.SetChecksum produces corrupt pdb System.Reflection.Emit SetChecksum produces invalid pdb Nov 22, 2024
@vcsjones vcsjones added area-System.Reflection.Emit and removed needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners labels Nov 23, 2024
Copy link
Contributor

Tagging subscribers to this area: @dotnet/area-system-reflection-emit
See info in area-owners.md if you want to be subscribed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-System.Reflection.Emit in-pr There is an active PR which will close this issue when it is merged untriaged New issue has not been triaged by the area owner
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants