Skip to content

Instantly share code, notes, and snippets.

@buyaa-n
Last active March 18, 2024 18:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save buyaa-n/9d53c5e0ebbc1f5a30cdf031b6feae2a to your computer and use it in GitHub Desktop.
Save buyaa-n/9d53c5e0ebbc1f5a30cdf031b6feae2a to your computer and use it in GitHub Desktop.
API shape for PDB support

New APIs:

public abstract partial class ModuleBuilder : System.Reflection.Module
{
+    public virtual ISymbolDocumentWriter DefineDocument(string url, Guid language = default) { }
}

 public abstract class ILGenerator
 {
    public abstract void BeginScope();
    public abstract void EndScope();
+   public virtual void MarkSequencePoint(ISymbolDocumentWriter document, int startLine, int startColumn, int endLine, int endColumn) { }
    public abstract void UsingNamespace(string usingNamespace);
 }
 
public abstract class LocalBuilder : LocalVariableInfo
{
+    public virtual void SetLocalSymInfo(string name) { }
}

These new APIs will be used for populating PDB tables when populating metadata with MetadataBuilder GenerateMetadata(out BlobBuilder ilStream, out BlobBuilder mappedFieldData). This is a new API we added recently in PersistedAssemblyBuilder.

public sealed class PersistedAssemblyBuilder : AssemblyBuilder
{
    public PersistedAssemblyBuilder(AssemblyName name, Assembly coreAssembly, IEnumerable<CustomAttributeBuilder>? assemblyAttributes = null);

    public void Save(Stream stream);
    public void Save(string assemblyFileName);
    public MetadataBuilder GenerateMetadata(out BlobBuilder ilStream, out BlobBuilder mappedFieldData)
}

API usage:

static void Test ()
{
    PersistedAssemblyBuilder ab = new PersistedAssemblyBuilder(new AssemblyName("MyAssembly"), typeof(object).Assembly);
    ModuleBuilder mb = ab.DefineDynamicModule("MyModule");
    TypeBuilder tb = mb.DefineType("MyType", TypeAttributes.Public | TypeAttributes.Class);
    MethodBuilder mb1 = tb.DefineMethod("SumMethod", MethodAttributes.Public | MethodAttributes.Static, typeof(int), [typeof(int), typeof(int)]);
    ISymbolDocumentWriter srcDoc = mb.DefineDocument("MySourceFile.cs", SymLanguageType.CSharp);
    ILGenerator il = mb1.GetILGenerator();
    il.MarkSequencePoint(srcDoc, 7, 0, 7, 11);
    il.Emit(OpCodes.Ldarg_0);
    il.Emit(OpCodes.Ldarg_1);
    il.Emit(OpCodes.Add);
    il.Emit(OpCodes.Ret);
    MethodBuilder entryPoint = tb.DefineMethod("Main", MethodAttributes.HideBySig | MethodAttributes.Public | MethodAttributes.Static);
    ILGenerator il2 = entryPoint.GetILGenerator();
    il2.Emit(OpCodes.Ldc_I4_S, 10);
    il2.Emit(OpCodes.Ldc_I4_1);
    il2.MarkSequencePoint(srcDoc, 12, 0, 12, 38);
    il2.Emit(OpCodes.Call, mb1);
    il2.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", [typeof(int)])!);
    il2.Emit(OpCodes.Ret);
    tb.CreateType();

    MetadataBuilder metadataBuilder = ab.GenerateMetadata(out BlobBuilder ilStream, out BlobBuilder fieldData, addPdb: true);

    MetadataRootBuilder mdRootBuilder = new MetadataRootBuilder(metadataBuilder);
    MethodDefinitionHandle entryPointHandle = MetadataTokens.MethodDefinitionHandle(entryPoint.MetadataToken);

    PEHeaderBuilder peHeaderBuilder = new PEHeaderBuilder(imageCharacteristics: Characteristics.ExecutableImage, subsystem: Subsystem.WindowsCui);

    var portablePdbBlob = new BlobBuilder();
    PortablePdbBuilder pdbBuilder = new PortablePdbBuilder(metadataBuilder, mdRootBuilder.Sizes.RowCounts, entryPointHandle);
    BlobContentId pdbContentId = pdbBuilder.Serialize(portablePdbBlob);
    // In case saving PDB to a file
    using var pdbFileStream = new FileStream("MyAssembly.pdb", FileMode.Create, FileAccess.Write);
    portablePdbBlob.WriteContentTo(pdbFileStream);
    
    DebugDirectoryBuilder debugDirectoryBuilder = new DebugDirectoryBuilder();

    // standalone PDB
    debugDirectoryBuilder.AddCodeViewEntry("MyAssembly.pdb", pdbContentId, pdbBuilder.FormatVersion);

    // in case embedded PDB
    // debugDirectoryBuilder.AddEmbeddedPortablePdbEntry(portablePdbBlob, pdbBuilder.FormatVersion);

    ManagedPEBuilder peBuilder = new ManagedPEBuilder(
                    header: peHeaderBuilder,
                    metadataRootBuilder: mdRootBuilder,
                    ilStream: ilStream,
                    mappedFieldData: fieldData,
                    debugDirectoryBuilder: debugDirectoryBuilder,
                    entryPoint: entryPointHandle);

    BlobBuilder peBlob = new BlobBuilder();
    peBuilder.Serialize(peBlob);

    using var fileStream = new FileStream("MyAssembly.exe", FileMode.Create, FileAccess.Write);
    peBlob.WriteContentTo(fileStream);

Below are the things that will not be included, at least with 1st version:

  • .NET framework have no API for adding local constant - so will not added.
  • No support for CustomDebugInformation in .NET framework - will not be added
  • dotnet framework do not support source embedding and source indexing - will not be supported.
  • In .Net framework to generated PDB used module creation public ModuleBuilder DefineDynamicModule (string name, bool emitSymbolInfo); and public ModuleBuilder DefineDynamicModule (string name, string fileName, bool emitSymbolInfo);, for we will have option, can be added later (debug info will be populated in metadata if there any added).
  • ModuleBuilder.GetSymWriter() Method that returns ISymbolWriter is for calling native code to add debug info, we are not using native APIs - so it will not be added.
  • Validation difference in sequence points: portable PDB: If Start Line is equal to End Line then End Column is greater than Start Column. this was not validated in .NET framework. Now in Core it will throw.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment