AI智能摘要
调试Linux内核曾是令人头痛的难关,传统方法依赖命令行log与GDB,效率低下且过程繁琐。现在,结合QEMU系统模拟与VSCode图形化调试,开发者可轻松搭建现代Linux内核调试环境:全程支持源码级断点、变量实时查看和调用栈分析,大幅提升定位问题的直观性和效率。文章详细梳理从WSL环境准备、内核和BusyBox源码获取与编译、根文件系统和虚拟硬盘制作,到VSCode与GDB的精确联调配置,逐步消除复杂性壁垒。全流程适配跨平台开发需求,并针对构建中遇到的实际问题给出解决思路。通过本文指引,开发者不仅能简化内核调试流程,还能享受可视化带来的高效与便捷,为深入探索系统底层保驾护航。
此摘要由AI分析文章内容生成,仅供参考。

前言

在 Linux 内核开发的征途上,调试往往被视为一道令人望而生畏的关卡。回顾过往,我们习惯了在充斥着复杂硬件依赖的环境中挣扎:要么面对着只有串口输出的“黑屏白字”,通过满屏滚动的 printk 日志大海捞针,试图从无数无关信息中捕捉那一丝错误的踪迹;要么只能在纯命令行界面的 GDB 中,机械地输入 listprint,在缺乏直观代码上下文的窘境下,凭借想象力去构建程序的执行路径。

这种传统的调试方式,不仅对开发者的经验与耐心提出了极高的要求,更因其低下的效率而成为技术探索的阻碍。每一次定位问题,都像是一场与未知的搏斗,繁琐且充满不确定性。

幸运的是,技术的进步为我们带来了新的可能。本文将带你跨越这一鸿沟,利用 QEMU 的系统模拟能力与 VSCode 强大的图形化调试界面,搭建一套现代化的 Linux 内核调试环境。我们将把那个只能在终端里敲命令的“远古时代”抛诸脑后,迎来一个支持源码级断点、变量实时查看、一键调用栈分析的图形化调试新纪元。

准备工作

这部分如果宿主机是windows系统,如果没有linux服务器作为内核编译的环境,我个人建议使用windows子系统WSL功能,安装可以百度进行。很方便也方便迁移。

只需要把这个文件拷贝到其他电脑,就可以完成迁移。

linux内核编译

安装linux内核编译工具

sudo apt-get install **gcc-8-aarch64-linux-gnu build-essential libncurses5-dev gdb-multiarch qemu-system-arm**
sudo apt-get install flex
sudo apt-get install bison
sudo apt-get install bc
sudo apt install libssl-dev

sudo apt install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu

下载linux内核源码

wget https://mirrors.tuna.tsinghua.edu.cn/kernel/v6.x/linux-6.1.80.tar.xz

备注:源码的来源网络上很多,也可以在 https://www.kernel.org/ 下载

编译linux内核

make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- O=out/ LLVM=1 defconfig

bear -- make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- O=out/ LLVM=1 -j32

注意:演示指令中bear -- 是为了生compile_commands.json ,这个文件是为了在vscode中通clangd 插件实现内核函数跳转

制作根文件系统

下载busybox

wget https://busybox.net/downloads/busybox-1.36.1.tar.bz2
tar -xvf busybox-1.36.1.tar.bz2
cd busybox-1.36.1

开启静态binary

make menuconfig

Settings ---->
    [*] Buidld Static binary (no shared libs)

编译busybox

sudo apt install gcc-arm-linux-gnueabi

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- -j32
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- CONFIG_PREFIX=../rootfs/arm64/ install

如果出现以下报错:

compilation
of tc.c fails with: 

networking/tc.c: In function ‘cbq_print_opt’:
networking/tc.c:236:27: error: ‘TCA_CBQ_MAX’ undeclared (first use in this
function); did you mean ‘TCA_CBS_MAX’?
  236 |         struct rtattr *tb[TCA_CBQ_MAX+1];
      |                           ^~~~~~~~~~~
      |                           TCA_CBS_MAX

解决方法

把tc.c从networking文件夹里移出去即可。这是一个bug。见[Bug 15934] New: Busybox fails to build with linux kernels >= 6.8

制作虚拟硬盘

mkdir virtdisk
cd virtdisk
dd if=/dev/zero of=rootfs_ext4.img bs=1M count=1024
mkfs.ext4 rootfs_ext4.img
mkdir tmpfs
sudo mount -t ext4 rootfs_ext4.img tmpfs/ -o loop

创建一些文件

  1. 创建fstab

ubuntu@sh-liuqiN:~/virtual_linux/virtdisk/tmpfs/etc$ ls
fstab  init.d  inittab  profile
ubuntu@sh-liuqiN:~/virtual_linux/virtdisk/tmpfs/etc$ cat fstab

proc /proc proc defaults 0 0
tmpfs /tmp tmpfs defaults 0 0
sysfs /sys sysfs defaults 0 0
tmpfs /dev tmpfs defaults 0 0
debugfs /sys/kernel/debug debugfs defaults 0 0

kmod_mount /mnt 9p trans-virtio 0 0
  1. 创建inittab

ubuntu@sh-liuqiN:~/virtual_linux/virtdisk/tmpfs/etc$ cat inittab 
::sysinit:/etc/init.d/rcS
::respawn:-/bin/sh

::askfirst:-/bin/sh
  1. 创建profile

ubuntu@sh-liuqiN:~/virtual_linux/virtdisk/tmpfs/etc$ cat profile 
export HOSTNAME=virt-machine
export USER=root
export HOME=/home
export PS1="[$USER@$HOSTNAME \W]\#"
PATH=/bin:/sbin:/usr/bin:/usr/sbin
LD_LIBRARY_PATH=/lib:/usr/lib:$LD_LIBRARY_PATH
export PATH LD_LIBRARY_PATH
  1. 创建rcS

