4. 内核调试符号与配置:开启调试选项与生成关键文件
内核调试的第一步是让内核在编译时携带足够的符号与追踪信息。没有这些信息,gdb、ftrace、perf 等工具将无法将地址解析为函数名、变量名或行号。本章详细讲解必须开启的内核配置项,以及编译后生成的 vmlinux 与 System.map 的作用与获取方法。
4.1 核心调试配置项一览
以下配置项是内核调试的基石,必须在 make menuconfig 或直接编辑 .config 时开启:
| 配置项 | 功能 | 调试场景 |
|---|---|---|
CONFIG_DEBUG_INFO |
生成 DWARF 格式的调试符号(.debug_* 段) | gdb/kgdb、crash、addr2line |
CONFIG_KALLSYMS |
导出内核符号表,支持地址到符号的解析 | ftrace、perf、dmesg 栈回溯 |
CONFIG_KALLSYMS_ALL |
额外导出静态函数与局部符号 | 深度 ftrace 追踪、内核函数级调试 |
CONFIG_FTRACE |
开启内核内置追踪框架 | 函数调用追踪、延迟分析、事件追踪 |
CONFIG_FUNCTION_TRACER |
启用函数级追踪(mcount/fentry) | ftrace function tracer |
CONFIG_DEBUG_KERNEL |
内核调试主开关,依赖项自动选择 | 所有调试功能的父选项 |
提示: 生产环境通常关闭
CONFIG_DEBUG_INFO 以减小内核体积;调试环境建议开启 CONFIG_DEBUG_INFO_REDUCED 作为折中方案,它只保留部分符号,体积减少约 50%。
4.2 配置操作流程
- 进入配置界面:
cd /path/to/linux-source make menuconfig - 依次开启以下路径:
Kernel hacking → Compile-time checks and compiler options → Compile the kernel with debug info(选中CONFIG_DEBUG_INFO)Kernel hacking → Kernel symbol table → Export kernel symbols(选中CONFIG_KALLSYMS与CONFIG_KALLSYMS_ALL)Kernel hacking → Tracers → Enable ftrace framework(选中CONFIG_FTRACE及CONFIG_FUNCTION_TRACER)
- 验证配置:
输出应全部为grep -E "DEBUG_INFO|KALLSYMS|FTRACE" .config=y,例如:CONFIG_DEBUG_INFO=y CONFIG_KALLSYMS=y CONFIG_KALLSYMS_ALL=y CONFIG_FTRACE=y CONFIG_FUNCTION_TRACER=y
注意: 如果使用模块化编译(
=m),CONFIG_DEBUG_INFO 对模块同样生效,但 CONFIG_KALLSYMS 必须为 =y 才能在内核启动时加载完整符号表。
4.3 编译产物:vmlinux 与 System.map
完成配置并执行 make -j$(nproc) 后,源码根目录下会生成两个关键调试文件:
| 文件 | 类型 | 内容 | 调试用途 |
|---|---|---|---|
vmlinux |
ELF 可执行文件(未压缩) | 包含完整代码、数据、调试符号段(.debug_*) | gdb 加载目标文件、crash 分析、addr2line 反查行号 |
System.map |
纯文本符号表 | 每行格式:地址 类型 符号名 |
手动地址查询、脚本解析、ftrace 过滤函数名 |
关键区别:
vmlinux 包含二进制指令与调试信息,体积较大(通常 500MB+);System.map 仅为文本映射,体积约 5-10MB,适合快速查看符号地址。
4.4 调试符号与符号表的关系(SVG 架构图)
下图展示了从内核源码到调试文件的编译流程,以及 vmlinux 与 System.map 在调试工具链中的位置:
4.5 验证调试符号是否生效
编译完成后,使用以下命令验证调试信息是否完整:
# 检查 vmlinux 是否包含调试段
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
# 检查 System.map 是否包含关键符号
grep " start_kernel" System.map
# 输出示例:ffffffff81000000 T start_kernel
# 使用 addr2line 验证地址解析
addr2line -e vmlinux -f ffffffff81000000
# 输出示例:start_kernel /path/to/linux/init/main.c:892
实用技巧: 如果
vmlinux 体积过大(超过 1GB),可以使用 objcopy --only-keep-debug vmlinux vmlinux.debug 剥离调试符号到单独文件,运行时通过 gdb -s vmlinux.debug -e vmlinux 加载。
4.6 常见问题与排查
| 问题现象 | 原因 | 解决方案 |
|---|---|---|
readelf -S vmlinux 无 .debug_* 段 |
CONFIG_DEBUG_INFO 未开启或编译时被覆盖 |
检查 .config 确认 CONFIG_DEBUG_INFO=y,重新 make clean && make |
System.map 为空或缺失 |
CONFIG_KALLSYMS 未开启 |
开启 CONFIG_KALLSYMS 与 CONFIG_KALLSYMS_ALL 后重新编译 |
| ftrace 无法显示函数名 | 内核运行时未加载 kallsyms 或 /proc/kallsyms 被限制 |
确认 CONFIG_KALLSYMS=y,且 kernel.kptr_restrict=0 |
| 编译时间过长 | CONFIG_DEBUG_INFO 增加大量 DWARF 数据 |
使用 CONFIG_DEBUG_INFO_REDUCED 或 CONFIG_DEBUG_INFO_SPLIT 加速 |
4.7 本章小结
- 必须开启
CONFIG_DEBUG_INFO、CONFIG_KALLSYMS、CONFIG_FTRACE三大配置项,才能获得完整的调试能力。 - vmlinux 是带调试符号的 ELF 文件,供 gdb/crash 等工具使用;System.map 是纯文本符号表,供 ftrace/perf 及脚本快速查询。
- 编译后务必使用
readelf和addr2line验证调试符号是否生效,避免调试时“符号找不到”的困境。 - 生产环境与调试环境的配置应分离,避免将带完整符号的内核部署到线上。