如何防止gcc重新排序x86帧指针保存/设置指令?

hec6srdp  于 2022-11-13  发布在  其他
关注(0)|答案(1)|浏览(216)

在我使用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)。很抱歉,没有可重复的示例。编辑:添加示例函数的源代码。

camsedfj

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 可能是相当小的。

相关问题