0%

使用qemu+gdb调试centos7内核

阅读kernel代码的时候,变量很多,尤其是读内存部分代码的时候,传来传去,常常把自己弄晕。改成什么样子了,内存布局到底变成什么了,利用qemu可以在线调试内核,如同用gdb调试app一样,打断点 打印变量值等等。qemu是个基于kvm的模拟器,说虚拟机也可以,android的模拟器,就是基于开源的qemu修改的,下面开始调试的旅程吧。

1.安装依赖

1
2
3
4
5
6
7
8
9
10
11
12
sed -e 's|^mirrorlist=|#mirrorlist=|g' \
-e 's|^#baseurl=http://mirror.centos.org|baseurl=https://mirrors.tuna.tsinghua.edu.cn|g' \
-i.bak \
/etc/yum.repos.d/CentOS-*.repo
yum install -y vim wget lrzsz
yum install -y gcc bc gcc-c++ ncurses ncurses-devel cmake elfutils-libelf-devel openssl-devel libgcrypt-devel glibc-static
yum install -y qemu-kvm gdb net-tools texinfo

systemctl stop firewalld
systemctl mask firewalld
setenforce 0
sed ‐i '/^SELINUX/s/=.*/=disabled/' /etc/selinux/config

2.下载内核版本解压并进入解压目录

1
wget https://vault.centos.org/7.9.2009/os/Source/SPackages/kernel-3.10.0-1160.el7.src.rpm

3.清理内核源目录

1
make mrproper

4.使用make menuconfig菜单来订制内核功能

1
2
3
4
5
make menuconfig
(
配置ext4、virtio为 Y
修改Makefile: -O2改为-O1; 删掉-Werror
)

5.编译内核

1
2
make vmlinux -j 4
make bzImage

6. 制作initramfs

下载busybox

1
2
wget https://busybox.net/downloads/busybox-1.28.0.tar.bz2
cd busybox-1.28.0

配置编译选项busybox,开启Build static binary (no shared libs)选项

1
2
3
4
5
6
make menuconfig
(
Settings --->
--- Build Options
[*] Build static binary (no shared libs)
)

编译busybox

1
2
make -j 4
make install // 编译安装文件在_install目录下

制作initramfs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
mkdir initramfs
cd initramfs
cp ../_install/* -rf ./

mkdir {dev,proc,sys}

cp -a /dev/{null,console,tty,tty1,tty2,tty3,tty4} dev/

rm linuxrc

vim init
(
#!/bin/busybox sh
mount –t proc none /proc
mount –t sysfs none /sys
exec /sbin/init
)

chmod a+x init
find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../initramfs.cpio.gz

7. 通过qemu-kvm调试内核

1
/usr/libexec/qemu-kvm -kernel arch/x86/boot/bzImage -initrd initramfs.cpio.gz -s -S -append nokaslr -vnc :0

-kernel bzImage:指定内核路径
-initrd file:指定initramdisk路径
-s:-gdb tcp::1234的缩写, 开启一个gdbserver, 可以通过TCP端口1234连接
-S: 启动后立即暂停
-append nokaslr: 指定内核参数nokaslr, 禁止内核地址随机化, 否则gdb打断点找不到地址
-vnc :0: 开启vnc监听0端口

PS:
--monitor stdio 开启qmp交互

打开另一个终端

1
2
3
gdb vmlinux 
或者
gdb vmlinux --tui
1
2
3
4
5
6
7
8
9
10
11
12
13
14
(gdb) target remote:1234
Remote debugging using :1234
0x0000000000000000 in irq_stack_union ()
(gdb) hb start_kernel // 设置硬件断点
Hardware assisted breakpoint 1 at 0xffffffff81d9ae44: file init/main.c, line 489.
(gdb) info b
Num Type Disp Enb Address What
1 hw breakpoint keep y 0xffffffff81d9ae44 in start_kernel at init/main.c:489
breakpoint already hit 1 time
(gdb) c
Continuing.

Breakpoint 1, start_kernel () at init/main.c:489
489 {

小技巧: gdb可以直接添加command
gdb vmlinux -ex="target remote:1234" -ex="hb start_kernel"

8. 遇到的问题

8.1 gdb vmlinux会出现 “Remote ‘g’ packet reply is too long”错误

Remote 'g' packet reply is too long: 0000000000000000d981ffffffff00000004000000000000001006000000000000040000000000000010060000000000903fc081ffffffff883fc081ffffffff0000000000000000130000000000000000000000000000000cc5130300000000ffffffff00000000200000000000000020a1d981ffffffff8e0000000000000044aed981ffffffff8200000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007f0300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000801f0000

解决办法: 修改gdb源码, 重新编译

1
2
3
wget ftp://sourceware.org/pub/gdb/releases/gdb-7.6.tar.gz
tar -xvf gdb-7.6.tar.gz
cd gdb-7.6

注释掉:

1
2
if (buf_len > 2 * rsa->sizeof_g_packet)
error (_(“Remote ‘g’ packet reply is too long: %s”), rs->buf);

并在后面添加:

1
2
3
4
5
6
7
8
9
10
11
12
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;
}
}

重新编译

1
2
3
4
mkdir -p /opt/gdb_7_6
./configure --prefix=/opt/gdb_7_6
make && make install
echo "PATH=\$PATH:/opt/gdb_7_6/bin" >> /root/.bashrc

参考

欢迎关注我的其它发布渠道