版權(quán)歸原作者所有,如有侵權(quán),請(qǐng)聯(lián)系我們

[科普中國(guó)]-核心轉(zhuǎn)儲(chǔ)

科學(xué)百科
原創(chuàng)
科學(xué)百科為用戶提供權(quán)威科普內(nèi)容,打造知識(shí)科普陣地
收藏

概述

在UNIX系統(tǒng)中,常將“主內(nèi)存”(main memory) 稱(chēng)為核心(core),因?yàn)樵谑褂冒雽?dǎo)體作為內(nèi)存材料之前,便是使用核心(core)。而核心映像(core image) 就是 “進(jìn)程”(process)執(zhí)行當(dāng)時(shí)的內(nèi)存內(nèi)容。當(dāng)進(jìn)程發(fā)生錯(cuò)誤或收到“信號(hào)”(signal) 而終止執(zhí)行時(shí),系統(tǒng)會(huì)將核心映像寫(xiě)入一個(gè)文件,以作為調(diào)試之用,這就是所謂的核心轉(zhuǎn)儲(chǔ)(core dump)。

有時(shí)程序并未經(jīng)過(guò)徹底測(cè)試,這使得它在執(zhí)行的時(shí)候一不小心就會(huì)找到破壞。這可能會(huì)導(dǎo)致核心轉(zhuǎn)儲(chǔ)(core dump)。幸好,現(xiàn)行的UNIX系統(tǒng)極少會(huì)面臨這樣的問(wèn)題。即使遇到,程序員可以通過(guò)核心映像(core image)調(diào)試程序來(lái)找到錯(cuò)誤原因1。

背景核心文件一詞來(lái)源于磁芯內(nèi)存(core memory),1950-1970年代的主要的隨機(jī)存取存儲(chǔ)介質(zhì)。

使用核心文件通常在系統(tǒng)收到特定的信號(hào)時(shí)由操作系統(tǒng)生成。信號(hào)可以由程序執(zhí)行過(guò)程中的異常觸發(fā),也可以由外部程序發(fā)送。動(dòng)作的結(jié)果一般是生成一個(gè)某個(gè)進(jìn)程的內(nèi)存轉(zhuǎn)儲(chǔ)的文件,文件包含了此進(jìn)程當(dāng)前的運(yùn)行堆棧信息。有時(shí)程序并未經(jīng)過(guò)徹底測(cè)試,這使得它在執(zhí)行的時(shí)候一不小心就會(huì)找到破壞。這可能會(huì)導(dǎo)致核心轉(zhuǎn)儲(chǔ)(core dump)。現(xiàn)在的UNIX系統(tǒng)極少會(huì)面臨這樣的問(wèn)題。即使遇到,程序員可以通過(guò)核心映像調(diào)試程序來(lái)找到錯(cuò)誤原因。

分析程序自身產(chǎn)生的coredump文件一般可以用來(lái)分析程序運(yùn)行到哪里出錯(cuò)了。

Linux平臺(tái)常用的coredump文件分析工具是gdb;Solaris平臺(tái)用pstack和pflags;Windows平臺(tái)用userdump和windbg。

外部程序觸發(fā)的dump一般用來(lái)分析進(jìn)程的運(yùn)行情況,比如分析內(nèi)存使用/線程狀態(tài)等。

Solaris的常用內(nèi)存分析工具umem就是需要先通過(guò)gcore pid得到coredump的文件然后繼續(xù)分析內(nèi)存情況。

C/C++程序員遇到的比較常見(jiàn)的一個(gè)問(wèn)題, 就是自己編寫(xiě)的代碼, 在運(yùn)行過(guò)程中出現(xiàn)了意想不到的核心轉(zhuǎn)儲(chǔ)。程序發(fā)生核心轉(zhuǎn)儲(chǔ)的原因是多方面的, 不同的核心轉(zhuǎn)儲(chǔ)問(wèn)題有著不同的解決辦法, 同時(shí), 不同的核心轉(zhuǎn)儲(chǔ)問(wèn)題解決的難易程度也存在很大的區(qū)別, 有些在短短幾秒鐘內(nèi)就可以定位問(wèn)題, 但是也有一些可能需要花費(fèi)數(shù)天時(shí)間才能解決, 這種問(wèn)題是對(duì)軟件開(kāi)發(fā)人員的極大的挑戰(zhàn)。筆者從事C/C++語(yǔ)言的軟件開(kāi)發(fā)工作多年, 前后解決了許多此類(lèi)問(wèn)題, 久而久之積累了一定的經(jīng)驗(yàn), 現(xiàn)把常見(jiàn)程序核心轉(zhuǎn)儲(chǔ)總結(jié)一下, 供軟件開(kāi)發(fā)人員共饗。

