天天看點

再談核心子產品加載(三)—子產品加載流程(下)

版權聲明:本文為CSDN部落客「[email protected]」的原創文章,遵循CC 4.0 BY-SA版權協定,轉載請附上原文出處連結及本聲明。

原文連結:https://blog.csdn.net/lidan113lidan/article/details/119813552

更多内容可關注微信公衆号 

再談核心子產品加載(三)—子產品加載流程(下)

   上接<再談核心子產品加載(二)—子產品加載流程(上)>

9.檢查是否有同名子產品已加載或正在加載

    err = add_unformed_module(mod);
    if (err)
        goto free_module;
           

    unformed是指加載到一半(也就是執行完目前函數),但還沒有完全加載好的子產品,此函數根據this_module.name檢測目前系統裡是否有同名子產品:

  • 如果有同名子產品但狀态是正在執行module_init或unformed(也就是和目前同狀态),則等待之前的子產品加載完成事件後重新檢測
  • 如果有同名子產品,但不屬于前面兩種狀态,則直接加載失敗(-EEXIST)
  • 如果沒有同名子產品,則将目前子產品加到核心的子產品樹清單中(表頭為mod_tree)

    此函數最後會通過mod_update_bounds函數更新核心已有子產品的位址範圍邊界.

static int add_unformed_module(struct module *mod)
{
    int err;
    struct module *old;
    mod->state = MODULE_STATE_UNFORMED;

again:
    mutex_lock(&module_mutex);
    //查找目前核心是否已經有同名子產品了,非正式的也算
    old = find_module_all(mod->name, strlen(mod->name), true);
    if (old != NULL) {
        //若此子產品是正在執行module_init,或和目前同狀态的子產品
        if (old->state == MODULE_STATE_COMING
            || old->state == MODULE_STATE_UNFORMED) {
            /* Wait in case it fails to load. */
            mutex_unlock(&module_mutex);
            //則等待子產品加載完畢,重新跑
            err = wait_event_interruptible(module_wq,
                           finished_loading(mod->name));
            if (err)
                goto out_unlocked;
            goto again;
        }
        //若非以上兩個狀态,代表同名子產品已經存在了,注意這裡子產品的名字是mod->name
        err = -EEXIST;
        goto out;
    }
    //更新系統已有子產品的VA位址邊界
    mod_update_bounds(mod);
    list_add_rcu(&mod->list, &modules);
    //在子產品樹中更新此子產品
    mod_tree_insert(mod);
    err = 0;

out:
    mutex_unlock(&module_mutex);
out_unlocked:
    return err;
}
           

10.為子產品簽名失敗但放過的情況提示warning

#ifdef CONFIG_MODULE_SIG
    /*
      若子產品簽名校驗失敗,則提示warning設定taint
      如果要求強制子產品簽名,前面module_sig_check就直接break了
    */
    mod->sig_ok = info->sig_ok;
    if (!mod->sig_ok) {
        pr_notice_once("%s: module verification failed: signature "
                   "and/or required key missing - tainting "
                   "kernel\n", mod->name);
        add_taint_module(mod, TAINT_UNSIGNED_MODULE, LOCKDEP_STILL_OK);
    }
#endif
           

11.precpu變量配置設定記憶體,子產品解除安裝資料結構的初始化,sysfs的mutex初始化

    err = percpu_modalloc(mod, info);
    if (err)
        goto unlink_mod;
    /*
      沒開啟CONFIG_MODULE_UNLOAD函數(代表不允許子產品解除安裝)則這裡為空
      開啟了這裡也隻是個初始化函數
    */
    err = module_unload_init(mod);
    if (err)
        goto unlink_mod;

    //sysfs的mutex初始話
    init_param_lock(mod);
           

  這裡要注意,子產品除了init_layout和core_layout外,還單獨為percpu變量配置設定了記憶體,記錄在mod.percpu中

