[整理] gcov lcov 覆蓋c/c++項目入門


寫在前面

這個過程幾乎從0開始,在此之前,我幾乎沒有在 linux 下編譯鏈接過項目、沒有接觸過 makefile、沒有讀過 man-db、只 gcov 過一個僅有幾個C文件的項目

現在,我用 gcov 完成了對 VIM 源碼的覆蓋,並通過 lcov 生成了非常易讀的覆蓋率報告

 

中間碰到了許多疑難雜症,但是更多的是若干教程中叮囑的“不要放棄”,所以我大概按照下面的節點完成了這個工具的入門:

虛擬機安裝Ubuntu,配置gcov和lcov環境

  --> 編譯鏈接單個C文件

    --> 寫一個多個C文件的項目,用 makefile 進行編譯連接,完成覆蓋

      --> 覆蓋優秀的開源軟件,例如 VIM

這里會按照上述節點逐漸展開,希望幫助和我一樣從0開始的朋友們更容易的完成這個過程

如果您對一些問題已經有了研究,那么這里的內容可能太過淺顯,敬請繼續往下翻閱

如果您沒有碰到這些問題,那么恭喜一切都很順利

如果您要深究一些技術的原理,那么這里可能無法提供您所需要的信息:一是我期望在這里精煉出成功配置環境的方法,更傾向於去解決問題而非深入研究;二是掌握一項技術歸根結底還要自己一步一步走下去,一點一點踏實學,絕不是一篇博文就能簡單解決的

好了,下面開始 :-)

 

虛擬機配置安裝 Ubuntu

1. 選擇虛擬機 VirtualBox,至於為什么不用 VMWare,請參考:

http://www.crifan.com/virtual_machine_soft_choice_vmware_or_virtualbox_definitely_recommend_virtualbox/

2. Ubuntu鏡像:

http://www.ubuntu.com/download

3. 安裝過程大體略去,只是您在用虛擬機加載鏡像的時候可能碰到 VT-x feature locked or unavailable 的問題,那么請參考:

http://www.crifan.com/virtualboxvt_x_features_locked_or_unavailable_in_msr_verr_vmx_msr_locked_or_disabled/

如果還不能成功,請從本地硬盤以管理員身份啟動 VirtualBox

或者,給這個虛擬機鏡像分配少一點的內存

(很奇怪的是,我在安裝 x86 Ubuntu 時,分配內存超過 3G 就會出現此問題,理論不是 4G 嗎?求解)

 

4. 安裝完之后您可能會向我一樣需要兩個功能:調整分辨率和共享文件夾,這都需要安裝虛擬機的增強功能

 

所謂的安裝增強功能,是在登錄到 Ubuntu 之后,掛載一個光盤鏡像,並安裝一些內容,但是您在安裝增強功能時可能會碰到以下問題:

“分配介質虛擬光盤 xxx\VBoxsGuestAdditions.iso 到虛擬電腦 xxx 失敗。您是否要強制卸載分配該介質?”

請參考:

http://www.crifan.com/virtualbox_ubuntu_install_guest_addtions_fail_could_not_mount_the_media/

原因簡而言之是,因為你需要加載一個鏡像到虛擬機的光驅,但是虛擬機光驅內已經有內容了,卸載掉,或者直接從已加載的內容中安裝

 

分辨率在重啟虛擬機之后就可以生效。

5. 共享文件夾,因為會需要從宿主 Windows 中共享一些資源到虛擬機的 Ubuntu 中,在安裝完增強功能之后

 

選擇好宿主機上的共享目錄,之后在 Ubuntu 終端上運行命令:

$ sudo mount -t vboxsf <shared_folder_on_windows> <mount_point_on_ubuntu>

 

這樣在 Ubuntu 的掛載點上就能訪問 Windows 上共享的資源了。

 

配置 Ubuntu,安裝 gcov & lcov

1. 網絡源和安裝軟件

為什么要配置源?您當然可以手動安裝各種軟件包,但是繁瑣的依賴關系會讓你痛不欲生。而從網絡源安裝會檢查依賴並自動部署(安裝),僅需一條命令:

