Linux debug

在 Linux 上编译和调试内核的完整指南

第一阶段:环境准备

1. 安装必需的编译工具和依赖

在 Ubuntu/Debian 系统上:

1
2
3
4
sudo apt update
sudo apt install -y git build-essential libncurses-dev flex bison libssl-dev libelf-dev dwarves
sudo apt install -y qemu-system-x86 # 虚拟机
sudo apt install -y gdb # 调试器

在 CentOS/RHEL/Fedora 系统上,使用 yum 或 dnf 安装类似的包。

2. 获取内核源码

方法A:从官方仓库克隆(推荐,获取最新版本)

1
2
git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
cd linux

方法B:下载稳定版本压缩包
https://kernel.org 下载一个稳定的源码压缩包(如 linux-6.6.6.tar.xz),然后解压:

1
2
tar -xvf linux-6.6.6.tar.xz
cd linux-6.6.6

第二阶段:配置与编译

3. 内核配置

这是最关键的一步。你可以基于当前运行的内核配置来开始,这样可以减少不必要的麻烦。

1
2
# 将当前系统的配置复制到源码目录
cp /boot/config-$(uname -r) .config

运行配置菜单:

1
make menuconfig

在出现的图形化界面中,确保开启以下关键调试选项(使用空格键选择,* 表示编译进内核,M 表示编译为模块):

  • Kernel hacking ->
    • Compile-time checks and compiler options ->
      • [*] Compile the kernel with debug info (CONFIG_DEBUG_INFO) - 这是最重要的,生成调试符号。
      • [*] Generate dwarf4 debuginfo (CONFIG_DEBUG_INFO_DWARF4)
      • [*] Provide GDB scripts for kernel debugging (CONFIG_GDB_SCRIPTS)
    • [*] KGDB: kernel debugger (CONFIG_KGDB)
      保存并退出配置菜单。
      如果直接使用现有配置,可以运行 make olddefconfig 来无声地接受所有新选项的默认值。

4. 编译内核

使用 -j 参数来并行编译,数字为你的CPU核心数 * 2,以大幅加快速度(例如,8核CPU用 -j16)。

1
2
3
4
# 获取CPU核心数
nproc
# 开始编译
make -j$(nproc)

这个过程会持续很长时间(从几十分钟到几小时),取决于你的硬件性能。
编译完成后,主要生成以下文件:

  • vmlinux: 在源码根目录,这是带有调试信息的原始内核可执行文件,是GDB调试的主要对象。
  • arch/x86/boot/bzImage: 压缩后的可引导内核镜像,用于启动系统。

第三阶段:使用 QEMU 启动和调试

为了避免损坏你的主机系统,在虚拟机中调试是最安全的方式。

5. 准备一个最小的根文件系统(rootfs)

我们需要一个简单的环境来运行内核。BusyBox 是一个很好的选择。

  1. 下载并编译 BusyBox:
1
2
3
4
5
cd ..
wget https://www.busybox.net/downloads/busybox-1.36.1.tar.bz2
tar -xf busybox-1.36.1.tar.bz2
cd busybox-1.36.1
make menuconfig

在 BusyBox 设置中,进入 Settings -> [*] Build static binary (no shared libs)。

1
make -j$(nproc) && make install
  1. 创建根文件系统:
1
2
3
cd _install
mkdir -p proc sys dev etc tmp
sudo cp -a /dev/{null,console,tty,tty1,tty2,tty3,tty4} dev/
  1. 创建一个简单的 init 脚本(必须是可执行的):
1
2
3
4
5
6
7
8
9
cat > init << EOF
#!/bin/sh
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs none /dev
echo "Welcome to QEMU Linux!"
exec /bin/sh
EOF
chmod +x init
  1. 打包成 initramfs:
1
2
find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../initramfs.cpio.gz
cd ../..

6. 使用 QEMU 启动编译好的内核

1
2
3
4
5
6
7
8
qemu-system-x86_64 \
-kernel linux/arch/x86/boot/bzImage \
-initrd busybox-1.36.1/initramfs.cpio.gz \
-append "console=ttyS0 nokaslr root=/dev/ram init=/init" \
-nographic \
-s -S \
-smp 2 \
-m 2G

参数解释:

  • -s: 缩写,相当于 -gdb tcp::1234,在TCP端口1234上开启GDB调试服务。
  • -S: 在启动时冻结CPU,等待GDB发出 continue 指令后再继续。这是开始调试的关键。
  • -nographic: 无图形界面,将QEMU控制台输出到当前终端。
  • -append “nokaslr”: 禁用内核地址空间布局随机化,这是调试所必需的,否则断点会错位。
  • -smp and -m: 指定CPU核心数和内存大小。
    此时,QEMU窗口会黑屏并等待GDB连接。

7. 使用 GDB 连接并调试

打开另一个终端标签页,进入你的内核源码目录。

  1. 启动 GDB,并加载带有调试信息的 vmlinux 文件:
1
2
cd linux
gdb vmlinux
  1. 在 GDB 中连接目标:
1
2
(gdb) break start_kernel
(gdb) continue
  • target remote :1234:连接到正在等待的 QEMU 实例。
  • break start_kernel:在内核启动的早期阶段设置一个断点。
  • continue:让被 -S 冻结的虚拟机继续运行,它很快就会在 start_kernel 处停下。

开始调试:
现在你可以像调试普通程序一样使用 GDB 命令了:

  • next (n): 单步跳过
  • step (s): 单步进入
  • break : 设置断点
  • list: 查看源代码
  • print : 打印变量值
  • continue (c): 继续运行
  • backtrace (bt): 查看调用栈

clangd

为 Linux 内核生成 compile_commands.json:

1
make compile_commands.json

​​生成 Clang 兼容的编译数据库​​:

使用 scripts/gen_compile_commands.py(内核自带脚本)生成时,它会自动过滤掉 Clang 不支持的选项:

1
make CC=clang compile_commands.json