`

关于 Unresolved Symbols 问题的一点资料

阅读更多


6. Unresolved Symbols

资料来源:Linux Loadable Kernel Module (LKM) HOWTO。
翻译:Wang Haiguang

在加载一个内核模块时, 最常见的和最令人沮丧的错误信息就是unresolved symbols, 如:
msdos.o: unresolved symbol fat_date_unix2dos
msdos.o: unresolved symbol fat_add_cluster1

多种原因可导致上述错误。但是,在任何情况下,你都可以通过查询/proc/ksyms文件来接近
导致问题的原因,并且确证错误信息中所列的symbols不在上述文件所包含的symbol列表中

6.1. Some LKMs Prerequire Other LKMs

导致这个问题的一个原因是用户没有加载另外一个内核模块, 该内核模块包含了当前模块所
需求的指令或数据。modprobe命令的一个主要目的就是防止这类错误。请参阅5.2节 (未翻译)。

6.2. An LKM Must Match The Base Kernel

动态加载内核模块的设计者意识到当内核包含多个版本时可能会出现问题。假设mydriver.o是
为Linux 1.2.1内核编写的,如果用户在运行Linux 1.2.2内核时加载这个模块会发生什么情
况呢?如果模块mydriver.o所调用的内核函数所包含的代码在内核1.2.1和1.2.2发生了变化,
会发生什么情况呢?这些都是内核函数,如何来阻止这些变化所导致的问题呢?

为了解决这个问题,可动态加载内核模块的创建者赋予每个外部可见函数一个内核版本号。在
mydriver.o目标文件的特殊的.modinfo节包含了版本信息,在上例中为“1.2.1”,原因是它
在编译时使用了内核1.2.1得头文件。在运行内核1.2.1时加载此模块,insmod 命令就会注意
到内核版本不匹配的问题因而停止加载该模块,并且会提示内核版本不匹配的信息。

但是,内核1.2.1和1.2.2的不兼容问题会在多大程度上影响mydriver.o呢? mydriver.o只使
用了内核的几个函数和数据结构而已。在内核改动很小的情况下,这些函数可能根本不会改变。
对每一个要加载该模块的特定内核,我们都必须从新编译该模块吗?

为了减少这些不必要的劳动,insmod命令有一个-f选项,该选项“强制”(forces)insmod
忽略内核版本不匹配的问题,强行加载一个模块。两个版本相近的内核几乎不可能有显著区别,
我推荐用户使用-f选项。但是,用户仍然会收到内核版本不匹配的警告。用户无法关闭该警告。

但是,动态加载内核模块的设计者仍然想解决由于不兼容的改变所导致的问题。于是他们就采用
了一个非常聪明的方法,该方法使得内核模块的加载程序可以很灵敏的探测到该模块所调用的函数
是否发生了改变。该方法利用了symbol版本号来检测程序的变化。此方法在编译内核时是可选的。
你可以通过CONFIG_MODVERSIONS来配置该选项。

当你编译一个基本内核或内核模块时使用了symbol版本号,各种向内核模块输出的symbol使用了宏
定义。这些宏定义是由原有的symbol名字加一个16进制的哈希值,该哈希值是从函数参数与返回值
得出的(通过genksyms程序来产生的)。 让我们来看register_chrdev函数。register_chrdev函数
是基本内核中一个经常被设备驱动程序调用的函数。当使用了symbol版本号时,有一个C宏定义如下:

#define register_chrdev register_chrdev_Rc8dc8350

该宏定义在定义该函数的源文件与任何使用该函数的文件中都是有效的。 所以当你在源文件中看到
register_chrdev函数时,C 预处理器知道这个函数实际上是register_chrdev_Rc8dc8350。

那么那个向垃圾一样的后缀有什么意义呢?他是register_chrdev的参数和返回值类型的哈希
值。对于不同的参数与返回值类型得组合,该值是独一无二的。

比如说在内核1.2.1到1.2.2的升级过程中,有人在register_chrdev加入了一个参数。在内核
1.2.1中,register_chrdev的宏定义是register_chrdev_Rc8dc8350, 但是在内核1.2.2中,
该宏定义是register_chrdev_R12f8dc01. 对于mydriver.o, 如果在编译时使用了内核1.2.1
的头文件,那么他有一个register_chrdev_Rc8dc8350的外部调用,但是在内核1.2.2中并无
此symbol的定义。相反的,在内核1.2.2中向外输出的symbol是register_chrdev_R12f8dc01。

所以当你要加载基于内核1.2.1的模块到内核1.2.2时,你就会遇到加载失败的问题。错误信息
不是内核版本不匹配,而是unresolved symbol reference。

但是这个方法并非十全十美的。对于完全相同的参数列表,genksyms有时会产生不同的哈希值。

symbol版本并不能完全保证兼容性。它只能捕捉到关于函数定义变化的信息。他并不能探测到
由于函数内部代码变化而导致的不兼容性。例如,register_chrdev在使用期中的一个参数时
发生了不兼容性变化。由于参数与返回值类型并没有变化,因此他的版本后缀并没有变化。

当symbol版本不一致时,即使在加载模块(insmod)时使用了-f选项也不能成功。所以,通常来
说最好不要使用symbol版本号。

当然,如果你在编译基本内核时打开了symbol版本选项,那么你编译内核模块是就必须也使用
该选项,反之亦然。否则,在加载模块时,你肯定会遇到unresolved symbol reference的错
误信息。

6.3. If You Run Multiple Kernels

当一个系统同时存在不同版本的内核时,通常使用modprobe命令来搜索对应的模块。该命令会
从/lib/modules/kernel-version目录下搜寻对应模块。

你可以通过编辑内核的makefile来设置 uname -- release值, modprobe使用该值来搜索模
块。

6.4. SMP symbols

除了以上的校验和之外, symbol版本的前缀还会包含 “smp”如果该symbol是面向多处理器
系统的。所以当基本内核在编译时打开了多处理器选项而内核模块在编译时未打开该选项,加
载时也会遇到unresolved symbol reference的错误信息,反之亦然。

6.5. You Are Not Licensed To Access The Symbol

一些内核代码的拥有者有时会在symbol版本号中加入GPLONLY以鼓励Linux使用与开发者公开其
源代码。细节请参考原著。

6.6. An LKM Must Match Prerequisite LKMs

内核模块在与基本内核兼容的同时,也必须与那些包含该模块所调用函数的其它内核模块兼容。

 

 

linux kernel里的unresolved symbol

 

linux 内核里面有很多export给其他模块使用的符号表,一般情况下,如果没有选择CONFIG_MODVERSIONS,这些符号是正常的字串;如果选择了 CONFIG_MODVERSIONS,这些符号就会在后面加一段校验字串。这样做的目的是避免模块不正确加载情况下,使得内核崩溃。
如果内核选择了CONFIG_MODVERSIONS选项,你的模块的Makefile要增加以下几行
CFLAGS += -DMODVERSIONS -include /usr/src/linux/include/linux/modversions.h
或者在你的C源文件里增加
#ifdef CONFIG_MODVERSIONS
#define MODVERSIONS
#include
#endif
这样就可以在编译内核模块时,如果模块里引用了内核符号表,就可以自动计算校验字串,而不会在加载模块时出现unresloved symbol的错误了。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics