【开发日常】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)

相关推荐
IC 见路不走1 小时前
LeetCode 第91题:解码方法
linux·运维·服务器
翻滚吧键盘2 小时前
查看linux中steam游戏的兼容性
linux·运维·游戏
小能喵2 小时前
Kali Linux Wifi 伪造热点
linux·安全·kali·kali linux
汀沿河2 小时前
8.1 prefix Tunning与Prompt Tunning模型微调方法
linux·运维·服务器·人工智能
zly35002 小时前
centos7 ping127.0.0.1不通
linux·运维·服务器
小哥山水之间3 小时前
基于dropbear实现嵌入式系统ssh服务端与客户端完整交互
linux
ldj20203 小时前
2025 Centos 安装PostgreSQL
linux·postgresql·centos
翻滚吧键盘3 小时前
opensuse tumbleweed上安装显卡驱动
linux
cui_win4 小时前
【内存】Linux 内核优化实战 - net.ipv4.tcp_tw_reuse
linux·网络·tcp/ip
CodeWithMe7 小时前
【Note】《深入理解Linux内核》 Chapter 15 :深入理解 Linux 页缓存
linux·spring·缓存