代碼覆蓋率分析(gcov)

爲什麼需要代碼覆蓋率分析?

在發佈代碼的時候,我們常常會對其進行一系列的測試來協調軟件的性能和功能,使他們和預計的相同。但是檢驗通常都是相當的困難,即使程序相當的簡單。開發者常常會藉助一些測試工具(test suite)來模擬或者重建執行腳本。如果測試程序組是徹底的,那麼程序的各個功能都將被測試到並且都可以證明是可以工作的。

但是怎樣纔算徹底呢?簡單點說就是測試程序的每一條路徑,驗證每一個結果,執行每一條語句,證明沒一句語句是沒用的。gcov就是一個用來檢驗你的每一句語句是否都執行了的工具。

什麼是代碼覆蓋率分析?
代碼覆蓋率分析就是找到定位沒用的或者不執行的代碼的過程。沒用的代碼不會存在什麼問題,但是他們會影響程序的可讀性;不執行的代碼則可能是未來bug的所在。所以找到他們,把他們從你的程序中移處是大有裨益的。
覆蓋率分析主要有下面的幾個過程:
    通過測試程序組找到不執行的程序段;
    添加額外測試程序組,以便增加代碼覆蓋率;
    決定代碼覆蓋率的定量測度,他也是程序質量的間接測度。

代碼覆蓋率分析的缺陷
代碼覆蓋率分析不能找出程序的邏輯錯誤。考慮一下下面的代碼
10:  rc = call_to_xx ();
11:  if (rc == ERROR_FATAL)
12:    exit(2);    /* exit with error code 2 */
13:  else
14:    /* continue on */
當測試程序段運行到11行時,11行始終都不能爲真。call_to_xx返回了另外的一個錯誤比如ERROR_HANDLE,除非我們加入這種錯誤的處理方式的代碼。
代碼覆蓋測試工具不會告訴你什麼是必須的,他們只能顯示已經存在的代碼的覆蓋率。

代碼覆蓋率的類型
gcov可以用來測量各種形式的代碼覆蓋率。最常見最有用的兩種是分支覆蓋(branch coverage)和循環覆蓋(loop coverage)
分支覆蓋證明各個方向的每一條分支都被執行到了。循環覆蓋試圖證明循環內部的每一條路徑都被測試到了。循環覆蓋似乎非常的複雜,但基本上只要滿足下面的三個狀況,就可以作了。
    1。循環條件不滿足,循環沒有內部沒有執行;
    2。循環條件就滿足了一次,循環內部就執行了一次;
    3。循環條件至少滿足了兩次,循環至少執行了兩次。
舉個例子
void  function(int number)
{
  if (number % 2) == 0)
    printf("even /n");
  for (;number < 9; number++){
    printf("number is %d/n", number);
  }
}

function(11);   滿足狀況一
function(8);    滿足狀況二
function(6);    滿足狀況三   

代碼覆蓋率工具gcov的使用
要使用gcov,需要在我們用gcc編譯程序時加入兩個參數fprofile-arcs和ftest-coverage.
fprofile-arcs參數使gcc創建一個程序的流圖,之後找到適合圖的生成樹。只有不在生成樹中的弧被操縱(instrumented):gcc添加了代碼來清點這些弧執行的次數。當這段弧是一個塊的唯一出口或入口時,操縱工具代碼(instrumentation code)將會添加到塊中,否則創建一個基礎塊來包含操縱工具代碼。
gcov主要使用.gcno和.gcda兩個文件
.gcno是由-ftest-coverage產生的,它包含了重建基本塊圖和相應的塊的源碼的行號的信息。
.gcda是由加了-fprofile-arcs編譯參數的編譯後的文件運行所產生的,它包含了弧跳變的次數和其他的概要信息。

下面是一個簡要的範例:
  1 #include <stdlib.h>
  2 #include <stdio.h>
  3
  4 int main(int argc,char** argv)
  5 {
  6     int x,y;
  7     int arraysize;
  8     int **array;
  9
 10     if(argc!=2)
 11     {
 12         printf("Usage: %s Enter arraysize value/n;",argv[0]);
 13         exit(-1);
 14     }
 15     else
 16     {
 17         arraysize = atoi(argv[1]);
 18         if(arraysize <=0)
 19         {
 20             printf("Array size must be larger than 0/n;");
 21             exit(-1);
 22         }
 23     }
 24
 25     array = (int**) malloc( arraysize*sizeof(int*));
 26
 27     printf("Creating an %d by %d array /n",arraysize,arraysize);
 28
 29     if(array == NULL)
 30     {
 31         printf("Malloc failed for array size %d /n",arraysize);
 32         exit(-1);
 33     }
 34
 35     for (x=0;x<arraysize;x++)
 36     {
 37         array[x] = (int*) malloc (arraysize*sizeof(int));
 38
 39         if(array[x] == NULL)
 40         {
 41             printf("Failed malloc for array size %d/n",arraysize);
 42             exit(-1);
 43         }
 44     }
 45
 46     exit(0);
 47 }

