三、基本原理

  1. 术语解释

  在了解背后原理之前,需要对覆盖率技术的一些概念有简单的了解。主要是基本块(Basic Block),基本块图(Basic Block Graph),行覆盖率(line coverage), 分支覆盖率(branch coverage)等。

  基本块(Basic Block),”A basic block is a sequence of instructions with only entry and only one exit. If any one of the instructions are executed, they will all be executed, and in sequence from first to last.” 这里可以把基本块看成一行整体的代码,基本块内的代码是线性的,要不全部运行,要不都不运行;

  基本块图(Basic Block Graph),基本块的后一条语句一般都要跳转,否则后面一条语句也会被计算为基本块的一部分。 如果跳转语句是有条件的,产生了一个分支(arc),该基本块有两个基本块作为目的地。如果把每个基本块当作一个节点,那么一个函数中的所有基本块构成了一个有向图,称之为基本块图(Basic Block Graph)。且只要知道图中部分BB或arc的执行次数可以推算出所有的BB和所有的arc的执行次数;

  打桩,意思是在有效的基本块之间增加计数器,计算该基本块被运行的次数;打桩的位置都是在基本块图的有效边上;

  行覆盖率(line coverage),源代码有效行数与被执行的代码行的比率;

  分支覆盖率(branch coverage),有判定语句的地方都会出现2个分支,整个程序经过的分支与所有分支的比率是分支覆盖率。注意,与条件覆盖率(condition coverage)有细微差别,条件覆盖率在判定语句的组合上有更细的划分。

  2. gcc/g++ 编译选项

  gcc需要静态注入目标程序编译选项,在编译链接的时候加入2个选项(-ftest-coverage -fprofile-arcs ),编译结束之后会生成 *.gcno 文件,而经过静态注入的目标程序在“正常结束”后,会在运行目录下产生*.gcda数据文件,通过gcov工具可产生覆盖率数据结果。

  -ftest-coverage

  Produce a notes file that the gcov code-coverage utility (see gcov—a Test Coverage Program) can use to show program coverage. Each source file’s note file is called auxname.gcno. Refer to the -fprofile-arcs option above for a description of auxname and instructions on how to generate test coverage data. Coverage data matches the source files more closely if you do not optimize.

  让编译器生成与源代码同名的.gcno文件(note file),这种文件含有重建基本块依赖图和将源代码关联至基本块的必要信息;

  -fprofile-arcs

  Add code so that program flow arcs are instrumented. During execution the program records how many times each branch and call is executed and how many times it is taken or returns. When the compiled program exits it saves this data to a file called auxname.gcda for each source file. The data may be used for profile-directed optimizations (-fbranch-probabilities), or for test coverage analysis (-ftest-coverage). Each object file’s auxname is generated from the name of the output file, if explicitly specified and it is not the final executable, otherwise it is the basename of the source file. In both cases any suffix is removed (e.g. foo.gcda for input file dir/foo.c, ordir/foo.gcda for output file specified as -o dir/foo.o). See Cross-profiling.

  让编译器静态注入对每个源代码行关联的计数器进行操作的代码,并在链接阶段链入经态度libgcov.a,其中包含在程序正常结束时生成*.gcda文件的逻辑;

  下面通过源码解析来说明到底这2个选项做了什么。通过g++ -S选项,产生汇编语言Rectangle.s 和 Rectangle_cc.s (增加–coverage选项),命令如下,

  g++ -c -o Rectangle.s Rectangle.cpp -g -Wall -S

  g++ -c -o Rectangle_cc.s Rectangle.cpp -g -Wall –coverage -S

  vimdiff Rectangle.s 和 Rectangle_cc.s,如下图

  通过这样汇编语言的对比,可以看出gcc通过这2个参数,把打桩的过程完成了。

  更深入的内容,例如,如果想知道gcno/gcda文件的格式,可以参考 @livelylittlefish 的一篇文章,GCC Coverage代码分析-.gcda/.gcno文件及其格式分析(http://blog.csdn.net/livelylittlefish/article/details/6448885)。