亚洲av成人无遮挡网站在线观看,少妇性bbb搡bbb爽爽爽,亚洲av日韩精品久久久久久,兔费看少妇性l交大片免费,无码少妇一区二区三区

  免費注冊 查看新帖 |

Chinaunix

  平臺 論壇 博客 文庫
最近訪問板塊 發(fā)新帖
查看: 4837 | 回復(fù): 9
打印 上一主題 下一主題

Linux 內(nèi)核調(diào)試器內(nèi)幕zt [復(fù)制鏈接]

論壇徽章:
0
跳轉(zhuǎn)到指定樓層
1 [收藏(0)] [報告]
發(fā)表于 2003-09-15 10:34 |只看該作者 |倒序瀏覽
Linux 內(nèi)核調(diào)試器內(nèi)幕         
原文地址:http://www-900.ibm.com/developer ... l-kdbug/index.shtml
內(nèi)容:
入門
初始化并設(shè)置環(huán)境變量
激活 KDB
KDB 命令
技巧和訣竅
結(jié)束語
參考資料


KDB 入門指南
Hariprasad Nellitheertha(nharipra@in.ibm.com)
軟件工程師,IBM
2003 年 9 月
調(diào)試內(nèi)核問題時,能夠跟蹤內(nèi)核執(zhí)行情況并查看其內(nèi)存和數(shù)據(jù)結(jié)構(gòu)是非常有用的。Linux 中的內(nèi)置內(nèi)核調(diào)試器 KDB 提供了這種功能。在本文中您將了解如何使用 KDB 所提供的功能,以及如何在 Linux 機器上安裝和設(shè)置 KDB。您還將熟悉 KDB 中可以使用的命令以及設(shè)置和顯示選項。
Linux 內(nèi)核調(diào)試器(KDB)允許您調(diào)試 Linux 內(nèi)核。這個恰如其名的工具實質(zhì)上是內(nèi)核代碼的補丁,它允許高手訪問內(nèi)核內(nèi)存和數(shù)據(jù)結(jié)構(gòu)。KDB 的主要優(yōu)點之一就是它不需要用另一臺機器進行調(diào)試:您可以調(diào)試正在運行的內(nèi)核。
設(shè)置一臺用于 KDB 的機器需要花費一些工作,因為需要給內(nèi)核打補丁并進行重新編譯。KDB 的用戶應(yīng)當熟悉 Linux 內(nèi)核的編譯(在一定程度上還要熟悉內(nèi)核內(nèi)部機理),但是如果您需要編譯內(nèi)核方面的幫助,請參閱本文結(jié)尾處的參考資料一節(jié)。
在本文中,我們將從有關(guān)下載 KDB 補丁、打補丁、(重新)編譯內(nèi)核以及啟動 KDB 方面的信息著手。然后我們將了解 KDB 命令并研究一些較常用的命令。最后,我們將研究一下有關(guān)設(shè)置和顯示選項方面的一些詳細信息。
入門
KDB 項目是由 Silicon Graphics 維護的(請參閱參考資料以獲取鏈接),您需要從它的 FTP 站點下載與內(nèi)核版本有關(guān)的補丁。(在編寫本文時)可用的最新 KDB 版本是 4.2。您將需要下載并應(yīng)用兩個補丁。一個是“公共的”補丁,包含了對通用內(nèi)核代碼的更改,另一個是特定于體系結(jié)構(gòu)的補丁。補丁可作為 bz2 文件獲取。例如,在運行 2.4.20 內(nèi)核的 x86 機器上,您會需要 kdb-v4.2-2.4.20-common-1.bz2 和 kdb-v4.2-2.4.20-i386-1.bz2。
這里所提供的所有示例都是針對 i386 體系結(jié)構(gòu)和 2.4.20 內(nèi)核的。您將需要根據(jù)您的機器和內(nèi)核版本進行適當?shù)母。您還需要擁有 root 許可權(quán)以執(zhí)行這些操作。
將文件復(fù)制到 /usr/src/linux 目錄中并從用 bzip2 壓縮的文件解壓縮補丁文件:
  1. #bzip2 -d kdb-v4.2-2.4.20-common-1.bz2

  2. #bzip2 -d kdb-v4.2-2.4.20-i386-1.bz2
復(fù)制代碼

您將獲得 kdb-v4.2-2.4.20-common-1 和 kdb-v4.2-2.4-i386-1 文件。
現(xiàn)在,應(yīng)用這些補。
  1. #patch -p1 <kdb-v4.2-2.4.20-common-1

  2. #patch -p1 <kdb-v4.2-2.4.20-i386-1
復(fù)制代碼

這些補丁應(yīng)該干凈利落地加以應(yīng)用。查找任何以 .rej 結(jié)尾的文件。這個擴展名表明這些是失敗的補丁。如果內(nèi)核樹沒問題,那么補丁的應(yīng)用就不會有任何問題。
接下來,需要構(gòu)建內(nèi)核以支持 KDB。第一步是設(shè)置 CONFIG_KDB 選項。使用您喜歡的配置機制(xconfig 和 menuconfig 等)來完成這一步。轉(zhuǎn)到結(jié)尾處的“Kernel hacking”部分并選擇“Built-in Kernel Debugger support”選項。
您還可以根據(jù)自己的偏好選擇其它兩個選項。選擇“Compile the kernel with frame pointers”選項(如果有的話)則設(shè)置 CONFIG_FRAME_POINTER 標志。這將產(chǎn)生更好的堆;厮荩驗閹羔樇拇嫫鞅挥米鲙羔樁皇峭ㄓ眉拇嫫。您還可以選擇“KDB off by default”選項。這將設(shè)置 CONFIG_KDB_OFF 標志,并且在缺省情況下將關(guān)閉 KDB。我們將在后面一節(jié)中對此進行詳細介紹。
保存配置,然后退出。重新編譯內(nèi)核。建議在構(gòu)建內(nèi)核之前執(zhí)行“make clean”。用常用方式安裝內(nèi)核并引導它。
初始化并設(shè)置環(huán)境變量
您可以定義將在 KDB 初始化期間執(zhí)行的 KDB 命令。需要在純文本文件 kdb_cmds 中定義這些命令,該文件位于 Linux 源代碼樹(當然是在打了補丁之后)的 KDB 目錄中。該文件還可以用來定義設(shè)置顯示和打印選項的環(huán)境變量。文件開頭的注釋提供了編輯文件方面的幫助。使用這個文件的缺點是,在您更改了文件之后需要重新構(gòu)建并重新安裝內(nèi)核。
激活 KDB
如果編譯期間沒有選中 CONFIG_KDB_OFF,那么在缺省情況下 KDB 是活動的。否則,您需要顯式地激活它 - 通過在引導期間將 kdb=on 標志傳遞給內(nèi)核或者通過在掛裝了 /proc 之后執(zhí)行該工作:
  1. #echo "1" >;/proc/sys/kernel/kdb
復(fù)制代碼

倒過來執(zhí)行上述步驟則會取消激活 KDB。也就是說,如果缺省情況下 KDB 是打開的,那么將 kdb=off 標志傳遞給內(nèi)核或者執(zhí)行下面這個操作將會取消激活 KDB:
  1. #echo "0" >;/proc/sys/kernel/kdb
復(fù)制代碼

