使您的.NET语言在调试器中正确执行


135

首先,对于这个问题的冗长,我深表歉意。

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

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

第一个问题与步进有关。

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

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

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

我特意将每个表达式放在换行符上。

发出的代码通过ILSpy转换为C#,如下所示:

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'

注意:为了防止调试器仅突出显示整个方法,我将方法入口点设置为仅一列宽。

如您所见,每个表达式正确地映射到一行。

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

这些IgnoreSymbolStoreSequencePoints不适用。

  1. 用空arg调用baz,它可以正常工作。(null?x),然后是x。
  2. 用Cons arg调用baz,它可以正常工作。(null?x)然后(pair?x)然后(car x)。
  3. 与其他arg一起调用baz,它失败。(null?x)然后(pair?x)然后(car x)然后(assertion-violation ...)。

申请时IgnoreSymbolStoreSequencePoints(建议):

  1. 用空arg调用baz,它可以正常工作。(null?x),然后是x。
  2. 用Cons arg调用baz,它失败。(null?x),然后(pair?x)。
  3. 与其他arg一起调用baz,它失败。(null?x)然后(pair?x)然后(car x)然后(assertion-violation ...)。

我还发现,在此模式下,某些行(此处未显示)未正确突出显示,它们之间的距离为1。

这里有一些想法可能是什么原因:

  • 尾调用使调试器感到困惑
  • 重叠的位置(此处未显示)使调试器感到困惑(设置断点时效果非常好)
  • ????

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

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

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

结论

VS调试器可能只是普通的越野车:(

参考文献:

  1. 使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:

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

更新4:

现在回到家,删除了“无法验证”的代码,该代码在VS2008上运行,情况变得更糟。答案可能是为了进行适当的调试而运行无法验证的代码。在“发布”模式下,所有输出仍将是可验证的。

更新5:

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

另外,我的第二个问题也已解决。:)

这是我最后得到的一些截屏视频。它会达到断点,执行适当的步进(输入/输出/结束)等。总而言之,可以达到预期的效果。

但是,我仍然不接受此方法。对我来说感觉太过分了。对真实问题进行确认会很好。

更新6:

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

  1. 现在,第一个呼叫无法正确执行。(断言违反...)被击中。其他情况下效果很好。一些旧代码发出不必要的位置。删除了代码,按预期方式工作。:)
  2. 更严重的是,断点在程序的第二次调用时失败(使用内存内编译,将程序集转储到文件中似乎使断点再次令人满意)。

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

更新7:

如前所述,我使mdbg在64位下运行。不幸的是,它还存在断点问题,如果我重新运行该程序,它将无法中断(这意味着将对其进行重新编译,因此请不要使用相同的程序集,但仍使用相同的源代码)。

更新8:

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

更新:固定

更新9:

经过深思熟虑,使调试器满意的唯一方法似乎是执行SSA,因此每个步骤都可以隔离和顺序执行。我还没有证明这个概念。但这似乎合乎逻辑。显然,从SSA清除临时文件会中断调试,但是切换起来很容易,而且不会有太多开销。


任何想法都欢迎,我将尝试一下并将结果附加到问题中。前两个想法,尝试使用mdbg并在C#中编写相同的代码并进行比较。
leppie 2011年

6
问题是什么?
乔治·斯托克

9
我认为他的问题是“为什么我的语言不能正确地步调?”
麦凯

1
如果不通过认证,您将无法在部分信任的情况下运行,例如从映射驱动器或许多插件托管设置中运行。您如何生成IL,reflection.Emit或通过文本+ ilasm?无论哪种情况,您都可以随时输入.line 16707566,16707566:0,0''自己,这样您就不必担心他们在返回后点头。
Hippiehunter 2011年

@Hippiehunter:谢谢。不幸的是,如果没有nops,则步进将失败(我将再次对此进行确认)。我想这是我必须做出的牺牲。这不像VS甚至没有管理员权限也可以运行:)顺便说一句,它通过DLR使用了Reflection.Emit(一个非常容易破解的早期分支机构)。
2011年

Answers:


26

我是Visual Studio调试器团队的工程师。

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

我们可能需要维修人员才能准确诊断问题,但是这里有一些说明可能会有所帮助。

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

JIT根据以下规则创建隐式序列点:1. IL nop指令2. IL堆栈空点3.紧随调用指令的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 /

谢谢,

路加


非常感谢。如有需要,我会尝试进行复制。
2011年

至于确认,是的,您是正确的,但是我仍然希望在不破坏可验证性的情况下解决步进问题。但是正如我所说,如果我能够证明它永远不会抛出运行时,我愿意牺牲VerificationException
leppie 2011年

关于第6点,我只是将其设为“基于行”。实际上,调试器确实按照我的意图正确地执行了输入/输出/结束表达式:)方法的唯一问题是,如果“外部”表达式包含断点,则在“内部”表达式上设置断点;VS中的调试器倾向于将最外部的表达式作为断点。
leppie 2011年

我认为我已经隔离了断点问题。在CLR2下运行时,即使再次运行新代码,断点似乎也会重新评估。在CLR4上,断点仅“粘”在原始代码上。我已经对CLR4行为进行了小截屏@ screencast.com/t/eiSilNzL5Nr。请注意,类型名称在两次调用之间更改。
leppie

我在连接时提出了一个错误。复制非常简单:) connect.microsoft.com/VisualStudio/feedback/details/684089
leppie 2011年

3

我是SharpDevelop调试器团队的工程师:-)

您解决问题了吗?

您是否尝试在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”(好的)以及“ #f nooo x”(不好的?)的代码
  • 关于退后的点头。那么stloc,ldloc,ret呢?我认为C#使用此技巧使ret成为不同的序列点。

大卫


+1首先,感谢您的反馈,这是代码生成器的不幸副作用,但似乎无害。第二点,这正是我所需要的,但是我明白你的意思,将对此进行调查。不知道最后一点是什么意思。
leppie 2011年

第三点是.line 22,22:7,40''应该在IL_0020之前。否则在IL_0020之前应该有一些东西,否则代码仍然算作.line 20,20:7,14''。第四个要点是“ ret,nop”可以替换为“ sloc,.line,ldloc,ret”。我以前看过这种模式,也许是多余的,但也许有原因。
dsrbecky 2011年

我无法在退出之前执行stloc,ldloc,因为我会松开尾声。我明白了,您指的是原始输出?
leppie
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.