程式設計面試:如何用 eBPF CO-RE 編寫可移植的核心追蹤程式?
題幹與適用場景
觀測代理需要在多種發行版和核心版本上統計系統呼叫延遲。手工為每個核心編譯會造成發布矩陣爆炸,直接讀取核心結構偏移又容易失效。請解釋 BTF、CO-RE 重定位和 libbpf 的職責,並設計可驗證的建置、載入、事件讀取和相容路徑。
面試官考察點
- 是否理解 BTF 型別資訊、編譯器產生的重定位記錄和載入期修補的關係。
- 能否區分 CO-RE 解決的結構布局差異與 verifier、helper、程式型別限制。
- 能否設計 ring buffer 或 perf buffer 的事件生命週期、遺失統計和使用者態解碼。
- 能否處理缺少 BTF、欄位不存在、核心能力不足和程式載入失敗。
回答前需要釐清的問題
- 目標核心是否啟用 BTF,發行版是否提供
/sys/kernel/btf/vmlinux? - 追蹤點、kprobe、fentry 或 LSM 哪種程式型別可用,權限和特權邊界是什麼?
- 事件量、延遲預算、允許的遺失率和使用者態消費模型是什麼?
- 需要支援哪些架構、核心版本和容器部署方式?
- 沒有目標欄位或 verifier 拒絕時,代理應關閉該指標還是使用替代探針?
30 秒回答框架
我會用 clang 產生帶 BTF 和 CO-RE 重定位資訊的單一 BPF 物件,由 libbpf 在目標機載入時根據執行中核心的 BTF 修補欄位偏移。使用者態透過產生的 skeleton 管理 map、程式和 ring buffer,並檢查能力、BTF 與程式型別。欄位缺失或載入失敗時記錄原因,停用該指標或切換到穩定 tracepoint,不回傳偽造資料。基準涵蓋多核心、並發事件、遺失與卸載恢復。
分步驟深入解答
第一步:劃分編譯期和載入期職責
編譯器把 BPF 程式、型別資訊和 CO-RE relocation 寫入物件檔。載入時 libbpf 使用目標核心 BTF 找到結構、欄位和偏移,更新指令中的立即數或偏移。CO-RE 因此減少按核心建置的需要,但不會讓任意 helper、kfunc 或程式型別自動可用。
第二步:選擇穩定的探針邊界
優先使用穩定 tracepoint 或 fentry,並確認目標核心暴露的事件欄位;kprobe 可覆蓋更多路徑,卻更依賴符號和參數布局。對每個欄位用 CO-RE 存取巨集表達意圖,避免手寫固定偏移。程式只讀取必要欄位,縮小 verifier 分析和事件大小。
第三步:設計事件結構與傳輸
在 BPF 側記錄 pid、時間戳、系統呼叫識別和必要延遲資料,使用 ring buffer 或 perf buffer 傳送到使用者態。事件結構保持固定大小或明確版本欄位,使用者態檢查長度和版本。每個 CPU、程序和佇列都應有遺失計數,不能把緩衝區溢出靜默當成零延遲。
第四步:讓 verifier 約束成為設計輸入
邊界檢查、迴圈上限、指標型別和 helper 回傳值必須滿足 verifier。把複雜解析移到使用者態,避免在 BPF 中做不可證明的遍歷。載入前編譯器和 skeleton 產生的 map 定義要與程式存取一致;日誌應保留 verifier 失敗摘要和核心版本,便於定位。
第五步:處理 BTF 與欄位差異
載入前檢查目標 BTF 是否可讀,並用 CO-RE 可選欄位或 feature probe 判斷欄位存在。若結構變更使重定位失敗,關閉該指標或選擇相容 tracepoint;不能把無法讀取的欄位填成預設值後繼續報告精確延遲。架構差異也要在 CI 的多核心矩陣中驗證。
第六步:設計建置與發布鏈路
固定 clang、libbpf 和 skeleton 產生版本,產出帶 BTF 的物件並記錄 build ID。發布包包含使用者態 loader、BPF 物件和最小能力清單;啟動時先自檢,再載入。容器環境要明確所需的 BPF 檔案系統、權限和核心設定,失敗時給出可行動的原因。
第七步:驗證效能、正確性與回滾
在不同發行版、架構和核心上比較欄位值、事件計數與獨立工具結果。壓測高事件率下的 CPU、記憶體、ring buffer 遺失、使用者態落後和 tail latency。升級時保留舊物件或穩定探針作為回滾路徑,卸載時確認 link、map 和執行緒都已釋放。
高品質示範回答
我會用 clang 產生包含 BTF 與 CO-RE relocation 的單一物件,由 libbpf 在目標機依據執行中核心 BTF 修補欄位偏移。探針優先選擇穩定 tracepoint 或 fentry,BPF 只記錄固定版本的最小事件,使用者態 skeleton 透過 ring buffer 解碼並統計遺失。啟動前檢查 BTF、程式型別、helper 能力和欄位存在性;重定位或 verifier 失敗就停用該指標或切換穩定探針,絕不填充偽造資料。CI 涵蓋多架構、多核心、高事件率和卸載恢復,發布包保留舊探針用於回滾。
常見錯誤
- 把 CO-RE 當成對所有 helper、kfunc 和 verifier 限制的相容層。
- 手寫結構偏移,繞過 BTF 和重定位,導致升級後讀取錯誤欄位。
- 事件結構沒有版本和長度檢查,使用者態把截斷資料當成有效樣本。
- 緩衝區遺失不計數,最後把觀測缺口報告成零延遲。
- 缺少 BTF 或欄位時填預設值繼續上報,隱藏相容性失敗。
追問及應對
追問一:CO-RE 重定位發生在什麼時候?
編譯階段把 relocation 寫入 BPF 物件,載入階段 libbpf 使用目標核心 BTF 解析並修補指令中的欄位資訊。它不是執行時每個事件都重新計算偏移。
追問二:沒有 vmlinux BTF 能執行嗎?
取決於程式和發行版提供的 BTF、探針型別及 libbpf 能力。應在啟動自檢中確認可用資訊;無法安全解析時停用該程式或改用不依賴該欄位的穩定探針。
追問三:為什麼不用 kprobe 覆蓋所有版本?
kprobe 靈活但更依賴符號、參數和內聯行為,升級後的語意穩定性較弱。穩定 tracepoint 或 fentry 可降低維護成本,仍需驗證目標核心是否提供。
追問四:如何證明沒有漏報?
同時統計 BPF 事件數、ring buffer 遺失數、使用者態消費數和獨立系統呼叫計數,按 CPU 與時間窗口對帳。任何不一致都應暴露為缺失或降級狀態,而非默認為零。
追問五:verifier 拒絕如何排查?
保存載入日誌、核心版本、目標架構、BTF ID 和 verifier 摘要,先縮小到指標邊界、迴圈上限、helper 或 map 定義差異,再用最小重現程式驗證。修復後重新跑多核心矩陣。