(信息量有点大)基于RK3576深入解读kernel-6.1/System.map:内核开发调试的“地址-功能”导航图 电子说
在 Linux 内核开发与调试场景中,你是否遇到过这些困惑?内核 panic 时打印的 pc: ffffffc00801c400 究竟对应哪个函数?编写模块时引用的foo 符号为何提示“未定义”?优化内核时如何判断某个功能是否被编译进去?
答案都藏在kernel-6.1/System.map 中—— 它不是内核代码,却是连接 “机器地址” 与 “人类可读功能” 的核心桥梁,是 kernel 开发者的 “调试字典” 与 “开发指南针”。
本文将从以下 5 个维度,带你吃透 kernel-6.1 System.map 的价值,让内核开发调试效率翻倍:
1.本质定位:System.map 是什么?kernel-6.1 中的结构如何解读?
2.核心知识点:符号类型、地址空间、内核段关联的底层逻辑(附 kernel-6.1 实例)
3.调试实战:查看该文件能解决哪些痛点?(Oops 定位、栈回溯等案例)
4.开发意义:对模块编写、内核裁剪、版本兼容的实际帮助
5.流程可视化:用流程图梳理实战场景(崩溃调试、模块开发)
kernel-6.1/System.map 是内核编译过程中由链接器ld 生成的符号表文件,核心作用是将内核运行时的“虚拟地址” 与 “可读符号(函数 / 变量)” 建立映射。
它就像内核的“身份证系统”—— 每个符号(如函数 die、变量jiffies_64)都有唯一的“地址身份证”,开发者通过地址查符号,就能快速定位功能归属。
•默认路径:kernel-6.1 编译后,默认存放在kernel-6.1/System.map;
•核心价值:
◦破解“地址黑盒”:将 Oops/panic 打印的虚拟地址(如 ffffffc00801c400)翻译成可读符号(如die);
◦验证符号有效性:判断模块引用的符号是否存在、是否可导出(如T 类型符号可被外部调用);
◦反推内核配置:通过符号是否存在,判断功能是否编译(如smp_send_reschedule 存在→ 开启 SMP)。
System.map 的每一行都遵循固定格式,以 kernel-6.1 中 ffffffc00801c400 T die 为例,拆解为 3 个关键字段:
|
字段
|
示例值
|
说明(结合 kernel-6.1)
|
|
虚拟地址
|
ffffffc00801c400
|
符号在内存中的虚拟地址(ARM64 内核地址多以 ffffffc0 开头,用户空间地址以00000000 开头)
|
|
符号类型
|
T
|
区分符号属性:大写为全局符号(可被外部模块引用),小写为局部符号(仅内核内部使用)
|
|
符号名
|
die
|
可读符号名(die 是 kernel-6.1 中内核崩溃的核心处理函数,定义在 kernel/exit.c)
|
符号类型直接反映符号的“归属段”(代码段 / 数据段)和 “可见性”,kernel-6.1 中高频出现的类型如下:
|
符号类型
|
含义
|
kernel-6.1 实例
|
对应内核段
|
|
T
|
全局代码段符号(可导出)
|
T _text(内核代码段起始地址)
|
.text(代码段)
|
|
t
|
局部代码段符号(仅内部使用)
|
t __bad_stack(异常栈处理函数)
|
.text(代码段)
|
|
A
|
绝对符号(地址编译时固定)
|
A PECOFF_FILE_ALIGNMENT(PECOFF 对齐值)
|
绝对段
|
|
W
|
弱符号(可被重定义)
|
W calibrate_delay_is_known(延迟校准标志)
|
.data(数据段)
|
|
B
|
全局数据段符号(已初始化)
|
B jiffies_64(系统滴答计数器)
|
.data(数据段)
|
看懂System.map 不只是“查地址”,更要通过符号反推 kernel-6.1 的内存布局、功能模块、配置状态—— 这才是它的深层价值。
kernel-6.1 的内存被划分为多个 “功能段”,符号类型 + 地址范围可直接判断归属,帮你快速定位功能场景:
•代码段(.text):T/t 类型符号的聚集地,存放内核所有执行函数,例如:
◦T _text(ffffffc008000000):kernel-6.1 代码段起始地址,内核启动后第一个执行的代码段;
◦T __irqentry_text_start ~ T __irqentry_text_end:中断入口代码段,包含gic_handle_irq(ARM64 GIC 中断处理函数);
◦T vectors(ffffffc008010800):ARM64 异常向量表,是内核处理中断、系统调用、异常的 “入口网关”。

