OpenAI 核心傾印流行病學:修復 18 年前的 libunwind 錯誤
OpenAI 核心傾印流行病學:修復 18 年前的 libunwind 錯誤
OpenAI 已經解決了 Rockset 中一系列無法解釋的崩潰,Rockset 是一個基於 C++ 的資料基礎設施服務,用於 ChatGPT 的資料外掛和對話搜尋。調查顯示,原本看似單一問題的事實上是兩個無關的錯誤:特定 Azure 主機上的靜默硬體損壞以及 GNU libunwind 庫中長期存在的競爭條件。
群體層級診斷與逐案除錯
調查的轉折點是在從「醫生」方法(分析單個核心傾印)轉變為「流行病學家」方法(分析整個崩潰群體)。透過建立自動化管道來分析一年份的生產核心傾印,OpenAI 能夠根據暫存器狀態和模式將崩潰分類為不同的群集。
此資料驅動的方法揭示了兩個獨立的崩潰群體:
- 錯誤對齊堆疊崩潰: 這些崩潰局限於單一區域內的一台實體主機,具有明確的開始日期,表明是硬體故障。
- 返回空指針崩潰: 這些崩潰分散於多個群集和區域,暗示是軟體層面的系統性問題。
錯誤 1:靜默硬體損壞
一個崩潰群體被追溯到單一實體 Azure 主機,該主機的 CPU 在執行時進行了錯誤的計算。這導致堆疊指針 (%rsp) 在執行過程中變得錯誤對齊,從而在函數返回時發生崩潰。
由於該損壞是無聲的且無法在受控環境中重現,OpenAI 透過將該主機加入拒絕名單來緩解問題。為防止未來再次發生,團隊改進了致命訊號處理器,使其在日誌中包含暫存器狀態,從而在不需要完整核心傾印的情況下實現更快速的偵測;並更新了控制平面,以 VM 重用優於回收,以便更容易偵測到問題節點。
錯誤 2:GNU libunwind 的 18 年前競爭條件
第二個較為複雜的問題涉及 GNU libunwind 中的競爭條件,GNU libunwind 是一個廣泛使用的開源 C++ 例外解繞庫。該錯誤發生在將控制權轉移至清理處理常式或 catch 區塊期間。
技術機制
當 GNU libunwind 執行 unwind 轉移時,它會在堆疊上合成一個 ucontext_t 結構體以保存所需的暫存器狀態。漏洞存在於 _Ux86_64_setcontext 匯編常式中:
- 該常式將堆疊指針 (
%rsp) 更新為活動堆疊的新底部。 - 一旦
%rsp被更新,ucontext_t結構體將不再是活動堆疊或「紅色區域」(%rsp下方 128 個位元組,受核心保護)的一部分。 - 如果在這個精確時刻發送一個訊號(例如 such asR
SIGUSR2),內核會在%rsp-128處構建訊號框架,可能會覆寫ucontext_t記憶體。 - 如果從此損壞的記憶體中讀取恢復的指令指針 (
%rip),程式會崩潰——通常會返回 NULL。
競爭視窗與機率
競爭視窗大約佔一條指令寬,估計為約 100 皮秒。雖然非常窄,但由於 Rockset 環境中的三個因素,崩潰頻率變得在操作上可見:
- 高異常率: Rockset 使用異常進行內部攝取背壓,在過載主機上每秒拋出高達 $10^4$ 個異常。
- 高訊號率:
coarse_thread_cputime_clock每幾毫秒 CPU 時間發送一次SIGUSR2訊號。 - 增加的堆疊使用: 對
SIGUSR2處理常式的最近更新加入了對timer_getoverrun的呼叫,增加了處理常式使用的堆疊量,使其更有可能覆寫過時的ucontext_t記憶體。
解決與緩解
OpenAI 透過從 GNU libunwind 切換到 libgcc 的解繞器來解決 libunwind 問題,這同時也透過減少大型 VM 上的鎖競爭帶來了效能優勢。此外,OpenAI 將一個自包含的重現程式和修復程式上游提交給 GNU libunwind 項目,以便更廣泛的社區能夠解決此錯誤。