关于C++ code coverage tool 的研究(1)

关于覆盖测试的基本概念可以上网查阅,这里直接从研究对比开始讲吧。因为内容太多,开始之前先给个目录:
(1)覆盖测试工具的简要对比
(2)LINUX下工具GCOV的实现原理
(3)LINUX下工具GCOV的使用说明
  (4 )  WINDOWS下工具coverage validator原理与使用说明
(5)修改GCOV适用于分布式测试覆盖率统计原理与方法

下面是部分C++ code coverage tool 的一个粗略的对比表格。这里重点研究了GCOV,COVTOOL,coverage validator,后续有时间的话,会针对旺旺重点研究下XCOVER. 有些工具没有具体研究,有兴趣的话,可以查看相应链接。

  Free orcommercial Platform/complier Coverage level Show Execute counters Easy to use output Useable in large implement others
gcov  Free Linux ,only gcc Decision coverage Yes yes HTML report No Instrument as compiling nonsupport shared library
COVTOOL  Free linux Line boolean no Merge.db, ASCII report yes Instrument as compiling Nonsupport thread
xcover  Free platform-independent library, gcc4.3+ Line coverage Yes no HTML report ? Source file only,denpend on stlsoft Written in c
GCT Free Unix,linux,Only for c Branch/condition no yes No HTML yes Instrument as compiling Only for c,work well with if,case,for
Coverage validator Commercial Windows decision yes yes HTML,XML, yes No recompile,With pdb file Powerful filters,do not affect performance
BullseyeCoverage  Commercial Windows,unix, Branch/condition no yes Csv report,Easy to change to perl yes hook Work well with vc,cppunit
Pure coverage Commercial Unix,windows decision yes yes HTML,XML, yes Object Code 


1、背景介绍

GCOV是一个GNU的本地覆盖测试工具, 伴随GCC发布,配合GCC共同实现对C或者C++文件的语句覆盖和分支覆盖测试。是一个命令行方式的控制台程序。需要工具链的支持。

LCOV由 IBM 开发,由 Linux Test Project 维护的开放源代码工具。是GCOV结果展现的一个前端。这个工具由一组构建于基于文本的GCOV 输出之上的 PERL 脚本构成,以实现基于 HTML 的输出, 并生成一棵完整的 HTML 树。输出包括覆盖率百分比、图表以及概述页,可以快速浏览覆盖率数据。支持大项目,提供三种等级视图,分别为目录视图,文件视图,源代码视图。

2、GCOV分析

2.1 基本概念

1. 基本块BB

如果一段程序的第一条语句被执行过一次,这段程序中的每一个都要执行一次,称为基本块。一个BB中的所有语句的执行次数一定是相同的。一般由多个顺序执行语句后边跟一个跳转语句组成。所以一般情况下BB的最后一条语句一定是一个跳转语句,跳转的目的地是另外一个BB的第一条语句,如果跳转时有条件的,就产生了分支,该BB就有两个BB作为目的地。

下图是个典型的基本块:

2.跳转ARC

从一个BB到另外一个BB的跳转叫做一个arc,要想知道程序中的每个语句和分支的执行次数,就必须知道每个BB和ARC的执行次数

3.  程序流图

如果把BB作为一个节点,这样一个函数中的所有BB就构成了一个有向图。,要想知道程序中的每个语句和分支的执行次数,就必须知道每个BB和ARC的执行次数。根据图论可以知道有向图中BB的入度和出度是相同的,所以只要知道了部分的BB或者arc大小,就可以推断所有的大小。

这里选择由arc的执行次数来推断BB的执行次数。

所以对部分 ARC插桩,只要满足可以统计出来所有的BB和ARC的执行次数即可。

以下是针对某个函数的程序流图:

2.2 GCOV原理与实现

2.2.1原理简介

GCOV是一个纯软件的覆盖测试工具,被测程序的预处理,插桩和编译成目标文件三个步骤由GCC一次完成。GCOV本身只负责数据处理和结果显示,下图是GCOV的工作原理。

gcov工作原理

从左图可以看出,GCOV统计覆盖率主要包括三个阶段:

l  编译阶段:

加入编译选项gcc –o hello –fprofile-arcs –ftest-coverage hello

