历史上的今天
今天是:2025年08月11日(星期一)
2021年08月11日 | arm上加载insmod驱动时出现Unknown symbol in module
2021-08-11 来源:eefocus
问题
这几天在arm上做蓝牙耳机驱动的时候,编译好了驱动但是在板子上insmod时候。

怎么会出现这种情况,不对呀,仔细查我们会发现,其实编译驱动的时候,就出现了一些警告,只是当时没有在意而已,而恰恰是这些警告导致的这些问题。
硬件设备
板子用的是realarm
内核linux-2.6.35
交叉编译器arm-linux-gcc 4.4.3
问题解析
究其原因,其原因就是我们的驱动找不到内核的几个函数,我们可以看到我们找不到的函数有两个,一个是kill_proc_info 一个是snd_hwdep_new 。
问题来了,我们内核编的好好的怎么会找不到这两个函数呢
System.map与内核符号表
亲自编译过linux内核的可能编译完内核都会发现在生成vmlinuz的同时,生成一个System.map文件。
nm /boot/vmlinux > System.map
通常我们会把发送到标准输出设备的链接映象信息重定向到一个文件中(如System.map)。编译内核时,System.map文件用于存放内核符号表信息。符号表是所有内核符号及其对应地址的一个列表,随着每次内核的编译,就会产生一个新的对应System.map文件,当内核运行出错时,通过System.map中的符号表解析,就可以查到一个地值应的变量名,或反之。
利用System.map,在内核或相关程序出错时,就可以获得我们比较容易识别的信息。
System.map位于使用它的软件(例如内核日志记录后台程序klogd)能够找到的地方。在系统启动时,如果没有以一个参数的形式为klogd给出System.map的位置,则klogd会在三个地方寻找System.map:
/boot/System.map
/System.map
/usr/src/linux/System.map
尽管内核本身实际上不使用System.map,但其它程序,像klogd、lsof、ps以及其它像dosemu等许多软件
都需要有一个正确的System.map文件。利用该文件,这些程序就可以根据已知的内存地址查找出对应的内核变量名称,便于对内核的调试工作。
首先我们可以查看下我们需要的函数是否导入到符号表中System.map
使用如下命令查看cat -n System.map | grep kill_proc_info 和 cat -n System.map | grep snd_hwdep_new

我们发现函数snd_hwdep_new这个函数不在内核中,但是kill_proc_info已经在符号标志中,也就是已经编译在内核里面了,但是为什么内核还是找不到它呢,没事我们逐个解决。
EXPORT_SYMBOL
EXPORT_SYMBOL只出现在2.6内核中,在2.4内核默认的非static 函数和变量都会自动导入到kernel 空间的, 都不用EXPORT_SYMBOL() 做标记的。
2.6以后的内核就必须用EXPORT_SYMBOL() 来导出来(因为2.6默认不到处所有的符号)。
EXPORT_SYMBOL的作用是什么?
EXPORT_SYMBOL标签内定义的函数或者符号对全部内核代码公开,不用修改内核代码就可以在您的内核模块中直接调用,即使用EXPORT_SYMBOL可以将一个函数以符号的方式导出给其他模块使用。
这里要和System.map做一下对比:
System.map 中的是链接时的函数地址。链接完成以后,在2.6内核运行过程中,是不知道哪个符号在哪个地址的。
EXPORT_SYMBOL 的符号, 是把这些符号和对应的地址保存起来,在内核运行的过程中,可以找到这些符号对应的地址。而模块在加载过程中,其本质就是能动态连接到内核,如果在模块中引用了内核或其它模块的符号,就要EXPORT_SYMBOL这些符号,这样才能找到对应的地址连接。
使用需要三个步骤
第一、在模块函数定义之后使用EXPORT_SYMBOL(函数名)
第二、在掉用该函数的模块中使用extern对之声明
第三、首先加载定义该函数的模块,再加载调用该函数的模块
另外,在编译调用某导出函数的模块时,往往会有
WARNING: "****" [**********] undefined!
这个正好就是我们编译驱动时出现的那个警告
解决kill_proc_info
问题解析
kill_proc_info这个函数已经编译进了内核(在符号表中有这个函数),但是模块仍然找不到地址,这个说明一个问题,就是我们的函数kill_proc_info并没有被导出,也就是加载模块时候,模块驱动找不到地址。
我们查找一下内核源码中,所有出现kill_proc_info 的地方

我们会发现没有EXPORT_SYMBOL 的记录,也就是说**该函数的确没有导出, 即外界不可访问。
那么我们就按照那三个步骤我们以此查找
在模块函数定义之后使用EXPORT_SYMBOL(函数名)
首先我们找到定义函数的地方,前我们已经用grep -r kill_proc_info * 查找内核源码中所有出现kill_proc_info的地方。
我们可以看到其声明在include/linux/sched.h 定义在kernel/signal.c 中
其声明的函数原型为
extern int kill_proc_info(int, struct siginfo *, pid_t);
那么我们就在kernel/signal.c 文件中函数定义之后添加如下代码
EXPORT_SYMBOL(kill_proc_info);
在掉用该函数的模块中使用extern对之声明
然后我们在自己的模块中,调用该函数之前,声明此函数,声明如下
extern int kill_proc_info(int, struct siginfo *, pid_t);
重新编译内核和我们的驱动模块
最后我们重新编译内核和我们的驱动模块,我们可以发现我们的
WARNING: "****" [kill_proc_info] undefined!
这个警告消失了,同时我们再次查找kill_proc_info 的信息,我们可以看到,除了EXPORT_SYMBOL的信息,System.map中也多了几项关于kill_proc_info的信息

