I am making a managed .NET debugger using an MDBG sample. It works for simple scripts, but has problems rewriting the method. Most critical parts are profitability and asynchronous methods.
I have already asked a more general question about these issues. Now I want to focus on solving local variables. Please pay attention to the code:
using System; using System.Threading.Tasks; class C { public static void Main() { var instance = new Instance(); instance.Start().Wait(); } } class Instance { public static async Task F() { for(var i=0; i<100; i++) { Console.WriteLine(i); await Task.Delay(100); } } public async Task Start() { var z = "test";<------- Breakpoint var x = 10; await F(); } }
When the debugger reaches the breakpoint, I request the debugger to get local variables, and the only this variable. Variables x and z rise to the generated structure and cannot be resolved directly
.
So the question is: How to resolve local variables in the yield method and asynchronous methods while debugging?
In the comments on my previous question, @Brian Reichle gave me some tips on how I can get a mapping between an existing variable and a raised one. Studying SymAttribute and the Roslyn source, I came to the conclusion that it does not preserve a direct mapping between them. SymAttribute used to get CustomDebugInfoRecord , which stores some of this information (uses the Pdb2Xml library from Roslyn to create it):
<method containingType="Instance+<Start>d__1" name="MoveNext"> <customDebugInfo> <forward declaringType="C" methodName="Main" /> <hoistedLocalScopes> <slot startOffset="0x0" endOffset="0xcc" /> <slot startOffset="0x0" endOffset="0xcc" /> </hoistedLocalScopes> <encLocalSlotMap> <slot kind="27" offset="0" /> <slot kind="33" offset="161" /> <slot kind="temp" /> <slot kind="temp" /> </encLocalSlotMap> </customDebugInfo> <sequencePoints> <entry offset="0x0" hidden="true" document="1" /> <entry offset="0x7" hidden="true" document="1" /> <entry offset="0xe" startLine="16" startColumn="37" endLine="16" endColumn="38" document="1" /> <entry offset="0xf" startLine="17" startColumn="14" endLine="17" endColumn="29" document="1" /> <entry offset="0x1a" startLine="18" startColumn="14" endLine="18" endColumn="35" document="1" /> <entry offset="0x26" startLine="19" startColumn="14" endLine="19" endColumn="25" document="1" /> <entry offset="0x2e" startLine="19" startColumn="25" endLine="19" endColumn="46" document="1" /> <entry offset="0x3a" startLine="20" startColumn="14" endLine="20" endColumn="24" document="1" /> <entry offset="0x45" hidden="true" document="1" /> <entry offset="0xa0" hidden="true" document="1" /> <entry offset="0xb8" startLine="21" startColumn="11" endLine="21" endColumn="12" document="1" /> <entry offset="0xc0" hidden="true" document="1" /> </sequencePoints> <asyncInfo> <kickoffMethod declaringType="Instance" methodName="Start" /> <await yield="0x57" resume="0x72" declaringType="Instance+<Start>d__1" methodName="MoveNext" /> </asyncInfo> </method>
Thus, the only way that I can see now for resolving inserted variables is:
- Check if the method is overwritten.
- For such a method, get asyncInfo and look for it to declare declaringType. It gives the name of the structure that is generated and where the variables go up.
- Solve
this.generatedStructureName - Roslyn source code shows naming conventions for inverted variables that you can use to translate the variable
x into <x>5__2
This approach seems wrong, and I'm not sure that it will ever work, but that is the only thing I can think of now. Is there any other way to solve this problem? How does VisualStudio handle this?
I created a small repo to reproduce the problem here
source share