(信息量有点大)基于RK3576深入解读kernel-6.1/System.map:内核开发调试的“地址-功能”导航图

电子说

1.4w人已加入

描述

 

 

 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.流程可视化:用流程图梳理实战场景(崩溃调试、模块开发)

 

 

一、System.map 本质:kernel-6.1 的 地址 符号” 映射字典

 

kernel-6.1/System.map 是内核编译过程中由链接器ld 生成的符号表文件,核心作用是将内核运行时的虚拟地址” 与 可读符号(函数 变量)” 建立映射。

 

 

它就像内核的身份证系统”—— 每个符号(如函数 die、变量jiffies_64)都有唯一的地址身份证,开发者通过地址查符号,就能快速定位功能归属。

 

 

1.1 生成路径与核心作用

 

默认路径kernel-6.1 编译后,默认存放在kernel-6.1/System.map

 

 

核心价值

 

 

破解地址黑盒:将 Oops/panic 打印的虚拟地址(如 ffffffc00801c400)翻译成可读符号(如die);

 

 

验证符号有效性:判断模块引用的符号是否存在、是否可导出(如T 类型符号可被外部调用);

 

 

反推内核配置:通过符号是否存在,判断功能是否编译(如smp_send_reschedule 存在→ 开启 SMP)。

 

 

1.2 kernel-6.1 符号结构解析(一行看懂)

 

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

 

 

1.3 kernel-6.1 常见符号类型对照表

 

符号类型直接反映符号的归属段(代码段 数据段)和 可见性kernel-6.1 中高频出现的类型如下:

 

 

符号类型

 

 

含义

 

 

kernel-6.1 实例

 

 

对应内核段

 

 

T

 

 

全局代码段符号(可导出)

 

 

T _text(内核代码段起始地址)

 

 

.text(代码段)

 

 

t

 

 

局部代码段符号(仅内部使用)

 

 

t __bad_stack(异常栈处理函数)

 

 

.text(代码段)

 

 

A

 

 

绝对符号(地址编译时固定)

 

 

A PECOFF_FILE_ALIGNMENTPECOFF 对齐值)

 

 

绝对段

 

 

W

 

 

弱符号(可被重定义)

 

 

W calibrate_delay_is_known(延迟校准标志)

 

 

.data(数据段)

 

 

B

 

 

全局数据段符号(已初始化)

 

 

B jiffies_64(系统滴答计数器)

 

 

.data(数据段)

 

 

二、必须掌握的 4 个核心知识点(结合 kernel-6.1

 

看懂System.map 不只是查地址,更要通过符号反推 kernel-6.1 内存布局、功能模块、配置状态—— 这才是它的深层价值。

 

 

2.1 符号类型 → 内核段归属:快速定位功能区域

 

kernel-6.1 的内存被划分为多个 功能段,符号类型 地址范围可直接判断归属,帮你快速定位功能场景:

 

 

代码段(.textT/t 类型符号的聚集地,存放内核所有执行函数,例如:

 

 

T _textffffffc008000000):kernel-6.1 代码段起始地址,内核启动后第一个执行的代码段;

T __irqentry_text_start ~ T __irqentry_text_end:中断入口代码段,包含gic_handle_irqARM64 GIC 中断处理函数);

 

 

T vectorsffffffc008010800):ARM64 异常向量表,是内核处理中断、系统调用、异常的 入口网关

内核

 

 

数据段(.dataW/B 类型符号所在,存放已初始化的全局变量,例如:

 

 

W calibration_delay_done:延迟校准完成标志,内核启动时用于判断是否跳过校准流程;

 

 

B jiffies_64:系统滴答计数器,记录内核运行时间,是定时器、调度的核心变量。

 

 

绝对段A 类型符号,地址编译时固定,不随内存布局变化,例如A _kernel_size_le_lo32kernel-6.1 内核大小低 32 位)。

 

 

2.2 ARM64 地址空间 → 符号的 居住区域

 

kernel-6.1ARM64 架构)的符号地址主要分两类,对应 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_rawsizePECOFF 数据原始大小);

 

 

特点:地址编译时固定,多用于内核早期启动(如 EFI stub 初始化)或文件格式相关(PECOFF 是 Windows 可执行文件格式,内核用于兼容引导)。

 

 

2.3 符号名 → 内核子系统映射:一眼识别功能

 

kernel-6.1 的符号名遵循 功能前缀” 规则,通过符号名可直接对应内核子系统,减少查源码的时间:

 

 

内核子系统

 

 

符号名前缀 / 关键词

 

 

kernel-6.1 实例

 

 

功能说明

 

 

中断处理

 

 

gic_irq_do_undef

 

 

t gic_handle_irqT do_undefinstr

 

 

GIC 中断处理、未定义指令异常处理

 

 

进程调度

 

 

sched_cpu_switch

 

 

