这应该是一个很基本的内核概念,和模块、系统调用等一样基础,但牵涉的东西却一点也不窄,一毫也不浅。(但也不深:-)
【内核符号表,kernel symbol table】
Linux的内核是个单内核monolithic,任一函数都可以访问公共数据结构和函数调用。在设计程序时,需要命名一些函数名、变量名等;同样内核中就含有很多的全局符号。
内核不是人脑皮层,要使用变量和函数-地址(指针)-来访问对应的变量和函数。
内核符号表就是为程序员通过符号来访问程序体的对应地址(指针),建立了一个动态的,可变更的映射表格。
一个符号表例子:
c03441a0 b dmi_broken
c03441a4 b is_sony_vaio_laptop
可以看出变量dmi_broken位于内核地址c03441a0处。
这和gdb的按图索骥的功能很相似,不同的是内核采用文件为载体的形式。
【/proc/ksyms】
ksyms是内核数据映像文件,在内核引导时创建,其实就是内核数据(/proc文件系统的特性及详解文末),没有实际大小的。
ksyms中的每一个表项代表着一个全局内核符号。这些符号可被LKM引用的,即可以看出LKM可以调用哪些函数(这里有一个安全问题)
【System.map】
位于/或者/boot、/usr/src/linux/下
在每次重新编译内核时,各符号名及其对应的地址指针将有所变化。(有变的,也有不变的)。所以系统需要自行更新此文件。
(关于System.map和系统出错的关系,需要额外说明)。
System.map文件作为特定内核的内核符号表,其链接了系统所使用的System.map。
一般的创建步骤:
当编译后生成内核vmlinux-2.X.Y后,存于/usr/src/linux/下,这时编译脚本将运行“nm /usr/src/linux/vmlinux-2.X.Y > System.map”,并将其拷入/boot下。
具体的生成过程可以从内核编译脚本中得到启示:
/usr/src/linux-2.X.Y/Makefile
[I]
nm vmlinux | grep -v ’(compiled)|(.o$$)|( [aUw] )|(..ng$$)|(LASH[RL]DI)’ | sort > System.map
cp /usr/src/linux/System.map /boot/System.map-2.X.Y
[/I]
注:nm vmlinux的作用是过滤掉其中不需要的符号。
值得注意的是内核本身并不真正使用System.map,但其它程序比如klogd, lsof和ps等软件需要一个正确的System.map。某些与内核头连接而非glibc库的驱动也需要System.map来解析符号(模块加载是与内核版本有关,但与内核版本一致而符号表发生变化的编译后内核无关)。
klogd 内核日志守护进程
为了执行名称-地址解析,klogd需要使用System.map。
man klogd可知,klogd将从一下路径查找System.map:
/boot/System.map
/System.map
/usr/src/linux/System.map
如:
# strace -f /sbin/klogd | grep ’System.map’
open("/boot/System.map-2.4.18", O_RDONLY|O_LARGEFILE) = 2
# strace lsof 2>&1 1> /dev/null | grep System
readlink("/proc/22711/fd/4", "/boot/System.map-2.4.18", 4095) = 23
# strace ps 2>&1 1> /dev/null | grep System
open("/boot/System.map-2.4.18", O_RDONLY|O_NONBLOCK|O_NOCTTY) = 6
【/proc文件系统】
一个伪文件系统,它只存在内存当中,而不占用外存空间。为访问系统内核数据的操作提供接口。有些是动态可变的。
以数字命名的目录是进程目录,以进程的PID号为目录名,而self目录则是读取进程本身的信息接口,是一个link,proc文件系统的由来也因为此。
apm 高级电源管理信息
cmdline 内核命令行
Cpuinfo 关于Cpu信息
Devices 可以用到的设备(块设备/字符设备)
Dma Used DMS channels
Filesystems 支持的文件系统
Interrupts 中断的使用
Ioports I/O端口的使用
Kcore 内核核心印象
Kmsg 内核消息
Ksyms 内核符号表
Loadavg 负载均衡
Locks 内核锁
Meminfo 内存信息
Misc Miscellaneous
Modules 加载模块列表
Mounts 加载的文件系统
Partitions 系统识别的分区表
Rtc Real time clock
Slabinfo Slab pool info
Stat 全面统计状态表s
Swaps 对换空间的利用情况
Version 内核版本
Uptime 系统正常运行时间
进程目录的结构如下:
目录名称 目录内容
Cmdline 命令行参数
Environ 环境变量值
Fd 一个包含所有文件描述符的目录
Mem 进程的内存被利用情况
Stat 进程状态
Status Process status in human readable form
Cwd 当前工作目录的链接
Exe Link to the executable of this process
Maps 内存印象
Statm 进程内存状态信息
Root 链接此进程的root目录
查看系统信息:cat /proc/mem
修改内核参数
/proc/sys不仅提供了内核信息,而且可以通过它修改内核参数。要改变内核的参数,可用Vi编辑或echo +参数重定向到文件中即可:
# echo 8192 > /proc/sys/fs/file-max
【关于ksyms被LKM调用的安全问题】
可以绕过LKM调用的符号表声明
static struct symbol_table module_syms= {
#include <linux/symtab_begin.h>
...
};
register_symtab(&module_syms);
register_symtab(NULL);
【关于System.map和内核出错Oops的关系】
※内核出错(oops)※
编程最常见的出错情况是段出错(segfault),信号11。
Linux内核中最常见的bug也是段出错。不过当内核引用了一个无效指针时,并不称其为段出错 -- 而被称为"oops"。一个oops表明内核存在一个bug。
当出现一个oops时,并不意味着内核肯定处于不稳定的状态;一个oops可能仅杀死了当前进程,并使余下的内核处于一个良好的、稳定的状态。-健壮的Linux
一个oops并非是内核死循环(panic)
在内核调用了panic()函数后,内核将停止运行,必须重启;
如果系统中关键部分遭到破坏,如关键的驱动等, 那么一个oops也可能会导致内核进入死循环(panic)。
当出现一个oops时,系统就会显示出用于调试问题的相关信息,比如所有CPU寄存器中的内容以及页描述符表的位置等,尤其会象下面那样打印出EIP(指令指针)的内容:
EIP: 0010:[<00000000>]
Call Trace: []
※与System.map的关系※
Linux使用klogd截取内核oops并且使用syslogd将其记录下来。klogd是一个内核消息记录器(logger),它可以通过System.map文件进行名字-地址之间的解析。通常是使用syslogd记录器。
深入说明: 其实klogd会执行两类地址解析活动。
静态转换,将使用System.map文件。
动态转换,该方式用于可加载模块,不使用System.map
Klogd动态转换
假设你加载了一个产生oops的内核模块。klogd就会截获消息。并解析出地址。如果该地址属于动态加载模块,在System.map文件中没有对应条目。此时klogd就会向内核查询该可加载模块输出的符号。
【内核符号表,kernel symbol table】
Linux的内核是个单内核monolithic,任一函数都可以访问公共数据结构和函数调用。在设计程序时,需要命名一些函数名、变量名等;同样内核中就含有很多的全局符号。
内核不是人脑皮层,要使用变量和函数-地址(指针)-来访问对应的变量和函数。
内核符号表就是为程序员通过符号来访问程序体的对应地址(指针),建立了一个动态的,可变更的映射表格。
一个符号表例子:
c03441a0 b dmi_broken
c03441a4 b is_sony_vaio_laptop
可以看出变量dmi_broken位于内核地址c03441a0处。
这和gdb的按图索骥的功能很相似,不同的是内核采用文件为载体的形式。
【/proc/ksyms】
ksyms是内核数据映像文件,在内核引导时创建,其实就是内核数据(/proc文件系统的特性及详解文末),没有实际大小的。
ksyms中的每一个表项代表着一个全局内核符号。这些符号可被LKM引用的,即可以看出LKM可以调用哪些函数(这里有一个安全问题)
【System.map】
位于/或者/boot、/usr/src/linux/下
在每次重新编译内核时,各符号名及其对应的地址指针将有所变化。(有变的,也有不变的)。所以系统需要自行更新此文件。
(关于System.map和系统出错的关系,需要额外说明)。
System.map文件作为特定内核的内核符号表,其链接了系统所使用的System.map。
一般的创建步骤:
当编译后生成内核vmlinux-2.X.Y后,存于/usr/src/linux/下,这时编译脚本将运行“nm /usr/src/linux/vmlinux-2.X.Y > System.map”,并将其拷入/boot下。
具体的生成过程可以从内核编译脚本中得到启示:
/usr/src/linux-2.X.Y/Makefile
[I]
nm vmlinux | grep -v ’(compiled)|(.o$$)|( [aUw] )|(..ng$$)|(LASH[RL]DI)’ | sort > System.map
cp /usr/src/linux/System.map /boot/System.map-2.X.Y
[/I]
注:nm vmlinux的作用是过滤掉其中不需要的符号。
值得注意的是内核本身并不真正使用System.map,但其它程序比如klogd, lsof和ps等软件需要一个正确的System.map。某些与内核头连接而非glibc库的驱动也需要System.map来解析符号(模块加载是与内核版本有关,但与内核版本一致而符号表发生变化的编译后内核无关)。
klogd 内核日志守护进程
为了执行名称-地址解析,klogd需要使用System.map。
man klogd可知,klogd将从一下路径查找System.map:
/boot/System.map
/System.map
/usr/src/linux/System.map
如:
# strace -f /sbin/klogd | grep ’System.map’
open("/boot/System.map-2.4.18", O_RDONLY|O_LARGEFILE) = 2
# strace lsof 2>&1 1> /dev/null | grep System
readlink("/proc/22711/fd/4", "/boot/System.map-2.4.18", 4095) = 23
# strace ps 2>&1 1> /dev/null | grep System
open("/boot/System.map-2.4.18", O_RDONLY|O_NONBLOCK|O_NOCTTY) = 6
【/proc文件系统】
一个伪文件系统,它只存在内存当中,而不占用外存空间。为访问系统内核数据的操作提供接口。有些是动态可变的。
以数字命名的目录是进程目录,以进程的PID号为目录名,而self目录则是读取进程本身的信息接口,是一个link,proc文件系统的由来也因为此。
apm 高级电源管理信息
cmdline 内核命令行
Cpuinfo 关于Cpu信息
Devices 可以用到的设备(块设备/字符设备)
Dma Used DMS channels
Filesystems 支持的文件系统
Interrupts 中断的使用
Ioports I/O端口的使用
Kcore 内核核心印象
Kmsg 内核消息
Ksyms 内核符号表
Loadavg 负载均衡
Locks 内核锁
Meminfo 内存信息
Misc Miscellaneous
Modules 加载模块列表
Mounts 加载的文件系统
Partitions 系统识别的分区表
Rtc Real time clock
Slabinfo Slab pool info
Stat 全面统计状态表s
Swaps 对换空间的利用情况
Version 内核版本
Uptime 系统正常运行时间
进程目录的结构如下:
目录名称 目录内容
Cmdline 命令行参数
Environ 环境变量值
Fd 一个包含所有文件描述符的目录
Mem 进程的内存被利用情况
Stat 进程状态
Status Process status in human readable form
Cwd 当前工作目录的链接
Exe Link to the executable of this process
Maps 内存印象
Statm 进程内存状态信息
Root 链接此进程的root目录
查看系统信息:cat /proc/mem
修改内核参数
/proc/sys不仅提供了内核信息,而且可以通过它修改内核参数。要改变内核的参数,可用Vi编辑或echo +参数重定向到文件中即可:
# echo 8192 > /proc/sys/fs/file-max
【关于ksyms被LKM调用的安全问题】
可以绕过LKM调用的符号表声明
static struct symbol_table module_syms= {
#include <linux/symtab_begin.h>
...
};
register_symtab(&module_syms);
register_symtab(NULL);
【关于System.map和内核出错Oops的关系】
※内核出错(oops)※
编程最常见的出错情况是段出错(segfault),信号11。
Linux内核中最常见的bug也是段出错。不过当内核引用了一个无效指针时,并不称其为段出错 -- 而被称为"oops"。一个oops表明内核存在一个bug。
当出现一个oops时,并不意味着内核肯定处于不稳定的状态;一个oops可能仅杀死了当前进程,并使余下的内核处于一个良好的、稳定的状态。-健壮的Linux
一个oops并非是内核死循环(panic)
在内核调用了panic()函数后,内核将停止运行,必须重启;
如果系统中关键部分遭到破坏,如关键的驱动等, 那么一个oops也可能会导致内核进入死循环(panic)。
当出现一个oops时,系统就会显示出用于调试问题的相关信息,比如所有CPU寄存器中的内容以及页描述符表的位置等,尤其会象下面那样打印出EIP(指令指针)的内容:
EIP: 0010:[<00000000>]
Call Trace: []
※与System.map的关系※
Linux使用klogd截取内核oops并且使用syslogd将其记录下来。klogd是一个内核消息记录器(logger),它可以通过System.map文件进行名字-地址之间的解析。通常是使用syslogd记录器。
深入说明: 其实klogd会执行两类地址解析活动。
静态转换,将使用System.map文件。
动态转换,该方式用于可加载模块,不使用System.map
Klogd动态转换
假设你加载了一个产生oops的内核模块。klogd就会截获消息。并解析出地址。如果该地址属于动态加载模块,在System.map文件中没有对应条目。此时klogd就会向内核查询该可加载模块输出的符号。