gcov使用小节

  Linux下可用gcov工具生成覆盖率统计信息,然后借助gcov的图形化工具lcov,可生成html格式的代码覆盖率报告,进一步提高覆盖率测试结果的可读性。
  当构建一个程序时,gcov会监视一个程序的执行,并且会标识出执行了哪一行源码,哪一行没有执行。更进一步,gcov可以标识出某一行源执行的次数,这对于执行配置很有用(程序在哪里花费了大多数的时间)。
  LCOV是GCOV图形化的前端工具,是Linux Test Project维护的开放源代码工具,最初被设计用来支持Linux内核覆盖率的度量基于Html输出,并生成一棵完整的HTML树。
输出包括概述、覆盖率百分比、图表,能快速浏览覆盖率数据 
支持大项目,提供三个级别的视图:目录视图、文件视图、源码视图
Gcc中添加-ftest-coverage编译选项后

一:后台程序预处理gcov
后台服务程序一旦启动很少主动退出.可以给待测程序注册一个信号处理函数(signal handler),处理SIGINT、SIGQUITSIGTERM等常见强制退出信号,并在信号处理函数中主动调用exit __gcov_flush函数,以便输出统计结果。 
解决办法,
在main.c 中的main中加入reg_sigterm_handler(sigterm_handler);即可
[cpp] view plain copy
  1. static void sigterm_handler(int signum)  
  2. {  
  3.     exit(0)  
  4. }  
  5. void reg_sigterm_handler(void (*handler)(int s))  
  6. {  
  7.     struct sigaction action, old_action;  
  8.   
  9.     action.sa_handler = handler;  
  10.     sigemptyset(&action.sa_mask);  
  11.     action.sa_flags = 0;  
  12.   
  13.     sigaction(SIGTERM, NULL, &old_action);  
  14.     if (old_action.sa_handler != SIG_IGN) {  
  15.         sigaction(SIGTERM, &action, NULL);  
  16.     }  
  17. }  
可以借用动态库预加载技术和gcc扩展的constructor属性,将signal handler和注册过程都封装到一个独立的动态库中,并在预加载动态库时实现信号拦截注册

预处理gcov代码可如下:
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#define SIMPLE_WAY
void sighandler(int signo)
{
#ifdef SIMPLE_WAY
    exit(signo);
#else
    extern void __gcov_flush();
    // flush out gcov stats data
    __gcov_flush();
    // raise the signal again to crash process
    raise(signo);
#endif
}
__attribute__ ((constructor))
void ctor()
{
    int sigs[] = {
        SIGILL, SIGFPE, SIGABRT, SIGBUS,
        SIGSEGV, SIGHUP, SIGINT, SIGQUIT,
        SIGTERM
    };
    int i;
    struct sigaction sa;
    sa.sa_handler = sighandler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_RESETHAND;
    for(i = 0; i < sizeof(sigs)/sizeof(sigs[0]); ++i) {
        if (sigaction(sigs[i], &sa, NULL) == -1) {
            perror("Could not set signal handler");
        }
    }
}

编译步骤:  gcc -shared -fPIC gcov_out.c -o gcov_out.so

二、安装LCOV
Gcov属于gcc工具集之一,是随gcc一起发布的,并不需要独立安装。lcov需要自己下载开源软件安装。Lcov开源软件的下载地址:
lcov-1.13.tar.gz为例,安装步骤比较简单:
tar –zxvf lcov-1.13.tar.gz
cd lcov-1.13/
make install

lcov可执行二进制默认安装在/usr/local/bin目录下,若该路径不在环境变量$PATH中,可先用export PATH=$PATH: /usr/local/bin添加进来,以便后续使用。

三、编译程序

修改模块的Makefile文件,增加编译选项CFLAGS += -fprofile-arcs -ftest-coverage,或CFLAGS += --coverage,其中-fprofile-arcs用于生成.gcno文件(其包含重建基本块图和相应的块的源码的行号的信息),-ftest-coverage用于生成.gcda文件(.gcda文件由增加此编译参数的编译后的文件运行所产生的,包含弧跳变次数和其他的概要信息)。
链接选项LDFLAGS += -lgcov,注:此选项仅在使用ld链接时需要,使用gcc时不需要。
gcov使用小节
执行make编译生成可执行文件和对应的.o 、.gcno文件。如:
gcov使用小节


