版权声明:本文为CSDN博主「ashimida@」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/lidan113lidan/article/details/45313535
更多内容可关注微信公众号
一般linux中有两个程序可以添加内核模块,modprobe和insmod,前者考虑到了各个模块之间可能出现的依赖关系,被依赖的模块会被自动载入,而insmod只是简单的尝试载入当前的模块。二者最终都是通过linux系统调用sys_init_module载入内核模块的。
编译好的内核模块一般是以*.ko结尾的,这类文件都是可重定位文件。
###sys_init_module
/* 模块加载主要就三步:
1. elf->内核
2. 文件映像->内存映像
3. 调用模块的init函数
模块的加载首先会在用户空间将模块的文件映射到内存(后面称之为文件映像),然后调用sys_init_module,内核会将文件映像复制到内核态,然后根据各个节的属性,再重新分配模块的空间,重新分配的空间我们后面称之为内存映像。
*/
SYSCALL_DEFINE3(init_module,
//一个指向用户地址空间的指针,模块的二进制代码位于其中。
void __user *, umod,
unsigned long, len, //用户地址空间的长度
const char __user *, uargs) //模块的参数
{
struct module *mod; //内核用struct module来表示一个模块
int ret = 0;
//selinux检查, 当前进程是否有insmod的权限
if (!capable(CAP_SYS_MODULE) || modules_disabled)
return -EPERM;
//载入elf模块,返回初始化好的module结构体(最麻烦的一个函数)
mod = load_module(umod, len, uargs);
if (IS_ERR(mod))
return PTR_ERR(mod);
//利用内核通知链,告诉内核其他子系统,有模块将要装入
blocking_notifier_call_chain(&module_notify_list,
MODULE_STATE_COMING, mod);
//设置这两段虚拟地址空间中所有页的属性
/* Set RO and NX regions for core */
set_section_ro_nx(mod->module_core,
mod->core_text_size,
mod->core_ro_size,
mod->core_size);
/* Set RO and NX regions for init */
set_section_ro_nx(mod->module_init,
mod->init_text_size,
mod->init_ro_size,
mod->init_size);
//调用mod中的ctors中的所有函数,这个ctors是啥?
do_mod_ctors(mod);
//调用模块的init函数
if (mod->init != NULL)
//基本上等于直接调用mod->init
ret = do_one_initcall(mod->init);
if (ret < 0) {
//init函数运行失败,模块状态改为正在卸载中
mod->state = MODULE_STATE_GOING;
synchronize_sched();
//减小模块的使用计数
module_put(mod);
//调用内核通知连,通知模块正在卸载中
blocking_notifier_call_chain(
&module_notify_list,
MODULE_STATE_GOING, mod);
//释放模块
free_module(mod);
//唤醒module_wq队列中的所有元素,由于内核的整个modules
//列表需要原子操作,如果有多个进程同时对modules列表进行
//操作,后来的就会进入这个module_wq队列。
wake_up(&module_wq);
return ret;
}
if (ret > 0) {
//如果加载模块成功
dump_stack(); //此函数可以打印当前cpu的堆栈信息
}
//设置模块状态为加载成功,唤醒所有等待此模块加载完成的进程。
mod->state = MODULE_STATE_LIVE;
wake_up(&module_wq);
//调用内核通知连,通知模块已加载
blocking_notifier_call_chain(&module_notify_list,
MODULE_STATE_LIVE, mod);
//等待所有的异步函数调用完成
async_synchronize_full();
mutex_lock(&module_mutex);
//减少引用计数
module_put(mod);
//应该是处理异常表
trim_init_extable(mod);
#ifdef CONFIG_KALLSYMS
//导出符号表,在/proc/kallsyms中能看到的应该是mod->symtab
mod->num_symtab = mod->core_num_syms;
mod->symtab = mod->core_symtab;
mod->strtab = mod->core_strtab;
#endif
//对init相关的region 取消 RO和NX属性的设置
//free init段
unset_module_init_ro_nx(mod);
module_free(mod, mod->module_init);
mod->module_init = NULL;
mod->init_size = 0;
mod->init_ro_size = 0;
mod->init_text_size = 0;
mutex_unlock(&module_mutex);
return 0;
}
其中struct module的主要结构定义如下:
struct module
{
/*
//模块的当前状态
MODULE_STATE_LIVE, //正常运行状态
MODULE_STATE_COMING, //正在装在中
MODULE_STATE_GOING, //正在移除中
*/
enum module_state state;
struct list_head list; //内核所有加载的模块的双链表,链表头部是定义在kernel/module.c中的全局变量static LIST_HEAD(modules);
char name[MODULE_NAME_LEN]; //模块名,必须唯一,内核中卸载的时候就是指定的这个文件名
//给sysfs文件系统提供信息的字段
struct module_kobject mkobj;
struct module_attribute *modinfo_attrs;
const char *version;
const char *srcversion;
struct kobject *holders_dir;
//所有模块均可使用的符号表
//一个数组指针,管理内核的导出符号
const struct kernel_symbol *syms;
//一个unsigned long数组,存储导出符号的校验和
const unsigned long *crcs;
unsigned int num_syms; //数组项个数
//加载模块时传入的参数和个数
struct kernel_param *kp;
unsigned int num_kp;
//仅供gpl兼容模块使用的符号表
unsigned int num_gpl_syms;
const struct kernel_symbol *gpl_syms;
const unsigned long *gpl_crcs;
//目前可用,但将来只提供给GPL模块的符号。
const struct kernel_symbol *gpl_future_syms;
const unsigned long *gpl_future_crcs;
unsigned int num_gpl_future_syms;
//模块的新的异常表
unsigned int num_exentries;
struct exception_table_entry *extable;
int (*init)(void); //模块的init函数地址
//如果module_init不为空,则在init函数返回后会调用vfree释放
//这块内存。init区域的内存只在初始化(调用init的时候)有用。
void *module_init;
//core区域存放非初始化的代码和数据,在模块卸载的时候才可释放
void *module_core;
//init和core两个区域的大小
unsigned int init_size, core_size;
//每个区域中可执行代码的大小
unsigned int init_text_size, core_text_size;
//两个区域中只读数据段的大小
unsigned int init_ro_size, core_ro_size;
//特定于体系结构的信息
struct mod_arch_specific arch;
unsigned int taints; //如果模块会污染内核,则设置此位
#ifdef CONFIG_KALLSYMS
Elf_Sym *symtab, *core_symtab;
unsigned int num_symtab, core_num_syms;
char *strtab, *core_strtab;
/* Section attributes */
struct module_sect_attrs *sect_attrs;
/* Notes attributes */
struct module_notes_attrs *notes_attrs;
#endif
/* The command line arguments (may be mangled). People like keeping pointers to this stuff */
char *args; //模块的命令行参数
#ifdef CONFIG_SMP //多处理器相关的字段
void __percpu *percpu; //percpu数据
unsigned int percpu_size;
#endif
#ifdef CONFIG_MODULE_UNLOAD
//是否允许模块强行移除,即使内核仍有引用该模块的地方
//依赖于当前模块的模块
struct list_head source_list;
//当前模块依赖的模块
struct list_head target_list;
//等待当前模块卸载完成的进程的task_struct
struct task_struct *waiter;
//模块卸载函数
void (*exit)(void);
//模块的引用计数,系统中每个cpu都对应该数组中的一个数组项
struct module_ref __percpu *refptr;
#endif
};
struct load_info {
Elf_Ehdr *hdr; //elf文件头指针
unsigned long len; //elf文件大小
Elf_Shdr *sechdrs; //section header,节区头部表指针
/*
secstrings一般指向.shstrtab段,即节区头部表的字符串表节区。secstrings是这个节区的起始地址(elf头地址+文件偏移),因为字符串表的格式是字符串数组,所以secstrings也可以认为是个char *的数组。
strtab 是当前elf文件中,SHT_SYMTAB属性的节对应的字符串表所在节的地址。一个ko文件中,类型为SHT_SYMTAB的节应该只有一个
*/
char *secstrings, *strtab;
unsigned long symoffs, stroffs;
struct _ddebug *debug;
unsigned int num_debug;
struct {
unsigned int sym, str, mod, vers, info, pcpu;
} index;
//index就是索引的意思,这个结构体,记录几个关键节的索引
//vers: __versions节的索引
//info: .modinfo节的索引
//sym: 唯一一个属性为SHT_SYMTAB的节的索引
//str: sym节对应的字符串表的索引
//mod: .gnu.linkonce.this_module节的索引
//pcpu: 在单处理器下为0,多处理器下为.data..percpu段的索引
//不一定每个模块都有percpu这个节的,不需要就没有
};
###sys_init_module->load_module
static struct module *load_module(
void __user *umod, //文件映像的用户态指针
unsigned long len, //文件映像长度
const char __user *uargs) //加载参数
{
//info只记录文件映像中的信息
struct load_info info = { NULL, };
struct module *mod;
long err;
//复制文件映像到内核,做一些文件格式,大小的基本检查
//最终设置info->hdr指向文件映像首地址, info->len为
//文件映像长度。
err = copy_and_check(&info, umod, len, uargs);
if (err)
return ERR_PTR(err);
//计算elf中所有需要分配到内核中的节(分内init和core两种),为其分配空间,并将节的内容复制到新内核空间,然后将文件映像中的节表头部表指向这新节的新空间,由于module也是新节之一,返回新分配的节中module的地址。则个module_init和module_core区域,后面成为内存映像。
//module为内存映像中的this_module节地址,&info还是指向文件映像中的elf信息(elf信息是不会复制到内核的)。
mod = layout_and_allocate(&info);
if (IS_ERR(mod)) {
err = PTR_ERR(mod);
goto free_copy;
}
//以下都是初始化mod相关的
//1. 初始化mod里的链表一类的
err = module_unload_init(mod);
if (err)
goto free_module;
//2. 在elf文件中查找各个节的地址,将其填充到mod中的相关字段
//如__param,__ksymtab,__kcrctab等节,这时候找到的地址
//都是内存映像中的地址了(前面修正过sh_addr)
find_module_sections(mod, &info);
//3. 版本检查,主要是__kcrctab 表在不在,而不是真正比较crc
err = check_module_license_and_versions(mod);
if (err)
goto free_unload;
//4. 根据.modinfo段设置模块信息
setup_modinfo(mod, &info);
//修复所有的符号表,如果是模块内部符号,则重定位一下地址
//如果是模块外部符号,则解决未决引用。
err = simplify_symbols(mod, &info);
if (err < 0)
goto free_modinfo;
//重定位
err = apply_relocations(mod, &info);
if (err < 0)
goto free_modinfo;
//重定位之后,处理模块中的异常表,percpu变量,unwind相关处理
err = post_relocation(mod, &info);
if (err < 0)
goto free_modinfo;
flush_module_icache(mod);
/* Now copy in args */
//处理用户态传入的参数,实际上就是判断下大小
//是否过大,然后复制到内核态,指向args。
mod->args = strndup_user(uargs, ~0UL >> 1);
if (IS_ERR(mod->args)) {
err = PTR_ERR(mod->args);
goto free_arch_cleanup;
}
//状态为正在装在模块
mod->state = MODULE_STATE_COMING;
mutex_lock(&module_mutex);
//发现重名模块
if (find_module(mod->name)) {
err = -EEXIST;
goto unlock;
}
//验证新模块是否有重复的符号
err = verify_export_symbols(mod);
if (err < 0)
goto ddebug;
module_bug_finalize(info.hdr, info.sechdrs, mod);
list_add_rcu(&mod->list, &modules);
mutex_unlock(&module_mutex);
//解析参数到mod->kp 和mod->num_kp
err = parse_args(mod->name, mod->args, mod->kp, mod->num_kp,-32768, 32767, NULL);
if (err < 0)
goto unlink;
/* Link in to syfs. */
//模块信息加入到sysfs中
err = mod_sysfs_setup(mod, &info, mod->kp, mod->num_kp);
if (err < 0)
goto unlink;
//文件映像被释放了,最终只留下了内存映像
free_copy(&info);
/* Done! */
trace_module_load(mod);
return mod;
unlink:
mutex_lock(&module_mutex);
/* Unlink carefully: kallsyms could be walking list. */
list_del_rcu(&mod->list);
module_bug_cleanup(mod);
ddebug:
dynamic_debug_remove(info.debug);
unlock:
mutex_unlock(&module_mutex);
synchronize_sched();
kfree(mod->args);
free_arch_cleanup:
module_arch_cleanup(mod);
free_modinfo:
free_modinfo(mod);
free_unload:
module_unload_free(mod);
free_module:
module_deallocate(mod, &info);
free_copy:
free_copy(&info);
return ERR_PTR(err);
}
####sys_init_module->load_module->copy_and_check
/* Sets info->hdr and info->len. */
static int copy_and_check(
struct load_info *info, //要填充的info结构
const void __user *umod, //用户态文件映像的地址
unsigned long len, //文件映像的长度
const char __user *uargs)//用户态传入的参数
{
int err;
Elf_Ehdr *hdr;
//检查文件映像长度是否比一个标准elf头大
if (len < sizeof(*hdr))
return -ENOEXEC;
//将文件映像复制到内核
if ((hdr = vmalloc(len)) == NULL)
return -ENOMEM;
if (copy_from_user(hdr, umod, len) != 0) {
err = -EFAULT;
goto free_hdr;
}
//对文件映像做初步检查
if (
//是否为0x7f ELF开头
memcmp(hdr->e_ident, ELFMAG, SELFMAG) != 0
//类型是否为可重定位文件
|| hdr->e_type != ET_REL
//检查体系结构是否正确
|| !elf_check_arch(hdr)
//节表头部表表项大小是否与当前内核中代表节表头部表的数据
//结构Elf_Shdr结构体大小相等。
|| hdr->e_shentsize != sizeof(Elf_Shdr)) {
err = -ENOEXEC;
goto free_hdr;
}
//检查文件长度是否比elf中说明的节区头部表的末端大,小则出错
if (len < hdr->e_shoff + hdr->e_shnum *
sizeof(Elf_Shdr)) {
err = -ENOEXEC;
goto free_hdr;
}
//设置info的头指针和长度指针,hdr指向复制到内核的文件映像首地址
info->hdr = hdr;
info->len = len;
return 0;
free_hdr:
vfree(hdr);
return err;
}
####sys_init_module->load_module->layout_and_allocate
//计算elf中所有需要分配到内核中的节(分内init和core两种),为其分配空间,并将节的内容复制到新内核空间,然后将文件映像中的节表头部表指向这新节的新空间,由于module也是新节之一,返回新分配的节中module的地址。
static struct module *layout_and_allocate(struct load_info *info)
{
//这个mod先临时指向文件映像,最后会修正为内存映像
struct module *mod;
Elf_Shdr *pcpusec; //Elf_Shdr为节区头部表表项指针
int err;
//主要用来设置号info中的各个字段,返回临时的module指针
//这个指针指向文件映像的.gnu.linkonce.this_module节
mod = setup_load_info(info);
if (IS_ERR(mod))
return mod;
//检查模块版本信息,是否符合加载要求
err = check_modinfo(mod, info);
if (err)
return ERR_PTR(err);
//体系结构相关的段处理函数,在arm下为空
err = module_frob_arch_sections(info->hdr, info->sechdrs,info->secstrings, mod);
if (err < 0)
goto out;
//如果有pcup单独的变量,则处理,一般来说如果不是显示指定
//编译的内核模块都不需要pcpu变量
pcpusec = &info->sechdrs[info->index.pcpu];
if (pcpusec->sh_size) {
err = percpu_modalloc(mod,pcpusec->sh_size, pcpusec->sh_addralign);
if (err)
goto out;
pcpusec->sh_flags &= ~(unsigned long)SHF_ALLOC;
}
//分析模块中有SHF_ALLOC属性的段(有些段是主动加上/去掉SHF_ALLOC属性的),区分是不是初始化段,计算这些段需要的空间(以内存格式对齐后的空间),最终内存映像中节大小记录在文件映像各个节的sh_entsize字段,内存映像大小相关信息记录在init_text_size,init_ro_size,init_size,core_size,core_text_size, core_ro_size 等字段
layout_sections(mod, info);
//core_size上加上所有核心符号的字符串和符号表的大小
//init_size上加上整个符号表对应的字符串表的大小
//最终的逻辑是,先把符号表临时放在module_init段,将核心符号表复制到module_core里面,然后释放module_init。
layout_symtab(mod, info);
//分配module_core和module_init的内存,复制有SHF_ALLOC的
//节表到新分配的空间,节表头部表是不复制的,所以还要修正节表头
//部表中的sh_addr指针指向新的节的位置。
err = move_module(mod, info);
if (err)
goto free_percpu;
//mod结构所在的节肯定会被复制到内存,根据修正后的sh_addr,将Mod指向修正后的内核module结构
mod=(void *)info->sechdrs[info->index.mod].sh_addr;
kmemleak_load_module(mod, info);
return mod;
free_percpu:
percpu_modfree(mod);
out:
return ERR_PTR(err);
}
####sys_init_module->load_module->layout_and_allocate->setup_load_info
//主要用来设置info中的各个字段,返回临时的module指针
static struct module *setup_load_info(struct load_info *info)
{
unsigned int i;
int err;
struct module *mod;
//设置info中的节区头部表指针info->sechdrs
//hdr是elf头指针,e_shoff在elf头部,记录节区头部表(section Header Table)文件偏移的
info->sechdrs = (void *)info->hdr + info->hdr->e_shoff;
//设置info中的节区头部表字符串标指针info->secstrings
//e_shstrndx在elf头部,记录节区头部表中,每个节区名字的字符串表的索引, 这个字符串标也是elf其中的一个节区,这个索引是节区头部表中的下标。最后的sh_offset是节区头部表的字符串表的起始文件偏移
info->secstrings = (void *)info->hdr + info->sechdrs[info->hdr->e_shstrndx].sh_offset;
//去掉__versions, .modinfo节的SHF_ALLOC属性(.exit)也有可能被改,并将其节索引号记录到info结构中,将所有节的addr指向其文件映像中节区的虚拟地址
err = rewrite_section_headers(info);
if (err)
return ERR_PTR(err);
//遍历所有节表
for (i = 1; i < info->hdr->e_shnum; i++) {
//找到第一个属性为SHT_SYMTAB的节(静态符号表),
//和其对应的符号字符串标,记录到info中
if (info->sechdrs[i].sh_type == SHT_SYMTAB) {
//设置索引项
info->index.sym = i;
//对于SHT_SYMTAB类型的节,其sh_link的值为这个节对应的字符串表的索引
info->index.str = info->sechdrs[i].sh_link;
//设置info中这个节的地址
info->strtab = (char *)info->hdr +\
info->sechdrs[info->index.str].sh_offset;
break;
}
}
info->index.mod = find_sec(info, ".gnu.linkonce.this_module");
//如果没找到.gnu.linkonce.this_module这个节,直接返回出错
if (!info->index.mod) {
return ERR_PTR(-ENOEXEC);
}
//当前模块struct module * mod 指向mod节的地址,也就是
//.gnu.linkonce.this_module节的首地址,这个是临时的
mod = (void *)info->sechdrs[info->index.mod].sh_addr;
//如果符号表没找到,非返回出错
if (info->index.sym == 0) {
return ERR_PTR(-ENOEXEC);
}
//这个应该是和SMP相关的,对于多处理器才有效
info->index.pcpu = find_pcpusec(info);
//!!!!!注意这里有一处检查!!!!
if (!check_modstruct_version(info->sechdrs, info->index.vers, mod))
return ERR_PTR(-ENOEXEC);
return mod;
}
####sys_init_module->load_module->layout_and_allocate->setup_load_info->rewrite_section_headers
//去掉__versions, .modinfo节的SHF_ALLOC属性(.exit)也有可能被改,并将其节索引号记录到info结构中,将所有节的addr指向其文件映像中节区的虚拟地址
static int rewrite_section_headers(struct load_info *info)
{
unsigned int i;
//标准中规定,节区头部表的第一个项应该永远是0,这里再设置一遍 to make sure。
info->sechdrs[0].sh_addr = 0;
//遍历所有节区,e_shnum是节区的数目
for (i = 1; i < info->hdr->e_shnum; i++) {
//第i个节区的节区头部表指针
Elf_Shdr *shdr = &info->sechdrs[i];
//检查所有非SHT_NOBITS节,如果这个节超过了elf的文件范围,则返回错误
if (shdr->sh_type != SHT_NOBITS && info->len < shdr->sh_offset + shdr->sh_size) {
return -ENOEXEC;
}
//修改每个节区的addr,指向文件映像中这个节区的虚拟内存地址(为整个模块的首地址 + 其文件偏移)。可能是因为内核模块是可重定位文件的原因,其文件映像中所有节的sh_addr都默认为0,
shdr->sh_addr = (size_t)info->hdr + shdr->sh_offset;
#ifndef CONFIG_MODULE_UNLOAD
//如果没有定义UNLOAD宏,则模块不可卸载,直接在其elf文件的节表中修改.exit节的属性,去掉这个节的SHF_ALLOC属性。(info->secstrings + shdr->sh_name 是节表字符串表首地址,加上字符串的文件偏移)
if (strstarts(info->secstrings + shdr->sh_name, ".exit"))
shdr->sh_flags &= ~(unsigned long)SHF_ALLOC;
#endif
}
//在info中记录vers/info节的索引,但去掉这两个节的SHF_ALLOC
//属性,这两个节最终不进入内存映像。
//find_sec(info,x)函数用来在文件映像中找到节名为x,且属性带SHF_ALLOC的节的索引,找不到返回0.
info->index.vers = find_sec(info, "__versions");
info->index.info = find_sec(info, ".modinfo");
info->sechdrs[info->index.info].sh_flags &= ~(unsigned long)SHF_ALLOC;
info->sechdrs[info->index.vers].sh_flags &= ~(unsigned long)SHF_ALLOC;
return 0;
}
####sys_init_module->load_module->layout_and_allocate->check_modinfo
static int check_modinfo(struct module *mod, struct load_info *info)
{
//在.modinfo节区查找vermagic = 开头的字符串(相当于作为key查找),返回的是vermagic=后面的字符串
//如vermagic=3.4.0-g35ba5ff preempt mod_unload ARMv7 p2v8
//返回"3.4.0-g35ba5ff preempt mod_unload ARMv7 p2v8"
const char *modmagic = get_modinfo(info, "vermagic");
int err;
if (!modmagic) {
//没找到版本信息
//如果配置了CONFIG_MODULE_FORCE_LOAD,则强制加载,标记taints,返回true,否则返回错误(-ENOEXEC)
err = try_to_force_load(mod, "bad vermagic");
if (err)
return err;
} else if (!same_magic(modmagic, vermagic, info->index.vers)) {
//字符串比较,如果有vers,则加上vers比较
//vermagic 是个全局变量,在最终导出的vmlinux中可以找到。
return -ENOEXEC;
}
//标记内核是否被污染
if (!get_modinfo(info, "intree"))
add_taint_module(mod, TAINT_OOT_MODULE);
if (get_modinfo(info, "staging")) {
add_taint_module(mod, TAINT_CRAP);
/* Set up license info based on the info section */
set_license(mod, get_modinfo(info, "license"));
return 0;
}
####sys_init_module->load_module->layout_and_allocate->layout_sections
static void layout_sections(struct module *mod, struct load_info *info)
{
//这个数组中的所有项,都有SHF_ALLOC属性,但只有第一行这种是可执行的
static unsigned long const masks[][2] = {
{ 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 }
};
unsigned int m, i;
//e_shnum为节区头部表中的表项数
for (i = 0; i < info->hdr->e_shnum; i++)
//将每个节区的元素长度设为0xffff???
info->sechdrs[i].sh_entsize = ~0UL;
//对masks数组中的每种节属性组合,执行以下操作
for (m = 0; m < ARRAY_SIZE(masks); ++m) {
//遍历所有节区
for (i = 0; i < info->hdr->e_shnum; ++i) {
//获取第i个节表的头部表
Elf_Shdr *s = &info->sechdrs[i];
//这个节的名字
const char *sname = info->secstrings + s->sh_name;
//对于满足mask的非.init开头的节
if (
//如果节区与masks掩码不匹配
(s->sh_flags & masks[m][0]) != masks[m][0]
//或者节区flag不匹配
|| (s->sh_flags & masks[m][1])
//或者节区元素长度不匹配
|| s->sh_entsize != ~0UL
//或者是初始化段
|| strstarts(sname, ".init"))
//则不处理
continue;
//这里剩下的主要是有SHF_ALLOC的非初始化段了,这样的段最终会被放到module.module_core,也就是内存映像中去,这里重用s->sh_entsize,来记录当前段在内存映像中的偏移。core_size为最终内存映像的大小,get_offset的时候会加上当前这个节的大小。
s->sh_entsize = get_offset(mod, &mod->core_size, s, i);
}
//对于各种类型的段的大小,记录到mod都相应字段中
switch (m) {
case 0: /* executable */
//对其
mod->core_size = debug_align(mod->core_size);
//如果段属性为 SHF_EXECINSTR | SHF_ALLOC,则记录到core_text_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;
}
}
//对于只在初始化时候用到的段,做相同的处理
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;
//满足mask的.init开头的节
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);
}
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;
}
}
}
####sys_init_module->load_module->layout_and_allocate->layout_symtab
static void layout_symtab(struct module *mod, struct load_info *info)
{
//符号表节区起始地址
Elf_Shdr *symsect = info->sechdrs + info->index.sym;
//字符串表节区起始地址
Elf_Shdr *strsect = info->sechdrs + info->index.str;
//临时变量,指向当前符号
const Elf_Sym *src;
unsigned int i, nsrc, ndst, strtab_size;
//给符号表加上SHF_ALLOC属性
symsect->sh_flags |= SHF_ALLOC;
//sh_entsize = 符号表所在节区对齐后的大小
symsect->sh_entsize = get_offset(mod, &mod->init_size, symsect,info->index.sym) | INIT_OFFSET_MASK;
//指向第一个符号表项
src = (void *)info->hdr + symsect->sh_offset;
//符号表项数
nsrc = symsect->sh_size / sizeof(*src);
/* strtab always starts with a nul, so offset 0 is the empty string. */
strtab_size = 1; //字符串表所在节开始的字符串是个空串.
//计算所有核心符号字符串的总大小
for (ndst = i = 0; i < nsrc; i++) {
if (i == 0 ||
//如果是核心符号(核心符号指的是:非SHN_UNDEF,符号的st_shndx与一个节区绑定,且被绑定的节区有ALLOC属性的符号)
is_core_symbol(src+i, info->sechdrs, info->hdr->e_shnum)) {
//加上符号对应字符串的长度,i=0的时候是个空串
strtab_size += strlen(\
&info->strtab[src[i].st_name])+1;
ndst++;
}
}
//在core_size的末尾增加这两个表的空间,符号表和其字符串表
info->symoffs = ALIGN(mod->core_size, symsect->sh_addralign ?: 1);
info->stroffs = mod->core_size = info->symoffs + ndst * sizeof(Elf_Sym);
mod->core_size += strtab_size;
//给字符串表加上SHF_ALLOC属性,并算到module_init中。
strsect->sh_flags |= SHF_ALLOC;
strsect->sh_entsize = get_offset(mod, &mod->init_size, strsect,info->index.str) | INIT_OFFSET_MASK;
}
####sys_init_module->load_module->layout_and_allocate->move_module
static int move_module(struct module *mod, struct load_info *info)
{
int i;
void *ptr;
//为模块分配core_size大小的内存
ptr = module_alloc_update_bounds(mod->core_size);
//标记到内存泄露检测里
kmemleak_not_leak(ptr);
if (!ptr)
return -ENOMEM;
//初始化
memset(ptr, 0, mod->core_size);
//设置module.module_core指针
mod->module_core = ptr;
//为init分配空间
ptr = module_alloc_update_bounds(mod->init_size);
//当泄漏时不扫描或报告对象
kmemleak_ignore(ptr);
if (!ptr && mod->init_size) {
module_free(mod, mod->module_core);
return -ENOMEM;
}
memset(ptr, 0, mod->init_size);
//设置module.module_init
mod->module_init = ptr;
//复制所有有SHF_ALLOC属性的节到新内核空间
for (i = 0; i < info->hdr->e_shnum; i++) {
void *dest;
//第i个节区的头部表
Elf_Shdr *shdr = &info->sechdrs[i];
//如果没有SHF_ALLOC属性,则continue
if (!(shdr->sh_flags & SHF_ALLOC))
continue;
//如果这个节区属于init的节区
if (shdr->sh_entsize & INIT_OFFSET_MASK)
//找到当前这个节应该复制到的位置
dest = mod->module_init + (shdr->sh_entsize & ~INIT_OFFSET_MASK);
else
//如果是非init也一样
dest = mod->module_core + shdr->sh_entsize
if (shdr->sh_type != SHT_NOBITS)
//复制数据
memcpy(dest, (void *)shdr->sh_addr, shdr->sh_size);
//修改复制后的文件映像中所有节的sh_addr指向内核新分配的地址,注:sh_addr是在节表头部表中的,而复制到内核中的只是有SHF_ALLOC的节表,节表头部表是没有复制到内核中的。
shdr->sh_addr = (unsigned long)dest;
}
return 0;
}
###sys_init_module->load_module->simplify_symbols
//修复所有的符号表,如果是模块内部符号,则重定位一下地址
//如果是模块外部符号,则解决未觉引用。
static int simplify_symbols(struct module *mod, const struct load_info *info)
{
//符号表节表的首地址(文件映像中)
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 st_shndx可以认为是个索引
switch (sym[i].st_shndx) {
case SHN_COMMON:
//一般来说,未初始化的全局符号是这种类型,在ko文件中
//不应该出现这种类型的符号,出现则直接返回错误了
ret = -ENOEXEC;
break;
case SHN_ABS:
//包含一个绝对值,不受重定位的影响,可能是节索引编号
break;
case SHN_UNDEF:
//这是一个未定义符号,需要内核来找这个符号的定义
//内核会查找自身+所有模块,如果找到,返回ksym,
//指向这个符号。
ksym = resolve_symbol_wait(mod, info, name);
if (ksym && !IS_ERR(ksym)) {
//符号表的这个st_value,指向这个符号的内存地址
sym[i].st_value = ksym->value;
break;
}
/* Ok if weak. */
if (!ksym && ELF_ST_BIND(sym[i].st_info) == STB_WEAK)
break;
ret = PTR_ERR(ksym) ?: -ENOENT;
break;
default:
//其他的st_shndex,都认为是指向节区索引,则找到节区起始地址(内存映像中的)
if (sym[i].st_shndx == info->index.pcpu)
//pcpu要单独处理的。
secbase =(unsigned long)mod_percpu(mod);
else
secbase=info->sechdrs[sym[i].st_shndx]. sh_addr;
//st_value是偏移,这里加上节区起始地址(内存映像中的),为符号真正的地址。
sym[i].st_value += secbase;
break;
}
}
return ret;
}
####sys_init_module->load_module->simplify_symbols->resolve_symbol_wait
//此函数主要调用resolve_symbol,这里直接分析resolve_symbol
static const struct kernel_symbol *resolve_symbol(
struct module *mod,
const struct load_info *info,
const char *name,
char ownername[])
{
struct module *owner;
const struct kernel_symbol *sym;
const unsigned long *crc;
int err;
mutex_lock(&module_mutex);
//返回符号对应的符号表,所属模块,crc
sym = find_symbol(name, &owner, &crc,!(mod->taints & (1 << TAINT_PROPRIETARY_MODULE)), true);
if (!sym)
goto unlock;
//检测内核中当前符号的crc和新模块中的记录的crc是否相符
if (!check_version(info->sechdrs, info->index.vers, name, mod, crc,owner)) {
sym = ERR_PTR(-EINVAL);
goto getname;
}
//增加模块间的依赖关系
err = ref_module(mod, owner);
if (err) {
sym = ERR_PTR(err);
goto getname;
}
getname:
strncpy(ownername, module_name(owner), MODULE_NAME_LEN);
unlock:
mutex_unlock(&module_mutex);
return sym;
}
//其中最主要函数为find_symbol:
//查找内核符号,返回描述该符号的内核结构体指针,如果内核符号存在于
//动态插入的模块,且owner不会空,则owner返回符号所在模块的的struct module *
const struct kernel_symbol *find_symbol(
const char *name, //要查找的内核符号名 ,输入
//是否返回模块的struct module,输出
struct module **owner,
//返回内核符号crc的地址(输出参数),输出
const unsigned long **crc,
bool gplok, //表示当前要搜索的模块是不是gplok的,输入
bool warn) //表示是否输出警告,输入
{
//临时结构,为了不污染输出参数,且让子函数变得简洁
struct find_symbol_arg fsa;
fsa.name = name;
fsa.gplok = gplok;
fsa.warn = warn;
//此函数对内核所有的符号表进行搜索,顺序是:
//内核自身的__ksymtab, __ksymtab_gpl, __ksymtab_gpl_future
//每个模块的__ksymtab, __ksymtab_gpl, __ksymtab_gpl_future
//如果找到了,则根据当前搜索模块的性质(即gplok参数)判断是否返回true
//如果当前传入的gplok = true,表示当前模块是gplok的,返回true
//如果传入的gplok = false, 则符号属于__ksymtab节区返回true, __ksymtab_gpl_future也返回true,提示warning,表示这个模块以后会是gpl的, __ksymtab_gpl 返回false了
//如果要返回true,在返回之前会设置fsa.owner,表示从哪个模块搜出来的
//fsa.sym表示当前符号的kernel_symbol结构
//fsa.crc返回这个函数的crc
if (each_symbol_section(find_symbol_in_section, &fsa)) {
if (owner)
*owner = fsa.owner;
if (crc)
*crc = fsa.crc;
return fsa.sym;
}
return NULL;
}