一直以來都沒有花太多精力放在學習調試方面,主要還是平時調試的機會相對較少,一般情況下,用strace、gdb、以及通過列印log基本上就能解決問題了,還有就是,與其花精力去提高調試技能,還不如在設計、防禦式編程和單元測試等能力去提高,以及提高自已編碼的品質,減少BUG的出現或者縮少BUG的範圍。
但是,有時使用調試工具並不是為了尋找BUG,在閱讀和分析原始碼時也非常有用,下面的代碼示範如何在自已的程式中列印呼叫堆疊,有時你想知道某一個函數在某一時刻被哪一個函數調用了,只要在這個函數中列印一下呼叫堆疊即可,在一些不方便使用調試工具的場合(例如程式在開發板上運行時)還是比較有用的。
下面的範例程式碼我從網路上找來的,我去掉了注釋,並在上面加上了我自已的注釋,可以通過看注釋來瞭解是怎麼實現的,注意在用gcc編譯代碼時,要加上選項-rdynamic和-g:
#include <stdio.h> #include <execinfo.h> void print_trace(void); void funcC() { /* 列印呼叫堆疊,看看誰調用了本函數 */ print_trace(); } void funcB() { funcC(); } void funcA() { funcB(); } int main (void) { funcA(); return 0; } void print_trace(void) { int i; const int MAX_CALLSTACK_DEPTH = 32; /* 需要列印堆棧的最大深度 */ void *traceback[MAX_CALLSTACK_DEPTH]; /* 用來儲存呼叫堆疊中的地址 */ /* 利用 addr2line 命令可以列印出一個函數地址所在的原始碼位置 * 調用格式為: addr2line -f -e /tmp/a.out 0x400618 * 使用前,原始碼編譯時間要加上 -rdynamic -g 選項 */ char cmd[512] = "addr2line -f -e "; char *prog = cmd + strlen(cmd); /* 得到當前可執行程式的路徑和檔案名稱 */ int r = readlink("/proc/self/exe",prog,sizeof(cmd)-(prog-cmd)-1); /* popen會fork出一個子進程來調用/bin/sh, 並執行cmd字串中的命令, * 同時,會建立一個管道,由於參數是'w', 管道將與標準輸入相串連, * 並返回一個FILE的指標fp指向所建立的管道,以後只要用fp往管理裡寫任何內容, * 內容都會被送往到標準輸入, * 在下面的代碼中,會將呼叫堆疊中的函數地址寫入管道中, * addr2line程式會從標準輸入中得到該函數地址,然後根據地址列印出原始碼位置和函數名。 */ FILE *fp = popen(cmd, "w"); /* 得到當前呼叫堆疊中的所有函數地址,放到traceback數組中 */ int depth = backtrace(traceback, MAX_CALLSTACK_DEPTH); for (i = 0; i < depth; i++) { /* 得到呼叫堆疊中的函數的地址,然後將地址發送給 addr2line */ fprintf(fp, "%p/n", traceback[i]); /* addr2line 命令在收到地址後,會將函數地址所在的原始碼位置列印到標準輸出 */ } fclose(fp); }