使您的 .NET 语言在调试器中正确执行
首先,我对这个问题的长度表示歉意。
我是 IronScheme 的作者。最近,我一直在努力发出合适的调试信息,以便我可以使用“本机”.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,它可以正常工作。 (空?x)然后(对?x)然后(汽车x)。
- 使用其他参数调用 baz,失败。 (null? x) 然后 (pair? x) 然后 (car x) 然后 (断言违反 ...)。
当应用IgnoreSymbolStoreSequencePoints
时(建议):
- 使用 null arg 调用 baz,它可以正常工作。 (null?x) 后跟 x。
- 使用 Cons arg 调用 baz 失败。 (空?x)然后(对?x)。
- 使用其他参数调用 baz 失败。 (null? x) 然后 (pair? x) 然后 (car x) 然后 (断言违反 ...)。
我还发现在此模式下,某些行(此处未显示)未正确突出显示,它们偏离了 1。
以下是一些可能的原因:
- Tailcalls 混淆了调试器
- 重叠位置(此处未显示)混淆了调试器(它设置断点时效果很好)
- ???
第二个也是严重的问题是调试器在某些情况下无法中断/命中断点。
我可以让调试器正确(并且一致)中断的唯一地方是方法入口点。
当不应用 IgnoreSymbolStoreSequencePoints 时,情况会好一些。
结论
VS 调试器可能只是简单的错误:(
参考资料:
更新 1:
Mdbg 不适用于 64 位程序集。因此我没有更多的 32 位机器来测试它。更新: 我确信这不是大问题,有人可以解决吗?编辑: 是的。 ,愚蠢的我,只需在 x64 命令提示符下启动 mdbg :)
更新 2:
我创建了一个 C# 应用程序,并尝试剖析线路信息。
我的发现:
- 在任何
brXXX
指令之后,您需要有一个序列点(如果无效,又名“#line hide”,则发出nop
)。 - 在任何
brXXX
指令之前,发出“#line hide”和nop
。
然而,应用这个并不能解决问题(单独?)。
但添加以下内容可以得到所需的结果:)
- 在
ret
之后,发出“#line hide”和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 报告由于 < ret
之后的 code>nop 我真的不明白这个问题。就像死代码(除了它甚至不是代码) ...哦,好吧,实验继续。
更新4:
现在回到家,删除了“无法验证”的代码,在 VS2008 上运行,也许为了正确运行无法验证的代码。调试可能是答案。在“发布”模式下,所有输出仍然是可验证的。
更新 5:
我现在决定,尽管生成的代码无法验证,但我的上述想法是唯一可行的选择。 ,我还没有找到任何 VerificationException
的情况。我不知道这种情况会对最终用户产生什么影响。
作为奖励,我的第二个问题也得到了解决。 :)
这是我最终得到的一些截屏。它命中断点,执行正确的单步执行(进/出/越)等。总而言之,达到了预期的效果。
然而,我仍然不接受这种做法。对我来说这感觉太老套了。对真正的问题进行确认会很好。
更新6:
刚刚进行了更改以在VS2010上测试代码,似乎存在一些问题:
第一个调用现在无法正确执行。 (断言违反...)被击中。其他情况工作正常。一些旧代码发出了不必要的位置。删除代码,按预期工作。 :)- 更严重的是,断点在程序的第二次调用时失败(使用内存中编译,将程序集转储到文件似乎使断点再次满意)。
这两种情况在VS2008下都可以正常工作。主要区别在于,在 VS2010 下,整个应用程序针对 .NET 4 进行编译,而在 VS2008 下,则编译为 .NET 2。两者都运行 64 位。
更新 7:
如前所述,我让 mdbg 在 64 位下运行。不幸的是,它也存在断点问题,如果我重新运行程序,它就无法中断(这意味着它被重新编译,因此不使用相同的程序集,但仍然使用相同的源)。
更新 8:
我提交了一个错误有关断点问题的 MS Connect 站点。
更新:已修复
更新 9:
经过一番长时间的思考,让调试器满意的唯一方法似乎是进行 SSA,因此每个步骤都可以隔离并按顺序进行。不过我还没有证明这个想法。但这似乎合乎逻辑。显然,从 SSA 中清除临时值会破坏调试,但这很容易切换,并且保留它们不会有太多开销。
Firstly, I apologize for the length of this question.
I am the author of IronScheme. Recently I have been working hard on emitting decent debug info, so that I can use the 'native' .NET debugger.
While this has been partly successful, I am running into some teething problems.
The first problem is related to stepping.
Due to Scheme being an expression language, everything tends to be wrapped in parenthesis, unlike the major .NET languages which seems to be statement (or line) based.
The original code (Scheme) looks like:
(define (baz x)
(cond
[(null? x)
x]
[(pair? x)
(car x)]
[else
(assertion-violation #f "nooo" x)]))
I have on purpose laid out each expression on a newline.
The emitted code transforms to C# (via ILSpy) looks like:
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));
}
As you can see, pretty simple.
Note: If the code was transformed into a conditional expression (?:) in C#, the whole thing would just be one debug step, keep that in mind.
Here is IL output with source and line numbers:
.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'
Note: To prevent the debugger from simply highlighting the entire method, I make the method entry point just 1 column wide.
As you can see, each expression maps correctly to a line.
Now the problem with stepping (tested on VS2010, but same/similar issue on VS2008):
These are with IgnoreSymbolStoreSequencePoints
not applied.
- Call baz with null arg, it works correctly. (null? x) followed by x.
- Call baz with Cons arg, it works correctly. (null? x) then (pair? x) then (car x).
- Call baz with other arg, it fails. (null? x) then (pair? x) then (car x) then (assertion-violation ...).
When applying IgnoreSymbolStoreSequencePoints
(as recommended):
- Call baz with null arg, it works correctly. (null? x) followed by x.
- Call baz with Cons arg, it fails. (null? x) then (pair? x).
- Call baz with other arg, it fails. (null? x) then (pair? x) then (car x) then (assertion-violation ...).
I also find in this mode that some lines (not shown here) are incorrectly highlighted, they are off by 1.
Here are some ideas what could be the causes:
- Tailcalls confuses the debugger
- Overlapping locations (not shown here) confuses the debugger (it does so very well when setting a breakpoint)
- ????
The second, but also serious, issue is the debugger failing to break/hit breakpoints in some cases.
The only place where I can get the debugger to break correctly (and consistantly), is at the method entry point.
The situation gets a bit better when IgnoreSymbolStoreSequencePoints
is not applied.
Conclusion
It might be that the VS debugger is just plain buggy :(
References:
Update 1:
Mdbg does not work for 64-bit assemblies. So that is out. I have no more 32-bit machines to test it on. Update: I am sure this is no big problem, does anyone have a fix? Edit: Yes, silly me, just start mdbg under the x64 command prompt :)
Update 2:
I have created a C# app, and tried to dissect the line info.
My findings:
- After any
brXXX
instruction you need to have a sequence point (if not valid aka '#line hidden', emit anop
). - Before any
brXXX
instruction, emit a '#line hidden' and anop
.
Applying this, does not however fix the issues (alone?).
But adding the following, gives the desired result :)
- After
ret
, emit a '#line hidden' and anop
.
This is using the mode where IgnoreSymbolStoreSequencePoints
is not applied. When applied, some steps are still skipped :(
Here is the IL output when above has been applied:
.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'
Update 3:
Problem with above 'semi-fix'. Peverify reports errors on all methods due to the nop
after ret
. I dont understand the problem really. How can a nop
break verification after a ret
. It is like dead code (except that it is NOT even code) ... Oh well, experimentation continues.
Update 4:
Back at home now, removed the 'unverifiable' code, running on VS2008 and things are a lot worse. Perhaps running unverifiable code for the sake of proper debugging might be the answer. In 'release' mode, all output would still be verifiable.
Update 5:
I have now decided my above idea is the only viable option for now. Although the generated code is unverifiable, I have yet to find any VerificationException
's. I dont know what the impact will be on the end user with this scenario.
As a bonus, my second issue has also be solved. :)
Here is a little screencast of what I ended up with. It hits breakpoints, does proper stepping (in/out/over), etc. All in all, the desired effect.
I, however, am still not accepting this as the way to do it. It feel overly-hacky to me. Having a confirmation on the real issue would be nice.
Update 6:
Just had the change to test the code on VS2010, there seems to be some problems:
The first call now does not step correctly. (assertion-violation ...) is hit. Other cases works fine.Some old code emitted unnecessary positions. Removed the code, works as expected. :)- More seriously, breakpoints fail on the second invocation of the program (using in-memory compilation, dumping assembly to file seems to make breakpoints happy again).
Both these cases work correctly under VS2008. The main difference is that under VS2010, the entire application is compiled for .NET 4 and under VS2008, compiles to .NET 2. Both running 64-bit.
Update 7:
Like mentioned, I got mdbg running under 64-bit. Unfortunately, it also have the breakpoint issue where it fails to break if I rerun the program (this implies it gets recompiled, so not using the same assembly, but still using the same source).
Update 8:
I have filed a bug at the MS Connect site regarding the breakpoint issue.
Update: Fixed
Update 9:
After some long thinking, the only way to make the debugger happy seems to be doing SSA, so every step can be isolated and sequential. I am yet to prove this notion though. But it seems logical. Obviously, cleaning up temps from SSA will break debugging, but that is easy to toggle, and leaving them does not have much overhead.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
我是 Visual Studio 调试器团队的一名工程师。
如果我错了,请纠正我,但听起来唯一剩下的问题是,当从 PDB 切换到 .NET 4 动态编译符号格式时,会丢失一些断点。
我们可能需要重现来准确诊断问题,但是这里有一些可能有帮助的注释。
JIT 根据以下规则创建隐式序列点:
1. IL nop指令
2. IL堆栈空点
3. 紧随调用指令之后的 IL 指令
如果事实证明我们确实需要重现来解决您的问题,您可以提交连接错误并通过该介质安全地上传文件。
更新:
我们鼓励遇到此问题的其他用户尝试 http://www.microsoft.com/download/en/details.aspx?displaylang=en&id=27543 并发表任何反馈意见。 (必须针对 4.5)
更新 2:
Leppie 已验证该修复程序适用于他在 http://www.microsoft.com/visualstudio/11/en-us/downloads 如连接错误中所述https://connect.microsoft.com/VisualStudio/feedback/details/684089/。
谢谢,
卢克
I am an engineer on the Visual Studio Debugger team.
Correct me if I am wrong, but it sounds like the only issue left is that when switching from PDBs to the .NET 4 dynamic compile symbol format some breakpoints are being missed.
We would probably need a repro to exactly diagnose the issue, however here are some notes that might help.
The JIT creates an implicit sequence point based on the following rules:
1. IL nop instructions
2. IL stack empty points
3. The IL instruction immediately following a call instruction
If it turns out we do need a repro to solve your issue, you can file a connect bug and upload files securely through that medium.
Update:
We are encouraging other users experiencing this issue to try the Developer Preview of Dev11 from http://www.microsoft.com/download/en/details.aspx?displaylang=en&id=27543 and comment with any feedback. (Must target 4.5)
Update 2:
Leppie has verified the fix to work for him on the Beta version of Dev11 available at http://www.microsoft.com/visualstudio/11/en-us/downloads as noted in the connect bug https://connect.microsoft.com/VisualStudio/feedback/details/684089/.
Thanks,
Luke
我是 SharpDevelop 调试器团队的一名工程师 :-)
您解决问题了吗?
您是否尝试在 SharpDevelop 中调试它?如果 .NET 中存在错误,我想知道我们是否需要实施一些解决方法。我不知道这个问题。
您是否尝试在 ILSpy 中调试它?特别是没有调试符号。它可以调试 C# 代码,但它会告诉我们 IL 指令是否可以很好地调试。 (请注意,ILSpy 调试器是测试版)
原始 IL 代码的快速注释:
大卫
I am an engineer on the SharpDevelop Debugger team :-)
Did you solve the problem?
Did you try to debug it in SharpDevelop? If there is a bug in .NET, I wonder if we need to implement some workaround. I am not aware of this issue.
Did you try to debug it in ILSpy? Especially without debug symbols. It would debug C# code, but it would tell us if the IL instructions are nicely debugable. (Mind that ILSpy debugger is beta though)
Quick notes on the original IL code:
David