17

A curious case of Visual Studio 2010 debugger(it can not hit a break point)

This is the code that reproduces the problem:

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
}

Add a break point to the last statement of the code, then debug it in vs2010, you'll see that the break point can not be hit.

To reproduce this curious case, you'll need to meet the following conditions:

  1. Operation system: windows 7 x64;
  2. VS build configuration: release;
  3. VS build platform target: x64 or Any Cpu;
  4. VS build debug info: pdb only or full;
  5. VS build optimize code: enabled;

I am not sure those conditions are sufficient to reproduce it, but it's how my machine was configured when I found this issue.

Why is the debugger not able to hit the break point?

Thanks in advance!

And if you can reproduce this issue, please consider voting on this post.

13
  • 1
    I've got this configuration and I cannot reproduce your stated behavior. Would it matter that I'm using Win7 64 Bit SP 1? The rest of the setup is the same, but I hit the breakpoint every time.
    – Bob G
    Commented Apr 21, 2011 at 13:13
  • 2
    I imagine the line has been optimised out by the compiler if you step it, is it running the console.writeline("1") twice, but supplying different arguments?
    – forsvarir
    Commented Apr 21, 2011 at 13:15
  • 2
    @CuiPengFei: You're right, it's odd... if you put the breakpoint on the actual call to WriteLine (in the disassembly view), then it does get hit, but you have to keep reenabling it for each run.
    – forsvarir
    Commented Apr 21, 2011 at 13:31
  • 2
    @CuiPengFei: What I think is even odder, is if you duplicate the final Console.WriteLine (copy it to the line below), you can set the breakpoint on the last one and it will be hit, but the one with your current breakpoint on it still won't be...
    – forsvarir
    Commented Apr 21, 2011 at 13:37
  • 1
    This looks like a job for connect.microsoft.com. Commented Apr 23, 2011 at 15:35

6 Answers 6

12
+50

When the provided example is built in release mode and then JIT-ed into 64-bit machine code, it does not contain enough information for the debugger to correlate the breakpoint with any particular machine instruction. That’s why debugger never stops at this breakpoint during execution of a JIT-ed machine code. It just does not know where to stop. Probably it is some kind of misbehavior or even a bug in 64-bit CLR debugger because it is reproducible only when it is JIT-ed into 64-bit machine code but not into 32-bit machine code.

When the debugger sees a breakpoint in your code it tries to find out a machine instruction in the JIT-ed code that corresponds to the location marked by the breakpoint. First, it needs to find an IL instruction that corresponds to a breakpoint location in your C# code. Then it needs to find a machine instruction that corresponds to the IL command. Then it sets a real breakpoint on the found machine instruction and starts execution of the method. In your case, it looks like that the debugger just ignores a breakpoint because it cannot map it to a particular machine instruction.

The debugger cannot find an address of a machine instruction that immediately follows if…else statement. The if…else statement and the code inside it somehow causes this behavior. It does not matter what statement follows the if…else. You can replace the Console.WriteLine(“2”) statement with some other one and you will be still able to reproduce the issue.

You will see that the C# compiler emits a try…catch block around the logic that reads the list if you will disassemble the resulting assembly with Reflector. It is a documented feature of the C# compiler. You can read more about it at The foreach statement

A try…catch…finally block has a pretty invasive effect on a JIT-ed code. It uses the Windows SEH mechanism under the hood and rewrites your code badly. I cannot find a link to a good article right now but I’m sure that you can find one out there if you are interested.

It is what happens here. The try…finally block inside of if…else statement causes the debugger to hiccup. You can reproduce your issue with a much simple code.

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

This code does not call any external functions (it eliminates effect of method inlining proposed by one of the answers) and it compiles directly into IL without any additional coded added by the C# compiler.

It is reproducible only in release mode because in the debug mode the compiler emits the IL NOP instruction for every line of your C# code. The IL NOP instruction does nothing and it is directly compiled to the CPU NOP instruction by the JITer that does nothing too. The usefulness of this instruction is that it can be used by the debugger as an anchor for breakpoints even if the rest of the code is badly rewritten by the JITer.

I was able to make the debugger to work correctly by putting one NOP instruction right before the statement that follows the if…else.

You can read more about NOP operations and debugger mapping process here Debugging IL

You can try to use WinDbg and SOS extension for it to examine JIT-ed version of the method. You can try to examine machine code that JIT-er generates and try to understand why it cannot map back that machine code to particular line of C#.