12.記憶體布局中struct module的初始化與檢查

    err = find_module_sections(mod, info);
    if (err)
        goto free_unload;
    err = check_module_license_and_versions(mod);
    if (err)
        goto free_unload;
           

  find_module_sections 函數擷取子產品布局中節區的首位址傳回到mod的各個字段中(如mod->kp),同時傳回節區中的元素個數(如mod->num_kp),具體含義參考struct module結構體的定義,這裡查找子產品布局的方法實際上還是周遊子產品記憶體二進制的節區頭部表,但由于在move_module函數中配置設定子產品布局記憶體時已經将節區頭部表中的sh_addr修正為記憶體布局中此節區的位址,故這裡傳回的也是記憶體布局中的位址.

static int find_module_sections(struct module *mod, struct load_info *info)
{
    /*
        輸出參數sizeof(*mod->kp)指定一個元素的大小,傳回值為記憶體布局中此節區的首位址
       節區内元素個數記錄重寫回到mod->num_kp中.
    */
    mod->kp = section_objs(info, "__param",
                   sizeof(*mod->kp), &mod->num_kp);

    mod->syms = section_objs(info, "__ksymtab",
                 sizeof(*mod->syms), &mod->num_syms);
    mod->crcs = section_addr(info, "__kcrctab");
    mod->gpl_syms = section_objs(info, "__ksymtab_gpl",
                     sizeof(*mod->gpl_syms),
                     &mod->num_gpl_syms);
    mod->gpl_crcs = section_addr(info, "__kcrctab_gpl");
    mod->gpl_future_syms = section_objs(info,
                        "__ksymtab_gpl_future",
                        sizeof(*mod->gpl_future_syms),
                        &mod->num_gpl_future_syms);
    mod->gpl_future_crcs = section_addr(info, "__kcrctab_gpl_future");

#ifdef CONFIG_UNUSED_SYMBOLS
    mod->unused_syms = section_objs(info, "__ksymtab_unused",
                    sizeof(*mod->unused_syms),
                    &mod->num_unused_syms);
    mod->unused_crcs = section_addr(info, "__kcrctab_unused");
    mod->unused_gpl_syms = section_objs(info, "__ksymtab_unused_gpl",
                        sizeof(*mod->unused_gpl_syms),
                        &mod->num_unused_gpl_syms);
    mod->unused_gpl_crcs = section_addr(info, "__kcrctab_unused_gpl");
#endif
#ifdef CONFIG_CONSTRUCTORS
    mod->ctors = section_objs(info, ".ctors",
                  sizeof(*mod->ctors), &mod->num_ctors);
    if (!mod->ctors)
        mod->ctors = section_objs(info, ".init_array",
                sizeof(*mod->ctors), &mod->num_ctors);
    else if (find_sec(info, ".init_array")) {
        /*
         * This shouldn't happen with same compiler and binutils
         * building all parts of the module.
         */
        pr_warn("%s: has both .ctors and .init_array.\n",
               mod->name);
        return -EINVAL;
    }
#endif
           

  check_module_license_and_versions函數主要用來過濾一些子產品,并檢查CRC表是否正确. 此函數首先通過子產品名過濾了一些子產品,然後在核心定義了CONFIG_MODVERSIONS的情況下(代表要檢查子產品中函數的CRC),如果子產品有某類型的導出函數,則要有此類型的crc表,沒有則報錯。

static int check_module_license_and_versions(struct module *mod)
{
    int prev_taint = test_taint(TAINT_PROPRIETARY_MODULE);
    /*
        這裡還單獨加了case....
    */
    if (strcmp(mod->name, "ndiswrapper") == 0)
        add_taint(TAINT_PROPRIETARY_MODULE, LOCKDEP_NOW_UNRELIABLE);

    /* driverloader was caught wrongly pretending to be under GPL */
    if (strcmp(mod->name, "driverloader") == 0)
        add_taint_module(mod, TAINT_PROPRIETARY_MODULE,
                 LOCKDEP_NOW_UNRELIABLE);

    /* lve claims to be GPL but upstream won't provide source */
    if (strcmp(mod->name, "lve") == 0)
        add_taint_module(mod, TAINT_PROPRIETARY_MODULE,
                 LOCKDEP_NOW_UNRELIABLE);

    if (!prev_taint && test_taint(TAINT_PROPRIETARY_MODULE))
        pr_warn("%s: module license taints kernel.\n", mod->name);

#ifdef CONFIG_MODVERSIONS
    /*
        這裡是通用檢查,如果目前子產品有導出符号,但導出符号沒有CRC,而目前核心又配置了
        CONFIG_MODVERSIONS,則代表目前核心被污染了.
    */
    if ((mod->num_syms && !mod->crcs)
        || (mod->num_gpl_syms && !mod->gpl_crcs)
        || (mod->num_gpl_future_syms && !mod->gpl_future_crcs)
#ifdef CONFIG_UNUSED_SYMBOLS
        || (mod->num_unused_syms && !mod->unused_crcs)
        || (mod->num_unused_gpl_syms && !mod->unused_gpl_crcs)
#endif
        ) {
        //這裡要增加taint标記
        return try_to_force_load(mod,
                     "no versions for exported symbols");
    }
#endif
    return 0;
}
           

13.為init_layout中的靜态連結符号表做符号決議

    err = simplify_symbols(mod, info);
    if (err < 0)
        goto free_modinfo;
           

  此函數叫simplify_symbols的意思是此函數決議了子產品靜态連結符号表中的符号,後面再使用此符号時候,不需要再決議了,是以稱為簡化.   此函數實際上是讓子產品記憶體中的靜态連結重定位表(init_layout中)中的符号,全部指向其真實的記憶體位址:    * 對于未定義符号/弱符号找核心(已加載子產品)的符号表,    * 其他符号(屬于目前子產品)則直接使用st_value += 段基址計算

static int simplify_symbols(struct module *mod, const struct load_info *info)
{
    //擷取子產品記憶體中的符号表,這個是存在init_layout中的,完全複制自目标檔案的符号表
    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 (sym[i].st_shndx) {
        /*
          正常應該是沒有common符号的(-fno-common),也就是說.S中不能有.comm定義
          正常的未初始化全局變量都直接扔bss段.
        */
        case SHN_COMMON:
            /* Ignore common symbols */
            if (!strncmp(name, "__gnu_lto", 9))
                break;

            /* We compiled with -fno-common.  These are not
               supposed to happen.  */
            pr_debug("Common symbol: %s\n", name);
            pr_warn("%s: please compile with -fno-common\n",
                   mod->name);
            ret = -ENOEXEC;
            break;

        //絕對符号不處理
        case SHN_ABS:
            /* Don't need to do anything */
            pr_debug("Absolute symbol: 0x%08lx\n",
                   (long)sym[i].st_value);
            break;

        case SHN_LIVEPATCH:
            /* Livepatch symbols are resolved by livepatch */
            break;

        //未定義符号
        case SHN_UNDEF:
            /*
               此函數負責在核心和所有已加載子產品中查找名為name的符号,若找到了傳回此符号的符号表項,
              并設定ownername為其擁有者的名,如果開啟了CRC校驗則會同時檢測CRC.
               子產品中的未定義符号實際上是在編譯期間引用了編譯環境的核心或其他子產品中的符号,雖然是
              未定義,但其編譯時候會記錄此符号的CRC,
               這裡的CRC檢查,實際上檢查的是子產品編譯環境符号的CRC和子產品運作環境符号的CRC是否一緻.
            */
            ksym = resolve_symbol_wait(mod, info, name);
            /* Ok if resolved.  */
            if (ksym && !IS_ERR(ksym)) {
                //将靜态連結符号表中,此符号的值,設定為核心符号中此符号的值
                sym[i].st_value = kernel_symbol_value(ksym);
                break;
            }

            /* Ok if weak.  */
            //如果在核心沒有找到此符号,且此未定義符号是一個弱符号,則未定義就未定義了
            //弱符号應該在重定位的時候是按照未定義處理的,這樣才會找一下是否有重名符号
            if (!ksym && ELF_ST_BIND(sym[i].st_info) == STB_WEAK)
                break;

            //否則就是沒找到符号,報錯
            ret = PTR_ERR(ksym) ?: -ENOENT;
            pr_warn("%s: Unknown symbol %s (err %d)\n",
                mod->name, name, ret);
            break;

        default:
            /* Divert to percpu allocation if a percpu var. */
            if (sym[i].st_shndx == info->index.pcpu)
                //percpu變量,則直接使用percpu(段)的首位址
                secbase = (unsigned long)mod_percpu(mod);
            else
                //sym[i].st_shndx是符号表所在的節區,這裡擷取節區首位址
                secbase = info->sechdrs[sym[i].st_shndx].sh_addr;
            //符号值直接加上節區首位址
            sym[i].st_value += secbase;
            break;
        }
    }
    return ret;
}
           