在引導期間還可以將另一個標志傳遞給內(nèi)核。kdb=early 標志將導致在引導過程的初始階段就把控制權(quán)傳遞給 KDB。如果您需要在引導過程初始階段進行調(diào)試,那么這將有所幫助。
調(diào)用 KDB 的方式有很多。如果 KDB 處于打開狀態(tài),那么只要內(nèi)核中有緊急情況就自動調(diào)用它。按下鍵盤上的 PAUSE 鍵將手工調(diào)用 KDB。調(diào)用 KDB 的另一種方式是通過串行控制臺。當然,要做到這一點,需要設(shè)置串行控制臺(請參閱參考資料以獲取這方面的幫助)并且需要一個從串行控制臺進行讀取的程序。按鍵序列 Ctrl-A 將從串行控制臺調(diào)用 KDB。
KDB 命令
KDB 是一個功能非常強大的工具,它允許進行幾個操作,比如內(nèi)存和寄存器修改、應(yīng)用斷點和堆棧跟蹤。根據(jù)這些,可以將 KDB 命令分成幾個類別。下面是有關(guān)每一類中最常用命令的詳細信息。
內(nèi)存顯示和修改
這一類別中最常用的命令是 md、mdr、mm 和 mmW。
md 命令以一個地址/符號和行計數(shù)為參數(shù),顯示從該地址開始的 line-count 行的內(nèi)存。如果沒有指定 line-count,那么就使用環(huán)境變量所指定的缺省值。如果沒有指定地址,那么 md 就從上一次打印的地址繼續(xù)。地址打印在開頭,字符轉(zhuǎn)換打印在結(jié)尾。
mdr 命令帶有地址/符號以及字節(jié)計數(shù),顯示從指定的地址開始的 byte-count 字節(jié)數(shù)的初始內(nèi)存內(nèi)容。它本質(zhì)上和 md 一樣,但是它不顯示起始地址并且不在結(jié)尾顯示字符轉(zhuǎn)換。mdr 命令較少使用。
mm 命令修改內(nèi)存內(nèi)容。它以地址/符號和新內(nèi)容作為參數(shù),用 new-contents 替換地址處的內(nèi)容。
mmW 命令更改從地址開始的 W 個字節(jié)。請注意,mm 更改一個機器字。
示例
  1. 顯示從 0xc000000 開始的 15 行內(nèi)存:
  2. [0]kdb>; md 0xc000000 15
  3. 將內(nèi)存位置為 0xc000000 上的內(nèi)容更改為 0x10:
  4. [0]kdb>; mm 0xc000000 0x10
復(fù)制代碼

寄存器顯示和修改
這一類別中的命令有 rd、rm 和 ef。
rd 命令(不帶任何參數(shù))顯示處理器寄存器的內(nèi)容。它可以有選擇地帶三個參數(shù)。如果傳遞了 c 參數(shù),則 rd 顯示處理器的控制寄存器;如果帶有 d 參數(shù),那么它就顯示調(diào)試寄存器;如果帶有 u 參數(shù),則顯示上一次進入內(nèi)核的當前任務(wù)的寄存器組。
rm 命令修改寄存器的內(nèi)容。它以寄存器名稱和 new-contents 作為參數(shù),用 new-contents 修改寄存器。寄存器名稱與特定的體系結(jié)構(gòu)有關(guān)。目前,不能修改控制寄存器。
ef 命令以一個地址作為參數(shù),它顯示指定地址處的異常幀。
示例
顯示通用寄存器組:
  1. [0]kdb>; rd
  2. 將寄存器 ebx 的內(nèi)容設(shè)置成 0x25:
  3. [0]kdb>; rm %ebx 0x25
復(fù)制代碼

斷點
常用的斷點命令有 bp、bc、bd、be 和 bl。
bp 命令以一個地址/符號作為參數(shù),它在地址處應(yīng)用斷點。當遇到該斷點時則停止執(zhí)行并將控制權(quán)交予 KDB。該命令有幾個有用的變體。bpa 命令對 SMP 系統(tǒng)中的所有處理器應(yīng)用斷點。bph 命令強制在支持硬件寄存器的系統(tǒng)上使用它。bpha 命令類似于 bpa 命令,差別在于它強制使用硬件寄存器。
bd 命令禁用特殊斷點。它接收斷點號作為參數(shù)。該命令不是從斷點表中除去斷點,而只是禁用它。斷點號從 0 開始,根據(jù)可用性順序分配給斷點。
be 命令啟用斷點。該命令的參數(shù)也是斷點號。
bl 命令列出當前的斷點集。它包含了啟用的和禁用的斷點。
bc 命令從斷點表中除去斷點。它以具體的斷點號或 * 作為參數(shù),在后一種情況下它將除去所有斷點。
示例
  1. 對函數(shù) sys_write() 設(shè)置斷點:
  2. [0]kdb>; bp sys_write
  3. 列出斷點表中的所有斷點:
  4. [0]kdb>; bl
  5. 清除斷點號 1:
  6. [0]kdb>; bc 1
復(fù)制代碼

堆棧跟蹤
主要的堆棧跟蹤命令有 bt、btp、btc 和 bta。
bt 命令設(shè)法提供有關(guān)當前線程的堆棧的信息。它可以有選擇地將堆棧幀地址作為參數(shù)。如果沒有提供地址,那么它采用當前寄存器來回溯堆棧。否則,它假定所提供的地址是有效的堆棧幀起始地址并設(shè)法進行回溯。如果內(nèi)核編譯期間設(shè)置了 CONFIG_FRAME_POINTER 選項,那么就用幀指針寄存器來維護堆棧,從而就可以正確地執(zhí)行堆棧回溯。如果沒有設(shè)置 CONFIG_FRAME_POINTER,那么 bt 命令可能會產(chǎn)生錯誤的結(jié)果。
btp 命令將進程標識作為參數(shù),并對這個特定進程進行堆;厮。
btc 命令對每個活動 CPU 上正在運行的進程執(zhí)行堆棧回溯。它從第一個活動 CPU 開始執(zhí)行 bt,然后切換到下一個活動 CPU,以此類推。
bta 命令對處于某種特定狀態(tài)的所有進程執(zhí)行回溯。若不帶任何參數(shù),它就對所有進程執(zhí)行回溯?梢杂羞x擇地將各種參數(shù)傳遞給該命令。將根據(jù)參數(shù)處理處于特定狀態(tài)的進程。選項以及相應(yīng)的狀態(tài)如下:
"D:不可中斷狀態(tài)
"R:正運行
"S:可中斷休眠
"T:已跟蹤或已停止
"Z:僵死
"U:不可運行
這類命令中的每一個都會打印出一大堆信息。請查閱下面的參考資料以獲取這些字段的詳細文檔。
示例
  1. 跟蹤當前活動線程的堆棧:
  2. [0]kdb>; bt
  3. 跟蹤標識為 575 的進程的堆棧:
  4. [0]kdb>; btp 575
復(fù)制代碼

其它命令
下面是在內(nèi)核調(diào)試過程中非常有用的其它幾個 KDB 命令。
id 命令以一個地址/符號作為參數(shù),它對從該地址開始的指令進行反匯編。環(huán)境變量 IDCOUNT 確定要顯示多少行輸出。
ss 命令單步執(zhí)行指令然后將控制返回給 KDB。該指令的一個變體是 ssb,它執(zhí)行從當前指令指針地址開始的指令(在屏幕上打印指令),直到它遇到將引起分支轉(zhuǎn)移的指令為止。分支轉(zhuǎn)移指令的典型示例有 call、return 和 jump。
go 命令讓系統(tǒng)繼續(xù)正常執(zhí)行。一直執(zhí)行到遇到斷點為止(如果已應(yīng)用了一個斷點的話)。
reboot 命令立刻重新引導系統(tǒng)。它并沒有徹底關(guān)閉系統(tǒng),因此結(jié)果是不可預(yù)測的。
ll 命令以地址、偏移量和另一個 KDB 命令作為參數(shù)。它對鏈表中的每個元素反復(fù)執(zhí)行作為參數(shù)的這個命令。所執(zhí)行的命令以列表中當前元素的地址作為參數(shù)。
示例
  1. 反匯編從例程 schedule 開始的指令。所顯示的行數(shù)取決于環(huán)境變量 IDCOUNT:
  2. [0]kdb>; id schedule
  3. 執(zhí)行指令直到它遇到分支轉(zhuǎn)移條件(在本例中為指令 jne)為止:
  4. [0]kdb>; ssb

  5. 0xc0105355 default_idle+0x25: cli
  6. 0xc0105356 default_idle+0x26: mov 0x14(%edx),%eax
  7. 0xc0105359 default_idle+0x29: test %eax, %eax
  8. 0xc010535b default_idle+0x2b: jne 0xc0105361 default_idle+0x31
