源碼分析是程序員離不開的話題。無論是研究開源項目,還是平時做各類移植、開發,都避免不了對源碼的深入解讀。
工欲善其事,必先利其器。
之前已經介紹了如何通過 Callgraph 靜態分析源代碼,這裡介紹如何分析程序運行時的實際函數執行情況,考慮到應用部分和內核部分有比較大的差異,該篇先介紹應用部分。
主要介紹三款工具,一款是 gprof
,另外一款是 valgrind
,再一款則是能夠把前兩款的結果導出為 dot 圖形的工具,叫 gprof2dot
,它的功能有點類似於我們上次介紹的 tree2dotx
。
需要事先準備好幾個相關的工具。
gprof2dot: converts the output from many profilers into a dot graph
$ sudo apt-get install python python-pip
$ sudo pip install gprof2dot
graphviz: dot 格式處理
$ sudo apt-get install graphviz
gprof: display call graph profile data
$ sudo apt-get install gprof
valgrind: a suite of tools for debugging and profiling programs
$ sudo apt-get install valgrind
工具好了,再來一個典型的 C 程序,保存為:fib.c
#include <stdio.h>
int fibonacci(int n);
int main(int argc, char **argv)
{
int fib;
int n;
for (n = 0; n <= 42; n++) {
fib = fibonacci(n);
printf("fibonnaci(%d) = %dn", n, fib);
}
return 0;
}
int fibonacci(int n)
{
int fib;
if (n <= 0) {
fib = 0;
} else if (n == 1) {
fib = 1;
} else {
fib = fibonacci(n -1) + fibonacci(n - 2);
}
return fib;
}
Gprof 用於對某次應用的運行時代碼執行情況進行分析。
它需要對源代碼採用 -pg
編譯,然後運行:
$ gcc -pg -o fib fib.c
$ ./fib
運行完以後,會生成一份日誌文件:
$ ls gmon.out
gmon.out
可以分析之:
$ gprof -b ./fib | gprof2dot | dot -Tsvg -o fib-gprof.svg
查看 fib-gprof.svg
如下:
可以觀察到,這個圖表除了調用關係,還有每個函數的執行次數以及百分比。
Valgrind 是開源的性能分析利器。它不僅可以用來檢查內存洩漏等問題,還可以用來生成函數的調用圖。
Valgrind 不依賴 -pg
編譯選項,可以直接編譯運行:
$ gcc -o fib fib.c
$ valgrind --tool=callgrind ./fib
然後會看到一份日誌文件:
$ ls callgrind*
callgrind.out.22737
然後用 gprof2dot
分析:
$ gprof2dot -f callgrind ./callgrind.out.22737 | dot -Tsvg -o fib-callgrind.svg
查看 fib-callgrind.svg
如下:
需要提到的是 Valgrind 提取出了比 gprof 更多的信息,包括 main 函數的父函數。
不過 Valgrind 實際提供了更多的信息,用 -n0 -e0
把執行百分比限制去掉,所有執行過的全部展示出來:
$ gprof2dot -f callgrind -n0 -e0 ./callgrind.out.22737 | dot -Tsvg -o fib-callgrind-all.svg
結果如下:
所有的調用情況都展示出來了。熱點調用分支用紅色標記了出來。因為實際上一個程序運行時背後做了很多其他的事情,比如動態符號鏈接,還有比如 main
實際代碼裡頭也調用到 printf
,雖然佔比很低。
考慮到上述結果太多,不便於分析,如果只想關心某個函數的調用情況,以 main
為例,則可以:
$ gprof2dot -f callgrind -n0 -e0 ./callgrind.out.22737 --root=main | dot -Tsvg -o fib-callgrind-main.svg
需要提到的是,實際上除了 gprof2dot
,kcachegrind
也可以用來展示 Valgrind's callgrind
的數據:
$ sudo apt-get install kcachegrind
$ kcachegrind ./callgrind.out.22737
通過 File --> Export Graph
可以導出調用圖。只不過一個是圖形工具,一個是命令行,而且 kcachegrind
不能一次展示所有分支,不過它可以靈活逐個節點查看。
上文我們展示了從運行時角度來分析源碼的實際執行路徑,目前只是深入到了函數層次。
結果上跟上次的靜態分析稍微有些差異。
main
的父函數,甚至還有庫函數如 printf
的內部調用細節,給我們提供了一種途徑去理解程序背後運行的細節。本文只是介紹到了應用程序部分(實際上是程序運行時的用戶空間),下回我們將分析,當某個應用程序執行時,哪些內核接口(系統調用)被調用到,那些接口的執行情況以及深入到內核空間的函數調用情況。