Search code examples
cgccoptimizationgdbabort

gdb showing incorrect back trace by pointing at the wrong line of code


We can reproduce this problem with very simple example by having more than one abort call in our source code. In the below example code we have total of four abort calls with in different conditions but when we compile with optimization flag(-O3) we can see debug info for only one abort call . so where ever crash happens with in these four abort calls gdb always gives the one which has debug info.

#include <stdio.h>
#include <stdlib.h>

void level_aa(int a)
{
        if (a == 0) 
          abort(); 
        if (a == 1) 
          abort();
        if (a == 2) 
          abort();

        abort();
}

int main(int argc,char *argv[])
{       int D;
        D = atoi(argv[1]);
        printf(" Value = %d", D);
        level_aa(D);
        return 0;
}

Comiple the above code with optimization flags(-O3) and run with gdb to reproduce the issue.

>gcc -g -O3 abort_crash.c -o abort
>gdb ./abort
(gdb)run 1
(gdb) bt
#0  0x00007ffff7ab2945 in *__GI_raise (sig=<optimized out>) at ../nptl/sysdeps/unix/sysv/linux/raise.c:64
#1  0x00007ffff7ab3f21 in *__GI_abort () at abort.c:92
#2  0x0000000000400634 in level_aa (a=<optimized out>) at abort_crash.c:13
#3  main (argc=<optimized out>, argv=<optimized out>) at abort_crash.c:20
(gdb) 

If we observe frame 2(#2), the crash actually happened at line no 9 but gdb showing line no 13. I can understand that it is happening because of optimization of source code. Is it possible to make gdb work fine in this case? (with out removing optimization) Thanks in advance for your help.


Solution

  • Is it possible to make gdb work fine in this case?

    GDB is working fine in this case: it shows you what actually happened, which is ...

    (with out removing optimization)

    ... the compiler discovered that multiple paths lead to the abort function which can't return and have no side effects, and so merged these paths together, reducing the size of generated code. Just look at the resulting assembly:

    (gdb) disas level_aa
    Dump of assembler code for function level_aa:
       0x0000000000400620 <+0>: sub    $0x8,%rsp
       0x0000000000400624 <+4>: callq  0x4004b0 <abort@plt>
    End of assembler dump.
    

    All your ifs are completely optimized out.

    If you want to distinguish between the cases, you must make it known to the compiler that the paths are not equivalent.

    Putting a printf call, or a call to external function that is not visible to the compiler in the current translation unit (the function itself may call abort):

    extern int my_abort(int);  // must be in a different TU
    
    void level_aa(int a)
    {
            if (a == 0)
              my_abort(a + 1);
            if (a == 1)
              my_abort(a + 2);
            if (a == 2)
              my_abort(a + 3);
    
            abort();
    }
    

    Note: the a+1, a+2 etc. are needed to prevent the compiler from merging separate paths. If you merely called my_abort(a), the compiler may still merge them together as if you wrote:

    if (a >= 0 && a <= 2) my_abort(a);
    

    Note that even this external function approach may fail if you use whole program optimization.