復(fù)制代碼

技巧和訣竅
調(diào)試一個問題涉及到:使用調(diào)試器(或任何其它工具)找到問題的根源以及使用源代碼來跟蹤導致問題的根源。單單使用源代碼來確定問題是極其困難的,只有老練的內(nèi)核黑客才有可能做得到。相反,大多數(shù)的新手往往要過多地依靠調(diào)試器來修正錯誤。這種方法可能會產(chǎn)生不正確的問題解決方案。我們擔心的是這種方法只會修正表面癥狀而不能解決真正的問題。此類錯誤的典型示例是添加錯誤處理代碼以處理 NULL 指針或錯誤的引用,卻沒有查出無效引用的真正原因。
結(jié)合研究代碼和使用調(diào)試工具這兩種方法是識別和修正問題的最佳方案。
調(diào)試器的主要用途是找到錯誤的位置、確認癥狀(在某些情況下還有起因)、確定變量的值,以及確定程序是如何出現(xiàn)這種情況的(即,建立調(diào)用堆棧)。有經(jīng)驗的黑客會知道對于某種特定的問題應(yīng)使用哪一個調(diào)試器,并且能迅速地根據(jù)調(diào)試獲取必要的信息,然后繼續(xù)分析代碼以識別起因。
因此,這里為您介紹了一些技巧,以便您能使用 KDB 快速地取得上述結(jié)果。當然,要記住,調(diào)試的速度和精確度來自經(jīng)驗、實踐和良好的系統(tǒng)知識(硬件和內(nèi)核內(nèi)部機理等)。
技巧 #1
在 KDB 中,在提示處輸入地址將返回與之最為匹配的符號。這在堆棧分析以及確定全局數(shù)據(jù)的地址/值和函數(shù)地址方面極其有用。同樣,輸入符號名則返回其虛擬地址。
示例
  1. 表明函數(shù) sys_read 從地址 0xc013db4c 開始:
  2. [0]kdb>; 0xc013db4c

  3. 0xc013db4c = 0xc013db4c (sys_read)
  4. 同樣,
  5. 同樣,表明 sys_write 位于地址 0xc013dcc8:
  6. [0]kdb>; sys_write

  7. sys_write = 0xc013dcc8 (sys_write)
  8. 這些有助于在分析堆棧時找到全局數(shù)據(jù)和函數(shù)地址。
復(fù)制代碼

技巧 #2在編譯帶 KDB 的內(nèi)核時,只要 CONFIG_FRAME_POINTER 選項出現(xiàn)就使用該選項。為此,需要在配置內(nèi)核時選擇“Kernel hacking”部分下面的“Compile the kernel with frame pointers”選項。這確保了幀指針寄存器將被用作幀指針,從而產(chǎn)生正確的回溯。實際上,您可以手工轉(zhuǎn)儲幀指針寄存器的內(nèi)容并跟蹤整個堆棧。例如,在 i386 機器上,%ebp 寄存器可以用來回溯整個堆棧。
例如,在函數(shù) rmqueue() 上執(zhí)行第一個指令后,堆?瓷先ヮ愃朴谙旅孢@樣:
  1. [0]kdb>; md %ebp

  2. 0xc74c9f38 c74c9f60 c0136c40 000001f0 00000000
  3. 0xc74c9f48 08053328 c0425238 c04253a8 00000000
  4. 0xc74c9f58 000001f0 00000246 c74c9f6c c0136a25
  5. 0xc74c9f68 c74c8000 c74c9f74 c0136d6d c74c9fbc
  6. 0xc74c9f78 c014fe45 c74c8000 00000000 08053328

  7. [0]kdb>; 0xc0136c40

  8. 0xc0136c40 = 0xc0136c40 (__alloc_pages +0x44)

  9. [0]kdb>; 0xc0136a25

  10. 0xc0136a25 = 0xc0136a25 (_alloc_pages +0x19)

  11. [0]kdb>; 0xc0136d6d

  12. 0xc0136d6d = 0xc0136d6d (__get_free_pages +0xd)
復(fù)制代碼

我們可以看到 rmqueue() 被 __alloc_pages 調(diào)用,后者接下來又被 _alloc_pages 調(diào)用,以此類推。
每一幀的第一個雙字(double word)指向下一幀,這后面緊跟著調(diào)用函數(shù)的地址。因此,跟蹤堆棧就變成一件輕松的工作了。
技巧 #3
go 命令可以有選擇地以一個地址作為參數(shù)。如果您想在某個特定地址處繼續(xù)執(zhí)行,則可以提供該地址作為參數(shù)。另一個辦法是使用 rm 命令修改指令指針寄存器,然后只要輸入 go。如果您想跳過似乎會引起問題的某個特定指令或一組指令,這就會很有用。但是,請注意,該指令使用不慎會造成嚴重的問題,系統(tǒng)可能會嚴重崩潰。
技巧 #4
您可以利用一個名為 defcmd 的有用命令來定義自己的命令集。例如,每當遇到斷點時,您可能希望能同時檢查某個特殊變量、檢查某些寄存器的內(nèi)容并轉(zhuǎn)儲堆棧。通常,您必須要輸入一系列命令,以便能同時執(zhí)行所有這些工作。defcmd 允許您定義自己的命令,該命令可以包含一個或多個預(yù)定義的 KDB 命令。然后只需要用一個命令就可以完成所有這三項工作。其語法如下:
  1. [0]kdb>; defcmd name "usage" "help"

  2. [0]kdb>; [defcmd] type the commands here

  3. [0]kdb>; [defcmd] endefcmd
復(fù)制代碼

例如,可以定義一個(簡單的)新命令 hari,它顯示從地址 0xc000000 開始的一行內(nèi)存、顯示寄存器的內(nèi)容并轉(zhuǎn)儲堆棧:
  1. [0]kdb>; defcmd hari "" "no arguments needed"

  2. [0]kdb>; [defcmd] md 0xc000000 1

  3. [0]kdb>; [defcmd] rd

  4. [0]kdb>; [defcmd] md %ebp 1

  5. [0]kdb>; [defcmd] endefcmd
復(fù)制代碼

該命令的輸出會是:
  1. [0]kdb>; hari

  2. [hari]kdb>; md 0xc000000 1

  3. 0xc000000 00000001 f000e816 f000e2c3 f000e816

  4. [hari]kdb>; rd

  5. eax = 0x00000000 ebx = 0xc0105330 ecx = 0xc0466000 edx = 0xc0466000
  6. ....
  7. ...

  8. [hari]kdb>; md %ebp 1

  9. 0xc0467fbc c0467fd0 c01053d2 00000002 000a0200

  10. [0]kdb>;
復(fù)制代碼

技巧 #5
可以使用 bph 和 bpha 命令(假如體系結(jié)構(gòu)支持使用硬件寄存器)來應(yīng)用讀寫斷點。這意味著每當從某個特定地址讀取數(shù)據(jù)或?qū)?shù)據(jù)寫入該地址時,我們都可以對此進行控制。當調(diào)試數(shù)據(jù)/內(nèi)存毀壞問題時這可能會極其方便,在這種情況中您可以用它來識別毀壞的代碼/進程。
示例
  1. 每當將四個字節(jié)寫入地址 0xc0204060 時就進入內(nèi)核調(diào)試器:
  2. [0]kdb>; bph 0xc0204060 dataw 4
  3. 在讀取從 0xc000000 開始的至少兩個字節(jié)的數(shù)據(jù)時進入內(nèi)核調(diào)試器:
  4. [0]kdb>; bph 0xc000000 datar 2
