關於覆蓋測試的基本概念可以上網查閱,這裏直接從研究對比開始講吧。因爲內容太多,開始之前先給個目錄:
(1)覆蓋測試工具的簡要對比
(2)LINUX下工具GCOV的實現原理
(3)LINUX下工具GCOV的使用說明
(4 ) WINDOWS下工具coverage validator原理與使用說明
(5)修改GCOV適用於分佈式測試覆蓋率統計原理與方法
下面是部分C++ code coverage tool 的一個粗略的對比表格。這裏重點研究了GCOV,COVTOOL,coverage validator,後續有時間的話,會針對旺旺重點研究下XCOVER. 有些工具沒有具體研究,有興趣的話,可以查看相應鏈接。
Free orcommercial | Platform/complier | Coverage level | Show Execute counters | Easy to use | output | Useable in large | implement | others | |
gcov | Free | Linux ,only gcc | Decision coverage | Yes | yes | HTML report | No | Instrument as compiling | nonsupport shared library |
COVTOOL | Free | linux | Line | boolean | no | Merge.db, ASCII report | yes | Instrument as compiling | Nonsupport thread |
xcover | Free | platform-independent library, gcc4.3+ | Line coverage | Yes | no | HTML report | ? | Source file only,denpend on stlsoft | Written in c |
GCT | Free | Unix,linux,Only for c | Branch/condition | no | yes | No HTML | yes | Instrument as compiling | Only for c,work well with if,case,for |
Coverage validator | Commercial | Windows | decision | yes | yes | HTML,XML, | yes | No recompile,With pdb file | Powerful filters,do not affect performance |
BullseyeCoverage | Commercial | Windows,unix, | Branch/condition | no | yes | Csv report,Easy to change to perl | yes | hook | Work well with vc,cppunit |
Pure coverage | Commercial | Unix,windows | decision | yes | yes | HTML,XML, | yes | Object Code |
1、背景介紹
GCOV是一個GNU的本地覆蓋測試工具, 伴隨GCC發佈,配合GCC共同實現對C或者C++文件的語句覆蓋和分支覆蓋測試。是一個命令行方式的控制檯程序。需要工具鏈的支持。
LCOV由 IBM 開發,由 Linux Test Project 維護的開放源代碼工具。是GCOV結果展現的一個前端。這個工具由一組構建於基於文本的GCOV 輸出之上的 PERL 腳本構成,以實現基於 HTML 的輸出, 並生成一棵完整的 HTML 樹。輸出包括覆蓋率百分比、圖表以及概述頁,可以快速瀏覽覆蓋率數據。支持大項目,提供三種等級視圖,分別爲目錄視圖,文件視圖,源代碼視圖。
2、GCOV分析
2.1 基本概念
1. 基本塊BB
如果一段程序的第一條語句被執行過一次,這段程序中的每一個都要執行一次,稱爲基本塊。一個BB中的所有語句的執行次數一定是相同的。一般由多個順序執行語句後邊跟一個跳轉語句組成。所以一般情況下BB的最後一條語句一定是一個跳轉語句,跳轉的目的地是另外一個BB的第一條語句,如果跳轉時有條件的,就產生了分支,該BB就有兩個BB作爲目的地。
下圖是個典型的基本塊:
2.跳轉ARC
從一個BB到另外一個BB的跳轉叫做一個arc,要想知道程序中的每個語句和分支的執行次數,就必須知道每個BB和ARC的執行次數
3. 程序流圖
如果把BB作爲一個節點,這樣一個函數中的所有BB就構成了一個有向圖。,要想知道程序中的每個語句和分支的執行次數,就必須知道每個BB和ARC的執行次數。根據圖論可以知道有向圖中BB的入度和出度是相同的,所以只要知道了部分的BB或者arc大小,就可以推斷所有的大小。
這裏選擇由arc的執行次數來推斷BB的執行次數。
所以對部分 ARC插樁,只要滿足可以統計出來所有的BB和ARC的執行次數即可。
以下是針對某個函數的程序流圖:
2.2 GCOV原理與實現
2.2.1原理簡介
GCOV是一個純軟件的覆蓋測試工具,被測程序的預處理,插樁和編譯成目標文件三個步驟由GCC一次完成。GCOV本身只負責數據處理和結果顯示,下圖是GCOV的工作原理。
gcov工作原理
從左圖可以看出,GCOV統計覆蓋率主要包括三個階段:
l 編譯階段:
加入編譯選項gcc –o hello –fprofile-arcs –ftest-coverage hello
除了爲每個C文件生成*.o目標文件以外還要生成數據文件*.bb和*.bbg(在早期的GCC版本中是包含這兩個文件,後期變成*.gcno文件,但是內部仍然包含這兩個文件的結構),分別記錄行信息和程序流圖信息,供GCOV計算覆蓋率時用。
l 數據收集與提取階段:
./hello 執行時收集數據。
被測程序運行後爲每個源文件生成一個*.da數據文件,後期編譯器成爲.gcda文件,分別記錄了*.c文件中程序的執行情況。
l 報告形成階段:
gcov –a hello.c 收集某個源文件的覆蓋率情況
執行後會生成輸出文件覆蓋率情況,可以重定向保存到某個文件中,同時生成hello.c.gcov形式的文件,文件格式是帶有標示信息的源文件。
從右邊圖可以看出,GCOV的插樁時段,是在編譯階段完成:
被測程序文件首先經過編譯預處理,然後編譯成彙編文件,在生成彙編文件的同時完成插樁。插樁是在生成彙編文件的階段完成的,因此插樁是彙編時候的插樁,每個樁點插入3~4條彙編語句,直接插入生成的*.s文件中,最後彙編文件彙編生成目標文件。在程序運行過程中樁點負責收集程序的執行信息。
2.2.2 覆蓋率收集過程
gcov的實現源文件包括:coverge.c, gcov.c, gcov-io.c, libgcov.c, profile.c ,libgcc2.c以及他們的頭文件,以下從具體的三個階段來討論覆蓋率收集的過程。
1、編譯階段
在編譯階段,當加入相應的編譯選項後,由toplev.c中的函數調用coverage.c與profile.c中的函數,這些函數又調用gcov-io.c中的函數。其中coverage.c中的build_gcov_info 產生一些數據結構,並調用gcov_init 。 同時profile.c會創建程序流圖,由profile.c中的函數創建的程序流圖,同時gcno中的每個arc會調用insert_insn_on_edge函數來增加counter.
2、數據收集階段
在數據收集階段,即在程序運行時。會調用libgcov.c中的函數來增加struct gcov_info的count字段的信息,當程序退出時,gcov_exit會被調用,這個函數將收集到的數據信息寫入到.gcda文件中。
3、生成報告階段
運行($gcov 源文件)後就產生這個後綴文件。gcov要分析的話需要依賴於.gcno,.gcda,.c三個文件,記住這個分析一定要保證:.gcda產生的時候依賴的.gcno要一致,就是說我.gcda和.gcno一定要是配套的。
gcov的分析過程:(用.bbg與.bb代替.gcno來講)
- gcov讀取.bbg中的程序流圖信息,建立被測源文件中每個函數的程序流圖
- 讀取.gcda信息,將已知的弧執行次數填入到程序流圖中
- 根據節點入度等於出度的原理推算出其他的弧與基本塊的執行次數
- 讀取.bb文件,根本對應關係計算出每行代碼的執行次數
- 對應分支的話還需要計算分支的起始位置
- 輸出計算結果
2.2.3 插樁方法
現在爲止,我們知道,
Gcc編譯運行產生了什麼數據以及gcov分析覆蓋率的過程。
還有兩個問題:
a. gcc加入編譯參數後,是怎麼插樁的
b. 在什麼位置,插入了什麼數據呢?
1. 插樁過程所進行的修改
1) 每個源文件對應樁點數組:
GCC在插樁的過程中會像源文件的末尾插入一個靜態數組,BX2.,數組的大小就是這個源文件中樁點的個數。
BX2+0代表第0個樁點的位置,BX2+n代表第n個樁點的位置。
數組的值就是樁點的執行次數。
2) 每個樁點插入彙編語句:
按照我的理解插入的彙編語句是inc$(BX2+n).
3) BX2數組鏈表:
爲了便於統計,gcc還將各個源文件中的BX2數組鏈接成一個鏈表,這個鏈表結構是在測試main函數之前就產生了,在調用main之前會有一個類似構造函數的函數,進行構建鏈表。這個函數會在退出時調用exit函數計算執行次數生成.gcda文件。
2. 一些數據結構與函數功能
1) BX2數組:
每個源文件對應一個,記錄每個樁點的執行次數。
2) bb結構:
因爲要將各個源文件的BX2組織起來,便於統一輸出,爲每個源文件定義一個bb結構如下:
struct bb
{
long zero_word; //是否被插入到鏈表中
const char *file_name; //當前被測試文件名
long *count;//指向bx2的指針
long ncounts;//樁點個數
struct bb *next;//下一個文件的BX2信息
};
3) BX鏈表:
將BX2組織起來,頭指針bb_head,鏈表元素結構爲bb結構。調用void _bb_init_func(struct bb * block), 傳入頭指針bb_head創建。
4) 創建鏈表過程:void _bb_init_func(struct bb * block)
GCC爲被測源文件插入了一個構造函數_GLOBAL_$I$XXXGCOV()的定義,其中XXX指當前被測文件中的第一個全局函數的函數名的生成,此函數在main函數調用之前會同構造函數一起被調用,這個全局函數的功能就是調用_bb_init_func函數,以該文件的bb結構的起始地址爲參數進行調用。
該函數定義在GCC自帶的庫文件Libgcc.a中,源碼位於gcc/libgcc2.c中,定義如下:
void _bb_init_func(struct bb * blocks)
{
if(block->zero_word)//已經連接不管
Return;
if(!bb_head)
ON_EXIT(_bb_exit_func,0);//程序退出時候,寫.gcda數據
blocks->zero_word=0;
blocks->next=bb_head;//插入到鏈表中
bb_head=blocks;
}
該函數首先檢查bb結構是否被插入到鏈表中,如果是則返回,接着檢查bb結構的鏈頭是否被初始化,否則註冊退出時執行函數_bb_exit_func,該函數負責返回bb鏈中的每個結構,並生成.gcda數據文件。
這樣在main函數之前,所有的bb結構都被連接成一個鏈表。
5)寫入數據文件的過程:_bb_exit_func()
在被測程序運行完成之後,註冊退出時會執行函數_bb_exit_func(),將從這個鏈表的頭開始爲每一個bb結構開始爲源文件創建.gcda文件。寫入的文件格式就是BX2數組內容,可以從bb結構中的BX2結構指針找到。
至此,整個插樁過程就講完了。
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不相配導致的,因爲兩者之間都有個時間戳用來記錄是不是相同的。
Coverage validator
1. 簡介
2. 原理簡介
3. 使用
4. 優缺點
1. Coverage validator簡介
Coverage validator(Software Verification Limited公司的產品)是一個代碼覆蓋測試工具。可供軟件開發者和軟件質量測試人員使用。Coverage validator可以幫助你確定工程的代碼覆蓋率,識別出單元測試中未測試的功能,以交互,實時的方式顯示出代碼覆蓋情況來提高軟件測試質量,你也可以合併所有單元測試的覆蓋測試數據。可以在創建單元測試報告的同時生成測試報告。
常見的各種覆蓋測試工具像CoverageMeter,gcov主要原理是替換了原有的編譯器,在代碼中進行插樁。因此, 這些覆蓋測試工具的特點是需要重編被測試代碼。這也是大部分覆蓋率工具常用的方法。而Coverage Validator,不需要重編被測代碼,只需要提供被測二進制程序的pdb文件,就能統計其代碼覆蓋率,所以對於每個應用的每個DLL/EXE模塊,只是簡單的需要PDB或者MAP文件即可。它能同時統計行覆蓋,分支覆蓋,函數覆蓋等。 Coverage validator的insrumention是很快的,只需要幾秒,而不是幾分鐘。跟non-instrumented應用速度是差不多的,不像其他的工具要慢上2到10倍。
Coverage Validator有個很大的好處是可以設置過濾條件,可以設置只統計部分模塊的覆蓋率數據。可以設置只統計某個DLL,某個類的覆蓋率數據,而且返回結果也可以以文件或者函數返回。返回結果也非常直觀,可以導出HTML報告或者XML報告。
目前,它只支持Windows平臺。它能支持的調試信息格式參見下面描述:
Coverage Validator can understand debug in information in the following formats:
· Microsoft Program Database (PDB)
· Borland Turbo Debugger System (TDS)
· CodeView NB10
· COFF
2. Coverage validator使用方法
2.1 下載安裝
在其官網上下載30的適用版本:
https://www.softwareverify.com/cpp/coverage/index.html
2.2 使用
Converage validator的使用是非常簡單的,以下以一個五子棋程序的測試過程來展現coverage validator的功能特點:
整體圖:
先看一下它的運行主頁,可以發現coverage validator的功能是相當強大的,提供整體測試覆蓋情況,每個文件的覆蓋測試,分支覆蓋測試,函數覆蓋測試,單元覆蓋測試,行覆蓋測試和診斷分析等。
步驟一:選擇要測試的程序或模塊
通過菜單”File”-”Start Application” ,選擇要進行覆蓋測試的工程。如下圖所示選擇exe文件或者某個DLL模塊即可,同時可以設置相應的環境變量,參數,輸入輸出文件等。
一直next,直到看到如下界面,點擊開始測試即可。
步驟三:開始測試過程
點擊開始應用後,即會跳出客戶端界面,開始測試。
步驟四:結果顯示
在測試的同時,coverage validator會即時的顯示測試的結果信息,你可以變測試擺弄查看相應的測試覆蓋率信息,下圖是總的測試情況,分別顯示已經待測試的文件,函數,分支,代碼行,及單元測試組的覆蓋率情況。如下,顯示的總文件個數,被訪問的文件個數,未被訪問的文件個數與完全覆蓋文件的個數。
同時還顯示一些comment來給出一些提示,下邊是我之前跑的一個程序,會給出一些信息,提示有些內聯函數和模塊庫中的文件沒有被覆蓋。這個覆蓋率是隨着功能測試的過程動態變化的,可以變測試邊顯示覆蓋測試結果。
也可以查看單個源文件的測試情況,如下圖:
左邊顯示了各個源文件覆蓋情況,其中淺藍色的文件表示100%覆蓋,紅色的表示0覆蓋,黃色的表示部分覆蓋。對於每個文件分別顯示了文件的總行數,被訪問的行數,hook的行數,和測試覆蓋率情況。右邊顯示了選擇出了的單個文件的具體覆蓋信息。黃色表示被覆蓋的行,並在行的前邊表示了該行代碼的執行次數,紅色爲未訪問的行,沒有顏色的表示是沒有HOOK的行。在文件的上方有具體的信息。同樣可以點擊左下方的refresh按鈕來動態即時的顯示代碼覆蓋情況。
利用覆蓋測試工具,增加測試用例的方法,從上圖的左邊可以看出更改用戶名模塊的代碼覆蓋情況爲0,查看此文件的覆蓋測試結果,如下圖所示,紅色的行表示都沒有覆蓋,這個時候需要添加用戶名更改的測試。
下圖是增加了測試用例後的用戶名更改模塊的覆蓋情況:
同樣也可以按照函數的名字,類名字,目錄等查看function coverage,如下圖左下角的refresh用於更新,Type可以選擇相應的顯示方式。
下圖爲選擇的類圖顯示方式。
如果檢查的是CppUnit工程的代碼覆蓋率,需將Testrunnerd.dll文件複製到可執行文件所在目錄。
1. 可以設置過濾條件,只統計加載的某個模塊的覆蓋率數據。比如,你要測試的是一個DLL,你就可以設置過濾條件,只統計該DLL的代碼覆蓋率。你還可以設置過濾只統計某個類,某個函數的覆蓋率數據。設置方法:菜單:”Configure” – “Settings” – “Filters”。
3. Coverage validator優缺點
優點:
1. 不需要重編被測代碼,只需要提供被測二進制程序的pdb文件,可以單獨的測試DLL/EXE模塊
2. 結果數據輸出直觀,查看方便,代碼窗口有顏色標記,詳細顯示各個函數,分支,文件覆蓋情況,並標記每一行代碼執行次數。有HTML報告和XML報告
3. 可配合cppunit使用
4. 插樁很快,應用程序的速度也很快
5. 可以設置過濾條件,只統計加載的某個模塊的覆蓋率數據,某個類,某個函數的覆蓋率數據,也可以設置排除條件,排除統計某部分的覆蓋率數據。可以一個文件一個文件的返回,也可以一個函數一個函數的返回。
6. 可以即時的查看代碼覆蓋測試結果信息,在執行的各個階段查看。
7. 可以用於Native-mode與mixed-mode.net模式
缺點:
1). 結果的自動合成功能不太好,只是在一個SESSION的末期將結果合成。
2). 提供的覆蓋測試功能最高達到分支覆蓋。
3). 不能夠覆蓋所有的行,會有數據丟失
4. 參考
https://www.softwareverify.com/cpp/coverage/index.html