printk
可以根据地址打印函数名/符号名,记录内核符号表生成及使用过程
System.map
System.map 文件是编译内核时生成的,它记录了内核中的符号列表,以及符号在内存中的虚拟地址,由脚本 scripts/mksysmap
生成
/proc/kallsyms
内核必须打开 CONFIG_KALLSYMS
编译选项,和 System.map 的区别是它同时包含了内核模块的符号列表,是在内核启动后生成的,位于文件系统的 /proc 目录下,实现代码见 kernel/kallsyms.c
内核符号表
System.map 和内核启动后的 /proc/kallsyms 文件中的符号表只是给我们看的,内核不会使用它们,而是在编译内核时,向 vmlinux 嵌入了一个符号表
内核符号表如何嵌入内核查看文件 scripts/link-vmlinux.sh
内核符号表结构
由工具 scripts/kallsyms
生成,工具源码在同目录,用法如下
nm -n vmlinux | scripts/kallsyms [--all-symbols] > symbols.S
使用 nm 工具获得符号表之后进行整理得到汇编文件,包括 6 个全局变量
kallsyms_addresses
数组,存放所有符号的地址列表,按地址升序排列kallsyms_num_syms
符号的数量kallsyms_names
数组,存放所有符号的名称,和kallsyms_addresses
一一对应kallsyms_markers
存储索引用于加速搜索kallsyms_token_table
数组,存储压缩字符串kallsyms_token_index
记录每个 token 首字符在kallsyms_token_table
中的偏移
查找过程
c0008000 T _text
c0008000 T stext
c000808c t __create_page_tables
计算符号 c000808c t __create_page_tables
偏移为 0x8c
,从 kallsyms_addresses
中得到索引为 2
kallsyms_addresses:
PTR _text + 0
PTR _text + 0
PTR _text + 0x8c
kallsyms_names:
.byte 0x04, 0x9b, 0xef, 0x78, 0x74
.byte 0x05, 0x54, 0x5f, 0xef, 0x78, 0x74
.byte 0x0a, 0xff, 0xe1, 0xf5, 0x8b, 0xa7, 0x18, 0xfd, 0x62, 0xd2, 0x73
kallsyms_markers:
PTR 0
PTR 2917
PTR 5993
根据 2 >> 8
计算出在 kallsyms_markers
第一组 PTR 0
,指出起始字符的偏移在 kallsyms_names[0]
,同时根据 2 && 0xFF
找到具体位置
.byte 0x0a, 0xff, 0xe1, 0xf5, 0x8b, 0xa7, 0x18, 0xfd, 0x62, 0xd2, 0x73
为压缩字符,需要根据 kallsyms_token_table
解析
- 0x0a 长度
- 0xff
t_
- 0xe1
_c
- 0xf5
re
- 0x8b
ate_
- 0xa7
pa
- 0x18
get_
- 0xfd
ta
- 0x62
b
- 0xd2
le
- 0x73
s
t__create_page_tables
其中 t
为符号类型,去除之后为 __create_page_tables
内核实现
内核文件参照 kernel/kallsyms.c
提供接口用于内核符号表查找
get_symbol_pos
根据地址查找索引及偏移module_address_lookup
内核模块符号查找get_symbol_offset
根据kallsyms_markers
获取偏移kallsyms_expand_symbol
获取符号名kallsyms_get_symbol_type
获取符号类型
例如函数 kallsyms_lookup
if (is_ksym_addr(addr)) {
unsigned long pos;
pos = get_symbol_pos(addr, symbolsize, offset);
/* Grab name */
kallsyms_expand_symbol(get_symbol_offset(pos),
namebuf, KSYM_NAME_LEN);
if (modname)
*modname = NULL;
return namebuf;
}
/* See if it's in a module. */
return module_address_lookup(addr, symbolsize, offset, modname,
namebuf);