復(fù)制代碼

結(jié)束語
對于執(zhí)行內(nèi)核調(diào)試,KDB 是一個方便的且功能強大的工具。它提供了各種選項,并且使我們能夠分析內(nèi)存內(nèi)容和數(shù)據(jù)結(jié)構(gòu)。最妙的是,它不需要用另一臺機器來執(zhí)行調(diào)試。
參考資料
"請在 Documentation/kdb 目錄中查找 KDB 手冊頁。
"有關(guān)設(shè)置串行控制臺的信息,請查找 Documentation 目錄中的 serial-console.txt。
"請在 SGI 的內(nèi)核調(diào)試器項目網(wǎng)站上下載 KDB。
"有關(guān)幾個基于方案的 Linux 調(diào)試技術(shù)的概述,請閱讀“掌握 Linux 調(diào)試技術(shù)”(developerWorks,2002 年 8 月)。
"教程“編譯 Linux 內(nèi)核”(developerWorks,2000 年 8 月)讓您完整地了解配置、編譯和安裝內(nèi)核的過程。
"IBM AIX 用戶可以在 KDB Kernel Debugger and Command 頁面上獲取有關(guān)用于 AIX 的 KDB 的使用幫助。
"那些尋求有關(guān)調(diào)試 OS/2 信息的讀者應(yīng)該閱讀 IBM 紅皮書 The OS/2 Debugging Handbook(共四卷)的第 II 卷。
"在 developerWorks Linux 專區(qū)中查找更多針對 Linux 開發(fā)人員的參考資料。

論壇徽章:
0
2 [報告]
發(fā)表于 2003-09-15 10:38 |只看該作者

Linux 內(nèi)核調(diào)試器內(nèi)幕zt

掌握 Linux 調(diào)試技術(shù)         
內(nèi)容:
常見調(diào)試方法
第 1 種情況:內(nèi)存調(diào)試工具
MEMWATCH
YAMD
Electric Fence
第 2 種情況:使用 strace
第 3 種情況:使用 gdb 和 Oops
kgdb
Oops 分析
kdb
第 4 種情況:使用魔術(shù)鍵控順序獲取反跟蹤
結(jié)束語

論壇徽章:
0
3 [報告]
發(fā)表于 2003-09-15 10:42 |只看該作者

Linux 內(nèi)核調(diào)試器內(nèi)幕zt

在 Linux 上找出并解決程序錯誤的主要方法
Steve Best(sbest@us.ibm.com)
JFS 核心小組成員,IBM
2002 年 8 月
您可以用各種方法來監(jiān)控運行著的用戶空間程序:可以為其運行調(diào)試器并單步調(diào)試該程序,添加打印語句,或者添加工具來分析程序。本文描述了幾種可以用來調(diào)試在 Linux 上運行的程序的方法。我們將回顧四種調(diào)試問題的情況,這些問題包括段錯誤,內(nèi)存溢出和泄漏,還有掛起。
本文討論了四種調(diào)試 Linux 程序的情況。在第 1 種情況中,我們使用了兩個有內(nèi)存分配問題的樣本程序,使用 MEMWATCH 和 Yet Another Malloc Debugger(YAMD)工具來調(diào)試它們。在第 2 種情況中,我們使用了 Linux 中的 strace 實用程序,它能夠跟蹤系統(tǒng)調(diào)用和信號,從而找出程序發(fā)生錯誤的地方。在第 3 種情況中,我們使用 Linux 內(nèi)核的 Oops 功能來解決程序的段錯誤,并向您展示如何設(shè)置內(nèi)核源代碼級調(diào)試器(kernel source level debugger,kgdb),以使用 GNU 調(diào)試器(GNU debugger,gdb)來解決相同的問題;kgdb 程序是使用串行連接的 Linux 內(nèi)核遠程 gdb。在第 4 種情況中,我們使用 Linux 上提供的魔術(shù)鍵控順序(magic key sequence)來顯示引發(fā)掛起問題的組件的信息。
常見調(diào)試方法
當您的程序中包含錯誤時,很可能在代碼中某處有一個條件,您認為它為真(true),但實際上是假(false)。找出錯誤的過程也就是在找出錯誤后推翻以前一直確信為真的某個條件過程。
以下幾個示例是您可能確信成立的條件的一些類型:
•在源代碼中的某處,某變量有特定的值。
•在給定的地方,某個結(jié)構(gòu)已被正確設(shè)置。
•對于給定的 if-then-else 語句,if 部分就是被執(zhí)行的路徑。
•當子例程被調(diào)用時,該例程正確地接收到了它的參數(shù)。
找出錯誤也就是要確定上述所有情況是否存在。如果您確信在子例程被調(diào)用時某變量應(yīng)該有特定的值,那么就檢查一下情況是否如此。如果您相信 if 結(jié)構(gòu)會被執(zhí)行,那么也檢查一下情況是否如此。通常,您的假設(shè)都會是正確的,但最終您會找到與假設(shè)不符的情況。結(jié)果,您就會找出發(fā)生錯誤的地方。
調(diào)試是您無法逃避的任務(wù)。進行調(diào)試有很多種方法,比如將消息打印到屏幕上、使用調(diào)試器,或只是考慮程序執(zhí)行的情況并仔細地揣摩問題所在。
在修正問題之前,您必須找出它的源頭。舉例來說,對于段錯誤,您需要了解段錯誤發(fā)生在代碼的哪一行。一旦您發(fā)現(xiàn)了代碼中出錯的行,請確定該方法中變量的值、方法被調(diào)用的方式以及關(guān)于錯誤如何發(fā)生的詳細情況。使用調(diào)試器將使找出所有這些信息變得很簡單。如果沒有調(diào)試器可用,您還可以使用其它的工具。(請注意,產(chǎn)品環(huán)境中可能并不提供調(diào)試器,而且 Linux 內(nèi)核沒有內(nèi)建的調(diào)試器。)
實用的內(nèi)存和內(nèi)核工具
您可以使用 Linux 上的調(diào)試工具,通過各種方式跟蹤用戶空間和內(nèi)核問題。請使用下面的工具和技術(shù)來構(gòu)建和調(diào)試您的源代碼:
用戶空間工具
•內(nèi)存工具:MEMWATCH 和 YAMD
•strace
•GNU 調(diào)試器(gdb)
•魔術(shù)鍵控順序
內(nèi)核工具
•內(nèi)核源代碼級調(diào)試器(kgdb)
•內(nèi)建內(nèi)核調(diào)試器(kdb)
•Oops
本文將討論一類通過人工檢查代碼不容易找到的問題,而且此類問題只在很少見的情況下存在。內(nèi)存錯誤通常在多種情況同時存在時出現(xiàn),而且您有時只能在部署程序之后才能發(fā)現(xiàn)內(nèi)存錯誤。

論壇徽章:
0
4 [報告]
發(fā)表于 2003-09-15 10:46 |只看該作者

Linux 內(nèi)核調(diào)試器內(nèi)幕zt

