自動 core dump 分析把崩潰程序的 core 檔案交給工具,由工具重建程序在崩潰那一刻正在做甚麼——而不需要您在 GDB 裡手動逐層排查。OpenResty XRay 就是為崩潰的 OpenResty/Nginx 應用做這件事:把它指向 core 檔案,它就會返回一份完整的報告——C 和 Lua 呼叫棧、精確的崩潰原始碼行、Lua 協程分析、Lua GC 物件引用圖、libc 記憶體分配分析,以及崩潰時正在處理的併發 HTTP 請求——全程無需手動使用 GDB。完整的分步演示請觀看本文頂部的影片。

定位 OpenResty/Nginx 的 core dump 檔案

當 OpenResty 或 Nginx 應用的某個 worker 程序崩潰時,Linux 核心會寫出一個 core dump 檔案。在這臺崩潰的伺服器上,worker 程序在使用者的 home 目錄下留下了一個 core.175276 檔案——用 ls 就能找到它,再用 readlink -f 得到它的絕對路徑。複製這個路徑即可;OpenResty XRay 會就地分析該檔案,因此無需移動它,也無需手動匹配相關的庫檔案。

崩潰的 OpenResty 伺服器 home 目錄下列出的 core dump 檔案 core.175276

在 OpenResty XRay 中啟動引導式 core dump 分析

在瀏覽器中開啟 OpenResty XRay 的 Web 控制檯,選擇目標機器(這裡是 biz-app-server),進入 Guided Analysis(引導式分析)頁面。問題型別選擇 Core dumps or process crashes(core dump 或程序崩潰)。

OpenResty XRay 引導式分析的問題列表,已選中 Core dumps or process crashes

貼上 core 檔案路徑。XRay 讀取該 core dump,自動提取可執行檔案路徑(/usr/local/openresty/nginx/sbin/nginx)和應用型別(OpenResty),然後開始分析。如果二進位制被 strip、除錯符號缺失,XRay 會先自動重建缺失的除錯符號——無需安裝 nginx-dbg 包,也無需重新編譯——下文的崩潰報告依然能解析出真實的函式名與原始碼行。

引導式分析步驟顯示 core 檔案路徑以及自動識別出的 nginx 可執行檔案資訊

解讀崩潰報告:訊號、暫存器與 C 呼叫棧

報告首先展示執行上下文。該 worker 程序被 SEGV 訊號中止——SEGV 是 “Segmentation Violation”(段錯誤)的縮寫,通常表示發生了非法的記憶體訪問。

OpenResty XRay 報告指出觸發 core dump 的 SEGV 訊號

在機器碼層面,XRay 用紅色箭頭標出了導致出錯的那條指令——mov ecx, DWORD PTR [rsi+rdx*1-0x4]

導致段錯誤的出錯指令附近的反彙編程式碼

XRay 還捕獲了崩潰發生那一刻的所有 CPU 暫存器。rsirdx 的值都是 0x4,因此這條指令的源地址 [rsi+rdx*1-0x4] 計算下來正好是 0x4——一個幾乎等於空的地址,正是空指標解引用的典型特徵。

core dump 中的 CPU 暫存器值,rsi 和 rdx 都為 0x4,因此出錯的讀取落在地址 0x4 上

C 呼叫棧解釋了執行是如何走到這條指令的。Nginx 的 ngx_http_core_content_phase 透過 ngx_http_lua_run_thread 執行 Lua 內容處理器;隨後 LuaJIT 為某個 cdata 型別分發了 FFI__index 元方法,經過 LuaJIT 的 C 型別轉換後進入 glibc 的 memcpy——段錯誤就是在這裡觸發的。

core dump 的 C 呼叫棧,從 glibc memcpy 一直向上到 LuaJIT FFI 元方法和 Nginx Lua 模組

從 Lua 呼叫棧定位到精確的崩潰程式碼行

XRay 直接從 core dump 重建 Lua 層面的呼叫棧,它是從 Lua CPU 火焰圖推匯出來的。崩潰的呼叫路徑為 content_by_luagoapi.lua)→ handlerouter/order.lua)→ process_orderorder/core.lua:78)→ decode_order_dataorder/processor.lua:79)。

Lua 層的 CPU 火焰圖,崩潰的 decode_order_data 程式碼路徑以紅色高亮

它還給出完整的 Lua 呼叫棧,包含每個函式幀的引數和區域性變數,因此您無需復現任何問題就能檢視崩潰時的精確狀態。

從 core dump 中捕獲的完整 Lua 呼叫棧,含區域性變數和引數

開啟 processor.lua 的第 79 行,就能看到出錯的語句 order.uid = order_cdata.user_idorder_cdata 本身是一個有效的指標型 cdata 物件,但它儲存的 C 指標值為 NULL。讀取 order_cdata.user_id 會在這個 NULL 基址上解引用偏移 4,這正是出錯地址為 0x4 的原因。修復辦法是在訪問欄位前先用 order_cdata == nil 檢查 order_cdata 內部的指標是否為 NULL。

崩潰的 Lua 原始碼行 order.uid = order_cdata.user_id,位於 processor.lua 第 79 行

這次崩潰是一次空指標解引用。對於另一類故障——透過“記錄與回放”定位的 use-after-free(釋放後使用)崩潰——請參閱 從崩潰到根因:OpenResty XRay 如何將 Nginx 記憶體踩踏問題分析得明明白白

崩潰時的 Lua 協程、併發 HTTP 請求與 Lua GC 記憶體

XRay 分析的不止崩潰的那一個協程。它還會分析 core dump 中所有存活的 Lua 協程,並給出其中最常見的 Lua 呼叫棧——本例中是一條停在 sleep 呼叫上的程式碼路徑:同樣是 process_order 業務程式碼,正在 ngx.sleepngx_http_lua_ngx_sleep)裡等待。

