在我使用Flamegraph进行性能分析的过程中,我发现即使所有代码库都使用-fno-omit-frame-pointer
标志编译,调用堆栈有时也会被破坏。(即push %rbp; move %rsp, %rbp
),有时甚至在ret
指令之后出现一些分支.如下面的例子所示,push %rbp; move %rsp, %rbp
被放在函数的底部。当perf碰巧在帧指针正确设置之前对函数中的指令进行采样时,它会导致不完整和误导性的调用堆栈。
C代码:
int flextcp_fd_slookup(int fd, struct socket **ps)
{
struct socket *s;
if (fd >= MAXSOCK || fhs[fd].type != FH_SOCKET) {
errno = EBADF;
return -1;
}
uint32_t lock_val = 1;
s = fhs[fd].data.s;
asm volatile (
"1:\n"
"xchg %[locked], %[lv]\n"
"test %[lv], %[lv]\n"
"jz 3f\n"
"2:\n"
"pause\n"
"cmpl $0, %[locked]\n"
"jnz 2b\n"
"jmp 1b\n"
"3:\n"
: [locked] "=m" (s->sp_lock), [lv] "=q" (lock_val)
: "[lv]" (lock_val)
: "memory");
*ps = s;
return 0;
}
CMake Debug
配置文件:
0000000000007c73 <flextcp_fd_slookup>:
7c73: f3 0f 1e fa endbr64
7c77: 55 push %rbp
7c78: 48 89 e5 mov %rsp,%rbp
7c7b: 48 83 ec 20 sub $0x20,%rsp
7c7f: 89 7d ec mov %edi,-0x14(%rbp)
7c82: 48 89 75 e0 mov %rsi,-0x20(%rbp)
7c86: 81 7d ec ff ff 0f 00 cmpl $0xfffff,-0x14(%rbp)
7c8d: 7f 1b jg 7caa <flextcp_fd_slookup+0x37>
7c8f: 8b 45 ec mov -0x14(%rbp),%eax
7c92: 48 98 cltq
7c94: 48 c1 e0 04 shl $0x4,%rax
7c98: 48 89 c2 mov %rax,%rdx
7c9b: 48 8d 05 86 86 00 00 lea 0x8686(%rip),%rax # 10328 <fhs+0x8>
7ca2: 0f b6 04 02 movzbl (%rdx,%rax,1),%eax
7ca6: 3c 01 cmp $0x1,%al
7ca8: 74 12 je 7cbc <flextcp_fd_slookup+0x49>
7caa: e8 31 b9 ff ff callq 35e0 <__errno_location@plt>
7caf: c7 00 09 00 00 00 movl $0x9,(%rax)
7cb5: b8 ff ff ff ff mov $0xffffffff,%eax
7cba: eb 53 jmp 7d0f <flextcp_fd_slookup+0x9c>
7cbc: c7 45 f4 01 00 00 00 movl $0x1,-0xc(%rbp)
7cc3: 8b 45 ec mov -0x14(%rbp),%eax
7cc6: 48 98 cltq
7cc8: 48 c1 e0 04 shl $0x4,%rax
7ccc: 48 89 c2 mov %rax,%rdx
7ccf: 48 8d 05 4a 86 00 00 lea 0x864a(%rip),%rax # 10320 <fhs>
7cd6: 48 8b 04 02 mov (%rdx,%rax,1),%rax
7cda: 48 89 45 f8 mov %rax,-0x8(%rbp)
7cde: 48 8b 55 f8 mov -0x8(%rbp),%rdx
7ce2: 8b 45 f4 mov -0xc(%rbp),%eax
7ce5: 87 82 c0 00 00 00 xchg %eax,0xc0(%rdx)
7ceb: 85 c0 test %eax,%eax
7ced: 74 0d je 7cfc <flextcp_fd_slookup+0x89>
7cef: f3 90 pause
7cf1: 83 ba c0 00 00 00 00 cmpl $0x0,0xc0(%rdx)
7cf8: 75 f5 jne 7cef <flextcp_fd_slookup+0x7c>
7cfa: eb e9 jmp 7ce5 <flextcp_fd_slookup+0x72>
7cfc: 89 45 f4 mov %eax,-0xc(%rbp)
7cff: 48 8b 45 e0 mov -0x20(%rbp),%rax
7d03: 48 8b 55 f8 mov -0x8(%rbp),%rdx
7d07: 48 89 10 mov %rdx,(%rax)
7d0a: b8 00 00 00 00 mov $0x0,%eax
7d0f: c9 leaveq
7d10: c3 retq
CMake Release
配置文件:
0000000000007d80 <flextcp_fd_slookup>:
7d80: f3 0f 1e fa endbr64
7d84: 81 ff ff ff 0f 00 cmp $0xfffff,%edi
7d8a: 7f 44 jg 7dd0 <flextcp_fd_slookup+0x50>
7d8c: 48 63 ff movslq %edi,%rdi
7d8f: 48 8d 05 6a 85 00 00 lea 0x856a(%rip),%rax # 10300 <fhs>
7d96: 48 c1 e7 04 shl $0x4,%rdi
7d9a: 48 01 c7 add %rax,%rdi
7d9d: 80 7f 08 01 cmpb $0x1,0x8(%rdi)
7da1: 75 2d jne 7dd0 <flextcp_fd_slookup+0x50>
7da3: 48 8b 17 mov (%rdi),%rdx
7da6: b8 01 00 00 00 mov $0x1,%eax
7dab: 87 82 c0 00 00 00 xchg %eax,0xc0(%rdx)
7db1: 85 c0 test %eax,%eax
7db3: 74 0d je 7dc2 <flextcp_fd_slookup+0x42>
7db5: f3 90 pause
7db7: 83 ba c0 00 00 00 00 cmpl $0x0,0xc0(%rdx)
7dbe: 75 f5 jne 7db5 <flextcp_fd_slookup+0x35>
7dc0: eb e9 jmp 7dab <flextcp_fd_slookup+0x2b>
7dc2: 31 c0 xor %eax,%eax
7dc4: 48 89 16 mov %rdx,(%rsi)
7dc7: c3 retq
7dc8: 0f 1f 84 00 00 00 00 nopl 0x0(%rax,%rax,1)
7dcf: 00
7dd0: 55 push %rbp
7dd1: 48 89 e5 mov %rsp,%rbp
7dd4: e8 b7 b7 ff ff callq 3590 <__errno_location@plt>
7dd9: c7 00 09 00 00 00 movl $0x9,(%rax)
7ddf: b8 ff ff ff ff mov $0xffffffff,%eax
7de4: 5d pop %rbp
7de5: c3 retq
7de6: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
7ded: 00 00 00
有什么方法可以阻止gcc重新排序这两条指令吗?
编辑:我在Ubuntu 22.04上使用默认的工具链(gcc-11.2.0 + glibc 2.35)。很抱歉,没有可重复的示例。编辑:添加示例函数的源代码。
1条答案
按热度按时间camsedfj1#
试试
-fno-shrink-wrap
这看起来像是“收缩 Package ”优化:只在需要的代码路径中执行函数的序言。通常的好处是在序言之前运行一个早期退出检查,而不是通过函数在该路径上保存/恢复一堆寄存器。
但是在这里,GCC决定只在它必须调用另一个函数时才执行序言(设置一个帧指针)。这个函数是错误返回路径中的
__errno_location
。哎呀。:P(GCC正确地意识到这是一种不常见的情况,并通过快速路径将它放在ret
之后的行外。因此,快速路径可以是一条没有分支的直线,而不是在asm()
内部。它不是一个单独的函数,只是源代码中显示的那个函数的尾部复制。)通过函数的主路径非常小,只有几个C赋值语句和一个
asm()
语句。GCC并不清楚asm
块有多大(虽然我认为有一些启发,但仍然愿意内联一个)。它也不知道是否有循环或在asm块中花费的任何重要时间。这是一个已知的问题,GCC bug #98018建议GCC应该有一个选项来强制在函数的实际顶部设置帧指针。因为目前还没有一个选项是100%可靠的,除了禁用不可用的优化。(感谢@Margaret Bloom找到并链接了这个选项。)
正如关于GCC bug的注解6所提到的,禁用收缩 Package 是确保GCC在函数本身的顶部设置帧指针所必需的,而不仅仅是在一些需要序言的
if
内部。GCC的问题似乎是在考虑一个可以停止函数内联的特性,这样回溯就可以完全反映C抽象机的函数调用嵌套。这超出了你所寻找的,我认为这只是在优化后asm中存在的函数的入口处设置帧指针。
禁用收缩 Package 将迫使整个序幕在那里发生,包括其他规则的推送(如果有的话)。不仅仅是帧指针。但这里没有任何其他规则。尽管如此,在总体上启用优化后,丢失收缩 Package 可能是相当小的。