1.無(wú)效指針引起的程序核心轉(zhuǎn)儲(chǔ)這種情況是一種最常見(jiàn)的核心轉(zhuǎn)儲(chǔ), 大致可以有4 種原因?qū)е鲁绦虺霈F(xiàn)異常:

(1) 對(duì)空指針進(jìn)行了操作。

(2) 對(duì)一個(gè)未初始化的指針進(jìn)行了操作。

(3) 對(duì)一個(gè)已經(jīng)調(diào)用delete 釋放了內(nèi)存的指針再次調(diào)用了

delete 去重復(fù)釋放(誰(shuí)讓你不在第一次delete 后, 將指針賦值為NULL 呢)。

(4) 多線程訪問(wèn)全局變量, 導(dǎo)致內(nèi)存值異常而程序核心轉(zhuǎn)儲(chǔ)。

此類(lèi)問(wèn)題通常是代碼編寫(xiě)時(shí)的疏漏造成的, 屬于低級(jí)故障, 也比較容易解決, 用調(diào)試工具調(diào)試一下產(chǎn)生的core 文件,對(duì)照代碼定位問(wèn)題出現(xiàn)的原因,10 分鐘就可以搞定。

2. 指針越界引起的程序核心轉(zhuǎn)儲(chǔ)

這種情況屬于一種隱藏比較深的核心轉(zhuǎn)儲(chǔ), 比較難以解決。遇到這種問(wèn)題時(shí), 用調(diào)試工具調(diào)試這個(gè)core 文件, 盡管也能定位到代碼行, 但是從對(duì)應(yīng)行的代碼看, 可能這行代碼本身并沒(méi)有什么問(wèn)題, 它只是一個(gè)“被陷害者”。這種核心轉(zhuǎn)儲(chǔ)很難發(fā)現(xiàn), 解決起來(lái)難度較大。根據(jù)筆者的經(jīng)驗(yàn), 這種核心轉(zhuǎn)儲(chǔ)很可能是其他代碼處理過(guò)程中的內(nèi)存越界造成的, 通常由以下兩個(gè)因素引起。第一個(gè)因素:核心轉(zhuǎn)儲(chǔ)所在的代碼行是一個(gè)很簡(jiǎn)單的操作, 例如賦值語(yǔ)句, “這怎么可能出錯(cuò)呢?” 注釋掉該語(yǔ)句運(yùn)行程序, 核心轉(zhuǎn)儲(chǔ)又發(fā)生在下一行代碼上。此時(shí),相應(yīng)代碼行的操作很可能是對(duì)某個(gè)全局變量B 的操作, 在這種情況下, 需要將視線轉(zhuǎn)移到該全局變量的定義行代碼, 仔細(xì)看看該全局變量前后附近定義的變量A,C 因?yàn)椴僮飨到y(tǒng)不同, 變量位置也不同,有的需要關(guān)注B 變量前面定義的變量A, 有的需要關(guān)注B 變量后面定義的變量C, 仔細(xì)搜索代碼, 看看對(duì)A,C 變量的處理有沒(méi)有可能導(dǎo)致內(nèi)存越界的地方, 很可能就是因?yàn)閷?duì)A,C 操作出現(xiàn)內(nèi)存越界導(dǎo)致B 變量的操作受到傷害, B 夠背運(yùn)吧。

第二因素: 核心轉(zhuǎn)儲(chǔ)的位置內(nèi)存變量的值莫名其妙, 出現(xiàn)

了異常的值。此時(shí), 需要仔細(xì)分析代碼和處理流程了。首先排查本函數(shù)的代碼處理是否有問(wèn)題, 重點(diǎn)關(guān)注memcpy、strstr、sprintf、strcpy 和strcat 等極易出現(xiàn)問(wèn)題的代碼行, 如果確認(rèn)本函數(shù)處理沒(méi)有問(wèn)題, 那么就需要根據(jù)流程來(lái)仔細(xì)走查代碼, 在這種情況下, 最需要的是耐心和信心。對(duì)于這類(lèi)問(wèn)題, 肯定是代碼走到了某個(gè)特殊的邏輯里面, 代碼處理缺少必要的保護(hù)而引起的, 出現(xiàn)一次, 沒(méi)有足夠的日志記錄流程, 很難分析, 從core 文件的內(nèi)存變量的值也無(wú)法定位問(wèn)題原因。但是, 如果再次出現(xiàn), 那么就具有比較大的參考價(jià)值了, 前后兩次的core文件內(nèi)存變量必然存在某種共性, 需要根據(jù)這個(gè)特征來(lái)分析并復(fù)現(xiàn)故障了。筆者曾經(jīng)遇到對(duì)一個(gè)未初始化的緩沖區(qū)A 做字符串操作strstr (此時(shí)它并不會(huì)核心轉(zhuǎn)儲(chǔ)), 但是當(dāng)流程走了很多之后走到另一個(gè)對(duì)變量B 的操作時(shí), 出現(xiàn)了核心轉(zhuǎn)儲(chǔ); 更有甚者, 模塊內(nèi)一個(gè)鏈表算法出現(xiàn)了失誤, 導(dǎo)致指針越界。