四、运行程序
gcov_out.so,可执行程序拷贝至测试环境,执行LD_PRELOAD=./gcov_out.so ./SNMPCommDetect
测试完毕后可直接 kill 掉 daemon 进程,并获得正常的统计结果文件 *.gcda。

(注:kill -15 PID,pkill 进程名可以正常产生*.gcda;kill -9 不能产生*.gcda文件)

我们的程序是在编译机器上把程序编译好,然后拷贝到测试机器上运行。编译的时候在机器A上,路径是/mnt/hgfs/shared/UniAccess/project1/src/test/SNMPCommDetect,我在机器B上运行的目录是
/home/leagsoft/Agentless/Bin/,程序运行完毕生成.gcda文件失败,出现若干个提示如下:
profiling:/home/qli:Cannot create directory
profiling:/home/qli/debug/server/Selection.pb.gcda:Skip
在我的运行环境中没有相应的目录,所以不能生成.gcda文件,只要设一下GCOV_PREFIX和
GCOV_PREFIX_STRIP这两个环境变量就好了。GCOV_PREFIX就是制定生成数据文件的前缀,GCOV_PREFIX_STRIP就是
需要在原来的路径上去掉多少层目录,这2个变量配合使用就能把数据文件生成到我们想要的地方。假如我编译时候的路径是“
/mnt/hgfs/shared/UniAccess/project1/src/test/SNMPCommDetect”,我现在的运行路径是/home/leagsoft/Agentless/Bin/,那么执行如下命令即可
export GCOV_PREFIX=/home/leagsoft/Agentless/Bin/
export GCOV_PREFIX_STRIP=8
执行完这两行命令,就可以开始运行被测程序,然后正常退出,就会看到.gcda文件生成出来了。
gcov使用小节

五、生成覆盖率测试报告
执行gcov +可执行程序生成覆盖率的测试报告
gcov使用小节

注意 :
    在处理的过程中遇到以下错误:“stamp mismatch with graph file”。
gcov使用小节
    导致出现这个错误的原因是.gcda和.gcno文件并不是同一次build出来的,其对应的tag不一致。 在编译的时候会产生gcno文件,这个时候会在gcno文件上打一个tag,同时在生成gcda文件的时候,这个tag会传递给gcda,这个tag的作用在于区别不同的build。 即每一次build,产生的gcno的tag都不一致,这样设计有一个好处就是如果你不小心把build跟弄混了,gcov会自动识别。
    可使用如下命令查看.gcno和.gcda的tag值:
     hexdump -e '"%x\n"' -s8 -n4 main.gcno
     hexdump -e '"%x\n"' -s8 -n4 main.gcda
gcov使用小节

六、覆盖率分析
1、查看生成的.gcov文件(.gcov文件列出了每行代码的执行次数和没有执行的行等)
gcov使用小节

第一列是覆盖情况,第二列是行号加源程序,其中第一列中数字开头的是执行的次数,####开头的是没有执行到的语句
2.借助lcov对覆盖率文件进行改造,在html中查看
  • 生成info文件:lcov -c -d . -o SNMPCommDectect.info
  • gcov使用小节
生成.info中间文件
参数选项含义:
-c: lcov 的一个操作,表示要去捕获覆盖率数据
-o: 输出文件
-d: .gcno .gcda 所在的文件夹,注意这里有个“.”,是从当前文件夹中获取数据的
 注:gcc 版本为 4.7.2,需要 lcov 1.10+ 版本支持,使用 1.09 或更低版本的 lcov 会出现 Negative length 的问题

  • 生成html格式的报告:genhtml -o result SNMPCommDectect.info
gcov使用小节
  genhtml 是安装 lcov 时附带的,使用上面产生的 .info 文件生成报告,存放于 result文件夹中
这里的报告并不只是一个文件,有好多存放在你 -o 指定的目录下,生成之后进入 result就可以看见index.html
  • windows界面下,双击index.html,显示目录级别下的覆盖率统计信息
  • gcov使用小节
点击进入SNMPCommDectect目录, 显示文件级别的覆盖率统计信息
gcov使用小节
进一步点击文件main.cpp,显示文件内分支语句的覆盖率详细信息;
gcov使用小节
标红行是执行./SNMPCommDectect时没有执行到的语句,可看到一些异常情况未测到。可以为我们进一步优化代码或补充测试用例提供了参考。