$ sudo apt-get install <software_name>

 

比如,分別執行如下兩條命令,就自動完成 gcov 和 lcov 的安裝

$ sudo apt-get install gcov
$ sudo apt-get install lcov

 

等一下,網絡源還沒配……

其實就是把“要從哪里獲取資源”這個信息告訴 Ubuntu,可以參考:

http://wiki.ubuntu.org.cn/index.php?title=Qref/Source&variant=zh-cn

上述資料簡述成為以下三步:

  a) 備份系統自帶的源,原始的源列表就在 /etc/apt/sources.list

$ sudo cp /etc/apt/sources.list /etc/apt/sources.list_backup

 

  以上命令的意思是,把原始內容原封不動的 backup 一下,以后你就可以從這個 sources.list_backup 中恢復了

  b) 全世界有很多的源可供您維護系統更新軟件,依據您的網絡環境添加合適的源(例如,如果人在歐洲,那么添加網易的開源鏡像服務器就不太合適了)

  很久以前在大三的水過的 Linux 課上老師提過 CN99 放在常州,不過現在我使用的網易的源,中間的變遷似乎還有段野史,感興趣的請自行搜索

  c) 獲取源的目錄等信息,改成你期望的源之后,請一定要執行以下命令:

$sudo apt-get update

 

2. 就在上面這條指令更新源的時候碰到的問題:Hash 和 public key ,詳細錯誤信息類似下面這兩條:

W: There is no public key available for the following key IDs:

xxxxxxxxxxxxxxxx
W: You may want to run apt-get update to correct these problems
W: Failed to fetch 

bzip2:/var/lib/apt/lists/partial/ppa.launchpad.net_webupd8team_java_ubuntu_dists_precise_main_binary-i386_Packages 

Hash Sum mismatch

 

Public key not available

在選用更新源的時候,切記不能混用不同發行版的源,甚至同一發行版的不同版本也不能混用

在上面更新源的簡介資料中,給出的范例是 Quantal(12.10) 版本,而我虛擬機安裝的其實是 Precise(12.04) 版本,因此參考網易的幫助文檔:

http://mirrors.163.com/.help/ubuntu.html

選用正確的 Precise 源更新到 sources.list 文件中,公鑰問題解決。

Hash Sum mismatch

參考以下材料,似乎國內總會碰到這樣的問題,似乎和敏**詞過濾有關,內容被篡改了?

http://forum.ubuntu.org.cn/viewtopic.php?t=393662

這個問題的原因尚不確定,有經驗的朋友也請給些解答。在家就總會 Hash Sum mismatch ,而在公司就解決了。

解決了以上問題之后,就可以用 sudo apt-get install 把 gcov lcov 裝起來了。

 

GCOV 用於簡單項目的覆蓋

gcov 適用的場合:GNU C/C++,因此適用的編譯器:cc, gcc, g++

這里舉斐波那契數列的一個程序為例

 1 #include <stdio.h>
 2 
 3 int fibonacci(int n);
 4 
 5 int main ()
 6 {
 7    int fib;
 8    int n;
 9 
10    for (n = 0; n <= 41; n++) {
11       fib = fibonacci(n);
12       printf("fibonnaci(%d) = %d\n", n, fib);
13    }
14 
15    return 0;
16 }
17 
18 int fibonacci(int n)
19 {
20    int fib;
21    if (n <= 0) {
22       fib = 0;
23    }
24    else if (n == 1) {
25       fib = 1;
26    }
27    else {
28       fib = fibonacci(n -1) + fibonacci(n - 2);
29    }
30 
31    return fib;
32 }

 

1. 編譯

$ gcc -c fib.c -ftest-coverage -fprofile-arcs

 

除了 fib.o 之外,還生成了 fib.gcno 的話,成功了

.gcno是由-ftest-coverage產生的,它包含了重建基本塊圖和相應的塊的源碼的行號的信息。

2. 鏈接

$ gcc fib.o -o fib

 

誒……怎么回事?

 

