版权声明:本文为CSDN博主「ashimida@」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/lidan113lidan/article/details/119743237更多内容可关注微信公众号
几年前写过一篇内核模块加载的文章<内核模块的加载> ,最近又基于5.2内核重新读了一遍模块加载的流程,此文则结合一些新的认知重新整理而成.
1.内核模块的编译分为两个阶段,可分别称为stage1和stage2:
2.mod.c文件的内容
tangyuan@ubuntu:~/qemu_aarch64_device/linux-4.19.109$ strings lib/xxhash.ko |grep vermagic
vermagic=4.19.109-g6c232927e-dirty SMP preempt mod_unload modversions aarch64
* 在.modinfo段增加字符串变量name,其内容为当前模块名,如:
tangyuan@ubuntu:~/qemu_aarch64_device/linux-4.19.109$ strings ./net/ipv6/ipv6.ko|grep "name="
name=ipv6
* struct module __this_modules,此结构体是一个内置的struct modules,其中记录着模块的初始化函数等信息
//*.mod.c文件都拥有相同的文件头,生成此头文件的代码在./scripts/mod/modpost.c中
#include
#include
#include
#include
BUILD_SALT;
//MODULE_INFO(tag,name)宏的作用是在.modinfo段添加变量 字符串变量tag = "tag = info"
//VERMAGIC_STRING为内核版本信息
MODULE_INFO(vermagic, VERMAGIC_STRING);
//KBUILD_MODNAME是cc时传入的参数,其在Makefile.lib中定义:
//modname_flags = -DKBUILD_MODNAME=$(call name-fix,$(modname))
MODULE_INFO(name, KBUILD_MODNAME);
__visible struct module __this_module
__attribute__((section(".gnu.linkonce.this_module"))) = {
.name = KBUILD_MODNAME,
.init = init_module,
.arch = MODULE_ARCH_INIT,
};
//*.mod.c文件都拥有相同的文件头,生成此头文件的代码在./scripts/mod/modpost.c中
MODULE_INFO(intree, "Y");
也就是在.modinfo段增加了字符串变量intree,其内容为"intree=Y"(对于out-tree的模块直接没有此字段)
static const struct modversion_info ____versions[]
__used __attribute__((section("__versions"))) = {
{ 0xe57c29f2, "module_layout" },
{ 0x633475c7, "static_key_enable" },
{ 0x2cdf87a1, "proc_dointvec_minmax" },
{ 0x609f1c7e, "synchronize_net" },
{ 0xa0c9d45, "inet_peer_base_init" },
......
};
内核模块为____versions数组单独分配了一个段__versions,需要注意的是____versions数组中存储的实际上都是当前模块中未定义的,但在内核或其他模块中已定义的符号(若二者都没定义则报错了),____versions数组的作用是在模块装载时确定其未定义符号(导入符号)是否与内核中此符号的定义匹配,故其中并不需要保存当前模块的任何其他符号(如导出符号,EXPORT_SYMBOL_XXX)的信息(导出符号的信息是保存在模块单独的段中的,参考4)
static const char __module_depends[] = "depends=module1,module2,..."
注意这里的字符串名为 __module_depends,但.modinfo中的字段为depends
3. CRC的生成
#define EXPORT_SYMBOL(sym) __EXPORT_SYMBOL(sym, "")
#define ___EXPORT_SYMBOL(sym, sec) \
extern typeof(sym) sym; \
__CRC_SYMBOL(sym, sec) \
static const char __kstrtab_##sym[] __attribute__((section("__ksymtab_strings"), used, aligned(1))) = #sym;\
__KSYMTAB_ENTRY(sym, sec)
#if defined(CONFIG_MODULE_REL_CRCS)
......
#else
#define __CRC_SYMBOL(sym, sec) \
asm(" .section \"___kcrctab" sec "+" #sym "\", \"a\" \n" \
" .weak __crc_" #sym " \n" \
" .long __crc_" #sym " \n" \
" .previous \n");
#endif
#ifdef CONFIG_HAVE_ARCH_PREL32_RELOCATIONS
......
#else
#define __KSYMTAB_ENTRY(sym, sec) \
static const struct kernel_symbol __ksymtab_##sym \
__attribute__((section("___ksymtab" sec "+" #sym), used)) \
= { (unsigned long)&sym, __kstrtab_##sym }
struct kernel_symbol {
unsigned long value;
const char *name;
};
#endif
EXPORT_SYMBOL(x)中,x是内核的一个函数或变量,此宏实际上做了三件事情:
tangyuan@ubuntu:~/qemu_aarch64_device/linux-4.19.109/net/ipv6$ readelf -S --wide ipv6.o |grep -E "inet6_getname|ksymtab_string"|grep -v rela
[27] ___kcrctab+inet6_getname PROGBITS 0000000000000000 042498 000004 00 A 0 0 1
[29] ___ksymtab+inet6_getname PROGBITS 0000000000000000 0424a0 000008 00 A 0 0 8
[57] __ksymtab_strings PROGBITS 0000000000000000 047094 0005da 00 A 0 0 1
tangyuan@ubuntu:~/qemu_aarch64_device/linux-4.19.109/net/ipv6$ readelf -s --wide ipv6.o |grep -E "inet6_getname"
209: 0000000000000000 0 NOTYPE LOCAL DEFAULT 29 __ksymtab_inet6_getname
210: 0000000000000066 14 OBJECT LOCAL DEFAULT 57 __kstrtab_inet6_getname
1835: 00000000ae0b6bd1 0 NOTYPE GLOBAL DEFAULT ABS __crc_inet6_getname
2292: 00000000000000c0 296 FUNC GLOBAL DEFAULT 1 inet6_getname //原始符号
__crc_blk_queue_flag_set = 0x564adbd5;
__crc_blk_queue_flag_clear = 0x52d55223;
__crc_blk_queue_flag_test_and_set = 0x5474d529;
__crc_blk_queue_flag_test_and_clear = 0xb029eaf1;
3) LD -r ipv6_old.o -o ipv6_new.o -T .tmp_ipv6.ver
4.内核和模块的导出符号表
./include/asm-generic/vmlinux.lds.h
#define RO_DATA(align) RO_DATA_SECTION(align)
#define RO_DATA_SECTION(align) \
......
__ksymtab : AT(ADDR(__ksymtab) - LOAD_OFFSET) { \
__start___ksymtab = .; \
KEEP(*(SORT(___ksymtab+*))) \
__stop___ksymtab = .; \
}
......
__kcrctab : AT(ADDR(__kcrctab) - LOAD_OFFSET) { \
__start___kcrctab = .; \
KEEP(*(SORT(___kcrctab+*))) \
__stop___kcrctab = .; \
}
......
__ksymtab_strings : AT(ADDR(__ksymtab_strings) - LOAD_OFFSET) { \
*(__ksymtab_strings) \
}
./arch/arm64/kernel/vmlinux.lds.S
SECTIONS
{
......
RO_DATA(PAGE_SIZE)
......
}
./scripts/module-common.lds
SECTIONS {
/DISCARD/ : {
*(.discard)
*(.discard.*)
}
__ksymtab 0 : { *(SORT(___ksymtab+*)) }
__ksymtab_gpl 0 : { *(SORT(___ksymtab_gpl+*)) }
__ksymtab_unused 0 : { *(SORT(___ksymtab_unused+*)) }
__ksymtab_unused_gpl 0 : { *(SORT(___ksymtab_unused_gpl+*)) }
__ksymtab_gpl_future 0 : { *(SORT(___ksymtab_gpl_future+*)) }
__kcrctab 0 : { *(SORT(___kcrctab+*)) }
__kcrctab_gpl 0 : { *(SORT(___kcrctab_gpl+*)) }
__kcrctab_unused 0 : { *(SORT(___kcrctab_unused+*)) }
__kcrctab_unused_gpl 0 : { *(SORT(___kcrctab_unused_gpl+*)) }
__kcrctab_gpl_future 0 : { *(SORT(___kcrctab_gpl_future+*)) }
.init_array 0 : ALIGN(8) { *(SORT(.init_array.*)) *(.init_array) }
__jump_table 0 : ALIGN(8) { KEEP(*(__jump_table)) }
}
可以看出,二者实际上都将所有的导出符号表段(___ksymtab+x)合并为一个__ksymtab段,所有的crc段合并为一个__kcrctab段,输出到最终的vmlinux[.o]或ko中了(gpl等其他段同理,这里先忽略)。
//模块
tangyuan@ubuntu:~/qemu_aarch64_device/linux-4.19.109/net/ipv6$ readelf -S --wide ipv6.ko |grep -E "inet6_getname|ksymtab_string"|grep -v rela
[26] __ksymtab_strings PROGBITS 0000000000000000 04745c 0005da 00 A 0 0 1
tangyuan@ubuntu:~/qemu_aarch64_device/linux-4.19.109/net/ipv6$ readelf -S --wide ipv6.ko |grep -E "ksymtab|crc"|grep -v rela
[10] __ksymtab PROGBITS 0000000000000000 03f2d0 000128 00 A 0 0 8
[12] __ksymtab_gpl PROGBITS 0000000000000000 03f3f8 000160 00 A 0 0 8
[14] __kcrctab PROGBITS 0000000000000000 03f558 000094 00 A 0 0 1
[16] __kcrctab_gpl PROGBITS 0000000000000000 03f5ec 0000b0 00 A 0 0 1
[26] __ksymtab_strings PROGBITS 0000000000000000 04745c 0005da 00 A 0 0 1
tangyuan@ubuntu:~/qemu_aarch64_device/linux-4.19.109/net/ipv6$ readelf -s --wide ipv6.ko |grep -E "inet6_getname|ksymtab_string"|grep -v rela
57: 0000000000000028 0 NOTYPE LOCAL DEFAULT 10 __ksymtab_inet6_getname
58: 0000000000000066 14 OBJECT LOCAL DEFAULT 26 __kstrtab_inet6_getname
1592: 00000000ae0b6bd1 0 NOTYPE GLOBAL DEFAULT ABS __crc_inet6_getname
2049: 00000000000000c0 296 FUNC GLOBAL DEFAULT 2 inet6_getname
//内核
tangyuan@ubuntu:~/qemu_aarch64_device/linux-4.19.109$ readelf -S vmlinux|grep -E "ksymtab|crc"
[ 5] __ksymtab PROGBITS ffff000008f90550 00f20550
[ 6] __ksymtab_gpl PROGBITS ffff000008f99d88 00f29d88
[ 7] __kcrctab PROGBITS ffff000008fa4e28 00f34e28
[ 8] __kcrctab_gpl PROGBITS ffff000008fa9a44 00f39a44
[ 9] __ksymtab_strings PROGBITS ffff000008faf294 00f3f294
5.模块的导入符号表
6.关于3-5的总结:
文件格式 |
DLL/PIE/PDE
|
static PDE
|
relocatable
|
内核vmlinux[.o](relocatable)
|
模块*.ko(relocatable)
|
静态链接符号表
|
.symtab
|
.symtab
|
.symtab
|
.symtab
|
.symtab
|
动态链接符号表
|
.dynsym
|
无
|
无
|
无
|
无
|
导入表
|
.dynsym中非UND符号
|
正常无UND符号,为空 |
未链接,无此概念
|
正常无UND符号,为空
|
.symtab中的UND符号
|
导出表
|
.dynsym中UND符号
|
无
|
未链接,无此概念
|
__ksymtab
|
__ksymtab
|
内存符号表
|
无
|
无
|
无
|
kallsyms_names
......
|
struct module.core_kallsyms
struct module.init_kallsyms
|
内存导出表
|
无
|
无
|
无
|
[__start___ksymtab,__stop___ksymtab]
......
|
struct module.syms
struct module.gpl_syms
|
导出表访问函数
|
无
|
无
|
无
|
find_symbol
|
find_symbol
|
内存符号表访问函数
|
无
|
无
|
我
|
kallsyms_lookup_name
|
kallsyms_lookup_name
|