处理完命令行参数,INSMOD_MAIN接下来调用arch_create_got。这个函数在./modutils-2.4.0/obj/obj_i386.c中。
Insmod——arch_create_got函数
158 int
159 arch_create_got (struct obj_file *f)
160 {
161 struct i386_file *ifile = (struct i386_file *)f;
162 int i, n, offset = 0, gotneeded = 0;
163
164 n = ifile->root.header.e_shnum;
165 for (i = 0; i < n; ++i)
166 {
167 struct obj_section *relsec, *symsec, *strsec;
168 Elf32_Rel *rel, *relend;
169 Elf32_Sym *symtab;
170 const char *strtab;
171
172 relsec = ifile->root.sections[i];
173 if (relsec->header.sh_type != SHT_REL)
174 continue;
175
176 symsec = ifile->root.sections[relsec->header.sh_link];
177 strsec = ifile->root.sections[symsec->header.sh_link];
178
179 rel = (Elf32_Rel *)relsec->contents;
180 relend = rel + (relsec->header.sh_size / sizeof(Elf32_Rel));
181 symtab = (Elf32_Sym *)symsec->contents;
182 strtab = (const char *)strsec->contents;
183
184 for (; rel < relend; ++rel)
185 {
186 Elf32_Sym *extsym;
187 struct i386_symbol *intsym;
188 const char *name;
189
190 switch (ELF32_R_TYPE(rel->r_info))
191 {
192 case R_386_GOTPC:
193 case R_386_GOTOFF:
194 gotneeded = 1;
195 default:
196 continue;
197
198 case R_386_GOT32:
199 break;
200 }
201
202 extsym = &symtab[ELF32_R_SYM(rel->r_info)];
203 if (extsym->st_name)
204 name = strtab + extsym->st_name;
205 else
206 name = f->sections[extsym->st_shndx]->name;
207 intsym = (struct i386_symbol *)obj_find_symbol(&ifile->root, name);
208
209 if (!intsym->gotent.offset_done)
210 {
211 intsym->gotent.offset_done = 1;
212 intsym->gotent.offset = offset;
213 offset += 4;
214 }
215 }
216 }
217
218 if (offset > 0 || gotneeded)
219 ifile->got = obj_create_alloced_section(&ifile->root, ".got", 4, offset);
220
221 return 1;
222 }
这个函数的作用是创建文件的.GOT段。这是怎么回事呢?.GOT的全称是global offset table。在这个段里保存的是绝对地址,这些地址不受重定位的影响。如果程序需要直接引用符号的绝对地址,这些符号就必须在.GOT段中出现(原文:that symbol will have a global offset table entry)。什么情况下程序需要引用符号的绝对地址呢?这跟重定位操作的类型有关。重定位操作为类型R_386_GOTOFF、R_386_GOTPC及R_386_GOT32的符号,会使用.GOT段。
函数里使用了i386_file和i386_symbol结构,这2个结构定义在同一文件里。
34 struct i386_got_entry
35 {
36 int offset;
37 unsigned offset_done : 1;
38 unsigned reloc_done : 1;
39 };
40
41 struct i386_file
42 {
43 struct obj_file root;
44 struct obj_section *got;
45 };
46
47 struct i386_symbol
48 {
49 struct obj_symbol root;
50 struct i386_got_entry gotent;
51 };
这些结构都很简单,只是添加了些辅助控制位。
接下来,INSMOD_MAIN通过hide_special_symbols将符号“cleanup_module”,“init_module”,“kernel_version”的属性改为local,从而使其外部不可见。这个函数也在insmod.c里。
Insmod——hide_special_symbols函数
283 static void hide_special_symbols(struct obj_file *f)
284 {
285 struct obj_symbol *sym;
286 const char *const *p;
287 static const char *const specials[] =
288 {
289 "cleanup_module",
290 "init_module",
291 "kernel_version",
292 NULL
293 };
294
295 for (p = specials; *p; ++p)
296 if ((sym = obj_find_symbol(f, *p)) != NULL)
297 sym->info = ELFW(ST_INFO) (STB_LOCAL, ELFW(ST_TYPE) (sym->info));
298 }
接下来,如果命令行参数来自文件,将文件名保存好,下面要从那里读出参数值。
1700 if (persist_parms && persist_name && *persist_name) {
1701 f->persist = persist_name;
1702 persist_name = NULL;
1703 }
1704
1705 if (persist_parms &&
1706 persist_name && !*persist_name) {
1707 /* -e "". This is ugly. Take the filename, compare it against
1708 * each of the module paths until we find a match on the start
1709 * of the filename, assume the rest is the relative path. Have
1710 * to do it this way because modprobe uses absolute filenames
1711 * for module names in modules.dep and the format of modules.dep
1712 * does not allow for any backwards compatible changes, so there
1713 * is nowhere to store the relative filename. The only way this
1714 * should fail to calculate a relative path is "insmod ./xxx", for
1715 * that case the user has to specify -e filename.
1716 */
1717 int j, l = strlen(filename);
1718 char *relative = NULL;
1719 char *p;
1720 for (i = 0; i < nmodpath; ++i) {
1721 p = modpath[i].path;
1722 j = strlen(p);
1723 while (j && p[j] == '/')
1724 --j;
1725 if (j < l && strncmp(filename, p, j) == 0 && filename[j] == '/') {
1726 while (filename[j] == '/')
1727 ++j;
1728 relative = xstrdup(filename+j);
1729 break;
1730 }
1731 }
1732 if (relative) {
1733 i = strlen(relative);
1734 if (i > 3 && strcmp(relative+i-3, ".gz") == 0)
1735 relative[i -= 3] = '/0';
1736 if (i > 2 && strcmp(relative+i-2, ".o") == 0)
1737 relative[i -= 2] = '/0';
1738 else if (i > 4 && strcmp(relative+i-4, ".mod") == 0)
1739 relative[i -= 4] = '/0';
1740 f->persist = xmalloc(strlen(persistdir) + 1 + i + 1);
1741 strcpy(f->persist, persistdir); /* safe, xmalloc */
1742 strcat(f->persist, "/"); /* safe, xmalloc */
1743 strcat(f->persist, relative); /* safe, xmalloc */
1744 free(relative);
1745 }
1746 else
1747 error("Cannot calculate persistent filename");
1748 }
这段代码没什么可说的。1705~1748行是防止-e “”这样的恶作剧。接下来是一些健康检查,并且处理从文件里读出的参数值。
1750 if (f->persist && *(f->persist) != '/') {
1751 error("Persistent filenames must be absolute, ignoring '%s'",
1752 f->persist);
1753 free(f->persist);
1754 f->persist = NULL;
1755 }
1756
1757 if (f->persist && !flag_ksymoops) {
1758 error("has persistent data but ksymoops symbols are not available");
1759 free(f->persist);
1760 f->persist = NULL;
1761 }
1762
1763 if (f->persist && !k_new_syscalls) {
1764 error("has persistent data but the kernel is too old to support it");
1765 free(f->persist);
1766 f->persist = NULL;
1767 }
1768
1769 if (persist_parms && flag_verbose) {
1770 if (f->persist)
1771 lprintf("Persist filename '%s'", f->persist);
1772 else
1773 lprintf("No persistent filename available");
1774 }
1775
1776 if (f->persist) {
1777 FILE *fp = fopen(f->persist, "r");
1778 if (!fp) {
1779 if (flag_verbose)
1780 lprintf("Cannot open persist file '%s' %m", f->persist);
1781 }
1782 else {
1783 int pargc = 0;
1784 char *pargv[1000]; /* hard coded but big enough */
1785 char line[3000]; /* hard coded but big enough */
1786 char *p;
1787 while (fgets(line, sizeof(line), fp)) {
1788 p = strchr(line, '/n');
1789 if (!p) {
1790 error("Persistent data line is too long/n%s", line);
1791 break;
1792 }
1793 *p = '/0';
1794 p = line;
1795 while (isspace(*p))
1796 ++p;
1797 if (!*p || *p == '#')
1798 continue;
1799 if (pargc == sizeof(pargv)/sizeof(pargv[0])) {
1800 error("More than %d persistent parameters", pargc);
1801 break;
1802 }
1803 pargv[pargc++] = xstrdup(p);
1804 }
1805 fclose(fp);
1806 if (!process_module_arguments(f, pargc, pargv, 0))
1807 goto out;
1808 while (pargc--)
1809 free(pargv[pargc]);
1810 }
1811 }
这里处理的主体是process_module_arguments,这个函数我们已经看过了。从这里可以看到文件里的内容类似,A=a B=b。
1813 if (flag_ksymoops)
1814 add_ksymoops_symbols(f, filename, m_name);
如果定义了ksymoops符号,调用add_ksymoops_symbols函数。ksymoops是一个调试辅助工具,它将试图将代码转换为指令并将堆栈值映射到内核符号。在很多情况下,这些信息就足够您确定错误的可能原因是什么了。这个函数也在insmod.c中。
Insmod——add_ksymoops_symbols函数
619 /* add module source, timestamp, kernel version and a symbol for the
620 * start of some sections. this info is used by ksymoops to do better
621 * debugging.
622 */
623 static void add_ksymoops_symbols(struct obj_file *f, const char *filename,
624 const char *m_name)
625 {
626 struct obj_section *sec;
627 struct obj_symbol *sym;
628 char *name, *absolute_filename;
629 char str[STRVERSIONLEN], real[PATH_MAX];
630 int i, l, lm_name, lfilename, use_ksymtab, version;
631 struct stat statbuf;
632
633 static const char *section_names[] = {
634 ".text",
635 ".rodata",
636 ".data",
637 ".bss"
638 };
639
640 if (realpath(filename, real)) {
641 absolute_filename = xstrdup(real);
642 }
643 else {
644 int save_errno = errno;
645 error("cannot get realpath for %s", filename);
646 errno = save_errno;
647 perror("");
648 absolute_filename = xstrdup(filename);
649 }
650
651 lm_name = strlen(m_name);
652 lfilename = strlen(absolute_filename);
653
654 /* add to ksymtab if it already exists or there is no ksymtab and other symbols
655 * are not to be exported. otherwise leave ksymtab alone for now, the
656 * "export all symbols" compatibility code will export these symbols later.
657 */
658
659 use_ksymtab = obj_find_section(f, "__ksymtab") || !flag_export;
660
661 if ((sec = obj_find_section(f, ".this"))) {
662 /* tag the module header with the object name, last modified
663 * timestamp and module version. worst case for module version
664 * is 0xffffff, decimal 16777215. putting all three fields in
665 * one symbol is less readable but saves kernel space.
666 */
667 l = sizeof(symprefix)+ /* "__insmod_" */
668 lm_name+ /* module name */
669 2+ /* "_O" */
670 lfilename+ /* object filename */
671 2+ /* "_M" */
672 2*sizeof(statbuf.st_mtime)+ /* mtime in hex */
673 2+ /* "_V" */
674 8+ /* version in dec */
675 1; /* nul */
676 name = xmalloc(l);
677 if (stat(absolute_filename, &statbuf) != 0)
678 statbuf.st_mtime = 0;
679 version = get_module_version(f, str); /* -1 if not found */
680 snprintf(name, l, "%s%s_O%s_M%0*lX_V%d",
681 symprefix, m_name, absolute_filename,
682 2*sizeof(statbuf.st_mtime), statbuf.st_mtime,
683 version);
684 sym = obj_add_symbol(f, name, -1,
685 ELFW(ST_INFO) (STB_GLOBAL, STT_NOTYPE),
686 sec->idx, sec->header.sh_addr, 0);
687 if (use_ksymtab)
688 add_ksymtab(f, sym);
689 }
690 free(absolute_filename);
691
692 /* record where the persistent data is going, same address as previous symbol */
693
694 if (f->persist) {
695 l = sizeof(symprefix)+ /* "__insmod_" */
696 lm_name+ /* module name */
697 2+ /* "_P" */
698 strlen(f->persist)+ /* data store */
699 1; /* nul */
700 name = xmalloc(l);
701 snprintf(name, l, "%s%s_P%s",
702 symprefix, m_name, f->persist);
703 sym = obj_add_symbol(f, name, -1, ELFW(ST_INFO) (STB_GLOBAL, STT_NOTYPE),
704 sec->idx, sec->header.sh_addr, 0);
705 if (use_ksymtab)
706 add_ksymtab(f, sym);
707 }
708
709 /* tag the desired sections if size is non-zero */
710
711 for (i = 0; i < sizeof(section_names)/sizeof(section_names[0]); ++i) {
712 if ((sec = obj_find_section(f, section_names[i])) &&
713 sec->header.sh_size) {
714 l = sizeof(symprefix)+ /* "__insmod_" */
715 lm_name+ /* module name */
716 2+ /* "_S" */
717 strlen(sec->name)+ /* section name */
718 2+ /* "_L" */
719 8+ /* length in dec */
720 1; /* nul */
721 name = xmalloc(l);
722 snprintf(name, l, "%s%s_S%s_L%ld",
723 symprefix, m_name, sec->name,
724 (long)sec->header.sh_size);
725 sym = obj_add_symbol(f, name, -1, ELFW(ST_INFO) (STB_GLOBAL, STT_NOTYPE),
726 sec->idx, sec->header.sh_addr, 0);
727 if (use_ksymtab)
728 add_ksymtab(f, sym);
729 }
730 }
731 }
这个函数做的事情不复杂,就是记录经过修饰的模块名和经过修饰的长度不为0的段名。另外,如果模块运行时参数是通过文件传入的,也要修饰、记录这个文件名。在这里修饰的目的,应该是为了唯一确定所使用的模块。
在这里flag_export是全局变量,初始化为1,当运行insmod时,如果使用了-x,就置为0,如果使用了-X,就置为1。默认为1。
在存在__ksymtab段或者不存在这个段,而且其他符号都不导出的情况下,还要将这些符号添加入__symtab段。这个由函数add_ksymtab完成。这个函数在同一文件里。
Insmod——add_ksymtab函数
476 /* add an entry to the __ksymtab section, creating it if necessary */
477 static void add_ksymtab(struct obj_file *f, struct obj_symbol *sym)
478 {
479 struct obj_section *sec;
480 ElfW(Addr) ofs;
481
482 /* ensure __ksymtab is allocated, EXPORT_NOSYMBOLS creates a non-alloc section.
483 * If __ksymtab is defined but not marked alloc, x out the first character
484 * (no obj_delete routine) and create a new __ksymtab with the correct
485 * characteristics.
486 */
487 sec = obj_find_section(f, "__ksymtab");
488 if (sec && !(sec->header.sh_flags & SHF_ALLOC)) {
489 *((char *)(sec->name)) = 'x'; /* override const */
490 sec = NULL;
491 }
492 if (!sec)
493 sec = obj_create_alloced_section(f, "__ksymtab", tgt_sizeof_void_p, 0);
494 if (!sec)
495 return;
496 sec->header.sh_flags |= SHF_ALLOC;
497
498 ofs = sec->header.sh_size;
499 obj_symbol_patch(f, sec->idx, ofs, sym);
500 obj_string_patch(f, sec->idx, ofs + tgt_sizeof_void_p, sym->name);
501 obj_extend_section(sec, 2 * tgt_sizeof_char_p);
502 }
在这个函数里,如果存在__symtab段,但是没有SHF_ALLOC标志(表明段在程序执行时不占据内存资源),那么直接在段的开头写入“x”,标识它已删除。为了使段容易扩展,在段里面保存的都是“指针”。所以,函数使用obj_symbol_patch来保存这些“额外”的symbol。这个函数在obj_reloc.c中。
Insmod——obj_symbol_patch函数
65 int
66 obj_symbol_patch(struct obj_file *f, int secidx, ElfW(Addr) offset,
67 struct obj_symbol *sym)
68 {
69 struct obj_symbol_patch_struct *p;
70
71 p = xmalloc(sizeof(*p));
72 p->next = f->symbol_patches;
73 p->reloc_secidx = secidx;
74 p->reloc_offset = offset;
75 p->sym = sym;
76 f->symbol_patches = p;
77
78 return 1;
79 }
如果内核版本在v2.1.x以上,调用create_module_symtab。这个函数也在insmod.c里。
1816 if (k_new_syscalls)
1817 create_module_ksymtab(f);
Insmod——create_module_ksymtab函数
504 static int create_module_ksymtab(struct obj_file *f)
505 {
506 struct obj_section *sec;
507 int i;
508
509 /* We must always add the module references. */
510
511 if (n_ext_modules_used) {
512 struct module_ref *dep;
513 struct obj_symbol *tm;
514
515 sec = obj_create_alloced_section(f, ".kmodtab", tgt_sizeof_void_p,
516 (sizeof(struct module_ref)
517 * n_ext_modules_used));
518 if (!sec)
519 return 0;
520
521 tm = obj_find_symbol(f, "__this_module");
522 dep = (struct module_ref *) sec->contents;
523 for (i = 0; i < n_module_stat; ++i)
524 if (module_stat[i].status /* used */) {
525 dep->dep = module_stat[i].addr;
526 obj_symbol_patch(f, sec->idx, (char *) &dep->ref - sec->contents, tm);
527 dep->next_ref = 0;
528 ++dep;
529 }
530 }
531 if (flag_export && !obj_find_section(f, "__ksymtab")) {
532 int *loaded;
533
534 /* We don't want to export symbols residing in sections that
535 aren't loaded. There are a number of these created so that
536 we make sure certain module options don't appear twice. */
537
538 loaded = alloca(sizeof(int) * (i = f->header.e_shnum));
539 while (--i >= 0)
540 loaded[i] = (f->sections[i]->header.sh_flags & SHF_ALLOC) != 0;
541
542 for (i = 0; i < HASH_BUCKETS; ++i) {
543 struct obj_symbol *sym;
544 for (sym = f->symtab[i]; sym; sym = sym->next) {
545 if (ELFW(ST_BIND) (sym->info) != STB_LOCAL
546 && sym->secidx <= SHN_HIRESERVE
547 && (sym->secidx >= SHN_LORESERVE
548 || loaded[sym->secidx])) {
549 add_ksymtab(f, sym);
550 }
551 }
552 }
553 }
554 return 1;
555 }
n_ext_modules_used是该模块引用模块的数目。内核所有加载的模块的信息都保存在module_stat数组里,每个被引用的模块的module_stat里的status为1(见add_kernel_symbols)。函数在511~530行创建一个.kmodtab段保存该模块所引用的模块信息(包括这些模块对该模块的引用信息)。
如果需要导出外部(extern)符号,而__ksymtab段不存在(这种情况下,add_ksymoops_symbols里是不会创建__ksymtab段的。而实际上,模块一般是不会不包含__ksymtab段的,因为模块的导出符号都位于这个段里。),那么通过add_ksymtab创建__ksymtab段,并加入模块要导出的符号。在这里可以发现,如果模块需要导出符号,那么经过修饰的模块名、模块文件名,甚至参数文件名会在这里加入__ksymtab段(因为在前面的add_ksymoops_symbols函数里,这些符号被设为STB_GLOBOL了,段序号指向.this段)。
注意,在这里545行的if!在不存在__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有关,谁知道它放了什么进来?!)。