?
? 嵌入式 linux下利用backtrace追蹤函數調用堆棧以及定位段錯誤
2015-05-27 14:19?184人閱讀? ?分類: ? 嵌入式(928)?
一般察看函數運行時堆棧的方法是使用GDB(bt命令)之類的外部調試器,但是,有些時候為了分析程序的BUG,(主要針對長時間運行程序的分析),在程序出錯時打印出函數的調用堆棧是非常有用的。
在glibc頭文件"execinfo.h"中聲明了三個函數用于獲取當前線程的函數調用堆棧。
?
[cpp]?view plaincopy
int?backtrace(void?**buffer,int?size)?? [cpp]?view plain
?copy ? <span?style="font-size:12px;">int?backtrace(void?**buffer,int?size)</span>?? 該函數用于獲取當前線程的調用堆棧,獲取的信息將會被存放在buffer中,它是一個指針列表。參數?size?用來指定buffer中可以保存多少個void*?元素。函數返回值是實際獲取的指針個數,最大不超過size大小
在buffer中的指針實際是從堆棧中獲取的返回地址,每一個堆棧框架有一個返回地址
注意:某些編譯器的優化選項對獲取正確的調用堆棧有干擾,另外內聯函數沒有堆棧框架;刪除框架指針也會導致無法正確解析堆棧內容
?
[cpp]?view plaincopy
char?**?backtrace_symbols?(void?*const?*buffer,?int?size)?? [cpp]?view plain
?copy ? <span?style="font-size:12px;">char?**?backtrace_symbols?(void?*const?*buffer,?int?size)</span>?? backtrace_symbols將從backtrace函數獲取的信息轉化為一個字符串數組.?參數buffer應該是從backtrace函數獲取的指針數組,size是該數組中的元素個數(backtrace的返回值)???
???
函數返回值是一個指向字符串數組的指針,它的大小同buffer相同.每個字符串包含了一個相對于buffer中對應元素的可打印信息.它包括函數名,函數的偏移地址,和實際的返回地址
現在,只有使用ELF二進制格式的程序才能獲取函數名稱和偏移地址.在其他系統,只有16進制的返回地址能被獲取.另外,你可能需要傳遞相應的符號給鏈接器,以能支持函數名功能(比如,在使用GNU?ld鏈接器的系統中,你需要傳遞(-rdynamic),?-rdynamic可用來通知鏈接器將所有符號添加到動態符號表中,如果你的鏈接器支持-rdynamic的話,建議將其加上!)
該函數的返回值是通過malloc函數申請的空間,因此調用者必須使用free函數來釋放指針.
注意:如果不能為字符串獲取足夠的空間函數的返回值將會為NULL
?
[cpp]?view plaincopy
void?backtrace_symbols_fd?(void?*const?*buffer,?int?size,?int?fd)?? [cpp]?view plain
?copy ? <span?style="font-size:12px;">void?backtrace_symbols_fd?(void?*const?*buffer,?int?size,?int?fd)</span>?? backtrace_symbols_fd與backtrace_symbols?函數具有相同的功能,不同的是它不會給調用者返回字符串數組,而是將結果寫入文件描述符為fd的文件中,每個函數對應一行.它不需要調用malloc函數,因此適用于有可能調用該函數會失敗的情況
?
下面是glibc中的實例(稍有修改):
[cpp]?view plaincopy
#include?<execinfo.h>???#include?<stdio.h>???#include?<stdlib.h>?????void?print_trace?(void)??{??????void?*array[10];?? ????size_t?size;?? ????char?**strings;?? ???size_t?i;?? ??????size?=?backtrace?(array,?10);??????strings?=?backtrace_symbols?(array,?size);??????if?(NULL?==?strings)?? ????{?? ???????perror("backtrace_synbols");?? ????????Exit(EXIT_FAILURE);??????}????????printf?("Obtained?%zd?stack?frames.\n",?size);?? ??????for?(i?=?0;?i?<?size;?i++)?? ????????printf?("%s\n",?strings[i]);?? ??????free?(strings);?? ???strings?=?NULL;??}????void?dummy_function?(void)??{??????print_trace?();??}????int?main?(int?argc,?char?*argv[])??{??????dummy_function?();??????return?0;?? }?? [cpp]?view plain
?copy ? <span?style="font-size:12px;">#include?<execinfo.h>?? #include?<stdio.h>??#include?<stdlib.h>????void?print_trace?(void)??{??????void?*array[10];?? ????size_t?size;?? ????char?**strings;?? ???size_t?i;?? ??????size?=?backtrace?(array,?10);??????strings?=?backtrace_symbols?(array,?size);??????if?(NULL?==?strings)?? ????{?? ???????perror("backtrace_synbols");?? ????????Exit(EXIT_FAILURE);??????}????????printf?("Obtained?%zd?stack?frames.\n",?size);?? ??????for?(i?=?0;?i?<?size;?i++)?? ????????printf?("%s\n",?strings[i]);?? ??????free?(strings);?? ???strings?=?NULL;??}????void?dummy_function?(void)??{??????print_trace?();??}????int?main?(int?argc,?char?*argv[])??{??????dummy_function?();??????return?0;?? }</span>?? 輸出如下:
[cpp]?view plaincopy
Obtained?4?stack?frames.??./execinfo()?[0x80484dd]??./execinfo()?[0x8048549]??./execinfo()?[0x8048556]??/lib/i386-linux-gnu/libc.so.6(__libc_start_main+0xf3)?[0x70a113]?? [cpp]?view plain
?copy ? <span?style="font-size:12px;">Obtained?4?stack?frames.?? ./execinfo()?[0x80484dd]??./execinfo()?[0x8048549]??./execinfo()?[0x8048556]??/lib/i386-linux-gnu/libc.so.6(__libc_start_main+0xf3)?[0x70a113]??</span>??
?
我們還可以利用這backtrace來定位段錯誤位置。
通常情況系,程序發生段錯誤時系統會發送SIGSEGV信號給程序,缺省處理是退出函數。我們可以使用?signal(SIGSEGV,?&your_function);函數來接管SIGSEGV信號的處理,程序在發生段錯誤后,自動調用我們準備好的函數,從而在那個函數里來獲取當前函數調用棧。
舉例如下:
[cpp]?view plaincopy
#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;?? }?? [cpp]?view plain
?copy ? <span?style="font-size:12px;">#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;?? }</span>??
?
編譯程序:
gcc?-g?-rdynamic?test.c?-o?test;?./test
輸出如下:
[cpp]?view plaincopy
Obtained6stackframes.nm??./backstrace_debug(dump+0x45)[0x80487c9]??[0x468400]??./backstrace_debug(func_b+0x8)[0x804888c]??./backstrace_debug(func_a+0x8)[0x8048896]??./backstrace_debug(main+0x33)[0x80488cb]??/lib/i386-linux-gnu/libc.so.6(__libc_start_main+0xf3)[0x129113]?? [cpp]?view plain
?copy ? <span?style="font-size:12px;">Obtained6stackframes.nm?? ./backstrace_debug(dump+0x45)[0x80487c9]??[0x468400]??./backstrace_debug(func_b+0x8)[0x804888c]??./backstrace_debug(func_a+0x8)[0x8048896]??./backstrace_debug(main+0x33)[0x80488cb]??/lib/i386-linux-gnu/libc.so.6(__libc_start_main+0xf3)[0x129113]</span>?? ?(這里有個疑問: 多次運行的結果是/lib/i368-linux-gnu/libc.so.6和[0x468400]的返回地址是變化的,但不變的是后三位, 不知道為什么)
接著:
objdump?-d?test?>?test.s
在test.s中搜索804888c如下:
?
[cpp]?view plaincopy
8048884?<func_b>:??8048884:????55??????????????push?%ebp??8048885:????89?e5????????????mov?%esp,?%ebp??8048887:????e8?eb?ff?ff?ff???????call?8048877?<func_c>??804888c:????5d????????????????pop?%ebp??804888d:????c3????????????????ret?? [cpp]?view plain
?copy ? <span?style="font-size:12px;">8048884?<func_b>:?? 8048884:????55??????????????push?%ebp??8048885:????89?e5????????????mov?%esp,?%ebp??8048887:????e8?eb?ff?ff?ff???????call?8048877?<func_c>??804888c:????5d????????????????pop?%ebp??804888d:????c3????????????????ret</span>?? 其中80488c時調用(call?8048877)C函數后的地址,雖然并沒有直接定位到C函數,通過匯編代碼,?基本可以推出是C函數出問題了(pop指令不會導致段錯誤的)。
我們也可以通過addr2line來查看
[cpp]?view plaincopy
addr2line?0x804888c?-e?backstrace_debug?-f?? [cpp]?view plain
?copy ? <span?style="font-size:12px;">addr2line?0x804888c?-e?backstrace_debug?-f</span>?? 輸出:
[cpp]?view plaincopy
func_b??/home/astrol/c/backstrace_debug.c:57?? [cpp]?view plain
?copy ? <span?style="font-size:12px;">func_b?? /home/astrol/c/backstrace_debug.c:57??</span>??
?
以下是簡單的backtrace原理實現:
?
[cpp]?view plaincopy
#include?<stdio.h>???#include?<stdlib.h>???#include?<string.h>?????#define?LEN?4???#define?FILENAME?"stack"?????int?backtrace(void?**buffer,?int?size)??{??????int?i?=?0;?? ????unsigned?long?int?reg_eip?=?0;?? ????unsigned?long?int?reg_ebp?=?0;?? ????char?cmd[size][64];?? ??????memset(cmd,?0,?size?*?64);??????__asm__?volatile?(?? ???????? ????????"movl?%%ebp,?%0?\n\t"?? ????????:"=r"(reg_ebp)?? ????????:??????? ????????:"memory"??? ????);??????????for?(i?=?0;?i?<?size;?i++)?? ????{??????????reg_eip?=?*(unsigned?long?int?*)(reg_ebp?+?4);?? ????????reg_ebp?=?*(unsigned?long?int?*)(reg_ebp);?? ????????buffer[i]?=?(void?*)reg_eip;?? ????????fprintf(stderr,?"%p?->?",?buffer[i]);?? ????????sprintf(cmd[i],?"addr2line?%p?-e?",?buffer[i]);?? ????????strncat(cmd[i],?FILENAME"?-f",?strlen(FILENAME)+3);?? ????????system(cmd[i]);??????????puts("");????????? ????}????????return?size;?? }????static?void?test2(void)??{??????int?i?=?0;?? ????void?*buffer[LEN]?=?{0};?? ????backtrace(buffer,?LEN);??????return;?? }????static?void?test1(void)??{??????test2();??}????static?void?test(void)??{??????test1();??}????int?main(int?argc,?const?char?*argv[])??{??????test();??????return?0;?? }??
與50位技術專家面對面20年技術見證,附贈技術全景圖
總結
以上是生活随笔為你收集整理的嵌入式 linux下利用backtrace追踪函数调用堆栈以及定位段错误的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。