程序分析是以某種語(yǔ)言書(shū)寫(xiě)的程序?yàn)閷?duì)象,對(duì)其內(nèi)部的運(yùn)作流程進(jìn)行分析。程序分析的目的主要有三點(diǎn):一是通過(guò)程序內(nèi)部各個(gè)模塊之間的調(diào)用關(guān)系,整體上把握程序的運(yùn)行流程,從而更好地理解程序,從中汲取有價(jià)值的內(nèi)容。二是以系統(tǒng)優(yōu)化為目的,通過(guò)對(duì)程序中關(guān)鍵函數(shù)的跟蹤或者運(yùn)行時(shí)信息的統(tǒng)計(jì),找到系統(tǒng)性能的瓶頸,從而采取進(jìn)一步行動(dòng)對(duì)程序進(jìn)行優(yōu)化。最后一點(diǎn),程序分析也有可能用于系統(tǒng)測(cè)試和程序調(diào)試中。當(dāng)系統(tǒng)跟蹤起來(lái)比較復(fù)雜,而某個(gè)BUG又比較難找時(shí),可以通過(guò)一些特殊的數(shù)據(jù)構(gòu)造一個(gè)測(cè)試用例,然后將分析到的函數(shù)調(diào)用關(guān)系和運(yùn)行時(shí)實(shí)際的函數(shù)調(diào)用關(guān)系進(jìn)行對(duì)比,從而找出錯(cuò)誤代碼的位置。
程序分析工具不同于調(diào)試器,它只產(chǎn)生程序運(yùn)行時(shí)某些函數(shù)的調(diào)用次數(shù)、執(zhí)行時(shí)間等等宏觀信息,而不是每條語(yǔ)句執(zhí)行時(shí)的詳細(xì)信息。Gprof是Linux下一個(gè)強(qiáng)有力的程序分析工具。對(duì)于C、Pascal或者Fortran77語(yǔ)言的程序,它能夠以“日志”的形式記錄程序運(yùn)行時(shí)的統(tǒng)計(jì)信息:程序運(yùn)行中各個(gè)函數(shù)消耗的時(shí)間和函數(shù)調(diào)用關(guān)系,以及每個(gè)函數(shù)被調(diào)用的次數(shù)等等。從而可以幫助程序員找出眾多函數(shù)中耗時(shí)最多的函數(shù),也可以幫助程序員分析程序的運(yùn)行流程。相信這些功能對(duì)于分析開(kāi)源代碼的程序員來(lái)說(shuō),有著相當(dāng)大的誘惑力。
用gprof分析程序
用gprof對(duì)程序進(jìn)行分析主要分以下三個(gè)步驟:
l 用編譯器對(duì)程序進(jìn)行編譯,加上-pg參數(shù)。
l 運(yùn)行編譯后的程序。
l 用gprof命令查看程序的運(yùn)行時(shí)信息。
先以一個(gè)簡(jiǎn)單的例子演示一下吧。隨便找一個(gè)能夠運(yùn)行的程序的源代碼,比如下面的文件test.c:
1
2 int IsEven(int x)
3
4 {
5
6 return 0 == x & 1;
7
8 }
9
10 int main(int argc, char *argv[]
11
12 {
13
14 int i = 0;
15
16 while(++i < 1000) IsEven(i);
17
18 }
首先,用以下命令進(jìn)行編譯:
[root@localhost]#gcc –o test –pg test.c
然后,運(yùn)行可執(zhí)行文件test.
[root@localhost]#./test
運(yùn)行后,在當(dāng)前目錄下將生成一個(gè)文件gmon.out,這就是gprof生成的文件,保存有程序運(yùn)行期間函數(shù)調(diào)用等信息。
最后,用gprof命令查看gmon.out保存的信息:
[root@localhost]#gprof test gmon.out –b
這樣就有一大堆信息輸出到屏幕上,有函數(shù)執(zhí)行單間,函數(shù)調(diào)用關(guān)系圖等等,如下:
Flat profile:
Each sample counts as 0.01 seconds.
no time accumulated
% cumulative self self total
time seconds seconds calls Ts/call Ts/call name
0.00 0.00 0.00 1000 0.00 0.00 IsEven(int)
Call graph
granularity: each sample hit covers 2 byte(s) no time propagated
index % time self children called name
0.00 0.00 1000/1000 main [7]
[8] 0.0 0.00 0.00 1000 IsEven(int) [8]
-----------------------------------------------
Index by function name
[8] IsEven(int)
以上介紹了gprof最簡(jiǎn)單的使用方法,下面針對(duì)其使用過(guò)程中的三個(gè)步驟詳細(xì)說(shuō)明。
編譯和鏈接
上面的例子中,程序比較簡(jiǎn)單,只有一個(gè)文件。如果源代碼有多個(gè)文件,或者代碼結(jié)構(gòu)比較復(fù)雜,編譯過(guò)程中先生成若干個(gè)目標(biāo)文件,然后又由鏈接器將這些目標(biāo)文件鏈接到一起,這時(shí)該怎么使用gprof呢?
對(duì)于由多個(gè)源文件組成的程序,編譯時(shí)需要在生成每個(gè).o文件的時(shí)候加上-pg參數(shù),同時(shí)在鏈接的時(shí)候也要加上-pg參數(shù)。對(duì)于鏈接器不是GCC的情況,如ld,又有特殊的要求。
同時(shí),-pg參數(shù)只能記錄源代碼中各個(gè)函數(shù)的調(diào)用關(guān)系,而不能記錄庫(kù)函數(shù)的調(diào)用情況。要想記錄每個(gè)庫(kù)函數(shù)的調(diào)用情況,鏈接的時(shí)候必須指定庫(kù)函數(shù)的動(dòng)態(tài)(或者靜態(tài))鏈接庫(kù)libc_p.a,即加上-lc_p,而不是-lc。
還要說(shuō)明的是,如果有一部分代碼在編譯時(shí)指定了-pg參數(shù),而另一部分代碼沒(méi)有指定,則生成的gmon.out文件中將缺少一部分函數(shù),也沒(méi)有那些函數(shù)的調(diào)用關(guān)系。但是并不影響gprof對(duì)其它函數(shù)進(jìn)行記錄。
運(yùn)行
編譯好的程序運(yùn)行時(shí)和運(yùn)行一般的程序沒(méi)有什么不同,只是比正常的程序多生成了一個(gè)文件gmon.out。注意,這個(gè)文件名是固定的,沒(méi)法通過(guò)參數(shù)的設(shè)置進(jìn)行改變。如果程序目錄中已經(jīng)有一個(gè)gmon.out,則它會(huì)被新的gmon.out覆蓋掉。
關(guān)于生成的gmon.out文件所在的目錄,也有以下約定:程序退出時(shí)所運(yùn)行的文件所在目錄就是生成的gmon.out文件所在的目錄。如果一個(gè)程序執(zhí)行過(guò)程中調(diào)用了另一個(gè)程序,并在另一個(gè)程序的運(yùn)行中終止,則gmon.out會(huì)在另一個(gè)程序所在的目錄中生成。
還有一點(diǎn)要注意的就是當(dāng)程序非正常終止時(shí)不會(huì)生成gmon.out文件,也因此就沒(méi)法查看程序運(yùn)行時(shí)的信息。只有當(dāng)程序從main函數(shù)中正常退出,或者通過(guò)系統(tǒng)調(diào)用exit()函數(shù)而退出時(shí),才會(huì)生成gmon.out文件。而通過(guò)底層調(diào)用如_exit()等退出時(shí)不會(huì)生成gmon.out。
查看
查看程序運(yùn)行信息的命令是gprof,它以gmon.out文件作為輸入,也就是將gmon.out文件翻譯成可讀的形式展現(xiàn)給用戶。其命令格式如下:
gprof [可執(zhí)行文件] [gmon.out文件] [其它參數(shù)]
方括號(hào)中的內(nèi)容可以省略。如果省略了“可執(zhí)行文件”,gprof會(huì)在當(dāng)前目錄下搜索a.out文件作為可執(zhí)行文件,而如果省略了gmon.out文件,gprof也會(huì)在當(dāng)前目錄下尋找gmon.out。其它參數(shù)可以控制gprof輸出內(nèi)容的格式等信息。最常用的參數(shù)如下:
l -b 不再輸出統(tǒng)計(jì)圖表中每個(gè)字段的詳細(xì)描述。
l -p 只輸出函數(shù)的調(diào)用圖(Call graph的那部分信息)。
l -q 只輸出函數(shù)的時(shí)間消耗列表。
l -e Name 不再輸出函數(shù)Name 及其子函數(shù)的調(diào)用圖(除非它們有未被限制的其它父函數(shù))。可以給定多個(gè) -e 標(biāo)志。一個(gè) -e 標(biāo)志只能指定一個(gè)函數(shù)。
l -E Name 不再輸出函數(shù)Name 及其子函數(shù)的調(diào)用圖,此標(biāo)志類似于 -e 標(biāo)志,但它在總時(shí)間和百分比時(shí)間的計(jì)算中排除了由函數(shù)Name 及其子函數(shù)所用的時(shí)間。
l -f Name 輸出函數(shù)Name 及其子函數(shù)的調(diào)用圖。可以指定多個(gè) -f 標(biāo)志。一個(gè) -f 標(biāo)志只能指定一個(gè)函數(shù)。
l -F Name 輸出函數(shù)Name 及其子函數(shù)的調(diào)用圖,它類似于 -f 標(biāo)志,但它在總時(shí)間和百分比時(shí)間計(jì)算中僅使用所打印的例程的時(shí)間。可以指定多個(gè) -F 標(biāo)志。一個(gè) -F 標(biāo)志只能指定一個(gè)函數(shù)。-F 標(biāo)志覆蓋 -E 標(biāo)志。
l -z 顯示使用次數(shù)為零的例程(按照調(diào)用計(jì)數(shù)和累積時(shí)間計(jì)算)。