$ gcc -fprofile-arcs -ftest-coverage -g -o sample test.c
$ ./sample 10
Creating an 10 by 10 array
 $ gcov test.c
File '/usr/include/sys/sysmacros.h'
Lines executed:0.00% of 6
/usr/include/sys/sysmacros.h:creating 'sysmacros.h.gcov'

File 'test.c'
Lines executed:57.89% of 19
test.c:creating 'test.c.gcov'
$ cat test.c.gcov
        -:    0:Source:test.c
        -:    0:Graph:test.gcno
        -:    0:Data:test.gcda
        -:    0:Runs:1
        -:    0:Programs:1
        -:    1:#include <stdlib.h>
        -:    2:#include <stdio.h>
        -:    3:
        -:    4:int main(int argc,char** argv)
        1:    5:{
        -:    6:        int x,y;
        -:    7:        int arraysize;
        -:    8:        int **array;
        -:    9:
        1:   10:        if(argc!=2)
        -:   11:        {
    #####:   12:                printf("Usage: %s Enter arraysize value;/n",argv[0]);
    #####:   13:                exit(-1);
        -:   14:        }
        -:   15:        else
        -:   16:        {
        1:   17:                arraysize = atoi(argv[1]);
        1:   18:                if(arraysize <=0)
        -:   19:                {
    #####:   20:                        printf("Array size must be larger than 0;/n");
    #####:   21:                        exit(-1);
        -:   22:                }
        -:   23:        }
        -:   24:
        1:   25:        array = (int**) malloc( arraysize*sizeof(int*));
        -:   26:
        1:   27:        printf("Creating an %d by %d array /n",arraysize,arraysize);
        -:   28:
        1:   29:        if(array == NULL)
        -:   30:        {
    #####:   31:                printf("Malloc failed for array size %d /n",arraysize);
    #####:   32:                exit(-1);
        -:   33:        }
        -:   34:
       11:   35:        for (x=0;x<arraysize;x++)
        -:   36:        {
       10:   37:                array[x] = (int*) malloc (arraysize*sizeof(int));
        -:   38:
       10:   39:                if(array[x] == NULL)
        -:   40:                {
    #####:   41:                        printf("Failed malloc for array size %d/n",arraysize);
    #####:   42:                        exit(-1);
        -:   43:                }
        -:   44:        }
        -:   45:
        1:   46:        exit(0);
        -:   47:}

正如我們看到的,我們現在的覆蓋率是57.89%,#####後面的語句是現在沒有夠執行到的。

$ ./sample
Usage: ./sample Enter arraysize value;
$ gcov test.c
File '/usr/include/sys/sysmacros.h'
Lines executed:0.00% of 6
/usr/include/sys/sysmacros.h:creating 'sysmacros.h.gcov'

File 'test.c'
Lines executed:68.42% of 19
test.c:creating 'test.c.gcov'
現在運行了沒有參數的情況,這種情況佔了10.53%(68.42-57.89)

$ ./sample 0
Array size must be larger than 0;
$ gcov test.c
File '/usr/include/sys/sysmacros.h'
Lines executed:0.00% of 6
/usr/include/sys/sysmacros.h:creating 'sysmacros.h.gcov'

File 'test.c'
Lines executed:78.95% of 19
test.c:creating 'test.c.gcov'
有測試了一種情況,那就是arraysize=0現在的覆蓋率已經達到了78.95%

