使用KGDB调试内核 on QEMU(一步一步跟我学)

/ 28评 / 5

本文由daviyang35起草,DDD最终完成。
对于本文有任何疑问,可直接在文章后评论询问。

1: 编译Linux + KGDB

1.1: 安装编译工具

请参考其他相关教程,推荐在安装系统的时候就选择上编译器及支持库

1.2: 下载最新内核代码

lmkl.org下载最新的内核源码, 可以直接使用浏览器下载代码,当然也可以使用命令wget来下载,下载后需使用tar来解压源代码。这里我们选择linux-2.6.34.1来演示。

假设缺省工作目录为/usr/src/work

1
2
3
4
5
sudo mkdir -p  /usr/src/work
sudo chmod 777 /usr/src/work -R
cd /usr/src/work
wget http://www.kernel.org/pub/linux/kernel/v2.6/linux-2.6.34.1.tar.bz2
tar -jxvf linux-2.6.34.1.tar.bz2

1.3: 配置内核选择

使能kgdb调试,并使用kgdboc作为与gdb通信模块。

1
2
3
cd linux-2.6.34.1
make defconfig
make menuconfig

需确保如下选项被选中(参考kgdb内核选项配置)

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

1.4: 编译

1
make

如果你的机器是多核的,可以使用-j+CPU数来进行并行编译,从而加快编译速度,如:

1
make -j2

编译完成后,复制bzImage和vmlinux到工作目录下备用

1
2
cp arch/x86/boot/bzImage /usr/src/work
cp vmlinux /usr/src/work

2: 制作自己的文件系统

2.1: 下载busybox

busybox站点下载一个busybox源码包,并解压。

1
2
3
4
cd /usr/src/work
wget http://www.busybox.net/downloads/busybox-1.17.0.tar.bz2
tar -jxvf busybox-1.17.0.tar.bz2
cd busybox-1.17.0

2.2: 编译busybox

1
2
3
4
5
6
7
8
9
10
11
12
13
14
make menuconfig
 
Busybox Settings  ---> 
   Build Options  --->
        [ * ] Build BusyBox as a static binary (no shared libs)
   Installation Options  --->
        [ * ] Don't use /usr
Miscellaneous Utilities  ---> 
[ ] flashcp 
[ ] flash_lock
[ ] flash_unlock
[ ] flash_eraseall
 
注:[ ] 表示不选择

保存配置文件后开始编译和安装

1
2
make
make install

此时在当前目录下生成了一个_install目录,里面就是busybox的执行文件

2.3: 制作文件系统

使用如下命令来创建一个虚拟文件系统磁盘文件,

在当前目录下创建一个名为busybox.img,大小为100M的文件,并将其格式化为ext3的文件系统

1
2
3
cd /usr/src/work
dd if=/dev/zero of=./busybox.img bs=1M count=100
mkfs.ext3 busybox.img

将这个虚拟磁盘文件到本地系统中,这样我们可以像访问本地文件一样访问它,
并将生成好的busybox的文件拷贝到这个文件里。