T cpu_switch_tot pick_next_task_fair

 

 

进程切换、CFS 调度器选任务

 

 

内存管理

 

 

pgd_do_page_fault

 

 

T pgd_alloct do_page_fault

 

 

页表分配、页错误处理

 

 

系统调用

 

 

__arm64_sys_

 

 

T __arm64_sys_mmapT __arm64_sys_exit

 

 

ARM64 架构的 mmap/exit 系统调用

 

 

EFI 引导

 

 

__efistub_efi_

 

 

A __efistub_primary_entry_offset

 

 

EFI stub 启动入口偏移量

 

 

2.4 符号存在性 → 内核配置判断

 

kernel-6.1 中某个符号是否存在,直接反映内核编译时的配置(.config):

 

 

若存在T smp_send_reschedule → 开启 CONFIG_SMP(对称多处理器);

 

 

若存在T __arm64_sys_fanotify_init → 开启 CONFIG_FANOTIFY(文件系统事件通知);

 

 

若不存在t has_no_fpsimd → 开启 CONFIG_FPSIMDARM64 浮点 向量支持)。

 

 

这对内核裁剪优化非常有用:若不需要 SMP 功能,编译时关闭 CONFIG_SMPsmp_send_reschedule 等符号会消失,减少内核体积。

 

 

三、调试时关注 System.map:解决 大核心痛点

 

kernel-6.1 调试中,System.map 效率工具”—— 没有它,你可能需要花几小时猜地址;有了它,几分钟就能定位问题。

 

 

3.1 Oops 崩溃定位:从地址到函数的 一秒翻译

 

内核 Oops 是最常见的调试场景,例如打印:

 

 

  •  
  •  
Oops: 0000000000000005 [#1] PREEMPT SMPPC is at ffffffc00801c400

此时查System.map 即可快速定位:

 

 

1.打开kernel-6.1/System.map,搜索ffffffc00801c400

 

 

2.找到对应行:ffffffc00801c400 T die → 确认崩溃发生在 die 函数;

 

 

3.查看die 源码(kernel/exit.c),结合 Oops 上下文(如寄存器值、调用链),判断是 空指针访问” 还是 非法内存地址

内核

流程图

 

 

内核


3.2 内核恐慌栈回溯:补全函数调用链

 

当内核 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,快速定位错误传播路径。

内核

3.3 模块开发符号验证:避免 未定义引用

 

编写 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(); 即可正常编译。

 

 

3.4 性能分析地址翻译:perf 采样结果落地

 

perf record -g 采样内核性能时,结果会包含大量地址,例如:

 

 

  •  
  •  
  •  
Samples100  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 开发的 正确性保障” 和 效率加速器

 

 

4.1 提升内核调试效率

 

没有System.map 时,调试需通过addr2line 工具(需带调试信息的内核镜像vmlinux),且依赖内核编译时保留调试符号;有了System.map,直接查地址符号,无需额外工具,尤其适合无调试信息的 release 版本内核。

 

 

4.2 保障模块开发正确性

 

kernel-6.1 模块开发中,符号引用错误是常见问题(如引用不存在的符号、引用局部符号)。System.map 可提前验证符号有效性,避免模块加载时因符号未定义” 被内核拒绝(insmod: ERROR: could not insert module xxx.ko: Unknown symbol in module)。

 

 

模块开发流程图

 

 

内核

4.3 辅助内核裁剪与优化

 

kernel-6.1 支持按需裁剪功能,System.map 可验证裁剪效果:

 

 

若不需要 EFI 引导,关闭 CONFIG_EFI 后,__efistub_ 前缀的符号会消失,说明裁剪成功;

 

 

若不需要浮点支持,关闭CONFIG_FPSIMD 后,fpsimd_ 前缀的符号会消失,减少内核体积。

 

 

4.4 验证版本兼容性

 

不同内核版本(如 kernel-6.1 与 kernel-6.2)的符号地址可能变化,若模块硬编码地址,会导致加载失败。通过对比 System.map

 

 

foo 符号在 kernel-6.1 中地址为 ffffffc0080164a4,在 kernel-6.2 中为 ffffffc008016500,说明地址偏移,需修改模块为符号引用” 而非 地址硬编码

 

 

五、总结:System.map 是 kernel-6.1 开发的 基础设施

 

kernel-6.1/System.map 看似是简单的地址 符号” 列表,实则是内核的 功能导航图”—— 它连接了机器可识别的 地址” 与人类可理解的 功能,解决了调试中的 地址黑盒” 问题,保障了开发中的 符号正确性

 

 

无论是内核崩溃定位、模块开发,还是性能优化、版本兼容,System.map 都能帮你从盲目试错” 转向 精准操作,是 linux 开发者必须掌握的核心工具。

 

 

下次遇到内核问题时,先打开System.map—— 它或许能帮你省下几小时的调试时间。

 

 

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分