14.子產品記憶體布局的靜态連結重定位

    err = apply_relocations(mod, info);
    if (err < 0)
        goto free_modinfo;
           

  此函數周遊目标檔案中的所有記憶體節區(SHF_ALLOC)的重定位節區(REL/RELA),并周遊每個節區中的每個靜态連結重定位表項,對其做靜态連結。apply_relocate_add是真正負責重定位的函數,其中一共處理了月40種重定位類型。這裡需要說明的是,對于CALL26/JUMP26類型的指令,若短跳轉不夠有可能會嘗試基于plt的長跳轉(CONFIG_ARM64_MODULE_PLTS開啟情況下,未開啟則直接報錯),此時會在子產品core/init_layout的plt表中增加一個表項.

  此函數之後,子產品作為目标檔案的靜态連結動态的完成了.

static int apply_relocations(struct module *mod, const struct load_info *info)
{
    unsigned int i;
    int err = 0;

    /* Now do relocations. */
    /*
       這裡實際上是周遊所有重定位表(類型為SHT_REL或SHT_RELA),若其要重定位的節區是記憶體節區 (SHF_ALLOC),則處理,否則pass.
       在目标檔案中隻有靜态連結重定位表,每個重定位表都隻對應一個節區.
    */
    for (i = 1; i < info->hdr->e_shnum; i++) {
        unsigned int infosec = info->sechdrs[i].sh_info;
        /* Not a valid relocation section? */
        /*
          對于REL/RELA類型節區來說sh_info記錄其要重定位的節區,這裡是判斷是否節區越界
        */
        if (infosec >= info->hdr->e_shnum)
            continue;

        /* Don't bother with non-allocated sections */
        //如果重定位的目标節區是記憶體節區
        if (!(info->sechdrs[infosec].sh_flags & SHF_ALLOC))
            continue;

        /* Livepatch relocation sections are applied by livepatch */
        if (info->sechdrs[i].sh_flags & SHF_RELA_LIVEPATCH)
            continue;

        if (info->sechdrs[i].sh_type == SHT_REL)
            err = apply_relocate(info->sechdrs, info->strtab,
                         info->index.sym, i, mod);
        else if (info->sechdrs[i].sh_type == SHT_RELA)
            /*
                sechdrs是節區頭部表指針
                strtab是符号表字元串表
                info->index.sym是符号表節區索引
                i是目前的重定位節區索引
                mod是目前子產品
                此函數負責具體類型的重定位操作,對于超過範圍的call26/jump26指令,若開啟了CONFIG_ARM64_MODULE_PLTS,則會在PLT表中為其增加一個表項.
            */
            err = apply_relocate_add(info->sechdrs, info->strtab,
                         info->index.sym, i, mod);
        if (err < 0)
            break;
    }
    return err;
}
           

  注:   靜态連結一共分為三步:位址空間配置設定,符号決議,重定位,正好對應子產品加載時的三個階段:

  1. move_module包括了子產品記憶體空間布局的配置設定
  2. simplify_symbols負責子產品中的未定義符号的決議(和已定義符号的位址修正)
  3. apply_relocations負責靜态連結的重定位(使用的是靜态連結重定位表)

    此三步和靜态連結的步驟一模一樣,故子產品加載實際上可以認為是核心運作時動态的執行了一次靜态連結流程,向自身動态的靜态連結了一個目标檔案.

