日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

嵌入式 linux下利用backtrace追踪函数调用堆栈以及定位段错误

發布時間:2025/3/20 linux 20 豆豆
生活随笔 收集整理的這篇文章主要介紹了 嵌入式 linux下利用backtrace追踪函数调用堆栈以及定位段错误 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

?

?

嵌入式 linux下利用backtrace追蹤函數調用堆棧以及定位段錯誤

?分類: ? 嵌入式(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>???
  • ??
  • /*?Obtain?a?backtrace?and?print?it?to?@code{stdout}.?*/??
  • 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;??
  • }??
  • ??
  • /*?A?dummy?function?to?make?the?backtrace?more?interesting.?*/??
  • 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>??
  • ??
  • /*?Obtain?a?backtrace?and?print?it?to?@code{stdout}.?*/??
  • 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;??
  • }??
  • ??
  • /*?A?dummy?function?to?make?the?backtrace?more?interesting.?*/??
  • 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?(??
  • ????????/*?get?current?EBP?*/??
  • ????????"movl?%%ebp,?%0?\n\t"??
  • ????????:"=r"(reg_ebp)??/*?output?register?*/??
  • ????????:???????/*?input??register?*/??
  • ????????:"memory"???/*?cloberred?register?*/??
  • ????);????
  • ??
  • ????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追踪函数调用堆栈以及定位段错误的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。