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 匯編常式中:

  1. 該常式將堆疊指針 (%rsp) 更新為活動堆疊的新底部。
  2. 一旦 %rsp 被更新,ucontext_t 結構體將不再是活動堆疊或「紅色區域」(%rsp 下方 128 個位元組,受核心保護)的一部分。
  3. 如果在這個精確時刻發送一個訊號(例如 such asRSIGUSR2),內核會在 %rsp-128 處構建訊號框架,可能會覆寫 ucontext_t 記憶體。
  4. 如果從此損壞的記憶體中讀取恢復的指令指針 (%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 項目,以便更廣泛的社區能夠解決此錯誤。

Sources