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

在调试器中正确执行.NET语言步骤,.net,visual-studio,visual-studio-2010,debugging,clr,.net,Visual Studio,Visual Studio 2010,Debugging,Clr,首先,我为这个问题的长度道歉 我是这本书的作者。最近,我一直在努力发出体面的调试信息,以便我可以使用“本机”.NET调试器 虽然这在一定程度上取得了成功,但我在出牙时遇到了一些问题 第一个问题与步进有关 由于Scheme是一种表达式语言,所以所有内容都倾向于用括号括起来,不像主要的.NET语言那样似乎是基于语句(或行)的 原始代码(方案)如下所示: (define (baz x) (cond [(null? x) x] [(pair? x) (ca

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

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

虽然这在一定程度上取得了成功,但我在出牙时遇到了一些问题

第一个问题与步进有关

由于Scheme是一种表达式语言,所以所有内容都倾向于用括号括起来,不像主要的.NET语言那样似乎是基于语句(或行)的

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

(define (baz x)
  (cond
    [(null? x) 
      x]
    [(pair? x) 
      (car x)]
    [else
      (assertion-violation #f "nooo" x)]))
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#(通过ILSpy),如下所示:

(define (baz x)
  (cond
    [(null? x) 
      x]
    [(pair? x) 
      (car x)]
    [else
      (assertion-violation #f "nooo" x)]))
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上存在相同/类似的问题):

这些都是未应用的
ignoresymbolstoresquencepoints

  • 使用null arg调用baz,它可以正常工作。(null?x)后跟x
  • 用Cons arg调用baz,它工作正常。(空?x)然后(对?x)然后(车x)
  • 用其他参数调用baz,失败。(空?x)然后(对?x)然后(车x)然后(断言冲突…)
  • 应用
    忽略符号存储序列点时(推荐):

  • 使用null arg调用baz,它可以正常工作。(null?x)后跟x
  • 用Cons arg调用baz,失败。(空?x)然后(对?x)
  • 用其他参数调用baz,失败。(空?x)然后(对?x)然后(车x)然后(断言冲突…)
  • 我还发现在这种模式下,一些行(此处未显示)被错误地高亮显示,它们被1关闭

    以下是一些可能的原因:

    • Tailcalls会混淆调试器
    • 重叠位置(此处未显示)会混淆调试器(设置断点时,调试器的效果非常好)
    第二个也是严重的问题是调试器在某些情况下无法中断/命中断点

    唯一可以让调试器正确(且一致)中断的地方是方法入口点

    如果不应用
    ignoresymbolstoresquencepoints
    ,情况会有所好转

    结论

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

    参考文献:

  • 更新1:

    Mdbg不适用于64位程序集。因此,这已过时。我没有更多的32位计算机可对其进行测试。更新:我确信这不是大问题,有人有解决方案吗?编辑:是的,我很傻,只需在x64命令提示符下启动Mdbg:)

    更新2:

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

    我的调查结果:

    • 在任何
      brXXX
      指令之后,您需要有一个序列点(如果无效,即“隐藏线”,则发出
      nop
    • 在任何
      brXXX
      指令之前,发出一个“#行隐藏”和一个
      nop
    然而,应用这一点并不能(单独地)解决问题

    但添加以下内容会得到所需的结果:)

    • ret
      之后,发出一个“#行隐藏”和一个
      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:

    上述“半修复”的问题。Peverify报告所有方法的错误,这是由于
    nop
    ret
    之后出现的。我真的不理解这个问题。一个
    nop
    如何在
    ret
    之后破坏验证。这就像死代码(除了它甚至不是代码)…哦,实验还在继续

    更新4:

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

    更新5:

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

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

    下面是我最后得到的一些东西。它命中断点,执行正确的步进(in/out/over),等等。总之,达到了预期的效果

    一、 然而,我仍然不接受这样做的方式。我觉得太讨厌了。对真正的问题有一个确认会很好

    更新6:

    刚刚更改在VS2010上测试代码,似乎存在一些问题:

  • 现在的第一个呼叫步骤不正确。(断言冲突…)被命中。其他情况也可以。一些旧代码发出不必要的位置。已删除代码,按预期工作。:)
  • 更严重的是,断点在程序的第二次调用时失败(使用内存编译,将程序集转储到文件中似乎使断点再次感到高兴)
  • 这两种情况在VS2008下都能正常工作。主要区别在于,在VS2010下,整个应用程序都是针对.NET 4编译的,而在VS2008下,则是针对.NET 2编译的。两者都运行64位

    更新7:

    如前所述,我让mdbg在64位下运行。不幸的是,它也有breakpo