使用backtrace獲取堆疊資訊

2021-08-10 11:36:43 字數 4269 閱讀 4855

一些記憶體檢測工具如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 ...