在调试器中正确地使你的.NET语言步骤正确

首先,我对这个问题的长度表示歉意。

我是IronScheme的作者。 最近我一直在努力发布体面的调试信息,以便我可以使用“本地”.NET调试器。

虽然这部分取得了成功,但我遇到了一些初期问题。

第一个问题与步进有关。

由于Scheme是一种表达式语言,所有东西都倾向于包装在括号中,而不像主要的.NET语言,它似乎是基于语句(或行)。

原始代码(Scheme)看起来像:

(define (baz x)
  (cond
    [(null? x) 
      x]
    [(pair? x) 
      (car x)]
    [else
      (assertion-violation #f "nooo" x)]))

我故意在换行符上列出每个表达式。

发出的代码转换为C#(通过ILSpy)看起来像:

public static object ::baz(object x)
{
  if (x == null)
  {
    return x;
  }
  if (x is Cons)
  {
    return Builtins.Car(x);
  }
  return #.ironscheme.exceptions::assertion-violation+(
     RuntimeHelpers.False, "nooo", Builtins.List(x));
}

正如你所看到的,非常简单。

注意:如果代码在C#中转换为条件表达式(?:),则整个事情只是一个调试步骤,请记住这一点。

这里是IL输出的源代码和行号:

  .method public static object  '::baz'(object x) cil managed
  {
    // Code size       56 (0x38)
    .maxstack  6
    .line 15,15 : 1,2 ''
//000014: 
//000015: (define (baz x)
    IL_0000:  nop
    .line 17,17 : 6,15 ''
//000016:   (cond
//000017:     [(null? x) 
    IL_0001:  ldarg.0
    IL_0002:  brtrue     IL_0009

    .line 18,18 : 7,8 ''
//000018:       x]
    IL_0007:  ldarg.0
    IL_0008:  ret

    .line 19,19 : 6,15 ''
//000019:     [(pair? x) 
    .line 19,19 : 6,15 ''
    IL_0009:  ldarg.0
    IL_000a:  isinst [IronScheme]IronScheme.Runtime.Cons
    IL_000f:  ldnull
    IL_0010:  cgt.un
    IL_0012:  brfalse    IL_0020

    IL_0017:  ldarg.0
    .line 20,20 : 7,14 ''
//000020:       (car x)]
    IL_0018:  tail.
    IL_001a:  call object [IronScheme]IronScheme.Runtime.Builtins::Car(object)
    IL_001f:  ret

    IL_0020:  ldsfld object 
         [Microsoft.Scripting]Microsoft.Scripting.RuntimeHelpers::False
    IL_0025:  ldstr      "nooo"
    IL_002a:  ldarg.0
    IL_002b:  call object [IronScheme]IronScheme.Runtime.Builtins::List(object)
    .line 22,22 : 7,40 ''
//000021:     [else
//000022:       (assertion-violation #f "nooo" x)]))
    IL_0030:  tail.
    IL_0032:  call object [ironscheme.boot]#::
       'ironscheme.exceptions::assertion-violation+'(object,object,object)
    IL_0037:  ret
  } // end of method 'eval-core(033)'::'::baz'

注:为了防止调试器简单地突出显示整个方法,我使方法入口点只有1列宽。

正如你所看到的,每个表达式都正确地映射到一行。

现在步进问题(在VS2010上测试,但在VS2008上有相同/相似的问题):

这些与IgnoreSymbolStoreSequencePoints不适用。

  • 用null arg调用baz,它可以正常工作。 (null?x)后跟x。
  • 用Cons arg调用baz,它工作正常。 (空?x)然后(对?x)然后(车x)。
  • 与其他参数调用baz,它失败。 (null?x)然后(pair?x)然后(car x)then(assertion-violation ...)。
  • 当应用IgnoreSymbolStoreSequencePoints (按照建议)时:

  • 用null arg调用baz,它可以正常工作。 (null?x)后跟x。
  • 用Cons arg调用baz,它失败。 (null?x)then(pair?x)。
  • 与其他参数调用baz,它失败。 (null?x)然后(pair?x)然后(car x)then(assertion-violation ...)。
  • 我还发现,在这种模式下,某些行(此处未显示)被错误地突出显示,它们被关闭1。

    以下是一些想法,原因可能是:

  • Tailcalls混淆了调试器
  • 重叠的位置(这里没有显示)混淆了调试器(它在设置断点时很好)
  • ????
  • 第二,也是严重的问题是调试器在某些情况下无法打破/命中断点。

    唯一可以让调试器正确(并且一致)破解的地方是方法入口点。

    IgnoreSymbolStoreSequencePoints未应用时情况会好一些。

    结论

    这可能是VS调试器只是普通的buggy :(

    参考文献:

  • 使CLR / .NET语言可调试
  • 更新1:

    Mdbg不适用于64位程序集。 所以那已经结束了。 我没有更多的32位机器来测试它。 更新:我相信这不是一个大问题,有没有人有修复? 编辑:是的,愚蠢的我,只需在x64命令提示符下启动mdbg :)

    更新2:

    我创建了一个C#应用程序,并试图剖析行信息。

    我的发现:

  • 在任何brXXX指令之后,你需要有一个序列点(如果不是有效的,也就是'#line hidden',发出一个nop )。
  • 在任何brXXX指令之前,发出'#line hidden'和一个nop
  • 但是,应用这个问题并不能解决问题(单独?)。

    但添加以下,给出了预期的结果:)

  • ret ,发出'#line hidden'和nop
  • 这是使用IgnoreSymbolStoreSequencePoints未应用的模式。 应用时,某些步骤仍然跳过:(

    以上是应用上面的IL输出:

      .method public static object  '::baz'(object x) cil managed
      {
        // Code size       63 (0x3f)
        .maxstack  6
        .line 15,15 : 1,2 ''
        IL_0000:  nop
        .line 17,17 : 6,15 ''
        IL_0001:  ldarg.0
        .line 16707566,16707566 : 0,0 ''
        IL_0002:  nop
        IL_0003:  brtrue     IL_000c
    
        .line 16707566,16707566 : 0,0 ''
        IL_0008:  nop
        .line 18,18 : 7,8 ''
        IL_0009:  ldarg.0
        IL_000a:  ret
    
        .line 16707566,16707566 : 0,0 ''
        IL_000b:  nop
        .line 19,19 : 6,15 ''
        .line 19,19 : 6,15 ''
        IL_000c:  ldarg.0
        IL_000d:  isinst     [IronScheme]IronScheme.Runtime.Cons
        IL_0012:  ldnull
        IL_0013:  cgt.un
        .line 16707566,16707566 : 0,0 ''
        IL_0015:  nop
        IL_0016:  brfalse    IL_0026
    
        .line 16707566,16707566 : 0,0 ''
        IL_001b:  nop
        IL_001c:  ldarg.0
        .line 20,20 : 7,14 ''
        IL_001d:  tail.
        IL_001f:  call object [IronScheme]IronScheme.Runtime.Builtins::Car(object)
        IL_0024:  ret
    
        .line 16707566,16707566 : 0,0 ''
        IL_0025:  nop
        IL_0026:  ldsfld object 
          [Microsoft.Scripting]Microsoft.Scripting.RuntimeHelpers::False
        IL_002b:  ldstr      "nooo"
        IL_0030:  ldarg.0
        IL_0031:  call object [IronScheme]IronScheme.Runtime.Builtins::List(object)
        .line 22,22 : 7,40 ''
        IL_0036:  tail.
        IL_0038:  call object [ironscheme.boot]#::
          'ironscheme.exceptions::assertion-violation+'(object,object,object)
        IL_003d:  ret
    
        .line 16707566,16707566 : 0,0 ''
        IL_003e:  nop
      } // end of method 'eval-core(033)'::'::baz'
    

    更新3:

    上述“半固定”问题。 由于ret之后的nop ,Peverify会报告所有方法的错误。 我真的不明白这个问题。 一个nop如何在ret之后破解验证。 这就像死了的代码(除了它甚至不是代码)......哦,实验仍在继续。

    更新4:

    现在回到家中,删除了“无法验证”的代码,运行在VS2008上,情况变得更糟。 也许为了正确的调试运行无法验证的代码可能是答案。 在'释放'模式下,所有输出仍然是可验证的。

    更新5:

    我现在已经决定我的上述想法是现在唯一可行的选择。 虽然生成的代码是无法验证的,但我还没有找到任何VerificationException的。 我不知道这种情况下最终用户会产生什么样的影响。

    作为奖励,我的第二个问题也解决了。 :)

    这里是我最终结束的一个小屏幕录像。 它击中断点,做适当的步进(进/出/结束)等。总而言之,期望的效果。

    但是,我仍然不接受这种做法。 对我来说,这感觉很过分。 对实际问题进行确认会很好。

    更新6:

    只是在VS2010上测试代码的变化,似乎有一些问题:

  • 第一个电话现在不能正确地执行。 (断言违反......)被击中。 其他情况下工作正常。 一些旧代码排除了不必要的位置 删除了代码,按预期工作。 :)
  • 更严重的是,第二次调用程序时断点失败(使用内存中的汇编,将汇编程序转储到文件似乎使断点再次变得快乐)。
  • 这两种情况在VS2008下都能正常工作。 主要区别在于,在VS2010下,整个应用程序是针对.NET 4编译的,并且在VS2008下编译为.NET 2.两者均运行64位。

    更新7:

    就像上面提到的,我得到了mdbg在64位下运行。 不幸的是,如果我重新运行程序(这意味着它被重新编译,所以不使用相同的程序集,但仍然使用相同的源代码),它也有断点问题。

    更新8:

    我已经在MS Connect网站上提交了一个关于断点问题的错误。

    更新:修正

    更新9:

    经过长时间的思考,让调试器开心的唯一方法似乎是做SSA,所以每一步都可以是独立的和顺序的。 尽管我还没有证明这个观点。 但这似乎合乎逻辑。 显然,清理SSA的临时数据将会破坏调试,但很容易切换,并且不会有太多开销。


    我是Visual Studio Debugger团队的一名工程师。

    纠正我,如果我错了,但它听起来像剩下的唯一问题是,从PDB切换到.NET 4动态编译符号格式时,某些断点正在错过。

    我们可能需要一个repro来准确诊断问题,但是这里有一些注释可能会有所帮助。

  • VS(2008+)可以作为非管理员运行
  • 第二次加载任何符号吗? 您可以通过突破来进行测试(通过异常或调用System.Diagnostic.Debugger.Break())
  • 假设符号加载,是否有可以发送给我们的repro?
  • 可能的区别是动态编译代码的符号格式在.NET 2(PDB流)和.NET 4(IL DB我认为他们称之为?)之间是100%不同的。
  • 'nop的声音很正确。 请参阅下面的生成隐式序列点的规则。
  • 你实际上并不需要在不同的线路上发射东西。 默认情况下,VS会在步骤'符号语句'中作为编译器编写器来定义“符号语句”的含义。 所以如果你想让每个表达式在符号文件中成为一个独立的东西,那就可以正常工作了。
  • JIT基于以下规则创建隐式序列点:1. IL nop指令2. IL堆栈空点3.紧随呼叫指令后的IL指令

    如果事实证明,我们确实需要repro来解决您的问题,您可以提交连接错误并通过该介质安全地上传文件。

    更新:

    我们鼓励遇到此问题的其他用户尝试从http://www.microsoft.com/download/en/details.aspx?displaylang=en&id=27543获取Dev11的开发人员预览版,并发表任何反馈意见。 (必须达到4.5)

    更新2:

    Leppie已经在http://www.microsoft.com/visualstudio/11/en-us/downloads上验证了Dev11 Beta版本中的修复方法,如连接错误https://connect.microsoft中所述。 COM / VisualStudio中/反馈/信息/ 684089 /。

    谢谢,

    卢克


    我是SharpDevelop Debugger团队的一名工程师:-)

    你解决了这个问题吗?

    你有没有尝试在SharpDevelop中进行调试? 如果.NET中存在错误,我不知道是否需要实施一些解决方法。 我不知道这个问题。

    你有没有尝试在ILSpy中进行调试? 特别是没有调试符号。 它会调试C#代码,但它会告诉我们IL指令是否可以很好地调试。 (请注意,ILSpy调试器虽然是beta版本)

    关于原始IL代码的简要说明:

  • .line 19,19:6,15''出现两次?
  • .line 20,20:7,14''不从隐式序列点开始(堆栈不为空)。 我很担心
  • .line 20,20:7,14''包含“car x”(good)以及“#f nooo x”(坏?)的代码
  • 关于ret之后的nop。 那么stloc,ldloc,ret呢? 我认为C#使用这个技巧使ret成为一个独特的序列点。
  • 大卫

    链接地址: http://www.djcxy.com/p/59987.html

    上一篇: Making your .NET language step correctly in the debugger

    下一篇: Haskell Repa meaning of BoundFixed?