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

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

我是 铁人计划的作者。最近我一直在努力发出体面的调试信息,以便我可以使用’原生’。NET 调试器。

虽然这种做法在一定程度上取得了成功,但我也遇到了一些初期的问题。

第一个问题与踏步有关。

由于 Scheme 是一种表达式语言,所以与 Major 不同,所有内容都包装在括号中。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没有应用。

  1. 用 null arg 调用 baz,它正常工作。
  2. 使用 Cons arg 调用 baz,它可以正常工作。
  3. 用其他参数调用 baz,它失败了。(null? x)然后(成对 x)然后(car x)然后(断言-违背...)。

应用 IgnoreSymbolStoreSequencePoints时(按建议) :

  1. 用 null arg 调用 baz,它正常工作。
  2. 使用 Cons arg 调用 baz,失败。
  3. 用其他参数调用 baz,它失败了。(null? x)然后(成对 x)然后(car x)然后(断言-违背...)。

我还发现在这种模式下,有些行(这里没有显示)高亮不正确,它们偏离了1。

以下是一些可能的原因:

  • 尾调用会混淆调试器
  • 重叠位置(这里没有显示)会使调试器感到困惑(它在设置断点时表现得非常好)
  • ????

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

唯一可以使调试器正确(并且始终如一地)中断的地方是方法入口点。

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

结论

VS 调试器可能只是一个普通的 bug: (

参考文献:

  1. 使 CLR/. NET 语言可调试

更新1:

Mdbg 不适用于64位程序集。所以这是出局了。我没有更多的32位机器来测试它。我肯定这不是什么大问题,有人能解决吗?编辑:是的,我真笨,只要在 x64命令提示符下启动 mdbg:)

更新2:

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

我的发现是:

  • 在任何 brXXX指令之后,您需要有一个序列点(如果不是有效的 aka’# line hide’,则发出一个 nop)。
  • 在任何 brXXX指令之前,发出一个“ # line hide”和一个 nop

然而,应用这种方法并不能解决问题(单独解决?)。

但加上以下内容,就得到了预期的结果:)

  • ret之后,发出一个“ # line hide”和一个 nop

这是使用不应用 IgnoreSymbolStoreSequencePoints的模式。当应用时,一些步骤仍然被跳过: (

以下是应用上述方法后的输出:

  .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 报告由于 ret之后的 nop导致的所有方法的错误。我真的不明白这个问题。在 ret之后,nop如何能够中断验证。它就像死代码(除了它甚至不是代码) ... ... 哦,好吧,实验还在继续。

更新4:

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

更新5:

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

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

这是我最后得到的一点 视频。它会触发断点,执行正确的单步执行(in/out/over)等。总之,达到了预期效果。

然而,我仍然不能接受这样做的方式。我觉得有点过了。确认真正的问题是件好事。

更新6:

刚刚在 VS2010上测试了代码,似乎有一些问题:

  1. 第一个调用现在步骤不正确。(断言-违反...)被击中。其他情况也不错。一些旧的代码发出不必要的位置。移除了代码,正常运行。:)
  2. 更严重的是,断点在程序的第二次调用时失败(使用内存编译,将程序集转储到文件中似乎可以使断点再次满意)。

在 VS2008下,这两种情况都能正常工作。主要区别在于,在 VS2010下,整个应用程序的编译目标是。NET 4和 VS2008下,编译为。NET 2.都是64位的。

更新7:

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

更新8:

关于断点问题,我在 MS 连接站点上有 装了窃听器

更新: 固定

更新9:

经过长时间的思考,使调试器高兴的唯一方法似乎是执行 SSA,因此每一步都可以是隔离的和连续的。不过我还没有证明这个观点。但这似乎是合乎逻辑的。显然,从 SSA 中清除临时数据会中断调试,但是这很容易切换,并且留下临时数据没有太大的开销。

4024 次浏览

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

如果我错了,请纠正我,但似乎唯一的问题是当从 PDB 切换到。NET4动态编译符号格式有些断点被遗漏了。

我们可能需要一个复制,以确切地诊断问题,但这里有一些注意事项,可能会有所帮助。

  1. VS (2008 +)可以作为非管理员运行
  2. 有没有符号会在第二次加载?您可以通过闯入(通过异常或调用 System)进行测试。诊断。调试器。休息()
  3. 假设那些符号上传了,有没有复制品可以发给我们?
  4. 可能的区别是,动态编译代码的符号格式在。NET 2(PDB 流)及。NET 4(我想他们称之为 IL DB)
  5. ‘ nop 的发音大致正确。请参阅下面生成隐式序列点的规则。
  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/中指出的那样。

谢谢,

卢克

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

你解决问题了吗?

你试过在 SharpDevelopment 中调试它吗?如果里面有窃听器。NET,我想知道我们是否需要实现一些变通方法。我不知道这个问题。

你试过用 ILSpy 调试吗?特别是没有调试符号的情况下。它会调试 C # 代码,但它会告诉我们 IL 指令是否可以很好地调试。(不过要注意 ILSpy 调试器是 beta 版)

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

  • . 第19行,19:6,15”出现两次?
  • .第20,20:7,14行“不从隐式序列点开始(栈不是空的)。我很担心
  • .第20,20:7,14行“包含了“ car x”(good)和“ # f nooo x”(bad?)的代码
  • 关于 Ret 之后的 Nop。那 stloc,ldloc,ret 呢?我认为 C # 使用这个技巧使 ret 成为一个独特的序列点。

大卫