Gcov作为gnu/gcc工作组件之一,是一款的免费的代码覆盖率测试工具,而且可以结合lcov生成美观的html的测试报表。本文介绍一些gcov的使用方法,基本原理,一些实际中可能会遇到的问题以及解决思路。
1.1 编译
Gcov的使用方法很简单,首先需要给gcc编译的时候打开覆盖测试的开关
例如要对srcfile.c单个文件生成的程序进行代码覆盖测试,在gcc编译的时候:
1
2
|
gcc -fprofile-arcs -ftest-coverage srcfile.c -o srcfile
|
或者简化成:
1
2
|
gcc --coverage srcfile.c -o srcfile
|
如果源文件很多,需要编译,链接的时候,在gcc编译的时候:
编译:
1
2
|
gcc -fprofile-arcs -ftest-coverage -c srcfile.c
|
链接:
1
2
|
gcc srcfile.o -o srcfile -lgcov
|
或者
1
2
|
gcc srcfile.o –o srcfile -fprofile-arcs
|
看出来了没有,gcov可以只针对大项目中的某几个单独的文件进行代码覆盖测试,只要在这几个文件编译的时候,加上-ftest-coverage,其他的文件不变就行了,爽吧。
1.2 生成报表
编译完成后会同时生成 *.gcno 文件(gcov notes),gcov生成覆盖率报告时需要参考该文件。
运行生成的可执行文件,给予正常的工作负载,待其正常退出后会生成覆盖率统计数据 *.gcda 文件(gcov data)
通过如下命令行之一查看覆盖率报告:
gcov 生成文本统计结果和带 line-count 标注的源代码:gcov srcfile
lcov 生成较正式的 HTML 报告:
1
2
|
lcov -c -d srcfile_dir -o srcfile.info
genhtml -o report_dir srcfile.info
|
注意:另外,编译选项中最好加入 -g3 -O0,前者是为了增加调试信息,后者是为了禁用优化,免得覆盖率测试不准确。
1.3 一个单文件的例子
一个例子程序如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
#include
#include
int
main(
int
argc,
char
* argv[]) {
int
i = 0;
printf
(
"Begin Test...\n"
);
if
(1 == argc) {
printf
(
"argc == 1\n"
);
}
else
{
printf
(
"argc != 1\n"
);
for
(i = 0; i < argc; i++)
printf
(
"%d\tof\t%d\n"
, i+1, argc);
}
printf
(
"End Test!\n"
);
}
|
编译:
1
|
gcc
test
.c -fprofile-arcs -ftest-coverage -o
test
|
生成文件如下:
1
|
test
test
.c
test
.gcno
|
运行:
1
|
.
/test
1 2 3 4
|
生成文件如下:
1
|
test
test
.c
test
.gcda
test
.gcno
|
生成覆盖测试报告:
1
|
gcov
test
|
生成的test.gcov如下:
第一列是覆盖情况,第二列是行号加源程序,其中第一列中数字开头的是执行的次数,####开头的是没有执行到的语句。
2. Gcov的实现原理简介
Gcc中指定-ftest-coverage 等覆盖率测试选项后,gcc 会:
2 对后台服务程序进行覆盖率测
从 gcc coverage test 实现原理可知,若用户进程并非调用 exit 正常退出,覆盖率统计数据就无法输出,也就无从生成报告了。后台服务程序若非专门设计,一旦启动就很少主动退出,用 kill 杀死进程强制退出时就不会调用 exit,因此没有覆盖率统计结果产生。
为了解决这个问题,我们可以给待测程序增加一个 signal handler,拦截 SIGHUP、SIGINT、SIGQUIT、SIGTERM 等常见强制退出信号,并在 signal handler 中主动调用 exit 或 __gcov_flush 函数输出统计结果即可。如何使用gcov完成对后台驻守程序的测试
该方案仍然需要修改待测程序代码,不过借用动态库预加载技术和 gcc 扩展的 constructor 属性,我们可以将 signalhandler 和其注册过程都封装到一个独立的动态库中,并在预加载动态库时实现信号拦截注册。这样,就可以简单地通过如下命令行来实现异常退出时的统计结果输出了:
1
|
LD_PRELOAD=.
/gcov_out
.so .
/daemon
|
测试完毕后可直接 kill 掉 daemon 进程,并获得正常的统计结果文件 *.gcda。
(注:kill -15 PID,pkill 进程名可以正常产生*.gcda;kill -9 不能产生*.gcda文件)
用来预加载的动态库gcov_out.so的代码如下,其中__attribute__ ((constructor))
是gcc的符号,它修饰的函数会在main函数执行之前调用,我们利用它把异常信号拦截到我们自己的函数中,然后调用__gcov_flush()输出错误信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
#include
#include
#include
#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"
);
}
}
}
|
编译:
1
|
gcc -shared -fPIC gcov_out.c -o gcov_out.so
|
4. 参考资料
man gcc
man gcov
lcov – http://ltp.sourceforge.net/coverage/lcov.php
注意,lcov 最好使用 1.9 及以上版本,否则可能遇到如下错误:
geninfo: ERROR: …: reached unexpected end of file
转载自:量子数科院[http://www.linezing.com/blog]。