Linux調用棧擷取分析及實現

來源:互聯網
上載者:User

   寫一下關於函數調用棧的一些相關知識,對於在Linux下面進行c/c++開發,在問題定位時 查看調用棧資訊是一個非常常用的定位方法,因為根據調用關係,可以知道程式的執行流程是什麼樣子。如果 不能查看調用棧,光知道程式在某個函數出錯,還是比較難定位,假如這個函數在很多地方被調用,就很難知道是由於什麼情境導致錯誤發生的。所以通過查看調用棧,就可以知道調用關係,當然就知道是什麼情境導致問題發生。

   在gdb裡面常用的命令式:bt 或全稱“backtrace”就可以列印出當前函數執行的調用棧。如下面程式
   (gdb) bt
#0  0x080486da in func_3 ()
#1  0x08048766 in func_int ()
#2  0x080487ae in func_str ()
#3  0x080487ff in main ()
前面數字式層次關係,#0表示最上面,即當前函數。除了第0層前面的地址表示是當前pc值,其他地址資訊都表示函數調用的返回地址,例如上面:func_int() -->func_3() ,func_3執行完成後,接著會執行0x08048766地址的指令。

上面簡單介紹了一下Linux下面通過調用棧來定位問題,但調用棧的擷取原理,以及如何擷取,估計還是有些人會不知道的。之所以要介紹這個,因為對於一些大型系統,完善的日誌功能是必不可少的,否則系統出了問題,沒有相關日誌,是非常痛苦的。尤其是在某些環境下,如電信領域,大多數是伺服器或應用程式都是跑在單板上,出現問題了,不會像我們調試小程式那樣直接用gdb進行調試。雖然某些情況下可以使用gdb attach上出問題的進程,但大多數伺服器單板沒有相關調試工具。所以要定位問題,基本上都是通過分析日誌。還有一種情況,就是那種隨機性問題,如果沒有日誌,那就更加痛苦了,就算你能夠使用gdb也無能為力。所以log重要,但是log中通常需要記錄哪些資訊呢?通常情況會保護函數調用出錯時,把傳入該函數的參數資訊,或者一些關鍵全域變數資訊,有些時候會記錄日期,對於伺服器程式,日期一般都會記錄。另外還有一個也相對重要的就是調用棧資訊。

所以下面來介紹一下擷取調用棧的原理和方法:
在Linux+x86環境,c語言函數調用時,下面介紹一下c函數是怎麼壓棧的:棧是從高地址向下低地址移動。通常一個函數中會有參數,局部變數等相關資訊,這些資訊是通過下面原則分配棧的:
1、棧的資訊排布為:先是局部變數存放,調用函數傳回值存放,然後是調用其它函數參數函數,

如下面程式:  int B(int c, int d) {return c+d; }  int A(int a, int b) {int c = 0xff, d = 0xffff;return B(c, d); }  通過objdump -d 命令可以查看反組譯碼指令 反組譯碼出來後如下: 00000079 <B>:  79:   55                      push   %ebp  7a:   89 e5                   mov    %esp,%ebp  7c:   8b 45 0c                mov    0xc(%ebp),%eax  7f:   03 45 08                add    0x8(%ebp),%eax  82:   5d                      pop    %ebp  83:   c3                      ret00000084 <A>:  84:   55                      push   %ebp  85:   89 e5                   mov    %esp,%ebp  87:   83 ec 18                sub    $0x18,%esp  8a:   c7 45 fc ff 00 00 00    movl   $0xff,-0x4(%ebp)  91:   c7 45 f8 ff ff 00 00    movl   $0xffff,-0x8(%ebp)  98:   8b 45 f8                mov    -0x8(%ebp),%eax  9b:   89 44 24 04             mov    %eax,0x4(%esp)  9f:   8b 45 fc                mov    -0x4(%ebp),%eax  a2:   89 04 24                mov    %eax,(%esp)  a5:   e8 fc ff ff ff          call   a6 <A+0x22>  aa:   c9                      leave  ab:   c3                      ret  從上面反組譯碼可以看出,在A調用B時,A的調用棧布局資訊如下,高地址:  |---------| |   ebp   |<--|  push   %ebp  -------------A----------------- |---------|   | |   c     |   |  movl   $0xff,-0x4(%ebp)   ;A函數局部變數 c |---------|   | |   d     |   |  movl   $0xffff,-0x8(%ebp) ;A函數局部變數 d |---------|   | |         |   | |---------|   | |         |   | |---------|   |  c+%ebp|   d     |   |  mov    %eax,0x4(%esp)    ;A調用B函數時,準備好參數d |---------|   |  8+%ebp|   c     |   |  mov    %eax,(%esp)       ;A調用B函數時,準備好參數c |---------|   |<----%esp      -------------A----------------  4+%ebp| retaddr |   | A 調用B的返回地址,在執行call指令時,指令自動把call指令下一條壓入這個地方。 |---------|   |  %ebp->|  ebp    |---  對應於執行B函數 :push %ebp時,把在A函數運行時的ebp儲存到該位置中。 |---------| 低地址:

