怎样才能拿到正确的callchain

本文探讨了GCC的-fomit-frame-pointer优化选项如何影响callchain的准确性,并介绍了使用libunwind解析eh_frame section来获取正确的callchain的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

怎样获得正确的callchain

承刚   

本文针对在某些场景下通过perf或tcmalloc等功能获取的callchain不准确的问题进行了分析。 

开启GCC的O*优化选项时,会默认开启一个称为“-fomit-frame-pointer”的优化选项。该选项会将栈底指针寄存器EBP用作通用寄存器。这在一定程度上会减少GCC生成的Binary文件的footprint,并多提供一个通用寄存器。我们知道,如果程序的footprint较小,那么程序执行期间触发L1I Cache的Miss Rate就会降低。从而能够加快程序的执行。在加上多出一个通用寄存器所起的作用,可以说优化选项“-fomit-frame-pointer”还是能起到一定作用的。

本文通过以下实验测试了这条优化选项的加速比。  实验中编写了一个很简单的benchmark,如图1所示。

#include <stdio.h>

#include "ptime.h"

 #define LIFE (u64)1e9 

int do_some(int i)

{        

        return i++;



int main(void)

{

        u64 start_t = rdclock();

         u64 sum = 0;

          while (1) {

                 if (rdclock() - start_t > LIFE)

                         break;

                  sum += do_some(1);

         }

          printf("sum: %llu\n", sum);

          return 0;

}图1. Benchmark

  图1中的程序在1秒钟内不断调用一个函数,最后输出计算结果。通过对比计算结果的不同,便能够初略估计出“-fomit-frame-pointer”选项带来的优化加速比。  上述实验得到的数据如下:

  1)未开启“-fomit-frame-pointer”选项:5428437

  2)开启“-fomit-frame-pointer”选项:5525344

  由此可以得出该选项的优化加速比为:1.79%。

  通过上述实验,可以得知在如此简单的程序中,“-fomit-frame-pointer”选项都能够带来一定的性能优化。在性能优化上精益求精的GCC便将其作为了-O*的默认选项。  但是这项优化带来了很大的麻烦,开启该选项后,便无法通过常规的方式获得程序的callchain了。如果EBP寄存器扮演栈底指针的角色,我们就能够通过它获得函数的返回地址,以及上个frame的返回地址。以图1中的程序为例,函数do_some()的汇编指令如图2所示。

0000000000400538 <do_some>:   400538:  55                push   %rbp

   400539:  48 89 e5           mov    %rsp,%rbp

   40053c:  89 7d fc           mov    %edi,0xfffffffffffffffc(%rbp)

   40053f:  8b 45 fc            mov    0xfffffffffffffffc(%rbp),%eax

   400542:  83 45 fc 01         addl   $0x1,0xfffffffffffffffc(%rbp)

   400546:  c9                leaveq

   400547:  c3                retq

 图2. do_some()函数的汇编指令

  在执行do_some()函数之初,首先将rbp寄存器压栈。此时rbp中是上个frame的栈底地址。而栈底保存的是该函数的返回地址,也恰恰就是我们需要的callchain上的一个函数地址。利用此机制,便能够遍历stack上的每个frame,并拿到每个frame的返回地址。从而获得整个callchain。

  但是,当开启了优化选项“-fomit-frame-pointer”之后,RBP寄存器就不再保存栈底指针了。因此再通过上述方式获得callchain,就是错误的。这也正是大家在利用perf的callchain功能进行profiling时,看到的是一堆混乱的地址或符号的原因。

  callchain功能是程序调试,性能分析的必备功能。既然无法通过遍历用户栈的frame来获取callchain,系统就必须提供其它的方式。目前的GCC通过CFI(Call Function Instructions)机制,在编译出的ELF文件中加入了一个名为“eh_frame”的section。该section能够提供callchain相关的信息。

  开源软件“libunwind”支持了对“eh_frame”的分析。只要能够提供某个时刻的指令地址与相应进程的stack,libunwind就能够正确获取此时的callchain。待我弄懂libunwind的原理后,会另外撰文详述。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值