繁体   English   中英

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

[英]How to prevent gcc from reordering x86 frame pointer saving/setup instructions?

在我使用火焰图进行分析期间,我发现调用堆栈有时会被破坏,即使所有代码库都使用-fno-omit-frame-pointer标志编译。 通过检查 gcc 生成的二进制文件,我注意到 gcc 可能会重新排序 x86 帧指针保存/设置指令(即, push %rbp; move %rsp, %rbp ret 如下例所示, push %rbp; move %rsp, %rbp push %rbp; move %rsp, %rbp放在 function 的底部。 在正确设置帧指针之前,当 perf 碰巧在 function 中采样指令时,它会导致不完整和误导性的调用堆栈。

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)。 抱歉,没有可重复的示例。 编辑:添加示例 function 的源代码。

试试-fno-shrink-wrap


这看起来像“收缩包装”优化:只在需要的代码路径中执行 function 序言。 通常的好处是在序言之前运行早期检查,而不是通过 function 在该路径上保存/恢复一堆寄存器。

但是在这里,GCC 决定只在必须调用另一个 function 时才做序言(设置帧指针)。 __errno_location是错误返回路径中的 __errno_location 。 哎呀。 :P (并且 GCC 正确地意识到这是不常见的情况,并在ret通过快速路径之后将其置于脱线状态。因此,除了在您的asm()内部之外,快速路径可以是一条没有分支的直线。它不是一个单独的 function,它只是您显示源代码的尾部复制。)

通过 function 的主要路径非常小,只有几个 C 赋值语句和一个asm()语句。 GCC 并不清楚asm块有多大(虽然我认为有一些启发式方法,但仍然愿意内联一个)。 它不知道是否可能存在循环或在 asm 块中花费的任何大量时间。


这是一个已知问题, GCC 错误 #98018建议 GCC 应该有一个选项来强制在 ZC1C425268E68385D1AB5074C17A 的实际顶部设置帧指针。 因为目前没有 100% 可靠的选项,除了禁用不可用的优化。 (感谢@Margaret Bloom 找到并链接此内容。)

As comment 6 on that GCC bug mentions, disabling shrink-wrapping is part of what's necessary to make sure GCC sets up the frame pointer at the top of the function itself, not just inside some if that needs the prologue.

That GCC issue seems to be considering a feature that would stop function inlining, so backtraces would fully reflect the C abstract machine's nesting of function calls. 这超出了您正在寻找的内容,我认为这只是在优化后存在于 asm 中的函数的入口处设置帧指针。

禁用收缩包装将迫使整个序幕在那里发生,包括推送其他 regs,如果有的话。 不仅仅是帧指针。 但这里没有其他人。 尽管如此,在一般启用优化的情况下,丢失收缩包装可能非常小。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM