什么是内核符号?
内核符号表就是内核中 “名字 → 信息(地址、类型、可见性)” 的映射表。名字通常是内核的函数名或全局变量名,符号表让内核本身与可加载模块(.ko)相互找到并链接这些名字。而表项的名字就是内核符号。
内核符号表存在于哪里?
- 构建时:
vmlinux(未压缩的内核镜像)包含完整符号信息。内核构建后会生成System.map,格式为地址 类型 名称,并通常放在/boot/System.map-$(uname -r)。 - 运行时:内核会把符号信息以可查询的形式暴露到
/proc/kallsyms(如果内核启用了CONFIG_KALLSYMS)。
符号行的格式与常见类型
通过head /proc/kallsyms获取符号表的前几行:
80008000 T stext
80008000 T _text
8000808c t __create_page_tables
80008138 t __turn_mmu_on_loc
80008144 t __fixup_smp
800081ac t __fixup_smp_on_up
800081d0 t __fixup_pv_table
80008224 t __vet_atags
80100000 T _stext
80100000 T __turn_mmu_on
其输出格式为:地址 类型字母 名称
常见类型字母:
- T / t:代码段(text,函数),T 表示全局可见(外部),t 表示本地/静态。
- D / d:已初始化的数据段(全局/本地)。
- B / b:BSS(未初始化全局变量)。
- R:只读数据(rodata)。
- U:未定义(undefined;通常出现在模块,表示对某符号有引用但还未解析)。
对于构建好的内核或者内核模块文件,也可以通过readelf -s vmlinux,readelf -s hello.ko来获取他们的符号表,它的输出内容更为丰富。以前面解释的第一个内核模块为例:
readelf -s simplest_drv.koSymbol table '.symtab' contains 42 entries:Num: Value Size Type Bind Vis Ndx Name0: 00000000 0 NOTYPE LOCAL DEFAULT UND 1: 00000000 0 SECTION LOCAL DEFAULT 1 .note.gnu.build-id2: 00000000 0 SECTION LOCAL DEFAULT 2 .text3: 00000000 0 SECTION LOCAL DEFAULT 3 .init.text4: 00000000 0 SECTION LOCAL DEFAULT 5 .exit.text...............18: 00000000 0 FILE LOCAL DEFAULT ABS simplest_drv.c19: 00000000 0 NOTYPE LOCAL DEFAULT 3 $a20: 00000000 32 FUNC LOCAL DEFAULT 3 simplest_drv_init25: 00000000 28 FUNC LOCAL DEFAULT 5 simplest_drv_exit...............36: 00000000 192 OBJECT LOCAL DEFAULT 15 ____versions37: 00000000 512 OBJECT GLOBAL DEFAULT 17 __this_module38: 00000000 28 FUNC GLOBAL DEFAULT 5 cleanup_module39: 00000000 32 FUNC GLOBAL DEFAULT 3 init_module40: 00000000 0 NOTYPE GLOBAL DEFAULT UND printk41: 00000000 0 NOTYPE GLOBAL DEFAULT UND __aeabi_unwind_c[...]
| 列 | 含义 |
|---|---|
| Num | 符号的索引号(从0开始) |
| Value | 符号的内存地址(VMA) |
| Size | 符号大小(字节) |
| Type | 符号类型,常见的有:NOTYPE - 未指定类型 OBJECT - 数据对象FUNC - 函数或可执行代码SECTION - 关联到节区FILE - 源文件名 |
| Bind | 符号绑定属性:LOCAL - 局部符号(static)GLOBAL - 全局符号(extern)WEAK - 弱引用符号 |
| Vis | 可见性:DEFAULT - 默认可见规则PROTECTED - 保护可见性HIDDEN - 隐藏符号 |
| Ndx | 关联的SECTION索引,特殊值包括:ABS - 绝对值(不重定位) UND - 未定义(外部引用) COM - 未初始化的公共块 |
| Name | 符号名 |
这里解释一下Ndx,当它为数值时,表示当前符号属于Num这一行对应数值的符号,通常类型是SECTION。例如以上面为例:
20: 00000000 32 FUNC LOCAL DEFAULT 3 simplest_drv_init
我们知道simplest_drv_init是模块初始化函数,它被放到了一个特殊的段里面:初始化段。它的Nxd是3,对应为:
3: 00000000 0 SECTION LOCAL DEFAULT 3 .init.text
这行类型为SECTION,也就是一个段,名称为.init.text,正是内核模块初始化段的名字。
内核符号导出与模块加载
在编译,链接内核模块时如果符号属于外部符号,则Nxd会被标记为未定义(UND),且绑定属性Bind为GLOBAL,在加载内核模块时,就会搜索/proc/kallsysms,以获得它的信息(地址、类型、可见性)。如果未搜索到,则模块加载会失败。
