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

Cui*_*崔鹏飞 16 .net c# debugging breakpoints visual-studio

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

这是重现问题的代码:

class Program {
  static void Main(string[] args) {
    bool b = false;

    if (b) {
        List<string> list = new List<string>();
        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
}
Run Code Online (Sandbox Code Playgroud)

在代码的最后一个语句中添加一个断点,然后在vs2010中调试它,你会看到无法命中断点.

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

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

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

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

提前致谢!

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

Den*_*nis 11

当提供的示例在发布模式下构建然后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#编译器的文档功能.您可以在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;
Run Code Online (Sandbox Code Playgroud)

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

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

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

你可以阅读更多关于NOP操作和调试映射过程在这里调试IL

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

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

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

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

  • 有更简单的方法来查看JIT编码.点击此处查看说明http://blogs.msdn.com/b/vancem/archive/2006/02/20/535807.aspx (2认同)

Bli*_*ndy 5

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

证明