第 1 種情況:內(nèi)存調(diào)試工具[/b]
C 語言作為 Linux 系統(tǒng)上標準的編程語言給予了我們對動態(tài)內(nèi)存分配很大的控制權(quán)。然而,這種自由可能會導致嚴重的內(nèi)存管理問題,而這些問題可能導致程序崩潰或隨時間的推移導致性能降級。
內(nèi)存泄漏(即 malloc() 內(nèi)存在對應(yīng)的 free() 調(diào)用執(zhí)行后永不被釋放)和緩沖區(qū)溢出(例如對以前分配到某數(shù)組的內(nèi)存進行寫操作)是一些常見的問題,它們可能很難檢測到。這一部分將討論幾個調(diào)試工具,它們極大地簡化了檢測和找出內(nèi)存問題的過程。
MEMWATCH
MEMWATCH 由 Johan Lindh 編寫,是一個開放源代碼 C 語言內(nèi)存錯誤檢測工具,您可以自己下載它(請參閱本文后面部分的參考資料)。只要在代碼中添加一個頭文件并在 gcc 語句中定義了 MEMWATCH 之后,您就可以跟蹤程序中的內(nèi)存泄漏和錯誤了。MEMWATCH 支持 ANSI C,它提供結(jié)果日志紀錄,能檢測雙重釋放(double-free)、錯誤釋放(erroneous free)、沒有釋放的內(nèi)存(unfreed memory)、溢出和下溢等等。
清單 1. 內(nèi)存樣本(test1.c)

  1. #include <stdlib.h>;
  2. #include <stdio.h>;
  3. #include "memwatch.h"

  4. int main(void)
  5. {
  6.   char *ptr1;
  7.   char *ptr2;

  8.   ptr1 = malloc(512);
  9.   ptr2 = malloc(512);

  10.   ptr2 = ptr1;
  11.   free(ptr2);
  12.   free(ptr1);
  13. }
復(fù)制代碼

清單 1 中的代碼將分配兩個 512 字節(jié)的內(nèi)存塊,然后指向第一個內(nèi)存塊的指針被設(shè)定為指向第二個內(nèi)存塊。結(jié)果,第二個內(nèi)存塊的地址丟失,從而產(chǎn)生了內(nèi)存泄漏。
現(xiàn)在我們編譯清單 1 的 memwatch.c。下面是一個 makefile 示例:
test1

  1. gcc -DMEMWATCH -DMW_STDIO test1.c memwatch c -o test1
復(fù)制代碼

當您運行 test1 程序后,它會生成一個關(guān)于泄漏的內(nèi)存的報告。清單 2 展示了示例 memwatch.log 輸出文件。
清單 2. test1 memwatch.log 文件

  1. MEMWATCH 2.67 Copyright (C) 1992-1999 Johan Lindh

  2. ...
  3. double-free: <4>; test1.c(15), 0x80517b4 was freed from test1.c(14)
  4. ...
  5. unfreed: <2>; test1.c(11), 512 bytes at 0x80519e4
  6. {FE FE FE FE FE FE FE FE FE FE FE FE ..............}

  7. Memory usage statistics (global):
  8.   N)umber of allocations made:         2
  9.   L)argest memory usage :         1024
  10.   T)otal of all alloc() calls:         1024
  11.   U)nfreed bytes totals :         512
復(fù)制代碼

MEMWATCH 為您顯示真正導致問題的行。如果您釋放一個已經(jīng)釋放過的指針,它會告訴您。對于沒有釋放的內(nèi)存也一樣。日志結(jié)尾部分顯示統(tǒng)計信息,包括泄漏了多少內(nèi)存,使用了多少內(nèi)存,以及總共分配了多少內(nèi)存。
YAMD
YAMD 軟件包由 Nate Eldredge 編寫,可以查找 C 和 C++ 中動態(tài)的、與內(nèi)存分配有關(guān)的問題。在撰寫本文時,YAMD 的最新版本為 0.32。請下載 yamd-0.32.tar.gz(請參閱參考資料)。執(zhí)行 make 命令來構(gòu)建程序;然后執(zhí)行 make install 命令安裝程序并設(shè)置工具。
一旦您下載了 YAMD 之后,請在 test1.c 上使用它。請刪除 #include memwatch.h 并對 makefile 進行如下小小的修改:
使用 YAMD 的 test1

gcc -g test1.c -o test1
清單 3 展示了來自 test1 上的 YAMD 的輸出。
清單 3. 使用 YAMD 的 test1 輸出

  1. YAMD version 0.32
  2. Executable: /usr/src/test/yamd-0.32/test1
  3. ...
  4. INFO: Normal allocation of this block
  5. Address 0x40025e00, size 512
  6. ...
  7. INFO: Normal allocation of this block
  8. Address 0x40028e00, size 512
  9. ...
  10. INFO: Normal deallocation of this block
  11. Address 0x40025e00, size 512
  12. ...
  13. ERROR: Multiple freeing At
  14. free of pointer already freed
  15. Address 0x40025e00, size 512
  16. ...
  17. WARNING: Memory leak
  18. Address 0x40028e00, size 512
  19. WARNING: Total memory leaks:
  20. 1 unfreed allocations totaling 512 bytes

  21. *** Finished at Tue ... 10:07:15 2002
  22. Allocated a grand total of 1024 bytes 2 allocations
  23. Average of 512 bytes per allocation
  24. Max bytes allocated at one time: 1024
  25. 24 K alloced internally / 12 K mapped now / 8 K max
  26. Virtual program size is 1416 K
  27. End.
復(fù)制代碼

YAMD 顯示我們已經(jīng)釋放了內(nèi)存,而且存在內(nèi)存泄漏。讓我們在清單 4 中另一個樣本程序上試試 YAMD。
清單 4. 內(nèi)存代碼(test2.c)

  1. #include <stdlib.h>;
  2. #include <stdio.h>;

  3. int main(void)
  4. {
  5.   char *ptr1;
  6.   char *ptr2;
  7.   char *chptr;
  8.   int i = 1;
  9.   ptr1 = malloc(512);
  10.   ptr2 = malloc(512);
  11.   chptr = (char *)malloc(512);
  12.   for (i; i <= 512; i++) {
  13.     chptr[i] = 'S';
  14.   }       
  15.   ptr2 = ptr1;
  16.   free(ptr2);
  17.   free(ptr1);
  18.   free(chptr);
  19. }
復(fù)制代碼

您可以使用下面的命令來啟動 YAMD:
  1. ./run-yamd /usr/src/test/test2/test2
復(fù)制代碼

清單 5 顯示了在樣本程序 test2 上使用 YAMD 得到的輸出。YAMD 告訴我們在 for 循環(huán)中有“越界(out-of-bounds)”的情況。
清單 5. 使用 YAMD 的 test2 輸出

  1. Running /usr/src/test/test2/test2
  2. Temp output to /tmp/yamd-out.1243
  3. *********
  4. ./run-yamd: line 101: 1248 Segmentation fault (core dumped)
  5. YAMD version 0.32
  6. Starting run: /usr/src/test/test2/test2
  7. Executable: /usr/src/test/test2/test2
  8. Virtual program size is 1380 K
  9. ...
  10. INFO: Normal allocation of this block
  11. Address 0x40025e00, size 512
  12. ...
  13. INFO: Normal allocation of this block
  14. Address 0x40028e00, size 512
  15. ...
  16. INFO: Normal allocation of this block
  17. Address 0x4002be00, size 512
  18. ERROR: Crash
  19. ...
  20. Tried to write address 0x4002c000
  21. Seems to be part of this block:
  22. Address 0x4002be00, size 512
  23. ...
  24. Address in question is at offset 512 (out of bounds)
  25. Will dump core after checking heap.
  26. Done.
復(fù)制代碼

MEMWATCH 和 YAMD 都是很有用的調(diào)試工具,它們的使用方法有所不同。對于 MEMWATCH,您需要添加包含文件 memwatch.h 并打開兩個編譯時間標記。對于鏈接(link)語句,YAMD 只需要 -g 選項。
Electric Fence
多數(shù) Linux 分發(fā)版包含一個 Electric Fence 包,不過您也可以選擇下載它。Electric Fence 是一個由 Bruce Perens 編寫的 malloc() 調(diào)試庫。它就在您分配內(nèi)存后分配受保護的內(nèi)存。如果存在 fencepost 錯誤(超過數(shù)組末尾運行),程序就會產(chǎn)生保護錯誤,并立即結(jié)束。通過結(jié)合 Electric Fence 和 gdb,您可以精確地跟蹤到哪一行試圖訪問受保護內(nèi)存。Electric Fence 的另一個功能就是能夠檢測內(nèi)存泄漏。

