透過虛擬化技術體驗 kgdb

/ 0评 / 0

本文转载自 Jserv's blog

Jserv老师在调试器方面有很深的造诣,并且通过文章,演讲和培训的方式与大家一起分享,让大家学到很多东西。这里转载一篇Jserv老师的kgdb文章。

延續去年 [深入淺出 Hello World - Part III] 的內容,打算於今年分享 Part IV,秉持 "Everything can be hacked" 的原則,正式要從 user-space 的 "Hello World" 應用程式,一口氣鑽進 Linux Kernel 的「黑盒子」,並揭開其中的奧秘。當然,在這之前,我們需要良好的工具,稍早在 [快快樂樂學 GNU Debugger] 的教育訓練提過 gdb 的重要概念與用法,而對於 kernel,則需要 [kgdb]。過去,Linux + kgdb 的繁複過程讓人望之卻步,我們只能眼巴巴看著 BSD kernel hacker 玩弄著 bsd kgdb,游刃於巧妙的指令與系統變化間,如今,Linux 2.6.26 就內建了延宕多年的 kgdb,且讓我們試著操作。

今年五月 3 日,Linus Torvalds 宣佈 [釋出 2.6.26-rc1] 並關閉 Linux-2.6.26 的 merge window,除了大量的驅動程式更新外,首次對主要的架構引入了 kgdb,作為內建的 source-level debugger,當然,稍早在 linux-2.6.25 時,其實像是 ARM 架構就加入 kgdb,但這次是連同 x86, powerpc/ppc, sparc, sparc64, sh, blackfin 等硬體架構都納入支援。此舉終結了長達一年的 kgdb merge 討論,這過程中,Ingo Molnar 甚至改寫原本由 LinSysSoft Technologies 所維護的 [kgdb],而建立名為 "kgdb light" git tree 分支以精簡 kgdb 的設計並導入新版核心中,最後,Linux-2.6.26 終於正式整合 kgdb,並提供主流硬體架構的支援,Linus Torvalds 本人對此表示:

1
2
3
"because people have tried to get me to merge it for some long is kgdb support.
 Which really turned out pretty small and clean, once people started putting 
 their effort into making it so."

我們需要取得 Linux kernel 2.6.26 (已於 Jul 13, 2008 釋出),而在 x86 平台上,全然不需要施加 patch,大致操作如下:

1
2
$ tar jxvf linux-2.6.26.tar.bz2
$ cd linux-2.6.26

剩下就是 make menuconfig 即可,過去的繁瑣動作,全都免了!需要留意的是 kernel configuration 的方式,關鍵部份如下圖:

也就是 Kernel hacking -> Kernel debugging -> Compile the kernel...,注意,需要同時讓 "Compile the kernel with debug info" 與 "Compile the kernel with frame pointers" 生效,如下圖:

這樣,kgdb 的項目才會出現:

確認 kgdb 正確被編譯到核心的方式,為檢查 .config 的配置項目,應該要包含以下:

1
2
3
CONFIG_DEBUG_INFO=y
CONFIG_KGDB=y
CONFIG_KGDB_SERIAL_CONSOLE=y

透過 kvm / qemu 與 Xen,我們可很輕鬆地以虛擬化技術,在單機上操控 kgdb,而不必像過去得準備兩台以上的電腦,並需要張羅網路與 RS-232 線路傳輸,本文中,筆者將探討 kvm / qemu + kgdb,至於 Xen + kgdb,其實也相當類似,只是核心組態與啟動方式不同罷了。比照前文 [深入理解 Linux 2.6 的 initramfs 機制 (上)],我們可透過 initramfs + busybox 弄個輕巧的 kernel image,筆者的參考組態為 [.config],預先編入 qemu 系統模擬所需的 device drivers,若看倌的系統跟筆者一樣用 IA32,在設定檔中的 source path 也可直接指向筆者準備的 [busybox-initramfs-dist.tar.bz2 ] 壓縮檔所解開的目錄。將 initramfs 的 source path 略作修改後,即可建構核心:

1
$ make bzImage

建構好核心後,是否躍躍欲試呢?不,必須考慮到 kgdb 的「非本地端」偵錯、除錯本質,要作些額外準備。首先,來看看整個系統示意:

由圖可見,我們將會邏輯上,區分兩個硬體系統,其中目標端、待除錯分析的那台自然稱為 Target,相對來說,提供開發環境的系統,就是 Host,這兩台系統透過 RS-232 (serial) 或 Ethernet 來通訊。不過,透過 Xen/qemu,我們可在單一實體環境中,模擬出這兩台。當我們透過 GNU Debugger 時,系統示意如下圖:

當我們要對 target program (可能是 kernel 本身,或者單一的 process) 作除錯分析時,需透過特製的額外小程式,稱為 GDB stub,連透過 RS-232 / Ethernet 建立 GDB Remote Debugging Protocol 上的通訊,如此一來,Host 上才可透過 GNU Debugger 來遠端除錯。依據此等情境,kgdb 可說是涵蓋了 GDB stub 與 Linux kernel 銜接的部份,讓執行於 Target 端的 kernel,得以透過 GDB Protocol 來除錯。

