2024年运维最新Linux EXPORT_SYMBOL宏详解(4),原理讲解

最后的话

最近很多小伙伴找我要Linux学习资料,于是我翻箱倒柜,整理了一些优质资源,涵盖视频、电子书、PPT等共享给大家!

资料预览

给大家整理的视频资料:

给大家整理的电子书资料:

如果本文对你有帮助,欢迎点赞、收藏、转发给朋友,让我有持续创作的动力!

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以点击这里获取!

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

extern typeof(sym) sym;
__CRC_SYMBOL(sym, sec)
static const char __kstrtab_##sym[]
__attribute__((section(“__ksymtab_strings”), aligned(1)))
= VMLINUX_SYMBOL_STR(sym);
static const struct kernel_symbol __ksymtab_##sym
__used
__attribute__((section(“___ksymtab” sec “+” #sym), used))
= { (unsigned long)&sym, __kstrtab_##sym }

#define EXPORT_SYMBOL(sym)
__EXPORT_SYMBOL(sym, “”)

#define EXPORT_SYMBOL_GPL(sym)
__EXPORT_SYMBOL(sym, “_gpl”)

#define EXPORT_SYMBOL_GPL_FUTURE(sym)
__EXPORT_SYMBOL(sym, “_gpl_future”)


C语言的宏定义中 # 和 ## 运算符:  
 #运算符是将其后面的宏参数转化为字符串,用来创建字符串,例如#sym,表示"sym"。  
 ##运算符用来替换粘合两个不同的符号,例如\_\_ksymtab\_##sym,就表示"\_\_ksymtab\_sym"。


**attribute**:\_\_attribute\_\_实际上是GCC的一种编译器命令,用来指示编译器执行实现某些高级操作。\_\_attribute\_\_可以设置函数属性(Function Attribute)、变量属性(Variable Attribute)和类型属性(Type Attribute),函数属性可以帮助开发人员向函数声明中添加一些特性,这可以使编译器在错误检查方面增强。


使用EXPORT\_SYMBOL(export\_function)导出export\_function函数,上述宏可以扩展为:



static const char __kstrtab_export_function[] = “export_function”;
static const struct kernel_symbol __ksymtab_export_function = {(unsigned long)&export_function, __kstrtab_export_function };


第一个变量是一个字符串静态变量,用来表示导出的符号名称"export\_function"。  
 第二个变量类型是struct kernel\_symbol数据结构,用来表示一个内核符号的实例,struct kernel\_symbol的定义为:



// linux-4.10.1/include/linux/export.h

struct kernel_symbol
{
unsigned long value;
const char *name;
};


其中,value是该符号在内存中的地址,name是符号名。所以,由该数据结构可以知道,用EXPORT\_SYMBOL(export\_function)来导出符号"export\_function",实际上是要通过struct kernel\_symbol的一个对象告诉外部关于这个符号的两点信息:符号名称和地址。这样使得内核根据函数的字符串名称,即可找到匹配的代码地址,在解决未定义的引用时需要这要做。


因此,由EXPORT\_SYMBOL等宏导出的符号,与一般的变量定义并没有实质性的差异,唯一的不同点在于它们被放在了特定的section中。


上面的符号"export\_function"会放在"\_\_ksymtab\_strings"的section中,struct kernel\_symbol \_\_ksymtab\_export\_function会放在  
 “\_\_ksymtab"的section中。对于EXPORT\_SYMBOL\_GPL和EXPORT\_SYMBOL\_GPL\_FUTURE而言,其struct kernel\_symbol实例所在的section名称则分别为”\_\_ksymtab\_gpl"和"\_\_ksymtab\_gpl\_future")。


我已 nf\_conntrack.ko 模块为例子:



readelf -S nf_conntrack.ko


![在这里插入图片描述](https://img-blog.csdnimg.cn/3af7c1f376f24e31a566dc9011c7144c.png)  
 对这些section的使用需要经过一个中间环节,即链接脚本与链接器部分。链接脚本告诉链接器把所有目标文件中的名为“\_\_ksymtab”的section放置在最终内核(或者是内核模块)映像文件的名为“\_\_ksymtab”的section中(对于目标文件中的名为“\_\_ksymtab\_gpl”、“\_\_ksymtab\_gpl\_future”、“\_\_kcrctab”、“\_\_kcrctab\_gpl”和“\_\_kcrctab\_gpl\_future”的section都同样处理)。


如下所示:



// linux-4.10.1/include/asm-generic/vmlinux.lds.h

/* Kernel symbol table: Normal symbols */
__ksymtab : AT(ADDR(__ksymtab) - LOAD_OFFSET) {
VMLINUX_SYMBOL(__start___ksymtab) = .;
KEEP(*(SORT(___ksymtab+*)))
VMLINUX_SYMBOL(__stop___ksymtab) = .;
}

/* Kernel symbol table: GPL-only symbols */
__ksymtab_gpl : AT(ADDR(__ksymtab_gpl) - LOAD_OFFSET) {
VMLINUX_SYMBOL(__start___ksymtab_gpl) = .;
KEEP(*(SORT(___ksymtab_gpl+*)))
VMLINUX_SYMBOL(__stop___ksymtab_gpl) = .;
} \

......
								\
/\* Kernel symbol table: GPL-future-only symbols \*/		\
__ksymtab_gpl_future : AT(ADDR(__ksymtab_gpl_future) - LOAD_OFFSET) { \
	VMLINUX\_SYMBOL(__start___ksymtab_gpl_future) = .;	\
	KEEP(\*(SORT(___ksymtab_gpl_future+\*)))			\
	VMLINUX\_SYMBOL(__stop___ksymtab_gpl_future) = .;	\
}								\
								\
/\* Kernel symbol table: Normal symbols \*/			\
__kcrctab         : AT(ADDR(__kcrctab) - LOAD_OFFSET) {		\
	VMLINUX\_SYMBOL(__start___kcrctab) = .;			\
	KEEP(\*(SORT(___kcrctab+\*)))				\
	VMLINUX\_SYMBOL(__stop___kcrctab) = .;			\
}								\
								\
/\* Kernel symbol table: GPL-only symbols \*/			\
__kcrctab_gpl     : AT(ADDR(__kcrctab_gpl) - LOAD_OFFSET) {	\
	VMLINUX\_SYMBOL(__start___kcrctab_gpl) = .;		\
	KEEP(\*(SORT(___kcrctab_gpl+\*)))				\
	VMLINUX\_SYMBOL(__stop___kcrctab_gpl) = .;		\
}								\
								\
......
								\
/\* Kernel symbol table: GPL-future-only symbols \*/		\
__kcrctab_gpl_future : AT(ADDR(__kcrctab_gpl_future) - LOAD_OFFSET) { \
	VMLINUX\_SYMBOL(__start___kcrctab_gpl_future) = .;	\
	KEEP(\*(SORT(___kcrctab_gpl_future+\*)))			\
	VMLINUX\_SYMBOL(__stop___kcrctab_gpl_future) = .;	\
}								\
								\
/\* Kernel symbol table: strings \*/				\
    __ksymtab_strings : AT(ADDR(__ksymtab_strings) - LOAD_OFFSET) {	\
	\*(__ksymtab_strings)					\
}								\

这里之所以要把所有向外界导出的符号统一放到一个特殊的section里面,是为了在加载其他模块时用来处理那些“未解决的引用”符号。上述由链接脚本定义的几个变量\_\_start\_\_\_ksymtab、\_\_stop\_\_\_ksymtab、\_\_start\_\_\_ksymtab\_gpl、\_\_stop\_\_\_ksymtab\_gpl、\_\_start\_\_\_ksymtab\_gpl\_future、\_\_stop\_\_\_ksymtab\_gpl\_future等,它们会在对内核或者是某一内核模块的导出符号表进行查找时用到。


内核源码中为使用这些链接器产生的变量作了如下的声明:



// linux-4.10.1/kernel/module.c

/* Provided by the linker */
extern const struct kernel_symbol __start___ksymtab[];
extern const struct kernel_symbol __stop___ksymtab[];
extern const struct kernel_symbol __start___ksymtab_gpl[];
extern const struct kernel_symbol __stop___ksymtab_gpl[];
extern const struct kernel_symbol __start___ksymtab_gpl_future[];
extern const struct kernel_symbol __stop___ksymtab_gpl_future[];
extern const s32 __start___kcrctab[];
extern const s32 __start___kcrctab_gpl[];
extern const s32 __start___kcrctab_gpl_future[];


内核代码便可以直接使用这些变量而不会引起编译错误。内核模块的加载器在处理模块中“未解决的引用”的符号时,会使用到这里定义的这些变量。


## 三、模块导出符号


由前面我们可以知道模块不仅可以使用内核或者其他模块导出的符号,而且可以向外部导出自己的符号,模块导出符号使用的宏和内核导出符号所使用的完全一样:EXPORT\_SYMBOL、EXPORT\_SYMBOL\_GPL和EXPORT\_SYMBOL\_FUTURE。


内核模块会把导出的符号分别放到“\_\_ksymtab”、“\_\_ksymtab\_gpl”和“\_\_ksymtab\_gpl\_future”section中。如果一个内核模块向外界导出了自己的符号,那么将由模块的编译工具链负责生成这些导出符号section,而且这些section都带有A标志,所以在模块加载过程中会被搬移到CORE section区域中。如果模块没有向外界导出任何符号,那么在模块的ELF文件中,将不会产生这些section。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/87fb846b5fc749318ed93cc331a8136b.png)  
 备注:A (alloc),表示 Section 的属性,alloc(SHF\_ALLOC) 表示 Section 在模块运行期间需要占据内存。  
 没有SHF\_ALLOC标志的section,这样的section最终不占有实际内存地址。


显然,内核需要对模块导出的符号进行管理,以便在处理其他模块中那些“未解决的引用”符号时能够找到这些符号。内核对模块导出的符号的管理使用到了struct module结构中如下的成员变量。


内核中为每一个内核模块都分配了一个struct module实例:



// linux-4.10.1/include/linux/module.h

struct module {

/* Exported symbols */
const struct kernel_symbol *syms; //内核模块导出的符号所在起始地址
const s32 *crcs; //内核模块导出符号的校验码所在起始地址
unsigned int num_syms; //核模块导出的符号的数目

/* GPL-only exported symbols. */
unsigned int num_gpl_syms;
const struct kernel_symbol *gpl_syms;
const s32 *gpl_crcs;

/* symbols that will be GPL-only in the near future. */
const struct kernel_symbol *gpl_future_syms;
const s32 *gpl_future_crcs;
unsigned int num_gpl_future_syms;

}


以struct module结构体的 Exported symbols 为例:  
 const struct kernel\_symbol \*syms表示:内核模块导出的符号所在起始地址。  
 const s32 \*crcs表示:内核模块导出符号的校验码所在起始地址。  
 unsigned int num\_syms:表示内核模块导出的符号的数目。


syms是一个数组,有num\_syms个数组项,数组的类型是struct kernel\_symbol,负责将表示符name分配到内存地址value。  
 crcs也是一个有num\_syms个数组项的数组,存储了导出符号的校验和,用于实现版本控制。



// linux-4.10.1/include/linux/export.h

struct kernel_symbol
{
unsigned long value;
const char *name;
};


内核通过调用 find\_module\_sections函数在Section header table的查找,获得“\_\_ksymtab”、“\_\_ksymtab\_gpl”和“\_\_ksymtab\_gpl\_future”section在CORE section中的地址,将其记录在mod->syms、mod->crcs、mod->gpl\_syms、mod->gpl\_crcs、mod->gpl\_future\_syms、mod->gpl\_future\_crcs中,其中 syms表示:内核模块导出的符号所在起始地址,crcs表示:内核模块导出符号的校验码所在起始地址。如下所示:



// linux-4.10.1/kernel/module.c

/* Find a module section: 0 means not found. */
static unsigned int find_sec(const struct load_info *info, const char *name)
{
unsigned int i;

for (i = 1; i < info->hdr->e_shnum; i++) {
	Elf_Shdr \*shdr = &info->sechdrs[i];
	/\* Alloc bit cleared means "ignore it." \*/
	if ((shdr->sh_flags & SHF_ALLOC)
	    && strcmp(info->secstrings + shdr->sh_name, name) == 0)
		return i;
}
return 0;

}

/* Find a module section, or NULL. Fill in number of “objects” in section. */
static void *section_objs(const struct load_info *info,
const char *name,
size_t object_size,
unsigned int *num)
{
unsigned int sec = find_sec(info, name);

最后的话

最近很多小伙伴找我要Linux学习资料,于是我翻箱倒柜,整理了一些优质资源,涵盖视频、电子书、PPT等共享给大家!

资料预览

给大家整理的视频资料:

给大家整理的电子书资料:

如果本文对你有帮助,欢迎点赞、收藏、转发给朋友,让我有持续创作的动力!

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以点击这里获取!

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

]

如果本文对你有帮助,欢迎点赞、收藏、转发给朋友,让我有持续创作的动力!

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以点击这里获取!

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值