在GDB core的时候,经常去查看EBP,ESP寄存器,来查找一些有用的调用信息,但是感觉这个EBP,ESP中值的变化,并不是像书中描述的那种函数调用的标准流程。

在用perf做性能分析,打印函数的调用路径时,打印调用路径也有问题。
后来才清楚,原来是-fno-omit-frame-pointer这个优化导致的问题。

下面以一个实例来说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>

int add(int a, int b)
{

return a + b;
}

int main(void)
{

int sum = 0;
sum = add(1, 2);
printf("%d", sum);
return 0;
}
1
2
gcc -o a.s -S a.c -fno-omit-frame-pointer
vim a.s

add函数汇编:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.LFB0:
.cfi_startproc
pushq %rbp #保存栈基址
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp #将栈顶做为新的栈基址
.cfi_def_cfa_register 6
movl %edi, -4(%rbp)
movl %esi, -8(%rbp)
movl -8(%rbp), %eax #取参数1
movl -4(%rbp), %edx #取参数2
leal (%rdx,%rax), %eax #相加
leave
.cfi_def_cfa 7, 8
ret #返回
1
2
3
4
gcc -o a.s -S a.c -fomit-frame-pointer
vim a.s
add函数汇编
add:
1
2
3
4
5
6
7
8
.LFB0:
.cfi_startproc
movl %edi, -4(%rsp) #取参数1
movl %esi, -8(%rsp) #取参数2
movl -8(%rsp), %eax
movl -4(%rsp), %edx
leal (%rdx,%rax), %eax #相加
ret

从汇编结果可以看出,第二种少了几条切换栈基址的指令,会有性能提升,但会带来一个问题,由于没有存储rbp,那么从add函数并不能追溯整个函数的调用栈。
比如,当perf采样到add函数内的时候,它只能打印当前的指令是在哪个函数内,但不能打印整个调用函数栈。

其它

在用perf做性能分析时,发现使用perf report -g打出的函数调用链(callchain)是错误的,打出的是一堆混乱的地址。

perf获取callchain原理

内核会在采样时,会基于EBP(栈底寄存器),对整个栈进行便历。但是当使用-O优化后,GCC会将栈底指针优化掉,并把EBP当作一个通用寄存器来使用,所以从EBP中读出来的就不是栈底指针了,所以得到的callchain是错误的。

解决方法

编译时加上-fno-omit-frame-pointer选项,禁用把EBP当作通用寄存器来使用.

新的内核版本,可以不通过EBP,而是使用libunwind获得callchain。

参考文章:

1) http://www.cnblogs.com/islandscape/p/3444122.html
2) http://kernel.taobao.org/index.php/Documents/Perf_FAQ
3) http://kernel.taobao.org/index.php/Documents/Perf_FAQ

Comments

2014-10-31