3. 编译支持调试的内核:内核源码获取、配置选项与编译流程
要使用 KGDB 进行内核调试,首先需要构建一个包含调试符号且启用了 KGDB 相关功能的内核镜像。本章将详细讲解从源码获取、关键配置选项到完整编译流程的每一个步骤,确保你能够生成一个可被 QEMU 加载并支持远程 GDB 连接的内核。
3.1 内核源码获取与版本选择
推荐从 kernel.org 获取主线或长期支持(LTS)版本的内核源码。对于 KGDB 与 QEMU 的集成实践,建议选择 5.x 或 6.x 的 LTS 版本,这些版本对 KGDB 和 QEMU 的虚拟化支持最为成熟。
🔍 版本选择建议: 避免使用过于古老(如 2.6.x)或过于前沿的 RC 版本。LTS 版本通常具有更好的稳定性和文档支持。
# 以 Linux 6.1 LTS 为例
wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.1.tar.xz
tar -xf linux-6.1.tar.xz
cd linux-6.1
3.2 关键内核配置选项详解
内核配置是决定调试功能是否可用的核心步骤。以下表格列出了与 KGDB 和 QEMU 调试相关的所有关键选项及其作用。
| 配置选项 | 依赖/位置 | 说明 | 推荐值 |
|---|---|---|---|
CONFIG_DEBUG_INFO |
Kernel hacking → Compile-time checks | 生成 DWARF 格式的调试符号,是 GDB 进行源码级调试的基础 | y |
CONFIG_DEBUG_INFO_DWARF5 |
Kernel hacking → Compile-time checks | 使用 DWARF5 格式(较新工具链支持),提供更丰富的调试信息 | y(可选) |
CONFIG_KGDB |
Kernel hacking → KGDB | 主开关,启用内核调试器核心框架 | y |
CONFIG_KGDB_SERIAL_CONSOLE |
Kernel hacking → KGDB | 允许通过串口(QEMU 的 -serial)进行 KGDB 通信 |
y |
CONFIG_KGDB_KDB |
Kernel hacking → KGDB | 启用 KDB 内核调试器(可选,用于本地串口调试) | n(纯 GDB 模式) |
CONFIG_MAGIC_SYSRQ |
Kernel hacking | 启用魔术键,可通过 SysRq-G 手动进入 KGDB 断点 | y |
CONFIG_DEBUG_KERNEL |
Kernel hacking | 启用内核调试菜单,是许多调试选项的父开关 | y |
CONFIG_FRAME_POINTER |
General setup → Compiler options | 保留帧指针寄存器,使堆栈回溯更可靠 | y |
CONFIG_RANDOMIZE_BASE |
Processor type and features | 内核地址空间随机化(KASLR),会干扰 GDB 符号加载 | n |
⚠️ 关键冲突:
CONFIG_RANDOMIZE_BASE(KASLR)必须关闭。如果启用,内核加载地址每次启动都会变化,导致 GDB 无法正确解析符号地址,断点设置会失败。
3.3 配置流程与自动化脚本
手动通过 menuconfig 逐项查找效率较低。推荐使用以下方法快速生成最小化调试配置。
方法一:基于现有配置修改
# 使用 x86_64 默认配置作为起点
make defconfig
# 通过脚本批量启用调试选项
scripts/config --enable CONFIG_DEBUG_INFO
scripts/config --enable CONFIG_DEBUG_INFO_DWARF5
scripts/config --enable CONFIG_KGDB
scripts/config --enable CONFIG_KGDB_SERIAL_CONSOLE
scripts/config --enable CONFIG_MAGIC_SYSRQ
scripts/config --enable CONFIG_DEBUG_KERNEL
scripts/config --enable CONFIG_FRAME_POINTER
scripts/config --disable CONFIG_RANDOMIZE_BASE
# 验证配置
make olddefconfig
方法二:使用最小化调试配置文件
你也可以直接创建一个 .config 文件,仅包含必要选项。以下是一个经过验证的最小配置片段:
CONFIG_64BIT=y
CONFIG_X86_64=y
CONFIG_SMP=y
CONFIG_NR_CPUS=2
CONFIG_HZ_100=y
CONFIG_SERIAL_8250=y
CONFIG_SERIAL_8250_CONSOLE=y
CONFIG_PRINTK=y
CONFIG_DEBUG_KERNEL=y
CONFIG_DEBUG_INFO=y
CONFIG_DEBUG_INFO_DWARF5=y
CONFIG_FRAME_POINTER=y
CONFIG_KGDB=y
CONFIG_KGDB_SERIAL_CONSOLE=y
CONFIG_MAGIC_SYSRQ=y
# 关闭 KASLR
CONFIG_RANDOMIZE_BASE=n
💡 提示: 使用
make olddefconfig 可以自动补全所有未显式设置的选项为默认值,避免因缺少依赖导致编译失败。
3.4 编译流程与产物验证
配置完成后,执行编译。建议使用 -j 参数加速多核编译。
# 编译内核和模块(使用 4 个并行任务)
make -j4
# 编译完成后,关键产物位于 arch/x86/boot/ 目录
ls -lh arch/x86/boot/bzImage
# 输出示例:-rw-r--r-- 1 user user 8.5M bzImage
# 调试符号文件位于 vmlinux(未压缩的内核镜像)
ls -lh vmlinux
# 输出示例:-rwxr-xr-x 1 user user 580M vmlinux
编译完成后,需要验证调试符号是否完整生成。
# 使用 readelf 检查调试信息段
readelf -S vmlinux | grep debug
# 应输出类似:
# [26] .debug_aranges PROGBITS
# [27] .debug_info PROGBITS
# [28] .debug_abbrev PROGBITS
# [29] .debug_line PROGBITS
# [30] .debug_str PROGBITS
# 使用 nm 检查 KGDB 相关符号
nm vmlinux | grep kgdb
# 应输出大量 kgdb_ 开头的符号,如 kgdb_breakpoint、kgdb_handle_exception
✅ 验证要点:
vmlinux文件大小通常在几百 MB,包含完整符号;bzImage是压缩后的启动镜像,约 8-15 MB。- 如果
readelf未找到.debug_*段,说明CONFIG_DEBUG_INFO未生效,请检查配置。 - 如果
nm未找到kgdb_breakpoint,说明CONFIG_KGDB未正确启用。
3.5 编译产物与调试文件关系
以下 SVG 图展示了内核编译产物的结构以及它们在 QEMU+KGDB 调试中的角色。
3.6 常见编译问题与解决方案
编译过程中可能遇到以下典型问题,提前了解可以节省排错时间。
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
No rule to make target 'debian/certs/signing_key.pem' |
内核模块签名证书缺失 | scripts/config --disable CONFIG_MODULE_SIG 或生成自签名证书 |
undefined reference to 'kgdb_breakpoint' |
KGDB 核心未编译进内核 | 确认 CONFIG_KGDB=y 且未设置为 m(模块) |
vmlinux 文件极小(几 MB) |
CONFIG_DEBUG_INFO 未生效 |
检查 .config 中 CONFIG_DEBUG_INFO 是否为 y,运行 make olddefconfig 补全依赖 |
QEMU 启动后内核 panic |
缺少根文件系统或控制台参数 | 添加 console=ttyS0 内核启动参数,并确保提供 initrd |
💡 最佳实践: 建议在编译完成后,立即使用
file vmlinux 命令检查文件类型。如果输出包含 ELF 64-bit LSB executable, x86-64, with debug_info, not stripped,则说明调试符号已正确嵌入。