• 1. 通过下面的方式编译运行:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    wuzesheng@ubuntu:~/work/test$ gcc test.cc -o test1
    wuzesheng@ubuntu:~/work/test$ ./test1
    stackstrace begin:
    ./test1() [0x400645]
    ./test1() [0x400607]
    ./test1() [0x400612]
    ./test1() [0x40061d]
    ./test1() [0x4005ed]
    /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xff) [0x7f5c59a91eff]
    ./test1() [0x400529]

          从上面的运行结果中,我们的确看到了函数的调用栈,但是都是16进制的地址,会有点小小的不爽。当然我们可以通过反汇编得到每个地址对应的函数,但这个还是有点麻烦了。不急,且听我慢慢道来,看第2步。

  • 2. 通过下面的方式编译运行:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    wuzesheng@ubuntu:~/work/test$ gcc test.cc -rdynamic -o test2
    wuzesheng@ubuntu:~/work/test$ ./test2
    stackstrace begin:
    ./test2(_Z16print_stacktracev+0x26) [0x4008e5]
    ./test2(_Z4fun1v+0x13) [0x4008a7]
    ./test2(_Z4fun2v+0x9) [0x4008b2]
    ./test2(_Z4fun3v+0x9) [0x4008bd]
    ./test2(main+0x9) [0x40088d]
    /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xff) [0x7f9370186eff]
    ./test2() [0x4007c9]

          这下终于可以看到函数的名字了,对比一下2和1的编译过程,2比1多了一个-rdynamic的选项,让我们来看看这个选项是干什么的(来自gcc mannual的说明):

    1
    2
    
    -rdynamic
               Pass the flag -export-dynamic to the ELF linker, on targets that support it. This instructs the linker to add all symbols, not only used ones, to the dynamic symbol table. This option is needed for some uses of "dlopen" or to allow obtaining backtraces from within a program.

          从上面的说明可以看出,它的主要作用是让链接器把所有的符号都加入到动态符号表中,这下明白了吧。不过这里还有一个问题,这里的函数名都是mangle过的,需要demangle才能看到原始的函数。关于c++的mangle/demangle机制,不了解的朋友可以在搜索引擎上搜一下,我这里就不多就介绍了。这里介绍如何用命令来demangle,通过c++filt命令便可以:

    1
    2
    
    wuzesheng@ubuntu:~/work/test$ c++filt < << "_Z16print_stacktracev"
    print_stacktrace()

          写到这里,大部分工作就ok了。不过不知道大家有没有想过这样一个问题,同一个函数可以在代码中多个地方调用,如果我们只是知道函数,而不知道在哪里调用的,有时候还是不够方便,bingo,这个也是有办法的,可以通过address2line命令来完成,我们用第2步中编译出来的test2来做实验(address2line的-f选项可以打出函数名, -C选项也可以demangle):

    1
    2
    3
    4
    
    wuzesheng@ubuntu:~/work/test$ addr2line -a 0x4008a7 -e test2 -f
    0x00000000004008a7
    _Z4fun1v
    ??:0

          Oh no,怎么打出来的位置信息是乱码呢?不急,且看我们的第3步。

  • 3. 通过下面的方式编译运行:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    
    wuzesheng@ubuntu:~/work/test$ gcc test.cc -rdynamic -g -o test3 wuzesheng@ubuntu:~/work/test$ ./test3
    stackstrace begin:
    ./test3(_Z16print_stacktracev+0x26) [0x4008e5]
    ./test3(_Z4fun1v+0x13) [0x4008a7]
    ./test3(_Z4fun2v+0x9) [0x4008b2]
    ./test3(_Z4fun3v+0x9) [0x4008bd]
    ./test3(main+0x9) [0x40088d]
    /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xff) [0x7fa9558c1eff]
    ./test3() [0x4007c9]
    wuzesheng@ubuntu:~/work/test$ addr2line -a 0x4008a7 -e test3 -f -C
    0x00000000004008a7
    fun1()
    /home/wuzesheng/work/test/test.cc:20

          看上面的结果,我们不仅得到了调用栈,而且可以得到每个函数的名字,以及被调用的位置,大功告成。在这里需要说明一下的是,第3步比第2步多了一个-g选项,-g选项的主要作用是生成调试信息,位置信息就属于调试信息的范畴,经常用gdb的朋友相信不会对这个选项感到陌生。