更多Linux文章請看:Linux操作系統應用專區
1. 幾種內核調試工具比較
kdb:只能在匯編代碼級進行調試;
優點是不需要兩台機器進行調試。
gdb:在調試模塊時缺少一些至關重要的功能,它可用來查看內核的運行情況,包括反匯編內核函數。
kgdb:能很方便的在源碼級對內核進行調試,缺點是kgdb只能進行遠程調試,它需要一根串口線及兩台機器來調試內核(也可以是在同一台主機上用vmware軟件運行兩個操作系統來調試)
使用kdb和gdb調試內核的方法相對比較簡單,這裡只描述如何使用kgdb來調試內核。
2.軟硬件准備
環境:
eloper(192.168.16.5 com1),一台測試機target(192.168.16.30 com2),都預裝redhat 9;一根串口線
下載以下軟件包:
linux內核2.4.23 linux-2.4.23.tar.bz2
kgdb內核補丁1.9版 linux-2.4.23-kgdb-1.9.patch
可調試內核模塊的gdb gdbmod-1.9.bz2
3.ok,開始
3.1 測試串口線
物理連接好串口線後,使用一下命令進行測試,stty可以對串口參數進行設置
在developer上執行:
stty ispeed 115200 ospeed 115200 -F /dev/ttyS0
echo hello > /dev/ttyS0
在target上執行:
stty ispeed 115200 ospeed 115200 -F /dev/ttyS1
cat /dev/ttyS1
串口線沒問題的話在target的屏幕上顯示hello
3.2 安裝與配置
3.2.1 安裝
下載linux-2.4.23.tar.bz2,linux-2.4.23-kgdb-1.9.patch,gdbmod-1.9.bz2到developer的/home/liangjian目錄
*在developer機器上
#cd /home/liangjian
#bunzip2 linux-2.4.23.tar.bz2
#tar -xvf linux-2.4.23.tar
#bunzip2 gdbmod-1.9.bz2
#cp gdbmod-1.9 /usr/local/bin
#cd linux-2.4.23
#patch -p1 < ../linux-2.4.23-kgdb-1.9.patch
#make menUConfig
在Kernel hacking配置項中將以下三項編譯進內核
KGDB: Remote (serial) kernel debugging with gdb
KGDB: Thread analysis
KGDB: Console messages through gdb
注意在編譯內核的時候需要加上-g選項
#make dep;make bzImage
使用scp進行將相關文件拷貝到target上(當然也可以使用其它的網絡工具)
#scp arch/i386/boot/bzImage [email protected]:/boot/vmlinuz-2.4.23-kgdb
#scp System.map [email protected]:/boot/System.map-2.4.23-kgdb
#scp arch/i386/kernel/gdbstart [email protected]:/sbin
gdbstart為kgdb提供的一個工具,用於激活內核鉤子,使內核處於調試狀態
*在developer機器上
在內核源碼目錄下編輯一文件.gdbinit(該文件用以對gdb進行初始化),內容如下:
#vi .gdbinit
define rmt
set remotebaud 115200
target remote /dev/ttyS0
end
#
以上在.gdbinit中定義了一個宏rmt,該宏主要是設置使用的串口號和速率
*在target機器上
編輯/etc/grub.conf文件,加入以下行:
#vi /etc/grub.conf
title Red Hat Linux (2.4.23-kgdb)
root (hd0,0)
kernel /boot/vmlinuz-2.4.23-kgdb ro root=/dev/hda1
#
在root目錄下建立一個腳本文件debugkernel,內容如下:
#vi debug
#!/bin/bash
gdbstart -s 115200 -t /dev/ttyS1 <<EOF
EOF
#chmod +x debugkernel
這個腳本主要是調用gdbstart程序設置target機上使用的串口及其速率,並使內核處於調試狀態
3.3 開始調試
target上的內核或內核模塊處於調試狀態時,可以查看其變量、設置斷點、查看堆棧等,並且是源碼級的調試,和用gdb調試用戶程序一樣
3.3.1 內核啟動後調試
*在target機器上
重啟系統,選擇以 2.4.23-kgdb內核啟動,啟動完成後運行debugkenel,這時內核將停止運行,在控制台屏幕上顯示信息,並等待來自developer的串口連接
#./debug
About to activate GDB stub in the kernel on /dev/ttyS1
Waiting for connection from remote gdb...
*在developer機器上
#cd /home/liangjian/linux-2.4.23
# gdb vmlinux
GNU gdb Red Hat Linux (5.3post-0.20021129.18rh)
Copyright 2003 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-redhat-linux-gnu"...
執行rmt宏
(gdb) rmt
breakpoint () at kgdbstub.c:1005
1005 atomic_set(&kgdb_setting_breakpoint, 0);
這時target上的內核處於調試狀態,可以查看其變量、設置斷點、查看堆棧等,和用gdb調試用戶程序一樣
查看堆棧
(gdb) BT
#0 breakpoint () at kgdbstub.c:1005
#1 0xc0387f48 in init_task_union ()
#2 0xc01bc867 in gdb_interrupt (irq=3, dev_id=0x0, regs=0xc0387f98) at
gdbserial.c:158
#3 0xc010937b in handle_IRQ_event (irq=3, regs=0xc0387f98, action=0xce5a9860)
at irq.c:452
#4 0xc0109597 in do_IRQ (regs=
{ebx = -1072671776, ecx = -1, edx = -1070047232, esi = -1070047232, edi
= -1070047232, ebp = -1070039092, eax = 0, xds
= -1070071784, xes = -1070071784, orig_eax = -253, eip = -1072671729, xcs =
16, eflags = 582, esp = -1070039072, xss = -1072671582}) at irq.c:639
#5 0xc010c0e8 in call_do_IRQ ()
查看jiffies變量的值
(gdb) p jiffies
$1 = 76153
如果想讓target上的內核繼續運行,執行continue命令
(gdb) continue
Continuing.
3.3.2 內核在引導時調試
kgdb可以在內核引導時就對其進行調試,但並不是所有引導過程都是可調試的,如在kgdb 1.9版中,它在init/main.c的start_kernel()函數中插入以下代碼:
start_kernel()
{
......
smp_init();
#ifdef CONFIG_KGDB
if (gdb_enter) {
gdb_hook(); /* right at boot time */
}
#endif
......
}
所以在smp_init()之前的初始化引導過程是不能調試的。
另外要想讓target的內核在引導時就處於調試狀態,需要修改其/etc/grub.conf文件為如下形式:
title Red Hat Linux (2.4.23-kgdb)
root (hd0,0)
kernel /boot/vmlinuz-2.4.23-kgdb ro root=/dev/hda1 gdb gdbttyS=1 gdbbaud=115200
引導2.4.23-kgdb內核,內核將在短暫的運行後暫停並進入調試狀態,打印如下信息:
Waiting for connection from remote gdb...
*在developer機器上
#cd /home/liangjian/linux-2.4.23
# gdb vmlinux
GNU gdb Red Hat Linux (5.3post-0.20021129.18rh)
Copyright 2003 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-redhat-linux-gnu"...
執行rmt宏
(gdb) rmt
breakpoint () at kgdbstub.c:1005
1005 atomic_set(&kgdb_setting_breakpoint, 0);
查看當前堆棧
(gdb) bt
#0 breakpoint () at kgdbstub.c:1005
#1 0xc0387fe0 in init_task_union ()
#2 0xc01bc984 in gdb_hook () at gdbserial.c:250
#3 0xc0388898 in start_kernel () at init/main.c:443
在do_basic_setup函數處設置斷點,並讓內核恢復運行
(gdb) b do_basic_setup
Breakpoint 1 at 0xc0388913: file current.h, line 9.
(gdb) continue
Continuing.
[New Thread 1]
[Switching to Thread 1]
Breakpoint 1, do_basic_setup () at current.h:9
9 __asm__("andl %%esp,%0; ":"=r" (current) : "0" (~8191UL));
內核在do_basic_setup斷點處停止運行後查看當前堆棧
(gdb) bt
#0 do_basic_setup () at current.h:9
(gdb)
3.3.3 內核模塊調試調試
要想調試內核模塊,需要相應的gdb支持,kgdb的主頁上提供了一個工具gdbmod,它修正了gdb 6.0在解析模塊地址時的錯誤,可以用來正確的調試內核模塊
*在developer機器上
寫了個測試用的內核模塊orig,如下:
void xcspy_func()
{
printk("<1>xcspy_func\n");
printk("<1>aaaaaaaaaaa\n");
}
int xcspy_init()
{
printk("<1>xcspy_init_module\n");
return 0;
}
void xcspy_exit()
{
printk("<1>xcspy_cleanup_module\n");
}
module_init(xcspy_init);
module_exit(xcspy_exit);
編譯該模塊:
#cd /home/liangjian/lkm
#gcc -D__KERNEL__ -DMODULE -I/home/liangjian/linux-2.4.23/include -O -Wall -g -c -o orig.o orig.c
#scp orig.o [email protected]:/root
開始調試:
# gdbmod vmlinux
GNU gdb 6.0
Copyright 2003 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i686-pc-linux-gnu"...
設置符號文件的搜索路徑
(gdb) set solib-search-path /home/liangjian/lkm
執行rmt宏
(gdb) rmt
breakpoint () at kgdbstub.c:1005
1005 atomic_set(&kgdb_setting_breakpoint, 0);
設置斷點使得可以調試內核模塊的init函數,查內核源碼可知,內核是通過module.c文件的第566行(sys_init_module函數中)mod->init來調用模塊的init函數的
(gdb) b module.c:566
Breakpoint 1 at 0xc011cd83: file module.c, line 566.
(gdb) c
Continuing.
[New Thread 1352]
[Switching to Thread 1352]
這時在target機器上執行insmod orig.o,developer則相應的在斷點處被暫停,如下
Breakpoint 1, sys_init_module (name_user=0xc03401bc "\001",
mod_user=0x80904d8) at module.c:566
566 if (mod->init && (error = mod->init()) != 0) {
使用step命令進入模塊的init函數
(gdb) step
xcspy_init () at orig.c:12
12 printk("<1>xcspy_init_module\n");
(gdb) n
15 }
(gdb)
說明:
調試內核模塊的非init函數相對比較簡單,只要先在target上執行insmod orig.o,這時由於模塊的符號被加載,可以直接在developer的gdb中對想調試的模塊函數設置斷點,如bt xcspy_func,後面當xcspy_func被調用時就進入了調試狀態。
如果想調試內核模塊的init函數,由於在執行insmod之前模塊的符號還沒有被加載,不能直接對模塊的init函數設置斷點,所以相對來說要困難一些。可以采用兩種變通的方法:1,采用上面介紹的在內核調用模塊的init函數被調用之前的某處插入斷點,如bt sys_init_module()或bt module.c:566;2,在developer上讓內核處於運行狀態,在target上先執行一遍insmod orig.o,這時orig.o的符號已經被加載到內存中,可以直接在developer的gdb中對模塊的init函數設置斷點,如bt xcspy_init,然後在target上rmmod orig.o,當下次在target上重新加載orig.o時就進入了調試狀態,developer在xcspy_init處被暫停。
使用step命令進入模塊的init函數
(gdb) step
xcspy_init () at orig.c:12
12 printk("<1>xcspy_init_module\n");
(gdb) n
15 }
(gdb)
說明:
調試內核模塊的非init函數相對比較簡單,只要先在target上執行insmod orig.o,這時由於模塊的符號被加載,可以直接在developer的gdb中對想調試的模塊函數設置斷點,如bt xcspy_func,後面當xcspy_func被調用時就進入了調試狀態。
如果想調試內核模塊的init函數,由於在執行insmod之前模塊的符號還沒有被加載,不能直接對模塊的init函數設置斷點,所以相對來說要困難一些。可以采用兩種變通的方法:1,采用上面介紹的在內核調用模塊的init函數被調用之前的某處插入斷點,如bt sys_init_module()或bt module.c:566;2,在developer上讓內核處於運行狀態,在target上先執行一遍insmod orig.o,這時orig.o的符號已經被加載到內存中,可以直接在developer的gdb中對模塊的init函數設置斷點,如bt xcspy_init,然後在target上rmmod orig.o,當下次在target上重新加載orig.o時就進入了調試狀態,developer在xcspy_init處被暫停。
使用step命令進入模塊的init函數
(gdb) step
xcspy_init () at orig.c:12
12 printk("<1>xcspy_init_module\n");
(gdb) n
15 }
(gdb)
說明:
調試內核模塊的非init函數相對比較簡單,只要先在target上執行insmod orig.o,這時由於模塊的符號被加載,可以直接在developer的gdb中對想調試的模塊函數設置斷點,如bt xcspy_func,後面當xcspy_func被調用時就進入了調試狀態。
如果想調試內核模塊的init函數,由於在執行insmod之前模塊的符號還沒有被加載,不能直接對模塊的init函數設置斷點,所以相對來說要困難一些。可以采用兩種變通的方法:1,采用上面介紹的在內核調用模塊的init函數被調用之前的某處插入斷點,如bt sys_init_module()或bt module.c:566;2,在developer上讓內核處於運行狀態,在target上先執行一遍insmod orig.o,這時orig.o的符號已經被加載到內存中,可以直接在developer的gdb中對模塊的init函數設置斷點,如bt xcspy_init,然後在target上rmmod orig.o,當下次在target上重新加載orig.o時就進入了調試狀態,developer在xcspy_init處被暫停。