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've already asked a more general question about these problems. Now I want to focus on local variables resolution. Please consider 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 debugger reaches Breakpoint I'm querying debugger to get local variables and the only variable is this
. 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 here