gcov代码覆盖率使用gcov完成代码覆盖率的测试

2023-10-29

 

Gcov作为gnu/gcc工作组件之一,是一款的免费的代码覆盖率测试工具,而且可以结合lcov生成美观的html的测试报表。本文介绍一些gcov的使用方法,基本原理,一些实际中可能会遇到的问题以及解决思路。

  1. 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 <stdio.h>
#include <stdlib.h>
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如下:

gcov

第一列是覆盖情况,第二列是行号加源程序,其中第一列中数字开头的是执行的次数,####开头的是没有执行到的语句。

2. Gcov的实现原理简介

Gcc中指定-ftest-coverage 等覆盖率测试选项后,gcc 会:

  • 在输出目标文件中留出一段存储区保存统计数据
  • 在源代码中每行可执行语句生成的代码之后附加一段更新覆盖率统计结果的代码
  • 在最终可执行文件中进入用户代码 main 函数之前调用 gcov_init 内部函数初始化统计数据区,并将gcov_exit 内部函数注册为 exit handlers
  • 用户代码调用 exit 正常结束时,gcov_exit 函数得到调用,其继续调用 __gcov_flush 函数输出统计数据到 *.gcda 文件中

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 <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" );
         }
     }
}

编译:

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]。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

gcov代码覆盖率使用gcov完成代码覆盖率的测试 的相关文章

  • 重写继承的构造函数字段时的差异?

    考虑这个简单的 Scala 类 class A val d Int Scala 之间是否存在差异 无论是行为还是生成的字节码 class B d Int extends A d and class B override val d Int
  • 使用 AVX 内在函数代替 SSE 并不能提高速度 - 为什么?

    我已经使用 Intel 的 SSE 内在函数相当长一段时间了 并取得了良好的性能提升 因此 我希望 AVX 内在函数能够进一步加速我的程序 不幸的是 直到现在情况并非如此 可能我犯了一个愚蠢的错误 所以如果有人能帮助我 我将非常感激 我使用
  • 用更有意义的文本替换 GCC 输出中的 a-hats [重复]

    这个问题在这里已经有答案了 可能的重复 为什么 gcc 的所有错误消息中都有 https stackoverflow com questions 547071 why does gcc have a in all its error mes
  • `printf()` 中格式说明符“%qd”的用途是什么?

    我看到格式说明符 qd浏览时github https github com Microsoft clang blob master test Sema format strings c代码 然后我检查了 GCC 编译器 它工作正常 incl
  • 在 Linux 上将 libquadmath 与 C++ 链接

    我有一个示例代码 include
  • Ruby 对象打印为指针

    我正在尝试创建一个类 它有一个带有单个参数的构造函数 当我创建该对象的新实例时 它返回一个指针 class Adder def initialize my num my num my num end end y Adder new 12 p
  • 为什么 ld 无法从 /etc/ld.so.conf 中的路径找到库?

    我想添加 opt vertica lib64进入系统库路径 所以我执行以下步骤 1 添加 opt vertica lib64 into etc ld so conf 然后运行ldconfig 2 检查 bash ldconfig p gre
  • 反对“initialize()”方法而不是构造函数的争论

    我目前负责查找代码库中的所有不良做法 并说服我的同事修复有问题的代码 在我的探索过程中 我注意到这里很多人都使用以下模式 class Foo public Foo Do nothing here bool initialize Do all
  • 如果在构造函数中使用 super 调用重写方法会发生什么

    有两个班级Super1 and Sub1 超1级 public class Super1 Super1 this printThree public void printThree System out println Print Thre
  • 错误:“uint16_t”未声明? [复制]

    这个问题在这里已经有答案了 我有代码 include
  • gcc 中 -g 选项的作用是什么

    我看到很多关于 gdb 的教程要求在编译 c 程序时使用 g 选项 我无法理解 g 选项的实际作用 它使编译器将调试信息添加到生成的二进制文件中 此信息允许调试器将代码中的指令与源代码文件和行号相关联 拥有调试符号可以使某些类型的调试 例如
  • gcc 中的“假设”子句

    gcc 最新版本 4 8 4 9 是否有类似于以下的 假设 子句 assume 内置icc支持吗 例如 assume n 8 0 从 gcc 4 8 2 开始 gcc 中没有 assume 的等效项 我不知道为什么 这会非常有用 马夫索建议
  • Ubuntu 11.10 上的 c 数学链接器问题 [重复]

    这个问题在这里已经有答案了 我从 Ubuntu 升级后出现了一些奇怪的错误 10 11 11 04 i dont know 到 11 10 我正在得到一个undefined reference to sqrt 使用 math h 时并与 l
  • arm-linux-gnueabi 编译器选项

    我在用 ARM Linux gnueabi gcc在 Linux 中为 ARM 处理器编译 C 程序 但是 我不确定它编译的默认 ARM 模式是什么 例如 对于 C 代码 test c unsigned int main return 0x
  • 在构造函数中调用可重写的方法,例如 Swing 的 add()

    我知道从构造函数调用可重写的方法是一个坏主意 但我也看到到处都是用 Swing 完成的 其中代码如下add new JLabel Something 一直出现在构造函数中 以 NetBeans IDE 为例 它对构造函数中的可重写调用非常挑
  • 为什么是 ”\?” C/C++ 中的转义序列?

    C C 中有四种特殊的非字母字符需要转义 单引号 双引号 反斜杠 和问号 显然是因为它们有特殊的含义 对于单身char 对于字符串文字 对于转义序列 但为什么是 其中之一 我今天读了教科书上的转义序列表 我意识到我已经never逃脱了 以前
  • PHP7构造函数类名

    我有一个 Laravel 4 2 应用程序 它可以与 PHP5 一起使用 没有任何问题 由于我安装了一个运行 PHP7 的新 vagrant box 一旦我运行一个模型 其中函数名称与类名称 关系函数 相同 就会出现错误 如下所示
  • C++派生模板类继承自模板基类,无法调用基类构造函数[重复]

    这个问题在这里已经有答案了 我试图从基类 模板 继承 派生类也是模板 它们具有相同的类型 T 我收到编译错误 非法成员初始化 Base 不是基类或成员 为什么 如何调用基类构造函数 include
  • 如何在 GCC 5 中处理双 ABI?

    我尝试了解如何克服 GCC 5 中引入的双重 ABI 的问题 但是 我没能做到 这是一个重现错误的非常简单的示例 我使用的GCC版本是5 2 如您所见 我的主要函数 在 main cpp 文件中 非常简单 main cpp include
  • PHP - 扩展 __construct

    我想知道你是否可以帮助我 我有两个类 一个扩展了另一个 B 类将由各种不同的对象扩展 并用于常见的数据库交互 现在我希望 B 类能够处理其连接和断开连接 而无需来自 A 类或任何外部输入的指示 据我了解 问题是扩展类不会自动运行其 cons

