您现在的位置是:首页 > 正文

覆盖率工具gcov,lcov实践

2024-02-01 01:40:25阅读 1

覆盖率工具gcov,lcov实践记录

背景

本文记录的是gcov和lcov在统计一个分布式数据库的代码覆盖率过程中遇到的问题。

介绍

*gcov是一个测试代码覆盖率的工具,随着gcc一起发布,不需要进行特别安装;而lcov是配合gcov生成覆盖率文件的工具,需要单独安装。
*lcov是基于源码安装的,下载源码解压后make install即可安装。在安装完成后,使用lcov -v可以正确查询到安装的lcov版本。

使用流程

1.首先,在使用前配置各个子目录下的Makefile.am文件,加入编译开关:
if COVERAGE:
CXXFLAGS+=-fprofile-arcs -ftest-coverage
AM_LDFLAGS+=-lgcov
endif

2.之后,使用如下编译命令进行编译:

sh build.sh init
./configure --perfix=~/build --with-test-case=no --with-coverage=yes
make -j all -C src/
make install

注意:编译过程中,不能加入–with-release参数;若加入该参数可能会导致产生的覆盖率文件只统计构造函数,而忽略其他函数,原因暂时未知。

3.编译完成后,使用deploy跑测试程序,并统计测试覆盖率,命令大体如下:

lcov --rc lcov_branch_coverage=1 -c -o ~/info/xxx.info -d .

4.基于统计出的覆盖率info文件,我们准备对其进行汇总分析,获得代码覆盖率的完整信息。但是在后续统计中发现,一些必然会跑到的入口函数在覆盖率文件中并没有被统计到,所以展开这次调查。

遇到的问题

1.Can’t locate PerlIO/gzip.gm in @INC ,该错误是在使用lcov工具收集info文件时产生的,使用的命令为:

lcov --rc lcov_branch_coverage=1 -c -o ~/info/xxx.info -d .

这个错误是由于缺少PerlIO/gzip.gm和JSON.pm包导致的,找到对应的包安装即可。(本文是在离线环境下进行的,所以部分依赖没有一并安装)

2.覆盖率统计文件info中只统计了构造函数,其他函数都没有被统计
1)起初,发现部分必然会被覆盖到的函数在info文件的统计信息中值为0,代表该函数未被覆盖,所以基于这些函数入手,开始追踪这些函数的上层逻辑有无覆盖;
2)在追踪上层函数时发现,这几个函数都会进入一个名为xxx.so的动态链接库,于是猜想可能是gcov经过动态链接库后无法追踪到下层函数,于是写了一个简单的c程序进行验证,结论是gcov可以经过.so动态链接库追踪到下层函数,但是.so内的函数gcov并不进行统计。验证过程记录如下:
a.验证过程中使用了三个.cpp文件和一个头文件,分别为test.cpp,add.cpp,sub.cpp,head.h具体内容如下:
test.cpp:

#include <stdio.h>
#include "head.h"

int main() {
	int a = 0;
	int b = add(a);
	printf("main is %d\n", b);
	return 0;

add.cpp:

#include <stdio.h>
#include "head.h"

int add(int a) {
	a += 1;
	printf("add is %d\n", a);
	a = sub(a);
	return a;

sub.cpp:

#include <stdio.h>
#include "head.h"

int add(int a) {
	a -= 1;
	printf("sub is %d\n", a);
	return a;

b.将add.c文件编译为add.so文件,编译指令如下:

gcc -fPIC -shared -o add.so add.c

c.将所有文件编译,并在编译时增加gcov相关的编译选项,编译命令如下:

gcc -fprofile -ftest-coverage -o test test.c sub.c head.h ./add.so

d.编译完成后运行可执行文件test,运行后会生成gcda文件,使用gcov指令运行gcda文件即可在后缀为.gcov文件中看到覆盖率信息。在生成gcda文件时可以看到,动态链接库对应的add.gcda文件并没有生成。整个过程的编译命令如下:

./test
gcov test.gcda

3)到此,自底向上追踪上层函数确定问题的方法已经不能再继续,因此,选择从上而下的方式,选择一个sql语句,追踪sql语句完整的执行流程,以便找到问题所在。在本次查找问题中选择的是insert语句,通过debug加看源码的方式,对insert语句进行追踪,从词法解析,语法解析,逻辑计划产生,物理计划产生以及物理计划的执行整个流程,在跟踪过程中发现即便是最外层的函数,在覆盖率统计时也不会覆盖到。基于此,我们不再细究代码层面的问题,而是将调查重心转到数据库源码编译的方式gcov工具的使用方法受否有错误等问题上。

4)初始的数据库源码编译方式如下:

./configure --perfix=~/build --with-test-case=no --with-coverage=yes --with-release=yes

我们尝试修改–with-release编译选项的值,将其分别设置为yes,no,结果没有发生变化;但是当我们把–with-release编译选项去掉后,在部分覆盖率文件中出现了除了构造函数以外的函数被覆盖到的情况(原因未知),这说明–with-release这个编译选项可能是覆盖率文件不完整的原因。修改后的编译源码的方式:

./configure --perfix=~/build --with-test-case=no --with-coverage=yes 

5)经过多次验证,在去掉–with-release编译选项后,确实会产生部分正确的覆盖率文件,但是仍然有部分覆盖率文件时错误,其中只有构造函数才被统计到。