3. 操作系統(tǒng)相關(guān)的特殊性造成的程序核心轉(zhuǎn)儲(chǔ)

初學(xué)者對(duì)于這種情況, 必然讓人備感莫名其妙的, “這么簡(jiǎn)單而又規(guī)范的代碼, 怎么會(huì)出這種問(wèn)題?” 這種問(wèn)題與2 的區(qū)別在于, 問(wèn)題很容易復(fù)現(xiàn), 可能程序一運(yùn)行就核心轉(zhuǎn)儲(chǔ)。盡管對(duì)這種核心轉(zhuǎn)儲(chǔ)很不解, 但應(yīng)該相信: 越容易出現(xiàn)的問(wèn)題,越容易解決。就像作為程序員編譯一個(gè)程序, 一下子出現(xiàn)了幾百個(gè)編譯錯(cuò)誤, 根本不用擔(dān)心, 很可能就是某一行代碼多了幾個(gè)字符, 當(dāng)把這些代碼刪去再編譯, 幾百個(gè)編譯錯(cuò)誤全都消失了。

常遇到的此類(lèi)問(wèn)題有兩種情況。

第一種情況: 字節(jié)對(duì)齊方式引起的程序核心轉(zhuǎn)儲(chǔ)??赡苡袃蓚€(gè)原因: 其一, 合作伙伴的模塊與自身模塊所定義的結(jié)構(gòu)體的字節(jié)對(duì)齊方式不同, 而導(dǎo)致程序出現(xiàn)核心轉(zhuǎn)儲(chǔ); 其二, 在代碼中, 把引用到的別的模塊的頭文件包含到自身文件中的字節(jié)對(duì)齊方式語(yǔ)法聲明的中間了, 結(jié)果導(dǎo)致字節(jié)對(duì)齊方式出現(xiàn)了變化。此類(lèi)問(wèn)題不是很常見(jiàn), 不過(guò)一旦出現(xiàn), 往往讓人覺(jué)得很蹊蹺。其實(shí)此類(lèi)問(wèn)題從源頭上解決應(yīng)該還是比較簡(jiǎn)單的, 關(guān)鍵在于一個(gè)良好的習(xí)慣, 如果在定義接口消息的時(shí)候, 多花點(diǎn)時(shí)間規(guī)整結(jié)構(gòu)體的字段定義, 把它往4 字節(jié)的倍數(shù)上靠即可, 應(yīng)該沒(méi)必要處處節(jié)省那么點(diǎn)內(nèi)存。第二種情況: 程序核心轉(zhuǎn)儲(chǔ)情況, 是與程序編譯時(shí)的鏈接參數(shù)不正確而導(dǎo)致程序運(yùn)行在反復(fù)操作大內(nèi)存時(shí)出現(xiàn)核心轉(zhuǎn)儲(chǔ)。經(jīng)過(guò)反復(fù)的代碼刪減、編譯和運(yùn)行的試驗(yàn), 最終發(fā)現(xiàn)問(wèn)題規(guī)律, 于是懷疑和操作系統(tǒng)或者編譯有關(guān), 最終解決了這個(gè)問(wèn)題。程序發(fā)生核心轉(zhuǎn)儲(chǔ)的根本原因還是程序員自己進(jìn)行程序設(shè)計(jì)時(shí)的編碼失誤造成的, 這種代碼失誤絕大多數(shù)都是因?yàn)闆](méi)有嚴(yán)格遵守相應(yīng)的代碼編寫(xiě)規(guī)范, 所以, 要從根本上杜絕或者減少程序核心轉(zhuǎn)儲(chǔ)現(xiàn)象的發(fā)生, 還是要從嚴(yán)格遵守代碼編寫(xiě)規(guī)范來(lái)做起2。