後面B在執行mov    0xc(%ebp),%eax時,簡單用語言描述一下函數調用過程,就那上A調用B來說,首先A函數準備好參數,即把局部變數c,d放到棧上,然後執行call B(call   a6 <A+0x22>)指令,call指令執行時預設會把當前指令的下一條指令壓入棧中,然後執行B函數第一條指令即(push %ebp),所以當執行到B函數push %ebp時,棧的資訊就是上面那種樣子了。  知道一般程式是怎麼壓棧的,並且A函數調用B函數會把A函數中調用B函數的那條call指令的下一條指令壓棧棧中,通常情況一個函數第一條指令都是push
%ebp, 功能是儲存調用函數棧幀,第2條指令時mov %esp , %ebp,即把esp賦值給ebp,即初始化當前函數棧幀。  在執行過程中,函數調用首先指向call執行,然後執行被調用者第一條指令(push %ebp),c語言函數調用通常都是這樣情況的,而call指令又一個隱藏動作就是把下一指令(返回地址)壓棧。所以在棧裡面排布就是

  --------- | ret_addr| |---------|  |   ebp   |   |---------|   我們再看一下第二條指令,mov %esp , %ebp , 初始化當前函數棧幀。最終結果如下  --------- | ret_addr|   | |---------|   | |    ebp  |---/    |---------|<--| |   ...   |   | |---------|   | | ret_addr|   | |---------|   | |  ebp    |---/ |---------|<--|  |  ...    |   | |---------|   |  | ret_addr|   | |---------|   | |   ebp   |---/ |---------|---| 

所以我們只要知道當前%epb的值,就可以通過上面那種圖示方法進行調用棧分析了。有人會問為什麼libc有函數實現了,自己就沒有必要了,但libc只提供擷取當前線程的調用棧資訊,有些時候需要擷取其他線程的調用棧資訊,這個時候就需要自己分析實現了,總體思路一樣,只需要擷取到其它線程的%ebp資訊即可,但通常情況在使用者態是不能夠擷取%ebp寄存器的,可以藉助記憶體模組來實現。

下面寫的一個小程式,一種方法使用libc庫裡面backtrace函數實現,還有一種就是自己通過分析調用棧資訊來實現。

#include <stdio.h>#include <string.h>#include <execinfo.h>/* 擷取ebp寄存器值 */void get_ebp(unsigned long *ebp){        __asm__ __volatile__("mov %%ebp, %0 \r\n"                 :"=m"(*ebp)                 ::"memory");}int my_backtrace(void **stack, int size, unsigned long ebp){        int layer = 0;    while(layer < size && ebp != 0 && *(unsigned long*)ebp != 0 && *(unsigned long *)ebp != ebp)    {            stack[layer++] = *(unsigned long *)(ebp+4);            ebp = *(unsigned long*)ebp;    }    return layer;}int func_3(int a, int b, int c){       void *stack_addr[10];       int layer;       int i;       char **ppstack_funcs;   /* 通過調用libc函數實現 */       layer = backtrace(stack_addr, 10);       ppstack_funcs = backtrace_symbols(stack_addr, layer);       for(i = 0; i < layer; i++)             printf("\n%s:%p\n", ppstack_funcs[i], stack_addr[i]);   /* 自己實現 */   unsigned long ebp = 0;       get_ebp(&ebp);       memset(stack_addr, 0, sizeof(stack_addr));       layer = my_backtrace(stack_addr, 10, ebp);       for(i = 0; i < layer; i++)             printf("\nmy: %p\n", stack_addr[i]); free(ppstack_funcs);     return 3;}int func_int(int a, int b, int c, int d){        int aa,bb,cc;        int ret= func_3(aa,bb,cc);        return (a+ b+ c+ d + ret);}int func_str(){        int a = 1, b = 2;        int ret;        ret = func_int(a, a, b, b);        return ret;}int B(int c, int d){        return c+d;}int A(int a, int b){        int c = 0xff, d = 0xffff;        return B(c, d);}int main(int argc, char *argv[]){        int ret = func_str();        return 0;}
程式編譯加上-rdynaminc
否則擷取調用棧只有地址,沒有函數名資訊。
運行結果:
./exe() [0x80484dd]:0x80484dd./exe() [0x80485ea]:0x80485ea./exe() [0x8048632]:0x8048632./exe() [0x8048683]:0x8048683/lib/tls/i686/cmov/libc.so.6(__libc_start_main+0xe6) [0xb7dd5bd6]:0xb7dd5bd6./exe() [0x8048401]:0x8048401my: 0x804858amy: 0x80485eamy: 0x8048632my: 0x8048683my: 0xb7dd5bd6

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.