模块加载过程代码分析1
2014-10-31 21:11
417 查看
一、概述
模块是作为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;//模块内部状态
//模块链表指针,将所有加载模块保存到一个双链表中,链表的表头是定义在 <kernel/module.c> 的全局变量 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;
}
转自: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;//模块内部状态
//模块链表指针,将所有加载模块保存到一个双链表中,链表的表头是定义在 <kernel/module.c> 的全局变量 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;
}
转自:http://blog.chinaunix.net/uid-27717694-id-3966290.html
相关文章推荐
- 模块加载过程代码分析2
- 模块加载过程代码分析2
- insmod模块加载过程代码分析1【转】
- OpenERP模块动态加载原理及启动代码分析
- ELF文件加载过程代码分析
- Spark SQL模块代码分析(查询语句到逻辑查询计划树的过程)
- OpenERP 模块动态加载原理及启动代码分析
- 模块加载过程分析:INSMOD DEMODEV.KO
- Openvswitch原理与代码分析(3): openvswitch内核模块的加载
- 基于visual c++之windows核心编程代码分析(19)枚举进程以及进程加载模块信息
- QEMU 代码分析:BIOS 的加载过程
- Insmod模块加载过程分析
- Chromium插件(Plugin)模块(Module)加载过程分析
- OpenERP 模块动态加载原理及启动代码分析
- Openvswitch原理与代码分析(3): openvswitch内核模块的加载
- Linux内核---40.模块加载过程分析
- seaJS 模块加载过程分析
- 一段java代码加载分析认识加载过程
- SD--对于定价过程参考步骤 (Condition Step)(T683S-STUNB and T683S-STUN2)的使用代码分析
- SD--对于定价过程参考步骤 (Condition Step)(T683S-STUNB and T683S-STUN2)的使用代码分析