了解 gcov 文件中的分支

7
我正在努力理解gcov工具的输出。运行它没有选项是有意义的,但我想尝试理解分支覆盖率选项。不幸的是,很难理解这些分支是什么以及为什么它们没有被执行。下面是一个方法的输出(使用最新的LLVM / Clang构建进行编译)。
function -[TestCoverageAppDelegate loopThroughArray:] called 5 returned 100% blocks executed 88%
        5:   30:- (NSInteger)loopThroughArray:(NSArray *)array {
        5:   31:    NSInteger i = 0;
       22:   32:    for (NSString *string in array) {
branch  0 taken 0
branch  1 taken 7
        -:   33:        
       22:   34:    }
branch  0 taken 4
branch  1 taken 3
branch  2 taken 0
branch  3 taken 3
        5:   35:    return i;
        -:   36:}

我已经运行了5个测试,分别传入nil、空数组、一个对象的数组、两个对象的数组和四个对象的数组。我猜在第一种情况下,分支1表示“进入循环”,但我不知道分支0是什么意思。在第二种情况下,分支0似乎是再次循环,分支1似乎是结束循环,分支3是继续/退出循环,但我不知道分支2是什么或者为什么/何时会被执行。
如果有人知道如何解密分支信息,或者知道任何关于这些信息的详细文档,我将不胜感激。

尝试获取您的函数的汇编,并检查其中的j **指令数量。 - osgx
我的汇编不是很好,但似乎有4个。第一个是je,如果没有对象枚举,则跳过循环。然后是另一个je,跳过枚举突变异常,jb我不知道,但会回到循环的顶部,然后是jne,如果还有对象要枚举,则移动到循环的顶部。有趣的是,突变导致第一个分支0被执行,解决了一个谜团,但第二个分支仍然让我困惑。 - Martin Pilkington
嗯...还有一种可能的情况是反汇编对象文件,这个对象文件是使用“-pg”(用于gcov运行)编译的。你应该在这样的反汇编中看到对某些gcov仪器函数的调用,例如“__llvm_gcov_ctr”增量或“__llvm_gcda_edge”呼叫。此外,你是否使用“-fno-inline”编译O0? - osgx
我一直在使用O0编译。我没有使用-fno-inline,但是我刚刚尝试了一下,似乎没有任何影响。在反汇编中有许多__llvm_gcov_ctr101注释,但我几乎不知道其余部分的含义。我怀疑这是Cocoa快速枚举的某种复杂性问题。或者这可能是LLVM/Clang中的一个错误,因为该功能相对较新,但在这种情况下,我更倾向于怀疑前者。 - Martin Pilkington
__llvm_gcov_ctr101注释是gcov将显示的计数器。函数的汇编文本中必须有不同的计数器。这不是一个错误,但这个ObjC结构变成了很多基本块(en.wikipedia.org/wiki/Basic_block)的汇编和gcov对汇编的基本块进行计数。 - osgx
啊哈,这确实帮了很多忙,现在我知道生成的汇编代码中所有 ## BB 和 LBB 的含义了。我可以将它们全部匹配起来,看到分支 2 是 jne。虽然我还需要复习一下汇编语言才能完全理解它的作用,但我现在知道它在源代码中的位置了。无论如何,我现在知道如何理解 gcov 的含义了,这正是我想要的。如果你想发表答案,我会选择它,感谢你的帮助! :) - Martin Pilkington
1个回答

4
Gcov的工作原理是在编译时对机器指令的每个基本块(可以将其视为汇编语言)进行插装。基本块 指的是一个线性代码段,其中没有分支和标签。因此,只有当您开始运行一个基本块时,才会到达基本块的结尾。基本块组织成 CFG(控制流图,将其视为有向图),显示基本块之间的关系(从 V1 到 V2 的边表示 V1 调用 V2;V2 被 V1 调用)。因此,编译器和 gcov 的 profile-arcs 模式想要获取每行的执行次数,并通过计算基本块执行次数来实现。CFG 中的一些边被插装了,而另一些则没有,因为图中的基本块之间存在代数关系。
您的 ObjC 构造(for..in)被降低(在早期编译时转换)为多个基本块。因此,gcov 看到 4 个分支,因为它只看到已经降低的 BBs。它不知道这种降低的情况,但它知道每个汇编指令对应的行(这是调试信息)。因此,分支是 CFG 的边缘。
如果您想查看基本块,应对编译后的程序进行汇编转储,或者对二进制文件进行反汇编或从编译器中转储CFG。您可以针对profile-arcs和非profile-arcs模式执行此操作并进行比较。 profile-arcs模式将有许多调用和类似于“__llvm_gcov_ctr”或“__llvm_gcda_edge”的增量 - 这是基本块的实际插装。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接