使用qemu 和 GDB 调试 Linux 5.19 内核

准备阶段

软件下载

  • linux 内核下载

官网 kernel.org

image-lyqy.png

阿里云源码镜像源

image-zfmd.png

下载busybox

wget https://busybox.net/downloads/busybox-1.34.0.tar.bz2

官网下载

image-vpjy.png

软件安装

  • 安装依赖库和构建套件
sudo apt install git -y # 代码管理工具
sudo apt install build-essential -y # 编译所需的常用软件,如gcc等
sudo apt install flex bison bc kmod pahole -y # 内核编译所需软件
sudo apt-get install libelf-dev libssl-dev libncurses-dev -y # 内核源码编译依赖的库
sudo apt install zstd -y
sudo apt-get install glibc-static -y # 编译busybox 依赖
  • 安装qemu
sudo apt-get install qemu qemu-kvm qemu-system -y # qemu虚拟机相关软件
sudo apt-get install virt-manager -y # docker中不需要安装,虚拟机图形界面,会安装iptables,可能需要重启才能以非root用户启动virt-manager,当然对于内核开发来说安装这个软件是为了生成自动生成virbr0网络接口

编译并运行Linux kernel

编译Linux kernel

# cd 进入下载有linux 内核源码压缩包的目录下
tar -zxvf linux-5.19.tar.gz && sync
cd linux-5.19
# 生成linux 编译配置文件 .config
make menuconfig

参考配置如下图

空格键 开启/关闭选项

回车 进入下级目录

连续两次按 esc 退出当前目录/退出menuconfig

File system ------->

开启ext4 文件系统debug 支持

image-nmvo.png

kernel hacking -----> printk and dmesg option ------>

调试信息时间戳 Show timing information on printks

调用函数显示 Show caller information on printk

image-hznb.png

编译

make -j12 # -j 后面接的数字表示本次编译使用的线程数,一般是设备核心数的1-2倍

编译busybox

# 进入下载有busybox压缩包的目录
tar -xvf busybox-1.36.0.tar.bz2 && sync
cd busybox-1.36
make menuconfig

参考配置

空格键 开启/关闭选项

回车 进入下级目录

连续两次按 esc 退出当前目录/退出menuconfig

Settings -----> 生成设置

make -j12 && make install# 编译busybox 并将编译好好的文件安装到_install 文件夹下
sync
ls _install # 此时在安装目录下会出现如下文件 :
# bin  linuxrc  sbin  usr

创建initramfs

创建带有BusyBox可执行程序、必要的设备文件、启动脚本 `init`的初始跟文件系统。这里没有内核模块,如果需要调试内核模块,可将需要的内核模块包含进来。`init`脚本只挂载了虚拟文件系统 `procfs`和 `sysfs`,没有挂载磁盘根文件系统,所有调试操作都在内存中进行,不会落磁盘。