除了为每个C文件生成*.o目标文件以外还要生成数据文件*.bb和*.bbg(在早期的GCC版本中是包含这两个文件,后期变成*.gcno文件,但是内部仍然包含这两个文件的结构),分别记录行信息和程序流图信息,供GCOV计算覆盖率时用。

l  数据收集与提取阶段:

./hello 执行时收集数据。

被测程序运行后为每个源文件生成一个*.da数据文件,后期编译器成为.gcda文件,分别记录了*.c文件中程序的执行情况。

l  报告形成阶段:

gcov –a hello.c 收集某个源文件的覆盖率情况

执行后会生成输出文件覆盖率情况,可以重定向保存到某个文件中,同时生成hello.c.gcov形式的文件,文件格式是带有标示信息的源文件。

从右边图可以看出,GCOV的插桩时段,是在编译阶段完成:

被测程序文件首先经过编译预处理,然后编译成汇编文件,在生成汇编文件的同时完成插桩。插桩是在生成汇编文件的阶段完成的,因此插桩是汇编时候的插桩,每个桩点插入3~4条汇编语句,直接插入生成的*.s文件中,最后汇编文件汇编生成目标文件。在程序运行过程中桩点负责收集程序的执行信息。

2.2.2 覆盖率收集过程

gcov的实现源文件包括:coverge.c, gcov.c, gcov-io.c, libgcov.c, profile.c ,libgcc2.c以及他们的头文件,以下从具体的三个阶段来讨论覆盖率收集的过程。

1、编译阶段

在编译阶段,当加入相应的编译选项后,由toplev.c中的函数调用coverage.c与profile.c中的函数,这些函数又调用gcov-io.c中的函数。其中coverage.c中的build_gcov_info 产生一些数据结构,并调用gcov_init 。 同时profile.c会创建程序流图,由profile.c中的函数创建的程序流图,同时gcno中的每个arc会调用insert_insn_on_edge函数来增加counter.

2、数据收集阶段

在数据收集阶段,即在程序运行时。会调用libgcov.c中的函数来增加struct gcov_info的count字段的信息,当程序退出时,gcov_exit会被调用,这个函数将收集到的数据信息写入到.gcda文件中。

3、生成报告阶段

运行($gcov 源文件)后就产生这个后缀文件。gcov要分析的话需要依赖于.gcno,.gcda,.c三个文件,记住这个分析一定要保证:.gcda产生的时候依赖的.gcno要一致,就是说我.gcda和.gcno一定要是配套的。

gcov的分析过程:(用.bbg与.bb代替.gcno来讲)

  1. gcov读取.bbg中的程序流图信息,建立被测源文件中每个函数的程序流图
  2. 读取.gcda信息,将已知的弧执行次数填入到程序流图中
  3. 根据节点入度等于出度的原理推算出其他的弧与基本块的执行次数
  4. 读取.bb文件,根本对应关系计算出每行代码的执行次数
  5. 对应分支的话还需要计算分支的起始位置
  6. 输出计算结果

2.2.3 插桩方法

现在为止,我们知道,

Gcc编译运行产生了什么数据以及gcov分析覆盖率的过程。

还有两个问题:

a.  gcc加入编译参数后,是怎么插桩的

b.  在什么位置,插入了什么数据呢?

1. 插桩过程所进行的修改

1) 每个源文件对应桩点数组:

GCC在插桩的过程中会像源文件的末尾插入一个静态数组,BX2.,数组的大小就是这个源文件中桩点的个数。

BX2+0代表第0个桩点的位置,BX2+n代表第n个桩点的位置。

数组的值就是桩点的执行次数。

2) 每个桩点插入汇编语句:

按照我的理解插入的汇编语句是inc$(BX2+n).

3) BX2数组链表:

为了便于统计,gcc还将各个源文件中的BX2数组链接成一个链表,这个链表结构是在测试main函数之前就产生了,在调用main之前会有一个类似构造函数的函数,进行构建链表。这个函数会在退出时调用exit函数计算执行次数生成.gcda文件。

2. 一些数据结构与函数功能

1) BX2数组:

每个源文件对应一个,记录每个桩点的执行次数。

2) bb结构:

因为要将各个源文件的BX2组织起来,便于统一输出,为每个源文件定义一个bb结构如下:

struct bb

{

long zero_word;  //是否被插入到链表中

const char *file_name; //当前被测试文件名

long *count;//指向bx2的指针

long ncounts;//桩点个数

struct bb *next;//下一个文件的BX2信息

};

