0 引言
在調試應用程序時,可能因各種原因導致運行發生段錯誤。因此,有必要將堆棧調用信息打印出來,方便定位錯誤。
1 方法
在glibc頭文件execinfo.h中提供了三個函數獲取當前線程的函數調用堆棧。
0) int backtrace(void **buffer, intsize)
該函數將獲取的堆棧信息保存至buffer中,參數size則是表示buffer大小
1) char** backtrace_symbols(void * const *buffer, int size)
backtrace_symbols將從backtrace函數獲取的信息轉換一個字符串數組,參數buffer是從backtrace函數獲取的指針數組。函數返回值是一個指向字符串數組的指針,它的大小同buffer相同,每個字符串包含了一個相對於buffer中對應元素的可打印的信息,包括函數名,函數偏移地址和實際返回地址。很可惜的是隻有使用ELF二進制格式的程序才能獲取函數名和函數偏移地址。其它格式文件只能返回實際返回地址。
注意:某些編譯器的優化選項對獲取正確的調用堆棧有干擾(如果源代碼編譯時使用了-O1或-O2優化選項,可執行代碼會把ebp/rbp/rsp寄存器當作普通寄存器使用,導致backtrace失敗。爲了防止這種情況發生,可以在編譯時使用-O2 –fno-omit-frame-pointer或-Og來避免優化中使用上述寄存器。),另外內聯函數沒有堆棧框架。另外,bactrace_symbols與backtrace不同,返回值需要手動free,即malloc是在函數內部實現中,但free交由調用者執行。
程序舉例如下;
#include <stdio.h> #include <stdlib.h> #include <stddef.h> #include <execinfo.h> #include <signal.h>
void dump(int signo) { void *buffer[30] = { 0 }; size_t size; char **strings = NULL; size_t i = 0;
size = backtrace(buffer, 30); fprintf(stdout, "Obtained %zd stack frames.nm\n", size); strings = backtrace_symbols(buffer, size); if (strings == NULL) { perror("backtrace_symbols."); exit(EXIT_FAILURE); }
for (i = 0; i < size; i++) { fprintf(stdout, "%s\n", strings[i]); } free(strings); strings = NULL; exit(0); }
void func_c() { *((volatile char *)0x0) = 0x9999; }
void func_b() { func_c(); }
void func_a() { func_b(); }
int main(int argc, const char *argv[]) { if (signal(SIGSEGV, dump) == SIG_ERR) perror("can't catch SIGSEGV"); func_a(); return 0; } |
2 addr2line工具
正如上所述,通常情況我們並不能拿到函數名,而是返回的地址。慶幸的是,gcc工具鏈中提供了一個將返回地址映射到函數文件的具體位置的工具----addr2line工具。
具體使用方法也比較簡單,-e 可執行程序文件名,用於讀取符號信息(因此要求輸入的文件是debug文件)。
3 其它方法
即基於BSP寄存器,手動解析堆棧調用關係。可以參考 http://blog.csdn.net/littlefang/article/details/42295803和http://blog.chinaunix.net/uid-24774106-id-3457205.html文章。