应用程序调用链不完整,用于glibc中的perf记录样本

时间:2017-11-10 09:15:21

标签: c++ compiler-optimization glibc perf

我想使用perf工具获取我的程序的callchain。但结果总是不完整,它总是缺少直接调用usleep的最后一个函数。我曾尝试记录sched:sched_switchusleep跟踪事件,但结果始终相同。以下是命令行:

$  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]

1 个答案:

答案 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