核心轉儲
核心轉儲
核心轉儲(core dump),在漢語中有時戲稱為吐核,是操作系統在進程收到某些信號而終止運行時,將此時進程地址空間的內容以及有關進程狀態的其他信息寫出的一個磁碟文件。這種信息往往用於調試。
在UNIX系統中,常將“主內存”(main memory) 稱為核心(core),因為在使用半導體作為內存材料之前,便是使用核心(core)。而核心映像(core image) 就是“進程”(process)執行當時的內存內容。當進程發生錯誤或收到“信號”(signal) 而終止執行時,系統會將核心映像寫入一個文件,以作為調試之用,這就是所謂的核心轉儲(core dump)。
有時程序並未經過徹底測試,這使得它在執行的時候一不小心就會找到破壞。這可能會導致核心轉儲(core dump)。幸好,現行的UNIX系統極少會面臨這樣的問題。即使遇到,程序員可以通過核心映像(core image)調試程序來找到錯誤原因。
核心文件一詞來源於磁芯內存(core memory),1950-1970年代的主要的隨機存取存儲介質。
核心文件通常在系統收到特定的信號時由操作系統生成。信號可以由程序執行過程中的異常觸發,也可以由外部程序發送。動作的結果一般是生成一個某個進程的內存轉儲的文件,文件包含了此進程當前的運行堆棧信息。有時程序並未經過徹底測試,這使得它在執行的時候一不小心就會找到破壞。這可能會導致核心轉儲(core dump)。現在的UNIX系統極少會面臨這樣的問題。即使遇到,程序員可以通過核心映像調試程序來找到錯誤原因。
程序自身產生的coredump文件一般可以用來分析程序運行到哪裡出錯了。
外部程序觸發的dump一般用來分析進程的運行情況,比如分析內存使用/線程狀態等。
Solaris的常用內存分析工具umem就是需要先通過gcore pid得到coredump的文件然後繼續分析內存情況。
C/C++程序員遇到的比較常見的一個問題,就是自己編寫的代碼,在運行過程中出現了意想不到的核心轉儲。程序發生核心轉儲的原因是多方面的,不同的核心轉儲問題有著不同的解決辦法,同時,不同的核心轉儲問題解決的難易程度也存在很大的區別,有些在短短几秒鐘內就可以定位問題,但是也有一些可能需要花費數天時間才能解決,這種問題是對軟體開發人員的極大的挑戰。筆者從事C/C++語言的軟體開發工作多年,前後解決了許多此類問題,久而久之積累了一定的經驗,現把常見程序核心轉儲總結一下,供軟體開發人員共饗。
1.無效指針引起的程序核心轉儲這種情況是一種最常見的核心轉儲,大致可以有4 種原因導致程序出現異常:
(1)對空指針進行了操作。
(2)對一個未初始化的指針進行了操作。
(3)對一個已經調用delete 釋放了內存的指針再次調用了
delete 去重複釋放(誰讓你不在第一次delete 后,將指針賦值為NULL 呢)。
此類問題通常是代碼編寫時的疏漏造成的,屬於低級故障,也比較容易解決,用調試工具調試一下產生的core 文件,對照代碼定位問題出現的原因,10 分鐘就可以搞定。
2. 指針越界引起的程序核心轉儲
這種情況屬於一種隱藏比較深的核心轉儲,比較難以解決。遇到這種問題時,用調試工具調試這個core 文件,儘管也能定位到代碼行,但是從對應行的代碼看,可能這行代碼本身並沒有什麼問題,它只是一個“被陷害者”。這種核心轉儲很難發現,解決起來難度較大。根據筆者的經驗,這種核心轉儲很可能是其他代碼處理過程中的內存越界造成的,通常由以下兩個因素引起。第一個因素:核心轉儲所在的代碼行是一個很簡單的操作,例如賦值語句, “這怎麼可能出錯呢?”註釋掉該語句運行程序,核心轉儲又發生在下一行代碼上。此時,相應代碼行的操作很可能是對某個全局變數B 的操作,在這種情況下,需要將視線轉移到該全局變數的定義行代碼,仔細看看該全局變數前後附近定義的變數A,C 因為操作系統不同,變數位置也不同,有的需要關注B 變數前面定義的變數A,有的需要關注B 變數後面定義的變數C,仔細搜索代碼,看看對A,C 變數的處理有沒有可能導致內存越界的地方,很可能就是因為對A,C 操作出現內存越界導致B 變數的操作受到傷害, B 夠背運吧。
第二因素:核心轉儲的位置內存變數的值莫名其妙,出現
了異常的值。此時,需要仔細分析代碼和處理流程了。首先排查本函數的代碼處理是否有問題,重點關注memcpy、strstr、sprintf、strcpy 和strcat 等極易出現問題的代碼行,如果確認本函數處理沒有問題,那麼就需要根據流程來仔細走查代碼,在這種情況下,最需要的是耐心和信心。對於這類問題,肯定是代碼走到了某個特殊的邏輯裡面,代碼處理缺少必要的保護而引起的,出現一次,沒有足夠的日誌記錄流程,很難分析,從core 文件的內存變數的值也無法定位問題原因。但是,如果再次出現,那麼就具有比較大的參考價值了,前後兩次的core文件內存變數必然存在某種共性,需要根據這個特徵來分析並復現故障了。筆者曾經遇到對一個未初始化的緩衝區A 做字元串操作strstr (此時它並不會核心轉儲),但是當流程走了很多之後走到另一個對變數B 的操作時,出現了核心轉儲;更有甚者,模塊內一個鏈表演演算法出現了失誤,導致指針越界。
3. 操作系統相關的特殊性造成的程序核心轉儲
初學者對於這種情況,必然讓人備感莫名其妙的, “這麼簡單而又規範的代碼,怎麼會出這種問題?”這種問題與2 的區別在於,問題很容易復現,可能程序一運行就核心轉儲。儘管對這種核心轉儲很不解,但應該相信:越容易出現的問題,越容易解決。就像作為程序員編譯一個程序,一下子出現了幾百個編譯錯誤,根本不用擔心,很可能就是某一行代碼多了幾個字元,當把這些代碼刪去再編譯,幾百個編譯錯誤全都消失了。
常遇到的此類問題有兩種情況。
第一種情況:位元組對齊方式引起的程序核心轉儲。可能有兩個原因:其一,合作夥伴的模塊與自身模塊所定義的結構體的位元組對齊方式不同,而導致程序出現核心轉儲;其二,在代碼中,把引用到的別的模塊的頭文件包含到自身文件中的位元組對齊方式語法聲明的中間了,結果導致位元組對齊方式出現了變化。此類問題不是很常見,不過一旦出現,往往讓人覺得很蹊蹺。其實此類問題從源頭上解決應該還是比較簡單的,關鍵在於一個良好的習慣,如果在定義介面消息的時候,多花點時間規整結構體的欄位定義,把它往4 位元組的倍數上靠即可,應該沒必要處處節省那麼點內存。第二種情況:程序核心轉儲情況,是與程序編譯時的鏈接參數不正確而導致程序運行在反覆操作大內存時出現核心轉儲。經過反覆的代碼刪減、編譯和運行的試驗,最終發現問題規律,於是懷疑和操作系統或者編譯有關,最終解決了這個問題。程序發生核心轉儲的根本原因還是程序員自己進行程序設計時的編碼失誤造成的,這種代碼失誤絕大多數都是因為沒有嚴格遵守相應的代碼編寫規範,所以,要從根本上杜絕或者減少程序核心轉儲現象的發生,還是要從嚴格遵守代碼編寫規範來做起。