调试 Linux 内核(使用 clang, lldb 工具)
How to debug linux kernel?
views
| comments
环境:Ubuntu 25.10
内核版本:Linux 6.18
安装依赖#
sudo apt-get install git make gcc flex bison libssl-dev libelf-dev \
clang lld llvm qemu-system-x86 python3bash配置与编译#
# 清理旧的构建
make LLVM=1 clean
# 生成默认配置 (x86_64)
make LLVM=1 defconfig
# 使用图形化界面进行一些其他配置
make LLVM=1 menuconfigbash- 保留调试信息:Kernel hacking -> Compile-time checks and compiler options -> Debug information,可以选择 Rely on the toolchain’s implicit default DWARF version”,让工具链决定 DWARF 版本。
- (可选)GDB 辅助脚本:Kernel hacking -> Compile-time checks and compiler options -> 勾选 Provide GDB scripts for kernel debugging
- 关闭 KASLR:Processor type and features -> 取消勾选 Randomize the address of the kernel image (KASLR)
然后编译内核
make LLVM=1 -j$(nproc)bash生成 compile_commands.json(供 clangd 使用)
# 这会在源码根目录生成 compile_commands.json
python3 scripts/clang-tools/gen_compile_commands.pybash启动 QEMU#
# -s: 开启 gdbserver (默认端口 1234)
# -S: 启动时冻结 CPU,直到调试器输入 'c'
# -nographic: 在终端显示输出
qemu-system-x86_64 \
-kernel arch/x86/boot/bzImage \
-append "console=ttyS0 root=/dev/sda earlyprintk=serial" \
-nographic \
-s -Sbash注意:这里没有挂载 rootfs,内核启动到最后会 panic,但足够调试内核启动过程。如果你有 rootfs 镜像,请通过 -hda 或 -initrd 加上。
配置 VSCode#
首先 VSCode 要打开远程 linux 源代码的根目录,然后配置 .vscode/launch.json。
{
"version": "0.2.0",
"configurations": [
{
"type": "lldb",
"request": "launch",
"name": "Debug Linux Kernel (Remote)",
"targetCreateCommands": [
// 加载带有符号表的 vmlinux
"target create ${workspaceFolder}/vmlinux"
],
"processCreateCommands": [
// 连接到 QEMU 的 GDB Stub
"gdb-remote localhost:1234"
],
// 解决源码映射问题(如果在不同机器或容器编译,需要调整这里)
// 如果是在本机编译,通常不需要
// "sourceMap": {
// "/proc/self/cwd": "${workspaceFolder}"
// }
}
]
}json然后直接在 init/main.c 文件下的 start_kernel 函数打个断点,启动调试,它就会停在梦开始的地方了。
到此为止,我们已经可以调试整个 linux 的启动过程了。
(可选)如何让内核成功启动#
构建 initramfs 目录结构#
在你想要的目录下构建即可
mkdir -p my_initramfs/{bin,sbin,etc,proc,sys,usr/bin,usr/sbin,dev}bash安装 BusyBox 的静态可执行文件#
BusyBox 被称为“嵌入式 Linux 的瑞士军刀”,它能为你提供 sh, ls, cat 等基本命令。
可以直接下载编译好的静态可执行文件,或者下载源码自己编译出 busybox 的二进制文件。
然后将 busybox 静态可执行文件放入上面创建的 my_initramfs/bin 文件夹里面即可。
编写 init 文件#
内核启动之后会在根目录下找 init 可执行文件然后执行,所以 init 可以是 elf 也可以是 shell 脚本。下面示例的是 init 为脚本的情况。
touch my_initramfs/init
chmod +x my_initramfs/initbash编辑 init 内容如下
#!/bin/busybox sh
# 挂载必要的核心文件系统
/bin/busybox mount -t proc none /proc
/bin/busybox mount -t sysfs none /sys
/bin/busybox mount -t devtmpfs devtmpfs /dev
# 自动创建所有软链接(给常用的工具创建软链接,比如 ls, ps 之类的)
# 不然每次都需要在命令前加 busybox 前缀
/bin/busybox --install -s /bin
# 友好提示语
echo "---------------------------------------"
echo " Welcome to your Debug Linux Kernel! "
echo "---------------------------------------"
# 启动一个交互式 Shell,并解决 tty 访问问题
exec setsid cttyhack shbash打包为 cpio 镜像#
cd my_initramfs
find . | cpio -o -H newc | gzip > ../initramfs.cpio.gzbash启动 QEMU#
# 这里的 rdinit=/init 就是指内核一进去要运行哪个可执行文件,这里是 /init,即根目录下的 init 文件。
qemu-system-x86_64 \
-kernel arch/x86/boot/bzImage \
-initrd initramfs.cpio.gz \
-append "console=ttyS0 rdinit=/init nokaslr" \
-nographic \
-s -Sbash到这里我们发现,其实不用安装 busybox 也可以,只要我们的 rdinit 指向一个静态可执行文件即可,可以是我们自己的可执行文件。