有的时候为了研究内核原理或者调试bios的时候,可以利用QEMU和gdb的方式来帮助我们调试问题. 这种操作利用了QEMU内建的gdb-stub能力.

重新编译内核

编译的时候开启内核参数CONFIG_DEBUG_INFO和CONFIG_GDB_SCRIPTS再进行编译, 如果硬件支持CONFIG_FRAME_POINTER也一并开启,但要关闭CONFIG_DEBUG_INFO_REDUCED。

cd /home/fang/Code/opensrc/linux
make modules -j`nproc`
make -j`nproc` 

调试内核

用下的命令行拉起QEMU,这里可以从自己的OS上选取一个initramfs传给QEMU或者直接挂载一个rootfs镜像, 记得配上nokaslr以免内核段基地址被随机映射. 这里-S参数可以让QEMU启动后CPU先Pause住不运行, -s参数是-gdb tcp::1234的简写,意思是让QEMU侧的gdb server侦听在1234端口等待调试.

/data/vm/qemu/x86_64-softmmu/qemu-system-x86_64 \
    -machine pc,accel=kvm \
    -cpu host \
    -smp 4 \
    -m 4096M \
    -nodefaults \
    -nographic \
    -drive id=test,file=$(pwd)/fedora33.raw,format=raw,if=none \
    -device virtio-blk-pci,drive=test \
    -netdev tap,id=tap,ifname=virbr0-tap,script=no,downscript=no \
    -device virtio-net-pci,netdev=tap \
    -kernel /home/fang/Code/opensrc/linux/vmlinux \
    -append "nokaslr earlyprintk=ttyS0 console=ttyS0 tsc=realiable root=/dev/vda rw" \
    -serial stdio -S -s

用上面的qemu脚本拉起虚拟机,关键是最后的-S -s参数的含义是让qemu运行内建的gdbserver并监听在本地的1234 端口。注意:测试发现高版本的qemu内建的gdbserver好像有兼容性问题,我用qemu-6.0发现无法调试内核, 但是回退到qemu-4.0可以调试内核。下载qemu自己编译一个合适的版本:

git clone https://github.com/qemu/qemu.git
cd qemu
git checkout v4.0.0
./configure --enable-kvm --target-list=x86_64-softmmu --disable-werror
make -j
# 目标文件是:
x86_64-softmmu/qemu-system-x86_64

如果运行后,gdb可能会报错Remote 'g' packet reply is too long:, 这个时候的解决办法是打上一个补丁然后重新编译gdb.

问题处在static void process_g_packet (struct regcache *regcache)函数,6113行,屏蔽对buf_len的判断.

如果gdb版本低(7.x)打这个补丁:

    if (buf_len > 2 * rsa->sizeof_g_packet)
    error (_("Remote 'g' packet reply is too long: %s"), rs->buf);
改为:
    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;
        }
    }

如果gdb版本高(8.x)打这个补丁:

    if (buf_len > 2 * rsa->sizeof_g_packet)
    error (_("Remote 'g' packet reply is too long: %s"), rs->buf);
改为:
    /* Further sanity checks, with knowledge of the architecture.  */
    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;
        }
    }

编辑~/.gdbinit文件,让gdb自动加载vmlinux-gdb.py文件,

add-auto-load-safe-path /path/to/linux-build

开始愉快地调试内核了:

$ cd linux
$ gdb vmlinux
$ lx-symbols
$ 
(gdb) target remote :1234
Remote debugging using :1234
0x000000000000fff0 in cpu_hw_events ()
(gdb) hb start_kernel
Hardware assisted breakpoint 1 at 0xffffffff827dabb2: file init/main.c, line 538.
(gdb) c
Continuing.

Thread 1 hit Breakpoint 1, start_kernel () at init/main.c:538
538 {
(gdb) l
533 {
534     rest_init();
535 }
536 
537 asmlinkage __visible void __init start_kernel(void)
538 {
539     char *command_line;
540     char *after_dashes;
541 
542     set_task_stack_end_magic(&init_task);
(gdb) 

另外可以使用mkinitrd(mkinitramfs)命令来生成initramfs.img文件,例如:

sudo mkinitrd -v initramfs.img 5.0.0-rc4+

第一个参数是initramfs的输出文件名,第二个参数是内核版本号。 然后我们可以直接boot这个内核:

x86_64-softmmu/qemu-system-x86_64 \
    -kernel /mnt/code/linux/arch/x86/boot/bzImage \
    -nographic \
    -append "console=ttyS0 nokalsr" \
    -enable-kvm \
    -cpu host \
    -initrd initramfs.img \
    -m 1024

参考文献

1.linux kernel debug with qemu

2.Debugging kernel and modules via gdb

Share on: TwitterFacebookEmail


Related Posts


Published

Category

linux

Tags

Contact