W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗(yàn)值獎勵
調(diào)試模塊的最后手段是使用調(diào)試器來單步調(diào)試代碼, 查看變量值和機(jī)器寄存器. 這個方法費(fèi)時(shí), 應(yīng)當(dāng)盡量避免. 但是, 通過調(diào)試器獲得的代碼的細(xì)粒度視角有時(shí)是很有價(jià)值的.
在內(nèi)核上使用一個交互式調(diào)試器是一個挑戰(zhàn). 內(nèi)核代表系統(tǒng)中的所有進(jìn)程運(yùn)行在自己的地址空間. 結(jié)果, 用戶空間調(diào)試器所提供的一些普通功能, 例如斷點(diǎn)和單步, 在內(nèi)核中更難得到. 本節(jié)中, 我們看一下幾個調(diào)試內(nèi)核的方法; 每個都有缺點(diǎn)和優(yōu)點(diǎn).
gdb 對于看系統(tǒng)內(nèi)部是非常有用. 在這個級別精通調(diào)試器的使用要求對 gdb 命令有信心, 需要理解目標(biāo)平臺的匯編代碼, 以及對應(yīng)源碼和優(yōu)化的匯編碼的能力.
調(diào)試器必須把內(nèi)核作為一個應(yīng)用程序來調(diào)用. 除了指定內(nèi)核映象的文件名之外, 你需要在命令行提供一個核心文件的名子. 對于一個運(yùn)行的內(nèi)核, 核心文件是內(nèi)核核心映象, /proc/kcore. 一個典型的 gdb 調(diào)用看來如下:
gdb /usr/src/linux/vmlinux /proc/kcore
第一個參數(shù)是非壓縮的 ELF 內(nèi)核可執(zhí)行文件的名子, 不是 zImage 或者 bzImage 或者給啟動環(huán)境特別編譯的任何東東.
gdb 命令行的第二個參數(shù)是核心文件的名子. 如同任何 /proc 中的文件, /proc/kcore 是在被讀的時(shí)候產(chǎn)生的. 當(dāng) read 系統(tǒng)調(diào)用在 /proc 文件系統(tǒng)中執(zhí)行時(shí), 它映射到一個數(shù)據(jù)產(chǎn)生函數(shù),而不是一個數(shù)據(jù)獲取函數(shù); 我們已經(jīng)在本章"使用 /proc 文件系統(tǒng)"一節(jié)中利用了這個特點(diǎn). kcore 用來代表內(nèi)核"可執(zhí)行文件", 以一個核心文件的形式; 它是一個巨大的文件, 因?yàn)樗碚麄€的內(nèi)核地址空間, 對應(yīng)于所有的物理內(nèi)存. 從 gdb 中, 你可查看內(nèi)核變量,通過發(fā)出標(biāo)準(zhǔn) gdb 命令. 例如, p jiffies 打印時(shí)鐘的從啟動到當(dāng)前時(shí)間的嘀噠數(shù).
當(dāng)你從gdb打印數(shù)據(jù), 內(nèi)核仍然在運(yùn)行, 各種數(shù)據(jù)項(xiàng)在不同時(shí)間有不同的值; 然而, gdb 通過緩存已經(jīng)讀取的數(shù)據(jù)來優(yōu)化對核心文件的存取. 如果你試圖再次查看 jiffies 變量, 你會得到和以前相同的答案. 緩存值來避免額外的磁盤存取對傳統(tǒng)核心文件是正確的做法, 但是在使用一個"動態(tài)"核心映象時(shí)就不方便. 解決方法是任何時(shí)候你需要刷新 gdb 緩存時(shí)發(fā)出命令 core-file /proc/kcore; 調(diào)試器準(zhǔn)備好使用新的核心文件并且丟棄任何舊信息. 然而, 你不會一直需要發(fā)出 core-file 在讀取一個新數(shù)據(jù)時(shí); gdb 讀取核心以多個幾KB的塊的方式, 并且只緩存它已經(jīng)引用的塊.
gdb 通常提供的不少功能在你使用內(nèi)核時(shí)不可用. 例如, gdb 不能修改內(nèi)核數(shù)據(jù); 它希望在操作內(nèi)存前在它自己的控制下運(yùn)行一個被調(diào)試的程序. 也不可能設(shè)置斷點(diǎn)或觀察點(diǎn), 或者單步過內(nèi)核函數(shù).
注意, 為了給 gdb 符號信息, 你必須設(shè)置 CONFIG_DEBUG_INFO 來編譯你的內(nèi)核. 結(jié)果是一個很大的內(nèi)核映象在磁盤上, 但是, 沒有這個信息, 深入內(nèi)核變量幾乎不可能.
有了調(diào)試信息, 你可以知道很多內(nèi)核內(nèi)部的事情. gdb 愉快地打印出結(jié)構(gòu), 跟隨指針, 等等. 而有一個事情比較難, 然而, 是檢查 modules. 因?yàn)槟K不是傳遞給gdb 的 vmlinux 映象, 調(diào)試器對它們一無所知. 幸運(yùn)的是, 作為 2.6.7 內(nèi)核, 有可能教給 gdb 需要如何檢查可加載模塊.
Linux 可加載模塊是 ELF 格式的可執(zhí)行映象; 這樣, 它們被分成幾個節(jié). 一個典型的模塊可能包含一打或更多節(jié), 但是有 3 個典型的與一次調(diào)試會話相關(guān):
.text
這個節(jié)包含有模塊的可執(zhí)行代碼. 調(diào)試器必須知道在哪里以便能夠給出回溯或者設(shè)置斷點(diǎn).( 這些操作都不相關(guān), 當(dāng)運(yùn)行一個調(diào)試器在 /proc/kcore 上, 但是它們在使用 kgdb 時(shí)可能有用, 下面描述).
.bss.data
這 2 個節(jié)持有模塊的變量. 在編譯時(shí)不初始化的任何變量在 .bss 中, 而那些要初始化的在 .data 里.
使 gdb 能夠處理可加載模塊需要通知調(diào)試器一個給定模塊的節(jié)加載在哪里. 這個信息在 sysfs 中, 在 /sys/module 下. 例如, 在加載 scull 模塊后, 目錄 /sys/module/scull/sections 包含名子為 .text 的文件; 每個文件的內(nèi)容是那個節(jié)的基地址.
我們現(xiàn)在該發(fā)出一個 gdb 命令來告訴它關(guān)于我們的模塊. 我們需要的命令是 add-symble-flile; 這個命令使用模塊目標(biāo)文件名, .text 基地址作為參數(shù), 以及一系列描述任何其他感興趣的節(jié)安放在哪里的參數(shù). 在深入位于 sysfs 的模塊節(jié)數(shù)據(jù)后, 我們可以構(gòu)建這樣一個命令:
(gdb) add-symbol-file .../scull.ko 0xd0832000 \
-s .bss 0xd0837100 \
-s .data 0xd0836be0
我們已經(jīng)包含了一個小腳本在例子代碼里( gdbline ), 它為給定的模塊可以創(chuàng)建這個命令.
我們現(xiàn)在使用 gdb 檢查我們的可加載模塊中的變量. 這是一個取自 scull 調(diào)試會話的快速例子:
(gdb) add-symbol-file scull.ko 0xd0832000 \
-s .bss 0xd0837100 \
-s .data 0xd0836be0
add symbol table from file "scull.ko" at
.text_addr = 0xd0832000
.bss_addr = 0xd0837100
.data_addr = 0xd0836be0
(y or n) y
Reading symbols from scull.ko...done.
(gdb) p scull_devices[0]
$1 = {data = 0xcfd66c50,
quantum = 4000,
qset = 1000,
size = 20881,
access_key = 0,
...}
這里我們看到第一個 scull 設(shè)備當(dāng)前持有 20881 字節(jié). 如果我們想, 我們可以跟隨數(shù)據(jù)鏈, 或者查看其他任何感興趣的模塊中的東東.
這是另一個值得知道的有用技巧:
(gdb) print *(address)
這里, 填充 address 指向的一個 16 進(jìn)制地址; 輸出是對應(yīng)那個地址的代碼的文件和行號. 這個技術(shù)可能有用, 例如, 來找出一個函數(shù)指針真正指向哪里.
我們?nèi)匀徊荒苓M(jìn)行典型的調(diào)試任務(wù), 如設(shè)置斷點(diǎn)或者修改數(shù)據(jù); 為進(jìn)行這些操作, 我們需要使用象 kdb( 下面描述 ) 或者 kgdb ( 我們馬上就到 )這樣的工具.
許多讀者可能奇怪為什么內(nèi)核沒有建立更多高級的調(diào)試特性在里面.答案, 非常簡單, 是 Linus 不相信交互式的調(diào)試器. 他擔(dān)心它們會導(dǎo)致不好的修改, 這些修改給問題打了補(bǔ)丁而不是找到問題的真正原因. 因此, 沒有內(nèi)嵌的調(diào)試器.
其他內(nèi)核開發(fā)者, 但是, 見到了交互式調(diào)試工具的一個臨時(shí)使用. 一個這樣的工具是 kdb 內(nèi)嵌式內(nèi)核調(diào)試器, 作為來自 oss.sgi.com 的一個非官方補(bǔ)丁. 要使用 kdb, 你必須獲得這個補(bǔ)丁(確認(rèn)獲得一個匹配你的內(nèi)核版本的版本), 應(yīng)用它, 重建并重安裝內(nèi)核. 注意, 直到本書編寫時(shí), kdb 只在IA-32(x86)系統(tǒng)中運(yùn)行(盡管一個給 IA-64 的版本在主線內(nèi)核版本存在了一陣子, 在被去除之前.)
一旦你運(yùn)行一個使能了kdb的內(nèi)核, 有幾個方法進(jìn)入調(diào)試器. 在控制臺上按下 Pause(或者 Break) 鍵啟動調(diào)試器. kdb 在一個內(nèi)核 oops 發(fā)生時(shí)或者命中一個斷點(diǎn)時(shí)也啟動, 在任何一種情況下, 你看到象這樣的一個消息:
Entering kdb (0xc0347b80) on processor 0 due to Keyboard Entry
[0]kdb>
注意, 在kdb運(yùn)行時(shí)內(nèi)核停止任何東西. 在你調(diào)用 kdb 的系統(tǒng)中不應(yīng)當(dāng)運(yùn)行其他東西; 特別, 你不應(yīng)當(dāng)打開網(wǎng)絡(luò) -- 除非, 當(dāng)然, 你在調(diào)試一個網(wǎng)絡(luò)驅(qū)動. 一般地以單用戶模式啟動系統(tǒng)是一個好主意, 如果你將使用 kdb.
作為一個例子, 考慮一個快速 scull 調(diào)試會話. 假設(shè)驅(qū)動已經(jīng)加載, 我們可以這樣告訴 kdb 在 sucll_read 中設(shè)置一個斷點(diǎn):
[0]kdb> bp scull_read
Instruction(i) BP #0 at 0xcd087c5dc (scull_read)
is enabled globally adjust 1
[0]kdb> go
bp 命令告訴 kdb 在下一次內(nèi)核進(jìn)入 scull_read 時(shí)停止. 你接著鍵入 go 來繼續(xù)執(zhí)行. 在將一些東西放入一個 scull 設(shè)備后, 我們可以試著通過在另一個終端的外殼下運(yùn)行 cat 命令來讀取它, 產(chǎn)生下面:
Instruction(i) breakpoint #0 at 0xd087c5dc (adjusted)
0xd087c5dc scull_read: int3
Entering kdb (current=0xcf09f890, pid 1575) on processor 0 due to
Breakpoint @ 0xd087c5dc
[0]kdb>
我們現(xiàn)在位于 scull_read 的開始. 為看到我們?nèi)魏蔚侥抢锏? 我們可以獲得一個堆?;厮?
[0]kdb> bt
ESP EIP Function (args)
0xcdbddf74 0xd087c5dc [scull]scull_read
0xcdbddf78 0xc0150718 vfs_read+0xb8
0xcdbddfa4 0xc01509c2 sys_read+0x42
0xcdbddfc4 0xc0103fcf syscall_call+0x7
[0]kdb>
kdb 試圖打印出調(diào)用回溯中每個函數(shù)的參數(shù). 然而, 它被編譯器的優(yōu)化技巧搞糊涂了. 因此, 它無法打印 scull_read 的參數(shù).
到時(shí)候查看一些數(shù)據(jù)了. mds 命令操作數(shù)據(jù); 我們可以查詢 schull_devices 指針的值, 使用這樣一個命令:
[0]kdb> mds scull_devices 1
0xd0880de8 cf36ac00 ....
這里我們要求一個(4字節(jié))字, 起始于 scull_devices 的位置; 答案告訴我們的設(shè)備數(shù)組在地址 0xd0880de8; 第一個設(shè)備結(jié)構(gòu)自己在 0xcf36ac00. 為查看那個設(shè)備結(jié)構(gòu), 我們需要使用這個地址:
[0]kdb> mds cf36ac00
0xcf36ac00 ce137dbc ....
0xcf36ac04 00000fa0 ....
0xcf36ac08 000003e8 ....
0xcf36ac0c 0000009b ....
0xcf36ac10 00000000 ....
0xcf36ac14 00000001 ....
0xcf36ac18 00000000 ....
0xcf36ac1c 00000001 ....
這里的 8 行對應(yīng)于 scull_dev 結(jié)構(gòu)的開始部分. 因此, 我們看到第一個設(shè)備的內(nèi)存位于 0xce137dbc, quantum 是 4000 (16進(jìn)制 fa0), 量子集大小是 1000 (16進(jìn)制 3e8 ), 當(dāng)前有 155( 16進(jìn)制 9b) 字節(jié)存于設(shè)備中.
kdb 也可以改變數(shù)據(jù). 假想我們要截短一些數(shù)據(jù)從設(shè)備中:
[0]kdb> mm cf26ac0c 0x50
0xcf26ac0c = 0x50
在設(shè)備上一個后續(xù)的 cat 會返回比之前少的數(shù)據(jù).
kdb 有不少其他功能, 包括單步(指令, 不是 C 源碼的一行), 在數(shù)據(jù)存取上設(shè)置斷點(diǎn), 反匯編代碼, 步入鏈表, 存取寄存器數(shù)據(jù), 還有更多. 在你應(yīng)用了 kdb 補(bǔ)丁后, 一個完整的手冊頁集能夠在你的源碼樹的 documentation/kdb 下發(fā)現(xiàn).
目前為止我們看到的 2 個交互式調(diào)試方法( 使用 gdb 于 /proc/kcore 和 kdb) 都缺乏應(yīng)用程序開發(fā)者已經(jīng)熟悉的那種環(huán)境. 如果有一個真正的內(nèi)核調(diào)試器支持改變變量, 斷點(diǎn)等特色, 不是很好?
確實(shí), 有這樣一個解決方案. 在本書編寫時(shí), 2 個分開的補(bǔ)丁在流通中, 它允許 gdb, 具備完全功能, 針對內(nèi)核運(yùn)行. 這 2 個補(bǔ)丁都稱為 kgdb. 它們通過分開運(yùn)行測試內(nèi)核的系統(tǒng)和運(yùn)行調(diào)試器的系統(tǒng)來工作; 這 2 個系統(tǒng)典型地是通過一個串口線連接起來. 因此, 開發(fā)者可以在穩(wěn)定地桌面系統(tǒng)上運(yùn)行 gdb, 而操作一個運(yùn)行在專門測試的盒子中的內(nèi)核. 這種方式建立 gdb 開始需要一些時(shí)間, 但是很快會得到回報(bào),當(dāng)一個難問題出現(xiàn)時(shí).
這些補(bǔ)丁目前處于健壯的狀態(tài), 在某些點(diǎn)上可能被合并, 因此我們避免說太多, 除了它們在哪里以及它們的基本特色. 鼓勵感興趣的讀者去看這些的當(dāng)前狀態(tài).
第一個 kgdb 補(bǔ)丁當(dāng)前在 -mm 內(nèi)核樹里 -- 補(bǔ)丁進(jìn)入 2.6 主線的集結(jié)場. 補(bǔ)丁的這個版本支持 x86, SuperH, ia64, x86_64, 和 32位 PPC 體系. 除了通過串口操作的常用模式, 這個版本的 kgdb 可以通過一個局域網(wǎng)通訊. 使能以太網(wǎng)模式并且使用 kgdboe參數(shù)指定發(fā)出調(diào)試命令的 IP 地址來啟動內(nèi)核. 在 Documentation/i386/kgdb 下的文檔描述了如何建立.[16]
作為一個選擇, 你可使用位于 http://kgdb.sf.net 的kgdb補(bǔ)丁. 這個調(diào)試器的版本不支持網(wǎng)絡(luò)通訊模式(盡管據(jù)說在開發(fā)中), 但是它確實(shí)有內(nèi)嵌的使用可加載模塊的支持. 它支持 x86, x86_64, PowerPC, 和 S/390 體系.
用戶模式 Linux (UML) 是一個有趣的概念. 它被構(gòu)建為一個分開的 Linux 內(nèi)核移植, 有它自己的 arch/um 子目錄. 它不在一個新的硬件類型上運(yùn)行, 但是; 相反, 它運(yùn)行在一個由 Linux 系統(tǒng)調(diào)用接口實(shí)現(xiàn)的虛擬機(jī)上. 如此, UML 使用 Linux 內(nèi)核來運(yùn)行, 作為一個Linux 系統(tǒng)上的獨(dú)立的用戶模式進(jìn)程.
有一個作為用戶進(jìn)程運(yùn)行的內(nèi)核拷貝有幾個優(yōu)點(diǎn). 因?yàn)樗鼈冞\(yùn)行在一個受限的虛擬的處理器上, 一個錯誤的內(nèi)核不能破壞"真實(shí)的"系統(tǒng). 可以在同一臺盒子輕易的嘗試不同的硬件和軟件配置. 并且, 也許對內(nèi)核開發(fā)者而言, 用戶模式內(nèi)核可容易地使用 gdb 和 其他調(diào)試器操作.
畢竟, 它只是一個進(jìn)程. UML 顯然有加快內(nèi)核開發(fā)的潛力.
然而, UML 有個大的缺點(diǎn),從驅(qū)動編寫者的角度看: 用戶模式內(nèi)核無法存取主機(jī)系統(tǒng)的硬件. 因此, 雖然它對于調(diào)試大部分本書的例子驅(qū)動是有用的, UML 對于不得不處理真實(shí)硬件的驅(qū)動的調(diào)試還是沒有用處.
看 http://user-mode-linux.sf.net/ 關(guān)于 UML 的更多信息.
Linux Trace Toolkit (LTT) 是一個內(nèi)核補(bǔ)丁以及一套相關(guān)工具, 允許追蹤內(nèi)核中的事件. 這個追蹤包括時(shí)間信息, 可以創(chuàng)建一個給定時(shí)間段內(nèi)發(fā)生事情的合理的完整圖像. 因此, 它不僅用來調(diào)試也可以追蹤性能問題.
LTT, 同廣泛的文檔一起, 可以在 http://www.opersys.com/LTT 找到.
Dynamic Probes ( DProbes ) 是由 IBM 發(fā)行的(在 GPL 之下)為 IA-32 體系的 Linux 的調(diào)試工具. 它允許安放一個"探針"在幾乎系統(tǒng)中任何地方, 用戶空間和內(nèi)核空間都可以. 探針由一些代碼組成( 有一個特殊的,面向堆棧的語言寫成), 當(dāng)控制命中給定的點(diǎn)時(shí)執(zhí)行. 這個代碼可以報(bào)告信息給用戶空間, 改變寄存器, 或者做其他很多事情. DProbes 的有用特性是, 一旦這個能力建立到內(nèi)核中, 探針可以在任何地方插入在一個運(yùn)行中的系統(tǒng)中, 不用內(nèi)核建立或者重啟. DProbes 可以和 LTT 一起來插入一個新的跟蹤事件在任意位置.
DProbes 工具可以從 IBM 的開放源碼網(wǎng)站:http://oss.sof-ware.ibm.com 下載.
[16] 確實(shí)是忽略了指出, 你應(yīng)當(dāng)使你的網(wǎng)絡(luò)適配卡建立在內(nèi)核中, 然而, 否則調(diào)試器在啟動時(shí)找不到它會關(guān)掉它自己.
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報(bào)電話:173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: