這次來看如何調試內核模塊,也就是驅動程序,模塊的調試跟普通程序略有不同,不論是內核還是普通應用程序,在連接之後便以得知代碼將要加載的位置,用戶態程序有虛擬地址映射機制,而內核獨占物理內存。內核運行與共享的內核地址空間,所以不能使用相同的線性地址,只能由內核加載模塊時指定起始地址,模塊中都以此為偏移運行。所以內核的調試不能使用普通的方式,需要知道模塊的加載地址。
而且Qemu的調試原理與UML相似,也可用相同的方法進行模塊的調試,這裡僅以UML模塊調試舉例
首先需要完成一個內核模塊,這裡可以照搬之前的一篇日志 Linux 內核調試 ,保存源文件為go.c,完成Makefile和編譯工作,內核源碼以編譯UML的內核為准:
# Makefile 內容
obj-m := go.o
# 編譯命令
[cpp@dark go]$ make -C /home/cpp/fox/linux-2.6.36 M=$PWD modules ARCH=um
接下來啟動帶網絡的UML,拷貝go.ko模塊文件到UML虛擬機中,之後啟動GDB:
# 查看 linux 進程 ID
[cpp@dark linux-2.6.36]$ ps -A | grep linux
7333 pts/0 00:00:38 linux
7340 pts/0 00:00:00 linux
7341 pts/0 00:00:00 linux
7342 pts/0 00:00:00 linux
7343 pts/0 00:00:00 linux
7481 pts/0 00:00:00 linux
7546 pts/0 00:00:00 linux
9864 pts/0 00:00:00 linux
9866 pts/0 00:00:00 linux
9868 pts/0 00:00:00 linux
# 啟動 GDB
[cpp@dark linux-2.6.36]$ gdb linux
GNU gdb (GDB) Fedora (7.1-18.fc13)
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-RedHat-linux-gnu".
For bug reporting instructions, please see:...
Reading symbols from /home/cpp/fox/linux-2.6.36/linux...done.
# attach 到 linux 進程,attach第一個進程
(gdb) attach 7333
# 忽略段錯誤
(gdb) handle SIGSEGV pass nostop noprint
# 對 sys_init_module 設置斷點,截獲模塊加載事件
(gdb) br sys_init_module
Breakpoint 1 at 0x60052421: file kernel/module.c, line 2696.
# 繼續執行
(gdb) c
Continuing.
# 在虛擬機中加載 go.ko 模塊
localhost ~ # insmod go.ko
# 主機GDB截獲斷點
Breakpoint 1, sys_init_module (umod=0x603030, len=124282,
uargs=0x603010) at kernel/module.c:2696
2696 if (!capable(CAP_SYS_MODULE) || modules_disabled)
(gdb)
# 模塊加載 load_module 調用結果
(gdb) n 2691 {
(gdb) n 2696 if (!capable(CAP_SYS_MODULE) || modules_disabled)
(gdb) n 2700 mod = load_module(umod, len, uargs);
(gdb) n 2701 if (IS_ERR(mod))
(gdb) print mod
$1 = (struct module *) 0x628482b0
# 從 include/linux/module.h 查看 module.h結構
# struct module_sect_attrs *sect_attrs; 為模塊的段屬性
# 查看模塊名
(gdb) print mod->name
$2 = "go", '\000'
# 查看模塊段個數
(gdb) print mod->sect_attrs->nsections
$3 = 11
# 查看第一個段名
(gdb) print mod->sect_attrs->attrs[0]->name
$4 = 0x61daa3a0 ".note.gnu.build-id"
# 找到 .text 段,並顯示基址
(gdb) print mod->sect_attrs->attrs[1]->name
$5 = 0x61daa3c0 ".text"
(gdb) print /x mod->sect_attrs->attrs[1]->address
$6 = 0x62848000
# 加載符號,使用 GDB 的 add-symbol-file 命令加載模塊符號到基址
(gdb) add-symbol-file go/go.ko 0x62848000 add symbol table from file "go/go.ko" at .text_addr = 0x62848000 (y or n) y Reading symbols from /home/cpp/fox/linux-2.6.36/go/go.ko...done.
# 設置模塊斷點
(gdb) br simple_read Breakpoint 2 at 0x62848028: file /home/cpp/fox/linux-2.6.36/go/go.c, line 21.
# 繼續運行
(gdb) c Continuing.
# UML 虛擬機中操作模塊 localhost ~
# cat /dev/simple
# 主機斷點生效 Breakpoint 2, simple_read (pfile=0x61c74180, buf=0x60d000
, size=32768, ppos=0x61e0bec0) at /home/cpp/fox/linux-2.6.36/go/go.c:21 21 { (gdb) # 查看信息並調試 (gdb) n 22 if (copy_to_user(buf, “test data\n”, 10)) (gdb)
當然加載符號時還可以查看 .data 段和 .bss 段的基址,加載go.ko時同時可以設置二者的基址,以便可以調試全局變量等等,初看起來這個過程比較復雜,其實就是通過調試內核加載位置來確定模塊最終將要加載的基址,而後通過機制加載模塊符號。
這個復雜的過程如果每次都需要手動處理是非常煩人的一件事,好在GDB本身有腳本擴展(甚至可執行Python腳本),來簡化這個過程,這裡來試著寫一個簡單的打印模塊section名字和基址的腳本。
GDB腳本運行可以由兩種方式,一種是在GDB啟動時,在當前目錄查找.gdbinit文件解釋執行,另一種在GDB運行期間使用 source script-file 命令來執行,腳本的說明可查閱文檔,以下是簡單的打印段信息的腳本:
define modsec
set $index = 0
while $index < mod->sect_attrs->nsections
printf "Name\t%s\tAddress\t0x%x\n", mod->sect_attrs->attrs[$index]->name, mod->sect_attrs->attrs[$index]->address
set $index = $index + 1
end
end
以下是加載並執行腳本效果:
# 加載腳本
(gdb) source modsec
(gdb) c
Continuing.
# 在虛擬機中加載 go.ko 模塊
# 並單步執行到 mod = load_module(umod, len, uargs);
Breakpoint 1, sys_init_module (umod=0x603030, len=124282,
uargs=0x603010) at kernel/module.c:2696
2696 if (!capable(CAP_SYS_MODULE) || modules_disabled)
(gdb) n
2691 {
(gdb) n
2696 if (!capable(CAP_SYS_MODULE) || modules_disabled)
(gdb) n
2700 mod = load_module(umod, len, uargs);
(gdb) n
2701 if (IS_ERR(mod))
# 調用定義命令打印段
(gdb) modsec
Name .note.gnu.build-id Address 0x6286f09c
Name .text Address 0x6286f000
Name .exit.text Address 0x6286f050
Name .init.text Address 0x62872000
Name .rodata.str1.1 Address 0x6286f0c0
Name .eh_frame Address 0x6286f118
Name .data Address 0x6286f1e0
Name .gnu.linkonce.this_module Address 0x6286f2b0
Name .bss Address 0x6286f490
Name .symtab Address 0x62872098
Name .strtab Address 0x628725c0
如此便可稍微方便一點的打印所有段名和段基址,如果添加其他腳本擴展,可以更加方便且高效的調試Linux內核。
相關系列文章:
Linux 內核調試1-UML http://www.linuxidc.com/Linux/2012-07/66410.htm
Linux 內核調試2-UML調試內核 http://www.linuxidc.com/Linux/2012-07/66411.htm
Linux 內核調試3-UML網絡配置 http://www.linuxidc.com/Linux/2012-07/66412.htm
Linux 內核調試4-Qemu調試Linux內核 http://www.linuxidc.com/Linux/2012-07/66413.htm
Linux 內核調試5-UML和Qemu調試模塊 http://www.linuxidc.com/Linux/2012-07/66414.htm
Linux 內核調試6-使用KGDB雙機調試 http://www.linuxidc.com/Linux/2012-07/66415.htm