一、前言
Linux内核是一个整体结构,而模块是插入到内核中的插件。尽管内核不是一个可安装模块,但为了方便起见,Linux把内核也看作一个模块。那么模块与模块之间如何进行交互呢,一种常用的方法就是共享变量和函数。但并不是模块中的每个变量和函数都能被共享,内核只把各个模块中主要的变量和函数放在一个特定的区段,这些变量和函数就统称为符号。
因此,内核也有一个module结构,叫做kernel_module。另外,从kernel_module开始,所有已安装模块的module结构都链在一起成为一条链,内核中的全局变量module_list就指向这条链:
struct module *module_list = &kernel_module;
一般来说,内核只会导出由EXPORT_PARM宏指定的符号给模块使用。为了使debugger提供更好的调试功能,需要使用kallsyms工具为内核生成__kallsyms段数据,该段描述所有不处在堆栈上的内核符号。这样debugger就能更好地解析内核符号,而不仅仅是内核指定导出的符号。
二、简介
在v2.6.0 的内核中,为了更好地调试内核,引入新的功能kallsyms.kallsyms把内核用到的所有函数地址和名称连接进内核文件,当内核启动后,同时加载到内存中.当发生oops,例如在内核中访问空地址时,内核就会解析eip位于哪个函数中,并打印出形如:
EIP is at cleanup_module+0xb/0x1d [client]的信息,
调用栈也用可读的方式显示出来.
Call Trace:
[] sys_delete_module+0x191/0x1ce
[] do_page_fault+0x189/0x51d
[] syscall_call+0x7/0xb
当然功能不仅仅于此,还可以查找某个函数例如的sys_fork的地址,然后hook它,kprobe就是这么干的。在v2.6.20 中,还可以包含所有符号的地址,应此功能更强大,就相当于内核中有了System.map了,此时查找sys_call_table的地址易如反掌。
三.sym的生成
1.形成过程
Linux内核符号表/proc/kallsyms的形成过程
(1)./scripts/kallsyms.c负责生成System.map
(2)./kernel/kallsyms.c负责生成/proc/kallsyms
(3)./scripts/kallsyms.c解析vmlinux(.tmp_vmlinux)生成kallsyms.S(.tmp_kallsyms.S),然后内核编译过程中将kallsyms.S(内核符号表)编入内核镜像uImage.内核启动后./kernel/kallsyms.c解析uImage形成/proc/kallsyms
2.内核配置
在2.6 内核中,为了更好地调试内核,引入了kallsyms。kallsyms抽取了内核用到的所有函数地址(全局的、静态的)和非栈数据变量地址,生成一个数据块,作为只读数据链接进kernel p_w_picpath,相当于内核中存了一个System.map。需要配置CONFIG_KALLSYMS。
.config
CONFIG_KALLSYMS=y 符号表中包含所有的函数
CONFIG_KALLSYMS_ALL=y 符号表中包括所有的变量(包括没有用EXPORT_SYMBOL导出的变量)
CONFIG_KALLSYMS_EXTRA_PASS=y
make menuconfig
General setup --->
[*] Configure standard kernel features (for small systems) --->
[*] Load all symbols for debugging/ksymoops (选中此项,才有/proc/kallsyms接口文件, oops问题,选中此选项即可,子选项可以忽略)
[*] Include all symbols in kallsyms
[*] Do an extra kallsyms pass
3.编译生成列表
内核编译的最后阶段,make会执行
nm -n vmlinux|scripts/kallsyms
nm -n vmlinux生成所有的内核符号,并按地址排序,形如
......
c0100000 T startup_32
c0100000 A _text
c01000c6 t checkCPUtype
c0100147 t is486
c010014e t is386
c010019f t L6
c01001a1 t check_x87
c01001ca t setup_idt
c01001e7 t rp_sidt
c01001f4 t ignore_int
c0100228 T calibrate_delay
c0100228 T stext
c0100228 T _stext
c010036b t rest_init
c0100410 t do_pre_smp_initcalls
c0100415 t run_init_process
......
v2.6.0 的行数是2.5万左右
4.处理列表
scripts/kallsyms则处理这个列表,并生成连接所需的S文件kallsyms.S。在linux3.12中使用/scripts/kallsyms处理此列表。v2.6.0中形如:
#include
#if BITS_PER_LONG == 64
#define PTR .quad
#define ALGN .align 8
#else
#define PTR .long
#define ALGN .align 4
#endif
.data
.globl kallsyms_addresses
ALGN
kallsyms_addresses:
PTR 0xc0100228
PTR 0xc010036b
PTR 0xc0100410
PTR 0xc0100415
PTR 0xc010043c
PTR 0xc0100614
...
.globl kallsyms_num_syms
ALGN
kallsyms_num_syms:
PTR 11228
.globl kallsyms_names
ALGN
kallsyms_names:
.byte 0x00
.asciz "calibrate_delay"
.byte 0x00
.asciz "stext"
.byte 0x00
.asciz "_stext"
...
生成的符号表部分如下:
/*
......
c1618b03 t __raw_write_unlock_irq.constprop.29
c1618b19 T panic
c1618c91 T printk
......
c16a4d6b r __func__.17404
c16a4d78 R kallsyms_addresses
c16ef0dc R kallsyms_num_syms
c16ef0e0 R kallsyms_names
c17d5468 R kallsyms_markers
c17d590c R kallsyms_token_table
c17d5c78 R kallsyms_token_index
......
*/
5.生成的符号数组解析
1)kallsyms_addresses数组包含所有内核函数的地址(经过排序的),v2.6.0 中相同的地址在kallsyms_addresses中只允许出现一次,到后面的版本例如相同的地址可以出现多次,这样就允许同地址函数名的出现。
例如:
kall