15.重排異常表、per-cpu變量複制、複制核心符号表

    err = post_relocation(mod, info);
    if (err < 0)
        goto free_modinfo;
           

  post_relocation一共包含3個主要函數,其中:

  • sort_extable 負責重排子產品異常向量表
  • percpu_modcopy 負責将per-cpu變量的初始複制到各個cpu的記憶體
  • add_kallsyms負責從init_layout中的靜态連結符号表複制所有需要複制的符号到core_layout的核心符号表(同時包括核心符号表字元串表、類型表的複制)
static int post_relocation(struct module *mod, const struct load_info *info)
{
    /* Sort exception table now relocations are done. */
    //重定位結束後要重排一下異常向量表
    sort_extable(mod->extable, mod->extable + mod->num_exentries);

    /* Copy relocated percpu area over. */
    //per-cpu變量複制到各個cpu對應記憶體
    percpu_modcopy(mod, (void *)info->sechdrs[info->index.pcpu].sh_addr,
               info->sechdrs[info->index.pcpu].sh_size);

    /* Setup kallsyms-specific fields. */
    //這裡從靜态連結符号表中複制子產品的核心符号表
    add_kallsyms(mod, info);

    /* Arch-specific module finalizing. */
    return module_finalize(info->hdr, info->sechdrs, mod);
}
           