1
2
3
sudo mkdir /mnt/disk
sudo mount -o loop /usr/src/work/busybox.img /mnt/disk
sudo cp -rf /usr/src/work/busybox-1.17.0/_install/* /mnt/disk
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
创建必须的文件系统目录
cd /mnt/disk/
sudo mkdir dev sys proc etc lib mnt
 
使用busybox默认的设置文件
sudo cp -a /usr/src/work/busybox-1.17.0/examples/bootfloppy/etc/* /mnt/disk/etc
sudo vi /mnt/disk/etc/init.d/rcS
 
将下面内容拷贝到rcS里:
 
#! /bin/sh
/bin/mount -a
/bin/mount -t  sysfs sysfs /sys
/bin/mount -t tmpfs tmpfs /dev
#动态添加虚拟机环境中的设备
/sbin/mdev -s

做完上面对工作后,我们就可以卸载虚拟磁盘文件了。

1
2
cd /usr/src/work
sudo umount /mnt/disk

3: 安装qemu

1
2
3
4
5
Ubuntu/Debian:
sudo apt-get install qemu
 
Fedora:
sudo yum install qemu

4: 使用qemu运行自己编译的内核

1
2
3
4
5
 qemu -kernel /usr/src/work/bzImage -append "root=/dev/hda" -boot c -hda
/usr/src/work/busybox.img -k en-us
 
note:
如果你的硬盘是sata接口的,你也许需要将上面的 "root=/dev/hda" 替换为 "root=/dev/sda".

如果顺利的话,自己编译的内核+文件系统就会在那qemu黑乎乎的窗口里展现出来,
给自己倒杯水,庆祝下吧!

5: gdb + kgdb 调试内核

使能kgdb可以在内核启动时增加使能参数,也可以在内核启动后echo kgdboc模块的参数来达到目的,这里我们采取在内核启动时增加启动参数(kgdboc=ttyS0,115200 kgdbwait)的方式:

1
2
qemu -kernel /usr/src/work/bzImage -append "root=/dev/hda kgdboc=ttyS0,115200
kgdbwait" -boot c -hda /usr/src/work/busybox.img -k en-us -serial tcp::4321,server

这时,运行qemu的终端将提示等待远程连接到本地端口4321:
QEMU waiting for connection on: tcp:0.0.0.0:4321,server

这时使用另外一个控制台执行:

1
2
gdb /usr/src/work/vmlinux
(gdb) target remote localhost:4321

然后qemu就可以继续正常运行下去,最后停止内核,并显示如下信息:
kgdb: Waiting for connection from remote gdb...

这时gdb这边就可以看到如下的提示:

1
2
3
4
5
(gdb) target remote localhost:4321
Remote debugging using localhost:4321
kgdb_breakpoint () at kernel/debug/debug_core.c:983
983		wmb(); /* Sync point after breakpoint */
(gdb)

开始你的内核之旅吧~~~

如果gdb提示如下信息:
warning: Invalid remote reply:
可以使用Ctrl+C来终止当前gdb的操作,再次使用下面命令重新连接一次kgdb即可:
(gdb) target remote localhost:4321

6: gdb + kgdb 调试内核操作示例

待完善...

7: 引用/扩展阅读:

1: 使用 KGDB 调试 Linux 内核(on qemu)
该文章对qemu和kgdb的一些参数介绍得非常详细,本文的4和5章节都是参考它写的,强烈推荐大家看看。
2: setting up kgdb using kvmqemu
该文章也是关于qemu和kgdb,不过它介绍qemu如何联网等内容。PS:和上面的文章不同的是,它将qemu的虚拟串口导向到本地的一个"pty"设备上,
而前面我们是导向到一个socket端口上. qemu -serial参数介绍如下:

1
2
3
4
-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.