還有兩種情況,那就是二維數組的第一維和第二維內存空間的申請了。在現在的狀況下,malloc()不太可能分配失敗,所以我們藉助工具gdb來模擬malloc失敗時的情況。
$ gdb sample
GNU gdb Red Hat Linux (6.6-16.fc7rh)
Copyright (C) 2006 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i386-redhat-linux-gnu"...
Using host libthread_db library "/lib/libthread_db.so.1".
(gdb) l
1       #include <stdlib.h>
2       #include <stdio.h>
3
4       int main(int argc,char** argv)
5       {
6               int x,y;
7               int arraysize;
8               int **array;
9
10              if(argc!=2)
(gdb) l
11              {
12                      printf("Usage: %s Enter arraysize value/n;",argv[0]);
13                      exit(-1);
14              }
15              else
16              {
17                      arraysize = atoi(argv[1]);
18                      if(arraysize <=0)
19                      {
20                              printf("Array size must be larger than 0/n;");
(gdb) l
21                              exit(-1);
22                      }
23              }
24
25              array = (int**) malloc( arraysize*sizeof(int*));
26
27              printf("Creating an %d by %d array /n",arraysize,arraysize);
28
29              if(array == NULL)
30              {
(gdb) b 29
Breakpoint 1 at 0x8048ada: file test.c, line 29.
(gdb) r 10
Starting program: /home/secularbird/Templates/test/sample 10
Creating an 10 by 10 array

Breakpoint 1, main (argc=2, argv=0xbf9b59f4) at test.c:29
29              if(array == NULL)
(gdb) print array
$1 = (int **) 0x90af008
(gdb) set array=0
(gdb) print array
$2 = (int **) 0x0
(gdb) step
31                      printf("Malloc failed for array size %d /n",arraysize);
(gdb) cont
Continuing.
Malloc failed for array size 10

Program exited with code 0377.
(gdb) quit
l命令是list的簡寫,顯示了程序的源代碼。
b 29是在29行設置一個斷點,之所以這樣設置是因爲array空間申請過了,我們爲了模擬需要改變它的值。
r 10是run 10,也就是運行程序,10是傳遞的命令行參數。
print array打印array的地址
set array=0將array指向空地址
step一步一步的運行程序,簡寫是s
cont繼續運行程序
quit退出gdb。

接下來做類似的工作產生另一個malloc fail。
$ gdb sample
GNU gdb Red Hat Linux (6.6-16.fc7rh)
Copyright (C) 2006 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i386-redhat-linux-gnu"...
Using host libthread_db library "/lib/libthread_db.so.1".
(gdb) l
1       #include <stdlib.h>
2       #include <stdio.h>
3
4       int main(int argc,char** argv)
5       {
6               int x,y;
7               int arraysize;
8               int **array;
9
10              if(argc!=2)
(gdb) l
11              {
12                      printf("Usage: %s Enter arraysize value/n;",argv[0]);
13                      exit(-1);
14              }
15              else
16              {
17                      arraysize = atoi(argv[1]);
18                      if(arraysize <=0)
19                      {
20                              printf("Array size must be larger than 0/n;");
(gdb) l
21                              exit(-1);
22                      }
23              }
24
25              array = (int**) malloc( arraysize*sizeof(int*));
26
27              printf("Creating an %d by %d array /n",arraysize,arraysize);
28
29              if(array == NULL)
30              {
(gdb) l
31                      printf("Malloc failed for array size %d /n",arraysize);
32                      exit(-1);
33              }
34
35              for (x=0;x<arraysize;x++)
36              {
37                      array[x] = (int*) malloc (arraysize*sizeof(int));
38
39                      if(array[x] == NULL)
40                      {
(gdb) b 39
Breakpoint 1 at 0x8048b7a: file test.c, line 39.
(gdb) r 10
Starting program: /home/secularbird/Templates/test/sample 10
Creating an 10 by 10 array

Breakpoint 1, main (argc=2, argv=0xbfdcae04) at test.c:39
39                      if(array[x] == NULL)
(gdb) print array[0]
$1 = (int *) 0x9bc2038
(gdb) set array[0]=0
(gdb) step
41                              printf("Failed malloc for array size %d/n",arraysize);
(gdb) cont
Continuing.
Failed malloc for array size 10

Program exited with code 0377.
(gdb) quit

$ gcov test.c
File '/usr/include/sys/sysmacros.h'
Lines executed:0.00% of 6
/usr/include/sys/sysmacros.h:creating 'sysmacros.h.gcov'

File 'test.c'
Lines executed:100.00% of 19
test.c:creating 'test.c.gcov'
到現在爲止每條路徑都測試過了,代碼覆蓋率也已經達到了100%,也可以查看一下test.c.gcov文件
裏面已經沒有#####了。

參考資料:
Linux® Debugging and Performance Tuning: Tips and Techniques By Steve Best
man gcov
http://gcc.gnu.org/onlinedocs/gcc/Gcov-Data-Files.html#Gcov-Data-Files



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