在程序運行的過程當中,若是出現異常,一般會發出一個信號進入信號處理函數中處理。有些故障過於嚴重到沒法實現程序的自恢復。這個時候,程序只能無奈的輸出一些錯誤信息。固然這些錯誤信息對程序的調試也是很是有幫助的,咱們在Java中若是出現異常的話,通常都會打印出堆棧跟蹤的信息。sql
固然,除了打印堆棧信息外,也能在程序的某些點設置一些調試信息方便輸出程序出錯的行號,函數名和文件名。可是這種方式的功能畢竟是有限的,不少異常出現的位置可能並無設置這樣的調試語句。這樣,仍是堆棧信息比較重要。由於這是運行時輸出的信息,而輸出行號,文件名,函數名的方式只能在編譯時肯定。
堆棧跟蹤主要和三個函數相關,分別爲backtrace, backtrace_symbols,以及backtrace_symbols_fd.關於這三個函數的信息以下:
#include <execinfo.h>
int backtrace(void **buffer, int size);
char **backtrace_symbols(void *const *buffer, int size);
void backtrace_symbols_fd(void *const *buffer, int size, int fd);
咱們知道函數調用的過程是嵌套的,在存儲器中通常將每一個函數對應爲一個堆棧幀,每一個堆棧幀保存相應函數的相關信息,如:參數信息,返回地址信息,函數正文段,動態鏈表,詞法鏈表等。函數調用的過程就是堆棧幀動態變化的過程,每調用一個函數,堆棧中就爲該函數創建一塊堆棧幀,堆棧幀的具體實現對應爲一個堆棧幀數據結構,數據結構中保存前面提到的信息。
backtrace函數返回backtrace函數被調用時的堆棧信息。這些信息存放在緩衝區buffer中。backtrace函數有兩個參數:
buffer:用於存放堆棧信息的緩衝區
size:用於指示buffer的大小。
要充分考慮到buffer的大小,由於若是函數調用的層次過深可能致使buffer空間不夠,這樣就只能保存一部分堆棧信息,靠近main函數這端的信息會由於空間不夠而被裁掉。buffer是一個指向二維數組的指針,類型爲void. buffer中所保存的是一系列堆棧幀的返回地址。traceback函數將返回堆棧中跟蹤到的函數的個數。
將trace函數返回的buffer和返回的函數個數分別做爲backtrace_symbols就能夠解析出這些跟蹤到的函數的符號名稱,確切的說,backtrace_symbols函數將針對buffer中每個函數返回地址進行解析,解析後的格式爲:./程序名(<函數名+>函數的在程序中的十六進制格式偏移地址) [函數實際的返回十六進制格式的地址],解析後的結果保存爲二維字符數組,返回值爲二維數組的起始地址。
設返回的二維數組爲strings,因爲在backtrace_symbols內部調用了malloc開闢空間,因此須要用戶來釋放空間。不過用戶只須要釋放strings所指向的字符串指針數組便可,對於每一個字符串不用釋放,也不該該釋放,內部會自動維護。
函數traceback_symbols_fd將輸出堆棧信息到fd所說明的文件中。這樣有一個好處就是不用調用malloc函數了,避免了內存分配失敗的可能性。
然而,須要注意的是,有些函數是不能經過traceback_symbols函數解析出來的,這些狀況爲:
l
內聯函數(沒有對應的堆棧幀)
l
優化級別較高的編譯選項會致使某些函數的堆棧指針不會被保存
l
尾遞歸優化可能致使多個函數(遞歸)只使用一個堆棧幀
l
靜態函數的名字不能解析到
另外在連接的時候須要添加一些特殊的選項,對於GCC這個選項是 –rdynamic。下面是一個簡單的程序,展現怎樣使用backtrace系列函數。
- /*
- *Author:Chaos Lee
- *Date:2012-02-26 21:35
- */
- #include<stdio.h>
- #include<execinfo.h>
- #include<stdlib.h>
- #define MAX_LEN 256
- void show_stack_info()
- {
- void *buffer[MAX_LEN];
- int returned_size;
- char **strings;
- int i=0;
- returned_size=backtrace(buffer,MAX_LEN);
- printf("%d addresses are returned.\n",returned_size);
- strings=backtrace_symbols(buffer,returned_size);
- if(strings==NULL)
- exit(1);
- for(i=0;i<returned_size;i++)
- {
- printf("%s\n",strings[i]);
- }
- }
- void func4(int a)
- {
- if(a>0)
- func4(--a);
- else
- show_stack_info();
- }
- static void func3(int a)
- {
- func4(--a);
- }
- void func2(int a)
- {
- func3(--a);
- }
- void func1(int a)
- {
- func2(--a);
- }
- int main()
- {
- func1(10);
- return 0;
- }
編譯指令爲:
- gcc traceback.c -o traceback –rdynamic
而後運行之:./traceback
輸出結果以下:
- 15 addresses are returned.
- ./traceback(show_stack_info+0x23) [0x40085b]
- ./traceback(func4+0x29) [0x4008e9]
- ./traceback(func4+0x1d) [0x4008dd]
- ./traceback(func4+0x1d) [0x4008dd]
- ./traceback(func4+0x1d) [0x4008dd]
- ./traceback(func4+0x1d) [0x4008dd]
- ./traceback(func4+0x1d) [0x4008dd]
- ./traceback(func4+0x1d) [0x4008dd]
- ./traceback(func4+0x1d) [0x4008dd]
- ./traceback [0x400902]
- ./traceback(func2+0x17) [0x40091b]
- ./traceback(func1+0x17) [0x400934]
- ./traceback(main+0xe) [0x400944]
- /lib64/libc.so.6(__libc_start_main+0xf4) [0x38ba61d8a4]
- ./traceback [0x4007a9]
注意,func3聲明爲靜態函數,因此不能將其符號名稱解析出來。其實,backtrace函數更多的是用在信號處理函數中,這在下一篇文章再做介紹。