9. ORC 解棧器

9.1. 概述

核心 CONFIG_UNWINDER_ORC 選項啟用 ORC 解棧器,它在概念上類似於 DWARF 解棧器。區別在於 ORC 資料的格式比 DWARF 簡單得多,這反過來使得 ORC 解棧器也簡單得多且速度更快。

ORC 資料由 objtool 生成的解棧表組成。它們包含核心 ORC 解棧器使用的帶外資料。Objtool 首先進行編譯時堆疊元資料驗證 (CONFIG_STACK_VALIDATION) 來生成 ORC 資料。在分析 .o 檔案的所有程式碼路徑後,它確定檔案中每個指令地址的堆疊狀態資訊,並將該資訊輸出到 .orc_unwind 和 .orc_unwind_ip 段。

每個物件的 ORC 段在連結時組合,並在啟動時進行排序和後處理。解棧器使用生成的資料將指令地址與執行時它們的堆疊狀態相關聯。

9.2. ORC 與幀指標

啟用幀指標後,GCC 會將檢測程式碼新增到核心中的每個函式。核心的 .text 大小增加約 3.2%,導致整個核心範圍內的速度下降。Mel Gorman 的測量 [1] 顯示,對於某些工作負載,速度下降了 5-10%。

相比之下,ORC 解棧器對文字大小或執行時效能沒有影響,因為 debuginfo 是帶外的。因此,如果您停用幀指標並啟用 ORC 解棧器,您將獲得全面的效能提升,並且仍然具有可靠的堆疊跟蹤。

Ingo Molnar 說

“請注意,這不僅僅是效能改進,而且還是指令快取區域性性的改進:節省 3.2% 的 .text 幾乎直接轉化為類似大小的快取佔用減少。對於快取區域性性處於臨界狀態的工作負載,這可以轉化為更高的加速。”

與幀指標相比,ORC 的另一個優點是它可以可靠地跨中斷和異常解棧。如果中斷的函式是葉函式,或者中斷在幀指標儲存之前命中,則基於幀指標的解棧有時會跳過中斷函式的呼叫者。

與幀指標相比,ORC 解棧器的主要缺點是它需要更多的記憶體來儲存 ORC 解棧表:大約 2-4MB,具體取決於核心配置。

9.3. ORC 與 DWARF

ORC debuginfo 優於 DWARF 的優勢在於它更簡單。它擺脫了複雜的 DWARF CFI 狀態機,也擺脫了對不必要暫存器的跟蹤。這使得解棧器更加簡單,這意味著更少的錯誤,這對於任務關鍵型 oops 程式碼尤其重要。

更簡單的 debuginfo 格式還使解棧器比 DWARF 快得多,這對於 perf 和 lockdep 很重要。在 Jiri Slaby 的一項基本效能測試中 [2],ORC 解棧器比樹外的 DWARF 解棧器快約 20 倍。(注意:該測量是在新增一些效能調整之前進行的,這些調整使效能翻了一番,因此相對於 DWARF 的加速可能接近 40 倍。)

與 DWARF 相比,ORC 資料格式確實有一些缺點。ORC 解棧表佔用的 RAM 比基於 DWARF 的 eh_frame 表多約 50%(在 x86 defconfig 核心上多 +1.3MB)。

另一個潛在的缺點是,隨著 GCC 的發展,ORC 資料可能最終變得簡單,無法描述某些最佳化的堆疊狀態。但 IMO 這不太可能,因為 GCC 會儲存幀指標用於它所做的任何不尋常的堆疊調整,所以我懷疑我們實際上只需要在呼叫幀之間跟蹤堆疊指標和幀指標。但即使我們最終不得不跟蹤 DWARF 跟蹤的所有暫存器,至少我們仍然可以控制格式,例如,沒有複雜的狀態機。

9.4. ORC 解棧表生成

