12345678910 | wuzesheng@ubuntu:~/work/test$ gcc test.cc -o test1wuzesheng@ubuntu:~/work/test$ ./test1stackstrace 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] |
從上面的運(yùn)行結(jié)果中,我們的確看到了函數(shù)的調(diào)用棧,但是都是16進(jìn)制的地址,會(huì)有點(diǎn)小小的不爽。當(dāng)然我們可以通過(guò)反匯編得到每個(gè)地址對(duì)應(yīng)的函數(shù),但這個(gè)還是有點(diǎn)麻煩了。不急,且聽(tīng)我慢慢道來(lái),看第2步。
12345678910 | wuzesheng@ubuntu:~/work/test$ gcc test.cc -rdynamic -o test2wuzesheng@ubuntu:~/work/test$ ./test2stackstrace 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] |
這下終于可以看到函數(shù)的名字了,對(duì)比一下2和1的編譯過(guò)程,2比1多了一個(gè)-rdynamic的選項(xiàng),讓我們來(lái)看看這個(gè)選項(xiàng)是干什么的(來(lái)自gcc mannual的說(shuō)明):
12 | -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. |
從上面的說(shuō)明可以看出,它的主要作用是讓鏈接器把所有的符號(hào)都加入到動(dòng)態(tài)符號(hào)表中,這下明白了吧。不過(guò)這里還有一個(gè)問(wèn)題,這里的函數(shù)名都是mangle過(guò)的,需要demangle才能看到原始的函數(shù)。關(guān)于c++的mangle/demangle機(jī)制,不了解的朋友可以在搜索引擎上搜一下,我這里就不多就介紹了。這里介紹如何用命令來(lái)demangle,通過(guò)c++filt命令便可以:
12 | wuzesheng@ubuntu:~/work/test$ c++filt < << "_Z16print_stacktracev"print_stacktrace() |
寫(xiě)到這里,大部分工作就ok了。不過(guò)不知道大家有沒(méi)有想過(guò)這樣一個(gè)問(wèn)題,同一個(gè)函數(shù)可以在代碼中多個(gè)地方調(diào)用,如果我們只是知道函數(shù),而不知道在哪里調(diào)用的,有時(shí)候還是不夠方便,bingo,這個(gè)也是有辦法的,可以通過(guò)address2line命令來(lái)完成,我們用第2步中編譯出來(lái)的test2來(lái)做實(shí)驗(yàn)(address2line的-f選項(xiàng)可以打出函數(shù)名, -C選項(xiàng)也可以demangle):
1234 | wuzesheng@ubuntu:~/work/test$ addr2line -a 0x4008a7 -e test2 -f0x00000000004008a7_Z4fun1v??:0 |
Oh no,怎么打出來(lái)的位置信息是亂碼呢?不急,且看我們的第3步。
1234567891011121314 | wuzesheng@ubuntu:~/work/test$ gcc test.cc -rdynamic -g -o test3wuzesheng@ubuntu:~/work/test$ ./test3stackstrace 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 -C0x00000000004008a7fun1()/home/wuzesheng/work/test/test.cc:20 |
看上面的結(jié)果,我們不僅得到了調(diào)用棧,而且可以得到每個(gè)函數(shù)的名字,以及被調(diào)用的位置,大功告成。在這里需要說(shuō)明一下的是,第3步比第2步多了一個(gè)-g選項(xiàng),-g選項(xiàng)的主要作用是生成調(diào)試信息,位置信息就屬于調(diào)試信息的范疇,經(jīng)常用gdb的朋友相信不會(huì)對(duì)這個(gè)選項(xiàng)感到陌生。