1、函數原型
#include <execinfo.h>
int backtrace(void **buffer, int size);
該函數獲取當前線程的調用堆棧,獲取的信息將會被存放在buffer中,它是一個指針數組,參數size用來指定buffer中可以保存多少個void*元素。函數的返回值是實際返回的void*元素個數。buffer中的void*元素實際是從堆棧中獲取的返回地址。
char **backtrace_symbols(void *const *buffer, int size);
該函數將backtrace函數獲取的信息轉化為一個字符串數組,參數buffer是backtrace獲取的堆棧指針,size是backtrace返回值。函數返回值是一個指向字符串數組的指針,它包含char*元素個數為size。每個字符串包含了一個相對於buffer中對應元素的可打印信息,包括函數名、函數偏移地址和實際返回地址。
backtrace_symbols生成的字符串占用的內存是malloc出來的,但是是一次性malloc出來的,釋放是只需要一次性釋放返回的二級指針即可。
void backtrace_symbols_fd(void *const *buffer, int size, int fd);
該函數與backtrace_symbols函數功能相同,只是它不會malloc內存,而是將結果寫入文件描述符為fd的文件中,每個函數對應一行。該函數可重入。
2、函數使用注意事項
- backtrace的實現依賴於棧指針(fp寄存器),在gcc編譯過程中任何非零的優化等級(-On參數)或加入了棧指針優化參數-fomit-frame-pointer后多將不能正確得到程序棧信息;
- backtrace_symbols的實現需要符號名稱的支持,在gcc編譯過程中需要加入-rdynamic參數;
- 內聯函數沒有棧幀,它在編譯過程中被展開在調用的位置;
- 尾調用優化(Tail-call Optimization)將復用當前函數棧,而不再生成新的函數棧,這將導致棧信息不能正確被獲取。
3、捕獲異常信號並打印堆棧
當程序出現崩潰等異常時,會接收到內核發送給進程的異常信號,進程接收到異常信號后,可以在處理信號的時候將程序的堆棧信息打印出來,以便於程序調試。
4、程序示例:
#include <stdio.h> #include <execinfo.h> #include <unistd.h> #include <stdlib.h> #define BACKTRACE_SIZE 100 void print_backtrace() { void* buffer[BACKTRACE_SIZE]={0}; int pointer_num = backtrace(buffer, BACKTRACE_SIZE); char** string_buffer = backtrace_symbols(buffer, pointer_num); if(string_buffer == NULL) { printf("backtrace_symbols error"); exit(-1); } printf("print backtrace begin\n"); for(int i = 0; i < pointer_num; i++) { printf("%s\n", string_buffer[i]); } printf("print backtrace end\n"); free(string_buffer); return; } void func(int num) { if(num > 0) { func(--num); } else { print_backtrace(); } } int main(int argc, char* argv[]) { if(argc != 2) { printf("input param error"); return -1; } int input_num = atoi(argv[1]); func(input_num); return 0; }
執行結果如下,注意在編譯時帶-rdynamic參數