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