I'm making a managed .NET debugger using MDBG sample. It works for straightforward scenarios, but has issues when method rewriting occurs. Most critical parts are yield method and async methods.
I want to focus on local variables resolution. Please consider the code:
using System;
using System.Threading.Tasks;class C{publicstaticvoidMain(){var instance =newInstance();
instance.Start().Wait();}}classInstance{publicstatic async Task F(){for(var i=0; i<100; i++){Console.WriteLine(i); await Task.Delay(100);}}public async TaskStart(){var z ="test";<-------Breakpointvar x =10;
await F();}}
When debugger reaches Breakpoint I'm querying debugger to get local variables and the only variable isthis
. Variables x
and z
are hoisted on generated structure and cannot be resolved directly .
So the question is: How to resolve during debug local variables in yield method and async methods?
In comments to my previous question @Brian Reichle gave me some hints how I can get mapping between existing variable and hoisted one. Exploring SymAttribute and Roslyn source I came to conclusion that it doesn't directly store mapping between them.SymAttribute
is used to get CustomDebugInfoRecord
, which stores part of this information(Used Pdb2Xml library from Roslyn to generate 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>
So the only way I can see now to resolve hoisted variables is:
- Check if method is rewritten.
- For such method get asyncInfo and find it's await declaringType. It gives the name of structure that is generated and where the variables are hoisted.
- Resolve
this.generatedStructureName
- Roslyn source code reveals naming conventions for hoisted variables, that can be used to translate
x
variable into<x>5__2
This approach doesn't seems right and I'm not sure if it will ever work out, but it's the only thing I can think of now. Is there any other possibility to solve this problem? How does VisualStudio tackle it?
I've created a small repo to reproduce the problem, but I cannot post the link...