ORC 資料由 objtool 生成。憑藉現有的編譯時堆疊元資料驗證特性,objtool 已經遵循所有程式碼路徑,因此它已經擁有了從頭開始生成 ORC 資料所需的所有資訊。因此,從堆疊驗證到 ORC 資料生成是一個簡單的步驟。

應該可以使用一個簡單的工具來生成 ORC 資料,該工具將 DWARF 轉換為 ORC 資料。但是,由於核心廣泛使用 asm、內聯 asm 以及異常表等特殊段,因此這樣的解決方案是不完整的。

可以透過使用 GNU 彙編器 .cfi 註釋在 .S 檔案中手動註釋這些特殊的程式碼路徑,併為 .c 檔案中的內聯 asm 新增自定義註釋來糾正這一點。但是,過去曾嘗試過 asm 註釋,但發現它們無法維護。它們通常不正確/不完整,並且使程式碼更難閱讀和保持更新。並且根據檢視 glibc 程式碼,在 .c 檔案中註釋內聯 asm 可能會更糟。

Objtool 仍然需要一些註釋,但僅在對堆疊執行不尋常操作的程式碼中,例如入口程式碼。即使這樣,所需的註釋也比 DWARF 需要的少得多,因此它們比 DWARF CFI 註釋更易於維護。

因此,使用 objtool 生成 ORC 資料的優點是它可以提供更準確的 debuginfo,並且註釋非常少。它還可以使核心免受工具鏈錯誤的影響,因為我們經常需要解決工具鏈舊版本中的問題多年,這可能非常痛苦。

缺點是解棧器現在依賴於 objtool 反向工程 GCC 程式碼流的能力。如果 GCC 最佳化對於 objtool 來說變得太複雜而無法遵循,則 ORC 資料生成可能會停止工作或變得不完整。(值得注意的是,livepatch 已經對 objtool 遵循 GCC 程式碼流的能力有這樣的依賴性。)

如果較新版本的 GCC 出現一些破壞 objtool 的最佳化,我們可能需要重新審視當前的實現。一些可能的解決方案是要求 GCC 使最佳化更容易接受,或者讓 objtool 使用 DWARF 作為附加輸入,或者建立一個 GCC 外掛來幫助 objtool 進行分析。但就目前而言,objtool 可以很好地遵循 GCC 程式碼。

9.5. 解棧器實現細節

Objtool 透過與編譯時堆疊元資料驗證特性整合來生成 ORC 資料,該特性在 tools/objtool/Documentation/objtool.txt 中有詳細描述。在分析 .o 檔案的所有程式碼路徑後,它會建立一個 orc_entry 結構陣列,以及一個與這些結構相關聯的指令地址並行陣列,並將它們分別寫入 .orc_unwind 和 .orc_unwind_ip 段。

ORC 資料被分成兩個陣列是出於效能原因,以使資料的可搜尋部分 (.orc_unwind_ip) 更緊湊。這些陣列在啟動時並行排序。

透過使用在執行時建立的快速查詢表進一步提高了效能。快速查詢表將給定的地址與 .orc_unwind 表的一系列索引相關聯,以便只需要搜尋該表的一個小子集。

9.6. 詞源學

獸人是中世紀民間傳說中可怕的生物,是矮人的天敵。同樣,ORC 解棧器也是為了對抗 DWARF 的複雜性和緩慢性而建立的。

“雖然獸人很少考慮問題的多種解決方案,但他們擅長完成事情,因為他們是行動的生物,而不是思考的生物。” [3] 類似地,與深奧的 DWARF 解棧器不同,真實的 ORC 解棧器不會浪費時間或矽酮精力來解碼基於可變長度零擴充套件無符號整數字節編碼狀態機的除錯資訊條目。

類似於獸人經常破壞其對手精心策劃的計劃,ORC 解棧器經常以殘酷、毫不妥協的效率解開堆疊。

ORC 代表 Oops Rewind Capability (Oops 回溯能力)。