16.init/core_layout緩存重新整理

   flush_module_icache(mod);
           

  此函數負責重新整理init/core_layout VA[base,base+size]範圍内的指令緩存

17.複制使用者态參數到核心

    //使用者參數複制到核心,并記錄到args中
    mod->args = strndup_user(uargs, ~0UL >> 1);
    if (IS_ERR(mod->args)) {
        err = PTR_ERR(mod->args);
        goto free_arch_cleanup;
    }
           

18.導出符号檢查與RONX保護

    err = complete_formation(mod, info);
    if (err)
        goto ddebug_cleanup;
           

  此函數包含兩步操作:

  1)verify_exported_symbols函數周遊子產品中的所有導出函數,并檢查在核心中是否有出現同名的導出符号     實際上周遊的是子產品的syms/gpl_syms/gpl_future_syms等所有導出表,然後針對每個導出表中的每個導出符号,在核心查找是否已有同名的導出符号.     但需要注意的是,這裡隻檢查了子產品導出的符号在核心是否有重名符号,并沒有說子產品中的所有符号(核心符号或靜态連結符号表中的符号)都不能重名.   2)為子產品的init_layout/core_layout做RONX保護     RONX的邏輯後面核心安全特性中單獨說,這裡需要強調一點事ro_after_init段在這裡設定為rw的了,子產品初始化之後才會設定回RO

static int complete_formation(struct module *mod, struct load_info *info)
{
    int err;
    mutex_lock(&module_mutex);
    /* Find duplicate symbols (must be called under lock). */
    /*
      這裡是檢驗核心中是否有同名的導出符号,實際上是周遊syms/gpl_syms/gpl_future_syms等子產品的多個導出表
     然後針對每個導出符号,在核心查找是否已有同名的導出符号.
    */
    err = verify_exported_symbols(mod);
    if (err < 0)
        goto out;

    //所有該RO的RO
    module_enable_ro(mod, false);
    //除了text段全NX
    module_enable_nx(mod);

    /* Mark state as coming so strong_try_module_get() ignores us,
     * but kallsyms etc. can see us. */
    mod->state = MODULE_STATE_COMING;
    mutex_unlock(&module_mutex);
    return 0;
out:
    mutex_unlock(&module_mutex);
    return err;
}
           

19.發送子產品加載通知鍊

    err = prepare_coming_module(mod);
    if (err)
        goto bug_cleanup;
           

  這個沒啥可說的

static int prepare_coming_module(struct module *mod)
{
    int err;

    ftrace_module_enable(mod);
    err = klp_module_coming(mod);
    if (err)
        return err;

    blocking_notifier_call_chain(&module_notify_list,
                     MODULE_STATE_COMING, mod);
    return 0;
}
           

20.參數解析與sysfs、livepatch設定

    /*
       此函數負責子產品的參數解析,其中args是insmod傳入的實參,而kp則是子產品的實參類型,記錄在子產品的段__param中
    */
    after_dashes = parse_args(mod->name, mod->args, mod->kp, mod->num_kp,
                  -32768, 32767, mod,
                  unknown_module_param_cb);
    if (IS_ERR(after_dashes)) {
        err = PTR_ERR(after_dashes);
        goto coming_cleanup;
    } else if (after_dashes) {
        pr_warn("%s: parameters '%s' after `--' ignored\n",
               mod->name, after_dashes);
    }

    /* Link in to sysfs. */
    //連結到sysfs的輸出
    err = mod_sysfs_setup(mod, info, mod->kp, mod->num_kp);
    if (err < 0)
        goto coming_cleanup;
    
    //livepatch用的,pass
    if (is_livepatch_module(mod)) {
        err = copy_module_elf(mod, info);
        if (err < 0)
            goto sysfs_cleanup;
    }
           

  這個暫時也沒啥好說的

