.net - 在调试器中正确执行 .NET 语言步骤

标签 .net visual-studio visual-studio-2010 debugging clr

首先,我为这个问题的长度道歉。

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

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

第一个问题与步进有关。

由于 Scheme 是一种表达式语言,所以一切都倾向于用括号括起来,这与主要的 .NET 语言似乎是基于语句(或行)不同。

原始代码(方案)如下所示:

(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,它可以正常工作。 (null? x) then (pair? x) then (car x)。
  • 用其他 arg 调用 baz,它失败了。 (null? x) then (pair? x) then (car x) then (assertion-violation ...)。

  • 申请时IgnoreSymbolStoreSequencePoints (推荐):
  • 使用 null arg 调用 baz,它可以正常工作。 (null? x) 后跟 x。
  • 使用 Cons arg 调用 baz,它失败了。 (空?x)然后(对?x)。
  • 用其他 arg 调用 baz,它失败了。 (null? x) then (pair? x) then (car x) then (assertion-violation ...)。

  • 我还发现在这种模式下,某些行(此处未显示)未正确突出显示,它们相差 1。

    以下是一些可能的原因:
  • 尾调用混淆了调试器
  • 重叠位置(此处未显示)会混淆调试器(设置断点时效果很好)
  • ???

  • 第二个也是严重的问题是调试器在某些情况下无法中断/命中断点。

    唯一能让调试器正确(并持续)中断的地方是方法入口点。

    IgnoreSymbolStoreSequencePoints 时情况会好一些不适用。

    结论

    可能是 VS 调试器只是简单的错误:(

    引用文献:
  • Making a CLR/.NET Language Debuggable

  • 更新 1:

    Mdbg 不适用于 64 位程序集。所以就这样了。我没有更多的 32 位机器来测试它。更新:我确定这不是大问题,有没有人有解决办法?编辑:是的,愚蠢的我,只需在 x64 命令提示符下启动 mdbg :)

    更新 2:

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

    我的发现:
  • 任何后 brXXX指令你需要有一个序列点(如果无效,又名'#line hidden',发出一个 nop )。
  • 在任何之前 brXXX指令,发出 '#line hidden' 和 nop .

  • 然而,应用这个并不能解决问题(单独?)。

    但是添加以下内容,可以得到所需的结果:)
  • ret , 发出一个 '#line hidden' 和一个 nop .

  • 这是使用模式 where 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:

    上述“半修复”的问题。由于 nop,Peverify 报告所有方法的错误之后 ret .我真的不明白这个问题。怎么可以nop ret 后中断验证.它就像死代码(除了它甚至不是代码)......哦,实验还在继续。

    更新 4:

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

    更新 5:

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

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

    这里有一点screencast我最终得到了什么。它击中断点,进行适当的步进(进/出/越过)等。总而言之,达到了预期的效果。

    然而,我仍然不接受这是这样做的方式。对我来说,这感觉太过分了。对真正的问题进行确认会很好。

    更新 6:

    刚刚在VS2010上改了测试代码,好像有一些问题:
  • 现在第一个调用步骤不正确。 (assertion-violation ...) 被击中。其他情况下工作正常。一些旧代码发出了不必要的位置。删除了代码,按预期工作。 :)
  • 更严重的是,断点在程序的第二次调用时失败(使用内存编译,将程序集转储到文件似乎使断点再次快乐)。

  • 这两种情况在 VS2008 下都可以正常工作。主要区别在于,在 VS2010 下,整个应用程序针对 .NET 4 编译,而在 VS2008 下,则编译为 .NET 2。两者都运行 64 位。

    更新 7:

    如前所述,我让 mdbg 在 64 位下运行。不幸的是,它也有断点问题,如果我重新运行程序,它就无法中断(这意味着它被重新编译,所以不使用相同的程序集,但仍然使用相同的源代码)。

    更新 8:

    我有 filed a bug在 MS Connect 站点上关于断点问题。

    更新:固定

    更新 9:

    经过长时间的思考,让调试器满意的唯一方法似乎是做 SSA,所以每一步都可以隔离和顺序执行。我还没有证明这个概念。但这似乎是合乎逻辑的。显然,从 SSA 中清除临时文件会破坏调试,但这很容易切换,并且保留它们不会有太多开销。

    最佳答案

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

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

    我们可能需要一个重现来准确诊断问题,但是这里有一些可能会有所帮助的说明。

  • VS (2008+) 可以以非管理员身份运行
  • 第二次是否加载了任何符号?您可以通过闯入(通过异常或调用 System.Diagnostic.Debugger.Break())进行测试
  • 假设符号加载,是否有您可以发送给我们的复制品?
  • 可能的区别是动态编译代码的符号格式在 .NET 2(PDB 流)和 .NET 4(我认为他们称之为 IL DB?)之间 100% 不同
  • 'nop' 的声音是对的。请参阅下面生成隐式序列点的规则。
  • 您实际上不需要在不同的行上发出内容。默认情况下,VS 将执行“符号语句”,作为编译器编写者,您可以定义“符号语句”的含义。因此,如果您希望每个表达式在符号文件中都是一个单独的东西,那将工作得很好。

  • JIT 根据以下规则创建一个隐式序列点:
    1. IL nop 指令
    2.IL栈空点
    3. 紧跟在 call 指令之后的 IL 指令

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

    更新:

    我们鼓励遇到此问题的其他用户尝试来自 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/feedback/details/684089/ 中所述.

    谢谢,

    卢克

    关于.net - 在调试器中正确执行 .NET 语言步骤,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6937198/

    相关文章:

    .net - 如何使Uri.EscapeDataString符合RFC 3986

    c++ - 我是否在 Visual Studio 中正确设置了 boost 包含和链接器路径? (提供的步骤)

    c++ - Visual Studio 2010 列表复制构造抛出 C2664 错误

    c# - .netcore 2.2 Httpcontext 没有 SignInAsync

    c# - EF SaveChanges() 背后发生了什么

    .net - 是否有用于查看使用二进制序列化创建的文件的工具?

    c# - 是否可以在我的项目中包含一个二进制文件并使用它?

    c++ - 如果使用预定义的 map ,重建项目需要非常长的时间?

    wpf - 如何使 Resharper 解析 CustomBinding MarkupExtension 的路径

    database - 如何从Access数据库中随机选择多项选择题