天天看點

在unix系統中實作堆棧跟蹤

 在程式運作的過程中,如果出現異常,通常會發出一個信号進入信号處理函數中處理。有些故障過于嚴重到無法實作程式的自恢複。這個時候,程式隻能無奈的輸出一些錯誤資訊。當然這些錯誤資訊對程式的調試也是非常有幫助的,我們在Java中如果出現異常的話,一般都會列印出堆棧跟蹤的資訊。

當然,除了列印堆棧資訊外,也能在程式的某些點設定一些調試資訊友善輸出程式出錯的行号,函數名和檔案名。但是這種方式的功能畢竟是有限的,很多異常出現的位置可能并沒有設定這樣的調試語句。這樣,還是堆棧資訊比較重要。因為這是運作時輸出的資訊,而輸出行号,檔案名,函數名的方式隻能在編譯時确定。

堆棧跟蹤主要和三個函數相關,分别為backtrace, backtrace_symbols,以及backtrace_symbols_fd.關于這三個函數的資訊如下:

#include <execinfo.h>

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);

我們知道函數調用的過程是嵌套的,在存儲器中一般将每個函數對應為一個堆棧幀,每個堆棧幀儲存相應函數的相關資訊,如:參數資訊,傳回位址資訊,函數正文段,動态連結清單,詞法連結清單等。函數調用的過程就是堆棧幀動态變化的過程,每調用一個函數,堆棧中就為該函數建立一塊堆棧幀,堆棧幀的具體實作對應為一個堆棧幀資料結構,資料結構中儲存前面提到的資訊。

backtrace函數傳回backtrace函數被調用時的堆棧資訊。這些資訊存放在緩沖區buffer中。backtrace函數有兩個參數:

buffer:用于存放堆棧資訊的緩沖區

size:用于訓示buffer的大小。

要充分考慮到buffer的大小,因為如果函數調用的層次過深可能導緻buffer空間不夠,這樣就隻能儲存一部分堆棧資訊,靠近main函數這端的資訊會因為空間不夠而被裁掉。buffer是一個指向二維數組的指針,類型為void. buffer中所儲存的是一系列堆棧幀的傳回位址。traceback函數将傳回堆棧中跟蹤到的函數的個數。

将trace函數傳回的buffer和傳回的函數個數分别作為backtrace_symbols就可以解析出這些跟蹤到的函數的符号名稱,确切的說,backtrace_symbols函數将針對buffer中每一個函數傳回位址進行解析,解析後的格式為:./程式名(<函數名+>函數的在程式中的十六進制格式偏移位址) [函數實際的傳回十六進制格式的位址],解析後的結果儲存為二維字元數組,傳回值為二維數組的起始位址。

設傳回的二維數組為strings,由于在backtrace_symbols内部調用了malloc開辟空間,是以需要使用者來釋放空間。不過使用者隻需要釋放strings所指向的字元串指針數組即可,對于每個字元串不用釋放,也不應該釋放,内部會自動維護。

函數traceback_symbols_fd将輸出堆棧資訊到fd所說明的檔案中。這樣有一個好處就是不用調用malloc函數了,避免了記憶體配置設定失敗的可能性。

然而,需要注意的是,有些函數是不能通過traceback_symbols函數解析出來的,這些情況為:

l  内聯函數(沒有對應的堆棧幀)

l  優化級别較高的編譯選項會導緻某些函數的堆棧指針不會被儲存

l  尾遞歸優化可能導緻多個函數(遞歸)隻使用一個堆棧幀

l  靜态函數的名字不能解析到

另外在連結的時候需要添加一些特殊的選項,對于GCC這個選項是 –rdynamic。下面是一個簡單的程式,展示怎樣使用backtrace系列函數。

  1. /* 
  2. *Author:Chaos Lee 
  3. *Date:2012-02-26  21:35 
  4. */ 
  5. #include<stdio.h> 
  6. #include<execinfo.h> 
  7. #include<stdlib.h> 
  8. #define MAX_LEN 256 
  9. void show_stack_info() 
  10.          void *buffer[MAX_LEN]; 
  11.          int returned_size; 
  12.          char **strings; 
  13.          int i=0; 
  14.          returned_size=backtrace(buffer,MAX_LEN); 
  15.          printf("%d addresses are returned.\n",returned_size); 
  16.          strings=backtrace_symbols(buffer,returned_size); 
  17.          if(strings==NULL) 
  18.                    exit(1); 
  19.          for(i=0;i<returned_size;i++) 
  20.          { 
  21.                    printf("%s\n",strings[i]); 
  22.          } 
  23. void func4(int a) 
  24.          if(a>0) 
  25.                    func4(--a); 
  26.          else 
  27.                    show_stack_info(); 
  28. static void func3(int a) 
  29.          func4(--a); 
  30. void func2(int a) 
  31.          func3(--a); 
  32. void func1(int a) 
  33.          func2(--a); 
  34. int main() 
  35.          func1(10); 
  36.          return 0; 

編譯指令為:

  1. gcc traceback.c -o traceback –rdynamic 

然後運作之:./traceback

輸出結果如下:

  1. 15 addresses are returned. 
  2. ./traceback(show_stack_info+0x23) [0x40085b] 
  3. ./traceback(func4+0x29) [0x4008e9] 
  4. ./traceback(func4+0x1d) [0x4008dd] 
  5. ./traceback(func4+0x1d) [0x4008dd] 
  6. ./traceback(func4+0x1d) [0x4008dd] 
  7. ./traceback(func4+0x1d) [0x4008dd] 
  8. ./traceback(func4+0x1d) [0x4008dd] 
  9. ./traceback(func4+0x1d) [0x4008dd] 
  10. ./traceback(func4+0x1d) [0x4008dd] 
  11. ./traceback [0x400902] 
  12. ./traceback(func2+0x17) [0x40091b] 
  13. ./traceback(func1+0x17) [0x400934] 
  14. ./traceback(main+0xe) [0x400944] 
  15. /lib64/libc.so.6(__libc_start_main+0xf4) [0x38ba61d8a4] 
  16. ./traceback [0x4007a9]