28条回应:“使用KGDB调试内核 on QEMU(一步一步跟我学)”

  1. cls说道:

    networking/libiproute/iplink.c: In function ‘do_change’:
    networking/libiproute/iplink.c:336: 错误:‘IFLA_LINKINFO’ 未声明 (在此函数内第一次使用)
    networking/libiproute/iplink.c:336: 错误:(即使在一个函数内多次出现,每个未声明的标识符在其
    networking/libiproute/iplink.c:336: 错误:所在的函数内只报告一次。)
    networking/libiproute/iplink.c:337: 错误:‘IFLA_INFO_KIND’ 未声明 (在此函数内第一次使用)
    make[1]: *** [networking/libiproute/iplink.o] 错误 1
    make: *** [networking/libiproute] 错误 2

    我在网上找了下,好像说要打补丁,不知道怎么打补丁,是下载更高版本的busybox吗

    [回复]

    DDD 回复:

    @cls

    cls :

    networking/libiproute/iplink.c: In function ‘do_change’:
    networking/libiproute/iplink.c:336: 错误:‘IFLA_LINKINFO’ 未声明 (在此函数内第一次使用)
    networking/libiproute/iplink.c:336: 错误:(即使在一个函数内多次出现,每个未声明的标识符在其
    networking/libiproute/iplink.c:336: 错误:所在的函数内只报告一次。)
    networking/libiproute/iplink.c:337: 错误:‘IFLA_INFO_KIND’ 未声明 (在此函数内第一次使用)
    make[1]: *** [networking/libiproute/iplink.o] 错误 1
    make: *** [networking/libiproute] 错误 2

    我在网上找了下,好像说要打补丁,不知道怎么打补丁,是下载更高版本的busybox吗

    [回复]

    这个看起来你的系统头文件linux/if_link.h里没有定义 IFLA_LINKINFO和IFLA_INFO_KIND.
    为了简单,你可以:
    1: 选择不编译iplink.
    2: 暴力的修改iplink.c文件,加入如下代码(不一定对,但可以帮助你编译通过)

    — a/networking/libiproute/iplink.c
    +++ b/networking/libiproute/iplink.c
    @@ -15,6 +15,12 @@
    #include “rt_names.h”
    #include “utils.h”
    +#ifndef IFLA_LINKINFO
    +# define IFLA_LINKINFO 18
    +#endif
    +#ifndef IFLA_INFO_KIND
    +# define IFLA_INFO_KIND 1
    +#endif
    /* taken from linux/sockios.h */
    #define SIOCSIFNAME 0x8923 /* set interface name */

    [回复]

  2. cls说道:

    Could not open ‘/dev/kqemu’ – QEMU acceleration layer not activated: Is a directory

    warning: unrecognized item “timeout” in “qSupported” response
    Ignoring packet error, continuing…

    这2个错误,怎么解决,网上找了,没找到答案。

    [回复]

    DDD 回复:

    @cls 再搜索下吧,这个问题太容易找到到答案了,我就不答复了.
    搜索并从结果中提取对自己有用的信息也是一种可自己学习和提高能力.

    [回复]

    hello 回复:

    @DDD 我也遇到这种情况,是不是kernel编译时的选项,lz可否放一个.config

    [回复]

    DDD 回复:

    @hello 命令“ make defconfig”就是表示用内核缺省的.config,这样也许比让大家再去
    下载一个.config要方便些。

  3. hello说道:

    /usr/bin/ld: cannot find -lcrypt
    /usr/bin/ld: cannot find -lm

    是缺少什么库呢,库也找不到

    [回复]

    DDD 回复:

    @hello @hello
    安装 libm 和 libcrypt 库

    [回复]

    hello 回复:

    @DDD 已经弄好了,thx

    [回复]

    hello 回复:

    @hello 安装了glibc-static静态库就好了

  4. 睡不死说道:

    在利用busybox制作文件系统时,好像漏了一步啊,
    cd dev/
    mknod -m 666 console c 5 1
    mknod -m 666 null c 1 3
    这样才能正常启动起来,

    [回复]

  5. hmily说道:

    请问,如果我想调试TCP/IP协议栈的话,该怎么做呢?基本的内核中没有网卡的驱动呀。最近要修改协议栈,不知道怎么调试,唉,向您求助

    [回复]

    DDD 回复:

    @hmily @hmily
    可以通过使用make menuconfig命令还配置下内核,选上e100(可能是e1000)网卡驱动,然后再选上协议栈支持,这样应该就能做些简单的调试了。(抱歉服务器的发送邮件功能有问题,现在才看到留言)

    [回复]

  6. moonknight说道:

    看到您还能回复我太激动… 我按照您的步骤实现了.. 但是我想查看 983 wmb(); /* Sync point after breakpoint */
    这前的函数… 尤其是 init.c start_kernel(); 然后我在您的这行代码之后
    qemu -kernel /usr/src/work/bzImage -append “root=/dev/hda” -boot c -hda
    /usr/src/work/busybox.img -k en-us 加上了 -S
    就能实现从kernel启动 设定断点…
    可是 接下来 我的next 命令就莫名的实效了.. 程序开始乱跳了..不是单步了…

    [回复]

  7. moonknight说道:

    您好~我按照您的思路实现了。 但是 调试总是从 kgdb开始, 于是我在命令参数中加上了 -S 。实现了从 linux
    内核启动开始调试。
    然而之后的 next 命令就无效了,不再是 单步执行了~ 但不一次 就跳了很远

    [回复]

    DDD 回复:

    @moonknight ”qemu -S “
    如果你加了-S命令,是使用qemu的内建调试器支持,它是可以调试系统启动代码的。

    由于kgdb的局限性,kgdb是不方便来调试 来init.c start_kernel()。
    (可以自己修改代码来使得kgdb可以调试部分启动代码,但那也得在串口设备初始化以后)

    ”然而之后的 next 命令就无效了,不再是 单步执行了~“
    这个可能是gdb对代码的解析不好,或者你在对一些汇编代码执行next操作,用”s”命令来取代next试试
    (next 操作在gdb里的含义是:执行到下一句C代码)

    如果”s”还是不能正常的运行,你可能得在编译的时候,给内核加上一些调试信息。

    [回复]

  8. 内核学习者说道:

    你好,我按写的步骤做,在最后的一步出了问题:在另一个终端执行 target remote localhost:4321后会出现 (gdb) target remote localhost:4321
    Remote debugging using localhost:4321
    kgdb_breakpoint () at kernel/kgdb.c:1749
    1749 wmb(); /* Sync point after breakpoint */
    trace API error 0x2.
    (gdb)
    每次都报这个trace API error 0x2.,运行QUMU的终端还是一直显示QEMU waiting for connection on: tcp:0.0.0.0:4321,server。不知是什么问题 ,万分感谢!

    [回复]

    DDD 回复:

    @内核学习者 @内核学习者
    这个错误比较奇怪,看输出,是已经连接kgdb成功了,我回头去看看gdb的代码。
    你把 $gdb –version 命令的输出粘贴给我看看你gdb 的版本信息。

    [回复]

    内核学习者 回复:

    @DDD GNU gdb (Ubuntu/Linaro 7.2-1ubuntu11) 7.2
    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 “i686-linux-gnu”.
    For bug reporting instructions, please see:
    .
    看到你回答, 我很激动。谢谢。

    [回复]

    内核学习者 回复:

    @内核学习者 你好,我看到了一些信息:在终端执行target remote localhost:4321后启动了QEMU,QEMU界面有个报错信息:[ 0.259998] [drm:i915_init] *ERROR* drm/i915 can’t work without intel_agp module!

    DDD 回复:

    @内核学习者 你用的是哪个版本的内核?要不你关闭下drm配置选项,重新编译下内核试试看,

  9. tangruidon说道:

    我也是一步步按照你的流程做的,可是当 qemu -kernel /usr/src/work/bzImage -append “root=/dev/hda” -boot c -hda
    /usr/src/work/busybox.img -k en-us
    的时候却显示无法挂载unable to mount root fs on unknown ,我想是没有initrd吧,于是加了 initrd /boot/initrd.img-3.2.2 ,可是就是出不来,老显示unpacking initramfs ,不知何故?

    [回复]

    DDD 回复:

    @tangruidon @tangruidon
    看情况可能是
    1:没用执行 “mkfs.ext3 busybox.img”?

    2:编译内核的时候没用选择 ext3文件系统支持,你在内核配置选项里选上ext3文件系统支持试试?

    cd /usr/src/work
    dd if=/dev/zero of=./busybox.img bs=1M count=100
    mkfs.ext3 busybox.img

    [回复]

  10. tangrui说道:

    我按照你的流程做了一遍(我用的是3.2.2的内核),可是当 qemu -kernel /usr/src/work/bzImage -append “root=/dev/hda” -boot c -hda
    /usr/src/work/busybox.img -k en-us
    的时候却显示unable to mount root fs unknown,我想应该是initrd的问题吧,于是我加了 -initrd /boot/initrd.img-3.2.2 却无法启动,一直显示unpacking initramfs ,我又换成 initrd.img-2.6.32 ,能进去,可是驱动不对,进入了busybox ,不知何故?

    [回复]

  11. tangruidon说道:

    我也用了mkfs.ext3 busybox.img,ext3我也编进内核了,实在搞不明白,你能不能远程啊,呵呵

    [回复]

    DDD 回复:

    @tangruidon 搜索了下initrd.img的用处,
    initrd.img是Linux启动过程中很重要的一个文件,如果你编译内核时将一部分功能编译为可加载模块。如果系统的一些设备的驱动编译为可加载模,那么启动时如果没有指定INITRD=/path_to_initrd.img,那么系统启动或者会失败,或者启动后会有设备无法使用(像网卡或者其它设备)。
    如果没指定initrd.img或者指定的initrd.img中并没有包含正确的驱动模块,则系统启动时会挂起,并报告”kernel panic: VFS: Unable to mount root fs on 08:06″的错误。
    而你前面的操作说 “换成 initrd.img-2.6.32 ,能进去”,
    这样的话,应该是编译的时候有子系统编译成了模块,但没有拷贝到自己制作的文件系统里。
    简单的处理方式是,将模块编译进内核,这样的话,也许就不需要intrd.img了。

    [回复]

  12. JeiGiye说道:

    有没有调试过4.10版本的内核?

    [回复]

cls进行回复 取消回复

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

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