#+SETUPFILE: ~/Dropbox/Doc/Org_Templates/level-1.org
最近在研究 Linux Kernel, 由于我看问题喜欢直接看本质, 所以直接从代码开始看起, 但是 Linux 发展到现在代码何其多, 何其复杂, 里面的流程, 逻辑, 甚至各种变量绝对不是 我以前开发的项目能比的, 比如说里面全局变量的大量使用, 各种 goto 的使用, 所以必须要有一个好的阅读方法和好的阅读手段. 阅读代码的 Emacs 和 cscope 足以. 但是对于习惯 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. */