程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> 在unix系統中實現堆棧跟蹤

在unix系統中實現堆棧跟蹤

編輯:關於C語言

 在程序運行的過程中,如果出現異常,通常會發出一個信號進入信號處理函數中處理。有些故障過於嚴重到無法實現程序的自恢復。這個時候,程序只能無奈的輸出一些錯誤信息。當然這些錯誤信息對程序的調試也是非常有幫助的,我們在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函數解析出來的,這些情況為:

內聯函數沒有對應的堆棧幀)

優化級別較高的編譯選項會導致某些函數的堆棧指針不會被保存

尾遞歸優化可能導致多個函數遞歸)只使用一個堆棧幀

靜態函數的名字不能解析到

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

 

  1. /* 
  2.  
  3. *Author:Chaos Lee 
  4.  
  5. *Date:2012-02-26  21:35 
  6.  
  7. */ 
  8.  
  9. #include<stdio.h> 
  10.  
  11. #include<execinfo.h> 
  12.  
  13. #include<stdlib.h> 
  14.  
  15. #define MAX_LEN 256 
  16.  
  17. void show_stack_info() 
  18.  
  19.  
  20.          void *buffer[MAX_LEN]; 
  21.  
  22.          int returned_size; 
  23.  
  24.          char **strings; 
  25.  
  26.          int i=0; 
  27.  
  28.          returned_size=backtrace(buffer,MAX_LEN); 
  29.  
  30.          printf("%d addresses are returned.\n",returned_size); 
  31.  
  32.          strings=backtrace_symbols(buffer,returned_size); 
  33.  
  34.          if(strings==NULL) 
  35.  
  36.                    exit(1); 
  37.  
  38.          for(i=0;i<returned_size;i++) 
  39.  
  40.          { 
  41.  
  42.                    printf("%s\n",strings[i]); 
  43.  
  44.          } 
  45.  
  46.  
  47. void func4(int a) 
  48.  
  49.  
  50.          if(a>0) 
  51.  
  52.                    func4(--a); 
  53.  
  54.          else 
  55.  
  56.                    show_stack_info(); 
  57.  
  58.  
  59. static void func3(int a) 
  60.  
  61.  
  62.          func4(--a); 
  63.  
  64.  
  65. void func2(int a) 
  66.  
  67.  
  68.          func3(--a); 
  69.  
  70.  
  71. void func1(int a) 
  72.  
  73.  
  74.          func2(--a); 
  75.  
  76.  
  77. int main() 
  78.  
  79.  
  80.          func1(10); 
  81.  
  82.          return 0; 
  83.  

編譯指令為:

  1. gcc traceback.c -o traceback –rdynamic 

然後運行之:./traceback

輸出結果如下:

  1. 15 addresses are returned. 
  2.  
  3. ./traceback(show_stack_info+0x23) [0x40085b] 
  4.  
  5. ./traceback(func4+0x29) [0x4008e9] 
  6.  
  7. ./traceback(func4+0x1d) [0x4008dd] 
  8.  
  9. ./traceback(func4+0x1d) [0x4008dd] 
  10.  
  11. ./traceback(func4+0x1d) [0x4008dd] 
  12.  
  13. ./traceback(func4+0x1d) [0x4008dd] 
  14.  
  15. ./traceback(func4+0x1d) [0x4008dd] 
  16.  
  17. ./traceback(func4+0x1d) [0x4008dd] 
  18.  
  19. ./traceback(func4+0x1d) [0x4008dd] 
  20.  
  21. ./traceback [0x400902] 
  22.  
  23. ./traceback(func2+0x17) [0x40091b] 
  24.  
  25. ./traceback(func1+0x17) [0x400934] 
  26.  
  27. ./traceback(main+0xe) [0x400944] 
  28.  
  29. /lib64/libc.so.6(__libc_start_main+0xf4) [0x38ba61d8a4] 
  30.  
  31. ./traceback [0x4007a9] 

注意,func3聲明為靜態函數,所以不能將其符號名稱解析出來。其實,backtrace函數更多的是用在信號處理函數中,這在下一篇文章再作介紹。

本文出自 “相信並熱愛著” 博客,轉載請與作者聯系!

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved