版权声明:本文为CSDN博主「ashimida@」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/lidan113lidan/article/details/119813552更多内容可关注微信公众号
上接<再谈内核模块加载(二)—模块加载流程(上)>
err = add_unformed_module(mod);
if (err)
goto free_module;
unformed是指加载到一半(也就是执行完当前函数),但还没有完全加载好的模块,此函数根据this_module.name检测当前系统里是否有同名模块:
static int add_unformed_module(struct module *mod)
{
int err;
struct module *old;
mod->state = MODULE_STATE_UNFORMED;
again:
mutex_lock(&module_mutex);
//查找当前内核是否已经有同名模块了,非正式的也算
old = find_module_all(mod->name, strlen(mod->name), true);
if (old != NULL) {
//若此模块是正在执行module_init,或和当前同状态的模块
if (old->state == MODULE_STATE_COMING
|| old->state == MODULE_STATE_UNFORMED) {
/* Wait in case it fails to load. */
mutex_unlock(&module_mutex);
//则等待模块加载完毕,重新跑
err = wait_event_interruptible(module_wq,
finished_loading(mod->name));
if (err)
goto out_unlocked;
goto again;
}
//若非以上两个状态,代表同名模块已经存在了,注意这里模块的名字是mod->name
err = -EEXIST;
goto out;
}
//更新系统已有模块的VA地址边界
mod_update_bounds(mod);
list_add_rcu(&mod->list, &modules);
//在模块树中更新此模块
mod_tree_insert(mod);
err = 0;
out:
mutex_unlock(&module_mutex);
out_unlocked:
return err;
}
10.为模块签名失败但放过的情况提示warning
#ifdef CONFIG_MODULE_SIG
/*
若模块签名校验失败,则提示warning设置taint
如果要求强制模块签名,前面module_sig_check就直接break了
*/
mod->sig_ok = info->sig_ok;
if (!mod->sig_ok) {
pr_notice_once("%s: module verification failed: signature "
"and/or required key missing - tainting "
"kernel\n", mod->name);
add_taint_module(mod, TAINT_UNSIGNED_MODULE, LOCKDEP_STILL_OK);
}
#endif
err = percpu_modalloc(mod, info);
if (err)
goto unlink_mod;
/*
没开启CONFIG_MODULE_UNLOAD函数(代表不允许模块卸载)则这里为空
开启了这里也只是个初始化函数
*/
err = module_unload_init(mod);
if (err)
goto unlink_mod;
//sysfs的mutex初始话
init_param_lock(mod);
这里要注意,模块除了init_layout和core_layout外,还单独为percpu变量分配了内存,记录在mod.percpu中
12.内存布局中struct module的初始化与检查
err = find_module_sections(mod, info);
if (err)
goto free_unload;
err = check_module_license_and_versions(mod);
if (err)
goto free_unload;
find_module_sections 函数获取模块布局中节区的首地址返回到mod的各个字段中(如mod->kp),同时返回节区中的元素个数(如mod->num_kp),具体含义参考struct module结构体的定义,这里查找模块布局的方法实际上还是遍历模块内存二进制的节区头部表,但由于在move_module函数中分配模块布局内存时已经将节区头部表中的sh_addr修正为内存布局中此节区的地址,故这里返回的也是内存布局中的地址.
static int find_module_sections(struct module *mod, struct load_info *info)
{
/*
输出参数sizeof(*mod->kp)指定一个元素的大小,返回值为内存布局中此节区的首地址
节区内元素个数记录重写回到mod->num_kp中.
*/
mod->kp = section_objs(info, "__param",
sizeof(*mod->kp), &mod->num_kp);
mod->syms = section_objs(info, "__ksymtab",
sizeof(*mod->syms), &mod->num_syms);
mod->crcs = section_addr(info, "__kcrctab");
mod->gpl_syms = section_objs(info, "__ksymtab_gpl",
sizeof(*mod->gpl_syms),
&mod->num_gpl_syms);
mod->gpl_crcs = section_addr(info, "__kcrctab_gpl");
mod->gpl_future_syms = section_objs(info,
"__ksymtab_gpl_future",
sizeof(*mod->gpl_future_syms),
&mod->num_gpl_future_syms);
mod->gpl_future_crcs = section_addr(info, "__kcrctab_gpl_future");
#ifdef CONFIG_UNUSED_SYMBOLS
mod->unused_syms = section_objs(info, "__ksymtab_unused",
sizeof(*mod->unused_syms),
&mod->num_unused_syms);
mod->unused_crcs = section_addr(info, "__kcrctab_unused");
mod->unused_gpl_syms = section_objs(info, "__ksymtab_unused_gpl",
sizeof(*mod->unused_gpl_syms),
&mod->num_unused_gpl_syms);
mod->unused_gpl_crcs = section_addr(info, "__kcrctab_unused_gpl");
#endif
#ifdef CONFIG_CONSTRUCTORS
mod->ctors = section_objs(info, ".ctors",
sizeof(*mod->ctors), &mod->num_ctors);
if (!mod->ctors)
mod->ctors = section_objs(info, ".init_array",
sizeof(*mod->ctors), &mod->num_ctors);
else if (find_sec(info, ".init_array")) {
/*
* This shouldn't happen with same compiler and binutils
* building all parts of the module.
*/
pr_warn("%s: has both .ctors and .init_array.\n",
mod->name);
return -EINVAL;
}
#endif
check_module_license_and_versions函数主要用来过滤一些模块,并检查CRC表是否正确. 此函数首先通过模块名过滤了一些模块,然后在内核定义了CONFIG_MODVERSIONS的情况下(代表要检查模块中函数的CRC),如果模块有某类型的导出函数,则要有此类型的crc表,没有则报错。
static int check_module_license_and_versions(struct module *mod)
{
int prev_taint = test_taint(TAINT_PROPRIETARY_MODULE);
/*
这里还单独加了case....
*/
if (strcmp(mod->name, "ndiswrapper") == 0)
add_taint(TAINT_PROPRIETARY_MODULE, LOCKDEP_NOW_UNRELIABLE);
/* driverloader was caught wrongly pretending to be under GPL */
if (strcmp(mod->name, "driverloader") == 0)
add_taint_module(mod, TAINT_PROPRIETARY_MODULE,
LOCKDEP_NOW_UNRELIABLE);
/* lve claims to be GPL but upstream won't provide source */
if (strcmp(mod->name, "lve") == 0)
add_taint_module(mod, TAINT_PROPRIETARY_MODULE,
LOCKDEP_NOW_UNRELIABLE);
if (!prev_taint && test_taint(TAINT_PROPRIETARY_MODULE))
pr_warn("%s: module license taints kernel.\n", mod->name);
#ifdef CONFIG_MODVERSIONS
/*
这里是通用检查,如果当前模块有导出符号,但导出符号没有CRC,而当前内核又配置了
CONFIG_MODVERSIONS,则代表当前内核被污染了.
*/
if ((mod->num_syms && !mod->crcs)
|| (mod->num_gpl_syms && !mod->gpl_crcs)
|| (mod->num_gpl_future_syms && !mod->gpl_future_crcs)
#ifdef CONFIG_UNUSED_SYMBOLS
|| (mod->num_unused_syms && !mod->unused_crcs)
|| (mod->num_unused_gpl_syms && !mod->unused_gpl_crcs)
#endif
) {
//这里要增加taint标记
return try_to_force_load(mod,
"no versions for exported symbols");
}
#endif
return 0;
}
13.为init_layout中的静态链接符号表做符号决议
err = simplify_symbols(mod, info);
if (err < 0)
goto free_modinfo;
static int simplify_symbols(struct module *mod, const struct load_info *info)
{
//获取模块内存中的符号表,这个是存在init_layout中的,完全复制自目标文件的符号表
Elf_Shdr *symsec = &info->sechdrs[info->index.sym];
Elf_Sym *sym = (void *)symsec->sh_addr;
unsigned long secbase;
unsigned int i;
int ret = 0;
const struct kernel_symbol *ksym;
//遍历符号表
for (i = 1; i < symsec->sh_size / sizeof(Elf_Sym); i++) {
const char *name = info->strtab + sym[i].st_name;
switch (sym[i].st_shndx) {
/*
正常应该是没有common符号的(-fno-common),也就是说.S中不能有.comm定义
正常的未初始化全局变量都直接扔bss段.
*/
case SHN_COMMON:
/* Ignore common symbols */
if (!strncmp(name, "__gnu_lto", 9))
break;
/* We compiled with -fno-common. These are not
supposed to happen. */
pr_debug("Common symbol: %s\n", name);
pr_warn("%s: please compile with -fno-common\n",
mod->name);
ret = -ENOEXEC;
break;
//绝对符号不处理
case SHN_ABS:
/* Don't need to do anything */
pr_debug("Absolute symbol: 0x%08lx\n",
(long)sym[i].st_value);
break;
case SHN_LIVEPATCH:
/* Livepatch symbols are resolved by livepatch */
break;
//未定义符号
case SHN_UNDEF:
/*
此函数负责在内核和所有已加载模块中查找名为name的符号,若找到了返回此符号的符号表项,
并设置ownername为其拥有者的名,如果开启了CRC校验则会同时检测CRC.
模块中的未定义符号实际上是在编译期间引用了编译环境的内核或其他模块中的符号,虽然是
未定义,但其编译时候会记录此符号的CRC,
这里的CRC检查,实际上检查的是模块编译环境符号的CRC和模块运行环境符号的CRC是否一致.
*/
ksym = resolve_symbol_wait(mod, info, name);
/* Ok if resolved. */
if (ksym && !IS_ERR(ksym)) {
//将静态链接符号表中,此符号的值,设置为内核符号中此符号的值
sym[i].st_value = kernel_symbol_value(ksym);
break;
}
/* Ok if weak. */
//如果在内核没有找到此符号,且此未定义符号是一个弱符号,则未定义就未定义了
//弱符号应该在重定位的时候是按照未定义处理的,这样才会找一下是否有重名符号
if (!ksym && ELF_ST_BIND(sym[i].st_info) == STB_WEAK)
break;
//否则就是没找到符号,报错
ret = PTR_ERR(ksym) ?: -ENOENT;
pr_warn("%s: Unknown symbol %s (err %d)\n",
mod->name, name, ret);
break;
default:
/* Divert to percpu allocation if a percpu var. */
if (sym[i].st_shndx == info->index.pcpu)
//percpu变量,则直接使用percpu(段)的首地址
secbase = (unsigned long)mod_percpu(mod);
else
//sym[i].st_shndx是符号表所在的节区,这里获取节区首地址
secbase = info->sechdrs[sym[i].st_shndx].sh_addr;
//符号值直接加上节区首地址
sym[i].st_value += secbase;
break;
}
}
return ret;
}
14.模块内存布局的静态链接重定位
err = apply_relocations(mod, info);
if (err < 0)
goto free_modinfo;
此函数遍历目标文件中的所有内存节区(SHF_ALLOC)的重定位节区(REL/RELA),并遍历每个节区中的每个静态链接重定位表项,对其做静态链接。apply_relocate_add是真正负责重定位的函数,其中一共处理了月40种重定位类型。这里需要说明的是,对于CALL26/JUMP26类型的指令,若短跳转不够有可能会尝试基于plt的长跳转(CONFIG_ARM64_MODULE_PLTS开启情况下,未开启则直接报错),此时会在模块core/init_layout的plt表中增加一个表项.
此函数之后,模块作为目标文件的静态链接动态的完成了.
static int apply_relocations(struct module *mod, const struct load_info *info)
{
unsigned int i;
int err = 0;
/* Now do relocations. */
/*
这里实际上是遍历所有重定位表(类型为SHT_REL或SHT_RELA),若其要重定位的节区是内存节区 (SHF_ALLOC),则处理,否则pass.
在目标文件中只有静态链接重定位表,每个重定位表都只对应一个节区.
*/
for (i = 1; i < info->hdr->e_shnum; i++) {
unsigned int infosec = info->sechdrs[i].sh_info;
/* Not a valid relocation section? */
/*
对于REL/RELA类型节区来说sh_info记录其要重定位的节区,这里是判断是否节区越界
*/
if (infosec >= info->hdr->e_shnum)
continue;
/* Don't bother with non-allocated sections */
//如果重定位的目标节区是内存节区
if (!(info->sechdrs[infosec].sh_flags & SHF_ALLOC))
continue;
/* Livepatch relocation sections are applied by livepatch */
if (info->sechdrs[i].sh_flags & SHF_RELA_LIVEPATCH)
continue;
if (info->sechdrs[i].sh_type == SHT_REL)
err = apply_relocate(info->sechdrs, info->strtab,
info->index.sym, i, mod);
else if (info->sechdrs[i].sh_type == SHT_RELA)
/*
sechdrs是节区头部表指针
strtab是符号表字符串表
info->index.sym是符号表节区索引
i是当前的重定位节区索引
mod是当前模块
此函数负责具体类型的重定位操作,对于超过范围的call26/jump26指令,若开启了CONFIG_ARM64_MODULE_PLTS,则会在PLT表中为其增加一个表项.
*/
err = apply_relocate_add(info->sechdrs, info->strtab,
info->index.sym, i, mod);
if (err < 0)
break;
}
return err;
}
注: 静态链接一共分为三步:地址空间分配,符号决议,重定位,正好对应模块加载时的三个阶段:
15.重排异常表、per-cpu变量复制、复制核心符号表
err = post_relocation(mod, info);
if (err < 0)
goto free_modinfo;
post_relocation一共包含3个主要函数,其中:
static int post_relocation(struct module *mod, const struct load_info *info)
{
/* Sort exception table now relocations are done. */
//重定位结束后要重排一下异常向量表
sort_extable(mod->extable, mod->extable + mod->num_exentries);
/* Copy relocated percpu area over. */
//per-cpu变量复制到各个cpu对应内存
percpu_modcopy(mod, (void *)info->sechdrs[info->index.pcpu].sh_addr,
info->sechdrs[info->index.pcpu].sh_size);
/* Setup kallsyms-specific fields. */
//这里从静态链接符号表中复制模块的核心符号表
add_kallsyms(mod, info);
/* Arch-specific module finalizing. */
return module_finalize(info->hdr, info->sechdrs, mod);
}
16.init/core_layout缓存刷新
flush_module_icache(mod);
此函数负责刷新init/core_layout VA[base,base+size]范围内的指令缓存
17.复制用户态参数到内核
//用户参数复制到内核,并记录到args中
mod->args = strndup_user(uargs, ~0UL >> 1);
if (IS_ERR(mod->args)) {
err = PTR_ERR(mod->args);
goto free_arch_cleanup;
}
18.导出符号检查与RONX保护
err = complete_formation(mod, info);
if (err)
goto ddebug_cleanup;
此函数包含两步操作:
static int complete_formation(struct module *mod, struct load_info *info)
{
int err;
mutex_lock(&module_mutex);
/* Find duplicate symbols (must be called under lock). */
/*
这里是检验内核中是否有同名的导出符号,实际上是遍历syms/gpl_syms/gpl_future_syms等模块的多个导出表
然后针对每个导出符号,在内核查找是否已有同名的导出符号.
*/
err = verify_exported_symbols(mod);
if (err < 0)
goto out;
//所有该RO的RO
module_enable_ro(mod, false);
//除了text段全NX
module_enable_nx(mod);
/* Mark state as coming so strong_try_module_get() ignores us,
* but kallsyms etc. can see us. */
mod->state = MODULE_STATE_COMING;
mutex_unlock(&module_mutex);
return 0;
out:
mutex_unlock(&module_mutex);
return err;
}
19.发送模块加载通知链
err = prepare_coming_module(mod);
if (err)
goto bug_cleanup;
这个没啥可说的
static int prepare_coming_module(struct module *mod)
{
int err;
ftrace_module_enable(mod);
err = klp_module_coming(mod);
if (err)
return err;
blocking_notifier_call_chain(&module_notify_list,
MODULE_STATE_COMING, mod);
return 0;
}
20.参数解析与sysfs、livepatch设置
/*
此函数负责模块的参数解析,其中args是insmod传入的实参,而kp则是模块的实参类型,记录在模块的段__param中
*/
after_dashes = parse_args(mod->name, mod->args, mod->kp, mod->num_kp,
-32768, 32767, mod,
unknown_module_param_cb);
if (IS_ERR(after_dashes)) {
err = PTR_ERR(after_dashes);
goto coming_cleanup;
} else if (after_dashes) {
pr_warn("%s: parameters '%s' after `--' ignored\n",
mod->name, after_dashes);
}
/* Link in to sysfs. */
//链接到sysfs的输出
err = mod_sysfs_setup(mod, info, mod->kp, mod->num_kp);
if (err < 0)
goto coming_cleanup;
//livepatch用的,pass
if (is_livepatch_module(mod)) {
err = copy_module_elf(mod, info);
if (err < 0)
goto sysfs_cleanup;
}
这个暂时也没啥好说的
return do_init_module(mod);
此函数主要负责:
static noinline int do_init_module(struct module *mod)
{
int ret = 0;
struct mod_initfree *freeinit;
//分配一个 mod_initfree 结构体
freeinit = kmalloc(sizeof(*freeinit), GFP_KERNEL);
if (!freeinit) {
ret = -ENOMEM;
goto fail;
}
//free结构体指向init_layout基地址
freeinit->module_init = mod->init_layout.base;
current->flags &= ~PF_USED_ASYNC;
//若模块指定了构造函数,则调用其构造函数
do_mod_ctors(mod);
/* Start the module */
//若模块有init函数,则调用其init函数,用do_one_initcall包裹前后有事件追踪
if (mod->init != NULL)
ret = do_one_initcall(mod->init);
if (ret < 0) {
goto fail_free_freeinit;
}
if (ret > 0) {
pr_warn("%s: '%s'->init suspiciously returned %d, it should "
"follow 0/-E convention\n"
"%s: loading module anyway...\n",
__func__, mod->name, ret, __func__);
dump_stack();
}
/* Now it's a first class citizen! */
mod->state = MODULE_STATE_LIVE;
//通知链
blocking_notifier_call_chain(&module_notify_list,
MODULE_STATE_LIVE, mod);
if (!mod->async_probe_requested && (current->flags & PF_USED_ASYNC))
async_synchronize_full();
ftrace_free_mem(mod, mod->init_layout.base, mod->init_layout.base +
mod->init_layout.size);
mutex_lock(&module_mutex);
/* Drop initial reference. */
module_put(mod);
trim_init_extable(mod);
#ifdef CONFIG_KALLSYMS
/* Switch to core kallsyms now init is done: kallsyms may be walking! */
//模块符号表指向核心符号表
rcu_assign_pointer(mod->kallsyms, &mod->core_kallsyms);
#endif
//这里主要重新保护了一下ro_after_init
module_enable_ro(mod, true);
mod_tree_remove_init(mod);
module_arch_freeing_init(mod);
//清空init_layout相关结构体
mod->init_layout.base = NULL;
mod->init_layout.size = 0;
mod->init_layout.ro_size = 0;
mod->init_layout.ro_after_init_size = 0;
mod->init_layout.text_size = 0;
//这里插入模块的释放队列,实际上最终对应函数 do_free_init 释放掉整个init_layout
//这里do_free_init应该是释放物理内存+释放VA空间,不需要提前设置init_layout的属性
if (llist_add(&freeinit->node, &init_free_list))
schedule_work(&init_free_wq);
mutex_unlock(&module_mutex);
wake_up_all(&module_wq);
return 0;
fail_free_freeinit:
kfree(freeinit);
fail:
/* Try to protect us from buggy refcounters. */
mod->state = MODULE_STATE_GOING;
synchronize_rcu();
module_put(mod);
blocking_notifier_call_chain(&module_notify_list,
MODULE_STATE_GOING, mod);
klp_module_going(mod);
ftrace_release_mod(mod);
free_module(mod);
wake_up_all(&module_wq);
return ret;
}
到此模块加载完毕,下图更清晰的描述了此整个过程