rmmodできないLKMをrmmodする
カーネルを調べるにあたって、LKMは欠かせません。しかしそれだけに、モジュール初期化の段階で強制終了してしまって、再コンパイルにあたりMakefileでモジュール名を変更しながら、再度insmodしたりしています。この際このあたりの仕組みの調査兼ねて、rmmodできなくなったモジュールをrmmodできるLKMを作成しました。
モジュールのロードは、 init_module()で行われます。ここからload_module()がコールされ、struct module *modnに、ロードするモジュールを取得/設定いたします。この時mod->state = MODULE_STATE_COMINGとしています。そこから復帰して、モジュール参照カンターを、インクリメント(正確には参照カウンターをインクリメント)、LKMソースのmodule_init()で定義する関数が起動されます。その結果が0以上(正の場合、KERN_WARNINGとしてスタックをダンプするだけです。)なら、mod->state = MODULE_STATE_LIVEとし、またモジュール参照カンターを、デクリメント(正確には非参照カウンターをインクリメント)いたします。
なお負の値を帰り値とした場合、モジュールはlkmとして登録されません。1発処理のlkmでは、返り値をマイナスで返したほうが、改めてrmmodしてinsmodする手間が省けます。訝しげなエラーがでますが、これはinsmodコマンドで出力しているだけで、lkmとしてシステム的な問題はありません。ただカーネルに登録されないことだけです。ここではreturn -1としています。
module_init()の処理で強制終了すると、参照状態のまま、mod->stateはMODULE_STATE_COMINGのままになってしまいます。
rmmodコマンドはシステムコールdelete_moduleが呼ばれます。解放チェックは、mod->state = MODULE_STATE_LIVE、module_xit()が定義されている。(されてない場合コンパイルオプションにより強制的に解放することができます。)、そして参照カンターが0(正確には参照カウンター - 参照終了したカウンター = 0)の時です。
下記は上記設定をリセットするLKMです。
モジュールのロードは、 init_module()で行われます。ここからload_module()がコールされ、struct module *modnに、ロードするモジュールを取得/設定いたします。この時mod->state = MODULE_STATE_COMINGとしています。そこから復帰して、モジュール参照カンターを、インクリメント(正確には参照カウンターをインクリメント)、LKMソースのmodule_init()で定義する関数が起動されます。その結果が0以上(正の場合、KERN_WARNINGとしてスタックをダンプするだけです。)なら、mod->state = MODULE_STATE_LIVEとし、またモジュール参照カンターを、デクリメント(正確には非参照カウンターをインクリメント)いたします。
なお負の値を帰り値とした場合、モジュールはlkmとして登録されません。1発処理のlkmでは、返り値をマイナスで返したほうが、改めてrmmodしてinsmodする手間が省けます。訝しげなエラーがでますが、これはinsmodコマンドで出力しているだけで、lkmとしてシステム的な問題はありません。ただカーネルに登録されないことだけです。ここではreturn -1としています。
module_init()の処理で強制終了すると、参照状態のまま、mod->stateはMODULE_STATE_COMINGのままになってしまいます。
rmmodコマンドはシステムコールdelete_moduleが呼ばれます。解放チェックは、mod->state = MODULE_STATE_LIVE、module_xit()が定義されている。(されてない場合コンパイルオプションにより強制的に解放することができます。)、そして参照カンターが0(正確には参照カウンター - 参照終了したカウンター = 0)の時です。
SYSCALL_DEFINE2(delete_module, const char __user *, name_user, unsigned int, flags) { : mod = find_module(name); : if (mod->state != MODULE_STATE_LIVE) { DEBUGP("%s already dying\n", mod->name); ret = -EBUSY; goto out; } : if (mod->init && !mod->exit) { forced = try_force_unload(flags); if (!forced) { /* This module can't be removed */ ret = -EBUSY; goto out; } } : if (!forced && module_refcount(mod) != 0) wait_for_zero_refcount(mod); : }
[root@localhost kitamura]# cat /proc/modules decmod 648 0 - Live 0xd1986000 bug_mod1 1591 1 - Loading 0xd13d0000 bug_mod2 1579 0 [permanent] Live 0xd14c0000 : ipv6 229420 28 - Live 0xd11bc000 uinput 5228 0 - Live 0xd106d000 ppdev 6808 0 - Live 0xd1010000 :順に、名前/サイズ/参照数/モード/状態/アドレスとなります。rmmodするにはモジュールの参照数が0で、状態がLiveである必要があります。rmmodできないモジュールはどちらかが、そうなっていないからです。なお[permanent]はmodule_exit()を定義していないモジュールで、このモジュールは原則rmmodできません。
下記は上記設定をリセットするLKMです。
#include <linux/module.h> #include <linux/kernel.h> #include <linux/string.h> #include <linux/init.h> MODULE_AUTHOR("y.kitamura"); MODULE_DESCRIPTION("pre_rmmod"); MODULE_LICENSE("GPL"); static char* name = ""; module_param(name, charp, S_IRUGO); static int __init pre_rmmod_init(void) { truct module* mod; if (name == NULL) { printk("insmod pre_rmmod.ko name=module name\n"); return -1; } mod = find_module(name); if (!mod) { printk("not found %s\n", name); return -1; } if (module_refcount(mod)) { module_put(mod); } mod->state = MODULE_STATE_LIVE; printk("%s:%d\n", mod->name, module_refcount(mod)); return -1; } module_init(pre_rmmod_init);
使用例
bug_mod1はrmmodできません。引数にbug_mod1でpre_rmmod.koをinsmodします。エラーが出ますが問題ありません。pre_rmmod.koはlkmとしてカーネルに登録されません。mmod bug_mod1.koでbug_mod1.koが削除されました。[root@localhost kitamura]# rmmod bug_mod1.ko ERROR:Module bug_mod1 is in use [root@localhost kitamura]# insmod pre_rmmod.ko name=bug_mod1 insmod: error inserting 'pre_rmmod.ko': -1 Operation not permitted [root@localhost kitamura]# rmmod bug_mod1.ko [root@localhost kitamura]#