linux kernelにおけるinsmodの裏側を確認

ちょっと早い夏休みだ自由研究をしよう(3日目)

# TL;DR

- kernel objectのinsmod処理は、kernel/module.cの中で行われる。
- syscall(init_module)が起点となる。
- 最終的にはkernelへのnotifierが呼ばれる。

# はじめに

insmodってよく使いますよね！　でも、その裏側は見た記憶があまりない…　ここでは順番にその流れを確認していきたい。

busybox
--------
組込エンジニアなので、busyboxのinsmodを起点とする。

- ユーザーからの要求は、[busybox/tree/modutils/insmod.c](https://git.busybox.net/busybox/tree/modutils/insmod.c)で受け取る。
- kernelへの要求は、[busybox/tree/modutils/modutils.c](https://git.busybox.net/busybox/tree/modutils/modutils.c)で行われる。

```plantuml
[-> busybox : insmod
busybox -> busybox : bb_init_module()
activate busybox
alt finit_moduleが有効な場合
busybox -> kernel: open()
busybox -> kernel: finit_module() *syscall
busybox -> kernel: close()
busybox <-- kernel
else finit_moduleが有効ではない or 失敗した
busybox -> kernel : try_to_mmap_module()
busybox -> kernel : init_module() *syscall
end
```

Kernel
--------
kernel側のソースコードは、大部分が[kernel/module.c](https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/kernel/module.c?h=v5.13.7)にまとまっている。

```plantuml
[-> kernel : syscall(init_module)
participant module.c as module 
kernel -> module : init_module
group 初期化、読み出し
module -> module : may_init_module()
module -> module : copy_module_from_user()
module -> module : load_module()
activate module
module -> module: module_sig_check()
module -> module : elf_validity_check()
module -> module : setup_load_info()
end
group 初期化、読み出し
module -> module : rewrite_section_headers()
module -> module : check_modstruct_version()
module -> module : layout_and_allocate()
module -> module : percpu_modalloc()
module -> module : module_unload_init()
module -> module : find_module_sections()
module -> module : check_module_license_and_versions()
module -> module : setup_modinfo()
module -> module : simplify_symbols()
module -> module : apply_relocations()
module -> module : post_relocation
module -> module : flush_module_icache()
end
group 後処理、通知
module -> module : complete_formation()
module -> module : prepare_coming_module()
module -> module : mod_sysfs_setup()
module -> module : do_init_module()
activate module
module -> kernel : mod_put()
module -> kernel : blocking_notifier_call_chain(MODULE_STATE_LIVE);
module -> kernel : kobject_uevent(KOBJ_ADD)
deactivate module
end
deactivate module
module --> kernel
```

# point
## Q. moduleの未定義シンボルはどこで解決されるの？
A. simplify_symbols()から呼ばれた、resolve_symbol()で。

```plantuml
participant module.c as module 
module -> module : resolve_symbol()
activate module
loop 全シンボルについて
alt SHN_COMMON
else SHN_COMMON
else SHN_ABS
else SHN_UNDEF
module -> module : resolve_symbol_wait()
activate module
module -> module : resolve_symbol()
activate module
deactivate module
deactivate module
alt error
module -> kernel : pr_warn("$s: unknown symbol ...");
end
end
end
deactivate  module
```

## Q. moduleのアドレス再割り当てはどこで行われる？
A. [apply_relocations()](https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/kernel/module.c?h=v5.13.7#n2353) で補正される。

```c
static int apply_relocations(struct module *mod, const struct load_info *info)
{
	unsigned int i;
	int err = 0;

	/* Now do relocations. */
	for (i = 1; i < info->hdr->e_shnum; i++) {
		unsigned int infosec = info->sechdrs[i].sh_info;

		/* Not a valid relocation section? */
		if (infosec >= info->hdr->e_shnum)
			continue;

		/* Don't bother with non-allocated sections */
		if (!(info->sechdrs[infosec].sh_flags & SHF_ALLOC))
			continue;

		if (info->sechdrs[i].sh_flags & SHF_RELA_LIVEPATCH)
			err = klp_apply_section_relocs(mod, info->sechdrs,
						       info->secstrings,
						       info->strtab,
						       info->index.sym, i,
						       NULL);
		else if (info->sechdrs[i].sh_type == SHT_REL)
			err = apply_relocate(info->sechdrs, info->strtab,
					     info->index.sym, i, mod);
		else if (info->sechdrs[i].sh_type == SHT_RELA)
			err = apply_relocate_add(info->sechdrs, info->strtab,
						 info->index.sym, i, mod);
		if (err < 0)
			break;
	}
	return err;
}
```

この関数内の、`apply_relocate()`は、例えば、ARMの場合ならば、[arm/kernel/modules.c](https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/arch/arm/kernel/module.c?h=v5.13.7#n71)内に定義されている。

## Q. kernelはどうやってinsmodされたことを知覚するの？
A. [do_init_module()](https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/kernel/module.c?h=v5.13.7#n3647)内の関数が、kernelへの通知処理。

```c
/*
 * This is where the real work happens.
 *
 * Keep it uninlined to provide a reliable breakpoint target, e.g. for the gdb
 * helper command 'lx-symbols'.
 */
static noinline int do_init_module(struct module *mod)
{
	int ret = 0;
	struct mod_initfree *freeinit;

	freeinit = kmalloc(sizeof(*freeinit), GFP_KERNEL);
	if (!freeinit) {
		ret = -ENOMEM;
		goto fail;
	}
	freeinit->module_init = mod->init_layout.base;

	/*
	 * We want to find out whether @mod uses async during init.  Clear
	 * PF_USED_ASYNC.  async_schedule*() will set it.
	 */
	current->flags &= ~PF_USED_ASYNC;

	do_mod_ctors(mod);
	/* Start the module */
	if (mod->init != NULL)
		ret = do_one_initcall(mod->init);
	if (ret < 0) {
		goto fail_free_freeinit;
	}
	if (ret > 0) {
		pr_warn("%s: '%s'->init suspiciously returned %d, it should "
			"follow 0/-E convention\n"
			"%s: loading module anyway...\n",
			__func__, mod->name, ret, __func__);
		dump_stack();
	}

	/* Now it's a first class citizen! */
	mod->state = MODULE_STATE_LIVE;
	blocking_notifier_call_chain(&module_notify_list,
				     MODULE_STATE_LIVE, mod);

	/* Delay uevent until module has finished its init routine */
	kobject_uevent(&mod->mkobj.kobj, KOBJ_ADD);


```



# まとめ
- kernel objectのinsmod処理は、kernel/module.cの中で行われる。
- syscall(init_module)が起点となる。
- 最終的にはkernelへのnotifierが呼ばれる。