簡體   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