在看到了一封乘坐了時光機的來自2003年的郵件之后,查閱了一下 gcc 的 man-db

 

我當時郵件給時光機的兩個主角問了一下 gcov 的近況,並沒有期望得到回復

但是就在昨天 Nathan 他老人家竟然回郵件了!帶上以上所有已經提供的信息,他還感慨了一下 gcov has changed a lot since then...

回歸正題,鏈接的時候下面三條任選一個執行即可

$ gcc fib.o -o fib --coverage
$ gcc fib.o -o fib -lgcov
$ gcc fib.o -o fib -fprofile-arcs

 

應該會正常生成 fib

3. 運行程序 fib

$ ./fib

 

會生成 .gcda 文件,.gcda是由加了-fprofile-arcs編譯參數的編譯后的文件運行所產生的,它包含了弧跳變的次數和其他的概要信息。

4. 生成 gcov 報告

$ gcov fib.c

 

生成的 fib.c.gcov 文件中就包含了代碼覆蓋的統計數據,數字代表了每行代碼被執行的次數及行號,相信這個不難分析

         -:    0:Source:fib.c
         -:    0:Graph:fib.gcno
         -:    0:Data:fib.gcda
         -:    0:Runs:1
         -:    0:Programs:1
         -:    1:#include <stdio.h>
         -:    2:
         -:    3:int fibonacci(int n);
         -:    4:
         1:    5:int main ()
         -:    6:{
         -:    7:   int fib;
         -:    8:   int n;
         -:    9:
        43:   10:   for (n = 0; n <= 41; n++) {
        42:   11:      fib = fibonacci(n);
        42:   12:      printf("fibonnaci(%d) = %d\n", n, fib);
         -:   13:   }
         -:   14:
         1:   15:   return 0;
         -:   16:}
         -:   17:
1402817422:   18:int fibonacci(int n)
         -:   19:{
         -:   20:   int fib;
1402817422:   21:   if (n <= 0) {
 267914296:   22:      fib = 0;
         -:   23:   }
1134903126:   24:   else if (n == 1) {
 433494436:   25:      fib = 1;
         -:   26:   }
         -:   27:   else {
 701408690:   28:      fib = fibonacci(n -1) + fibonacci(n - 2);
         -:   29:   }
         -:   30:
1402817422:   31:   return fib;
         -:   32:}
         -:   33:

 

至於 gcov 的更多選項,例如 -b 分支覆蓋 -f 函數覆蓋, 就 man 吧。

5. 存在的問題

gcov 對每個源碼的分析分散在對應的 .cov 文件中,不容易整理分析;文本,無圖表……

這就是要使用 lcov 的原因

另外,如果您對gcc也十分不熟悉,正在尋求入門的話,可以參考這里:

http://wiki.ubuntu.org.cn/Gcchowto

 

LCOV 整理覆蓋率數據

1. 匯總覆蓋率數據,使用已經生成的 .gcno .gcda 文件生成覆蓋率數據

$ lcov -c -o fib.info -d .

 

簡單解釋一下三個選項

-c: lcov 的一個操作,表示要去捕獲覆蓋率數據

-o: 輸出文件

-d: .gcno .gcda 所在的文件夾,注意這里有個“.”,是從當前文件夾中獲取數據的

問題又來了,開始在 lcov 的過程中,碰到 Negative length 的問題,順着提示找到 lcov 源碼中的一處 $(length) ,之后並沒有頭緒為什么會是負值傳入的,於是根據 sourceforge 上面的地址,發了一封郵件詢問了一下,回信意思是我使用的 gcc 版本為 4.7.2,需要 lcov 1.10+ 版本支持,使用 1.09 或更低版本的 lcov 會出現這樣的問題。於是到以下地址去下載了最新的 lcov

http://ltp.sourceforge.net/coverage/lcov.php

在 lcov 1.10 的 release notes 中寫明了對 gcc 4.7+ 提供了支持。

2. 生成 html 格式的報告

$ genhtml fib.info -o fib_result

 

