GCC就像一個巨大的寶藏,只要你願意花時間,總能淘到好東西。
在看一些大中型的軟件的源代碼時,你是不是非常希望有一個工具能夠方便的生成各個函數之間的調用關係圖呢?爲了實現這個目標,你可以通過對源代碼進行靜態掃描得到函數的調用關係,但是你無法通過這種方法獲得更多的信息,(如:對某個函數的調用次數,被調用的函數執行了多長時間等,這些信息對於軟件的優化具有很好的參考價值)除了靜態掃描之外,還存在一些動態的方法,即在程序的運行過程中記錄相關的信息,不過這些動態的方法通常都需要有編譯器的支持,通過編譯器在編譯的過程中插入相應的代碼。下面簡單的介紹一下,GCC的function instrumentation機制。
簡單的來說,gcc function instrumentation就是在每個函數調用之前調用一個名爲__cyg_profile_func_enter的函數,在函數調用介紹的時候調用__cyg_profile_func_exit。這兩個函數在gcc看來只是兩個普通的函數,是可以由用戶自己進行定製的。下面先看個例子:
- #include <stdio.h>
- #define DUMP(func, call) \
- printf("%s: func = %p, called by = %p\n", __FUNCTION__, func, call)
- void __attribute__((__no_instrument_function__))
- __cyg_profile_func_enter(void *this_func, void *call_site)
- {
- DUMP(this_func, call_site);
- }
- void __attribute__((__no_instrument_function__))
- __cyg_profile_func_exit(void *this_func, void *call_site)
- {
- DUMP(this_func, call_site);
- }
- main()
- {
- puts("Hello World!");
- return 0;
- }
編譯與運行:
$ 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“來看下程序反彙編之後的結果。
- 08048488 <main>:
- 8048488: 8d 4c 24 04 lea 0x4(%esp),%ecx
- 804848c: 83 e4 f0 and $0xfffffff0,%esp
- 804848f: ff 71 fc pushl 0xfffffffc(%ecx)
- 8048492: 55 push %ebp
- 8048493: 89 e5 mov %esp,%ebp
- 8048495: 53 push %ebx
- 8048496: 51 push %ecx
- 8048497: 83 ec 10 sub $0x10,%esp
- 804849a: 8b 45 04 mov 0x4(%ebp),%eax
- 804849d: 89 44 24 04 mov %eax,0x4(%esp)
- 80484a1: c7 04 24 88 84 04 08 movl $0x8048488,(%esp)
- 80484a8: e8 87 ff ff ff call 8048434 <__cyg_profile_func_enter>
- 80484ad: c7 04 24 03 86 04 08 movl $0x8048603,(%esp)
- 80484b4: e8 8f fe ff ff call 8048348 <puts@plt>
- 80484b9: bb 00 00 00 00 mov $0x0,%ebx
- 80484be: 8b 45 04 mov 0x4(%ebp),%eax
- 80484c1: 89 44 24 04 mov %eax,0x4(%esp)
- 80484c5: c7 04 24 88 84 04 08 movl $0x8048488,(%esp)
- 80484cc: e8 8d ff ff ff call 804845e <__cyg_profile_func_exit>
- 80484d1: 89 d8 mov %ebx,%eax
- 80484d3: 83 c4 10 add $0x10,%esp
- 80484d6: 59 pop %ecx
- 80484d7: 5b pop %ebx
- 80484d8: 5d pop %ebp
- 80484d9: 8d 61 fc lea 0xfffffffc(%ecx),%esp
- 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