Fil-C 記憶體安全上下文切換

Fil-C 記憶體安全上下文切換

Fil-C 提供了 setjmplongjmpucontext API (getcontextsetcontextmakecontextswapcontext) 的記憶體安全實作。這些機制確保了對上下文切換 API 的誤用不會導致堆疊損壞或違反 Fil-C 的能力模型 (capability model),特別是防止了懸空堆疊 (dangling stacks) 的還原。

記憶體安全的 setjmp 與 longjmp

Fil-C 透過將跳躍緩衝區 (jump buffer) 視為不透明物件,並強制執行嚴格的堆疊框架 (stack-frame) 驗證,來防止 setjmplongjmp 常見的記憶體安全陷阱。

標準 setjmp/longjmp 的危險性

在標準 C 語言中,setjmp 會儲存暫存器狀態 (callee-save registers、堆疊指標和指令指標),但不會儲存堆疊本身。這會產生兩個主要的安全性風險:

  1. 懸空堆疊 (Dangling Stacks): 如果程式呼叫了 setjmp 然後從該函數返回,儲存的上下文將變得無效。隨後的 longjmp 若切換回該上下文,會嘗試還原一個已不存在的堆疊框架,導致機器狀態撕裂 (torn machine state)。
  2. 編譯器優化錯誤: 因為 setjmp 可能會返回兩次,編譯器必須為呼叫它的函數禁用某些優化 (例如 spill slot 重用),以避免錯誤。如果編譯器不知道某個函數正在呼叫 setjmp (例如透過函數指標),它可能會重用 spill slots,導致變數在 longjmp 之後持有錯誤或垃圾值。

Fil-C 的實作策略

Fil-C 透過以下機制來減輕這些風險:

  • 不透明跳躍緩衝區: jmp_buf 包含一個指向由 Fil-C 執行時 (runtime) 唯一管理的、不透明 zjmp_buf 物件的指標。這防止了使用者可以偽造或手動操縱跳躍狀態。

  • 編譯器強制執行: Fil-C 編譯器 (filcc) 要求 setjmp 必須被直接呼叫。任何試圖模糊化呼叫方式的行為 (例如使用函數指標) 都會導致編譯器錯誤或內部編譯器錯誤 (ICE),從而確保編譯器的 returns_twice 邏輯始終觸發。

  • 堆疊框架註冊:setjmp 被呼叫時,它會配置一個 zjmp_buf 並將其與當前堆疊框架註冊。每個框架都會維護一個包含有效 zjmp_buf 目標的弱集合 (weak set)。

  • 祖先驗證: 除非當前堆疊框架是建立 zjmp_buf 的框架之子代,否則 longjmp 將會觸發 panic。這意味著目標框架必須是當前呼叫堆疊上的祖先。這可以透過遍歷堆疊並在框架的有效集合中檢查 zjmp_buf 來驗證。

  • GC 集成: zjmp_bufsetjmp 呼叫時儲存了該框架在當時的 GC roots 的副本。只要 zjmp_buf 仍然存活,GC 就會繼續標記這些 roots。

記憶體安全的 ucontext API

ucontext API 的支援是在 Fil-C 版本 0.680 中引入的。這些 API 被用於實作纖維 (fibers) 和協程 (coroutines),透過允許儲存與還原執行上下文。

ucontext 狀態機

為了防止誤用,Fil-C 為 zfiber_context 物件實作了受限的狀態機:

  • Uninitialized (未初始化): 初始狀態。僅允許使用 getcontext 或作為 swapcontextfrom 參數。
  • After_getcontext (getcontext 之後): getcontext 返回後的狀態。僅允許使用 makecontext 或作為 swapcontextfrom 參數。
  • Runnable (可執行): makecontext 之後或作為 swapcontextfrom 參數之後的狀態。僅允許使用 setcontext 或作為 swapcontextto argument 參數。
  • Running (執行中): 上下文目前正在執行的狀態。僅允許使用 swapcontext,且僅限於該上下文是目前在執行緒 (thread) 執行中的上下文。

安全性限制與實作

  • 堆疊管理: Fil-C 忽略 ucontext_t 中使用者提供的 ss_sp (堆疊指標)。相反,執行時 (runtime) 會在 makecontext 期間內部配置一個受管理的堆疊,以確保堆疊與 Fil-C 的溢位處理機制相容。
  • 執行緒親和性 (Thread Affinity): 為了維持 ABI 一致性,zfiber_context 會追蹤其被建立時所在的執行緒。它禁止切換到來自不同執行緒的上下文,以防止堆疊在不同執行緒之間遷移。
  • 不透明狀態:jmp_buf 類似,ucontext_t 包含一個指向不透明 zfiber_context 物件的指標,對使用者隱藏了內部的機器狀態。

垃圾回收與纖維切換

將纖維與即時垃圾回收器 (on-the-fly GC) 整合,需要仔細追蹤那些目前不屬於任何執行緒的堆疊。

灰色纖維問題 (The Grey Fiber Problem)

當一個纖維處於 runnable 狀態 (目前未在執行) 時,其堆疊必須由 GC 進行掃描。然而,如果一個變動器 (mutator) 在 GC 標記階段切換到一個纖維,然後在標記期間切換離開,GC 可能會錯過該纖維的堆疊,如果它已經將該纖維標記為「黑色」(已處理)。

Fil-C 解決此問題的方法是追蹤 灰色纖維 (grey fibers)。當在標記階段呼叫 swapcontext 時,被切換「離開」的上下文會被加入到當前執行緒的 grey_fibers 列表。在 GC 終止前的最後一次堆疊重新掃描 (rescan) 期間,執行緒會遍歷其列表中的所有灰色纖維的堆疊,以確保不會錯過任何存活的物件。

API 支援摘要

API Fil-C 安全機制 主要使用案例
setjmp / longjmp 祖先驗證 & 不透明 zjmp_buf 例外處理、信號處理程序
getcontext / makecontext 狀態機 & 受管理堆疊 纖維/協程引導 (bootstrapping)
swapcontext 執行緒親和性 & 灰色纖維 GC 追蹤 纖維上下文切換
                              • [n/a] - [x] - [x] - [x] - [x] - [x] - [x] - [n/a] - [x] - [n/a] - [x] - [n/a] - [x] - [n/a] - [x] - [n/a] - [x] - [n/a] - [x] - [n/a] - [x] - [n/a] - [x] - [n/a] - [x] - [n/a] - [x] - [n/a] - [x] - [n/a] |

Sources