内核模块的加载

文章来源:http://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->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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值