3) BX链表:

将BX2组织起来,头指针bb_head,链表元素结构为bb结构。调用void _bb_init_func(struct bb * block), 传入头指针bb_head创建。

4) 创建链表过程:void _bb_init_func(struct bb * block)

GCC为被测源文件插入了一个构造函数_GLOBAL_$I$XXXGCOV()的定义,其中XXX指当前被测文件中的第一个全局函数的函数名的生成,此函数在main函数调用之前会同构造函数一起被调用,这个全局函数的功能就是调用_bb_init_func函数,以该文件的bb结构的起始地址为参数进行调用。

该函数定义在GCC自带的库文件Libgcc.a中,源码位于gcc/libgcc2.c中,定义如下:

void _bb_init_func(struct bb * blocks)

{

if(block->zero_word)//已经连接不管

Return;

if(!bb_head)

ON_EXIT(_bb_exit_func,0);//程序退出时候,写.gcda数据

blocks->zero_word=0;

blocks->next=bb_head;//插入到链表中

bb_head=blocks;

}

该函数首先检查bb结构是否被插入到链表中,如果是则返回,接着检查bb结构的链头是否被初始化,否则注册退出时执行函数_bb_exit_func,该函数负责返回bb链中的每个结构,并生成.gcda数据文件。

这样在main函数之前,所有的bb结构都被连接成一个链表。

5)写入数据文件的过程:_bb_exit_func()

在被测程序运行完成之后,注册退出时会执行函数_bb_exit_func(),将从这个链表的头开始为每一个bb结构开始为源文件创建.gcda文件。写入的文件格式就是BX2数组内容,可以从bb结构中的BX2结构指针找到。

至此,整个插桩过程就讲完了。

1.         使用说明 

使用GCOV进行代码覆盖率统计,需要注意:

1)            在编译时不要加优化选项,因为加编译选项后,代码会发生变化,这样就找不到哪些是自己写的热点代码。

2)            如果代码中使用复杂的宏,比如说这个宏展开后是循环或者其他控制结构, gcov只在宏调用出现的那一行报告 ,如果复杂的宏看起来像函数,可以用内联函数来代替。

3)            代码在编写时要注意,每一行最好只有一条语句。

4)            可以用gcov,lcov测试linux内核覆盖率,参考

http://ltp.sourceforge.net/coverage/gcov.php

这里只讨论应用程序的覆盖率。

2.         使用实例

使用主要有三个步骤:

1)            编译阶段:加入编译选项gcc –o hello –fprofile-arcs –ftest-coverage hello,生成记录程序流程图等信息

2)            数据收集与提取阶段:./hello.,生成具体的运行信息

这一阶段生成的数据信息自动保存到。o文件所在的目录,也就是说如果存在/usr/build/hello.o,则生成/usr/build/hello.gcda,但是如果前边目录不存在就会出错。

3)            生成数据报告: gcov  hello.c

以下给出gcov针对c++项目nmap的应用过程

Nmap是一个强大的端口扫描程序,同时Nmap也是著名安全工具Nessus所依赖工具。代码行数在3万行以上。

执行:

  1. CXXFLAGS=”-fprofile-arcs -ftest-coverage” LIBS=-lgcov ./configure  èmakefile
  2. Make     è 每个源文件产生一个.gcno文件
  3. ./nmap   è每个源文件生成一个.gcda文件
  4. Gcov *cc  è每个源文件生成一个.gcov文件

    

步骤一:检测代码

按照nmap项目readme文档编译运行一遍,保证代码正确性

步骤二:增加使用gcov的编译选项:-fprofile-arcs -ftest-coverage,链接选项-lgcov或者-coverage

对于手动写的Makefile代码,直接增加编译选项即可

对一些自动生成的Makefile文件,运行./configure –h 查看增加Makefile编译连接选项的方法,增加编译选项:-fprofile-arcs -ftest-coverage ,通过一些环境变量设置即可,比如本程序设置为 CXXFLAGS=”-fprofile-arcs -ftest-coverage” LIBS=-lgcov ./configure

 

步骤三:编译连接

修改Makefile文件后,执行make, 针对每个源文件会生成.gcno文件

步骤四:测试

运行单个测试用例或测试用例组,生成.gcda文件,如下运行./nmap 127.0.0.1后,结果如下