解决snd_hwdep_new
问题解析
对于snd_hwdep_new 我们采用同kill_proc_info 同样的方法逐个排除其原因

我们发现该函数
声明在includesound/hwdep.c
定义在sound/core/hwdep.c
已经被EXPORT_SYMBOL导出
符号表System.map中没有这个函数
这个说明我们的函数所属的模块在内核编译的过程中,没有被编译进内核中。 这样我们的驱动模块使用它的函数同样找不到地址。
查找所属模块
那么我们来确认一下我们推断的正确性。
我们需要查找到该函数所属的模块,然后到该模块sound/core下,查看Kconfig和Makefile的信息。
首先进入该模块soundcore,源代码文件为hwdep.c,那么目标代码很有可能就叫做hwdep.o,我们看目录下有没有这个目标文件

很明显没有,但是我们并不能保证它就没有参与编译,因为它编译的目标文件也有可能不是hwdep.o , 这个依赖关系我们可以到Makefile中查找。

很明显编译后,的确会生成hwdep.o
而且我们也发现,这个配置信息是CONFIG_HW_DEP
那么我们就去Kconfig中查找这个变量的配置

我们可以发现这个模块属于ALSA SOUND,那么我们就好解决了,可以有多种方案实现
,其实解决方案的本质是一样的,
一个是直接修改配置文件Kconfig,使其编译进去内核
一个是使用make menuconfig或者其他配置工具,选择编译所属模块
我选择了第一种,因为我们现在已经找到了其Kconfig配置所在的位置,直接修改即可,选择第二种,方案的话往往不好取舍。
配置编译
我们在模块配置的地方加入default y
意为在编译内核的时候,直接将模块编译进内核,
这就相当于在make menuconfig的时候在该模块的地方选择y(或者*)

重新编译内核和我们的驱动模块
最后我们重新编译内核和我们的驱动模块,我们可以发现我们的
WARNING: "****" [snd_hwdep_new] undefined!
这个警告消失了,同时我们再次查找snd_hwdep_new 的信息,我们可以看到,除了EXPORT_SYMBOL的信息,System.map中出现关于snd_hwdep_new的信息

总结
出现Unknown symbol in module,其本质就是模块在加载过程中,找不到函数的地址
那么我们就查找System.map和函数声明和定义的地方时候用EXPORT_SYMBOL
然后确认其具体出现原因,一般有两个
①函数未被EXPORT_SYMBOL,导致加载时找不到链接地址
②函数所在模块未被编译到内核中,导致加载时找不到链接地址
我们逐个排查,找出原因所在后,逐个解决即可,完成后重新编译内核已经驱动模块。
但是要注意如果您依赖的函数也是一个驱动模块,则应该首先加载定义该函数的模块,再加载调用该函数的模块
史海拾趣
|
IAR5.3(评估版)编译老是提示如下的错误,是怎么一回事啊 Error[Lp021]: the destination for compressed initializer batch \"P2 mid-1\" is placed at an address that is dependent on the size of the batch, which is not allowed when using packbits compression. Consider using \"initialize by c ...… 查看全部问答> |
|
我用的是2450,问一下LCD横屏转竖屏,驱动程序里除了在头文件处修改分辨率外,还要修改什么地方呢? 我只修改了头文件处定义的分辨率的情况下,屏幕变窄了(部分屏幕黑色没图象),竖直方向靠下的部分没被显示出来。 不知道在哪(几)个文件里的函 ...… 查看全部问答> |
|
下了个WINCE6.0用的GPS软件,为什么EXE文件运行的时候要让我显示打开方式呢? rt 我用的是OMAP3530开发板,内置GPS模块,WINCE6.0环境已经建立,网上下了个GPS软件,EXE格式的用U盘考进去板子里打不开,出现类似于WINDOWS下的让你选择打开方式的窗口,不是EXE是可执行文件吗?而且下的是WINCE6.0可用的版本,谢谢各位了!… 查看全部问答> |
|
现有一外部中断,中断来临后要求驱动马上读取数据,现在问题是:中断来临后,怎么通知用户主动读取数据,现在我用的是使用了中断上下部,下部处理中断,一产生中断马上进入上半部分处理接收,并传送到用户空间,那么此时的读如果在没有 ...… 查看全部问答> |
|
1.安装前须仔细核对型号及规格,指示指针不得偏出零位标记的黑框,否则应重新校验或更换。 硅橡胶电缆 2、仪表应安装于周围环境(或介质)温度-40~ 55℃,相对湿度不大于85%,振动或被测压力的急剧脉动对正确读数等无影响的环境下使用。 3151压力变 ...… 查看全部问答> |
|
这两天,下了个ucos在STM32F103ZE-SK开发板上移植的官方例程(uCOSII-ST-STM32F103ZE-SK),这其中有两个文件夹,u ...… 查看全部问答> |
|
我用STC12LE5A32S2做了个东西。 用定时器0产生定时时间。 定时器是这样的: void Time0(void) interrupt 1 { ET0 = 0; //定时器0中断关 TH0=0xE5;//(65536-n*FOSC/12/1000)/256;//n=10ms 这里采用8M晶振计算的 TL0=0xF5;// ...… 查看全部问答> |