Here are couple link about using WinDbg for breaking in managed code and getting a memory address of a JIT-ed method. I believe that you should be able to find a way to get JIT-ed code for a method from there: Setting a breakpoint in WinDbg for Managed Code, SOS Cheat Sheet (.NET 2.0/3.0/3.5).

You can also try to report an issue to Microsoft. Probably this is a CLR debugger bug.

Thank you for the interesting question.

1
5

Using VS2010 SP1, it stops on the last line if you set a breakpoint in release mode. You should really install it, it specifically mentions it fixes debugger issues where it would sometimes skip over breakpoints (although not this specific case).

Proof

7
  • thanks, I'll try that on Monday when I get back to work (My PC at home is Windows7 x86). Commented Apr 23, 2011 at 16:23
  • 1
    @Blindy: I'm running VS2010 SP1 (Win 7, 64-bit) and it skips over this breakpoint. Commented Apr 24, 2011 at 0:41
  • 1
    I have VS2010 SP1 and I can still reproduce it.
    – Dennis
    Commented Apr 24, 2011 at 0:59
  • 1
    Win7 64-bit VS SP1: x86|Release build works, x64|Release does not. OP stated x64 or Any CPU. Commented Apr 24, 2011 at 6:38
  • 1
    @cuipengfei, your only chance is your connect ticket then
    – Blindy
    Commented Apr 25, 2011 at 1:47
2

Change your build configuration to "Debug", instead of "Release".

7
  • 1
    That's not really an answer as to why isn't not being hit, it's how to make it behave as expected....
    – forsvarir
    Commented Apr 21, 2011 at 13:17
  • Yes, I know that'll make the break point hittable. But I just want to know why it's not working in Release mode. Commented Apr 21, 2011 at 13:18
  • 2
    @forsvarir This is a perfectly valid answer. In release mode the compiler is much more aggressive in the optimizations it makes, the result is that your view of the source code has little similarity to the compiled, optimized result.
    – MattDavey
    Commented Apr 21, 2011 at 15:44
  • 1
    @MattDavey: It really isn't an answer. In fact, your comment is more of an answer to the OP's question. They already stated the required configuration to reproduce the issue. They never asked how to fix it. They asked WHY it wasn't hit. This answer, gives a one line instruction, it doesn't give a reason for the issue.
    – forsvarir
    Commented Apr 21, 2011 at 17:02
  • 1
    @MattDavey: "the source code has little similarity to the compiled, optimized result". even though, other break points are hittable, only the one on the last line does not work. Commented Apr 22, 2011 at 1:38
1

The JIT compiler uses optimization techniques that may cause this.

One such optimization is called method inlining, that may be responsible for this behavior.

I cannot tell exactly right now, because I am using another person's computer... but you can test that yourself:

1) Create the following method:

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

2) replace the calls to "Console.WriteLine" with just "MyMethod"

3) Set the breakpoint, and try it.

4) Now, remove the "MethodImpl" attribute from the method "MyMethod".

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

5) Run again, with the breakpoint in the same place.

6) If it stops in the first run, but not in the second run... then this is the reason.

2
  • Thanks, this seems like a good suggestion. I'll try it on Monday since my PC at home is Win7 x86. Commented Apr 24, 2011 at 3:21
  • I just tested it. With or without the attribute, it can not hit the break point. Commented Apr 25, 2011 at 1:38
0

I think that when you debug using release mode your breakpoints may not correspond to actual lines in the code because the machine code may have been optimized. In the case of your code you are actually not doing anything part from printing "1" and then "2" so it would be safe to assume that the compiler would remove the code of the (b==false) since it will never be reached.

3
  • I decompiled it and saw the IL code, I am not very familiar with MSIL code, but I think the if clause is still there. Commented Apr 23, 2011 at 16:21
  • @CuiPengFei: The "if (false)" statement is probably going to be optimized by the JIT compiler then. Not all optimizations need to be made to IL code. The compiler may just let the known JIT optimizations be made by JIT compiler. Commented Apr 24, 2011 at 0:43
  • 1
    When you start debug session from VS it suppresses JIT optimization. I can reproduce an issue even if Suppress JIT optimization on module load (Managed only) settings enabled for my VS. It means that JIT optimization has nothing to do to this behavior.
    – Dennis
    Commented Apr 24, 2011 at 0:52
0

Building the release omits creating the debug-symbols, which in itself is obvious.

You can override this by going to the properties of your project. select the Release-configuration and hit 'Advanced' on the Build-tab. Set the Debug-Info to full. enter image description here

1
  • I tried both full and pdb-only and I mentioned that in the question Commented Apr 23, 2011 at 16:28

Not the answer you're looking for? Browse other questions tagged or ask your own question.