先上图:
HDR视图的第二次改写
在这次改写中,HDR视图中绝大多数的section会被搬移到新的内存空间中,之后会根据这些section新的内存地址再次改写图1·2中的HDR视图,使其中section header table中各entry的sh_addr指向新的也是最终的内存地址。
load_module->layout_and_allocate->layout_sections
static void layout_sections(struct module *mod, struct load_info *info) { static unsigned long const masks[][2] = { /* NOTE: all executable code must be the first section * in this array; otherwise modify the text_size * finder in the two loops below */ { SHF_EXECINSTR | SHF_ALLOC, ARCH_SHF_SMALL }, { SHF_ALLOC, SHF_WRITE | ARCH_SHF_SMALL }, { SHF_WRITE | SHF_ALLOC, ARCH_SHF_SMALL }, { ARCH_SHF_SMALL | SHF_ALLOC, 0 } };//标记了SHF_ALLOC的section定义了四种类型: //code、only-data、read-write data和small data。 unsigned int m, i; //遍历Section header table for (i = 0; i < info->hdr->e_shnum; i++) info->sechdrs[i].sh_entsize = ~0UL; pr_debug("Core section allocation order:\n"); for (m = 0; m < ARRAY_SIZE(masks); ++m) { for (i = 0; i < info->hdr->e_shnum; ++i) { Elf_Shdr *s = &info->sechdrs[i]; const char *sname = info->secstrings + s->sh_name; if ((s->sh_flags & masks[m][0]) != masks[m][0] || (s->sh_flags & masks[m][1]) || s->sh_entsize != ~0UL || strstarts(sname, ".init")) continue; s->sh_entsize = get_offset(mod, &mod->core_size, s, i); pr_debug("\t%s\n", sname); } switch (m) { case 0: /* executable */ mod->core_size = debug_align(mod->core_size); mod->core_text_size = mod->core_size; break; case 1: /* RO: text and ro-data */ mod->core_size = debug_align(mod->core_size); mod->core_ro_size = mod->core_size; break; case 3: /* whole core */ mod->core_size = debug_align(mod->core_size); break; } } pr_debug("Init section allocation order:\n"); for (m = 0; m < ARRAY_SIZE(masks); ++m) { for (i = 0; i < info->hdr->e_shnum; ++i) { Elf_Shdr *s = &info->sechdrs[i]; const char *sname = info->secstrings + s->sh_name; if ((s->sh_flags & masks[m][0]) != masks[m][0] || (s->sh_flags & masks[m][1]) || s->sh_entsize != ~0UL || !strstarts(sname, ".init")) continue; s->sh_entsize = (get_offset(mod, &mod->init_size, s, i) | INIT_OFFSET_MASK); pr_debug("\t%s\n", sname); } switch (m) { case 0: /* executable */ mod->init_size = debug_align(mod->init_size); mod->init_text_size = mod->init_size; break; case 1: /* RO: text and ro-data */ mod->init_size = debug_align(mod->init_size); mod->init_ro_size = mod->init_size; break; case 3: /* whole init */ mod->init_size = debug_align(mod->init_size); break; } } }
在为那些需要移动的section分配新的内存空间地址之前,内核需要决定出HDR视图中哪些section需要移动,如果移动的话要移动到什么位置。内核代码中layout_sections函数用来做这件事,在layout_sections函数中,内核会遍历HDR视图中的每一个section,对每一个标记有SHF_ALLOC的section,将其划分到两大类section当中:CORE和INIT。为了完成这种分类,layout_sections函数首先为标记了SHF_ALLOC的section定义了四种类型:code、only-data、read-write data和small-datao任何一个标记了SHF_ALLOC的section必定属于这四类中的一类。之后,对应每一个分类,函数都会遍历Section header table中的所有项,将section name不是以".init"开始的section划归为CORE section,并且修改HDR视图中Section header table中对应entry的sh_entsize,用以记录当前section在COREsection中的偏移量。
mod->core_size = debug_align(mod->core_size);
同时用struct module结构中的成员变量core size记录下到当前正在操作的section为止CORE section的空间大小。
mod->core_size = debug_align(mod->core_size);
对于CORE section中的code section,内核用struct module结构中的core text size来记录。
mod->core_text_size = mod->core_size
对于INIT section的分类,和CORE section的划分基本一样,不同的地方在于属于INIT section的section,其name必须以与nit"开始,内核用struct module结构中的成员变量init size来记录当前INITsection空间的大小。
mod->init_size = debug_align(mod->init_size);
对于INIT section中的code section,内核用struct module结构中的init text size来记录。在对section进行搬移之前,接下来会有个对符号表的处理,内核代码中通过调用layout_symtab函数来完成。Linux的内核源码中根据是否启用了内核配置选项CONFIG_KALLSYMS给出了layout_symtab函数的两种不同的定义。
如果没有启用CONFIG_KALLSYMS,那么layout_symtab函数就是个空函数,不做任何事情。
在启用了CONFIG_KALLSYMS选项的LINUX源码树基础上编译内核模块,会导致内核模块也会保留模块中的所有符号,这些符号都放在ELF符号表section中。由于在内核模块的ELF文件中,符号表所在的section没有SHFALLOC标志,所以上面提到的layout_secttons函数不会把符号表section划到CORE section或者是INITsecuon中,这也是为什么要通过另外一个函数layout_symtab来把符号表搬移到CORE section内存区中的原因。
内核会遍历HDR视图中的每一个section,对每一个标记有SHF_ALLOC的section,将其划分到两大类section当中:CORE和INIT。为了完成这种分类,layout_sections函数首先为标记了SHF_ALLOC的section定义了四种类型:code、only-data、read-write data和small-datao任何一个标记了SHF_ALLOC的section必定属于这四类中的一类。之后,对应每一个分类,函数都会遍历Section header table中的所有项,将section name不是以".init"开始的section划归为CORE section,并且修改HDR视图中Section header table中对应entry的sh_entsize,用以记录当前section在COREsection中的偏移量。
load_module->layout_and_allocate->move_module
static int move_module(struct module *mod, struct load_info *info) { int i; void *ptr; /* Do the allocs. */ ptr = module_alloc_update_bounds(mod->core_size); /* * The pointer to this block is stored in the module structure * which is inside the block. Just mark it as not being a * leak. */ kmemleak_not_leak(ptr); if (!ptr) return -ENOMEM; memset(ptr, 0, mod->core_size); mod->module_core = ptr; if (mod->init_size) { ptr = module_alloc_update_bounds(mod->init_size); /* * The pointer to this block is stored in the module structure * which is inside the block. This block doesn't need to be * scanned as it contains data and code that will be freed * after the module is initialized. */ kmemleak_ignore(ptr); if (!ptr) { module_memfree(mod->module_core); return -ENOMEM; } memset(ptr, 0, mod->init_size); mod->module_init = ptr; } else mod->module_init = NULL; /* Transfer each section which specifies SHF_ALLOC */ pr_debug("final section addresses:\n"); for (i = 0; i < info->hdr->e_shnum; i++) { void *dest; Elf_Shdr *shdr = &info->sechdrs[i]; if (!(shdr->sh_flags & SHF_ALLOC)) continue; if (shdr->sh_entsize & INIT_OFFSET_MASK) dest = mod->module_init + (shdr->sh_entsize & ~INIT_OFFSET_MASK); else dest = mod->module_core + shdr->sh_entsize; if (shdr->sh_type != SHT_NOBITS) memcpy(dest, (void *)shdr->sh_addr, shdr->sh_size); /* Update sh_addr to point to copy in image. */ shdr->sh_addr = (unsigned long)dest; pr_debug("\t0x%lx %s\n", (long)shdr->sh_addr, info->secstrings + shdr->sh_name); } return 0; }
这里之所以要对HDR视图中的某些section做这样的搬移,是因为在模块加载过程结束时,系统会释放掉IIDR视图所在的内存区域,不仅如此,在模块初始化工作完成后,INITsection所在的内存区域也会被释放掉。由此可见,当一个模块被成功加载进系统,初始化工作完成之后,最终留下的仅仅是CORE section中的内容,因此CORE section中的数据应是模块在系统中整个存活期会使用到的数据。
如此处理之后,我们在图1·2的基础上得到了图1·4:
模块导出的符号
如果一个内核模块向外界导出了自己的符号,那么将由模块的编译工具链负责生成这些导出符号section,而且这些section都带有SHFALLOC标志,所以在模块加载过程中会被搬移到CORE section区域中。如果模块没有向外界导出任何符号,那么在模块的ELF文件中,将不会产生这些section。
显然,内核需要对模块导出的符号进行管理,以便在处理其他模块中那些“未解决的引用”符号时能够找到这些符号。内核对模块导出的符号的管理使用到了structmodule结构中如下的成员变量:
struct module { 。。。。 #ifdef CONFIG_UNUSED_SYMBOLS /* unused exported symbols. */ const struct kernel_symbol *unused_syms; const unsigned long *unused_crcs; unsigned int num_unused_syms; /* GPL-only, unused exported symbols. */ unsigned int num_unused_gpl_syms; const struct kernel_symbol *unused_gpl_syms; const unsigned long *unused_gpl_crcs; #endif #ifdef CONFIG_MODULE_SIG /* Signature was verified. */ bool sig_ok; #endif /* symbols that will be GPL-only in the near future. */ const struct kernel_symbol *gpl_future_syms; const unsigned long *gpl_future_crcs; unsigned int num_gpl_future_syms; 。。。。 }
在把HDR视图中的section搬移到最终的CORE section和INIT section之后,内核通过对HDR视图中Section header table的查找,获得“__ksymtab",“__ksymtab_gpl”和”__ksymtab_gpl_future”section在CORE section中的地址,将其记录在mod->syms、mod->gpl_syms和mod->gpl_future_syms中。
- find_symbol
在模块加载过程中,find_symbol是个非常重要的函数,顾名思义,它用来查找一个符号。该函数的原型如下:
const struct kernel_symbol *find_symbol(const char *name, struct module **owner, const unsigned long **crc, bool gplok, bool warn) { struct find_symbol_arg fsa; fsa.name = name; fsa.gplok = gplok; fsa.warn = warn; if (each_symbol_section(find_symbol_in_section, &fsa)) { if (owner) *owner = fsa.owner; if (crc) *crc = fsa.crc; return fsa.sym; } pr_debug("Failed to find symbol %s\n", name); return NULL; }