LLVM 中的區段堆疊

簡介

區段堆疊允許以遞增方式配置堆疊空間,而不是在執行緒初始化時配置一個整體區塊(具有某種最糟情況的大小)。這是透過配置堆疊區塊(以下稱為「堆疊區塊」)並將其連結到雙向鏈結串列來完成的。函式序言負責檢查目前的堆疊區塊是否有足夠的空間供函式執行;如果沒有,則呼叫 libgcc 執行階段以配置更多堆疊空間。區段堆疊是使用 LLVM 函式上的 "split-stack" 屬性來啟用的。

執行階段功能已存在於 libgcc 中

實作細節

配置堆疊區塊

如上所述,函式序言會檢查目前的堆疊區塊是否有足夠的空間。目前的方法是在 TCB 中使用一個 slot 來儲存目前的堆疊限制(減去配置新區塊所需的空間量) - 這個 slot 的偏移量也是由 libgcc 決定。產生的組譯程式碼在 x86-64 上看起來像這樣

  leaq     -8(%rsp), %r10
  cmpq     %fs:112,  %r10
  jg       .LBB0_2

  # More stack space needs to be allocated
  movabsq  $8, %r10   # The amount of space needed
  movabsq  $0, %r11   # The total size of arguments passed on stack
  callq    __morestack
  ret                 # The reason for this extra return is explained below
.LBB0_2:
  # Usual prologue continues here

堆疊上函式引數的大小需要傳遞給 __morestack(這個函式在 libgcc 中實作),因為需要將這些位元組從先前的堆疊區塊複製到目前的堆疊區塊。這是為了讓函式引數的 SP(和 FP)相對定址按預期運作。

需要不尋常的 ret 指令,以便呼叫 __morestack 的函式能夠正確返回。__morestack 不是返回,而是呼叫 .LBB0_2。這是可能的,因為 ret 指令的大小和呼叫 __morestack 的 PC 都是已知的。當函式主體返回時,控制權會轉移回 __morestack__morestack 然後解除配置新的堆疊區塊,還原正確的 SP 值,並執行第二次返回,將控制權返回給正確的呼叫者。

變數大小的 alloca

關於 分配堆疊區塊 的章節自動假設每個堆疊框架的大小都是固定的。然而,LLVM 允許使用 llvm.alloca 內建函式在堆疊上分配動態大小的記憶體區塊。當遇到這種可變大小的 alloca 時,會產生以下程式碼:

  • 檢查目前的堆疊區塊是否有足夠的空間。如果有的話,就如同一般情況一樣,只需調整堆疊指標 (SP)。

  • 如果沒有足夠的空間,則產生對 libgcc 的呼叫,從堆積中分配記憶體。

從堆積分配的記憶體會鏈結到目前堆疊區塊中的清單中,並在堆疊區塊釋放時一併釋放。這可以防止記憶體洩漏。