一些記憶體檢測工具如valgrind,除錯工具如gdb,可以檢視程式執行時函式呼叫的堆疊資訊,有時候在分析程式時要獲得堆疊資訊,借助於backtrace是很有幫助的,其原型如下:
#include
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);
標頭檔案「execinfo.h」提供了三個相關的函式,簡單的說,backtrace函式用於獲取堆疊的位址資訊, backtrace_symbols函式把堆疊位址翻譯成我們易識別的字串, backtrace_symbols_fd函式則把字串堆疊資訊輸出到檔案中。
backtrace:該函式用於獲取當前執行緒的函式呼叫堆疊,獲取的資訊將存放在buffer中,buffer是乙個二級指標,可以當作指標陣列來用,陣列中的元素型別是void*,即從堆疊中獲取的返回位址,每乙個堆疊框架stack frame有乙個返回位址,引數 size 用來指定buffer中可以儲存void* 元素的最大值,函式返回值是buffer中實際獲取的void*指標個數,最大不超過引數size的大小。
backtrace_symbols:該函式把從backtrace函式獲取的資訊buffer轉化為乙個字串陣列char**,每個字串包含了相對於buffer中對應元素的可列印資訊,包括函式名、函式的偏移位址和實際的返回位址,size指定了該陣列中的元素個數,可以是backtrace函式的返回值,也可以小於這個值。需要注意的是,backtrace_symbols的返回值呼叫了malloc以分配儲存空間,為了防止記憶體洩露,我們要手動呼叫free來釋放這塊記憶體。
backtrace_symbols_fd:該函式與backtrace_symbols 函式功能類似,不同的是,這個函式直接把結果輸出到檔案描述符為fd的檔案中,且沒有呼叫malloc。
在使用以上三個函式時,還需要注意一下幾點:
(1)如果使用的是gcc編譯鏈結的話,建議加上「-rdynamic」引數,這個引數的意思是告訴elf聯結器新增「-export-dynamic」標記,這樣所有的符號資訊symbols就會新增到動態符號表中,以便檢視完整的堆疊資訊。
(2)static函式不會匯出符號資訊symbols,在backtrace中無效。
(3)某些編譯器的優化選項對獲取正確的函式呼叫堆疊有干擾,內聯函式沒有堆疊框架,刪除框架指標也會導致無法正確解析堆疊內容。
下面是乙個簡單的例子:
//backtrace_ex.cpp
#include
#include
#include
void my_backtrace()
; char **trace = null;
int size = backtrace(buffer, 100);
trace = backtrace_symbols(buffer, size);
if (null == trace)
for (int i = 0; i < size; ++i)
free(trace);
printf("----------done----------\n");
}void func2()
void func()
int main()
編譯執行上面的檔案:
g++ backtrace_ex.cpp
./a.out
./a.out() [0x400811]
./a.out() [0x400baf]
./a.out() [0x400bba]
./a.out() [0x400bc5]
/lib/x86_64-linux-gnu/libc.so
.6(__libc_start_main+0xf5) [0x7f2473cf5ec5]
./a.out() [0x400709]
----------done----------
咦!堆疊資訊雖然打出來了,但是函式呼叫棧並不是很明確,原因是少了「-rdynamic」引數,重新編譯執行如下:
g++ -rdynamic backtrace_ex.cpp
./a.out
./a.out(_z12my_backtracev+0x44) [0x400b11]
./a.out(_z5func2v+0x9) [0x400eaf]
./a.out(_z4funcv+0x9) [0x400eba]
./a.out(main+0x9) [0x400ec5]
/lib/x86_64-linux-gnu/libc.so
.6(__libc_start_main+0xf5) [0x7f006bdfbec5]
./a.out() [0x400a09]
----------done----------
加了「-rdynamic」引數後就很好了,我們可以看到函式名稱,由於不同的平台、編譯器有不同的編譯規則,所以用backtrace解析出來的函式名形式是不同的,以「./a.out(_z4funcv+0x9) [0x400eba]」為例說明,重點在於圓括號中的內容,「_z」是個函式名開始識別符號,後面的「4」表示函式名長度,接著便是真正的函式名「func」,後面的「v」表示函式引數型別為void,隨後的「+0x9」是偏移位址。雖然有一定的編譯規則,但可讀性還不是很好,我們可以用下面介紹的方法demangle來解析這些符號。
demangle即符號重組,函式原型如下:
#include
char* __cxa_demangle(const
char* __mangled_name,
char* __output_buffer,
size_t* __length,
int* __status);
cxxabi.h是乙個c++函式執行時庫,要用g++編譯鏈結,gcc會有問題。__mangled_name即原符號資訊,是個字串,以空字元結尾,__output_buffer用來儲存符號重組後的資訊,長度為__length,__status表示demangle結果,為0時表示成功,返回值指向符號重組後的字串首位址,字串以空字元結尾。
我們使用demangle來改進上面的例子:(把my_backtrace替換為my_backtrace2)
void my_backtrace2()
; char **trace = null;
int size = backtrace(buffer, 100);
trace = backtrace_symbols(buffer, size);
if (null == trace)
size_t name_size = 100;
char *name = (char*)malloc(name_size);
for (int i = 0; i < size; ++i)
else
if (*p == '+' && begin_name)
else
if (*p == ')' && begin_offset)
}if (begin_name && begin_offset && end_offset )
else
}else
}free(name);
free(trace);
printf("----------done----------\n");
}
結果如下:
g++ -rdynamic backtrace_ex.cpp
./a.out
./a.out:my_backtrace2()+0x44
./a.out:func2()+0x9
./a.out:func()+0x9
./a.out:main()+0x9
/lib/x86_64-linux-gnu/libc.so
.6:__libc_start_main()+0xf5
./a.out() [0x400a09]
----------done----------
可以看出來,demangle後函式名已清晰地顯示出來了,沒有那些奇奇怪怪的符號了。 使用backtrace獲取堆疊資訊
gdb將當前函式的棧幀編號為0,為外層函式的棧幀依次加1,這些編號將成為一些gdb命令的引數,以指明將要操作的是哪乙個函式的棧幀。gdb還支援使用address作為棧幀的識別符號,可在棧幀編號被破壞的情況下使用。1.在棧幀之間切換 gdb中有很多針對呼叫堆疊的命令,都需要乙個目標棧幀,例如列印區域性...
backtrace 堆疊列印除錯
有的時候程式需要檢視程序在某些極端情況下進入某個函式時,需要看是哪個函式呼叫它。這個時候可以使用backtrace進行列印。include include includevoid print trace printf obtained zd stack frames.n size for i 0 i...
通過程式設計方式獲取backtrace
在用gdb 偵錯程式時可以檢視所謂的 backtrace 它包含一系列的函式呼叫資訊,用命令 backtrace或bt 可以在gdb 中檢視函式呼叫棧的資訊。有些場合沒法使用 gdb時,則可以用 glibc 庫函式中的一些相關函式來得到 backtrace 的資訊 在標頭檔案 execinfo.h ...