Reputation: 3885
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:
this.generatedStructureName
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
Upvotes: 6
Views: 184
Reputation: 3885
Well, I haven't found anyone here or on msdn forums who would enlighten me about how VS algorithm works, but I've found that SharpDevelop supports variables resolution for async methods. Surprisingly it was using similar algorithm to what I've described in my question: just parsing hoisted field names.
Related source could is available here on gitHub, if someone else will run into similar problems and will be stuck. Still, I don't consider this a good solution and hoping there is a better way to solve the issue...
Upvotes: 2