既然用了 kgdb,當然要體驗強悍的 source-level debugging,所以,我們也應該將網路環境預先設定好,以讓 target-host 能便利分享資源。kvm / qemu 都支援 TAP/TUN 的網路連結模式,其示意圖如下:

qemu 中模擬的 guest OS 可透過 TAP/TUN 所建立的通道,經由虛擬的 NIC (網路裝置),輾轉透過實體網路進行通訊。vtun 則是比較特別的變形,採用 user-space socket 重新導向 TCP/IP,所以,筆者就設想以下情境:

也就是將虛擬與實體網路裝置一併建立橋接 (bridge),這樣,無論在 Target 抑或 Host 端,都能夠對外連線,詳情可參閱 [Host-only networking (or high-speed net-connection to host)] 一文的組態設定,在 Debian/Ubuntu 上,我們需要先安裝以下套件:

1
$ sudo apt-get install bridge-utils uml-utilities vtun

並且為了簡化後續操作,建立一個專屬的設定 script,名稱為 "/etc/init.d/kvm-network",以下是其內容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#!/bin/bash-x
 
# id of the user running qemu (kvm). Make sure you change it appropriately.
USERID=1000
 
# number of TUN/TAP devices to setup
NUM_OF_DEVICES=1
 
case $1 in
start)
        modprobe tun
        /etc/init.d/vtun start
        chmod a+rw /dev/net/tun
        echo -n "Setting up bridge device br0"
        brctl addbr br0
        ifconfig br0 192.168.99.1 netmask 255.255.255.0 up
        for ((i=0; i < NUM_OF_DEVICES ; i++)); do
                echo -n "Setting up "
                tunctl -b -u $USERID -t qtap$i
                brctl addif br0 qtap$i
                ifconfig qtap$i up 0.0.0.0 promisc
        done
        ;;
stop)
        for ((i=0; i < NUM_OF_DEVICES ; i++)); do
                ifconfig qtap$i down
                brctl delif br0 qtap$i
                tunctl -d qtap$i
        done
        ifconfig br0 down
        brctl delbr br0
        /etc/init.d/vtun stop
        ;;
*)
        echo "Usage: $(basename $0) (start|stop)"
        ;;
esac

需注意的是,以上的 "USERID" 值要正確設定,端視現在的 user ID 而定,若採用 Ubuntu 預設安裝,應該都是 "1000",表示第一位系統使用者。編輯此檔後,作 chmod +x 的動作,然後以 root 身份啟動 TAP/VTUN/Bridge 的服務:

1
2
# /etc/init.d/kvm-network stop
# /etc/init.d/kvm-network start

也應依據實際網路狀態,避開 192.168.99.1/24 的網段,這要保留給 vtun/bridge 所用。準備就緒後,我們就可試著啟動剛剛編譯出的 kernel image,指令如下:

1
2
3
qemu -kernel arch/i386/boot/bzImage \
     -hda /dev/zero \
     -net nic -net tap,ifname=qtap0,script=no

其中 "-net tap,ifname=qtap0" 就是指定 qemu 透過 TAP/TUN 的模式存取網路,其執行畫面如下圖:

當 qemu 裡面的 guest OS (當然就是 Linux + initramfs) 啟動完畢後,需要在 qemu 模擬的環境中執行網路配置:

1
2
3
# ifconfig eth0 192.168.99.2/24
# route add default gw 192.168.99.1 eth0
# ping -c 5 192.168.99.1

圖中可見,筆者為確保網路可正確運作,以 ping 查驗虛擬與實體網路裝置間的橋接 (bridge),也該從 Host 端反過來 ping 192.168.99.2 (Target 端的 IP)。下一步就是多傳遞啟動 kgdb 所需的參數,如下:

1
2
3
4
5
6
7
$ qemu \
	-no-acpi \
	-kernel arch/i386/boot/bzImage -hda /dev/zero \
	-net nic \
	-net tap,ifname=qtap0,script=no \
	-serial "stdio" -serial "pty" \
	-append '$console kgdbwait kgdboc=ttyS1'

注意到上述指令列多了一行:

1
	-serial "stdio" -serial "pty"

執行畫面大致如下:

透過 -serial 時,qemu 會輸出其(被)重導的裝置,就如上圖出現的 "/dev/ttyp9" (以紅線標注),咱們看看 qemu 線上說明:

1
2
3
4
5
-serial dev
 
Redirect the virtual serial port to host character device dev. The default 
device is "vc" in graphical mode and "stdio" in non graphical mode. This
option can be used several times to simulate up to 4 serials ports.

