【开发日常】insmod: error inserting ‘*.ko‘: -1 Unknown symbol in module原理分析

问题的起源是一次面试,面试官询问加载内核的时候,如果insmod失败,且提示Unknown symbol in module。请问我里面的原理是什么呢?为什么内核知道当前缺少的是这个symbol?

想了解下具体的原因。


首先是模拟一个环境。

写一个module模块,里面调用了其他模块的符号表。同时给予函数声明。在编译的阶段,不会报错,但是在加载KO的时候,报错了。提示"Unknown symbol in module"


原因和内核符号表有关。

linux2.6开始的"/prob/kallsyms"文件对应着内核符号表,记录了符号以及符号所在的内存地址。

内核符号表:记录了内核中所有的符号(函数、全局变量等)的地址以及名字,这个符号表被嵌入到内核镜像中,使得内核可以在运行过程中随时获得一个符号地址对应的符号名

在Linux内核中,驱动程序和内核的其他部分之间是通过叫做symbol(符号)的接口来通信的,一个symbol可以是一个函数、一个全局变量或其他对象等等。默认情况下,这些symbol只能在内核中使用,因此,它们不会被其他程序访问。如果想要将symbol导出给外部模块使用,可以使用EXPORT_SYMBOL和EXPORT_SYMBOL_GPL宏来实现。

模块可以使用如下宏导出符号到内核符号表:

EXPORT_SYMBOL(符号名);  
EXPORT_SYMBOL_GPL(符号名)  

导出的符号可以被其他模块使用,不过使用之前一定要声明一下。

EXPORT_SYMBOL和EXPORT_SYMBOL_GPL的区别在于,EXPORT_SYMBOL_GPL只导出给使用了GPL或可以互操作的许可证的模块使用。由于这些许可证限制了使用这些符号的模块的范围,因此EXPORT_SYMBOL_GPL被认为比EXPORT_SYMBOL更加安全。

代码演示:

//hello.c文件,定义2个函数,用于导出  
#include <<a href="http://lib.csdn.net/base/linux" class='replace_word' title="Linux知识库" target='_blank' style='color:#df3434; font-weight:bold;'>Linux</a>/init.h>  
#include <linux/module.h>  
MODULE_LICENSE("Dual BSD/GPL");  
int add_integar(int a,int b)  
{  
    return a + b;  
}  
int sub_integar(int a,int b)  
{  
    return a - b;  
}  
EXPORT_SYMBOL(add_integar);  
EXPORT_SYMBOL(sub_integar);  
//test.c 用于调用hello模块导出的函数  
#include <linux/init.h>  
#include <linux/module.h>  
MODULE_LICENSE("Dual BSD/GPL");  
extern int add_integar(int ,int); //声明要调用的函数  
extern int sub_integar(int ,int); //声明要调用的函数  
int result(void)  
{  
    int a,b;  
    a = add_integar(1,1);  
    b = sub_integar(1,1);  
      
    printk("%d/n",a);  
    printk("%d/n",b);  
      
    return 0;  
}  

make后,先加在hello模块,再加载test模块。

然后cat /proc/kallsyms | grep integer

显示:

[root@localhost test]# cat /proc/kallsyms |grep integar  
e053d000 u add_integar  [test]  
e053d004 u sub_integar  [test]  
e053d02c r __ksymtab_sub_integar        [hello]  
e053d03c r __kstrtab_sub_integar        [hello]  
e053d034 r __ksymtab_add_integar        [hello]  
e053d048 r __kstrtab_add_integar        [hello]  
e053d000 T add_integar  [hello]  
e053d004 T sub_integar  [hello]  

再深入了解,什么是内核符号表?

一、什么是符号(Symbols)?

什么是Symbol? 其实就是kernel中的变量(Variable Name)或函数名称(Function Name)

这样可以方便程序员在写程序时可以直接参照这一份Symbol的索引文件,找到所需要的kernel信息,这一份Symbol的索引文件又称为kernel symbol table。

二、内核符号表(Kernel Symbol Table)?

定义

内核符号表,就是在内核的内部函数或变量中,可供外部引用的函数和变量的符号表。

其实说白了就是一个索引文件,它存在的目的就是让外部软件可以知道kernel文件内部实际分配的位置。

所在文件

编译内核时,System.map文件用于存放内核符号表信息。

(System.map文件位于/或者/boot、/usr/src/linux/下)

文件的生成

符号表是所有内核符号及其对应地址的一个列表,随着每次内核的编译,就会产生一个新的对应System.map文件,当内核运行出错时,通过System.map中的符号表解析,就可以查到一个地址值对应的变量名。System.map文件记录了所有代码的运行地址(所有函数和变量)。

内核并不使用符号名。它是通过变量或函数的地址(指针)来使用变量或函数的。其实内核连System.map文件都不使用,只是生成用于调试用的。

system.map文件的格式

线性地址 类型 符号
ffffffff81002590 t create_dev
ffffffff81009c00 T show_regs

