Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to debug dll generated from Roslyn compilation?

I’m using Roslyn CSharpCompilation to generate dll files for my plugins – files have OptimizationLevel.Debug and pdb file is generated. Next I’m loading those files to my program (UWP + .NET Standard 2.0 libs) using Assembly.Load and create instance of types I’m interested in. My problem is that I can’t get Visual Studio (version 2017 15.7.3) to find source code when I’m debugging – it is treading it like external library, so when exception is thrown inside I can't find where. I have tired to search solution on stackoverflow but all solution are not working. I have checked this:

  • Pdb is generated
  • Module window in VS shows that symbols are loaded
  • Tried different version of Assembly Load/LoadFrom
  • Setting “Use Managed Compatibility Mode” in debug options

Is there any way to make the file debuggable? Maybe I have to use some roslyn option while compiling or change something in VS?

like image 565
dnf Avatar asked Jun 01 '18 19:06

dnf


People also ask

How do I debug a referenced DLL with PDB?

What I suggest is 1) Include the path of the dll in your B project, 2) Then compile in debug your A project 3) Control that the path points on the A dll and de pdb file.... 4)After that you start in debug the B project and if all is ok, you will be able to debug in both projects !


1 Answers

The code sample below should help you on your way. It`s based on the code generation part of thlamare IOC container lamar, the successor of StructureMap made by Jeremy D Miller.

I have only added debugging capabilities. The trick was to make the source text embeddable, choosing the right formats, and setting encoding values where needed.

Check out the original work for more details of e.g. adding references.

public Assembly CreateAssembly(string code)
{
    var encoding = Encoding.UTF8;

    var assemblyName = Path.GetRandomFileName();
    var symbolsName = Path.ChangeExtension(assemblyName, "pdb");
    var sourceCodePath = "generated.cs";

    var buffer = encoding.GetBytes(code);
    var sourceText = SourceText.From(buffer, buffer.Length, encoding, canBeEmbedded: true);

    var syntaxTree = CSharpSyntaxTree.ParseText(
        sourceText, 
        new CSharpParseOptions(), 
        path: sourceCodePath);

    var syntaxRootNode = syntaxTree.GetRoot() as CSharpSyntaxNode;
    var encoded = CSharpSyntaxTree.Create(syntaxRootNode, null, sourceCodePath, encoding);

    var optimizationLevel = OptimizationLevel.Debug;

    CSharpCompilation compilation = CSharpCompilation.Create(
        assemblyName,
        syntaxTrees: new[] { encoded },
        references: references,
        options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
            .WithOptimizationLevel(optimizationLevel)
            .WithPlatform(Platform.AnyCpu)
    );

    using (var assemblyStream = new MemoryStream())
    using (var symbolsStream = new MemoryStream())
    {
        var emitOptions = new EmitOptions(
                debugInformationFormat: DebugInformationFormat.PortablePdb,
                pdbFilePath: symbolsName);

        var embeddedTexts = new List<EmbeddedText>
        {
            EmbeddedText.FromSource(sourceCodePath, sourceText),
        };

        EmitResult result = compilation.Emit(
            peStream: assemblyStream,
            pdbStream: symbolsStream,
            embeddedTexts: embeddedTexts,
            options: emitOptions);

        if (!result.Success)
        {
            var errors = new List<string>();

            IEnumerable<Diagnostic> failures = result.Diagnostics.Where(diagnostic =>
                diagnostic.IsWarningAsError ||
                diagnostic.Severity == DiagnosticSeverity.Error);

            foreach (Diagnostic diagnostic in failures)
                errors.Add($"{diagnostic.Id}: {diagnostic.GetMessage()}");

            throw new Exception(String.Join("\n", errors));
        }

        Console.WriteLine(code);

        assemblyStream.Seek(0, SeekOrigin.Begin);
        symbolsStream?.Seek(0, SeekOrigin.Begin);

        var assembly = AssemblyLoadContext.Default.LoadFromStream(assemblyStream, symbolsStream);
        return assembly;
    }
}

Usage:

[Test]
public void Verify()
{
    var code =
        @"namespace Debuggable
        {
            public class HelloWorld
            {
                public string Greet(string name)
                {
                    var result = ""Hello, "" + name;
                    return result;
                }
            }
        }
        ";

    var codeGenerator = new CodeGenerator();
    var assembly = codeGenerator.CreateAssembly(code);

    dynamic instance = assembly.CreateInstance("Debuggable.HelloWorld");

    // Set breakpoint here
    string result = instance.Greet("Roslyn");

    result.Should().Be("Hello, Roslyn");
}
like image 132
Stian Øyna Avatar answered Sep 23 '22 21:09

Stian Øyna