檢視原始碼 如何解讀 Erlang 崩潰傾印檔

本節描述 Erlang 運行系統異常終止時產生的 erl_crash.dump 檔案。

注意

Erlang 崩潰傾印檔在 Erlang/OTP R9C 中進行了大幅改版。因此,本節中的資訊並不直接適用於較舊的傾印檔。但是,如果您在較舊的傾印檔上使用 crashdump_viewer,崩潰傾印檔將被轉換為類似於此的格式。

系統會在模擬器的目前目錄中或環境變數(在目前的作業系統上表示的任何意思) ERL_CRASH_DUMP 所指向的檔案中寫入崩潰傾印檔。要寫入崩潰傾印檔,必須掛載可寫入的檔案系統。

寫入崩潰傾印檔的主要原因有兩個:要麼是從正在執行的 Erlang 程式碼中明確使用字串引數呼叫內建函式 erlang:halt/1,要麼是運行系統偵測到無法處理的錯誤。系統無法處理錯誤最常見的原因是外部限制,例如記憶體不足。內部錯誤導致的崩潰傾印可能是因為系統在模擬器本身中達到限制(例如系統中的原子數量,或同時存在的 ETS 表格過多)。通常,可以重新設定模擬器或作業系統以避免崩潰,這就是為什麼正確解讀崩潰傾印檔很重要。

在支援作業系統訊號的系統上,也可以透過傳送 SIGUSR1 訊號來停止運行系統並產生崩潰傾印檔。

Erlang 崩潰傾印檔是一個可讀的文字檔,但可能難以閱讀。使用 Observer 應用程式中的 Crashdump Viewer 工具可以簡化這項任務。這是一個基於 wx-widget 的工具,用於瀏覽 Erlang 崩潰傾印檔。

一般資訊

崩潰傾印檔的第一部分顯示以下資訊:

  • 傾印檔的建立時間
  • 指示傾印原因的標語
  • 傾印檔來源節點的系統版本
  • 原子表中原子的數量
  • 導致崩潰傾印的執行系統執行緒

崩潰傾印的原因(標語)

傾印原因會在檔案開頭顯示為

Slogan: <reason>

如果系統是由 BIF erlang:halt/1 停止的,標語會是傳遞給 BIF 的字串引數,否則會是模擬器或 (Erlang) 核心產生的描述。通常,訊息足以理解問題,但此處會描述一些訊息。請注意,建議的崩潰原因僅為建議。錯誤的確切原因可能因本機應用程式和底層作業系統而異。

  • <A>: 無法配置 <N> 位元組的記憶體(類型為 "<T>") - 系統記憶體不足。<A> 是無法配置記憶體的配置器,<N> 是 <A> 嘗試配置的位元組數,而 <T> 是需要記憶體的記憶體區塊類型。最常見的情況是進程儲存大量的資料。在這種情況下,<T> 最常見的是 heapold_heapheap_fragbinary。有關配置器的詳細資訊,請參閱 erts_alloc(3)

  • <A>: 無法重新配置 <N> 位元組的記憶體(類型為 "<T>") - 與上述相同,只是系統記憶體不足時重新配置而非配置記憶體。

  • 意外的運算碼 <N> - 編譯程式碼中的錯誤、beam 檔案損壞或編譯器中的錯誤。

  • 模組 <Name> 未定義 | 函式 <Name> 未定義 | 沒有函式 <Name>:<Name>/1 | 沒有函式 <Name>:start/2 - 核心/STDLIB 應用程式已損壞或啟動腳本已損壞。

  • 使用過大的檔案描述符 N 呼叫 Driver_select - socket 的檔案描述符數量超過 1024 (僅限 Unix)。在某些 Unix 版本中,檔案描述符的限制可以設定為超過 1024,但 Erlang 一次只能使用 1024 個 socket/管道(因為 Unix select 呼叫的限制)。開啟的一般檔案數量不受此影響。

  • 收到 SIGUSR1 - 將 SIGUSR1 訊號傳送到 Erlang 機台 (僅限 Unix) 會強制產生崩潰傾印。此標語反映 Erlang 機台因收到該訊號而產生崩潰傾印。

  • 核心 pid 終止 (<Who>) (<Exit reason>) - 核心監視器偵測到失敗,通常是 application_controller 已關閉 (Who = application_controller, Why = shutdown)。應用程式控制器可能因多種原因關閉,最常見的原因是分散式 Erlang 節點的節點名稱已在使用中。完整的監視器樹狀結構「崩潰」(也就是頂層監視器已退出)會產生大致相同的結果。此訊息來自 Erlang 程式碼,而不是來自虛擬機器本身。這始終是因為應用程式(無論是 OTP 內部還是「使用者撰寫」的應用程式)發生某些失敗。查看您應用程式的錯誤記錄檔可能是要採取的首要步驟。

  • Init 在 do_boot () 中終止 - 原始 Erlang 啟動序列已終止,很可能是因為啟動腳本有錯誤或無法讀取。這通常是組態錯誤;系統可能使用錯誤的 -boot 參數或來自錯誤 OTP 版本的啟動腳本啟動。

  • 無法啟動核心 pid (<Who>) () - 其中一個核心進程無法啟動。這可能是因為錯誤的引數(例如 -config 引數中的錯誤)或錯誤的組態檔案。請檢查所有檔案是否位於正確的位置,以及組態檔案(如果有的話)是否損壞。通常也會將訊息寫入控制終端和/或錯誤記錄檔,說明錯誤的原因。