步骤五:运行gcov生成覆盖测试信息

如下所示,分析其中的一个源文件及其相关联文件的测试覆盖率情况,默认情况下会生成sourefilename.c.gcov文件,可以添加-l,-p选项生成具体的目录及长文件名。如下所示,分别对main.cc与output.cc的进行覆盖率统计。这里查看的是行覆盖率,也可以添加-b,-f给出分支覆盖率信息,具体可以通过man gcov查看

下边是给出的生成行覆盖率的信息:

main.cc

Output.cc

步骤六:查看.gcov文件

显示源代码的执行情况,如下所示,查看output.cc的执行情况,以下分别是output.cc.gcov文件的头与部分信息,其中红框部分标注该行代码的执行次数,-表示没有被插桩到的代码行。

下图是分支覆盖率信息:

函数开始前给出整体信息:

一些分支情况:

步骤七: 用lcov查看整体代码覆盖情况

使用lcov前对覆盖率数据清空,lcov –z –d ./

在源码路径下运行lcov –b ./  -d ./  -c -o output.info,-b指示以当前目录作为相对路径,-d表示统计目录中的覆盖数据文件而不是内核数据,并将生成的信息存于-o所示文件,具体参数参考:lcov –h 查看

      最后,可以合并多个覆盖率信息,用-a 选项

      Lcov  –add-tracefile .out/a.info –a ./out.info –a ./out/b.info

步骤八:用genhtml查看总体视图与网页视图

如下,可以看出本次覆盖测试成功instrument的行数有近两万行,执行的行数却只有三千多行,可以反复的增加测试用例,提高覆盖测试率。下图分别给出了整体覆盖率和各个源文件的覆盖情况。

3.         常见错误

1.        .gcda文件目录出错,找不到要创建的目录,这种主要用于跨平台情况。

这个是由于.gcda文件的生成默认保存到.o所在的目录,但是如果.o所在目录不存在,就会出现错误。

设置环境变量可以解决这个问题。

设置GCOV_PREFIX=/target/run’同GCOV_PREFIX_STRIP=1

则生成的.gcda文件 将会保存到 /target/run/build/foo.gcda。

2.        the gcov message “Merge mismatch for summaries”

可以将.gcda全部删除或者对整个文件全部编译,而不是单个改变的文件,这个是由于gcda与gcno不相配导致的,因为两者之间都有个时间戳用来记录是不是相同的。





Coverage validator

1. 简介

2. 原理简介

3. 使用

4. 优缺点

 1.         Coverage validator简介

Coverage validator(Software Verification Limited公司的产品)是一个代码覆盖测试工具。可供软件开发者和软件质量测试人员使用。Coverage validator可以帮助你确定工程的代码覆盖率,识别出单元测试中未测试的功能,以交互,实时的方式显示出代码覆盖情况来提高软件测试质量,你也可以合并所有单元测试的覆盖测试数据。可以在创建单元测试报告的同时生成测试报告。

常见的各种覆盖测试工具像CoverageMeter,gcov主要原理是替换了原有的编译器,在代码中进行插桩。因此, 这些覆盖测试工具的特点是需要重编被测试代码。这也是大部分覆盖率工具常用的方法。而Coverage Validator,不需要重编被测代码,只需要提供被测二进制程序的pdb文件,就能统计其代码覆盖率,所以对于每个应用的每个DLL/EXE模块,只是简单的需要PDB或者MAP文件即可。它能同时统计行覆盖,分支覆盖,函数覆盖等。 Coverage validator的insrumention是很快的,只需要几秒,而不是几分钟。跟non-instrumented应用速度是差不多的,不像其他的工具要慢上2到10倍。

Coverage Validator有个很大的好处是可以设置过滤条件,可以设置只统计部分模块的覆盖率数据。可以设置只统计某个DLL,某个类的覆盖率数据,而且返回结果也可以以文件或者函数返回。返回结果也非常直观,可以导出HTML报告或者XML报告。

目前,它只支持Windows平台。它能支持的调试信息格式参见下面描述:

Coverage Validator can understand debug in information in the following formats:
· Microsoft Program Database (PDB) 
· Borland Turbo Debugger System (TDS) 
· CodeView NB10 
· COFF

2.         Coverage validator使用方法

2.1 下载安装

在其官网上下载30的适用版本:

https://www.softwareverify.com/cpp/coverage/index.html

