Search code examples
c#visual-studiodebugging

Debugger shows invalid instruction


Sometimes it happens, that Visual Studio debugger shows invalid current instruction. The simplest possible reasons are:

  • Outdated .pdb files. These files contain information about which offset of the binary file corresponds to which line of code. If these files are outdated, obviously debugger will show invalid line.
  • Program compiled in Release mode instead of Debug. Release mode usually has the optimizations turned on, such that optimizer may modify the code to be faster (or smaller). In such case, the binary file no longer corresponds to the source code, so the debugger cannot show the actual executed line.

But my case was different. Here's a piece of source code (C#):

if (match.Groups.Count != 4)
    throw new InvalidOperationException("Internal error: Invalid regular expression!");

MyEnum myEnum;
try
{
    // (...)

I debugged the source. Debugger stopped at the conditional statement and shown, that match.Groups.Count actually is equal to 4. Obviously, the throw statement should have been skipped, but surprisingly, was not. However, after "executing" the throw statement, debugger went to the next valid statement and continued executing as it was supposed to.

What happened?


Solution

  • In such cases it is usually worth to look at the disassembly. In my case it looked like:

        if (match.Groups.Count != 4)
    
    00000344  mov         rax,qword ptr [rbp+30h] 
    00000348  mov         rax,qword ptr [rax] 
    (...)
    00000399  test        eax,eax 
    0000039b  jne         00000000000003ED // (1)
    
        throw new InvalidOperationException("Internal error: Invalid regular expression!");
    
    0000039d  lea         rdx,[00049AC0h] 
    000003a4  mov         ecx,7000024Eh 
    000003a9  call        000000005F7CC994 
    (...)
    000003dc  call        000000005ED32730 
    000003e1  mov         rcx,qword ptr [rbp+000000F8h] 
    000003e8  call        000000005F7CC64C 
    000003ed  nop // (2)
    
        MyEnum myEnum;
        try
        {
    
    000003ee  nop // (3)
    

    I set the breakpoint at the jne instruction (1). For those, who don't know the assembler much (not, that I actually do), conditional statements are often compiled to a pair: test and some kind of conditional jump, such as jne (jump if not equal). So the place, where I set the breakpoint was actually a final decision, whether to execute the throw statement or not.

    After a step-over (F10), debugger jumped to the (2) location, so it correctly skipped the throw statement. Again for those, who doesn't know that, the nop (No operation) is an assembler instruction, which actually does nothing. Compiler usually uses it to align the assembly code, such that it performs better (low level processor stuff, I guess).

    But the compiler screwed up and saved the information in .pdb file, that mentioned nop is a part of the throw statement. Debugger read that and positioned the current instruction marker on the throw statement. But then, it just executed it (did nothing) and kept on executing, because the actual assembly code was correct.

    I leave this example in case, that someone finds himself stuck with similar problem - hopefully it'll help to find the cause. It's rather rare to encounter, but who knows?...