•数据段(.data):W/B 类型符号所在,存放已初始化的全局变量,例如:
◦W calibration_delay_done:延迟校准完成标志,内核启动时用于判断是否跳过校准流程;
◦B jiffies_64:系统滴答计数器,记录内核运行时间,是定时器、调度的核心变量。
•绝对段:A 类型符号,地址编译时固定,不随内存布局变化,例如A _kernel_size_le_lo32(kernel-6.1 内核大小低 32 位)。
kernel-6.1(ARM64 架构)的符号地址主要分两类,对应 Linux 内核的 “地址空间隔离” 设计:
1.内核虚拟地址(ffffffc0 开头):
◦示例:ffffffc00801c400 T die(崩溃处理)、ffffffc008010628 T __entry_text_start(系统调用入口段起始);
◦特点:与用户空间地址(00000000~ffff0000)完全隔离,保障内核安全性,仅内核态可访问。
1.早期 / 特定段地址(00000000 开头):
◦示例:00000000 A _kernel_flags_le_hi32(内核标志高 32 位)、00000000 A __pecoff_data_rawsize(PECOFF 数据原始大小);
◦特点:地址编译时固定,多用于内核早期启动(如 EFI stub 初始化)或文件格式相关(PECOFF 是 Windows 可执行文件格式,内核用于兼容引导)。
kernel-6.1 的符号名遵循 “功能前缀” 规则,通过符号名可直接对应内核子系统,减少查源码的时间:
|
内核子系统
|
符号名前缀 / 关键词
|
kernel-6.1 实例
|
功能说明
|
|
中断处理
|
gic_、irq_、do_undef
|
t gic_handle_irq、T do_undefinstr
|
GIC 中断处理、未定义指令异常处理
|
|
进程调度
|
sched_、cpu_switch
|
T cpu_switch_to、t pick_next_task_fair
|
进程切换、CFS 调度器选任务
|
|
内存管理
|
pgd_、do_page_fault
|
T pgd_alloc、t do_page_fault
|
页表分配、页错误处理
|
|
系统调用
|
__arm64_sys_
|
T __arm64_sys_mmap、T __arm64_sys_exit
|
ARM64 架构的 mmap/exit 系统调用
|
|
EFI 引导
|
__efistub_、efi_
|
A __efistub_primary_entry_offset
|
EFI stub 启动入口偏移量
|
kernel-6.1 中某个符号是否存在,直接反映内核编译时的配置(.config):
•若存在T smp_send_reschedule → 开启 CONFIG_SMP(对称多处理器);
•若存在T __arm64_sys_fanotify_init → 开启 CONFIG_FANOTIFY(文件系统事件通知);
•若不存在t has_no_fpsimd → 开启 CONFIG_FPSIMD(ARM64 浮点 / 向量支持)。
这对内核裁剪优化非常有用:若不需要 SMP 功能,编译时关闭 CONFIG_SMP,smp_send_reschedule 等符号会消失,减少内核体积。
kernel-6.1 调试中,System.map 是“效率工具”—— 没有它,你可能需要花几小时猜地址;有了它,几分钟就能定位问题。
内核 Oops 是最常见的调试场景,例如打印:
Oops: 0000000000000005 [PC is at ffffffc00801c400
此时查System.map 即可快速定位:
1.打开kernel-6.1/System.map,搜索ffffffc00801c400;
2.找到对应行:ffffffc00801c400 T die → 确认崩溃发生在 die 函数;
3.查看die 源码(kernel/exit.c),结合 Oops 上下文(如寄存器值、调用链),判断是 “空指针访问” 还是 “非法内存地址”。

流程图:

当内核 panic 打印栈回溯(Backtrace)时,会输出一串函数地址,例如:
Backtrace:ffffffc00801c400 → ffffffc00801c680 → ffffffc00801d8e0
通过System.map 翻译地址:
•ffffffc00801c400 → die(崩溃处理);
•ffffffc00801c680 → arm64_force_sig_fault(强制发送信号);
•ffffffc00801d8e0 → do_serror(系统错误处理)。
瞬间补全调用链:do_serror → arm64_force_sig_fault → die,快速定位错误传播路径。

编写 kernel-6.1 内核模块时,若引用 foo 函数却提示“undefined reference to foo”,可通过 System.map 排查:
1.搜索foo 符号:
◦若不存在→ 内核未编译 foo 对应的功能,需开启相关配置(如CONFIG_FOO=y);
◦若存在但类型为t → foo 是局部符号(仅内核内部使用),无法被模块引用,需修改内核源码将foo 导出(添加EXPORT_SYMBOL(foo));
◦若存在且类型为T → foo 是全局符号,模块中声明extern int foo(); 即可正常编译。
用perf record -g 采样内核性能时,结果会包含大量地址,例如:
Samples: 100 of event 'cycles', Event count (approx.): 123456ffffffc0080164a4 → 20%ffffffc0080165d0 → 15%
通过System.map 翻译:
•ffffffc0080164a4 T cpu_switch_to(进程切换);
•ffffffc0080165d0 T fpsimd_thread_switch(浮点上下文切换)。

可快速判断性能瓶颈在“进程切换”,进而优化调度策略。
kernel-6.1/System.map 不只是调试工具,更是 kernel 开发的 “正确性保障” 和 “效率加速器”。
没有System.map 时,调试需通过addr2line 工具(需带调试信息的内核镜像vmlinux),且依赖内核编译时保留调试符号;有了System.map,直接查地址→符号,无需额外工具,尤其适合无调试信息的 release 版本内核。
kernel-6.1 模块开发中,符号引用错误是常见问题(如引用不存在的符号、引用局部符号)。System.map 可提前验证符号有效性,避免模块加载时因“符号未定义” 被内核拒绝(insmod: ERROR: could not insert module xxx.ko: Unknown symbol in module)。
模块开发流程图:

kernel-6.1 支持按需裁剪功能,System.map 可验证裁剪效果:
•若不需要 EFI 引导,关闭 CONFIG_EFI 后,__efistub_ 前缀的符号会消失,说明裁剪成功;
•若不需要浮点支持,关闭CONFIG_FPSIMD 后,fpsimd_ 前缀的符号会消失,减少内核体积。
不同内核版本(如 kernel-6.1 与 kernel-6.2)的符号地址可能变化,若模块硬编码地址,会导致加载失败。通过对比 System.map:
•若foo 符号在 kernel-6.1 中地址为 ffffffc0080164a4,在 kernel-6.2 中为 ffffffc008016500,说明地址偏移,需修改模块为“符号引用” 而非 “地址硬编码”。
kernel-6.1/System.map 看似是简单的“地址 - 符号” 列表,实则是内核的 “功能导航图”—— 它连接了机器可识别的 “地址” 与人类可理解的 “功能”,解决了调试中的 “地址黑盒” 问题,保障了开发中的 “符号正确性”。
无论是内核崩溃定位、模块开发,还是性能优化、版本兼容,System.map 都能帮你从“盲目试错” 转向 “精准操作”,是 linux 开发者必须掌握的核心工具。
下次遇到内核问题时,先打开System.map—— 它或许能帮你省下几小时的调试时间。
全部0条评论
快来发表一下你的评论吧 !