1.GCOV查看arm-linux程式碼涵蓋範圍
一、 關於gcov工具
gcov伴隨gcc 發布。gcc編譯加入-fprofile-arcs -ftest-coverage 參數產生二進位程式,執行測試案例產生程式碼涵蓋範圍資訊。
1、如何使用gcov
用GCC編譯的時候加上-fprofile-arcs -ftest-coverage選項,連結的時候也加上。
fprofile-arcs參數使gcc建立一個程式的流圖,之後找到適合圖的產生樹。只有不在產生樹中的弧被操縱(instrumented):gcc添加了代碼來清點這些弧執行的次數。當這段弧是一個塊的唯一出口或入口時,操縱工具代碼(instrumentation code)將會添加到塊中,否則建立一個基礎塊來包含操縱工具代碼。
gcov主要使用.gcno和.gcda兩個檔案。
.gcno是由-ftest-coverage產生的,它包含了重建基本塊圖和相應的塊的源碼的行號的資訊。
.gcda是由加了-fprofile-arcs編譯參數的編譯後的檔案運行所產生的,它包含了弧跳變的次數和其他的概要資訊(而gcda只能在程式運行完畢後才能產生的)。
Gcov執行函數覆蓋、語句覆蓋和分支覆蓋。
舉個例子,程式碼由main.c和tmp.c兩個檔案組成,編譯、連結、運行程式
編譯:gcc -fprofile-arcs -ftest-coverage -o myapp main.c tmp.c
運行:./myapp
然後 輸入
命令: gcov main.c,gcov tmp.c
這個時候目前的目錄下有了新的文檔main.c.gcov,和tmp.c.gcov
若想儲存覆蓋率檔案,上述命令修改為:
命令:gcov main.c >>yourfilename,gcov tmp.c >>yourfilename
而這時候的main.c.gcov,和tmp.c.gcov就包含了函數和代碼執行次數的資訊,我們可以查看結果:
-: 65:/***************************************************************************************
-: 66: * name : main
-: 67: * return : 0 OK
-: 68: * other ERROR
-: 69: * history : 2006-06-13
-: 70:****************************************************************************************/
-: 71:int main( int argc, char *argv[] ) /* the entrance for program
*/
function main called 4 returned 100% blocks executed 81%
4: 72:{
4: 73: int loop = 0 ;
4: 74: int ret = OK ;
4: 75: int empty_line = 0 ;
4: 76: int code_line = 0 ;
4: 77: int annotation_line = 0 ;
4: 78: struct stat file_stat ; /* use for file state */
4: 79: char recu_name[256] ;
4: 80: char *pwd = NULL ;
4: 81: char *tmp = NULL ;
-: 82:
4: 83: if( argc = MAX_FILE ){ /* file size larger than max size */
#####: 98: printf( "file [%s] size is over 64K! /ncontinue..../n", argv[loop] ) ;
#####: 99: continue ;
-: 100: }
##### 這就是表示沒跑到的
各個參數使用如下:
gcov [-b] [-c] [-v] [-n] [-l] [-f] [-o directory] sourcefile
-b
Write branch frequencies to the output file, and write branch summary info to the standard output. This option allows you to
see how often each branch in your program was taken.
//b(ranch),分支測試
-c
Write branch frequencies as the number of branches taken, rather than the percentage of branches taken.
-v
Display the gcov version number (on the standard error stream).
//太簡單了吧,我上面用了
-n
Do not create the gcov output file.
-l
Create long file names for included source files. For example, if the header file `x.h' contains code, and was included in the
file `a.c', then running gcov on the file `a.c' will produce an output file called `a.c.x.h.gcov' instead of `x.h.gcov'. This can
be useful if `x.h' is included in multiple source files.
-f
Output summaries for each function in addition to the file level summary.
-o
The directory where the object files live. Gcov will search for `.bb', `.bbg', and `.da' files in this directory.
新版的是這麼說的
-o directory│file
--object-directory directory
--object-file file
Specify either the directory containing the gcov data files, or the
object path name. The .gcno, and .gcda data files are searched for
using this option. If a directory is specified, the data files are
in that directory and named after the source file name, without its
extension. If a file is specified here, the data files are named
after that file, without its extension. If this option is not sup-
plied, it defaults to the current directory.
其他的更有新版的-u,
-u
--unconditional-branches
When branch counts are given, include those of unconditional
branches. Unconditional branches are normally not interesting.
-p
--preserve-paths
Preserve complete path information in the names of generated .gcov
files. Without this option, just the filename component is used.
With this option, all directories are used, with ’/’ characters
translated to ’#’ characters, ’.’ directory components removed and
’..’ components renamed to ’^’. This is useful if sourcefiles are
in several different directories. It also affects the -l option.
二、關於lcov
Lcov則是上的gcov 結果展現的一個前端,可以將覆蓋率資訊轉換成html展現。
1. 如何訪問使用者空間應用程式代碼覆蓋資料的樣本
---------------------------------------------------------------------
前提條件:使用 GCC 以 -fprofile-arcs 和-ftest-coverage 選項編譯器。假設編譯目錄名稱為 "/root/test/code_cover/",然後執行:
以我的一個執行個體/root/test/code_cover/下的fork.c為例看代碼的覆蓋率:
首先進入/root/test/code_cover/目錄
a) 重設計數器
lcov --directory . --zerocounters
b) 收集當前代碼覆蓋狀態到一個檔案(應用程式啟動和停止至少一次後,該命令才能正常工作)
lcov --directory . --capture --output-file app.info
Capturing coverage data from .
Found gcov version: 3.4.6
Scanning . for .gcda files ...
Found 1 data files in .
Processing ./TestQuery.gcda
Finished .info-file creation
c) 擷取 HTML 輸出
genhtml -o results app.info
其中的“-o results”是指示結果存放在什麼位置的。
Reading data file app.info
Found 18 entries.
Found common filename prefix "/home/search/isearch_yb/src"
Writing .css and .png files.
Generating output.
Processing file cpp/core/basis/GlobalDef.h
Processing file cpp/core/search/QueryCache.h
...
Writing directory view page.
Overall coverage rate: 117 of 514 lines (22.8%)
使用 網頁瀏覽器開啟 index.html 檔案查看代碼覆蓋結果。
三、Linux核心覆蓋率測試:
將最終的 gcov 核心模組檔案複製到 system wide modules 目錄或者 PERL 指令碼所在目錄。以 root 身份, 執行:
Gcov:在對Linux核心程式進行程式碼涵蓋範圍測試時,同樣可以採用gcov,但是需要對kernel打一個補丁。Gcov的核心補丁:http://ltp.sourceforge.net/coverage/gcov.php。下載gcov核心補丁(wget http://downloads.sourceforge.net/ltp/gcov-kernel-2.tar.gz),解壓補丁,然後為一個kernel打補丁(patch
–p1 < /home/linux-v3.4-mem/gcov-kernel-2/linux-2.6.18-gcov.patch)注意打補丁時應該處於核心的主目錄下也即/home/linux-v3.4-mem,打完補丁之後,通過make menuconfig配置gcov,配置頁面顯示如下:
配置完畢之後,重新編譯核心,將編譯成功的gcov這個核心模組/home/linux-v3.4-mem /kernel/gcov/gcov-proc.ko拷貝到網路檔案系統下面,arm linux系統啟動後載入這個模組
(1)/insmod gcov-proc.ko
然後再跑其他測試程式,跑了一段時間,你會發現在/proc/gcov目錄下有各種gcov的統計檔案:
/proc/gcov # ls
arch crypto init kernel security vmlinux
block drivers ipc net sound
把這整個目錄拷貝到fedora虛擬機器下的一個目錄,我是拷貝到/nfs/kernel_test/gcov目錄下,然後
(2)收集當前代碼覆蓋狀態到一個檔案
lcov --directory . --output-file kernel.info
(3) 擷取 HTML 輸出
genhtml kernel.info
使用 網頁瀏覽器開啟 index.html 檔案查看代碼覆蓋結果。
上面是怎樣擷取核心運行程式碼涵蓋範圍的一般方法及流程。
但如果我們只想擷取一個程式運行時的核心程式碼涵蓋範圍,改怎麼辦呢???
這裡先說幾個gcov-proc模組的特性:
(1)模組屬性:
我們發現/sys/module/gcov_proc下有parameters檔案夾,進去我們發現有兩個屬性檔案:
/sys/module/gcov_proc/parameters # ls
gcov_link gcov_persist
這兩個屬性是控制什麼的呢???看看官方表述:
- gcov_link=0/1 (default is 1): When set to non-zero, symbolic links to
source and gcov graph files are created in /proc/gcov along with the data
files.
- gcov_persist=0/1 (default is 0): When set to non-zero, gcov data for
kernel modules is kept even after those modules are unloaded so that
coverage measurements can be extended to module cleanup code. To clear
this persistent data, write to /proc/vmlinux.
(2)重啟核心覆蓋率採集的資料
To reset coverage data for a specific file, simply write to the associated data
file in the /proc/gcov hierarchy:
echo 0 > /proc/gcov/kernel/signal.da
To reset all coverage data, write to the extra file '/proc/gcov/vmlinux':
echo 0 > /proc/gcov/vmlinux
四、擷取程式運行時段的核心覆蓋率
我的方法是在運行應用程式(這裡面是以我的/nfs/memtest/timetest下的timetest應用程式為例)的開始先用“echo 0 > /proc/gcov/vmlinux”指令將我們的/proc/gcov下的統計資訊全部清空讓它重新計數的,下面說下我們的方法和步驟:
1、 先獲得一個含義gcov資訊的核心和gcov-proc.ko,這個上面已經說過了;
2、 啟動linux核心,然後安裝gcov-proc.ko
/memtest # insmod gcov-proc.ko
gcov-proc: initializing proc module: persist=0 link=1 format=gcc 3.4
gcov-proc: init done
/memtest # cd timetest/
/memtest/timetest # ls
20100106.log fp2 timetest timetest.c timetest.gcno
20100111.log results timetest-lcov timetest.gcda
3、 這時先用“echo 0 > /proc/gcov/vmlinux”指令清空gcov,然後運行timetest,然後將
/proc/gcov拷貝到/tmp下面,這是防止直接拷到虛擬機器下會產生大量的網路函數調用,增加統計誤差。
/memtest/timetest # echo 0 > /proc/gcov/vmlinux && ./timetest && cp -r /proc/gcov/ /tmp
game over count1 is 0xc9413e40,count2 is 0x0,count3 is 0x3c8b1e45,count4 is 0x0
/memtest/timetest # ls /tmp
gcov
4、 現在統計的資料是放在/tp/gcov下面的,我們需要將之拷貝到上位機的虛擬機器中才能分析,因為我們的lcov只能在虛擬機器中才能啟動並執行。
/memtest/timetest # cp -r /tmp/gcov/ ./
/memtest/timetest #
5、 現在需要轉到虛擬機器下接著運行lcov來產生最後的html頁面了。
[root@localhost timetest]# cd gcov/
[root@localhost gcov]# lcov --directory . --capture --output-file timetest.info
[root@localhost gcov]# genhtml -o results timetest.info
這樣就產生了最後基本上只運行在timetest時間段的核心程式碼涵蓋範圍了。
2.gcov使用執行個體
1. 使用說明
使用GCOV進行程式碼涵蓋範圍統計,需要注意:
1) 在編譯時間不要加最佳化選項,因為加編譯選項後,代碼會發生變化,這樣就找不到哪些是自己寫的熱點代碼。
2) 如果代碼中使用複雜的宏,比如說這個宏展開後是迴圈或者其他控制結構, gcov只在宏調用出現的那一行報告 ,如果複雜的宏看起來像函數,可以用內嵌函式來代替。
3) 代碼在編寫時要注意,每一行最好只有一條語句。
4) 可以用gcov,lcov測試linux核心覆蓋率,參考
http://ltp.sourceforge.net/coverage/gcov.php
這裡只討論應用程式的覆蓋率。
2. 使用執行個體
使用主要有三個步驟:
1) 編譯階段:加入編譯選項gcc –o hello –fprofile-arcs –ftest-coverage hello,產生記錄程式流程圖等資訊
2) 資料收集與提取階段:./hello.,產生具體的運行資訊
這一階段產生的資料資訊自動儲存到。o檔案所在的目錄,也就是說如果存在/usr/build/hello.o,則產生/usr/build/hello.gcda,但是如果前邊目錄不存在就會出錯。
3) 產生資料報告: gcov hello.c
以下給出gcov針對c++項目nmap的應用過程
Nmap是一個強大的連接埠掃描程式,同時Nmap也是著名安全工具Nessus所依賴工具。程式碼數在3萬行以上。
執行:
- CXXFLAGS=”-fprofile-arcs -ftest-coverage” LIBS=-lgcov ./configure èmakefile
- Make è 每個源檔案產生一個.gcno檔案
- ./nmap è每個源檔案產生一個.gcda檔案
- Gcov *cc è每個源檔案產生一個.gcov檔案
步驟一:檢測代碼
按照nmap項目readme文檔編譯運行一遍,保證代碼正確性
步驟二:增加使用gcov的編譯選項:-fprofile-arcs -ftest-coverage,連結選項-lgcov或者-coverage
對於手動寫的Makefile代碼,直接增加編譯選項即可
對一些自動產生的Makefile檔案,運行./configure –h 查看增加Makefile編譯串連選項的方法,增加編譯選項:-fprofile-arcs -ftest-coverage ,通過一些環境變數設定即可,比如本程式設定為 CXXFLAGS=”-fprofile-arcs -ftest-coverage” LIBS=-lgcov ./configure
步驟三:編譯串連
修改Makefile檔案後,執行make, 針對每個源檔案會產生.gcno檔案
步驟四:測試
運行單個測試案例或測試案例組,產生.gcda檔案,如下運行./nmap 127.0.0.1後,結果如下
步驟五:運行gcov產生覆蓋測試資訊
如下所示,分析其中的一個源檔案及其相關聯檔案的測試覆蓋率情況,預設情況下會產生sourefilename.c.gcov檔案,可以添加-l,-p選項產生具體的目錄及長檔名。如下所示,分別對main.cc與output.cc的進行覆蓋率統計。這裡查看的是行覆蓋率,也可以添加-b,-f給出分支覆蓋率資訊,具體可以通過man gcov查看
下邊是給出的產生行覆蓋率的資訊:
main.cc
Output.cc
步驟六:查看.gcov檔案
顯示原始碼的執行情況,如下所示,查看output.cc的執行情況,以下分別是output.cc.gcov檔案的頭與部分資訊,其中紅框部分標註該行代碼的執行次數,-表示沒有被插樁到的程式碼。
是分支覆蓋率資訊:
函數開始前給出整體資訊:
一些分支情況:
步驟七: 用lcov查看整體代碼覆蓋情況
使用lcov前對覆蓋率資料清空,lcov –z –d ./
在源碼路徑下運行lcov –b ./ -d ./ -c -o output.info,-b指示以目前的目錄作為相對路徑,-d表示統計目錄中的覆蓋資料檔案而不是核心資料,並將產生的資訊存於-o所示檔案,具體參數參考:lcov –h 查看
最後,可以合并多個覆蓋率資訊,用-a 選項
Lcov –add-tracefile .out/a.info –a ./out.info –a ./out/b.info
步驟八:用genhtml查看總體視圖與網頁視圖
如下,可以看出本次覆蓋測試成功instrument的行數有近兩萬行,執行的行數卻只有三千多行,可以反覆的增加測試案例,提高覆蓋測試率。分別給出了整體覆蓋率和各個源檔案的覆蓋情況。
3. 常見錯誤
1. .gcda檔案目錄出錯,找不到要建立的目錄,這種主要用於跨平台情況。
這個是由於.gcda檔案的產生預設儲存到.o所在的目錄,但是如果.o所在目錄不存在,就會出現錯誤。
設定環境變數可以解決這個問題。
設定GCOV_PREFIX=/target/run’同GCOV_PREFIX_STRIP=1
則產生的.gcda檔案 將會儲存到 /target/run/build/foo.gcda。
2. the gcov message “Merge mismatch for summaries”
可以將.gcda全部刪除或者對整個檔案全部編譯,而不是單個改變的檔案,這個是由於gcda與gcno不相配導致的,因為兩者之間都有個時間戳記用來記錄是不是相同的。