NetBSD dtrace格闘記(道半ば)
NetBSD6.0はdtrace対応している。
手順は http://wiki.netbsd.org/how_to_enable_and_run_dtrace/ に書いてあったから簡単に行くと思いきや・・・。なかなか上手くいかないもので。
未だ格闘中で使えるようにはなっていないけれど、途中色々調べたことがあったので、長いけれどまとめてみることにします。
(1)build.shでdistributionを作るときの注意
http://wiki.netbsd.org/how_to_enable_and_run_dtrace/ によれば、「You also need to build distribution with the options MKMODULAR=yes and MKDTRACE=yes.」とある。この"option"の指定の仕方は-Vオプションで指定する。
つまり、
./build.sh -O ../obj -T ../tools -u -V MKMODULAR=yes -V MKDTRACE=yes distribution
のように。
(正確には"option"というよりも"make variables"らしい。これは/etc/mk.confでも指定が可能である。)
(2)新しいユーザランドをインストールするときの注意
build.shに-Uオプションをつけると
don't know how to make /usr/src../obj/destdir.i386/METALOG. Stop
と言われてしまうようなので、-Uオプションをつけない。
ただし、この場合、rootで作業する必要がある。
(3)モジュールがロードできない!
Running hello worldの項で
modload solaris
modload dtrace
modload sdt
modload fbt
とあるが、modload solaris以外は失敗してしまう。このときのエラーメッセージは「No such file or directory」である。
dtraceモジュールなどが生成されていないと思われるので、ルートディレクトリの下でfindコマンドを実行して探してみる。
すると、solarisモジュールは/stand/i386/6.0/modules/solarisに存在する(他にも存在するがいずれも/usr/srcもしくは/usr/objの下かもしくは明らかにmoduleでなさそうなファイルなので除外)に対し、dtraceはいずれも/usr/srcの下かもしくは/usr/objの下にしか存在しない。(sdt,fbtも同様)
しかも/stand/i386/6.0/modules/solarisの日付をlsコマンドで確かめると、./build.shで配布物を構築しているときに相当している。これはどういうことだろう。
ということはシステムにdtrace,sdt,fbtがインストールされていないようだ。
ここで二つの疑問が生じる。
疑問1.なぜ、これら3つのモジュールがシステムに反映されていないのか
疑問2.modloadが見に行くディレクトリはどうなっているのか
まずは疑問1について。まずは./build.shとタイプしてヘルプを見る。すると、Other operationsのところに install=idirとは別にinstallmodules=idirというオペレーションがある。
このことより、モジュールのインストールは別途行う必要があると思われる。
しかし、installmodulesに指定するディレクトリにつき、何が正解なのかわからない。
そこで、疑問2.を解決してみることにする。
modloadが見に行くディレクトリが分かれば、そのディレクトリをinstallmodulesオペレーションで指定することによって正しくモジュールがインストールできる見込みが立つからである。
疑問2を解消すべく、sbin/modloadのソースを読む。
まず、modloadのmain.cを追う。modload実行時は、オプションはつけていないので、output_propsはfalse。よって、else節が実行される。
else節にあるprog_modctl()でモジュールのロードをやっているのだろう。
prog_ops.hでprog_modctlの定義がある。
おそらくはCRUNCHOPSの定義はないので、op_modctlポインタ経由での呼び出しになる。
op_modctlを初期化している箇所は2箇所あるが、rumpはとりあえず関係なさそうなので、modctlが関数ポインタの実体となっていることが判断できる。
modctlについてgrepなどで調べた結果、どうやらシステムコールらしい事が分かる。
カーネルソースを引き続き読んでみる。sys_modctlではcmdを見てMODCTL_LOADだった場合はhandle_modctl_loadを呼び出す。
う〜ん、sbin/modloadのソースの時点から気になっていた「prop」がまだついて回っている。(sys_modctlとhandle_modctl_loadはkern/sys_module.c)
とりあえず先に進んで、module_loadを読んでみる。(module_loadはkern/kern_module.c)
module_loadでの処理本体はmodule_do_loadが担っている模様。
module_do_loadではmodule_builtinsリストを調べる。そしてロードしたいモジュールが見つかった場合はreturnしているように見える。
module_buildinsリストで見つからない場合、module_bootlistリストを調べる。
そして見つかったら、module_bootlistリストからpendingリストにモジュールを移し替える。
見つからない場合「if(isdep)」の節には入らずに(isdepはfalse)、module_newmodule関数がコールされる。(エラーだったときのメッセージから判断するに、これはモジュールをロードするための領域確保の関数ではないか?)
次にmodule_load_vfs_vecがコールされる。そして正常終了した段階でモジュールをpendingリストに加える。(pendingリストに加えられたモジュールはロードに成功した段階でリストから外される。)
以後の処理はロードしようとしているモジュールの妥当性の確認とロードそのものに関する処理のように見えるので、これらの処理を読んでも疑問は解消されないと思われる。よって、
(1)module_builtinsリストとは何者?
(2)module_bootlistリストとは何者?
(3)module_load_vfs_vec関数の処理内容
を追ってみることで、modloadがどこのディレクトリを探しにいくのかがわかるかな、と思う。
(1)module_builtinsリストについて
module_builtin_add()によってこのリストにエントリが追加される。
module_builtin_add()は(rumpを別にすれば)module_init()から呼ばれている。
module_init()では
__link_set_decl(modules, modinfo_t)
と
__link_set_foreach(mip,modules)
があり、このforeachの中でmodule_builtin_add()が呼ばれている。
__link_set_decl()の実体はsys/cdefs_elf.h内で以下のように定義されている。
#define __link_set_decl(set, ptype) \ extern ptype * const __start_link_set_##set[]; \ extern ptype * const __stop_link_set_##set[] \
つまり、__link_set_decl(modules, modinfo_t)は
extern modinfo_t * const __start_link_set_modules[]; extern modinfo_t * const __stop_link_set_modules[]
と展開される。
__start_link_set_modulesおよび__stop_link_set_modulesについてカーネルソースを検索してみるが、rumpのldscript以外には存在しない。
試しにビルドを行ったNetBSD側のsysの下をgrepすると、netbsd.mapファイルに記述があるようだ。
netbsd.mapによると
link_set_modules
0xc0bbd8ac 0x1e4 load address 0x00bbd8ac
0xc0bbd8ac . = ALIGN (0x4)
0xc0bbd8ac PROVIDE (__start_link_set_modules, ABSOLUTE (.))
となっており、以後は
link_set_modules
0xc0bbd8ac 0x4 accf_data.o
link_set_modules
0xc0bbd8b0 0x4 accf_http.o
(略)
link_set_modules
0xc0bbda8c 0x4 xc5k.o
0xc0bbda90 PROVIDE (__stop_link_set_modules, .)
となっている。
試しにnetbsdに対してreadelf -Sを実行すると
[ 4] link_set_modules PROGBITS c0bbd8ac ...
という行が表示される。
これらの事から、module_builtinsリストには、link_set_modulesセクションの中身が順次突っ込まれると考えられる。
そこで、link_set_modulesセクションの中身について調べる。
link_set_modules
0xc0bbd8ac 0x4 accf_data.o
という記述があるので、試しにaccf_data.cを探してみて、その中に何が書かれているのかを見ることにする。
accf_data.cはnetinet/accf_data.cにある。中身はいくつかの関数と構造体定義があるのみである。
しかし、ソースの上の方に
MODULE(MODULE_CLASS_MISC, accf_dataready, NULL);
という行がある。
MODULEマクロはsys/module.hの中で以下のように定義されている。
#define MODULE(class, name, required) \ static int name##_modcmd(modcmd_t, void *); \ static const modinfo_t name##_modinfo = { \ .mi_version = __NetBSD_Version__, \ .mi_class = (class), \ .mi_modcmd = name##_modcmd, \ .mi_name = #name, \ .mi_required = (required) \ }; \ __link_set_add_rodata(modules, name##_modinfo);
これは構造体の実体を定義するためのマクロであるが、末尾の__link_set_add_rodataが気になる。
これはsys/cdefs_elf.hで
#define __link_set_add_rodata(set, sym) __link_set_make_entry(set, sym)
と定義されている。__link_set_make_entryは同じくsys/cdefs_elf.hで
#define __link_set_make_entry(set, sym) \ static void const * const __link_set_##set##_sym_##sym \ __section("link_set_" #set) __used = &sym
と定義されている。
例えば「MODULE(MODULE_CLASS_MISC, accf_dataready, NULL);」
の場合、
__link_set_add_rodata(modules, accf_dataready_modinfo);
|
__link_set_make_entry(modules, accf_dataready_modinfo)
|
static void const * const __link_set_modules_sym_accf_dataready_modinfo \
__section("link_set_modules") __used = &accf_dataready_modinfo
のようにマクロ展開される。このことから、link_set_modulesセクションにはMODULEマクロで定義されたmodinfo_t型構造体のアドレスが格納される事が分かる。
おそらくシステムに静的に組み込まれているモジュールであろう。あとから動的に追加されるものではない。
よって、module_builtinsリストについてこれ以上追っても今回疑問に上がっている「modloadがどこのディレクトリを見にいくのか」についてはわからないということになる。
なので、次に「(2)module_bootlistリストとは何者?」について追ってみることにする。
(2)module_bootlistリストとは何者?
このリストにエントリを追加している関数はkern/kern_module.cのmodule_prime()である。
説明書きを見ると「ブートローダによってロードされたモジュールを内部リストに押し込む」とある。
arch/x86/x86/x86_machdep.cからのみ呼ばれている。中をみたがよくわからん。
ブートローダによってロードされた、と言っている時点で「modloadが何処のディレクトリを見にいくのか」についての疑問が解消する可能性が低そうである。
なので、(2)は置いておき、「(3)module_load_vfs_vec関数の処理内容」を追ってみることにする。
(3)module_load_vfs_vec関数の処理内容
module_load_vfs_vecは関数ポインタであり、module_load_vfs_init()でmodule_load_vfs()を指すように初期化される。(kern/kern_module_vfs.c)
module_load_vfs()はkern/kern_module_vfs.cにある関数である。
今回のような呼び出し経路の場合、autoloadはfalseで呼び出される。で、普通に「modload dtrace」とした場合「/」は含まないのでENOENTとなる。ENOENTの場合で指定したモジュール名に「/」を含まない場合、「module_baseで指定されるディレクトリ/モジュール名/モジュール名.kmod」というパスで示されるファイルをロードしようと試みる。
それでもダメならエラーで戻る。
となると、module_baseの内容が何か調べれば、今回の疑問が解消するかもしれない。
module_baseはどうやらkern/kern_module.cで宣言されているようだ。
この変数はmodule_init()で以下のように初期化されているようだ。
#if __NetBSD_Version__ / 1000000 % 100 == 99 /* -current */ snprintf(module_base, sizeof(module_base), "/stand/%s/%s/modules", module_machine, osrelease); #else /* release */ snprintf(module_base, sizeof(module_base), "/stand/%s/%d.%d/modules", module_machine, __NetBSD_Version__ / 100000000, __NetBSD_Version__ / 1000000 % 100); #endif
module_machineは何か?module_init()では
if (!module_machine) module_machine = machine;
となっている。
今回のi386では特に初期化がされていないので、machineの内容がmodule_machineになる。
grepなどで追うのはかなり面倒(machineというキーワードに大量に引っかかるため)なので、実際のNetBSD環境の/standの下を見る。
すると、「i386」というディレクトリのみがあった。よって、machineの内容は「i386」だと思われる。
今回NetBSDのバージョンは6.0なので、最終的なmodule_baseは「/stand/i386/6.0/modules」となる。
実際のNetBSD環境と比較しても一致しているため、これで間違いが無いと思われる。
よって、modloadがモジュールの有無を見に行くディレクトリは「/stand/i386/6.0/modules」であると言えそうだ。
今日はここまで。次回は得られたパスを元にbuild.shのinstallmodulesを実施する事にする。