除了這些錯誤之外,還可能發生其他錯誤,因為 erlang:halt/1 BIF 可以產生任何訊息。如果訊息不是由 BIF 產生的,且未出現在上述清單中,則可能是模擬器發生錯誤。但是,可能會出現一些不尋常的訊息(此處未提及),這些訊息仍然與應用程式失敗有關。還有更多可用資訊,因此徹底閱讀崩潰傾印檔可以揭示崩潰原因。進程的大小、ETS 表格的數量以及每個進程堆疊上的 Erlang 資料對於找出問題可能很有用。

原子數量

系統在崩潰時的原子數量會顯示為 Atoms: <number>。幾萬個原子是完全正常的,但更多則可能表示使用 BIF erlang:list_to_atom/1 動態產生許多不同的原子,這絕不是一個好主意。

排程器資訊

在標籤 =scheduler 下,會顯示有關執行系統中排程器的目前狀態和統計資訊。在允許暫停其他執行緒的作業系統上,此部分中的資料會反映發生崩潰時執行系統的外觀。

以下欄位可能存在於進程中

  • =scheduler:id - 標題。說明排程器識別碼。

  • 排程器睡眠資訊旗標 - 如果為空,則表示排程器正在執行某些工作。如果不為空,則表示排程器處於某種睡眠狀態或已暫停。

  • 排程器睡眠資訊輔助工作 - 如果不為空,則表示已排程要完成排程器內部輔助工作。

  • 目前連接埠 - 排程器目前正在執行的連接埠的連接埠識別碼。

  • 目前進程 - 排程器目前正在執行的進程的進程識別碼。如果存在此類進程,則此項目後面會接著該進程的狀態內部狀態程式計數器CP。這些項目在 進程資訊 一節中描述。

    請注意,這是開始產生崩潰傾印檔時項目的確切快照。因此,它們很可能與在 =proc 區段中找到的相同進程的項目不同(且更具指示性)。如果目前沒有正在執行的進程,則只會顯示目前進程項目。

  • 目前進程有限堆疊追蹤 - 僅在存在目前進程時才會顯示此項目。它類似於 =proc_stack,只是只顯示函式框架(也就是省略了堆疊變數)。此外,僅顯示堆疊的頂部和底部。如果堆疊較小(< 512 個插槽),則會顯示整個堆疊。否則,會顯示項目跳過 ## 個插槽,其中 ## 會取代為已跳過的插槽數。

  • 執行佇列 - 顯示有關此排程器上排程的不同優先級的進程和連接埠數量的統計資訊。

  • *** 崩潰 *** - 此項目通常不會顯示。這表示由於某些原因,無法取得有關此排程器的其餘資訊。

