Debugger Engine. Method rewriting, local variables hoisting and variables resolution
Asked Answered
C

1

6

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

Costin answered 13/9, 2016 at 13:35 Comment(0)
C
2

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

Costin answered 3/10, 2016 at 8:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.