Kernel Symbols and CONFIG_MODVERSIONS
了解modversion挺不错的文章,后面有linuxforum的朋友翻译的中文版
-------------------------------------
Kernel Symbols and CONFIG_MODVERSIONS
-------------------------------------
Mark McLoughlin
Tue Mar 13, 2001
=================
Exporting Symbols
=================
By default, any global variables or functions defined in a module
are exported to the kernel symbol table when the module is loaded.
However, there are ways which you may control which symbols are
exported.
If you only require that none of the module's symbols are exported
you can use the EXPORT_NO_SYMBOLS macro.
If however, you require that only some of your global symbols
are exported you will need to use the EXPORT_SYMBOL macro to export it.
If CONFIG_MODVERSIONS is turned on a further step is required in the
build process, but that will be explained later.
======================
So How Does This Work?
======================
A kernel module that explicitly exports symbols will have two
special sections in its object file: the symbol table '__ksymtab' and
the string table '.kstrtab'. When a symbol is exported by a module
using EXPORT_SYMBOL, two things happen:
o a string, that is either the symbol name or, in the case that
CONFIG_MODVERSIONS is turned on, the symbol name with some
extra versioning info attached, is defined in the string table.
o a module_symbol structure is defined in the symbol table. This
structure contains a pointer to the symbol itself and a
pointer to the entry in the string table.
When a module is loaded this info is added to the kernels symbol
table and these symbols are now treated like any of the kernel's
exported symbols.
To take a peek at your module's symbol table do
$> objdump --disassemble -j __ksymtab sunrpc.o
or the string table do
$> objdump --disassemble -j .kstrtab sunrpc.o
========================
CONFIG_MODVERSIONS et al.
========================
CONFIG_MODVERSIONS is a notion thought up to make people's lives
easier. In essence, what it is meant to achieve is that if you have a
module you can attempt to load that module into any kernel, safe in the
knowledge that it will fail to load if any of the kernel data
structures, types or functions that the module uses have changed.
If your kernel is not compiled with CONFIG_MODVERSIONS enabled you
will only be able to load modules that were compiled specifically for
that kernel version and that were also compiled without MODVERSIONS
enabled.
However, if your kernel is compiled with CONFIG_MODVERSIONS enabled
you will be able to load a module that was compiled for the same
kernel version with MODVERSIONS turned off. But - here's the important
part folks - you will also be able to load any modules compiled with
MDOVERSIONS turned on, as long as the kernel API that the module uses
hasn't changed.
======================
So How Does This Work?
======================
When CONFIG_MODVERSIONS is turned on the kernel then a special
piece of versioning info is appended to every symbol exported using
EXPORT_SYMBOL.
This versioning info is calculated using the genksyms command whose
man page has this to say about how the info is calculated :
When a symbol table is found in the source, the symbol will be
expanded to its full definition, where all struct's, unions,
enums and typedefs will be expanded down to their basic part,
recursively. This final string will then be used as input to a
CRC algorithm that will give an integer that will change as
soon as any of the included definitions changes, for this symbol.
The version information in the kernel normally looks like:
symbol_R12345678, where 12345678 is the hexadecimal
representation of the CRC.
What this means is that the versioning info is calculated in such a
way as that it will only change when the definition of that symbol
changes.
The versioning string is 'appended' by the use of a #define in
linux/modversions.h for every exported symbol. The #define usually
winds up looking something like this (simplified):
#define printk printk_R1b7d4074
What this does is effectively get rid of the function 'printk' -
alas, poor printk - and replace it with the much more handsome
'printk_R1b7d4074'.
If you have a look at linux/modversions.h you'll notice that it
just includes loads of .ver files. These are generated using a command
similar to
$> gcc -E -D__GENKSYMS__ $ | genksyms -k $ > $
Notice that the c file if first passed through the c preprocessor
before being passed to genksyms. This is to collapse all macros and
stuff beforehand.
================================
What does this mean for modules?
================================
When modules are being compiled for a kernel with
CONFIG_MODVERSIONS turned on, the header file linux/modversions.h must
be included at the top of every c file. This you be an awful pain to
do, so we just do it with the gcc flag '-include'.
ifdef CONFIG_MODULES
ifdef CONFIG_MODVERSIONS
MODFLAGS += -DMODVERSIONS -include $(HPATH)/linux/modversions.h
endif
The extra MODVERSIONS flag is used to indicate that this is a
module being compiled with CONFIG_MODVERSIONS turned on as opposed to
the kernel being compiled with CONFIG_MODVERSIONS enabled.
#######################
译者:这是我看到过的将linux的modversion讲的最清楚的文章,结合前一阵看insmod的源码
的相关部分,整理出一份文档,希望对以后的学习者有所帮助。
=================
符号输出
=================
缺省情况下,在模块加载的时候,所有模块中定义的全局变量和全局函数都会被输出到内核
符号表中(译者:此处理解不要出现偏差,作者的意思是,以后的模块可是使用这些变量和函数,
但是,并不是加到内核变量kernel_module中,ksyms的查找是通过module_list进行的。)。然而,
还是有方法控制模块中的符号的输出的。
如果你想什么符号都不输出,只需要定义宏EXPORT_NO_SYMBOLS就可以了。然而如果你想输出
模块中的部分符号,你可以使用宏EXPORT_SYMBOL完成目的。如果CONFIG_MODVERSIONS宏在这时
被定义了,那么在module被编译的时候,会有一些动作被完成,我们将在后面解释。
======================
那么它是怎样工作的?
======================
一个编译后的包含输出符号的内核模块文件中,会有两个section(译者:此处section不好翻译
,是ELF文件中的节,具体信息见elfspec.pdf),一个是符号表section '__ksymtab',一个是字符
串section 'kstrtab'。当然必须是在模块中定义了EXPORT_SYMBOL的情况下,(译者:此处正如作者所说,
如果模块中定义了EXPORT_SYMBOL,编译后的object文件中才会后这个两个节,如果insmod发现object中
没有这两个section,insmod会从object抽取所有的全局符号生成这两个section,而这两个section在
sys_init_module系统调用后会体现在module结构中的sym中,被后来者使用。)这时会发生两件事:
1.字符串的构成即使符号的名字,同时,如果CONFIG_MODVERSIONS被定义,符号名后面将会
有一些多出的版本方面的信息。(_RXXXXXXXX)
2.module_symbol结构将在符号表中被定义,这个结构包含符号的值和符号的名字的值,并不是
字符串,而是指向字符串表的索引。
当模块被加载时,这些符号就被加载到内核符号表中,就像本身就是内核输出的一样。(译者:此段理解同上)
我们可以通过下面两个命令查看这个两个section的内容
$> objdump --disassemble -j __ksymtab sunrpc.o
$> objdump --disassemble -j .kstrtab sunrpc.o
========================
CONFIG_MODVERSIONS的含义
========================
CONFIG_MODVERSIONS的理念是想让人们使用的更方便,基本上它的意思是说如果你想要加载一个
模块到内核中,为了保证系统安全,如果被模块使用的内核中的数据结构,类型和函数发生变化,那
么这次加载将会失败。
如果你的内核编译的时候是没有定义了CONFIG_MODVERSIONS宏,那么想要成功加载一个模块需要
满足的条件是:模块的编译时的版本号和加载时的内核版本号一致,或模块编译时没有定义MODVERSIONS
(译者:我原来以为作者的意思是‘与’,所以认为他错了,其实是或的意思,感谢hzeng帮助纠正)
然而,如果你的内核是定义了宏CONFIG_MODVERSIONS编译的,那么即使你的模块在同版本的内核下
编译时没有定义MODVERSIONS,模块仍然会被成功加载。但是,如果你模块编译时定义了MODVERSIONS
那么想要成功加载的前提条件是,模块中使用的内核中的接口没有发生一点变化。
======================
那么它又是怎样工作的?
======================
当内核编译时,如果CONFIG_MODVERSIONS宏被定义,那么一个特定的一段信息将会被附加在每个通过
宏EXPORT_SYMBOL定义而输出的符号名上。
这段附加信息是通过命令genksyms来生成的,它的手册描述如下:
genksyms将会讲一个符号中的所有定义,包括结构,联合,枚举,类型定义的结构解释开,变成
它编译前的最终内容,然后将其内容用CRC算法生成一个整数,这样如果一个符号中的任何定义
发生变化,这个整数将会不一样,例如:
内核中的这个关于版本的附加信息有可能象symbol_R12345678,这个12345678就是CRC算出的整数
的十六进制表示形式。
这就表示一个版本相关的附加信息是这样计算出来的,如果符号的定义发生变化,那么这个变化一定会
体现在这个附加信息上。
这段附加信息的添加是通过linux/modversions.h中的宏完成的通常象下面这样
#define printk printk_R1b7d4074
这样并不影响编程中使用printk函数,然而真正编译时使用的符号确是带有附加信息的函数。
如果你仔细查看linux/modversions.h文件,你会发现很多.ver的包含,其实它就是通过类似下面的命令
生成的
$> gcc -E -D__GENKSYMS__ $ | genksyms -k $ > $
================================
这对模块意味着什么?
================================
如果你的模块在一台CONFIG_MODVERSIONS被定义的内核中编译,那么在所有的c程序前linux/modversions.h
这个头文件必须被包含,这是一件非常痛苦的事情,所以我们可以通过gcc的-include选项完成
ifdef CONFIG_MODULES
ifdef CONFIG_MODVERSIONS
MODFLAGS += -DMODVERSIONS -include $(HPATH)/linux/modversions.h
endif
宏MODVERSIONS用于表示此模块是用于定义了CONFIG_MODVERSIONS内核
译者后续:
关于原作者的一些观点,有些疑问,见insmod源代码一段和此相关的内容,
k_crcs = is_kernel_checksummed();//内核是否用了版本相关的附加信息
m_crcs = is_module_checksummed(f);//模块是否用了版本相关的附加信息
if ((m_crcs == 0 || k_crcs == 0) &&//至少有一个有,并且版本号也不匹配进入
//如果双方都有,那么比较CRC的值就可以了,没必要比较版本号。
strncmp(k_strversion, m_strversion, STRVERSIONLEN) != 0) {
if (flag_force_load) {//如果有-f标志,只给警告
lprintf("Warning: kernel-module version mismatch\n"
"\t%s was compiled for kernel version %s\n"
"\twhile this kernel is version %s",
filename, m_strversion, k_strversion);
} else {//如果不是,报错。
if (!quiet)
error("kernel-module version mismatch\n"
"\t%s was compiled for kernel version %s\n"
"\twhile this kernel is version %s.",
filename, m_strversion, k_strversion);
goto out;
}
}
if (m_crcs != k_crcs)//注意这里
obj_set_symbol_compare(f, ncv_strcmp, ncv_symbol_hash);
如果两者不相等,将符号名比较函数变为ncv_strcmp,而不是原来的strcmp函数,
这样使得printk和printk_R1b7d4074是一样的。哈希函数也变为ncv_symbol_hash,使得计算hash时抛弃
后面的CRC值。如果时这样的话如果kernel没有定义CONFIG_MODVERSIONS宏,而module定义了modversion时
应给也是可以被成功加载的。
后记: 试验发现:
kernel配置中不要定义CONFIG_MODVERSION ,驱动程序中也不用去管version的问题, 这样不会出现:
insmod: couldn't find the kernel version the module was compiled for
的问题。
方便!
阅读(1430) | 评论(0) | 转发(0) |