記憶體資訊

在標籤 =memory 下,會顯示類似於使用 erlang:memory() 在作用中節點上取得的資訊。

內部表格資訊

在標籤 =hash_table:<table_name>=index_table:<table_name> 下,會顯示內部表格。這些表格主要供執行系統開發人員使用。

已配置區域

在標籤 =allocated_areas 下,會顯示類似於使用 erlang:system_info(allocated_areas) 在作用中節點上取得的資訊。

記憶體配置器 (Allocator)

在標籤 =allocator:<A> 下,會顯示關於記憶體配置器 <A> 的各種資訊。這些資訊與在運作中的節點上使用 erlang:system_info({allocator, <A>}) 所能取得的資訊類似。如需更多資訊,請參閱 erts_alloc(3)

程序資訊 (Process Information)

Erlang 崩潰轉儲 (crashdump) 包含系統中每個運作中 Erlang 程序的清單。一個程序可能存在以下欄位:

  • =proc:<pid> - 標題。指出程序的識別碼。

  • State - 程序狀態。可能為以下其中一種:

    • Scheduled - 程序已排程執行,但目前未執行(在「執行佇列」中)。

    • Waiting - 程序正在等待某些事件(在 receive 中)。

    • Running - 程序目前正在執行。如果呼叫了 BIF erlang:halt/1,則此為呼叫它的程序。

    • Exiting - 程序正在退出中。

    • Garbing - 這是不好的情況,在寫入崩潰轉儲時,程序正在進行垃圾回收。此程序的其餘資訊有限。

    • Suspended - 程序已暫停,可能是因為 BIF erlang:suspend_process/1 或因為嘗試寫入忙碌的埠。

  • Registered name - 程序的註冊名稱(如果有的話)。

  • Spawned as - 程序的進入點,也就是在啟動程序的 spawnspawn_link 呼叫中所參照的函式。

  • Last scheduled in for | Current call - 程序目前的函式。這些欄位不一定存在。

  • Spawned by - 程序的父程序,也就是執行 spawnspawn_link 的程序。

  • Started - 程序啟動的日期和時間。

  • Message queue length - 程序訊息佇列中的訊息數量。

  • Number of heap fragments - 配置的堆積片段數量。

  • Heap fragment data - 以字 (words) 為單位表示的片段化堆積資料大小。這些資料可能是傳送給程序的訊息或 Erlang BIF 所建立的。此數值取決於太多因素,因此此欄位通常不重要。

  • Link list - 連結到此程序的程序 ID。也可能包含埠。如果使用程序監控,此欄位也會說明監控的方向。也就是說,連結「到」某個程序的連結表示「目前」程序正在監控另一個程序,而連結「自」某個程序的連結表示另一個程序正在監控目前程序。

  • Reductions - 程序消耗的 reductions 數量。

  • Stack+heap - 以字 (words) 為單位表示的堆疊和堆積大小(它們共用記憶體區段)。

  • OldHeap - 以字 (words) 為單位表示的「舊堆積」大小。Erlang 虛擬機器使用兩代的世代垃圾回收。有一個堆積用於新的資料項目,另一個堆積用於已存活兩次垃圾回收的資料。假設(幾乎總是正確的)是,存活兩次垃圾回收的資料可以「永久化」到較少進行垃圾回收的堆積,因為它們會存活很長一段時間。這是虛擬機器中常用的技術。堆積和堆疊的總和構成了程序所分配的大部分記憶體。

  • Heap unused, OldHeap unused - 以字 (words) 為單位表示的每個堆積上未使用的記憶體量。此資訊通常無用。

  • Memory - 此程序使用的總記憶體量,以位元組為單位。這包括呼叫堆疊、堆積和內部結構。與 erlang:process_info(Pid,memory) 相同。

  • Program counter - 目前的指令指標。這僅對執行期系統開發人員有意義。指令指標指向的函式是程序目前的函式。

  • CP - 繼續指標,也就是目前呼叫的返回位址。通常除了執行期系統開發人員之外,其他人都不會使用到。之後可能會接著 CP 指向的函式,也就是呼叫目前函式的函式。

  • Arity - 作用中的引數暫存器數量。如果存在任何作用中的引數暫存器,將會接著顯示。這些可能包含函式的引數,如果它們尚未移至堆疊的話。

  • Internal State - 此程序狀態的更詳細內部表示。

