我想使用perf
工具获取我的程序的callchain。但结果总是不完整,它总是缺少直接调用usleep
的最后一个函数。我曾尝试记录sched:sched_switch
和usleep
跟踪事件,但结果始终相同。以下是命令行:
$ g++ -g -rdynamic test_sleep.cpp -o test_sleep
$ ./test_sleep &
$ ps -ef |grep test_sleep |grep -v grep
root 15292 14879 0 16:31 pts/1 00:00:02 ./test_sleep
$ perf record -e probe_libc:usleep -e sched:sched_switch -gp 15292
$ perf script
test_sleep 15292 [019] 159309.652668: probe_libc:usleep: (7ff615142030)
ee030 usleep (/usr/lib64/libc-2.17.so)
1039 funcs1 (/home/test_sleep)
1070 fun (/home/test_sleep)
107b main (/home/test_sleep)
21b15 __libc_start_main (/usr/lib64/libc-2.17.so)
test_sleep 15292 [019] 159309.663728: sched:sched_switch: test_sleep:15292 [120] S ==> swapper/19:0 [120]
7fff8168dbb0 __schedule ([kernel.kallsyms])
7fff8168e069 schedule ([kernel.kallsyms])
7fff8168cf36 do_nanosleep ([kernel.kallsyms])
7fff810b786b hrtimer_nanosleep ([kernel.kallsyms])
7fff810b79ae sys_nanosleep ([kernel.kallsyms])
7fff81699089 system_call_fastpath ([kernel.kallsyms])
bd410 __GI___libc_nanosleep (/usr/lib64/libc-2.17.so)
1039 funcs1 (/home/test_sleep)
1070 fun (/home/test_sleep)
107b main (/home/test_sleep)
21b15 __libc_start_main (/usr/lib64/libc-2.17.so)
以下是我的程序代码:
void funcs2()
{
for(i = 0; i< 100; i++){};
while(1)
usleep(10000);
}
void funcs1()
{
for(i = 0; i< 100; i++){};
funcs2();
}
void fun()
{
for(i = 0; i< 100; i++){};
funcs1();
}
main()
{
fun();
return 0;
}
我们可以看到callchain中缺少funcs2
。这个函数是由g++
编译器优化的吗?但我没有使用-O
选项,我在我的程序中使用backtrace_symbols
捕获完整的callstack,下面是backtrace_symbol
记录的callstack:
./test_sleep(_Z6funcs2v+0x32) [0x400e4f]
./test_sleep(_Z6funcs1v+0x35) [0x401039]
./test_sleep(_Z3funv+0x35) [0x401070]
./test_sleep(main+0x9) [0x40107b]
/lib64/libc.so.6(__libc_start_main+0xf5) [0x7ff615075b15]
./test_sleep() [0x400d59]
答案 0 :(得分:0)
默认情况下,perf
使用帧指针在用户空间内生成调用链。不幸的是,由于优化,帧指针不可靠。您的问题出现是因为您的glibc
是使用特定优化(-fomit-frame-pointer
)编译的,这会阻止并轻松堆叠展开。
让我们看看perf
的作用。内核中的callchain使用不同的机制
7fff8168dbb0 __schedule ([kernel.kallsyms])
7fff8168e069 schedule ([kernel.kallsyms])
7fff8168cf36 do_nanosleep ([kernel.kallsyms])
7fff810b786b hrtimer_nanosleep ([kernel.kallsyms])
7fff810b79ae sys_nanosleep ([kernel.kallsyms])
7fff81699089 system_call_fastpath ([kernel.kallsyms])
初始用户空间条目使用当前用户空间指令指针,因此perf得到了正确的
bd410 __GI___libc_nanosleep (/usr/lib64/libc-2.17.so)
现在perf使用用户空间帧指针来确定下一个函数。由于优化,libc
没有更新帧指针。它获得funcs2
的帧指针(而不是__GI___libc_nanosleep
)。 funcs2
的返回地址指向funcs2
- 这就是在堆栈中下一步看到的内容。
1039 funcs1 (/home/test_sleep)
1070 fun (/home/test_sleep)
107b main (/home/test_sleep)
21b15 __libc_start_main (/usr/lib64/libc-2.17.so)
因此,这与程序中的优化无关。你甚至很幸运libc
至少让你的帧指针完好无损,所以除了最顶层的函数之外你继续看到堆栈。
也就是说,perf
提供了另外两个用于生成调用链的选项。 --call-graph=dwarf
实际上存储了堆栈的一部分,并通过后处理展开它。在我的系统上,这显示funcs2
,但在用户空间中没有任何内容。还有--call-graph=lbr
使用Intel CPU的一些硬件支持。不幸的是我无法测试它。
此行为会影响libc中的任何perf样本,无论是不同的事件,系统调用还是硬件样本。作为参考,请参阅例如此debian bug report讨论libc的帧指针版本。你当然可以使用框架指针构建自己的libc
。