一个奇怪的Visual Studio 2010调试器案例(它不能达到一个断点)

一个奇怪的Visual Studio 2010调试器案例(它不能达到一个断点)

这是重现问题的代码:

class Program { static void Main(string[] args) { bool b = false; if (b) { List list = new List(); foreach (var item in list) { } } else { Console.WriteLine("1"); } Console.WriteLine("2");//add a break point here in VS2010 } //1. configuration: release //2. platform target: x64 or Any Cpu //3. debug info: pdb only or full //4. OS: Win7 x64 //5. optimize code: enabled } 

在代码的最后一个语句中添加一个断点,然后在vs2010中调试它,你会看到断点不能被命中。

要重现这个奇怪的情况,您需要满足以下条件:

  1. 操作系统:windows 7 x64;
  2. VS build配置:发布;
  3. VS构建平台目标:x64或任何Cpu;
  4. VS构建调试信息:pdb only或full;
  5. VS build优化代码:启用;

我不确定这些条件是否足以重现它,但是当我发现这个问题时,这就是我的机器的配置方式。

为什么调试器无法达到断点?

提前致谢!

如果您可以重现此问题,请考虑对此帖进行投票。

当提供的示例在发布模式下构建然后JIT编译为64位机器代码时,它不包含足够的信息供调试器将断点与任何特定的机器指令相关联。 这就是为什么调试器在执行JIT-ed机器代码期间永远不会在此断点处停止的原因。 它只是不知道在哪里停下来。 可能是64位CLR调试器中的某种不当行为甚至是错误,因为只有当它被JIT转换为64位机器代码而不是32位机器代码时才可以重现。

当调试器在代码中看到断点时,它会尝试在JIT编码中找到与断点标记的位置对应的机器指令。 首先,它需要找到与C#代码中的断点位置对应的IL指令。 然后,它需要找到与IL命令对应的机器指令。 然后它在找到的机器指令上设置一个真正的断点并开始执行该方法。 在您的情况下,看起来调试器只是忽略断点,因为它无法将其映射到特定的机器指令。

调试器找不到紧跟在if语句之后的机器指令的地址。 if … else语句及其中的代码以某种方式导致此行为。 if … else后面的语句无关紧要。 您可以将Console.WriteLine(“2”)语句替换为其他语句,您仍然可以重现该问题。

您将看到C#编译器在读取列表的逻辑周围发出try … catch块,如果您将使用Reflector反汇编生成的程序集。 它是C#编译器的文档function。 您可以在foreach声明中阅读更多相关信息

尝试… catch … finally块对JIT编码有很大的侵入性。 它使用引擎盖下的Windows SEH机制并严重重写您的代码。 我现在找不到一篇好文章的链接,但我相信如果你有兴趣,你可以在那里找到一个。

这就是这里发生的事情。 if … else语句中的try … finally块导致调试器打嗝。 您可以使用非常简单的代码重现您的问题。

 bool b = false; if (b) { try { b = true; } finally { b = true; } } else { b = true; } b = true; 

此代码不调用任何外部函数(它消除了其中一个答案提出的方法内联的影响),并且它直接编译到IL中,而不需要C#编译器添加任何其他编码。

它只能在发布模式下重现,因为在调试模式下,编译器会为C#代码的每一行发出IL NOP指令。 IL NOP指令不执行任何操作,它由JITer直接编译为CPU NOP指令,它也不执行任何操作。 该指令的用处在于它可以被调试器用作断点的锚点,即使其余代码被JITer严重重写也是如此。

通过在if … else之后的语句之前放置一条NOP指令,我能够使调试器正常工作。

您可以在此处阅读有关NOP操作和调试器映射过程的更多信息

您可以尝试使用WinDbg和SOS扩展来检查方法的JIT版本。 您可以尝试检查JIT-er生成的机器代码,并尝试理解为什么它无法将该机器代码映射回特定的C#行。

下面是关于使用WinDbg打破托管代码和获取JIT-ed方法的内存地址的几个链接。 我相信你应该能够找到一种从那里获取方法的JIT代码的方法: 在WinDbg中为托管代码设置断点 , SOS备忘单(.NET 2.0 / 3.0 / 3.5) 。

您还可以尝试向Microsoft报告问题。 可能这是一个CLR调试器错误。

谢谢你提出这个有趣的问题。

使用VS2010 SP1,如果在释放模式下设置断点,它将在最后一行停止。 你应该真的安装它,它特别提到它修复了调试器问题,它有时会跳过断点(虽然不是这个特定情况)。

证明

将构建配置更改为“Debug”,而不是“Release”。

JIT编译器使用可能导致此问题的优化技术。

一种这样的优化称为方法内联 ,可能是这种行为的原因。

我现在说不清楚,因为我正在使用另一个人的电脑……但你可以自己测试一下:

1)创建以下方法:

 [MethodImpl(MethodImplOptions.NoInlining)] public static void MyMethod(string str) { str += "-CONCAT-STRING"; } 

2)用“MyMethod”替换对“Console.WriteLine”的调用

3)设置断点,然后尝试。

4)现在,从方法“MyMethod”中删除“MethodImpl”属性。

 //[MethodImpl(MethodImplOptions.NoInlining)] public static void MyMethod(string str) { str += "-CONCAT-STRING"; } 

5)再次运行,断点在同一个地方。

6)如果它在第一次运行中停止,而不是在第二次运行中停止……那么这就是原因。

我认为当您使用发布模式进行调试时,您的断点可能与代码中的实际行不对应,因为机器代码可能已经过优化。 在你的代码的情况下,你实际上没有做任何事情,从打印“1”然后“2”,所以可以安全地假设编译器将删除(b == false)的代码,因为它永远不会到达。

构建版本省略了创建调试符号,这本身就很明显。

您可以通过转到项目的属性来覆盖它。 选择Release-configuration并在Build-tab上点击’Advanced’。 将Debug-Info设置为full。 在此处输入图像描述