core dump 中所有存活 Lua 協程裡最常見的 Lua 呼叫棧,從 content_by_lua 經 process_order 進入 ngx.sleep

報告中高亮顯示的最常見協程呼叫棧頂部的 sleep 幀

報告還捕獲了所有正在處理的 HTTP 請求。崩潰 worker 當時正在處理的請求是 POST https://e.woniu.com:1443/order/api(客戶端 127.0.0.1curl/7.76.1),同時還有三個併發的 GET /order/api?request_type=search_orders 請求。

崩潰時正在處理的 POST /order/api 請求的詳細資訊

在記憶體方面,XRay 對最熱的 Lua GC 物件引用路徑進行排序。這裡排名第一的路徑是 registry_LOADEDengines.sre.sre_librun_rules,佔用了 40.12 MB 存活的 Lua GC 物件——這是從 Lua GC 物件記憶體分佈火焰圖中自動推匯出來的。

core dump 記憶體分析中排名第一的最熱 Lua GC 物件引用路徑

全自動分析與報告

您不必手動執行引導式分析。OpenResty XRay 會監控線上應用產生的新 core dump,自動分析它們,並把結果以日報和週報的形式釋出在 Insights 頁面上。

OpenResty XRay Insights 日報,列出自動分析出的問題

正因如此,引導式分析主要適用於應用開發和演示;在生產環境中,自動報告會為您呈現新出現的崩潰。

常見問題

如何從 core dump 中獲取 Lua 呼叫棧?

OpenResty XRay 直接從 core 檔案重建 Lua 層面的呼叫棧,它是從 Lua CPU 火焰圖自動推匯出來的。它會展示每個 Lua 協程的完整呼叫棧,包括每個函式幀及其引數和區域性變數的值——這些正是您原本需要在 GDB 中手動挖掘的上下文。

LuaJIT 為甚麼會在 FFI cdata 上發生段錯誤?

訪問一個底層 C 指標為 NULL 的指標型 cdata 的欄位,會導致段錯誤。在這個 core dump 中,order_cdata 是一個有效的 cdata 物件,但它所封裝的指標為 NULL;decode_order_data 讀取它的 user_id 欄位,在 NULL 基址上解引用偏移 4(出錯地址 0x4),從而觸發 SEGV 並使 worker 程序崩潰。修復辦法是在解引用欄位之前,用 order_cdata == nil 檢查所封裝的指標是否為 NULL。

甚麼訊號會觸發 OpenResty 或 Nginx 的 core dump?

在這次崩潰中,core dump 是由 SEGV 訊號觸發的。SEGV 是 “Segmentation Violation”(段錯誤)的縮寫,通常表示發生了非法的記憶體訪問——程序訪問了一個不允許它訪問的記憶體地址。OpenResty XRay 會報告確切的訊號、出錯的指令以及崩潰那一刻的 CPU 暫存器值。

如何找到崩潰的確切 Lua 程式碼行?

報告會把崩潰的 Lua 函式幀關聯回它的原始碼。把滑鼠懸停在函式的方框上,就會顯示原始檔及其完整路徑,報告還會給出確切的原始碼行號。這裡指向的是 decode_order_data 的第 79 行,也就是解引用 cdata 內部所封裝的 NULL 指標的那一行。

能不能在不手動執行 GDB 的情況下分析 core dump?

可以。OpenResty XRay 會監控線上應用產生的新 core dump,並在 Insights 頁面生成以日和周為週期的自動分析報告,因此您無需手動執行引導式分析。在底層,它使用 Y 語言,並根據不同場景在 Stap+、eBPF+、GDB、ODB 等多種執行時之上執行。

關於 OpenResty XRay

OpenResty XRay 是一個動態追蹤產品,它可以自動分析執行中的應用程式,以解決效能問題、行為問題和安全漏洞,並提供可行的建議。在底層實現上,OpenResty XRay 由我們的 Y 語言驅動,可以在不同環境下支援多種不同的執行時,如 Stap+、eBPF+、GDB 和 ODB。

關於作者

章亦春是開源 OpenResty® 專案創始人兼 OpenResty Inc. 公司 CEO 和創始人。

章亦春(Github ID: agentzh),生於中國江蘇,現定居美國灣區。他是中國早期開源技術和文化的倡導者和領軍人物,曾供職於多家國際知名的高科技企業,如 Cloudflare、雅虎、阿里巴巴, 是 “邊緣計算“、”動態追蹤 “和 “機器程式設計 “的先驅,擁有超過 22 年的程式設計及 16 年的開源經驗。作為擁有超過 4000 萬全球域名使用者的開源專案的領導者。他基於其 OpenResty® 開源專案打造的高科技企業 OpenResty Inc. 位於美國矽谷中心。其主打的兩個產品 OpenResty XRay(利用動態追蹤技術的非侵入式的故障剖析和排除工具)和 OpenResty Edge(最適合微服務和分散式流量的全能型閘道器軟體),廣受全球眾多上市及大型企業青睞。在 OpenResty 以外,章亦春為多個開源專案貢獻了累計超過百萬行程式碼,其中包括,Linux 核心、Nginx、LuaJITGDBSystemTapLLVM、Perl 等,並編寫過 60 多個開源軟體庫。

關注我們

如果您喜歡本文,歡迎關注我們 OpenResty Inc. 公司的部落格網站 。也歡迎掃碼關注我們的微信公眾號:

我們的微信公眾號

翻譯

我們提供了英文版原文和中譯版(本文)。我們也歡迎讀者提供其他語言的翻譯版本,只要是全文翻譯不帶省略,我們都將會考慮採用,非常感謝!