轉自:http://blog.chinaunix.net/uid-27717694-id-3966290.html
一、概述
模塊是作為ELF對象文件存放在文件系統中的,並通過執行insmod程序鏈接到內核中。對於每個模塊,系統都要分配一個包含以下數據結構的內存區。
一個module對象,表示模塊名的一個以null結束的字符串,實現模塊功能的代碼。在2.6內核以前,insmod模塊過程主要是通過modutils中的insmod加載,大量工作都是在用戶空間完成。但在2.6內核以后,系統使用busybox的insmod指令,把大量工作移到內核代碼處理,參見模塊加載過程代碼分析2.
二、相關數據結構
1.module對象描述一個模塊。一個雙向循環鏈表存放所有module對象,鏈表頭部存放在modules變量中,而指向相鄰單元的指針存放在每個module對象的list字段中。
struct module
{
/*state 表示該模塊的當前狀態。
enum module_state
{
MODULE_STATE_LIVE,
MODULE_STATE_COMING,
MODULE_STATE_GOING,
};
在裝載期間,狀態為 MODULE_STATE_COMING;
正常運行(完成所有初始化任務之后)時,狀態為 MODULE_STATE_LIVE;
在模塊正在卸載移除時,狀態為 MODULE_STATE_GOING.
*/
enum module_state state;//模塊內部狀態
//模塊鏈表指針,將所有加載模塊保存到一個雙鏈表中,鏈表的表頭是定義在 的全局變量 modules。
struct list_head list;
char name[MODULE_NAME_LEN];//模塊名
/* Sysfs stuff. */
struct module_kobject mkobj;//包含一個kobject數據結構
struct module_attribute *modinfo_attrs;
const char *version;
const char *srcversion;
struct kobject *holders_dir;
/*syms,num_syms,crcs 用於管理模塊導出的符號。syms是一個數組,有 num_syms 個數組項,
數組項類型為 kernel_symbol,負責將標識符(name)分配到內存地址(value):
struct kernel_symbol
{
unsigned long value;
const char *name;
};
crcs 也是一個 num_syms 個數組項的數組,存儲了導出符號的校驗和,用於實現版本控制
*/
const struct kernel_symbol *syms;//指向導出符號數組的指針
const unsigned long *crcs;//指向導出符號CRC值數組的指針
unsigned int num_syms;//導出符號數
struct kernel_param *kp;//內核參數
unsigned int num_kp;//內核參數個數
/*在導出符號時,內核不僅考慮了可以有所有模塊(不考慮許可證類型)使用的符號,還要考慮只能由 GPL 兼容模塊使用的符號。 第三類的符號當前仍然可以有任意許可證的模塊使用,但在不久的將來也會轉變為只適用於 GPL 模塊。gpl_syms,num_gpl_syms,gpl_crcs 成員用於只提供給 GPL 模塊的符號;gpl_future_syms,num_gpl_future_syms,gpl_future_crcs 用於將來只提供給 GPL 模塊的符號。unused_gpl_syms 和 unused_syms 以及對應的計數器和校驗和成員描述。 這兩個數組用於存儲(只適用於 GPL)已經導出, 但 in-tree 模塊未使用的符號。在out-of-tree 模塊使用此類型符號時,內核將輸出一個警告消息。
*/
unsigned int num_gpl_syms;//GPL格式導出符號數
const struct kernel_symbol *gpl_syms;//指向GPL格式導出符號數組的指針
const unsigned long *gpl_crcs;//指向GPL格式導出符號CRC值數組的指針
#ifdef CONFIG_MODULE_SIG
bool sig_ok;/* Signature was verified. */
#endif
/* symbols that will be GPL-only in the near future. */
const struct kernel_symbol *gpl_future_syms;
const unsigned long *gpl_future_crcs;
unsigned int num_gpl_future_syms;
/*如果模塊定義了新的異常,異常的描述保存在 extable數組中。 num_exentries 指定了數組的長度。 */
unsigned int num_exentries;
struct exception_table_entry *extable;
/*模塊的二進制數據分為兩個部分;初始化部分和核心部分。
前者包含的數據在轉載結束后都可以丟棄(例如:初始化函數),后者包含了正常運行期間需要的所有數據。
初始化部分的起始地址保存在 module_init,長度為 init_size 字節;
核心部分有 module_core 和 core_size 描述。
*/
int (*init)(void);//模塊初始化方法,指向一個在模塊初始化時調用的函數
void *module_init;//用於模塊初始化的動態內存區指針
void *module_core;//用於模塊核心函數與數據結構的動態內存區指針
//用於模塊初始化的動態內存區大小和用於模塊核心函數與數據結構的動態內存區指針
unsigned int init_size, core_size;
//模塊初始化的可執行代碼大小,模塊核心可執行代碼大小,只當模塊鏈接時使用
unsigned int init_text_size, core_text_size;
/* Size of RO sections of the module (text+rodata) */
unsigned int init_ro_size, core_ro_size;
struct mod_arch_specific arch;//依賴於體系結構的字段
/*如果模塊會污染內核,則設置 taints.污染意味着內核懷疑該模塊做了一個有害的事情,可能妨礙內核的正常運作。
如果發生內核恐慌(在發生致命的內部錯誤,無法恢復正常運作時,將觸發內核恐慌),那么錯誤診斷也會包含為什么內核被污染的有關信息。
這有助於開發者區分來自正常運行系統的錯誤報告和包含某些可疑因素的系統錯誤。
add_taint_module 函數用來設置 struct module 的給定實例的 taints 成員。
模塊可能因兩個原因污染內核:
1,如果模塊的許可證是專有的,或不兼容 GPL,那么在模塊載入內核時,會使用 TAINT_PROPRIETARY_MODULE.
由於專有模塊的源碼可能弄不到,模塊在內核中作的任何事情都無法跟蹤,因此,bug 很可能是由模塊引入的。
內核提供了函數 license_is_gpl_compatible 來判斷給定的許可證是否與 GPL 兼容。
2,TAINT_FORCED_MODULE 表示該模塊是強制裝載的。如果模塊中沒有提供版本信息,也稱為版本魔術(version magic),
或模塊和內核某些符號的版本不一致,那么可以請求強制裝載。
*/
unsigned int taints; /* same bits as kernel:tainted */
char *args;//模塊鏈接時使用的命令行參數
#ifdef CONFIG_SMP/
void __percpu *percpu;/*percpu 指向屬於模塊的各 CPU 數據。它在模塊裝載時初始化*/
unsigned int percpu_size;
#endif
#ifdef CONFIG_TRACEPOINTS
unsigned int num_tracepoints;
struct tracepoint * const *tracepoints_ptrs;
#endif
#ifdef HAVE_JUMP_LABEL
struct jump_entry *jump_entries;
unsigned int num_jump_entries;
#endif
#ifdef CONFIG_TRACING
unsigned int num_trace_bprintk_fmt;
const char **trace_bprintk_fmt_start;
#endif
#ifdef CONFIG_EVENT_TRACING
struct ftrace_event_call **trace_events;
unsigned int num_trace_events;
#endif
#ifdef CONFIG_FTRACE_MCOUNT_RECORD
unsigned int num_ftrace_callsites;
unsigned long *ftrace_callsites;
#endif
#ifdef CONFIG_MODULE_UNLOAD
/* What modules depend on me? */
struct list_head source_list;
/* What modules do I depend on? */
struct list_head target_list;
struct task_struct *waiter;//正卸載模塊的進程
void (*exit)(void);//模塊退出方法
/*module_ref 用於引用計數。系統中的每個 CPU,都對應到該數組中的數組項。該項指定了系統中有多少地方使用了該模塊。
內核提供了 try_module_get 和 module_put 函數,用對引用計數器加1或減1,如果調用者確信相關模塊當前沒有被卸載,
也可以使用 __module_get 對引用計數加 1.相反,try_module_get 會確認模塊確實已經加載。
struct module_ref {
unsigned int incs;
unsigned int decs;
}
*/
struct module_ref __percpu *refptr;//模塊計數器,每個cpu一個
#endif
#ifdef CONFIG_CONSTRUCTORS
/* Constructor functions. */
ctor_fn_t *ctors;
unsigned int num_ctors;
#endif
};
三、模塊鏈接過程
用戶可以通過執行insmod外部程序把一個模塊鏈接到正在運行的內核中。該過程執行以下操作:
1.從命令行中讀取要鏈接的模塊名
2.確定模塊對象代碼所在的文件在系統目錄樹中的位置。
3.從磁盤讀入存有模塊目標代碼的文件。
4.調用init_module()系統調用。函數將模塊二進制文件復制到內核,然后由內核完成剩余的任務。
5.init_module函數通過系統調用層,進入內核到達內核函數 sys_init_module,這是加載模塊的主要函數。
6.結束。
四、insmod過程解析
1.obj_file記錄模塊信息
struct obj_file
{
ElfW(Ehdr) header;//指向elf header
ElfW(Addr) baseaddr;//模塊的基址
struct obj_section **sections;//指向節區頭部表,包含每個節區頭部
struct obj_section *load_order;//節區load順序
struct obj_section **load_order_search_start;//
struct obj_string_patch_struct *string_patches;//patch的字符串
struct obj_symbol_patch_struct *symbol_patches;//patch的符號
int (*symbol_cmp)(const char *, const char *);//指向strcmp函數
unsigned long (*symbol_hash)(const char *);//指向obj_elf_hash函數
unsigned long local_symtab_size;//局部符號表大小
struct obj_symbol **local_symtab;//局部符號表
struct obj_symbol *symtab[HASH_BUCKETS];//符號hash表
const char *filename;//模塊名
char *persist;
};
2.obj_section記錄模塊節區信息
struct obj_section
{
ElfW(Shdr) header;//指向本節區頭結構
const char *name;//節區名
char *contents;//從節區頭內容偏移處獲得的節區內容
struct obj_section *load_next;//指向下一個節區
int idx;//該節區索引值
};
3.module_stat結構記錄每一個模塊的信息
struct module_stat {
char *name;//模塊名稱
unsigned long addr;//模塊地址
unsigned long modstruct; /* COMPAT_2_0! *//* depends on architecture? */
unsigned long size;//模塊大小
unsigned long flags;//標志
long usecount;//模塊計數
size_t nsyms;//模塊中的符號個數
struct module_symbol *syms;//指向模塊符號
size_t nrefs;//模塊依賴其他模塊的個數
struct module_stat **refs;//依賴的模塊數組
unsigned long status;//模塊狀態
};
4.
struct load_info {
Elf_Ehdr *hdr;//指向elf頭
unsigned long len;
Elf_Shdr *sechdrs;//指向節區頭
char *secstrings;//指向節區名稱的字符串節區
char *strtab;//指向符號節區
unsigned long symoffs, stroffs;
struct _ddebug *debug;
unsigned int num_debug;
bool sig_ok;
struct {
unsigned int sym, str, mod, vers, info, pcpu;
} index;
};
5.代碼分析
- int main(int argc, char **argv)
- {
- /* List of possible program names and the corresponding mainline routines */
- static struct { char *name; int (*handler)(int, char **); } mains[] =
- {
- { "insmod", &insmod_main },
- #ifdef COMBINE_modprobe
- { "modprobe", &modprobe_main },
- #endif
- #ifdef COMBINE_rmmod
- { "rmmod", &rmmod_main },
- #endif
- #ifdef COMBINE_ksyms
- { "ksyms", &ksyms_main },
- #endif
- #ifdef COMBINE_lsmod
- { "lsmod", &lsmod_main },
- #endif
- #ifdef COMBINE_kallsyms
- { "kallsyms", &kallsyms_main },
- #endif
- };
- #define MAINS_NO (sizeof(mains)/sizeof(mains[0]))
- static int mains_match;
- static int mains_which;
- char *p = strrchr(argv[0], '/');//查找‘/’字符出現的位置
- char error_id1[2048] = "The "; /* Way oversized */
- char error_id2[2048] = ""; /* Way oversized */
- int i;
- p = p ? p + 1 : argv[0];//得到命令符,這里是insmod命令
- for (i = 0; i < MAINS_NO; ++i) {
- if (i) {
- xstrcat(error_id1, "/", sizeof(error_id1));//字符串連接函數
- if (i == MAINS_NO-1)
- xstrcat(error_id2, " or ", sizeof(error_id2));
- else
- xstrcat(error_id2, ", ", sizeof(error_id2));
- }
- xstrcat(error_id1, mains[i].name, sizeof(error_id1));
- xstrcat(error_id2, mains[i].name, sizeof(error_id2));
- if (strstr(p, mains[i].name)) {//命令跟數組的數據比較
- ++mains_match;//insmod命令時,mains_match=1
- mains_which = i;//得到insmod命令所在數組的位置,insmod命令為0
- }
- }
- /* Finish the error identifiers */
- if (MAINS_NO != 1)
- xstrcat(error_id1, " combined", sizeof(error_id1));
- xstrcat(error_id1, " binary", sizeof(error_id1));
- if (mains_match == 0 && MAINS_NO == 1)
- ++mains_match; /* Not combined, any name will do */
- if (mains_match == 0) {
- error("%s does not have a recognisable name, ""the name must contain one of %s.",error_id1, error_id2);
- return(1);
- }
- else if (mains_match > 1) {
- error("%s has an ambiguous name, it must contain %s%s.", error_id1, MAINS_NO == 1 ? "" : "exactly one of ", error_id2);
- return(1);
- }
- else//mains_match=1,表示在數組中找到對應的指令
- return((mains[mains_which].handler)(argc, argv));//調用insmod_main()函數
- }
- int insmod_main(int argc, char **argv)
- {
- if (arch64())
- return insmod_main_64(argc, argv);
- else
- return insmod_main_32(argc, argv);
- }
- #if defined(COMMON_3264) && defined(ONLY_32)
- #define INSMOD_MAIN insmod_main_32 /* 32 bit version */
- #elif defined(COMMON_3264) && defined(ONLY_64)
- #define INSMOD_MAIN insmod_main_64 /* 64 bit version */
- #else
- #define INSMOD_MAIN insmod_main /* Not common code */
- #endif
- int INSMOD_MAIN(int argc, char **argv)
- {
- int k_version;
- int k_crcs;
- char k_strversion[STRVERSIONLEN];
- struct option long_opts[] = {
- {"force", 0, 0, 'f'},
- {"help", 0, 0, 'h'},
- {"autoclean", 0, 0, 'k'},
- {"lock", 0, 0, 'L'},
- {"map", 0, 0, 'm'},
- {"noload", 0, 0, 'n'},
- {"probe", 0, 0, 'p'},
- {"poll", 0, 0, 'p'}, /* poll is deprecated, remove in 2.5 */
- {"quiet", 0, 0, 'q'},
- {"root", 0, 0, 'r'},
- {"syslog", 0, 0, 's'},
- {"kallsyms", 0, 0, 'S'},
- {"verbose", 0, 0, 'v'},
- {"version", 0, 0, 'V'},
- {"noexport", 0, 0, 'x'},
- {"export", 0, 0, 'X'},
- {"noksymoops", 0, 0, 'y'},
- {"ksymoops", 0, 0, 'Y'},
- {"persist", 1, 0, 'e'},
- {"numeric-only", 1, 0, 'N'},
- {"name", 1, 0, 'o'},
- {"blob", 1, 0, 'O'},
- {"prefix", 1, 0, 'P'},
- {0, 0, 0, 0}
- };
- char *m_name = NULL;
- char *blob_name = NULL; /* Save object as binary blob */
- int m_version;
- ElfW(Addr) m_addr;
- unsigned long m_size;
- int m_crcs;
- char m_strversion[STRVERSIONLEN];
- char *filename;
- char *persist_name = NULL; /* filename to hold any persistent data */
- int fp;
- struct obj_file *f;
- struct obj_section *kallsyms = NULL, *archdata = NULL;
- int o;
- int noload = 0;
- int dolock = 1; /*Note: was: 0; */
- int quiet = 0;
- int exit_status = 1;
- int force_kallsyms = 0;
- int persist_parms = 0; /* does module have persistent parms? */
- int i;
- int gpl;
- error_file = "insmod";
- /* To handle repeated calls from combined modprobe */
- errors = optind = 0;
- /* Process the command line. */
- while ((o = getopt_long(argc, argv, "fhkLmnpqrsSvVxXyYNe:o:O:P:R:",&long_opts[0], NULL)) != EOF)
- switch (o) {
- case 'f': /* force loading */
- flag_force_load = 1;
- break;
- case 'h': /* Print the usage message. */
- insmod_usage();
- break;
- case 'k': /* module loaded by kerneld, auto-cleanable */
- flag_autoclean = 1;
- break;
- case 'L': /* protect against recursion. */
- dolock = 1;
- break;
- case 'm': /* generate load map */
- flag_load_map = 1;
- break;
- case 'n': /* don't load, just check */
- noload = 1;
- break;
- case 'p': /* silent probe mode */
- flag_silent_probe = 1;
- break;
- case 'q': /* Don't print unresolved symbols */
- quiet = 1;
- break;
- case 'r': /* allow root to load non-root modules */
- root_check_off = !root_check_off;
- break;
- case 's': /* start syslog */
- setsyslog("insmod");
- break;
- case 'S': /* Force kallsyms */
- force_kallsyms = 1;
- break;
- case 'v': /* verbose output */
- flag_verbose = 1;
- break;
- case 'V':
- fputs("insmod version " MODUTILS_VERSION "\n", stderr);
- break;
- case 'x': /* do not export externs */
- flag_export = 0;
- break;
- case 'X': /* do export externs */
- #ifdef HAS_FUNCTION_DESCRIPTORS
- fputs("This architecture has function descriptors, exporting everything is unsafe\n"
- "You must explicitly export the desired symbols with EXPORT_SYMBOL()\n", stderr);
- #else
- flag_export = 1;
- #endif
- break;
- case 'y': /* do not define ksymoops symbols */
- flag_ksymoops = 0;
- break;
- case 'Y': /* do define ksymoops symbols */
- flag_ksymoops = 1;
- break;
- case 'N': /* only check numeric part of kernel version */
- flag_numeric_only = 1;
- break;
- case 'e': /* persistent data filename */
- free(persist_name);
- persist_name = xstrdup(optarg);
- break;
- case 'o': /* name the output module */
- m_name = optarg;
- break;
- case 'O': /* save the output module object */
- blob_name = optarg;
- break;
- case 'P': /* use prefix on crc */
- set_ncv_prefix(optarg);
- break;
- default:
- insmod_usage();
- break;
- }
- if (optind >= argc) {//參數為0,則輸出insmod用法介紹
- insmod_usage();
- }
- filename = argv[optind++];//獲得要加載的模塊路徑名
- if (config_read(0, NULL, "", NULL) < 0) {//modutil配置相關???
- error("Failed handle configuration");
- }
- //清空persist_name
- if (persist_name && !*persist_name &&(!persistdir || !*persistdir)) {
- free(persist_name);
- persist_name = NULL;
- if (flag_verbose) {
- lprintf("insmod: -e \"\" ignored, no persistdir");
- ++warnings;
- }
- }
- if (m_name == NULL) {
- size_t len;
- char *p;
- //根據模塊的路徑名獲取模塊的名稱
- if ((p = strrchr(filename, '/')) != NULL)//找到最后一個'/'的位置
- p++;
- else
- p = filename;
- len = strlen(p);
- //去除模塊名的后綴,保存在m_name中
- if (len > 2 && p[len - 2] == '.' && p[len - 1] == 'o')
- len -= 2;
- else if (len > 4 && p[len - 4] == '.' && p[len - 3] == 'm'&& p[len - 2] == 'o' && p[len - 1] == 'd')
- len -= 4;
- #ifdef CONFIG_USE_ZLIB
- else if (len > 5 && !strcmp(p + len - 5, ".o.gz"))
- len -= 5;
- #endif
- m_name = xmalloc(len + 1);
- memcpy(m_name, p, len);//模塊名稱拷貝到m_name[]中
- m_name[len] = '\0';
- }
- //根據模塊路徑,檢查模塊是否存在
- if (!strchr(filename, '/') && !strchr(filename, '.')) {
- char *tmp = search_module_path(filename);//查找模塊路徑???
- if (tmp == NULL) {
- error("%s: no module by that name found", filename);
- return 1;
- }
- filename = tmp;
- lprintf("Using %s", filename);
- } else if (flag_verbose)
- lprintf("Using %s", filename);
- //打開要加載的模塊文件
- if ((fp = gzf_open(filename, O_RDONLY)) == -1) {
- error("%s: %m", filename);
- return 1;
- }
- /* Try to prevent multiple simultaneous loads. */
- if (dolock)
- flock(fp, LOCK_EX);
- /*
- type的三種類型,影響get_kernle_info的流程。
- #define K_SYMBOLS 1 //Want info about symbols
- #define K_INFO 2 Want extended module info
- #define K_REFS 4 Want info about references
- */
- //負責取得kernel中先以注冊的modules,放入module_stat中,並將kernel實現的各個symbol放入ksyms中,個數為ksyms。get_kernel_info最終調用new_get_kernel_info
- if (!get_kernel_info(K_SYMBOLS))
- goto out;
- set_ncv_prefix(NULL);//判斷symbol name中是否有前綴,象_smp之類,這里沒有。
- for (i = 0; !noload && i < n_module_stat; ++i) {//判斷是否有同名的模塊存在
- if (strcmp(module_stat[i].name, m_name) == 0) {//遍歷kernel中所有的模塊,比較名稱
- error("a module named %s already exists", m_name);
- goto out;
- }
- }
- error_file = filename;
- if ((f = obj_load(fp, ET_REL, filename)) == NULL)//將模塊文件讀入到struct obj_file結構f
- goto out;
- if (check_gcc_mismatch(f, filename))//檢查編譯器版本
- goto out;
- //檢查內核和module的版本信息
- k_version = get_kernel_version(k_strversion);
- m_version = get_module_version(f, m_strversion);
- if (m_version == -1) {
- error("couldn't find the kernel version the module was compiled for");
- goto out;
- }
- //接下來還要測試內核和模塊是否使用了版本的附加信息
- k_crcs = is_kernel_checksummed();
- m_crcs = is_module_checksummed(f);
- if ((m_crcs == 0 || k_crcs == 0) &&strncmp(k_strversion, m_strversion, STRVERSIONLEN) != 0) {
- if (flag_force_load) {
- lprintf("Warning: kernel-module version mismatch\n"
- "\t%s was compiled for kernel version %s\n"
- "\twhile this kernel is version %s",
- filename, m_strversion, k_strversion);
- ++warnings;
- } else {
- if (!quiet)
- error("kernel-module version mismatch\n"
- "\t%s was compiled for kernel version %s\n"
- "\twhile this kernel is version %s.",
- filename, m_strversion, k_strversion);
- goto out;
- }
- }
- if (m_crcs != k_crcs)//設置新的符號比較函數和hash函數,重構hash表(即symtab表)。
- obj_set_symbol_compare(f, ncv_strcmp, ncv_symbol_hash);
- //檢查GPL license
- gpl = obj_gpl_license(f, NULL) == 0;
- //替換模塊中的symbol值為其他已經存在的模塊中相同符號的值
- //如果內核模塊中有該符號,則將該符號的value該為內核中(ksyms[])的符號值
- //修改的是hash符號表中或者局部符號表中的符號值
- add_kernel_symbols(f, gpl);
- #ifdef COMPAT_2_0//linux 內核2.0版本以前
- if (k_new_syscalls ? !create_this_module(f, m_name): !old_create_mod_use_count(f))
- goto out;
- #else
- if (!create_this_module(f, m_name))//創建.this節區,添加一個"__this_module"符號
- goto out;
- #endif
- //這個函數的作用是創建文件的.GOT段,.GOT全稱是global offset table。在這個節區里保存的是絕對地址,這些地址不受重定位的影響。如果程序需要直接引用符號的絕對地址,這些符號就必須在.GOT段中出現
- arch_create_got(f);
- if (!obj_check_undefineds(f, quiet)) {//檢查是否還有未解析的symbol
- if (!gpl && !quiet) {
- if (gplonly_seen)
- error("\n"
- "Hint: You are trying to load a module without a GPL compatible license\n"
- " and it has unresolved symbols. The module may be trying to access\n"
- " GPLONLY symbols but the problem is more likely to be a coding or\n"
- " user error. Contact the module supplier for assistance, only they\n"
- " can help you.\n");
- else
- error("\n"
- "Hint: You are trying to load a module without a GPL compatible license\n"
- " and it has unresolved symbols. Contact the module supplier for\n"
- " assistance, only they can help you.\n");
- }
- goto out;
- }
- obj_allocate_commons(f);//處理未分配資源的符號
- //檢查模塊參數,即檢查那些模塊通過module_param(type, charp, S_IRUGO);聲明的參數
- check_module_parameters(f, &persist_parms);
- check_tainted_module(f, noload);//???
- if (optind < argc) {//如果命令行里帶了參數,處理命令行參數
- if (!process_module_arguments(f, argc - optind, argv + optind, 1))
- goto out;
- }
- //將符號“cleanup_module”,“init_module”,“kernel_version”的屬性改為local(局部),從而使其外部不可見
- hide_special_symbols(f);
- //如果命令行參數來自文件,將文件名保存好,下面要從那里讀出參數值
- if (persist_parms && persist_name && *persist_name) {
- f->persist = persist_name;
- persist_name = NULL;
- }
- //防止-e""這樣的惡作劇
- if (persist_parms &&persist_name && !*persist_name) {
- int j, l = strlen(filename);
- char *relative = NULL;
- char *p;
- for (i = 0; i < nmodpath; ++i) {
- p = modpath[i].path;
- j = strlen(p);
- while (j && p[j] == '/')
- --j;
- if (j < l && strncmp(filename, p, j) == 0 && filename[j] == '/') {
- while (filename[j] == '/')
- ++j;
- relative = xstrdup(filename+j);
- break;
- }
- }
- if (relative) {
- i = strlen(relative);
- if (i > 3 && strcmp(relative+i-3, ".gz") == 0)
- relative[i -= 3] = '\0';
- if (i > 2 && strcmp(relative+i-2, ".o") == 0)
- relative[i -= 2] = '\0';
- else if (i > 4 && strcmp(relative+i-4, ".mod") == 0)
- relative[i -= 4] = '\0';
- f->persist = xmalloc(strlen(persistdir) + 1 + i + 1);
- strcpy(f->persist, persistdir); /* safe, xmalloc */
- strcat(f->persist, "/"); /* safe, xmalloc */
- strcat(f->persist, relative); /* safe, xmalloc */
- free(relative);
- }
- else
- error("Cannot calculate persistent filename");
- }
- //接下來是一些健康檢查
- if (f->persist && *(f->persist) != '/') {
- error("Persistent filenames must be absolute, ignoring '%s'", f->persist);
- free(f->persist);
- f->persist = NULL;
- }
- if (f->persist && !flag_ksymoops) {
- error("has persistent data but ksymoops symbols are not available");
- free(f->persist);
- f->persist = NULL;
- }
- if (f->persist && !k_new_syscalls) {
- error("has persistent data but the kernel is too old to support it");
- free(f->persist);
- f->persist = NULL;
- }
- if (persist_parms && flag_verbose) {
- if (f->persist)
- lprintf("Persist filename '%s'", f->persist);
- else
- lprintf("No persistent filename available");
- }
- if (f->persist) {
- FILE *fp = fopen(f->persist, "r");
- if (!fp) {
- if (flag_verbose)
- lprintf("Cannot open persist file '%s' %m", f->persist);
- }
- else {
- int pargc = 0;
- char *pargv[1000]; /* hard coded but big enough */
- char line[3000]; /* hard coded but big enough */
- char *p;
- while (fgets(line, sizeof(line), fp)) {
- p = strchr(line, '\n');
- if (!p) {
- error("Persistent data line is too long\n%s", line);
- break;
- }
- *p = '\0';
- p = line;
- while (isspace(*p))
- ++p;
- if (!*p || *p == '#')
- continue;
- if (pargc == sizeof(pargv)/sizeof(pargv[0])) {
- error("More than %d persistent parameters", pargc);
- break;
- }
- pargv[pargc++] = xstrdup(p);
- }
- fclose(fp);
- if (!process_module_arguments(f, pargc, pargv, 0))
- goto out;
- while (pargc--)
- free(pargv[pargc]);
- }
- }
- //ksymoops 是一個調試輔助工具,它將試圖將代碼轉換為指令並將堆棧值映射到內核符號。
- if (flag_ksymoops)
- add_ksymoops_symbols(f, filename, m_name);
- if (k_new_syscalls)//k_new_syscalls標志用於測試內核版本
- create_module_ksymtab(f);//創建模塊要導出的符號節區ksymtab,並將要導出的符號加入該節區
- //創建名為“__archdata” (宏 ARCH_SEC_NAME 的定義)的段
- if (add_archdata(f, &archdata))
- goto out;
- //如果symbol使用的都是kernel提供的,就添加一個.kallsyms節區
- //這個函數主要是處理內核導出符號。
- if (add_kallsyms(f, &kallsyms, force_kallsyms))
- goto out;
- /**** No symbols or sections to be changed after kallsyms above ***/
- if (errors)
- goto out;
- //如果flag_slient_probe已經設置,說明我們不想真正安裝模塊,只是想測試一下,那么到這里測試已經完成了,模塊一切正常.
- if (flag_silent_probe) {
- exit_status = 0;
- goto out;
- }
- //計算載入模塊所需的大小,即各個節區的大小和
- m_size = obj_load_size(f);
- //如果noload設置了,那么我們選擇不真正加載模塊。隨便給加載地址就完了.
- if (noload) {
- m_addr = 0x12340000;
- } else {
- errno = 0;
- //調用sys_create_module系統調用創建模塊,分配module的空間,返回模塊在內核空間的地址.這里的module結構不是內核使用的那個,它定義在./modutilst-2.4.0/include/module.h中。函數最終會調用系統調用sys_create_module,生成一個模塊對象,並鏈入模塊的內核鏈表
- //模塊對象的大小就是各個節區大小的和,這里為什么分配的空間m_size不是sizeof(module)+各個節區大小的和?因為第一個節區.this的大小正好就是sizeof(module),所以第一個節區就是struct module結構。
- //注意:區分后邊還有一次在用戶空間為模塊分配空間,然后先把模塊section拷貝到用戶空間的模塊影像中,然后再由sys_init_module()函數將用戶空間的模塊映像拷貝到內核空間的模塊地址,即這里的m_addr。
- m_addr = create_module(m_name, m_size);
- m_addr |= arch_module_base (f);//#define arch_module_base(m) ((ElfW(Addr))0)
- //檢查是否成功創建module結構
- switch (errno) {
- case 0:
- break;
- case EEXIST:
- if (dolock) {
- exit_status = 0;
- goto out;
- }
- error("a module named %s already exists", m_name);
- goto out;
- case ENOMEM:
- error("can't allocate kernel memory for module; needed %lu bytes",m_size);
- goto out;
- default:
- error("create_module: %m");
- goto out;
- }
- }
- //如果模塊運行時參數使用了文件,而且需要真正加載
- if (f->persist && !noload) {
- struct {
- struct module m;
- int data;
- } test_read;
- memset(&test_read, 0, sizeof(test_read));
- test_read.m.size_of_struct = -sizeof(test_read.m); /* -ve size => read, not write */
- test_read.m.read_start = m_addr + sizeof(struct module);
- test_read.m.read_end = test_read.m.read_start + sizeof(test_read.data);
- if (sys_init_module(m_name, (struct module *) &test_read)) {
- int old_errors = errors;
- error("has persistent data but the kernel is too old to support it."
- " Expect errors during rmmod as well");
- errors = old_errors;
- }
- }
- //模塊在內核的地址.而在模塊elf文件里,節區在內存的位置是假設文件從0地址加載而得出的,現在就要根據base值調整。base就是create_module時分配的地址m_addr
- if (!obj_relocate(f, m_addr)) {
- if (!noload)
- delete_module(m_name);
- goto out;
- }
- //至此相當於磁盤中的elf文件格式的.ko文件的內容,已經全部加載到內存中,需要重定位的符號已經進行了重定位,符號地址變成了真正的在內存中的地址,即絕對地址。
- /* Do archdata again, this time we have the final addresses */
- if (add_archdata(f, &archdata))
- goto out;
- //用絕對地址重新生成kallsyms段的內容
- if (add_kallsyms(f, &kallsyms, force_kallsyms))
- goto out;
- #ifdef COMPAT_2_0//2.0以前的版本
- if (k_new_syscalls)
- init_module(m_name, f, m_size, blob_name, noload, flag_load_map);
- else if (!noload)
- old_init_module(m_name, f, m_size);
- #else
- init_module(m_name, f, m_size, blob_name, noload, flag_load_map);
- #endif
- if (errors) {
- if (!noload)
- delete_module(m_name);
- goto out;
- }
- if (warnings && !noload)
- lprintf("Module %s loaded, with warnings", m_name);
- exit_status = 0;
- out:
- if (dolock)
- flock(fp, LOCK_UN);
- close(fp);
- if (!noload)
- snap_shot(NULL, 0);
- return exit_status;
- }
- static int new_get_kernel_info(int type)
- {
- struct module_stat *modules;
- struct module_stat *m;
- struct module_symbol *syms;
- struct module_symbol *s;
- size_t ret;
- size_t bufsize;
- size_t nmod;
- size_t nsyms;
- size_t i;
- size_t j;
- char *module_names;
- char *mn;
- drop();//首先清除module_stat內容,module_stat是個全局變量,保存模塊信息
- //指針分配空間
- module_names = xmalloc(bufsize = 256);
- //取得系統中現有所有的module名稱,ret返回個數,module_names返回各個module名稱,字符0分割
- while (query_module(NULL, QM_MODULES, module_names, bufsize, &ret)) {
- if (errno != ENOSPC) {
- error("QM_MODULES: %m\n");
- return 0;
- }
- /*
- 會調用realloc(void *mem_address, unsigned int newsize)函數,此先判斷當前的指針是否有足夠的連續空間,如果有,擴大mem_address指向的地址,並且將mem_address返回,如果空間不夠,先按照newsize指定的大小分配空間,將原有數據從頭到尾拷貝到新分配的內存區域,而后釋放原來mem_address所指內存區域(注意:原來指針是自動釋放,不需要使用free),同時返回新分配的內存區域的首地址。即重新分配存儲器塊的地址。
- */
- module_names = xrealloc(module_names, bufsize = ret);
- }
- module_name_list = module_names;//指向系統中所有模塊名稱的起始地址
- l_module_name_list = bufsize;//所有模塊名稱的大小,即module_names大小
- n_module_stat = nmod = ret;//返回模塊個數
- //分配空間,地址付給全局變量module_stat
- module_stat = modules = xmalloc(nmod * sizeof(struct module_stat));
- memset(modules, 0, nmod * sizeof(struct module_stat));
- //循環取得各個module的信息,QM_INFO的使用。
- for (i = 0, mn = module_names, m = modules;i < nmod;++i, ++m, mn += strlen(mn) + 1) {
- struct module_info info;
- //info包括module的地址,大小,flag和使用計數器。
- m->name = mn;//模塊名稱給module_stat結構
- if (query_module(mn, QM_INFO, &info, sizeof(info), &ret)) {
- if (errno == ENOENT) {
- m->flags = NEW_MOD_DELETED;
- continue;
- }
- error("module %s: QM_INFO: %m", mn);
- return 0;
- }
- m->addr = info.addr;//模塊地址給module_stat結構
- if (type & K_INFO) {//取得module的信息
- m->size = info.size;
- m->flags = info.flags;
- m->usecount = info.usecount;
- m->modstruct = info.addr;
- }//將info值傳給module_stat結構
- if (type & K_REFS) {//取得module的引用關系
- int mm;
- char *mrefs;
- char *mr;
- mrefs = xmalloc(bufsize = 64);
- while (query_module(mn, QM_REFS, mrefs, bufsize, &ret)) {//查找mn模塊引用的模塊名
- if (errno != ENOSPC) {
- error("QM_REFS: %m");
- return 1;
- }
- mrefs = xrealloc(mrefs, bufsize = ret);
- }
- for (j = 0, mr = mrefs;j < ret;++j, mr += strlen(mr) + 1) {
- for (mm = 0; mm < i; ++mm) {
- if (strcmp(mr, module_stat[mm].name) == 0) {
- m->nrefs += 1;
- m->refs = xrealloc(m->refs, m->nrefs * sizeof(struct module_stat **));
- m->refs[m->nrefs - 1] = module_stat + mm;//引用的模塊名
- break;
- }
- }
- }
- free(mrefs);
- }
- //這里是遍歷內核中其他模塊的所有符號
- if (type & K_SYMBOLS) { /* 取得symbol信息,正是我們要得*/
- syms = xmalloc(bufsize = 1024);
- //取得mn模塊的符號信息,保存在syms數組中
- while (query_module(mn, QM_SYMBOLS, syms, bufsize, &ret)) {
- if (errno == ENOSPC) {
- syms = xrealloc(syms, bufsize = ret);
- continue;
- }
- if (errno == ENOENT) {
- m->flags = NEW_MOD_DELETED;
- free(syms);
- goto next;
- } else {
- error("module %s: QM_SYMBOLS: %m", mn);
- return 0;
- }
- }
- nsyms = ret;
- //syms是module_symbol結構,ret返回symbol個數
- m->nsyms = nsyms;//符號個數
- m->syms = syms;//符號信息
- //name原來只是一個結構內的偏移,加上結構地址為真正的字符串地址
- for (j = 0, s = syms; j < nsyms; ++j, ++s)
- s->name += (unsigned long) syms;
- }
- next:
- }
- //這里是取得內核符號
- if (type & K_SYMBOLS) { /* Want info about symbols */
- syms = xmalloc(bufsize = 16 * 1024);
- //name為NULL,返回內核符號信息
- while (query_module(NULL, QM_SYMBOLS, syms, bufsize, &ret)) {
- if (errno != ENOSPC) {
- error("kernel: QM_SYMBOLS: %m");
- return 0;
- }
- syms = xrealloc(syms, bufsize = ret);//擴展空間
- }
- //將值返回給nksyms和ksyms兩個全局變量存儲。
- nksyms = nsyms = ret;//內核符號個數
- ksyms = syms;//內核符號
- /* name原來只是一個結構內的偏移,加上結構地址為真正的字符串地址 */
- for (j = 0, s = syms; j < nsyms; ++j, ++s)
- s->name += (unsigned long) syms;
- }
- return 1;
- }
- struct obj_file *obj_load (int fp, Elf32_Half e_type, const char *filename)
- {
- struct obj_file *f;
- ElfW(Shdr) *section_headers;
- int shnum, i;
- char *shstrtab;
- f = arch_new_file();//創建一個新的obj_file結構
- memset(f, 0, sizeof(*f));
- f->symbol_cmp = strcmp;//設置symbol名的比較函數就是strcmp
- f->symbol_hash = obj_elf_hash;//設置計算symbol hash值的函數
- f->load_order_search_start = &f->load_order;//??
- gzf_lseek(fp, 0, SEEK_SET);//文件指針設置到文件頭
- //取得object文件的ELF頭結構。
- if (gzf_read(fp, &f->header, sizeof(f->header)) != sizeof(f->header))
- {
- error("cannot read ELF header from %s", filename);
- return NULL;
- }
- //判斷ELF的magic,是否是ELF文件格式
- if (f->header.e_ident[EI_MAG0] != ELFMAG0
- || f->header.e_ident[EI_MAG1] != ELFMAG1
- || f->header.e_ident[EI_MAG2] != ELFMAG2
- || f->header.e_ident[EI_MAG3] != ELFMAG3)
- {
- error("%s is not an ELF file", filename);
- return NULL;
- }
- //檢查architecture
- if (f->header.e_ident[EI_CLASS] != ELFCLASSM//i386的機器上為ELFCLASS32,表示32bit
- || f->header.e_ident[EI_DATA] != ELFDATAM//此處值為ELFDATA2LSB,表示編碼方式
- || f->header.e_ident[EI_VERSION] != EV_CURRENT//此值固定,表示版本
- || !MATCH_MACHINE(f->header.e_machine))//機器類型
- {
- error("ELF file %s not for this architecture", filename);
- return NULL;
- }
- //判斷目標文件類型
- if (f->header.e_type != e_type && e_type != ET_NONE)//.ko文件類型必為ET_REL
- {
- switch (e_type) {
- case ET_REL:
- error("ELF file %s not a relocatable object", filename);
- break;
- case ET_EXEC:
- error("ELF file %s not an executable object", filename);
- break;
- default:
- error("ELF file %s has wrong type, expecting %d got %d",
- filename, e_type, f->header.e_type);
- break;
- }
- return NULL;
- }
- //檢查elf文件頭指定的節區頭的大小是否一致
- if (f->header.e_shentsize != sizeof(ElfW(Shdr)))
- {
- error("section header size mismatch %s: %lu != %lu",filename,
- (unsigned long)f->header.e_shentsize,
- (unsigned long)sizeof(ElfW(Shdr)));
- return NULL;
- }
- shnum = f->header.e_shnum;//文件中節區個數
- f->sections = xmalloc(sizeof(struct obj_section *) * shnum);//為section開辟空間
- memset(f->sections, 0, sizeof(struct obj_section *) * shnum);
- //每個節區都有一個節區頭部表,為shnum個節區頭部表分配空間
- section_headers = alloca(sizeof(ElfW(Shdr)) * shnum);
- gzf_lseek(fp, f->header.e_shoff, SEEK_SET);//指針移到節區頭部表開始位置
- //讀取shnum個節區頭部表(每個節區都有一個節區頭部表)內容保存在section_headers中
- if (gzf_read(fp, section_headers, sizeof(ElfW(Shdr))*shnum) != sizeof(ElfW(Shdr))*shnum)
- {
- error("error reading ELF section headers %s: %m", filename);
- return NULL;
- }
- for (i = 0; i < shnum; ++i)//遍歷所有的節區
- {
- struct obj_section *sec;
- f->sections[i] = sec = arch_new_section();//分配內存給每個section
- memset(sec, 0, sizeof(*sec));
- sec->header = section_headers[i];//設置obj_section結構的sec的header指向本節區的節區頭部表
- sec->idx = i;//節區索引
- switch (sec->header.sh_type)//section的類型
- {
- case SHT_NULL:
- case SHT_NOTE:
- case SHT_NOBITS:/* ignore */
- break;
- case SHT_PROGBITS:
- case SHT_SYMTAB:
- case SHT_STRTAB:
- case SHT_RELM://將以上各種類型的section內容讀到sec->contents結構中。
- if (sec->header.sh_size > 0)
- {
- sec->contents = xmalloc(sec->header.sh_size);
- //指針移到節區的第一個字節與文件頭之間的偏移
- gzf_lseek(fp, sec->header.sh_offset, SEEK_SET);
- //讀取節區中內容
- if (gzf_read(fp, sec->contents, sec->header.sh_size) != sec->header.sh_size)
- {
- error("error reading ELF section data %s: %m", filename);
- return NULL;
- }
- }
- else
- sec->contents = NULL;
- break;
- //描述relocation的section
- #if SHT_RELM == SHT_REL
- case SHT_RELA:
- if (sec->header.sh_size) {
- error("RELA relocations not supported on this architecture %s", filename);
- return NULL;
- }
- break;
- #else
- case SHT_REL:
- if (sec->header.sh_size) {
- error("REL relocations not supported on this architecture %s", filename);
- return NULL;
- }
- break;
- #endif
- default:
- if (sec->header.sh_type >= SHT_LOPROC)
- {
- if (arch_load_proc_section(sec, fp) < 0)
- return NULL;
- break;
- }
- error("can't handle sections of type %ld %s",(long)sec->header.sh_type, filename);
- return NULL;
- }
- }
- //shstrndx存的是section字符串表的索引值,就是第幾個section
- //shstrtab就是那個section了。找到節區名字符串節區,把字符串節區內容地址付給shstrtab指針
- shstrtab = f->sections[f->header.e_shstrndx]->contents;
- for (i = 0; i < shnum; ++i)
- {
- struct obj_section *sec = f->sections[i];
- sec->name = shstrtab + sec->header.sh_name;//sh_name字段是節區頭部字符串表節區的索引
- }//根據strtab,取得每個section的名字
- //遍歷節區查找符號表
- for (i = 0; i < shnum; ++i)
- {
- struct obj_section *sec = f->sections[i];
- //也就是說即使modinfo和modstring有此標志位,也去掉。
- if (strcmp(sec->name, ".modinfo") == 0 ||strcmp(sec->name, ".modstring") == 0)
- sec->header.sh_flags &= ~SHF_ALLOC;//ALLOC表示此section是否占用內存,這兩個節區不占用內存
- if (sec->header.sh_flags & SHF_ALLOC)//此節區在進程執行過程中占用內存
- obj_insert_section_load_order(f, sec);//確定section load的順序,根據的是flag的類型加權得到優先級
- switch (sec->header.sh_type)
- {
- case SHT_SYMTAB://符號表節區,就是.symtab節區
- {
- unsigned long nsym, j;
- char *strtab;
- ElfW(Sym) *sym;
- //節區的大小若不等於符號表結構的大小,則出錯
- if (sec->header.sh_entsize != sizeof(ElfW(Sym)))
- {
- error("symbol size mismatch %s: %lu != %lu",filename,(unsigned long)sec->header.sh_entsize,(unsigned long)sizeof(ElfW(Sym)));
- return NULL;
- }
- //計算符號表表項個數,nsym也就是symbol個數,我的fedcore有560個符號(結構)
- nsym = sec->header.sh_size / sizeof(ElfW(Sym));
- //sh_link是符號字符串表的索引值,f->sections[sec->header.sh_link]就是.strtab節區
- strtab = f->sections[sec->header.sh_link]->contents;//符號字符串表節區的內容
- sym = (ElfW(Sym) *) sec->contents;//符號表節區的內容Elf32_sym結構
- j = f->local_symtab_size = sec->header.sh_info;//本模塊局部符號的size
- f->local_symtab = xmalloc(j *= sizeof(struct obj_symbol *));//為本模塊局部符號分配空間
- memset(f->local_symtab, 0, j);
- //遍歷要加載模塊的符號表節區內容的符號Elf32_sym結構
- for (j = 1, ++sym; j < nsym; ++j, ++sym)
- {
- const char *name;
- if (sym->st_name)//有值就是符號字符串表strtab的索引值
- name = strtab+sym->st_name;
- else//如果為零,此symbol name是一個section的name,比如.rodata之類的
- name = f->sections[sym->st_shndx]->name;
- //obj_add_symbol將符號加入到f->symbab這個hash表中,sym->st_shndx是相關節區頭部表索引。如果一個符號的取值引用了某個節區中的特定位置,那么它的節區索引成員(st_shndx)包含了其在節區頭部表中的索引。(比如說符號“function_x”定義在節區.rodata中,則sym->st_shndx是節區.rodata的索引值,sym->st_value是指在該節區中的到符號位置的偏移,即符號“function_x”在模塊中的地址由節區.rodata->contens+sym->st_value位置處的數值指定)
- //局部符號會添加到f->local_symtab表中,其余符號會添加到hash表f->symtab中(),注意:局部符號也可能添加到hash表中
- obj_add_symbol(f, name, j, sym->st_info, sym->st_shndx,sym->st_value, sym->st_size);
- }
- }
- break;
- }
- }
- //重定位是將符號引用與符號定義進行連接的過程。例如,當程序調用了一個函數時,相關的調用指令必須把控制傳輸到適當的目標執行地址。
- for (i = 0; i < shnum; ++i)
- {
- struct obj_section *sec = f->sections[i];
- switch (sec->header.sh_type)
- {
- case SHT_RELM://找到描述重定位的section
- {
- unsigned long nrel, j;
- ElfW(RelM) *rel;
- struct obj_section *symtab;
- char *strtab;
- if (sec->header.sh_entsize != sizeof(ElfW(RelM)))
- {
- error("relocation entry size mismatch %s: %lu != %lu",
- filename,(unsigned long)sec->header.sh_entsize,
- (unsigned long)sizeof(ElfW(RelM)));
- return NULL;
- }
- //算出rel有幾項,存到nrel中
- nrel = sec->header.sh_size / sizeof(ElfW(RelM));
- rel = (ElfW(RelM) *) sec->contents;
- //rel的section中sh_link相關值是符號section的索引值,即.symtab符號節區
- symtab = f->sections[sec->header.sh_link];
- //而符號section中sh_link是符號字符串section的索引值,即找到.strtab節區
- strtab = f->sections[symtab->header.sh_link]->contents;
- //存儲需要relocate的符號的rel的類型
- for (j = 0; j < nrel; ++j, ++rel)
- {
- ElfW(Sym) *extsym;
- struct obj_symbol *intsym;
- unsigned long symndx;
- symndx = ELFW(R_SYM)(rel->r_info);//取得要進行重定位的符號表索引
- if(symndx)
- {
- //取得要進行重定位的符號(Elf32_sym結構)
- extsym = ((ElfW(Sym) *) symtab->contents) + symndx;
- //符號信息是局部的,別的文件不可見
- if (ELFW(ST_BIND)(extsym->st_info) == STB_LOCAL)
- {
- intsym = f->local_symtab[symndx];//要從局部符號表中獲取
- }
- else//其他類型,從hash表中取
- {
- const char *name;
- if (extsym->st_name)//有值就是符號字符串表.strtab的索引值
- name = strtab + extsym->st_name;
- else//如果為零,此symbol name是一個section的name,比如.rodata之類的
- name = f->sections[extsym->st_shndx]->name;
- //因為前邊已添加到hash表中,從hash表中獲取該要重定位的符號
- intsym = obj_find_symbol(f, name);
- }
- intsym->r_type = ELFW(R_TYPE)(rel->r_info);//設置該符號的重定位類型,為以后進行符號重定位時使用
- }
- }
- }
- break;
- }
- }
- f->filename = xstrdup(filename);
- return f;
- }
- struct obj_symbol *obj_add_symbol (struct obj_file *f, const char *name, unsigned long symidx,int info, int secidx, ElfW(Addr) value, unsigned long size)
- {
- //參數:name符號名,symidx符號索引值(在此模塊中),secidx節區索引值,value符號值
- struct obj_symbol *sym;
- unsigned long hash = f->symbol_hash(name) % HASH_BUCKETS;//計算出hash值
- int n_type = ELFW(ST_TYPE)(info);//要添加的符號類型
- int n_binding = ELFW(ST_BIND)(info);//要添加的符號綁定類型,例如:STB_LOCAL或STB_GLOBAL
- //遍歷hash表中是否已添加了此符號,根據符號類型做不同處理
- for (sym = f->symtab[hash]; sym; sym = sym->next)
- if (f->symbol_cmp(sym->name, name) == 0)
- {
- int o_secidx = sym->secidx;
- int o_info = sym->info;
- int o_type = ELFW(ST_TYPE)(o_info);
- int o_binding = ELFW(ST_BIND)(o_info);
- if (secidx == SHN_UNDEF)//如果要加入的符號的屬性是SHN_UNDEF,即未定義,不用處理
- return sym;
- else if (o_secidx == SHN_UNDEF)//如果已加入的符號屬性是SHN_UNDEF,則用新的符號替換。
- goto found;
- else if (n_binding == STB_GLOBAL && o_binding == STB_LOCAL)//新添加的符號是global,而old符號是局部符號,那么就將STB_GLOBAL符號替換掉STB_LOCAL 符號。
- {
- struct obj_symbol *nsym, **p;
- nsym = arch_new_symbol();
- nsym->next = sym->next;
- nsym->ksymidx = -1;
- //從鏈表中刪除舊的符號
- for (p = &f->symtab[hash]; *p != sym; p = &(*p)->next)
- continue;
- *p = sym = nsym;//新的符號替換舊的符號
- goto found;
- }
- else if (n_binding == STB_LOCAL)//新添加的符號是局部符號,則加入到local_symtab表中,不加入 symtab
- {
- sym = arch_new_symbol();
- sym->next = NULL;
- sym->ksymidx = -1;
- f->local_symtab[symidx] = sym;
- goto found;
- }
- else if (n_binding == STB_WEAK)//新加入的符號是weak屬性,則不需處理
- return sym;
- else if (o_binding == STB_WEAK)//如果已加入的符號屬性是STB_WEAK,則用新的符號替換。
- goto found;
- else if (secidx == SHN_COMMON&& (o_type == STT_NOTYPE || o_type == STT_OBJECT))
- return sym;
- else if (o_secidx == SHN_COMMON&& (n_type == STT_NOTYPE || n_type == STT_OBJECT))
- goto found;
- else
- {
- if (secidx <= SHN_HIRESERVE)
- error("%s multiply defined", name);
- return sym;
- }
- }
- //該符號沒有在hash符號表中添加過,所以有可能一個局部符號添加到了hash表中
- sym = arch_new_symbol();//分配一個新的符號結構體
- sym->next = f->symtab[hash];//鏈入hash數組中f->symtab[hash]
- f->symtab[hash] = sym;
- sym->ksymidx = -1;
- //若是局部符號(別的文件不可見),則將該符號添加到f->local_symtab[]數組中
- if (ELFW(ST_BIND)(info) == STB_LOCAL && symidx != -1) {
- if (symidx >= f->local_symtab_size)
- error("local symbol %s with index %ld exceeds local_symtab_size %ld",
- name, (long) symidx, (long) f->local_symtab_size);
- else
- f->local_symtab[symidx] = sym;
- }
- found:
- sym->name = name;//符號名
- sym->value = value;//符號值
- sym->size = size;//符號大小
- sym->secidx = secidx;//節區索引值
- sym->info = info;//符號類型和綁定信息
- sym->r_type = 0;//重定位類型初始為0
- return sym;
- }
- void obj_set_symbol_compare (struct obj_file *f,int (*cmp)(const char *, const char *),
- unsigned long (*hash)(const char *))
- {
- if (cmp)
- f->symbol_cmp = cmp;//符號比較函數
- if (hash)
- {
- struct obj_symbol *tmptab[HASH_BUCKETS], *sym, *next;
- int i;
- f->symbol_hash = hash;//hash函數
- memcpy(tmptab, f->symtab, sizeof(tmptab));//先將符號信息保存在臨時數組tmptab
- memset(f->symtab, 0, sizeof(f->symtab));//清空hash表
- //重新使用hash函數將符號添加到符號表f->symtab中
- for (i = 0; i < HASH_BUCKETS; ++i)
- for (sym = tmptab[i]; sym ; sym = next)
- {
- unsigned long h = hash(sym->name) % HASH_BUCKETS;
- next = sym->next;
- sym->next = f->symtab[h];
- f->symtab[h] = sym;
- }
- }
- }
- static const char *gpl_licenses[] = {
- "GPL",
- "GPL v2",
- "GPL and additional rights",
- "Dual BSD/GPL",
- "Dual MPL/GPL",
- };
- int obj_gpl_license(struct obj_file *f, const char **license)
- {
- struct obj_section *sec;
- //找到.modinfo節區
- if ((sec = obj_find_section(f, ".modinfo"))) {
- const char *value, *ptr, *endptr;
- ptr = sec->contents;//指向該節區內容其實地址
- endptr = ptr + sec->header.sh_size;//節區內容結束地址
- while (ptr < endptr) {
- //找到以”license=“起始的字符串
- if ((value = strchr(ptr, '=')) && strncmp(ptr, "license", value-ptr) == 0) {
- int i;
- if (license)
- *license = value+1;
- for (i = 0; i < sizeof(gpl_licenses)/sizeof(gpl_licenses[0]); ++i) {
- if (strcmp(value+1, gpl_licenses[i]) == 0)//比較是否與以上數組相同的license
- return(0);
- }
- return(2);
- }
- //否則從下一個字符串開始再查找
- if (strchr(ptr, '\0'))
- ptr = strchr(ptr, '\0') + 1;
- else
- ptr = endptr;
- }
- }
- return(1);
- }
- static void add_kernel_symbols(struct obj_file *f)
- {
- struct module_stat *m;
- size_t i, nused = 0;
- //注意:此處雖然將模塊的全局符號用內核和其他模塊的符號替換過了,而且符號結構obj_symbol的secidx字段被重新寫成了SHN_HIRESERVE以上的數值。對於一些全局的未定義符號(原來的secidx為SHN_UNDEF),現在這些未定義符號的secidx字段也被重新寫成了SHN_HIRESERVE以上的數值。但是這些未定義符號對於elf文件格式的符號結構Elf32_sym中的字段st_shndx的取值並未改變,仍是SHN_UNDEF。這樣做的原因是:在模塊的編譯過程中會生成一個__versions節區,該節區中存放的都是該模塊中使用到,但沒被定義的符號,也就是所謂的 unresolved symbol,它們或在基本內核中定義,或在其他模塊中定義,內核使用它們來做 Module versioning。注意其中的 module_layout 符號,這是一個 dummy symbol。內核使用它來跟蹤不同內核版本關於模塊處理的相關數據結構的變化。所以該節區的未定義的符號雖然在這里被內核符號或其他模塊符號已替換,但是它本身的SHN_UNDEF性質沒有改變,用來對之后模塊加載時的crc校驗。因為crc校驗時就是檢查這些SHN_UNDEF性質的符號的crc值。參見內核函數simplify_symbols()。
- //而且__versions節區的未定義的符號必須是內核或內核其他模塊用到的符號,這樣在此系統下編譯的模塊安裝到另一個系統上時,根據這些全局符號的crc值就能知道此模塊是不是在此系統上編譯的。還有這些符號雖然被設置為未定義的,但是通過符號替換,仍能得到符號的絕對地址,從而被模塊引用。
- /* 使用系統中已有的module中的symbol,更新symbol的值,重新寫入hash表或者局部符號表。注意:要加載的模塊還沒有加入到module_stat數組中 */
- for (i = 0, m = module_stat; i < n_module_stat; ++i, ++m)
- //遍歷每個模塊的符號表,符號對應的節區是SHN_LORESERVE以上的節區號。節區序號大於SHN_LORESERVE的符號,是沒有對應的節區的。因此,符號里的值就認為是絕對地址。內核和已加載模塊導出符號就處在這個區段。
- if (m->nsyms && add_symbols_from(f, SHN_HIRESERVE + 2 + i, m->syms, m->nsyms))
- {
- m->status = 1;//表示此模塊被引用了
- ++nused;
- }
- n_ext_modules_used = nused;//該模塊依賴的內核其余模塊的個數
- //使用kernel導出的symbol,更新symbol的值,重新寫入hash表或者局部符號表.SHN_HIRESERVE對應系統保留節區的上限,使用SHN_HIRESERVE以上的節區來保存已加載模塊的符號和內核符號。
- if (nksyms)
- add_symbols_from(f, SHN_HIRESERVE + 1, ksyms, nksyms);
- }
- static int add_symbols_from(struct obj_file *f, int idx,struct module_symbol *syms, size_t nsyms)
- {
- struct module_symbol *s;
- size_t i;
- int used = 0;
- //遍歷該模塊的所有符號
- for (i = 0, s = syms; i < nsyms; ++i, ++s) {
- struct obj_symbol *sym;
- //從hash表中是否有需要此名字的的symbol,局部符號表中的符號不需要內核符號替換
- sym = obj_find_symbol(f, (char *) s->name);
- //從要加載模塊的hash表中找到該符號(必須為非局部符號),表示要加載模塊需要改符號
- if (sym && !ELFW(ST_BIND) (sym->info) == STB_LOCAL) {
- /*將hash表中的待解析的symbol的value添成正確的值s->value*/
- sym = obj_add_symbol(f, (char *) s->name, -1,ELFW(ST_INFO) (STB_GLOBAL, STT_NOTYPE),idx, s->value, 0);
- if (sym->secidx == idx)
- used = 1;//表示發生了符號替換
- }
- }
- return used;
- }
- static int create_this_module(struct obj_file *f, const char *m_name)
- {
- struct obj_section *sec;
- //創建一個.this節區,顯然准備在這個節區里存放module結構。注意:這個節區是load時的首個節區,即起始節區
- sec = obj_create_alloced_section_first(f, ".this", tgt_sizeof_long,sizeof(struct module));
- memset(sec->contents, 0, sizeof(struct module));
- //添加一個"__this_module"符號,所在節區是.this,屬性是 STB_LOCAL,類型是 STT_OBJECT,symidx 為-1,所以這個符號不加入 local_symtab 中(因為這個符號不是文件原有的)
- obj_add_symbol(f, "__this_module", -1, ELFW(ST_INFO) (STB_LOCAL, STT_OBJECT),sec->idx, 0, sizeof(struct module));
- /*為了能在obj_file里引用模塊名(回憶一下,每個字符串要么與節區名對應,要么對應於一個符號,而在這里,模塊名沒有對應的符號或節區),因此obj_file通過obj_string_patch_struct結構收留這些孤獨的字符串*/
- //創建.kstrtab節區,若存在該節區則擴展該節區,給該節區內容賦值為m_name,這里即”fedcore.ko“
- obj_string_patch(f, sec->idx, offsetof(struct module, name), m_name);
- return 1;
- }
- struct obj_section *obj_create_alloced_section_first (struct obj_file *f, const char *name,
- unsigned long align, unsigned long size)
- {
- int newidx = f->header.e_shnum++;//elf文件頭中的節區頭數量加1
- struct obj_section *sec;
- //為節區頭分配空間
- f->sections = xrealloc(f->sections, (newidx+1) * sizeof(sec));
- f->sections[newidx] = sec = arch_new_section();
- memset(sec, 0, sizeof(*sec));//.this節區的偏移地址是0,sec->header.sh_addr=0
- sec->header.sh_type = SHT_PROGBITS;
- sec->header.sh_flags = SHF_WRITE|SHF_ALLOC;
- sec->header.sh_size = size;
- sec->header.sh_addralign = align;
- sec->name = name;
- sec->idx = newidx;
- if (size)
- sec->contents = xmalloc(size);//節區內容分配空間
- sec->load_next = f->load_order;
- f->load_order = sec;
- if (f->load_order_search_start == &f->load_order)
- f->load_order_search_start = &sec->load_next;
- return sec;
- }
- int obj_string_patch(struct obj_file *f, int secidx, ElfW(Addr) offset,const char *string)
- {
- struct obj_string_patch_struct *p;
- struct obj_section *strsec;
- size_t len = strlen(string)+1;
- char *loc;
- p = xmalloc(sizeof(*p));
- p->next = f->string_patches;
- p->reloc_secidx = secidx;
- p->reloc_offset = offset;
- f->string_patches = p;//patch 字符串
- //查找.kstrtab節區
- strsec = obj_find_section(f, ".kstrtab");
- if (strsec == NULL)
- {
- //該節區不存在則創建該節區
- strsec = obj_create_alloced_section(f, ".kstrtab", 1, len, 0);
- p->string_offset = 0;
- loc = strsec->contents;
- }
- else
- {
- p->string_offset = strsec->header.sh_size;//字符串偏移地址
- loc = obj_extend_section(strsec, len);//擴展該節區內容的地址大小
- }
- memcpy(loc, string, len);//賦值給節區內容
- return 1;
- }
- int obj_check_undefineds(struct obj_file *f, int quiet)
- {
- unsigned long i;
- int ret = 1;
- //遍歷模塊的hash表的所有符號,檢查是否還有未定義的符號
- for (i = 0; i < HASH_BUCKETS; ++i)
- {
- struct obj_symbol *sym;
- //一般來說此處不會有未定義的符號,因為前邊經過了一次內核符號和其他模塊符號的替換操作add_kernel_symbols,但是在模塊的編譯
- for (sym = f->symtab[i]; sym ; sym = sym->next)
- if (sym->secidx == SHN_UNDEF)//如果有未定義的符號
- {
- //對於屬性為weak的符號,如果未能解析,鏈接器只是將它置0完事
- if (ELFW(ST_BIND)(sym->info) == STB_WEAK)
- {
- sym->secidx = SHN_ABS;//符號具有絕對取值,不會因為重定位而發生變化。
- sym->value = 0;
- }
- else if (sym->r_type) /* assumes R_arch_NONE is 0 on all arch */
- {//如果不是weak屬性,而且受重定位影響那就出錯了
- if (!quiet)
- error("%s: unresolved symbol %s",f->filename, sym->name);
- ret = 0;
- }
- }
- }
- return ret;
- }
- void obj_allocate_commons(struct obj_file *f)
- {
- struct common_entry
- {
- struct common_entry *next;
- struct obj_symbol *sym;
- } *common_head = NULL;
- unsigned long i;
- for (i = 0; i < HASH_BUCKETS; ++i)
- {
- struct obj_symbol *sym;
- //遍歷該模塊的hash符號表
- for (sym = f->symtab[i]; sym ; sym = sym->next)
- //若設置了該標志SHN_COMMON,則表示符號標注了一個尚未分配的公共塊,例如未分配的C外部變量。就是說,鏈接編輯器將為符號分配存儲空間,地址位於 st_value 的倍數處。符號的大小給出了所需要的字節數。
- //找出所有SHN_COMMON的符號,並按符號大小排序,鏈入到common_head鏈表中
- if (sym->secidx == SHN_COMMON)
- {
- {
- struct common_entry **p, *n;
- for (p = &common_head; *p ; p = &(*p)->next)
- if (sym->size <= (*p)->sym->size)
- break;
- n = alloca(sizeof(*n));
- n->next = *p;
- n->sym = sym;
- *p = n;
- }
- }
- }
- //遍歷該模塊的局部符號表local_symtab,找出所有SHN_COMMON的符號,並按符號大小排序,鏈入到common_head鏈表中
- for (i = 1; i < f->local_symtab_size; ++i)
- {
- struct obj_symbol *sym = f->local_symtab[i];
- if (sym && sym->secidx == SHN_COMMON)
- {
- struct common_entry **p, *n;
- for (p = &common_head; *p ; p = &(*p)->next)
- if (sym == (*p)->sym)
- break;
- else if (sym->size < (*p)->sym->size)
- {
- n = alloca(sizeof(*n));
- n->next = *p;
- n->sym = sym;
- *p = n;
- break;
- }
- }
- }
- if (common_head)
- {
- for (i = 0; i < f->header.e_shnum; ++i)
- //SHT_NOBITS表明這個節區不占據文件空間,這正是.bss節區的類型,這里就是為了查找.bss節區
- if (f->sections[i]->header.sh_type == SHT_NOBITS)
- break;
- //如果沒有找到.bss節區,則就創建一個.bss節區
- //.bss 含義:包含將出現在程序的內存映像中的為初始化數據。根據定義,當程序開始執行,系統將把這些數據初始化為 0。此節區不占用文件空間。
- if (i == f->header.e_shnum)
- {
- struct obj_section *sec;
- f->sections = xrealloc(f->sections, (i+1) * sizeof(sec));
- f->sections[i] = sec = arch_new_section();//分配節區結構obj_section
- f->header.e_shnum = i+1;//節區數量加1
- memset(sec, 0, sizeof(*sec));
- sec->header.sh_type = SHT_PROGBITS;//節區類型
- sec->header.sh_flags = SHF_WRITE|SHF_ALLOC;
- sec->name = ".bss";//節區名稱
- sec->idx = i;//節區索引值
- }
- {
- ElfW(Addr) bss_size = f->sections[i]->header.sh_size;//節區大小
- ElfW(Addr) max_align = f->sections[i]->header.sh_addralign;//對齊邊界
- struct common_entry *c;
- //根據SHN_COMMON的符號重新計算該.bss節區大小和對齊邊界
- for (c = common_head; c ; c = c->next)
- {
- ElfW(Addr) align = c->sym->value;
- if (align > max_align)
- max_align = align;
- if (bss_size & (align - 1))
- bss_size = (bss_size | (align - 1)) + 1;
- c->sym->secidx = i;//該符號的節區索引值也改變了,即是.bss節區
- c->sym->value = bss_size;//符號值也變了,變成.bss節區符號偏移量
- bss_size += c->sym->size;
- }
- f->sections[i]->header.sh_size = bss_size;
- f->sections[i]->header.sh_addralign = max_align;
- }
- }
- //為SHT_NOBITS節區分配資源,並將它設為SHT_PROGBITS。從這里可以看到,文件定義的靜態、全局變量,還有引用的外部變量,最終放在了.bss節區中
- for (i = 0; i < f->header.e_shnum; ++i)
- {
- struct obj_section *s = f->sections[i];
- if (s->header.sh_type == SHT_NOBITS)//找到.bss節區
- {
- if (s->header.sh_size)
- //節區內容都初始化為0,即.bss節區中符號對應的文件定義的靜態、全局變量,還有引用的外部變量都初始化為0
- s->contents = memset(xmalloc(s->header.sh_size),0, s->header.sh_size);
- else
- s->contents = NULL;
- s->header.sh_type = SHT_PROGBITS;
- }
- }
- }
- static void check_module_parameters(struct obj_file *f, int *persist_flag)
- {
- struct obj_section *sec;
- char *ptr, *value, *n, *endptr;
- int namelen, err = 0;
- //查找 ".modinfo"節區
- sec = obj_find_section(f, ".modinfo");
- if (sec == NULL) {
- return;
- }
- ptr = sec->contents;//節區內容起始地址
- endptr = ptr + sec->header.sh_size;//節區內容結束地址
- while (ptr < endptr && !err) {
- value = strchr(ptr, '=');//定位到該字符串的”=“位置出
- n = strchr(ptr, '\0');
- if (value) {
- namelen = value - ptr;//找到該字符串的名稱
- //查找相對應的"parm_"或"parm_desc_"開頭的字符串
- if (namelen >= 5 && strncmp(ptr, "parm_", 5) == 0
- && !(namelen > 10 && strncmp(ptr, "parm_desc_", 10) == 0)) {
- char *pname = xmalloc(namelen + 1);
- strncpy(pname, ptr + 5, namelen - 5);//取得該字符串名
- pname[namelen - 5] = '\0';
- //檢查該參數字符串的內容
- err = check_module_parameter(f, pname, value+1, persist_flag);
- free(pname);
- }
- } else {
- if (n - ptr >= 5 && strncmp(ptr, "parm_", 5) == 0) {
- error("parameter %s found with no value", ptr);
- err = 1;
- }
- }
- ptr = n + 1;//下一個字符串
- }
- if (err)
- *persist_flag = 0;
- return;
- }
- static int check_module_parameter(struct obj_file *f, char *key, char *value, int *persist_flag)
- {
- struct obj_symbol *sym;
- int min, max;
- char *p = value;
- //確定該符號是否存在
- sym = obj_find_symbol(f, key);
- if (sym == NULL) {
- lprintf("Warning: %s symbol for parameter %s not found", error_file, key);
- ++warnings;
- return(1);
- }
- //解析參數值個數的聲明,如果沒有參數值個數的取值聲明就默認為1
- if (isdigit(*p)) {
- min = strtoul(p, &p, 10);
- if (*p == '-')
- max = strtoul(p + 1, &p, 10);
- else
- max = min;
- } else
- min = max = 1;
- if (max < min) {
- lprintf("Warning: %s parameter %s has max < min!", error_file, key);
- ++warnings;
- return(1);
- }
- //處理變量類型
- switch (*p) {
- case 'c':
- if (!isdigit(p[1])) {
- lprintf("%s parameter %s has no size after 'c'!", error_file, key);
- ++warnings;
- return(1);
- }
- while (isdigit(p[1]))
- ++p; /* swallow c array size */
- break;
- case 'b': /* drop through */
- case 'h': /* drop through */
- case 'i': /* drop through */
- case 'l': /* drop through */
- case 's':
- break;
- case '\0':
- lprintf("%s parameter %s has no format character!", error_file, key);
- ++warnings;
- return(1);
- default:
- lprintf("%s parameter %s has unknown format character '%c'", error_file, key, *p);
- ++warnings;
- return(1);
- }
- switch (*++p) {
- case 'p':
- if (*(p-1) == 's') {
- error("parameter %s is invalid persistent string", key);
- return(1);
- }
- *persist_flag = 1;
- break;
- case '\0':
- break;
- default:
- lprintf("%s parameter %s has unknown format modifier '%c'", error_file, key, *p);
- ++warnings;
- return(1);
- }
- return(0);
- }
- static int process_module_arguments(struct obj_file *f, int argc, char **argv, int required)
- {
- //遍歷命令行參數
- for (; argc > 0; ++argv, --argc) {
- struct obj_symbol *sym;
- int c;
- int min, max;
- int n;
- char *contents;
- char *input;
- char *fmt;
- char *key;
- char *loc;
- //因為使用命令行參數時,一定是param=value這樣的形式
- if ((input = strchr(*argv, '=')) == NULL)
- continue;
- n = input - *argv;//參數長度
- input += 1; /* skip '=' */
- key = alloca(n + 6);
- if (m_has_modinfo) {
- //將該參數組合成參數符號格式”parm_xxx“
- memcpy(key, "parm_", 5);
- memcpy(key + 5, *argv, n);
- key[n + 5] = '\0';
- //從節區.modinfo中查找該模塊參數
- if ((fmt = get_modinfo_value(f, key)) == NULL) {
- if (required || flag_verbose) {
- lprintf("Warning: ignoring %s, no such parameter in this module", *argv);
- ++warnings;
- continue;
- }
- }
- key += 5;
- //解析參數值個數的聲明,如果沒有參數值個數的取值聲明就默認為1
- if (isdigit(*fmt)) {
- min = strtoul(fmt, &fmt, 10);
- if (*fmt == '-')
- max = strtoul(fmt + 1, &fmt, 10);
- else
- max = min;
- } else
- min = max = 1;
- } else { /* not m_has_modinfo */
- memcpy(key, *argv, n);
- key[n] = '\0';
- if (isdigit(*input))
- fmt = "i";
- else
- fmt = "s";
- min = max = 0;
- }
- //到hash符號表中查找該參數符號
- sym = obj_find_symbol(f, key);
- if (sym == NULL || sym->secidx > SHN_HIRESERVE) {
- error("symbol for parameter %s not found", key);
- return 0;
- }
- //找到符號,讀取該符號所在節區的內容
- contents = f->sections[sym->secidx]->contents;
- loc = contents + sym->value;//存儲該參數符號的值地址付給loc指針,下邊就會給參數符號重新賦值
- n = 1;
- while (*input) {
- char *str;
- switch (*fmt) {
- case 's':
- case 'c':
- if (*input == '"') {
- char *r;
- str = alloca(strlen(input));
- for (r = str, input++; *input != '"'; ++input, ++r) {
- if (*input == '\0') {
- error("improperly terminated string argument for %s", key);
- return 0;
- }
- /* else */
- if (*input != '\\') {
- *r = *input;
- continue;
- }
- /* else handle \ */
- switch (*++input) {
- case 'a': *r = '\a'; break;
- case 'b': *r = '\b'; break;
- case 'e': *r = '\033'; break;
- case 'f': *r = '\f'; break;
- case 'n': *r = '\n'; break;
- case 'r': *r = '\r'; break;
- case 't': *r = '\t'; break;
- case '0':
- case '1':
- case '2':
- case '3':
- case '4':
- case '5':
- case '6':
- case '7':
- c = *input - '0';
- if ('0' <= input[1] && input[1] <= '7') {
- c = (c * 8) + *++input - '0';
- if ('0' <= input[1] && input[1] <= '7')
- c = (c * 8) + *++input - '0';
- }
- *r = c;
- break;
- default: *r = *input; break;
- }
- }
- *r = '\0';
- ++input;
- } else {
- /*
- * The string is not quoted.
- * We will break it using the comma
- * (like for ints).
- * If the user wants to include commas
- * in a string, he just has to quote it
- */
- char *r;
- /* Search the next comma */
- if ((r = strchr(input, ',')) != NULL) {
- /*
- * Found a comma
- * Recopy the current field
- */
- str = alloca(r - input + 1);
- memcpy(str, input, r - input);
- str[r - input] = '\0';
- /* Keep next fields */
- input = r;
- } else {
- /* last string */
- str = input;
- input = "";
- }
- }
- if (*fmt == 's') {
- /* Normal string */
- obj_string_patch(f, sym->secidx, loc - contents, str);
- loc += tgt_sizeof_char_p;
- } else {
- /* Array of chars (in fact, matrix !) */
- long charssize; /* size of each member */
- /* Get the size of each member */
- /* Probably we should do that outside the loop ? */
- if (!isdigit(*(fmt + 1))) {
- error("parameter type 'c' for %s must be followed by the maximum size", key);
- return 0;
- }
- charssize = strtoul(fmt + 1, (char **) NULL, 10);
- /* Check length */
- if (strlen(str) >= charssize-1) {
- error("string too long for %s (max %ld)",key, charssize - 1);
- return 0;
- }
- /* Copy to location */
- strcpy((char *) loc, str); /* safe, see check above */
- loc += charssize;
- }
- /*
- * End of 's' and 'c'
- */
- break;
- case 'b':
- *loc++ = strtoul(input, &input, 0);
- break;
- case 'h':
- *(short *) loc = strtoul(input, &input, 0);
- loc += tgt_sizeof_short;
- break;
- case 'i':
- *(int *) loc = strtoul(input, &input, 0);
- loc += tgt_sizeof_int;
- break;
- case 'l':
- *(tgt_long *) loc = tgt_strtoul(input, &input, 0);
- loc += tgt_sizeof_long;
- break;
- default:
- error("unknown parameter type '%c' for %s",*fmt, key);
- return 0;
- }
- /*
- * end of switch (*fmt)
- */
- while (*input && isspace(*input))
- ++input;
- if (*input == '\0')
- break; /* while (*input) */
- /* else */
- if (*input == ',') {
- if (max && (++n > max)) {
- error("too many values for %s (max %d)", key, max);
- return 0;
- }
- ++input;
- /* continue with while (*input) */
- } else {
- error("invalid argument syntax for %s: '%c'",key, *input);
- return 0;
- }
- } /* end of while (*input) */
- if (min && (n < min)) {
- error("too few values for %s (min %d)", key, min);
- return 0;
- }
- } /* end of for (;argc > 0;) */
- return 1;
- }
- static void hide_special_symbols(struct obj_file *f)
- {
- struct obj_symbol *sym;
- const char *const *p;
- static const char *const specials[] =
- {
- "cleanup_module",
- "init_module",
- "kernel_version",
- NULL
- };
- //查找數組中的幾個符號,找到后類型改為STB_LOCAL(局部)
- for (p = specials; *p; ++p)
- if ((sym = obj_find_symbol(f, *p)) != NULL)
- sym->info = ELFW(ST_INFO) (STB_LOCAL, ELFW(ST_TYPE) (sym->info));
- }
- static void add_ksymoops_symbols(struct obj_file *f, const char *filename,const char *m_name)
- {
- struct obj_section *sec;
- struct obj_symbol *sym;
- char *name, *absolute_filename;
- char str[STRVERSIONLEN], real[PATH_MAX];
- int i, l, lm_name, lfilename, use_ksymtab, version;
- struct stat statbuf;
- static const char *section_names[] = {
- ".text",
- ".rodata",
- ".data",
- ".bss"
- ".sbss"
- };
- //找到模塊所在的完整路徑名
- if (realpath(filename, real)) {
- absolute_filename = xstrdup(real);
- }
- else {
- int save_errno = errno;
- error("cannot get realpath for %s", filename);
- errno = save_errno;
- perror("");
- absolute_filename = xstrdup(filename);
- }
- lm_name = strlen(m_name);
- lfilename = strlen(absolute_filename);
- //查找"__ksymtab"節區是否存在或者模塊符號是否需要導出
- use_ksymtab = obj_find_section(f, "__ksymtab") || !flag_export;
- //找到.this節區,記錄經過修飾的模塊名和經過修飾的長度不為0的節區。在這里修飾的目的,應該是為了唯一確定所使用的模塊。
- if ((sec = obj_find_section(f, ".this"))) {
- l = sizeof(symprefix)+ /* "__insmod_" */
- lm_name+ /* module name */
- 2+ /* "_O" */
- lfilename+ /* object filename */
- 2+ /* "_M" */
- 2*sizeof(statbuf.st_mtime)+ /* mtime in hex */
- 2+ /* "_V" */
- 8+ /* version in dec */
- 1; /* nul */
- name = xmalloc(l);
- if (stat(absolute_filename, &statbuf) != 0)
- statbuf.st_mtime = 0;
- version = get_module_version(f, str); /* -1 if not found */
- snprintf(name, l, "%s%s_O%s_M%0*lX_V%d",symprefix, m_name, absolute_filename,
- (int)(2*sizeof(statbuf.st_mtime)), statbuf.st_mtime,version);
- //添加一個符號,該符號所在節區是.this節區,符號value給出該符號的地址在節區sec->header.sh_addr(值為0)的偏移地址處
- sym = obj_add_symbol(f, name, -1,ELFW(ST_INFO) (STB_GLOBAL, STT_NOTYPE),sec->idx, sec->header.sh_addr, 0);
- if (use_ksymtab)
- add_ksymtab(f, sym);//該符號添加到__ksymtab節區中
- }
- free(absolute_filename);
- //參數若是通過文件傳入的,也要修飾記錄這個文件名
- if (f->persist) {
- l = sizeof(symprefix)+ /* "__insmod_" */
- lm_name+ /* module name */
- 2+ /* "_P" */
- strlen(f->persist)+ /* data store */
- 1; /* nul */
- name = xmalloc(l);
- snprintf(name, l, "%s%s_P%s",symprefix, m_name, f->persist);
- sym = obj_add_symbol(f, name, -1, ELFW(ST_INFO) (STB_GLOBAL, STT_NOTYPE),sec->idx, sec->header.sh_addr, 0);
- if (use_ksymtab)
- add_ksymtab(f, sym);//該符號添加到__ksymtab節區中,要導出的符號加入該節區"__ksymtab"
- }
- /* tag the desired sections if size is non-zero */
- for (i = 0; i < sizeof(section_names)/sizeof(section_names[0]); ++i) {
- if ((sec = obj_find_section(f, section_names[i])) &&sec->header.sh_size) {
- l = sizeof(symprefix)+ /* "__insmod_" */
- lm_name+ /* module name */
- 2+ /* "_S" */
- strlen(sec->name)+ /* section name */
- 2+ /* "_L" */
- 8+ /* length in dec */
- 1; /* nul */
- name = xmalloc(l);
- snprintf(name, l, "%s%s_S%s_L%ld",symprefix, m_name, sec->name,(long)sec->header.sh_size);
- sym = obj_add_symbol(f, name, -1, ELFW(ST_INFO) (STB_GLOBAL, STT_NOTYPE),sec->idx, sec->header.sh_addr, 0);
- if (use_ksymtab)
- add_ksymtab(f, sym);//該符號添加到__ksymtab節區中,要導出的符號加入該節區"__ksymtab"
- }
- }
- }
- static int create_module_ksymtab(struct obj_file *f)
- {
- struct obj_section *sec;
- int i;
- //n_ext_modules_used是該模塊引用的模塊的數量
- if (n_ext_modules_used) {
- struct module_ref *dep;
- struct obj_symbol *tm;
- //創建一個.kmodtab節區保存該模塊所引用的模塊信息
- sec = obj_create_alloced_section(f, ".kmodtab",tgt_sizeof_void_p,sizeof(struct module_ref) * n_ext_modules_used, 0);
- if (!sec)
- return 0;
- tm = obj_find_symbol(f, "__this_module");
- dep = (struct module_ref *) sec->contents;
- //
- for (i = 0; i < n_module_stat; ++i)
- if (module_stat[i].status) {//模塊若被引用,則status為1
- dep->dep = module_stat[i].addr;
- dep->dep |= arch_module_base (f);
- obj_symbol_patch(f, sec->idx, (char *) &dep->ref - sec->contents, tm);
- dep->next_ref = 0;
- ++dep;
- }
- }
- // 在存在__ksymtab節區或者不存在這個節區,而且其他符號都不導出的情況下,還要將這些符號添加入__symtab節區
- //如果需要導出外部(extern)符號,而__ksymtab段不存在(這種情況下,add_ksymoops_symbols里是不會創建__ksymtab段的。而實際上,模塊一般是不會不包含__ksymtab段的,因為模塊的導出符號都位於這個段里。),那么通過add_ksymtab創建__ksymtab段,並加入模塊要導出的符號。在這里可以發現,如果模塊需要導出符號,那么經過修飾的模塊名、模塊文件名,甚至參數文件名會在這里加入__ksymtab段(因為在前面的add_ksymoops_symbols函數里,這些符號被設為STB_GLOBOL了,段序號指向.this段)。
- //注意,在不存在__ksymtab段的情況下(這時模塊沒有定義任何需要導出的參數),直接導出序號在SHN_LORESERVE~SHN_HIRESERVE的符號,以及其他已分配資源段的非local符號。(根據elf規范里,位於SHN_LORESERVE~SHN_HIRESERVE區的已定義序號有SHN_LOPROC、SHN_HIPROC、SHN_ABS、SHN_COMMON。經過前面的處理,到這里SHN_COMMON對應的符號已經不存在了。這些序號的節區實際上是不存在的,存在的是持有這些序號的符號。其中SHN_ABS是不受重定位影響的符號,其他2個則是與處理器有關的。至於為什么模塊不需要導出符號時,要導出這些區的符號,我也不太清楚,可以肯定的是,這和GCC有關,誰知道它放了什么進來?!)。
- if (flag_export && !obj_find_section(f, "__ksymtab")) {
- int *loaded;
- loaded = alloca(sizeof(int) * (i = f->header.e_shnum));
- while (--i >= 0)
- loaded[i] = (f->sections[i]->header.sh_flags & SHF_ALLOC) != 0;//節區是否占用內存
- for (i = 0; i < HASH_BUCKETS; ++i) {
- struct obj_symbol *sym;
- for (sym = f->symtab[i]; sym; sym = sym->next)
- {
- if (ELFW(ST_BIND) (sym->info) != STB_LOCAL && sym->secidx <= SHN_HIRESERVE&& (sym->secidx >= SHN_LORESERVE || loaded[sym->secidx])) {
- add_ksymtab(f, sym);//要導出的符號加入該節區"__ksymtab"
- }
- }
- }
- }
- return 1;
- }
- static void add_ksymtab(struct obj_file *f, struct obj_symbol *sym)
- {
- struct obj_section *sec;
- ElfW(Addr) ofs;
- //查找是否已經存在"__ksymtab"節區
- sec = obj_find_section(f, "__ksymtab");
- //如果存在這個節區,但是沒有標示SHF_ALLOC,則直接將該節區的名字修改掉,標示刪除
- if (sec && !(sec->header.sh_flags & SHF_ALLOC)) {
- *((char *)(sec->name)) = 'x'; /* override const */
- sec = NULL;
- }
- if (!sec)
- sec = obj_create_alloced_section(f, "__ksymtab",tgt_sizeof_void_p, 0, 0);
- if (!sec)
- return;
- sec->header.sh_flags |= SHF_ALLOC;
- sec->header.sh_addralign = tgt_sizeof_void_p; /* Empty section mightbe byte-aligned */
- ofs = sec->header.sh_size;
- obj_symbol_patch(f, sec->idx, ofs, sym);//使用f->sybol_ptches保存這些符號
- obj_string_patch(f, sec->idx, ofs + tgt_sizeof_void_p, sym->name);//使用f->string_patches保存符號名
- obj_extend_section(sec, 2 * tgt_sizeof_char_p);//擴展"__ksymtab"節區
- }
- static int add_archdata(struct obj_file *f,struct obj_section **sec)
- {
- size_t i;
- *sec = NULL;
- //查找是否有ARCHDATA_SEC_NAME=“__archdata”節區,沒有則創建該節區
- for (i = 0; i < f->header.e_shnum; ++i) {
- if (strcmp(f->sections[i]->name, ARCHDATA_SEC_NAME) == 0) {
- *sec = f->sections[i];
- break;
- }
- }
- if (!*sec)
- *sec = obj_create_alloced_section(f, ARCHDATA_SEC_NAME, 16, 0, 0);
- //x86體系下是空函數
- if (arch_archdata(f, *sec))
- return(1);
- return 0;
- }
- static int add_kallsyms(struct obj_file *f,struct obj_section **module_kallsyms, int force_kallsyms)
- {
- struct module_symbol *s;
- struct obj_file *f_kallsyms;
- struct obj_section *sec_kallsyms;
- size_t i;
- int l;
- const char *p, *pt_R;
- unsigned long start = 0, stop = 0;
- //遍歷所有內核符號,內核符號都保存在全局變量ksyms中
- for (i = 0, s = ksyms; i < nksyms; ++i, ++s) {
- p = (char *)s->name;
- pt_R = strstr(p, "_R");//查找符號中是否有 "_R",計算符號長度是,去掉這兩個字符
- if (pt_R)
- l = pt_R - p;
- else
- l = strlen(p);
- //內核導出符號位於“__start_kallsyms”和“__stop_kallsyms”符號所指向的地址之間
- if (strncmp(p, "__start_" KALLSYMS_SEC_NAME, l) == 0)
- start = s->value;
- else if (strncmp(p, "__stop_" KALLSYMS_SEC_NAME, l) == 0)
- stop = s->value;
- }
- if (start >= stop && !force_kallsyms)
- return(0);
- /* The kernel contains all symbols, do the same for this module. */
- //找到該模塊的“kallsyms”節區
- for (i = 0; i < f->header.e_shnum; ++i) {
- if (strcmp(f->sections[i]->name, KALLSYMS_SEC_NAME) == 0) {
- *module_kallsyms = f->sections[i];
- break;
- }
- }
- //如果沒有找到,則創建一個kallsyms節區
- if (!*module_kallsyms)
- *module_kallsyms = obj_create_alloced_section(f, KALLSYMS_SEC_NAME, 0, 0, 0);
- //這個函數的作用是將輸入obj_file里的符號提取出來,忽略不用於調試的符號.構建出obj_file只包含kallsyms節區。這個段可以任意定位,因為不包含重定位信息。
- //實際上,f_kallsyms這個文件就是將輸入文件里的信息整理整理,更方便使用(記住__kallsyms節區是用作輔助內核調試),整個輸出文件只是臨時的調試倉庫。
- if (obj_kallsyms(f, &f_kallsyms))//???
- return(1);
- sec_kallsyms = f_kallsyms->sections[KALLSYMS_IDX];//臨時創建obj_file里的kallsyms節區
- (*module_kallsyms)->header.sh_addralign = sec_kallsyms->header.sh_addralign;//節區對齊大小
- (*module_kallsyms)->header.sh_size = sec_kallsyms->header.sh_size;//節區大小
- free((*module_kallsyms)->contents);
- (*module_kallsyms)->contents = sec_kallsyms->contents;//內容賦值給kallsyms節區
- sec_kallsyms->contents = NULL;
- obj_free(f_kallsyms);
- return 0;
- }
- unsigned long obj_load_size (struct obj_file *f)
- {
- unsigned long dot = 0;
- struct obj_section *sec;
- //前面提到段按對其邊界大小排序,可以減少空間占用,就是體現在這里。
- for (sec = f->load_order; sec ; sec = sec->load_next)
- {
- ElfW(Addr) align;
- align = sec->header.sh_addralign;
- if (align && (dot & (align - 1)))
- dot = (dot | (align - 1)) + 1;
- sec->header.sh_addr = dot;
- dot += sec->header.sh_size;//各個節區大小累加
- }
- return dot;
- }
- asmlinkage unsigned long sys_create_module(const char *name_user, size_t size)
- {
- char *name;
- long namelen, error;
- struct module *mod;
- if (!capable(CAP_SYS_MODULE))//是否具有創建模塊的特權
- return EPERM;
- lock_kernel();
- if ((namelen = get_mod_name(name_user, &name)) < 0) {//模塊名拷貝到內核空間
- error = namelen;
- goto err0;
- }
- if (size < sizeof(struct module)+namelen) {//檢查模塊名的大小
- error = EINVAL;
- goto err1;
- }
- if (find_module(name) != NULL) {//在內存中查找是否模塊已經安裝
- error = EEXIST;
- goto err1;
- }
- //分配module空間 #define module_map(x) vmalloc(x)
- if ((mod = (struct module *)module_map(size)) == NULL) {
- error = ENOMEM;
- goto err1;
- }
- memset(mod, 0, sizeof(*mod));
- mod->size_of_struct = sizeof(*mod);
- mod->next = module_list;//掛入鏈表
- mod->name = (char *)(mod + 1);//module結構下邊用來存儲模塊名
- mod->size = size;
- memcpy((char*)(mod+1), name, namelen+1);//拷貝模塊名
- put_mod_name(name);//釋放name的空間
- module_list = mod;//掛入鏈表
- error = (long) mod;
- goto err0;
- err1:
- put_mod_name(name);
- err0:
- unlock_kernel();
- return error;
- }
- //base是模塊在內核空間的起始地址,也是.this節區的偏移地址
- int obj_relocate (struct obj_file *f, ElfW(Addr) base)
- {
- int i, n = f->header.e_shnum;
- int ret = 1;
- //節區在內存的位置是假設文件從0地址加載而得出的,現在就要根據base值調整
- arch_finalize_section_address(f, base);
- //處理重定位符號,遍歷每一個節區
- for (i = 0; i < n; ++i)
- {
- struct obj_section *relsec, *symsec, *targsec, *strsec;
- ElfW(RelM) *rel, *relend;
- ElfW(Sym) *symtab;
- const char *strtab;
- unsigned long nsyms;
- relsec = f->sections[i];
- if (relsec->header.sh_type != SHT_RELM)//如果該節區不是重定位類型,則繼續查找
- continue;
- //重定位節區會引用兩個其它節區:符號表、要修改的節區。符號表是symsec,要修改的節區是targsec節區.例如重定位節區(relsec).rel.text是對節區是.text(targsec)的進行重定位.原來重定位的目標節區(.text)的內容contents只是讀入到內存中,這塊內存是系統在堆上malloc的,內容中對應的地址不是真正的符號所在的地址。現在符號的真正的地址已經計算出,就要修改contents區的符號對應的地址,從而將符號鏈接到正確的地址。
- //重定位節區的sh_link表示相關符號表的節區頭部索引,即.symtab符號節區
- symsec = f->sections[relsec->header.sh_link];
- //重定位節區的sh_info表示重定位所適用的節區的節區頭部索引,像.text等節區
- targsec = f->sections[relsec->header.sh_info];
- //找到重定位節區相關符號表字符串的節區,即.strtab
- strsec = f->sections[symsec->header.sh_link];
- if (!(targsec->header.sh_flags & SHF_ALLOC))
- continue;
- //讀出該節區重定位信息開始地址
- rel = (ElfW(RelM) *)relsec->contents;
- //重定位信息結束地址
- relend = rel + (relsec->header.sh_size / sizeof(ElfW(RelM)));
- //找到重定位節區相關符號表的內容
- symtab = (ElfW(Sym) *)symsec->contents;
- nsyms = symsec->header.sh_size / symsec->header.sh_entsize;
- strtab = (const char *)strsec->contents;//找到重定位節區相關符號表字符串的節區的內容
- for (; rel < relend; ++rel)
- {
- ElfW(Addr) value = 0;
- struct obj_symbol *intsym = NULL;
- unsigned long symndx;
- const char *errmsg;
- //給出要重定位的符號表索引
- symndx = ELFW(R_SYM)(rel->r_info);
- if (symndx)
- {
- /* Note we've already checked for undefined symbols. */
- if (symndx >= nsyms)
- {
- error("%s: Bad symbol index: %08lx >= %08lx",f->filename, symndx, nsyms);
- continue;
- }
- //這些要重定位的符號就是重定位目標節區(例如.text節區)里的符號,然后把該符號的絕對地址在覆蓋這個節區的(例如.text節區)對應符號的地址
- obj_find_relsym(intsym, f, f, rel, symtab, strtab);//返回要找的重定位符號intsym
- value = obj_symbol_final_value(f, intsym);//計算符號的絕對地址(是內核空間的絕對地址,因為base就是內核空間的一個地址)
- }
- #if SHT_RELM == SHT_RELA
- value += rel->r_addend;
- #endif
- //獲得了絕對地址,可以進行操作了
- //注意:這里雖然重新定義了符號的絕對地址,但是節區的內容還沒有拷貝到分配模塊返回的內核空間地址處。后邊會進行這項操作。先將節區內容拷貝的用戶空間中,再從用戶空間拷貝到內核空間地址處,即base處。
- //f:objfile結構,targsec:.text節,symsec:.symtab節,rel:.rel結構,value:絕對地址
- switch (arch_apply_relocation(f,targsec,symsec,intsym,rel,value))
- {
- case obj_reloc_ok:
- break;
- case obj_reloc_overflow:
- errmsg = "Relocation overflow";
- goto bad_reloc;
- case obj_reloc_dangerous:
- errmsg = "Dangerous relocation";
- goto bad_reloc;
- case obj_reloc_unhandled:
- errmsg = "Unhandled relocation";
- goto bad_reloc;
- case obj_reloc_constant_gp:
- errmsg = "Modules compiled with -mconstant-gp cannot be loaded";
- goto bad_reloc;
- bad_reloc:
- error("%s: %s of type %ld for %s", f->filename, errmsg,
- (long)ELFW(R_TYPE)(rel->r_info), intsym->name);
- ret = 0;
- break;
- }
- }
- }
- /* Finally, take care of the patches. */
- if (f->string_patches)
- {
- struct obj_string_patch_struct *p;
- struct obj_section *strsec;
- ElfW(Addr) strsec_base;
- strsec = obj_find_section(f, ".kstrtab");
- strsec_base = strsec->header.sh_addr;
- for (p = f->string_patches; p ; p = p->next)
- {
- struct obj_section *targsec = f->sections[p->reloc_secidx];
- *(ElfW(Addr) *)(targsec->contents + p->reloc_offset)= strsec_base + p->string_offset;
- }
- }
- if (f->symbol_patches)
- {
- struct obj_symbol_patch_struct *p;
- for (p = f->symbol_patches; p; p = p->next)
- {
- struct obj_section *targsec = f->sections[p->reloc_secidx];
- *(ElfW(Addr) *)(targsec->contents + p->reloc_offset)= obj_symbol_final_value(f, p->sym);
- }
- }
- return ret;
- }
- int arch_finalize_section_address(struct obj_file *f, Elf32_Addr base)
- {
- int i, n = f->header.e_shnum;
- //每個節區的起始地址都要加上模塊在內核的起始地址
- f->baseaddr = base;//模塊在內核的起始地址
- for (i = 0; i < n; ++i)
- f->sections[i]->header.sh_addr += base;
- return 1;
- }
- #define obj_find_relsym(isym, f, find, rel, symtab, strtab) \
- { \
- unsigned long symndx = ELFW(R_SYM)((rel)->r_info); \
- ElfW(Sym) *extsym = (symtab)+symndx; \//在符號表節區的內容中根據索引值找到相關符號
- if (ELFW(ST_BIND)(extsym->st_info) == STB_LOCAL) { \//若是局部符號
- isym = (typeof(isym)) (f)->local_symtab[symndx]; \//直接在局部符號表中返回要找的重定位符號
- } \
- else { \
- const char *name; \
- if (extsym->st_name) \//有值的話,就是strtab字符串節區內容的索引值
- name = (strtab) + extsym->st_name; \//找到符號名
- else \//否則就是節區名
- name = (f)->sections[extsym->st_shndx]->name; \
- isym = (typeof(isym)) obj_find_symbol((find), name); \//在符號hash表中找到該重定位符號
- } \
- }
- ElfW(Addr) obj_symbol_final_value (struct obj_file *f, struct obj_symbol *sym)
- {
- if (sym)
- {
- //在保留區內直接返回符號值,即符號絕對地址(一般是內核符號,因為前邊將模塊內的符號用內核中或已存在的模塊中相同符號的value更寫過了,並且節區索引值設置高於SHN_HIRESERVE,所以這些符號的value保存的就是符號的實際地址)
- if (sym->secidx >= SHN_LORESERVE)
- return sym->value;
- //其余節區內的符號value要加上現在節區的偏移地址,才是符號指向的實際地址(因為節區的偏移地址已經更改了)
- return sym->value + f->sections[sym->secidx]->header.sh_addr;//符號值加上節區頭偏移地址
- }
- else
- {
- /* As a special case, a NULL sym has value zero. */
- return 0;
- }
- }
- //x86架構
- /*
- 。“重定位表”記錄的是每個需要重定位的地方(即有了重定位表,就可知道哪個段、偏移多少的地方的地址或值是需要修改的)、重定位的類型、符號的名字(即重定位的地方屬於哪個符號)。(靜態)鏈接器在鏈接目標文件的時候,會掃描每個目標文件,為每個目標文件中的段分配運行時的地址(這個地址就是進程地址空間的地址,因為每個操作系統都會位應用程序指定裝載地址、如eos和windows的0x00400000,linux的0x0804800,通過用操作系統指定的地址配置鏈接器,鏈接器根據每個段的屬性、大小和對齊屬性等,就可得到每個段運行時的地址了),這樣每個段的地址就確定下來了,從而,每個符號的地址都確定了,然后把符號表中符號的值修改為正確的地址。然后連接器就會把所有目標文件的符號表合並為一個全局的符號表(主要是為了定位的方便)。接下來,連接器就讀取每個目標文件,通過重定位表找到需要重定位的地方和這個符號的名字,然后用這個符號去檢索全局符號表,得到符號的地址,再用個這個地址填入需要修正的地方(對於相對跳轉指令是用這個地址和修正處的地址和修正處存放的值參運算計算出正確的跳轉偏移,然后填入),最后把所有經過了重定位的目標文件中相同段名和屬性的段合並,輸出到最終的可執行文件中並建立相應的數據結構,可執行文件也是有格式的,linux 常見的elf,windows和eos的pe格式。
- */
- //重定位結構(Elf32_Rel)中的字段r_info指定重定位地址屬於哪個符號,r_offset字段指定重定位的地方。然后把這個符號的絕對地址寫到重定位的地方
- enum obj_reloc arch_apply_relocation (struct obj_file *f,
- struct obj_section *targsec,
- struct obj_section *symsec,
- struct obj_symbol *sym,
- Elf32_Rel *rel,
- Elf32_Addr v)
- {
- struct i386_file *ifile = (struct i386_file *)f;
- struct i386_symbol *isym = (struct i386_symbol *)sym;
- ////找到重定位的地方,下邊在這個地方寫入符號的絕對地址
- Elf32_Addr *loc = (Elf32_Addr *)(targsec->contents + rel->r_offset);
- Elf32_Addr dot = targsec->header.sh_addr + rel->r_offset;
- Elf32_Addr got = ifile->got ? ifile->got->header.sh_addr : 0;
- enum obj_reloc ret = obj_reloc_ok;
- switch (ELF32_R_TYPE(rel->r_info))//重定位類型
- {
- case R_386_NONE:
- break;
- case R_386_32:
- *loc += v;
- break;
- case R_386_PLT32:
- case R_386_PC32:
- *loc += v - dot;
- break;
- case R_386_GLOB_DAT:
- case R_386_JMP_SLOT:
- *loc = v;
- break;
- case R_386_RELATIVE:
- *loc += f->baseaddr;
- break;
- case R_386_GOTPC:
- assert(got != 0);
- *loc += got - dot;
- break;
- case R_386_GOT32:
- assert(isym != NULL);
- if (!isym->gotent.reloc_done)
- {
- isym->gotent.reloc_done = 1;
- *(Elf32_Addr *)(ifile->got->contents + isym->gotent.offset) = v;
- }
- *loc += isym->gotent.offset;
- break;
- case R_386_GOTOFF:
- assert(got != 0);
- *loc += v - got;
- break;
- default:
- ret = obj_reloc_unhandled;
- break;
- }
- return ret;
- }
- //insmod包含在modutils包里。我們感興趣的東西是insmod.c文件里的init_module()函數。
- static int init_module(const char *m_name, struct obj_file *f,unsigned long m_size, const char *blob_name,unsigned int noload, unsigned int flag_load_map)
- {
- //傳入的參數m_name是模塊名稱,obj_file *f已經將模塊的節區信息添加到該結構中,m_size是模塊大小
- struct module *module;
- struct obj_section *sec;
- void *image;
- int ret = 0;
- tgt_long m_addr;
- //從節區數組中查找.this節區
- sec = obj_find_section(f, ".this");
- module = (struct module *) sec->contents;//取得本節區的內容,是一個module結構
- //下邊的操作就是初始化module結構,都保存在.this節區的contents中。
- //.this節區的偏移地址地址就是module結構的在內核空間的起始地址,因為.this節區是首節區,前邊該節區的偏移地址根據內核空間的模塊起始地址做了一個偏移即sec->header.sh_addr+m_addr,又因為.this節區的sh_addr初始值是0,所以加了這個偏移,就正好指向模塊在內核空間的起始地址。
- m_addr = sec->header.sh_addr;
- module->size_of_struct = sizeof(*module);//模塊結構大小
- module->size = m_size;//模塊大小
- module->flags = flag_autoclean ? NEW_MOD_AUTOCLEAN : 0;
- //查找__ksymtab節表,保存的是該模塊導出的符號
- sec = obj_find_section(f, "__ksymtab");
- if (sec && sec->header.sh_size) {
- module->syms = sec->header.sh_addr;
- module->nsyms = sec->header.sh_size / (2 * tgt_sizeof_char_p);
- }
- //查找.kmodtab節表,module的依賴對象對應於.kmodtab節區
- if (n_ext_modules_used) {
- sec = obj_find_section(f, ".kmodtab");
- module->deps = sec->header.sh_addr;
- module->ndeps = n_ext_modules_used;
- }
- //obj_find_symbol()函數遍歷符號列表查找名字為init_module的符號,然后提取這個結構體符號(struct symbol)並把它傳遞給 obj_symbol_final_value()。后者從這個結構體符號提取出init_module函數的地址。
- module->init = obj_symbol_final_value(f, obj_find_symbol(f, "init_module"));
- module->cleanup = obj_symbol_final_value(f,obj_find_symbol(f, "cleanup_module"));
- //查找__ex_table節表
- sec = obj_find_section(f, "__ex_table");
- if (sec) {
- module->ex_table_start = sec->header.sh_addr;
- module->ex_table_end = sec->header.sh_addr + sec->header.sh_size;
- }
- //查找.text.init節表
- sec = obj_find_section(f, ".text.init");
- if (sec) {
- module->runsize = sec->header.sh_addr - m_addr;
- }
- //查找.data.init節表
- sec = obj_find_section(f, ".data.init");
- if (sec) {
- if (!module->runsize || module->runsize > sec->header.sh_addr - m_addr)
- module->runsize = sec->header.sh_addr - m_addr;
- }
- sec = obj_find_section(f, ARCHDATA_SEC_NAME);
- if (sec && sec->header.sh_size) {
- module->archdata_start = sec->header.sh_addr;
- module->archdata_end = module->archdata_start + sec->header.sh_size;
- }
- //查找kallsyms節區
- sec = obj_find_section(f, KALLSYMS_SEC_NAME);
- if (sec && sec->header.sh_size) {
- module->kallsyms_start = sec->header.sh_addr;//符號開始地址
- module->kallsyms_end = module->kallsyms_start + sec->header.sh_size;//符號結束地址
- }
- if (!arch_init_module(f, module))//x86下什么也不干
- return 0;
- //在用戶空間分配module的image的內存,返回模塊起始地址
- image = xmalloc(m_size);
- //各個section的內容拷入image中,包括原文件中的section和后來構造的section
- obj_create_image(f, image);//模塊內容從f結構指定的地址中拷貝到image中,構建模塊映像
- if (flag_load_map)
- print_load_map(f);
- if (blob_name) {//是否指定了要輸出模塊到指定的文件blob_name
- int fd, l;
- fd = open(blob_name, O_WRONLY|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
- if (fd < 0) {
- error("open %s failed %m", blob_name);
- ret = -1;
- }
- else {
- if ((l = write(fd, image, m_size)) != m_size) {
- error("write %s failed %m", blob_name);
- ret = -1;
- }
- close(fd);
- }
- }
- if (ret == 0 && !noload) {
- fflush(stdout); /* Flush any debugging output */
- //sys_init_module() 這個系統調用通知內核加載相應模塊,這個函數的代碼可以在 /usr/src/linux/kernel/module.c
- //在此函數中把在用戶空間的module映像復制到前邊由create_module創建地內核空間中。
- ret = sys_init_module(m_name, (struct module *) image);
- if (ret) {
- error("init_module: %m");
- lprintf("Hint: insmod errors can be caused by incorrect module parameters, "
- "including invalid IO or IRQ parameters.You may find more information in syslog or the output from dmesg
- }
- }
- free(image);
- return ret == 0;
- }
- int obj_create_image (struct obj_file *f, char *image)
- {
- struct obj_section *sec;
- ElfW(Addr) base = f->baseaddr;
- //注意:首個load的節區是.this節區
- for (sec = f->load_order; sec ; sec = sec->load_next)
- {
- char *secimg;
- if (sec->contents == 0)
- continue;
- secimg = image + (sec->header.sh_addr - base);
- //所有的節區內容拷貝到以image地址開始的地方,當然第一個節區的內容恰好是struct module結構
- memcpy(secimg, sec->contents, sec->header.sh_size);
- }
- return 1;
- }
- int sys_init_module(const char *name, const struct module *info)
- {
- //調用系統調用init_module(),系統調用init_module()在內核中的實現是sys_init_module(),這是由內核提供的,全內核只有這一個函數。注意,這是linux V2.6以前的調用。
- return init_module(name, info);
- }
- asmlinkage long sys_init_module(const char *name_user, struct module *mod_user)
- {
- struct module mod_tmp, *mod;
- char *name, *n_name, *name_tmp = NULL;
- long namelen, n_namelen, i, error;
- unsigned long mod_user_size;
- struct module_ref *dep;
- //檢查模塊加載的權限
- if (!capable(CAP_SYS_MODULE))
- return EPERM;
- lock_kernel();
- //復制模塊名到內核空間name中
- if ((namelen = get_mod_name(name_user, &name)) < 0) {
- error = namelen;
- goto err0;
- }
- //從module_list中找到之前通過creat_module()在內核空間創建的module結構,創建模塊時已經將模塊結構鏈入module_list
- if ((mod = find_module(name)) == NULL) {
- error = ENOENT;
- goto err1;
- }
- //把用戶空間的module結構的size_of_struct復制到內核中加以檢查,將模塊結構的大小size_of_struct賦值給mod_user_size,即sizeof(struct module)
- if ((error = get_user(mod_user_size, &mod_user->size_of_struct)) != 0)
- goto err1;
- //對用戶空間和內核空間的module結構的大小進行檢查
- //如果申請的模塊頭部長度小於舊版的模塊頭長度或者大於將來可能的模塊頭長度
- if (mod_user_size < (unsigned long)&((struct module *)0L)->persist_start
- || mod_user_size > sizeof(struct module) + 16*sizeof(void*)) {
- printk(KERN_ERR "init_module: Invalid module header size.\n"
- KERN_ERR "A new version of the modutils is likely ""needed.\n");
- error = EINVAL;
- goto err1;
- }
- mod_tmp = *mod;//內核中的module結構先保存到堆棧中
- //分配保存內核空間模塊名的空間
- name_tmp = kmalloc(strlen(mod->name) + 1, GFP_KERNEL); /* Where's kstrdup()? */
- if (name_tmp == NULL) {
- error = ENOMEM;
- goto err1;
- }
- strcpy(name_tmp, mod->name);//將內核中module的name復制給name_tmp,在堆棧中先保存起來
- //將用戶空間的模塊結構到內核空間原來的module地址處(即將用戶空間的模塊映像覆蓋掉在內核空間模塊映像的所在地址,這樣前邊設置的符號地址就不需要改變了,正好就是我們在前邊按照內核空間的模塊起始地址設置的),mod_user_size是模塊結構的大小,即sizeof(struct module)
- error = copy_from_user(mod, mod_user, mod_user_size);
- if (error) {
- error = EFAULT;
- goto err2;
- }
- error = EINVAL;
- //比較內核空間和用戶空間模塊大小,若在insmod用戶空間創建的module結構大小大於內核空間的module大小,就出錯退出
- if (mod->size > mod_tmp.size) {
- printk(KERN_ERR "init_module: Size of initialized module ""exceeds size of created module.\n");
- goto err2;
- }
- if (!mod_bound(mod->name, namelen, mod)) {//用來檢查用戶指針所指的對象是否落在模塊的邊界內
- printk(KERN_ERR "init_module: mod->name out of bounds.\n");
- goto err2;
- }
- if (mod->nsyms && !mod_bound(mod->syms, mod->nsyms, mod)) {// 符號表的區域是否在模塊體中
- printk(KERN_ERR "init_module: mod->syms out of bounds.\n");
- goto err2;
- }
- if (mod->ndeps && !mod_bound(mod->deps, mod->ndeps, mod)) {//依賴關系表是否在模塊體中
- printk(KERN_ERR "init_module: mod->deps out of bounds.\n");
- goto err2;
- }
- if (mod->init && !mod_bound(mod->init, 0, mod)) {//init函數是否是模塊體中
- printk(KERN_ERR "init_module: mod->init out of bounds.\n");
- goto err2;
- }
- if (mod->cleanup && !mod_bound(mod->cleanup, 0, mod)) {//cleanup函數是否是模塊體中
- printk(KERN_ERR "init_module: mod->cleanup out of bounds.\n");
- goto err2;
- }
- //檢查模塊的異常描述表是否在模塊影像內
- if (mod->ex_table_start > mod->ex_table_end|| (mod->ex_table_start
- &&!((unsigned long)mod->ex_table_start >= ((unsigned long)mod + mod->size_of_struct)
- && ((unsigned long)mod->ex_table_end< (unsigned long)mod + mod->size)))||
- (((unsigned long)mod->ex_table_start-(unsigned long)mod->ex_table_end)% sizeof(struct exception_table_entry))) {
- printk(KERN_ERR "init_module: mod->ex_table_* invalid.\n");
- goto err2;
- }
- if (mod->flags & ~MOD_AUTOCLEAN) {
- printk(KERN_ERR "init_module: mod->flags invalid.\n");
- goto err2;
- }
- if (mod_member_present(mod, can_unload)//檢查結構的大小,看用戶模塊是否包含can_unload字段
- && mod->can_unload && !mod_bound(mod->can_unload, 0, mod)) {//若包含can_unload字段,就檢查其邊界
- printk(KERN_ERR "init_module: mod->can_unload out of bounds.\n");
- goto err2;
- }
- if (mod_member_present(mod, kallsyms_end)) {
- if (mod->kallsyms_end &&(!mod_bound(mod->kallsyms_start, 0, mod) ||!mod_bound(mod->kallsyms_end, 0, mod))) {
- printk(KERN_ERR "init_module: mod->kallsyms out of bounds.\n");
- goto err2;
- }
- if (mod->kallsyms_start > mod->kallsyms_end) {
- printk(KERN_ERR "init_module: mod->kallsyms invalid.\n");
- goto err2;
- }
- }
- if (mod_member_present(mod, archdata_end)) {
- if (mod->archdata_end &&(!mod_bound(mod->archdata_start, 0, mod) ||
- !mod_bound(mod->archdata_end, 0, mod))) {
- printk(KERN_ERR "init_module: mod->archdata out of bounds.\n");
- goto err2;
- }
- if (mod->archdata_start > mod->archdata_end) {
- printk(KERN_ERR "init_module: mod->archdata invalid.\n");
- goto err2;
- }
- }
- if (mod_member_present(mod, kernel_data) && mod->kernel_data) {
- printk(KERN_ERR "init_module: mod->kernel_data must be zero.\n");
- goto err2;
- }
- //在把用戶空間的模塊名從用戶空間拷貝進來
- if ((n_namelen = get_mod_name(mod->name-(unsigned long)mod+ (unsigned long)mod_user,&n_name)) < 0) {
- printk(KERN_ERR "init_module: get_mod_name failure.\n");
- error = n_namelen;
- goto err2;
- }
- //比較內核空間中和用戶空間中模塊名是否相同
- if (namelen != n_namelen || strcmp(n_name, mod_tmp.name) != 0) {
- printk(KERN_ERR "init_module: changed module name to ""`%s' from `%s'\n",n_name, mod_tmp.name);
- goto err3;
- }
- //拷貝除module結構本身以外的其它section到內核空間中,就是拷貝模塊映像
- if (copy_from_user((char *)mod+mod_user_size,(char *)mod_user+mod_user_size,mod->size-mod_user_size)) {
- error = EFAULT;
- goto err3;
- }
- if (module_arch_init(mod))//空操作
- goto err3;
- flush_icache_range((unsigned long)mod, (unsigned long)mod + mod->size);
- mod->next = mod_tmp.next;//mod->next在拷貝模塊頭時被覆蓋了
- mod->refs = NULL;//由於是新加載的,還沒有別的模塊引用我
- //檢查模塊的依賴關系,依賴的模塊仍在內核中
- for (i = 0, dep = mod->deps; i < mod->ndeps; ++i, ++dep) {
- struct module *o, *d = dep->dep;
- if (d == mod) {//依賴的模塊不能使自身
- printk(KERN_ERR "init_module: selfreferential""dependency in mod->deps.\n");
- goto err3;
- }
- //若依賴的模塊已經不在module_list中,則系統調用失敗
- for (o = module_list; o != &kernel_module && o != d; o = o->next);
- if (o != d) {
- printk(KERN_ERR "init_module: found dependency that is ""(no longer?) a module.\n");
- goto err3;
- }
- }
- //再掃描,將每個module_ref結構鏈入到所依賴模塊的refs隊列中,並將結構中的ref指針指向正在安裝的nodule結構。這樣每個module_ref結構既存在於所屬模塊的deps[]數組中,又出現於該模塊所依賴的某個模塊的refs隊列中。
- for (i = 0, dep = mod->deps; i < mod->ndeps; ++i, ++dep) {
- struct module *d = dep->dep;
- dep->ref = mod;
- dep->next_ref = d->refs;
- d->refs = dep;
- d->flags |= MOD_USED_ONCE;
- }
- put_mod_name(n_name);//釋放空間
- put_mod_name(name);//釋放空間
- mod->flags |= MOD_INITIALIZING;//設置模塊初始化標志
- atomic_set(&mod->uc.usecount,1);//用戶計數設為1
- //檢查模塊的init_module()函數,然后執行模塊的init_module()函數,注意此函數與內核中的
- //init_module()函數是不一樣的,內核中的調用sys_init_module()
- if (mod->init && (error = mod->init()) != 0) {
- atomic_set(&mod->uc.usecount,0);//模塊的計數加1
- mod->flags &= ~MOD_INITIALIZING;//初始化標志清零
- if (error > 0) /* Buggy module */
- error = EBUSY;
- goto err0;
- }
- atomic_dec(&mod->uc.usecount);//遞減計數
- //初始化標志清零,標志設置為MOD_RUNNING
- mod->flags = (mod->flags | MOD_RUNNING) & ~MOD_INITIALIZING;
- error = 0;
- goto err0;
- err3:
- put_mod_name(n_name);
- err2:
- *mod = mod_tmp;
- strcpy((char *)mod->name, name_tmp); /* We know there is room for this */
- err1:
- put_mod_name(name);
- err0:
- unlock_kernel();
- kfree(name_tmp);
- return error;
- }
---恢復內容結束---
一、概述
模塊是作為ELF對象文件存放在文件系統中的,並通過執行insmod程序鏈接到內核中。對於每個模塊,系統都要分配一個包含以下數據結構的內存區。
一個module對象,表示模塊名的一個以null結束的字符串,實現模塊功能的代碼。在2.6內核以前,insmod模塊過程主要是通過modutils中的insmod加載,大量工作都是在用戶空間完成。但在2.6內核以后,系統使用busybox的insmod指令,把大量工作移到內核代碼處理,參見模塊加載過程代碼分析2.
二、相關數據結構
1.module對象描述一個模塊。一個雙向循環鏈表存放所有module對象,鏈表頭部存放在modules變量中,而指向相鄰單元的指針存放在每個module對象的list字段中。
struct module
{
/*state 表示該模塊的當前狀態。
enum module_state
{
MODULE_STATE_LIVE,
MODULE_STATE_COMING,
MODULE_STATE_GOING,
};
在裝載期間,狀態為 MODULE_STATE_COMING;
正常運行(完成所有初始化任務之后)時,狀態為 MODULE_STATE_LIVE;
在模塊正在卸載移除時,狀態為 MODULE_STATE_GOING.
*/
enum module_state state;//模塊內部狀態
//模塊鏈表指針,將所有加載模塊保存到一個雙鏈表中,鏈表的表頭是定義在 的全局變量 modules。
struct list_head list;
char name[MODULE_NAME_LEN];//模塊名
/* Sysfs stuff. */
struct module_kobject mkobj;//包含一個kobject數據結構
struct module_attribute *modinfo_attrs;
const char *version;
const char *srcversion;
struct kobject *holders_dir;
/*syms,num_syms,crcs 用於管理模塊導出的符號。syms是一個數組,有 num_syms 個數組項,
數組項類型為 kernel_symbol,負責將標識符(name)分配到內存地址(value):
struct kernel_symbol
{
unsigned long value;
const char *name;
};
crcs 也是一個 num_syms 個數組項的數組,存儲了導出符號的校驗和,用於實現版本控制
*/
const struct kernel_symbol *syms;//指向導出符號數組的指針
const unsigned long *crcs;//指向導出符號CRC值數組的指針
unsigned int num_syms;//導出符號數
struct kernel_param *kp;//內核參數
unsigned int num_kp;//內核參數個數
/*在導出符號時,內核不僅考慮了可以有所有模塊(不考慮許可證類型)使用的符號,還要考慮只能由 GPL 兼容模塊使用的符號。 第三類的符號當前仍然可以有任意許可證的模塊使用,但在不久的將來也會轉變為只適用於 GPL 模塊。gpl_syms,num_gpl_syms,gpl_crcs 成員用於只提供給 GPL 模塊的符號;gpl_future_syms,num_gpl_future_syms,gpl_future_crcs 用於將來只提供給 GPL 模塊的符號。unused_gpl_syms 和 unused_syms 以及對應的計數器和校驗和成員描述。 這兩個數組用於存儲(只適用於 GPL)已經導出, 但 in-tree 模塊未使用的符號。在out-of-tree 模塊使用此類型符號時,內核將輸出一個警告消息。
*/
unsigned int num_gpl_syms;//GPL格式導出符號數
const struct kernel_symbol *gpl_syms;//指向GPL格式導出符號數組的指針
const unsigned long *gpl_crcs;//指向GPL格式導出符號CRC值數組的指針
#ifdef CONFIG_MODULE_SIG
bool sig_ok;/* Signature was verified. */
#endif
/* symbols that will be GPL-only in the near future. */
const struct kernel_symbol *gpl_future_syms;
const unsigned long *gpl_future_crcs;
unsigned int num_gpl_future_syms;
/*如果模塊定義了新的異常,異常的描述保存在 extable數組中。 num_exentries 指定了數組的長度。 */
unsigned int num_exentries;
struct exception_table_entry *extable;
/*模塊的二進制數據分為兩個部分;初始化部分和核心部分。
前者包含的數據在轉載結束后都可以丟棄(例如:初始化函數),后者包含了正常運行期間需要的所有數據。
初始化部分的起始地址保存在 module_init,長度為 init_size 字節;
核心部分有 module_core 和 core_size 描述。
*/
int (*init)(void);//模塊初始化方法,指向一個在模塊初始化時調用的函數
void *module_init;//用於模塊初始化的動態內存區指針
void *module_core;//用於模塊核心函數與數據結構的動態內存區指針
//用於模塊初始化的動態內存區大小和用於模塊核心函數與數據結構的動態內存區指針
unsigned int init_size, core_size;
//模塊初始化的可執行代碼大小,模塊核心可執行代碼大小,只當模塊鏈接時使用
unsigned int init_text_size, core_text_size;
/* Size of RO sections of the module (text+rodata) */
unsigned int init_ro_size, core_ro_size;
struct mod_arch_specific arch;//依賴於體系結構的字段
/*如果模塊會污染內核,則設置 taints.污染意味着內核懷疑該模塊做了一個有害的事情,可能妨礙內核的正常運作。
如果發生內核恐慌(在發生致命的內部錯誤,無法恢復正常運作時,將觸發內核恐慌),那么錯誤診斷也會包含為什么內核被污染的有關信息。
這有助於開發者區分來自正常運行系統的錯誤報告和包含某些可疑因素的系統錯誤。
add_taint_module 函數用來設置 struct module 的給定實例的 taints 成員。
模塊可能因兩個原因污染內核:
1,如果模塊的許可證是專有的,或不兼容 GPL,那么在模塊載入內核時,會使用 TAINT_PROPRIETARY_MODULE.
由於專有模塊的源碼可能弄不到,模塊在內核中作的任何事情都無法跟蹤,因此,bug 很可能是由模塊引入的。
內核提供了函數 license_is_gpl_compatible 來判斷給定的許可證是否與 GPL 兼容。
2,TAINT_FORCED_MODULE 表示該模塊是強制裝載的。如果模塊中沒有提供版本信息,也稱為版本魔術(version magic),
或模塊和內核某些符號的版本不一致,那么可以請求強制裝載。
*/
unsigned int taints; /* same bits as kernel:tainted */
char *args;//模塊鏈接時使用的命令行參數
#ifdef CONFIG_SMP/
void __percpu *percpu;/*percpu 指向屬於模塊的各 CPU 數據。它在模塊裝載時初始化*/
unsigned int percpu_size;
#endif
#ifdef CONFIG_TRACEPOINTS
unsigned int num_tracepoints;
struct tracepoint * const *tracepoints_ptrs;
#endif
#ifdef HAVE_JUMP_LABEL
struct jump_entry *jump_entries;
unsigned int num_jump_entries;
#endif
#ifdef CONFIG_TRACING
unsigned int num_trace_bprintk_fmt;
const char **trace_bprintk_fmt_start;
#endif
#ifdef CONFIG_EVENT_TRACING
struct ftrace_event_call **trace_events;
unsigned int num_trace_events;
#endif
#ifdef CONFIG_FTRACE_MCOUNT_RECORD
unsigned int num_ftrace_callsites;
unsigned long *ftrace_callsites;
#endif
#ifdef CONFIG_MODULE_UNLOAD
/* What modules depend on me? */
struct list_head source_list;
/* What modules do I depend on? */
struct list_head target_list;
struct task_struct *waiter;//正卸載模塊的進程
void (*exit)(void);//模塊退出方法
/*module_ref 用於引用計數。系統中的每個 CPU,都對應到該數組中的數組項。該項指定了系統中有多少地方使用了該模塊。
內核提供了 try_module_get 和 module_put 函數,用對引用計數器加1或減1,如果調用者確信相關模塊當前沒有被卸載,
也可以使用 __module_get 對引用計數加 1.相反,try_module_get 會確認模塊確實已經加載。
struct module_ref {
unsigned int incs;
unsigned int decs;
}
*/
struct module_ref __percpu *refptr;//模塊計數器,每個cpu一個
#endif
#ifdef CONFIG_CONSTRUCTORS
/* Constructor functions. */
ctor_fn_t *ctors;
unsigned int num_ctors;
#endif
};
三、模塊鏈接過程
用戶可以通過執行insmod外部程序把一個模塊鏈接到正在運行的內核中。該過程執行以下操作:
1.從命令行中讀取要鏈接的模塊名
2.確定模塊對象代碼所在的文件在系統目錄樹中的位置。
3.從磁盤讀入存有模塊目標代碼的文件。
4.調用init_module()系統調用。函數將模塊二進制文件復制到內核,然后由內核完成剩余的任務。
5.init_module函數通過系統調用層,進入內核到達內核函數 sys_init_module,這是加載模塊的主要函數。
6.結束。
四、insmod過程解析
1.obj_file記錄模塊信息
struct obj_file
{
ElfW(Ehdr) header;//指向elf header
ElfW(Addr) baseaddr;//模塊的基址
struct obj_section **sections;//指向節區頭部表,包含每個節區頭部
struct obj_section *load_order;//節區load順序
struct obj_section **load_order_search_start;//
struct obj_string_patch_struct *string_patches;//patch的字符串
struct obj_symbol_patch_struct *symbol_patches;//patch的符號
int (*symbol_cmp)(const char *, const char *);//指向strcmp函數
unsigned long (*symbol_hash)(const char *);//指向obj_elf_hash函數
unsigned long local_symtab_size;//局部符號表大小
struct obj_symbol **local_symtab;//局部符號表
struct obj_symbol *symtab[HASH_BUCKETS];//符號hash表
const char *filename;//模塊名
char *persist;
};
2.obj_section記錄模塊節區信息
struct obj_section
{
ElfW(Shdr) header;//指向本節區頭結構
const char *name;//節區名
char *contents;//從節區頭內容偏移處獲得的節區內容
struct obj_section *load_next;//指向下一個節區
int idx;//該節區索引值
};
3.module_stat結構記錄每一個模塊的信息
struct module_stat {
char *name;//模塊名稱
unsigned long addr;//模塊地址
unsigned long modstruct; /* COMPAT_2_0! *//* depends on architecture? */
unsigned long size;//模塊大小
unsigned long flags;//標志
long usecount;//模塊計數
size_t nsyms;//模塊中的符號個數
struct module_symbol *syms;//指向模塊符號
size_t nrefs;//模塊依賴其他模塊的個數
struct module_stat **refs;//依賴的模塊數組
unsigned long status;//模塊狀態
};
4.
struct load_info {
Elf_Ehdr *hdr;//指向elf頭
unsigned long len;
Elf_Shdr *sechdrs;//指向節區頭
char *secstrings;//指向節區名稱的字符串節區
char *strtab;//指向符號節區
unsigned long symoffs, stroffs;
struct _ddebug *debug;
unsigned int num_debug;
bool sig_ok;
struct {
unsigned int sym, str, mod, vers, info, pcpu;
} index;
};
5.代碼分析
- int main(int argc, char **argv)
- {
- /* List of possible program names and the corresponding mainline routines */
- static struct { char *name; int (*handler)(int, char **); } mains[] =
- {
- { "insmod", &insmod_main },
- #ifdef COMBINE_modprobe
- { "modprobe", &modprobe_main },
- #endif
- #ifdef COMBINE_rmmod
- { "rmmod", &rmmod_main },
- #endif
- #ifdef COMBINE_ksyms
- { "ksyms", &ksyms_main },
- #endif
- #ifdef COMBINE_lsmod
- { "lsmod", &lsmod_main },
- #endif
- #ifdef COMBINE_kallsyms
- { "kallsyms", &kallsyms_main },
- #endif
- };
- #define MAINS_NO (sizeof(mains)/sizeof(mains[0]))
- static int mains_match;
- static int mains_which;
- char *p = strrchr(argv[0], '/');//查找‘/’字符出現的位置
- char error_id1[2048] = "The "; /* Way oversized */
- char error_id2[2048] = ""; /* Way oversized */
- int i;
- p = p ? p + 1 : argv[0];//得到命令符,這里是insmod命令
- for (i = 0; i < MAINS_NO; ++i) {
- if (i) {
- xstrcat(error_id1, "/", sizeof(error_id1));//字符串連接函數
- if (i == MAINS_NO-1)
- xstrcat(error_id2, " or ", sizeof(error_id2));
- else
- xstrcat(error_id2, ", ", sizeof(error_id2));
- }
- xstrcat(error_id1, mains[i].name, sizeof(error_id1));
- xstrcat(error_id2, mains[i].name, sizeof(error_id2));
- if (strstr(p, mains[i].name)) {//命令跟數組的數據比較
- ++mains_match;//insmod命令時,mains_match=1
- mains_which = i;//得到insmod命令所在數組的位置,insmod命令為0
- }
- }
- /* Finish the error identifiers */
- if (MAINS_NO != 1)
- xstrcat(error_id1, " combined", sizeof(error_id1));
- xstrcat(error_id1, " binary", sizeof(error_id1));
- if (mains_match == 0 && MAINS_NO == 1)
- ++mains_match; /* Not combined, any name will do */
- if (mains_match == 0) {
- error("%s does not have a recognisable name, ""the name must contain one of %s.",error_id1, error_id2);
- return(1);
- }
- else if (mains_match > 1) {
- error("%s has an ambiguous name, it must contain %s%s.", error_id1, MAINS_NO == 1 ? "" : "exactly one of ", error_id2);
- return(1);
- }
- else//mains_match=1,表示在數組中找到對應的指令
- return((mains[mains_which].handler)(argc, argv));//調用insmod_main()函數
- }
- int insmod_main(int argc, char **argv)
- {
- if (arch64())
- return insmod_main_64(argc, argv);
- else
- return insmod_main_32(argc, argv);
- }
- #if defined(COMMON_3264) && defined(ONLY_32)
- #define INSMOD_MAIN insmod_main_32 /* 32 bit version */
- #elif defined(COMMON_3264) && defined(ONLY_64)
- #define INSMOD_MAIN insmod_main_64 /* 64 bit version */
- #else
- #define INSMOD_MAIN insmod_main /* Not common code */
- #endif
- int INSMOD_MAIN(int argc, char **argv)
- {
- int k_version;
- int k_crcs;
- char k_strversion[STRVERSIONLEN];
- struct option long_opts[] = {
- {"force", 0, 0, 'f'},
- {"help", 0, 0, 'h'},
- {"autoclean", 0, 0, 'k'},
- {"lock", 0, 0, 'L'},
- {"map", 0, 0, 'm'},
- {"noload", 0, 0, 'n'},
- {"probe", 0, 0, 'p'},
- {"poll", 0, 0, 'p'}, /* poll is deprecated, remove in 2.5 */
- {"quiet", 0, 0, 'q'},
- {"root", 0, 0, 'r'},
- {"syslog", 0, 0, 's'},
- {"kallsyms", 0, 0, 'S'},
- {"verbose", 0, 0, 'v'},
- {"version", 0, 0, 'V'},
- {"noexport", 0, 0, 'x'},
- {"export", 0, 0, 'X'},
- {"noksymoops", 0, 0, 'y'},
- {"ksymoops", 0, 0, 'Y'},
- {"persist", 1, 0, 'e'},
- {"numeric-only", 1, 0, 'N'},
- {"name", 1, 0, 'o'},
- {"blob", 1, 0, 'O'},
- {"prefix", 1, 0, 'P'},
- {0, 0, 0, 0}
- };
- char *m_name = NULL;
- char *blob_name = NULL; /* Save object as binary blob */
- int m_version;
- ElfW(Addr) m_addr;
- unsigned long m_size;
- int m_crcs;
- char m_strversion[STRVERSIONLEN];
- char *filename;
- char *persist_name = NULL; /* filename to hold any persistent data */
- int fp;
- struct obj_file *f;
- struct obj_section *kallsyms = NULL, *archdata = NULL;
- int o;
- int noload = 0;
- int dolock = 1; /*Note: was: 0; */
- int quiet = 0;
- int exit_status = 1;
- int force_kallsyms = 0;
- int persist_parms = 0; /* does module have persistent parms? */
- int i;
- int gpl;
- error_file = "insmod";
- /* To handle repeated calls from combined modprobe */
- errors = optind = 0;
- /* Process the command line. */
- while ((o = getopt_long(argc, argv, "fhkLmnpqrsSvVxXyYNe:o:O:P:R:",&long_opts[0], NULL)) != EOF)
- switch (o) {
- case 'f': /* force loading */
- flag_force_load = 1;
- break;
- case 'h': /* Print the usage message. */
- insmod_usage();
- break;
- case 'k': /* module loaded by kerneld, auto-cleanable */
- flag_autoclean = 1;
- break;
- case 'L': /* protect against recursion. */
- dolock = 1;
- break;
- case 'm': /* generate load map */
- flag_load_map = 1;
- break;
- case 'n': /* don't load, just check */
- noload = 1;
- break;
- case 'p': /* silent probe mode */
- flag_silent_probe = 1;
- break;
- case 'q': /* Don't print unresolved symbols */
- quiet = 1;
- break;
- case 'r': /* allow root to load non-root modules */
- root_check_off = !root_check_off;
- break;
- case 's': /* start syslog */
- setsyslog("insmod");
- break;
- case 'S': /* Force kallsyms */
- force_kallsyms = 1;
- break;
- case 'v': /* verbose output */
- flag_verbose = 1;
- break;
- case 'V':
- fputs("insmod version " MODUTILS_VERSION "\n", stderr);
- break;
- case 'x': /* do not export externs */
- flag_export = 0;
- break;
- case 'X': /* do export externs */
- #ifdef HAS_FUNCTION_DESCRIPTORS
- fputs("This architecture has function descriptors, exporting everything is unsafe\n"
- "You must explicitly export the desired symbols with EXPORT_SYMBOL()\n", stderr);
- #else
- flag_export = 1;
- #endif
- break;
- case 'y': /* do not define ksymoops symbols */
- flag_ksymoops = 0;
- break;
- case 'Y': /* do define ksymoops symbols */
- flag_ksymoops = 1;
- break;
- case 'N': /* only check numeric part of kernel version */
- flag_numeric_only = 1;
- break;
- case 'e': /* persistent data filename */
- free(persist_name);
- persist_name = xstrdup(optarg);
- break;
- case 'o': /* name the output module */
- m_name = optarg;
- break;
- case 'O': /* save the output module object */
- blob_name = optarg;
- break;
- case 'P': /* use prefix on crc */
- set_ncv_prefix(optarg);
- break;
- default:
- insmod_usage();
- break;
- }
- if (optind >= argc) {//參數為0,則輸出insmod用法介紹
- insmod_usage();
- }
- filename = argv[optind++];//獲得要加載的模塊路徑名
- if (config_read(0, NULL, "", NULL) < 0) {//modutil配置相關???
- error("Failed handle configuration");
- }
- //清空persist_name
- if (persist_name && !*persist_name &&(!persistdir || !*persistdir)) {
- free(persist_name);
- persist_name = NULL;
- if (flag_verbose) {
- lprintf("insmod: -e \"\" ignored, no persistdir");
- ++warnings;
- }
- }
- if (m_name == NULL) {
- size_t len;
- char *p;
- //根據模塊的路徑名獲取模塊的名稱
- if ((p = strrchr(filename, '/')) != NULL)//找到最后一個'/'的位置
- p++;
- else
- p = filename;
- len = strlen(p);
- //去除模塊名的后綴,保存在m_name中
- if (len > 2 && p[len - 2] == '.' && p[len - 1] == 'o')
- len -= 2;
- else if (len > 4 && p[len - 4] == '.' && p[len - 3] == 'm'&& p[len - 2] == 'o' && p[len - 1] == 'd')
- len -= 4;
- #ifdef CONFIG_USE_ZLIB
- else if (len > 5 && !strcmp(p + len - 5, ".o.gz"))
- len -= 5;
- #endif
- m_name = xmalloc(len + 1);
- memcpy(m_name, p, len);//模塊名稱拷貝到m_name[]中
- m_name[len] = '\0';
- }
- //根據模塊路徑,檢查模塊是否存在
- if (!strchr(filename, '/') && !strchr(filename, '.')) {
- char *tmp = search_module_path(filename);//查找模塊路徑???
- if (tmp == NULL) {
- error("%s: no module by that name found", filename);
- return 1;
- }
- filename = tmp;
- lprintf("Using %s", filename);
- } else if (flag_verbose)
- lprintf("Using %s", filename);
- //打開要加載的模塊文件
- if ((fp = gzf_open(filename, O_RDONLY)) == -1) {
- error("%s: %m", filename);
- return 1;
- }
- /* Try to prevent multiple simultaneous loads. */
- if (dolock)
- flock(fp, LOCK_EX);
- /*
- type的三種類型,影響get_kernle_info的流程。
- #define K_SYMBOLS 1 //Want info about symbols
- #define K_INFO 2 Want extended module info
- #define K_REFS 4 Want info about references
- */
- //負責取得kernel中先以注冊的modules,放入module_stat中,並將kernel實現的各個symbol放入ksyms中,個數為ksyms。get_kernel_info最終調用new_get_kernel_info
- if (!get_kernel_info(K_SYMBOLS))
- goto out;
- set_ncv_prefix(NULL);//判斷symbol name中是否有前綴,象_smp之類,這里沒有。
- for (i = 0; !noload && i < n_module_stat; ++i) {//判斷是否有同名的模塊存在
- if (strcmp(module_stat[i].name, m_name) == 0) {//遍歷kernel中所有的模塊,比較名稱
- error("a module named %s already exists", m_name);
- goto out;
- }
- }
- error_file = filename;
- if ((f = obj_load(fp, ET_REL, filename)) == NULL)//將模塊文件讀入到struct obj_file結構f
- goto out;
- if (check_gcc_mismatch(f, filename))//檢查編譯器版本
- goto out;
- //檢查內核和module的版本信息
- k_version = get_kernel_version(k_strversion);
- m_version = get_module_version(f, m_strversion);
- if (m_version == -1) {
- error("couldn't find the kernel version the module was compiled for");
- goto out;
- }
- //接下來還要測試內核和模塊是否使用了版本的附加信息
- k_crcs = is_kernel_checksummed();
- m_crcs = is_module_checksummed(f);
- if ((m_crcs == 0 || k_crcs == 0) &&strncmp(k_strversion, m_strversion, STRVERSIONLEN) != 0) {
- if (flag_force_load) {
- lprintf("Warning: kernel-module version mismatch\n"
- "\t%s was compiled for kernel version %s\n"
- "\twhile this kernel is version %s",
- filename, m_strversion, k_strversion);
- ++warnings;
- } else {
- if (!quiet)
- error("kernel-module version mismatch\n"
- "\t%s was compiled for kernel version %s\n"
- "\twhile this kernel is version %s.",
- filename, m_strversion, k_strversion);
- goto out;
- }
- }
- if (m_crcs != k_crcs)//設置新的符號比較函數和hash函數,重構hash表(即symtab表)。
- obj_set_symbol_compare(f, ncv_strcmp, ncv_symbol_hash);
- //檢查GPL license
- gpl = obj_gpl_license(f, NULL) == 0;
- //替換模塊中的symbol值為其他已經存在的模塊中相同符號的值
- //如果內核模塊中有該符號,則將該符號的value該為內核中(ksyms[])的符號值
- //修改的是hash符號表中或者局部符號表中的符號值
- add_kernel_symbols(f, gpl);
- #ifdef COMPAT_2_0//linux 內核2.0版本以前
- if (k_new_syscalls ? !create_this_module(f, m_name): !old_create_mod_use_count(f))
- goto out;
- #else
- if (!create_this_module(f, m_name))//創建.this節區,添加一個"__this_module"符號
- goto out;
- #endif
- //這個函數的作用是創建文件的.GOT段,.GOT全稱是global offset table。在這個節區里保存的是絕對地址,這些地址不受重定位的影響。如果程序需要直接引用符號的絕對地址,這些符號就必須在.GOT段中出現
- arch_create_got(f);
- if (!obj_check_undefineds(f, quiet)) {//檢查是否還有未解析的symbol
- if (!gpl && !quiet) {
- if (gplonly_seen)
- error("\n"
- "Hint: You are trying to load a module without a GPL compatible license\n"
- " and it has unresolved symbols. The module may be trying to access\n"
- " GPLONLY symbols but the problem is more likely to be a coding or\n"
- " user error. Contact the module supplier for assistance, only they\n"
- " can help you.\n");
- else
- error("\n"
- "Hint: You are trying to load a module without a GPL compatible license\n"
- " and it has unresolved symbols. Contact the module supplier for\n"
- " assistance, only they can help you.\n");
- }
- goto out;
- }
- obj_allocate_commons(f);//處理未分配資源的符號
- //檢查模塊參數,即檢查那些模塊通過module_param(type, charp, S_IRUGO);聲明的參數
- check_module_parameters(f, &persist_parms);
- check_tainted_module(f, noload);//???
- if (optind < argc) {//如果命令行里帶了參數,處理命令行參數
- if (!process_module_arguments(f, argc - optind, argv + optind, 1))
- goto out;
- }
- //將符號“cleanup_module”,“init_module”,“kernel_version”的屬性改為local(局部),從而使其外部不可見
- hide_special_symbols(f);
- //如果命令行參數來自文件,將文件名保存好,下面要從那里讀出參數值
- if (persist_parms && persist_name && *persist_name) {
- f->persist = persist_name;
- persist_name = NULL;
- }
- //防止-e""這樣的惡作劇
- if (persist_parms &&persist_name && !*persist_name) {
- int j, l = strlen(filename);
- char *relative = NULL;
- char *p;
- for (i = 0; i < nmodpath; ++i) {
- p = modpath[i].path;
- j = strlen(p);
- while (j && p[j] == '/')
- --j;
- if (j < l && strncmp(filename, p, j) == 0 && filename[j] == '/') {
- while (filename[j] == '/')
- ++j;
- relative = xstrdup(filename+j);
- break;
- }
- }
- if (relative) {
- i = strlen(relative);
- if (i > 3 && strcmp(relative+i-3, ".gz") == 0)
- relative[i -= 3] = '\0';
- if (i > 2 && strcmp(relative+i-2, ".o") == 0)
- relative[i -= 2] = '\0';
- else if (i > 4 && strcmp(relative+i-4, ".mod") == 0)
- relative[i -= 4] = '\0';
- f->persist = xmalloc(strlen(persistdir) + 1 + i + 1);
- strcpy(f->persist, persistdir); /* safe, xmalloc */
- strcat(f->persist, "/"); /* safe, xmalloc */
- strcat(f->persist, relative); /* safe, xmalloc */
- free(relative);
- }
- else
- error("Cannot calculate persistent filename");
- }
- //接下來是一些健康檢查
- if (f->persist && *(f->persist) != '/') {
- error("Persistent filenames must be absolute, ignoring '%s'", f->persist);
- free(f->persist);
- f->persist = NULL;
- }
- if (f->persist && !flag_ksymoops) {
- error("has persistent data but ksymoops symbols are not available");
- free(f->persist);
- f->persist = NULL;
- }
- if (f->persist && !k_new_syscalls) {
- error("has persistent data but the kernel is too old to support it");
- free(f->persist);
- f->persist = NULL;
- }
- if (persist_parms && flag_verbose) {
- if (f->persist)
- lprintf("Persist filename '%s'", f->persist);
- else
- lprintf("No persistent filename available");
- }
- if (f->persist) {
- FILE *fp = fopen(f->persist, "r");
- if (!fp) {
- if (flag_verbose)
- lprintf("Cannot open persist file '%s' %m", f->persist);
- }
- else {
- int pargc = 0;
- char *pargv[1000]; /* hard coded but big enough */
- char line[3000]; /* hard coded but big enough */
- char *p;
- while (fgets(line, sizeof(line), fp)) {
- p = strchr(line, '\n');
- if (!p) {
- error("Persistent data line is too long\n%s", line);
- break;
- }
- *p = '\0';
- p = line;
- while (isspace(*p))
- ++p;
- if (!*p || *p == '#')
- continue;
- if (pargc == sizeof(pargv)/sizeof(pargv[0])) {
- error("More than %d persistent parameters", pargc);
- break;
- }
- pargv[pargc++] = xstrdup(p);
- }
- fclose(fp);
- if (!process_module_arguments(f, pargc, pargv, 0))
- goto out;
- while (pargc--)
- free(pargv[pargc]);
- }
- }
- //ksymoops 是一個調試輔助工具,它將試圖將代碼轉換為指令並將堆棧值映射到內核符號。
- if (flag_ksymoops)
- add_ksymoops_symbols(f, filename, m_name);
- if (k_new_syscalls)//k_new_syscalls標志用於測試內核版本
- create_module_ksymtab(f);//創建模塊要導出的符號節區ksymtab,並將要導出的符號加入該節區
- //創建名為“__archdata” (宏 ARCH_SEC_NAME 的定義)的段
- if (add_archdata(f, &archdata))
- goto out;
- //如果symbol使用的都是kernel提供的,就添加一個.kallsyms節區
- //這個函數主要是處理內核導出符號。
- if (add_kallsyms(f, &kallsyms, force_kallsyms))
- goto out;
- /**** No symbols or sections to be changed after kallsyms above ***/
- if (errors)
- goto out;
- //如果flag_slient_probe已經設置,說明我們不想真正安裝模塊,只是想測試一下,那么到這里測試已經完成了,模塊一切正常.
- if (flag_silent_probe) {
- exit_status = 0;
- goto out;
- }
- //計算載入模塊所需的大小,即各個節區的大小和
- m_size = obj_load_size(f);
- //如果noload設置了,那么我們選擇不真正加載模塊。隨便給加載地址就完了.
- if (noload) {
- m_addr = 0x12340000;
- } else {
- errno = 0;
- //調用sys_create_module系統調用創建模塊,分配module的空間,返回模塊在內核空間的地址.這里的module結構不是內核使用的那個,它定義在./modutilst-2.4.0/include/module.h中。函數最終會調用系統調用sys_create_module,生成一個模塊對象,並鏈入模塊的內核鏈表
- //模塊對象的大小就是各個節區大小的和,這里為什么分配的空間m_size不是sizeof(module)+各個節區大小的和?因為第一個節區.this的大小正好就是sizeof(module),所以第一個節區就是struct module結構。
- //注意:區分后邊還有一次在用戶空間為模塊分配空間,然后先把模塊section拷貝到用戶空間的模塊影像中,然后再由sys_init_module()函數將用戶空間的模塊映像拷貝到內核空間的模塊地址,即這里的m_addr。
- m_addr = create_module(m_name, m_size);
- m_addr |= arch_module_base (f);//#define arch_module_base(m) ((ElfW(Addr))0)
- //檢查是否成功創建module結構
- switch (errno) {
- case 0:
- break;
- case EEXIST:
- if (dolock) {
- exit_status = 0;
- goto out;
- }
- error("a module named %s already exists", m_name);
- goto out;
- case ENOMEM:
- error("can't allocate kernel memory for module; needed %lu bytes",m_size);
- goto out;
- default:
- error("create_module: %m");
- goto out;
- }
- }
- //如果模塊運行時參數使用了文件,而且需要真正加載
- if (f->persist && !noload) {
- struct {
- struct module m;
- int data;
- } test_read;
- memset(&test_read, 0, sizeof(test_read));
- test_read.m.size_of_struct = -sizeof(test_read.m); /* -ve size => read, not write */
- test_read.m.read_start = m_addr + sizeof(struct module);
- test_read.m.read_end = test_read.m.read_start + sizeof(test_read.data);
- if (sys_init_module(m_name, (struct module *) &test_read)) {
- int old_errors = errors;
- error("has persistent data but the kernel is too old to support it."
- " Expect errors during rmmod as well");
- errors = old_errors;
- }
- }
- //模塊在內核的地址.而在模塊elf文件里,節區在內存的位置是假設文件從0地址加載而得出的,現在就要根據base值調整。base就是create_module時分配的地址m_addr
- if (!obj_relocate(f, m_addr)) {
- if (!noload)
- delete_module(m_name);
- goto out;
- }
- //至此相當於磁盤中的elf文件格式的.ko文件的內容,已經全部加載到內存中,需要重定位的符號已經進行了重定位,符號地址變成了真正的在內存中的地址,即絕對地址。
- /* Do archdata again, this time we have the final addresses */
- if (add_archdata(f, &archdata))
- goto out;
- //用絕對地址重新生成kallsyms段的內容
- if (add_kallsyms(f, &kallsyms, force_kallsyms))
- goto out;
- #ifdef COMPAT_2_0//2.0以前的版本
- if (k_new_syscalls)
- init_module(m_name, f, m_size, blob_name, noload, flag_load_map);
- else if (!noload)
- old_init_module(m_name, f, m_size);
- #else
- init_module(m_name, f, m_size, blob_name, noload, flag_load_map);
- #endif
- if (errors) {
- if (!noload)
- delete_module(m_name);
- goto out;
- }
- if (warnings && !noload)
- lprintf("Module %s loaded, with warnings", m_name);
- exit_status = 0;
- out:
- if (dolock)
- flock(fp, LOCK_UN);
- close(fp);
- if (!noload)
- snap_shot(NULL, 0);
- return exit_status;
- }
- static int new_get_kernel_info(int type)
- {
- struct module_stat *modules;
- struct module_stat *m;
- struct module_symbol *syms;
- struct module_symbol *s;
- size_t ret;
- size_t bufsize;
- size_t nmod;
- size_t nsyms;
- size_t i;
- size_t j;
- char *module_names;
- char *mn;
- drop();//首先清除module_stat內容,module_stat是個全局變量,保存模塊信息
- //指針分配空間
- module_names = xmalloc(bufsize = 256);
- //取得系統中現有所有的module名稱,ret返回個數,module_names返回各個module名稱,字符0分割
- while (query_module(NULL, QM_MODULES, module_names, bufsize, &ret)) {
- if (errno != ENOSPC) {
- error("QM_MODULES: %m\n");
- return 0;
- }
- /*
- 會調用realloc(void *mem_address, unsigned int newsize)函數,此先判斷當前的指針是否有足夠的連續空間,如果有,擴大mem_address指向的地址,並且將mem_address返回,如果空間不夠,先按照newsize指定的大小分配空間,將原有數據從頭到尾拷貝到新分配的內存區域,而后釋放原來mem_address所指內存區域(注意:原來指針是自動釋放,不需要使用free),同時返回新分配的內存區域的首地址。即重新分配存儲器塊的地址。
- */
- module_names = xrealloc(module_names, bufsize = ret);
- }
- module_name_list = module_names;//指向系統中所有模塊名稱的起始地址
- l_module_name_list = bufsize;//所有模塊名稱的大小,即module_names大小
- n_module_stat = nmod = ret;//返回模塊個數
- //分配空間,地址付給全局變量module_stat
- module_stat = modules = xmalloc(nmod * sizeof(struct module_stat));
- memset(modules, 0, nmod * sizeof(struct module_stat));
- //循環取得各個module的信息,QM_INFO的使用。
- for (i = 0, mn = module_names, m = modules;i < nmod;++i, ++m, mn += strlen(mn) + 1) {
- struct module_info info;
- //info包括module的地址,大小,flag和使用計數器。
- m->name = mn;//模塊名稱給module_stat結構
- if (query_module(mn, QM_INFO, &info, sizeof(info), &ret)) {
- if (errno == ENOENT) {
- m->flags = NEW_MOD_DELETED;
- continue;
- }
- error("module %s: QM_INFO: %m", mn);
- return 0;
- }
- m->addr = info.addr;//模塊地址給module_stat結構
- if (type & K_INFO) {//取得module的信息
- m->size = info.size;
- m->flags = info.flags;
- m->usecount = info.usecount;
- m->modstruct = info.addr;
- }//將info值傳給module_stat結構
- if (type & K_REFS) {//取得module的引用關系
- int mm;
- char *mrefs;
- char *mr;
- mrefs = xmalloc(bufsize = 64);
- while (query_module(mn, QM_REFS, mrefs, bufsize, &ret)) {//查找mn模塊引用的模塊名
- if (errno != ENOSPC) {
- error("QM_REFS: %m");
- return 1;
- }
- mrefs = xrealloc(mrefs, bufsize = ret);
- }
- for (j = 0, mr = mrefs;j < ret;++j, mr += strlen(mr) + 1) {
- for (mm = 0; mm < i; ++mm) {
- if (strcmp(mr, module_stat[mm].name) == 0) {
- m->nrefs += 1;
- m->refs = xrealloc(m->refs, m->nrefs * sizeof(struct module_stat **));
- m->refs[m->nrefs - 1] = module_stat + mm;//引用的模塊名
- break;
- }
- }
- }
- free(mrefs);
- }
- //這里是遍歷內核中其他模塊的所有符號
- if (type & K_SYMBOLS) { /* 取得symbol信息,正是我們要得*/
- syms = xmalloc(bufsize = 1024);
- //取得mn模塊的符號信息,保存在syms數組中
- while (query_module(mn, QM_SYMBOLS, syms, bufsize, &ret)) {
- if (errno == ENOSPC) {
- syms = xrealloc(syms, bufsize = ret);
- continue;
- }
- if (errno == ENOENT) {
- m->flags = NEW_MOD_DELETED;
- free(syms);
- goto next;
- } else {
- error("module %s: QM_SYMBOLS: %m", mn);
- return 0;
- }
- }
- nsyms = ret;
- //syms是module_symbol結構,ret返回symbol個數
- m->nsyms = nsyms;//符號個數
- m->syms = syms;//符號信息
- //name原來只是一個結構內的偏移,加上結構地址為真正的字符串地址
- for (j = 0, s = syms; j < nsyms; ++j, ++s)
- s->name += (unsigned long) syms;
- }
- next:
- }
- //這里是取得內核符號
- if (type & K_SYMBOLS) { /* Want info about symbols */
- syms = xmalloc(bufsize = 16 * 1024);
- //name為NULL,返回內核符號信息
- while (query_module(NULL, QM_SYMBOLS, syms, bufsize, &ret)) {
- if (errno != ENOSPC) {
- error("kernel: QM_SYMBOLS: %m");
- return 0;
- }
- syms = xrealloc(syms, bufsize = ret);//擴展空間
- }
- //將值返回給nksyms和ksyms兩個全局變量存儲。
- nksyms = nsyms = ret;//內核符號個數
- ksyms = syms;//內核符號
- /* name原來只是一個結構內的偏移,加上結構地址為真正的字符串地址 */
- for (j = 0, s = syms; j < nsyms; ++j, ++s)
- s->name += (unsigned long) syms;
- }
- return 1;
- }
- struct obj_file *obj_load (int fp, Elf32_Half e_type, const char *filename)
- {
- struct obj_file *f;
- ElfW(Shdr) *section_headers;
- int shnum, i;
- char *shstrtab;
- f = arch_new_file();//創建一個新的obj_file結構
- memset(f, 0, sizeof(*f));
- f->symbol_cmp = strcmp;//設置symbol名的比較函數就是strcmp
- f->symbol_hash = obj_elf_hash;//設置計算symbol hash值的函數
- f->load_order_search_start = &f->load_order;//??
- gzf_lseek(fp, 0, SEEK_SET);//文件指針設置到文件頭
- //取得object文件的ELF頭結構。
- if (gzf_read(fp, &f->header, sizeof(f->header)) != sizeof(f->header))
- {
- error("cannot read ELF header from %s", filename);
- return NULL;
- }
- //判斷ELF的magic,是否是ELF文件格式
- if (f->header.e_ident[EI_MAG0] != ELFMAG0
- || f->header.e_ident[EI_MAG1] != ELFMAG1
- || f->header.e_ident[EI_MAG2] != ELFMAG2
- || f->header.e_ident[EI_MAG3] != ELFMAG3)
- {
- error("%s is not an ELF file", filename);
- return NULL;
- }
- //檢查architecture
- if (f->header.e_ident[EI_CLASS] != ELFCLASSM//i386的機器上為ELFCLASS32,表示32bit
- || f->header.e_ident[EI_DATA] != ELFDATAM//此處值為ELFDATA2LSB,表示編碼方式
- || f->header.e_ident[EI_VERSION] != EV_CURRENT//此值固定,表示版本
- || !MATCH_MACHINE(f->header.e_machine))//機器類型
- {
- error("ELF file %s not for this architecture", filename);
- return NULL;
- }
- //判斷目標文件類型
- if (f->header.e_type != e_type && e_type != ET_NONE)//.ko文件類型必為ET_REL
- {
- switch (e_type) {
- case ET_REL:
- error("ELF file %s not a relocatable object", filename);
- break;
- case ET_EXEC:
- error("ELF file %s not an executable object", filename);
- break;
- default:
- error("ELF file %s has wrong type, expecting %d got %d",
- filename, e_type, f->header.e_type);
- break;
- }
- return NULL;
- }
- //檢查elf文件頭指定的節區頭的大小是否一致
- if (f->header.e_shentsize != sizeof(ElfW(Shdr)))
- {
- error("section header size mismatch %s: %lu != %lu",filename,
- (unsigned long)f->header.e_shentsize,
- (unsigned long)sizeof(ElfW(Shdr)));
- return NULL;
- }
- shnum = f->header.e_shnum;//文件中節區個數
- f->sections = xmalloc(sizeof(struct obj_section *) * shnum);//為section開辟空間
- memset(f->sections, 0, sizeof(struct obj_section *) * shnum);
- //每個節區都有一個節區頭部表,為shnum個節區頭部表分配空間
- section_headers = alloca(sizeof(ElfW(Shdr)) * shnum);
- gzf_lseek(fp, f->header.e_shoff, SEEK_SET);//指針移到節區頭部表開始位置
- //讀取shnum個節區頭部表(每個節區都有一個節區頭部表)內容保存在section_headers中
- if (gzf_read(fp, section_headers, sizeof(ElfW(Shdr))*shnum) != sizeof(ElfW(Shdr))*shnum)
- {
- error("error reading ELF section headers %s: %m", filename);
- return NULL;
- }
- for (i = 0; i < shnum; ++i)//遍歷所有的節區
- {
- struct obj_section *sec;
- f->sections[i] = sec = arch_new_section();//分配內存給每個section
- memset(sec, 0, sizeof(*sec));
- sec->header = section_headers[i];//設置obj_section結構的sec的header指向本節區的節區頭部表
- sec->idx = i;//節區索引
- switch (sec->header.sh_type)//section的類型
- {
- case SHT_NULL:
- case SHT_NOTE:
- case SHT_NOBITS:/* ignore */
- break;
- case SHT_PROGBITS:
- case SHT_SYMTAB:
- case SHT_STRTAB:
- case SHT_RELM://將以上各種類型的section內容讀到sec->contents結構中。
- if (sec->header.sh_size > 0)
- {
- sec->contents = xmalloc(sec->header.sh_size);
- //指針移到節區的第一個字節與文件頭之間的偏移
- gzf_lseek(fp, sec->header.sh_offset, SEEK_SET);
- //讀取節區中內容
- if (gzf_read(fp, sec->contents, sec->header.sh_size) != sec->header.sh_size)
- {
- error("error reading ELF section data %s: %m", filename);
- return NULL;
- }
- }
- else
- sec->contents = NULL;
- break;
- //描述relocation的section
- #if SHT_RELM == SHT_REL
- case SHT_RELA:
- if (sec->header.sh_size) {
- error("RELA relocations not supported on this architecture %s", filename);
- return NULL;
- }
- break;
- #else
- case SHT_REL:
- if (sec->header.sh_size) {
- error("REL relocations not supported on this architecture %s", filename);
- return NULL;
- }
- break;
- #endif
- default:
- if (sec->header.sh_type >= SHT_LOPROC)
- {
- if (arch_load_proc_section(sec, fp) < 0)
- return NULL;
- break;
- }
- error("can't handle sections of type %ld %s",(long)sec->header.sh_type, filename);
- return NULL;
- }
- }
- //shstrndx存的是section字符串表的索引值,就是第幾個section
- //shstrtab就是那個section了。找到節區名字符串節區,把字符串節區內容地址付給shstrtab指針
- shstrtab = f->sections[f->header.e_shstrndx]->contents;
- for (i = 0; i < shnum; ++i)
- {
- struct obj_section *sec = f->sections[i];
- sec->name = shstrtab + sec->header.sh_name;//sh_name字段是節區頭部字符串表節區的索引
- }//根據strtab,取得每個section的名字
- //遍歷節區查找符號表
- for (i = 0; i < shnum; ++i)
- {
- struct obj_section *sec = f->sections[i];
- //也就是說即使modinfo和modstring有此標志位,也去掉。
- if (strcmp(sec->name, ".modinfo") == 0 ||strcmp(sec->name, ".modstring") == 0)
- sec->header.sh_flags &= ~SHF_ALLOC;//ALLOC表示此section是否占用內存,這兩個節區不占用內存
- if (sec->header.sh_flags & SHF_ALLOC)//此節區在進程執行過程中占用內存
- obj_insert_section_load_order(f, sec);//確定section load的順序,根據的是flag的類型加權得到優先級
- switch (sec->header.sh_type)
- {
- case SHT_SYMTAB://符號表節區,就是.symtab節區
- {
- unsigned long nsym, j;
- char *strtab;
- ElfW(Sym) *sym;
- //節區的大小若不等於符號表結構的大小,則出錯
- if (sec->header.sh_entsize != sizeof(ElfW(Sym)))
- {
- error("symbol size mismatch %s: %lu != %lu",filename,(unsigned long)sec->header.sh_entsize,(unsigned long)sizeof(ElfW(Sym)));
- return NULL;
- }
- //計算符號表表項個數,nsym也就是symbol個數,我的fedcore有560個符號(結構)
- nsym = sec->header.sh_size / sizeof(ElfW(Sym));
- //sh_link是符號字符串表的索引值,f->sections[sec->header.sh_link]就是.strtab節區
- strtab = f->sections[sec->header.sh_link]->contents;//符號字符串表節區的內容
- sym = (ElfW(Sym) *) sec->contents;//符號表節區的內容Elf32_sym結構
- j = f->local_symtab_size = sec->header.sh_info;//本模塊局部符號的size
- f->local_symtab = xmalloc(j *= sizeof(struct obj_symbol *));//為本模塊局部符號分配空間
- memset(f->local_symtab, 0, j);
- //遍歷要加載模塊的符號表節區內容的符號Elf32_sym結構
- for (j = 1, ++sym; j < nsym; ++j, ++sym)
- {
- const char *name;
- if (sym->st_name)//有值就是符號字符串表strtab的索引值
- name = strtab+sym->st_name;
- else//如果為零,此symbol name是一個section的name,比如.rodata之類的
- name = f->sections[sym->st_shndx]->name;
- //obj_add_symbol將符號加入到f->symbab這個hash表中,sym->st_shndx是相關節區頭部表索引。如果一個符號的取值引用了某個節區中的特定位置,那么它的節區索引成員(st_shndx)包含了其在節區頭部表中的索引。(比如說符號“function_x”定義在節區.rodata中,則sym->st_shndx是節區.rodata的索引值,sym->st_value是指在該節區中的到符號位置的偏移,即符號“function_x”在模塊中的地址由節區.rodata->contens+sym->st_value位置處的數值指定)
- //局部符號會添加到f->local_symtab表中,其余符號會添加到hash表f->symtab中(),注意:局部符號也可能添加到hash表中
- obj_add_symbol(f, name, j, sym->st_info, sym->st_shndx,sym->st_value, sym->st_size);
- }
- }
- break;
- }
- }
- //重定位是將符號引用與符號定義進行連接的過程。例如,當程序調用了一個函數時,相關的調用指令必須把控制傳輸到適當的目標執行地址。
- for (i = 0; i < shnum; ++i)
- {
- struct obj_section *sec = f->sections[i];
- switch (sec->header.sh_type)
- {
- case SHT_RELM://找到描述重定位的section
- {
- unsigned long nrel, j;
- ElfW(RelM) *rel;
- struct obj_section *symtab;
- char *strtab;
- if (sec->header.sh_entsize != sizeof(ElfW(RelM)))
- {
- error("relocation entry size mismatch %s: %lu != %lu",
- filename,(unsigned long)sec->header.sh_entsize,
- (unsigned long)sizeof(ElfW(RelM)));
- return NULL;
- }
- //算出rel有幾項,存到nrel中
- nrel = sec->header.sh_size / sizeof(ElfW(RelM));
- rel = (ElfW(RelM) *) sec->contents;
- //rel的section中sh_link相關值是符號section的索引值,即.symtab符號節區
- symtab = f->sections[sec->header.sh_link];
- //而符號section中sh_link是符號字符串section的索引值,即找到.strtab節區
- strtab = f->sections[symtab->header.sh_link]->contents;
- //存儲需要relocate的符號的rel的類型
- for (j = 0; j < nrel; ++j, ++rel)
- {
- ElfW(Sym) *extsym;
- struct obj_symbol *intsym;
- unsigned long symndx;
- symndx = ELFW(R_SYM)(rel->r_info);//取得要進行重定位的符號表索引
- if(symndx)
- {
- //取得要進行重定位的符號(Elf32_sym結構)
- extsym = ((ElfW(Sym) *) symtab->contents) + symndx;
- //符號信息是局部的,別的文件不可見
- if (ELFW(ST_BIND)(extsym->st_info) == STB_LOCAL)
- {
- intsym = f->local_symtab[symndx];//要從局部符號表中獲取
- }
- else//其他類型,從hash表中取
- {
- const char *name;
- if (extsym->st_name)//有值就是符號字符串表.strtab的索引值
- name = strtab + extsym->st_name;
- else//如果為零,此symbol name是一個section的name,比如.rodata之類的
- name = f->sections[extsym->st_shndx]->name;
- //因為前邊已添加到hash表中,從hash表中獲取該要重定位的符號
- intsym = obj_find_symbol(f, name);
- }
- intsym->r_type = ELFW(R_TYPE)(rel->r_info);//設置該符號的重定位類型,為以后進行符號重定位時使用
- }
- }
- }
- break;
- }
- }
- f->filename = xstrdup(filename);
- return f;
- }
- struct obj_symbol *obj_add_symbol (struct obj_file *f, const char *name, unsigned long symidx,int info, int secidx, ElfW(Addr) value, unsigned long size)
- {
- //參數:name符號名,symidx符號索引值(在此模塊中),secidx節區索引值,value符號值
- struct obj_symbol *sym;
- unsigned long hash = f->symbol_hash(name) % HASH_BUCKETS;//計算出hash值
- int n_type = ELFW(ST_TYPE)(info);//要添加的符號類型
- int n_binding = ELFW(ST_BIND)(info);//要添加的符號綁定類型,例如:STB_LOCAL或STB_GLOBAL
- //遍歷hash表中是否已添加了此符號,根據符號類型做不同處理
- for (sym = f->symtab[hash]; sym; sym = sym->next)
- if (f->symbol_cmp(sym->name, name) == 0)
- {
- int o_secidx = sym->secidx;
- int o_info = sym->info;
- int o_type = ELFW(ST_TYPE)(o_info);
- int o_binding = ELFW(ST_BIND)(o_info);
- if (secidx == SHN_UNDEF)//如果要加入的符號的屬性是SHN_UNDEF,即未定義,不用處理
- return sym;
- else if (o_secidx == SHN_UNDEF)//如果已加入的符號屬性是SHN_UNDEF,則用新的符號替換。
- goto found;
- else if (n_binding == STB_GLOBAL && o_binding == STB_LOCAL)//新添加的符號是global,而old符號是局部符號,那么就將STB_GLOBAL符號替換掉STB_LOCAL 符號。
- {
- struct obj_symbol *nsym, **p;
- nsym = arch_new_symbol();
- nsym->next = sym->next;
- nsym->ksymidx = -1;
- //從鏈表中刪除舊的符號
- for (p = &f->symtab[hash]; *p != sym; p = &(*p)->next)
- continue;
- *p = sym = nsym;//新的符號替換舊的符號
- goto found;
- }
- else if (n_binding == STB_LOCAL)//新添加的符號是局部符號,則加入到local_symtab表中,不加入 symtab
- {
- sym = arch_new_symbol();
- sym->next = NULL;
- sym->ksymidx = -1;
- f->local_symtab[symidx] = sym;
- goto found;
- }
- else if (n_binding == STB_WEAK)//新加入的符號是weak屬性,則不需處理
- return sym;
- else if (o_binding == STB_WEAK)//如果已加入的符號屬性是STB_WEAK,則用新的符號替換。
- goto found;
- else if (secidx == SHN_COMMON&& (o_type == STT_NOTYPE || o_type == STT_OBJECT))
- return sym;
- else if (o_secidx == SHN_COMMON&& (n_type == STT_NOTYPE || n_type == STT_OBJECT))
- goto found;
- else
- {
- if (secidx <= SHN_HIRESERVE)
- error("%s multiply defined", name);
- return sym;
- }
- }
- //該符號沒有在hash符號表中添加過,所以有可能一個局部符號添加到了hash表中
- sym = arch_new_symbol();//分配一個新的符號結構體
- sym->next = f->symtab[hash];//鏈入hash數組中f->symtab[hash]
- f->symtab[hash] = sym;
- sym->ksymidx = -1;
- //若是局部符號(別的文件不可見),則將該符號添加到f->local_symtab[]數組中
- if (ELFW(ST_BIND)(info) == STB_LOCAL && symidx != -1) {
- if (symidx >= f->local_symtab_size)
- error("local symbol %s with index %ld exceeds local_symtab_size %ld",
- name, (long) symidx, (long) f->local_symtab_size);
- else
- f->local_symtab[symidx] = sym;
- }
- found:
- sym->name = name;//符號名
- sym->value = value;//符號值
- sym->size = size;//符號大小
- sym->secidx = secidx;//節區索引值
- sym->info = info;//符號類型和綁定信息
- sym->r_type = 0;//重定位類型初始為0
- return sym;
- }
- void obj_set_symbol_compare (struct obj_file *f,int (*cmp)(const char *, const char *),
- unsigned long (*hash)(const char *))
- {
- if (cmp)
- f->symbol_cmp = cmp;//符號比較函數
- if (hash)
- {
- struct obj_symbol *tmptab[HASH_BUCKETS], *sym, *next;
- int i;
- f->symbol_hash = hash;//hash函數
- memcpy(tmptab, f->symtab, sizeof(tmptab));//先將符號信息保存在臨時數組tmptab
- memset(f->symtab, 0, sizeof(f->symtab));//清空hash表
- //重新使用hash函數將符號添加到符號表f->symtab中
- for (i = 0; i < HASH_BUCKETS; ++i)
- for (sym = tmptab[i]; sym ; sym = next)
- {
- unsigned long h = hash(sym->name) % HASH_BUCKETS;
- next = sym->next;
- sym->next = f->symtab[h];
- f->symtab[h] = sym;
- }
- }
- }
- static const char *gpl_licenses[] = {
- "GPL",
- "GPL v2",
- "GPL and additional rights",
- "Dual BSD/GPL",
- "Dual MPL/GPL",
- };
- int obj_gpl_license(struct obj_file *f, const char **license)
- {
- struct obj_section *sec;
- //找到.modinfo節區
- if ((sec = obj_find_section(f, ".modinfo"))) {
- const char *value, *ptr, *endptr;
- ptr = sec->contents;//指向該節區內容其實地址
- endptr = ptr + sec->header.sh_size;//節區內容結束地址
- while (ptr < endptr) {
- //找到以”license=“起始的字符串
- if ((value = strchr(ptr, '=')) && strncmp(ptr, "license", value-ptr) == 0) {
- int i;
- if (license)
- *license = value+1;
- for (i = 0; i < sizeof(gpl_licenses)/sizeof(gpl_licenses[0]); ++i) {
- if (strcmp(value+1, gpl_licenses[i]) == 0)//比較是否與以上數組相同的license
- return(0);
- }
- return(2);
- }
- //否則從下一個字符串開始再查找
- if (strchr(ptr, '\0'))
- ptr = strchr(ptr, '\0') + 1;
- else
- ptr = endptr;
- }
- }
- return(1);
- }
- static void add_kernel_symbols(struct obj_file *f)
- {
- struct module_stat *m;
- size_t i, nused = 0;
- //注意:此處雖然將模塊的全局符號用內核和其他模塊的符號替換過了,而且符號結構obj_symbol的secidx字段被重新寫成了SHN_HIRESERVE以上的數值。對於一些全局的未定義符號(原來的secidx為SHN_UNDEF),現在這些未定義符號的secidx字段也被重新寫成了SHN_HIRESERVE以上的數值。但是這些未定義符號對於elf文件格式的符號結構Elf32_sym中的字段st_shndx的取值並未改變,仍是SHN_UNDEF。這樣做的原因是:在模塊的編譯過程中會生成一個__versions節區,該節區中存放的都是該模塊中使用到,但沒被定義的符號,也就是所謂的 unresolved symbol,它們或在基本內核中定義,或在其他模塊中定義,內核使用它們來做 Module versioning。注意其中的 module_layout 符號,這是一個 dummy symbol。內核使用它來跟蹤不同內核版本關於模塊處理的相關數據結構的變化。所以該節區的未定義的符號雖然在這里被內核符號或其他模塊符號已替換,但是它本身的SHN_UNDEF性質沒有改變,用來對之后模塊加載時的crc校驗。因為crc校驗時就是檢查這些SHN_UNDEF性質的符號的crc值。參見內核函數simplify_symbols()。
- //而且__versions節區的未定義的符號必須是內核或內核其他模塊用到的符號,這樣在此系統下編譯的模塊安裝到另一個系統上時,根據這些全局符號的crc值就能知道此模塊是不是在此系統上編譯的。還有這些符號雖然被設置為未定義的,但是通過符號替換,仍能得到符號的絕對地址,從而被模塊引用。
- /* 使用系統中已有的module中的symbol,更新symbol的值,重新寫入hash表或者局部符號表。注意:要加載的模塊還沒有加入到module_stat數組中 */
- for (i = 0, m = module_stat; i < n_module_stat; ++i, ++m)
- //遍歷每個模塊的符號表,符號對應的節區是SHN_LORESERVE以上的節區號。節區序號大於SHN_LORESERVE的符號,是沒有對應的節區的。因此,符號里的值就認為是絕對地址。內核和已加載模塊導出符號就處在這個區段。
- if (m->nsyms && add_symbols_from(f, SHN_HIRESERVE + 2 + i, m->syms, m->nsyms))
- {
- m->status = 1;//表示此模塊被引用了
- ++nused;
- }
- n_ext_modules_used = nused;//該模塊依賴的內核其余模塊的個數
- //使用kernel導出的symbol,更新symbol的值,重新寫入hash表或者局部符號表.SHN_HIRESERVE對應系統保留節區的上限,使用SHN_HIRESERVE以上的節區來保存已加載模塊的符號和內核符號。
- if (nksyms)
- add_symbols_from(f, SHN_HIRESERVE + 1, ksyms, nksyms);
- }
- static int add_symbols_from(struct obj_file *f, int idx,struct module_symbol *syms, size_t nsyms)
- {
- struct module_symbol *s;
- size_t i;
- int used = 0;
- //遍歷該模塊的所有符號
- for (i = 0, s = syms; i < nsyms; ++i, ++s) {
- struct obj_symbol *sym;
- //從hash表中是否有需要此名字的的symbol,局部符號表中的符號不需要內核符號替換
- sym = obj_find_symbol(f, (char *) s->name);
- //從要加載模塊的hash表中找到該符號(必須為非局部符號),表示要加載模塊需要改符號
- if (sym && !ELFW(ST_BIND) (sym->info) == STB_LOCAL) {
- /*將hash表中的待解析的symbol的value添成正確的值s->value*/
- sym = obj_add_symbol(f, (char *) s->name, -1,ELFW(ST_INFO) (STB_GLOBAL, STT_NOTYPE),idx, s->value, 0);
- if (sym->secidx == idx)
- used = 1;//表示發生了符號替換
- }
- }
- return used;
- }
- static int create_this_module(struct obj_file *f, const char *m_name)
- {
- struct obj_section *sec;
- //創建一個.this節區,顯然准備在這個節區里存放module結構。注意:這個節區是load時的首個節區,即起始節區
- sec = obj_create_alloced_section_first(f, ".this", tgt_sizeof_long,sizeof(struct module));
- memset(sec->contents, 0, sizeof(struct module));
- //添加一個"__this_module"符號,所在節區是.this,屬性是 STB_LOCAL,類型是 STT_OBJECT,symidx 為-1,所以這個符號不加入 local_symtab 中(因為這個符號不是文件原有的)
- obj_add_symbol(f, "__this_module", -1, ELFW(ST_INFO) (STB_LOCAL, STT_OBJECT),sec->idx, 0, sizeof(struct module));
- /*為了能在obj_file里引用模塊名(回憶一下,每個字符串要么與節區名對應,要么對應於一個符號,而在這里,模塊名沒有對應的符號或節區),因此obj_file通過obj_string_patch_struct結構收留這些孤獨的字符串*/
- //創建.kstrtab節區,若存在該節區則擴展該節區,給該節區內容賦值為m_name,這里即”fedcore.ko“
- obj_string_patch(f, sec->idx, offsetof(struct module, name), m_name);
- return 1;
- }
- struct obj_section *obj_create_alloced_section_first (struct obj_file *f, const char *name,
- unsigned long align, unsigned long size)
- {
- int newidx = f->header.e_shnum++;//elf文件頭中的節區頭數量加1
- struct obj_section *sec;
- //為節區頭分配空間
- f->sections = xrealloc(f->sections, (newidx+1) * sizeof(sec));
- f->sections[newidx] = sec = arch_new_section();
- memset(sec, 0, sizeof(*sec));//.this節區的偏移地址是0,sec->header.sh_addr=0
- sec->header.sh_type = SHT_PROGBITS;
- sec->header.sh_flags = SHF_WRITE|SHF_ALLOC;
- sec->header.sh_size = size;
- sec->header.sh_addralign = align;
- sec->name = name;
- sec->idx = newidx;
- if (size)
- sec->contents = xmalloc(size);//節區內容分配空間
- sec->load_next = f->load_order;
- f->load_order = sec;
- if (f->load_order_search_start == &f->load_order)
- f->load_order_search_start = &sec->load_next;
- return sec;
- }
- int obj_string_patch(struct obj_file *f, int secidx, ElfW(Addr) offset,const char *string)
- {
- struct obj_string_patch_struct *p;
- struct obj_section *strsec;
- size_t len = strlen(string)+1;
- char *loc;
- p = xmalloc(sizeof(*p));
- p->next = f->string_patches;
- p->reloc_secidx = secidx;
- p->reloc_offset = offset;
- f->string_patches = p;//patch 字符串
- //查找.kstrtab節區
- strsec = obj_find_section(f, ".kstrtab");
- if (strsec == NULL)
- {
- //該節區不存在則創建該節區
- strsec = obj_create_alloced_section(f, ".kstrtab", 1, len, 0);
- p->string_offset = 0;
- loc = strsec->contents;
- }
- else
- {
- p->string_offset = strsec->header.sh_size;//字符串偏移地址
- loc = obj_extend_section(strsec, len);//擴展該節區內容的地址大小
- }
- memcpy(loc, string, len);//賦值給節區內容
- return 1;
- }
- int obj_check_undefineds(struct obj_file *f, int quiet)
- {
- unsigned long i;
- int ret = 1;
- //遍歷模塊的hash表的所有符號,檢查是否還有未定義的符號
- for (i = 0; i < HASH_BUCKETS; ++i)
- {
- struct obj_symbol *sym;
- //一般來說此處不會有未定義的符號,因為前邊經過了一次內核符號和其他模塊符號的替換操作add_kernel_symbols,但是在模塊的編譯
- for (sym = f->symtab[i]; sym ; sym = sym->next)
- if (sym->secidx == SHN_UNDEF)//如果有未定義的符號
- {
- //對於屬性為weak的符號,如果未能解析,鏈接器只是將它置0完事
- if (ELFW(ST_BIND)(sym->info) == STB_WEAK)
- {
- sym->secidx = SHN_ABS;//符號具有絕對取值,不會因為重定位而發生變化。
- sym->value = 0;
- }
- else if (sym->r_type) /* assumes R_arch_NONE is 0 on all arch */
- {//如果不是weak屬性,而且受重定位影響那就出錯了
- if (!quiet)
- error("%s: unresolved symbol %s",f->filename, sym->name);
- ret = 0;
- }
- }
- }
- return ret;
- }
- void obj_allocate_commons(struct obj_file *f)
- {
- struct common_entry
- {
- struct common_entry *next;
- struct obj_symbol *sym;
- } *common_head = NULL;
- unsigned long i;
- for (i = 0; i < HASH_BUCKETS; ++i)
- {
- struct obj_symbol *sym;
- //遍歷該模塊的hash符號表
- for (sym = f->symtab[i]; sym ; sym = sym->next)
- //若設置了該標志SHN_COMMON,則表示符號標注了一個尚未分配的公共塊,例如未分配的C外部變量。就是說,鏈接編輯器將為符號分配存儲空間,地址位於 st_value 的倍數處。符號的大小給出了所需要的字節數。
- //找出所有SHN_COMMON的符號,並按符號大小排序,鏈入到common_head鏈表中
- if (sym->secidx == SHN_COMMON)
- {
- {
- struct common_entry **p, *n;
- for (p = &common_head; *p ; p = &(*p)->next)
- if (sym->size <= (*p)->sym->size)
- break;
- n = alloca(sizeof(*n));
- n->next = *p;
- n->sym = sym;
- *p = n;
- }
- }
- }
- //遍歷該模塊的局部符號表local_symtab,找出所有SHN_COMMON的符號,並按符號大小排序,鏈入到common_head鏈表中
- for (i = 1; i < f->local_symtab_size; ++i)
- {
- struct obj_symbol *sym = f->local_symtab[i];
- if (sym && sym->secidx == SHN_COMMON)
- {
- struct common_entry **p, *n;
- for (p = &common_head; *p ; p = &(*p)->next)
- if (sym == (*p)->sym)
- break;
- else if (sym->size < (*p)->sym->size)
- {
- n = alloca(sizeof(*n));
- n->next = *p;
- n->sym = sym;
- *p = n;
- break;
- }
- }
- }
- if (common_head)
- {
- for (i = 0; i < f->header.e_shnum; ++i)
- //SHT_NOBITS表明這個節區不占據文件空間,這正是.bss節區的類型,這里就是為了查找.bss節區
- if (f->sections[i]->header.sh_type == SHT_NOBITS)
- break;
- //如果沒有找到.bss節區,則就創建一個.bss節區
- //.bss 含義:包含將出現在程序的內存映像中的為初始化數據。根據定義,當程序開始執行,系統將把這些數據初始化為 0。此節區不占用文件空間。
- if (i == f->header.e_shnum)
- {
- struct obj_section *sec;
- f->sections = xrealloc(f->sections, (i+1) * sizeof(sec));
- f->sections[i] = sec = arch_new_section();//分配節區結構obj_section
- f->header.e_shnum = i+1;//節區數量加1
- memset(sec, 0, sizeof(*sec));
- sec->header.sh_type = SHT_PROGBITS;//節區類型
- sec->header.sh_flags = SHF_WRITE|SHF_ALLOC;
- sec->name = ".bss";//節區名稱
- sec->idx = i;//節區索引值
- }
- {
- ElfW(Addr) bss_size = f->sections[i]->header.sh_size;//節區大小
- ElfW(Addr) max_align = f->sections[i]->header.sh_addralign;//對齊邊界
- struct common_entry *c;
- //根據SHN_COMMON的符號重新計算該.bss節區大小和對齊邊界
- for (c = common_head; c ; c = c->next)
- {
- ElfW(Addr) align = c->sym->value;
- if (align > max_align)
- max_align = align;
- if (bss_size & (align - 1))
- bss_size = (bss_size | (align - 1)) + 1;
- c->sym->secidx = i;//該符號的節區索引值也改變了,即是.bss節區
- c->sym->value = bss_size;//符號值也變了,變成.bss節區符號偏移量
- bss_size += c->sym->size;
- }
- f->sections[i]->header.sh_size = bss_size;
- f->sections[i]->header.sh_addralign = max_align;
- }
- }
- //為SHT_NOBITS節區分配資源,並將它設為SHT_PROGBITS。從這里可以看到,文件定義的靜態、全局變量,還有引用的外部變量,最終放在了.bss節區中
- for (i = 0; i < f->header.e_shnum; ++i)
- {
- struct obj_section *s = f->sections[i];
- if (s->header.sh_type == SHT_NOBITS)//找到.bss節區
- {
- if (s->header.sh_size)
- //節區內容都初始化為0,即.bss節區中符號對應的文件定義的靜態、全局變量,還有引用的外部變量都初始化為0
- s->contents = memset(xmalloc(s->header.sh_size),0, s->header.sh_size);
- else
- s->contents = NULL;
- s->header.sh_type = SHT_PROGBITS;
- }
- }
- }
- static void check_module_parameters(struct obj_file *f, int *persist_flag)
- {
- struct obj_section *sec;
- char *ptr, *value, *n, *endptr;
- int namelen, err = 0;
- //查找 ".modinfo"節區
- sec = obj_find_section(f, ".modinfo");
- if (sec == NULL) {
- return;
- }
- ptr = sec->contents;//節區內容起始地址
- endptr = ptr + sec->header.sh_size;//節區內容結束地址
- while (ptr < endptr && !err) {
- value = strchr(ptr, '=');//定位到該字符串的”=“位置出
- n = strchr(ptr, '\0');
- if (value) {
- namelen = value - ptr;//找到該字符串的名稱
- //查找相對應的"parm_"或"parm_desc_"開頭的字符串
- if (namelen >= 5 && strncmp(ptr, "parm_", 5) == 0
- && !(namelen > 10 && strncmp(ptr, "parm_desc_", 10) == 0)) {
- char *pname = xmalloc(namelen + 1);
- strncpy(pname, ptr + 5, namelen - 5);//取得該字符串名
- pname[namelen - 5] = '\0';
- //檢查該參數字符串的內容
- err = check_module_parameter(f, pname, value+1, persist_flag);
- free(pname);
- }
- } else {
- if (n - ptr >= 5 && strncmp(ptr, "parm_", 5) == 0) {
- error("parameter %s found with no value", ptr);
- err = 1;
- }
- }
- ptr = n + 1;//下一個字符串
- }
- if (err)
- *persist_flag = 0;
- return;
- }
- static int check_module_parameter(struct obj_file *f, char *key, char *value, int *persist_flag)
- {
- struct obj_symbol *sym;
- int min, max;
- char *p = value;
- //確定該符號是否存在
- sym = obj_find_symbol(f, key);
- if (sym == NULL) {
- lprintf("Warning: %s symbol for parameter %s not found", error_file, key);
- ++warnings;
- return(1);
- }
- //解析參數值個數的聲明,如果沒有參數值個數的取值聲明就默認為1
- if (isdigit(*p)) {
- min = strtoul(p, &p, 10);
- if (*p == '-')
- max = strtoul(p + 1, &p, 10);
- else
- max = min;
- } else
- min = max = 1;
- if (max < min) {
- lprintf("Warning: %s parameter %s has max < min!", error_file, key);
- ++warnings;
- return(1);
- }
- //處理變量類型
- switch (*p) {
- case 'c':
- if (!isdigit(p[1])) {
- lprintf("%s parameter %s has no size after 'c'!", error_file, key);
- ++warnings;
- return(1);
- }
- while (isdigit(p[1]))
- ++p; /* swallow c array size */
- break;
- case 'b': /* drop through */
- case 'h': /* drop through */
- case 'i': /* drop through */
- case 'l': /* drop through */
- case 's':
- break;
- case '\0':
- lprintf("%s parameter %s has no format character!", error_file, key);
- ++warnings;
- return(1);
- default:
- lprintf("%s parameter %s has unknown format character '%c'", error_file, key, *p);
- ++warnings;
- return(1);
- }
- switch (*++p) {
- case 'p':
- if (*(p-1) == 's') {
- error("parameter %s is invalid persistent string", key);
- return(1);
- }
- *persist_flag = 1;
- break;
- case '\0':
- break;
- default:
- lprintf("%s parameter %s has unknown format modifier '%c'", error_file, key, *p);
- ++warnings;
- return(1);
- }
- return(0);
- }
- static int process_module_arguments(struct obj_file *f, int argc, char **argv, int required)
- {
- //遍歷命令行參數
- for (; argc > 0; ++argv, --argc) {
- struct obj_symbol *sym;
- int c;
- int min, max;
- int n;
- char *contents;
- char *input;
- char *fmt;
- char *key;
- char *loc;
- //因為使用命令行參數時,一定是param=value這樣的形式
- if ((input = strchr(*argv, '=')) == NULL)
- continue;
- n = input - *argv;//參數長度
- input += 1; /* skip '=' */
- key = alloca(n + 6);
- if (m_has_modinfo) {
- //將該參數組合成參數符號格式”parm_xxx“
- memcpy(key, "parm_", 5);
- memcpy(key + 5, *argv, n);
- key[n + 5] = '\0';
- //從節區.modinfo中查找該模塊參數
- if ((fmt = get_modinfo_value(f, key)) == NULL) {
- if (required || flag_verbose) {
- lprintf("Warning: ignoring %s, no such parameter in this module", *argv);
- ++warnings;
- continue;
- }
- }
- key += 5;
- //解析參數值個數的聲明,如果沒有參數值個數的取值聲明就默認為1
- if (isdigit(*fmt)) {
- min = strtoul(fmt, &fmt, 10);
- if (*fmt == '-')
- max = strtoul(fmt + 1, &fmt, 10);
- else
- max = min;
- } else
- min = max = 1;
- } else { /* not m_has_modinfo */
- memcpy(key, *argv, n);
- key[n] = '\0';
- if (isdigit(*input))
- fmt = "i";
- else
- fmt = "s";
- min = max = 0;
- }
- //到hash符號表中查找該參數符號
- sym = obj_find_symbol(f, key);
- if (sym == NULL || sym->secidx > SHN_HIRESERVE) {
- error("symbol for parameter %s not found", key);
- return 0;
- }
- //找到符號,讀取該符號所在節區的內容
- contents = f->sections[sym->secidx]->contents;
- loc = contents + sym->value;//存儲該參數符號的值地址付給loc指針,下邊就會給參數符號重新賦值
- n = 1;
- while (*input) {
- char *str;
- switch (*fmt) {
- case 's':
- case 'c':
- if (*input == '"') {
- char *r;
- str = alloca(strlen(input));
- for (r = str, input++; *input != '"'; ++input, ++r) {
- if (*input == '\0') {
- error("improperly terminated string argument for %s", key);
- return 0;
- }
- /* else */
- if (*input != '\\') {
- *r = *input;
- continue;
- }
- /* else handle \ */
- switch (*++input) {
- case 'a': *r = '\a'; break;
- case 'b': *r = '\b'; break;
- case 'e': *r = '\033'; break;
- case 'f': *r = '\f'; break;
- case 'n': *r = '\n'; break;
- case 'r': *r = '\r'; break;
- case 't': *r = '\t'; break;
- case '0':
- case '1':
- case '2':
- case '3':
- case '4':
- case '5':
- case '6':
- case '7':
- c = *input - '0';
- if ('0' <= input[1] && input[1] <= '7') {
- c = (c * 8) + *++input - '0';
- if ('0' <= input[1] && input[1] <= '7')
- c = (c * 8) + *++input - '0';
- }
- *r = c;
- break;
- default: *r = *input; break;
- }
- }
- *r = '\0';
- ++input;
- } else {
- /*
- * The string is not quoted.
- * We will break it using the comma
- * (like for ints).
- * If the user wants to include commas
- * in a string, he just has to quote it
- */
- char *r;
- /* Search the next comma */
- if ((r = strchr(input, ',')) != NULL) {
- /*
- * Found a comma
- * Recopy the current field
- */
- str = alloca(r - input + 1);
- memcpy(str, input, r - input);
- str[r - input] = '\0';
- /* Keep next fields */
- input = r;
- } else {
- /* last string */
- str = input;
- input = "";
- }
- }
- if (*fmt == 's') {
- /* Normal string */
- obj_string_patch(f, sym->secidx, loc - contents, str);
- loc += tgt_sizeof_char_p;
- } else {
- /* Array of chars (in fact, matrix !) */
- long charssize; /* size of each member */
- /* Get the size of each member */
- /* Probably we should do that outside the loop ? */
- if (!isdigit(*(fmt + 1))) {
- error("parameter type 'c' for %s must be followed by the maximum size", key);
- return 0;
- }
- charssize = strtoul(fmt + 1, (char **) NULL, 10);
- /* Check length */
- if (strlen(str) >= charssize-1) {
- error("string too long for %s (max %ld)",key, charssize - 1);
- return 0;
- }
- /* Copy to location */
- strcpy((char *) loc, str); /* safe, see check above */
- loc += charssize;
- }
- /*
- * End of 's' and 'c'
- */
- break;
- case 'b':
- *loc++ = strtoul(input, &input, 0);
- break;
- case 'h':
- *(short *) loc = strtoul(input, &input, 0);
- loc += tgt_sizeof_short;
- break;
- case 'i':
- *(int *) loc = strtoul(input, &input, 0);
- loc += tgt_sizeof_int;
- break;
- case 'l':
- *(tgt_long *) loc = tgt_strtoul(input, &input, 0);
- loc += tgt_sizeof_long;
- break;
- default:
- error("unknown parameter type '%c' for %s",*fmt, key);
- return 0;
- }
- /*
- * end of switch (*fmt)
- */
- while (*input && isspace(*input))
- ++input;
- if (*input == '\0')
- break; /* while (*input) */
- /* else */
- if (*input == ',') {
- if (max && (++n > max)) {
- error("too many values for %s (max %d)", key, max);
- return 0;
- }
- ++input;
- /* continue with while (*input) */
- } else {
- error("invalid argument syntax for %s: '%c'",key, *input);
- return 0;
- }
- } /* end of while (*input) */
- if (min && (n < min)) {
- error("too few values for %s (min %d)", key, min);
- return 0;
- }
- } /* end of for (;argc > 0;) */
- return 1;
- }
- static void hide_special_symbols(struct obj_file *f)
- {
- struct obj_symbol *sym;
- const char *const *p;
- static const char *const specials[] =
- {
- "cleanup_module",
- "init_module",
- "kernel_version",
- NULL
- };
- //查找數組中的幾個符號,找到后類型改為STB_LOCAL(局部)
- for (p = specials; *p; ++p)
- if ((sym = obj_find_symbol(f, *p)) != NULL)
- sym->info = ELFW(ST_INFO) (STB_LOCAL, ELFW(ST_TYPE) (sym->info));
- }
- static void add_ksymoops_symbols(struct obj_file *f, const char *filename,const char *m_name)
- {
- struct obj_section *sec;
- struct obj_symbol *sym;
- char *name, *absolute_filename;
- char str[STRVERSIONLEN], real[PATH_MAX];
- int i, l, lm_name, lfilename, use_ksymtab, version;
- struct stat statbuf;
- static const char *section_names[] = {
- ".text",
- ".rodata",
- ".data",
- ".bss"
- ".sbss"
- };
- //找到模塊所在的完整路徑名
- if (realpath(filename, real)) {
- absolute_filename = xstrdup(real);
- }
- else {
- int save_errno = errno;
- error("cannot get realpath for %s", filename);
- errno = save_errno;
- perror("");
- absolute_filename = xstrdup(filename);
- }
- lm_name = strlen(m_name);
- lfilename = strlen(absolute_filename);
- //查找"__ksymtab"節區是否存在或者模塊符號是否需要導出
- use_ksymtab = obj_find_section(f, "__ksymtab") || !flag_export;
- //找到.this節區,記錄經過修飾的模塊名和經過修飾的長度不為0的節區。在這里修飾的目的,應該是為了唯一確定所使用的模塊。
- if ((sec = obj_find_section(f, ".this"))) {
- l = sizeof(symprefix)+ /* "__insmod_" */
- lm_name+ /* module name */
- 2+ /* "_O" */
- lfilename+ /* object filename */
- 2+ /* "_M" */
- 2*sizeof(statbuf.st_mtime)+ /* mtime in hex */
- 2+ /* "_V" */
- 8+ /* version in dec */
- 1; /* nul */
- name = xmalloc(l);
- if (stat(absolute_filename, &statbuf) != 0)
- statbuf.st_mtime = 0;
- version = get_module_version(f, str); /* -1 if not found */
- snprintf(name, l, "%s%s_O%s_M%0*lX_V%d",symprefix, m_name, absolute_filename,
- (int)(2*sizeof(statbuf.st_mtime)), statbuf.st_mtime,version);
- //添加一個符號,該符號所在節區是.this節區,符號value給出該符號的地址在節區sec->header.sh_addr(值為0)的偏移地址處
- sym = obj_add_symbol(f, name, -1,ELFW(ST_INFO) (STB_GLOBAL, STT_NOTYPE),sec->idx, sec->header.sh_addr, 0);
- if (use_ksymtab)
- add_ksymtab(f, sym);//該符號添加到__ksymtab節區中
- }
- free(absolute_filename);
- //參數若是通過文件傳入的,也要修飾記錄這個文件名
- if (f->persist) {
- l = sizeof(symprefix)+ /* "__insmod_" */
- lm_name+ /* module name */
- 2+ /* "_P" */
- strlen(f->persist)+ /* data store */
- 1; /* nul */
- name = xmalloc(l);
- snprintf(name, l, "%s%s_P%s",symprefix, m_name, f->persist);
- sym = obj_add_symbol(f, name, -1, ELFW(ST_INFO) (STB_GLOBAL, STT_NOTYPE),sec->idx, sec->header.sh_addr, 0);
- if (use_ksymtab)
- add_ksymtab(f, sym);//該符號添加到__ksymtab節區中,要導出的符號加入該節區"__ksymtab"
- }
- /* tag the desired sections if size is non-zero */
- for (i = 0; i < sizeof(section_names)/sizeof(section_names[0]); ++i) {
- if ((sec = obj_find_section(f, section_names[i])) &&sec->header.sh_size) {
- l = sizeof(symprefix)+ /* "__insmod_" */
- lm_name+ /* module name */
- 2+ /* "_S" */
- strlen(sec->name)+ /* section name */
- 2+ /* "_L" */
- 8+ /* length in dec */
- 1; /* nul */
- name = xmalloc(l);
- snprintf(name, l, "%s%s_S%s_L%ld",symprefix, m_name, sec->name,(long)sec->header.sh_size);
- sym = obj_add_symbol(f, name, -1, ELFW(ST_INFO) (STB_GLOBAL, STT_NOTYPE),sec->idx, sec->header.sh_addr, 0);
- if (use_ksymtab)
- add_ksymtab(f, sym);//該符號添加到__ksymtab節區中,要導出的符號加入該節區"__ksymtab"
- }
- }
- }
- static int create_module_ksymtab(struct obj_file *f)
- {
- struct obj_section *sec;
- int i;
- //n_ext_modules_used是該模塊引用的模塊的數量
- if (n_ext_modules_used) {
- struct module_ref *dep;
- struct obj_symbol *tm;
- //創建一個.kmodtab節區保存該模塊所引用的模塊信息
- sec = obj_create_alloced_section(f, ".kmodtab",tgt_sizeof_void_p,sizeof(struct module_ref) * n_ext_modules_used, 0);
- if (!sec)
- return 0;
- tm = obj_find_symbol(f, "__this_module");
- dep = (struct module_ref *) sec->contents;
- //
- for (i = 0; i < n_module_stat; ++i)
- if (module_stat[i].status) {//模塊若被引用,則status為1
- dep->dep = module_stat[i].addr;
- dep->dep |= arch_module_base (f);
- obj_symbol_patch(f, sec->idx, (char *) &dep->ref - sec->contents, tm);
- dep->next_ref = 0;
- ++dep;
- }
- }
- // 在存在__ksymtab節區或者不存在這個節區,而且其他符號都不導出的情況下,還要將這些符號添加入__symtab節區
- //如果需要導出外部(extern)符號,而__ksymtab段不存在(這種情況下,add_ksymoops_symbols里是不會創建__ksymtab段的。而實際上,模塊一般是不會不包含__ksymtab段的,因為模塊的導出符號都位於這個段里。),那么通過add_ksymtab創建__ksymtab段,並加入模塊要導出的符號。在這里可以發現,如果模塊需要導出符號,那么經過修飾的模塊名、模塊文件名,甚至參數文件名會在這里加入__ksymtab段(因為在前面的add_ksymoops_symbols函數里,這些符號被設為STB_GLOBOL了,段序號指向.this段)。
- //注意,在不存在__ksymtab段的情況下(這時模塊沒有定義任何需要導出的參數),直接導出序號在SHN_LORESERVE~SHN_HIRESERVE的符號,以及其他已分配資源段的非local符號。(根據elf規范里,位於SHN_LORESERVE~SHN_HIRESERVE區的已定義序號有SHN_LOPROC、SHN_HIPROC、SHN_ABS、SHN_COMMON。經過前面的處理,到這里SHN_COMMON對應的符號已經不存在了。這些序號的節區實際上是不存在的,存在的是持有這些序號的符號。其中SHN_ABS是不受重定位影響的符號,其他2個則是與處理器有關的。至於為什么模塊不需要導出符號時,要導出這些區的符號,我也不太清楚,可以肯定的是,這和GCC有關,誰知道它放了什么進來?!)。
- if (flag_export && !obj_find_section(f, "__ksymtab")) {
- int *loaded;
- loaded = alloca(sizeof(int) * (i = f->header.e_shnum));
- while (--i >= 0)
- loaded[i] = (f->sections[i]->header.sh_flags & SHF_ALLOC) != 0;//節區是否占用內存
- for (i = 0; i < HASH_BUCKETS; ++i) {
- struct obj_symbol *sym;
- for (sym = f->symtab[i]; sym; sym = sym->next)
- {
- if (ELFW(ST_BIND) (sym->info) != STB_LOCAL && sym->secidx <= SHN_HIRESERVE&& (sym->secidx >= SHN_LORESERVE || loaded[sym->secidx])) {
- add_ksymtab(f, sym);//要導出的符號加入該節區"__ksymtab"
- }
- }
- }
- }
- return 1;
- }
- static void add_ksymtab(struct obj_file *f, struct obj_symbol *sym)
- {
- struct obj_section *sec;
- ElfW(Addr) ofs;
- //查找是否已經存在"__ksymtab"節區
- sec = obj_find_section(f, "__ksymtab");
- //如果存在這個節區,但是沒有標示SHF_ALLOC,則直接將該節區的名字修改掉,標示刪除
- if (sec && !(sec->header.sh_flags & SHF_ALLOC)) {
- *((char *)(sec->name)) = 'x'; /* override const */
- sec = NULL;
- }
- if (!sec)
- sec = obj_create_alloced_section(f, "__ksymtab",tgt_sizeof_void_p, 0, 0);
- if (!sec)
- return;
- sec->header.sh_flags |= SHF_ALLOC;
- sec->header.sh_addralign = tgt_sizeof_void_p; /* Empty section mightbe byte-aligned */
- ofs = sec->header.sh_size;
- obj_symbol_patch(f, sec->idx, ofs, sym);//使用f->sybol_ptches保存這些符號
- obj_string_patch(f, sec->idx, ofs + tgt_sizeof_void_p, sym->name);//使用f->string_patches保存符號名
- obj_extend_section(sec, 2 * tgt_sizeof_char_p);//擴展"__ksymtab"節區
- }
- static int add_archdata(struct obj_file *f,struct obj_section **sec)
- {
- size_t i;
- *sec = NULL;
- //查找是否有ARCHDATA_SEC_NAME=“__archdata”節區,沒有則創建該節區
- for (i = 0; i < f->header.e_shnum; ++i) {
- if (strcmp(f->sections[i]->name, ARCHDATA_SEC_NAME) == 0) {
- *sec = f->sections[i];
- break;
- }
- }
- if (!*sec)
- *sec = obj_create_alloced_section(f, ARCHDATA_SEC_NAME, 16, 0, 0);
- //x86體系下是空函數
- if (arch_archdata(f, *sec))
- return(1);
- return 0;
- }
- static int add_kallsyms(struct obj_file *f,struct obj_section **module_kallsyms, int force_kallsyms)
- {
- struct module_symbol *s;
- struct obj_file *f_kallsyms;
- struct obj_section *sec_kallsyms;
- size_t i;
- int l;
- const char *p, *pt_R;
- unsigned long start = 0, stop = 0;
- //遍歷所有內核符號,內核符號都保存在全局變量ksyms中
- for (i = 0, s = ksyms; i < nksyms; ++i, ++s) {
- p = (char *)s->name;
- pt_R = strstr(p, "_R");//查找符號中是否有 "_R",計算符號長度是,去掉這兩個字符
- if (pt_R)
- l = pt_R - p;
- else
- l = strlen(p);
- //內核導出符號位於“__start_kallsyms”和“__stop_kallsyms”符號所指向的地址之間
- if (strncmp(p, "__start_" KALLSYMS_SEC_NAME, l) == 0)
- start = s->value;
- else if (strncmp(p, "__stop_" KALLSYMS_SEC_NAME, l) == 0)
- stop = s->value;
- }
- if (start >= stop && !force_kallsyms)
- return(0);
- /* The kernel contains all symbols, do the same for this module. */
- //找到該模塊的“kallsyms”節區
- for (i = 0; i < f->header.e_shnum; ++i) {
- if (strcmp(f->sections[i]->name, KALLSYMS_SEC_NAME) == 0) {
- *module_kallsyms = f->sections[i];
- break;
- }
- }
- //如果沒有找到,則創建一個kallsyms節區
- if (!*module_kallsyms)
- *module_kallsyms = obj_create_alloced_section(f, KALLSYMS_SEC_NAME, 0, 0, 0);
- //這個函數的作用是將輸入obj_file里的符號提取出來,忽略不用於調試的符號.構建出obj_file只包含kallsyms節區。這個段可以任意定位,因為不包含重定位信息。
- //實際上,f_kallsyms這個文件就是將輸入文件里的信息整理整理,更方便使用(記住__kallsyms節區是用作輔助內核調試),整個輸出文件只是臨時的調試倉庫。
- if (obj_kallsyms(f, &f_kallsyms))//???
- return(1);
- sec_kallsyms = f_kallsyms->sections[KALLSYMS_IDX];//臨時創建obj_file里的kallsyms節區
- (*module_kallsyms)->header.sh_addralign = sec_kallsyms->header.sh_addralign;//節區對齊大小
- (*module_kallsyms)->header.sh_size = sec_kallsyms->header.sh_size;//節區大小
- free((*module_kallsyms)->contents);
- (*module_kallsyms)->contents = sec_kallsyms->contents;//內容賦值給kallsyms節區
- sec_kallsyms->contents = NULL;
- obj_free(f_kallsyms);
- return 0;
- }
- unsigned long obj_load_size (struct obj_file *f)
- {
- unsigned long dot = 0;
- struct obj_section *sec;
- //前面提到段按對其邊界大小排序,可以減少空間占用,就是體現在這里。
- for (sec = f->load_order; sec ; sec = sec->load_next)
- {
- ElfW(Addr) align;
- align = sec->header.sh_addralign;
- if (align && (dot & (align - 1)))
- dot = (dot | (align - 1)) + 1;
- sec->header.sh_addr = dot;
- dot += sec->header.sh_size;//各個節區大小累加
- }
- return dot;
- }
- asmlinkage unsigned long sys_create_module(const char *name_user, size_t size)
- {
- char *name;
- long namelen, error;
- struct module *mod;
- if (!capable(CAP_SYS_MODULE))//是否具有創建模塊的特權
- return EPERM;
- lock_kernel();
- if ((namelen = get_mod_name(name_user, &name)) < 0) {//模塊名拷貝到內核空間
- error = namelen;
- goto err0;
- }
- if (size < sizeof(struct module)+namelen) {//檢查模塊名的大小
- error = EINVAL;
- goto err1;
- }
- if (find_module(name) != NULL) {//在內存中查找是否模塊已經安裝
- error = EEXIST;
- goto err1;
- }
- //分配module空間 #define module_map(x) vmalloc(x)
- if ((mod = (struct module *)module_map(size)) == NULL) {
- error = ENOMEM;
- goto err1;
- }
- memset(mod, 0, sizeof(*mod));
- mod->size_of_struct = sizeof(*mod);
- mod->next = module_list;//掛入鏈表
- mod->name = (char *)(mod + 1);//module結構下邊用來存儲模塊名
- mod->size = size;
- memcpy((char*)(mod+1), name, namelen+1);//拷貝模塊名
- put_mod_name(name);//釋放name的空間
- module_list = mod;//掛入鏈表
- error = (long) mod;
- goto err0;
- err1:
- put_mod_name(name);
- err0:
- unlock_kernel();
- return error;
- }
- //base是模塊在內核空間的起始地址,也是.this節區的偏移地址
- int obj_relocate (struct obj_file *f, ElfW(Addr) base)
- {
- int i, n = f->header.e_shnum;
- int ret = 1;
- //節區在內存的位置是假設文件從0地址加載而得出的,現在就要根據base值調整
- arch_finalize_section_address(f, base);
- //處理重定位符號,遍歷每一個節區
- for (i = 0; i < n; ++i)
- {
- struct obj_section *relsec, *symsec, *targsec, *strsec;
- ElfW(RelM) *rel, *relend;
- ElfW(Sym) *symtab;
- const char *strtab;
- unsigned long nsyms;
- relsec = f->sections[i];
- if (relsec->header.sh_type != SHT_RELM)//如果該節區不是重定位類型,則繼續查找
- continue;
- //重定位節區會引用兩個其它節區:符號表、要修改的節區。符號表是symsec,要修改的節區是targsec節區.例如重定位節區(relsec).rel.text是對節區是.text(targsec)的進行重定位.原來重定位的目標節區(.text)的內容contents只是讀入到內存中,這塊內存是系統在堆上malloc的,內容中對應的地址不是真正的符號所在的地址。現在符號的真正的地址已經計算出,就要修改contents區的符號對應的地址,從而將符號鏈接到正確的地址。
- //重定位節區的sh_link表示相關符號表的節區頭部索引,即.symtab符號節區
- symsec = f->sections[relsec->header.sh_link];
- //重定位節區的sh_info表示重定位所適用的節區的節區頭部索引,像.text等節區
- targsec = f->sections[relsec->header.sh_info];
- //找到重定位節區相關符號表字符串的節區,即.strtab
- strsec = f->sections[symsec->header.sh_link];
- if (!(targsec->header.sh_flags & SHF_ALLOC))
- continue;
- //讀出該節區重定位信息開始地址
- rel = (ElfW(RelM) *)relsec->contents;
- //重定位信息結束地址
- relend = rel + (relsec->header.sh_size / sizeof(ElfW(RelM)));
- //找到重定位節區相關符號表的內容
- symtab = (ElfW(Sym) *)symsec->contents;
- nsyms = symsec->header.sh_size / symsec->header.sh_entsize;
- strtab = (const char *)strsec->contents;//找到重定位節區相關符號表字符串的節區的內容
- for (; rel < relend; ++rel)
- {
- ElfW(Addr) value = 0;
- struct obj_symbol *intsym = NULL;
- unsigned long symndx;
- const char *errmsg;
- //給出要重定位的符號表索引
- symndx = ELFW(R_SYM)(rel->r_info);
- if (symndx)
- {
- /* Note we've already checked for undefined symbols. */
- if (symndx >= nsyms)
- {
- error("%s: Bad symbol index: %08lx >= %08lx",f->filename, symndx, nsyms);
- continue;
- }
- //這些要重定位的符號就是重定位目標節區(例如.text節區)里的符號,然后把該符號的絕對地址在覆蓋這個節區的(例如.text節區)對應符號的地址
- obj_find_relsym(intsym, f, f, rel, symtab, strtab);//返回要找的重定位符號intsym
- value = obj_symbol_final_value(f, intsym);//計算符號的絕對地址(是內核空間的絕對地址,因為base就是內核空間的一個地址)
- }
- #if SHT_RELM == SHT_RELA
- value += rel->r_addend;
- #endif
- //獲得了絕對地址,可以進行操作了
- //注意:這里雖然重新定義了符號的絕對地址,但是節區的內容還沒有拷貝到分配模塊返回的內核空間地址處。后邊會進行這項操作。先將節區內容拷貝的用戶空間中,再從用戶空間拷貝到內核空間地址處,即base處。
- //f:objfile結構,targsec:.text節,symsec:.symtab節,rel:.rel結構,value:絕對地址
- switch (arch_apply_relocation(f,targsec,symsec,intsym,rel,value))
- {
- case obj_reloc_ok:
- break;
- case obj_reloc_overflow:
- errmsg = "Relocation overflow";
- goto bad_reloc;
- case obj_reloc_dangerous:
- errmsg = "Dangerous relocation";
- goto bad_reloc;
- case obj_reloc_unhandled:
- errmsg = "Unhandled relocation";
- goto bad_reloc;
- case obj_reloc_constant_gp:
- errmsg = "Modules compiled with -mconstant-gp cannot be loaded";
- goto bad_reloc;
- bad_reloc:
- error("%s: %s of type %ld for %s", f->filename, errmsg,
- (long)ELFW(R_TYPE)(rel->r_info), intsym->name);
- ret = 0;
- break;
- }
- }
- }
- /* Finally, take care of the patches. */
- if (f->string_patches)
- {
- struct obj_string_patch_struct *p;
- struct obj_section *strsec;
- ElfW(Addr) strsec_base;
- strsec = obj_find_section(f, ".kstrtab");
- strsec_base = strsec->header.sh_addr;
- for (p = f->string_patches; p ; p = p->next)
- {
- struct obj_section *targsec = f->sections[p->reloc_secidx];
- *(ElfW(Addr) *)(targsec->contents + p->reloc_offset)= strsec_base + p->string_offset;
- }
- }
- if (f->symbol_patches)
- {
- struct obj_symbol_patch_struct *p;
- for (p = f->symbol_patches; p; p = p->next)
- {
- struct obj_section *targsec = f->sections[p->reloc_secidx];
- *(ElfW(Addr) *)(targsec->contents + p->reloc_offset)= obj_symbol_final_value(f, p->sym);
- }
- }
- return ret;
- }
- int arch_finalize_section_address(struct obj_file *f, Elf32_Addr base)
- {
- int i, n = f->header.e_shnum;
- //每個節區的起始地址都要加上模塊在內核的起始地址
- f->baseaddr = base;//模塊在內核的起始地址
- for (i = 0; i < n; ++i)
- f->sections[i]->header.sh_addr += base;
- return 1;
- }
- #define obj_find_relsym(isym, f, find, rel, symtab, strtab) \
- { \
- unsigned long symndx = ELFW(R_SYM)((rel)->r_info); \
- ElfW(Sym) *extsym = (symtab)+symndx; \//在符號表節區的內容中根據索引值找到相關符號
- if (ELFW(ST_BIND)(extsym->st_info) == STB_LOCAL) { \//若是局部符號
- isym = (typeof(isym)) (f)->local_symtab[symndx]; \//直接在局部符號表中返回要找的重定位符號
- } \
- else { \
- const char *name; \
- if (extsym->st_name) \//有值的話,就是strtab字符串節區內容的索引值
- name = (strtab) + extsym->st_name; \//找到符號名
- else \//否則就是節區名
- name = (f)->sections[extsym->st_shndx]->name; \
- isym = (typeof(isym)) obj_find_symbol((find), name); \//在符號hash表中找到該重定位符號
- } \
- }
- ElfW(Addr) obj_symbol_final_value (struct obj_file *f, struct obj_symbol *sym)
- {
- if (sym)
- {
- //在保留區內直接返回符號值,即符號絕對地址(一般是內核符號,因為前邊將模塊內的符號用內核中或已存在的模塊中相同符號的value更寫過了,並且節區索引值設置高於SHN_HIRESERVE,所以這些符號的value保存的就是符號的實際地址)
- if (sym->secidx >= SHN_LORESERVE)
- return sym->value;
- //其余節區內的符號value要加上現在節區的偏移地址,才是符號指向的實際地址(因為節區的偏移地址已經更改了)
- return sym->value + f->sections[sym->secidx]->header.sh_addr;//符號值加上節區頭偏移地址
- }
- else
- {
- /* As a special case, a NULL sym has value zero. */
- return 0;
- }
- }
- //x86架構
- /*
- 。“重定位表”記錄的是每個需要重定位的地方(即有了重定位表,就可知道哪個段、偏移多少的地方的地址或值是需要修改的)、重定位的類型、符號的名字(即重定位的地方屬於哪個符號)。(靜態)鏈接器在鏈接目標文件的時候,會掃描每個目標文件,為每個目標文件中的段分配運行時的地址(這個地址就是進程地址空間的地址,因為每個操作系統都會位應用程序指定裝載地址、如eos和windows的0x00400000,linux的0x0804800,通過用操作系統指定的地址配置鏈接器,鏈接器根據每個段的屬性、大小和對齊屬性等,就可得到每個段運行時的地址了),這樣每個段的地址就確定下來了,從而,每個符號的地址都確定了,然后把符號表中符號的值修改為正確的地址。然后連接器就會把所有目標文件的符號表合並為一個全局的符號表(主要是為了定位的方便)。接下來,連接器就讀取每個目標文件,通過重定位表找到需要重定位的地方和這個符號的名字,然后用這個符號去檢索全局符號表,得到符號的地址,再用個這個地址填入需要修正的地方(對於相對跳轉指令是用這個地址和修正處的地址和修正處存放的值參運算計算出正確的跳轉偏移,然后填入),最后把所有經過了重定位的目標文件中相同段名和屬性的段合並,輸出到最終的可執行文件中並建立相應的數據結構,可執行文件也是有格式的,linux 常見的elf,windows和eos的pe格式。
- */
- //重定位結構(Elf32_Rel)中的字段r_info指定重定位地址屬於哪個符號,r_offset字段指定重定位的地方。然后把這個符號的絕對地址寫到重定位的地方
- enum obj_reloc arch_apply_relocation (struct obj_file *f,
- struct obj_section *targsec,
- struct obj_section *symsec,
- struct obj_symbol *sym,
- Elf32_Rel *rel,
- Elf32_Addr v)
- {
- struct i386_file *ifile = (struct i386_file *)f;
- struct i386_symbol *isym = (struct i386_symbol *)sym;
- ////找到重定位的地方,下邊在這個地方寫入符號的絕對地址
- Elf32_Addr *loc = (Elf32_Addr *)(targsec->contents + rel->r_offset);
- Elf32_Addr dot = targsec->header.sh_addr + rel->r_offset;
- Elf32_Addr got = ifile->got ? ifile->got->header.sh_addr : 0;
- enum obj_reloc ret = obj_reloc_ok;
- switch (ELF32_R_TYPE(rel->r_info))//重定位類型
- {
- case R_386_NONE:
- break;
- case R_386_32:
- *loc += v;
- break;
- case R_386_PLT32:
- case R_386_PC32:
- *loc += v - dot;
- break;
- case R_386_GLOB_DAT:
- case R_386_JMP_SLOT:
- *loc = v;
- break;
- case R_386_RELATIVE:
- *loc += f->baseaddr;
- break;
- case R_386_GOTPC:
- assert(got != 0);
- *loc += got - dot;
- break;
- case R_386_GOT32:
- assert(isym != NULL);
- if (!isym->gotent.reloc_done)
- {
- isym->gotent.reloc_done = 1;
- *(Elf32_Addr *)(ifile->got->contents + isym->gotent.offset) = v;
- }
- *loc += isym->gotent.offset;
- break;
- case R_386_GOTOFF:
- assert(got != 0);
- *loc += v - got;
- break;
- default:
- ret = obj_reloc_unhandled;
- break;
- }
- return ret;
- }
- //insmod包含在modutils包里。我們感興趣的東西是insmod.c文件里的init_module()函數。
- static int init_module(const char *m_name, struct obj_file *f,unsigned long m_size, const char *blob_name,unsigned int noload, unsigned int flag_load_map)
- {
- //傳入的參數m_name是模塊名稱,obj_file *f已經將模塊的節區信息添加到該結構中,m_size是模塊大小
- struct module *module;
- struct obj_section *sec;
- void *image;
- int ret = 0;
- tgt_long m_addr;
- //從節區數組中查找.this節區
- sec = obj_find_section(f, ".this");
- module = (struct module *) sec->contents;//取得本節區的內容,是一個module結構
- //下邊的操作就是初始化module結構,都保存在.this節區的contents中。
- //.this節區的偏移地址地址就是module結構的在內核空間的起始地址,因為.this節區是首節區,前邊該節區的偏移地址根據內核空間的模塊起始地址做了一個偏移即sec->header.sh_addr+m_addr,又因為.this節區的sh_addr初始值是0,所以加了這個偏移,就正好指向模塊在內核空間的起始地址。
- m_addr = sec->header.sh_addr;
- module->size_of_struct = sizeof(*module);//模塊結構大小
- module->size = m_size;//模塊大小
- module->flags = flag_autoclean ? NEW_MOD_AUTOCLEAN : 0;
- //查找__ksymtab節表,保存的是該模塊導出的符號
- sec = obj_find_section(f, "__ksymtab");
- if (sec && sec->header.sh_size) {
- module->syms = sec->header.sh_addr;
- module->nsyms = sec->header.sh_size / (2 * tgt_sizeof_char_p);
- }
- //查找.kmodtab節表,module的依賴對象對應於.kmodtab節區
- if (n_ext_modules_used) {
- sec = obj_find_section(f, ".kmodtab");
- module->deps = sec->header.sh_addr;
- module->ndeps = n_ext_modules_used;
- }
- //obj_find_symbol()函數遍歷符號列表查找名字為init_module的符號,然后提取這個結構體符號(struct symbol)並把它傳遞給 obj_symbol_final_value()。后者從這個結構體符號提取出init_module函數的地址。
- module->init = obj_symbol_final_value(f, obj_find_symbol(f, "init_module"));
- module->cleanup = obj_symbol_final_value(f,obj_find_symbol(f, "cleanup_module"));
- //查找__ex_table節表
- sec = obj_find_section(f, "__ex_table");
- if (sec) {
- module->ex_table_start = sec->header.sh_addr;
- module->ex_table_end = sec->header.sh_addr + sec->header.sh_size;
- }
- //查找.text.init節表
- sec = obj_find_section(f, ".text.init");
- if (sec) {
- module->runsize = sec->header.sh_addr - m_addr;
- }
- //查找.data.init節表
- sec = obj_find_section(f, ".data.init");
- if (sec) {
- if (!module->runsize || module->runsize > sec->header.sh_addr - m_addr)
- module->runsize = sec->header.sh_addr - m_addr;
- }
- sec = obj_find_section(f, ARCHDATA_SEC_NAME);
- if (sec && sec->header.sh_size) {
- module->archdata_start = sec->header.sh_addr;
- module->archdata_end = module->archdata_start + sec->header.sh_size;
- }
- //查找kallsyms節區
- sec = obj_find_section(f, KALLSYMS_SEC_NAME);
- if (sec && sec->header.sh_size) {
- module->kallsyms_start = sec->header.sh_addr;//符號開始地址
- module->kallsyms_end = module->kallsyms_start + sec->header.sh_size;//符號結束地址
- }
- if (!arch_init_module(f, module))//x86下什么也不干
- return 0;
- //在用戶空間分配module的image的內存,返回模塊起始地址
- image = xmalloc(m_size);
- //各個section的內容拷入image中,包括原文件中的section和后來構造的section
- obj_create_image(f, image);//模塊內容從f結構指定的地址中拷貝到image中,構建模塊映像
- if (flag_load_map)
- print_load_map(f);
- if (blob_name) {//是否指定了要輸出模塊到指定的文件blob_name
- int fd, l;
- fd = open(blob_name, O_WRONLY|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
- if (fd < 0) {
- error("open %s failed %m", blob_name);
- ret = -1;
- }
- else {
- if ((l = write(fd, image, m_size)) != m_size) {
- error("write %s failed %m", blob_name);
- ret = -1;
- }
- close(fd);
- }
- }
- if (ret == 0 && !noload) {
- fflush(stdout); /* Flush any debugging output */
- //sys_init_module() 這個系統調用通知內核加載相應模塊,這個函數的代碼可以在 /usr/src/linux/kernel/module.c
- //在此函數中把在用戶空間的module映像復制到前邊由create_module創建地內核空間中。
- ret = sys_init_module(m_name, (struct module *) image);
- if (ret) {
- error("init_module: %m");
- lprintf("Hint: insmod errors can be caused by incorrect module parameters, "
- "including invalid IO or IRQ parameters.You may find more information in syslog or the output from dmesg
- }
- }
- free(image);
- return ret == 0;
- }
- int obj_create_image (struct obj_file *f, char *image)
- {
- struct obj_section *sec;
- ElfW(Addr) base = f->baseaddr;
- //注意:首個load的節區是.this節區
- for (sec = f->load_order; sec ; sec = sec->load_next)
- {
- char *secimg;
- if (sec->contents == 0)
- continue;
- secimg = image + (sec->header.sh_addr - base);
- //所有的節區內容拷貝到以image地址開始的地方,當然第一個節區的內容恰好是struct module結構
- memcpy(secimg, sec->contents, sec->header.sh_size);
- }
- return 1;
- }
- int sys_init_module(const char *name, const struct module *info)
- {
- //調用系統調用init_module(),系統調用init_module()在內核中的實現是sys_init_module(),這是由內核提供的,全內核只有這一個函數。注意,這是linux V2.6以前的調用。
- return init_module(name, info);
- }
- asmlinkage long sys_init_module(const char *name_user, struct module *mod_user)
- {
- struct module mod_tmp, *mod;
- char *name, *n_name, *name_tmp = NULL;
- long namelen, n_namelen, i, error;
- unsigned long mod_user_size;
- struct module_ref *dep;
- //檢查模塊加載的權限
- if (!capable(CAP_SYS_MODULE))
- return EPERM;
- lock_kernel();
- //復制模塊名到內核空間name中
- if ((namelen = get_mod_name(name_user, &name)) < 0) {
- error = namelen;
- goto err0;
- }
- //從module_list中找到之前通過creat_module()在內核空間創建的module結構,創建模塊時已經將模塊結構鏈入module_list
- if ((mod = find_module(name)) == NULL) {
- error = ENOENT;
- goto err1;
- }
- //把用戶空間的module結構的size_of_struct復制到內核中加以檢查,將模塊結構的大小size_of_struct賦值給mod_user_size,即sizeof(struct module)
- if ((error = get_user(mod_user_size, &mod_user->size_of_struct)) != 0)
- goto err1;
- //對用戶空間和內核空間的module結構的大小進行檢查
- //如果申請的模塊頭部長度小於舊版的模塊頭長度或者大於將來可能的模塊頭長度
- if (mod_user_size < (unsigned long)&((struct module *)0L)->persist_start
- || mod_user_size > sizeof(struct module) + 16*sizeof(void*)) {
- printk(KERN_ERR "init_module: Invalid module header size.\n"
- KERN_ERR "A new version of the modutils is likely ""needed.\n");
- error = EINVAL;
- goto err1;
- }
- mod_tmp = *mod;//內核中的module結構先保存到堆棧中
- //分配保存內核空間模塊名的空間
- name_tmp = kmalloc(strlen(mod->name) + 1, GFP_KERNEL); /* Where's kstrdup()? */
- if (name_tmp == NULL) {
- error = ENOMEM;
- goto err1;
- }
- strcpy(name_tmp, mod->name);//將內核中module的name復制給name_tmp,在堆棧中先保存起來
- //將用戶空間的模塊結構到內核空間原來的module地址處(即將用戶空間的模塊映像覆蓋掉在內核空間模塊映像的所在地址,這樣前邊設置的符號地址就不需要改變了,正好就是我們在前邊按照內核空間的模塊起始地址設置的),mod_user_size是模塊結構的大小,即sizeof(struct module)
- error = copy_from_user(mod, mod_user, mod_user_size);
- if (error) {
- error = EFAULT;
- goto err2;
- }
- error = EINVAL;
- //比較內核空間和用戶空間模塊大小,若在insmod用戶空間創建的module結構大小大於內核空間的module大小,就出錯退出
- if (mod->size > mod_tmp.size) {
- printk(KERN_ERR "init_module: Size of initialized module ""exceeds size of created module.\n");
- goto err2;
- }
- if (!mod_bound(mod->name, namelen, mod)) {//用來檢查用戶指針所指的對象是否落在模塊的邊界內
- printk(KERN_ERR "init_module: mod->name out of bounds.\n");
- goto err2;
- }
- if (mod->nsyms && !mod_bound(mod->syms, mod->nsyms, mod)) {// 符號表的區域是否在模塊體中
- printk(KERN_ERR "init_module: mod->syms out of bounds.\n");
- goto err2;
- }
- if (mod->ndeps && !mod_bound(mod->deps, mod->ndeps, mod)) {//依賴關系表是否在模塊體中
- printk(KERN_ERR "init_module: mod->deps out of bounds.\n");
- goto err2;
- }
- if (mod->init && !mod_bound(mod->init, 0, mod)) {//init函數是否是模塊體中
- printk(KERN_ERR "init_module: mod->init out of bounds.\n");
- goto err2;
- }
- if (mod->cleanup && !mod_bound(mod->cleanup, 0, mod)) {//cleanup函數是否是模塊體中
- printk(KERN_ERR "init_module: mod->cleanup out of bounds.\n");
- goto err2;
- }
- //檢查模塊的異常描述表是否在模塊影像內
- if (mod->ex_table_start > mod->ex_table_end|| (mod->ex_table_start
- &&!((unsigned long)mod->ex_table_start >= ((unsigned long)mod + mod->size_of_struct)
- && ((unsigned long)mod->ex_table_end< (unsigned long)mod + mod->size)))||
- (((unsigned long)mod->ex_table_start-(unsigned long)mod->ex_table_end)% sizeof(struct exception_table_entry))) {
- printk(KERN_ERR "init_module: mod->ex_table_* invalid.\n");
- goto err2;
- }
- if (mod->flags & ~MOD_AUTOCLEAN) {
- printk(KERN_ERR "init_module: mod->flags invalid.\n");
- goto err2;
- }
- if (mod_member_present(mod, can_unload)//檢查結構的大小,看用戶模塊是否包含can_unload字段
- && mod->can_unload && !mod_bound(mod->can_unload, 0, mod)) {//若包含can_unload字段,就檢查其邊界
- printk(KERN_ERR "init_module: mod->can_unload out of bounds.\n");
- goto err2;
- }
- if (mod_member_present(mod, kallsyms_end)) {
- if (mod->kallsyms_end &&(!mod_bound(mod->kallsyms_start, 0, mod) ||!mod_bound(mod->kallsyms_end, 0, mod))) {
- printk(KERN_ERR "init_module: mod->kallsyms out of bounds.\n");
- goto err2;
- }
- if (mod->kallsyms_start > mod->kallsyms_end) {
- printk(KERN_ERR "init_module: mod->kallsyms invalid.\n");
- goto err2;
- }
- }
- if (mod_member_present(mod, archdata_end)) {
- if (mod->archdata_end &&(!mod_bound(mod->archdata_start, 0, mod) ||
- !mod_bound(mod->archdata_end, 0, mod))) {
- printk(KERN_ERR "init_module: mod->archdata out of bounds.\n");
- goto err2;
- }
- if (mod->archdata_start > mod->archdata_end) {
- printk(KERN_ERR "init_module: mod->archdata invalid.\n");
- goto err2;
- }
- }
- if (mod_member_present(mod, kernel_data) && mod->kernel_data) {
- printk(KERN_ERR "init_module: mod->kernel_data must be zero.\n");
- goto err2;
- }
- //在把用戶空間的模塊名從用戶空間拷貝進來
- if ((n_namelen = get_mod_name(mod->name-(unsigned long)mod+ (unsigned long)mod_user,&n_name)) < 0) {
- printk(KERN_ERR "init_module: get_mod_name failure.\n");
- error = n_namelen;
- goto err2;
- }
- //比較內核空間中和用戶空間中模塊名是否相同
- if (namelen != n_namelen || strcmp(n_name, mod_tmp.name) != 0) {
- printk(KERN_ERR "init_module: changed module name to ""`%s' from `%s'\n",n_name, mod_tmp.name);
- goto err3;
- }
- //拷貝除module結構本身以外的其它section到內核空間中,就是拷貝模塊映像
- if (copy_from_user((char *)mod+mod_user_size,(char *)mod_user+mod_user_size,mod->size-mod_user_size)) {
- error = EFAULT;
- goto err3;
- }
- if (module_arch_init(mod))//空操作
- goto err3;
- flush_icache_range((unsigned long)mod, (unsigned long)mod + mod->size);
- mod->next = mod_tmp.next;//mod->next在拷貝模塊頭時被覆蓋了
- mod->refs = NULL;//由於是新加載的,還沒有別的模塊引用我
- //檢查模塊的依賴關系,依賴的模塊仍在內核中
- for (i = 0, dep = mod->deps; i < mod->ndeps; ++i, ++dep) {
- struct module *o, *d = dep->dep;
- if (d == mod) {//依賴的模塊不能使自身
- printk(KERN_ERR "init_module: selfreferential""dependency in mod->deps.\n");
- goto err3;
- }
- //若依賴的模塊已經不在module_list中,則系統調用失敗
- for (o = module_list; o != &kernel_module && o != d; o = o->next);
- if (o != d) {
- printk(KERN_ERR "init_module: found dependency that is ""(no longer?) a module.\n");
- goto err3;
- }
- }
- //再掃描,將每個module_ref結構鏈入到所依賴模塊的refs隊列中,並將結構中的ref指針指向正在安裝的nodule結構。這樣每個module_ref結構既存在於所屬模塊的deps[]數組中,又出現於該模塊所依賴的某個模塊的refs隊列中。
- for (i = 0, dep = mod->deps; i < mod->ndeps; ++i, ++dep) {
- struct module *d = dep->dep;
- dep->ref = mod;
- dep->next_ref = d->refs;
- d->refs = dep;
- d->flags |= MOD_USED_ONCE;
- }
- put_mod_name(n_name);//釋放空間
- put_mod_name(name);//釋放空間
- mod->flags |= MOD_INITIALIZING;//設置模塊初始化標志
- atomic_set(&mod->uc.usecount,1);//用戶計數設為1
- //檢查模塊的init_module()函數,然后執行模塊的init_module()函數,注意此函數與內核中的
- //init_module()函數是不一樣的,內核中的調用sys_init_module()
- if (mod->init && (error = mod->init()) != 0) {
- atomic_set(&mod->uc.usecount,0);//模塊的計數加1
- mod->flags &= ~MOD_INITIALIZING;//初始化標志清零
- if (error > 0) /* Buggy module */
- error = EBUSY;
- goto err0;
- }
- atomic_dec(&mod->uc.usecount);//遞減計數
- //初始化標志清零,標志設置為MOD_RUNNING
- mod->flags = (mod->flags | MOD_RUNNING) & ~MOD_INITIALIZING;
- error = 0;
- goto err0;
- err3:
- put_mod_name(n_name);
- err2:
- *mod = mod_tmp;
- strcpy((char *)mod->name, name_tmp); /* We know there is room for this */
- err1:
- put_mod_name(name);
- err0:
- unlock_kernel();
- kfree(name_tmp);
- return error;
- }
