關於C++ code coverage tool 的研究(1)

關於覆蓋測試的基本概念可以上網查閱,這裏直接從研究對比開始講吧。因爲內容太多,開始之前先給個目錄:
(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來講)

  1. gcov讀取.bbg中的程序流圖信息,建立被測源文件中每個函數的程序流圖
  2. 讀取.gcda信息,將已知的弧執行次數填入到程序流圖中
  3. 根據節點入度等於出度的原理推算出其他的弧與基本塊的執行次數
  4. 讀取.bb文件,根本對應關係計算出每行代碼的執行次數
  5. 對應分支的話還需要計算分支的起始位置
  6. 輸出計算結果

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萬行以上。

執行:

  1. CXXFLAGS=”-fprofile-arcs -ftest-coverage” LIBS=-lgcov ./configure  èmakefile
  2. Make     è 每個源文件產生一個.gcno文件
  3. ./nmap   è每個源文件生成一個.gcda文件
  4. 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





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章