求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 
  
 
 
     
   
分享到
关于C++ code coverage tool 的研究
 
作者:hanjianya,发布于2012-8-31,来源:淘测试
 

实习快两个月了,发些研究成果与总结。关于覆盖测试的基本概念可以上网查阅,这里直接从研究对比开始讲吧。因为内容太多,开始之前先给个目录:

(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 Insertion Work well with Purify

GCOV 实现原理

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来讲)

gcov读取.bbg中的程序流图信息,建立被测源文件中每个函数的程序流图

读取.gcda信息,将已知的弧执行次数填入到程序流图中

根据节点入度等于出度的原理推算出其他的弧与基本块的执行次数

读取.bb文件,根本对应关系计算出每行代码的执行次数

对应分支的话还需要计算分支的起始位置

输出计算结果

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结构指针找到。

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


相关文章

微服务测试之单元测试
一篇图文带你了解白盒测试用例设计方法
全面的质量保障体系之回归测试策略
人工智能自动化测试探索
相关文档

自动化接口测试实践之路
jenkins持续集成测试
性能测试诊断分析与优化
性能测试实例
相关课程

持续集成测试最佳实践
自动化测试体系建设与最佳实践
测试架构的构建与应用实践
DevOps时代的测试技术与最佳实践


LoadRunner性能测试基础
软件测试结果分析和质量报告
面向对象软件测试技术研究
设计测试用例的四条原则
功能测试中故障模型的建立
性能测试综述
更多...   


性能测试方法与技术
测试过程与团队管理
LoadRunner进行性能测试
WEB应用的软件测试
手机软件测试
白盒测试方法与技术


某博彩行业 数据库自动化测试
IT服务商 Web安全测试
IT服务商 自动化测试框架
海航股份 单元测试、重构
测试需求分析与测试用例分析
互联网web测试方法与实践
基于Selenium的Web自动化测试
更多...