Linux內(nèi)核調(diào)試器內(nèi)幕(4)
1.下載您的 Linux 內(nèi)核版本適用的補(bǔ)丁。
2.將組件構(gòu)建到內(nèi)核,因?yàn)檫@是使用 kgdb 最簡(jiǎn)單的方法。(請(qǐng)注意,有兩種方法可以構(gòu)建多數(shù)內(nèi)核組件,比如作為模塊或直接構(gòu)建到內(nèi)核中。舉例來(lái)說(shuō),日志紀(jì)錄文件系統(tǒng)(Journaled File System,JFS)可以作為模塊構(gòu)建,或直接構(gòu)建到內(nèi)核中。通過(guò)使用 gdb 補(bǔ)丁,我們就可以將 JFS 直接構(gòu)建到內(nèi)核中。)
3.應(yīng)用內(nèi)核補(bǔ)丁并重新構(gòu)建內(nèi)核。
4.創(chuàng)建一個(gè)名為 .gdbinit 的文件,并將其保存在內(nèi)核源文件子目錄中(換句話說(shuō)就是 /usr/src/linux)。文件 .gdbinit 中有下面四行代碼:
[code:1:627becdd94]oset remotebaud 115200
osymbol-file vmlinux
otarget remote /dev/ttyS0
oset output-radix 16 [/code:1:627becdd94]
5.將 append=gdb 這一行添加到 lilo,lilo 是用來(lái)在引導(dǎo)內(nèi)核時(shí)選擇使用哪個(gè)內(nèi)核的引導(dǎo)載入程序。
[code:1:627becdd94]oimage=/boot/bzImage-2.4.17
olabel=gdb2417
oread-only
oroot=/dev/sda8
oappend=gdb gdbttyS=1 gdb-baud=115200 nmi_watchdog=0 [/code:1:627becdd94]
清單 7 是一個(gè)腳本示例,它將您在開(kāi)發(fā)機(jī)器上構(gòu)建的內(nèi)核和模塊引入測(cè)試機(jī)器。您需要修改下面幾項(xiàng):
?best@sfb:用戶標(biāo)識(shí)和機(jī)器名。
?/usr/src/linux-2.4.17:內(nèi)核源代碼樹(shù)的目錄。
?bzImage-2.4.17:測(cè)試機(jī)器上將引導(dǎo)的內(nèi)核名。
?rcp 和 rsync:必須允許它在構(gòu)建內(nèi)核的機(jī)器上運(yùn)行。
清單 7. 引入測(cè)試機(jī)器的內(nèi)核和模塊的腳本
[code:1:627becdd94]set -x
rcp best@sfb: /usr/src/linux-2.4.17/arch/i386/boot/bzImage /boot/bzImage-2.4.17
rcp best@sfb:/usr/src/linux-2.4.17/System.map /boot/System.map-2.4.17
rm -rf /lib/modules/2.4.17
rsync -a best@sfb:/lib/modules/2.4.17 /lib/modules
chown -R root /lib/modules/2.4.17
lilo[/code:1:627becdd94]
現(xiàn)在我們可以通過(guò)改為使用內(nèi)核源代碼樹(shù)開(kāi)始的目錄來(lái)啟動(dòng)開(kāi)發(fā)機(jī)器上的 gdb 程序了。在本示例中,內(nèi)核源代碼樹(shù)位于 /usr/src/linux-2.4.17。輸入 gdb 啟動(dòng)程序。
如果一切正常,測(cè)試機(jī)器將在啟動(dòng)過(guò)程中停止。輸入 gdb 命令 cont 以繼續(xù)啟動(dòng)過(guò)程。一個(gè)常見(jiàn)的問(wèn)題是,空調(diào)制解調(diào)器電纜可能會(huì)被連接到錯(cuò)誤的串口。如果 gdb 不啟動(dòng),將端口改為第二個(gè)串口,這會(huì)使 gdb 啟動(dòng)。
[color=darkblue:627becdd94]使用 kgdb 調(diào)試內(nèi)核問(wèn)題[/color:627becdd94]
清單 8 列出了 jfs_mount.c 文件的源代碼中被修改過(guò)的代碼,我們?cè)诖a中創(chuàng)建了一個(gè)空指針異常,從而使代碼在第 109 行產(chǎn)生段錯(cuò)誤。
清單 8. 修改過(guò)后的 jfs_mount.c 代碼
[code:1:627becdd94]int jfs_mount(struct super_block *sb)
{
...
int ptr; /* line 1 added */
jFYI(1, (
Mount JFS
));
/ *
* read/validate superblock
* (initialize mount inode from the superblock)
* /
if ((rc = chkSuper(sb))) {
goto errout20;
}
108 ptr=0; /* line 2 added */
109 printk(%d
,*ptr); /* line 3 added */[/code:1:627becdd94]
清單 9 在向文件系統(tǒng)發(fā)出 mount 命令之后顯示一個(gè) gdb 異常。kgdb 提供了幾條命令,如顯示數(shù)據(jù)結(jié)構(gòu)和變量值以及顯示系統(tǒng)中的所有任務(wù)處于什么狀態(tài)、它們駐留在何處、它們?cè)谀男┑胤绞褂昧?CPU 等等。清單 9 將顯示回溯跟蹤為該問(wèn)題提供的信息;where 命令用來(lái)執(zhí)行反跟蹤,它將告訴被執(zhí)行的調(diào)用在代碼中的什么地方停止。
清單 9. gdb 異常和反跟蹤
[code:1:627becdd94]mount -t jfs /dev/sdb /jfs
Program received signal SIGSEGV, Segmentation fault.
jfs_mount (sb=0xf78a3800) at jfs_mount.c:109
109 printk(%d
,*ptr);
(gdb)where
#0 jfs_mount (sb=0xf78a3800) at jfs_mount.c:109
#1 0xc01a0dbb in jfs_read_super ... at super.c:280
#2 0xc0149ff5 in get_sb_bdev ... at super.c:620
#3 0xc014a89f in do_kern_mount ... at super.c:849
#4 0xc0160e66 in do_add_mount ... at namespace.c:569
#5 0xc01610f4 in do_mount ... at namespace.c:683
#6 0xc01611ea in sys_mount ... at namespace.c:716
#7 0xc01074a7 in system_call () at af_packet.c:1891
#8 0x0 in ?? ()
(gdb)[/code:1:627becdd94]
下一部分還將討論這個(gè)相同的 JFS 段錯(cuò)誤問(wèn)題,但不設(shè)置調(diào)試器,如果您在非 kgdb 內(nèi)核環(huán)境中執(zhí)行清單 8 中的代碼,那么它使用內(nèi)核可能生成的 Oops 消息。
[color=darkblue:627becdd94]Oops 分析[/color:627becdd94]
Oops(也稱 panic,慌張)消息包含系統(tǒng)錯(cuò)誤的細(xì)節(jié),如 CPU 寄存器的內(nèi)容。在 Linux 中,調(diào)試系統(tǒng)崩潰的傳統(tǒng)方法是分析在發(fā)生崩潰時(shí)發(fā)送到系統(tǒng)控制臺(tái)的 Oops 消息。一旦您掌握了細(xì)節(jié),就可以將消息發(fā)送到 ksymoops 實(shí)用程序,它將試圖將代碼轉(zhuǎn)換為指令并將堆棧值映射到內(nèi)核符號(hào)。在很多情況下,這些信息就足夠您確定錯(cuò)誤的可能原因是什么了。請(qǐng)注意,Oops 消息并不包括核心文件。
讓我們假設(shè)系統(tǒng)剛剛創(chuàng)建了一條 Oops 消息。作為編寫(xiě)代碼的人,您希望解決問(wèn)題并確定什么導(dǎo)致了 Oops 消息的產(chǎn)生,或者您希望向顯示了 Oops 消息的代碼的開(kāi)發(fā)者提供有關(guān)您的問(wèn)題的大部分信息,從而及時(shí)地解決問(wèn)題。Oops 消息是等式的一部分,但如果不通過(guò) ksymoops 程序運(yùn)行它也于事無(wú)補(bǔ)。下面的圖顯示了格式化 Oops 消息的過(guò)程。
[color=darkblue:627becdd94]格式化 Oops 消息[/color:627becdd94]見(jiàn)附圖
ksymoops 需要幾項(xiàng)內(nèi)容:Oops 消息輸出、來(lái)自正在運(yùn)行的內(nèi)核的 System.map 文件,還有 /proc/ksyms、 vmlinux 和 /proc/modules。關(guān)于如何使用 ksymoops,內(nèi)核源代碼 /usr/src/linux/Documentation/oops-tracing.txt 中或 ksymoops 手冊(cè)頁(yè)上有完整的說(shuō)明可以參考。Ksymoops 反匯編代碼部分,指出發(fā)生錯(cuò)誤的指令,并顯示一個(gè)跟蹤部分表明代碼如何被調(diào)用。
首先,將 Oops 消息保存在一個(gè)文件中以便通過(guò) ksymoops 實(shí)用程序運(yùn)行它。清單 10 顯示了由安裝 JFS 文件系統(tǒng)的 mount 命令創(chuàng)建的 Oops 消息,問(wèn)題是由清單 8 中添加到 JFS 安裝代碼的那三行代碼產(chǎn)生的。
清單 10. ksymoops 處理后的 Oops 消息
[code:1:627becdd94]ksymoops 2.4.0 on i686 2.4.17. Options used
... 15:59:37 sfb1 kernel: Unable to handle kernel NULL pointer dereference at
virtual address 0000000
... 15:59:37 sfb1 kernel: c01588fc
... 15:59:37 sfb1 kernel: *pde = 0000000
... 15:59:37 sfb1 kernel: Oops: 0000
... 15:59:37 sfb1 kernel: CPU: 0
... 15:59:37 sfb1 kernel: EIP: 0010:[jfs_mount+60/704]
... 15:59:37 sfb1 kernel: Call Trace: [jfs_read_super+287/688]
[get_sb_bdev+563/736] [do_kern_mount+189/336] [do_add_mount+35/208]
[do_page_fault+0/1264]
... 15:59:37 sfb1 kernel: Call Trace: [c0155d4f>]...
... 15:59:37 sfb1 kernel: [c0106e04 ...
... 15:59:37 sfb1 kernel: Code: 8b 2d 00 00 00 00 55 ...
>>EIP; c01588fc jfs_mount+3c/2c0> =====
...
Trace; c0106cf3 system_call+33/40>
Code; c01588fc jfs_mount+3c/2c0>
00000000 _EIP>:
Code; c01588fc jfs_mount+3c/2c0> =====
0: 8b 2d 00 00 00 00 mov 0x0,%ebp =====
Code; c0158902 jfs_mount+42/2c0>
6: 55 push %ebp[/code:1:627becdd94]
接下來(lái),您要確定 jfs_mount 中的哪一行代碼引起了這個(gè)問(wèn)題。Oops 消息告訴我們問(wèn)題是由位于偏移地址 3c 的指令引起的。做這件事的辦法之一是對(duì) jfs_mount.o 文件使用 objdump 實(shí)用程序,然后查看偏移地址 3c。Objdump 用來(lái)反匯編模塊函數(shù),看看您的 C 源代碼會(huì)產(chǎn)生什么匯編指令。清單 11 顯示了使用 objdump 后您將看到的內(nèi)容,接著,我們查看 jfs_mount 的 C 代碼,可以看到空值是第 109 行引起的。偏移地址 3c 之所以很重要,是因?yàn)?Oops 消息將該處標(biāo)識(shí)為引起問(wèn)題的位置。
清單 11. jfs_mount 的匯編程序清單
[code:1:627becdd94]109 printk(%d
,*ptr);
objdump jfs_mount.o
jfs_mount.o: file format elf32-i386
Disassembly of section .text:
00000000 jfs_mount>:
0:55 push %ebp
...
2c: e8 cf 03 00 00 call 400 chkSuper>
31: 89 c3 mov %eax,%ebx
33: 58 pop %eax
34: 85 db test %ebx,%ebx
36: 0f 85 55 02 00 00 jne 291 jfs_mount+0x291>
3c: 8b 2d 00 00 00 00 mov 0x0,%ebp problem line above
42: 55 push %ebp[/code:1:627becdd94]
[color=darkblue:627becdd94]kdb[/color:627becdd94]
Linux 內(nèi)核調(diào)試器(Linux kernel debugger,kdb)是 Linux 內(nèi)核的補(bǔ)丁,它提供了一種在系統(tǒng)能運(yùn)行時(shí)對(duì)內(nèi)核內(nèi)存和數(shù)據(jù)結(jié)構(gòu)進(jìn)行檢查的辦法。請(qǐng)注意,kdb 不需要兩臺(tái)機(jī)器,不過(guò)它也不允許您像 kgdb 那樣進(jìn)行源代碼級(jí)別上的調(diào)試。您可以添加額外的命令,給出該數(shù)據(jù)結(jié)構(gòu)的標(biāo)識(shí)或地址,這些命令便可以格式化和顯示基本的系統(tǒng)數(shù)據(jù)結(jié)構(gòu)。目前的命令集允許您控制包括以下操作在內(nèi)的內(nèi)核操作:
?處理器單步執(zhí)行
?執(zhí)行到某條特定指令時(shí)停止
?當(dāng)存?。ɑ蛐薷模┠硞€(gè)特定的虛擬內(nèi)存位置時(shí)停止
?當(dāng)存取輸入/輸出地址空間中的寄存器時(shí)停止
?對(duì)當(dāng)前活動(dòng)的任務(wù)和所有其它任務(wù)進(jìn)行堆棧回溯跟蹤(通過(guò)進(jìn)程 ID)
?對(duì)指令進(jìn)行反匯編
[color=blue:627becdd94]追擊內(nèi)存溢出[/color:627becdd94]
您肯定不想陷入類似在幾千次調(diào)用之后發(fā)生分配溢出這樣的情形。
我們的小組花了許許多多時(shí)間來(lái)跟蹤稀奇古怪的內(nèi)存錯(cuò)誤問(wèn)題。應(yīng)用程序在我們的開(kāi)發(fā)工作站上能運(yùn)行,但在新的產(chǎn)品工作站上,這個(gè)應(yīng)用程序在調(diào)用 malloc() 兩百萬(wàn)次之后就不能運(yùn)行了。真正的問(wèn)題是在大約一百萬(wàn)次調(diào)用之后發(fā)生了溢出。新系統(tǒng)之所有存在這個(gè)問(wèn)題,是因?yàn)楸槐A舻?malloc() 區(qū)域的布局有所不同,從而這些零散內(nèi)存被放置在了不同的地方,在發(fā)生溢出時(shí)破壞了一些不同的內(nèi)容。
我們用多種不同技術(shù)來(lái)解決這個(gè)問(wèn)題,其中一種是使用調(diào)試器,另一種是在源代碼中添加跟蹤功能。在我職業(yè)生涯的大概也是這個(gè)時(shí)候,我便開(kāi)始關(guān)注內(nèi)存調(diào)試工具,希望能更快更有效地解決這些類型的問(wèn)題。在開(kāi)始一個(gè)新項(xiàng)目時(shí),我最先做的事情之一就是運(yùn)行 MEMWATCH 和 YAMD,看看它們是不是會(huì)指出內(nèi)存管理方面的問(wèn)題。
內(nèi)存泄漏是應(yīng)用程序中常見(jiàn)的問(wèn)題,不過(guò)您可以使用本文所講述的工具來(lái)解決這些問(wèn)題。
評(píng)論