日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

在C/C++程序中打印當(dāng)前函數(shù)調(diào)用棧

 quasiceo 2013-02-19

在C/C++程序中打印當(dāng)前函數(shù)調(diào)用棧

      前幾天幫同事跟蹤的一個程序莫名退出,沒有core dump(當(dāng)然ulimit是打開的)的問題。我們知道,正常情況下,如果程序因?yàn)槟撤N異常條件退出的話,應(yīng)該會產(chǎn)生core dump,而如果程序正常退出的話,應(yīng)該是直接或者間接的調(diào)用了exit()相關(guān)的函數(shù)?;谶@個事實(shí),我想到了這樣一個辦法,在程序開始時,通過系統(tǒng)提 供的atexit(),向系統(tǒng)注冊一個回調(diào)函數(shù),在程序調(diào)用exit()退出的時候,這個回調(diào)函數(shù)就會被調(diào)用,然后我們在回調(diào)函數(shù)中打印出當(dāng)前的函數(shù)調(diào)用 棧,由此便可以知道exit()是在哪里調(diào)用,從而上述問題便迎刃而解了。上述方法用來解決類似問題是非常行之有效的。在上面,我提到了在“回調(diào)函數(shù)中打 印出當(dāng)前的函數(shù)調(diào)用棧”,相信細(xì)心的朋友應(yīng)該注意到這個了,本文的主要內(nèi)容就是詳細(xì)介紹,如何在程序中打印中當(dāng)前的函數(shù)調(diào)用棧。
      我之前寫過一篇題目為《介紹幾個關(guān)于C/C++程序調(diào)試的函數(shù)》的文章,看到這里,請讀者朋友先看一下前面這篇,因?yàn)楸疚氖且郧懊孢@篇文章為基礎(chǔ)的。我正是用了backtrace()和backtrace_symbols()這兩個函數(shù)實(shí)現(xiàn)的,下面是一個簡單的例子,通過這個例子我們來介紹具體的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <execinfo .h>
#include <stdio .h>
#include <stdlib .h>
 
void fun1();
void fun2();
void fun3();
 
void print_stacktrace();
 
int main()
{
    fun3();
}
 
void fun1()
{
    printf("stackstrace begin:\n");
    print_stacktrace();
}
 
void fun2()
{
    fun1();
}
 
void fun3()
{
    fun2();
}
 
void print_stacktrace()
{
    int size = 16;
    void * array[16];
    int stack_num = backtrace(array, size);
    char ** stacktrace = backtrace_symbols(array, stack_num);
    for (int i = 0; i < stack_num; ++i)
    {
        printf("%s\n", stacktrace[i]);
    }
    free(stacktrace);
}

(說明:下面的介紹采用的環(huán)境是ubuntu 11.04, x86_64, gcc-4.5.2)

  • 1. 通過下面的方式編譯運(yùn)行:
  • 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]

          從上面的運(yùn)行結(jié)果中,我們的確看到了函數(shù)的調(diào)用棧,但是都是16進(jìn)制的地址,會有點(diǎn)小小的不爽。當(dāng)然我們可以通過反匯編得到每個地址對應(yīng)的函數(shù),但這個還是有點(diǎn)麻煩了。不急,且聽我慢慢道來,看第2步。

  • 2. 通過下面的方式編譯運(yùn)行:
  • 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]

          這下終于可以看到函數(shù)的名字了,對比一下2和1的編譯過程,2比1多了一個-rdynamic的選項(xiàng),讓我們來看看這個選項(xiàng)是干什么的(來自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.

          從上面的說明可以看出,它的主要作用是讓鏈接器把所有的符號都加入到動態(tài)符號表中,這下明白了吧。不過這里還有一個問題,這里的函數(shù)名 都是mangle過的,需要demangle才能看到原始的函數(shù)。關(guān)于c++的mangle/demangle機(jī)制,不了解的朋友可以在搜索引擎上搜一 下,我這里就不多就介紹了。這里介紹如何用命令來demangle,通過c++filt命令便可以:

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

          寫到這里,大部分工作就ok了。不過不知道大家有沒有想過這樣一個問題,同一個函數(shù)可以在代碼中多個地方調(diào)用,如果我們只是知道函數(shù), 而不知道在哪里調(diào)用的,有時候還是不夠方便,bingo,這個也是有辦法的,可以通過address2line命令來完成,我們用第2步中編譯出來的 test2來做實(shí)驗(yàn)(address2line的-f選項(xiàng)可以打出函數(shù)名, -C選項(xiàng)也可以demangle):

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

          Oh no,怎么打出來的位置信息是亂碼呢?不急,且看我們的第3步。

  • 3. 通過下面的方式編譯運(yùn)行:
  • 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

          看上面的結(jié)果,我們不僅得到了調(diào)用棧,而且可以得到每個函數(shù)的名字,以及被調(diào)用的位置,大功告成。在這里需要說明一下的是,第3步比第 2步多了一個-g選項(xiàng),-g選項(xiàng)的主要作用是生成調(diào)試信息,位置信息就屬于調(diào)試信息的范疇,經(jīng)常用gdb的朋友相信不會對這個選項(xiàng)感到陌生。

      本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報。
      轉(zhuǎn)藏 分享 獻(xiàn)花(0

      0條評論

      發(fā)表

      請遵守用戶 評論公約

      類似文章 更多