另請參閱 程序資料 一節。

埠資訊 (Port Information)

此章節列出開啟的埠、其擁有者、任何連結的程序,以及其驅動程式或外部程序的名稱。

ETS 表格 (ETS Tables)

此章節包含系統中所有 ETS 表格的資訊。以下欄位是每個表格中需要關注的:

  • =ets:<owner> - 標題。指出表格擁有者(程序識別碼)。

  • Table - 表格的識別碼。如果表格是 named_table,則此為名稱。

  • Name - 表格名稱,無論其是否為 named_table

  • Hash table, Buckets - 如果表格是雜湊表,也就是說,如果它不是 ordered_set

  • Hash table, Chain Length - 如果表格是雜湊表。包含關於表格的統計資訊,例如最大、最小和平均鏈長度。如果最大值遠大於平均值,且標準差遠大於預期的標準差,則表示某些詞彙的雜湊行為不佳。

  • Ordered set (AVL tree), Elements - 如果表格是 ordered_set。(元素數量與表格中的物件數量相同。)

  • Fixed - 如果使用 ets:safe_fixtable/2 或某些內部機制來固定表格。

  • Objects - 表格中的物件數量。

  • Words - 分配給表格中資料的字 (words) 數量。

  • Type - 表格類型,也就是 setbagduplicate_bagordered_set

  • Compressed - 表格是否已壓縮。

  • Protection - 表格的保護類型。

  • Write Concurrency - 是否為表格啟用 write_concurrency

  • Read Concurrency - 是否為表格啟用 read_concurrency

計時器 (Timers)

此章節包含有關使用 BIF erlang:start_timer/3erlang:send_after/3 啟動的所有計時器的資訊。每個計時器都存在以下欄位:

  • =timer:<owner> - 標題。指出計時器擁有者(程序識別碼),也就是計時器過期時接收訊息的程序。

  • Message - 要傳送的訊息。

  • Time left - 直到訊息傳送的剩餘毫秒數。

分散式資訊 (Distribution Information)

如果 Erlang 節點處於運作狀態,也就是設定為與其他節點通訊,則此章節會列出作用中的連線。可能存在以下欄位:

  • =node:<node_name> - 節點名稱。

  • no_distribution - 如果節點未分散式。

  • =visible_node:<channel> - 可見節點的標題,也就是與崩潰的節點建立連線的運作中節點。指出節點的通道號碼。

  • =hidden_node:<channel> - 隱藏節點的標題。隱藏節點與可見節點相同,只是它是使用 "-hidden" 標誌啟動的。指出節點的通道號碼。

  • =not_connected:<channel> - 先前已連線至崩潰節點的節點標題。在崩潰時,存在參照(也就是程序或埠識別碼)至未連線節點。指出節點的通道號碼。

  • Name - 遠端節點的名稱。

  • Controller - 控制與遠端節點通訊的埠。

  • Creation - 一個整數 (1-3),與節點名稱一起識別節點的特定執行個體。

  • Remote monitoring: <local_proc> <remote_proc> - 本地程序在崩潰時正在監控遠端程序。

  • Remotely monitored by: <local_proc> <remote_proc> - 遠端程序在崩潰時正在監控本地程序。

  • Remote link: <local_proc> <remote_proc> - 在崩潰時,本地程序與遠端程序之間存在連結。

