cgdb

Why isn't GDB breaking in this instruction?


In C, BUFFER_SIZE is a macro, this should be a compile-time memory allocation, and gdb does not break and skips the line.

#define BUFFER_SIZE 50
int main(){
  const int sz = BUFFER_SIZE;
  char buffer[BUFFER_SIZE]; // does not break when a breakpoint is specified here
}

This is a run-time allocation, gdb does break.

#define BUFFER_SIZE 50
int main(){
  const int sz = BUFFER_SIZE;
  char buffer[sz]; // it does break here
}

I think it's because compile-time evaluated instructions cannot be debugged because gdb debugs at runtime, is that correct or is there another explanation ?


Solution

  • TL;DR

    You can set a breakpoint only on lines that have machine instructions.

    On what else could a debugger break? The same is true for single stepping.

    The array definition as a local variable needs only instructions when it is a VLA, which is constructed at run-time.


    The following experiments are done with MinGW for a 64 bit Windows 10 machine version 13.2.0 that I have at hand.

    I compiled with the options -Wall -pedantic -g. Of course, the compiler gives me warnings because of the unused variables.

    The assembly listings were generated with objdump -dS from the executables.

    Compiled as C without optimization

    Without optimization (-O0), as you did, the compiler generates different code.

    In the first program, you have a compile-time constant for the array size. Therefore, the compiler includes the array directly in the stack space allocated for the local variables. Consequently, there is no instruction for the defining line.

    0000000140001450 <main>:
    #define BUFFER_SIZE 50
    int main(){
       140001450:   55                      push   %rbp
       140001451:   48 89 e5                mov    %rsp,%rbp
       140001454:   48 83 ec 60             sub    $0x60,%rsp
       140001458:   e8 c3 00 00 00          call   140001520 <__main>
      const int sz = BUFFER_SIZE;
       14000145d:   c7 45 fc 32 00 00 00    movl   $0x32,-0x4(%rbp)
       140001464:   b8 00 00 00 00          mov    $0x0,%eax
      char buffer[BUFFER_SIZE]; // does not break when a breakpoint is specified here
    }
       140001469:   48 83 c4 60             add    $0x60,%rsp
       14000146d:   5d                      pop    %rbp
       14000146e:   c3                      ret
    

    In the second program, the compiler see a VLA because of the variable. It generates instructions for the allocation, so you can set a breakpoint. (Side note: By default, the compiler checks that the stack has room for the array.)

    0000000140001450 <main>:
    #define BUFFER_SIZE 50
    int main(){
       140001450:   55                      push   %rbp
       140001451:   48 89 e5                mov    %rsp,%rbp
       140001454:   48 83 ec 40             sub    $0x40,%rsp
       140001458:   e8 03 01 00 00          call   140001560 <__main>
       14000145d:   48 89 e0                mov    %rsp,%rax
       140001460:   48 89 c2                mov    %rax,%rdx
      const int sz = BUFFER_SIZE;
       140001463:   c7 45 fc 32 00 00 00    movl   $0x32,-0x4(%rbp)
      char buffer[sz]; // it does break here
       14000146a:   8b 45 fc                mov    -0x4(%rbp),%eax
       14000146d:   48 98                   cltq
       14000146f:   48 83 e8 01             sub    $0x1,%rax
       140001473:   48 89 45 f0             mov    %rax,-0x10(%rbp)
       140001477:   8b 45 fc                mov    -0x4(%rbp),%eax
       14000147a:   48 98                   cltq
       14000147c:   48 83 c0 0f             add    $0xf,%rax
       140001480:   48 c1 e8 04             shr    $0x4,%rax
       140001484:   48 c1 e0 04             shl    $0x4,%rax
       140001488:   e8 a3 10 00 00          call   140002530 <___chkstk_ms>
       14000148d:   48 29 c4                sub    %rax,%rsp
       140001490:   48 8d 44 24 20          lea    0x20(%rsp),%rax
       140001495:   48 83 c0 00             add    $0x0,%rax
       140001499:   48 89 45 e8             mov    %rax,-0x18(%rbp)
       14000149d:   48 89 d4                mov    %rdx,%rsp
       1400014a0:   b8 00 00 00 00          mov    $0x0,%eax
    }
       1400014a5:   48 89 ec                mov    %rbp,%rsp
       1400014a8:   5d                      pop    %rbp
       1400014a9:   c3                      ret
    

    Compiled as C++ without optimization

    When compiled as C++, the constant variable sz of the second program is semantically a compile-time constant, so there is no difference between both executables.

    In both cases no breakpoint can be set.

    0000000140001450 <main>:
    #define BUFFER_SIZE 50
    int main(){
       140001450:   55                      push   %rbp
       140001451:   48 89 e5                mov    %rsp,%rbp
       140001454:   48 83 ec 60             sub    $0x60,%rsp
       140001458:   e8 c3 00 00 00          call   140001520 <__main>
      const int sz = BUFFER_SIZE;
       14000145d:   c7 45 fc 32 00 00 00    movl   $0x32,-0x4(%rbp)
      char buffer[BUFFER_SIZE]; // does not break when a breakpoint is specified here
    }
       140001464:   b8 00 00 00 00          mov    $0x0,%eax
       140001469:   48 83 c4 60             add    $0x60,%rsp
       14000146d:   5d                      pop    %rbp
       14000146e:   c3                      ret
    
    0000000140001450 <main>:
    #define BUFFER_SIZE 50
    int main(){
       140001450:   55                      push   %rbp
       140001451:   48 89 e5                mov    %rsp,%rbp
       140001454:   48 83 ec 60             sub    $0x60,%rsp
       140001458:   e8 c3 00 00 00          call   140001520 <__main>
      const int sz = BUFFER_SIZE;
       14000145d:   c7 45 fc 32 00 00 00    movl   $0x32,-0x4(%rbp)
      char buffer[sz]; // it does break here
    }
       140001464:   b8 00 00 00 00          mov    $0x0,%eax
       140001469:   48 83 c4 60             add    $0x60,%rsp
       14000146d:   5d                      pop    %rbp
       14000146e:   c3                      ret
    

    Compiled as C with optimization

    Finally I compiled both programs with -O3, which produced identical machine code. The compiler found that the variables are unused and removed them. Consequently, you cannot set a breakpoint.

    0000000140002920 <main>:
    #define BUFFER_SIZE 50
    int main(){
       140002920:   48 83 ec 28             sub    $0x28,%rsp
       140002924:   e8 d7 eb ff ff          call   140001500 <__main>
      const int sz = BUFFER_SIZE;
      char buffer[BUFFER_SIZE]; // does not break when a breakpoint is specified here
    }
       140002929:   31 c0                   xor    %eax,%eax
       14000292b:   48 83 c4 28             add    $0x28,%rsp
       14000292f:   c3                      ret
    
    0000000140002920 <main>:
    #define BUFFER_SIZE 50
    int main(){
       140002920:   48 83 ec 28             sub    $0x28,%rsp
       140002924:   e8 d7 eb ff ff          call   140001500 <__main>
      const int sz = BUFFER_SIZE;
      char buffer[BUFFER_SIZE]; // does not break when a breakpoint is specified here
    }
       140002929:   31 c0                   xor    %eax,%eax
       14000292b:   48 83 c4 28             add    $0x28,%rsp
       14000292f:   c3                      ret