2.2 使用

Converage validator的使用是非常简单的,以下以一个五子棋程序的测试过程来展现coverage validator的功能特点:

整体图:

先看一下它的运行主页,可以发现coverage validator的功能是相当强大的,提供整体测试覆盖情况,每个文件的覆盖测试,分支覆盖测试,函数覆盖测试,单元覆盖测试,行覆盖测试和诊断分析等。

步骤一:选择要测试的程序或模块

 通过菜单”File”-”Start Application” ,选择要进行覆盖测试的工程。如下图所示选择exe文件或者某个DLL模块即可,同时可以设置相应的环境变量,参数,输入输出文件等。

 

一直next,直到看到如下界面,点击开始测试即可。

 

步骤三:开始测试过程

点击开始应用后,即会跳出客户端界面,开始测试。

步骤四:结果显示

在测试的同时,coverage validator会即时的显示测试的结果信息,你可以变测试摆弄查看相应的测试覆盖率信息,下图是总的测试情况,分别显示已经待测试的文件,函数,分支,代码行,及单元测试组的覆盖率情况。如下,显示的总文件个数,被访问的文件个数,未被访问的文件个数与完全覆盖文件的个数。

 

同时还显示一些comment来给出一些提示,下边是我之前跑的一个程序,会给出一些信息,提示有些内联函数和模块库中的文件没有被覆盖。这个覆盖率是随着功能测试的过程动态变化的,可以变测试边显示覆盖测试结果。

 也可以查看单个源文件的测试情况,如下图:

左边显示了各个源文件覆盖情况,其中浅蓝色的文件表示100%覆盖,红色的表示0覆盖,黄色的表示部分覆盖。对于每个文件分别显示了文件的总行数,被访问的行数,hook的行数,和测试覆盖率情况。右边显示了选择出了的单个文件的具体覆盖信息。黄色表示被覆盖的行,并在行的前边表示了该行代码的执行次数,红色为未访问的行,没有颜色的表示是没有HOOK的行。在文件的上方有具体的信息。同样可以点击左下方的refresh按钮来动态即时的显示代码覆盖情况。

 

利用覆盖测试工具,增加测试用例的方法,从上图的左边可以看出更改用户名模块的代码覆盖情况为0,查看此文件的覆盖测试结果,如下图所示,红色的行表示都没有覆盖,这个时候需要添加用户名更改的测试。

 

下图是增加了测试用例后的用户名更改模块的覆盖情况:

 

同样也可以按照函数的名字,类名字,目录等查看function coverage,如下图左下角的refresh用于更新,Type可以选择相应的显示方式。

 

下图为选择的类图显示方式。

 

如果检查的是CppUnit工程的代码覆盖率,需将Testrunnerd.dll文件复制到可执行文件所在目录。

1.        可以设置过滤条件,只统计加载的某个模块的覆盖率数据。比如,你要测试的是一个DLL,你就可以设置过滤条件,只统计该DLL的代码覆盖率。你还可以设置过滤只统计某个类,某个函数的覆盖率数据。设置方法:菜单:”Configure” – “Settings” – “Filters”。

 

3.         Coverage validator优缺点

优点:

1.         不需要重编被测代码,只需要提供被测二进制程序的pdb文件,可以单独的测试DLL/EXE模块

2.         结果数据输出直观,查看方便,代码窗口有颜色标记,详细显示各个函数,分支,文件覆盖情况,并标记每一行代码执行次数。有HTML报告和XML报告

3.         可配合cppunit使用

4.         插桩很快,应用程序的速度也很快

5.         可以设置过滤条件,只统计加载的某个模块的覆盖率数据,某个类,某个函数的覆盖率数据,也可以设置排除条件,排除统计某部分的覆盖率数据。可以一个文件一个文件的返回,也可以一个函数一个函数的返回。

6.         可以即时的查看代码覆盖测试结果信息,在执行的各个阶段查看。

7.         可以用于Native-mode与mixed-mode.net模式

缺点:

1).        结果的自动合成功能不太好,只是在一个SESSION的末期将结果合成。

2).        提供的覆盖测试功能最高达到分支覆盖。

3).        不能够覆盖所有的行,会有数据丢失

4.         参考

https://www.softwareverify.com/cpp/coverage/index.html





你可能感兴趣的:(C++,汇编,gcc,测试,makefile,测试工具)