已載入模組資訊 (Loaded Module Information)

此章節包含有關所有已載入模組的資訊。

首先,摘要說明已載入程式碼使用的記憶體:

  • Current code - 程式碼是模組目前的最新版本。

  • Old code - 程式碼在系統中存在較新版本,但舊版本尚未清除。

然後,會列出所有已載入模組。存在以下欄位:

  • =mod:<module_name> - 標題。指出模組名稱。

  • Current size - 已載入程式碼使用的記憶體,以位元組為單位。

  • Old size - 舊程式碼使用的記憶體,以位元組為單位。

  • Current attributes - 目前程式碼的模組屬性。使用 Crashdump Viewer 工具查看時,會解碼此欄位。

  • Old attributes - 舊程式碼的模組屬性(如果有的話)。使用 Crashdump Viewer 工具查看時,會解碼此欄位。

  • Current compilation info - 目前程式碼的編譯資訊(選項)。使用 Crashdump Viewer 工具查看時,會解碼此欄位。

  • Old compilation info - 舊程式碼的編譯資訊(選項)(如果有的話)。使用 Crashdump Viewer 工具查看時,會解碼此欄位。

Fun 資訊 (Fun Information)

此章節列出所有 fun。每個 fun 都存在以下欄位:

  • =fun - 標題。

  • Module - 定義 fun 的模組名稱。

  • Uniq, Index - 識別碼。

  • Address - fun 程式碼的位址。

  • Refc - fun 的參照數量。

程序資料 (Process Data)

對於每個程序,至少會有一個 =proc_stack 和一個 =proc_heap 標籤,接著是該程序堆疊和堆積的原始記憶體資訊。

對於每個程序,如果程序訊息佇列非空,則還會有一個 =proc_messages 標籤;如果程序字典(即 put/2get/1 的機制)非空,則會有一個 =proc_dictionary 標籤。

原始記憶體資訊可以使用 Crashdump Viewer 工具解碼。然後您可以看到堆疊傾印、訊息佇列(如果有的話)和字典(如果有的話)。

堆疊傾印是 Erlang 程序堆疊的傾印。大多數的動態資料(即,目前正在使用的變數)都放在堆疊上;因此這可能會很有趣。雖然必須「猜測」什麼是什麼,但是由於資訊是符號化的,因此徹底閱讀這些資訊可能會很有用。例如,在以下範例中,我們可以找到 Erlang 原始載入器在線的狀態變數 (5)(6)

(1)  3cac44   Return addr 0x13BF58 (<terminate process normally>)
(2)  y(0)     ["/view/siri_r10_dev/clearcase/otp/erts/lib/kernel/ebin",
(3)            "/view/siri_r10_dev/clearcase/otp/erts/lib/stdlib/ebin"]
(4)  y(1)     <0.1.0>
(5)  y(2)     {state,[],none,#Fun<erl_prim_loader.6.7085890>,undefined,#Fun<erl_prim_loader.7.9000327>,
(6)            #Fun<erl_prim_loader.8.116480692>,#Port<0.2>,infinity,#Fun<erl_prim_loader.9.10708760>}
(7)  y(3)     infinity

在解讀程序的資料時,了解匿名函式物件(funs)會被賦予以下資訊是很有幫助的:

  • 從建立它們的函式名稱建構而成的名稱
  • 一個數字(從 0 開始),表示該函式中 fun 的編號

原子

本節介紹系統中的所有原子。只有當懷疑動態生成原子可能是一個問題時,才需要關注此部分,否則可以忽略此部分。

請注意,最後建立的原子會最先顯示。

免責聲明

當 OTP 版本之間演進時,損毀傾印的格式也會隨之改變。這裡描述的一些資訊可能不適用於您的版本。像這樣的描述永遠不會是完整的;它旨在作為對損毀傾印的總體解釋,並在嘗試查找應用程式錯誤時提供幫助,而不是作為完整的規格說明。