0%

Linux 内核符号表的生成和查找

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);