Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Debugger Engine. Method rewriting, local variables hoisting and variables resolution

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 Image.

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+&lt;Start&gt;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+&lt;Start&gt;d__1" methodName="MoveNext" />
      </asyncInfo>
    </method>

So the only way I can see now to resolve hoisted variables is:

  1. Check if method is rewritten.
  2. 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.
  3. Resolve this.generatedStructureName
  4. 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

like image 868
3615 Avatar asked Sep 13 '16 13:09

3615


People also ask

Which of the following windows display the variables while debugging?

The Autos and Locals windows show variable values while you are debugging. The windows are only available during a debugging session. The Autos window shows variables used around the current breakpoint.

How do I view locals in Visual Studio?

You can manually invoke this window from the Visual Studio menu, Debug | Windows | Locals, or by pressing the keyboard shortcut Ctrl + D + L: Get Mastering Visual Studio 2017 now with the O'Reilly learning platform.

How do I view variables in Visual Studio?

If you want to continue to watch a variable, you can add it to a Watch window from a data tip. Right-click the variable in the data tip, and select Add Watch. The variable appears in the Watch window. If your Visual Studio edition supports more than one Watch window, the variable appears in Watch 1.


1 Answers

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...

like image 198
3615 Avatar answered Oct 12 '22 12:10

3615