内核模块的加载

版权声明:本文为CSDN博主「ashimida@」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/lidan113lidan/article/details/45313535
更多内容可关注微信公众号在这里插入图片描述

更新

概述

  • 一般linux中有两个程序可以添加内核模块,modprobe和insmod,前者考虑到了各个模块之间可能出现的依赖关系,被依赖的模块会被自动载入,而insmod只是简单的尝试载入当前的模块。二者最终都是通过linux系统调用sys_init_module载入内核模块的。

  • 编译好的内核模块一般是以*.ko结尾的,这类文件都是可重定位文件。

  • elf的基本结构如图:
    这里写图片描述


###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;
}
  • 4
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值