简介
在这章里,我们将会探讨gcov实用程序,并且了解一下如何使用gcov来帮助测试与支持软件配置与优化。我们将会了解如何使用gcov来构建软件,并且理解他所提供的各种数据类型。最后,我们将探讨当执行保险测试时要避免的事情。
gcov是什么?
我们从gcov可以为我们做什么开始。gcov是一个保险测试工具。当构建一个程序时,gcov会监视一个程序的执行,并且会标识出执行了哪一行源码,哪一行没有执行。更进一步,gcov可以标识出某一行源执行的次数,这对于执行配置很有用(程序在哪里花费了大多数的时间)。因为gcov可以分辨出哪一行没有执行,这对于保险测试工具是很有用的。
我们将会讨论3.2.2版本的GNU编译器工具链的gcov的用法。
准备镜像
让我们来看一下如何为gcov的使用准备镜像。我们将会在接下来的部分提供更为详细的gcov的选项,所以这里只是作为一个介绍。我们将将会使用下面的所列的bubblesort的源码:
1: #include <stdio.h>
2:
3: void bubbleSort( int list[], int size )
4: {
5: int i, j, temp, swap = 1;
6:
7: while (swap) {
8:
9: swap = 0;
10:
11: for ( i = (size-1) ; i >= 0 ; i— ) {
12:
13: for ( j = 1 ; j <= i ; j++ ) {
14:
15: if ( list[j-1] > list[j] ) {
16:
17: temp = list[j-1];
18: list[j-1] = list[j];
19: list[j] = temp;
20: swap = 1;
21:
22: }
23:
24: }
25:
26: }
27:
28: }
29:
30: }
31:
32: int main()
33: {
34: int theList[10]={10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
35: int i;
36:
37: /* Invoke the bubble sort algorithm */
38: bubbleSort( theList, 10 );
39:
40: /* Print out the final list */
41: for (i = 0 ; i < 10 ; i++) {
42: printf("%d\n", theList[i]);
43: }
44:
45: }
gcov程序将会与编译器工具链一起使用。这就意味着我们将要在其上进行保险测试的镜像必须用一个特殊的选项集合进行编译。下面是我们用来演示编译bubbleSort.c的命令:
gcc bubblesort.c -o bubblesort -ftest-coverage -fprofile-arcs
当我们执行生成的程序时会生成一些包含关于程序的相关数据的文件。gcov程序将会使用这些文件来报告数据并且向开发者提供相应的信息。当指定-ftest -coverage选项时会为每一个源码生成两个文件。这些文件会使用.bb与.bbg作为扩展名,并且用这些文件来重组每一个可执行程序的程序流图。对于-fprofile-arcs,将会生成一个包含每一个指令分支的执行计数的以.da为扩展名的文件。这些文件会在执行以后与源码文件一起使用,来标识源码的执行行为。
使用gcov程序
现在我们准备好了我们的程序镜像了,让我们继续我们其余的部分。运行我们的程序就会生成我们在前面所讨论的数据集文件。然后我们使用我们希望进行检测的源码运行gcov程序。如下面所示:
$ ./bubblesort
...
$ gcov bubblesort.c
100.00% of 17 source lines executed in file bubblesort.c
Creating bubblesort.c.gcov.
这告诉我们在我们的例子程序中所有的源码行至少都执行了一次。我们可以通过查看所生成的bubblesort.c.gcov文件来了解每一源码行所实际运行的次数。如下面所示:
-: 0:Source:bubblesort.c
-: 0:Graph:bubblesort.gcno
-: 0:Data:bubblesort.gcda
-: 0:Runs:1
-: 0:Programs:1
-: 1:#include <stdio.h>
-: 2:void bubbleSort(int list[],int size)
1: 3:{
1: 4: int i,j,temp,swap=1;
4: 5: while(swap)
-: 6: {
2: 7: swap=0;
22: 8: for(i=(size-1);i>=0;i--)
-: 9: {
110: 10: for(j=1;j<=i;j++)
-: 11: {
90: 12: if(list[j-1]>list[j])
-: 13: {
45: 14: temp=list[j-1];
45: 15: list[j-1]=list[j];
45: 16: list[j]=temp;
45: 17: swap=1;
-: 18: }
-: 19: }
-: 20: }
-: 21: }
1: 22:}
-: 23:int main()
1: 24:{
1: 25: int theList[10]={10,9,8,7,6,5,4,3,2,1};
-: 26: int i;
-: 27: /*Invoke the buble sort algorithm*/
1: 28: bubbleSort(theList,10);
-: 29:
-: 30: /*print out the final list*/
11: 31: for(i=0;i<10;i++)
-: 32: {
10: 33: printf("%d\n",theList[i]);
-: 34: }
1: 35: return 0;
-: 36:}
现在让我们来看一下其中的一些关键点,看一下他所提供的内容。第一列显示了源码中每一行源码所执行的次数。在一些情况下,执行次数并没有提供。这些只是并不会影响代码的简单C源码元素。
这些计数可以提供一些关于程序执行的信息。例如,测试的第12行执行了90次,而14-17行的代码只是执行了45次。这告诉我们当这个函数调用了90次,真正成功的仅是45次。换句话说,大部分的测试时间浪费在两个元素的交换上。这是由于测试数据的顺序所造成的。
从这里我们可以看到代码段中最常执行的部分就是排序算法的内循环部分。这是因为由于退出测试第10行要比第12行执行的次数多一些。
查看分支概率
我们也可以使用-b选项来查看程序的分支数据。这个选项会输出程序中每一个分支的频度与相应的摘要。例如,我们使用-b选项来执行gcov命令:
$ gcov -b bubblesort.c
100.00% of 17 source lines executed in file bubblesort.c
100.00% of 12 branches executed in file bubblesort.c
100.00% of 12 branches taken at least once in file bubblesort.c
100.00% of 2 calls executed in file bubblesort.c
Creating bubblesort.c.gcov.
所生成的bubblesort.c.gcov文件如下所示。
-: 0:Source:bubblesort.c
-: 0:Graph:bubblesort.gcno
-: 0:Data:bubblesort.gcda
-: 0:Runs:1
-: 0:Programs:1
-: 1:#include <stdio.h>
-: 2:void bubbleSort(int list[],int size)
function bubbleSort called 1 returned 100% blocks executed 100%
1: 3:{
1: 4: int i,j,temp,swap=1;
4: 5: while(swap)
branch 0 taken 67%
branch 1 taken 33% (fallthrough)
-: 6: {
2: 7: swap=0;
22: 8: for(i=(size-1);i>=0;i--)
branch 0 taken 91%
branch 1 taken 9% (fallthrough)
-: 9: {
110: 10: for(j=1;j<=i;j++)
branch 0 taken 82%
branch 1 taken 18% (fallthrough)
-: 11: {
90: 12: if(list[j-1]>list[j])
branch 0 taken 50% (fallthrough)
branch 1 taken 50%
-: 13: {
45: 14: temp=list[j-1];
45: 15: list[j-1]=list[j];
45: 16: list[j]=temp;
45: 17: swap=1;
-: 18: }
-: 19: }
-: 20: }
-: 21: }
1: 22:}
-: 23:int main()
function main called 1 returned 100% blocks executed 100%
1: 24:{
1: 25: int theList[10]={10,9,8,7,6,5,4,3,2,1};
-: 26: int i;
-: 27: /*Invoke the buble sort algorithm*/
1: 28: bubbleSort(theList,10);
call 0 returned 100%
-: 29:
-: 30: /*print out the final list*/
11: 31: for(i=0;i<10;i++)
branch 0 taken 91%
branch 1 taken 9% (fallthrough)
-: 32: {
10: 33: printf("%d\n",theList[i]);
call 0 returned 100%
-: 34: }
1: 35: return 0;
-: 36:}
从这里我们可看到这与上面的文件相类似,但是这一次每一个分支点都用他们的频度进行了标示。
分支点依赖于目标结构建指令集。第12行是一个简单的if语句,所以有一个分支点。在这里我们可以注意到这是50%,这通过我们前面观察程序的执行次数可以看出。其他的分支点有一些难于分析。例如,第7行是一个while语句,有两个分支点。在X86汇编中,这一行分编译成我们下面所看到的样子:
1: cmpl $0, -20(%ebp)
2: jne .L4
3: jmp .L1
从这里我们可看出,swap变量与0进行比较。如果他不等于0,就会跳转到第2行,.L4。否则要跳转到第3行,.L1。第2行所示的分支概率为67%。这是因为这一行执行3次,但是jne只执行了两次。当第2行的jne并没有执行时,我们直势头跳转到第3行。这只执行一次,但是一旦执行,程序就结束了。所以分支1要花费100%的时间。
所以分支概率在理解程序流时是要相当有用的,但是要参考汇编需要理解分支点在哪里。
不完整程序测试
当gcov计数一个测试并不是100%的程序时,并没有执行的行是标记为####,而不是执行次数。下面显示的是一个由gcov创建的文件来显示少于100%的测试。
1: #include <stdio.h>
2:
3: int main()
4: 1 {
5: 1 int a=1, b=2;
6:
7: 1 if (a == 1) {
8: 1 printf("a = 1\n");
9: } else {
10: ###### printf("a != 1\n");
11: }
12:
13: 1 if (b == 1) {
14: ###### printf("b = 1\n");
15: } else {
16: 1 printf("b != 1\n");
17: }
18:
19: 1 return 0;
20: }
当这个程序运行时,gcov实用程序也会向标准输出输出相应的信息。他会显示可能执行的源码行的行数以及实际运行的百分比。
$ gcov incomptest.c
77.78% of 9 source lines executed in file incomptest.c
Creating incomptest.c.gcov.
$
如果我们的例子程序有多个函数,我们可以通过使用-f选项来查看每一个函数的执行情况。如下面的我们以bubbleSort程序所进行的演示:
$ gcov -f bubblesort.c
100.00% of 11 source lines executed in function bubbleSort
100.00% of 6 source lines executed in function main
100.00% of 17 source lines executed in file bubblesort.c
Creating bubblesort.c.gcov.
$
gcov可用的选项
gcov程序调用的格式为:
gcov [options] sourcefile
其可用的选项如下:
选项 目的
-v,-version 打印版本信息
-h,-help 打印帮助信息
-b,-branch-probabilities向输出文件输出分支频度
-c,-branch-counts 打印分支计数而不是分支频度
-n,-no-output 不创建gcov输出文件
-l,-long-file-names 创建长文件名
-f,-function-summaries 打印每一个函数的概要
-o,-object-directory .bb,.bbg,.da文件存放的目录
从上面这个表中,我们可以看到一个单个字符选项,以及一个长选项。当从命令行中使用gcov命令时短选项是比较有用的,但是当gcov是Makefile的一个部分时,应使用长选项,因为这更易于理解。
当了解gcov程序的版本信息时,可以使用-v选项。因为gcov是与一个指定的编译器工具链联系在一起的(实际上是由gcc工具链而构建的),gcc版本与gcov的版本是相同的。
gcov程序的简介以及选项帮助可以用-h选项来进行显示。
undefined reference to _gcov_init问题
On the link line you have to either
a) add -fprofile-arcs
b) add -lgcov