课程咨询: 400-996-5531 / 投诉建议: 400-111-8989
认真做教育 专心促就业
代码覆盖对于软件编程开发程序员来说是轻车熟路的一项工作。今天我们就通过案例分析来简单了解一下,关于代码覆盖的原理都有哪些。
覆盖率检测原理
生成覆盖率报告,先需要在Xcode中配置编译选项,编译后会为每个可执行文件生成对应的.gcno文件;之后在代码中调用覆盖率分发函数,会生成对应的.gcda文件。
其中,.gcno包含了代码计数器和源码的映射关系,.gcda记录了每段代码具体的执行次数。覆盖率解析工具需要结合这两个文件给出后的检测报表。接下来先看看.gcno的生成逻辑。
.gcno
利用Clang分别生成源文件的AST和IR文件,对比发现,AST中不存在计数指令,而IR中存在用来记录执行次数的代码。搜索LLVM源码可以找到覆盖率映射关系生成源码。覆盖率映射关系生成源码是LLVM的一个Pass,(下文简称GCOVPass)用来向IR中插入计数代码并生成.gcno文件(关联计数指令和源文件)。
下面分别介绍IR插桩逻辑和.gcno文件结构。
IR插桩逻辑
代码行是否执行到,需要在运行中统计,这就需要对代码本身做一些修改,LLVM通过修改IR插入了计数代码,因此我们不需要改动任何源文件,仅需在编译阶段增加编译器选项,就能实现覆盖率检测了。
从编译器角度看,基本块(BasicBlock,下文简称BB)是代码执行的基本单元,LLVM基于BB进行覆盖率计数指令的插入,BB的特点是:
只有一个入口。
只有一个出口。
只要基本块中一条指令被执行,那么基本块内所有指令都会顺序执行一次。
覆盖率计数指令的插入会进行两次循环,外层循环遍历编译单元中的函数,内层循环遍历函数的基本块。函数遍历仅用来向.gcno中写入函数位置信息,这里不再赘述。
一个函数中基本块的插桩方法如下:
统计所有BB的后继数n,创建和后继数大小相同的数组ctr[n]。
以后继数编号为序号将执行次数依次记录在ctr[i]位置,对于多后继情况根据条件判断插入。
.gcno计数符号和文件位置关联
.gcno是用来保存计数插桩位置和源文件之间关系的文件。GCOVPass在通过两层循环插入计数指令的同时,会将文件及BB的信息写入.gcno文件。写入步骤如下:
创建.gcno文件,写入Magicnumber(oncg+version)。
随着函数遍历写入文件地址、函数名和函数在源文件中的起止行数(标记文件名,函数在源文件对应行数)。
随着BB遍历,写入BB编号、BB起止范围、BB的后继节点编号(标记基本块跳转关系)。
写入函数中BB对应行号信息(标注基本块与源码行数关系)。
从上面的写入步骤可以看出,.gcno文件结构由四部分组成:
文件结构
函数结构
BB结构
BB行结构
通过这四部分结构可以完全还原插桩代码和源码的关联,我们以BB结构/BB行结构为例,给出结构图2(a)BB结构,(b)BB行信息结构,在本章末尾覆盖率解析部分,我们利用这个结构图还原代码执行次数(每行等高格代表64bit):
【免责声明】:本内容转载于网络,转载目的在于传递信息。文章内容为作者个人意见,本平台对文中陈述、观点保持中立,不对所包含内容的准确性、可靠性与完整性提供形式地保证。请读者仅作参考。