21. 調用子產品初始化函數,解除安裝init_layout并完成加載

    return do_init_module(mod);
           

  此函數主要負責:

  • 調用子產品的初始化函數(如果有構造函數則先調用構造函數,然後才是init函數)
  • 之後對ro_after_init段做隻讀保護,然後釋放掉整個init_layout的記憶體
  • 同時清空mod->init_layout資料結構,将mod->kallsyms指向核心符号表(mod->core_kallsyms,靜态連結符号表已随init_layout釋放了)
static noinline int do_init_module(struct module *mod)
{
    int ret = 0;
    struct mod_initfree *freeinit;

    //配置設定一個 mod_initfree 結構體
    freeinit = kmalloc(sizeof(*freeinit), GFP_KERNEL);
    if (!freeinit) {
        ret = -ENOMEM;
        goto fail;
    }
    //free結構體指向init_layout基位址
    freeinit->module_init = mod->init_layout.base;

    current->flags &= ~PF_USED_ASYNC;

    //若子產品指定了構造函數,則調用其構造函數
    do_mod_ctors(mod);
    
    /* Start the module */
    //若子產品有init函數,則調用其init函數,用do_one_initcall包裹前後有事件追蹤
    if (mod->init != NULL)
        ret = do_one_initcall(mod->init);
    if (ret < 0) {
        goto fail_free_freeinit;
    }
    if (ret > 0) {
        pr_warn("%s: '%s'->init suspiciously returned %d, it should "
            "follow 0/-E convention\n"
            "%s: loading module anyway...\n",
            __func__, mod->name, ret, __func__);
        dump_stack();
    }

    /* Now it's a first class citizen! */
    mod->state = MODULE_STATE_LIVE;
    //通知鍊
    blocking_notifier_call_chain(&module_notify_list,
                     MODULE_STATE_LIVE, mod);

    if (!mod->async_probe_requested && (current->flags & PF_USED_ASYNC))
        async_synchronize_full();

    ftrace_free_mem(mod, mod->init_layout.base, mod->init_layout.base +
            mod->init_layout.size);
    mutex_lock(&module_mutex);
    /* Drop initial reference. */
    module_put(mod);
    trim_init_extable(mod);
#ifdef CONFIG_KALLSYMS
    /* Switch to core kallsyms now init is done: kallsyms may be walking! */
    //子產品符号表指向核心符号表
    rcu_assign_pointer(mod->kallsyms, &mod->core_kallsyms);
#endif
    //這裡主要重新保護了一下ro_after_init
    module_enable_ro(mod, true);
    mod_tree_remove_init(mod);
    module_arch_freeing_init(mod);
    //清空init_layout相關結構體
    mod->init_layout.base = NULL;
    mod->init_layout.size = 0;
    mod->init_layout.ro_size = 0;
    mod->init_layout.ro_after_init_size = 0;
    mod->init_layout.text_size = 0;

    //這裡插入子產品的釋放隊列,實際上最終對應函數 do_free_init 釋放掉整個init_layout
    //這裡do_free_init應該是釋放實體記憶體+釋放VA空間,不需要提前設定init_layout的屬性
    if (llist_add(&freeinit->node, &init_free_list))
        schedule_work(&init_free_wq);

    mutex_unlock(&module_mutex);
    wake_up_all(&module_wq);

    return 0;

fail_free_freeinit:
    kfree(freeinit);
fail:
    /* Try to protect us from buggy refcounters. */
    mod->state = MODULE_STATE_GOING;
    synchronize_rcu();
    module_put(mod);
    blocking_notifier_call_chain(&module_notify_list,
                     MODULE_STATE_GOING, mod);
    klp_module_going(mod);
    ftrace_release_mod(mod);
    free_module(mod);
    wake_up_all(&module_wq);
    return ret;
}
           

到此子產品加載完畢,下圖更清晰的描述了此整個過程 

再談核心子產品加載(三)—子產品加載流程(下)