首先,对于这个问题的冗长,我深表歉意。
我是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
不适用。
- 用空arg调用baz,它可以正常工作。(null?x),然后是x。
- 用Cons arg调用baz,它可以正常工作。(null?x)然后(pair?x)然后(car x)。
- 与其他arg一起调用baz,它失败。(null?x)然后(pair?x)然后(car x)然后(assertion-violation ...)。
申请时IgnoreSymbolStoreSequencePoints
(建议):
- 用空arg调用baz,它可以正常工作。(null?x),然后是x。
- 用Cons arg调用baz,它失败。(null?x),然后(pair?x)。
- 与其他arg一起调用baz,它失败。(null?x)然后(pair?x)然后(car x)然后(assertion-violation ...)。
我还发现,在此模式下,某些行(此处未显示)未正确突出显示,它们之间的距离为1。
这里有一些想法可能是什么原因:
- 尾调用使调试器感到困惑
- 重叠的位置(此处未显示)使调试器感到困惑(设置断点时效果非常好)
- ????
第二个也是一个很严重的问题是,在某些情况下,调试器无法中断/命中断点。
我可以使调试器正确(一致地)中断的唯一地方是方法入口点。
IgnoreSymbolStoreSequencePoints
不应用时情况会好一些。
结论
VS调试器可能只是普通的越野车:(
参考文献:
更新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:
上述“半修复”问题。由于nop
after,Peverify报告所有方法的错误ret
。我真的不明白这个问题。nop
验证后如何进行中断验证ret
。就像死代码一样(除了它甚至不是代码)...哦,好的,实验还在继续。
更新4:
现在回到家,删除了“无法验证”的代码,该代码在VS2008上运行,情况变得更糟。答案可能是为了进行适当的调试而运行无法验证的代码。在“发布”模式下,所有输出仍将是可验证的。
更新5:
我现在决定,上述想法是目前唯一可行的选择。尽管生成的代码不可验证,但我还没有找到任何代码VerificationException
。我不知道在这种情况下会对最终用户产生什么影响。
另外,我的第二个问题也已解决。:)
这是我最后得到的一些截屏视频。它会达到断点,执行适当的步进(输入/输出/结束)等。总而言之,可以达到预期的效果。
但是,我仍然不接受此方法。对我来说感觉太过分了。对真实问题进行确认会很好。
更新6:
刚进行了更改以在VS2010上测试代码,似乎存在一些问题:
现在,第一个呼叫无法正确执行。(断言违反...)被击中。其他情况下效果很好。一些旧代码发出不必要的位置。删除了代码,按预期方式工作。:)- 更严重的是,断点在程序的第二次调用时失败(使用内存内编译,将程序集转储到文件中似乎使断点再次令人满意)。
这两种情况都可以在VS2008下正常工作。主要区别在于,在VS2010下,整个应用程序是针对.NET 4编译的,而在VS2008下,则是针对.NET 2编译的。两者均运行64位。
更新7:
如前所述,我使mdbg在64位下运行。不幸的是,它还存在断点问题,如果我重新运行该程序,它将无法中断(这意味着将对其进行重新编译,因此请不要使用相同的程序集,但仍使用相同的源代码)。
更新8:
我已经在MS Connect网站上提交了有关断点问题的错误。
更新:固定
更新9:
经过深思熟虑,使调试器满意的唯一方法似乎是执行SSA,因此每个步骤都可以隔离和顺序执行。我还没有证明这个概念。但这似乎合乎逻辑。显然,从SSA清除临时文件会中断调试,但是切换起来很容易,而且不会有太多开销。
nop
s,则步进将失败(我将再次对此进行确认)。我想这是我必须做出的牺牲。这不像VS甚至没有管理员权限也可以运行:)顺便说一句,它通过DLR使用了Reflection.Emit(一个非常容易破解的早期分支机构)。