mkdir initramfs
cd initramfs
cp ../_install/* -rf ./
mkdir dev proc sys
sudo cp -a /dev/{null, console, tty, tty1, tty2, tty3, tty4} dev/ # 此处用bash 执行可能会报错,可以用touch 挨个创建大括号中的文件
rm linuxrc
vim init #编写init 脚本,内容见下

init 启动脚本

#!/bin/busybox sh
mount -t proc none /proc
mount -t sysfs none /sys

exec /sbin/init
chmod a+x init # 给init 启动脚本可执行权限
ls
# bin   dev  init  proc  sbin  sys   usr

打包根文件系统

find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../initramfs.cpio.gz

使用qemu 启动linux 内核

cd busybox-1.36.0
qemu-system-x86_64 -s -kernel /path/to/vmlinux -initrd initramfs.cpio.gz -nographic -append "console=ttyS0"
# 启动内核后 ls 查看系统目录
/ # ls
bin   dev  init  proc  root  sbin  sys   usr

  • -s-gdb tcp::1234缩写,监听1234端口,在GDB中可以通过 target remote localhost:1234连接;
  • -kernel指定编译好的调试版内核;
  • -initrd指定制作的initramfs;
  • -nographic取消图形输出窗口,使QEMU成简单的命令行程序;
  • -append "console=ttyS0"将输出重定向到console,将会显示在标准输出stdio。

使用GDB 连接qemu

cd linux-5.19
/usr/local/bin/gdb vmlinux
(gdb) target remote localhost:1234

调试Linux 内核

```bash

# 在函数cmdline_proc_show设置断点,虚拟机中运行cat /proc/cmdline命令即会触发。
(gdb) b cmdline_proc_show
Breakpoint 1 at 0xffffffff81298d99: file fs/proc/cmdline.c, line 9.
(gdb) c
Continuing.

Breakpoint 1, cmdline_proc_show (m=0xffff880006695000, v=0x1 <irq_stack_union+1>) at fs/proc/cmdline.c:9
9               seq_printf(m, "%s\n", saved_command_line);
(gdb) bt
#0  cmdline_proc_show (m=0xffff880006695000, v=0x1 <irq_stack_union+1>) at fs/proc/cmdline.c:9
#1  0xffffffff81247439 in seq_read (file=0xffff880006058b00, buf=<optimized out>, size=<optimized out>, ppos=<optimized out>) at fs/seq_file.c:234
#2  0xffffffff812908b3 in proc_reg_read (file=<optimized out>, buf=<optimized out>, count=<optimized out>, ppos=<optimized out>) at fs/proc/inode.c:217
#3  0xffffffff8121f174 in do_loop_readv_writev (filp=0xffff880006058b00, iter=0xffffc900001bbb38, ppos=<optimized out>, type=0, flags=<optimized out>) at fs/read_write.c:694
#4  0xffffffff8121ffed in do_iter_read (file=0xffff880006058b00, iter=0xffffc900001bbb38, pos=0xffffc900001bbd00, flags=0) at fs/read_write.c:918
#5  0xffffffff81220138 in vfs_readv (file=0xffff880006058b00, vec=<optimized out>, vlen=<optimized out>, pos=0xffffc900001bbd00, flags=0) at fs/read_write.c:980
#6  0xffffffff812547ed in kernel_readv (offset=<optimized out>, vlen=<optimized out>, vec=<optimized out>, file=<optimized out>) at fs/splice.c:361
#7  default_file_splice_read (in=0xffff880006058b00, ppos=0xffffc900001bbdd0, pipe=<optimized out>, len=<optimized out>, flags=<optimized out>) at fs/splice.c:416
#8  0xffffffff81253c7c in do_splice_to (in=0xffff880006058b00, ppos=0xffffc900001bbdd0, pipe=0xffff8800071a1f00, len=16777216, flags=<optimized out>) at fs/splice.c:880
#9  0xffffffff81253f77 in splice_direct_to_actor (in=<optimized out>, sd=0xffffc900001bbe18, actor=<optimized out>) at fs/splice.c:952
#10 0xffffffff812540e3 in do_splice_direct (in=0xffff880006058b00, ppos=0xffffc900001bbec0, out=<optimized out>, opos=<optimized out>, len=<optimized out>, flags=<optimized out>) at fs/splice.c:1061
#11 0xffffffff8122147f in do_sendfile (out_fd=<optimized out>, in_fd=<optimized out>, ppos=0x0 <irq_stack_union>, count=<optimized out>, max=<optimized out>) at fs/read_write.c:1434
#12 0xffffffff812216f5 in SYSC_sendfile64 (count=<optimized out>, offset=<optimized out>, in_fd=<optimized out>, out_fd=<optimized out>) at fs/read_write.c:1495
#13 SyS_sendfile64 (out_fd=1, in_fd=3, offset=0, count=<optimized out>) at fs/read_write.c:1481
#14 0xffffffff8175edb7 in entry_SYSCALL_64_fastpath () at arch/x86/entry/entry_64.S:203
#15 0x0000000000000000 in ?? ()
(gdb) p saved_command_line

可能遇到的问题

  • cp -a {} 报错
<p> sudo cp -a /dev/{null, console, tty, tty1, tty2, tty3, tty4} dev/ # 此处用bash 执行可能会报错,可以用touch 挨个创建大括号中的文件 </p>
  • make bzImage -j12 构建内核镜像报错 缺少证书停止构建

No rule to make target ‘debian/canonical-certs.pem‘, needed by ‘certs/x509\_certificate\_list‘

image-cibj.png

cd linux-5.19
vim .config
# 注释掉.config 文件以下内容:
# CONFIG_SYSTEM_TRUSTED_KEYS="debian/canonical-certs.pem" 
# CONFIG_SYSTEM_REVOCATION_KEYS="debian/canonical-revoked-certs.pem"
# 编辑完成后按esc + 冒号输入 wq! + 回车 退出并保存

参考


https://chenxiaosong.com/courses/kernel/kernel-dev-environment.html
https://blog.csdn.net/hknaruto/article/details/133672892
https://consen.github.io/2018/01/17/debug-linux-kernel-with-qemu-and-gdb/
https://blog.51cto.com/u_12947/10201710