文章目录
GCOV 工具简介
gcov是一个测试代码覆盖率的工具,它是 gcc 自带的查看代码覆盖率的工具。
与GCC结合使用,可以分析您的程序以帮助创建更高效、运行更快的代码,并发现程序中未经测试的部分。您可以将gcov作为性能分析工具使用,以帮助发现优化工作对代码产生最佳效果的位置。您还可以将gcov与另一种性能分析工具gprof一起使用,以评估代码的哪些部分使用了最多的计算时间。
性能分析工具可以帮助您分析代码的性能。使用像gcov或gprof这样的分析器,您可以找出一些基本的性能统计数据,例如:
- 每行代码执行了多少次
- 实际执行了哪些代码行
- 每个代码段使用了多少计算时间
一旦您了解了编译后的代码如何工作,就可以查看每个模块,看看哪些模块应该进行优化。gcov可以帮助您确定在哪里进行优化工作。
软件开发人员还结合测试套件使用覆盖率测试,以确保软件实际上足够好,可以发布。测试套件可以验证程序按预期工作;覆盖率程序测试测试套件执行了程序的多少部分。然后,开发人员可以确定需要向测试套件中添加哪些类型的测试用例,以创建更好的测试和更好的最终产品。
如果您计划使用gcov,应该在不优化的情况下编译代码,因为通过将一些代码行组合成一个函数的优化,可能无法为您提供寻找"热点"所需的信息,即代码使用大量计算时间的地方。同样,因为gcov按行(最低分辨率)累积统计数据,它最适合一种编程风格,即每行只放置一个语句。如果您使用复杂的宏,这些宏展开为循环或其他控制结构,统计数据就不那么有用------它们只报告宏调用出现的行。如果您的复杂宏表现得像函数,您可以用内联函数替换它们来解决这个问题。
gcov创建一个名为sourcefile.gcov的日志文件,指示源文件sourcefile.c的每一行执行了多少次。您可以将这些日志文件与gprof一起使用,以帮助微调程序的性能。gprof提供您可以与从gcov获得的信息一起使用的计时信息。
gcov仅适用于用GCC编译的代码。它与任何其他性能分析或测试覆盖机制不兼容。
使用效果如下图所示:
程序运行完成后,可以查看每个文件的代码覆盖率情况,上面报告中展示了每个文件的行覆盖率,函数覆盖率和分支覆盖率。
打开一个文件的覆盖率报告,页面对开始有文件的基本信息描述,以 FreeRTOS 的 task.c 为例,它的有效代码行数为 921 行,共 24 个函数(几千行的文件其实也没多少嘛~)
在覆盖率的正文,有该文件的完整代码,并用不同颜色进行高亮标注了:
- 蓝色表示运行被覆盖的代码,前面的数字表示代码执行次数。
- 红色表示未执行代码。
- 白色表示无效代码,包括注释,空行和未编译代码。
gcov 使用
使用 gcov 的流程非常简单,只需要三步即可。 下面以 hello world 为例,展示生成覆盖率报告。
代码如下:
c
//main.c
#include <stdio.h>
int main(int argc, char **argv) {
printf("hello world\n");
return 0;
}
第一步: 添加编译参数 -fprofile-arcs -ftest-coverage
在所需要的生产覆盖率的文件中,添加编译参数,编译代码生成目标文件,同时会生成 *.gcno 文件,其中包含文件的行号等信息。
GCC C/C++代码覆盖率统计生成
shell
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 文件,里面包含代码执行次数等数据。
shell
gcc main.o -lgcov -o main
# 运行程序
./main
# hello world
ls # 输出文件列表:
#main main.c main.gcda main.gcno main.o
第三步: 输出覆盖率报告 使用下面命令输出覆盖率报告
shell
# 第一次使用前安装工具
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 就可以看到最开始展示的覆盖率信息了。
lcov
lcov 是 GCC 覆盖率测试工具 gcov 的图形化前端,用于收集多个源文件的行、函数和分支覆盖率数据,并创建包含覆盖信息的 HTML 页面。它还添加了概览页面,以便在文件结构中进行方便的导航。
使用 lcov 收集覆盖率数据,并使用 genhtml 创建 HTML 页面。覆盖率数据可以从当前正在运行的 Linux 内核或用户空间应用程序中收集。要做到这一点,您需要完成以下准备步骤:
- 收集覆盖率数据 :首先,使用 lcov 工具来收集代码的覆盖率数据。您可以通过在编译时添加
-ftest-coverage
和-fprofile-arcs
选项来生成覆盖率数据的中间文件。然后,通过运行应用程序或测试套件来执行代码路径,以便生成覆盖率数据。 - 生成 HTML 页面:接下来,使用 genhtml 工具将收集到的覆盖率数据转换为 HTML 页面。genhtml 将分析覆盖率数据,并生成带有覆盖信息注释的源代码的 HTML 页面,以及用于导航文件结构的概览页面。
这些工具的结合使用使得开发人员能够更直观地查看代码的覆盖情况,从而更好地理解测试覆盖范围和质量。通过分析生成的 HTML 页面,开发人员可以识别哪些部分的代码需要更多的测试覆盖,以及哪些部分已经得到了良好的覆盖。
总的来说,lcov 和 genhtml 工具的组合为开发人员提供了一种强大的方式来收集和可视化代码覆盖率数据,从而帮助他们更好地进行测试和代码质量管理。如果您需要进一步解释或有其他问题,请随时告诉我,我很乐意为您提供帮助。
关键选项:
-r tracefile pattern
--remove tracefile pattern
Remove data from tracefile.
Use this switch if you want to remove coverage data for a particular set of files from a tracefile. Additional command line parameters will be interpreted as shell wildcard patterns (note
that they may need to be escaped accordingly to prevent the shell from expanding them first). Every file entry in tracefile which matches at least one of those patterns will be removed.
Note: The pattern must be specified to match the absolute path of each source file.
The result of the remove operation will be written to stdout or the tracefile specified with -o.
Only one of -z, -c, -a, -e, -r, -l, --diff or --summary may be specified at a time.
--exclude pattern
Exclude source files matching pattern.
Use this switch if you want to exclude coverage data for a particular set of source files matching any of the given patterns. Multiple patterns can be specified by using multiple --exclude
command line switches. The patterns will be interpreted as shell wildcard patterns (note that they may need to be escaped accordingly to prevent the shell from expanding them first).
Note: The pattern must be specified to match the absolute path of each source file.
Can be combined with the --include command line switch. If a given file matches both the include pattern and the exclude pattern, the exclude pattern will take precedence.
--include pattern
Include source files matching pattern.
Use this switch if you want to include coverage data for only a particular set of source files matching any of the given patterns. Multiple patterns can be specified by using multiple --in‐
clude command line switches. The patterns will be interpreted as shell wildcard patterns (note that they may need to be escaped accordingly to prevent the shell from expanding them first).
Note: The pattern must be specified to match the absolute path of each source file.
--rc keyword=value
Override a configuration directive.
Use this option to specify a keyword=value statement which overrides the corresponding configuration statement in the lcovrc configuration file. You can specify this option more than once to
override multiple configuration statements. See lcovrc(5) for a list of available keywords and their meaning.
相关编译选项
- -fprofile-arcs
该选项用于在程序执行时对程序流程弧进行插装,记录每个分支和调用被执行的次数以及它们被取或返回的次数。对于支持具有优先级支持的构造函数的目标,剖析能够正确处理构造函数、析构函数以及用作全局变量类型的类的C++构造函数(和析构函数)。
当编译后的程序退出时,会将这些数据保存到名为 auxname.gcda
的文件中,每个源文件都会生成一个相应的 .gcda
文件。这些数据可以用于基于profile-directed 的优化(例如 -fbranch-probabilities
),或者用于测试覆盖率分析(例如 -ftest-coverage
)。每个目标文件的 auxname
是从输出文件的名称生成的,如果显式指定了输出文件并且它不是最终可执行文件,则 auxname
会采用源文件的基本名称。在这两种情况下,任何后缀都会被移除(例如对于输入文件 dir/foo.c
,auxname
为 foo.gcda
;对于指定为 -o dir/foo.o
的输出文件,auxname
为 dir/foo.gcda
)。
简而言之,通过使用 -fprofile-arcs
编译选项,您可以收集程序在执行过程中的代码覆盖率和执行路径信息,从而可以进行优化或测试覆盖率分析。
- --coverage
这个选项是 -fprofile-arcs -ftest-coverage
(编译时)和 -lgcov
(链接时)的同义词。主要做了以下工作:
- 使用
-fprofile-arcs
和优化、代码生成选项来编译源文件。对于测试覆盖率分析,还需额外加上-ftest-coverage
选项。并不需要对程序中的每个源文件都进行剖析。 - 另外使用
-fprofile-abs-path
选项来编译源文件,以在.gcno
文件中创建绝对路径名。这样可以让 gcov 在项目中的编译发生在不同工作目录的情况下正确找到源文件。 - 使用
-lgcov
或-fprofile-arcs
(后者暗含了前者)来链接您的目标文件。 - 运行程序以生成弧剖析信息。可以多次重复这个过程。您可以同时运行多个程序实例,只要文件系统支持锁定,数据文件就会被正确地更新。除非启用了严格的 ISO C 方言选项,否则 "fork" 调用会被检测并正确处理,而不会重复计数。
- 对于基于profile-directed的优化,需要使用相同的优化和代码生成选项再次编译源文件,并加上
-fbranch-probabilities
。 - 对于测试覆盖率分析,使用 gcov 从
.gcno
和.gcda
文件中生成人类可读的信息。请参考 gcov 文档以获取更多信息。
特殊环境使用注意点:
在正常使用 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.gcda