Linux Kernel调试环境搭建(正式版)
简单记录下可行的kernel调试工具和步骤。
(本文是从本人的github上迁移过来的)
调试使用4.18版本的kernel, 用到的工具是qemu+gdb
1. 调试环境说明
Host主机是centos7.6的环境
2. 编译需要调试的内核(4.18 version)
首先下载内核tar包:
wget https://mirrors.aliyun.com/linux-kernel/v4.x/linux-4.18.2.tar.gz
可能需要安装:
yum install flex.x86_64 bison.x86_64 elfutils-libelf-devel -y
可能需要安装的依赖:
yum groupinstall "Development Tools" -y && yum install ncurses-devel -y && yum install hmaccalc zlib-devel binutils-devel elfutils-libelf-devel -y
yum install openssl-devel -y
yum install bc -y
yum install glibc-static -y
yum install openssl -y
解压后开始编译:
make defconfig
make kvmconfig
make -j4
参考:https://www.cnblogs.com/powerrailgun/category/1554674.html
以上步骤结束后会产生用于调试的内核bzImage镜像,位于arch/x86/boot/bzImage
更多可选的:
config - Update current config utilising a line-oriented program'
@echo ' nconfig - Update current config utilising a ncurses menu based program'
@echo ' menuconfig - Update current config utilising a menu based program'
@echo ' xconfig - Update current config utilising a Qt based front-end'
@echo ' gconfig - Update current config utilising a GTK+ based front-end'
@echo ' oldconfig - Update current config utilising a provided .config as base'
@echo ' localmodconfig - Update current config disabling modules not loaded'
@echo ' localyesconfig - Update current config converting local mods to core'
@echo ' defconfig - New config with default from ARCH supplied defconfig'
@echo ' savedefconfig - Save current config as ./defconfig (minimal config)'
@echo ' allnoconfig - New config where all options are answered with no'
@echo ' allyesconfig - New config where all options are accepted with yes'
@echo ' allmodconfig - New config selecting modules when possible'
@echo ' alldefconfig - New config with all symbols set to default'
@echo ' randconfig - New config with random answer to all options'
@echo ' listnewconfig - List new options'
@echo ' olddefconfig - Same as oldconfig but sets new symbols to their'
@echo ' default value without prompting'
@echo ' kvmconfig - Enable additional options for kvm guest kernel support'
@echo ' xenconfig - Enable additional options for xen dom0 and guest kernel support'
@echo ' tinyconfig - Configure the tiniest possible kernel'
@echo ' testconfig - Run Kconfig unit tests (requires python3 and pytest)'
3. 编译较新版本的qemu
下载qemu-4.1.1.tar.gz版本
https://download.qemu.org/
解压之。
具体需要的依赖包视实际情况而定,参考:
yum install pixman-devel -y
最终安装后的版本:
pixman-devel-0.34.0-1.el7.x86_64
pixman-0.34.0-1.el7.x86_64
可能还会安装诸如:bison和flex这样的工具。
为了不和以后系统安装的qemu发生冲突,这里将qemu安装到/data
目录下:
./configure --enable-rbd --enable-debug --enable-trace-backends=simple --enable-debug-stack-usage --enable-kvm --enable-vnc --prefix=/data --target-list=x86_64-softmmu
注意:
如果不加上--target-list=x86_64-softmmu
选项,那么会编译所有平台的模拟器,即就是不限于x86_64
--enable-rbd
可能需要安装librdb
库。--enable-trace-backends=simple
该选项可能会影响性能,故生产环境中应关闭该选项,调试环境中推荐开启。
接着,开始编译和安装:
make -j4
make install
4. 编译较新版本的gdb
download gdb source code:
wget https://mirrors.ustc.edu.cn/gnu/gdb/gdb-8.2.tar.gz
wget https://github.com/libexpat/libexpat/releases/download/R_2_2_8/expat-2.2.8.tar.bz2
4.1 先编译expat
解压:expat-2.2.8.tar.bz2
然后开始执行编译:
./configure --prefix=/data/expat-2.2.8
make -j4 && make install
4.2 开始编译安装gdb
and edit gdb/remote.c
将remote_target::process_g_packet
函数中的一部分内容修改成这个样子,否则gdb调试的时候可能出错:
/* Further sanity checks, with knowledge of the architecture. */
// if (buf_len > 2 * rsa->sizeof_g_packet)
// error (_("Remote 'g' packet reply is too long (expected %ld bytes, got %d "
// "bytes): %s"), rsa->sizeof_g_packet, buf_len / 2, 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.2的目录下,编译gdb:
./configure --prefix=/opt --with-expat --includedir=/data/expat-2.2.8/include/ --libdir=/data/expat-2.2.8/lib
还需要安装一个包,否则在make install时会出现错误。
yum install texinfo -y
然后执行make:
make
make install
这里假设我的expat放置在/data/
路径下。
5. 制作rootfs
在第1步,已经将内核编译好了,还需要一个rootfs,用于启动完整的Linux系统。
参考以下脚本制作一个rootfs:
yum install debootstrap.noarch -y
rm -rf /data/ubuntu_1604
rm -rf /data/rootfs.img
/data/bin/qemu-img create /data/rootfs.img 5G
mkfs.ext4 /data/rootfs.img
mkdir /data/ubuntu_1604
mount -o loop /data/rootfs.img /data/ubuntu_1604
debootstrap --arch=amd64 xenial /data/ubuntu_1604 https://mirrors.aliyun.com/ubuntu/
#auto login
mkdir -p /data/ubuntu_1604/etc/systemd/system/serial-getty@ttyS0.service.d
cat << EOF > /data/ubuntu_1604/etc/systemd/system/serial-getty@ttyS0.service.d/autologin.conf
[Service]
ExecStart=
ExecStart=-/sbin/agetty --noissue --autologin root %I $TERM
Type=idle
EOF
umount /data/ubuntu_1604
经过以上步骤,制作好了一个rootfs,再结合第1步生成的内核bzImage,就可以在qemu中启动了。
以上制作的rootfs是Ubuntu的,如果要制作基于CentOS的rootfs,参考链接:http://linuxcoming.com/blog/2019/07/04/build_ram_os_of_centos.html
6. 调试实例
参考以下脚本,启动一个调试实例:
/data/qemu-4.1.0/x86_64-softmmu/qemu-system-x86_64 -kernel linux-4.8/arch/x86/boot/bzImage \
-drive file=./rootfs.img,if=virtio \
-netdev tap,id=tap0,ifname=virbr0-nic,vhost=on,script=no -m 2048 \
-device virtio-net-pci,netdev=tap0 \
-append "root=/dev/vda rw console=ttyS0 nokaslr" \
-nographic \
--enable-kvm -S -gdb tcp::8889
可能会用到的qemu参数:
-device virtio-blk-pci,scsi=off,bus=pci.0,addr=0x6,drive=drive-virtio-disk0,id=virtio-disk0 -drive file=fake.img,format=qcow2,if=none,id=drive-virtio-disk0,cache=writeback
### 或者直接使用以下命令行可以调试qemu+Linux kernel
/opt/bin/gdb -nh --args /data/bin/qemu-system-x86_64 -m 1024 -nographic \
-kernel /root/code/linux-4.18.2/arch/x86/boot/bzImage \
-device virtio-blk-pci,scsi=off,bus=pci.0,addr=0x6,drive=drive-virtio-disk0,id=virtio-disk0 \
-drive format=raw,if=none,id=drive-virtio-disk0,cache=writeback,file=/data/rootfs.img \
-netdev tap,id=tap0,ifname=virbr0-nic,vhost=no,script=no \
-device virtio-net-pci,netdev=tap0 \
-append "root=/dev/vda rw console=ttyS0 nokaslr" -S --enable-kvm -gdb tcp::8889
注意以上的磁盘那里不要再指定if=virtio
,因此我们已经通过id指定了。重复指定会报错。
另外,添加这样的参数可以用于测试legacy和modern参数:-device disable-modern=on,disable-legacy=off
。
gdb加上-nh
参数是不让其使用~/.gdbinit
中设置的命令。
参考:https://heiko-sieger.info/tuning-vm-disk-performance/
上面将qemu运行起来后,qemu会在初始阶段停住等待gdb的调试命令。
下面,将使用gdb调试内核了。
# Attach gdb
gdb ./linux-4.18.2/vmlinux
(gdb) target remote :8889
或者将上述初始化命令写入到~/.gdbinit
文件中,如下:
add-auto-load-safe-path /root/code/linux-4.18.2/vmlinux-gdb.py
file /root/code/linux-4.18.2/vmlinux
directory /root/code/linux-4.18.2/
target remote:8889
注意:在设置breakpoint的时候,对于QEMU模拟的VM,可以使用break,但对于KVM模拟的VM,需要使用hbreak。
7. QEMU trace的使用
假设在第3步启用了--enable-trace-backends=simple
这个选项来编译qemu,那么可以尝试使用下qemu的trace功能帮助调试。
以下两个链接有助于使用qemu trace:
https://blog.csdn.net/scaleqiao/article/details/50787340
https://blog.csdn.net/weixin_34144450/article/details/91744086
参考链接:
https://www.anmolsarma.in/post/single-step-kernel/
https://www.binss.me/blog/how-to-debug-linux-kernel/
在docker 容器中运行:
创建容器:
docker run --rm -it --name build_upstream_qemu -d --cap-add NET_ADMIN --cap-add=SYS_PTRACE --security-opt seccomp=unconfined --device=/dev/kvm:/dev/kvm --device=/dev/net/tun:/dev/net/tun -v /root/kernel:/root/kernel docker.io/centos:centos7 bash