/*本篇由真胖子同志倾心打造,殊为不易,忘转载时标明出处http://blog.youkuaiyun.com/figtingforlove/article/details/20156159*/
(1)符号的重定位
想想正在加载的模块有一个“未解决的引用”符号是另一个模块导出的,看看我们上节得到的sym[i].st_value = ksym->value。
虽然“__ksymtab”段被搬移到了最终的内存地址,但是kernel_symbol类型符号sym中的value值依然是内核模块在静态编译时产生的地址(导出符号时EXPORT_SYMBOL,模块编译工具链中的(unsigned long)&sym),根本不是这些模块被加载进内核后在内存中的实际地址,这就需要对模块导出的符号进行重定位。
内核模块有导出符号时,编译工具链会为这个模块的ELF文件生成一个特殊的重定位段“.rel__ksymtab”专门用于对“__ksymtab”段的重定位,这个段有下面的数据结构形成一个数组。
Linux/include/uapi/linux/elf.h
160 typedef struct elf32_rel { 161 Elf32_Addr r_offset; //重定位后段距原来段的偏移量 162 Elf32_Word r_info; 163 } Elf32_Rel;
模块加载时内核关于重定位的代码,其原理就是遍历段头表中的每一项,为有重定位标记SHF_REL的段进行重定位,段头中的sh_info成员指明了被重定位的段在段头表中的索引值。
1998 static int apply_relocations(struct module *mod, const struct load_info *info) 1999 { 2000 unsigned int i; 2001 int err = 0; 2002 2003 /* Now do relocations. */ 2004 for (i = 1; i < info->hdr->e_shnum; i++) { 2005 unsigned int infosec = info->sechdrs[i].sh_info; 2006 2007 /* Not a valid relocation section? */ 2008 if (infosec >= info->hdr->e_shnum) 2009 continue; 2010 2011 /* Don't bother with non-allocated sections */ 2012 if (!(info->sechdrs[infosec].sh_flags & SHF_ALLOC)) 2013 continue; 2014 //是重定位段 2015 if (info->sechdrs[i].sh_type == SHT_REL) 2016 err = apply_relocate(info->sechdrs, info->strtab, 2017 info->index.sym, i, mod); 2018 else if (info->sechdrs[i].sh_type == SHT_RELA) 2019 err = apply_relocate_add(info->sechdrs, info->strtab, 2020 info->index.sym, i, mod); 2021 if (err < 0) 2022 break; 2023 } 2024 return err; 2025 }
对于重定位段,调用apply_relocate函数进行重定位,这是一个体系结构相关的函数,我们看看x86_32下的实现。
Linux/arch/x86/kernel/module.c
55 #ifdef CONFIG_X86_32 56 int apply_relocate(Elf32_Shdr *sechdrs, 57 const char *strtab, 58 unsigned int symindex, 59 unsigned int relsec, 60 struct module *me) 61 { 62 unsigned int i; //当前正在处理的重定位段 63 Elf32_Rel *rel = (void *)sechdrs[relsec].sh_addr; 64 Elf32_Sym *sym; 65 uint32_t *location; 66 67 DEBUGP("Applying relocate section %u to %u\n", 68 relsec, sechdrs[relsec].sh_info); //遍历重定位段中所有需要重定位的符号 69 for (i = 0; i < sechdrs[relsec].sh_size / sizeof(*rel); i++) { 70 /* This is where to make the change */ //符号表段地址 + 重定位偏移 得到 指向符号表中需要修改的地址的指针 71 location = (void*)sechdrs[sechdrs[relsec].sh_info].sh_addr 72 + rel[i].r_offset; 73 /* This is the symbol it is referring to. Note that all undefined symbols have been resolved. */ //符号表地址 + 需要定位的符号在符号表中的偏移量得到符号地址 75 sym = (Elf32_Sym *)sechdrs[symindex].sh_addr 76 + ELF32_R_SYM(rel[i].r_info); 77 78 switch (ELF32_R_TYPE(rel[i].r_info)) { 79 case R_386_32: 80 /* We add the value into the location given */ 81 *location += sym->st_value; 82 break; 83 case R_386_PC32: 84 /* Add the value, subtract its position */ 85 *location += sym->st_value - (uint32_t)location; 86 break; 87 default: 88 pr_err("%s: Unknown relocation: %u\n", 89 me->name, ELF32_R_TYPE(rel[i].r_info)); 90 return -ENOEXEC; 91 } 92 } 93 return 0; 94 }
到此,模块加载最艰巨的部分算是结束了~~~~~接下来,继续看看其余的一些处理。接下来的处理就比较简单了,就是调用模块的初始化函数,释放HDR视图和INIT section占用的内存空间等善后工作。
有了上面的基础,下面我们主要分析内核模块加载器函数的具体实现,忽略部分细节,主要理解函数的调用流程和完成的功能。
(1)进入系统调用函数
umod表示用户空间模块视图地址,len代表长度,uargs是用户空间传入的模块参数/*模块加载过程中传递的信息*/ 173 struct load_info { 174 Elf_Ehdr *hdr; 175 unsigned long len; 176 Elf_Shdr *sechdrs; 177 char *secstrings, *strtab; 178 unsigned long symoffs, stroffs; 179 struct _ddebug *debug; 180 unsigned int num_debug; 181 bool sig_ok; 182 struct { 183 unsigned int sym, str, mod, vers, info, pcpu; 184 } index; 185 };
从用户空间拷贝模块ELF文件视图3301 SYSCALL_DEFINE3(init_module, void __user *, umod, 3302 unsigned long, len, const char __user *, uargs) 3303 { 3304 int err; 3305 struct load_info info = { }; 3313 3314 err = copy_module_from_user(umod, len, &info); 3315 if (err) 3316 return err; 3317 3318 return load_module(&info, uargs, 0); 3319 }
ELF各段的重新布局/* Sets info->hdr and info->len. */ 2499 static int copy_module_from_user(const void __user *umod, unsigned long len, 2500 struct load_info *info) 2501 { 2502 int err; 2503 2504 info->len = len; 2505 if (info->len < sizeof(*(info->hdr))) 2506 return -ENOEXEC; 2507 /*安全性检查*/ 2508 err = security_kernel_module_from_file(NULL); 2509 if (err) 2510 return err; 2511 2512 /* 分配内存 */ 2513 info->hdr = vmalloc(info->len); 2514 if (!info->hdr) 2515 return -ENOMEM; 2516 /*从用户空间拷贝*/ 2517 if (copy_from_user(info->hdr, umod, info->len) != 0) { 2518 vfree(info->hdr); 2519 return -EFAULT; 2520 } 2521 2522 return 0; 2523 } (2) 加载函数load_module 3146 static int load_module(struct load_info *info, const char __user *uargs, int flags) 3148 { 3149 struct module *mod, *old; 3151 /*一些检查性工作,如检查ELF头*/ /* 前面讲的段搬移,即ELF视图的修改*/ 3161 mod = layout_and_allocate(info, flags); /*模块仍然处于启动状态*/ 3172 mod->state = MODULE_STATE_UNFORMED; 3173 again: 3174 mutex_lock(&module_mutex); /*如果系统中已经有一个该模块正在加载,我们不能加载两次*/ 3175 if ((old = find_module_all(mod->name, true)) != NULL) { 3176 if (old->state == MODULE_STATE_COMING 3177 || old->state == MODULE_STATE_UNFORMED) { 3178 /* Wait in case it fails to load. */ 3179 mutex_unlock(&module_mutex); 3180 err = wait_event_interruptible(module_wq, finished_loading(mod->name)); 3182 if (err) 3183 goto free_module; 3184 goto again; 3185 } 3186 err = -EEXIST; 3187 mutex_unlock(&module_mutex); 3188 goto free_module; 3189 } /*添加到内核模块全局链表*/ 3190 list_add_rcu(&mod->list, &modules); 3191 mutex_unlock(&module_mutex); 3199 /*初始化模块unload section相关的成员*/ 3200 err = module_unload_init(mod); 3204 /* Now we've got everything in the final locations, we can find optional sections. */ /*初始化模块mod中各个指向各段的指针*/ 3206 find_module_sections(mod, info); 3207 /*版本和许可证检查*/ /*前面介绍的对“未解决的引用”符号的处理*/ 3216 err = simplify_symbols(mod, info); /*加载过程中的导出符号的重定位*/ 3220 err = apply_relocations(mod, info); /*...*/ 3230 /* Now copy in args */ 3231 mod->args = strndup_user(uargs, ~0UL >> 1); /*…*/ 3239 mutex_lock(&module_mutex); 3240 /*检查模块的导出符号表 */ 3241 err = verify_export_symbols(mod); 3248 /* Mark state as coming so strong_try_module_get() ignores us, but kallsyms etc. can see us. */ 3250 mod->state = MODULE_STATE_COMING; 3252 mutex_unlock(&module_mutex); 3253 3254 /* Module is ready to execute: parsing args may do that. */ /*模块参数的处理,将mod->args的值复制到“__param”段*/ 3255 err = parse_args(mod->name, mod->args, mod->kp, mod->num_kp, 3256 -32768, 32767, &ddebug_dyndbg_module_param_cb); /*…*/ 3260 /* Link in to syfs. */ 3261 err = mod_sysfs_setup(mod, info, mod->kp, mod->num_kp); 3265 /* Get rid of temporary copy.vfree(HDR) */ 3266 free_copy(info); 3267 /*现在模块加载的艰巨工作基本做完了,下一步就是执行模块的init函数,并做一些后续处理*/ 3268 /* Done! */ 3269 trace_module_load(mod); 3270 3271 return do_init_module(mod); 3272 /*前面一些检查失败的处理操作*/ 3299 }
2929 static struct module *layout_and_allocate(struct load_info *info, int flags) 2930 { 2931 /* Module within temporary copy. */ 2932 struct module *mod; 2935 /*主要是第一次HDR视图的改写*/ 2936 mod = setup_load_info(info, flags); 2949 /*一些检查工作和pcpu段的操作*/ /*前面讲的段划分:INIT和CORE*/ 2963 layout_sections(mod, info); /*前面讲的符号表划分:CORE*/ 2964 layout_symtab(mod, info); 2965 2966 /*INIT和CORE内存分配和段搬移 to the final place */ 2967 err = move_module(mod, info); /* Module has been copied to its final place now: return it. */ /*前面讲的修改mod指针指向最终位置*/ 2972 mod = (void *)info->sechdrs[info->index.mod].sh_addr; 2973 kmemleak_load_module(mod, info); 2974 return mod; 2980 } 初始化load_info的一些成员信息,并完后HDR视图的第一次改写 2634 static struct module *setup_load_info(struct load_info *info, int flags) 2635 { 2640 /* Set up the convenience variables */ /*段头表和段名称字符串表指针*/ 2641 info->sechdrs = (void *)info->hdr + info->hdr->e_shoff; 2642 info->secstrings = (void *)info->hdr 2643 + info->sechdrs[info->hdr->e_shstrndx].sh_offset; 2644 /*HDR视图的第一次改写,主要是前面讲的shdr->sh_addr = (size_t)info->hdr + shdr->sh_offset;的修改*/ 2645 err = rewrite_section_headers(info, flags); 2648 2649 /* 模块内部符号表的处理. */ 2659 /*mod段的索引*/ 2660 info->index.mod = find_sec(info, ".gnu.linkonce.this_module"); /*前面讲的mod变量的赋值*/ mod = (void *)info->sechdrs[info->index.mod].sh_addr; 2667 /*其他一些段的相关处理*/ 2680 return mod; 2681 }
(3)加载模块的后续执行(模块真实的工作)
3039 /* This is where the real work happens */ 3040 static int do_init_module(struct module *mod) 3041 { /*...*/ /*呼叫内核模块通知链,该模块正在加载,随后介绍该机制*/ blocking_notifier_call_chain(&module_notify_list, 3051 MODULE_STATE_COMING, mod); 3052 3053 /* Set RO and NX regions for core */ 3054 set_section_ro_nx(mod->module_core, 3055 mod->core_text_size, 3056 mod->core_ro_size, 3057 mod->core_size); 3058 3059 /* Set RO and NX regions for init */ 3060 set_section_ro_nx(mod->module_init, 3061 mod->init_text_size, 3062 mod->init_ro_size, 3063 mod->init_size); 3064 3065 do_mod_ctors(mod); 3066/* Start the module,执行模块定义的初始化函数代码 */ 3067 if (mod->init != NULL) 3068 ret = do_one_initcall(mod->init); 3069 if (ret < 0) { 3070 /* Init routine failed: abort. Try to protect us from 3071 buggy refcounters. */ 3072 mod->state = MODULE_STATE_GOING; 3073 synchronize_sched(); 3074 module_put(mod); 3075 blocking_notifier_call_chain(&module_notify_list, 3076 MODULE_STATE_GOING, mod); 3077 free_module(mod); 3078 wake_up_all(&module_wq); 3079 return ret; 3080 } /*...*/ 3089 /*模块已经加载进系统*/ 3090 /* Now it's a first class citizen! */ 3091 mod->state = MODULE_STATE_LIVE; blocking_notifier_call_chain(&module_notify_list, 3093 MODULE_STATE_LIVE, mod); 3094 /*...*/ 3116 /* Drop initial reference.释放init section占据的内存 */ 3117 module_put(mod); 3118 trim_init_extable(mod); 3119 #ifdef CONFIG_KALLSYMS 3120 mod->num_symtab = mod->core_num_syms; 3121 mod->symtab = mod->core_symtab; 3122 mod->strtab = mod->core_strtab; 3123 #endif 3124 unset_module_init_ro_nx(mod); 3125 module_free(mod, mod->module_init); 3126 mod->module_init = NULL; 3127 mod->init_size = 0; 3128 mod->init_ro_size = 0; 3129 mod->init_text_size = 0; 3130 mutex_unlock(&module_mutex); /*唤醒等待该模块加载完成的那些等待链表上的节点*/ 3131 wake_up_all(&module_wq); 3132 3133 return 0; 3134 }
至此,模块的加载告一段落了!!!!!~~~~~~~确实是个明智而艰巨的工作,下一节介绍内核模块的依赖关系和模块的卸载。