2. 调试环境搭建:编译内核开启调试选项、安装GDB与交叉工具链、准备调试符号
调试环境是内核调试的基石。一个正确配置的环境能让你在断点命中时看到完整的变量、堆栈和源码,而不是一堆乱码或“??”。本章从内核编译配置、工具链安装到符号文件准备,逐步搭建一套可复现的调试环境。
环境概览:本课程以 x86_64 架构 + QEMU 模拟器 + GDB 12.1 为默认调试平台。若使用 ARM64 或 RISC-V,仅交叉工具链前缀不同,其余步骤完全一致。
2.1 编译内核:开启调试相关配置
内核的调试信息由 CONFIG_DEBUG_INFO 系列选项控制。以下配置确保生成完整的 DWARF 调试符号,并关闭可能干扰调试的优化或随机化。
| 配置项 | 推荐值 | 作用说明 |
|---|---|---|
CONFIG_DEBUG_INFO |
y | 生成 DWARF 格式调试信息,是符号文件的基础 |
CONFIG_DEBUG_INFO_DWARF5 |
y | 使用 DWARF 5 标准(GDB 12+ 支持更好) |
CONFIG_GDB_SCRIPTS |
y | 安装内核专用的 GDB 辅助脚本(lx-* 命令) |
CONFIG_RANDOMIZE_BASE |
n | 关闭 KASLR,确保符号地址与运行时地址一致 |
CONFIG_FRAME_POINTER |
y | 保留帧指针寄存器,提升回溯准确性 |
CONFIG_KALLSYMS |
y | 导出所有内核符号表,辅助符号解析 |
配置步骤:
- 进入内核源码根目录,执行
make menuconfig或make x86_64_defconfig。 - 依次进入 Kernel hacking → Compile-time checks and compiler options,勾选上述选项。
- 保存退出后,执行
make -j$(nproc)编译。
验证符号生成:编译完成后,检查 vmlinux 文件是否包含调试段。
readelf -S vmlinux | grep debug
# 应输出类似 .debug_info、.debug_abbrev、.debug_line 等段
若没有这些段,说明 CONFIG_DEBUG_INFO 未生效,请检查 .config 文件。
2.2 安装 GDB 与交叉工具链
调试内核需要 GDB 支持目标架构的指令集。对于 x86_64 本地调试,系统自带的 GDB 即可;若调试 ARM64 或 RISC-V 内核,则必须使用交叉编译版本的 GDB。
2.2.1 本地调试(x86_64)
# Ubuntu / Debian
sudo apt install gdb
# Fedora / RHEL
sudo dnf install gdb
# 验证版本
gdb --version | head -1
# 输出示例:GNU gdb (Ubuntu 12.1-0ubuntu1~22.04) 12.1
2.2.2 交叉调试(ARM64 为例)
# 安装交叉工具链(包含 aarch64-linux-gnu-gdb)
sudo apt install gdb-multiarch # 或 gdb-aarch64-linux-gnu
# 验证
aarch64-linux-gnu-gdb --version | head -1
| 目标架构 | 交叉 GDB 包名 | 目标文件前缀 |
|---|---|---|
| x86_64 | gdb(系统自带) | 无 |
| ARM64 | gdb-multiarch 或 gdb-aarch64-linux-gnu | aarch64-linux-gnu- |
| RISC-V 64 | gdb-multiarch 或 gdb-riscv64-linux-gnu | riscv64-linux-gnu- |
版本要求:GDB 版本建议 ≥ 10.1,推荐 12.1 或更高。低版本 GDB 对 DWARF 5 和内核 GDB 脚本支持不完整,可能导致断点失效或变量显示异常。
2.3 准备调试符号文件
调试符号文件是 GDB 将内存地址映射为函数名、变量名和源码行号的关键。内核调试涉及两类符号文件:
- vmlinux:未压缩的内核映像,包含完整调试信息。位于内核编译目录根下。
- 内核模块 .ko 文件:每个内核模块独立编译,调试符号内嵌在 .ko 文件中。
2.3.1 分离调试符号(可选但推荐)
将调试符号从 vmlinux 中剥离出来,减小部署文件体积,同时保留调试能力。
# 从 vmlinux 中提取调试符号
objcopy --only-keep-debug vmlinux vmlinux.debug
# 剥离后的内核(无调试信息,用于运行)
objcopy --strip-debug vmlinux vmlinux.stripped
# 建立符号链接(GDB 自动查找)
objcopy --add-gnu-debuglink=vmlinux.debug vmlinux.stripped
2.3.2 加载符号到 GDB
# 启动 GDB 并加载 vmlinux
gdb vmlinux
# 或在 GDB 内部手动加载
(gdb) symbol-file /path/to/vmlinux
# 加载内核模块符号(模块运行时)
(gdb) add-symbol-file /path/to/module.ko 0xffffffffa0000000
验证符号加载:在 GDB 中执行以下命令,确认符号可用。
(gdb) info functions start_kernel
# 应输出:start_kernel 的地址和源码位置
(gdb) list start_kernel
# 应显示 start_kernel 函数的源码上下文
若输出“No symbol table is loaded”,请检查 symbol-file 路径或 CONFIG_DEBUG_INFO 是否开启。
2.4 环境验证清单
完成上述步骤后,执行以下快速验证,确保环境就绪:
| 检查项 | 验证命令 | 预期结果 |
|---|---|---|
| 内核调试符号 | readelf -S vmlinux | grep debug_info |
输出包含 .debug_info 段 |
| GDB 版本 | gdb --version |
版本 ≥ 10.1 |
| GDB 加载符号 | gdb -batch -ex "info functions start_kernel" vmlinux |
输出 start_kernel 地址 |
| 内核 GDB 脚本 | gdb -batch -ex "lx-version" vmlinux |
输出内核版本字符串 |
常见问题:
• 符号加载慢:vmlinux 调试信息较大(几百 MB),首次加载需等待。可考虑使用
• 断点无法命中:检查 KASLR 是否关闭,或使用
• 源码显示乱码:确保 GDB 的
• 符号加载慢:vmlinux 调试信息较大(几百 MB),首次加载需等待。可考虑使用
split-dwarf 或只加载必要模块。• 断点无法命中:检查 KASLR 是否关闭,或使用
hbreak(硬件断点)代替 break。• 源码显示乱码:确保 GDB 的
directory 指向内核源码根目录。