lkm起動について(2)
insmodは引数のlkmモジュールを組み込みます。これはシステムコール__NR_init_moduleをコールする事で行っています。sys_init_module()は、引数のvoid __user *umodから、load_module()でstruct module *modを作成し、静的変数modulesをヘッドとするリストに登録して、lkmのスタート関数のmod->init()を呼び出すことにあります。
引数のvoid __user *umodは、漠然とlkmのファイル名だと思っていましたが、改めて読み返すと、lkmファイルイメージだと言うことでした。
最後に、改めて複写した領域のイメージ内の.rel.gnu.linkonce.this_moduleを、modとして、ユーザ引数として割り当てた、カーネル空間メモリを開放しています。
引数のvoid __user *umodは、漠然とlkmのファイル名だと思っていましたが、改めて読み返すと、lkmファイルイメージだと言うことでした。
#define __NR_init_module 175 __SYSCALL(__NR_init_module, sys_init_module) asmlinkage long sys_init_module(void __user *umod, unsigned long len, const char __user *uargs) { struct module *mod; int ret; if (!capable(CAP_SYS_MODULE)) return -EPERM; if (down_interruptible(&module_mutex) != 0) return -EINTR; mod = load_module(umod, len, uargs); if (IS_ERR(mod)) { up(&module_mutex); return PTR_ERR(mod); } if (mod->module_init) flush_icache_range((unsigned long)mod->module_init, (unsigned long)mod->module_init + mod->init_size); flush_icache_range((unsigned long)mod->module_core, (unsigned long)mod->module_core + mod->core_size); spin_lock_irq(&modlist_lock); list_add(&mod->list, &modules); spin_unlock_irq(&modlist_lock); up(&module_mutex); down(¬ify_mutex); notifier_call_chain(&module_notify_list, MODULE_STATE_COMING, mod); up(¬ify_mutex); ret = mod->init(); if (ret < 0) { /* Init routine failed: abort. Try to protect us from buggy refcounters. */ mod->state = MODULE_STATE_GOING; synchronize_kernel(); if (mod->unsafe) printk(KERN_ERR "%s: module is now stuck!\n", mod->name); else { module_put(mod); down(&module_mutex); free_module(mod); up(&module_mutex); } return ret; } down(&module_mutex); mod->state = MODULE_STATE_LIVE; /* Drop initial reference. */ module_put(mod); module_free(mod, mod->module_init); mod->module_init = NULL; mod->init_size = 0; mod->init_text_size = 0; up(&module_mutex); return 0; }hogehog.cのlkmをmakeすると、動的にhogehoge.mod.cが作成されます。ここにセクション.gnu.linkonce.this_moduleに、メンバー.name=lkm名/.init=初期関数/・・・等が設定された、struct module構造体が配置されます。
[root@localhost lkm]# cat hogehoge.mod.c #include <linux/module.h> #include <linux/vermagic.h> #include <linux/compiler.h> MODULE_INFO(vermagic, VERMAGIC_STRING); struct module __this_module __attribute__((section(".gnu.linkonce.this_module"))) = { .name = KBUILD_MODNAME, .init = init_module, #ifdef CONFIG_MODULE_UNLOAD .exit = cleanup_module, #endif .arch = MODULE_ARCH_INIT, }; static const char __module_depends[] __used __attribute__((section(".modinfo"))) = "depends=";実際のlkmモジュールのセクションです。注目すべき所は、.gnu.linkonce.this_moduleです。
[root@localhost lkm]# readelf -WS hogehoge.ko There are 26 section headers, starting at offset 0xc9a4: Section Headers: [Nr] Name Type Addr Off Size ES Flg Lk Inf Al [ 0] NULL 00000000 000000 000000 00 0 0 0 [ 1] .note.gnu.build-id NOTE 00000000 000034 000024 00 A 0 0 4 [ 2] .text PROGBITS 00000000 000060 00001c 00 AX 0 0 16 [ 3] .rel.text REL 00000000 00cdb4 000010 08 24 2 4 [ 4] .modinfo PROGBITS 00000000 00007c 000072 00 A 0 0 1 [ 5] __mcount_loc PROGBITS 00000000 0000f0 000008 00 A 0 0 4 [ 6] .rel__mcount_loc REL 00000000 00cdc4 000010 08 24 5 4 [ 7] .data PROGBITS 00000000 0000f8 000000 00 WA 0 0 4 [ 8] .gnu.linkonce.this_module PROGBITS 00000000 000100 00017c 00 WA 0 0 32 [ 9] .rel.gnu.linkonce.this_module REL 00000000 00cdd4 000010 08 24 8 4 [10] .bss NOBITS 00000000 00027c 000000 00 WA 0 0 4 [11] .debug_info PROGBITS 00000000 00027c 006cec 00 0 0 1 [12] .rel.debug_info REL 00000000 00cde4 003338 08 24 11 4 [13] .debug_abbrev PROGBITS 00000000 006f68 000562 00 0 0 1 [14] .debug_aranges PROGBITS 00000000 0074ca 000020 00 0 0 1 [15] .rel.debug_aranges REL 00000000 01011c 000010 08 24 14 4 [16] .debug_line PROGBITS 00000000 0074ea 000737 00 0 0 1 [17] .rel.debug_line REL 00000000 01012c 000008 08 24 16 4 [18] .debug_str PROGBITS 00000000 007c21 004be1 01 MS 0 0 1 [19] .comment PROGBITS 00000000 00c802 00005a 01 MS 0 0 1 [20] .note.GNU-stack PROGBITS 00000000 00c85c 000000 00 0 0 1 [21] .debug_frame PROGBITS 00000000 00c85c 000054 00 0 0 4 [22] .rel.debug_frame REL 00000000 010134 000020 08 24 21 4 [23] .shstrtab STRTAB 00000000 00c8b0 0000f1 00 0 0 1 [24] .symtab SYMTAB 00000000 010154 0001d0 10 25 25 4 [25] .strtab STRTAB 00000000 010324 0000c9 00 0 0 1.gnu.linkonce.this_moduleの内容(32ビット)は、hogehogeの.nameだけが設定されています。順にenum module_state/struct list_head となります。.init/.exitについては、ここでのアドレスは0として、.rel.gnu.linkonce.this_moduleのリロケーションセクションとして設定されていると思います。
[root@localhost lkm]# objdump -s -j .gnu.linkonce.this_module hogehoge.ko 0000 00000000 00000000 00000000 686f6765 ............hoge 0010 686f6765 00000000 00000000 00000000 hoge............ 0020 00000000 00000000 00000000 00000000 ................ 0030 00000000 00000000 00000000 00000000 ................ : : 0140 00000000 00000000 00000000 00000000 ................ 0150 00000000 00000000 00000000 00000000 ................ 0160 00000000 00000000 00000000 00000000 ................ 0170 00000000 00000000 00000000 ............ちなみにstruct moduleは、以下のようになっています。struct list_headは*next/*prevの2つのアドレスを持つメンバーです。
struct module { enum module_state state; struct list_head list; char name[MODULE_NAME_LEN]; : : }load_module()の処理の概要は、lkmイメージのvoid __user *umodを、カーネル空間メモリに複写して、ELFセクションの.rel.gnu.linkonce.this_moduleのアドレスを、struct module *modといたします。このmodに各セクションからの必要な情報を設定した後、改めて実行イメージとして配置するため、各セクションが連続するようにアドレスをサイズを考慮して、再設定していきます。lkmの最終サイズが決定すると、改めてメモリーをアロケートし、SHF_ALLOC属性のセクションをそこに複写していきます。
最後に、改めて複写した領域のイメージ内の.rel.gnu.linkonce.this_moduleを、modとして、ユーザ引数として割り当てた、カーネル空間メモリを開放しています。
static struct module *load_module(void __user *umod, unsigned long len, const char __user *uargs) { Elf_Ehdr *hdr; Elf_Shdr *sechdrs; char *secstrings, *args, *modmagic, *strtab = NULL; unsigned int i, symindex = 0, strindex = 0, setupindex, exindex, exportindex, modindex, obsparmindex, infoindex, gplindex, crcindex, gplcrcindex, versindex, pcpuindex; long arglen; struct module *mod; long err = 0; void *percpu = NULL, *ptr = NULL; /* Stops spurious gcc warning */ DEBUGP("load_module: umod=%p, len=%lu, uargs=%p\n", umod, len, uargs); if (len < sizeof(*hdr)) return ERR_PTR(-ENOEXEC); lkmサイズは64Mを超えてはならない。 OKならそのサイズをカーネル空間メモリを取得し、 ELFイメージのまま、その空間に転送する。 if (len > 64 * 1024 * 1024 || (hdr = vmalloc(len)) == NULL) return ERR_PTR(-ENOMEM); if (copy_from_user(hdr, umod, len) != 0) { err = -EFAULT; goto free_hdr; } イメージがELFフォーマットとか、ELFイメージそのもにチェック if (memcmp(hdr->e_ident, ELFMAG, 4) != 0 || hdr->e_type != ET_REL || !elf_check_arch(hdr) || hdr->e_shentsize != sizeof(*sechdrs)) { err = -ENOEXEC; goto free_hdr; } hdr->e_shnum個のセクション情報をsechdrs[i]に取得 sechdrs = (void *)hdr + hdr->e_shoff; secstrings = (void *)hdr + sechdrs[hdr->e_shstrndx].sh_offset; sechdrs[0].sh_addr = 0; symindex = strindex = 0; for (i = 1; i < hdr->e_shnum; i++) { /* Mark all sections sh_addr with their address in the temporary image. */ sechdrs[i].sh_addr = (size_t)hdr + sechdrs[i].sh_offset; /* Internal symbols and strings. */ if (sechdrs[i].sh_type == SHT_SYMTAB) { symindex = i; strindex = sechdrs[i].sh_link; strtab = (char *)hdr + sechdrs[strindex].sh_offset; } #ifndef CONFIG_MODULE_UNLOAD /* Don't load .exit sections */ if (strncmp(secstrings+sechdrs[i].sh_name, ".exit", 5) == 0) sechdrs[i].sh_flags &= ~(unsigned long)SHF_ALLOC; #endif } セクション.gnu.linkonce.this_moduleのアドレスを、modとする modindex = find_sec(hdr, sechdrs, secstrings, ".gnu.linkonce.this_module"); if (!modindex) { printk(KERN_WARNING "No module found in object\n"); err = -ENOEXEC; goto free_hdr; } mod = (void *)sechdrs[modindex].sh_addr; 各セクションのインデックスの取得 exportindex = find_sec(hdr, sechdrs, secstrings, "__ksymtab"); gplindex = find_sec(hdr, sechdrs, secstrings, "__ksymtab_gpl"); crcindex = find_sec(hdr, sechdrs, secstrings, "__kcrctab"); gplcrcindex = find_sec(hdr, sechdrs, secstrings, "__kcrctab_gpl"); setupindex = find_sec(hdr, sechdrs, secstrings, "__param"); exindex = find_sec(hdr, sechdrs, secstrings, "__ex_table"); obsparmindex = find_sec(hdr, sechdrs, secstrings, "__obsparm"); versindex = find_sec(hdr, sechdrs, secstrings, "__versions"); infoindex = find_sec(hdr, sechdrs, secstrings, ".modinfo"); pcpuindex = find_pcpusec(hdr, sechdrs, secstrings); .modinfoセクションは、実行してしまえば必要ないため、SHF_ALLOCを解除 sechdrs[infoindex].sh_flags &= ~(unsigned long)SHF_ALLOC; #ifdef CONFIG_KALLSYMS CONFIG_KALLSYMSなら、シンボル/文字列は保持 sechdrs[symindex].sh_flags |= SHF_ALLOC; sechdrs[strindex].sh_flags |= SHF_ALLOC; #endif モジュールバージョン等のチェック if (!check_modstruct_version(sechdrs, versindex, mod)) { err = -ENOEXEC; goto free_hdr; } modmagic = get_modinfo(sechdrs, infoindex, "vermagic"); if (!modmagic) { tainted |= TAINT_FORCED_MODULE; printk(KERN_WARNING "%s: no version magic, tainting kernel.\n", mod->name); } else if (!same_magic(modmagic, vermagic)) { printk(KERN_ERR "%s: version magic '%s' should be '%s'\n", mod->name, modmagic, vermagic); err = -ENOEXEC; goto free_hdr; } モジュールの引数の設定 arglen = strlen_user(uargs); if (!arglen) { err = -EFAULT; goto free_hdr; } args = kmalloc(arglen, GFP_KERNEL); if (!args) { err = -ENOMEM; goto free_hdr; } if (copy_from_user(args, uargs, arglen) != 0) { err = -EFAULT; goto free_mod; } if (find_module(mod->name)) { err = -EEXIST; goto free_mod; } mod->state = MODULE_STATE_COMING; err = module_frob_arch_sections(hdr, sechdrs, secstrings, mod); if (err < 0) goto free_mod; if (pcpuindex) { /* We have a special allocation for this section. */ percpu = percpu_modalloc(sechdrs[pcpuindex].sh_size, sechdrs[pcpuindex].sh_addralign); if (!percpu) { err = -ENOMEM; goto free_mod; } sechdrs[pcpuindex].sh_flags &= ~(unsigned long)SHF_ALLOC; mod->percpu = percpu; } イメージを転送するための、トータルサイズおよび、各セクションのアドレスを再設定し、 トータルサイズのメモリを取得し、それをptrとする。ここにイメージが転送される。 layout_sections(mod, hdr, sechdrs, secstrings); ptr = module_alloc(mod->core_size); if (!ptr) { err = -ENOMEM; goto free_percpu; } memset(ptr, 0, mod->core_size); mod->module_core = ptr; ptr = module_alloc(mod->init_size); if (!ptr && mod->init_size) { err = -ENOMEM; goto free_core; } memset(ptr, 0, mod->init_size); mod->module_init = ptr; SHF_ALLOC属性の各セクションの内容を、ptr以下にに転送してゆく。 for (i = 0; i < hdr->e_shnum; i++) { void *dest; if (!(sechdrs[i].sh_flags & SHF_ALLOC)) continue; if (sechdrs[i].sh_entsize & INIT_OFFSET_MASK) dest = mod->module_init + (sechdrs[i].sh_entsize & ~INIT_OFFSET_MASK); else dest = mod->module_core + sechdrs[i].sh_entsize; if (sechdrs[i].sh_type != SHT_NOBITS) memcpy(dest, (void *)sechdrs[i].sh_addr, sechdrs[i].sh_size); sechdrs[i].sh_addr = (unsigned long)dest; } セクション.gnu.linkonce.this_moduleも転送されたので、modのアドレスを転送先のアドレスに再設定 mod = (void *)sechdrs[modindex].sh_addr; 色々と各種設定をやっています・・・、そして最後に必要なくなったhdrのメモリを解放します。 module_unload_init(mod); set_license(mod, get_modinfo(sechdrs, infoindex, "license")); err = simplify_symbols(sechdrs, symindex, strtab, versindex, pcpuindex, mod); if (err < 0) goto cleanup; mod->num_syms = sechdrs[exportindex].sh_size / sizeof(*mod->syms); mod->syms = (void *)sechdrs[exportindex].sh_addr; if (crcindex) mod->crcs = (void *)sechdrs[crcindex].sh_addr; mod->num_gpl_syms = sechdrs[gplindex].sh_size / sizeof(*mod->gpl_syms); mod->gpl_syms = (void *)sechdrs[gplindex].sh_addr; if (gplcrcindex) mod->gpl_crcs = (void *)sechdrs[gplcrcindex].sh_addr; #ifdef CONFIG_MODVERSIONS if ((mod->num_syms && !crcindex) || (mod->num_gpl_syms && !gplcrcindex)) { printk(KERN_WARNING "%s: No versions for exported symbols." " Tainting kernel.\n", mod->name); tainted |= TAINT_FORCED_MODULE; } #endif mod->num_exentries = sechdrs[exindex].sh_size / sizeof(*mod->extable); mod->extable = (void *)sechdrs[exindex].sh_addr; for (i = 1; i < hdr->e_shnum; i++) { const char *strtab = (char *)sechdrs[strindex].sh_addr; if (sechdrs[i].sh_type == SHT_REL) err = apply_relocate(sechdrs, strtab, symindex, i,mod); else if (sechdrs[i].sh_type == SHT_RELA) err = apply_relocate_add(sechdrs, strtab, symindex, i, mod); if (err < 0) goto cleanup; } percpu_modcopy(mod->percpu, (void *)sechdrs[pcpuindex].sh_addr, sechdrs[pcpuindex].sh_size); err = module_finalize(hdr, sechdrs, mod); if (err < 0) goto cleanup; #ifdef CONFIG_KALLSYMS add_kallsyms(mod, sechdrs, symindex, strindex, secstrings); #endif mod->args = args; if (obsparmindex) { err = obsolete_params(mod->name, mod->args, (struct obsolete_modparm *) sechdrs[obsparmindex].sh_addr, sechdrs[obsparmindex].sh_size / sizeof(struct obsolete_modparm), sechdrs, symindex, (char *)sechdrs[strindex].sh_addr); } else { err = parse_args(mod->name, mod->args, (struct kernel_param *) sechdrs[setupindex].sh_addr, sechdrs[setupindex].sh_size / sizeof(struct kernel_param), NULL); } if (err < 0) goto arch_cleanup; vfree(hdr); return mod; arch_cleanup: module_arch_cleanup(mod); cleanup: module_unload_free(mod); module_free(mod, mod->module_init); free_core: module_free(mod, mod->module_core); free_percpu: if (percpu) percpu_modfree(percpu); free_mod: kfree(args); free_hdr: vfree(hdr); if (err < 0) return ERR_PTR(err); else return ptr; }