在 QEMU 上使用 KGDB 调试内核

36 views
Skip to first unread message

ETY001

unread,
Jan 5, 2012, 8:58:44 AM1/5/12
to ldlug

最近在研究 Linux Kernel, 由于我看问题喜欢直接看本质, 所以直接从代码开始看起,
但是 Linux 发展到现在代码何其多, 何其复杂, 里面的流程, 逻辑, 甚至各种变量绝对不是
我以前开发的项目能比的, 比如说里面全局变量的大量使用, 各种 goto 的使用,
所以必须要有一个好的阅读方法和好的阅读手段. 阅读代码的 Emacs 和 cscope 足以.
但是对于习惯 gdb 调试的我, 还是希望可以利用 gdb 的强大优势帮助学习. 在加上
QEMU 来作为 kernel 的运行平台, 这样的组合不事半功倍都说不过去.

内核构建

下载内核

经我测试 linux-2.6.24 和 linux-2.6.25 没有 KGDB 的支持. 为了能有 KGDB 的
支持, 我选择版本稍微高一点内核.

1
2
3
# mkdir -p ~/Develop/Linux && cd ~/Develop/Linux
# tar xf linux-2.6.34.tar.bz2

配置内核

1
2
3
# cd ~/Develop/Linux/linux-2.6.34
# make defconfig # 用 defconfig 生成 一个精简内核
# make menuconfig

确保下面的被选中

1
2
3
4
5
6
7
General setup  --->
     [ * ] Prompt for development and/or incomplete code/drivers
Kernel hacking  --->
     [ * ] Compile the kernel with debug info
     [ * ] Compile the kernel with frame pointers
     [ * ] KGDB: kernel debugger  --->
           < * >   KGDB: use kgdb over the serial console

OK, 下面开始编译内核

1
# make -j5 # 因为我是 4 核的 CPU, 所以这里使用5个线程同时并发执行

QEMU 构建

安装 QEMU

这里可以选择多种安装方式, 可以选择从源码安装或者从发行版的二进制安装.
作为一个比较喜欢追根究底的 geek, 我选择的是源码安装.

另外, 我调试的只是 kernel, 所以没有硬件虚拟话我是可以忍受的,
所以我把 kvm 从编译参数那里去掉了.

1
2
3
4
5
6
7
# mkdir -p ~/Develop/QEMU
# cd ~/Develop/QEMU
# tar xf qemu-1.0.tar.gz
# cd qemu-1.0
# ./configure --prefix=./ --target-list="i386-softmmu x86_64-softmmu" --disable-kvm
# make -j3 && make install

注意, 因为我不想把 qemu 和我系统的 qemu 冲突, 我简单的将 qemu
安装在 qemu 源码目录下.

文件系统构建

安装 busybox

1
2
3
4
5
# mkdir ~/Develop/busybox
# tar xf busybox-1.19.3
# cd busybox-1.19.3
# make menuconfig

静态编译的选择很重要, 如果不选择的话, 需要把 libc.so 和 ld.so 等
复制到文件系统里面, 稍微麻烦一些, 这里我们选择最简单的方式.

另外这个版本静态编译的时候 mount umount会出错, 方正对我来说不需要,
我暂时去掉.

1
2
3
4
5
6
Busybox Settings  --->
   Build Options  --->
        [ * ] Build BusyBox as a static binary (no shared libs)
Linux System Utilities  --->
   [ ] mount
   [ ] umount

执行 make install 后, 会在 busybox 的源码目录地下创建一个 _install
的文件夹, 这个文件夹就是需要复制到文件系统里面的东西.
# make install

制作文件系统

首先创建一个虚拟盘, 并挂载到当前 tmp 目录下.

1
2
3
4
5
# cd ~/Develop
# dd if=/dev/zero of=initrd.img bs=1k count=8192
# mkfs.ext3 -F -v -m0 initrd.img
# mkdir tmp
# mount -o loop initrd.img  tmp

然后把编译好的 busybox 复制到挂在虚拟盘的目录里面.

1
# cp -dpRrf ~/Develop/Linux/busybox-1.19.3//_install/* tmp

创建一些必须的设备文件, 其实设备号几乎是通用的, 所以我直接把本机的设备文件
复制过来了.

1
2
3
4
5
6
# cp /dev/console tmp/dev
# cp -dfrpa /dev/tty* tmp/dev
# cp -dfrpa /dev/mem tmp/dev
# cp -dfrpa /dev/null tmp/dev
# cp -dfrpa /dev/random tmp/dev
# umount tmp

运行

1
2
3
# ~/Develop/QEMU/qemu-1.0/bin/qemu-system-x86_64 \
# -kernel ~/Develop/Linux/linux-2.6.34/arch/x86/boot/bzImage \
# -hda ~/Develop/initrd.img -m 2048 -append "root=/dev/sda init=/bin/sh"

在运行的时候碰到了 init 段错误的问题. 我不知道是不是静态编译导致的.
解决这个问题有两个办法, 或者从其他发行版复制一个静态的 busybox过来,
我试过, 没有问题. 或者把内核启动参数改为 init=/bin/sh 不让 kernel
去启动 init. 我选的是后者.

开始调试

-s 表示用默认的 1234 端口, 开启 gdb server

1
2
3
# ~/Develop/QEMU/qemu-1.0/bin/qemu-system-x86_64 \
# -s -S -kernel ~/Develop/Linux/linux-2.6.34/arch/x86/boot/bzImage \
# -hda ~/Develop/initrd.img -m 2048 -append "root=/dev/sda init=/bin/sh"

在我的 Emacs 里面, 使用 /tmp/gdb/bin/gdb –annotate=3 ~/Develop/Linux/linux-2.6.34/vmlinux
来启动, 进去以后, 设置断点, 然后 target remote localhost:1234 连接 gdb server

其它.

我 gdb 的启动指令是 /tmp/gdb/bin/gdb 而不是默认的 gdb, 因为默认的 gdb 在我的 x86
平台上调试的时候有一个小 bug, 根据邮件列表上的说法.
需要 hack 代码, 然后重新编译. 我打了一个小 patch.

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
--- gdb-7.3.1-orign/gdb/remote.c    2011-07-15 10:04:29.000000000 +0800
+++ gdb-7.3.1/gdb/remote.c  2011-12-27 18:37:34.319902796 +0800
@@ -5702,9 +5702,21 @@
   buf_len = strlen (rs->buf);
 
   /* Further sanity checks, with knowledge of the architecture.  */
+#if 0
   if (buf_len > 2 * rsa->sizeof_g_packet)
     error (_("Remote 'g' packet reply is too long: %s"), rs->buf);
-
+#endif
if (buf_len > 2 * rsa->sizeof_g_packet) {
+     rsa->sizeof_g_packet = buf_len ;
+     for (i = 0; i < gdbarch_num_regs (gdbarch); i++) {
+         if (rsa->regs[i].pnum == -1)
+             continue;
+         if (rsa->regs[i].offset >= rsa->sizeof_g_packet)
+             rsa->regs[i].in_g_packet = 0;
+         else
+             rsa->regs[i].in_g_packet = 1;
+     }
+  }
   /* Save the size of the packet sent to us by the target.  It is used
      as a heuristic when determining the max size of packets that the
      target can safely receive.  */

最后献上一张截图: (Emacs 又立功了)

Emacs_KGDB

Reply all
Reply to author
Forward
0 new messages