在 QEMU 上使用 KGDB 调试内核

#+SETUPFILE: ~/Dropbox/Doc/Org_Templates/level-1.org

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

内核构建

下载内核

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

# mkdir -p ~/Develop/Linux && cd ~/Develop/Linux
# wget http://www.kernel.org/pub/linux/kernel/v2.6/linux-2.6.34.tar.bz2
# tar xf linux-2.6.34.tar.bz2

配置内核

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

确保下面的被选中

 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, 下面开始编译内核

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

QEMU 构建

安装 QEMU

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

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

# mkdir -p ~/Develop/QEMU
# cd ~/Develop/QEMU
# wget http://wiki.qemu.org/download/qemu-1.0.tar.gz
# 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

# mkdir ~/Develop/busybox
# wget http://www.busybox.net/downloads/busybox-1.19.3.tar.bz2
# tar xf busybox-1.19.3
# cd busybox-1.19.3
# make menuconfig

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

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

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 目录下.

# 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 复制到挂在虚拟盘的目录里面.

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

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

# cp -dfrpa /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

运行

# ~/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

# ~/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.

--- 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.  */

打赏一个呗

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