Fil-C 記憶體安全上下文切換
Fil-C 記憶體安全上下文切換
Fil-C 提供了 setjmp、longjmp 和 ucontext API (getcontext、setcontext、makecontext 和 swapcontext) 的記憶體安全實作。這些機制確保了對上下文切換 API 的誤用不會導致堆疊損壞或違反 Fil-C 的能力模型 (capability model),特別是防止了懸空堆疊 (dangling stacks) 的還原。
記憶體安全的 setjmp 與 longjmp
Fil-C 透過將跳躍緩衝區 (jump buffer) 視為不透明物件,並強制執行嚴格的堆疊框架 (stack-frame) 驗證,來防止 setjmp 和 longjmp 常見的記憶體安全陷阱。
標準 setjmp/longjmp 的危險性
在標準 C 語言中,setjmp 會儲存暫存器狀態 (callee-save registers、堆疊指標和指令指標),但不會儲存堆疊本身。這會產生兩個主要的安全性風險:
- 懸空堆疊 (Dangling Stacks): 如果程式呼叫了
setjmp然後從該函數返回,儲存的上下文將變得無效。隨後的longjmp若切換回該上下文,會嘗試還原一個已不存在的堆疊框架,導致機器狀態撕裂 (torn machine state)。 - 編譯器優化錯誤: 因為
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_buf在setjmp呼叫時儲存了該框架在當時的 GC roots 的副本。只要zjmp_buf仍然存活,GC 就會繼續標記這些 roots。
記憶體安全的 ucontext API
對 ucontext API 的支援是在 Fil-C 版本 0.680 中引入的。這些 API 被用於實作纖維 (fibers) 和協程 (coroutines),透過允許儲存與還原執行上下文。
ucontext 狀態機
為了防止誤用,Fil-C 為 zfiber_context 物件實作了受限的狀態機:
- Uninitialized (未初始化): 初始狀態。僅允許使用
getcontext或作為swapcontext的from參數。 - After_getcontext (getcontext 之後):
getcontext返回後的狀態。僅允許使用makecontext或作為swapcontext的from參數。 - Runnable (可執行):
makecontext之後或作為swapcontext的from參數之後的狀態。僅允許使用setcontext或作為swapcontext的toargument 參數。 - 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] |
-
-
-
-
-
-
-
-
-
-
-
-
-