genhtml 是安裝 lcov 時附帶的,使用上面產生的 .info 文件生成報告,存放於 fib_result 文件夾中

沒錯,這里的報告並不只是一個文件,有好多存放在你 -o 指定的目錄下,生成之后進入 fib_result 就可以看見念想很久的 index.html 了

這里再分享一下怎么從 terminal 用瀏覽器打開網頁:

$ firefox index.html

 

3. gcov lcov 資料匯總

在學習過程中檢索到的一些文章有對這兩個工具的解讀,我將有所收獲、編排整齊的幾篇列舉如下,由淺入深,您也可以直接參考他們的文章:

i) gcov 和 lcov 的簡明使用教程:http://magustest.com/blog/whiteboxtesting/using-gcov-lcov/

ii) gcov 和 lcov 的簡單介紹,包括一些選項的含義,

  gcov: http://blog.csdn.net/livelylittlefish/article/details/6321861

  lcov: http://blog.csdn.net/livelylittlefish/article/details/6321887

iii) gcov 產生的覆蓋率結果會存放在 .cov 文件中,這里有對 .cov 文件的解讀:http://blog.csdn.net/ashhyc/article/details/1558598

iv) lcov 中間產物 .info 文件的解讀:http://blog.csdn.net/vivasoft/article/details/8330186

v) gcov lcov 產生各類文件的簡介:http://wx782870649.blog.163.com/blog/static/12989164120127224317532/

 

vi) gcov official online manual: http://gcc.gnu.org/onlinedocs/gcc/Gcov.html

vii) gcov FAQ: https://oss.oracle.com/~smushran/.debug/gcov/FAQ

viii) 這里提到了怎么用 gcov 對 linux kernel 進行覆蓋:http://blog.csdn.net/yukin_xue/article/details/7653482

ix) 這里分析了 gcov 的工作原理,並直接操縱其獲取數據的出入口,實現了對后台進程的覆蓋統計:

http://blog.linezing.com/2011/03/使用gcov完成代碼覆蓋率的測試

 

覆蓋大項目-學習Makefile

為什么要有 makefile ?

  因為編譯、鏈接項目如果需要一條一條手動敲命令的話,那對那種動輒幾十幾百個文件的項目實在太恐怖了,需要這樣一個建設性的懶惰,於是有了 makefile 

makefile 是什么?

  原本歸根結底,makefile 是原來的編譯、鏈接命令的集合,把源文件逐個編譯、最后鏈接,產生可執行文件

  至於為了靈活性而衍生出來的各類語法、變量、函數、隱晦規則……剛入門時可以先不必糾結

makefile 我還總結不出什么心得,這一陣兒學習是參考的陳皓老師的博客:http://blog.csdn.net/haoel/article/details/2886

或者這里有 pdf 文檔,http://ishare.iask.sina.com.cn/f/8359780.html

我覺得,為了后面的工作,至少讀通這份 pdf 的前8頁,知道 makefile 怎么使用變量

1. 環境變量

相信您或多或少都聽說過環境變量這個詞,也知道他大概是什么意思,很多我們看不到的系統調用會用到這些變量,舉個栗子:

打出命令 gcc 干嘛干嘛的時候,系統怎么執行你這個命令?系統不會聽人說話,其實您已經調用了一個可執行文件 gcc

那這個 gcc 又是從哪調用的?其實系統會從一些目錄下去找這個執行文件 gcc ,而這些目錄就寫在環境變量 $(PATH) 中,可以打印這個變量出來看看

$ echo $(PATH)

 

而 gcc 可執行程序在 /usr/bin 這個文件夾中,他的路徑已經寫在 $(PATH) 里了,應該可以看到

Ubuntu 系統的環境變量存儲在以下5個配置文件中:

/etc/environment

  系統登錄時讀取的第一個文件,用於為所有進程設置環境變量

/etc/profile

  系統登錄時讀取的第二個文件,會設定所有用戶的環境變量

~/.profile

  對應當前登錄用戶的 profile 文件,用於定制當前用戶的個人工作環境