論壇徽章:
0
5 [報告]
發(fā)表于 2003-09-15 10:49 |只看該作者

Linux 內(nèi)核調(diào)試器內(nèi)幕zt

第 2 種情況:使用 strace
strace 命令是一種強大的工具,它能夠顯示所有由用戶空間程序發(fā)出的系統(tǒng)調(diào)用。strace 顯示這些調(diào)用的參數(shù)并返回符號形式的值。strace 從內(nèi)核接收信息,而且不需要以任何特殊的方式來構(gòu)建內(nèi)核。將跟蹤信息發(fā)送到應(yīng)用程序及內(nèi)核開發(fā)者都很有用。在清單 6 中,分區(qū)的一種格式有錯誤,清單顯示了 strace 的開頭部分,內(nèi)容是關(guān)于調(diào)出創(chuàng)建文件系統(tǒng)操作(mkfs)的。strace 確定哪個調(diào)用導致問題出現(xiàn)。
清單 6. mkfs 上 strace 的開頭部分

  1. execve("/sbin/mkfs.jfs", ["mkfs.jfs", "-f", "/dev/test1"], &
  2. ...
  3. open("/dev/test1", O_RDWR|O_LARGEFILE) = 4
  4. stat64("/dev/test1", {st_mode=&, st_rdev=makedev(63, 255), ...}) = 0
  5. ioctl(4, 0x40041271, 0xbfffe128) = -1 EINVAL (Invalid argument)
  6. write(2, "mkfs.jfs: warning - cannot setb" ..., 98mkfs.jfs: warning -
  7. cannot set blocksize on block device /dev/test1: Invalid argument )
  8.   = 98
  9. stat64("/dev/test1", {st_mode=&, st_rdev=makedev(63, 255), ...}) = 0
  10. open("/dev/test1", O_RDONLY|O_LARGEFILE) = 5
  11. ioctl(5, 0x80041272, 0xbfffe124) = -1 EINVAL (Invalid argument)
  12. write(2, "mkfs.jfs: can\'t determine device"..., ..._exit(1)
  13.   = ?
復(fù)制代碼

清單 6 顯示 ioctl 調(diào)用導致用來格式化分區(qū)的 mkfs 程序失敗。ioctl BLKGETSIZE64 失敗。(BLKGET-SIZE64 在調(diào)用 ioctl 的源代碼中定義。) BLKGETSIZE64 ioctl 將被添加到 Linux 中所有的設(shè)備,而在這里,邏輯卷管理器還不支持它。因此,如果 BLKGETSIZE64 ioctl 調(diào)用失敗,mkfs 代碼將改為調(diào)用較早的 ioctl 調(diào)用;這使得 mkfs 適用于邏輯卷管理器。

論壇徽章:
0
6 [報告]
發(fā)表于 2003-09-15 10:57 |只看該作者

Linux 內(nèi)核調(diào)試器內(nèi)幕zt

第 3 種情況:使用 gdb 和 Oops
您可以從命令行使用 gdb 程序(Free Software Foundation 的調(diào)試器)來找出錯誤,也可以從諸如 Data Display Debugger(DDD)這樣的幾個圖形工具之一使用 gdb 程序來找出錯誤。您可以使用 gdb 來調(diào)試用戶空間程序或 Linux 內(nèi)核。這一部分只討論從命令行運行 gdb 的情況。
使用 gdb program name 命令啟動 gdb。gdb 將載入可執(zhí)行程序符號并顯示輸入提示符,讓您可以開始使用調(diào)試器。您可以通過三種方式用 gdb 查看進程:
"使用 attach 命令開始查看一個已經(jīng)運行的進程;attach 將停止進程。
"使用 run 命令執(zhí)行程序并從頭開始調(diào)試程序。
"查看已有的核心文件來確定進程終止時的狀態(tài)。要查看核心文件,請用下面的命令啟動 gdb。
gdb programname corefilename
要用核心文件進行調(diào)試,您不僅需要程序的可執(zhí)行文件和源文件,還需要核心文件本身。要用核心文件啟動 gdb,請使用 -c 選項:
gdb -c core programname
gdb 顯示哪行代碼導致程序發(fā)生核心轉(zhuǎn)儲。
在運行程序或連接到已經(jīng)運行的程序之前,請列出您覺得有錯誤的源代碼,設(shè)置斷點,然后開始調(diào)試程序。您可以使用 help 命令查看全面的 gdb 在線幫助和詳細的教程。
kgdb
kgdb 程序(使用 gdb 的遠程主機 Linux 內(nèi)核調(diào)試器)提供了一種使用 gdb 調(diào)試 Linux 內(nèi)核的機制。kgdb 程序是內(nèi)核的擴展,它讓您能夠在遠程主機上運行 gdb 時連接到運行用 kgdb 擴展的內(nèi)核機器。您可以接著深入到內(nèi)核中、設(shè)置斷點、檢查數(shù)據(jù)并進行其它操作(類似于您在應(yīng)用程序上使用 gdb 的方式)。這個補丁的主要特點之一就是運行 gdb 的主機在引導過程中連接到目標機器(運行要被調(diào)試的內(nèi)核)。這讓您能夠盡早開始調(diào)試。請注意,補丁為 Linux 內(nèi)核添加了功能,所以 gdb 可以用來調(diào)試 Linux 內(nèi)核。
使用 kgdb 需要兩臺機器:一臺是開發(fā)機器,另一臺是測試機器。一條串行線(空調(diào)制解調(diào)器電纜)將通過機器的串口連接它們。您希望調(diào)試的內(nèi)核在測試機器上運行;gdb 在開發(fā)機器上運行。gdb 使用串行線與您要調(diào)試的內(nèi)核通信。
請遵循下面的步驟來設(shè)置 kgdb 調(diào)試環(huán)境:
1.下載您的 Linux 內(nèi)核版本適用的補丁。
2.將組件構(gòu)建到內(nèi)核,因為這是使用 kgdb 最簡單的方法。(請注意,有兩種方法可以構(gòu)建多數(shù)內(nèi)核組件,比如作為模塊或直接構(gòu)建到內(nèi)核中。舉例來說,日志紀錄文件系統(tǒng)(Journaled File System,JFS)可以作為模塊構(gòu)建,或直接構(gòu)建到內(nèi)核中。通過使用 gdb 補丁,我們就可以將 JFS 直接構(gòu)建到內(nèi)核中。)
3.應(yīng)用內(nèi)核補丁并重新構(gòu)建內(nèi)核。
4.創(chuàng)建一個名為 .gdbinit 的文件,并將其保存在內(nèi)核源文件子目錄中(換句話說就是 /usr/src/linux)。文件 .gdbinit 中有下面四行代碼:
  1. oset remotebaud 115200
  2. osymbol-file vmlinux
  3. otarget remote /dev/ttyS0
  4. oset output-radix 16
復(fù)制代碼


5.將 append=gdb 這一行添加到 lilo,lilo 是用來在引導內(nèi)核時選擇使用哪個內(nèi)核的引導載入程序。
  1. oimage=/boot/bzImage-2.4.17
  2. olabel=gdb2417
  3. oread-only
  4. oroot=/dev/sda8
  5. oappend="gdb gdbttyS=1 gdb-baud=115200 nmi_watchdog=0"
復(fù)制代碼

清單 7 是一個腳本示例,它將您在開發(fā)機器上構(gòu)建的內(nèi)核和模塊引入測試機器。您需要修改下面幾項:
"best@sfb:用戶標識和機器名。
"/usr/src/linux-2.4.17:內(nèi)核源代碼樹的目錄。
"bzImage-2.4.17:測試機器上將引導的內(nèi)核名。
"rcp 和 rsync:必須允許它在構(gòu)建內(nèi)核的機器上運行。
清單 7. 引入測試機器的內(nèi)核和模塊的腳本

  1. set -x
  2. rcp best@sfb: /usr/src/linux-2.4.17/arch/i386/boot/bzImage /boot/bzImage-2.4.17
  3. rcp best@sfb:/usr/src/linux-2.4.17/System.map /boot/System.map-2.4.17
  4. rm -rf /lib/modules/2.4.17
  5. rsync -a best@sfb:/lib/modules/2.4.17 /lib/modules
  6. chown -R root /lib/modules/2.4.17
  7. lilo
復(fù)制代碼

現(xiàn)在我們可以通過改為使用內(nèi)核源代碼樹開始的目錄來啟動開發(fā)機器上的 gdb 程序了。在本示例中,內(nèi)核源代碼樹位于 /usr/src/linux-2.4.17。輸入 gdb 啟動程序。
如果一切正常,測試機器將在啟動過程中停止。輸入 gdb 命令 cont 以繼續(xù)啟動過程。一個常見的問題是,空調(diào)制解調(diào)器電纜可能會被連接到錯誤的串口。如果 gdb 不啟動,將端口改為第二個串口,這會使 gdb 啟動。
使用 kgdb 調(diào)試內(nèi)核問題
清單 8 列出了 jfs_mount.c 文件的源代碼中被修改過的代碼,我們在代碼中創(chuàng)建了一個空指針異常,從而使代碼在第 109 行產(chǎn)生段錯誤。
清單 8. 修改過后的 jfs_mount.c 代碼

  1. int jfs_mount(struct super_block *sb)
  2. {
  3. ...
  4. int ptr;                         /* line 1 added */
  5. jFYI(1, ("\nMount JFS\n"));
  6. / *
  7. * read/validate superblock
  8. * (initialize mount inode from the superblock)
  9. * /
  10. if ((rc = chkSuper(sb))) {
  11.                 goto errout20;
  12.         }
  13. 108         ptr=0;                         /* line 2 added */
  14. 109         printk("%d\n",*ptr);         /* line 3 added */
復(fù)制代碼

清單 9 在向文件系統(tǒng)發(fā)出 mount 命令之后顯示一個 gdb 異常。kgdb 提供了幾條命令,如顯示數(shù)據(jù)結(jié)構(gòu)和變量值以及顯示系統(tǒng)中的所有任務(wù)處于什么狀態(tài)、它們駐留在何處、它們在哪些地方使用了 CPU 等等。清單 9 將顯示回溯跟蹤為該問題提供的信息;where 命令用來執(zhí)行反跟蹤,它將告訴被執(zhí)行的調(diào)用在代碼中的什么地方停止。
清單 9. gdb 異常和反跟蹤

  1. mount -t jfs /dev/sdb /jfs

  2. Program received signal SIGSEGV, Segmentation fault.
  3. jfs_mount (sb=0xf78a3800) at jfs_mount.c:109
  4. 109                 printk("%d\n",*ptr);
  5. (gdb)where
  6. #0 jfs_mount (sb=0xf78a3800) at jfs_mount.c:109
  7. #1 0xc01a0dbb in jfs_read_super ... at super.c:280
  8. #2 0xc0149ff5 in get_sb_bdev ... at super.c:620
  9. #3 0xc014a89f in do_kern_mount ... at super.c:849
  10. #4 0xc0160e66 in do_add_mount ... at namespace.c:569
  11. #5 0xc01610f4 in do_mount ... at namespace.c:683
  12. #6 0xc01611ea in sys_mount ... at namespace.c:716
  13. #7 0xc01074a7 in system_call () at af_packet.c:1891
  14. #8 0x0 in ?? ()
  15. (gdb)
復(fù)制代碼

下一部分還將討論這個相同的 JFS 段錯誤問題,但不設(shè)置調(diào)試器,如果您在非 kgdb 內(nèi)核環(huán)境中執(zhí)行清單 8 中的代碼,那么它使用內(nèi)核可能生成的 Oops 消息。
Oops 分析
Oops(也稱 panic,慌張)消息包含系統(tǒng)錯誤的細節(jié),如 CPU 寄存器的內(nèi)容。在 Linux 中,調(diào)試系統(tǒng)崩潰的傳統(tǒng)方法是分析在發(fā)生崩潰時發(fā)送到系統(tǒng)控制臺的 Oops 消息。一旦您掌握了細節(jié),就可以將消息發(fā)送到 ksymoops 實用程序,它將試圖將代碼轉(zhuǎn)換為指令并將堆棧值映射到內(nèi)核符號。在很多情況下,這些信息就足夠您確定錯誤的可能原因是什么了。請注意,Oops 消息并不包括核心文件。
讓我們假設(shè)系統(tǒng)剛剛創(chuàng)建了一條 Oops 消息。作為編寫代碼的人,您希望解決問題并確定什么導致了 Oops 消息的產(chǎn)生,或者您希望向顯示了 Oops 消息的代碼的開發(fā)者提供有關(guān)您的問題的大部分信息,從而及時地解決問題。Oops 消息是等式的一部分,但如果不通過 ksymoops 程序運行它也于事無補。下面的圖顯示了格式化 Oops 消息的過程。
格式化 Oops 消息見附圖

ksymoops 需要幾項內(nèi)容:Oops 消息輸出、來自正在運行的內(nèi)核的 System.map 文件,還有 /proc/ksyms、vmlinux 和 /proc/modules。關(guān)于如何使用 ksymoops,內(nèi)核源代碼 /usr/src/linux/Documentation/oops-tracing.txt 中或 ksymoops 手冊頁上有完整的說明可以參考。Ksymoops 反匯編代碼部分,指出發(fā)生錯誤的指令,并顯示一個跟蹤部分表明代碼如何被調(diào)用。
首先,將 Oops 消息保存在一個文件中以便通過 ksymoops 實用程序運行它。清單 10 顯示了由安裝 JFS 文件系統(tǒng)的 mount 命令創(chuàng)建的 Oops 消息,問題是由清單 8 中添加到 JFS 安裝代碼的那三行代碼產(chǎn)生的。
清單 10. ksymoops 處理后的 Oops 消息

  1. ksymoops 2.4.0 on i686 2.4.17. Options used
  2. ... 15:59:37 sfb1 kernel: Unable to handle kernel NULL pointer dereference at
  3. virtual address 0000000
  4. ... 15:59:37 sfb1 kernel: c01588fc
  5. ... 15:59:37 sfb1 kernel: *pde = 0000000
  6. ... 15:59:37 sfb1 kernel: Oops: 0000
  7. ... 15:59:37 sfb1 kernel: CPU:    0
  8. ... 15:59:37 sfb1 kernel: EIP:    0010:[jfs_mount+60/704]

  9. ... 15:59:37 sfb1 kernel: Call Trace: [jfs_read_super+287/688]
  10. [get_sb_bdev+563/736] [do_kern_mount+189/336] [do_add_mount+35/208]
  11. [do_page_fault+0/1264]
  12. ... 15:59:37 sfb1 kernel: Call Trace: [<c0155d4f>;]...
  13. ... 15:59:37 sfb1 kernel: [<c0106e04 ...
  14. ... 15:59:37 sfb1 kernel: Code: 8b 2d 00 00 00 00 55 ...

  15. >;>;EIP; c01588fc <jfs_mount+3c/2c0>; <=====
  16. ...
  17. Trace; c0106cf3 <system_call+33/40>;
  18. Code; c01588fc <jfs_mount+3c/2c0>;
  19. 00000000 <_EIP>;:
  20. Code; c01588fc <jfs_mount+3c/2c0>;  <=====
  21.    0: 8b 2d 00 00 00 00         mov         0x0,%ebp    <=====
  22. Code; c0158902 <jfs_mount+42/2c0>;
  23.    6:  55                         push         %ebp
復(fù)制代碼

接下來,您要確定 jfs_mount 中的哪一行代碼引起了這個問題。Oops 消息告訴我們問題是由位于偏移地址 3c 的指令引起的。做這件事的辦法之一是對 jfs_mount.o 文件使用 objdump 實用程序,然后查看偏移地址 3c。Objdump 用來反匯編模塊函數(shù),看看您的 C 源代碼會產(chǎn)生什么匯編指令。清單 11 顯示了使用 objdump 后您將看到的內(nèi)容,接著,我們查看 jfs_mount 的 C 代碼,可以看到空值是第 109 行引起的。偏移地址 3c 之所以很重要,是因為 Oops 消息將該處標識為引起問題的位置。
清單 11. jfs_mount 的匯編程序清單

  1. 109        printk("%d\n",*ptr);

  2. objdump jfs_mount.o

  3. jfs_mount.o:         file format elf32-i386

  4. Disassembly of section .text:

  5. 00000000 <jfs_mount>;:
  6.    0:55                         push %ebp
  7.   ...
  8.   2c:        e8 cf 03 00 00           call           400 <chkSuper>;
  9.   31:        89 c3                               mov     %eax,%ebx
  10.   33:        58                            pop     %eax
  11.   34:        85 db                               test         %ebx,%ebx
  12.   36:        0f 85 55 02 00 00 jne         291 <jfs_mount+0x291>;
  13.   3c:        8b 2d 00 00 00 00 mov         0x0,%ebp << problem line above
  14.   42:        55                        push         %ebp
復(fù)制代碼

kdb
Linux 內(nèi)核調(diào)試器(Linux kernel debugger,kdb)是 Linux 內(nèi)核的補丁,它提供了一種在系統(tǒng)能運行時對內(nèi)核內(nèi)存和數(shù)據(jù)結(jié)構(gòu)進行檢查的辦法。請注意,kdb 不需要兩臺機器,不過它也不允許您像 kgdb 那樣進行源代碼級別上的調(diào)試。您可以添加額外的命令,給出該數(shù)據(jù)結(jié)構(gòu)的標識或地址,這些命令便可以格式化和顯示基本的系統(tǒng)數(shù)據(jù)結(jié)構(gòu)。目前的命令集允許您控制包括以下操作在內(nèi)的內(nèi)核操作:
"處理器單步執(zhí)行
"執(zhí)行到某條特定指令時停止
"當存取(或修改)某個特定的虛擬內(nèi)存位置時停止
"當存取輸入/輸出地址空間中的寄存器時停止
"對當前活動的任務(wù)和所有其它任務(wù)進行堆;厮莞櫍ㄍㄟ^進程 ID)
"對指令進行反匯編
追擊內(nèi)存溢出
您肯定不想陷入類似在幾千次調(diào)用之后發(fā)生分配溢出這樣的情形。
我們的小組花了許許多多時間來跟蹤稀奇古怪的內(nèi)存錯誤問題。應(yīng)用程序在我們的開發(fā)工作站上能運行,但在新的產(chǎn)品工作站上,這個應(yīng)用程序在調(diào)用 malloc() 兩百萬次之后就不能運行了。真正的問題是在大約一百萬次調(diào)用之后發(fā)生了溢出。新系統(tǒng)之所有存在這個問題,是因為被保留的 malloc() 區(qū)域的布局有所不同,從而這些零散內(nèi)存被放置在了不同的地方,在發(fā)生溢出時破壞了一些不同的內(nèi)容。
我們用多種不同技術(shù)來解決這個問題,其中一種是使用調(diào)試器,另一種是在源代碼中添加跟蹤功能。在我職業(yè)生涯的大概也是這個時候,我便開始關(guān)注內(nèi)存調(diào)試工具,希望能更快更有效地解決這些類型的問題。在開始一個新項目時,我最先做的事情之一就是運行 MEMWATCH 和 YAMD,看看它們是不是會指出內(nèi)存管理方面的問題。
內(nèi)存泄漏是應(yīng)用程序中常見的問題,不過您可以使用本文所講述的工具來解決這些問題。

fig1.jpg (30.05 KB, 下載次數(shù): 70)

fig1.jpg

論壇徽章:
0
7 [報告]
發(fā)表于 2003-09-15 10:59 |只看該作者

Linux 內(nèi)核調(diào)試器內(nèi)幕zt

第 4 種情況:使用魔術(shù)鍵控順序進行回溯跟蹤
如果在 Linux 掛起時您的鍵盤仍然能用,那請您使用以下方法來幫助解決掛起問題的根源。遵循這些步驟,您便可以顯示當前運行的進程和所有使用魔術(shù)鍵控順序的進程的回溯跟蹤。
1.您正在運行的內(nèi)核必須是在啟用 CONFIG_MAGIC_SYS-REQ 的情況下構(gòu)建的。您還必須處在文本模式。CLTR+ALT+F1 會使您進入文本模式,CLTR+ALT+F7 會使您回到 X Windows。
2.當在文本模式時,請按 <ALT+ScrollLock>;,然后按 <Ctrl+ScrollLock>;。上述魔術(shù)的擊鍵會分別給出當前運行的進程和所有進程的堆棧跟蹤。
3.請查找 /var/log/messages。如果一切設(shè)置正確,則系統(tǒng)應(yīng)該已經(jīng)為您轉(zhuǎn)換了內(nèi)核的符號地址;厮莞檶⒈粚懙 /var/log/messages 文件中。

論壇徽章:
0
8 [報告]
發(fā)表于 2003-09-15 11:00 |只看該作者

Linux 內(nèi)核調(diào)試器內(nèi)幕zt

結(jié)束語
幫助調(diào)試 Linux 上的程序有許多不同的工具可供使用。本文講述的工具可以幫助您解決許多編碼問題。能顯示內(nèi)存泄漏、溢出等等的位置的工具可以解決內(nèi)存管理問題,我發(fā)現(xiàn) MEMWATCH 和 YAMD 很有幫助。
使用 Linux 內(nèi)核補丁會使 gdb 能在 Linux 內(nèi)核上工作,這對解決我工作中使用的 Linux 的文件系統(tǒng)方面的問題很有幫助。此外,跟蹤實用程序能幫助確定在系統(tǒng)調(diào)用期間文件系統(tǒng)實用程序什么地方出了故障。下次當您要擺平 Linux 中的錯誤時,請試試這些工具中的某一個。

論壇徽章:
6
2015年辭舊歲徽章
日期:2015-03-03 16:54:152015年迎新春徽章
日期:2015-03-03 17:33:522015元宵節(jié)徽章
日期:2015-03-06 15:50:39IT運維版塊每日發(fā)帖之星
日期:2016-01-11 06:20:00IT運維版塊每日發(fā)帖之星
日期:2016-03-19 06:20:0019周年集字徽章-19
日期:2019-09-06 18:56:11
9 [報告]
發(fā)表于 2003-09-15 11:07 |只看該作者

Linux 內(nèi)核調(diào)試器內(nèi)幕zt

zhchhui  寫的好收下慢慢看.

論壇徽章:
0
10 [報告]
發(fā)表于 2003-09-15 12:08 |只看該作者

Linux 內(nèi)核調(diào)試器內(nèi)幕zt

8錯,要多讀幾編。
您需要登錄后才可以回帖 登錄 | 注冊

本版積分規(guī)則 發(fā)表回復(fù)

  

北京盛拓優(yōu)訊信息技術(shù)有限公司. 版權(quán)所有 京ICP備16024965號-6 北京市公安局海淀分局網(wǎng)監(jiān)中心備案編號:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年舉報專區(qū)
中國互聯(lián)網(wǎng)協(xié)會會員  聯(lián)系我們:huangweiwei@itpub.net
感謝所有關(guān)心和支持過ChinaUnix的朋友們 轉(zhuǎn)載本站內(nèi)容請注明原作者名及出處

清除 Cookies - ChinaUnix - Archiver - WAP - TOP