版權聲明:本文為CSDN部落客「[email protected]」的原創文章,遵循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;
}