[Linux][Debian][C#] binfmtという魔法 - Linux上でC#とmonoが出会うまで
Linux上でC#を触り始めたのだが、少し不思議に思うことがあったので書いてみた。
Linux上でC#を実行
C#でHelloWorldプログラムを書く
// Hello.cs namespace Hello{ using System; class HelloWorld{ public static void Main(){ Console.WriteLine("Hello World!"); } } }
Linux上でmcsを使用してコンパイル。
exeができあがるのでそのまま実行。
$ mcs Hello.cs $ ./Hello.exe Hello World!
上出来。それにしても拡張子exeなんだ。。。
ものは試しと、できたHello.exeをWindows上にコピーしてコンソールで実行してみる。
> Hello.exe Hello World!
なんとそのまま実行されたではないか。これには驚いた。
Hello.exeをfileで調べてみると
$ file Hello.exe Hello.exe: MS-DOS executable PE for MS Windows (console) Intel 80386 32-bit Mono/.Net assembly
Windowsの実行形式であることがわかる。どうりで拡張子がexeなわけだ。
驚くべきはLinux上で素で実行されたことの方だったのだ。
binfmt_miscで遊ぶ
調べてみるとこれはbinfmt_miscというカーネルモジュールによって実現されているとことがわかった。
説明は後にしていきなりbinfmt_miscで遊んでみる。(以下コマンドの実行は自己責任でおねがいします。あとこれ以降の話はDebian固有かもしれないので他のディストリの方はご注意を。)
まずはフォーマットの登録
$ sudo /usr/sbin/update-binfmts --install test_fmt /bin/cat --magic 123
次にテキストファイルに実行属性を付けて実行してみる。
$ echo -e "123\nhoge" > test.txt $ chmod 755 test.txt $ ./test.txt 123 hoge
test.txtに対して/bin/catが実行されている。
後始末
$ sudo /usr/sbin/update-binfmts --remove test_fmt /bin/cat --magic 123
上でやったことの説明とか
Linuxではプログラムファイルのロードはbinfmtモジュールが担当している。elfならbinfmt_elf、a.outならbinfmt_aoutといった具合に主要なフォーマットは専用のモジュールが担当するのだが、ユーザがプログラムの扱いを指定したいときに使用するのがbinfmt_misc。
やっていることは単純でユーザの指定したフォーマットに合致するファイルであればユーザが指定したコマンドにファイルのパスを渡して実行するというもの。
上の例だとファイルの先頭が"123"であるファイルがきたら/bin/catにそのパスを渡すという意味になる。
binfmt_miscに登録されているフォーマットは/proc/sys/fs/binfmt_miscで参照できる。
上の登録を実行した後であればtest_fmtというファイルができていることが確認できるはずだ。
exeがmonoに出会うまで
/proc/sys/fs/binfmt_miscをみてみるとcliというファイルがある。
enabled interpreter /usr/share/binfmt-support/run-detectors flags: offset 0 magic 4d5a
4d5aはASCIIコードで"MZ"であり、こいつはexeの先頭に必ずあるmagicナンバーだ。
つまり先頭にMZがあるファイルがくるとrun-detectorsが実行されることになる。
run-detectorsはperlスクリプトで/var/lib/binfmtsの内容を参照しつつ最終的にファイルの実行をまかせるプログラムの決定をおこなう。ここでは/usr/bin/cliに実行がまかされることになる。/usr/bin/cliは/usr/bin/monoへのシンボリックリンクであり、これでめでたくexeがMono JIT compilerにより実行されることになるわけだ。