其中,类型 若是小写字母则表示局部引用, 若是大写字母则表示全局引用(外部)。

内核符号表的存在意义

1)把内核的符号地址,转化为程序猿可以看懂的符号名称

内核符号表就是为程序员通过符号来访问程序体的对应地址(指针),建立了一个动态的,可变更的映射表格。

例如:

内核不会使用符号名create_dev,而会使用ffffffff81002590来引用这个变量。但程序猿们更喜欢符号名create_dev这样直观的名字。

所以就需要这么一个对应关系表。这个类似于网络里的DNS,没有谁愿意去记那没规律的IP地址,而记个网址就好记多了。

2)转换之后,就可以更方便的调试内核代码

对于系统的oop消息、或者通过gdb的调试消息,都需要根据该对照表,将内核熟悉的函数地址转化为用户熟悉的函数名称,便于用户进行故障定位、运行监控。

而内核本身并不真正使用System.map,而只是用于做调试用。

2.6)内核符号表存储位置

System.map

磁盘中真实存在的文件,存储内核中静态编译的函数和变量地址,每个新编译内核对应一个System.map文件,当klogd输出内核消息时,会通过/boot/System.map来将函数、变量地址转换为名称,方便用户理解。该文件对应不同的编译内核有对应的实现文件。

/proc/kallsyms

内核启动时候创建,供oops时定位错误,文件大小总为0,包含当前内核导出的、可供使用的变量或者函数;它只是内核数据的简单表示形式。

System.map 和 /proc/kallsyms 区别

二者相似点:

都是内核函数、变量的符号表,结构一致;对于可导出的内核变量、函数,其运行时在物理内存中的位置是一样的。

二者区别

两者侧重点不同,

System.map文件面向内核,对于内核中的没有导出的变量或者函数名,比如kthread_create_list链表头指针,也有其相应的内核地址,该文件一般是只读的、固定大小的,没有动态添加模块中的变量、函数名;

而kallsyms在内核启动过程中创建,并实时更新,反映的是系统的当前最新情况,其内部也包含内核或者是已加载模块导出的函数、变量名称。

所以和System.map文件有差别,kallsyms文件动态变化,大小不固定。

总结就是:

System.map文件较单纯,是在用户一开始编译就产生的固定文件,不会因为任何原因更改,除非被换掉。

而/proc/kallsyms是一个在启动时由Linux kernel实时产生的文件,当系统有任何变更时,它就会马上做出修正。

因为这是动态的信息,当用户新增或删除一个module,都会自动做实时的修正(/proc下的都是这一类型的文件)

分类

Linux内核的符号表位于两个部分。

首先是内核的静态部分,也就是内核文件映像vmlinuz部分的符号表,对应于/proc/kallsyms和System.map这两个文件。

还有一部分则是Linux可配置模块部分的符号表。

形成过程

Linux内核符号表/proc/kallsyms的形成过程

./scripts/kallsyms.c负责生成System.map

./kernel/kallsyms.c负责生成/proc/kallsyms

./scripts/kallsyms.c解析vmlinux(.tmp_vmlinux)生成kallsyms.S(.tmp_kallsyms.S),然后内核编译过程中将kallsyms.S(内核符号表)编入内核镜像uImage 内核启动后./kernel/kallsyms.c解析uImage形成/proc/kallsyms

要在一个内核中启用 kallsyms 功能,必须用进行内核配置,make menuconfig设置 CONFIG_KALLSYMS 选项为y;如果你要在 kallsyms 中包含全部符号信息,必须设置 CONFIG_KALLSYMS_ALL 为y。

三、导出符号表

驱动程序中,如果该驱动程序中有被其他内核代码调用的部分,可以用EXPORT_SYMBOL导出到内核符号表中。

参考:

http://blog.csdn.net/lisan04/article/details/4076013

Linux内核:符号表详解 - 知乎 (zhihu.com)

相关推荐
冷曦_sole1 分钟前
linux-21 目录管理(一)mkdir命令,创建空目录
linux·运维·服务器
最后一个bug3 分钟前
STM32MP1linux根文件系统目录作用
linux·c语言·arm开发·单片机·嵌入式硬件
卫生纸不够用22 分钟前
子Shell及Shell嵌套模式
linux·bash
world=hello34 分钟前
关于科研中使用linux服务器的集锦
linux·服务器
soragui1 小时前
【ChatGPT】OpenAI 如何使用流模式进行回答
linux·运维·游戏
白云coy2 小时前
Redis 安装部署[主从、哨兵、集群](linux版)
linux·redis
Logintern092 小时前
Linux如何设置redis可以外网访问—执行使用指定配置文件启动redis
linux·运维·redis
娶不到胡一菲的汪大东2 小时前
Linux之ARM(MX6U)裸机篇----1.开发环境搭建
linux·运维·服务器
fat house cat_2 小时前
Linux环境下使用tomcat+nginx部署若依项目
linux·nginx·tomcat