/etc/bash.bashrc

  對應所有用戶的 bash 初始化文件,這里設定的環境變量將應用於所有用戶的 shell 中,此文件會在用戶每次打開 shell 時執行一次

~/.bashrc

  對應當前登錄用戶 bash 的初始化文件,當用戶每次打開shell時,系統都會執行此文件一次

這幾個文件的讀取書序依此是:

/etc/environment -> /etc/profile -> ~/.profile -> /etc/bash.bashrc -> ~/.bashrc

還可以進行一些實驗驗證,請參考:http://blog.sina.com.cn/s/blog_6405313801012pxw.html

2. makefile 中的變量

為什么要用變量?

  再舉個栗子,gcc 有選項 -O0 -O2,前者表示編譯時不優化,后者表示最大程度優化,現在有個 makefile 如下:

executable: main.o kbd.o command.o display.o \
        insert.o search.o files.o utils.o
    gcc -o executable \
        main.o kbd.o command.o display.o \
                insert.o search.o files.o utils.o

main.o: main.c defs.h
    gcc -O2 -c main.c
kbd.o: kbd.c defs.h command.h
    gcc -O2 -c kbd.c
command.o: command.c defs.h command.h
    gcc -O2 -c command.c
display.o: display.c defs.h buffer.h
    gcc -O2 -c display.c
insert.o: insert.c defs.h buffer.h
    gcc -O2 -c insert.c
search.o: search.c defs.h buffer.h
    gcc -O2 -c search.c
files.o: files.c defs.h buffer.h command.h
    gcc -O2 -c files.c
utils.o: utils.c defs.h
    gcc -O2 -c utils.c        

 

當你要做覆蓋率分析的時候,你期望編譯過程不要優化,於是又要把所有的 -O2 改為 -O0 ……

當未來有一個比 gcc 更好的編譯器 xcc ,又要把所有的 gcc 改為 xcc ......

當然,現在可以用 replace ,但是不管是期望更靈活的在以后來修改,還是強迫症……不如這樣改寫上述 makefile :

CC="gcc"
CFLAGS="-O2 -c"
object=main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o

executable: $(object)
$(CC) -o executable $(object)

main.o: main.c defs.h
$(CC) $(CFLAGS) main.c
kbd.o: kbd.c defs.h command.h
$(CC) $(CFLAGS) kbd.c
command.o: command.c defs.h command.h
$(CC) $(CFLAGS) command.c
display.o: display.c defs.h buffer.h
$(CC) $(CFLAGS) display.c
insert.o: insert.c defs.h buffer.h
$(CC) $(CFLAGS) insert.c
search.o: search.c defs.h buffer.h
$(CC) $(CFLAGS) search.c
files.o: files.c defs.h buffer.h command.h
$(CC) $(CFLAGS) files.c
utils.o: utils.c defs.h
$(CC) $(CFLAGS) utils.c

 

在 VIM src 的 INSTALL 文檔中有這么幾行

 

至於 CFLAGS, CXXFLAGS, LIBS 這些變量的含義,請參考:http://www.cnblogs.com/taskiller/archive/2012/12/14/2817650.html

這里我用了另一種方法來確定我需要關注那些變量,在項目路徑下,執行:

$ ./configure -h

 

會顯示 configure 的幫助文檔,其中有這么幾行:

 

把 gcvo lcov 中提到的知識應用到這兒,我們只需要設定好編譯和鏈接相關的兩個環境變量 CFLAGS 和 LIBS

如下設定:

$ export CFLAGS="-c -ftest-coverage -fprofile-arcs"
$ export LIBS="-fprofile-arcs"

 

隨后在 VIM 項目目錄中

$ make
$ make install

 

在 <VIM>/src/objects 中應該生成了許多 .o 和 .gcno 文件吧,隨后運行 VIM 生成 .gcda ,匯總覆蓋率數據生成 .info ,將信息整理成 html 格式的命令都可以參考上面有關 gcov lcov 的使用

最后打開 index.html,就可以看到本文最開始出現的覆蓋率數據了

 

交叉編譯項目及 Linux 內核的覆蓋

(待補完)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM