GCC Coverage代码分析-基本块图、插桩位置及桩代码执行分析

晨曦之光 发布于 2012/03/09 14:16
阅读 736
收藏 0

【Gopher China万字分享】华为云的Go语言云原生实战经验!>>>

本博客(http://blog.csdn.net/livelylittlefish)贴出作者(阿波)相关研究、学习内容所做的笔记,欢迎广大朋友指正!

Content

0.

1. 基本块概念

2. 基本块图及插桩点分析

2.1 基本块图

2.2 有效基本块图

2.3 带桩点信息的有效基本块图

2.4 插桩位置及桩代码执行情况分析

3. 小结

Appendix:源代码中对Basic Block的解释

 

 

0.

 

由前面几篇文章,例如:

Linux平台代码覆盖率测试-gcov-dump原理分析

Linux平台代码覆盖率测试-.gcda/.gcno文件及其格式分析

Linux平台代码覆盖率测试-GCC插桩基本概念及原理分析

Linux平台代码覆盖率测试-GCC插桩前后汇编代码对比分析

 

我们已经知道了代码覆盖率测试的一些相关概念,例如Basic BlockArcs等。本文重点叙述这些基本概念,并仍以前面的test.c为例说明基本块图(Basic Block Graph)及插桩点。

 

本文所用gcc版本为gcc-4.1.2test.c代码如下。

00001:#include

00002:

00003:intmain(void)

00004:{

00005:   inti,total;

00006:

00007:   total= ;

00008:

00009:   for(i=;i< 10;i++)

00010:   total+= i;

00011:

00012:   if(total!=45)

00013:       printf("Failure/n");

00014:   else

00015:       printf("Success/n");

00016:   return;

00017:}

00018:

1.基本块概念

 

通过基本块的划分,可以把程序分为一组代码段,每一段代码中的第一条语句被执行,则整段代码都被执行一次。

 

那么,什么是基本块(Basic Block)

 

基本块

  • 程序中一个顺序执行的语句序列
  • 只有一个出口语句和一个入口语句
  • 执行时只能从入口语句入,从出口语句退出
  • 基本块中的所有语句的执行次数一定是相同的
  • 一般由多个顺序执行语句后跟一个跳转语句组成

 

因此,基本块的最后一条语句一定是一个跳转语句,且跳转的目的地一定是另一个基本块的第一条语句。如果跳转语句是有条件的,就产生了一个分支(arc),该基本块就有两个基本块作为目的地。如果把每个基本块当作一个节点,那么一个函数中的所有基本块就构成了一个有向图,称之为基本块图(Basic Block Graph)。且只要知道图中部分BBarc的执行次数就可以推算出所有的BB和所有的arc的执行次数。

 

本文附录介绍了GCC源代码中对Basic Block的解释。下面以test.c为例,说明这些问题。

 

2.基本块图及插桩点分析

 

2.1基本块图

 

根据"Linux平台代码覆盖率测试-.gcda/.gcno文件及其格式分析"一文1.1节和2.1节的输出信息,我们可以画出如下的基本块图(Basic Block Graph)
 

1基本块图

 

其中,BB0BB8分别对应着开始和结束的虚拟基本块,表明程序从BB0开始,从BB8退出。因此,BB0没有入边,只有出边,而BB8只有入边,没有出边。8ARCS记录对应图中的8个有出度的节点,12条有向边实际上就是所有的arcs总数。

 

2.2有效基本块图

 

输出结果可以看出,BB0,BB8,BB3没有对应的代码行,可以当作虚拟的基本块。有效的基本块只有BB1, BB2, BB4, BB5, BB6, BB7

 

如果在图1中,我们删除虚拟基本块BB0BB8,并加入"Linux平台代码覆盖率测试-.gcda/.gcno文件及其格式分析"一文2.1节的输出的LINES信息(BB的行号信息),那么程序的逻辑会更清楚。如图2所示。

 

2有效基本块(带行号信息)

 

其中,每个节点旁边的lineno(行号)表示构成该BB的代码行,这些代码也显示在旁边。

 

这里需要说明的问题:为什么BB1BB2都有lineno=9的代码?

 

lineno=9的代码即为for语句本身,但实际上,for循环的执行顺序是:

(0)初始化(某些场合初始化可以在for外面)

(1)判断条件,如果条件成立,则执行for循环体,再对循环变量进行增/减操作;否则,直接进入for循环后的块;

 

因此,这里BB2中的lineno=9实际上只是for里面的循环变量自增语句(i++),这也是为什么BB2的代码行信息lineno=10写在lineno=9前面的原因;而BB1中的lineno=9实际上只用到判断条件(i < 10)

 

2.3含桩点信息的有效基本块图

 

结合"Linux平台代码覆盖率测试-GCC插桩前后汇编代码对比分析"一文对插入的桩代码的分析,可以很容易地在图2的基础上画出带有桩点信息的有效基本块图,如图3所示。

 

3有效基本块(带行号信息和桩点信息)

 

若将"Linux平台代码覆盖率测试-GCC插桩前后汇编代码对比分析"一文中前5段桩代码按其在文件中的先后顺序依次记为stub1, stub2, ..., stub5,则图中的stub1stub5分别与该文的stub1stub5一一对应。此处为读者方便理解,列出图中的第1个桩点stub1的代码,如下。关于桩代码的分析已在该文中详细叙述,不再赘述。

   movl   .LPBX1, %eax    #.LPBX1移到%eax,即%eax=.LPBX1

   movl   .LPBX1+4, %edx   #edx=.LPBX1+4

   addl   $1, %eax         #eax=%eax+1

   adcl   $0, %edx         #edx=%edx+0

   movl   %eax, .LPBX1    #%eax移回.LPBX1

   movl   %edx, .LPBX1+4  #%edx移回.LPBX1+4

关于该段桩代码的解释,可参考"Linux平台代码覆盖率测试-GCC插桩前后汇编代码对比分析"一文的3.1节。

 

2.4插桩位置及桩代码执行情况分析

 

根据2.3节的叙述及"Linux平台代码覆盖率测试-GCC插桩前后汇编代码对比分析"一文的解释,总结一下该例子的插桩位置。

 

stub1被插在第10行代码之前,被执行10次,存于.LPBX1+0位置。

stub2:被插在第13行代码之前,被执行次,存于.LPBX1+8位置。

stub3:被插在第13行代码之后,被执行次,存于.LPBX1+24位置。

stub4:被插在第15行代码之前,被执行1次,存于.LPBX1+16位置。

stub5:被插在第15行代码之后,被执行1次,存于.LPBX1+32位置。

 

上述执行次数就是"Linux平台代码覆盖率测试-GCC插桩前后汇编代码对比分析"一文3.1节中的静态数组.LPBX1元素的值(注意元素的存放位置)

 

+0     +4    +8    +12   +16   +20  +24   +28   +32   +36

10

0

0

0

1

0

0

0

1

0

 

而这些值就被作为COUNTERS写入了test.gcda文件,tag=0x01a10000,每个计数值(counter)8个字节。请参考"Linux平台代码覆盖率测试-gcov-dump原理分析""Linux平台代码覆盖率测试-.gcda/.gcno文件及其格式分析"

 

3.小结

 

本文简单介绍了基本块(Basic Block)和分支的基本概念,并通过例子重点叙述了基本块图(Basic Block Graph)、有效基本块图、含桩点信息的有效基本块图,同时分析了插桩位置和桩代码执行情况。

 

 

Reference

./gcc/basic_block.h(本文对Basic Block的英文解释即在该文件中)

http://blog.csdn.net/livelylittlefish/archive/2011/05/27/6448867.aspx

http://blog.csdn.net/livelylittlefish/archive/2011/05/27/6448885.aspx

http://blog.csdn.net/livelylittlefish/archive/2011/05/27/6448906.aspx

http://blog.csdn.net/livelylittlefish/archive/2011/05/27/6449256.aspx

 

 

Appendix:源代码中对Basic Block的解释

 

A basic block is a sequence ofinstructions withonly 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.

 

There may be COND_EXEC instructions in the basic block.  The COND_EXEC *instructions* will be executed -- but if the condition is false the conditionally executed *expressions* will of course not be executed. We don't consider the conditionally executed expression (which might have side-effects) to be in a separate basic block becausethe program counter willalways be at the same location after the COND_EXEC instruction, regardless of whether the condition is true or not.

 

Basic blocks need not start with a label nor end with ajump insn. For example, a previous basic block may just "conditionally fall" into the succeeding basic block, and the last basic block need not end with a jump insn. Block 0 is a descendant of the entry block.

 

A basic block beginning with two labels cannot have notes between the labels.

 

Data for jump tables are stored in jump_insns that occur in no basic block even though these insns can follow or precede insns in basic blocks.

 

Basic block information indexed byblock number.



Technorati 标签:


原文链接:http://blog.csdn.net/livelylittlefish/article/details/6451047
加载中
返回顶部
顶部