随机推荐

  • 在VSCode中移除不必要的扩展

    在VSCode中移除不必要的扩展 在VSCode中安装扩展是编辑器缓慢且耗电的主要原因之一 因为添加的每个新扩展都会增加应用程序的内存和 CPU 使用率 VSCode现在已经具备了非常多的功能 我们可以将一些重复工作的扩展移除掉 卸载这些现
  • 统计学习方法——EM算法及其推广(一)

    统计学习方法 EM算法及其推广 EM算法及其推广 一 EM算法引入 EM算法 EM算法的导出 可不看 在非监督学习中的应用 EM算法的收敛性 参考文献 EM算法及其推广 一 EM算法 期望极大算法 是一种迭代算法 用于含有隐变量的概率模型参
  • ( 22美赛C题)基于投资最优交易策略的研究(部分内容)

    投资目前成为了一种十分流行的理财方式了 据相关数据 我们了解到黄金与比特币在金融市场上价格都具有波动性 市场交易员不定期的买入和卖出资产 其目的是使其回报最大化 本文就基于该目的对黄金与比特币五年的价格变化进行了分析 并通过建立相关模型进行
  • 网页显示正在加载安全连接服务器,QQ网页显示正在加载页面请稍候是什么意思,打不开...

    问题描述 网页游戏黑屏 白屏 IE导致的无法调出flash 原因分析 Flash游戏无法运行时 可能由于IE内核导致无法调出flash插件导致 我们尝试通过对IE的升降级进行测试 以下是IE升降级的教程 简易步骤 IE降级 360安全卫士
  • java中file操作

    File fo new File E pic old txt File f new File E pic new File fn new File E pic new test txt 1 创建文件夹 boolean mkdir 创建此抽象
  • vue3项目总结

    1 Pinia优化重复请求 在项目中 吸顶组件和首页的头部内容是一样的 所以不用发送两次请求 通过Pinia集中管理数据 再把数据给组件使用 只需要把请求封装在一个store里 调用即可使用 2 面板组件的封装 由于项目中的新鲜好物和人气推
  • 格 (数学)

    格 数学 维基百科 自由的百科全书 本文介绍的是 数学中的格 关于与 格 数学 同名的其他主题 详见 格 术语 格 lattice 来源于描述这种次序的 哈斯图的形状 在数学中 格是其非空有限子集都有一个上确界 叫并 和一个下确界 叫交 的
  • Linux常用技巧系列: Linux创建软链接ln -s,(更改cuda版本,从8.0到9.0,Cuda多版本共存, 图文教程)

    创建软连接在系统崩溃的时候也是经常用的功能 如果你已经需要用到 说明你对Linux系统已经有了一定的熟练程度 尤其在配置和修复mysql 配置cuda 不同版本的切换的时候 会用到 用法也非常简单 ln s source dir targe
  • Python数据结构:解锁高效编程

    今天 我们一起探索Python数据结构 以及它们如何利用他们编写高效和优雅的代码 为什么数据结构很重要 想象一下 您正在建造一座房子 您不会随意将砖块扔在一起 对吧 您会仔细规划并安排它们 以创建坚固的结构 嗯 编程也适用同样的原则 数据结
  • windows中的凭据管理

    前言 我们访问某个 带有密码的共有文件夹之后 只有在第一次访问的时候需要输入密码 只要记住密码 今后就可以一直访问 如何实现 通过windows的凭据管理来实现 如何查看凭据管理 step1 控制面板 step2 用户账户 凭据管理器 访问
  • 期货交易心得 Round 4

    期货市场永远是有赌性的 大家都在博弈 期货市场具有偶然性 不像其他行业 只要是一个优秀的企业家他的赢利就包含了更多的必然性 既然有赌性就涉及到赌的原则方法问题 首先的原则应该是赌钱不赌命 这里包括两方面含义 第一应该拿出你亏得起的钱到期货市
  • 大学生团体天梯赛(第三届)

    题目地址 天梯赛 include
  • 与失眠危机说再见,AI为你带来安宁的夜晚

    不知道你有没有这样的感觉 忙碌了一天 明明已经很累了 可依旧辗转反侧 难以入眠 只能睁着眼睛熬到天亮 不是不想睡 也不是不累 只是睡不着 失眠的感觉实在是太痛苦了 特别是第二天早上常常顶着两只熊猫眼 干什么都提不起劲 身体仿佛要散架 为了拥
  • JS 实现队列

    通过JS实现队列的数据结构 首先是最普通的队列 先入先出 队列 function createQueue 队列 let queue 入队 const enQueue data gt if data null return queue pus
  • Python爬虫进阶必备

    XX街登陆密码加密 aHR0cDovL3NlbGxlci5jaHVjaHVqaWUuY29tL3NxZS5waHA cz0vVXNlci9pbmRleA 这个加密太简单了 五秒定位真的不是吹 所以直接来 输入错误的账号密码 发起登陆请求 可
  • SQL DATEPART()函数

    DATEPART datepart date 参数 datepart 是将为其返回 integer 的 date 日期或时间值 的一部分 下表列出了所有有效的 datepart 参数 用户定义的变量等效项是无效的 下表列出了所有 datep
  • 不涨薪的公司应不应该待?

    一个 5 年老员工 要求加薪 500 元遭拒 老板转头月薪 1 万招新人 结果 朋友出去转了一圈 找了个工资多 4000 的工作 立马就跳槽了 剩下 3 个人不干了 纷纷出去找工作 也找到了比之前多 4000 的工作 准备离职 老板一下子慌
  • 第十四章 网络

    一 客户端 服务器计算 Java提供ServerSocket类来创建服务器套接字 Socket类来创建客户端套接字 Internet 上的两个程序通过使用IO流的服务器套接字和客户端套接字进行通信 网络功能紧密地集成在Java中 Java
  • 情感分析学习笔记(3)——情感传播(sentiment propagation)

    sentiment propagation是我最近看论文最经常遇到的一个单词 并且网上这一块资源极其稀少 大部分都是新闻学或者心理学的论文 所以本文就谈谈我对情感传播的理解 Thanks to knowledge graph 让我能够百度的
  • gcov代码覆盖率使用gcov完成代码覆盖率的测试

    Gcov作为gnu gcc工作组件之一 是一款的免费的代码覆盖率测试工具 而且可以结合lcov生成美观的html的测试报表 本文介绍一些gcov的使用方法 基本原理 一些实际中可能会遇到的问题以及解决思路 Gcov的用法 1 1 编译 Gc