sudo mkdir -p /etc/init.d
touch /etc/init.d/rcS

ubuntu@sh-liuqiN:~/virtual_linux/virtdisk/tmpfs/etc$ cd init.d/
ubuntu@sh-liuqiN:~/virtual_linux/virtdisk/tmpfs/etc/init.d$ ls
rcS
ubuntu@sh-liuqiN:~/virtual_linux/virtdisk/tmpfs/etc/init.d$ cat rcS
mkdir -p /sys
mkdir -p /tmp
mkdir -p /proc
mkdir -p /mnt


/bin/mount -a

mkdir -p /dev/pts
mount -t devpts devpts /dev/pts

echo /sbin/mdev > /proc/sys/kernel/hotplug
mdev -s

vscode配置

配置launch.json

VSCode 将作为 GDB 的前端界面。

  1. 安装插件:安装 C/C++ 扩展(由 Microsoft 提供)。

  2. 创建配置文件:在 VSCode 中打开内核源码文件夹,按下 F5 或点击“运行和调试”,选择 C++ (GDB/LLDB),创建 launch.json 文件。

  3. 配置内容

    • program: 指向你的内核 ELF 文件(通常是源码根目录下的 vmlinux,不是 bzImage)。

    • miDebuggerPath: 指向你的 GDB 路径(如 /usr/bin/gdb/usr/bin/gdb-multiarch)。

    • miDebuggerServerAddress: 填写 localhost:1234

示例 launch.json

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "kernel debug",
            "type": "cppdbg",
            "request": "launch",
            "program": "/home/ubuntu/virtual_linux/linux-5.15/out/vmlinux", // 例如 /home/user/linux/vmlinux
            "miDebuggerServerAddress": "127.0.0.1:1234",
            "args": [],
            "stopAtEntry": false,
            "cwd": "${workspaceFolder}",
            "environment": [],
            "externalConsole": false,
            "MIMode": "gdb",
            "miDebuggerPath": "/usr/bin/gdb-multiarch",
        }
    ]
}

配置QEMU启动参数

通过命令行启动 QEMU,并让它暂停运行,等待 GDB 连接。

  • 关键参数说明

    • -s: 这是一个快捷参数,相当于 -gdb tcp::1234,让 QEMU 在 1234 端口开启 GDB Server。

    • -S: 冻结 CPU,直到 GDB 连接并发出“继续”指令。这对于调试内核启动过程非常有用。

    • nokaslr: 在内核启动参数(-append)中加入此选项,关闭地址空间布局随机化,防止符号地址每次启动都变,导致断点失效。

示例启动脚本 run_qemu.sh

# 虚拟linux和宿主机之间的文件传输靠这个
SHARE_DIR=./share

# 切不同的内核,只需要改这个
#KERNEL_IMAGE=/home/ubuntu/virtual_linux/linux-6.1.80/out/arch/arm64/boot/Image
KERNEL_IMAGE=/home/ubuntu/virtual_linux/linux-5.15/out/arch/arm64/boot/Image

qemu-system-aarch64 \
        -machine virt,gic-version=3  \
        -cpu cortex-a57 \
        -display none \
        -nographic \
        -m 1024 \
        -smp 4 \
        -kernel $KERNEL_IMAGE \
        --append "noinitrd root=/dev/vda rw console=ttyAMA0 loglevel=8 nokaslr" \
        -drive if=none,file=./virtdisk/rootfs_ext4.img,id=hd0 \
        -device virtio-blk-device,drive=hd0 \
        --fsdev local,id=kmod_dev,path=$SHARE_DIR,security_model=none \
        -device virtio-9p-device,fsdev=kmod_dev,mount_tag=kmod_mount \
        -s -S

启动调试

  1. 首先运行 run_qemu.sh

此时,命令行终端会卡在此处,等待GDB连接

  1. 进入linux内核,按下F5进入调试模式

注意:如果没有设置断点,当启动调试模式时,虚拟linux将会立即启动并完成开机,命令行终端将立即输出日志。

  1. 设置断点

设置函数跳转

利用vscode的 clangd 插件以及 compile_commands.json 完成函数跳转功能

  1. 下载并安装插件

插件安装成功后,会在右下角弹出安装clangd工具(如果服务器中没有安装)

  1. 设置与C/C++插件的冲突解决

因为我们需要利用C/C++插件来进行内核调试,又得利用clangd插件进行函数跳转,但是两者又同时开启又会有冲突!所以使用下面的方式可以解决冲突。

新建 .vscode/settings.json 和前面的 launch.json 同目录

{
    "clangd.path": "clangd",
    "clangd.arguments": [
        "--compile-commands-dir=${workspaceFolder}",
        "--background-index",
        "--completion-style=detailed",
        "--header-insertion=never",
        "--query-driver=aarch64-linux-gnu-gcc"
    ],
    "C_Cpp.intelliSenseEngine": "disabled",
    "editor.suggest.snippetsPreventQuickSuggestions": false
}
  1. 重启vscode,等待下一次启动时进行索引

索引结束后,我们就可以通过 ctrl + 鼠标左键,进行函数跳转了

总结

总而言之,掌握 QEMU 与 VSCode 的协同调试,意味着你拥有了一个强大而灵活的“内核显微镜”。希望这篇指南能帮助你顺利搭建起属于自己的调试环境,开启一段更高效、更愉悦的 Linux 内核开发与学习之旅。

我也建议我们在学习Linux内核的时候,要善于利用这种方式,做一些实验来验证猜想。比如我们在学习开源社区遇到的bug以及bufix时,就可以利用这种方式快速复现,以及快速验证bugfix,非常好用!