用GCC來跟蹤程序的函數調用關係

GCC就像一個巨大的寶藏,只要你願意花時間,總能淘到好東西。


在看一些大中型的軟件的源代碼時,你是不是非常希望有一個工具能夠方便的生成各個函數之間的調用關係圖呢?爲了實現這個目標,你可以通過對源代碼進行靜態掃描得到函數的調用關係,但是你無法通過這種方法獲得更多的信息,(如:對某個函數的調用次數,被調用的函數執行了多長時間等,這些信息對於軟件的優化具有很好的參考價值)除了靜態掃描之外,還存在一些動態的方法,即在程序的運行過程中記錄相關的信息,不過這些動態的方法通常都需要有編譯器的支持,通過編譯器在編譯的過程中插入相應的代碼。下面簡單的介紹一下,GCC的function instrumentation機制。


簡單的來說,gcc function instrumentation就是在每個函數調用之前調用一個名爲__cyg_profile_func_enter的函數,在函數調用介紹的時候調用__cyg_profile_func_exit。這兩個函數在gcc看來只是兩個普通的函數,是可以由用戶自己進行定製的。下面先看個例子:

  1. #include <stdio.h>
  2. #define DUMP(func, call) \
  3.     printf("%s: func = %p, called by = %p\n", __FUNCTION__, func, call)  
  4. void __attribute__((__no_instrument_function__))  
  5. __cyg_profile_func_enter(void *this_func, void *call_site)  
  6.         DUMP(this_func, call_site);
  7. }  
  8. void __attribute__((__no_instrument_function__))  
  9. __cyg_profile_func_exit(void *this_func, void *call_site)  
  10. {  
  11.         DUMP(this_func, call_site);  
  12. }  
  13. main()  
  14. {  
  15.         puts("Hello World!");  
  16.         return 0;  
  17. }  

編譯與運行:

$ gcc -finstrument-functions hello.c -o hello

$ ./hello

__cyg_profile_func_enter: func = 0x8048468, called by = 0xb7e36ebc

Hello World!

__cyg_profile_func_exit: func = 0x8048468, called by = 0xb7e36ebc


呵呵,是不是很神奇,只需要在軟件中簡單加入__cyg_profile_func_enter  __cyg_profile_func_exit這兩個函數的定義,然後再編譯的時候加上-finstrument-functions選項,就可以在每個函數調用前後調用這兩個函數獲取你所感興趣的信息。爲了對其具體的實現細節有所瞭解,可以通過”objdump -d hello“來看下程序反彙編之後的結果。

  1. 08048488 <main>:  
  2.  8048488:   8d 4c 24 04             lea    0x4(%esp),%ecx  
  3.  804848c:   83 e4 f0                and    $0xfffffff0,%esp  
  4.  804848f:   ff 71 fc                pushl  0xfffffffc(%ecx)  
  5.  8048492:   55                      push   %ebp  
  6.  8048493:   89 e5                   mov    %esp,%ebp  
  7.  8048495:   53                      push   %ebx  
  8.  8048496:   51                      push   %ecx  
  9.  8048497:   83 ec 10                sub    $0x10,%esp  
  10.  804849a:   8b 45 04                mov    0x4(%ebp),%eax  
  11.  804849d:   89 44 24 04             mov    %eax,0x4(%esp)  
  12.  80484a1:   c7 04 24 88 84 04 08    movl   $0x8048488,(%esp)  
  13.  80484a8:   e8 87 ff ff ff          call   8048434 <__cyg_profile_func_enter>  
  14.  80484ad:   c7 04 24 03 86 04 08    movl   $0x8048603,(%esp)  
  15.  80484b4:   e8 8f fe ff ff          call   8048348 <puts@plt>  
  16.  80484b9:   bb 00 00 00 00          mov    $0x0,%ebx  
  17.  80484be:   8b 45 04                mov    0x4(%ebp),%eax  
  18.  80484c1:   89 44 24 04             mov    %eax,0x4(%esp)  
  19.  80484c5:   c7 04 24 88 84 04 08    movl   $0x8048488,(%esp)  
  20.  80484cc:   e8 8d ff ff ff          call   804845e <__cyg_profile_func_exit>  
  21.  80484d1:   89 d8                   mov    %ebx,%eax  
  22.  80484d3:   83 c4 10                add    $0x10,%esp  
  23.  80484d6:   59                      pop    %ecx  
  24.  80484d7:   5b                      pop    %ebx  
  25.  80484d8:   5d                      pop    %ebp  
  26.  80484d9:   8d 61 fc                lea    0xfffffffc(%ecx),%esp  
  27.  80484dc:   c3                      ret  


從上面的彙編代碼可以看出,gcc是在進入函數完成堆棧的初始化之後調用__cyg_profile_func_enter的,在函數返回清理堆棧之前調用__cyg_profile_func_exit的。


經過上面的步驟,我們還只能獲取到函數調用時的信息,還沒有獲取到函數之間的調用關係,但是明白了上述的原理之後,參看參看參考資料[1]就能夠很容易的實現目標了。


參考資料

[1] http://www.ibm.com/developerworks/cn/linux/l-graphvis/

[2] http://blog.linux.org.tw/~jserv/archives/001870.html


原文地址:http://blog.csdn.net/iamljj/article/details/5886492

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章