gcov查看代码覆盖率
絮絮叨叨:想写的素材有很多,每次都是拖到最后也没写完,还是要多学习彭同学的 “先找软柿子捏” 。
GCOV 工具简介
gcov是一个测试代码覆盖率的工具。
它是 gcc 自带的查看代码覆盖率的工具,无需额外安装,在嵌入式的 arm-eabi-none-gcc 中同样可以使用(需要重写部分系统函数)。
使用效果如下图所示:
程序运行完成后,可以查看每个文件的代码覆盖率情况,上面报告中展示了每个文件的行覆盖率,函数覆盖率和分支覆盖率。
打开一个文件的覆盖率报告,页面对开始有文件的基本信息描述,以 FreeRTOS 的 task.c 为例,它的有效代码行数为 921 行,共 24 个函数(几千行的文件其实也没多少嘛~)
在覆盖率的正文,有该文件的完整代码,并用不同颜色进行高亮标注了:
- 蓝色表示运行被覆盖的代码,前面的数字表示代码执行次数。
- 红色表示未执行代码。
- 白色表示无效代码,包括注释,空行和未编译代码。
gcov 使用
使用 gcov 的流程非常简单,只需要三步即可。
下面以 hello world 为例,展示生成覆盖率报告。
代码如下:
//main.c
#include <stdio.h>
int main(int argc, char **argv)
{
printf("hello world\n");
return 0;
}
第一步: 添加编译参数 -fprofile-arcs -ftest-coverage
在所需要的生产覆盖率的文件中,添加编译参数,编译代码生成目标文件,同时会生成 *.gcno 文件,其中包含文件的行号等信息。
gcc main.c -c -fprofile-arcs -ftest-coverage -o main.o
ls # 输出文件列表:
# main.c main.gcno main.o
gcc main.o -lgcov -o main
第二步: 添加链接参数 -lgcov
,运行程序
运行程序后,会生成一个 *.gcda 文件,里面包含代码执行次数等数据。
gcc main.o -lgcov -o main
# 运行程序
./main
# hello world
ls # 输出文件列表:
#main main.c main.gcda main.gcno main.o
第三步: 输出覆盖率报告
使用下面命令输出覆盖率报告
# 第一次使用前安装工具
sudo apt install lcov
# 生成覆盖率文本报告
lcov -c -d . -o test.info --rc lcov_branch_coverage=1
# 生成覆盖率网页报告
genhtml --branch-coverage -o result test.info
输入上面两/三条命令后在,执行命令的文件路径可以看到一个 result 文件夹,在里面就是对应的网页覆盖率报告。
用浏览器打开 index.html 就可以看到最开始展示的覆盖率信息了。
特殊环境使用注意点:
在正常使用 gcov 是非常简单的,但是在特殊项目中使用 gcov 需要注意一些坑,否则就会跟我一样掉进去出不来。。。
链接时,会在 .init_array 段插入 __gcov_init() 函数,该函数在 main 运行之前初始化 gcov 运行环境。
如果修改过链接脚本,注意 .init_array 的全局构造函数是否执行成功。
和上面一样,链接时,会在全局析构函数中插入 __gcov_exit() 函数,在 main 执行结束后,输出 *.gcda 文件。
如果程序为异常退出,则不会生成 *.gcda 文件,此时需要在代码适当位置插入 __gcov_flush() 函数,将文件进行保存。
默认状态 *.gcda 文件和 *.gcno 文件所在文件夹相同,如果需要修改输出文件夹,可通过添加环境变量:GCOV_PREFIX 和 GCOV_PREFIX_STRIP
GCOV_PREFIX_STRIP=16 , 为将原有路径裁剪16个文件夹。
GCOV_PREFIX=/home/tester/build , 为将裁剪后的路径添加前缀
例如:上文中默认 main.gcda 所在的文件 /home/tester/main.gcda,裁剪后添加前缀后变为/home/tester/build/main.gcdagcov 内部使用了一些系统函数,需要确保这些哈是函数可用
实现原理
gcov 实现基本原理为在编译时,向代码中插桩,记录运行时的执行流,具体细节可见参考链接两位大佬的详细分析,我就在在此赘述了。