6)可以预见,还存在某些因素影响覆盖率文件的生成。在反复实验过程中,我们发现,杀死进程的方式会影响覆盖率文件的生成kill -9 杀死进程会导致****程序直接终止,没有将gcov守护进程所统计的信息添加到覆盖率文件中,所有导致覆盖率文件不生成在使用kill -15杀死进程后,在多数情况下会产生覆盖率文件,但是有时还是不产生覆盖率文件,于是对gcov刷新覆盖率文件的方式进行了了解。

7)在gcov中,守护进程之后在完全执行完成后,在返回结果进入用户代码 main 函数之前调用 gcov_init 内部函数初始化统计数据区,并将gcov_exit 内部函数注册为 exit handlers。用户代码调用 exit 正常结束时,gcov_exit 函数得到调用,其继续调用 __gcov_flush 函数输出统计数据到 *.gcda 文件中;

8)在gcov原理中,__gcov_flush函数是将数据写入.gcda覆盖率文件的核心,所以考虑在执行kill 进程时,主动调用__gcov_flush函数,将覆盖率信息写入文件中

9)本次实验所用的数据库源码只针对kill -15做了处理,因此在kill -15命令杀死进程过程中主动调用__gcov_flush函数,具体细节如下:
a) 数据库每次启停的服务包括rs,cs,ups,ms,通过跟踪kill -15 不同进程的流程发现,rs和ms时通过同一个方法启停服务,ups和cs是通过同一个方法启停服务,于是在两个启停服务的方法中加入了__gcov_flush函数。所加代码如下:

# 在对应的.c文件头部,加入引用
extern "C" void __gcov_flush(void);
# 在对应的方法体中加入__gcov_flush方法
__gcov_flush();

b)为了不影响原来程序的编译,在加入__gcov_flush函数的地方引入宏(宏已经在源码中定义过),判断是否需要编译相应的代码行,如下:

# 在对应的方法体中加入__gcov_flush方法
# ifdef COVERAGE
__gcov_flush();
# endif
  1. 至此,整个问题已解决。

网站文章

  • Java 进程 CPU 100% 问题排查

    Java 进程 CPU 100% 问题排查

    如果在时间片内,线程一直占有,则认为是 CPU 100% 。CPU 运行速度很快,即主频非常高,除非密集型耗费 CPU 的运算,其它类型任务一般都会在小于时间片的时间内结束。

    2024-02-01 01:40:19
  • spring源码-bean生命周期

    spring源码-bean生命周期

    创建非懒加载单例bean。

    2024-02-01 01:40:13
  • 调整elasticsearch的jvm heap值

    集群里有两台elasticsearch老是服务死机,报错大致如下:Mar 26 08:19:47 es104 systemd[1]: Started Elasticsearch.Mar 27 09:5...

    2024-02-01 01:40:06
  • 【转载】C# 日常小问题:DataGridview转换为DataTable

    【转载】C# 日常小问题:DataGridview转换为DataTable

    C# dgv与datatable转换

    2024-02-01 01:39:36
  • 芯片设计之CDC异步电路(二)

    芯片设计之CDC异步电路(二)

    上文芯片设计之CDC异步电路(一)对比讲述了异步电路在前仿真中的行为与实际芯片的行为区别,本文继续讲几种常用的异步电路处理。(一)单bit信号同步器最经典的2DFF 1-bit同步器如下,下图结构通常...

    2024-02-01 01:39:26
  • Java-集合(LinkedHashSet类)

    Java-集合(LinkedHashSet类)

    1)LinkedHashSet是HashSet的子类;2)LinkedHashSet底层是一个LinkedHashMap,底层维护了一个数组+双向链表;3)LinkedHashSet根据元素的hashCode值来决定元素的存储位置,同时使用链表维护元素的次序,这使得元素看起来是以插入顺序保存的;4)LinkedHashSet不允许添加重复元素。...

    2024-02-01 01:39:19
  • configure参数的理解

    configure脚本有大量的命令行选项.对不同的软件包来说,这些选项可能会有变化,但是许多基本的选项是不会改变的.带上--help选项执行configure脚本可以看到可用的所有选项.尽管许多选项是很少用到的,但是当你为了特殊的需求而configure一个包时,知道他们的存在是很有益处的.下面对每一个选项进行简略的介绍:--cache-file=FILEconfigure

    2024-02-01 01:38:49
  • 方向标Directional Sign

    方向标Directional Sign

    方向标题目信息输入输出注测试样例解答题目信息A carpenter has received an order for a wooden directional sign. Each board mu...

    2024-02-01 01:38:43
  • postgresql遇到“Connection refused”和“No route to host”大概的解决方法

    一般我们遇到这个问题的时候都是连接远程主机的时候,会出现这个报错 psql: could not connect to server: connection refused Is the server...

    2024-02-01 01:38:37
  • 数据结构(陈越、何钦铭)学习笔记

    数据结构(陈越、何钦铭)学习笔记

    本系列文章为浙江大学陈越、何钦铭数据结构学习笔记,系列文章链接如下:文章目录一、基本概念二、线性结构三、树四、图五、排序六、散列查找一、基本概念数据结构基础:P1-基本概念数据结构基础:P1-基本概念...

    2024-02-01 01:38:04