所以前述的指令就是將 (被模擬的硬體) 文字模式的 stdio 予以導向到本機 (執行 qemu 的環境) 上的 pty (p = pseduo,虛擬之意),也就是 "/dev/ttyp?",而稍早也提過系統示意的遠端除錯裡頭,建立連線的動作,即透過 serial 或 Ethernet,這裡的 pty 就視為一種 serial 介面,所以,只要 kgdb 生效後,就可依據此介面來讓 host 端進行除錯。有了這概念後,自然對啟動 qemu 的另一行指令不感到訝異:

1
	-append '$console kgdbwait kgdboc=ttyS1'

這行就是告訴 kgdb,欲開啟的 serial 裝置為 ttyS1,並要求完成必要的初始化後,等待來自 host 端的 gdb 連線,所以,我們可在上圖看到系統執行後,印出以下訊息並等待:

1
2
kgdb: Registered I/O driver kgdboc...
kgdb: Waiting for connection from remote gdb...

看來一切就緒,就待我們從 host 端來作遠端除錯了。建議開啟新的終端機,並切換到編譯核心的 linux-2.6.26 目錄下,我們要載入包含 debug info 的 Linux kernel image 是 "vmlinux",而非壓縮過的 "arch/x86/boot/bzImage" (qemu 內建 kernel loader,所以要提供已壓縮過的 kernel image),很容易從檔案長度看出彼此的差異:

1
2
3
$ ls -lh arch/x86/boot/bzImage vmlinux
-rw-r--r-- 1 jserv jserv 1.6M 2008-06-02 01:37 arch/x86/boot/bzImage
-rwxr-xr-x 1 jserv jserv  21M 2008-06-02 01:37 vmlinux

載入 gdb 的方式如下:

$ gdb ./vmlinux
GNU gdb 6.8-debian
Copyright (C) 2008 Free Software Foundation, Inc.
[...]
This GDB was configured as "i486-linux-gnu"...
(gdb) shell echo -e "\003" > /dev/ttyp9
(gdb) set remotebaud 115200
(gdb) target remote /dev/ttyp9
Remote debugging using /dev/ttyp9
kgdb_register_io_module (new_kgdb_io_ops=0xc028f914) at kernel/kgdb.c:1677
1677 wmb(); /* Sync point after breakpoint */
(gdb)

其中粗體字表示將由我們鍵入的指令 (以下皆以此標示),首先是要求 gdb 載入包含除錯資訊的 "vmlinux",再來,測試 serial 連線是否通暢,即以輸入 ASCII 0x03 字元處理、設定 baud rate 為 115200 bps,不過,這對於 pty 來說,總是會成功,但真實情況下就難說了,在此仍列入模擬的情境。最後一個指令就是重點:
(gdb) target remote /dev/ttyp9
這要求 host gdb 經由 serial 送達遠端除錯的指令,這時候,原本暫停執行的 target/kgdb 就會依據 remote gdb 通訊協定 (即 GDB Remote Serial Protocol,縮寫為 RSP) 之中的指令,做出因應的動作,而由上可知,就是取得 breakpoint 的資訊,當然,這裡不細談其具體動作。我們可查看目前的程式行為:

1
2
3
4
5
6
7
8
9
10
11
12
(gdb) list
1672	void kgdb_breakpoint(void)
1673	{
1674		atomic_set(&kgdb_setting_breakpoint, 1);
1675		wmb(); /* Sync point before breakpoint */
1676		arch_kgdb_breakpoint();
1677		wmb(); /* Sync point after breakpoint */
1678		atomic_set(&kgdb_setting_breakpoint, 0);
1679	}
1680	EXPORT_SYMBOL_GPL(kgdb_breakpoint);
1681	
(gdb)

由 gdb 列出的程式碼即 kgdb 設定 breakpoint 的行為,我們可單步執行看看:

1
2
3
4
5
(gdb) n
[New Thread 1]
1678		atomic_set(&kgdb_setting_breakpoint, 0);
(gdb) n
1632	}

至此,我們執行過 kgdb_breakpoint() 函式。透過 kgdb 得以窺見運作中 Linux kernel 的每一個面向,筆者挑選一個自 0.0.1 版的 Linux 即有的變數 "jiffies" 作為觀察。jiffies 為 Linux 的核心變數,長度為 32 位元 (unsigned long),用以紀錄系統自開幾以來,已過多少的 tick 數量。當系統觸發一次 timer interrupt 時,jiffies 變數值即會遞增一。需要留意的是,系統開機時,jiffies 變數值並非為零,而是一個特定的負數值 (稍後再詳述),而在 x86 架構下,另行定義一個與 jiffies 相關的變數為 "jiffies_64",顧名思義,此變數為 64 位元。以下就來觀察這兩個變數的值:

1
2
3
4
5
(gdb) p jiffies
$1 = -74817
(gdb) p jiffies_64
$2 = 4294892479
(gdb)

(待續)

由 jserv 發表於 July 18, 2008 01:23 AM

发表评论

电子邮件地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据