Linux 上でも mono を使うと .NET Framework を動作させることが出来ます。Linux 環境で mono をインストールする方法は 以前の記事 に書きましたが、このままでは .NET のプログラム(.NET Assenbly)を実行する都度、以下のように mono を実行する必要があります。
$ mono hello.exe
Hello, World!
binfmt_misc という仕組みを使うと、毎回、"mono" としてしなくても、あたかも ELF と同等に .NET Assembly を実行出来るようになります。余談ですが、binfmt_misc の公式ページ は 404 not found になっているようです。
テスト用プログラム
今回は CentOS 5.5 x86_64 環境で検証を行ないました。また、今回用意したテスト用プログラム "hello.cs" は以下の通りです。
using System;
class Hello
{
static void Main(string[] args)
{
Console.WriteLine("Hello, World!");
}
}
予め、このプログラムをコンパイルしておきます。
$ mcs hello.cs
$ ls
hello.cs hello.exe
file コマンドのバージョンアップ
今回の環境にインストールされていた file コマンド(バージョン 4.17)で .NET Assembly を確認すると、以下のように表示されました。
$ file --version
file-4.17
magic file from /usr/share/file/magic
$ file hello.exe
hello.exe: PE32 executable for MS Windows (console) Intel 80386 32-bit
必須ではありませんが、file コマンドのバージョンを上げると、明示的に ".NET Assembly である" ことが表示されるようになります。file コマンドのソースコードをダウンロードし、コンパイル&インストールします。現時点での最新版はバージョン 5.04 でした。
wget ftp://ftp.astron.com/pub/file/file-5.04.tar.gz
tar zxvf file-5.04.tar.gz
cd file-5.04
./configure
make
sudo make install
インストールが完了したことを確認します。
$ which file
/usr/local/bin/file
$ file --version
file-5.04
magic file from /usr/local/share/misc/magic
新しいバージョンの file コマンドだと、.NET Assembly であると明示的に表示されていることが分かります。
$ file hello.exe
hello.exe: PE32 executable for MS Windows (console) Intel 80386 32-bit Mono/.Net assembly
binfmt_misc で .NET Assembly と mono を関連付ける
mono を関連付けていない状態でシェル上から直接、.NET Assembly を実行すると、下記のようなエラーになります。
$ ./hello.exe
-bash: ./hello.exe: cannot execute binary file
mono の公式ページにあるドキュメント「Guide:Running Mono Applications」には以下のような記載があります。
echo ':CLR:M::MZ::/usr/bin/mono:' > /proc/sys/fs/binfmt_misc/register
しかし、Novell で公開されている RHEL 向けのパッケージは /opt 配下にインストールされる為、一見すると下記のように書き換えるのが正しいように思えますが、これでは正しく動作しません。
echo ':CLR:M::MZ::/opt/novell/mono/bin/mono:' > /proc/sys/fs/binfmt_misc/register
実際に試してみると、下記のようなエラーになります。余談ですが、上記コマンドの先頭(つまり、echo の前)に sudo を付与しても、リダイレクト先が元のユーザ権限で動作しますので、/proc 配下へ書き込むことが出来ません。こういった場合は下記のように、echo コマンドの結果をパイプし、sudo して root 権限を付与した tee コマンドへ渡してやります。
$ echo ':CLR:M::MZ::/opt/novell/mono/bin/mono:' | sudo tee /proc/sys/fs/binfmt_misc/register
$ ./hello.exe
-bash: ./hello.exe: cannot execute binary file
エラーの原因ですが「"mono" はシェルスクリプトである」ことが原因のようです。このスクリプトの内容は以下のようになっており、実際には ELF である mono.bin が呼ばれているようです。
$ file /opt/novell/mono/bin/mono
/opt/novell/mono/bin/mono: POSIX shell script text executable
$ cat /opt/novell/mono/bin/mono
#!/bin/sh
. /opt/novell/mono/bin/mono-addon-environment.sh
exec /opt/novell/mono/bin/mono.bin $@
$ file /opt/novell/mono/bin/mono.bin
/opt/novell/mono/bin/mono.bin: ELF 64-bit LSB executable, x86-64,
version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.9, not stripped
binfmt_misc の設定を変更し、"mono" ではなく、"mono.bin" を呼び出すようにします。先程、作成した設定は "echo -1" してやることで削除することが出来ます。
$ echo -1 | sudo tee CLR
$ echo ':CLR:M::MZ::/opt/novell/mono/bin/mono.bin:' | sudo tee /proc/sys/fs/binfmt_misc/register
これでいちいち mono コマンドを指定しなくても、シェルから直接、.NET Assembly を実行出来るようになりました。
$ ./hello.exe
Hello, World!
binfmt_misc のメカニズム
ソースコードを読んだわけでは無いのですが、binfmt_misc のメカニズムを動作結果から推測して記載しておきます。 Linux ネイティブの "ELF" 形式 のファイルで Hex ダンプを取得すると、以下のように冒頭部分は「.ELF」になっていることが分かります。
$ od -tx1z /bin/ls | head -1
0000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 >.ELF............<
この冒頭部分のことを「マジック・ナンバー」と呼びます。同様に .NET Assembly に対して Hex ダンプを取得するとマジック・ナンバーは "MZ" で始まっていることが分かります。
$ od -tx1z hello.exe | head -1
0000000 4d 5a 90 00 03 00 00 00 04 00 00 00 ff ff 00 00 >MZ..............<
binfmt_misc は、このマジック・ナンバーを見分けて、実行するプログラムを変更出来る仕組みのようです。今回の環境では特に何もしなくてもデフォルトで binfmt_misc が有効になっているようで、mount コマンドを実行すると binfmt_misc が /proc/sys/fs/binfmt_misc にマウントされていることが分かります。
$ mount
/dev/mapper/VolGroup00-LogVol00 on / type ext3 (rw,data=writeback)
proc on /proc type proc (rw)
sysfs on /sys type sysfs (rw)
devpts on /dev/pts type devpts (rw,gid=5,mode=620)
/dev/sda1 on /boot type ext3 (rw)
tmpfs on /dev/shm type tmpfs (rw)
none on /proc/sys/fs/binfmt_misc type binfmt_misc (rw)
sunrpc on /var/lib/nfs/rpc_pipefs type rpc_pipefs (rw)
/proc/sys/fs/binfmt_misc/register へ設定を書き込むことで、binfmt_misc に新たなマジック・ナンバーを登録することが出来ます。以下の通り、register は WriteOnly になっています。
$ ls -l /proc/sys/fs/binfmt_misc/
合計 0
--w------- 1 root root 0 11月 11 00:36 register
-rw-r--r-- 1 root root 0 11月 9 23:17 status
前述した通り、echo コマンドを使って register へ設定を書き込みます。echo している文字列ですが、"CLR" の部分は設定自体に割り当てる任意の名前、"MZ" 部分はマジック・ナンバー、"/opt/ 〜" 部分は実際に起動されるプログラム(これを「interpreter」と呼ぶようです)を意味しているようです。register へ echo した設定は /proc/sys/fs/binfmt_misc 配下に保存されますから、これらを cat 等で閲覧することで設定を確認することが可能です。
$ echo ':CLR:M::MZ::/opt/novell/mono/bin/mono.bin:' | sudo tee /proc/sys/fs/binfmt_misc/register
$ cat /proc/sys/fs/binfmt_misc/CLR
enabled
interpreter /opt/novell/mono/bin/mono.bin
flags:
offset 0
magic 4d5a
コメント