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 导出所有内核符号表,辅助符号解析

配置步骤:

  1. 进入内核源码根目录,执行 make menuconfig 或 make x86_64_defconfig。
  2. 依次进入 Kernel hacking → Compile-time checks and compiler options,勾选上述选项。
  3. 保存退出后,执行 make -j$(nproc) 编译。
验证符号生成:编译完成后,检查 vmlinux 文件是否包含调试段。
readelf -S vmlinux | grep debug
# 应输出类似 .debug_info、.debug_abbrev、.debug_line 等段
若没有这些段,说明 CONFIG_DEBUG_INFO 未生效,请检查 .config 文件。
内核编译配置流程 获取内核源码 make menuconfig 开启 DEBUG_INFO 关闭 KASLR make -j$(nproc) 验证 vmlinux 调试段

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 脚本支持不完整,可能导致断点失效或变量显示异常。
GDB 调试架构示意 开发主机 GDB (本地/交叉) vmlinux + 符号文件 连接方式 QEMU gdbserver / JTAG 目标系统 内核 vmlinux (运行中) /proc/kcore 或内存 关键路径 GDB 通过 gdbserver 协议连接目标 → 加载 vmlinux 符号 → 设置断点 → 读取/写入目标内存

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 是否开启。
调试符号文件结构 vmlinux .text (代码段) .data (数据段) .debug_info (符号) .debug_line (行号) GDB 解析 DWARF 符号查找 调试输出 函数名 + 地址 源码行号 + 文件 局部变量值 堆栈回溯

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),首次加载需等待。可考虑使用 split-dwarf 或只加载必要模块。
• 断点无法命中:检查 KASLR 是否关闭,或使用 hbreak(硬件断点)代替 break。
• 源码显示乱码:确保 GDB 的 directory 指向内核源码根目录。