?? (ldd) ch04-調(diào)試技術(shù)(轉(zhuǎn)載).txt
字號:
頁邊界(Alpha上的頁面大小是8KB,緩沖區(qū)正好在頁面的起始位置附近)。如果在你的
系統(tǒng)上讀取faulty沒有產(chǎn)生oops,試試wc,或者給dd顯式地指定塊大小。
使用ksymoops
oops消息的最大問題就是十六進(jìn)制數(shù)值對于程序員來說沒什么意義;需要將它們解析為
符號。
內(nèi)核源碼通過其所包含的ksymoops工具幫助開發(fā)人員――但是注意,版本1.2的源碼中沒
有這個(gè)程序。該工具將oops消息中的數(shù)值地址解析為內(nèi)核符號,但只限于PC機(jī)產(chǎn)生的oop
s消息。由于消息本身就是處理器相關(guān)的,每一體系結(jié)構(gòu)都有其自身的消息格式。
ksymoops從標(biāo)準(zhǔn)輸入獲得oops消息,并從命令行內(nèi)核符號表的名字。符號表通常就是/us
ksymoops從標(biāo)準(zhǔn)輸入獲得oops消息,并從命令行內(nèi)核符號表的名字。符號表通常就是/us
r/src/linux/System.map。程序以更可讀的方式打印調(diào)用軌跡和程序代碼,而不是最原
始的oops消息。下面的片斷就是用上一節(jié)的oops消息得出的結(jié)果:
(代碼)
由ksymoops反匯編出的代碼給出了失效的指令和其后的指令。很明顯――對于那些知道
一點(diǎn)匯編的人――repz movsl指令(REPeat till cx is Zero, MOVe a String of
Longs)用源索引(esi,是0x202e000)訪問了一個(gè)未映射頁面。用來獲得模塊信息的ks
ymoops -m命令給出,模塊映射到一個(gè)在0x0202dxxx的頁面上,這也確認(rèn)樂esi確實(shí)超出
了范圍。
由于faulty模塊所占用的內(nèi)存不在系統(tǒng)表中,被解碼的調(diào)用軌跡還給出了兩個(gè)數(shù)值地址
。這些值可以手動補(bǔ)充,或是通過ksyms命令的輸出,或是在/proc/ksyms中查詢模塊的
名字。
然而對于這個(gè)失效,這兩個(gè)地址并不對應(yīng)與代碼地址。如果你看了arch/i386/kernel/tr
aps.c,你就發(fā)現(xiàn),調(diào)用軌跡是從整個(gè)堆棧并利用一些啟發(fā)式方法區(qū)分?jǐn)?shù)據(jù)值(本地變量
和函數(shù)參數(shù))和返回地址獲得的。調(diào)用軌跡中只給出了引用內(nèi)核代碼的地址和引用模塊
的地址。由于模塊所占頁面既有代碼也有數(shù)據(jù),錯(cuò)綜復(fù)雜的棧可能會漏掉啟發(fā)式信息,
這就是上面兩個(gè)0x202xxxx地址的情況。
如果你不愿手動查看模塊地址,下面這組管道可以用來創(chuàng)建一個(gè)既有內(nèi)核又有模塊符號
如果你不愿手動查看模塊地址,下面這組管道可以用來創(chuàng)建一個(gè)既有內(nèi)核又有模塊符號
的符號表。無論何時(shí)你加載模塊,你都必須重新創(chuàng)建這個(gè)符號表。
(代碼)
這個(gè)管道將完整的系統(tǒng)表與/proc/ksyms中的公開內(nèi)核符號混合在一起,后者除了內(nèi)核符
號外,還包括了當(dāng)前內(nèi)核里的模塊符號。這些地址在insmod重定位代碼后就出現(xiàn)在/proc
/ksyms中。由于這兩個(gè)文件的格式不同,使用了sed和awk將所有的文本行轉(zhuǎn)換為一種合
適的格式。然后對這張表排序,去除重復(fù)部分,這樣ksymoops就可以用了。
如果我們重新運(yùn)行ksymoops,它從新的符號表中截取出如下信息:
(代碼)
正如你所見到的,當(dāng)跟蹤與模塊有關(guān)的oops消息時(shí),創(chuàng)建一個(gè)修訂的系統(tǒng)表是很有助益
的:現(xiàn)在ksymoops能夠?qū)χ噶钪羔樈獯a并完成整個(gè)調(diào)用軌跡了。還要注意,顯式反匯編
碼的格式和objdump所使用的格式一樣。objdump也是一個(gè)功能強(qiáng)大的工具;如果你需要
查看失敗前的指令,你調(diào)用命令objdump –d faulty.o。
在文件的匯編列表中,字串faulty_read+45/60標(biāo)記為失效行。有關(guān)objdump的更多的信
息和它的命令行選項(xiàng)可以參見該命令的手冊。
即便你構(gòu)建了你自己的修訂版符號表,上面提到的有關(guān)調(diào)用軌跡的問題仍然存在:雖然0
即便你構(gòu)建了你自己的修訂版符號表,上面提到的有關(guān)調(diào)用軌跡的問題仍然存在:雖然0
x202xxxx指針被解碼了,但仍然是假的。
學(xué)會解碼oops消息需要一定的經(jīng)驗(yàn),但是確實(shí)值得一做。用來學(xué)習(xí)的時(shí)間很快就會有所
回報(bào)。不過由于機(jī)器指令的Unix語法與Intel語法不同,唯一的問題在于從哪獲得有關(guān)匯
編語言的文檔;盡管你了解PC匯編語言,但你的經(jīng)驗(yàn)都是用Intel語法的編程獲得的。在
參考書目中,我給一些有所補(bǔ)益的書籍。
使用oops
使用ksymoops有些繁瑣。你需要C++編譯器編譯它,你還要構(gòu)建你自己的符號表來充分發(fā)
揮程序的能力,你還要將原始消息和ksymoops輸出合在一起組成可用的信息。
如果你不想找這么多麻煩,你可以使用oops程序。oops在本書的O’Reilly FTP站點(diǎn)給出
的源碼中。它源自最初的ksymoops工具,現(xiàn)在它的作者已經(jīng)不維護(hù)這個(gè)工具了。oops是
用C語言寫成的,而且直接查看/proc/ksyms而無需用戶每次加載模塊后構(gòu)建新的符號表
。
該程序試圖解碼所有的處理器寄存器并 顏 軌跡解析為符號值。它的缺點(diǎn)是,它要比ksy
moops羅嗦些,但通常你所有的信息越多,你發(fā)現(xiàn)錯(cuò)誤也就越快。oops的另一個(gè)優(yōu)點(diǎn)是,
它可以解析x86,Alpha和Sparc的oops消息。與內(nèi)核源碼相同,這個(gè)程序也按GPL發(fā)行。
oops產(chǎn)生的輸出與ksymoops的類似,但是更完全。這里給出前一個(gè)oops輸出的開始部分
—由于在這個(gè)oops消息中堆棧沒保存什么有用的東西,我不認(rèn)為應(yīng)該顯示整個(gè) 顏 軌跡
—由于在這個(gè)oops消息中堆棧沒保存什么有用的東西,我不認(rèn)為應(yīng)該顯示整個(gè) 顏 軌跡
:
(代碼)
當(dāng)你調(diào)試“真正的”模塊(faulty太短了,沒有什么意義)時(shí),將寄存器和堆棧解碼是
非常有益的,而且如果被調(diào)試的所有模塊符號都開放出來時(shí)更有幫助。在失效時(shí),處理
器寄存器一般不會指向模塊的符號,只有當(dāng)符號表開放給/proc/ksyms時(shí),你才能輸出中
標(biāo)別它們。
我們可以用一下步驟制作一張更完整的符號表。首先,我們不應(yīng)在模塊中聲明靜態(tài)變量
,否則我們就無法用insmod開放它們了。第二,如下面的截取自scull的init_module函
數(shù)的代碼所示,我們可以用#ifdef SCULL_DEBUG或類似的宏屏蔽register_symtab調(diào)用。
(代碼)
我們在第2章“編寫和運(yùn)行模塊”的“注冊符號表”一節(jié)中已經(jīng)看到了類似內(nèi)容,那里說
,如果模塊不注冊符號表,所有的全局符號就都開放。盡管這一功能僅在SCULL_DEBUG被
激活時(shí)才有效,為了避免內(nèi)核中的名字空間污染,所有的全局符號有合適的前綴(參見
第2章的“模塊與應(yīng)用程序”一節(jié))。
使用klogd
使用klogd
klogd守護(hù)進(jìn)程的近期版本可以在oops存放到記錄文件前對oops消息解碼。解碼過程只由
版本1.3或更新版本的守護(hù)進(jìn)程完成,而且只有將-k /usr/src/linux/System.map做為參
數(shù)傳遞給守護(hù)進(jìn)程時(shí)才解碼。(你可以用其他符號表文件代替System.map)
有新的klogd給出的faulty的oops如下所示,它寫到了系統(tǒng)記錄中:
(代碼)
我想能解碼的klogd對于調(diào)試一般的Linux安裝的核心來說是很好的工具。由klogd解碼的
消息包括大部分ksymoops的功能,而且也要求用戶編譯額外的工具,或是,當(dāng)系統(tǒng)出現(xiàn)
故障時(shí),為了給出完整的錯(cuò)誤報(bào)告而合并兩個(gè)輸出。當(dāng)oops發(fā)生在內(nèi)核時(shí),守護(hù)進(jìn)程還
會正確地解碼指令指針。它并不反匯編代碼,但這不是問題,當(dāng)錯(cuò)誤報(bào)告給出消息時(shí),
二進(jìn)制數(shù)據(jù)仍然存在,可以離線反匯編代碼。
守護(hù)進(jìn)程的另一個(gè)功能就是,如果符號表版本與當(dāng)前內(nèi)核不匹配,它會拒絕解析符號。
如果在系統(tǒng)記錄中解析出了符號,你可以確信它是正確的解碼。
然而,盡管它對Linux用戶很有幫助,這個(gè)工具在調(diào)試模塊時(shí)沒有什么幫助。我個(gè)人沒有
在開放軟件的電腦里使用解碼選項(xiàng)。klogd的問題是它不解析模塊中的符號;因?yàn)槭刈o(hù)進(jìn)
程在程序員加載模塊前就已經(jīng)運(yùn)行了,即使讀了/proc/ksyms也不會有什么幫助。記錄文
件中存在解析后的符號會使oops和ksymoops混淆,造成進(jìn)一步解析的困難。
如果你需要使用klogd調(diào)試你的模塊,最新版本的守護(hù)進(jìn)程需要加入一些新的特殊支持,
我期待它的完成,只要給內(nèi)核打一個(gè)小補(bǔ)丁就可以了。
系統(tǒng)掛起
盡管內(nèi)核代碼中的大多數(shù)錯(cuò)誤僅會導(dǎo)致一個(gè)oops消息,有時(shí)它們困難完全將系統(tǒng)掛起。
如果系統(tǒng)掛起了,沒有消息能夠打印出來。例如,如果代碼遇到一個(gè)死循環(huán),內(nèi)核停止
了調(diào)度過程,系統(tǒng)不會再響應(yīng)任何動作,包括魔法鍵Ctrl-Alt-Del組合。
處理系統(tǒng)掛起有兩個(gè)選擇――一個(gè)是防范與未然,另一個(gè)就是亡羊補(bǔ)牢,在發(fā)生掛起后
調(diào)試代碼。
通過在策略點(diǎn)上插入schedule調(diào)用可以防止死循環(huán)。schedule調(diào)用(正如你所猜想到的
)調(diào)用調(diào)度器,因此允許其他進(jìn)程偷取當(dāng)然進(jìn)程的CPU時(shí)間。如果進(jìn)程因你的驅(qū)動程序中
的錯(cuò)誤而在內(nèi)核空間循環(huán),你可以在跟蹤到這種情況后殺掉這個(gè)進(jìn)程。
在驅(qū)動程序代碼中插入schedule調(diào)用會給程序員帶來新的“問題”:函數(shù),,以及調(diào)用軌
跡中的所有函數(shù),必須是可重入的。在正常環(huán)境下,由于不同的進(jìn)程可能并發(fā)地訪問設(shè)
備,驅(qū)動程序做為整體是可重入的,但由于Linux內(nèi)核是不可搶占的,不必每個(gè)函數(shù)都是
可重入的。但如果驅(qū)動程序函數(shù)允許調(diào)度器中斷當(dāng)前進(jìn)程,另一個(gè)不同的進(jìn)程可能會進(jìn)
入同一個(gè)函數(shù)。如果schedule調(diào)用僅在調(diào)試期間打開,如果你不允許,你可以避免兩個(gè)
并發(fā)進(jìn)程訪問驅(qū)動程序,所以并發(fā)性倒不是什么非常重要的問題。在介紹阻塞型操作時(shí)
(第5章的“寫可重入代碼”)我們再詳細(xì)介紹并發(fā)性問題。
(第5章的“寫可重入代碼”)我們再詳細(xì)介紹并發(fā)性問題。
如果要調(diào)試死循環(huán),你可以利用Linux鍵盤的特殊鍵。默認(rèn)情況下,如果和修飾鍵一起按
了PrScr鍵(鍵碼是70),系統(tǒng)會向當(dāng)前控制臺打印有關(guān)機(jī)器狀態(tài)的有用信息。這一功能
在x86和Alpha系統(tǒng)都有。Linux的Sparc移植也有同樣的功能,但它使用了標(biāo)記為“Break
/Scroll Lock”的鍵(鍵碼是30)。
每一個(gè)特殊函數(shù)都有一個(gè)名字,并如下面所示都有一個(gè)按鍵事件與之對應(yīng)。組合鍵之后
的括號里是函數(shù)名。
Shift-PrScr(Show_Memory)
打印若干行關(guān)于內(nèi)存使用的信息,尤其是有關(guān)緩沖區(qū)高速緩存的使用情況。
?? 快捷鍵說明
復(fù)制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -